fix(autosave): autosave unsaved drawings + clean up .sv$ on file-close (#205)
Two gaps in the autosave recovery files: 1. A brand-new, never-saved drawing was never autosaved — on_autosave only wrote tabs that already had a path. A shared autosave_target() now writes saved drawings to <file>.sv$ and unsaved ones to the system temp dir (OpenCADStudio_<name>.sv$). 2. Closing a file (tab) left its .sv$ behind, which also defeated the program-close cleanup (it only scans still-open tabs). on_tab_close and the unsaved-discard path now delete the closing tab's .sv$, and Save/Save-As drop the temp copy a drawing used while it was still unsaved. Program-close cleanup (exit_app -> cleanup_autosaves) already worked. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
286caa9c61
commit
a9214db344
3 changed files with 39 additions and 13 deletions
|
|
@ -31,6 +31,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
self.pending_close = Some(crate::app::PendingClose::Tab(idx));
|
self.pending_close = Some(crate::app::PendingClose::Tab(idx));
|
||||||
return self.open_unsaved_dialog_window();
|
return self.open_unsaved_dialog_window();
|
||||||
}
|
}
|
||||||
|
// This tab is closing for good — drop its autosave recovery copy.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let _ = std::fs::remove_file(self.autosave_target(idx));
|
||||||
// Only-tab case: when the lone non-start tab closes, fall
|
// Only-tab case: when the lone non-start tab closes, fall
|
||||||
// back to the Start tab if it exists; otherwise spawn a
|
// back to the Start tab if it exists; otherwise spawn a
|
||||||
// fresh blank drawing (legacy behaviour).
|
// fresh blank drawing (legacy behaviour).
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,9 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||||
match self.pending_close.take() {
|
match self.pending_close.take() {
|
||||||
Some(crate::app::PendingClose::Tab(idx)) => {
|
Some(crate::app::PendingClose::Tab(idx)) => {
|
||||||
let close_win = self.close_unsaved_dialog_window();
|
let close_win = self.close_unsaved_dialog_window();
|
||||||
|
// Discarded — drop this tab's autosave recovery copy.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let _ = std::fs::remove_file(self.autosave_target(idx));
|
||||||
if self.tabs.len() == 1 {
|
if self.tabs.len() == 1 {
|
||||||
self.tab_counter += 1;
|
self.tab_counter += 1;
|
||||||
self.tabs[0] =
|
self.tabs[0] =
|
||||||
|
|
@ -272,6 +275,8 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_output(&format!("Saved: {}", path.display()));
|
.push_output(&format!("Saved: {}", path.display()));
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let _ = std::fs::remove_file(self.autosave_target(idx));
|
||||||
self.tabs[idx].current_path = Some(path);
|
self.tabs[idx].current_path = Some(path);
|
||||||
self.tabs[idx].dirty = false;
|
self.tabs[idx].dirty = false;
|
||||||
return self.update(Message::TabClose(idx));
|
return self.update(Message::TabClose(idx));
|
||||||
|
|
@ -293,6 +298,8 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_output(&format!("Saved: {}", path.display()));
|
.push_output(&format!("Saved: {}", path.display()));
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
let _ = std::fs::remove_file(self.autosave_target(i));
|
||||||
self.tabs[i].current_path = Some(path);
|
self.tabs[i].current_path = Some(path);
|
||||||
self.tabs[i].dirty = false;
|
self.tabs[i].dirty = false;
|
||||||
if self.tabs.iter().any(|t| t.dirty) {
|
if self.tabs.iter().any(|t| t.dirty) {
|
||||||
|
|
|
||||||
|
|
@ -615,9 +615,12 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_output(&format!("Saved: {}", path.display()));
|
.push_output(&format!("Saved: {}", path.display()));
|
||||||
|
// Drop any prior autosave copy — including the temp
|
||||||
|
// one used while the drawing was still unsaved —
|
||||||
|
// before the tab takes on its new path.
|
||||||
|
let _ = std::fs::remove_file(self.autosave_target(i));
|
||||||
self.tabs[i].current_path = Some(path.clone());
|
self.tabs[i].current_path = Some(path.clone());
|
||||||
self.tabs[i].dirty = false;
|
self.tabs[i].dirty = false;
|
||||||
// A clean save supersedes any autosave recovery copy.
|
|
||||||
let _ = std::fs::remove_file(path.with_extension("sv$"));
|
let _ = std::fs::remove_file(path.with_extension("sv$"));
|
||||||
if self.save_dialog_for_unsaved {
|
if self.save_dialog_for_unsaved {
|
||||||
let next = self.update(Message::UnsavedPickedSavePath(Some(path)));
|
let next = self.update(Message::UnsavedPickedSavePath(Some(path)));
|
||||||
|
|
@ -681,10 +684,28 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||||
self.on_save_dialog_confirm()
|
self.on_save_dialog_confirm()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Where the autosave recovery copy for tab `i` lives: beside a saved
|
||||||
|
/// drawing as `<file>.sv$`, or — for an unsaved drawing with no path yet —
|
||||||
|
/// under the system temp dir keyed by the tab's display name.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub(in crate::app) fn autosave_target(&self, i: usize) -> std::path::PathBuf {
|
||||||
|
match &self.tabs[i].current_path {
|
||||||
|
Some(p) => p.with_extension("sv$"),
|
||||||
|
None => {
|
||||||
|
let safe: String = self.tabs[i]
|
||||||
|
.tab_display_name()
|
||||||
|
.chars()
|
||||||
|
.map(|c| if c.is_alphanumeric() { c } else { '_' })
|
||||||
|
.collect();
|
||||||
|
std::env::temp_dir().join(format!("OpenCADStudio_{safe}.sv$"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Periodic autosave (SAVETIME): write a `.sv$` recovery copy for every
|
/// Periodic autosave (SAVETIME): write a `.sv$` recovery copy for every
|
||||||
/// dirty tab that has a path, at the document's own DWG version. Best-effort
|
/// dirty tab — beside the file if it's saved, else under the temp dir — at
|
||||||
/// and non-destructive — it never touches the original file or the dirty
|
/// the document's own DWG version. Best-effort and non-destructive: it never
|
||||||
/// flag, and skips unsaved (path-less) tabs.
|
/// touches the original file or the dirty flag.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub(super) fn on_autosave(&mut self) -> Task<Message> {
|
pub(super) fn on_autosave(&mut self) -> Task<Message> {
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
|
|
@ -692,21 +713,18 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||||
if !self.tabs[i].dirty {
|
if !self.tabs[i].dirty {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let Some(path) = self.tabs[i].current_path.clone() else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let version = self.tabs[i].scene.document.version;
|
let version = self.tabs[i].scene.document.version;
|
||||||
if let Ok(bytes) =
|
if let Ok(bytes) =
|
||||||
crate::io::save_to_bytes(&self.tabs[i].scene.document, "dwg", version)
|
crate::io::save_to_bytes(&self.tabs[i].scene.document, "dwg", version)
|
||||||
{
|
{
|
||||||
if std::fs::write(path.with_extension("sv$"), bytes).is_ok() {
|
if std::fs::write(self.autosave_target(i), bytes).is_ok() {
|
||||||
n += 1;
|
n += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_output(&format!("Autosaved {n} drawing(s) to .sv$"));
|
.push_output(&format!("Autosaved {n} drawing(s)"));
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
@ -720,10 +738,8 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||||
/// exist only to survive a crash, so a clean save or exit removes them.
|
/// exist only to survive a crash, so a clean save or exit removes them.
|
||||||
pub(in crate::app) fn cleanup_autosaves(&self) {
|
pub(in crate::app) fn cleanup_autosaves(&self) {
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
for t in &self.tabs {
|
for i in 0..self.tabs.len() {
|
||||||
if let Some(path) = &t.current_path {
|
let _ = std::fs::remove_file(self.autosave_target(i));
|
||||||
let _ = std::fs::remove_file(path.with_extension("sv$"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue