diff --git a/.changes/fs-watch-cleanup.md b/.changes/fs-watch-cleanup.md
new file mode 100644
index 00000000..c46559a7
--- /dev/null
+++ b/.changes/fs-watch-cleanup.md
@@ -0,0 +1,6 @@
+---
+fs: minor
+fs-js: minor
+---
+
+Reduce the overhead of `watch` and `unwatch`
diff --git a/Cargo.lock b/Cargo.lock
index 9956c98e..fa844bdb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6599,7 +6599,6 @@ dependencies = [
"thiserror 2.0.12",
"toml",
"url",
- "uuid",
]
[[package]]
diff --git a/examples/api/src-tauri/capabilities/base.json b/examples/api/src-tauri/capabilities/base.json
index b8f805a3..cefc4d8a 100644
--- a/examples/api/src-tauri/capabilities/base.json
+++ b/examples/api/src-tauri/capabilities/base.json
@@ -69,6 +69,7 @@
"fs:allow-mkdir",
"fs:allow-remove",
"fs:allow-write-text-file",
+ "fs:read-meta",
"fs:scope-download-recursive",
"fs:scope-resource-recursive",
{
diff --git a/examples/api/src/views/FileSystem.svelte b/examples/api/src/views/FileSystem.svelte
index f7222ef8..baf9f607 100644
--- a/examples/api/src/views/FileSystem.svelte
+++ b/examples/api/src/views/FileSystem.svelte
@@ -2,16 +2,18 @@
import * as fs from "@tauri-apps/plugin-fs";
import { convertFileSrc } from "@tauri-apps/api/core";
import { arrayBufferToBase64 } from "../lib/utils";
+ import { onDestroy } from "svelte";
export let onMessage;
export let insecureRenderHtml;
let path = "";
let img;
+ /** @type {fs.FileHandle} */
let file;
let renameTo;
let watchPath = "";
- let watchDebounceDelay = 0;
+ let watchDebounceDelay = "0";
let watchRecursive = false;
let unwatchFn;
let unwatchPath = "";
@@ -118,7 +120,7 @@
.getElementById("file-save")
.addEventListener("click", function () {
fs.writeTextFile(path, fileInput.value, {
- dir: getDir(),
+ baseDir: getDir(),
}).catch(onMessage);
});
});
@@ -170,6 +172,15 @@
unwatchFn = undefined;
unwatchPath = undefined;
}
+
+ onDestroy(() => {
+ if (file) {
+ file.close();
+ }
+ if (unwatchFn) {
+ unwatchFn();
+ }
+ })
diff --git a/plugins/fs/Cargo.toml b/plugins/fs/Cargo.toml
index 2335af72..b64052b8 100644
--- a/plugins/fs/Cargo.toml
+++ b/plugins/fs/Cargo.toml
@@ -35,7 +35,6 @@ tauri = { workspace = true }
thiserror = { workspace = true }
url = { workspace = true }
anyhow = "1"
-uuid = { version = "1", features = ["v4"] }
glob = { workspace = true }
# TODO: Remove `serialization-compat-6` in v3
notify = { version = "8", optional = true, features = [
diff --git a/plugins/fs/README.md b/plugins/fs/README.md
index dea88824..33031177 100644
--- a/plugins/fs/README.md
+++ b/plugins/fs/README.md
@@ -68,9 +68,9 @@ fn main() {
Afterwards all the plugin's APIs are available through the JavaScript guest bindings:
```javascript
-import { metadata } from '@tauri-apps/plugin-fs'
+import { stat } from '@tauri-apps/plugin-fs'
-await metadata('/path/to/file')
+await stat('/path/to/file')
```
## Contributing
diff --git a/plugins/fs/api-iife.js b/plugins/fs/api-iife.js
index e1b22e05..02063a22 100644
--- a/plugins/fs/api-iife.js
+++ b/plugins/fs/api-iife.js
@@ -1 +1 @@
-if("__TAURI__"in window){var __TAURI_PLUGIN_FS__=function(t){"use strict";function e(t,e,n,i){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?i:"a"===n?i.call(t):i?i.value:e.get(t)}function n(t,e,n,i,o){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,n),n}var i,o,r,a,s;"function"==typeof SuppressedError&&SuppressedError;const c="__TAURI_TO_IPC_KEY__";class f{constructor(t){i.set(this,void 0),o.set(this,0),r.set(this,[]),a.set(this,void 0),n(this,i,t||(()=>{})),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((t=>{const s=t.index;if("end"in t)return void(s==e(this,o,"f")?this.cleanupCallback():n(this,a,s));const c=t.message;if(s==e(this,o,"f")){for(e(this,i,"f").call(this,c),n(this,o,e(this,o,"f")+1);e(this,o,"f")in e(this,r,"f");){const t=e(this,r,"f")[e(this,o,"f")];e(this,i,"f").call(this,t),delete e(this,r,"f")[e(this,o,"f")],n(this,o,e(this,o,"f")+1)}e(this,o,"f")===e(this,a,"f")&&this.cleanupCallback()}else e(this,r,"f")[s]=c}))}cleanupCallback(){Reflect.deleteProperty(window,`_${this.id}`)}set onmessage(t){n(this,i,t)}get onmessage(){return e(this,i,"f")}[(i=new WeakMap,o=new WeakMap,r=new WeakMap,a=new WeakMap,c)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[c]()}}async function l(t,e={},n){return window.__TAURI_INTERNALS__.invoke(t,e,n)}class u{get rid(){return e(this,s,"f")}constructor(t){s.set(this,void 0),n(this,s,t)}async close(){return l("plugin:resources|close",{rid:this.rid})}}var p,w;function h(t){return{isFile:t.isFile,isDirectory:t.isDirectory,isSymlink:t.isSymlink,size:t.size,mtime:null!==t.mtime?new Date(t.mtime):null,atime:null!==t.atime?new Date(t.atime):null,birthtime:null!==t.birthtime?new Date(t.birthtime):null,readonly:t.readonly,fileAttributes:t.fileAttributes,dev:t.dev,ino:t.ino,mode:t.mode,nlink:t.nlink,uid:t.uid,gid:t.gid,rdev:t.rdev,blksize:t.blksize,blocks:t.blocks}}s=new WeakMap,t.BaseDirectory=void 0,(p=t.BaseDirectory||(t.BaseDirectory={}))[p.Audio=1]="Audio",p[p.Cache=2]="Cache",p[p.Config=3]="Config",p[p.Data=4]="Data",p[p.LocalData=5]="LocalData",p[p.Document=6]="Document",p[p.Download=7]="Download",p[p.Picture=8]="Picture",p[p.Public=9]="Public",p[p.Video=10]="Video",p[p.Resource=11]="Resource",p[p.Temp=12]="Temp",p[p.AppConfig=13]="AppConfig",p[p.AppData=14]="AppData",p[p.AppLocalData=15]="AppLocalData",p[p.AppCache=16]="AppCache",p[p.AppLog=17]="AppLog",p[p.Desktop=18]="Desktop",p[p.Executable=19]="Executable",p[p.Font=20]="Font",p[p.Home=21]="Home",p[p.Runtime=22]="Runtime",p[p.Template=23]="Template",t.SeekMode=void 0,(w=t.SeekMode||(t.SeekMode={}))[w.Start=0]="Start",w[w.Current=1]="Current",w[w.End=2]="End";class d extends u{async read(t){if(0===t.byteLength)return 0;const e=await l("plugin:fs|read",{rid:this.rid,len:t.byteLength}),n=function(t){const e=new Uint8ClampedArray(t),n=e.byteLength;let i=0;for(let t=0;t
t instanceof URL?t.toString():t)),options:i,onEvent:r});return()=>{L(a)}},t.watchImmediate=async function(t,e,n){const i={recursive:!1,...n,delayMs:null},o=Array.isArray(t)?t:[t];for(const t of o)if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const r=new f;r.onmessage=e;const a=await l("plugin:fs|watch",{paths:o.map((t=>t instanceof URL?t.toString():t)),options:i,onEvent:r});return()=>{L(a)}},t.writeFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");if(e instanceof ReadableStream){const i=await y(t,n),o=e.getReader();try{for(;;){const{done:t,value:e}=await o.read();if(t)break;await i.write(e)}}finally{o.releaseLock(),await i.close()}}else await l("plugin:fs|write_file",e,{headers:{path:encodeURIComponent(t instanceof URL?t.toString():t),options:JSON.stringify(n)}})},t.writeTextFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const i=new TextEncoder;await l("plugin:fs|write_text_file",i.encode(e),{headers:{path:encodeURIComponent(t instanceof URL?t.toString():t),options:JSON.stringify(n)}})},t}({});Object.defineProperty(window.__TAURI__,"fs",{value:__TAURI_PLUGIN_FS__})}
+if("__TAURI__"in window){var __TAURI_PLUGIN_FS__=function(t){"use strict";function e(t,e,n,i){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?i:"a"===n?i.call(t):i?i.value:e.get(t)}function n(t,e,n,i,o){if("function"==typeof e||!e.has(t))throw new TypeError("Cannot write private member to an object whose class did not declare it");return e.set(t,n),n}var i,o,r,a,s;"function"==typeof SuppressedError&&SuppressedError;const c="__TAURI_TO_IPC_KEY__";class f{constructor(t){i.set(this,void 0),o.set(this,0),r.set(this,[]),a.set(this,void 0),n(this,i,t||(()=>{})),this.id=function(t,e=!1){return window.__TAURI_INTERNALS__.transformCallback(t,e)}((t=>{const s=t.index;if("end"in t)return void(s==e(this,o,"f")?this.cleanupCallback():n(this,a,s));const c=t.message;if(s==e(this,o,"f")){for(e(this,i,"f").call(this,c),n(this,o,e(this,o,"f")+1);e(this,o,"f")in e(this,r,"f");){const t=e(this,r,"f")[e(this,o,"f")];e(this,i,"f").call(this,t),delete e(this,r,"f")[e(this,o,"f")],n(this,o,e(this,o,"f")+1)}e(this,o,"f")===e(this,a,"f")&&this.cleanupCallback()}else e(this,r,"f")[s]=c}))}cleanupCallback(){Reflect.deleteProperty(window,`_${this.id}`)}set onmessage(t){n(this,i,t)}get onmessage(){return e(this,i,"f")}[(i=new WeakMap,o=new WeakMap,r=new WeakMap,a=new WeakMap,c)](){return`__CHANNEL__:${this.id}`}toJSON(){return this[c]()}}async function l(t,e={},n){return window.__TAURI_INTERNALS__.invoke(t,e,n)}class u{get rid(){return e(this,s,"f")}constructor(t){s.set(this,void 0),n(this,s,t)}async close(){return l("plugin:resources|close",{rid:this.rid})}}var p,w;function d(t){return{isFile:t.isFile,isDirectory:t.isDirectory,isSymlink:t.isSymlink,size:t.size,mtime:null!==t.mtime?new Date(t.mtime):null,atime:null!==t.atime?new Date(t.atime):null,birthtime:null!==t.birthtime?new Date(t.birthtime):null,readonly:t.readonly,fileAttributes:t.fileAttributes,dev:t.dev,ino:t.ino,mode:t.mode,nlink:t.nlink,uid:t.uid,gid:t.gid,rdev:t.rdev,blksize:t.blksize,blocks:t.blocks}}s=new WeakMap,t.BaseDirectory=void 0,(p=t.BaseDirectory||(t.BaseDirectory={}))[p.Audio=1]="Audio",p[p.Cache=2]="Cache",p[p.Config=3]="Config",p[p.Data=4]="Data",p[p.LocalData=5]="LocalData",p[p.Document=6]="Document",p[p.Download=7]="Download",p[p.Picture=8]="Picture",p[p.Public=9]="Public",p[p.Video=10]="Video",p[p.Resource=11]="Resource",p[p.Temp=12]="Temp",p[p.AppConfig=13]="AppConfig",p[p.AppData=14]="AppData",p[p.AppLocalData=15]="AppLocalData",p[p.AppCache=16]="AppCache",p[p.AppLog=17]="AppLog",p[p.Desktop=18]="Desktop",p[p.Executable=19]="Executable",p[p.Font=20]="Font",p[p.Home=21]="Home",p[p.Runtime=22]="Runtime",p[p.Template=23]="Template",t.SeekMode=void 0,(w=t.SeekMode||(t.SeekMode={}))[w.Start=0]="Start",w[w.Current=1]="Current",w[w.End=2]="End";class h extends u{async read(t){if(0===t.byteLength)return 0;const e=await l("plugin:fs|read",{rid:this.rid,len:t.byteLength}),n=function(t){const e=new Uint8ClampedArray(t),n=e.byteLength;let i=0;for(let t=0;tt instanceof URL?t.toString():t)),options:n,onEvent:o}),a=new L(r);return()=>{a.close()}}return t.FileHandle=h,t.copyFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol||e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|copy_file",{fromPath:t instanceof URL?t.toString():t,toPath:e instanceof URL?e.toString():e,options:n})},t.create=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await l("plugin:fs|create",{path:t instanceof URL?t.toString():t,options:e});return new h(n)},t.exists=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await l("plugin:fs|exists",{path:t instanceof URL?t.toString():t,options:e})},t.lstat=async function(t,e){return d(await l("plugin:fs|lstat",{path:t instanceof URL?t.toString():t,options:e}))},t.mkdir=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|mkdir",{path:t instanceof URL?t.toString():t,options:e})},t.open=y,t.readDir=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await l("plugin:fs|read_dir",{path:t instanceof URL?t.toString():t,options:e})},t.readFile=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await l("plugin:fs|read_file",{path:t instanceof URL?t.toString():t,options:e});return n instanceof ArrayBuffer?new Uint8Array(n):Uint8Array.from(n)},t.readTextFile=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=await l("plugin:fs|read_text_file",{path:t instanceof URL?t.toString():t,options:e}),i=n instanceof ArrayBuffer?n:Uint8Array.from(n);return(new TextDecoder).decode(i)},t.readTextFileLines=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const n=t instanceof URL?t.toString():t;return await Promise.resolve({path:n,rid:null,async next(){null===this.rid&&(this.rid=await l("plugin:fs|read_text_file_lines",{path:n,options:e}));const t=await l("plugin:fs|read_text_file_lines_next",{rid:this.rid}),i=t instanceof ArrayBuffer?new Uint8Array(t):Uint8Array.from(t),o=1===i[i.byteLength-1];if(o)return this.rid=null,{value:null,done:o};return{value:(new TextDecoder).decode(i.slice(0,i.byteLength)),done:o}},[Symbol.asyncIterator](){return this}})},t.remove=async function(t,e){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|remove",{path:t instanceof URL?t.toString():t,options:e})},t.rename=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol||e instanceof URL&&"file:"!==e.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|rename",{oldPath:t instanceof URL?t.toString():t,newPath:e instanceof URL?e.toString():e,options:n})},t.size=async function(t){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");return await l("plugin:fs|size",{path:t instanceof URL?t.toString():t})},t.stat=async function(t,e){return d(await l("plugin:fs|stat",{path:t instanceof URL?t.toString():t,options:e}))},t.truncate=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");await l("plugin:fs|truncate",{path:t instanceof URL?t.toString():t,len:e,options:n})},t.watch=async function(t,e,n){return await R(t,e,{delayMs:2e3,...n})},t.watchImmediate=async function(t,e,n){return await R(t,e,{...n,delayMs:void 0})},t.writeFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");if(e instanceof ReadableStream){const i=await y(t,n),o=e.getReader();try{for(;;){const{done:t,value:e}=await o.read();if(t)break;await i.write(e)}}finally{o.releaseLock(),await i.close()}}else await l("plugin:fs|write_file",e,{headers:{path:encodeURIComponent(t instanceof URL?t.toString():t),options:JSON.stringify(n)}})},t.writeTextFile=async function(t,e,n){if(t instanceof URL&&"file:"!==t.protocol)throw new TypeError("Must be a file URL.");const i=new TextEncoder;await l("plugin:fs|write_text_file",i.encode(e),{headers:{path:encodeURIComponent(t instanceof URL?t.toString():t),options:JSON.stringify(n)}})},t}({});Object.defineProperty(window.__TAURI__,"fs",{value:__TAURI_PLUGIN_FS__})}
diff --git a/plugins/fs/build.rs b/plugins/fs/build.rs
index 9c34586e..47e27003 100644
--- a/plugins/fs/build.rs
+++ b/plugins/fs/build.rs
@@ -101,6 +101,7 @@ const COMMANDS: &[(&str, &[&str])] = &[
("fstat", &[]),
("exists", &[]),
("watch", &[]),
+ // TODO: Remove this in v3
("unwatch", &[]),
("size", &[]),
];
diff --git a/plugins/fs/guest-js/index.ts b/plugins/fs/guest-js/index.ts
index f19cbc80..b3e49e5c 100644
--- a/plugins/fs/guest-js/index.ts
+++ b/plugins/fs/guest-js/index.ts
@@ -1245,31 +1245,19 @@ type WatchEventKindRemove =
| { kind: 'folder' }
| { kind: 'other' }
+// TODO: Remove this in v3, return `Watcher` instead
/**
* @since 2.0.0
*/
type UnwatchFn = () => void
-async function unwatch(rid: number): Promise {
- await invoke('plugin:fs|unwatch', { rid })
-}
+class Watcher extends Resource {}
-/**
- * Watch changes (after a delay) on files or directories.
- *
- * @since 2.0.0
- */
-async function watch(
+async function watchInternal(
paths: string | string[] | URL | URL[],
cb: (event: WatchEvent) => void,
- options?: DebouncedWatchOptions
+ options: DebouncedWatchOptions
): Promise {
- const opts = {
- recursive: false,
- delayMs: 2000,
- ...options
- }
-
const watchPaths = Array.isArray(paths) ? paths : [paths]
for (const path of watchPaths) {
@@ -1283,15 +1271,35 @@ async function watch(
const rid: number = await invoke('plugin:fs|watch', {
paths: watchPaths.map((p) => (p instanceof URL ? p.toString() : p)),
- options: opts,
+ options,
onEvent
})
+ const watcher = new Watcher(rid)
+
return () => {
- void unwatch(rid)
+ void watcher.close()
}
}
+// TODO: Return `Watcher` instead in v3
+/**
+ * Watch changes (after a delay) on files or directories.
+ *
+ * @since 2.0.0
+ */
+async function watch(
+ paths: string | string[] | URL | URL[],
+ cb: (event: WatchEvent) => void,
+ options?: DebouncedWatchOptions
+): Promise {
+ return await watchInternal(paths, cb, {
+ delayMs: 2000,
+ ...options
+ })
+}
+
+// TODO: Return `Watcher` instead in v3
/**
* Watch changes on files or directories.
*
@@ -1302,32 +1310,10 @@ async function watchImmediate(
cb: (event: WatchEvent) => void,
options?: WatchOptions
): Promise {
- const opts = {
- recursive: false,
+ return await watchInternal(paths, cb, {
...options,
- delayMs: null
- }
-
- const watchPaths = Array.isArray(paths) ? paths : [paths]
-
- for (const path of watchPaths) {
- if (path instanceof URL && path.protocol !== 'file:') {
- throw new TypeError('Must be a file URL.')
- }
- }
-
- const onEvent = new Channel()
- onEvent.onmessage = cb
-
- const rid: number = await invoke('plugin:fs|watch', {
- paths: watchPaths.map((p) => (p instanceof URL ? p.toString() : p)),
- options: opts,
- onEvent
+ delayMs: undefined
})
-
- return () => {
- void unwatch(rid)
- }
}
/**
diff --git a/plugins/fs/src/commands.rs b/plugins/fs/src/commands.rs
index 29d1104f..bd1400ea 100644
--- a/plugins/fs/src/commands.rs
+++ b/plugins/fs/src/commands.rs
@@ -150,11 +150,6 @@ pub fn open(
Ok(rid)
}
-#[tauri::command]
-pub fn close(webview: Webview, rid: ResourceId) -> CommandResult<()> {
- webview.resources_table().close(rid).map_err(Into::into)
-}
-
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CopyFileOptions {
diff --git a/plugins/fs/src/lib.rs b/plugins/fs/src/lib.rs
index 731c7040..bdc6b170 100644
--- a/plugins/fs/src/lib.rs
+++ b/plugins/fs/src/lib.rs
@@ -397,7 +397,6 @@ pub fn init() -> TauriPlugin> {
commands::create,
commands::open,
commands::copy_file,
- commands::close,
commands::mkdir,
commands::read_dir,
commands::read,
@@ -420,8 +419,6 @@ pub fn init() -> TauriPlugin> {
commands::size,
#[cfg(feature = "watch")]
watcher::watch,
- #[cfg(feature = "watch")]
- watcher::unwatch
])
.setup(|app, api| {
let scope = Scope {
diff --git a/plugins/fs/src/watcher.rs b/plugins/fs/src/watcher.rs
index 7d851822..89446b88 100644
--- a/plugins/fs/src/watcher.rs
+++ b/plugins/fs/src/watcher.rs
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
-use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
-use notify_debouncer_full::{new_debouncer, DebounceEventResult, Debouncer, RecommendedCache};
+use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
+use notify_debouncer_full::{new_debouncer, DebouncedEvent, Debouncer, RecommendedCache};
use serde::Deserialize;
use tauri::{
ipc::{Channel, CommandScope, GlobalScope},
@@ -11,15 +11,7 @@ use tauri::{
Manager, Resource, ResourceId, Runtime, Webview,
};
-use std::{
- path::PathBuf,
- sync::{
- mpsc::{channel, Receiver},
- Mutex,
- },
- thread::spawn,
- time::Duration,
-};
+use std::time::Duration;
use crate::{
commands::{resolve_path, CommandResult},
@@ -27,79 +19,44 @@ use crate::{
SafeFilePath,
};
-struct InnerWatcher {
- pub kind: WatcherKind,
- paths: Vec,
-}
-
-pub struct WatcherResource(Mutex);
-impl WatcherResource {
- fn new(kind: WatcherKind, paths: Vec) -> Self {
- Self(Mutex::new(InnerWatcher { kind, paths }))
- }
-
- fn with_lock R>(&self, mut f: F) -> R {
- let mut watcher = self.0.lock().unwrap();
- f(&mut watcher)
- }
-}
-
-impl Resource for WatcherResource {}
-
+#[allow(unused)]
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(Ok(events)) = rx.recv() {
- for event in events {
- // TODO: Should errors be emitted too?
- let _ = on_event.send(event.event);
- }
- }
- });
-}
+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 async fn watch(
+pub fn watch(
webview: Webview,
paths: Vec,
options: WatchOptions,
- on_event: Channel,
+ on_event: Channel,
global_scope: GlobalScope,
command_scope: CommandScope,
) -> CommandResult {
- let mut resolved_paths = Vec::with_capacity(paths.capacity());
- for path in paths {
- resolved_paths.push(resolve_path(
- &webview,
- &global_scope,
- &command_scope,
- path,
- options.base_dir,
- )?);
- }
+ let resolved_paths = paths
+ .into_iter()
+ .map(|path| {
+ resolve_path(
+ &webview,
+ &global_scope,
+ &command_scope,
+ path,
+ options.base_dir,
+ )
+ })
+ .collect::>>()?;
let recursive_mode = if options.recursive {
RecursiveMode::Recursive
@@ -107,52 +64,40 @@ pub async fn watch(
RecursiveMode::NonRecursive
};
- let kind = if let Some(delay) = options.delay_ms {
- let (tx, rx) = channel();
- let mut debouncer = new_debouncer(Duration::from_millis(delay), None, tx)?;
+ 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)?;
}
- watch_debounced(on_event, rx);
WatcherKind::Debouncer(debouncer)
} else {
- let (tx, rx) = channel();
- let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
+ 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)?;
}
- watch_raw(on_event, rx);
WatcherKind::Watcher(watcher)
};
- let rid = webview
- .resources_table()
- .add(WatcherResource::new(kind, resolved_paths));
+ let rid = webview.resources_table().add(watcher_kind);
Ok(rid)
}
-
-#[tauri::command]
-pub async fn unwatch(webview: Webview, rid: ResourceId) -> CommandResult<()> {
- let watcher = webview.resources_table().take::(rid)?;
- WatcherResource::with_lock(&watcher, |watcher| {
- match &mut watcher.kind {
- WatcherKind::Debouncer(ref mut debouncer) => {
- for path in &watcher.paths {
- debouncer.unwatch(path).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).map_err(|e| {
- format!("failed to unwatch path: {} with error: {e}", path.display())
- })?;
- }
- }
- }
-
- Ok(())
- })
-}