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>
This commit is contained in:
Amr Bashir
2023-12-20 03:08:34 +02:00
committed by GitHub
parent 2cf8faa3e1
commit 69a1fa099c
16 changed files with 2175 additions and 985 deletions
+4 -4
View File
@@ -9,9 +9,9 @@ rust-version = { workspace = true }
links = "tauri-plugin-deep-link"
[package.metadata.docs.rs]
rustc-args = [ "--cfg", "docsrs" ]
rustdoc-args = [ "--cfg", "docsrs" ]
targets = [ "x86_64-linux-android" ]
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
targets = ["x86_64-linux-android"]
[build-dependencies]
serde = { workspace = true }
@@ -24,4 +24,4 @@ serde_json = { workspace = true }
tauri = { workspace = true }
log = { workspace = true }
thiserror = { workspace = true }
url = "2"
url = { workspace = true }
+2
View File
@@ -13,8 +13,10 @@ rustdoc-args = [ "--cfg", "docsrs" ]
[dependencies]
serde = { workspace = true }
serde_repr = "0.1"
tauri = { workspace = true }
thiserror = { workspace = true }
url = { workspace = true }
anyhow = "1"
uuid = { version = "1", features = [ "v4" ] }
glob = "0.3"
+1075 -448
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+700 -294
View File
File diff suppressed because it is too large Load Diff
+19 -10
View File
@@ -49,17 +49,29 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
PluginBuilder::<R, Option<Config>>::new("fs")
.js_init_script(include_str!("api-iife.js").to_string())
.invoke_handler(tauri::generate_handler![
commands::create,
commands::open,
commands::copy_file,
commands::close,
commands::mkdir,
commands::read_dir,
commands::read,
commands::read_file,
commands::read_text_file,
commands::read_text_file_lines,
commands::read_text_file_lines_next,
commands::remove,
commands::rename,
commands::seek,
commands::stat,
commands::lstat,
commands::fstat,
commands::truncate,
commands::ftruncate,
commands::write,
commands::write_file,
commands::read_dir,
commands::copy_file,
commands::create_dir,
commands::remove_dir,
commands::remove_file,
commands::rename_file,
commands::write_text_file,
commands::exists,
commands::metadata,
#[cfg(feature = "watch")]
watcher::watch,
#[cfg(feature = "watch")]
@@ -75,9 +87,6 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
.unwrap_or(&default_scope),
)?);
#[cfg(feature = "watch")]
app.manage(watcher::WatcherCollection::default());
Ok(())
})
.on_event(|app, event| {
+64 -34
View File
@@ -5,12 +5,13 @@
use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
use notify_debouncer_mini::{new_debouncer, DebounceEventResult, Debouncer};
use serde::Deserialize;
use tauri::{command, ipc::Channel, State};
use crate::Result;
use tauri::{
ipc::Channel,
path::{BaseDirectory, SafePathBuf},
AppHandle, Manager, Resource, ResourceId, Runtime,
};
use std::{
collections::HashMap,
path::PathBuf,
sync::{
mpsc::{channel, Receiver},
@@ -20,10 +21,26 @@ use std::{
time::Duration,
};
type Id = u32;
use crate::commands::{resolve_path, CommandResult};
#[derive(Default)]
pub struct WatcherCollection(Mutex<HashMap<Id, (WatcherKind, Vec<PathBuf>)>>);
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>),
@@ -55,63 +72,76 @@ fn watch_debounced(on_event: Channel, rx: Receiver<DebounceEventResult>) {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WatchOptions {
delay_ms: Option<u64>,
dir: Option<BaseDirectory>,
recursive: bool,
delay_ms: Option<u64>,
}
#[command]
pub async fn watch(
watchers: State<'_, WatcherCollection>,
id: Id,
paths: Vec<PathBuf>,
#[tauri::command]
pub async fn watch<R: Runtime>(
app: AppHandle<R>,
paths: Vec<SafePathBuf>,
options: WatchOptions,
on_event: Channel,
) -> Result<()> {
) -> 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 watcher = if let Some(delay) = options.delay_ms {
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 &paths {
watcher.watch(path, mode)?;
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 &paths {
watcher.watch(path, mode)?;
for path in &resolved_paths {
watcher.watch(path.as_ref(), mode)?;
}
watch_raw(on_event, rx);
WatcherKind::Watcher(watcher)
};
watchers.0.lock().unwrap().insert(id, (watcher, paths));
let rid = app
.resources_table()
.add(WatcherResource::new(kind, resolved_paths));
Ok(())
Ok(rid)
}
#[command]
pub async fn unwatch(watchers: State<'_, WatcherCollection>, id: Id) -> Result<()> {
if let Some((watcher, paths)) = watchers.0.lock().unwrap().remove(&id) {
match watcher {
WatcherKind::Debouncer(mut debouncer) => {
for path in paths {
debouncer.watcher().unwatch(&path)?
#[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(mut watcher) => {
for path in paths {
watcher.unwatch(&path)?
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(())
}
Ok(())
})
}
+1 -1
View File
@@ -20,7 +20,7 @@ tauri-plugin-fs = { path = "../fs", version = "2.0.0-alpha.5" }
glob = "0.3"
http = "0.2"
reqwest = { version = "0.11", default-features = false }
url = "2.4"
url = { workspace = true }
data-url = "0.3"
[features]
@@ -119,7 +119,7 @@ unsafe extern "system" fn single_instance_window_proc<R: Runtime>(
let data = CStr::from_ptr((*cds_ptr).lpData as _).to_string_lossy();
let mut s = data.split('|');
let cwd = s.next().unwrap();
let args = s.into_iter().map(|s| s.to_string()).collect();
let args = s.map(|s| s.to_string()).collect();
callback(app_handle, args, cwd.to_string());
}
1
+1 -1
View File
@@ -18,7 +18,7 @@ serde_json = { workspace = true }
thiserror = { workspace = true }
tokio = "1"
reqwest = { version = "0.11", default-features = false, features = [ "json", "stream" ] }
url = "2"
url = { workspace = true }
http = "0.2"
dirs-next = "2"
minisign-verify = "0.2"