use std::path::{Path, PathBuf};
use std::sync::mpsc;
use std::time::Duration;
use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher};
use reactive_core::{Emitter, Task, spawn_stream};
const COALESCE: Duration = Duration::from_millis(50);
const CANCEL_POLL: Duration = Duration::from_millis(500);
pub fn watch_path(path: impl Into<PathBuf>, mut on_change: impl FnMut() + 'static) -> Task {
let path = path.into();
spawn_stream(move |out| run(&path, out), move |()| on_change(), || {})
}
fn run(path: &Path, out: Emitter<()>) {
let (tx, rx) = mpsc::channel();
let mut watcher: RecommendedWatcher =
match notify::recommended_watcher(move |result: notify::Result<Event>| {
if result.is_ok() {
let _ = tx.send(());
}
}) {
Ok(watcher) => watcher,
Err(e) => {
tracing::warn!("cannot watch {}: {e}", path.display());
return;
}
};
if let Err(e) = watcher.watch(path, RecursiveMode::Recursive) {
tracing::warn!("cannot watch {}: {e}", path.display());
return;
}
loop {
match rx.recv_timeout(CANCEL_POLL) {
Ok(()) => {}
Err(mpsc::RecvTimeoutError::Timeout) => {
if out.is_cancelled() {
return;
}
continue;
}
Err(mpsc::RecvTimeoutError::Disconnected) => return,
}
while rx.recv_timeout(COALESCE).is_ok() {}
if out.is_cancelled() {
return;
}
out.emit(());
}
}