use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher}; use notify_debouncer_mini::{new_debouncer, DebounceEventResult, Debouncer}; use serde::Deserialize; use tauri::{api::ipc::Channel, command, Runtime, State}; use crate::Result; use std::{ collections::HashMap, path::PathBuf, sync::{ mpsc::{channel, Receiver}, Mutex, }, thread::spawn, time::Duration, }; type Id = u32; #[derive(Default)] pub struct WatcherCollection(Mutex)>>); enum WatcherKind { Debouncer(Debouncer), Watcher(RecommendedWatcher), } fn watch_raw(on_event: Channel, rx: Receiver>) { spawn(move || { while let Ok(event) = rx.recv() { if let Ok(event) = event { // TODO: Should errors be emitted too? let _ = on_event.send(&event); } } }); } fn watch_debounced(on_event: Channel, rx: Receiver) { spawn(move || { while let Ok(event) = rx.recv() { if let Ok(event) = event { // TODO: Should errors be emitted too? let _ = on_event.send(&event); } } }); } #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct WatchOptions { delay_ms: Option, recursive: bool, } #[command] pub async fn watch( watchers: State<'_, WatcherCollection>, id: Id, paths: Vec, options: WatchOptions, on_event: Channel, ) -> Result<()> { let mode = if options.recursive { RecursiveMode::Recursive } else { RecursiveMode::NonRecursive }; let watcher = if let Some(delay) = options.delay_ms { let (tx, rx) = channel(); let mut debouncer = new_debouncer(Duration::from_millis(delay), None, tx)?; let watcher = debouncer.watcher(); for path in &paths { watcher.watch(path, mode)?; } watch_debounced(on_event, rx); WatcherKind::Debouncer(debouncer) } else { let (tx, rx) = channel(); let mut watcher = RecommendedWatcher::new(tx, Config::default())?; for path in &paths { watcher.watch(path, mode)?; } watch_raw(on_event, rx); WatcherKind::Watcher(watcher) }; watchers.0.lock().unwrap().insert(id, (watcher, paths)); Ok(()) } #[command] pub async fn unwatch(watchers: State<'_, WatcherCollection>, id: Id) -> Result<()> { if let Some((watcher, paths)) = watchers.0.lock().unwrap().remove(&id) { match watcher { WatcherKind::Debouncer(mut debouncer) => { for path in paths { debouncer.watcher().unwatch(&path)? } } WatcherKind::Watcher(mut watcher) => { for path in paths { watcher.unwatch(&path)? } } }; } Ok(()) }