use std::path::{Path, PathBuf};
use std::sync::mpsc::{Receiver, Sender, channel};
use std::time::{Duration, Instant};
use notify::{EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use crate::server::Event;
const DEBOUNCE: Duration = Duration::from_millis(80);
pub fn watch_file(path: &Path, events_tx: Sender<Event>) -> notify::Result<RecommendedWatcher> {
let target = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
let parent = target
.parent()
.map(Path::to_path_buf)
.unwrap_or_else(|| PathBuf::from("."));
let (raw_tx, raw_rx) = channel::<notify::Result<notify::Event>>();
let mut watcher = notify::recommended_watcher(move |res| {
let _ = raw_tx.send(res);
})?;
watcher.watch(&parent, RecursiveMode::NonRecursive)?;
std::thread::spawn(move || debounce_loop(raw_rx, events_tx, target));
Ok(watcher)
}
fn debounce_loop(
raw_rx: Receiver<notify::Result<notify::Event>>,
events_tx: Sender<Event>,
target: PathBuf,
) {
loop {
let first = match raw_rx.recv() {
Ok(event) => event,
Err(_) => return,
};
let mut relevant = event_touches(&first, &target);
let deadline = Instant::now() + DEBOUNCE;
loop {
let now = Instant::now();
if now >= deadline {
break;
}
match raw_rx.recv_timeout(deadline - now) {
Ok(event) => relevant |= event_touches(&event, &target),
Err(_) => break,
}
}
if relevant && events_tx.send(Event::Reload).is_err() {
return;
}
}
}
fn event_touches(event: ¬ify::Result<notify::Event>, target: &Path) -> bool {
let Ok(event) = event else {
return false;
};
if matches!(event.kind, EventKind::Access(_)) {
return false;
}
event
.paths
.iter()
.any(|p| p == target || p.canonicalize().map(|c| c == target).unwrap_or(false))
}