use std::path::Path;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use notify::{Event, EventKind, RecursiveMode, Watcher};
pub struct Watch {
pub dirty: Arc<AtomicBool>,
_watcher: Option<Box<dyn Watcher + Send>>,
}
impl Watch {
pub fn idle() -> Self {
Self {
dirty: Arc::new(AtomicBool::new(false)),
_watcher: None,
}
}
pub fn try_new(state_dir: &Path) -> Self {
let dirty = Arc::new(AtomicBool::new(false));
let dirty_for_cb = dirty.clone();
let cb = move |res: notify::Result<Event>| {
if let Ok(ev) = res {
if relevant(&ev.kind) {
dirty_for_cb.store(true, Ordering::SeqCst);
}
}
};
let watcher = notify::recommended_watcher(cb).ok();
let mut watcher: Option<Box<dyn Watcher + Send>> =
watcher.map(|w| Box::new(w) as Box<dyn Watcher + Send>);
if let Some(w) = watcher.as_mut() {
if w.watch(state_dir, RecursiveMode::NonRecursive).is_err() {
return Self::idle();
}
}
Self {
dirty,
_watcher: watcher,
}
}
pub fn take_dirty(&self) -> bool {
self.dirty.swap(false, Ordering::SeqCst)
}
}
fn relevant(kind: &EventKind) -> bool {
matches!(
kind,
EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_)
)
}
#[cfg(test)]
mod tests {
use super::*;
use notify::event::{CreateKind, ModifyKind, RemoveKind};
#[test]
fn idle_watch_is_never_dirty() {
let w = Watch::idle();
assert!(!w.take_dirty());
assert!(!w.take_dirty());
}
#[test]
fn dirty_flag_clears_on_take() {
let w = Watch::idle();
w.dirty.store(true, Ordering::SeqCst);
assert!(w.take_dirty());
assert!(!w.take_dirty(), "second call sees the cleared flag");
}
#[test]
fn relevant_kinds_match_sqlite_commit_shape() {
assert!(relevant(&EventKind::Create(CreateKind::File)));
assert!(relevant(&EventKind::Modify(ModifyKind::Any)));
assert!(relevant(&EventKind::Remove(RemoveKind::File)));
assert!(!relevant(&EventKind::Access(
notify::event::AccessKind::Open(notify::event::AccessMode::Read)
)));
}
#[test]
fn try_new_on_missing_dir_returns_idle() {
let w = Watch::try_new(Path::new("/definitely/does/not/exist/teamctl-ui-test"));
assert!(!w.take_dirty(), "idle fallback never goes dirty");
}
}