use crate::{util, Error, Event, RECURSIVE_MODE};
use notify::Watcher as _;
use notify_debouncer_full::{new_debouncer, DebounceEventResult, Debouncer, FileIdMap};
use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::mpsc::{channel, Receiver},
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Flow {
Continue,
Exit,
}
impl Default for Flow {
fn default() -> Self {
Self::Continue
}
}
pub struct Hotwatch {
debouncer: Debouncer<notify::RecommendedWatcher, FileIdMap>,
handlers: HashMap<PathBuf, Box<dyn FnMut(Event) -> Flow>>,
rx: Receiver<DebounceEventResult>,
}
impl std::fmt::Debug for Hotwatch {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_struct("Hotwatch").finish()
}
}
impl Hotwatch {
pub fn new() -> Result<Self, Error> {
Self::new_with_custom_delay(std::time::Duration::from_secs(2))
}
pub fn new_with_custom_delay(delay: std::time::Duration) -> Result<Self, Error> {
let (tx, rx) = channel();
let debouncer = new_debouncer(delay, None, tx).map_err(Error::Notify)?;
Ok(Self {
debouncer,
handlers: Default::default(),
rx,
})
}
pub fn watch<P, F>(&mut self, path: P, handler: F) -> Result<(), Error>
where
P: AsRef<Path>,
F: 'static + FnMut(Event) -> Flow,
{
let absolute_path = path.as_ref().canonicalize()?;
self.debouncer
.watcher()
.watch(&absolute_path, RECURSIVE_MODE)?;
self.debouncer
.cache()
.add_root(&absolute_path, RECURSIVE_MODE);
self.handlers.insert(absolute_path, Box::new(handler));
Ok(())
}
pub fn unwatch<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Error> {
let absolute_path = path.as_ref().canonicalize()?;
self.debouncer.watcher().unwatch(&absolute_path)?;
self.debouncer.cache().remove_root(&absolute_path);
self.handlers.remove(&absolute_path);
Ok(())
}
pub fn run(&mut self) {
'watch: loop {
match self.rx.recv() {
Ok(result) => match result {
Ok(events) => {
for event in events {
util::log_event(&event);
if let Some(handler) =
util::handler_for_event(&event, &mut self.handlers)
{
if let Flow::Exit = handler(event) {
break 'watch;
}
}
}
}
Err(errs) => {
for err in errs {
util::log_error(&err);
}
}
},
Err(_) => {
util::log_dead();
break;
}
}
}
}
}