use crate::config::{Config, opencrabs_home};
use notify::{RecursiveMode, Watcher};
use std::sync::Arc;
use std::time::Duration;
pub type ReloadCallback = Arc<dyn Fn(Config) + Send + Sync>;
pub fn spawn(callbacks: Vec<ReloadCallback>) -> tokio::task::JoinHandle<()> {
tokio::task::spawn_blocking(move || {
let rt = tokio::runtime::Handle::current();
let base = opencrabs_home();
let config_path = base.join("config.toml");
let keys_path = base.join("keys.toml");
let commands_path = base.join("commands.toml");
let (tx, rx) = std::sync::mpsc::channel();
let mut watcher = match notify::recommended_watcher(move |res| {
if let Ok(event) = res {
let _ = tx.send(event);
}
}) {
Ok(w) => w,
Err(e) => {
tracing::error!("ConfigWatcher: failed to create watcher: {}", e);
return;
}
};
for path in [&config_path, &keys_path, &commands_path] {
if path.exists()
&& let Err(e) = watcher.watch(path, RecursiveMode::NonRecursive)
{
tracing::warn!("ConfigWatcher: cannot watch {:?}: {}", path, e);
}
}
tracing::info!(
"ConfigWatcher: watching config.toml, keys.toml and commands.toml in {:?}",
base
);
let debounce = Duration::from_millis(300);
while rx.recv().is_ok() {
let deadline = std::time::Instant::now() + debounce;
loop {
let remaining = deadline.saturating_duration_since(std::time::Instant::now());
if remaining.is_zero() {
break;
}
match rx.recv_timeout(remaining) {
Ok(_) => {}
Err(_) => break,
}
}
match Config::load() {
Ok(new_config) => {
tracing::info!(
"ConfigWatcher: reloaded — firing {} callback(s)",
callbacks.len()
);
for cb in &callbacks {
let cb = cb.clone();
let cfg = new_config.clone();
rt.spawn(async move { cb(cfg) });
}
}
Err(e) => {
tracing::warn!(
"ConfigWatcher: reload failed, keeping current config: {}",
e
);
}
}
}
tracing::info!("ConfigWatcher: stopped");
})
}