refactor(fs): reduce overhead of `watch`

pull/2613/head
Tony 3 months ago
parent 2445e103f2
commit cd8691831b
No known key found for this signature in database
GPG Key ID: 34BDD3EA27824956

@ -0,0 +1,6 @@
---
fs: minor
fs-js: minor
---
Reduce the overhead of `watch` and `unwatch`

@ -2,16 +2,18 @@
import * as fs from "@tauri-apps/plugin-fs"; import * as fs from "@tauri-apps/plugin-fs";
import { convertFileSrc } from "@tauri-apps/api/core"; import { convertFileSrc } from "@tauri-apps/api/core";
import { arrayBufferToBase64 } from "../lib/utils"; import { arrayBufferToBase64 } from "../lib/utils";
import { onDestroy } from "svelte";
export let onMessage; export let onMessage;
export let insecureRenderHtml; export let insecureRenderHtml;
let path = ""; let path = "";
let img; let img;
/** @type {fs.FileHandle} */
let file; let file;
let renameTo; let renameTo;
let watchPath = ""; let watchPath = "";
let watchDebounceDelay = 0; let watchDebounceDelay = "0";
let watchRecursive = false; let watchRecursive = false;
let unwatchFn; let unwatchFn;
let unwatchPath = ""; let unwatchPath = "";
@ -118,7 +120,7 @@
.getElementById("file-save") .getElementById("file-save")
.addEventListener("click", function () { .addEventListener("click", function () {
fs.writeTextFile(path, fileInput.value, { fs.writeTextFile(path, fileInput.value, {
dir: getDir(), baseDir: getDir(),
}).catch(onMessage); }).catch(onMessage);
}); });
}); });
@ -170,6 +172,15 @@
unwatchFn = undefined; unwatchFn = undefined;
unwatchPath = undefined; unwatchPath = undefined;
} }
onDestroy(() => {
if (file) {
file.close();
}
if (unwatchFn) {
unwatchFn();
}
})
</script> </script>
<div class="flex flex-col"> <div class="flex flex-col">

File diff suppressed because one or more lines are too long

@ -101,6 +101,7 @@ const COMMANDS: &[(&str, &[&str])] = &[
("fstat", &[]), ("fstat", &[]),
("exists", &[]), ("exists", &[]),
("watch", &[]), ("watch", &[]),
// TODO: Remove this in v3
("unwatch", &[]), ("unwatch", &[]),
("size", &[]), ("size", &[]),
]; ];

