perf: re-implement file watcher in an async way

This commit is contained in:
sxyazi 2024-04-05 01:54:37 +08:00
parent d04b549f4e
commit 1a079bf05c
No known key found for this signature in database
4 changed files with 89 additions and 71 deletions

View file

@ -22,4 +22,9 @@ pub mod which;
pub use clipboard::*; pub use clipboard::*;
pub use step::*; pub use step::*;
pub fn init() { CLIPBOARD.with(Default::default); } pub fn init() {
CLIPBOARD.with(Default::default);
manager::WATCHED.with(Default::default);
manager::LINKED.with(Default::default);
}

View file

@ -3,7 +3,7 @@ use std::borrow::Cow;
use yazi_proxy::ManagerProxy; use yazi_proxy::ManagerProxy;
use yazi_shared::{event::Cmd, fs::FilesOp, render}; use yazi_shared::{event::Cmd, fs::FilesOp, render};
use crate::{folder::Folder, manager::Manager, tab::Tab, tasks::Tasks}; use crate::{folder::Folder, manager::{Manager, LINKED}, tab::Tab, tasks::Tasks};
pub struct Opt { pub struct Opt {
op: FilesOp, op: FilesOp,
@ -22,7 +22,7 @@ impl Manager {
}; };
let mut ops = vec![opt.op]; let mut ops = vec![opt.op];
for u in self.watcher.linked.read().from_dir(ops[0].url()) { for u in LINKED.read().from_dir(ops[0].url()) {
ops.push(ops[0].chroot(u)); ops.push(ops[0].chroot(u));
} }

View file

@ -3,7 +3,7 @@ use std::collections::HashMap;
use yazi_dds::ValueSendable; use yazi_dds::ValueSendable;
use yazi_shared::{event::Cmd, fs::Url, render}; use yazi_shared::{event::Cmd, fs::Url, render};
use crate::{manager::Manager, tasks::Tasks}; use crate::{manager::{Manager, LINKED}, tasks::Tasks};
pub struct Opt { pub struct Opt {
data: ValueSendable, data: ValueSendable,
@ -23,7 +23,7 @@ impl Manager {
return; return;
}; };
let linked = self.watcher.linked.read(); let linked = LINKED.read();
let updates = opt let updates = opt
.data .data
.into_table_string() .into_table_string()

View file

@ -1,68 +1,67 @@
use std::{collections::{HashMap, HashSet}, sync::Arc, time::{Duration, SystemTime}}; use std::{collections::{HashMap, HashSet}, time::{Duration, SystemTime}};
use anyhow::Result; use anyhow::Result;
use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _Watcher}; use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
use parking_lot::RwLock; use parking_lot::RwLock;
use tokio::{fs, pin, sync::mpsc::{self, UnboundedReceiver}}; use tokio::{fs, pin, sync::{mpsc::{self, UnboundedReceiver}, watch}};
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
use tracing::error; use tracing::error;
use yazi_plugin::isolate; use yazi_plugin::isolate;
use yazi_proxy::WATCHER; use yazi_proxy::WATCHER;
use yazi_shared::fs::{File, FilesOp, Url}; use yazi_shared::{fs::{File, FilesOp, Url}, RoCell};
use super::Linked; use super::Linked;
use crate::folder::{Files, Folder}; use crate::folder::{Files, Folder};
pub(crate) static WATCHED: RoCell<RwLock<HashSet<Url>>> = RoCell::new();
pub static LINKED: RoCell<RwLock<Linked>> = RoCell::new();
pub struct Watcher { pub struct Watcher {
watcher: RecommendedWatcher, tx: watch::Sender<(HashSet<Url>, HashSet<Url>)>,
watched: Arc<RwLock<HashSet<Url>>>,
pub linked: Arc<RwLock<Linked>>,
} }
impl Watcher { impl Watcher {
pub(super) fn serve() -> Self { pub(super) fn serve() -> Self {
let (tx, rx) = mpsc::unbounded_channel(); let (in_tx, in_rx) = watch::channel(Default::default());
let watcher = RecommendedWatcher::new( let (out_tx, out_rx) = mpsc::unbounded_channel();
{
let tx = tx.clone();
move |res: Result<notify::Event, notify::Error>| {
let Ok(event) = res else { return };
match event.kind { let watcher = RecommendedWatcher::new(
EventKind::Create(_) => {} move |res: Result<notify::Event, notify::Error>| {
EventKind::Modify(kind) => match kind { let Ok(event) = res else { return };
ModifyKind::Data(_) => {}
ModifyKind::Metadata(md) => match md { match event.kind {
MetadataKind::WriteTime => {} EventKind::Create(_) => {}
MetadataKind::Permissions => {} EventKind::Modify(kind) => match kind {
MetadataKind::Ownership => {} ModifyKind::Data(_) => {}
_ => return, ModifyKind::Metadata(md) => match md {
}, MetadataKind::WriteTime => {}
ModifyKind::Name(_) => {} MetadataKind::Permissions => {}
MetadataKind::Ownership => {}
_ => return, _ => return,
}, },
EventKind::Remove(_) => {} ModifyKind::Name(_) => {}
_ => return, _ => return,
} },
EventKind::Remove(_) => {}
_ => return,
}
for path in event.paths { for path in event.paths {
tx.send(Url::from(path)).ok(); out_tx.send(Url::from(path)).ok();
}
} }
}, },
Default::default(), Default::default(),
); );
let instance = tokio::spawn(Self::on_in(in_rx, watcher.unwrap()));
Self { watcher: watcher.unwrap(), watched: Default::default(), linked: Default::default() }; tokio::spawn(Self::on_out(out_rx));
tokio::spawn(Self::on_changed(rx)); Self { tx: in_tx }
instance
} }
pub(super) fn watch(&mut self, mut new: HashSet<&Url>) { pub(super) fn watch(&mut self, mut new: HashSet<&Url>) {
new.retain(|&u| u.is_regular()); new.retain(|&u| u.is_regular());
let (to_unwatch, to_watch): (HashSet<_>, HashSet<_>) = { let (to_unwatch, to_watch): (HashSet<_>, HashSet<_>) = {
let guard = self.watched.read(); let guard = WATCHED.read();
let old: HashSet<_> = guard.iter().collect(); let old: HashSet<_> = guard.iter().collect();
( (
old.difference(&new).map(|&x| x.clone()).collect(), old.difference(&new).map(|&x| x.clone()).collect(),
@ -70,17 +69,7 @@ impl Watcher {
) )
}; };
for u in to_unwatch { self.tx.send((to_unwatch, to_watch)).ok();
self.watcher.unwatch(&u).ok();
}
for u in to_watch {
if self.watcher.watch(&u, RecursiveMode::NonRecursive).is_err() {
new.remove(&u);
}
}
*self.watched.write() = new.into_iter().cloned().collect();
self.sync_linked();
} }
pub(super) fn trigger_dirs(&self, folders: &[&Folder]) { pub(super) fn trigger_dirs(&self, folders: &[&Folder]) {
@ -114,33 +103,33 @@ impl Watcher {
}); });
} }
fn sync_linked(&self) { async fn on_in(
let mut new = self.watched.read().clone(); mut rx: watch::Receiver<(HashSet<Url>, HashSet<Url>)>,
self.linked.write().retain(|k, _| new.remove(k)); mut watcher: RecommendedWatcher,
) {
let watched = self.watched.clone(); loop {
let linked = self.linked.clone(); {
macro_rules! go { let (ref to_unwatch, ref to_watch) = *rx.borrow_and_update();
($todo:expr) => { for u in to_unwatch {
for from in $todo { if watcher.unwatch(u).is_ok() {
match fs::canonicalize(&from).await { WATCHED.write().remove(u);
Ok(to) if to != *from && watched.read().contains(&from) => {
linked.write().insert(from, Url::from(to));
}
_ => {}
} }
} }
}; for u in to_watch {
} if watcher.watch(u, RecursiveMode::NonRecursive).is_ok() {
WATCHED.write().insert(u.clone());
}
}
}
tokio::spawn(async move { Self::sync_linked();
let old: Vec<_> = linked.read().keys().cloned().collect(); if rx.changed().await.is_err() {
go!(new); break;
go!(old); }
}); }
} }
async fn on_changed(rx: UnboundedReceiver<Url>) { async fn on_out(rx: UnboundedReceiver<Url>) {
// TODO: revert this once a new notification is implemented // TODO: revert this once a new notification is implemented
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(50)); let rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(50));
pin!(rx); pin!(rx);
@ -171,4 +160,28 @@ impl Watcher {
} }
} }
} }
fn sync_linked() {
let mut new = WATCHED.read().clone();
LINKED.write().retain(|k, _| new.remove(k));
macro_rules! go {
($todo:expr) => {
for from in $todo {
match fs::canonicalize(&from).await {
Ok(to) if to != *from && WATCHED.read().contains(&from) => {
LINKED.write().insert(from, Url::from(to));
}
_ => {}
}
}
};
}
tokio::spawn(async move {
let old: Vec<_> = LINKED.read().keys().cloned().collect();
go!(new);
go!(old);
});
}
} }