diff --git a/src/main.rs b/src/main.rs
index a44a7ba..fb97f76 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -84,23 +84,32 @@ fn main() {
// handle is shared with the runtime, so configuring the clone
// reaches the running push channel.
{
- let push_clients: Vec<(NotifyPushClient, String, String, bool)> = account_manager
- .runtimes()
- .values()
- .filter_map(|runtime| {
- runtime.push_client().map(|client| {
- (
- client,
- runtime.account.server_url.clone(),
- runtime.account.login_name.clone(),
- runtime.account.sync.remote_push_enabled,
- )
+ let push_clients: Vec<(NotifyPushClient, String, String, String, bool)> =
+ account_manager
+ .runtimes()
+ .values()
+ .filter_map(|runtime| {
+ runtime.push_client().map(|client| {
+ (
+ client,
+ runtime.account.id.clone(),
+ runtime.account.server_url.clone(),
+ runtime.account.login_name.clone(),
+ runtime.account.sync.remote_push_enabled,
+ )
+ })
})
- })
- .collect();
- for (client, server, username, enabled) in push_clients {
- let keyring_user = username.clone();
- let task = gio::spawn_blocking(move || CredentialsStore::get(&keyring_user));
+ .collect();
+ for (client, account_id, server, username, enabled) in push_clients {
+ let server_for_lookup = server.clone();
+ let username_for_lookup = username.clone();
+ let task = gio::spawn_blocking(move || {
+ CredentialsStore::get_for_account(
+ &account_id,
+ &server_for_lookup,
+ &username_for_lookup,
+ )
+ });
glib::spawn_future_local(async move {
if let Ok(Ok(Some(password))) = task.await {
client.configure(&server, &username, &password, enabled);
diff --git a/src/nextcloud/credentials.rs b/src/nextcloud/credentials.rs
index fe3b542..865cebd 100644
--- a/src/nextcloud/credentials.rs
+++ b/src/nextcloud/credentials.rs
@@ -2,7 +2,11 @@
//!
//! Per-account passwords are stored in the default collection of the desktop
//! Secret Service (`org.freedesktop.secrets`) keyed by `account_id`, so they
-//! survive a reinstall and are shared with the Python `nextsync` v0.2.x.
+//! survive a reinstall. Accounts created by the Python `nextsync` app stored
+//! their secret with `{server, username}` attributes instead; those entries
+//! are picked up through [`CredentialsStore::get_for_account`] and adopted
+//! (re-stored under `account_id`, leaving the legacy item untouched for the
+//! Python app).
//!
//! Uses `secret_service::blocking` (feature `rt-tokio-crypto-rust`, DH
//! encrypted session). Blocking calls must not run on the async UI loop.
@@ -18,6 +22,10 @@ pub type CredentialError = secret_service::Error;
/// Attribute key used to index items by account id.
const ATTR_ACCOUNT_ID: &str = "account_id";
+/// Attribute keys used by the legacy Python `nextsync` entries.
+const ATTR_SERVER: &str = "server";
+const ATTR_USERNAME: &str = "username";
+
/// Secret content type used for stored passwords.
const CONTENT_TYPE: &str = "text/plain";
@@ -54,6 +62,36 @@ impl CredentialsStore {
Ok(Some(String::from_utf8_lossy(&secret).into_owned()))
}
+ /// Read the password for an account, falling back to the legacy entry.
+ ///
+ /// Tries the `account_id` item first; when absent, searches the legacy
+ /// Python `nextsync` attributes (`{server, username}`). A legacy hit is
+ /// adopted: the secret is re-stored under `account_id` so later lookups
+ /// hit the fast path, while the legacy item itself is left in place for
+ /// the Python app. Adoption failure is not fatal — the password is still
+ /// returned.
+ pub fn get_for_account(
+ account_id: &str,
+ server: &str,
+ login: &str,
+ ) -> Result