use crate::ported::lib::path::realpath;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
use std::time::SystemTime;
pub struct StatFileWatcher {
pub watches: Mutex<HashMap<PathBuf, SystemTime>>,
}
impl Default for StatFileWatcher {
fn default() -> Self {
Self::new()
}
}
impl StatFileWatcher {
pub fn new() -> Self {
Self {
watches: Mutex::new(HashMap::new()), }
}
pub fn watch<P: AsRef<Path>>(&self, path: P) {
let path = realpath(path); let mtime = path
.metadata()
.and_then(|m| m.modified())
.unwrap_or(SystemTime::UNIX_EPOCH);
let mut watches = self.watches.lock().unwrap(); watches.insert(path, mtime); }
pub fn unwatch<P: AsRef<Path>>(&self, path: P) {
let path = realpath(path); let mut watches = self.watches.lock().unwrap(); watches.remove(&path); }
pub fn is_watching<P: AsRef<Path>>(&self, path: P) -> bool {
let path = realpath(path);
let watches = self.watches.lock().unwrap(); watches.contains_key(&path) }
pub fn check<P: AsRef<Path>>(&self, path: P) -> bool {
let path = realpath(path); let current_mtime = path
.metadata()
.and_then(|m| m.modified())
.unwrap_or(SystemTime::UNIX_EPOCH);
let mut watches = self.watches.lock().unwrap(); match watches.get(&path).copied() {
None => {
watches.insert(path, current_mtime); true }
Some(stored) => {
if current_mtime != stored {
watches.insert(path, current_mtime); true } else {
false }
}
}
}
pub fn close(&self) {
let mut watches = self.watches.lock().unwrap(); watches.clear(); }
}
#[cfg(test)]
mod tests {
use super::*;
fn tmp_file_with_content(content: &str) -> PathBuf {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let mut p = std::env::temp_dir();
p.push(format!(
"powerliners-stat-test-{}-{}-{}.txt",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos(),
COUNTER.fetch_add(1, Ordering::SeqCst)
));
std::fs::write(&p, content).unwrap();
p
}
#[test]
fn watch_then_check_returns_false_unchanged() {
let p = tmp_file_with_content("a");
let w = StatFileWatcher::new();
w.watch(&p);
assert!(!w.check(&p));
std::fs::remove_file(&p).ok();
}
#[test]
fn check_returns_true_when_unwatched() {
let p = tmp_file_with_content("a");
let w = StatFileWatcher::new();
assert!(w.check(&p));
assert!(!w.check(&p));
std::fs::remove_file(&p).ok();
}
#[test]
fn check_returns_true_after_mtime_change() {
let p = tmp_file_with_content("a");
let w = StatFileWatcher::new();
w.watch(&p);
std::thread::sleep(std::time::Duration::from_millis(20));
std::fs::write(&p, "b").unwrap();
assert!(w.check(&p));
assert!(!w.check(&p));
std::fs::remove_file(&p).ok();
}
#[test]
fn unwatch_removes_from_set() {
let p = tmp_file_with_content("a");
let w = StatFileWatcher::new();
w.watch(&p);
assert!(w.is_watching(&p));
w.unwatch(&p);
assert!(!w.is_watching(&p));
std::fs::remove_file(&p).ok();
}
#[test]
fn close_clears_all_watches() {
let p1 = tmp_file_with_content("a");
let p2 = tmp_file_with_content("b");
let w = StatFileWatcher::new();
w.watch(&p1);
w.watch(&p2);
assert!(w.is_watching(&p1));
assert!(w.is_watching(&p2));
w.close();
assert!(!w.is_watching(&p1));
assert!(!w.is_watching(&p2));
std::fs::remove_file(&p1).ok();
std::fs::remove_file(&p2).ok();
}
}