use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use arc_swap::ArcSwap;
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
use crate::config::Config;
use crate::filter::FilterEngine;
pub struct HotReloader {
engine: Arc<ArcSwap<FilterEngine>>,
_watcher: RecommendedWatcher,
}
impl HotReloader {
pub fn start(
upstream_args: Vec<String>,
config_path: Option<PathBuf>,
preset_override: Option<String>,
) -> anyhow::Result<Self> {
let upstream_refs: Vec<&str> = upstream_args.iter().map(|s| s.as_str()).collect();
let config = Config::build(
&upstream_refs,
config_path.as_deref(),
preset_override.as_deref(),
)?;
let engine = Arc::new(FilterEngine::new(Arc::new(config)));
let swappable = Arc::new(ArcSwap::from(engine));
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::<()>();
let mut watcher =
notify::recommended_watcher(move |res: Result<notify::Event, notify::Error>| {
if let Ok(event) = res {
if event.kind.is_modify() || event.kind.is_create() || event.kind.is_remove() {
let _ = tx.send(());
}
}
})?;
if let Ok(dir) = crate::config::external_presets_dir() {
if let Err(e) = watcher.watch(&dir, RecursiveMode::NonRecursive) {
tracing::warn!("Failed to watch presets dir: {e}");
} else {
tracing::info!("Watching external presets: {}", dir.display());
}
}
if let Some(ref path) = config_path {
if let Err(e) = watcher.watch(path, RecursiveMode::NonRecursive) {
tracing::warn!("Failed to watch config file: {e}");
}
}
let swappable_clone = swappable.clone();
tokio::spawn(async move {
loop {
if rx.recv().await.is_none() {
break;
}
tokio::time::sleep(Duration::from_millis(500)).await;
while rx.try_recv().is_ok() {}
let refs: Vec<&str> = upstream_args.iter().map(|s| s.as_str()).collect();
match Config::build(&refs, config_path.as_deref(), preset_override.as_deref()) {
Ok(config) => {
let new_engine = Arc::new(FilterEngine::new(Arc::new(config)));
swappable_clone.store(new_engine);
tracing::info!("Hot-reloaded filter engine with updated presets");
}
Err(e) => {
tracing::warn!("Hot-reload failed, keeping previous config: {e}");
}
}
}
});
Ok(Self {
engine: swappable,
_watcher: watcher,
})
}
pub fn engine(&self) -> &Arc<ArcSwap<FilterEngine>> {
&self.engine
}
}