From cd9696d561c1eb4f66d747ce90c5a34173149fdc Mon Sep 17 00:00:00 2001 From: weikeyi Date: Sat, 23 May 2026 11:55:48 +0800 Subject: [PATCH] Fix active ASR settings sync ordering --- openless-all/app/src-tauri/src/commands.rs | 111 ++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/openless-all/app/src-tauri/src/commands.rs b/openless-all/app/src-tauri/src/commands.rs index a6701949..33d52fe1 100644 --- a/openless-all/app/src-tauri/src/commands.rs +++ b/openless-all/app/src-tauri/src/commands.rs @@ -179,10 +179,21 @@ fn persist_settings( let open_app_changed = previous.open_app_hotkey != prefs.open_app_hotkey; let active_asr_provider_changed = previous.active_asr_provider != prefs.active_asr_provider; let active_asr_provider = prefs.active_asr_provider.clone(); - coord.write_settings(prefs)?; if active_asr_provider_changed { coord.sync_active_asr_provider(&active_asr_provider)?; } + if let Err(error) = coord.write_settings(prefs) { + if active_asr_provider_changed { + if let Err(rollback_error) = + coord.sync_active_asr_provider(&previous.active_asr_provider) + { + return Err(format!( + "{error}; additionally failed to restore active ASR provider: {rollback_error}" + )); + } + } + return Err(error); + } if dictation_shortcut_changed || dictation_mode_changed { coord.refresh_dictation_hotkey(); } @@ -3324,6 +3335,8 @@ mod tests { struct FakeSettingsWriter { saved: Mutex>, active_asr_provider_syncs: Mutex>, + write_settings_error: Mutex>, + active_asr_provider_sync_error: Mutex>, dictation_refreshes: Mutex, qa_refreshes: Mutex, combo_refreshes: Mutex, @@ -3591,6 +3604,9 @@ mod tests { } fn write_settings(&self, prefs: UserPreferences) -> Result<(), String> { + if let Some(error) = self.write_settings_error.lock().unwrap().clone() { + return Err(error); + } *self.saved.lock().unwrap() = Some(prefs); Ok(()) } @@ -3600,6 +3616,14 @@ mod tests { .lock() .unwrap() .push(provider.to_string()); + if let Some(error) = self + .active_asr_provider_sync_error + .lock() + .unwrap() + .clone() + { + return Err(error); + } Ok(()) } @@ -3829,6 +3853,91 @@ mod tests { assert_eq!(*writer.open_app_refreshes.lock().unwrap(), 0); } + #[test] + fn persist_settings_does_not_save_when_active_asr_sync_fails() { + let writer = FakeSettingsWriter::default(); + let previous = UserPreferences::default(); + *writer.saved.lock().unwrap() = Some(previous.clone()); + *writer.active_asr_provider_sync_error.lock().unwrap() = Some("sync failed".to_string()); + let prefs = UserPreferences { + active_asr_provider: "whisper".to_string(), + microphone_device_name: "External Mic".to_string(), + hotkey: previous.hotkey, + dictation_hotkey: previous.dictation_hotkey, + qa_hotkey: previous.qa_hotkey, + translation_hotkey: previous.translation_hotkey, + switch_style_hotkey: previous.switch_style_hotkey, + open_app_hotkey: previous.open_app_hotkey, + ..Default::default() + }; + + let error = persist_settings(&writer, prefs).unwrap_err(); + + assert_eq!(error, "sync failed"); + let saved = writer + .saved + .lock() + .unwrap() + .clone() + .expect("previous settings remain saved"); + assert_eq!(saved.active_asr_provider, previous.active_asr_provider); + assert_eq!(saved.microphone_device_name, previous.microphone_device_name); + assert_eq!( + writer.active_asr_provider_syncs.lock().unwrap().clone(), + vec!["whisper".to_string()] + ); + assert_eq!(*writer.dictation_refreshes.lock().unwrap(), 0); + assert_eq!(*writer.combo_refreshes.lock().unwrap(), 0); + assert_eq!(*writer.qa_refreshes.lock().unwrap(), 0); + assert_eq!(*writer.translation_refreshes.lock().unwrap(), 0); + assert_eq!(*writer.switch_style_refreshes.lock().unwrap(), 0); + assert_eq!(*writer.open_app_refreshes.lock().unwrap(), 0); + } + + #[test] + fn persist_settings_restores_active_asr_provider_when_save_fails_after_sync() { + let writer = FakeSettingsWriter::default(); + let previous = UserPreferences::default(); + *writer.saved.lock().unwrap() = Some(previous.clone()); + *writer.write_settings_error.lock().unwrap() = Some("save failed".to_string()); + let prefs = UserPreferences { + active_asr_provider: "whisper".to_string(), + microphone_device_name: "External Mic".to_string(), + hotkey: previous.hotkey, + dictation_hotkey: previous.dictation_hotkey, + qa_hotkey: previous.qa_hotkey, + translation_hotkey: previous.translation_hotkey, + switch_style_hotkey: previous.switch_style_hotkey, + open_app_hotkey: previous.open_app_hotkey, + ..Default::default() + }; + + let error = persist_settings(&writer, prefs).unwrap_err(); + + assert_eq!(error, "save failed"); + let saved = writer + .saved + .lock() + .unwrap() + .clone() + .expect("previous settings remain saved"); + assert_eq!(saved.active_asr_provider, previous.active_asr_provider); + assert_eq!(saved.microphone_device_name, previous.microphone_device_name); + assert_eq!( + writer.active_asr_provider_syncs.lock().unwrap().clone(), + vec![ + "whisper".to_string(), + previous.active_asr_provider.clone() + ] + ); + assert_eq!(*writer.dictation_refreshes.lock().unwrap(), 0); + assert_eq!(*writer.combo_refreshes.lock().unwrap(), 0); + assert_eq!(*writer.qa_refreshes.lock().unwrap(), 0); + assert_eq!(*writer.translation_refreshes.lock().unwrap(), 0); + assert_eq!(*writer.switch_style_refreshes.lock().unwrap(), 0); + assert_eq!(*writer.open_app_refreshes.lock().unwrap(), 0); + } + #[test] fn sync_dictation_hotkey_sets_modifier_trigger_and_clears_combo() { let mut prefs = UserPreferences {