use crate::app::Msg;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::Sender;
use std::time::{Duration, SystemTime};
pub struct WatcherHandle {
stop: Arc<AtomicBool>,
}
impl Drop for WatcherHandle {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
}
}
type FileState = HashMap<PathBuf, (SystemTime, u64)>;
fn sweep(root: &Path, out: &mut FileState) {
let Ok(entries) = std::fs::read_dir(root) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
let Ok(file_type) = entry.file_type() else {
continue;
};
if file_type.is_dir() {
sweep(&path, out);
} else if file_type.is_file()
&& path.extension().is_some_and(|e| e == "md")
&& let Ok(meta) = entry.metadata()
{
let mtime = meta.modified().unwrap_or(SystemTime::UNIX_EPOCH);
out.insert(path, (mtime, meta.len()));
}
}
}
#[must_use]
pub fn spawn(root: PathBuf, interval: Duration, tx: Sender<Msg>) -> WatcherHandle {
let stop = Arc::new(AtomicBool::new(false));
let stop_flag = Arc::clone(&stop);
std::thread::spawn(move || {
let mut previous = FileState::new();
sweep(&root, &mut previous);
while !stop_flag.load(Ordering::Relaxed) {
std::thread::sleep(interval);
if stop_flag.load(Ordering::Relaxed) {
break;
}
let mut current = FileState::new();
sweep(&root, &mut current);
if current != previous {
previous = current;
if tx.send(Msg::FilesChanged).is_err() {
break;
}
}
}
});
WatcherHandle { stop }
}