Skip to content

Commit 80695c2

Browse files
d-b-c-eclaude
andauthored
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>
1 parent 04feb0b commit 80695c2

3 files changed

Lines changed: 46 additions & 22 deletions

File tree

src/sync/synchronization.rs

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ pub enum SyncProgress {
2727
FindingImages,
2828
DownloadingImages { to_download: usize },
2929
Done,
30+
/// Error occurred during sync - contains user-friendly error message
31+
Error { message: String },
3032
}
3133

3234
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
4244
shortcut.tags.retain(|s| s != BOILR_TAG);
4345
}
4446
}
45-
save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path));
47+
if let Err(e) = save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path)) {
48+
return Err(e);
49+
}
4650
}
4751
}
4852

@@ -105,7 +109,13 @@ pub fn sync_shortcuts(
105109

106110
shortcut_info.shortcuts.extend(all_shortcuts.clone());
107111

108-
save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path));
112+
if let Err(e) = save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path)) {
113+
eprintln!("Failed to save shortcuts for user {}: {}", user.user_id, e);
114+
if let Some(sender) = sender {
115+
let _ = sender.send(SyncProgress::Error { message: e });
116+
}
117+
// Continue with other users even if one fails
118+
}
109119

110120
if settings.steam.create_collections {
111121
match write_shortcut_collections(&user.user_id, platform_shortcuts) {
@@ -174,7 +184,10 @@ pub fn fix_all_shortcut_icons(settings: &Settings) -> eyre::Result<()> {
174184
settings.steam.optimize_for_big_picture,
175185
);
176186
if changes {
177-
save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path));
187+
if let Err(e) = save_shortcuts(&shortcut_info.shortcuts, Path::new(&shortcut_info.path)) {
188+
eprintln!("Failed to save shortcut icons for user {}: {}", user.user_id, e);
189+
// Continue with other users
190+
}
178191
}
179192
}
180193
}
@@ -239,7 +252,7 @@ pub fn get_platform_shortcuts(
239252
}
240253
}
241254

242-
fn save_shortcuts(shortcuts: &[ShortcutOwned], path: &Path) {
255+
fn save_shortcuts(shortcuts: &[ShortcutOwned], path: &Path) -> Result<(), String> {
243256
let mut shortcuts_refs = vec![];
244257
for shortcut in shortcuts {
245258
shortcuts_refs.push(shortcut.borrow());
@@ -248,20 +261,23 @@ fn save_shortcuts(shortcuts: &[ShortcutOwned], path: &Path) {
248261
match File::create(path) {
249262
Ok(mut file) => match file.write_all(new_content.as_slice()) {
250263
Ok(_) => {
251-
println!("Saved {} shortcuts", shortcuts.len())
264+
println!("Saved {} shortcuts", shortcuts.len());
265+
Ok(())
266+
}
267+
Err(e) => {
268+
Err(format!(
269+
"Failed to write shortcuts to {}: {}. Check that Steam is not running and you have write permissions to the Steam folder.",
270+
path.display(),
271+
e
272+
))
252273
}
253-
Err(e) => println!(
254-
"Failed to save shortcuts to {} error: {}",
255-
path.to_string_lossy(),
256-
e
257-
),
258274
},
259275
Err(e) => {
260-
println!(
261-
"Failed to save shortcuts to {} error: {}",
262-
path.to_string_lossy(),
276+
Err(format!(
277+
"Failed to create shortcuts file at {}: {}. Check that Steam is not running and you have write permissions to the Steam folder.",
278+
path.display(),
263279
e
264-
);
280+
))
265281
}
266282
}
267283
}

src/ui/images/ui_image_download.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ impl MyEguiApp {
105105
ui.ctx().request_repaint();
106106
return Some(UserAction::RefreshImages);
107107
}
108+
crate::sync::SyncProgress::Error { ref message } => {
109+
ui.colored_label(egui::Color32::RED, format!("Error: {}", message));
110+
}
108111
_ => {
109112
if ui.button("Download images for all games").clicked() {
110113
return Some(UserAction::DownloadAllImages);

src/ui/uiapp.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,23 +88,28 @@ impl MyEguiApp {
8888
}
8989

9090
fn render_import_button(&mut self, ui: &mut egui::Ui) {
91-
let (status_string, syncing) = match &*self.status_reciever.borrow() {
92-
SyncProgress::NotStarted => ("".to_string(), false),
93-
SyncProgress::Starting => ("Starting Import".to_string(), true),
91+
let (status_string, syncing, is_error) = match &*self.status_reciever.borrow() {
92+
SyncProgress::NotStarted => ("".to_string(), false, false),
93+
SyncProgress::Starting => ("Starting Import".to_string(), true, false),
9494
SyncProgress::FoundGames { games_found } => {
95-
(format!("Found {games_found} games to import"), true)
95+
(format!("Found {games_found} games to import"), true, false)
9696
}
97-
SyncProgress::FindingImages => ("Searching for images".to_string(), true),
97+
SyncProgress::FindingImages => ("Searching for images".to_string(), true, false),
9898
SyncProgress::DownloadingImages { to_download } => {
99-
(format!("Downloading {to_download} images "), true)
99+
(format!("Downloading {to_download} images"), true, false)
100+
}
101+
SyncProgress::Done => ("Done importing games".to_string(), false, false),
102+
SyncProgress::Error { message } => {
103+
(format!("Error: {}", message), false, true)
100104
}
101-
SyncProgress::Done => ("Done importing games".to_string(), false),
102105
};
103106
if syncing {
104107
ui.ctx().request_repaint();
105108
}
106109
if !status_string.is_empty() {
107-
if syncing {
110+
if is_error {
111+
ui.colored_label(egui::Color32::RED, &status_string);
112+
} else if syncing {
108113
ui.horizontal(|c| {
109114
c.spinner();
110115
c.label(&status_string);

0 commit comments

Comments
 (0)