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 <noreply@anthropic.com>
This commit is contained in:
d-b-c-e
2026-02-02 19:10:20 +01:00
committed by GitHub
co-authored by Claude Opus 4.5
parent 04feb0b52c
commit 80695c2b95
3 changed files with 46 additions and 22 deletions
+29 -13
View File
@@ -27,6 +27,8 @@ pub enum SyncProgress {
FindingImages, FindingImages,
DownloadingImages { to_download: usize }, DownloadingImages { to_download: usize },
Done, Done,
/// Error occurred during sync - contains user-friendly error message
Error { message: String },
} }
pub fn disconnect_shortcut(settings: &Settings, app_id: u32) -> Result<(), 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); 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()); 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 { if settings.steam.create_collections {
match write_shortcut_collections(&user.user_id, platform_shortcuts) { 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, settings.steam.optimize_for_big_picture,
); );
if changes { 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![]; let mut shortcuts_refs = vec![];
for shortcut in shortcuts { for shortcut in shortcuts {
shortcuts_refs.push(shortcut.borrow()); shortcuts_refs.push(shortcut.borrow());
@@ -248,20 +261,23 @@ fn save_shortcuts(shortcuts: &[ShortcutOwned], path: &Path) {
match File::create(path) { match File::create(path) {
Ok(mut file) => match file.write_all(new_content.as_slice()) { Ok(mut file) => match file.write_all(new_content.as_slice()) {
Ok(_) => { Ok(_) => {
println!("Saved {} shortcuts", shortcuts.len()) println!("Saved {} shortcuts", shortcuts.len());
Ok(())
} }
Err(e) => println!( Err(e) => {
"Failed to save shortcuts to {} error: {}", Err(format!(
path.to_string_lossy(), "Failed to write shortcuts to {}: {}. Check that Steam is not running and you have write permissions to the Steam folder.",
path.display(),
e e
), ))
}
}, },
Err(e) => { Err(e) => {
println!( Err(format!(
"Failed to save shortcuts to {} error: {}", "Failed to create shortcuts file at {}: {}. Check that Steam is not running and you have write permissions to the Steam folder.",
path.to_string_lossy(), path.display(),
e e
); ))
} }
} }
} }
+3
View File
@@ -105,6 +105,9 @@ impl MyEguiApp {
ui.ctx().request_repaint(); ui.ctx().request_repaint();
return Some(UserAction::RefreshImages); 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() { if ui.button("Download images for all games").clicked() {
return Some(UserAction::DownloadAllImages); return Some(UserAction::DownloadAllImages);
+13 -8
View File
@@ -88,23 +88,28 @@ impl MyEguiApp {
} }
fn render_import_button(&mut self, ui: &mut egui::Ui) { fn render_import_button(&mut self, ui: &mut egui::Ui) {
let (status_string, syncing) = match &*self.status_reciever.borrow() { let (status_string, syncing, is_error) = match &*self.status_reciever.borrow() {
SyncProgress::NotStarted => ("".to_string(), false), SyncProgress::NotStarted => ("".to_string(), false, false),
SyncProgress::Starting => ("Starting Import".to_string(), true), SyncProgress::Starting => ("Starting Import".to_string(), true, false),
SyncProgress::FoundGames { games_found } => { 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 } => { 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 { if syncing {
ui.ctx().request_repaint(); ui.ctx().request_repaint();
} }
if !status_string.is_empty() { if !status_string.is_empty() {
if syncing { if is_error {
ui.colored_label(egui::Color32::RED, &status_string);
} else if syncing {
ui.horizontal(|c| { ui.horizontal(|c| {
c.spinner(); c.spinner();
c.label(&status_string); c.label(&status_string);