pub mod blocking;
mod util;
use notify::Watcher as _;
pub use notify::{self, EventKind};
pub use notify_debouncer_full::DebouncedEvent as Event;
use notify_debouncer_full::{new_debouncer, DebounceEventResult, Debouncer, FileIdMap};
use std::{
collections::HashMap,
path::{Path, PathBuf},
sync::{
mpsc::{channel, Receiver},
Arc, Mutex,
},
};
const RECURSIVE_MODE: notify::RecursiveMode = notify::RecursiveMode::Recursive;
#[derive(Debug)]
pub enum Error {
Io(std::io::Error),
Notify(notify::Error),
}
impl std::fmt::Display for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Io(error) => error.fmt(fmt),
Self::Notify(error) => error.fmt(fmt),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(error) => error.source(),
Self::Notify(error) => error.source(),
}
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Self::Io(err)
}
}
impl From<notify::Error> for Error {
fn from(err: notify::Error) -> Self {
if let notify::ErrorKind::Io(err) = err.kind {
err.into()
} else {
Self::Notify(err)
}
}
}
type HandlerMap = HashMap<PathBuf, Box<dyn FnMut(Event) + Send>>;
pub struct Hotwatch {
debouncer: Debouncer<notify::RecommendedWatcher, FileIdMap>,
handlers: Arc<Mutex<HandlerMap>>,
}
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 handlers = Arc::<Mutex<_>>::default();
Self::run(Arc::clone(&handlers), rx);
let debouncer = new_debouncer(delay, None, tx).map_err(Error::Notify)?;
Ok(Self {
debouncer,
handlers,
})
}
pub fn watch<P, F>(&mut self, path: P, handler: F) -> Result<(), Error>
where
P: AsRef<Path>,
F: 'static + FnMut(Event) + Send,
{
let absolute_path = path.as_ref().canonicalize()?;
self.debouncer
.watcher()
.watch(&absolute_path, RECURSIVE_MODE)?;
self.debouncer
.cache()
.add_root(&absolute_path, RECURSIVE_MODE);
let mut handlers = self.handlers.lock().expect("handler mutex poisoned!");
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);
let mut handlers = self.handlers.lock().expect("handler mutex poisoned!");
handlers.remove(&absolute_path);
Ok(())
}
fn run(handlers: Arc<Mutex<HandlerMap>>, rx: Receiver<DebounceEventResult>) {
std::thread::spawn(move || loop {
match rx.recv() {
Ok(result) => match result {
Ok(events) => {
for event in events {
util::log_event(&event);
let mut handlers = handlers.lock().expect("handler mutex poisoned!");
if let Some(handler) = util::handler_for_event(&event, &mut handlers) {
handler(event);
}
}
}
Err(errs) => {
for err in errs {
util::log_error(&err);
}
}
},
Err(_) => {
util::log_dead();
break;
}
}
});
}
}