use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::time::SystemTime;
use crate::hasher::hash_bytes;
pub const MAX_REFRESH_FILES: usize = 64;
type Signature = (u64, Option<SystemTime>);
#[derive(Default)]
pub struct FreshnessCache {
verified: Mutex<HashMap<PathBuf, Signature>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Freshness {
Fresh,
Changed,
Missing,
}
impl FreshnessCache {
fn lock(&self) -> std::sync::MutexGuard<'_, HashMap<PathBuf, Signature>> {
self.verified.lock().unwrap_or_else(|e| e.into_inner())
}
pub fn check(&self, abs: &Path, indexed_hash: &str) -> (Freshness, Option<Signature>) {
let Ok(meta) = std::fs::metadata(abs) else {
self.lock().remove(abs);
return (Freshness::Missing, None);
};
let signature = (meta.len(), meta.modified().ok());
if self.lock().get(abs) == Some(&signature) {
return (Freshness::Fresh, Some(signature));
}
let Ok(bytes) = std::fs::read(abs) else {
return (Freshness::Missing, None);
};
if hash_bytes(&bytes) == indexed_hash {
self.lock().insert(abs.to_path_buf(), signature);
(Freshness::Fresh, Some(signature))
} else {
self.lock().remove(abs);
(Freshness::Changed, Some(signature))
}
}
pub fn record(&self, abs: &Path, signature: Option<Signature>) {
if let Some(signature) = signature {
self.lock().insert(abs.to_path_buf(), signature);
}
}
pub fn forget(&self, abs: &Path) {
self.lock().remove(abs);
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RefreshReport {
pub refreshed: Vec<String>,
pub removed: Vec<String>,
pub skipped: Vec<String>,
}
impl RefreshReport {
pub fn changed(&self) -> bool {
!self.refreshed.is_empty() || !self.removed.is_empty()
}
pub fn count(&self) -> usize {
self.refreshed.len() + self.removed.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_changed_file_is_detected_and_a_verified_one_is_not_rehashed() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("a.rs");
std::fs::write(&file, "fn a() {}").unwrap();
let cache = FreshnessCache::default();
let hash = hash_bytes(b"fn a() {}");
assert_eq!(cache.check(&file, &hash).0, Freshness::Fresh);
assert_eq!(cache.check(&file, &hash).0, Freshness::Fresh);
std::fs::write(&file, "fn a() {}\nfn b() {}").unwrap();
assert_eq!(cache.check(&file, &hash).0, Freshness::Changed);
std::fs::remove_file(&file).unwrap();
assert_eq!(cache.check(&file, &hash).0, Freshness::Missing);
}
}