use std::path::{Path, PathBuf};
use notify::Watcher;
use tokio::sync::mpsc;
pub struct FileWatcher {
_watcher: notify::RecommendedWatcher,
events: mpsc::Receiver<()>,
}
impl FileWatcher {
pub fn new(paths: &[PathBuf]) -> notify::Result<Self> {
let (tx, rx) = mpsc::channel(1);
let mut watcher = notify::recommended_watcher(move |res: notify::Result<notify::Event>| {
let send = match res {
Ok(event) => is_reload_trigger(&event.kind),
Err(_) => true,
};
if send {
let _ = tx.try_send(());
}
})?;
let mut dirs: Vec<&Path> = paths
.iter()
.filter_map(|p| p.parent())
.map(|p| if p.as_os_str().is_empty() { Path::new(".") } else { p })
.collect();
dirs.sort_unstable();
dirs.dedup();
for dir in dirs {
watcher.watch(dir, notify::RecursiveMode::NonRecursive)?;
}
Ok(Self {
_watcher: watcher,
events: rx,
})
}
pub async fn changed(&mut self) {
self.events
.recv()
.await
.expect("file watcher channel closed unexpectedly");
}
}
fn is_reload_trigger(kind: ¬ify::EventKind) -> bool {
use notify::EventKind;
use notify::event::{AccessKind, AccessMode};
match kind {
EventKind::Access(AccessKind::Close(AccessMode::Write)) => true,
EventKind::Access(_) => false,
EventKind::Create(_) | EventKind::Modify(_) => true,
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bare_filename_watches_current_dir() {
FileWatcher::new(&[PathBuf::from("cert.pem"), PathBuf::from("key.pem")])
.expect("bare filenames should watch the current directory");
}
#[test]
fn ignored_events_do_not_trigger_reload() {
use notify::EventKind;
use notify::event::{AccessKind, AccessMode, RemoveKind};
assert!(!is_reload_trigger(&EventKind::Access(AccessKind::Read)));
assert!(!is_reload_trigger(&EventKind::Access(AccessKind::Open(
AccessMode::Read
))));
assert!(!is_reload_trigger(&EventKind::Access(AccessKind::Open(
AccessMode::Any
))));
assert!(!is_reload_trigger(&EventKind::Access(AccessKind::Close(
AccessMode::Read
))));
assert!(!is_reload_trigger(&EventKind::Remove(RemoveKind::Any)));
}
#[test]
fn writes_and_rotations_trigger_reload() {
use notify::EventKind;
use notify::event::{AccessKind, AccessMode, CreateKind, ModifyKind};
assert!(is_reload_trigger(&EventKind::Access(AccessKind::Close(
AccessMode::Write
))));
assert!(is_reload_trigger(&EventKind::Create(CreateKind::Any)));
assert!(is_reload_trigger(&EventKind::Modify(ModifyKind::Any)));
}
}