1use notify::{Event, RecursiveMode, Watcher};
7use std::path::{Path, PathBuf};
8use std::sync::mpsc::{channel, Receiver};
9use std::time::Duration;
10use tracing::{debug, info, warn};
11
12#[derive(Debug, Clone)]
14pub enum FileChange {
15 Created(PathBuf),
16 Modified(PathBuf),
17 Deleted(PathBuf),
18}
19
20pub struct FileWatcher {
22 #[allow(dead_code)]
23 watcher: notify::RecommendedWatcher,
24 receiver: Receiver<FileChange>,
25}
26
27impl FileWatcher {
28 pub fn new(root: &Path) -> Result<Self, notify::Error> {
33 let (tx, rx) = channel();
34
35 let mut watcher = notify::recommended_watcher(move |res: Result<Event, notify::Error>| {
36 match res {
37 Ok(event) => {
38 for path in event.paths {
39 let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
41
42 if !arbor_core::languages::is_supported(ext) {
43 continue;
44 }
45
46 let change = match event.kind {
47 notify::EventKind::Create(_) => {
48 debug!("File created: {}", path.display());
49 Some(FileChange::Created(path))
50 }
51 notify::EventKind::Modify(_) => {
52 debug!("File modified: {}", path.display());
53 Some(FileChange::Modified(path))
54 }
55 notify::EventKind::Remove(_) => {
56 debug!("File deleted: {}", path.display());
57 Some(FileChange::Deleted(path))
58 }
59 _ => None,
60 };
61
62 if let Some(change) = change {
63 if tx.send(change).is_err() {
64 warn!("Failed to send file change event");
65 }
66 }
67 }
68 }
69 Err(e) => warn!("Watch error: {}", e),
70 }
71 })?;
72
73 watcher.watch(root, RecursiveMode::Recursive)?;
74
75 info!("Watching {} for changes", root.display());
76
77 Ok(Self {
78 watcher,
79 receiver: rx,
80 })
81 }
82
83 pub fn poll(&self) -> Vec<FileChange> {
87 self.receiver.try_iter().collect()
88 }
89
90 pub fn recv_timeout(&self, timeout: Duration) -> Option<FileChange> {
92 self.receiver.recv_timeout(timeout).ok()
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use std::fs;
100 use tempfile::tempdir;
101
102 #[test]
103 fn test_watcher_creation() {
104 let dir = tempdir().unwrap();
105 let watcher = FileWatcher::new(dir.path());
106 assert!(watcher.is_ok());
107 }
108
109 #[test]
110 fn test_watcher_detects_change() {
111 let dir = tempdir().unwrap();
112 let watcher = FileWatcher::new(dir.path()).unwrap();
113
114 let file_path = dir.path().join("test.rs");
116 fs::write(&file_path, "fn main() {}").unwrap();
117
118 let mut detected = false;
120 for _ in 0..10 {
121 std::thread::sleep(Duration::from_millis(50));
122 let changes = watcher.poll();
123 if !changes.is_empty() {
124 detected = true;
125 break;
126 }
127 }
128
129 if !detected {
132 eprintln!("Warning: File change not detected - may be unsupported environment");
133 }
134 }
135}