use crate::ported::lib::monotonic::monotonic;
use crate::ported::lib::path::realpath;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Mutex;
pub struct DummyTreeWatcher {
pub basedir: PathBuf,
pub is_dummy: bool,
}
impl DummyTreeWatcher {
pub fn new<P: AsRef<Path>>(basedir: P) -> Self {
Self {
basedir: realpath(basedir), is_dummy: true, }
}
pub fn check(&self) -> bool {
false }
}
pub struct TreeWatcher {
pub watches: Mutex<HashMap<PathBuf, DummyTreeWatcher>>,
pub last_query_times: Mutex<HashMap<PathBuf, f64>>,
pub expire_time: f64,
pub watcher_type: String,
}
impl TreeWatcher {
pub fn new(watcher_type: impl Into<String>, expire_time_min: f64) -> Self {
Self {
watches: Mutex::new(HashMap::new()), last_query_times: Mutex::new(HashMap::new()), expire_time: expire_time_min * 60.0, watcher_type: watcher_type.into(), }
}
pub fn get_watcher<P: AsRef<Path>>(&self, path: P) -> Result<DummyTreeWatcher, String> {
match self.watcher_type.as_str() {
"inotify" => Ok(DummyTreeWatcher::new(path)),
"uv" => Ok(DummyTreeWatcher::new(path)),
"dummy" => Ok(DummyTreeWatcher::new(path)),
"stat" => Ok(DummyTreeWatcher::new(path)),
"auto" => Ok(DummyTreeWatcher::new(path)),
other => Err(format!("Unknown watcher type: {}", other)),
}
}
pub fn watch<P: AsRef<Path>>(&self, path: P) -> Result<(), String> {
let path = realpath(path); let w = self.get_watcher(&path)?; self.watches.lock().unwrap().insert(path, w); Ok(())
}
pub fn expire_old_queries(&self) {
let now = monotonic();
let mut times = self.last_query_times.lock().unwrap();
times.retain(|_, lt| now - *lt <= self.expire_time);
}
pub fn check<P: AsRef<Path>>(&self, path: P) -> bool {
let path = realpath(path); self.expire_old_queries(); self.last_query_times .lock()
.unwrap()
.insert(path.clone(), monotonic());
let watches = self.watches.lock().unwrap();
if !watches.contains_key(&path) {
drop(watches);
let _ = self.watch(&path);
return true; }
if let Some(w) = watches.get(&path) {
w.check() } else {
true
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dummy_tree_watcher_always_false() {
let w = DummyTreeWatcher::new(".");
assert!(!w.check());
assert!(w.is_dummy);
}
#[test]
fn tree_watcher_unknown_type_errors() {
let t = TreeWatcher::new("xyz", 10.0);
assert!(t.get_watcher(".").is_err());
}
#[test]
fn tree_watcher_first_call_returns_true_and_records_watch() {
let t = TreeWatcher::new("auto", 10.0);
let cwd = std::env::current_dir().unwrap();
assert!(t.check(&cwd));
assert!(!t.check(&cwd));
}
#[test]
fn tree_watcher_expire_time_is_minutes_to_seconds() {
let t = TreeWatcher::new("auto", 5.0);
assert!((t.expire_time - 300.0).abs() < 1e-9);
}
#[test]
fn expire_old_queries_drops_stale_entries() {
let t = TreeWatcher::new("auto", 0.0); let cwd = std::env::current_dir().unwrap();
t.last_query_times
.lock()
.unwrap()
.insert(cwd.clone(), monotonic() - 1000.0);
t.expire_old_queries();
assert!(t.last_query_times.lock().unwrap().is_empty());
}
}