use notify::RecursiveMode;
use notify_debouncer_mini::{new_debouncer, DebouncedEvent, Debouncer};
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use tokio::sync::mpsc;
pub struct ConfigWatcher {
_debouncer: Debouncer<notify::RecommendedWatcher>,
config_path: PathBuf,
}
impl ConfigWatcher {
pub fn new(
config_path: PathBuf,
reload_tx: mpsc::UnboundedSender<()>,
saving_flag: Arc<AtomicBool>,
) -> Result<Self, notify::Error> {
let path_for_closure = config_path.clone();
let saving_flag_clone = Arc::clone(&saving_flag);
let (tx, rx) = std::sync::mpsc::channel();
let mut debouncer = new_debouncer(Duration::from_secs(1), tx)?;
debouncer
.watcher()
.watch(&config_path, RecursiveMode::NonRecursive)?;
log::info!("Started watching config file: {:?}", config_path);
tokio::spawn(async move {
loop {
match rx.recv() {
Ok(Ok(events)) => {
if Self::is_config_modified(&events, &path_for_closure, &saving_flag_clone) {
log::info!("External config file change detected, triggering reload");
if reload_tx.send(()).is_err() {
log::error!("Failed to send reload signal - receiver dropped");
break;
}
}
}
Ok(Err(error)) => {
log::error!("File watch error: {:?}", error);
}
Err(_) => {
log::info!("File watcher channel closed");
break;
}
}
}
});
Ok(Self {
_debouncer: debouncer,
config_path,
})
}
fn is_config_modified(
events: &[DebouncedEvent],
config_path: &PathBuf,
saving_flag: &Arc<AtomicBool>,
) -> bool {
if saving_flag.load(Ordering::SeqCst) {
log::trace!("Ignoring file events during programmatic save");
return false;
}
events.iter().any(|event| {
event.path == *config_path
})
}
}
impl Drop for ConfigWatcher {
fn drop(&mut self) {
log::info!("Stopping config file watcher for {:?}", self.config_path);
}
}