Files
tauri-plugins-workspace/plugins/fs/src/watcher.rs
T
Amr Bashir 69a1fa099c feat(fs): improved API (#751)
* feat(fs): improved API

* fmt

* fix unix builds

* again

* clippy

* clippy

* fix import in docs examples

* fmt, clippy

* Update linux.rs

* add API for watch

* fix with `watcher` feature flag

* use baseDir for all commands

* do not export close function

* fix build

* organize and address review comments

* fmt

* generated files

* rename FsFile to FileHandle, move APIs and docs

* extend example

* extend `Resource`

* actually extend it

---------

Co-authored-by: FabianLars <fabianlars@fabianlars.de>
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
2023-12-19 22:08:34 -03:00

148 lines
4.1 KiB
Rust

// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
use notify_debouncer_mini::{new_debouncer, DebounceEventResult, Debouncer};
use serde::Deserialize;
use tauri::{
ipc::Channel,
path::{BaseDirectory, SafePathBuf},
AppHandle, Manager, Resource, ResourceId, Runtime,
};
use std::{
path::PathBuf,
sync::{
mpsc::{channel, Receiver},
Mutex,
},
thread::spawn,
time::Duration,
};
use crate::commands::{resolve_path, CommandResult};
struct InnerWatcher {
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 {
Debouncer(Debouncer<RecommendedWatcher>),
Watcher(RecommendedWatcher),
}
fn watch_raw(on_event: Channel, rx: Receiver<notify::Result<Event>>) {
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<DebounceEventResult>) {
spawn(move || {
while let Ok(event) = rx.recv() {
if let Ok(event) = event {
// TODO: Should errors be emitted too?
let _ = on_event.send(&event);
}
}
});
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WatchOptions {
dir: Option<BaseDirectory>,
recursive: bool,
delay_ms: Option<u64>,
}
#[tauri::command]
pub async fn watch<R: Runtime>(
app: AppHandle<R>,
paths: Vec<SafePathBuf>,
options: WatchOptions,
on_event: Channel,
) -> CommandResult<ResourceId> {
let mut resolved_paths = Vec::with_capacity(paths.capacity());
for path in paths {
resolved_paths.push(resolve_path(&app, path, options.dir)?);
}
let mode = if options.recursive {
RecursiveMode::Recursive
} else {
RecursiveMode::NonRecursive
};
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();
for path in &resolved_paths {
watcher.watch(path.as_ref(), mode)?;
}
watch_debounced(on_event, rx);
WatcherKind::Debouncer(debouncer)
} else {
let (tx, rx) = channel();
let mut watcher = RecommendedWatcher::new(tx, Config::default())?;
for path in &resolved_paths {
watcher.watch(path.as_ref(), mode)?;
}
watch_raw(on_event, rx);
WatcherKind::Watcher(watcher)
};
let rid = app
.resources_table()
.add(WatcherResource::new(kind, resolved_paths));
Ok(rid)
}
#[tauri::command]
pub async fn unwatch<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> CommandResult<()> {
let watcher = app.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.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())
})?
}
}
}
Ok(())
})
}