waterui_cli/debug/
file_watcher.rs1use std::path::Path;
4use std::sync::mpsc;
5use std::time::SystemTime;
6
7use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher};
8use smol::channel::{self, Receiver};
9
10pub struct FileWatcher {
12 watcher: RecommendedWatcher,
13 rx: Receiver<()>,
14}
15
16impl std::fmt::Debug for FileWatcher {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 f.debug_struct("FileWatcher").finish_non_exhaustive()
19 }
20}
21
22impl FileWatcher {
23 pub fn new(project_path: &Path) -> notify::Result<Self> {
30 let (tx, rx) = channel::unbounded();
31
32 let (sync_tx, sync_rx) = mpsc::channel::<notify::Result<Event>>();
34
35 let tx_clone = tx;
37 let started_at = SystemTime::now();
38 std::thread::spawn(move || {
39 while let Ok(event) = sync_rx.recv() {
40 if let Ok(event) = event {
41 if is_relevant_change(&event, started_at) {
43 let _ = tx_clone.send_blocking(());
44 }
45 }
46 }
47 });
48
49 let watcher = notify::recommended_watcher(move |res| {
50 let _ = sync_tx.send(res);
51 })?;
52
53 let mut file_watcher = Self { watcher, rx };
54
55 let src_path = project_path.join("src");
57 if src_path.exists() {
58 file_watcher
59 .watcher
60 .watch(&src_path, RecursiveMode::Recursive)?;
61 }
62
63 Ok(file_watcher)
64 }
65
66 #[must_use]
70 pub const fn receiver(&self) -> &Receiver<()> {
71 &self.rx
72 }
73}
74
75fn is_relevant_change(event: &Event, started_at: SystemTime) -> bool {
77 use notify::{EventKind, event::ModifyKind};
78
79 let kind = &event.kind;
82 let is_relevant_kind = match kind {
83 EventKind::Create(_) | EventKind::Remove(_) => true,
84 EventKind::Modify(modify_kind) => !matches!(modify_kind, ModifyKind::Metadata(_)),
85 _ => false,
86 };
87
88 if !is_relevant_kind {
89 return false;
90 }
91
92 event
93 .paths
94 .iter()
95 .any(|path| is_relevant_path(path, *kind, started_at))
96}
97
98fn is_relevant_path(path: &Path, kind: notify::EventKind, started_at: SystemTime) -> bool {
99 use notify::{EventKind, event::ModifyKind};
100
101 if !path
102 .extension()
103 .is_some_and(|ext| ext == "rs" || ext == "toml")
104 {
105 return false;
106 }
107
108 if matches!(kind, EventKind::Remove(_)) {
110 return true;
111 }
112
113 if matches!(kind, EventKind::Modify(ModifyKind::Name(_))) {
116 return true;
117 }
118
119 std::fs::metadata(path)
123 .and_then(|m| m.modified())
124 .map_or(true, |modified| modified > started_at)
125}