Merge pull request #932 from vbasky/fix/894-recovery-snapshots

Keep recovery snapshots when session metadata is unreadable
This commit is contained in:
Brandon Thomas 2026-10-11 19:06:59 -04:00
commit b2c00d4cac
2 changed files with 31 additions and 8 deletions

View File

@ -572,15 +572,16 @@ pub fn scan_candidates(data_dir: &Path, skip: Option<&Path>) -> Vec<RecoveryCand
if !dir.is_dir() || Some(dir.as_path()) == skip || !lock_is_stale(&dir) {
continue;
}
let meta = fs::read(dir.join(META)).ok().and_then(|b| serde_json::from_slice::<JournalMeta>(&b).ok());
match meta {
Some(meta) if dir.join(SNAPSHOT).is_file() => {
out.push(RecoveryCandidate { id: e.file_name().to_string_lossy().into_owned(), dir, meta });
}
_ => {
let _ = fs::remove_dir_all(&dir);
}
let snap = dir.join(SNAPSHOT);
if !snap.is_file() {
// Empty leftover dirs (clean exit, or a crash before the first snapshot).
let _ = fs::remove_dir_all(&dir);
continue;
}
// Keep the snapshot even when session.json is missing or unreadable: the project
// is still recoverable. Meta fields fall back to defaults.
let meta = fs::read(dir.join(META)).ok().and_then(|b| serde_json::from_slice::<JournalMeta>(&b).ok()).unwrap_or_default();
out.push(RecoveryCandidate { id: e.file_name().to_string_lossy().into_owned(), dir, meta });
}
out.sort_by(|a, b| b.meta.saved_unix.cmp(&a.meta.saved_unix).then(b.id.cmp(&a.id)));
out

View File

@ -176,6 +176,28 @@ fn auto_save_ring_rotates_and_can_save_the_project() {
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn scan_keeps_snapshot_when_session_metadata_is_unreadable() {
// #894: a stale session with a snapshot must not be deleted because session.json is missing
// or corrupt — the recovered project would vanish from the list and the file would be gone.
let d = temp_dir("recover-nometa");
let root = autosave::recovery_root(&d);
let sess = root.join("123-1");
std::fs::create_dir_all(&sess).unwrap();
let snap = filmcraft_format::encode(&filmcraft_project::Project::default(), false);
std::fs::write(sess.join("snapshot.fcproj"), snap).unwrap();
std::fs::write(sess.join("session.json"), b"not json {").unwrap();
let found = autosave::scan_candidates(&d, None);
assert_eq!(found.len(), 1);
assert_eq!(found[0].id, "123-1");
assert!(sess.join("snapshot.fcproj").is_file(), "snapshot must survive the scan");
std::fs::remove_file(sess.join("session.json")).unwrap();
let found = autosave::scan_candidates(&d, None);
assert_eq!(found.len(), 1, "missing metadata still lists the snapshot");
assert!(sess.join("snapshot.fcproj").is_file());
let _ = std::fs::remove_dir_all(&d);
}
#[test]
fn portable_marker_keeps_data_next_to_the_executable() {
// #139: a portable install must not write to %APPDATA%.