// Copyright 2019-2023 Tauri Programme within The Commons Conservancy // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher}; use notify_debouncer_full::{new_debouncer, DebouncedEvent, Debouncer, RecommendedCache}; use serde::Deserialize; use tauri::{ ipc::{Channel, CommandScope, GlobalScope}, path::BaseDirectory, Manager, Resource, ResourceId, Runtime, Webview, }; use std::time::Duration; use crate::{ commands::{resolve_path, CommandResult}, scope::Entry, SafeFilePath, }; #[allow(unused)] enum WatcherKind { Debouncer(Debouncer), Watcher(RecommendedWatcher), } impl Resource for WatcherKind {} #[derive(Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct WatchOptions { base_dir: Option, #[serde(default)] recursive: bool, delay_ms: Option, } #[tauri::command] pub fn watch( webview: Webview, paths: Vec, options: WatchOptions, on_event: Channel, global_scope: GlobalScope, command_scope: CommandScope, ) -> CommandResult { let resolved_paths = paths .into_iter() .map(|path| { resolve_path( "watch", &webview, &global_scope, &command_scope, path, options.base_dir, ) }) .collect::>>()?; let recursive_mode = if options.recursive { RecursiveMode::Recursive } else { RecursiveMode::NonRecursive }; let watcher_kind = if let Some(delay) = options.delay_ms { let mut debouncer = new_debouncer( Duration::from_millis(delay), None, move |events: Result, Vec>| { if let Ok(events) = events { for event in events { // TODO: Should errors be emitted too? let _ = on_event.send(event.event); } } }, )?; for path in &resolved_paths { debouncer.watch(path, recursive_mode)?; } WatcherKind::Debouncer(debouncer) } else { let mut watcher = RecommendedWatcher::new( move |event| { if let Ok(event) = event { // TODO: Should errors be emitted too? let _ = on_event.send(event); } }, Config::default(), )?; for path in &resolved_paths { watcher.watch(path, recursive_mode)?; } WatcherKind::Watcher(watcher) }; let rid = webview.resources_table().add(watcher_kind); Ok(rid) } #[cfg(test)] mod tests { use notify::{ event::{DataChange, ModifyKind}, Event, EventKind, }; // the `WatchEvent` type of the JavaScript API relies on this format #[test] fn event_kind_is_flattened_into_the_event() { let event = Event::new(EventKind::Modify(ModifyKind::Data(DataChange::Content))) .add_path("/tmp/file".into()); assert_eq!( serde_json::to_value(event).unwrap(), serde_json::json!({ "type": "modify", "kind": "data", "mode": "content", "paths": ["/tmp/file"], "attrs": {} }) ); let event = Event::new(EventKind::Any); assert_eq!( serde_json::to_value(event).unwrap(), serde_json::json!({ "type": "any", "paths": [], "attrs": {} }) ); } }