use notify::EventKind; use serde::Serialize; use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::sync::mpsc::RecvTimeoutError; use std::time::{Duration, Instant}; use tauri::{AppHandle, Emitter}; #[derive(Serialize, Clone)] struct FileChangedPayload { path: String, } pub fn run_debounce_loop( rx: std::sync::mpsc::Receiver>, app: AppHandle, ) { let mut pending: HashMap = HashMap::new(); loop { match rx.recv_timeout(Duration::from_millis(10)) { Ok(Ok(event)) => { if matches!( event.kind, EventKind::Modify(_) | EventKind::Create(_) | EventKind::Remove(_) ) { for p in event.paths { if is_relevant_path(&p) { pending.insert(p, Instant::now()); } } } } Ok(Err(_)) => {} Err(RecvTimeoutError::Timeout) => {} Err(RecvTimeoutError::Disconnected) => break, } let now = Instant::now(); let ready: Vec = pending .iter() .filter(|(_, t)| now.duration_since(**t) >= Duration::from_millis(80)) .map(|(p, _)| p.clone()) .collect(); for p in ready { pending.remove(&p); let _ = app.emit( "file-changed", FileChangedPayload { path: p.to_string_lossy().to_string(), }, ); } } } fn is_relevant_path(path: &Path) -> bool { if path .components() .any(|c| c.as_os_str().to_string_lossy().starts_with('.')) { return false; } path.extension().is_some_and(|e| e == "md") } #[cfg(test)] mod tests { use super::*; use std::path::Path; #[test] fn relevant_path_md_file() { assert!(is_relevant_path(Path::new("/docs/notes.md"))); } #[test] fn relevant_path_nested_md() { assert!(is_relevant_path(Path::new("/wiki/section/page.md"))); } #[test] fn irrelevant_path_txt_extension() { assert!(!is_relevant_path(Path::new("/docs/readme.txt"))); } #[test] fn irrelevant_path_no_extension() { assert!(!is_relevant_path(Path::new("/docs/makefile"))); } #[test] fn irrelevant_path_hidden_file() { assert!(!is_relevant_path(Path::new("/docs/.hidden.md"))); } #[test] fn irrelevant_path_hidden_dir() { assert!(!is_relevant_path(Path::new("/docs/.git/file.md"))); } #[test] fn irrelevant_path_hidden_dir_at_root() { assert!(!is_relevant_path(Path::new(".hidden/file.md"))); } }