@ -1245,15 +1245,23 @@ type WatchEventKindRemove =
| { kind: 'folder' } | { kind: 'folder' }
| { kind: 'other' } | { kind: 'other' }
// TODO: Remove this in v3, return `Watcher` instead
/** /**
* @since 2.0.0 * @since 2.0.0
*/ */
type UnwatchFn = () => void type UnwatchFn = () => void
async function unwatch(rid: number): Promise<void> { class Watcher extends Resource {
await invoke('plugin:fs|unwatch', { rid }) constructor(rid: number) {
super(rid)
} }
unwatch = () => {
void this.close()
}
}
// TODO: Return `Watcher` instead in v3
/** /**
* Watch changes (after a delay) on files or directories. * Watch changes (after a delay) on files or directories.
* *
@ -1287,11 +1295,12 @@ async function watch(
onEvent onEvent
}) })
return () => { const watcher = new Watcher(rid)
void unwatch(rid)
} return watcher.unwatch
} }
// TODO: Return `Watcher` instead in v3
/** /**
* Watch changes on files or directories. * Watch changes on files or directories.
* *
@ -1325,9 +1334,9 @@ async function watchImmediate(
onEvent onEvent
}) })
return () => { const watcher = new Watcher(rid)
void unwatch(rid)
} return watcher.unwatch
} }
/** /**

@ -420,8 +420,6 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<config::Config>> {
commands::size, commands::size,
#[cfg(feature = "watch")] #[cfg(feature = "watch")]
watcher::watch, watcher::watch,
#[cfg(feature = "watch")]
watcher::unwatch
]) ])
.setup(|app, api| { .setup(|app, api| {
let scope = Scope { let scope = Scope {

@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher}; use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
use notify_debouncer_full::{new_debouncer, DebounceEventResult, Debouncer, RecommendedCache}; use notify_debouncer_full::{new_debouncer, DebouncedEvent, Debouncer, RecommendedCache};
use serde::Deserialize; use serde::Deserialize;
use tauri::{ use tauri::{
ipc::{Channel, CommandScope, GlobalScope}, ipc::{Channel, CommandScope, GlobalScope},
@ -11,15 +11,7 @@ use tauri::{
Manager, Resource, ResourceId, Runtime, Webview, Manager, Resource, ResourceId, Runtime, Webview,
}; };
use std::{ use std::time::Duration;
path::PathBuf,
sync::{
mpsc::{channel, Receiver},
Mutex,
},
thread::spawn,
time::Duration,
};
use crate::{ use crate::{
commands::{resolve_path, CommandResult}, commands::{resolve_path, CommandResult},
@ -27,51 +19,13 @@ use crate::{
SafeFilePath, SafeFilePath,
}; };
struct InnerWatcher { #[allow(unused)]
pub kind: WatcherKind,
paths: Vec<PathBuf>,
}
pub struct WatcherResource(Mutex<InnerWatcher>);
impl WatcherResource {
fn new(kind: WatcherKind, paths: Vec<PathBuf>) -> Self {
Self(Mutex::new(InnerWatcher { kind, paths }))
}
fn with_lock<R, F: FnMut(&mut InnerWatcher) -> R>(&self, mut f: F) -> R {
let mut watcher = self.0.lock().unwrap();
f(&mut watcher)
}
}
impl Resource for WatcherResource {}
enum WatcherKind { enum WatcherKind {
Debouncer(Debouncer<RecommendedWatcher, RecommendedCache>), Debouncer(Debouncer<RecommendedWatcher, RecommendedCache>),
Watcher(RecommendedWatcher), Watcher(RecommendedWatcher),
} }
fn watch_raw(on_event: Channel<Event>, rx: Receiver<notify::Result<Event>>) { impl Resource for WatcherKind {}
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<Event>, rx: Receiver<DebounceEventResult>) {
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);
}
}
});
}
#[derive(Clone, Deserialize)] #[derive(Clone, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -86,7 +40,7 @@ pub async fn watch<R: Runtime>(
webview: Webview<R>, webview: Webview<R>,
paths: Vec<SafeFilePath>, paths: Vec<SafeFilePath>,
options: WatchOptions, options: WatchOptions,
on_event: Channel<Event>, on_event: Channel<notify::Event>,
global_scope: GlobalScope<Entry>, global_scope: GlobalScope<Entry>,
command_scope: CommandScope<Entry>, command_scope: CommandScope<Entry>,
) -> CommandResult<ResourceId> { ) -> CommandResult<ResourceId> {
@ -107,52 +61,40 @@ pub async fn watch<R: Runtime>(
RecursiveMode::NonRecursive RecursiveMode::NonRecursive
}; };
let kind = if let Some(delay) = options.delay_ms { let watcher_kind = if let Some(delay) = options.delay_ms {
let (tx, rx) = channel(); let mut debouncer = new_debouncer(
let mut debouncer = new_debouncer(Duration::from_millis(delay), None, tx)?; Duration::from_millis(delay),
None,
move |events: Result<Vec<DebouncedEvent>, Vec<notify::Error>>| {
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 { for path in &resolved_paths {
debouncer.watch(path, recursive_mode)?; debouncer.watch(path, recursive_mode)?;
} }
watch_debounced(on_event, rx);
WatcherKind::Debouncer(debouncer) WatcherKind::Debouncer(debouncer)
} else { } else {
let (tx, rx) = channel(); let mut watcher = RecommendedWatcher::new(
let mut watcher = RecommendedWatcher::new(tx, Config::default())?; 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 { for path in &resolved_paths {
watcher.watch(path, recursive_mode)?; watcher.watch(path, recursive_mode)?;
} }
watch_raw(on_event, rx);
WatcherKind::Watcher(watcher) WatcherKind::Watcher(watcher)
}; };
let rid = webview let rid = webview.resources_table().add(watcher_kind);
.resources_table()
.add(WatcherResource::new(kind, resolved_paths));
Ok(rid) Ok(rid)
} }
#[tauri::command]
pub async fn unwatch<R: Runtime>(webview: Webview<R>, rid: ResourceId) -> CommandResult<()> {
let watcher = webview.resources_table().take::<WatcherResource>(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(())
})
}

Loading…
Cancel
Save