mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-11 20:08:37 +00:00
feat(electron): use affine native (#2329)
This commit is contained in:
@@ -17,10 +17,11 @@ napi = { version = "2", default-features = false, features = [
|
||||
] }
|
||||
napi-derive = "2"
|
||||
notify = { version = "5", features = ["serde"] }
|
||||
once_cell = "1"
|
||||
parking_lot = "0.12"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
tokio = "1"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
uuid = { version = "1", default-features = false, features = [
|
||||
"serde",
|
||||
"v4",
|
||||
|
||||
3
packages/native/fs-watcher.d.ts
vendored
Normal file
3
packages/native/fs-watcher.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import type { FsWatcher } from './index';
|
||||
|
||||
export function createFSWatcher(): typeof FsWatcher;
|
||||
6
packages/native/fs-watcher.js
Normal file
6
packages/native/fs-watcher.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports.createFSWatcher = function createFSWatcher() {
|
||||
// require it in the function level so that it won't break the `generate-main-exposed-meta.mjs`
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { FsWatcher } = require('./index');
|
||||
return FsWatcher;
|
||||
};
|
||||
11
packages/native/index.d.ts
vendored
11
packages/native/index.d.ts
vendored
@@ -22,21 +22,20 @@ export const enum WatcherKind {
|
||||
NullWatcher = 'NullWatcher',
|
||||
Unknown = 'Unknown',
|
||||
}
|
||||
export function watch(
|
||||
p: string,
|
||||
options?: WatchOptions | undefined | null
|
||||
): FSWatcher;
|
||||
export function moveFile(src: string, dst: string): Promise<void>;
|
||||
export class Subscription {
|
||||
toString(): string;
|
||||
unsubscribe(): void;
|
||||
}
|
||||
export type FSWatcher = FsWatcher;
|
||||
export class FsWatcher {
|
||||
get kind(): WatcherKind;
|
||||
static watch(p: string, options?: WatchOptions | undefined | null): FsWatcher;
|
||||
static kind(): WatcherKind;
|
||||
toString(): string;
|
||||
subscribe(
|
||||
callback: (event: import('./event').NotifyEvent) => void,
|
||||
errorCallback?: (err: Error) => void
|
||||
): Subscription;
|
||||
close(): void;
|
||||
static unwatch(p: string): void;
|
||||
static close(): void;
|
||||
}
|
||||
|
||||
@@ -263,9 +263,9 @@ if (!nativeBinding) {
|
||||
throw new Error(`Failed to load native binding`);
|
||||
}
|
||||
|
||||
const { WatcherKind, Subscription, watch, FsWatcher } = nativeBinding;
|
||||
const { WatcherKind, Subscription, FsWatcher, moveFile } = nativeBinding;
|
||||
|
||||
module.exports.WatcherKind = WatcherKind;
|
||||
module.exports.Subscription = Subscription;
|
||||
module.exports.watch = watch;
|
||||
module.exports.FsWatcher = FsWatcher;
|
||||
module.exports.moveFile = moveFile;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{collections::HashMap, path::Path, sync::Arc};
|
||||
use std::{collections::BTreeMap, path::Path, sync::Arc};
|
||||
|
||||
use napi::{
|
||||
bindgen_prelude::{FromNapiValue, ToNapiValue},
|
||||
@@ -6,8 +6,31 @@ use napi::{
|
||||
};
|
||||
use napi_derive::napi;
|
||||
use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
static GLOBAL_WATCHER: Lazy<napi::Result<GlobalWatcher>> = Lazy::new(|| {
|
||||
let event_emitter = Arc::new(Mutex::new(EventEmitter {
|
||||
listeners: Default::default(),
|
||||
error_callbacks: Default::default(),
|
||||
}));
|
||||
let event_emitter_in_handler = event_emitter.clone();
|
||||
let watcher: RecommendedWatcher =
|
||||
notify::recommended_watcher(move |res: notify::Result<Event>| {
|
||||
event_emitter_in_handler.lock().on(res);
|
||||
})
|
||||
.map_err(anyhow::Error::from)?;
|
||||
Ok(GlobalWatcher {
|
||||
inner: Mutex::new(watcher),
|
||||
event_emitter,
|
||||
})
|
||||
});
|
||||
|
||||
struct GlobalWatcher {
|
||||
inner: Mutex<RecommendedWatcher>,
|
||||
event_emitter: Arc<Mutex<EventEmitter>>,
|
||||
}
|
||||
|
||||
#[napi(object)]
|
||||
#[derive(Default)]
|
||||
pub struct WatchOptions {
|
||||
@@ -50,7 +73,6 @@ impl From<notify::WatcherKind> for WatcherKind {
|
||||
pub struct Subscription {
|
||||
id: uuid::Uuid,
|
||||
error_uuid: Option<uuid::Uuid>,
|
||||
event_emitter: Arc<Mutex<EventEmitter>>,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
@@ -62,61 +84,52 @@ impl Subscription {
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn unsubscribe(&mut self) {
|
||||
let mut event_emitter = self.event_emitter.lock();
|
||||
pub fn unsubscribe(&mut self) -> napi::Result<()> {
|
||||
let mut event_emitter = GLOBAL_WATCHER
|
||||
.as_ref()
|
||||
.map_err(|err| err.clone())?
|
||||
.event_emitter
|
||||
.lock();
|
||||
event_emitter.listeners.remove(&self.id);
|
||||
if let Some(error_uuid) = &self.error_uuid {
|
||||
event_emitter.error_callbacks.remove(error_uuid);
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn watch(p: String, options: Option<WatchOptions>) -> Result<FSWatcher, anyhow::Error> {
|
||||
let event_emitter = Arc::new(Mutex::new(EventEmitter {
|
||||
listeners: Default::default(),
|
||||
error_callbacks: Default::default(),
|
||||
}));
|
||||
let event_emitter_in_handler = event_emitter.clone();
|
||||
let mut watcher: RecommendedWatcher =
|
||||
notify::recommended_watcher(move |res: notify::Result<Event>| {
|
||||
event_emitter_in_handler.lock().on(res);
|
||||
})
|
||||
.map_err(anyhow::Error::from)?;
|
||||
|
||||
let options = options.unwrap_or_default();
|
||||
watcher
|
||||
.watch(
|
||||
Path::new(&p),
|
||||
if options.recursive == Some(false) {
|
||||
RecursiveMode::NonRecursive
|
||||
} else {
|
||||
RecursiveMode::Recursive
|
||||
},
|
||||
)
|
||||
.map_err(anyhow::Error::from)?;
|
||||
Ok(FSWatcher {
|
||||
inner: watcher,
|
||||
event_emitter,
|
||||
})
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub struct FSWatcher {
|
||||
inner: RecommendedWatcher,
|
||||
event_emitter: Arc<Mutex<EventEmitter>>,
|
||||
path: String,
|
||||
recursive: RecursiveMode,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl FSWatcher {
|
||||
#[napi(getter)]
|
||||
pub fn kind(&self) -> WatcherKind {
|
||||
#[napi(factory)]
|
||||
pub fn watch(p: String, options: Option<WatchOptions>) -> Self {
|
||||
let options = options.unwrap_or_default();
|
||||
FSWatcher {
|
||||
path: p,
|
||||
recursive: if options.recursive == Some(false) {
|
||||
RecursiveMode::NonRecursive
|
||||
} else {
|
||||
RecursiveMode::Recursive
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn kind() -> WatcherKind {
|
||||
RecommendedWatcher::kind().into()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn to_string(&self) -> napi::Result<String> {
|
||||
Ok(format!("{:?}", self.inner))
|
||||
Ok(format!(
|
||||
"{:?}",
|
||||
GLOBAL_WATCHER.as_ref().map_err(|err| err.clone())?.inner
|
||||
))
|
||||
}
|
||||
|
||||
#[napi]
|
||||
@@ -125,10 +138,23 @@ impl FSWatcher {
|
||||
#[napi(ts_arg_type = "(event: import('./event').NotifyEvent) => void")]
|
||||
callback: ThreadsafeFunction<serde_json::Value, ErrorStrategy::Fatal>,
|
||||
#[napi(ts_arg_type = "(err: Error) => void")] error_callback: Option<ThreadsafeFunction<()>>,
|
||||
) -> Subscription {
|
||||
) -> napi::Result<Subscription> {
|
||||
GLOBAL_WATCHER
|
||||
.as_ref()
|
||||
.map_err(|err| err.clone())?
|
||||
.inner
|
||||
.lock()
|
||||
.watch(Path::new(&self.path), self.recursive)
|
||||
.map_err(anyhow::Error::from)?;
|
||||
let uuid = uuid::Uuid::new_v4();
|
||||
let mut event_emitter = self.event_emitter.lock();
|
||||
event_emitter.listeners.insert(uuid, callback);
|
||||
let mut event_emitter = GLOBAL_WATCHER
|
||||
.as_ref()
|
||||
.map_err(|err| err.clone())?
|
||||
.event_emitter
|
||||
.lock();
|
||||
event_emitter
|
||||
.listeners
|
||||
.insert(uuid, (self.path.clone(), callback));
|
||||
let mut error_uuid = None;
|
||||
if let Some(error_callback) = error_callback {
|
||||
let uuid = uuid::Uuid::new_v4();
|
||||
@@ -136,32 +162,51 @@ impl FSWatcher {
|
||||
error_uuid = Some(uuid);
|
||||
}
|
||||
drop(event_emitter);
|
||||
Subscription {
|
||||
Ok(Subscription {
|
||||
id: uuid,
|
||||
error_uuid,
|
||||
event_emitter: self.event_emitter.clone(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn close(&mut self) -> napi::Result<()> {
|
||||
// drop the previous watcher
|
||||
self.inner = notify::recommended_watcher(|_| {}).map_err(anyhow::Error::from)?;
|
||||
self.event_emitter.lock().stop();
|
||||
pub fn unwatch(p: String) -> napi::Result<()> {
|
||||
let mut watcher = GLOBAL_WATCHER
|
||||
.as_ref()
|
||||
.map_err(|err| err.clone())?
|
||||
.inner
|
||||
.lock();
|
||||
watcher
|
||||
.unwatch(Path::new(&p))
|
||||
.map_err(anyhow::Error::from)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn close() -> napi::Result<()> {
|
||||
let global_watcher = GLOBAL_WATCHER.as_ref().map_err(|err| err.clone())?;
|
||||
global_watcher.event_emitter.lock().stop();
|
||||
let mut inner = global_watcher.inner.lock();
|
||||
*inner = notify::recommended_watcher(|_| {}).map_err(anyhow::Error::from)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct EventEmitter {
|
||||
listeners: HashMap<uuid::Uuid, ThreadsafeFunction<serde_json::Value, ErrorStrategy::Fatal>>,
|
||||
error_callbacks: HashMap<uuid::Uuid, ThreadsafeFunction<()>>,
|
||||
listeners: BTreeMap<
|
||||
uuid::Uuid,
|
||||
(
|
||||
String,
|
||||
ThreadsafeFunction<serde_json::Value, ErrorStrategy::Fatal>,
|
||||
),
|
||||
>,
|
||||
error_callbacks: BTreeMap<uuid::Uuid, ThreadsafeFunction<()>>,
|
||||
}
|
||||
|
||||
impl EventEmitter {
|
||||
fn on(&self, event: notify::Result<Event>) {
|
||||
match event {
|
||||
Ok(e) => match serde_json::value::to_value(e) {
|
||||
Ok(e) => match serde_json::value::to_value(&e) {
|
||||
Err(err) => {
|
||||
let err: napi::Error = anyhow::Error::from(err).into();
|
||||
for on_error in self.error_callbacks.values() {
|
||||
@@ -169,8 +214,10 @@ impl EventEmitter {
|
||||
}
|
||||
}
|
||||
Ok(v) => {
|
||||
for on_event in self.listeners.values() {
|
||||
on_event.call(v.clone(), ThreadsafeFunctionCallMode::NonBlocking);
|
||||
for (path, on_event) in self.listeners.values() {
|
||||
if e.paths.iter().any(|p| p.to_str() == Some(path)) {
|
||||
on_event.call(v.clone(), ThreadsafeFunctionCallMode::NonBlocking);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -188,3 +235,9 @@ impl EventEmitter {
|
||||
self.error_callbacks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub async fn move_file(src: String, dst: String) -> napi::Result<()> {
|
||||
tokio::fs::rename(src, dst).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"outDir": "lib"
|
||||
"noEmit": false,
|
||||
"outDir": "lib",
|
||||
"composite": true
|
||||
},
|
||||
"include": ["index.d.ts", "__tests__/**/*.mts"],
|
||||
"ts-node": {
|
||||
|
||||
Reference in New Issue
Block a user