From a8503f48b9fd86639af807c8e2ce39abe2971a3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tuomas=20Sepp=C3=A4nen?= Date: Thu, 9 Apr 2026 00:50:48 +0300 Subject: [PATCH] fix: prevent Last.fm auth loss from Ctrl+C or crash during settings save The Last.fm username (which is the keyring lookup key for the session) was only persisted to settings.txt by a periodic 2-second flush. Killing the app before the flush lost the username, making the session key in the keyring irrecoverable on next launch. - Save settings immediately when the Last.fm username changes - Use atomic temp-file-then-rename for settings writes so a crash mid-write cannot truncate the file - Log when keyring lookup returns no key or errors at startup --- src/frontend_bridge/config.rs | 19 +++++++++++++++---- src/frontend_bridge/events.rs | 4 ++++ src/lastfm/mod.rs | 10 +++++++++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/frontend_bridge/config.rs b/src/frontend_bridge/config.rs index f092219..43d7b86 100644 --- a/src/frontend_bridge/config.rs +++ b/src/frontend_bridge/config.rs @@ -277,11 +277,22 @@ pub(super) fn save_settings(settings: &BridgeSettings) { let Some(path) = settings_path() else { return; }; - if let Some(parent) = path.parent() { - let _ = fs::create_dir_all(parent); - } + let Some(parent) = path.parent() else { + return; + }; + let _ = fs::create_dir_all(parent); let text = format_settings_text(settings); - let _ = fs::write(path, text); + + // Atomic write: write to a temp file in the same directory, then rename. + // This prevents a crash during write from truncating/corrupting the settings file. + let tmp_path = path.with_extension("tmp"); + if let Err(err) = fs::write(&tmp_path, &text) { + eprintln!("Failed to write settings temp file: {err}"); + return; + } + if let Err(err) = fs::rename(&tmp_path, &path) { + eprintln!("Failed to rename settings temp file: {err}"); + } } pub(super) fn format_settings_text(settings: &BridgeSettings) -> String { diff --git a/src/frontend_bridge/events.rs b/src/frontend_bridge/events.rs index ffb50fd..61fab2d 100644 --- a/src/frontend_bridge/events.rs +++ b/src/frontend_bridge/events.rs @@ -430,6 +430,10 @@ pub(super) fn process_lastfm_event( if state.settings.integrations.lastfm_username != runtime.username { state.settings.integrations.lastfm_username = runtime.username; *settings_dirty = true; + // Flush immediately — the username is the lookup key for the + // session in the keyring. Losing it (e.g. Ctrl+C before the + // periodic 2 s save) makes the session irrecoverable. + super::config::save_settings(&state.settings); urgency = SnapshotUrgency::Immediate; } urgency diff --git a/src/lastfm/mod.rs b/src/lastfm/mod.rs index 6623fc9..d0188aa 100644 --- a/src/lastfm/mod.rs +++ b/src/lastfm/mod.rs @@ -316,14 +316,22 @@ impl Service { self.flush_queue(); } Ok(None) => { + eprintln!( + "Last.fm: no session key found in keyring for user \"{username}\" — \ + was the keyring cleared or is the keyring daemon unavailable?" + ); self.state.username = username; self.state.auth_state = AuthState::Disconnected; self.state.status_text = "Reconnect Last.fm to resume scrobbling.".to_string(); } Err(err) => { + let message = request_error_message(err); + eprintln!( + "Last.fm: keyring error loading session for user \"{username}\": {message}" + ); self.state.username = username; self.state.auth_state = AuthState::Error; - self.state.status_text = request_error_message(err); + self.state.status_text = message; } } }