// File watcher for markdown viewer // Uses notify crate to watch files and emit Tauri events on change use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher}; use serde::Serialize; use std::collections::HashMap; use std::path::PathBuf; use std::sync::Mutex; use tauri::Emitter; #[derive(Clone, Serialize)] struct FileChangedPayload { pane_id: String, path: String, content: String, } struct WatchEntry { _watcher: RecommendedWatcher, _path: PathBuf, } pub struct FileWatcherManager { watchers: Mutex>, } impl FileWatcherManager { pub fn new() -> Self { Self { watchers: Mutex::new(HashMap::new()), } } pub fn watch( &self, app: &tauri::AppHandle, pane_id: &str, path: &str, ) -> Result { let file_path = PathBuf::from(path); if !file_path.exists() { return Err(format!("File not found: {path}")); } // Read initial content let content = std::fs::read_to_string(&file_path) .map_err(|e| format!("Failed to read file: {e}"))?; // Set up watcher let app_handle = app.clone(); let pane_id_owned = pane_id.to_string(); let watch_path = file_path.clone(); let mut watcher = RecommendedWatcher::new( move |res: Result| { if let Ok(event) = res { if event.kind.is_modify() { if let Ok(new_content) = std::fs::read_to_string(&watch_path) { let _ = app_handle.emit( "file-changed", FileChangedPayload { pane_id: pane_id_owned.clone(), path: watch_path.to_string_lossy().to_string(), content: new_content, }, ); } } } }, Config::default(), ) .map_err(|e| format!("Failed to create watcher: {e}"))?; watcher .watch(file_path.parent().unwrap_or(&file_path), RecursiveMode::NonRecursive) .map_err(|e| format!("Failed to watch path: {e}"))?; let mut watchers = self.watchers.lock().unwrap(); watchers.insert(pane_id.to_string(), WatchEntry { _watcher: watcher, _path: file_path, }); Ok(content) } pub fn unwatch(&self, pane_id: &str) { let mut watchers = self.watchers.lock().unwrap(); watchers.remove(pane_id); } pub fn read_file(&self, path: &str) -> Result { std::fs::read_to_string(path) .map_err(|e| format!("Failed to read file: {e}")) } }