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,
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) => println!(
"Failed to save shortcuts to {} error: {}",
path.to_string_lossy(),
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(),
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
);
))
}
}
}
+3
View File
@@ -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);
+13 -8
View File
@@ -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);