use notify::{Event, RecursiveMode, Watcher};
use std::path::{Path, PathBuf};
use std::sync::mpsc::{channel, Receiver};
use std::time::Duration;
use tracing::{debug, info, warn};
#[derive(Debug, Clone)]
pub enum FileChange {
Created(PathBuf),
Modified(PathBuf),
Deleted(PathBuf),
}
pub struct FileWatcher {
#[allow(dead_code)]
watcher: notify::RecommendedWatcher,
receiver: Receiver<FileChange>,
}
impl FileWatcher {
pub fn new(root: &Path) -> Result<Self, notify::Error> {
let (tx, rx) = channel();
let mut watcher = notify::recommended_watcher(move |res: Result<Event, notify::Error>| {
match res {
Ok(event) => {
for path in event.paths {
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
if !arbor_core::languages::is_supported(ext) {
continue;
}
let change = match event.kind {
notify::EventKind::Create(_) => {
debug!("File created: {}", path.display());
Some(FileChange::Created(path))
}
notify::EventKind::Modify(_) => {
debug!("File modified: {}", path.display());
Some(FileChange::Modified(path))
}
notify::EventKind::Remove(_) => {
debug!("File deleted: {}", path.display());
Some(FileChange::Deleted(path))
}
_ => None,
};
if let Some(change) = change {
if tx.send(change).is_err() {
warn!("Failed to send file change event");
}
}
}
}
Err(e) => warn!("Watch error: {}", e),
}
})?;
watcher.watch(root, RecursiveMode::Recursive)?;
info!("Watching {} for changes", root.display());
Ok(Self {
watcher,
receiver: rx,
})
}
pub fn poll(&self) -> Vec<FileChange> {
self.receiver.try_iter().collect()
}
pub fn recv_timeout(&self, timeout: Duration) -> Option<FileChange> {
self.receiver.recv_timeout(timeout).ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_watcher_creation() {
let dir = tempdir().unwrap();
let watcher = FileWatcher::new(dir.path());
assert!(watcher.is_ok());
}
#[test]
fn test_watcher_detects_change() {
let dir = tempdir().unwrap();
let watcher = FileWatcher::new(dir.path()).unwrap();
let file_path = dir.path().join("test.rs");
fs::write(&file_path, "fn main() {}").unwrap();
let mut detected = false;
for _ in 0..10 {
std::thread::sleep(Duration::from_millis(50));
let changes = watcher.poll();
if !changes.is_empty() {
detected = true;
break;
}
}
if !detected {
eprintln!("Warning: File change not detected - may be unsupported environment");
}
}
}