mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-06-06 13:53:54 +02:00
feat: update to tauri beta, add permissions (#862)
Co-authored-by: Lucas Nogueira <lucas@tauri.app> Co-authored-by: Lucas Nogueira <lucas@crabnebula.dev>
This commit is contained in:
+176
-17
@@ -6,7 +6,9 @@
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use tauri::{
|
||||
ipc::{CommandScope, GlobalScope},
|
||||
path::{BaseDirectory, SafePathBuf},
|
||||
utils::config::FsScope,
|
||||
AppHandle, Manager, Resource, ResourceId, Runtime,
|
||||
};
|
||||
|
||||
@@ -18,7 +20,7 @@ use std::{
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use crate::{Error, FsExt};
|
||||
use crate::{scope::Entry, Error, FsExt};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum CommandError {
|
||||
@@ -71,10 +73,18 @@ pub struct BaseOptions {
|
||||
#[tauri::command]
|
||||
pub fn create<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
global_scope: GlobalScope<'_, Entry>,
|
||||
command_scope: CommandScope<'_, Entry>,
|
||||
path: SafePathBuf,
|
||||
options: Option<BaseOptions>,
|
||||
) -> CommandResult<ResourceId> {
|
||||
let resolved_path = resolve_path(&app, path, options.and_then(|o| o.base_dir))?;
|
||||
let resolved_path = resolve_path(
|
||||
&app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
path,
|
||||
options.and_then(|o| o.base_dir),
|
||||
)?;
|
||||
let file = File::create(&resolved_path).map_err(|e| {
|
||||
format!(
|
||||
"failed to create file at path: {} with error: {e}",
|
||||
@@ -113,10 +123,18 @@ fn default_true() -> bool {
|
||||
#[tauri::command]
|
||||
pub fn open<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
global_scope: GlobalScope<'_, Entry>,
|
||||
command_scope: CommandScope<'_, Entry>,
|
||||
path: SafePathBuf,
|
||||
options: Option<OpenOptions>,
|
||||
) -> CommandResult<ResourceId> {
|
||||
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base.base_dir))?;
|
||||
let resolved_path = resolve_path(
|
||||
&app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
path,
|
||||
options.as_ref().and_then(|o| o.base.base_dir),
|
||||
)?;
|
||||
|
||||
let mut opts = std::fs::OpenOptions::new();
|
||||
// default to read-only
|
||||
@@ -166,17 +184,23 @@ pub struct CopyFileOptions {
|
||||
#[tauri::command]
|
||||
pub fn copy_file<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
global_scope: GlobalScope<'_, Entry>,
|
||||
command_scope: CommandScope<'_, Entry>,
|
||||
from_path: SafePathBuf,
|
||||
to_path: SafePathBuf,
|
||||
options: Option<CopyFileOptions>,
|
||||
) -> CommandResult<()> {
|
||||
let resolved_from_path = resolve_path(
|
||||
&app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
from_path,
|
||||
options.as_ref().and_then(|o| o.from_path_base_dir),
|
||||
)?;
|
||||
let resolved_to_path = resolve_path(
|
||||
&app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
to_path,
|
||||
options.as_ref().and_then(|o| o.to_path_base_dir),
|
||||
)?;
|
||||
@@ -202,10 +226,18 @@ pub struct MkdirOptions {
|
||||
#[tauri::command]
|
||||
pub fn mkdir<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
global_scope: GlobalScope<'_, Entry>,
|
||||
command_scope: CommandScope<'_, Entry>,
|
||||
path: SafePathBuf,
|
||||
options: Option<MkdirOptions>,
|
||||
) -> CommandResult<()> {
|
||||
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base.base_dir))?;
|
||||
let resolved_path = resolve_path(
|
||||
&app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
path,
|
||||
options.as_ref().and_then(|o| o.base.base_dir),
|
||||
)?;
|
||||
|
||||
let mut builder = std::fs::DirBuilder::new();
|
||||
builder.recursive(options.as_ref().and_then(|o| o.recursive).unwrap_or(false));
|
||||
@@ -261,10 +293,18 @@ fn read_dir_inner<P: AsRef<Path>>(path: P) -> crate::Result<Vec<DirEntry>> {
|
||||
#[tauri::command]
|
||||
pub fn read_dir<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
global_scope: GlobalScope<'_, Entry>,
|
||||
command_scope: CommandScope<'_, Entry>,
|
||||
path: SafePathBuf,
|
||||
options: Option<BaseOptions>,
|
||||
) -> CommandResult<Vec<DirEntry>> {
|
||||
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base_dir))?;
|
||||
let resolved_path = resolve_path(
|
||||
&app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
path,
|
||||
options.as_ref().and_then(|o| o.base_dir),
|
||||
)?;
|
||||
|
||||
read_dir_inner(&resolved_path)
|
||||
.map_err(|e| {
|
||||
@@ -292,10 +332,18 @@ pub fn read<R: Runtime>(
|
||||
#[tauri::command]
|
||||
pub fn read_file<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
global_scope: GlobalScope<'_, Entry>,
|
||||
command_scope: CommandScope<'_, Entry>,
|
||||
path: SafePathBuf,
|
||||
options: Option<BaseOptions>,
|
||||
) -> CommandResult<Vec<u8>> {
|
||||
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base_dir))?;
|
||||
let resolved_path = resolve_path(
|
||||
&app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
path,
|
||||
options.as_ref().and_then(|o| o.base_dir),
|
||||
)?;
|
||||
std::fs::read(&resolved_path)
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
@@ -309,10 +357,18 @@ pub fn read_file<R: Runtime>(
|
||||
#[tauri::command]
|
||||
pub fn read_text_file<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
global_scope: GlobalScope<'_, Entry>,
|
||||
command_scope: CommandScope<'_, Entry>,
|
||||
path: SafePathBuf,
|
||||
options: Option<BaseOptions>,
|
||||
) -> CommandResult<String> {
|
||||
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base_dir))?;
|
||||
let resolved_path = resolve_path(
|
||||
&app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
path,
|
||||
options.as_ref().and_then(|o| o.base_dir),
|
||||
)?;
|
||||
std::fs::read_to_string(&resolved_path)
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
@@ -326,12 +382,20 @@ pub fn read_text_file<R: Runtime>(
|
||||
#[tauri::command]
|
||||
pub fn read_text_file_lines<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
global_scope: GlobalScope<'_, Entry>,
|
||||
command_scope: CommandScope<'_, Entry>,
|
||||
path: SafePathBuf,
|
||||
options: Option<BaseOptions>,
|
||||
) -> CommandResult<ResourceId> {
|
||||
use std::io::BufRead;
|
||||
|
||||
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base_dir))?;
|
||||
let resolved_path = resolve_path(
|
||||
&app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
path,
|
||||
options.as_ref().and_then(|o| o.base_dir),
|
||||
)?;
|
||||
|
||||
let file = File::open(&resolved_path).map_err(|e| {
|
||||
format!(
|
||||
@@ -374,10 +438,18 @@ pub struct RemoveOptions {
|
||||
#[tauri::command]
|
||||
pub fn remove<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
global_scope: GlobalScope<'_, Entry>,
|
||||
command_scope: CommandScope<'_, Entry>,
|
||||
path: SafePathBuf,
|
||||
options: Option<RemoveOptions>,
|
||||
) -> CommandResult<()> {
|
||||
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base.base_dir))?;
|
||||
let resolved_path = resolve_path(
|
||||
&app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
path,
|
||||
options.as_ref().and_then(|o| o.base.base_dir),
|
||||
)?;
|
||||
|
||||
let metadata = std::fs::symlink_metadata(&resolved_path).map_err(|e| {
|
||||
format!(
|
||||
@@ -434,17 +506,23 @@ pub struct RenameOptions {
|
||||
#[tauri::command]
|
||||
pub fn rename<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
global_scope: GlobalScope<'_, Entry>,
|
||||
command_scope: CommandScope<'_, Entry>,
|
||||
old_path: SafePathBuf,
|
||||
new_path: SafePathBuf,
|
||||
options: Option<RenameOptions>,
|
||||
) -> CommandResult<()> {
|
||||
let resolved_old_path = resolve_path(
|
||||
&app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
old_path,
|
||||
options.as_ref().and_then(|o| o.old_path_base_dir),
|
||||
)?;
|
||||
let resolved_new_path = resolve_path(
|
||||
&app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
new_path,
|
||||
options.as_ref().and_then(|o| o.new_path_base_dir),
|
||||
)?;
|
||||
@@ -490,10 +568,18 @@ pub fn seek<R: Runtime>(
|
||||
#[tauri::command]
|
||||
pub fn stat<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
global_scope: GlobalScope<'_, Entry>,
|
||||
command_scope: CommandScope<'_, Entry>,
|
||||
path: SafePathBuf,
|
||||
options: Option<BaseOptions>,
|
||||
) -> CommandResult<FileInfo> {
|
||||
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base_dir))?;
|
||||
let resolved_path = resolve_path(
|
||||
&app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
path,
|
||||
options.as_ref().and_then(|o| o.base_dir),
|
||||
)?;
|
||||
let metadata = std::fs::metadata(&resolved_path).map_err(|e| {
|
||||
format!(
|
||||
"failed to get metadata of path: {} with error: {e}",
|
||||
@@ -506,10 +592,18 @@ pub fn stat<R: Runtime>(
|
||||
#[tauri::command]
|
||||
pub fn lstat<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
global_scope: GlobalScope<'_, Entry>,
|
||||
command_scope: CommandScope<'_, Entry>,
|
||||
path: SafePathBuf,
|
||||
options: Option<BaseOptions>,
|
||||
) -> CommandResult<FileInfo> {
|
||||
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base_dir))?;
|
||||
let resolved_path = resolve_path(
|
||||
&app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
path,
|
||||
options.as_ref().and_then(|o| o.base_dir),
|
||||
)?;
|
||||
let metadata = std::fs::symlink_metadata(&resolved_path).map_err(|e| {
|
||||
format!(
|
||||
"failed to get metadata of path: {} with error: {e}",
|
||||
@@ -530,11 +624,19 @@ pub fn fstat<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> CommandResult<Fi
|
||||
#[tauri::command]
|
||||
pub fn truncate<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
global_scope: GlobalScope<'_, Entry>,
|
||||
command_scope: CommandScope<'_, Entry>,
|
||||
path: SafePathBuf,
|
||||
len: Option<u64>,
|
||||
options: Option<BaseOptions>,
|
||||
) -> CommandResult<()> {
|
||||
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base_dir))?;
|
||||
let resolved_path = resolve_path(
|
||||
&app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
path,
|
||||
options.as_ref().and_then(|o| o.base_dir),
|
||||
)?;
|
||||
let f = std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.open(&resolved_path)
|
||||
@@ -599,11 +701,19 @@ fn default_create_value() -> bool {
|
||||
|
||||
fn write_file_inner<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
global_scope: &GlobalScope<'_, Entry>,
|
||||
command_scope: &CommandScope<'_, Entry>,
|
||||
path: SafePathBuf,
|
||||
data: &[u8],
|
||||
options: Option<WriteFileOptions>,
|
||||
) -> CommandResult<()> {
|
||||
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base.base_dir))?;
|
||||
let resolved_path = resolve_path(
|
||||
&app,
|
||||
global_scope,
|
||||
command_scope,
|
||||
path,
|
||||
options.as_ref().and_then(|o| o.base.base_dir),
|
||||
)?;
|
||||
|
||||
let mut opts = std::fs::OpenOptions::new();
|
||||
// defaults
|
||||
@@ -644,35 +754,56 @@ fn write_file_inner<R: Runtime>(
|
||||
#[tauri::command]
|
||||
pub fn write_file<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
global_scope: GlobalScope<'_, Entry>,
|
||||
command_scope: CommandScope<'_, Entry>,
|
||||
path: SafePathBuf,
|
||||
data: Vec<u8>,
|
||||
options: Option<WriteFileOptions>,
|
||||
) -> CommandResult<()> {
|
||||
write_file_inner(app, path, &data, options)
|
||||
write_file_inner(app, &global_scope, &command_scope, path, &data, options)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn write_text_file<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
global_scope: GlobalScope<'_, Entry>,
|
||||
command_scope: CommandScope<'_, Entry>,
|
||||
path: SafePathBuf,
|
||||
data: String,
|
||||
options: Option<WriteFileOptions>,
|
||||
) -> CommandResult<()> {
|
||||
write_file_inner(app, path, data.as_bytes(), options)
|
||||
write_file_inner(
|
||||
app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
path,
|
||||
data.as_bytes(),
|
||||
options,
|
||||
)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn exists<R: Runtime>(
|
||||
app: AppHandle<R>,
|
||||
global_scope: GlobalScope<'_, Entry>,
|
||||
command_scope: CommandScope<'_, Entry>,
|
||||
path: SafePathBuf,
|
||||
options: Option<BaseOptions>,
|
||||
) -> CommandResult<bool> {
|
||||
let resolved_path = resolve_path(&app, path, options.as_ref().and_then(|o| o.base_dir))?;
|
||||
let resolved_path = resolve_path(
|
||||
&app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
path,
|
||||
options.as_ref().and_then(|o| o.base_dir),
|
||||
)?;
|
||||
Ok(resolved_path.exists())
|
||||
}
|
||||
|
||||
pub fn resolve_path<R: Runtime>(
|
||||
app: &AppHandle<R>,
|
||||
global_scope: &GlobalScope<'_, Entry>,
|
||||
command_scope: &CommandScope<'_, Entry>,
|
||||
path: SafePathBuf,
|
||||
base_dir: Option<BaseDirectory>,
|
||||
) -> CommandResult<PathBuf> {
|
||||
@@ -684,7 +815,35 @@ pub fn resolve_path<R: Runtime>(
|
||||
} else {
|
||||
path.as_ref().to_path_buf()
|
||||
};
|
||||
if app.fs_scope().is_allowed(&path) {
|
||||
|
||||
let scope = tauri::scope::fs::Scope::new(
|
||||
app,
|
||||
&FsScope::Scope {
|
||||
allow: app
|
||||
.fs_scope()
|
||||
.allowed
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.into_iter()
|
||||
.chain(global_scope.allows().iter().map(|e| e.path.clone()))
|
||||
.chain(command_scope.allows().iter().map(|e| e.path.clone()))
|
||||
.collect(),
|
||||
deny: app
|
||||
.fs_scope()
|
||||
.denied
|
||||
.lock()
|
||||
.unwrap()
|
||||
.clone()
|
||||
.into_iter()
|
||||
.chain(global_scope.denies().iter().map(|e| e.path.clone()))
|
||||
.chain(command_scope.denies().iter().map(|e| e.path.clone()))
|
||||
.collect(),
|
||||
require_literal_leading_dot: None,
|
||||
},
|
||||
)?;
|
||||
|
||||
if scope.is_allowed(&path) {
|
||||
Ok(path)
|
||||
} else {
|
||||
Err(CommandError::Plugin(Error::PathForbidden(path)))
|
||||
|
||||
@@ -3,9 +3,16 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use serde::Deserialize;
|
||||
use tauri::utils::config::FsScope;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
pub scope: FsScope,
|
||||
/// Whether or not paths that contain components that start with a `.`
|
||||
/// will require that `.` appears literally in the pattern; `*`, `?`, `**`,
|
||||
/// or `[...]` will not match. This is useful because such files are
|
||||
/// conventionally considered hidden on Unix systems and it might be
|
||||
/// desirable to skip them when listing files.
|
||||
///
|
||||
/// Defaults to `true` on Unix systems and `false` on Windows
|
||||
// dotfiles are not supposed to be exposed by default on unix
|
||||
pub require_literal_leading_dot: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@ use serde::{Serialize, Serializer};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
Json(#[from] serde_json::Error),
|
||||
#[error(transparent)]
|
||||
Tauri(#[from] tauri::Error),
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("forbidden path: {0}")]
|
||||
|
||||
+38
-18
@@ -11,21 +11,25 @@
|
||||
html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
|
||||
)]
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::Deserialize;
|
||||
use tauri::{
|
||||
ipc::ScopeObject,
|
||||
plugin::{Builder as PluginBuilder, TauriPlugin},
|
||||
scope::fs::Scope,
|
||||
utils::config::FsScope,
|
||||
FileDropEvent, Manager, RunEvent, Runtime, WindowEvent,
|
||||
utils::acl::Value,
|
||||
AppHandle, FileDropEvent, Manager, RunEvent, Runtime, WindowEvent,
|
||||
};
|
||||
|
||||
mod commands;
|
||||
mod config;
|
||||
mod error;
|
||||
mod scope;
|
||||
#[cfg(feature = "watch")]
|
||||
mod watcher;
|
||||
|
||||
pub use config::Config;
|
||||
pub use error::Error;
|
||||
pub use scope::{Event as ScopeEvent, Scope};
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
@@ -44,8 +48,27 @@ impl<R: Runtime, T: Manager<R>> FsExt<R> for T {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
|
||||
PluginBuilder::<R, Option<Config>>::new("fs")
|
||||
impl ScopeObject for scope::Entry {
|
||||
type Error = Error;
|
||||
fn deserialize<R: Runtime>(
|
||||
app: &AppHandle<R>,
|
||||
raw: Value,
|
||||
) -> std::result::Result<Self, Self::Error> {
|
||||
#[derive(Deserialize)]
|
||||
struct EntryRaw {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
let entry = serde_json::from_value::<EntryRaw>(raw.into())?;
|
||||
|
||||
Ok(Self {
|
||||
path: app.path().parse(entry.path)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R, Option<config::Config>> {
|
||||
PluginBuilder::<R, Option<config::Config>>::new("fs")
|
||||
.js_init_script(include_str!("api-iife.js").to_string())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
commands::create,
|
||||
@@ -76,16 +99,13 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
|
||||
#[cfg(feature = "watch")]
|
||||
watcher::unwatch
|
||||
])
|
||||
.setup(|app: &tauri::AppHandle<R>, api| {
|
||||
let default_scope = FsScope::default();
|
||||
app.manage(Scope::new(
|
||||
app,
|
||||
api.config()
|
||||
.as_ref()
|
||||
.map(|c| &c.scope)
|
||||
.unwrap_or(&default_scope),
|
||||
)?);
|
||||
|
||||
.setup(|app, api| {
|
||||
let mut scope = Scope::default();
|
||||
scope.require_literal_leading_dot = api
|
||||
.config()
|
||||
.as_ref()
|
||||
.and_then(|c| c.require_literal_leading_dot);
|
||||
app.manage(scope);
|
||||
Ok(())
|
||||
})
|
||||
.on_event(|app, event| {
|
||||
@@ -98,9 +118,9 @@ pub fn init<R: Runtime>() -> TauriPlugin<R, Option<Config>> {
|
||||
let scope = app.fs_scope();
|
||||
for path in paths {
|
||||
if path.is_file() {
|
||||
let _ = scope.allow_file(path);
|
||||
scope.allow_file(path);
|
||||
} else {
|
||||
let _ = scope.allow_directory(path, false);
|
||||
scope.allow_directory(path, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, schemars::JsonSchema)]
|
||||
pub struct Entry {
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
pub type EventId = u32;
|
||||
type EventListener = Box<dyn Fn(&Event) + Send>;
|
||||
|
||||
/// Scope change event.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Event {
|
||||
/// A path has been allowed.
|
||||
PathAllowed(PathBuf),
|
||||
/// A path has been forbidden.
|
||||
PathForbidden(PathBuf),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Scope {
|
||||
pub(crate) allowed: Mutex<Vec<PathBuf>>,
|
||||
pub(crate) denied: Mutex<Vec<PathBuf>>,
|
||||
event_listeners: Mutex<HashMap<EventId, EventListener>>,
|
||||
next_event_id: AtomicU32,
|
||||
pub(crate) require_literal_leading_dot: Option<bool>,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
/// Extend the allowed patterns with the given directory.
|
||||
///
|
||||
/// After this function has been called, the frontend will be able to use the Tauri API to read
|
||||
/// the directory and all of its files. If `recursive` is `true`, subdirectories will be accessible too.
|
||||
pub fn allow_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) {
|
||||
let path = path.as_ref();
|
||||
|
||||
let mut allowed = self.allowed.lock().unwrap();
|
||||
allowed.push(path.to_path_buf());
|
||||
allowed.push(path.join(if recursive { "**" } else { "*" }));
|
||||
|
||||
self.emit(Event::PathAllowed(path.to_path_buf()));
|
||||
}
|
||||
|
||||
/// Extend the allowed patterns with the given file path.
|
||||
///
|
||||
/// After this function has been called, the frontend will be able to use the Tauri API to read the contents of this file.
|
||||
pub fn allow_file<P: AsRef<Path>>(&self, path: P) {
|
||||
let path = path.as_ref();
|
||||
|
||||
self.allowed.lock().unwrap().push(path.to_path_buf());
|
||||
|
||||
self.emit(Event::PathAllowed(path.to_path_buf()));
|
||||
}
|
||||
|
||||
/// Set the given directory path to be forbidden by this scope.
|
||||
///
|
||||
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
|
||||
pub fn forbid_directory<P: AsRef<Path>>(&self, path: P, recursive: bool) {
|
||||
let path = path.as_ref();
|
||||
|
||||
let mut denied = self.denied.lock().unwrap();
|
||||
denied.push(path.to_path_buf());
|
||||
denied.push(path.join(if recursive { "**" } else { "*" }));
|
||||
|
||||
self.emit(Event::PathForbidden(path.to_path_buf()));
|
||||
}
|
||||
|
||||
/// Set the given file path to be forbidden by this scope.
|
||||
///
|
||||
/// **Note:** this takes precedence over allowed paths, so its access gets denied **always**.
|
||||
pub fn forbid_file<P: AsRef<Path>>(&self, path: P) {
|
||||
let path = path.as_ref();
|
||||
|
||||
self.denied.lock().unwrap().push(path.to_path_buf());
|
||||
|
||||
self.emit(Event::PathForbidden(path.to_path_buf()));
|
||||
}
|
||||
|
||||
/// List of allowed paths.
|
||||
pub fn allowed(&self) -> Vec<PathBuf> {
|
||||
self.allowed.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
/// List of forbidden paths.
|
||||
pub fn forbidden(&self) -> Vec<PathBuf> {
|
||||
self.denied.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
fn next_event_id(&self) -> u32 {
|
||||
self.next_event_id.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn emit(&self, event: Event) {
|
||||
let listeners = self.event_listeners.lock().unwrap();
|
||||
let handlers = listeners.values();
|
||||
for listener in handlers {
|
||||
listener(&event);
|
||||
}
|
||||
}
|
||||
|
||||
/// Listen to an event on this scope.
|
||||
pub fn listen<F: Fn(&Event) + Send + 'static>(&self, f: F) -> EventId {
|
||||
let id = self.next_event_id();
|
||||
self.event_listeners.lock().unwrap().insert(id, Box::new(f));
|
||||
id
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
use notify_debouncer_full::{new_debouncer, DebounceEventResult, Debouncer, FileIdMap};
|
||||
use serde::Deserialize;
|
||||
use tauri::{
|
||||
ipc::Channel,
|
||||
ipc::{Channel, CommandScope, GlobalScope},
|
||||
path::{BaseDirectory, SafePathBuf},
|
||||
AppHandle, Manager, Resource, ResourceId, Runtime,
|
||||
};
|
||||
@@ -21,7 +21,10 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::commands::{resolve_path, CommandResult};
|
||||
use crate::{
|
||||
commands::{resolve_path, CommandResult},
|
||||
scope::Entry,
|
||||
};
|
||||
|
||||
struct InnerWatcher {
|
||||
pub kind: WatcherKind,
|
||||
@@ -83,10 +86,18 @@ pub async fn watch<R: Runtime>(
|
||||
paths: Vec<SafePathBuf>,
|
||||
options: WatchOptions,
|
||||
on_event: Channel,
|
||||
global_scope: GlobalScope<'_, Entry>,
|
||||
command_scope: CommandScope<'_, Entry>,
|
||||
) -> CommandResult<ResourceId> {
|
||||
let mut resolved_paths = Vec::with_capacity(paths.capacity());
|
||||
for path in paths {
|
||||
resolved_paths.push(resolve_path(&app, path, options.dir)?);
|
||||
resolved_paths.push(resolve_path(
|
||||
&app,
|
||||
&global_scope,
|
||||
&command_scope,
|
||||
path,
|
||||
options.dir,
|
||||
)?);
|
||||
}
|
||||
|
||||
let mode = if options.recursive {
|
||||
|
||||
Reference in New Issue
Block a user