1use std::path::{Path, PathBuf};
8use std::sync::mpsc as std_mpsc;
9
10use notify::{Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
11use tokio::sync::mpsc;
12
13#[non_exhaustive]
15#[derive(Debug, Clone)]
16pub enum ChangeEvent {
17 Touched(Vec<PathBuf>),
19 Removed(Vec<PathBuf>),
21}
22
23#[non_exhaustive]
25#[derive(Debug, thiserror::Error)]
26pub enum WatcherError {
27 #[error("watcher setup failed: {0}")]
29 Setup(String),
30 #[error("watch failed: {0}")]
32 Watch(String),
33}
34
35pub struct FileWatcher {
37 _inner: RecommendedWatcher,
38 rx: mpsc::UnboundedReceiver<ChangeEvent>,
39}
40
41impl FileWatcher {
42 pub fn new(root: &Path) -> Result<Self, WatcherError> {
48 let (event_tx, event_rx) = std_mpsc::channel::<notify::Result<Event>>();
49 let mut watcher = notify::recommended_watcher(event_tx)
50 .map_err(|e| WatcherError::Setup(e.to_string()))?;
51 watcher
52 .watch(root, RecursiveMode::Recursive)
53 .map_err(|e| WatcherError::Watch(e.to_string()))?;
54
55 let (out_tx, out_rx) = mpsc::unbounded_channel();
56 std::thread::spawn(move || {
57 for result in event_rx {
58 let Ok(event) = result else { continue };
59 let mapped = map_event(event);
60 if let Some(change) = mapped {
61 if out_tx.send(change).is_err() {
62 break;
63 }
64 }
65 }
66 });
67
68 Ok(Self {
69 _inner: watcher,
70 rx: out_rx,
71 })
72 }
73
74 pub async fn next(&mut self) -> Option<ChangeEvent> {
76 self.rx.recv().await
77 }
78}
79
80fn map_event(event: Event) -> Option<ChangeEvent> {
81 match event.kind {
82 EventKind::Create(_) | EventKind::Modify(_) => Some(ChangeEvent::Touched(event.paths)),
83 EventKind::Remove(_) => Some(ChangeEvent::Removed(event.paths)),
84 _ => None,
85 }
86}