feat(fs/watch): migrate to `notify-debouncer-full` (#885)

* Add support for notify-debouncer-full

* Add fs watch to demo

* Remove notify-debouncer-mini

* Rename RawEvent to WatchEvent

* Add full type definition for EventKind

* Remove `track file ids` option from fs watcher

* Update plugins/fs/guest-js/index.ts
pull/912/head
Daniel Faust 1 year ago committed by GitHub
parent 0879a87a7e
commit 61edbbec0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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)

21
Cargo.lock generated

@ -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",

@ -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" }

@ -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;
}
</script>
<div class="flex flex-col">
@ -175,6 +215,33 @@
<button class="btn" on:click={stat}>Stat</button>
</div>
{/if}
<h3>Watch</h3>
<input
class="input grow"
placeholder="Type the path to watch..."
bind:value={watchPath}
/>
<br />
<div>
<label for="watch-debounce-delay">Debounce delay in milliseconds (`0` disables the debouncer)</label>
<input
class="input"
id="watch-debounce-delay"
bind:value={watchDebounceDelay}
/>
</div>
<br />
<div>
<input type="checkbox" id="watch-recursive" bind:checked={watchRecursive} />
<label for="watch-recursive">Recursive</label>
</div>
<br />
<div>
<button class="btn" on:click={watch}>Watch</button>
<button class="btn" on:click={unwatch}>Unwatch</button>
</div>
</div>
<br />

@ -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" ]

@ -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<void> {
*/
async function watch(
paths: string | string[] | URL | URL[],
cb: (event: DebouncedEvent) => void,
cb: (event: WatchEvent) => void,
options?: DebouncedWatchOptions,
): Promise<UnwatchFn> {
const opts = {
@ -1163,7 +1195,7 @@ async function watch(
}
}
const onEvent = new Channel<DebouncedEvent>();
const onEvent = new Channel<WatchEvent>();
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<UnwatchFn> {
const opts = {
@ -1201,7 +1233,7 @@ async function watchImmediate(
}
}
const onEvent = new Channel<RawEvent>();
const onEvent = new Channel<WatchEvent>();
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,
};

@ -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<RecommendedWatcher>),
Debouncer(Debouncer<RecommendedWatcher, FileIdMap>),
Watcher(RecommendedWatcher),
}
@ -60,10 +60,10 @@ fn watch_raw(on_event: Channel, rx: Receiver<notify::Result<Event>>) {
fn watch_debounced(on_event: Channel, rx: Receiver<DebounceEventResult>) {
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<R: Runtime>(
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<R: Runtime>(app: AppHandle<R>, 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())
})?
})?;
}
}
}

Loading…
Cancel
Save