#![cfg(feature = "hot-reload")]
use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
use std::sync::Arc;
use std::thread::JoinHandle;
use std::time::Duration;
use notify::RecursiveMode;
use notify_debouncer_full::new_debouncer;
use serde::de::DeserializeOwned;
use crate::config::Config;
use crate::error::ConfigError;
const DEBOUNCE_MS: u64 = 250;
#[must_use = "dropping `WatchHandle` immediately stops the file watcher"]
pub struct WatchHandle {
stop: Arc<AtomicBool>,
thread: Option<JoinHandle<()>>,
}
impl std::fmt::Debug for WatchHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WatchHandle")
.field("stopped", &self.stop.load(Ordering::Relaxed))
.finish_non_exhaustive()
}
}
impl Drop for WatchHandle {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
if let Some(h) = self.thread.take() {
let _ = h.join();
}
}
}
impl<C> Config<C>
where
C: DeserializeOwned + Send + Sync + 'static,
{
pub fn watch_files(&self) -> Result<WatchHandle, ConfigError> {
let paths: Vec<PathBuf> = self.sources.files.clone();
if paths.is_empty() {
return Err(ConfigError::Watch("no user files registered".into()));
}
let (tx, rx) = mpsc::channel();
let mut debouncer = new_debouncer(Duration::from_millis(DEBOUNCE_MS), None, tx)
.map_err(|e| ConfigError::Watch(format!("debouncer: {e}")))?;
for p in &paths {
debouncer
.watch(p, RecursiveMode::NonRecursive)
.map_err(|e| ConfigError::Watch(format!("watch {}: {e}", p.display())))?;
}
let this = self.clone();
let stop = Arc::new(AtomicBool::new(false));
let stop_thread = Arc::clone(&stop);
let thread = std::thread::Builder::new()
.name("rtb-config-watcher".into())
.spawn(move || {
let _debouncer = debouncer;
while !stop_thread.load(Ordering::Relaxed) {
match rx.recv_timeout(Duration::from_millis(100)) {
Ok(Ok(_events)) => {
let _ = this.reload();
}
Ok(Err(_notify_errs)) => { }
Err(mpsc::RecvTimeoutError::Timeout) => {}
Err(mpsc::RecvTimeoutError::Disconnected) => break,
}
}
})
.map_err(|e| ConfigError::Watch(format!("spawn watcher thread: {e}")))?;
Ok(WatchHandle { stop, thread: Some(thread) })
}
}