feat(io): delete .sv$ autosave files on clean save/exit (#205)

The .sv$ recovery copies only need to survive a crash, so they are removed on
a clean save (Save and Save-As both delete the file's .sv$ after writing) and
on a clean exit — a new exit_app() runs cleanup_autosaves over every open tab
before quitting, and all iced::exit() paths now funnel through it. A leftover
.sv$ therefore signals an unclean shutdown, i.e. something to recover.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-29 13:34:16 +03:00
1 parent 02d2764e45
commit 286caa9c61
4 files changed
+27 -6

No files matched your search

+1 -1
View File
@@ -211,7 +211,7 @@ impl OpenCADStudio {
if let Some(id) = self.main_window {
return Some(Task::done(Message::WindowCloseRequested(id)));
}
return Some(iced::exit());
return Some(self.exit_app());
}
// ── Frame-budget HUD (Phase 5.3) ───────────────────────────────
+3 -3
View File
@@ -186,7 +186,7 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
self.pending_close = Some(crate::app::PendingClose::Quit);
} else {
let close_win = self.close_unsaved_dialog_window();
return Task::batch(vec![close_win, iced::exit()]);
return Task::batch(vec![close_win, self.exit_app()]);
}
}
None => {}
@@ -252,7 +252,7 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
self.pending_close = Some(crate::app::PendingClose::Quit);
} else {
let close_win = self.close_unsaved_dialog_window();
return Task::batch(vec![close_win, iced::exit()]);
return Task::batch(vec![close_win, self.exit_app()]);
}
}
None => {}
@@ -299,7 +299,7 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
self.pending_close = Some(crate::app::PendingClose::Quit);
return self.open_unsaved_dialog_window();
} else {
return iced::exit();
return self.exit_app();
}
}
Err(e) => {
+21
View File
@@ -550,6 +550,8 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
self.command_line
.push_output(&format!("Saved: {}", path.display()));
self.tabs[i].dirty = false;
// A clean save supersedes any autosave recovery copy.
let _ = std::fs::remove_file(path.with_extension("sv$"));
}
Err(e) => self.command_line.push_error(&format!("Save failed: {e}")),
}
@@ -615,6 +617,8 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
.push_output(&format!("Saved: {}", path.display()));
self.tabs[i].current_path = Some(path.clone());
self.tabs[i].dirty = false;
// A clean save supersedes any autosave recovery copy.
let _ = std::fs::remove_file(path.with_extension("sv$"));
if self.save_dialog_for_unsaved {
let next = self.update(Message::UnsavedPickedSavePath(Some(path)));
return Task::batch([close, next]);
@@ -712,6 +716,23 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
Task::none()
}
/// Delete the `.sv$` autosave recovery files for all open drawings. They
/// exist only to survive a crash, so a clean save or exit removes them.
pub(in crate::app) fn cleanup_autosaves(&self) {
#[cfg(not(target_arch = "wasm32"))]
for t in &self.tabs {
if let Some(path) = &t.current_path {
let _ = std::fs::remove_file(path.with_extension("sv$"));
}
}
}
/// Remove the autosave recovery files, then quit the application.
pub(in crate::app) fn exit_app(&self) -> Task<Message> {
self.cleanup_autosaves();
iced::exit()
}
pub(super) fn on_page_setup_commit(&mut self) -> Task<Message> {
let i = self.active_tab;
let layout_name = self.tabs[i].scene.current_layout.clone();
+2 -2
View File
@@ -809,7 +809,7 @@ impl OpenCADStudio {
self.pending_close = Some(super::PendingClose::Quit);
return self.open_unsaved_dialog_window();
}
return iced::exit();
return self.exit_app();
}
Task::none()
}
@@ -818,7 +818,7 @@ impl OpenCADStudio {
// Only the main window exists now; all dialogs are in-canvas
// modals (Plan B). Closing it exits.
if self.main_window == Some(id) {
return iced::exit();
return self.exit_app();
}
Task::none()
}