diff --git a/ftl/core/preferences.ftl b/ftl/core/preferences.ftl
index cac8b9360..4ebb21585 100644
--- a/ftl/core/preferences.ftl
+++ b/ftl/core/preferences.ftl
@@ -67,6 +67,7 @@ preferences-note = Note
preferences-scheduler = Scheduler
preferences-user-interface = User Interface
preferences-import-export = Import/Export
+preferences-network-timeout = Network timeout
## NO NEED TO TRANSLATE. This text is no longer used by Anki, and will be removed in the future.
diff --git a/proto/anki/sync.proto b/proto/anki/sync.proto
index c4d1bffe9..b0c0404e7 100644
--- a/proto/anki/sync.proto
+++ b/proto/anki/sync.proto
@@ -24,6 +24,7 @@ service SyncService {
message SyncAuth {
string hkey = 1;
optional string endpoint = 2;
+ optional uint32 io_timeout_secs = 3;
}
message SyncLoginRequest {
diff --git a/qt/aqt/forms/preferences.ui b/qt/aqt/forms/preferences.ui
index 969861c2d..8803b82ce 100644
--- a/qt/aqt/forms/preferences.ui
+++ b/qt/aqt/forms/preferences.ui
@@ -712,6 +712,47 @@
+ -
+
+
-
+
+
+ preferences_network_timeout
+
+
+
+ -
+
+
+ 30
+
+
+ 99999
+
+
+
+ -
+
+
+ scheduling_seconds
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
-
-
@@ -1087,6 +1128,7 @@
syncOnProgramOpen
autoSyncMedia
fullSync
+ network_timeout
media_log
syncDeauth
custom_sync_url
diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py
index 7a96d1eaf..5fa3da198 100644
--- a/qt/aqt/preferences.py
+++ b/qt/aqt/preferences.py
@@ -183,6 +183,7 @@ class Preferences(QDialog):
qconnect(self.form.syncDeauth.clicked, self.sync_logout)
self.form.syncDeauth.setText(tr.sync_log_out_button())
self.form.custom_sync_url.setText(self.mw.pm.custom_sync_url())
+ self.form.network_timeout.setValue(self.mw.pm.network_timeout())
def on_media_log(self) -> None:
self.mw.media_syncer.show_sync_log()
@@ -211,6 +212,7 @@ class Preferences(QDialog):
if self.form.fullSync.isChecked():
self.mw.col.mod_schema(check=False)
self.mw.pm.set_custom_sync_url(self.form.custom_sync_url.text())
+ self.mw.pm.set_network_timeout(self.form.network_timeout.value())
# Global preferences
######################################################################
diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py
index 6b78207fa..8a7a2d2ac 100644
--- a/qt/aqt/profiles.py
+++ b/qt/aqt/profiles.py
@@ -642,7 +642,11 @@ create table if not exists profiles
def sync_auth(self) -> SyncAuth | None:
if not (hkey := self.profile.get("syncKey")):
return None
- return SyncAuth(hkey=hkey, endpoint=self.sync_endpoint())
+ return SyncAuth(
+ hkey=hkey,
+ endpoint=self.sync_endpoint(),
+ io_timeout_secs=self.network_timeout(),
+ )
def clear_sync_auth(self) -> None:
self.set_sync_key(None)
@@ -680,3 +684,9 @@ create table if not exists profiles
def set_show_browser_table_tooltips(self, val: bool) -> None:
self.profile["browserTableTooltips"] = val
+
+ def set_network_timeout(self, timeout_secs: int) -> None:
+ self.profile["networkTimeout"] = timeout_secs
+
+ def network_timeout(self) -> int:
+ return self.profile.get("networkTimeout") or 30
diff --git a/rslib/src/backend/sync/mod.rs b/rslib/src/backend/sync/mod.rs
index 4c7aba72b..e90a9229d 100644
--- a/rslib/src/backend/sync/mod.rs
+++ b/rslib/src/backend/sync/mod.rs
@@ -89,6 +89,7 @@ impl TryFrom for SyncAuth {
.or_invalid("Invalid sync server specified. Please check the preferences.")
})
.transpose()?,
+ io_timeout_secs: value.io_timeout_secs,
})
}
}
@@ -236,6 +237,7 @@ impl Backend {
ret.map(|a| pb::sync::SyncAuth {
hkey: a.hkey,
endpoint: None,
+ io_timeout_secs: None,
})
}
diff --git a/rslib/src/sync/collection/tests.rs b/rslib/src/sync/collection/tests.rs
index a332de1eb..eba4c52a5 100644
--- a/rslib/src/sync/collection/tests.rs
+++ b/rslib/src/sync/collection/tests.rs
@@ -98,6 +98,7 @@ where
let auth = SyncAuth {
hkey: AUTH.host_key.clone(),
endpoint: Some(endpoint),
+ io_timeout_secs: None,
};
let client = HttpSyncClient::new(auth);
op(client).await
diff --git a/rslib/src/sync/http_client/mod.rs b/rslib/src/sync/http_client/mod.rs
index 3aa099b96..4a3aff2de 100644
--- a/rslib/src/sync/http_client/mod.rs
+++ b/rslib/src/sync/http_client/mod.rs
@@ -32,11 +32,13 @@ pub struct HttpSyncClient {
session_key: String,
client: Client,
pub endpoint: Url,
+ pub io_timeout: Duration,
full_sync_progress_fn: Mutex