use std::time::{Duration, SystemTime, UNIX_EPOCH};
const SETTLE_WINDOW: Duration = Duration::from_millis(100);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) struct FileStamp {
mtime_nanos: u64,
len: u64,
}
impl FileStamp {
pub(crate) fn read(path: &str) -> Option<Self> {
let meta = std::fs::metadata(path).ok()?;
Some(Self {
mtime_nanos: meta
.modified()
.ok()
.and_then(|t| t.duration_since(UNIX_EPOCH).ok())
.map(|d| d.as_nanos() as u64)
.unwrap_or(0),
len: meta.len(),
})
}
pub(crate) fn settled(&self) -> bool {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
self.settled_at(now)
}
fn settled_at(&self, now_nanos: u64) -> bool {
now_nanos.saturating_sub(self.mtime_nanos) > SETTLE_WINDOW.as_nanos() as u64
}
}
#[cfg(test)]
mod tests {
use super::*;
fn stamp(mtime_nanos: u64, len: u64) -> FileStamp {
FileStamp { mtime_nanos, len }
}
#[test]
fn read_reports_length_and_a_missing_file() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("a.bin");
std::fs::write(&path, b"12345").unwrap();
let s = FileStamp::read(path.to_str().unwrap()).expect("stat");
assert_eq!(s.len, 5);
assert!(FileStamp::read("/no/such/file.bin").is_none());
}
#[test]
fn equal_length_rewrite_can_repeat_the_stamp() {
assert_eq!(stamp(1_000, 5), stamp(1_000, 5));
assert_ne!(stamp(1_000, 5), stamp(1_000, 6), "length must separate");
assert_ne!(stamp(1_000, 5), stamp(2_000, 5), "mtime must separate");
}
#[test]
fn settled_only_once_the_window_has_elapsed() {
let window = SETTLE_WINDOW.as_nanos() as u64;
let s = stamp(1_000_000, 5);
assert!(!s.settled_at(1_000_000), "a just-written file is unsettled");
assert!(
!s.settled_at(1_000_000 + window),
"the boundary itself is still unsettled"
);
assert!(s.settled_at(1_000_000 + window + 1), "past the window");
}
#[test]
fn a_future_mtime_is_never_settled() {
assert!(!stamp(u64::MAX, 5).settled_at(1_000));
}
#[test]
fn a_file_settles_after_the_window() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("a.bin");
std::fs::write(&path, b"12345").unwrap();
let s = FileStamp::read(path.to_str().unwrap()).expect("stat");
assert!(!s.settled(), "a file written just now is unsettled");
std::thread::sleep(SETTLE_WINDOW + Duration::from_millis(50));
assert!(s.settled(), "the same stamp settles once the window passes");
}
}