1use crate::app::Msg;
9use std::collections::HashMap;
10use std::path::{Path, PathBuf};
11use std::sync::Arc;
12use std::sync::atomic::{AtomicBool, Ordering};
13use std::sync::mpsc::Sender;
14use std::time::{Duration, SystemTime};
15
16pub struct WatcherHandle {
18 stop: Arc<AtomicBool>,
19}
20
21impl Drop for WatcherHandle {
22 fn drop(&mut self) {
23 self.stop.store(true, Ordering::Relaxed);
24 }
25}
26
27type FileState = HashMap<PathBuf, (SystemTime, u64)>;
28
29fn sweep(root: &Path, out: &mut FileState) {
30 let Ok(entries) = std::fs::read_dir(root) else {
31 return;
32 };
33 for entry in entries.flatten() {
34 let path = entry.path();
35 let Ok(file_type) = entry.file_type() else {
36 continue;
37 };
38 if file_type.is_dir() {
39 sweep(&path, out);
40 } else if file_type.is_file()
41 && path.extension().is_some_and(|e| e == "md")
42 && let Ok(meta) = entry.metadata()
43 {
44 let mtime = meta.modified().unwrap_or(SystemTime::UNIX_EPOCH);
45 out.insert(path, (mtime, meta.len()));
46 }
47 }
48}
49
50#[must_use]
53pub fn spawn(root: PathBuf, interval: Duration, tx: Sender<Msg>) -> WatcherHandle {
54 let stop = Arc::new(AtomicBool::new(false));
55 let stop_flag = Arc::clone(&stop);
56 std::thread::spawn(move || {
57 let mut previous = FileState::new();
58 sweep(&root, &mut previous);
59 while !stop_flag.load(Ordering::Relaxed) {
60 std::thread::sleep(interval);
61 if stop_flag.load(Ordering::Relaxed) {
62 break;
63 }
64 let mut current = FileState::new();
65 sweep(&root, &mut current);
66 if current != previous {
67 previous = current;
68 if tx.send(Msg::FilesChanged).is_err() {
69 break;
70 }
71 }
72 }
73 });
74 WatcherHandle { stop }
75}