diff --git a/.changes/fs-replace-notify-debouncer.md b/.changes/fs-replace-notify-debouncer.md new file mode 100644 index 00000000..86b190eb --- /dev/null +++ b/.changes/fs-replace-notify-debouncer.md @@ -0,0 +1,6 @@ +--- +"fs": "patch" +"fs-js": "patch" +--- + +Replace `notify-debouncer-mini` with `notify-debouncer-full`. [(plugins-workspace#885)](https://github.com/tauri-apps/plugins-workspace/pull/885) diff --git a/Cargo.lock b/Cargo.lock index abf9e6ce..e8e6dcc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1739,6 +1739,15 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "file-id" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6584280525fb2059cba3db2c04abf947a1a29a45ddae89f3870f8281704fafc9" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "filetime" version = "0.2.22" @@ -3574,15 +3583,17 @@ dependencies = [ ] [[package]] -name = "notify-debouncer-mini" -version = "0.4.1" +name = "notify-debouncer-full" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d40b221972a1fc5ef4d858a2f671fb34c75983eb385463dff3780eeff6a9d43" +checksum = "49f5dab59c348b9b50cf7f261960a20e389feb2713636399cd9082cd4b536154" dependencies = [ "crossbeam-channel", + "file-id", "log", "notify", - "serde", + "parking_lot", + "walkdir", ] [[package]] @@ -5999,7 +6010,7 @@ dependencies = [ "anyhow", "glob", "notify", - "notify-debouncer-mini", + "notify-debouncer-full", "serde", "serde_repr", "tauri", diff --git a/examples/api/src-tauri/Cargo.toml b/examples/api/src-tauri/Cargo.toml index 7645f026..a70a8a71 100644 --- a/examples/api/src-tauri/Cargo.toml +++ b/examples/api/src-tauri/Cargo.toml @@ -20,7 +20,7 @@ serde = { workspace = true } tiny_http = "0.11" log = { workspace = true } tauri-plugin-log = { path = "../../../plugins/log", version = "2.0.0-alpha.6" } -tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.0.0-alpha.7" } +tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.0.0-alpha.7", features = [ "watch" ] } tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.0.0-alpha.6" } tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.0.0-alpha.7" } tauri-plugin-http = { path = "../../../plugins/http", features = [ "multipart" ], version = "2.0.0-alpha.9" } diff --git a/examples/api/src/views/FileSystem.svelte b/examples/api/src/views/FileSystem.svelte index 081d26ab..dce83663 100644 --- a/examples/api/src/views/FileSystem.svelte +++ b/examples/api/src/views/FileSystem.svelte @@ -9,6 +9,11 @@ let img; let file; let renameTo; + let watchPath = ""; + let watchDebounceDelay = 0; + let watchRecursive = false; + let unwatchFn; + let unwatchPath = ""; function getDir() { const dirSelect = document.getElementById("dir"); @@ -141,6 +146,41 @@ function setSrc() { img.src = convertFileSrc(path); } + + function watch() { + unwatch(); + if (watchPath) { + onMessage(`Watching ${watchPath} for changes`); + let options = { + recursive: watchRecursive, + delayMs: parseInt(watchDebounceDelay), + }; + if (options.delayMs === 0) { + fs.watchImmediate(watchPath, onMessage, options) + .then((fn) => { + unwatchFn = fn; + unwatchPath = watchPath; + }) + .catch(onMessage); + } else { + fs.watch(watchPath, onMessage, options) + .then((fn) => { + unwatchFn = fn; + unwatchPath = watchPath; + }) + .catch(onMessage); + } + } + } + + function unwatch() { + if (unwatchFn) { + onMessage(`Stopped watching ${unwatchPath} for changes`); + unwatchFn(); + } + unwatchFn = undefined; + unwatchPath = undefined; + }
@@ -175,6 +215,33 @@
{/if} + +

Watch

+ + +
+
+ + +
+
+
+ + +
+
+
+ + +

diff --git a/plugins/fs/Cargo.toml b/plugins/fs/Cargo.toml index a3273731..7ea7da8d 100644 --- a/plugins/fs/Cargo.toml +++ b/plugins/fs/Cargo.toml @@ -21,7 +21,7 @@ anyhow = "1" uuid = { version = "1", features = [ "v4" ] } glob = "0.3" notify = { version = "6", optional = true, features = [ "serde" ] } -notify-debouncer-mini = { version = "0.4", optional = true, features = [ "serde" ] } +notify-debouncer-full = { version = "0.3", optional = true } [features] -watch = [ "notify", "notify-debouncer-mini" ] +watch = [ "notify", "notify-debouncer-full" ] diff --git a/plugins/fs/guest-js/index.ts b/plugins/fs/guest-js/index.ts index 3d650e16..feb1d663 100644 --- a/plugins/fs/guest-js/index.ts +++ b/plugins/fs/guest-js/index.ts @@ -1098,8 +1098,8 @@ interface DebouncedWatchOptions extends WatchOptions { /** * @since 2.0.0 */ -type RawEvent = { - type: RawEventKind; +type WatchEvent = { + type: WatchEventKind; paths: string[]; attrs: unknown; }; @@ -1107,28 +1107,60 @@ type RawEvent = { /** * @since 2.0.0 */ -type RawEventKind = - | "any " - | { - access?: unknown; - } - | { - create?: unknown; - } - | { - modify?: unknown; - } +type WatchEventKind = + | "any" + | { access: WatchEventKindAccess } + | { create: WatchEventKindCreate } + | { modify: WatchEventKindModify } + | { remove: WatchEventKindRemove } + | "other"; + +/** + * @since 2.0.0 + */ +type WatchEventKindAccess = + | { kind: "any" } + | { kind: "close"; mode: "any" | "execute" | "read" | "write" | "other" } + | { kind: "open"; mode: "any" | "execute" | "read" | "write" | "other" } + | { kind: "other" }; + +/** + * @since 2.0.0 + */ +type WatchEventKindCreate = + | { kind: "any" } + | { kind: "file" } + | { kind: "folder" } + | { kind: "other" }; + +/** + * @since 2.0.0 + */ +type WatchEventKindModify = + | { kind: "any" } + | { kind: "data"; mode: "any" | "size" | "content" | "other" } | { - remove?: unknown; + kind: "metadata"; + mode: + | "any" + | "access-time" + | "write-time" + | "permissions" + | "ownership" + | "extended" + | "other"; } - | "other"; + | { kind: "name"; mode: "any" | "to" | "from" | "both" | "other" } + | { kind: "other" }; /** * @since 2.0.0 */ -type DebouncedEvent = - | { kind: "Any"; path: string }[] - | { kind: "AnyContinuous"; path: string }[]; +type WatchEventKindRemove = + | { kind: "any" } + | { kind: "file" } + | { kind: "folder" } + | { kind: "other" }; /** * @since 2.0.0 @@ -1146,7 +1178,7 @@ async function unwatch(rid: number): Promise { */ async function watch( paths: string | string[] | URL | URL[], - cb: (event: DebouncedEvent) => void, + cb: (event: WatchEvent) => void, options?: DebouncedWatchOptions, ): Promise { const opts = { @@ -1163,7 +1195,7 @@ async function watch( } } - const onEvent = new Channel(); + const onEvent = new Channel(); onEvent.onmessage = cb; const rid: number = await invoke("plugin:fs|watch", { @@ -1184,7 +1216,7 @@ async function watch( */ async function watchImmediate( paths: string | string[] | URL | URL[], - cb: (event: RawEvent) => void, + cb: (event: WatchEvent) => void, options?: WatchOptions, ): Promise { const opts = { @@ -1201,7 +1233,7 @@ async function watchImmediate( } } - const onEvent = new Channel(); + const onEvent = new Channel(); onEvent.onmessage = cb; const rid: number = await invoke("plugin:fs|watch", { @@ -1232,8 +1264,12 @@ export type { FileInfo, WatchOptions, DebouncedWatchOptions, - DebouncedEvent, - RawEvent, + WatchEvent, + WatchEventKind, + WatchEventKindAccess, + WatchEventKindCreate, + WatchEventKindModify, + WatchEventKindRemove, UnwatchFn, }; diff --git a/plugins/fs/src/watcher.rs b/plugins/fs/src/watcher.rs index 05683586..07e46b61 100644 --- a/plugins/fs/src/watcher.rs +++ b/plugins/fs/src/watcher.rs @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher}; -use notify_debouncer_mini::{new_debouncer, DebounceEventResult, Debouncer}; +use notify_debouncer_full::{new_debouncer, DebounceEventResult, Debouncer, FileIdMap}; use serde::Deserialize; use tauri::{ ipc::Channel, @@ -43,7 +43,7 @@ impl WatcherResource { impl Resource for WatcherResource {} enum WatcherKind { - Debouncer(Debouncer), + Debouncer(Debouncer), Watcher(RecommendedWatcher), } @@ -60,10 +60,10 @@ fn watch_raw(on_event: Channel, rx: Receiver>) { fn watch_debounced(on_event: Channel, rx: Receiver) { spawn(move || { - while let Ok(event) = rx.recv() { - if let Ok(event) = event { + while let Ok(Ok(events)) = rx.recv() { + for event in events { // TODO: Should errors be emitted too? - let _ = on_event.send(&event); + let _ = on_event.send(&event.event); } } }); @@ -97,10 +97,9 @@ pub async fn watch( let kind = if let Some(delay) = options.delay_ms { let (tx, rx) = channel(); - let mut debouncer = new_debouncer(Duration::from_millis(delay), tx)?; - let watcher = debouncer.watcher(); + let mut debouncer = new_debouncer(Duration::from_millis(delay), None, tx)?; for path in &resolved_paths { - watcher.watch(path.as_ref(), mode)?; + debouncer.watcher().watch(path.as_ref(), mode)?; } watch_debounced(on_event, rx); WatcherKind::Debouncer(debouncer) @@ -130,14 +129,14 @@ pub async fn unwatch(app: AppHandle, rid: ResourceId) -> CommandR for path in &watcher.paths { debouncer.watcher().unwatch(path.as_ref()).map_err(|e| { format!("failed to unwatch path: {} with error: {e}", path.display()) - })? + })?; } } WatcherKind::Watcher(ref mut w) => { for path in &watcher.paths { w.unwatch(path.as_ref()).map_err(|e| { format!("failed to unwatch path: {} with error: {e}", path.display()) - })? + })?; } } }