From 80695c2b95fee654520ef907a0c91fb67f4881b5 Mon Sep 17 00:00:00 2001 From: d-b-c-e Date: Mon, 2 Feb 2026 12:10:20 -0600 Subject: [PATCH] Improve error handling for shortcut saving (#462) Errors from saving shortcuts were silently swallowed, leaving users with no feedback when writes failed (e.g., due to Steam running or permission issues). Changes: - Make save_shortcuts() return Result<(), String> with user-friendly error messages suggesting to check Steam is not running - Add SyncProgress::Error variant to surface errors through the UI - Display sync errors in red text in both the Import and Images tabs - Propagate save errors in disconnect_shortcut() - Continue processing other users when one fails in sync_shortcuts() and fix_all_shortcut_icons() Co-authored-by: Claude Opus 4.5 --- src/sync/synchronization.rs | 44 ++++++++++++++++++++---------- src/ui/images/ui_image_download.rs | 3 ++ src/ui/uiapp.rs | 21 ++++++++------ 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/src/sync/synchronization.rs b/src/sync/synchronization.rs index 47ff3d7..d2eec92 100644 --- a/src/sync/synchronization.rs +++ b/src/sync/synchronization.rs @@ -27,6 +27,8 @@ pub enum SyncProgress { FindingImages, DownloadingImages { to_download: usize }, Done, + /// Error occurred during sync - contains user-friendly error message + Error { message: String }, } pub fn disconnect_shortcut(settings: &Settings, app_id: u32) -> Result<(), String> { @@ -42,7 +44,9 @@ pub fn disconnect_shortcut(settings: &Settings, app_id: u32) -> Result<(), Strin shortcut.tags.retain(|s| s != BOILR_TAG); } } - save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path)); + if let Err(e) = save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path)) { + return Err(e); + } } } @@ -105,7 +109,13 @@ pub fn sync_shortcuts( shortcut_info.shortcuts.extend(all_shortcuts.clone()); - save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path)); + if let Err(e) = save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path)) { + eprintln!("Failed to save shortcuts for user {}: {}", user.user_id, e); + if let Some(sender) = sender { + let _ = sender.send(SyncProgress::Error { message: e }); + } + // Continue with other users even if one fails + } if settings.steam.create_collections { match write_shortcut_collections(&user.user_id, platform_shortcuts) { @@ -174,7 +184,10 @@ pub fn fix_all_shortcut_icons(settings: &Settings) -> eyre::Result<()> { settings.steam.optimize_for_big_picture, ); if changes { - save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path)); + if let Err(e) = save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path)) { + eprintln!("Failed to save shortcut icons for user {}: {}", user.user_id, e); + // Continue with other users + } } } } @@ -239,7 +252,7 @@ pub fn get_platform_shortcuts( } } -fn save_shortcuts(shortcuts: &[ShortcutOwned], path: &Path) { +fn save_shortcuts(shortcuts: &[ShortcutOwned], path: &Path) -> Result<(), String> { let mut shortcuts_refs = vec![]; for shortcut in shortcuts { shortcuts_refs.push(shortcut.borrow()); @@ -248,20 +261,23 @@ fn save_shortcuts(shortcuts: &[ShortcutOwned], path: &Path) { match File::create(path) { Ok(mut file) => match file.write_all(new_content.as_slice()) { Ok(_) => { - println!("Saved {} shortcuts", shortcuts.len()) + println!("Saved {} shortcuts", shortcuts.len()); + Ok(()) + } + Err(e) => { + Err(format!( + "Failed to write shortcuts to {}: {}. Check that Steam is not running and you have write permissions to the Steam folder.", + path.display(), + e + )) } - Err(e) => println!( - "Failed to save shortcuts to {} error: {}", - path.to_string_lossy(), - e - ), }, Err(e) => { - println!( - "Failed to save shortcuts to {} error: {}", - path.to_string_lossy(), + Err(format!( + "Failed to create shortcuts file at {}: {}. Check that Steam is not running and you have write permissions to the Steam folder.", + path.display(), e - ); + )) } } } diff --git a/src/ui/images/ui_image_download.rs b/src/ui/images/ui_image_download.rs index 5ab94e7..7ee7d60 100644 --- a/src/ui/images/ui_image_download.rs +++ b/src/ui/images/ui_image_download.rs @@ -105,6 +105,9 @@ impl MyEguiApp { ui.ctx().request_repaint(); return Some(UserAction::RefreshImages); } + crate::sync::SyncProgress::Error { ref message } => { + ui.colored_label(egui::Color32::RED, format!("Error: {}", message)); + } _ => { if ui.button("Download images for all games").clicked() { return Some(UserAction::DownloadAllImages); diff --git a/src/ui/uiapp.rs b/src/ui/uiapp.rs index 546df75..ebbaf7f 100644 --- a/src/ui/uiapp.rs +++ b/src/ui/uiapp.rs @@ -88,23 +88,28 @@ impl MyEguiApp { } fn render_import_button(&mut self, ui: &mut egui::Ui) { - let (status_string, syncing) = match &*self.status_reciever.borrow() { - SyncProgress::NotStarted => ("".to_string(), false), - SyncProgress::Starting => ("Starting Import".to_string(), true), + let (status_string, syncing, is_error) = match &*self.status_reciever.borrow() { + SyncProgress::NotStarted => ("".to_string(), false, false), + SyncProgress::Starting => ("Starting Import".to_string(), true, false), SyncProgress::FoundGames { games_found } => { - (format!("Found {games_found} games to import"), true) + (format!("Found {games_found} games to import"), true, false) } - SyncProgress::FindingImages => ("Searching for images".to_string(), true), + SyncProgress::FindingImages => ("Searching for images".to_string(), true, false), SyncProgress::DownloadingImages { to_download } => { - (format!("Downloading {to_download} images "), true) + (format!("Downloading {to_download} images"), true, false) + } + SyncProgress::Done => ("Done importing games".to_string(), false, false), + SyncProgress::Error { message } => { + (format!("Error: {}", message), false, true) } - SyncProgress::Done => ("Done importing games".to_string(), false), }; if syncing { ui.ctx().request_repaint(); } if !status_string.is_empty() { - if syncing { + if is_error { + ui.colored_label(egui::Color32::RED, &status_string); + } else if syncing { ui.horizontal(|c| { c.spinner(); c.label(&status_string);