mirror of
https://github.com/tauri-apps/plugins-workspace.git
synced 2026-07-24 17:20:51 +02:00
copy plugin sources
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "tauri-plugin-fs-watch"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
tauri.workspace = true
|
||||
log.workspace = true
|
||||
thiserror.workspace = true
|
||||
notify = "4.0"
|
||||
+72
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+64
@@ -0,0 +1,64 @@
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { appWindow } from '@tauri-apps/api/window';
|
||||
|
||||
const w = appWindow;
|
||||
async function unwatch(id) {
|
||||
await invoke("plugin:fs-watch|unwatch", { id });
|
||||
}
|
||||
async function watch(paths, options, cb) {
|
||||
const opts = {
|
||||
recursive: false,
|
||||
delayMs: 2000,
|
||||
...options,
|
||||
};
|
||||
let watchPaths;
|
||||
if (typeof paths === "string") {
|
||||
watchPaths = [paths];
|
||||
}
|
||||
else {
|
||||
watchPaths = paths;
|
||||
}
|
||||
const id = window.crypto.getRandomValues(new Uint32Array(1))[0];
|
||||
await invoke("plugin:fs-watch|watch", {
|
||||
id,
|
||||
paths: watchPaths,
|
||||
options: opts,
|
||||
});
|
||||
const unlisten = await w.listen(`watcher://debounced-event/${id}`, (event) => {
|
||||
cb(event.payload);
|
||||
});
|
||||
return () => {
|
||||
void unwatch(id);
|
||||
unlisten();
|
||||
};
|
||||
}
|
||||
async function watchImmediate(paths, options, cb) {
|
||||
const opts = {
|
||||
recursive: false,
|
||||
...options,
|
||||
delayMs: null,
|
||||
};
|
||||
let watchPaths;
|
||||
if (typeof paths === "string") {
|
||||
watchPaths = [paths];
|
||||
}
|
||||
else {
|
||||
watchPaths = paths;
|
||||
}
|
||||
const id = window.crypto.getRandomValues(new Uint32Array(1))[0];
|
||||
await invoke("plugin:fs-watch|watch", {
|
||||
id,
|
||||
paths: watchPaths,
|
||||
options: opts,
|
||||
});
|
||||
const unlisten = await w.listen(`watcher://raw-event/${id}`, (event) => {
|
||||
cb(event.payload);
|
||||
});
|
||||
return () => {
|
||||
void unwatch(id);
|
||||
unlisten();
|
||||
};
|
||||
}
|
||||
|
||||
export { watch, watchImmediate };
|
||||
//# sourceMappingURL=index.mjs.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.mjs","sources":["../index.ts"],"sourcesContent":[null],"names":[],"mappings":";;;AAIA,MAAM,CAAC,GAAkB,SAAS,CAAC;AA2BnC,eAAe,OAAO,CAAC,EAAU,EAAA;IAC/B,MAAM,MAAM,CAAC,yBAAyB,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;AAClD,CAAC;AAEM,eAAe,KAAK,CACzB,KAAwB,EACxB,OAA8B,EAC9B,EAAmC,EAAA;AAEnC,IAAA,MAAM,IAAI,GAAG;AACX,QAAA,SAAS,EAAE,KAAK;AAChB,QAAA,OAAO,EAAE,IAAI;AACb,QAAA,GAAG,OAAO;KACX,CAAC;AACF,IAAA,IAAI,UAAU,CAAC;AACf,IAAA,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AAC7B,QAAA,UAAU,GAAG,CAAC,KAAK,CAAC,CAAC;AACtB,KAAA;AAAM,SAAA;QACL,UAAU,GAAG,KAAK,CAAC;AACpB,KAAA;AAED,IAAA,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEhE,MAAM,MAAM,CAAC,uBAAuB,EAAE;QACpC,EAAE;AACF,QAAA,KAAK,EAAE,UAAU;AACjB,QAAA,OAAO,EAAE,IAAI;AACd,KAAA,CAAC,CAAC;AAEH,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,MAAM,CAC7B,CAA6B,0BAAA,EAAA,EAAE,CAAE,CAAA,EACjC,CAAC,KAAK,KAAI;AACR,QAAA,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AACpB,KAAC,CACF,CAAC;AAEF,IAAA,OAAO,MAAK;AACV,QAAA,KAAK,OAAO,CAAC,EAAE,CAAC,CAAC;AACjB,QAAA,QAAQ,EAAE,CAAC;AACb,KAAC,CAAC;AACJ,CAAC;AAEM,eAAe,cAAc,CAClC,KAAwB,EACxB,OAAqB,EACrB,EAA6B,EAAA;AAE7B,IAAA,MAAM,IAAI,GAAG;AACX,QAAA,SAAS,EAAE,KAAK;AAChB,QAAA,GAAG,OAAO;AACV,QAAA,OAAO,EAAE,IAAI;KACd,CAAC;AACF,IAAA,IAAI,UAAU,CAAC;AACf,IAAA,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;AAC7B,QAAA,UAAU,GAAG,CAAC,KAAK,CAAC,CAAC;AACtB,KAAA;AAAM,SAAA;QACL,UAAU,GAAG,KAAK,CAAC;AACpB,KAAA;AAED,IAAA,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEhE,MAAM,MAAM,CAAC,uBAAuB,EAAE;QACpC,EAAE;AACF,QAAA,KAAK,EAAE,UAAU;AACjB,QAAA,OAAO,EAAE,IAAI;AACd,KAAA,CAAC,CAAC;AAEH,IAAA,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,MAAM,CAC7B,CAAuB,oBAAA,EAAA,EAAE,CAAE,CAAA,EAC3B,CAAC,KAAK,KAAI;AACR,QAAA,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AACpB,KAAC,CACF,CAAC;AAEF,IAAA,OAAO,MAAK;AACV,QAAA,KAAK,OAAO,CAAC,EAAE,CAAC,CAAC;AACjB,QAAA,QAAQ,EAAE,CAAC;AACb,KAAC,CAAC;AACJ;;;;"}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
import { UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { appWindow, WebviewWindow } from "@tauri-apps/api/window";
|
||||
|
||||
const w: WebviewWindow = appWindow;
|
||||
|
||||
export interface WatchOptions {
|
||||
recursive?: boolean;
|
||||
}
|
||||
|
||||
export interface DebouncedWatchOptions extends WatchOptions {
|
||||
delayMs?: number;
|
||||
}
|
||||
|
||||
export interface RawEvent {
|
||||
path: string | null;
|
||||
operation: number;
|
||||
cookie: number | null;
|
||||
}
|
||||
|
||||
export type DebouncedEvent =
|
||||
| { type: "NoticeWrite"; payload: string }
|
||||
| { type: "NoticeRemove"; payload: string }
|
||||
| { type: "Create"; payload: string }
|
||||
| { type: "Write"; payload: string }
|
||||
| { type: "Chmod"; payload: string }
|
||||
| { type: "Remove"; payload: string }
|
||||
| { type: "Rename"; payload: string }
|
||||
| { type: "Rescan"; payload: null }
|
||||
| { type: "Error"; payload: { error: string; path: string | null } };
|
||||
|
||||
async function unwatch(id: number): Promise<void> {
|
||||
await invoke("plugin:fs-watch|unwatch", { id });
|
||||
}
|
||||
|
||||
export async function watch(
|
||||
paths: string | string[],
|
||||
options: DebouncedWatchOptions,
|
||||
cb: (event: DebouncedEvent) => void
|
||||
): Promise<UnlistenFn> {
|
||||
const opts = {
|
||||
recursive: false,
|
||||
delayMs: 2000,
|
||||
...options,
|
||||
};
|
||||
let watchPaths;
|
||||
if (typeof paths === "string") {
|
||||
watchPaths = [paths];
|
||||
} else {
|
||||
watchPaths = paths;
|
||||
}
|
||||
|
||||
const id = window.crypto.getRandomValues(new Uint32Array(1))[0];
|
||||
|
||||
await invoke("plugin:fs-watch|watch", {
|
||||
id,
|
||||
paths: watchPaths,
|
||||
options: opts,
|
||||
});
|
||||
|
||||
const unlisten = await w.listen<DebouncedEvent>(
|
||||
`watcher://debounced-event/${id}`,
|
||||
(event) => {
|
||||
cb(event.payload);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
void unwatch(id);
|
||||
unlisten();
|
||||
};
|
||||
}
|
||||
|
||||
export async function watchImmediate(
|
||||
paths: string | string[],
|
||||
options: WatchOptions,
|
||||
cb: (event: RawEvent) => void
|
||||
): Promise<UnlistenFn> {
|
||||
const opts = {
|
||||
recursive: false,
|
||||
...options,
|
||||
delayMs: null,
|
||||
};
|
||||
let watchPaths;
|
||||
if (typeof paths === "string") {
|
||||
watchPaths = [paths];
|
||||
} else {
|
||||
watchPaths = paths;
|
||||
}
|
||||
|
||||
const id = window.crypto.getRandomValues(new Uint32Array(1))[0];
|
||||
|
||||
await invoke("plugin:fs-watch|watch", {
|
||||
id,
|
||||
paths: watchPaths,
|
||||
options: opts,
|
||||
});
|
||||
|
||||
const unlisten = await w.listen<RawEvent>(
|
||||
`watcher://raw-event/${id}`,
|
||||
(event) => {
|
||||
cb(event.payload);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
void unwatch(id);
|
||||
unlisten();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "tauri-plugin-fs-watch-api",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT or APACHE-2.0",
|
||||
"type": "module",
|
||||
"browser": "dist/index.min.js",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
"import": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"browser": "./dist/index.min.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rollup -c"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"!dist/**/*.map",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"devDependencies": {
|
||||
"tslib": "^2.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.2.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { readFileSync } from "fs";
|
||||
|
||||
import { createConfig } from "../../../shared/rollup.config.mjs";
|
||||
|
||||
export default createConfig({
|
||||
pkg: JSON.parse(
|
||||
readFileSync(new URL("./package.json", import.meta.url), "utf8")
|
||||
),
|
||||
external: [/^@tauri-apps\/api/],
|
||||
});
|
||||
+1
@@ -0,0 +1 @@
|
||||
../../../shared/tsconfig.json
|
||||
@@ -0,0 +1,189 @@
|
||||
use notify::{
|
||||
raw_watcher, watcher, DebouncedEvent, Op, RawEvent, RecommendedWatcher, RecursiveMode,
|
||||
Watcher as _,
|
||||
};
|
||||
use serde::{ser::Serializer, Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
use tauri::{command, plugin::Plugin, AppHandle, Invoke, Manager, Runtime, State, Window};
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
mpsc::{channel, Receiver},
|
||||
Mutex,
|
||||
},
|
||||
thread::spawn,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
type Id = u32;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
Watch(#[from] notify::Error),
|
||||
}
|
||||
|
||||
impl Serialize for Error {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.to_string().as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct WatcherCollection(Mutex<HashMap<Id, (RecommendedWatcher, Vec<PathBuf>)>>);
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
struct RawEventWrapper {
|
||||
path: Option<PathBuf>,
|
||||
operation: u32,
|
||||
cookie: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
#[serde(tag = "type", content = "payload")]
|
||||
enum DebouncedEventWrapper {
|
||||
NoticeWrite(PathBuf),
|
||||
NoticeRemove(PathBuf),
|
||||
Create(PathBuf),
|
||||
Write(PathBuf),
|
||||
Chmod(PathBuf),
|
||||
Remove(PathBuf),
|
||||
Rename(PathBuf, PathBuf),
|
||||
Rescan,
|
||||
Error {
|
||||
error: String,
|
||||
path: Option<PathBuf>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<DebouncedEvent> for DebouncedEventWrapper {
|
||||
fn from(event: DebouncedEvent) -> Self {
|
||||
match event {
|
||||
DebouncedEvent::NoticeWrite(path) => Self::NoticeWrite(path),
|
||||
DebouncedEvent::NoticeRemove(path) => Self::NoticeRemove(path),
|
||||
DebouncedEvent::Create(path) => Self::Create(path),
|
||||
DebouncedEvent::Write(path) => Self::Write(path),
|
||||
DebouncedEvent::Chmod(path) => Self::Chmod(path),
|
||||
DebouncedEvent::Remove(path) => Self::Remove(path),
|
||||
DebouncedEvent::Rename(from, to) => Self::Rename(from, to),
|
||||
DebouncedEvent::Rescan => Self::Rescan,
|
||||
DebouncedEvent::Error(error, path) => Self::Error {
|
||||
error: error.to_string(),
|
||||
path,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn watch_raw<R: Runtime>(window: Window<R>, rx: Receiver<RawEvent>, id: Id) {
|
||||
spawn(move || {
|
||||
let event_name = format!("watcher://raw-event/{}", id);
|
||||
while let Ok(event) = rx.recv() {
|
||||
let _ = window.emit(
|
||||
&event_name,
|
||||
RawEventWrapper {
|
||||
path: event.path,
|
||||
operation: event.op.unwrap_or_else(|_| Op::empty()).bits(),
|
||||
cookie: event.cookie,
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn watch_debounced<R: Runtime>(window: Window<R>, rx: Receiver<DebouncedEvent>, id: Id) {
|
||||
spawn(move || {
|
||||
let event_name = format!("watcher://debounced-event/{}", id);
|
||||
while let Ok(event) = rx.recv() {
|
||||
let _ = window.emit(&event_name, DebouncedEventWrapper::from(event));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct WatchOptions {
|
||||
delay_ms: Option<u64>,
|
||||
recursive: bool,
|
||||
}
|
||||
|
||||
#[command]
|
||||
async fn watch<R: Runtime>(
|
||||
window: Window<R>,
|
||||
watchers: State<'_, WatcherCollection>,
|
||||
id: Id,
|
||||
paths: Vec<PathBuf>,
|
||||
options: WatchOptions,
|
||||
) -> Result<()> {
|
||||
let mode = if options.recursive {
|
||||
RecursiveMode::Recursive
|
||||
} else {
|
||||
RecursiveMode::NonRecursive
|
||||
};
|
||||
|
||||
let watcher = if let Some(delay) = options.delay_ms {
|
||||
let (tx, rx) = channel();
|
||||
let mut watcher = watcher(tx, Duration::from_millis(delay))?;
|
||||
for path in &paths {
|
||||
watcher.watch(path, mode)?;
|
||||
}
|
||||
watch_debounced(window, rx, id);
|
||||
watcher
|
||||
} else {
|
||||
let (tx, rx) = channel();
|
||||
let mut watcher = raw_watcher(tx)?;
|
||||
for path in &paths {
|
||||
watcher.watch(path, mode)?;
|
||||
}
|
||||
watch_raw(window, rx, id);
|
||||
watcher
|
||||
};
|
||||
|
||||
watchers.0.lock().unwrap().insert(id, (watcher, paths));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
async fn unwatch(watchers: State<'_, WatcherCollection>, id: Id) -> Result<()> {
|
||||
if let Some((mut watcher, paths)) = watchers.0.lock().unwrap().remove(&id) {
|
||||
for path in paths {
|
||||
watcher.unwatch(path)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tauri plugin.
|
||||
pub struct Watcher<R: Runtime> {
|
||||
invoke_handler: Box<dyn Fn(Invoke<R>) + Send + Sync>,
|
||||
}
|
||||
|
||||
impl<R: Runtime> Default for Watcher<R> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
invoke_handler: Box::new(tauri::generate_handler![watch, unwatch]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Runtime> Plugin<R> for Watcher<R> {
|
||||
fn name(&self) -> &'static str {
|
||||
"fs-watch"
|
||||
}
|
||||
|
||||
fn initialize(&mut self, app: &AppHandle<R>, _config: JsonValue) -> tauri::plugin::Result<()> {
|
||||
app.manage(WatcherCollection::default());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extend_api(&mut self, message: Invoke<R>) {
|
||||
(self.invoke_handler)(message)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user