Skip to content

Commit 61edbbe

Browse files
authored
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
1 parent 0879a87 commit 61edbbe

7 files changed

Lines changed: 161 additions & 42 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"fs": "patch"
3+
"fs-js": "patch"
4+
---
5+
6+
Replace `notify-debouncer-mini` with `notify-debouncer-full`. [(plugins-workspace#885)](https://github.com/tauri-apps/plugins-workspace/pull/885)

Cargo.lock

Lines changed: 16 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/api/src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ serde = { workspace = true }
2020
tiny_http = "0.11"
2121
log = { workspace = true }
2222
tauri-plugin-log = { path = "../../../plugins/log", version = "2.0.0-alpha.6" }
23-
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.0.0-alpha.7" }
23+
tauri-plugin-fs = { path = "../../../plugins/fs", version = "2.0.0-alpha.7", features = [ "watch" ] }
2424
tauri-plugin-clipboard-manager = { path = "../../../plugins/clipboard-manager", version = "2.0.0-alpha.6" }
2525
tauri-plugin-dialog = { path = "../../../plugins/dialog", version = "2.0.0-alpha.7" }
2626
tauri-plugin-http = { path = "../../../plugins/http", features = [ "multipart" ], version = "2.0.0-alpha.9" }

examples/api/src/views/FileSystem.svelte

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
let img;
1010
let file;
1111
let renameTo;
12+
let watchPath = "";
13+
let watchDebounceDelay = 0;
14+
let watchRecursive = false;
15+
let unwatchFn;
16+
let unwatchPath = "";
1217
1318
function getDir() {
1419
const dirSelect = document.getElementById("dir");
@@ -141,6 +146,41 @@
141146
function setSrc() {
142147
img.src = convertFileSrc(path);
143148
}
149+
150+
function watch() {
151+
unwatch();
152+
if (watchPath) {
153+
onMessage(`Watching ${watchPath} for changes`);
154+
let options = {
155+
recursive: watchRecursive,
156+
delayMs: parseInt(watchDebounceDelay),
157+
};
158+
if (options.delayMs === 0) {
159+
fs.watchImmediate(watchPath, onMessage, options)
160+
.then((fn) => {
161+
unwatchFn = fn;
162+
unwatchPath = watchPath;
163+
})
164+
.catch(onMessage);
165+
} else {
166+
fs.watch(watchPath, onMessage, options)
167+
.then((fn) => {
168+
unwatchFn = fn;
169+
unwatchPath = watchPath;
170+
})
171+
.catch(onMessage);
172+
}
173+
}
174+
}
175+
176+
function unwatch() {
177+
if (unwatchFn) {
178+
onMessage(`Stopped watching ${unwatchPath} for changes`);
179+
unwatchFn();
180+
}
181+
unwatchFn = undefined;
182+
unwatchPath = undefined;
183+
}
144184
</script>
145185

146186
<div class="flex flex-col">
@@ -175,6 +215,33 @@
175215
<button class="btn" on:click={stat}>Stat</button>
176216
</div>
177217
{/if}
218+
219+
<h3>Watch</h3>
220+
221+
<input
222+
class="input grow"
223+
placeholder="Type the path to watch..."
224+
bind:value={watchPath}
225+
/>
226+
<br />
227+
<div>
228+
<label for="watch-debounce-delay">Debounce delay in milliseconds (`0` disables the debouncer)</label>
229+
<input
230+
class="input"
231+
id="watch-debounce-delay"
232+
bind:value={watchDebounceDelay}
233+
/>
234+
</div>
235+
<br />
236+
<div>
237+
<input type="checkbox" id="watch-recursive" bind:checked={watchRecursive} />
238+
<label for="watch-recursive">Recursive</label>
239+
</div>
240+
<br />
241+
<div>
242+
<button class="btn" on:click={watch}>Watch</button>
243+
<button class="btn" on:click={unwatch}>Unwatch</button>
244+
</div>
178245
</div>
179246

180247
<br />

plugins/fs/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ anyhow = "1"
2121
uuid = { version = "1", features = [ "v4" ] }
2222
glob = "0.3"
2323
notify = { version = "6", optional = true, features = [ "serde" ] }
24-
notify-debouncer-mini = { version = "0.4", optional = true, features = [ "serde" ] }
24+
notify-debouncer-full = { version = "0.3", optional = true }
2525

2626
[features]
27-
watch = [ "notify", "notify-debouncer-mini" ]
27+
watch = [ "notify", "notify-debouncer-full" ]

plugins/fs/guest-js/index.ts

Lines changed: 60 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,37 +1098,69 @@ interface DebouncedWatchOptions extends WatchOptions {
10981098
/**
10991099
* @since 2.0.0
11001100
*/
1101-
type RawEvent = {
1102-
type: RawEventKind;
1101+
type WatchEvent = {
1102+
type: WatchEventKind;
11031103
paths: string[];
11041104
attrs: unknown;
11051105
};
11061106

11071107
/**
11081108
* @since 2.0.0
11091109
*/
1110-
type RawEventKind =
1111-
| "any "
1112-
| {
1113-
access?: unknown;
1114-
}
1115-
| {
1116-
create?: unknown;
1117-
}
1118-
| {
1119-
modify?: unknown;
1120-
}
1110+
type WatchEventKind =
1111+
| "any"
1112+
| { access: WatchEventKindAccess }
1113+
| { create: WatchEventKindCreate }
1114+
| { modify: WatchEventKindModify }
1115+
| { remove: WatchEventKindRemove }
1116+
| "other";
1117+
1118+
/**
1119+
* @since 2.0.0
1120+
*/
1121+
type WatchEventKindAccess =
1122+
| { kind: "any" }
1123+
| { kind: "close"; mode: "any" | "execute" | "read" | "write" | "other" }
1124+
| { kind: "open"; mode: "any" | "execute" | "read" | "write" | "other" }
1125+
| { kind: "other" };
1126+
1127+
/**
1128+
* @since 2.0.0
1129+
*/
1130+
type WatchEventKindCreate =
1131+
| { kind: "any" }
1132+
| { kind: "file" }
1133+
| { kind: "folder" }
1134+
| { kind: "other" };
1135+
1136+
/**
1137+
* @since 2.0.0
1138+
*/
1139+
type WatchEventKindModify =
1140+
| { kind: "any" }
1141+
| { kind: "data"; mode: "any" | "size" | "content" | "other" }
11211142
| {
1122-
remove?: unknown;
1143+
kind: "metadata";
1144+
mode:
1145+
| "any"
1146+
| "access-time"
1147+
| "write-time"
1148+
| "permissions"
1149+
| "ownership"
1150+
| "extended"
1151+
| "other";
11231152
}
1124-
| "other";
1153+
| { kind: "name"; mode: "any" | "to" | "from" | "both" | "other" }
1154+
| { kind: "other" };
11251155

11261156
/**
11271157
* @since 2.0.0
11281158
*/
1129-
type DebouncedEvent =
1130-
| { kind: "Any"; path: string }[]
1131-
| { kind: "AnyContinuous"; path: string }[];
1159+
type WatchEventKindRemove =
1160+
| { kind: "any" }
1161+
| { kind: "file" }
1162+
| { kind: "folder" }
1163+
| { kind: "other" };
11321164

11331165
/**
11341166
* @since 2.0.0
@@ -1146,7 +1178,7 @@ async function unwatch(rid: number): Promise<void> {
11461178
*/
11471179
async function watch(
11481180
paths: string | string[] | URL | URL[],
1149-
cb: (event: DebouncedEvent) => void,
1181+
cb: (event: WatchEvent) => void,
11501182
options?: DebouncedWatchOptions,
11511183
): Promise<UnwatchFn> {
11521184
const opts = {
@@ -1163,7 +1195,7 @@ async function watch(
11631195
}
11641196
}
11651197

1166-
const onEvent = new Channel<DebouncedEvent>();
1198+
const onEvent = new Channel<WatchEvent>();
11671199
onEvent.onmessage = cb;
11681200

11691201
const rid: number = await invoke("plugin:fs|watch", {
@@ -1184,7 +1216,7 @@ async function watch(
11841216
*/
11851217
async function watchImmediate(
11861218
paths: string | string[] | URL | URL[],
1187-
cb: (event: RawEvent) => void,
1219+
cb: (event: WatchEvent) => void,
11881220
options?: WatchOptions,
11891221
): Promise<UnwatchFn> {
11901222
const opts = {
@@ -1201,7 +1233,7 @@ async function watchImmediate(
12011233
}
12021234
}
12031235

1204-
const onEvent = new Channel<RawEvent>();
1236+
const onEvent = new Channel<WatchEvent>();
12051237
onEvent.onmessage = cb;
12061238

12071239
const rid: number = await invoke("plugin:fs|watch", {
@@ -1232,8 +1264,12 @@ export type {
12321264
FileInfo,
12331265
WatchOptions,
12341266
DebouncedWatchOptions,
1235-
DebouncedEvent,
1236-
RawEvent,
1267+
WatchEvent,
1268+
WatchEventKind,
1269+
WatchEventKindAccess,
1270+
WatchEventKindCreate,
1271+
WatchEventKindModify,
1272+
WatchEventKindRemove,
12371273
UnwatchFn,
12381274
};
12391275

plugins/fs/src/watcher.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// SPDX-License-Identifier: MIT
44

55
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
6-
use notify_debouncer_mini::{new_debouncer, DebounceEventResult, Debouncer};
6+
use notify_debouncer_full::{new_debouncer, DebounceEventResult, Debouncer, FileIdMap};
77
use serde::Deserialize;
88
use tauri::{
99
ipc::Channel,
@@ -43,7 +43,7 @@ impl WatcherResource {
4343
impl Resource for WatcherResource {}
4444

4545
enum WatcherKind {
46-
Debouncer(Debouncer<RecommendedWatcher>),
46+
Debouncer(Debouncer<RecommendedWatcher, FileIdMap>),
4747
Watcher(RecommendedWatcher),
4848
}
4949

@@ -60,10 +60,10 @@ fn watch_raw(on_event: Channel, rx: Receiver<notify::Result<Event>>) {
6060

6161
fn watch_debounced(on_event: Channel, rx: Receiver<DebounceEventResult>) {
6262
spawn(move || {
63-
while let Ok(event) = rx.recv() {
64-
if let Ok(event) = event {
63+
while let Ok(Ok(events)) = rx.recv() {
64+
for event in events {
6565
// TODO: Should errors be emitted too?
66-
let _ = on_event.send(&event);
66+
let _ = on_event.send(&event.event);
6767
}
6868
}
6969
});
@@ -97,10 +97,9 @@ pub async fn watch<R: Runtime>(
9797

9898
let kind = if let Some(delay) = options.delay_ms {
9999
let (tx, rx) = channel();
100-
let mut debouncer = new_debouncer(Duration::from_millis(delay), tx)?;
101-
let watcher = debouncer.watcher();
100+
let mut debouncer = new_debouncer(Duration::from_millis(delay), None, tx)?;
102101
for path in &resolved_paths {
103-
watcher.watch(path.as_ref(), mode)?;
102+
debouncer.watcher().watch(path.as_ref(), mode)?;
104103
}
105104
watch_debounced(on_event, rx);
106105
WatcherKind::Debouncer(debouncer)
@@ -130,14 +129,14 @@ pub async fn unwatch<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> CommandR
130129
for path in &watcher.paths {
131130
debouncer.watcher().unwatch(path.as_ref()).map_err(|e| {
132131
format!("failed to unwatch path: {} with error: {e}", path.display())
133-
})?
132+
})?;
134133
}
135134
}
136135
WatcherKind::Watcher(ref mut w) => {
137136
for path in &watcher.paths {
138137
w.unwatch(path.as_ref()).map_err(|e| {
139138
format!("failed to unwatch path: {} with error: {e}", path.display())
140-
})?
139+
})?;
141140
}
142141
}
143142
}

0 commit comments

Comments
 (0)