mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: dual watcher for local files (#3713)
This commit is contained in:
parent
0bb6379a2a
commit
9eb4d03a44
15 changed files with 250 additions and 115 deletions
46
Cargo.lock
generated
46
Cargo.lock
generated
|
|
@ -576,9 +576,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.43"
|
version = "0.4.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
|
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
|
@ -861,7 +861,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rustix 1.1.3",
|
"rustix 1.1.4",
|
||||||
"serde",
|
"serde",
|
||||||
"signal-hook 0.3.18",
|
"signal-hook 0.3.18",
|
||||||
"signal-hook-mio",
|
"signal-hook-mio",
|
||||||
|
|
@ -2163,9 +2163,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.88"
|
version = "0.3.89"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c7e709f3e3d22866f9c25b3aff01af289b18422cc8b4262fb19103ee80fe513d"
|
checksum = "f4eacb0641a310445a4c513f2a5e23e19952e269c6a38887254d5f837a305506"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
|
@ -2344,9 +2344,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.11.0"
|
version = "0.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "litrs"
|
name = "litrs"
|
||||||
|
|
@ -3866,14 +3866,14 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "1.1.3"
|
version = "1.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
|
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.11.0",
|
"linux-raw-sys 0.12.1",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -5029,9 +5029,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.111"
|
version = "0.2.112"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec1adf1535672f5b7824f817792b1afd731d7e843d2d04ec8f27e8cb51edd8ac"
|
checksum = "05d7d0fce354c88b7982aec4400b3e7fcf723c32737cef571bd165f7613557ee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|
@ -5042,9 +5042,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-futures"
|
name = "wasm-bindgen-futures"
|
||||||
version = "0.4.61"
|
version = "0.4.62"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fe88540d1c934c4ec8e6db0afa536876c5441289d7f9f9123d4f065ac1250a6b"
|
checksum = "ee85afca410ac4abba5b584b12e77ea225db6ee5471d0aebaae0861166f9378a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
|
@ -5056,9 +5056,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.111"
|
version = "0.2.112"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19e638317c08b21663aed4d2b9a2091450548954695ff4efa75bff5fa546b3b1"
|
checksum = "55839b71ba921e4f75b674cb16f843f4b1f3b26ddfcb3454de1cf65cc021ec0f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
|
|
@ -5066,9 +5066,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.111"
|
version = "0.2.112"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2c64760850114d03d5f65457e96fc988f11f01d38fbaa51b254e4ab5809102af"
|
checksum = "caf2e969c2d60ff52e7e98b7392ff1588bffdd1ccd4769eba27222fd3d621571"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -5079,9 +5079,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.111"
|
version = "0.2.112"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60eecd4fe26177cfa3339eb00b4a36445889ba3ad37080c2429879718e20ca41"
|
checksum = "0861f0dcdf46ea819407495634953cdcc8a8c7215ab799a7a7ce366be71c7b30"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
@ -5122,9 +5122,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.88"
|
version = "0.3.89"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d6bb20ed2d9572df8584f6dc81d68a41a625cadc6f15999d649a70ce7e3597a"
|
checksum = "10053fbf9a374174094915bbce141e87a6bf32ecd9a002980db4b638405e8962"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
|
@ -5217,7 +5217,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d"
|
checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"env_home",
|
"env_home",
|
||||||
"rustix 1.1.3",
|
"rustix 1.1.4",
|
||||||
"winsafe",
|
"winsafe",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"flagWords":[],"language":"en","version":"0.2","words":["Punct","KEYMAP","splitn","crossterm","YAZI","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","pdftoppm","poppler","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin","rposition","resvg","foldhash","tilded","futs","chdir","hashbrown","JEMALLOC","RUSTFLAGS","RDONLY","GETPATH","fcntl","casefold","inodes","Splatable","casefied","thiserror","memchr","memmem","russh","deadpool","keepalive","nodelay","publickey","deadpool","initing","treelize","TOCTOU"]}
|
{"version":"0.2","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","pdftoppm","poppler","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin","rposition","resvg","foldhash","tilded","futs","chdir","hashbrown","JEMALLOC","RUSTFLAGS","RDONLY","GETPATH","fcntl","casefold","inodes","Splatable","casefied","thiserror","memchr","memmem","russh","deadpool","keepalive","nodelay","publickey","deadpool","initing","treelize","TOCTOU","fellback","watchee"],"language":"en"}
|
||||||
|
|
@ -16,7 +16,8 @@ impl Partitions {
|
||||||
where
|
where
|
||||||
F: Fn() + Copy + Send + 'static,
|
F: Fn() + Copy + Send + 'static,
|
||||||
{
|
{
|
||||||
tokio::task::spawn_blocking(move || {
|
let rt = tokio::runtime::Handle::current();
|
||||||
|
std::thread::spawn(move || {
|
||||||
let session = unsafe { DASessionCreate(kCFAllocatorDefault) };
|
let session = unsafe { DASessionCreate(kCFAllocatorDefault) };
|
||||||
if session.is_null() {
|
if session.is_null() {
|
||||||
return error!("Cannot create a disk arbitration session");
|
return error!("Cannot create a disk arbitration session");
|
||||||
|
|
@ -39,11 +40,12 @@ impl Partitions {
|
||||||
}
|
}
|
||||||
|
|
||||||
let create_context = || {
|
let create_context = || {
|
||||||
|
let rt = rt.clone();
|
||||||
let boxed: Box<dyn Fn()> = Box::new(move || {
|
let boxed: Box<dyn Fn()> = Box::new(move || {
|
||||||
if mem::replace(&mut me.write().need_update, true) {
|
if mem::replace(&mut me.write().need_update, true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Self::update(me, cb);
|
Self::update(me, cb, &rt);
|
||||||
});
|
});
|
||||||
Box::into_raw(Box::new(boxed)) as *mut c_void
|
Box::into_raw(Box::new(boxed)) as *mut c_void
|
||||||
};
|
};
|
||||||
|
|
@ -69,8 +71,11 @@ impl Partitions {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(me: &'static Locked, cb: impl Fn() + Send + 'static) {
|
fn update<F>(me: &'static Locked, cb: F, rt: &tokio::runtime::Handle)
|
||||||
_ = tokio::task::spawn_blocking(move || {
|
where
|
||||||
|
F: Fn() + Send + 'static,
|
||||||
|
{
|
||||||
|
_ = rt.spawn_blocking(move || {
|
||||||
let result = Self::all_names().and_then(Self::all_partitions);
|
let result = Self::all_names().and_then(Self::all_partitions);
|
||||||
if let Err(ref e) = result {
|
if let Err(ref e) = result {
|
||||||
error!("Error encountered while updating mount points: {e:?}");
|
error!("Error encountered while updating mount points: {e:?}");
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,18 @@ pub struct Partition {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Partition {
|
impl Partition {
|
||||||
// Match mount types that do not reliably emit change notifications, or do not
|
// Match mount types that do not update directory mtime on changes,
|
||||||
// update directory metadata on changes, and should be refreshed frequently.
|
// and should be refreshed frequently.
|
||||||
pub fn heuristic(&self) -> bool {
|
pub fn timeless(&self) -> bool {
|
||||||
let b: &[u8] = self.fstype.as_ref().map_or(b"", |s| s.as_encoded_bytes());
|
let b: &[u8] = self.fstype.as_ref().map_or(b"", |s| s.as_encoded_bytes());
|
||||||
matches!(b, b"exfat" | b"fuse.rclone")
|
matches!(b, b"exfat")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match mount types that do not reliably emit change notifications,
|
||||||
|
// and should be polled for changes.
|
||||||
|
pub fn soundless(&self) -> bool {
|
||||||
|
let b: &[u8] = self.fstype.as_ref().map_or(b"", |s| s.as_encoded_bytes());
|
||||||
|
matches!(b, b"fuse.rclone")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
|
|
||||||
|
|
@ -31,14 +31,26 @@ impl Partitions {
|
||||||
self.inner.iter().find(|p| p.rdev == Some(dev))
|
self.inner.iter().find(|p| p.rdev == Some(dev))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn heuristic(&self, _cha: Cha) -> bool {
|
pub fn timeless(&self, _cha: Cha) -> bool {
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
{
|
{
|
||||||
self.by_dev(_cha.dev).is_some_and(|p| p.heuristic())
|
self.by_dev(_cha.dev).is_some_and(|p| p.timeless())
|
||||||
}
|
}
|
||||||
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||||
{
|
{
|
||||||
// For now, assume other targets update directory stat data correctly
|
// For now, assume other targets update directory mtime correctly
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn soundless(&self, _cha: Cha) -> bool {
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
|
{
|
||||||
|
self.by_dev(_cha.dev).is_some_and(|p| p.soundless())
|
||||||
|
}
|
||||||
|
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
||||||
|
{
|
||||||
|
// For now, assume other targets emit change notifications correctly
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ impl VfsFiles for Files {
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
match Cha::from_url(dir).await {
|
match Cha::from_url(dir).await {
|
||||||
Ok(c) if !c.is_dir() => FilesOp::issue_error(dir, ErrorKind::NotADirectory).await,
|
Ok(c) if !c.is_dir() => FilesOp::issue_error(dir, ErrorKind::NotADirectory).await,
|
||||||
Ok(c) if c.hits(cha) && !PARTITIONS.read().heuristic(cha) => {}
|
Ok(c) if c.hits(cha) && !PARTITIONS.read().timeless(cha) => {}
|
||||||
Ok(c) => return Some(c),
|
Ok(c) => return Some(c),
|
||||||
Err(e) => FilesOp::issue_error(dir, e).await,
|
Err(e) => FilesOp::issue_error(dir, e).await,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
use anyhow::Result;
|
use notify::Result;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use yazi_shared::url::AsUrl;
|
|
||||||
|
|
||||||
use crate::{Reporter, WATCHED, local::{self, LINKED, Linked}, remote};
|
use crate::{Reporter, WATCHED, Watchee, local::{self, LINKED, Linked}, remote};
|
||||||
|
|
||||||
pub(crate) struct Backend {
|
pub(crate) struct Backend {
|
||||||
local: local::Local,
|
local: local::Local,
|
||||||
|
|
@ -14,7 +13,7 @@ impl Backend {
|
||||||
pub(crate) fn serve() -> Self {
|
pub(crate) fn serve() -> Self {
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
yazi_fs::mounts::Partitions::monitor(&yazi_fs::mounts::PARTITIONS, || {
|
yazi_fs::mounts::Partitions::monitor(&yazi_fs::mounts::PARTITIONS, || {
|
||||||
yazi_proxy::MgrProxy::refresh();
|
yazi_proxy::MgrProxy::watch();
|
||||||
yazi_macro::err!(yazi_dds::Pubsub::pub_after_mount())
|
yazi_macro::err!(yazi_dds::Pubsub::pub_after_mount())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -29,26 +28,18 @@ impl Backend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn watch(&mut self, url: impl AsUrl) -> Result<()> {
|
pub(super) fn watch(&mut self, watchee: &mut Watchee) -> Result<()> {
|
||||||
let url = url.as_url();
|
match watchee {
|
||||||
if let Some(path) = url.as_local() {
|
Watchee::Local(..) => self.local.watch(watchee),
|
||||||
self.local.watch(path)?;
|
Watchee::Remote(_) => self.remote.watch(watchee),
|
||||||
} else {
|
|
||||||
self.remote.watch(url)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn unwatch(&mut self, url: impl AsUrl) -> Result<()> {
|
pub(super) fn unwatch(&mut self, watchee: &Watchee) -> Result<()> {
|
||||||
let url = url.as_url();
|
match watchee {
|
||||||
if let Some(path) = url.as_local() {
|
Watchee::Local(..) => self.local.unwatch(watchee),
|
||||||
self.local.unwatch(path)?;
|
Watchee::Remote(_) => self.remote.unwatch(watchee),
|
||||||
} else {
|
|
||||||
self.remote.unwatch(url)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn sync(self) -> Self {
|
pub(super) async fn sync(self) -> Self {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
yazi_macro::mod_pub!(local remote);
|
yazi_macro::mod_pub!(local remote);
|
||||||
|
|
||||||
yazi_macro::mod_flat!(backend reporter watched watcher);
|
yazi_macro::mod_flat!(backend reporter watched watchee watcher);
|
||||||
|
|
||||||
pub static WATCHED: yazi_shared::RoCell<parking_lot::RwLock<Watched>> = yazi_shared::RoCell::new();
|
pub static WATCHED: yazi_shared::RoCell<parking_lot::RwLock<Watched>> = yazi_shared::RoCell::new();
|
||||||
pub static WATCHER: yazi_shared::RoCell<tokio::sync::Semaphore> = yazi_shared::RoCell::new();
|
pub static WATCHER: yazi_shared::RoCell<tokio::sync::Semaphore> = yazi_shared::RoCell::new();
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ impl Linked {
|
||||||
let watched = watched.read();
|
let watched = watched.read();
|
||||||
|
|
||||||
// Remove entries that are no longer watched
|
// Remove entries that are no longer watched
|
||||||
linked.write().retain(|from, _| watched.contains(from));
|
linked.write().retain(|from, _| watched.contains_path(from));
|
||||||
|
|
||||||
// Update existing entries and remove broken links
|
// Update existing entries and remove broken links
|
||||||
for from in watched.paths() {
|
for from in watched.paths() {
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,27 @@
|
||||||
use std::{path::Path, time::Duration};
|
use std::{path::Path, time::Duration};
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use notify::{PollWatcher, RecommendedWatcher, RecursiveMode, Watcher};
|
use notify::{PollWatcher, RecommendedWatcher, RecursiveMode, Result, Watcher};
|
||||||
use tokio::{pin, sync::mpsc::{self, UnboundedReceiver}};
|
use tokio::{pin, sync::mpsc::{self, UnboundedReceiver}};
|
||||||
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
|
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
use yazi_fs::{File, FilesOp, provider};
|
use yazi_fs::{File, FilesOp, cha::Cha, mounts::PARTITIONS, provider};
|
||||||
use yazi_shared::url::{UrlBuf, UrlLike};
|
use yazi_shared::url::{UrlBuf, UrlLike};
|
||||||
use yazi_vfs::VfsFile;
|
use yazi_vfs::VfsFile;
|
||||||
|
|
||||||
use crate::{Reporter, WATCHER};
|
use crate::{Reporter, WATCHER, Watchee};
|
||||||
|
|
||||||
pub(crate) struct Local(Box<dyn notify::Watcher + Send>);
|
pub(crate) struct Local {
|
||||||
|
primary: Option<RecommendedWatcher>,
|
||||||
|
alternative: PollWatcher,
|
||||||
|
}
|
||||||
|
|
||||||
impl Local {
|
impl Local {
|
||||||
pub(crate) fn serve(rx: mpsc::UnboundedReceiver<UrlBuf>, reporter: Reporter) -> Self {
|
pub(crate) fn serve(rx: mpsc::UnboundedReceiver<UrlBuf>, reporter: Reporter) -> Self {
|
||||||
tokio::spawn(Self::changed(rx));
|
tokio::spawn(Self::changed(rx));
|
||||||
|
|
||||||
let config = notify::Config::default().with_poll_interval(Duration::from_millis(500));
|
let config = notify::Config::default().with_poll_interval(Duration::from_millis(500));
|
||||||
let handler = move |res: Result<notify::Event, notify::Error>| {
|
let handler = move |res: Result<notify::Event>| {
|
||||||
if let Ok(event) = res
|
if let Ok(event) = res
|
||||||
&& !event.kind.is_access()
|
&& !event.kind.is_access()
|
||||||
{
|
{
|
||||||
|
|
@ -27,35 +29,60 @@ impl Local {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if cfg!(target_os = "netbsd") || yazi_adapter::WSL.get() {
|
let primary = RecommendedWatcher::new(handler.clone(), config);
|
||||||
return Self(Box::new(PollWatcher::new(handler, config).unwrap()));
|
let alternative = PollWatcher::new(handler, config).unwrap();
|
||||||
|
|
||||||
|
if let Err(e) = &primary {
|
||||||
|
error!("Failed to initialize primary watcher: {e:?}");
|
||||||
}
|
}
|
||||||
|
|
||||||
Self(match RecommendedWatcher::new(handler.clone(), config) {
|
Self { primary: primary.ok(), alternative }
|
||||||
Ok(watcher) => Box::new(watcher),
|
}
|
||||||
Err(e) => {
|
|
||||||
error!("Falling back to PollWatcher due to RecommendedWatcher init failure: {e:?}");
|
pub(crate) fn watch(&mut self, watchee: &mut Watchee) -> Result<()> {
|
||||||
Box::new(PollWatcher::new(handler, config).unwrap())
|
let (path, alt) =
|
||||||
|
watchee.as_local_mut().ok_or_else(|| notify::Error::generic("Not a local watchee"))?;
|
||||||
|
|
||||||
|
if let Some(primary) = self.primary.as_mut().filter(|_| !*alt) {
|
||||||
|
match primary.watch(path, RecursiveMode::NonRecursive) {
|
||||||
|
Ok(()) => return Ok(()),
|
||||||
|
Err(e) => tracing::warn!("Failed to watch {path:?} with primary watcher: {e:?}"),
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn watch(&mut self, path: &Path) -> Result<()> {
|
|
||||||
match self.0.watch(path, RecursiveMode::NonRecursive) {
|
|
||||||
Ok(()) => Ok(()),
|
|
||||||
Err(e) if matches!(e.kind, notify::ErrorKind::PathNotFound) => Ok(()),
|
|
||||||
Err(e) => Err(e)?,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tracing::debug!("Watching {path:?} with alternative watcher");
|
||||||
|
*alt = true;
|
||||||
|
self.alternative.watch(path, RecursiveMode::NonRecursive)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn unwatch(&mut self, path: &Path) -> Result<()> {
|
pub(crate) fn unwatch(&mut self, watchee: &Watchee) -> Result<()> {
|
||||||
match self.0.unwatch(path) {
|
let (path, alt) =
|
||||||
|
watchee.as_local().ok_or_else(|| notify::Error::generic("Not a local watchee"))?;
|
||||||
|
|
||||||
|
let result = if alt {
|
||||||
|
self.alternative.unwatch(path)
|
||||||
|
} else if let Some(primary) = &mut self.primary {
|
||||||
|
primary.unwatch(path)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
match result {
|
||||||
Ok(()) => Ok(()),
|
Ok(()) => Ok(()),
|
||||||
Err(e) if matches!(e.kind, notify::ErrorKind::WatchNotFound) => Ok(()),
|
Err(e) if matches!(e.kind, notify::ErrorKind::WatchNotFound) => Ok(()),
|
||||||
Err(e) => Err(e)?,
|
Err(e) => Err(e)?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn soundless(path: &Path) -> bool {
|
||||||
|
if cfg!(target_os = "netbsd") || yazi_adapter::WSL.get() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Ok(meta) = path.metadata() else { return true };
|
||||||
|
PARTITIONS.read().soundless(Cha::new(path.file_name().unwrap_or_default(), meta))
|
||||||
|
}
|
||||||
|
|
||||||
async fn changed(rx: UnboundedReceiver<UrlBuf>) {
|
async fn changed(rx: UnboundedReceiver<UrlBuf>) {
|
||||||
// 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(250));
|
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(250));
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
|
use notify::Result;
|
||||||
use tokio::{pin, sync::mpsc::UnboundedReceiver};
|
use tokio::{pin, sync::mpsc::UnboundedReceiver};
|
||||||
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
|
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
|
||||||
use yazi_fs::{File, FilesOp};
|
use yazi_fs::{File, FilesOp};
|
||||||
use yazi_proxy::MgrProxy;
|
use yazi_proxy::MgrProxy;
|
||||||
use yazi_shared::url::{Url, UrlBuf, UrlLike};
|
use yazi_shared::url::{UrlBuf, UrlLike};
|
||||||
use yazi_vfs::VfsFile;
|
use yazi_vfs::VfsFile;
|
||||||
|
|
||||||
use crate::{Reporter, WATCHER};
|
use crate::{Reporter, WATCHER, Watchee};
|
||||||
|
|
||||||
pub(crate) struct Remote;
|
pub(crate) struct Remote;
|
||||||
|
|
||||||
|
|
@ -20,9 +20,9 @@ impl Remote {
|
||||||
Self
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn watch(&mut self, _url: Url) -> Result<()> { Ok(()) }
|
pub(crate) fn watch(&mut self, _watchee: &mut Watchee) -> Result<()> { Ok(()) }
|
||||||
|
|
||||||
pub(crate) fn unwatch(&mut self, _url: Url) -> Result<()> { Ok(()) }
|
pub(crate) fn unwatch(&mut self, _watchee: &Watchee) -> Result<()> { Ok(()) }
|
||||||
|
|
||||||
async fn changed(rx: UnboundedReceiver<(UrlBuf, bool)>) {
|
async fn changed(rx: UnboundedReceiver<(UrlBuf, bool)>) {
|
||||||
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(250));
|
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(250));
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ impl Reporter {
|
||||||
|
|
||||||
let watched = WATCHED.read();
|
let watched = WATCHED.read();
|
||||||
for parent in [parent].into_iter().chain(linked) {
|
for parent in [parent].into_iter().chain(linked) {
|
||||||
if watched.contains(parent) {
|
if watched.contains_url(parent) {
|
||||||
self.local_tx.send(url.to_owned()).ok();
|
self.local_tx.send(url.to_owned()).ok();
|
||||||
self.local_tx.send(parent.to_owned()).ok();
|
self.local_tx.send(parent.to_owned()).ok();
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +57,7 @@ impl Reporter {
|
||||||
|
|
||||||
fn report_remote(&self, url: UrlCow) {
|
fn report_remote(&self, url: UrlCow) {
|
||||||
let Some(parent) = url.parent() else { return };
|
let Some(parent) = url.parent() else { return };
|
||||||
if !WATCHED.read().contains(parent) {
|
if !WATCHED.read().contains_url(parent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,43 @@
|
||||||
use std::path::Path;
|
use std::{ops::{Deref, DerefMut}, path::Path};
|
||||||
|
|
||||||
use hashbrown::HashSet;
|
use hashbrown::HashSet;
|
||||||
use percent_encoding::percent_decode_str;
|
use percent_encoding::percent_decode_str;
|
||||||
use yazi_fs::{Xdg, path::PercentEncoding};
|
use yazi_fs::{Xdg, path::PercentEncoding};
|
||||||
use yazi_shared::{path::{Component, PathBufDyn, PathDyn, PathLike}, pool::InternStr, scheme::SchemeKind, url::{AsUrl, UrlBuf, UrlLike}};
|
use yazi_shared::{path::{Component, PathBufDyn, PathDyn, PathLike}, pool::InternStr, scheme::SchemeKind, url::{AsUrl, UrlBuf}};
|
||||||
|
|
||||||
|
use crate::Watchee;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Watched(HashSet<UrlBuf>);
|
pub struct Watched(HashSet<Watchee<'static>>);
|
||||||
|
|
||||||
|
impl Deref for Watched {
|
||||||
|
type Target = HashSet<Watchee<'static>>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for Watched {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
impl Watched {
|
impl Watched {
|
||||||
#[inline]
|
pub(super) fn contains_url(&self, url: impl AsUrl) -> bool {
|
||||||
pub(crate) fn contains(&self, url: impl AsUrl) -> bool { self.0.contains(&url.as_url()) }
|
let url = url.as_url();
|
||||||
|
if url.as_local().is_some() {
|
||||||
#[inline]
|
self.0.contains(&Watchee::Local(url.into(), false))
|
||||||
pub(crate) fn diff(&self, new: &HashSet<UrlBuf>) -> (Vec<UrlBuf>, Vec<UrlBuf>) {
|
|| self.0.contains(&Watchee::Local(url.into(), true))
|
||||||
(self.0.difference(new).cloned().collect(), new.difference(&self.0).cloned().collect())
|
} else {
|
||||||
|
self.0.contains(&Watchee::Remote(url.into()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub(super) fn contains_path(&self, path: &Path) -> bool {
|
||||||
pub(crate) fn insert(&mut self, url: impl Into<UrlBuf>) { self.0.insert(url.into()); }
|
self.0.iter().any(|watchee| watchee.as_url().as_local() == Some(path))
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn paths(&self) -> impl Iterator<Item = &Path> {
|
|
||||||
self.0.iter().filter_map(|u| u.as_local())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub(super) fn paths(&self) -> impl Iterator<Item = &Path> {
|
||||||
pub(crate) fn remove(&mut self, url: impl AsUrl) { self.0.remove(&url.as_url()); }
|
self.0.iter().filter_map(|watchee| watchee.as_url().as_local())
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn find_by_cache(&self, cache: PathDyn) -> Option<UrlBuf> {
|
pub(super) fn find_by_cache(&self, cache: PathDyn) -> Option<UrlBuf> {
|
||||||
let mut it = cache.try_strip_prefix(Xdg::cache_dir()).ok()?.components();
|
let mut it = cache.try_strip_prefix(Xdg::cache_dir()).ok()?.components();
|
||||||
|
|
@ -46,6 +57,6 @@ impl Watched {
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
let url = UrlBuf::Sftp { loc: path.into_unix().ok()?.into(), domain };
|
let url = UrlBuf::Sftp { loc: path.into_unix().ok()?.into(), domain };
|
||||||
if self.contains(&url) { Some(url) } else { None }
|
if self.contains_url(&url) { Some(url) } else { None }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
56
yazi-watcher/src/watchee.rs
Normal file
56
yazi-watcher/src/watchee.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
use std::{hash::Hash, path::Path};
|
||||||
|
|
||||||
|
use yazi_shared::url::{AsUrl, Url, UrlCow, UrlLike};
|
||||||
|
|
||||||
|
use crate::local::Local;
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub enum Watchee<'a> {
|
||||||
|
Local(UrlCow<'a>, bool),
|
||||||
|
Remote(UrlCow<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsUrl for Watchee<'_> {
|
||||||
|
fn as_url(&self) -> Url<'_> {
|
||||||
|
match self {
|
||||||
|
Self::Local(url, _) => url.as_url(),
|
||||||
|
Self::Remote(url) => url.as_url(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Watchee<'a> {
|
||||||
|
pub(super) fn as_local(&self) -> Option<(&Path, bool)> {
|
||||||
|
Some(match self {
|
||||||
|
Self::Local(url, alt) => (url.as_local()?, *alt),
|
||||||
|
Self::Remote(_) => None?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn as_local_mut(&mut self) -> Option<(&Path, &mut bool)> {
|
||||||
|
Some(match self {
|
||||||
|
Self::Local(url, alt) => (url.as_local()?, alt),
|
||||||
|
Self::Remote(_) => None?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn new<U>(url: U) -> Self
|
||||||
|
where
|
||||||
|
U: Into<UrlCow<'a>>,
|
||||||
|
{
|
||||||
|
let url = url.into();
|
||||||
|
if let Some(path) = url.as_local() {
|
||||||
|
let b = Local::soundless(path);
|
||||||
|
Self::Local(url, b)
|
||||||
|
} else {
|
||||||
|
Self::Remote(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn to_static(&self) -> Watchee<'static> {
|
||||||
|
match self {
|
||||||
|
Self::Local(url, alt) => Watchee::Local(url.to_owned().into(), *alt),
|
||||||
|
Self::Remote(url) => Watchee::Remote(url.to_owned().into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ use tracing::error;
|
||||||
use yazi_fs::FsUrl;
|
use yazi_fs::FsUrl;
|
||||||
use yazi_shared::url::{UrlBuf, UrlCow, UrlLike};
|
use yazi_shared::url::{UrlBuf, UrlCow, UrlLike};
|
||||||
|
|
||||||
use crate::{Reporter, WATCHED, backend::Backend};
|
use crate::{Reporter, WATCHED, Watchee, backend::Backend};
|
||||||
|
|
||||||
pub struct Watcher {
|
pub struct Watcher {
|
||||||
tx: watch::Sender<HashSet<UrlBuf>>,
|
tx: watch::Sender<HashSet<UrlBuf>>,
|
||||||
|
|
@ -52,7 +52,8 @@ impl Watcher {
|
||||||
|
|
||||||
async fn watched(mut rx: watch::Receiver<HashSet<UrlBuf>>, mut backend: Backend) {
|
async fn watched(mut rx: watch::Receiver<HashSet<UrlBuf>>, mut backend: Backend) {
|
||||||
loop {
|
loop {
|
||||||
let (to_unwatch, to_watch) = WATCHED.read().diff(&rx.borrow_and_update());
|
let (rx_, to_unwatch, to_watch) = Self::diff(rx).await;
|
||||||
|
rx = rx_;
|
||||||
|
|
||||||
if !to_unwatch.is_empty() || !to_watch.is_empty() {
|
if !to_unwatch.is_empty() || !to_watch.is_empty() {
|
||||||
backend = Self::sync(backend, to_unwatch, to_watch).await;
|
backend = Self::sync(backend, to_unwatch, to_watch).await;
|
||||||
|
|
@ -65,17 +66,42 @@ impl Watcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sync(mut backend: Backend, to_unwatch: Vec<UrlBuf>, to_watch: Vec<UrlBuf>) -> Backend {
|
async fn diff(
|
||||||
|
mut rx: watch::Receiver<HashSet<UrlBuf>>,
|
||||||
|
) -> (watch::Receiver<HashSet<UrlBuf>>, Vec<Watchee<'static>>, Vec<Watchee<'static>>) {
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
for u in to_unwatch {
|
let new = rx.borrow_and_update();
|
||||||
match backend.unwatch(&u) {
|
let new_: HashSet<_> = new.iter().map(Watchee::new).collect();
|
||||||
Ok(()) => WATCHED.write().remove(&u),
|
|
||||||
|
let old = WATCHED.read();
|
||||||
|
|
||||||
|
let to_unwatch = old.difference(&new_).map(Watchee::to_static).collect();
|
||||||
|
let to_watch = new_.difference(&old).map(Watchee::to_static).collect();
|
||||||
|
|
||||||
|
drop(new_);
|
||||||
|
drop(new);
|
||||||
|
(rx, to_unwatch, to_watch)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sync(
|
||||||
|
mut backend: Backend,
|
||||||
|
to_unwatch: Vec<Watchee<'static>>,
|
||||||
|
to_watch: Vec<Watchee<'static>>,
|
||||||
|
) -> Backend {
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
for watchee in to_unwatch {
|
||||||
|
match backend.unwatch(&watchee) {
|
||||||
|
Ok(()) => _ = WATCHED.write().remove(&watchee),
|
||||||
Err(e) => error!("Unwatch failed: {e:?}"),
|
Err(e) => error!("Unwatch failed: {e:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for u in to_watch {
|
for mut watchee in to_watch {
|
||||||
match backend.watch(&u) {
|
match backend.watch(&mut watchee) {
|
||||||
Ok(()) => WATCHED.write().insert(u),
|
Ok(()) => _ = WATCHED.write().insert(watchee),
|
||||||
|
Err(e) if matches!(e.kind, notify::ErrorKind::PathNotFound) => {}
|
||||||
Err(e) => error!("Watch failed: {e:?}"),
|
Err(e) => error!("Watch failed: {e:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue