From 7d29dfac585eb4d42245b04fa206136e463acb85 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Tue, 13 Oct 2026 16:35:21 +0200 Subject: [PATCH] Recheck file contents before deleting a moved original (#1500) --- crates/engine/src/import_move.rs | 5 +++-- crates/engine/src/tests_import_move.rs | 27 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/import_move.rs b/crates/engine/src/import_move.rs index db4421369..c92ee2796 100644 --- a/crates/engine/src/import_move.rs +++ b/crates/engine/src/import_move.rs @@ -223,11 +223,12 @@ pub(crate) fn rollback(p: &Placed) { } /// Remove a placed photo's source (its catalog record is committed and saved): only when the -/// destination is still there with the same size. Then its sidecars, unless another photo still +/// destination is still there with the same bytes. Then its sidecars, unless another photo still /// uses a shared one. `Err` = the source was kept (why); `Ok` lists sidecars kept (path, why). pub(crate) fn finish(p: &Placed) -> Result, String> { + // compare the bytes now: either file may have changed since the copy was verified let same = match (fs::metadata(&p.src), fs::metadata(&p.dst)) { - (Ok(s), Ok(d)) => s.is_file() && d.is_file() && s.len() == d.len(), + (Ok(s), Ok(d)) => s.is_file() && d.is_file() && same_bytes(&p.src, &p.dst).unwrap_or(false), _ => false, }; if !same { diff --git a/crates/engine/src/tests_import_move.rs b/crates/engine/src/tests_import_move.rs index 3e6e9208b..ce249df16 100644 --- a/crates/engine/src/tests_import_move.rs +++ b/crates/engine/src/tests_import_move.rs @@ -503,3 +503,30 @@ fn copies_run_side_by_side_with_the_same_outcome() { assert_eq!(first.file_name, "IMG_1.png"); let _ = std::fs::remove_dir_all(&base); } + +#[test] +fn finish_keeps_the_original_when_the_copy_changed_to_the_same_length() { + use crate::import_move::{finish, place}; + let base = temp_dir("finish-recheck"); + let (src, dest) = (base.join("card/IMG_1.jpg"), base.join("dest")); + std::fs::create_dir_all(src.parent().unwrap()).unwrap(); + std::fs::write(&src, b"original bytes").unwrap(); + inject(Fault::NoLink); + let placed = place(&src, &dest, "IMG_1.jpg").unwrap(); + // unchanged copy: the original goes + assert!(finish(&placed).is_ok()); + assert!(!src.exists()); + // a copy replaced by other bytes of the same length: the original stays + std::fs::write(&src, b"original bytes").unwrap(); + let placed = place(&src, &dest, "IMG_1.jpg").unwrap(); + std::fs::write(&placed.dst, b"different byte").unwrap(); + assert!(finish(&placed).is_err()); + assert_eq!(std::fs::read(&src).unwrap(), b"original bytes"); + // an original edited meanwhile to other bytes of the same length: it stays too + let placed = place(&src, &dest, "IMG_1.jpg").unwrap(); + std::fs::write(&src, b"edited bytes!").unwrap(); + assert!(finish(&placed).is_err()); + assert_eq!(std::fs::read(&src).unwrap(), b"edited bytes!"); + inject(Fault::None); + let _ = std::fs::remove_dir_all(&base); +}