1use std::path::PathBuf;
4use std::sync::mpsc;
5use std::time::{Duration, Instant};
6
7use notify::{Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
8
9use crate::errors::{MdqlError, ValidationError};
10
11pub struct FkWatcher {
13 _watcher: RecommendedWatcher,
14 errors_rx: mpsc::Receiver<Vec<ValidationError>>,
15}
16
17impl FkWatcher {
18 pub fn start(db_path: PathBuf) -> Result<Self, MdqlError> {
21 let (tx, rx) = mpsc::channel();
22
23 let watcher_db_path = db_path.clone();
24 let mut last_run = Instant::now() - Duration::from_secs(10);
25
26 let mut watcher = notify::recommended_watcher(move |res: Result<Event, notify::Error>| {
27 let event = match res {
28 Ok(e) => e,
29 Err(_) => return,
30 };
31
32 let dominated = matches!(
34 event.kind,
35 EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_)
36 );
37 if !dominated {
38 return;
39 }
40
41 let has_md = event.paths.iter().any(|p| {
43 p.extension().and_then(|e| e.to_str()) == Some("md")
44 });
45 if !has_md {
46 return;
47 }
48
49 let now = Instant::now();
51 if now.duration_since(last_run) < Duration::from_millis(500) {
52 return;
53 }
54 last_run = now;
55
56 if let Ok((_config, _tables, errors)) =
58 crate::loader::load_database(&watcher_db_path)
59 {
60 let fk_errors: Vec<_> = errors
61 .into_iter()
62 .filter(|e| e.error_type == "fk_violation" || e.error_type == "fk_missing_table")
63 .collect();
64 let _ = tx.send(fk_errors);
65 }
66 })
67 .map_err(|e| MdqlError::General(format!("Failed to start file watcher: {}", e)))?;
68
69 watcher
70 .watch(&db_path, RecursiveMode::Recursive)
71 .map_err(|e| MdqlError::General(format!("Failed to watch directory: {}", e)))?;
72
73 Ok(FkWatcher {
74 _watcher: watcher,
75 errors_rx: rx,
76 })
77 }
78
79 pub fn poll(&self) -> Option<Vec<ValidationError>> {
82 let mut latest = None;
83 while let Ok(errors) = self.errors_rx.try_recv() {
85 latest = Some(errors);
86 }
87 latest
88 }
89}