fix: data-safety fixes from the project review
- autosave: pages a failed flush could not write no longer end up in the notebook that is opened next; they go to the recovery folder - app: finish an open text edit and save before switching notebooks, and stay in the notebook when saving fails - trash: purge by the deletion time in the entry name, not by the folder's modification time, which survives the move into .trash - notebooks are identified by their folder name, so a copied folder is a notebook of its own instead of an alias of the original - manifest: fall back to manifest.json.bak only for a damaged file, not for a newer format version or an I/O error - strokes keep their width after scaling and a restart - PDF export draws shapes with their stroke width instead of a fixed 1.5 pt - a first page that fails to load shows the warning; a first page that was never saved is an empty page; make-space refuses read-only pages Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
1 parent
7c69edab12
commit
c2652ea05b
6 files changed
+540
-135
No files matched your search
+66
-1
@@ -618,11 +618,44 @@ impl AutosaveController {
|
||||
}
|
||||
|
||||
/// Wechselt das Zielverzeichnis für zukünftige Speicheroperationen (z. B. beim Dokumentenwechsel).
|
||||
///
|
||||
/// Whatever the flush could not write belongs to the old notebook and must never be
|
||||
/// written into the new folder, see `abandon_pending`.
|
||||
pub fn switch_notebook_dir(&mut self, new_notebook_dir: PathBuf) {
|
||||
let _ = self.flush();
|
||||
if let Err(err) = self.flush() {
|
||||
eprintln!(
|
||||
"[Autosave] Unsaved changes of '{}' dropped on notebook switch: {}",
|
||||
self.notebook_dir.display(),
|
||||
err.messages.join(", ")
|
||||
);
|
||||
}
|
||||
self.abandon_pending();
|
||||
self.notebook_dir = new_notebook_dir;
|
||||
}
|
||||
|
||||
/// Gives up on everything still waiting to be written: it goes into the recovery folder
|
||||
/// and leaves the queue. For the moment the notebook folder stops being the target
|
||||
/// (switch to another notebook, notebook moved to the trash).
|
||||
pub fn abandon_pending(&mut self) {
|
||||
if !self.pending_pages.is_empty() {
|
||||
match self.export_recovery(None) {
|
||||
Ok(files) => {
|
||||
for file in files {
|
||||
eprintln!("[Autosave] Recovery copy: {}", file.display());
|
||||
}
|
||||
}
|
||||
Err(e) => eprintln!("[Autosave] Recovery copy failed: {e}"),
|
||||
}
|
||||
}
|
||||
self.pending_pages.clear();
|
||||
self.pending_manifest = None;
|
||||
self.retries.clear();
|
||||
self.last_errors.clear();
|
||||
self.first_change_time = None;
|
||||
self.last_change_time = None;
|
||||
self.status = AutosaveStatus::Saved;
|
||||
}
|
||||
|
||||
/// Beendet den Hintergrund-Worker ordnungsgemäß nach einem Flush.
|
||||
pub fn shutdown(&mut self) -> Result<(), AutosaveError> {
|
||||
let res = self.flush();
|
||||
@@ -787,6 +820,38 @@ pub mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_switch_drops_pages_the_flush_could_not_write() {
|
||||
let clock = Arc::new(MockClock::new(Instant::now()));
|
||||
let store = Arc::new(FakePageStore::new());
|
||||
*store.fail_page_writes.lock().unwrap() = 1;
|
||||
let recovery = tempfile::tempdir().unwrap();
|
||||
std::env::set_var("XDG_DATA_HOME", recovery.path());
|
||||
|
||||
let mut controller = AutosaveController::with_options(
|
||||
PathBuf::from("/tmp/old_nb"),
|
||||
Duration::from_millis(100),
|
||||
Duration::from_secs(10),
|
||||
Arc::clone(&store) as Arc<dyn PageStore>,
|
||||
Arc::clone(&clock) as Arc<dyn Clock>,
|
||||
);
|
||||
let page = Page::new(7, PageBackground::Grid5mm);
|
||||
let manifest = NotebookManifest::new("old_nb", "Old");
|
||||
controller.mark_page_dirty(&page, &manifest);
|
||||
|
||||
controller.switch_notebook_dir(PathBuf::from("/tmp/new_nb"));
|
||||
let manifests_before = store.manifest_writes.load(Ordering::SeqCst);
|
||||
|
||||
// Nothing of the old notebook may reach the new folder
|
||||
assert!(controller.flush().is_ok());
|
||||
assert_eq!(store.page_writes.load(Ordering::SeqCst), 0);
|
||||
assert_eq!(
|
||||
store.manifest_writes.load(Ordering::SeqCst),
|
||||
manifests_before
|
||||
);
|
||||
assert_eq!(controller.status(), AutosaveStatus::Saved);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_debounce_bundles_changes() {
|
||||
let clock = Arc::new(MockClock::new(Instant::now()));
|
||||
|
||||
Reference in new issue
Block a user