fix(sync): compare small files by content to detect quick rewrites
File times are only as fine as the kernel clock tick. A manifest rewritten right after a check with the same length (one page replaced by another) looked unchanged, so the change was never picked up. Files up to 64 KiB (manifest, open marker, small pages) now also carry a content hash. Showed up as a flaky test_page_still_being_synced_is_tried_again in CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c95cde8ec8
commit
dd8e063e29
1 file changed
+22
-3
+22
-3
@@ -19,7 +19,21 @@ use crate::storage::{self, NotebookManifest, OpenMarker, PageData};
|
||||
/// Time between two checks.
|
||||
pub const INTERVAL: Duration = Duration::from_secs(3);
|
||||
|
||||
type Fingerprint = (Option<SystemTime>, u64);
|
||||
/// Modification time, size and, for small files, a hash of the content.
|
||||
type Fingerprint = (Option<SystemTime>, u64, Option<u64>);
|
||||
|
||||
/// Files up to this size are also compared by content: file times are only as fine as the
|
||||
/// kernel clock tick, so a file rewritten right after a check with the same length (e.g. a
|
||||
/// manifest with one page replaced by another) would otherwise look unchanged.
|
||||
const HASHED_FILE_SIZE: u64 = 64 * 1024;
|
||||
|
||||
fn content_hash(path: &Path) -> Option<u64> {
|
||||
use std::hash::{Hash, Hasher};
|
||||
let bytes = fs::read(path).ok()?;
|
||||
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
||||
bytes.hash(&mut hasher);
|
||||
Some(hasher.finish())
|
||||
}
|
||||
|
||||
/// What a check found and took over.
|
||||
#[derive(Debug, Default, Clone, PartialEq)]
|
||||
@@ -61,8 +75,13 @@ fn scan(dir: &Path) -> HashMap<String, Fingerprint> {
|
||||
return None;
|
||||
}
|
||||
let meta = entry.metadata().ok()?;
|
||||
meta.is_file()
|
||||
.then(|| (name, (meta.modified().ok(), meta.len())))
|
||||
if !meta.is_file() {
|
||||
return None;
|
||||
}
|
||||
let hash = (meta.len() <= HASHED_FILE_SIZE)
|
||||
.then(|| content_hash(&entry.path()))
|
||||
.flatten();
|
||||
Some((name, (meta.modified().ok(), meta.len(), hash)))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user