fix(sync): compare small files by content to detect quick rewrites
Build & Publish Release / Build Packages & Publish to Gitea (push) Successful in 27m40s
Build & Publish Release / Build Packages & Publish to Gitea (push) Successful in 27m40s
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
4ca21483e5
1 file changed
+22
-3
+22
-3
@@ -19,7 +19,21 @@ use crate::storage::{self, NotebookManifest, OpenMarker, PageData};
|
|||||||
/// Time between two checks.
|
/// Time between two checks.
|
||||||
pub const INTERVAL: Duration = Duration::from_secs(3);
|
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.
|
/// What a check found and took over.
|
||||||
#[derive(Debug, Default, Clone, PartialEq)]
|
#[derive(Debug, Default, Clone, PartialEq)]
|
||||||
@@ -61,8 +75,13 @@ fn scan(dir: &Path) -> HashMap<String, Fingerprint> {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let meta = entry.metadata().ok()?;
|
let meta = entry.metadata().ok()?;
|
||||||
meta.is_file()
|
if !meta.is_file() {
|
||||||
.then(|| (name, (meta.modified().ok(), meta.len())))
|
return None;
|
||||||
|
}
|
||||||
|
let hash = (meta.len() <= HASHED_FILE_SIZE)
|
||||||
|
.then(|| content_hash(&entry.path()))
|
||||||
|
.flatten();
|
||||||
|
Some((name, (meta.modified().ok(), meta.len(), hash)))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user