diff --git a/yazi-config/preset/keymap.toml b/yazi-config/preset/keymap.toml index bce3ed3e..a803ae7b 100644 --- a/yazi-config/preset/keymap.toml +++ b/yazi-config/preset/keymap.toml @@ -66,6 +66,7 @@ keymap = [ { on = [ "y" ], run = "yank", desc = "Copy the selected files" }, { on = [ "Y" ], run = "unyank", desc = "Cancel the yank status of files" }, { on = [ "x" ], run = "yank --cut", desc = "Cut the selected files" }, + { on = [ "X" ], run = "unyank", desc = "Cancel the yank status of files" }, { on = [ "p" ], run = "paste", desc = "Paste the files" }, { on = [ "P" ], run = "paste --force", desc = "Paste the files (overwrite if the destination exists)" }, { on = [ "-" ], run = "link", desc = "Symlink the absolute path of files" }, diff --git a/yazi-config/src/validation.rs b/yazi-config/src/validation.rs index 34a098f3..a0346eb9 100644 --- a/yazi-config/src/validation.rs +++ b/yazi-config/src/validation.rs @@ -3,9 +3,7 @@ use std::{borrow::Cow, process}; use validator::{ValidationErrors, ValidationErrorsKind}; pub fn check_validation(res: Result<(), ValidationErrors>) { - let Err(errors) = res else { - return; - }; + let Err(errors) = res else { return }; for (field, kind) in errors.into_errors() { match kind { diff --git a/yazi-core/src/clipboard.rs b/yazi-core/src/clipboard.rs index f7e75844..fb3a9bcd 100644 --- a/yazi-core/src/clipboard.rs +++ b/yazi-core/src/clipboard.rs @@ -1,12 +1,13 @@ -use std::{cell::RefCell, ffi::OsString}; +use std::ffi::OsString; +use parking_lot::Mutex; use yazi_shared::RoCell; pub static CLIPBOARD: RoCell = RoCell::new(); #[derive(Default)] pub struct Clipboard { - content: RefCell, + content: Mutex, } impl Clipboard { @@ -18,11 +19,11 @@ impl Clipboard { use yazi_shared::in_ssh_connection; if in_ssh_connection() { - return self.content.borrow().clone(); + return self.content.lock().clone(); } let all = [ - ("pbpaste", &[] as &[&str]), + ("pbpaste", &[][..]), ("wl-paste", &[]), ("xclip", &["-o", "-selection", "clipboard"]), ("xsel", &["-ob"]), @@ -36,7 +37,7 @@ impl Clipboard { return OsString::from_vec(output.stdout); } } - self.content.borrow().clone() + self.content.lock().clone() } #[cfg(windows)] @@ -48,7 +49,7 @@ impl Clipboard { return s.into(); } - self.content.borrow().clone() + self.content.lock().clone() } #[cfg(unix)] @@ -59,13 +60,13 @@ impl Clipboard { use tokio::{io::AsyncWriteExt, process::Command}; use yazi_shared::in_ssh_connection; - *self.content.borrow_mut() = s.as_ref().to_owned(); + *self.content.lock() = s.as_ref().to_owned(); if in_ssh_connection() { execute!(stdout(), osc52::SetClipboard::new(s.as_ref())).ok(); } let all = [ - ("pbcopy", &[] as &[&str]), + ("pbcopy", &[][..]), ("wl-copy", &[]), ("xclip", &["-selection", "clipboard"]), ("xsel", &["-ib"]), @@ -80,9 +81,7 @@ impl Clipboard { .kill_on_drop(true) .spawn(); - let Ok(mut child) = cmd else { - continue; - }; + let Ok(mut child) = cmd else { continue }; let mut stdin = child.stdin.take().unwrap(); if stdin.write_all(s.as_ref().as_encoded_bytes()).await.is_err() { @@ -101,7 +100,7 @@ impl Clipboard { use clipboard_win::{formats, set_clipboard}; let s = s.as_ref().to_owned(); - *self.content.borrow_mut() = s.clone(); + *self.content.lock() = s.clone(); tokio::task::spawn_blocking(move || set_clipboard(formats::Unicode, s.to_string_lossy())) .await diff --git a/yazi-core/src/completion/commands/trigger.rs b/yazi-core/src/completion/commands/trigger.rs index 84fea5dd..4fdf9901 100644 --- a/yazi-core/src/completion/commands/trigger.rs +++ b/yazi-core/src/completion/commands/trigger.rs @@ -40,9 +40,7 @@ impl Completion { let mut dir = fs::read_dir(&parent).await?; let mut cache = vec![]; while let Ok(Some(f)) = dir.next_entry().await { - let Ok(meta) = f.metadata().await else { - continue; - }; + let Ok(meta) = f.metadata().await else { continue }; cache.push(format!( "{}{}", diff --git a/yazi-core/src/input/commands/show.rs b/yazi-core/src/input/commands/show.rs index 7abfcafc..0e09f77f 100644 --- a/yazi-core/src/input/commands/show.rs +++ b/yazi-core/src/input/commands/show.rs @@ -5,9 +5,7 @@ use crate::input::Input; impl Input { pub fn show(&mut self, opt: impl TryInto) { - let Ok(opt) = opt.try_into() else { - return; - }; + let Ok(opt) = opt.try_into() else { return }; self.close(false); self.visible = true; diff --git a/yazi-core/src/manager/commands/bulk_rename.rs b/yazi-core/src/manager/commands/bulk_rename.rs new file mode 100644 index 00000000..8dca4fd6 --- /dev/null +++ b/yazi-core/src/manager/commands/bulk_rename.rs @@ -0,0 +1,130 @@ +use std::{collections::HashMap, ffi::{OsStr, OsString}, io::{stdout, BufWriter, Write}, path::PathBuf}; + +use anyhow::{anyhow, Result}; +use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}}; +use yazi_config::{OPEN, PREVIEW}; +use yazi_plugin::external::{self, ShellOpt}; +use yazi_proxy::AppProxy; +use yazi_scheduler::{HIDER, WATCHER}; +use yazi_shared::{fs::{accessible, max_common_root, File, FilesOp, Url}, term::Term, Defer}; + +use crate::manager::Manager; + +impl Manager { + pub(super) fn bulk_rename(&self) { + let Some(opener) = OPEN.block_opener("bulk.txt", "text/plain") else { + return AppProxy::notify_warn("Bulk rename", "No text opener found"); + }; + + let cwd = self.cwd().clone(); + let old: Vec<_> = self.selected_or_hovered(); + + let root = max_common_root(&old); + let old: Vec<_> = old.into_iter().map(|p| p.strip_prefix(&root).unwrap().to_owned()).collect(); + + tokio::spawn(async move { + let tmp = PREVIEW.tmpfile("bulk"); + let s = old.iter().map(|o| o.as_os_str()).collect::>().join(OsStr::new("\n")); + OpenOptions::new() + .write(true) + .create_new(true) + .open(&tmp) + .await? + .write_all(s.as_encoded_bytes()) + .await?; + + let _permit = HIDER.acquire().await.unwrap(); + let _defer1 = Defer::new(AppProxy::resume); + let _defer2 = Defer::new(|| tokio::spawn(fs::remove_file(tmp.clone()))); + AppProxy::stop().await; + + let mut child = external::shell(ShellOpt { + cmd: (*opener.run).into(), + args: vec![OsString::new(), tmp.to_owned().into()], + piped: false, + orphan: false, + })?; + child.wait().await?; + + let new: Vec<_> = fs::read_to_string(&tmp).await?.lines().map(PathBuf::from).collect(); + Self::bulk_rename_do(cwd, root, old, new).await + }); + } + + async fn bulk_rename_do( + cwd: Url, + root: PathBuf, + old: Vec, + new: Vec, + ) -> Result<()> { + Term::clear(&mut stdout())?; + if old.len() != new.len() { + println!("Number of old and new differ, press ENTER to exit"); + stdin().read_exact(&mut [0]).await?; + return Ok(()); + } + + let todo: Vec<_> = old.into_iter().zip(new).filter(|(o, n)| o != n).collect(); + if todo.is_empty() { + return Ok(()); + } + + { + let mut stdout = BufWriter::new(stdout().lock()); + for (o, n) in &todo { + writeln!(stdout, "{} -> {}", o.display(), n.display())?; + } + write!(stdout, "Continue to rename? (y/N): ")?; + stdout.flush()?; + } + + let mut buf = [0; 10]; + _ = stdin().read(&mut buf).await?; + if buf[0] != b'y' && buf[0] != b'Y' { + return Ok(()); + } + + let _permit = WATCHER.acquire().await.unwrap(); + let (mut failed, mut succeeded) = (Vec::new(), HashMap::with_capacity(todo.len())); + for (o, n) in todo { + let (old, new) = (root.join(&o), root.join(&n)); + + if accessible(&new).await { + failed.push((o, n, anyhow!("Destination already exists"))); + } else if let Err(e) = fs::rename(&old, &new).await { + failed.push((o, n, e.into())); + } else if let Ok(f) = File::from(new.into()).await { + succeeded.insert(Url::from(old), f); + } else { + failed.push((o, n, anyhow!("Failed to retrieve file info"))); + } + } + + if !succeeded.is_empty() { + FilesOp::Upserting(cwd, succeeded).emit(); + } + drop(_permit); + + if !failed.is_empty() { + Self::output_failed(failed).await?; + } + Ok(()) + } + + async fn output_failed(failed: Vec<(PathBuf, PathBuf, anyhow::Error)>) -> Result<()> { + Term::clear(&mut stdout())?; + + { + let mut stdout = BufWriter::new(stdout().lock()); + writeln!(stdout, "Failed to rename:")?; + for (o, n, e) in failed { + writeln!(stdout, "{} -> {}: {e}", o.display(), n.display())?; + } + writeln!(stdout, "\nPress ENTER to exit")?; + stdout.flush()?; + } + + stdin().read_exact(&mut [0]).await?; + Ok(()) + } +} diff --git a/yazi-core/src/manager/commands/create.rs b/yazi-core/src/manager/commands/create.rs index 8779a2af..6aa886d9 100644 --- a/yazi-core/src/manager/commands/create.rs +++ b/yazi-core/src/manager/commands/create.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; use tokio::fs; use yazi_config::popup::InputCfg; use yazi_proxy::{InputProxy, ManagerProxy}; -use yazi_shared::{event::Cmd, fs::{File, FilesOp, Url}}; +use yazi_shared::{event::Cmd, fs::{accessible, File, FilesOp, Url}}; use crate::manager::Manager; @@ -26,7 +26,7 @@ impl Manager { }; let path = cwd.join(&name); - if !opt.force && fs::symlink_metadata(&path).await.is_ok() { + if !opt.force && accessible(&path).await { match InputProxy::show(InputCfg::overwrite()).recv().await { Some(Ok(c)) if c == "y" || c == "Y" => (), _ => return Ok(()), diff --git a/yazi-core/src/manager/commands/mod.rs b/yazi-core/src/manager/commands/mod.rs index cc4e4cef..155a22a5 100644 --- a/yazi-core/src/manager/commands/mod.rs +++ b/yazi-core/src/manager/commands/mod.rs @@ -1,3 +1,4 @@ +mod bulk_rename; mod close; mod create; mod hover; diff --git a/yazi-core/src/manager/commands/paste.rs b/yazi-core/src/manager/commands/paste.rs index e1612e37..f551f023 100644 --- a/yazi-core/src/manager/commands/paste.rs +++ b/yazi-core/src/manager/commands/paste.rs @@ -16,13 +16,15 @@ impl From for Opt { impl Manager { pub fn paste(&mut self, opt: impl Into, tasks: &Tasks) { let opt = opt.into() as Opt; + let (src, dest) = (self.yanked.iter().collect::>(), self.cwd()); - let dest = self.cwd(); if self.yanked.cut { - tasks.file_cut(&self.yanked, dest, opt.force); + tasks.file_cut(&src, dest, opt.force); + + self.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(&src, false)); self.unyank(()); } else { - tasks.file_copy(&self.yanked, dest, opt.force, opt.follow); + tasks.file_copy(&src, dest, opt.force, opt.follow); } } } diff --git a/yazi-core/src/manager/commands/remove.rs b/yazi-core/src/manager/commands/remove.rs index 5ccfc112..6ff0d18f 100644 --- a/yazi-core/src/manager/commands/remove.rs +++ b/yazi-core/src/manager/commands/remove.rs @@ -52,8 +52,13 @@ impl Manager { pub fn remove_do(&mut self, opt: impl Into, tasks: &Tasks) { let opt = opt.into() as Opt; + + self.tabs.iter_mut().for_each(|t| { + t.selected.remove_many(&opt.targets, false); + }); + for u in &opt.targets { - self.active_mut().selected.remove(u); + self.yanked.remove(u); } tasks.file_remove(opt.targets, opt.permanently); diff --git a/yazi-core/src/manager/commands/rename.rs b/yazi-core/src/manager/commands/rename.rs index 3cb990e4..385b4b2f 100644 --- a/yazi-core/src/manager/commands/rename.rs +++ b/yazi-core/src/manager/commands/rename.rs @@ -1,12 +1,11 @@ -use std::{collections::HashMap, ffi::{OsStr, OsString}, io::{stdout, BufWriter, Write}, path::PathBuf}; +use std::collections::HashMap; -use anyhow::{anyhow, bail, Result}; -use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}}; -use yazi_config::{popup::InputCfg, OPEN, PREVIEW}; -use yazi_plugin::external::{self, ShellOpt}; -use yazi_proxy::{AppProxy, InputProxy, ManagerProxy}; -use yazi_scheduler::BLOCKER; -use yazi_shared::{event::Cmd, fs::{max_common_root, File, FilesOp, Url}, term::Term, Defer}; +use anyhow::Result; +use tokio::fs; +use yazi_config::popup::InputCfg; +use yazi_proxy::{InputProxy, ManagerProxy}; +use yazi_scheduler::WATCHER; +use yazi_shared::{event::Cmd, fs::{accessible, File, FilesOp, Url}}; use crate::manager::Manager; @@ -27,32 +26,6 @@ impl From for Opt { } impl Manager { - fn empty_url_part(url: &Url, by: &str) -> String { - if by == "all" { - return String::new(); - } - - let ext = url.extension(); - match by { - "stem" => ext.map_or_else(String::new, |s| format!(".{}", s.to_string_lossy().into_owned())), - "ext" if ext.is_some() => format!("{}.", url.file_stem().unwrap().to_string_lossy()), - "dot_ext" if ext.is_some() => url.file_stem().unwrap().to_string_lossy().into_owned(), - _ => url.file_name().map_or_else(String::new, |s| s.to_string_lossy().into_owned()), - } - } - - async fn rename_and_hover(old: Url, new: Url) -> Result<()> { - fs::rename(&old, &new).await?; - if old.parent() != new.parent() { - return Ok(()); - } - - let file = File::from(new.clone()).await?; - FilesOp::Deleting(file.parent().unwrap(), vec![new.clone()]).emit(); - FilesOp::Upserting(file.parent().unwrap(), HashMap::from_iter([(old, file)])).emit(); - Ok(ManagerProxy::hover(Some(new))) - } - pub fn rename(&mut self, opt: impl Into) { if !self.active_mut().try_escape_visual() { return; @@ -84,117 +57,45 @@ impl Manager { }; let new = hovered.parent().unwrap().join(name); - if opt.force || fs::symlink_metadata(&new).await.is_err() { - Self::rename_and_hover(hovered, Url::from(new)).await.ok(); + if opt.force || !accessible(&new).await { + Self::rename_do(hovered, Url::from(new)).await.ok(); return; } let mut result = InputProxy::show(InputCfg::overwrite()); if let Some(Ok(choice)) = result.recv().await { if choice == "y" || choice == "Y" { - Self::rename_and_hover(hovered, Url::from(new)).await.ok(); + Self::rename_do(hovered, Url::from(new)).await.ok(); } }; }); } - fn bulk_rename(&self) { - let old: Vec<_> = self.selected_or_hovered(); + async fn rename_do(old: Url, new: Url) -> Result<()> { + let _permit = WATCHER.acquire().await.unwrap(); - let root = max_common_root(&old); - let old: Vec<_> = old.into_iter().map(|p| p.strip_prefix(&root).unwrap().to_owned()).collect(); + fs::rename(&old, &new).await?; + if old.parent() != new.parent() { + return Ok(()); + } - let tmp = PREVIEW.tmpfile("bulk"); - tokio::spawn(async move { - let Some(opener) = OPEN.block_opener("bulk.txt", "text/plain") else { - bail!("No opener for bulk rename"); - }; - - { - let s = old.iter().map(|o| o.as_os_str()).collect::>().join(OsStr::new("\n")); - OpenOptions::new() - .write(true) - .create_new(true) - .open(&tmp) - .await? - .write_all(s.as_encoded_bytes()) - .await?; - } - - let _guard = BLOCKER.acquire().await.unwrap(); - let _defer = Defer::new(|| { - AppProxy::resume(); - tokio::spawn(fs::remove_file(tmp.clone())) - }); - AppProxy::stop().await; - - let mut child = external::shell(ShellOpt { - cmd: (*opener.run).into(), - args: vec![OsString::new(), tmp.to_owned().into()], - piped: false, - orphan: false, - })?; - child.wait().await?; - - let new: Vec<_> = fs::read_to_string(&tmp).await?.lines().map(PathBuf::from).collect(); - Self::bulk_rename_do(root, old, new).await - }); + let file = File::from(new.clone()).await?; + FilesOp::Deleting(file.parent().unwrap(), vec![new.clone()]).emit(); + FilesOp::Upserting(file.parent().unwrap(), HashMap::from_iter([(old, file)])).emit(); + Ok(ManagerProxy::hover(Some(new))) } - async fn bulk_rename_do(root: PathBuf, old: Vec, new: Vec) -> Result<()> { - Term::clear(&mut stdout())?; - if old.len() != new.len() { - println!("Number of old and new differ, press ENTER to exit"); - stdin().read_exact(&mut [0]).await?; - return Ok(()); + fn empty_url_part(url: &Url, by: &str) -> String { + if by == "all" { + return String::new(); } - let todo: Vec<_> = old.into_iter().zip(new).filter(|(o, n)| o != n).collect(); - if todo.is_empty() { - return Ok(()); + let ext = url.extension(); + match by { + "stem" => ext.map_or_else(String::new, |s| format!(".{}", s.to_string_lossy().into_owned())), + "ext" if ext.is_some() => format!("{}.", url.file_stem().unwrap().to_string_lossy()), + "dot_ext" if ext.is_some() => url.file_stem().unwrap().to_string_lossy().into_owned(), + _ => url.file_name().map_or_else(String::new, |s| s.to_string_lossy().into_owned()), } - - { - let mut stdout = BufWriter::new(stdout().lock()); - for (o, n) in &todo { - writeln!(stdout, "{} -> {}", o.display(), n.display())?; - } - write!(stdout, "Continue to rename? (y/N): ")?; - stdout.flush()?; - } - - let mut buf = [0; 10]; - _ = stdin().read(&mut buf).await?; - if buf[0] != b'y' && buf[0] != b'Y' { - return Ok(()); - } - - let mut failed = vec![]; - for (o, n) in todo { - if fs::symlink_metadata(&n).await.is_ok() { - failed.push((o, n, anyhow!("Destination already exists"))); - continue; - } - if let Err(e) = fs::rename(root.join(&o), root.join(&n)).await { - failed.push((o, n, e.into())); - } - } - if failed.is_empty() { - return Ok(()); - } - - Term::clear(&mut stdout())?; - { - let mut stdout = BufWriter::new(stdout().lock()); - writeln!(stdout, "Failed to rename:")?; - for (o, n, e) in failed { - writeln!(stdout, "{} -> {}: {e}", o.display(), n.display())?; - } - writeln!(stdout, "\nPress ENTER to exit")?; - stdout.flush()?; - } - - stdin().read_exact(&mut [0]).await?; - Ok(()) } } diff --git a/yazi-core/src/manager/commands/update_files.rs b/yazi-core/src/manager/commands/update_files.rs index 7da81b19..097751ca 100644 --- a/yazi-core/src/manager/commands/update_files.rs +++ b/yazi-core/src/manager/commands/update_files.rs @@ -16,8 +16,33 @@ impl TryFrom for Opt { } impl Manager { + pub fn update_files(&mut self, opt: impl TryInto, tasks: &Tasks) { + let Ok(opt) = opt.try_into() else { + return; + }; + + let mut ops = vec![opt.op]; + for u in self.watcher.linked.read().from_dir(ops[0].url()) { + ops.push(ops[0].chroot(u)); + } + + for op in ops { + let idx = self.tabs.idx; + self.yanked.apply_op(&op); + + for (_, tab) in self.tabs.iter_mut().enumerate().filter(|(i, _)| *i != idx) { + Self::update_tab(tab, Cow::Borrowed(&op), tasks); + } + Self::update_tab(self.active_mut(), Cow::Owned(op), tasks); + } + + self.active_mut().apply_files_attrs(); + } + fn update_tab(tab: &mut Tab, op: Cow, tasks: &Tasks) { let url = op.url(); + tab.selected.apply_op(&op); + if tab.current.cwd == *url { Self::update_current(tab, op, tasks); } else if matches!(&tab.parent, Some(p) if p.cwd == *url) { @@ -92,26 +117,4 @@ impl Manager { tab.leave(()); } } - - pub fn update_files(&mut self, opt: impl TryInto, tasks: &Tasks) { - let Ok(opt) = opt.try_into() else { - return; - }; - - let mut ops = vec![opt.op]; - for u in self.watcher.linked.read().from_dir(ops[0].url()) { - ops.push(ops[0].chroot(u)); - } - - for op in ops { - let idx = self.tabs.idx; - for (_, tab) in self.tabs.iter_mut().enumerate().filter(|(i, _)| *i != idx) { - Self::update_tab(tab, Cow::Borrowed(&op), tasks); - } - - Self::update_tab(self.active_mut(), Cow::Owned(op), tasks); - } - - self.active_mut().apply_files_attrs(); - } } diff --git a/yazi-core/src/manager/watcher.rs b/yazi-core/src/manager/watcher.rs index e8b0ecbf..f4e57def 100644 --- a/yazi-core/src/manager/watcher.rs +++ b/yazi-core/src/manager/watcher.rs @@ -7,6 +7,7 @@ use tokio::{fs, pin, sync::mpsc::{self, UnboundedReceiver}}; use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt}; use tracing::error; use yazi_plugin::isolate; +use yazi_scheduler::WATCHER; use yazi_shared::fs::{File, FilesOp, Url}; use super::Linked; @@ -25,9 +26,7 @@ impl Watcher { { let tx = tx.clone(); move |res: Result| { - let Ok(event) = res else { - return; - }; + let Ok(event) = res else { return }; match event.kind { EventKind::Create(_) => {} @@ -143,17 +142,15 @@ impl Watcher { async fn on_changed(rx: UnboundedReceiver) { // TODO: revert this once a new notification is implemented - let rx = UnboundedReceiverStream::new(rx).chunks_timeout(100, Duration::from_millis(20)); + let rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(50)); pin!(rx); while let Some(urls) = rx.next().await { - let urls: HashSet<_> = urls.into_iter().collect(); + let _permit = WATCHER.acquire().await.unwrap(); let mut reload = Vec::with_capacity(urls.len()); - for u in urls { - let Some(parent) = u.parent_url() else { - continue; - }; + for u in urls.into_iter().collect::>() { + let Some(parent) = u.parent_url() else { continue }; let Ok(file) = File::from(u.clone()).await else { FilesOp::Deleting(parent, vec![u]).emit(); diff --git a/yazi-core/src/manager/yanked.rs b/yazi-core/src/manager/yanked.rs index 438fed3d..8a33ec19 100644 --- a/yazi-core/src/manager/yanked.rs +++ b/yazi-core/src/manager/yanked.rs @@ -1,6 +1,6 @@ -use std::{collections::HashSet, ops::Deref}; +use std::{collections::HashSet, ops::{Deref, DerefMut}}; -use yazi_shared::fs::Url; +use yazi_shared::fs::{FilesOp, Url}; #[derive(Default)] pub struct Yanked { @@ -13,3 +13,22 @@ impl Deref for Yanked { fn deref(&self) -> &Self::Target { &self.urls } } + +impl DerefMut for Yanked { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.urls } +} + +impl Yanked { + pub fn apply_op(&mut self, op: &FilesOp) { + let (removal, addition) = match op { + FilesOp::Deleting(_, urls) => (urls.iter().collect(), vec![]), + FilesOp::Updating(_, urls) | FilesOp::Upserting(_, urls) => { + urls.iter().filter(|(u, _)| self.contains(u)).map(|(u, f)| (u, f.url())).unzip() + } + _ => (vec![], vec![]), + }; + + self.urls.retain(|u| !removal.contains(&u)); + self.urls.extend(addition); + } +} diff --git a/yazi-core/src/tab/commands/filter.rs b/yazi-core/src/tab/commands/filter.rs index 718a0828..56000b00 100644 --- a/yazi-core/src/tab/commands/filter.rs +++ b/yazi-core/src/tab/commands/filter.rs @@ -36,9 +36,7 @@ impl Tab { while let Some(result) = rx.next().await { let done = result.is_ok(); - let (Ok(s) | Err(InputError::Typed(s))) = result else { - continue; - }; + let (Ok(s) | Err(InputError::Typed(s))) = result else { continue }; emit!(Call( Cmd::args("filter_do", vec![s]) diff --git a/yazi-core/src/tab/commands/jump.rs b/yazi-core/src/tab/commands/jump.rs index e4df1ff2..470c0317 100644 --- a/yazi-core/src/tab/commands/jump.rs +++ b/yazi-core/src/tab/commands/jump.rs @@ -1,6 +1,6 @@ use yazi_plugin::external::{self, FzfOpt, ZoxideOpt}; use yazi_proxy::{AppProxy, TabProxy}; -use yazi_scheduler::BLOCKER; +use yazi_scheduler::HIDER; use yazi_shared::{event::Cmd, fs::ends_with_slash, Defer}; use crate::tab::Tab; @@ -37,7 +37,7 @@ impl Tab { let cwd = self.current.cwd.clone(); tokio::spawn(async move { - let _guard = BLOCKER.acquire().await.unwrap(); + let _permit = HIDER.acquire().await.unwrap(); let _defer = Defer::new(AppProxy::resume); AppProxy::stop().await; diff --git a/yazi-core/src/tab/selected.rs b/yazi-core/src/tab/selected.rs index 18e56f66..8e0f2117 100644 --- a/yazi-core/src/tab/selected.rs +++ b/yazi-core/src/tab/selected.rs @@ -1,6 +1,6 @@ use std::{collections::{HashMap, HashSet}, ops::Deref}; -use yazi_shared::fs::Url; +use yazi_shared::fs::{FilesOp, Url}; #[derive(Default)] pub struct Selected { @@ -63,27 +63,27 @@ impl Selected { #[inline] pub fn remove(&mut self, url: &Url) -> bool { self.remove_same(&[url]) == 1 } - pub fn remove_many(&mut self, urls: &[&Url], same: bool) -> usize { + pub fn remove_many(&mut self, urls: &[impl AsRef], same: bool) -> usize { if same { return self.remove_same(urls); } let mut grouped: HashMap<_, Vec<_>> = Default::default(); - for &u in urls { - if let Some(p) = u.parent_url() { + for u in urls { + if let Some(p) = u.as_ref().parent_url() { grouped.entry(p).or_default().push(u); } } grouped.into_values().map(|v| self.remove_same(&v)).sum() } - fn remove_same(&mut self, urls: &[&Url]) -> usize { - let count = urls.iter().map(|&u| self.inner.remove(u)).filter(|&b| b).count(); + fn remove_same(&mut self, urls: &[impl AsRef]) -> usize { + let count = urls.iter().map(|u| self.inner.remove(u.as_ref())).filter(|&b| b).count(); if count == 0 { return 0; } - let mut parent = urls[0].parent_url(); + let mut parent = urls[0].as_ref().parent_url(); while let Some(u) = parent { let n = self.parents.get_mut(&u).unwrap(); @@ -101,6 +101,23 @@ impl Selected { self.inner.clear(); self.parents.clear(); } + + pub fn apply_op(&mut self, op: &FilesOp) { + let (removal, addition) = match op { + FilesOp::Deleting(_, urls) => (urls.iter().collect(), vec![]), + FilesOp::Updating(_, urls) | FilesOp::Upserting(_, urls) => { + urls.iter().filter(|(u, _)| self.contains(u)).map(|(u, f)| (u, &f.url)).unzip() + } + _ => (vec![], vec![]), + }; + + if !removal.is_empty() { + self.remove_many(&removal, !op.url().is_search()); + } + if !addition.is_empty() { + self.add_many(&addition, !op.url().is_search()); + } + } } #[cfg(test)] diff --git a/yazi-core/src/tasks/commands/cancel.rs b/yazi-core/src/tasks/commands/cancel.rs index 815b58cd..e8c9aca6 100644 --- a/yazi-core/src/tasks/commands/cancel.rs +++ b/yazi-core/src/tasks/commands/cancel.rs @@ -4,7 +4,7 @@ use crate::tasks::Tasks; impl Tasks { pub fn cancel(&mut self, _: Cmd) { - let id = self.scheduler.running.lock().get_id(self.cursor); + let id = self.scheduler.ongoing.lock().get_id(self.cursor); if id.map(|id| self.scheduler.cancel(id)) != Some(true) { return; } diff --git a/yazi-core/src/tasks/commands/inspect.rs b/yazi-core/src/tasks/commands/inspect.rs index d75bfcb2..e1e0ff5a 100644 --- a/yazi-core/src/tasks/commands/inspect.rs +++ b/yazi-core/src/tasks/commands/inspect.rs @@ -3,25 +3,25 @@ use std::io::{stdout, Write}; use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time}; use yazi_proxy::AppProxy; -use yazi_scheduler::BLOCKER; +use yazi_scheduler::HIDER; use yazi_shared::{event::Cmd, term::Term, Defer}; use crate::tasks::Tasks; impl Tasks { pub fn inspect(&self, _: Cmd) { - let Some(id) = self.scheduler.running.lock().get_id(self.cursor) else { + let Some(id) = self.scheduler.ongoing.lock().get_id(self.cursor) else { return; }; let scheduler = self.scheduler.clone(); tokio::spawn(async move { - let _guard = BLOCKER.acquire().await.unwrap(); + let _permit = HIDER.acquire().await.unwrap(); let (tx, mut rx) = mpsc::unbounded_channel(); let buffered = { - let mut running = scheduler.running.lock(); - let Some(task) = running.get_mut(id) else { return }; + let mut ongoing = scheduler.ongoing.lock(); + let Some(task) = ongoing.get_mut(id) else { return }; task.logger = Some(tx); task.logs.clone() @@ -47,7 +47,7 @@ impl Tasks { stdout.write_all(b"\r\n").ok(); } _ = time::sleep(time::Duration::from_millis(500)) => { - if scheduler.running.lock().get(id).is_none() { + if scheduler.ongoing.lock().get(id).is_none() { stdout().write_all(b"Task finished, press `q` to quit\r\n").ok(); break; } @@ -61,7 +61,7 @@ impl Tasks { } } - if let Some(task) = scheduler.running.lock().get_mut(id) { + if let Some(task) = scheduler.ongoing.lock().get_mut(id) { task.logger = None; } while answer != b'q' { diff --git a/yazi-core/src/tasks/progress.rs b/yazi-core/src/tasks/progress.rs index f14eabd6..e62899a2 100644 --- a/yazi-core/src/tasks/progress.rs +++ b/yazi-core/src/tasks/progress.rs @@ -1,5 +1,5 @@ use serde::Serialize; -use yazi_scheduler::Running; +use yazi_scheduler::Ongoing; #[derive(Clone, Copy, Default, Eq, PartialEq, Serialize)] pub struct TasksProgress { @@ -11,14 +11,14 @@ pub struct TasksProgress { pub processed: u64, } -impl From<&Running> for TasksProgress { - fn from(running: &Running) -> Self { +impl From<&Ongoing> for TasksProgress { + fn from(ongoing: &Ongoing) -> Self { let mut progress = Self::default(); - if running.is_empty() { + if ongoing.is_empty() { return progress; } - for task in running.values() { + for task in ongoing.values() { progress.total += task.total; progress.succ += task.succ; progress.fail += task.fail; diff --git a/yazi-core/src/tasks/tasks.rs b/yazi-core/src/tasks/tasks.rs index 5d8d20ae..f2684c1b 100644 --- a/yazi-core/src/tasks/tasks.rs +++ b/yazi-core/src/tasks/tasks.rs @@ -29,13 +29,13 @@ impl Tasks { summaries: Default::default(), }; - let running = tasks.scheduler.running.clone(); + let ongoing = tasks.scheduler.ongoing.clone(); tokio::spawn(async move { let mut last = TasksProgress::default(); loop { sleep(Duration::from_millis(500)).await; - let new = TasksProgress::from(&*running.lock()); + let new = TasksProgress::from(&*ongoing.lock()); if last != new { last = new; emit!(Call(Cmd::new("update_progress").with_data(new), Layer::App)); @@ -52,8 +52,8 @@ impl Tasks { } pub fn paginate(&self) -> Vec { - let running = self.scheduler.running.lock(); - running.values().take(Self::limit()).map(Into::into).collect() + let ongoing = self.scheduler.ongoing.lock(); + ongoing.values().take(Self::limit()).map(Into::into).collect() } pub fn file_open(&self, hovered: &Url, targets: &[(Url, String)]) { @@ -78,10 +78,10 @@ impl Tasks { } } - pub fn file_cut(&self, src: &HashSet, dest: &Url, force: bool) { - for u in src { + pub fn file_cut(&self, src: &[&Url], dest: &Url, force: bool) { + for &u in src { let to = dest.join(u.file_name().unwrap()); - if force && u == &to { + if force && *u == to { debug!("file_cut: same file, skipping {:?}", to); } else { self.scheduler.file_cut(u.clone(), to, force); @@ -89,10 +89,10 @@ impl Tasks { } } - pub fn file_copy(&self, src: &HashSet, dest: &Url, force: bool, follow: bool) { - for u in src { + pub fn file_copy(&self, src: &[&Url], dest: &Url, force: bool, follow: bool) { + for &u in src { let to = dest.join(u.file_name().unwrap()); - if force && u == &to { + if force && *u == to { debug!("file_copy: same file, skipping {:?}", to); } else { self.scheduler.file_copy(u.clone(), to, force, follow); @@ -219,5 +219,5 @@ impl Tasks { impl Tasks { #[inline] - pub fn len(&self) -> usize { self.scheduler.running.lock().len() } + pub fn len(&self) -> usize { self.scheduler.ongoing.lock().len() } } diff --git a/yazi-fm/src/components/progress.rs b/yazi-fm/src/components/progress.rs index ce8d6d50..39de994e 100644 --- a/yazi-fm/src/components/progress.rs +++ b/yazi-fm/src/components/progress.rs @@ -14,9 +14,7 @@ impl Progress { let mut f = || { let comp: Table = LUA.globals().raw_get("Progress")?; for widget in comp.call_method::<_, Vec>("partial_render", ())? { - let Some(w) = cast_to_renderable(widget) else { - continue; - }; + let Some(w) = cast_to_renderable(widget) else { continue }; let area = w.area(); w.render(buf); diff --git a/yazi-fm/src/signals.rs b/yazi-fm/src/signals.rs index a1327e01..214e6ee5 100644 --- a/yazi-fm/src/signals.rs +++ b/yazi-fm/src/signals.rs @@ -44,7 +44,7 @@ impl Signals { fn spawn_system_task(&self) -> Result> { use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM}; use yazi_proxy::AppProxy; - use yazi_scheduler::BLOCKER; + use yazi_scheduler::HIDER; let mut signals = signal_hook_tokio::Signals::new([ // Terminating signals @@ -56,7 +56,7 @@ impl Signals { let tx = self.tx.clone(); Ok(tokio::spawn(async move { while let Some(signal) = signals.next().await { - if BLOCKER.try_acquire().is_err() { + if HIDER.try_acquire().is_err() { continue; } diff --git a/yazi-fm/src/which/layout.rs b/yazi-fm/src/which/layout.rs index 26aed46b..a284e1e8 100644 --- a/yazi-fm/src/which/layout.rs +++ b/yazi-fm/src/which/layout.rs @@ -39,7 +39,7 @@ impl Widget for Which<'_> { let chunks = { use Constraint::*; layout::Layout::horizontal(match cols { - 1 => &[Ratio(1, 1)] as &[Constraint], + 1 => &[Ratio(1, 1)][..], 2 => &[Ratio(1, 2), Ratio(1, 2)], _ => &[Ratio(1, 3), Ratio(1, 3), Ratio(1, 3)], }) diff --git a/yazi-scheduler/src/blocker.rs b/yazi-scheduler/src/blocker.rs deleted file mode 100644 index ecd429b5..00000000 --- a/yazi-scheduler/src/blocker.rs +++ /dev/null @@ -1,6 +0,0 @@ -use tokio::sync::Semaphore; -use yazi_shared::RoCell; - -pub static BLOCKER: RoCell = RoCell::new(); - -pub(super) fn init_blocker() { BLOCKER.init(Semaphore::new(1)) } diff --git a/yazi-scheduler/src/file/file.rs b/yazi-scheduler/src/file/file.rs index dec74cf8..4a9fb53c 100644 --- a/yazi-scheduler/src/file/file.rs +++ b/yazi-scheduler/src/file/file.rs @@ -5,7 +5,7 @@ use futures::{future::BoxFuture, FutureExt}; use tokio::{fs, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc}; use tracing::warn; use yazi_config::TASKS; -use yazi_shared::fs::{calculate_size, copy_with_progress, path_relative_to, Url}; +use yazi_shared::fs::{accessible, calculate_size, copy_with_progress, path_relative_to, Url}; use super::{FileOp, FileOpDelete, FileOpLink, FileOpPaste, FileOpTrash}; use crate::{TaskOp, TaskProg, LOW, NORMAL}; @@ -107,7 +107,7 @@ impl File { } FileOp::Delete(task) => { if let Err(e) = fs::remove_file(&task.target).await { - if e.kind() != NotFound && fs::symlink_metadata(&task.target).await.is_ok() { + if e.kind() != NotFound && accessible(&task.target).await { self.fail(task.id, format!("Delete task failed: {:?}, {e}", task))?; Err(e)? } @@ -225,16 +225,10 @@ impl File { let mut dirs = VecDeque::from([task.target]); while let Some(target) = dirs.pop_front() { - let mut it = match fs::read_dir(target).await { - Ok(it) => it, - Err(_) => continue, - }; + let Ok(mut it) = fs::read_dir(target).await else { continue }; while let Ok(Some(entry)) = it.next_entry().await { - let meta = match entry.metadata().await { - Ok(m) => m, - Err(_) => continue, - }; + let Ok(meta) = entry.metadata().await else { continue }; if meta.is_dir() { dirs.push_front(Url::from(entry.path())); diff --git a/yazi-scheduler/src/lib.rs b/yazi-scheduler/src/lib.rs index 9c807796..1d74cb35 100644 --- a/yazi-scheduler/src/lib.rs +++ b/yazi-scheduler/src/lib.rs @@ -1,23 +1,23 @@ #![allow(clippy::option_map_unit_fn, clippy::unit_arg)] -mod blocker; mod file; +mod ongoing; mod op; mod plugin; mod preload; mod process; -mod running; mod scheduler; +mod semaphore; mod task; -pub use blocker::*; +pub use ongoing::*; pub use op::*; -pub use running::*; pub use scheduler::*; +pub use semaphore::*; pub use task::*; const LOW: u8 = yazi_config::Priority::Low as u8; const NORMAL: u8 = yazi_config::Priority::Normal as u8; const HIGH: u8 = yazi_config::Priority::High as u8; -pub fn init() { init_blocker(); } +pub fn init() { init_semaphore(); } diff --git a/yazi-scheduler/src/running.rs b/yazi-scheduler/src/ongoing.rs similarity index 98% rename from yazi-scheduler/src/running.rs rename to yazi-scheduler/src/ongoing.rs index 86298d3c..21dbbd3d 100644 --- a/yazi-scheduler/src/running.rs +++ b/yazi-scheduler/src/ongoing.rs @@ -7,14 +7,14 @@ use super::{Task, TaskStage}; use crate::TaskKind; #[derive(Default)] -pub struct Running { +pub struct Ongoing { incr: usize, pub(super) hooks: HashMap BoxFuture<'static, ()>) + Send + Sync>>, pub(super) all: HashMap, } -impl Running { +impl Ongoing { pub fn add(&mut self, kind: TaskKind, name: String) -> usize { self.incr += 1; self.all.insert(self.incr, Task::new(self.incr, kind, name)); diff --git a/yazi-scheduler/src/process/process.rs b/yazi-scheduler/src/process/process.rs index 3d5bf253..02e26aa6 100644 --- a/yazi-scheduler/src/process/process.rs +++ b/yazi-scheduler/src/process/process.rs @@ -5,7 +5,7 @@ use yazi_proxy::AppProxy; use yazi_shared::Defer; use super::ProcessOpOpen; -use crate::{TaskProg, BLOCKER}; +use crate::{TaskProg, HIDER}; pub struct Process { prog: mpsc::UnboundedSender, @@ -63,7 +63,7 @@ impl Process { } async fn open_block(&self, task: ProcessOpOpen) -> Result<()> { - let _guard = BLOCKER.acquire().await.unwrap(); + let _permit = HIDER.acquire().await.unwrap(); let _defer = Defer::new(AppProxy::resume); AppProxy::stop().await; diff --git a/yazi-scheduler/src/scheduler.rs b/yazi-scheduler/src/scheduler.rs index fcca3ddb..2fdb3764 100644 --- a/yazi-scheduler/src/scheduler.rs +++ b/yazi-scheduler/src/scheduler.rs @@ -7,7 +7,7 @@ use yazi_config::{open::Opener, plugin::PluginRule, TASKS}; use yazi_plugin::ValueSendable; use yazi_shared::{fs::{unique_path, Url}, Throttle}; -use super::{Running, TaskProg, TaskStage}; +use super::{Ongoing, TaskProg, TaskStage}; use crate::{file::{File, FileOpDelete, FileOpLink, FileOpPaste, FileOpTrash}, plugin::{Plugin, PluginOpEntry}, preload::{Preload, PreloadOpRule, PreloadOpSize}, process::{Process, ProcessOpOpen}, TaskKind, TaskOp, HIGH, LOW, NORMAL}; pub struct Scheduler { @@ -18,8 +18,7 @@ pub struct Scheduler { micro: async_priority_channel::Sender, u8>, prog: mpsc::UnboundedSender, - // FIXME - pub running: Arc>, + pub ongoing: Arc>, } impl Scheduler { @@ -36,7 +35,7 @@ impl Scheduler { micro: micro_tx, prog: prog_tx, - running: Default::default(), + ongoing: Default::default(), }; for _ in 0..TASKS.micro_workers { @@ -69,7 +68,7 @@ impl Scheduler { let preload = self.preload.clone(); let prog = self.prog.clone(); - let running = self.running.clone(); + let ongoing = self.ongoing.clone(); tokio::spawn(async move { loop { @@ -79,7 +78,7 @@ impl Scheduler { } Ok((op, _)) = macro_.recv() => { let id = op.id(); - if !running.lock().exists(id) { + if !ongoing.lock().exists(id) { continue; } @@ -100,36 +99,36 @@ impl Scheduler { fn progress(&self, mut rx: UnboundedReceiver) { let micro = self.micro.clone(); - let running = self.running.clone(); + let ongoing = self.ongoing.clone(); tokio::spawn(async move { while let Some(op) = rx.recv().await { match op { TaskProg::New(id, size) => { - if let Some(task) = running.lock().get_mut(id) { + if let Some(task) = ongoing.lock().get_mut(id) { task.total += 1; task.found += size; } } TaskProg::Adv(id, succ, processed) => { - let mut running = running.lock(); - if let Some(task) = running.get_mut(id) { + let mut ongoing = ongoing.lock(); + if let Some(task) = ongoing.get_mut(id) { task.succ += succ; task.processed += processed; } if succ > 0 { - if let Some(fut) = running.try_remove(id, TaskStage::Pending) { + if let Some(fut) = ongoing.try_remove(id, TaskStage::Pending) { micro.try_send(fut, NORMAL).ok(); } } } TaskProg::Succ(id) => { - if let Some(fut) = running.lock().try_remove(id, TaskStage::Dispatched) { + if let Some(fut) = ongoing.lock().try_remove(id, TaskStage::Dispatched) { micro.try_send(fut, NORMAL).ok(); } } TaskProg::Fail(id, reason) => { - if let Some(task) = running.lock().get_mut(id) { + if let Some(task) = ongoing.lock().get_mut(id) { task.fail += 1; task.logs.push_str(&reason); task.logs.push('\n'); @@ -140,7 +139,7 @@ impl Scheduler { } } TaskProg::Log(id, line) => { - if let Some(task) = running.lock().get_mut(id) { + if let Some(task) = ongoing.lock().get_mut(id) { task.logs.push_str(&line); task.logs.push('\n'); @@ -155,29 +154,29 @@ impl Scheduler { } pub fn cancel(&self, id: usize) -> bool { - let mut running = self.running.lock(); - let b = running.all.remove(&id).is_some(); + let mut ongoing = self.ongoing.lock(); + let b = ongoing.all.remove(&id).is_some(); - if let Some(hook) = running.hooks.remove(&id) { + if let Some(hook) = ongoing.hooks.remove(&id) { self.micro.try_send(hook(true), HIGH).ok(); } b } pub fn file_cut(&self, from: Url, mut to: Url, force: bool) { - let mut running = self.running.lock(); - let id = running.add(TaskKind::User, format!("Cut {:?} to {:?}", from, to)); + let mut ongoing = self.ongoing.lock(); + let id = ongoing.add(TaskKind::User, format!("Cut {:?} to {:?}", from, to)); - running.hooks.insert(id, { + ongoing.hooks.insert(id, { let from = from.clone(); - let running = self.running.clone(); + let ongoing = self.ongoing.clone(); Box::new(move |canceled: bool| { async move { if !canceled { File::remove_empty_dirs(&from).await; } - running.lock().try_remove(id, TaskStage::Hooked); + ongoing.lock().try_remove(id, TaskStage::Hooked); } .boxed() }) @@ -198,7 +197,7 @@ impl Scheduler { pub fn file_copy(&self, from: Url, mut to: Url, force: bool, follow: bool) { let name = format!("Copy {:?} to {:?}", from, to); - let id = self.running.lock().add(TaskKind::User, name); + let id = self.ongoing.lock().add(TaskKind::User, name); let file = self.file.clone(); _ = self.micro.try_send( @@ -215,7 +214,7 @@ impl Scheduler { pub fn file_link(&self, from: Url, mut to: Url, relative: bool, force: bool) { let name = format!("Link {from:?} to {to:?}"); - let id = self.running.lock().add(TaskKind::User, name); + let id = self.ongoing.lock().add(TaskKind::User, name); let file = self.file.clone(); _ = self.micro.try_send( @@ -234,19 +233,19 @@ impl Scheduler { } pub fn file_delete(&self, target: Url) { - let mut running = self.running.lock(); - let id = running.add(TaskKind::User, format!("Delete {:?}", target)); + let mut ongoing = self.ongoing.lock(); + let id = ongoing.add(TaskKind::User, format!("Delete {:?}", target)); - running.hooks.insert(id, { + ongoing.hooks.insert(id, { let target = target.clone(); - let running = self.running.clone(); + let ongoing = self.ongoing.clone(); Box::new(move |canceled: bool| { async move { if !canceled { fs::remove_dir_all(target).await.ok(); } - running.lock().try_remove(id, TaskStage::Hooked); + ongoing.lock().try_remove(id, TaskStage::Hooked); } .boxed() }) @@ -264,7 +263,7 @@ impl Scheduler { pub fn file_trash(&self, target: Url) { let name = format!("Trash {:?}", target); - let id = self.running.lock().add(TaskKind::User, name); + let id = self.ongoing.lock().add(TaskKind::User, name); let file = self.file.clone(); _ = self.micro.try_send( @@ -277,7 +276,7 @@ impl Scheduler { } pub fn plugin_micro(&self, name: String, args: Vec) { - let id = self.running.lock().add(TaskKind::User, format!("Run micro plugin `{name}`")); + let id = self.ongoing.lock().add(TaskKind::User, format!("Run micro plugin `{name}`")); let plugin = self.plugin.clone(); _ = self.micro.try_send( @@ -290,13 +289,13 @@ impl Scheduler { } pub fn plugin_macro(&self, name: String, args: Vec) { - let id = self.running.lock().add(TaskKind::User, format!("Run macro plugin `{name}`")); + let id = self.ongoing.lock().add(TaskKind::User, format!("Run macro plugin `{name}`")); self.plugin.macro_(PluginOpEntry { id, name, args }).ok(); } pub fn preload_paged(&self, rule: &PluginRule, targets: Vec<&yazi_shared::fs::File>) { - let id = self.running.lock().add( + let id = self.ongoing.lock().add( TaskKind::Preload, format!("Run preloader `{}` with {} target(s)", rule.cmd.name, targets.len()), ); @@ -315,10 +314,10 @@ impl Scheduler { pub fn preload_size(&self, targets: Vec<&Url>) { let throttle = Arc::new(Throttle::new(targets.len(), Duration::from_millis(300))); - let mut running = self.running.lock(); + let mut ongoing = self.ongoing.lock(); for target in targets { - let id = running.add(TaskKind::Preload, format!("Calculate the size of {:?}", target)); + let id = ongoing.add(TaskKind::Preload, format!("Calculate the size of {:?}", target)); let target = target.clone(); let throttle = throttle.clone(); @@ -340,18 +339,18 @@ impl Scheduler { if args.is_empty() { s } else { format!("{s} with `{args}`") } }; - let mut running = self.running.lock(); - let id = running.add(TaskKind::User, name); + let mut ongoing = self.ongoing.lock(); + let id = ongoing.add(TaskKind::User, name); let (cancel_tx, mut cancel_rx) = oneshot::channel(); - running.hooks.insert(id, { - let running = self.running.clone(); + ongoing.hooks.insert(id, { + let ongoing = self.ongoing.clone(); Box::new(move |canceled: bool| { async move { if canceled { cancel_rx.close(); } - running.lock().try_remove(id, TaskStage::Hooked); + ongoing.lock().try_remove(id, TaskStage::Hooked); } .boxed() }) diff --git a/yazi-scheduler/src/semaphore.rs b/yazi-scheduler/src/semaphore.rs new file mode 100644 index 00000000..c98d689c --- /dev/null +++ b/yazi-scheduler/src/semaphore.rs @@ -0,0 +1,11 @@ +use tokio::sync::Semaphore; +use yazi_shared::RoCell; + +pub static HIDER: RoCell = RoCell::new(); + +pub static WATCHER: RoCell = RoCell::new(); + +pub(super) fn init_semaphore() { + HIDER.init(Semaphore::new(1)); + WATCHER.init(Semaphore::new(1)); +} diff --git a/yazi-shared/src/fs/fns.rs b/yazi-shared/src/fs/fns.rs index 94cb6e3d..9811a5bd 100644 --- a/yazi-shared/src/fs/fns.rs +++ b/yazi-shared/src/fs/fns.rs @@ -3,27 +3,26 @@ use std::{collections::VecDeque, path::{Path, PathBuf}}; use anyhow::Result; use tokio::{fs, io, select, sync::{mpsc, oneshot}, time}; +pub async fn accessible(path: &Path) -> bool { + match fs::symlink_metadata(path).await { + Ok(_) => true, + Err(e) => e.kind() != io::ErrorKind::NotFound, + } +} + pub async fn calculate_size(path: &Path) -> u64 { let mut total = 0; let mut stack = VecDeque::from([path.to_path_buf()]); while let Some(path) = stack.pop_front() { - let Ok(meta) = fs::symlink_metadata(&path).await else { - continue; - }; - + let Ok(meta) = fs::symlink_metadata(&path).await else { continue }; if !meta.is_dir() { total += meta.len(); continue; } - let Ok(mut it) = fs::read_dir(path).await else { - continue; - }; - + let Ok(mut it) = fs::read_dir(path).await else { continue }; while let Ok(Some(entry)) = it.next_entry().await { - let Ok(meta) = entry.metadata().await else { - continue; - }; + let Ok(meta) = entry.metadata().await else { continue }; if meta.is_dir() { stack.push_back(entry.path()); diff --git a/yazi-shared/src/fs/path.rs b/yazi-shared/src/fs/path.rs index da57046f..155a120b 100644 --- a/yazi-shared/src/fs/path.rs +++ b/yazi-shared/src/fs/path.rs @@ -1,7 +1,6 @@ use std::{borrow::Cow, env, ffi::OsString, path::{Component, Path, PathBuf, MAIN_SEPARATOR}}; -use tokio::fs; - +use super::accessible; use crate::fs::Url; #[inline] @@ -68,7 +67,7 @@ pub async fn unique_path(mut p: Url) -> Url { .unwrap_or_default(); let mut i = 0; - while fs::symlink_metadata(&p).await.is_ok() { + while accessible(&p).await { i += 1; let mut name = OsString::with_capacity(stem.len() + ext.len() + 5);