use crate::config::AppConfig;
use crate::event::Event;
use anyhow::Result;
use notify::Watcher as NotifyWatcherTrait; use notify_debouncer_full::{new_debouncer, DebouncedEvent};
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::mpsc::Sender;
use tracing::{debug, error, info, warn};
pub async fn run_watcher(app_config: Arc<AppConfig>, event_tx: Sender<Event>) -> Result<()> {
std::thread::spawn(move || {
let thread_app_config = app_config; let folders_to_watch = thread_app_config.folders_to_watch.clone();
let (debouncer_internal_tx, debouncer_internal_rx) = std::sync::mpsc::channel();
let mut debouncer = match new_debouncer(
Duration::from_secs(1), None, debouncer_internal_tx, ) {
Ok(d) => d,
Err(e) => {
error!("[WatcherThread] Failed to create debouncer: {}", e);
return; }
};
for folder_str in &folders_to_watch {
let path = Path::new(folder_str);
if !path.exists() {
warn!(
"[WatcherThread] Path does not exist, skipping: {}",
folder_str
);
continue;
}
if !path.is_dir() {
warn!(
"[WatcherThread] Path is not a directory, skipping: {}",
folder_str
);
continue;
}
match debouncer
.watcher() .watch(path, notify::RecursiveMode::Recursive)
{
Ok(_) => info!("[WatcherThread] Watching folder: {}", folder_str),
Err(e) => error!(
"[WatcherThread] Failed to watch folder {}: {}",
folder_str, e
),
}
debouncer
.cache()
.add_root(path, notify::RecursiveMode::Recursive);
}
info!(
"[WatcherThread] File system watcher thread started for {:?}",
folders_to_watch
);
loop {
match debouncer_internal_rx.recv() {
Ok(debouncer_result) => {
match debouncer_result {
Ok(events) => {
for debounced_event in events {
handle_debounced_event(&debounced_event, &event_tx);
}
}
Err(errors) => {
for error in errors {
error!("[WatcherThread] Debouncer reported error: {:?}", error);
}
}
}
}
Err(e) => {
error!("[WatcherThread] Debouncer internal channel error: {:?}. Watcher thread exiting.", e);
break; }
}
}
info!("[WatcherThread] Exiting normally.");
});
Ok(()) }
fn handle_debounced_event(debounced_event: &DebouncedEvent, event_tx: &Sender<Event>) {
if debounced_event.paths.is_empty() {
debug!(
"Received debounced event with no paths: {:?}",
debounced_event
);
return;
}
let path_str = debounced_event.paths[0].to_string_lossy().to_string();
let op_str = match debounced_event.kind {
notify::event::EventKind::Create(_) => "CREATE",
notify::event::EventKind::Modify(notify::event::ModifyKind::Data(_)) => "WRITE",
notify::event::EventKind::Modify(notify::event::ModifyKind::Name(
notify::event::RenameMode::To,
)) => "CREATE", notify::event::EventKind::Modify(notify::event::ModifyKind::Name(
notify::event::RenameMode::From,
)) => "REMOVE", notify::event::EventKind::Modify(notify::event::ModifyKind::Name(
notify::event::RenameMode::Both,
)) => "RENAME",
notify::event::EventKind::Modify(_) => "WRITE", notify::event::EventKind::Remove(_) => "REMOVE",
_ => {
debug!(
"[WatcherThread] Unhandled or ignored debounced event kind: {:?} for path {}",
debounced_event.kind, path_str
);
return;
}
};
let event = Event {
path: path_str.clone(),
op: op_str.to_string(),
};
debug!("[WatcherThread] Produced event: {:?}", event);
if let Err(e) = event_tx.blocking_send(event.clone()) {
error!(
"[WatcherThread] Failed to send event to main async channel: {}. Event: {:?}",
e, event
);
}
}