use std::collections::HashSet;
use std::path::{Path, PathBuf};
use std::sync::mpsc::{self, Receiver, Sender};
use std::thread;
use std::time::Duration;
use notify::{EventKind, RecommendedWatcher, RecursiveMode, Watcher as _, recommended_watcher};
use crate::events::Event;
const DEBOUNCE: Duration = Duration::from_millis(200);
pub struct Watcher {
watcher: Option<RecommendedWatcher>,
watched: HashSet<PathBuf>,
}
impl Watcher {
pub fn new(enabled: bool, events: Sender<Event>) -> Self {
Self {
watcher: match enabled {
true => start(events),
false => None,
},
watched: HashSet::new(),
}
}
pub fn watch(&mut self, path: &Path) {
let Some(watcher) = &mut self.watcher else {
return;
};
if !self.watched.insert(path.to_path_buf()) {
return;
}
let _ = watcher.watch(path, RecursiveMode::NonRecursive);
}
pub fn unwatch(&mut self, path: &Path) {
let Some(watcher) = &mut self.watcher else {
return;
};
if !self.watched.remove(path) {
return;
}
let _ = watcher.unwatch(path);
}
}
fn start(events: Sender<Event>) -> Option<RecommendedWatcher> {
let (raw, incoming) = mpsc::channel();
let watcher = recommended_watcher(move |result: notify::Result<notify::Event>| {
let Ok(event) = result else {
return;
};
if matches!(event.kind, EventKind::Access(_)) {
return;
}
let _ = raw.send(event.paths);
})
.ok()?;
thread::spawn(move || debounce(&incoming, &events));
Some(watcher)
}
fn debounce(incoming: &Receiver<Vec<PathBuf>>, events: &Sender<Event>) {
while let Ok(first) = incoming.recv() {
let mut paths: HashSet<PathBuf> = first.into_iter().collect();
while let Ok(more) = incoming.recv_timeout(DEBOUNCE) {
paths.extend(more);
}
if events
.send(Event::FsChange(paths.into_iter().collect()))
.is_err()
{
return;
}
}
}