use std::path::Path;
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;
use std::time::Duration;
use notify::Watcher;
use serde::de::DeserializeOwned;
use crate::error::ConfigError;
const DEBOUNCE: Duration = Duration::from_millis(300);
pub fn watch<T, F>(path: &Path, handler: F) -> Result<ConfigWatcher, ConfigError>
where
T: DeserializeOwned + Send + Sync + 'static,
F: Fn(T) + Send + Sync + 'static,
{
let (sender, receiver) = mpsc::channel();
let mut platform =
notify::recommended_watcher(sender.clone()).map_err(|source| ConfigError::WatchFailed {
source: Box::new(source),
})?;
platform
.watch(path, notify::RecursiveMode::NonRecursive)
.map_err(|source| ConfigError::WatchFailed {
source: Box::new(source),
})?;
let stop = Arc::new(AtomicBool::new(false));
let errors: Arc<Mutex<Option<Arc<ConfigError>>>> = Arc::new(Mutex::new(None));
let worker = Worker {
receiver,
watched: path.to_path_buf(),
handler: Box::new(handler),
errors: Arc::clone(&errors),
stop: Arc::clone(&stop),
};
let handle = std::thread::Builder::new()
.name("config-kit-watch".into())
.spawn(move || worker.run())
.map_err(|source| ConfigError::WatchFailed {
source: Box::new(source),
})?;
Ok(ConfigWatcher {
platform: Some(platform),
handle: Some(handle),
stop,
errors,
})
}
#[derive(Debug)]
pub struct ConfigWatcher {
platform: Option<notify::RecommendedWatcher>,
handle: Option<std::thread::JoinHandle<()>>,
stop: Arc<AtomicBool>,
errors: Arc<Mutex<Option<Arc<ConfigError>>>>,
}
impl ConfigWatcher {
#[must_use]
pub fn last_error(&self) -> Option<Arc<ConfigError>> {
match self.errors.lock() {
Ok(slot) => slot.clone(),
Err(_) => Some(Arc::new(ConfigError::Poisoned)),
}
}
pub fn stop(mut self) {
self.stop_flag(true);
self.platform = None;
if let Some(handle) = self.handle.take() {
let _ = handle.join();
}
}
fn stop_flag(&self, stopped: bool) {
self.stop.store(stopped, Ordering::Relaxed);
}
}
impl Drop for ConfigWatcher {
fn drop(&mut self) {
self.stop_flag(true);
}
}
struct Worker<T: DeserializeOwned + Send + Sync + 'static> {
receiver: mpsc::Receiver<Result<notify::Event, notify::Error>>,
watched: PathBuf,
handler: Box<dyn Fn(T) + Send + Sync>,
errors: Arc<Mutex<Option<Arc<ConfigError>>>>,
stop: Arc<AtomicBool>,
}
impl<T: DeserializeOwned + Send + Sync + 'static> Worker<T> {
fn run(mut self) {
let mut pending = false;
loop {
if self.stop.load(Ordering::Relaxed) {
return;
}
match self.receiver.recv_timeout(DEBOUNCE) {
Ok(Ok(event)) => {
if event.paths.iter().any(|path| path == &self.watched) {
pending = true;
}
}
Ok(Err(_)) => pending = true,
Err(mpsc::RecvTimeoutError::Timeout) => {
if pending {
pending = false;
self.parse_and_fire();
}
}
Err(mpsc::RecvTimeoutError::Disconnected) => return,
}
}
}
fn parse_and_fire(&mut self) {
match crate::builder::read_layer_value(&self.watched) {
Ok(value) => match T::deserialize(value) {
Ok(parsed) => (self.handler)(parsed),
Err(source) => self.record(ConfigError::Parse {
path: self.watched.clone(),
source: Box::new(source),
}),
},
Err(error) => self.record(error),
}
}
fn record(&self, error: ConfigError) {
if let Ok(mut slot) = self.errors.lock() {
*slot = Some(Arc::new(error));
}
}
}