schedule rename files

This commit is contained in:
TD-Sky 2025-05-01 17:39:54 +08:00
parent 6a70ea3c57
commit 82056280b3
7 changed files with 94 additions and 66 deletions

View file

@ -1,20 +1,26 @@
use std::{borrow::Cow, collections::HashMap, ffi::{OsStr, OsString}, io::{Read, Write}, path::PathBuf};
use std::{
borrow::Cow,
collections::HashMap,
ffi::{OsStr, OsString},
path::PathBuf,
sync::Arc,
};
use anyhow::{Result, anyhow};
use crossterm::{execute, style::Print};
use anyhow::Result;
use scopeguard::defer;
use tokio::{fs::{self, OpenOptions}, io::AsyncWriteExt};
use yazi_config::YAZI;
use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp, max_common_root, maybe_exists, paths_to_same_file};
use yazi_proxy::{AppProxy, HIDER, TasksProxy, WATCHER};
use yazi_shared::{terminal_clear, url::Url};
use yazi_term::tty::TTY;
use tokio::{
fs::{self, OpenOptions},
io::AsyncWriteExt,
};
use yazi_config::{YAZI, popup::ConfirmCfg};
use yazi_fs::max_common_root;
use yazi_proxy::{AppProxy, ConfirmProxy, TasksProxy};
use yazi_scheduler::Scheduler;
use crate::mgr::Mgr;
impl Mgr {
pub(super) fn bulk_rename(&self) {
pub(super) fn bulk_rename(&self, sched: Arc<Scheduler>) {
let Some(opener) = YAZI.opener.block(YAZI.open.all("bulk-rename.txt", "text/plain")) else {
return AppProxy::notify_warn("Bulk rename", "No text opener found");
};
@ -37,21 +43,25 @@ impl Mgr {
.await?;
defer! { tokio::spawn(fs::remove_file(tmp.clone())); }
TasksProxy::process_exec(Cow::Borrowed(opener), cwd, vec![
OsString::new(),
tmp.to_owned().into(),
])
TasksProxy::process_exec(
Cow::Borrowed(opener),
cwd,
vec![OsString::new(), tmp.to_owned().into()],
)
.await;
let _permit = HIDER.acquire().await.unwrap();
let new: Vec<_> =
fs::read_to_string(&tmp).await?.lines().take(old.len()).map(PathBuf::from).collect();
Self::bulk_rename_do(root, old, new).await
Self::bulk_rename_do(root, old, new, sched).await
});
}
async fn bulk_rename_do(root: PathBuf, old: Vec<PathBuf>, new: Vec<PathBuf>) -> Result<()> {
async fn bulk_rename_do(
root: PathBuf,
old: Vec<PathBuf>,
new: Vec<PathBuf>,
sched: Arc<Scheduler>,
) -> Result<()> {
if old.len() != new.len() {
AppProxy::notify_error(
"Bulk rename",
@ -74,46 +84,10 @@ impl Mgr {
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 maybe_exists(&new).await && !paths_to_same_file(&old, &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")));
}
for (old, new) in todo {
sched.file_rename_at(&root, &old, &new);
}
if !succeeded.is_empty() {
Pubsub::pub_from_bulk(succeeded.iter().map(|(o, n)| (o, &n.url)).collect());
FilesOp::rename(succeeded);
}
drop(permit);
if !failed.is_empty() {
Self::output_failed(failed).await?;
}
Ok(())
}
async fn output_failed(failed: Vec<(PathBuf, PathBuf, anyhow::Error)>) -> Result<()> {
let mut stdout = TTY.lockout();
terminal_clear(&mut *stdout)?;
writeln!(stdout, "Failed to rename:")?;
for (old, new, err) in failed {
writeln!(stdout, "{} -> {}: {err}", old.display(), new.display())?;
}
writeln!(stdout, "\nPress ENTER to exit")?;
stdout.flush()?;
TTY.reader().read_exact(&mut [0])?;
Ok(())
}

View file

@ -8,7 +8,7 @@ use yazi_fs::{File, FilesOp, maybe_exists, ok_or_not_found, paths_to_same_file,
use yazi_proxy::{ConfirmProxy, InputProxy, TabProxy, WATCHER};
use yazi_shared::{Id, event::CmdCow, url::{Url, UrnBuf}};
use crate::mgr::Mgr;
use crate::{mgr::Mgr, tasks::Tasks};
struct Opt {
hovered: bool,
@ -30,11 +30,11 @@ impl From<CmdCow> for Opt {
impl Mgr {
#[yazi_codegen::command]
pub fn rename(&mut self, opt: Opt) {
pub fn rename(&mut self, opt: Opt, tasks: &Tasks) {
if !self.active_mut().try_escape_visual() {
return;
} else if !opt.hovered && !self.active().selected.is_empty() {
return self.bulk_rename();
return self.bulk_rename(tasks.scheduler.clone());
}
let Some(hovered) = self.hovered() else { return };

View file

@ -10,7 +10,7 @@ use yazi_shared::event::Cmd;
use super::{TASKS_BORDER, TASKS_PADDING, TASKS_PERCENT, TasksProgress};
pub struct Tasks {
pub(super) scheduler: Arc<Scheduler>,
pub scheduler: Arc<Scheduler>,
handle: JoinHandle<()>,
pub visible: bool,

View file

@ -115,7 +115,7 @@ impl<'a> Executor<'a> {
on!(MGR, remove, &self.app.cx.tasks);
on!(MGR, remove_do, &self.app.cx.tasks);
on!(MGR, create);
on!(MGR, rename);
on!(MGR, rename, &self.app.cx.tasks);
on!(ACTIVE, copy);
on!(ACTIVE, shell);
on!(ACTIVE, hidden);

View file

@ -4,10 +4,15 @@ use anyhow::{Result, anyhow};
use tokio::{fs::{self, DirEntry}, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc};
use tracing::warn;
use yazi_config::YAZI;
use yazi_fs::{SizeCalculator, cha::Cha, copy_with_progress, maybe_exists, ok_or_not_found, path_relative_to, skip_path};
use yazi_fs::{
SizeCalculator, cha::Cha, copy_with_progress, maybe_exists, ok_or_not_found, path_relative_to,
paths_to_same_file, skip_path,
};
use yazi_shared::url::Url;
use super::{FileOp, FileOpDelete, FileOpHardlink, FileOpLink, FileOpPaste, FileOpTrash};
use super::{
FileOp, FileOpDelete, FileOpHardlink, FileOpLink, FileOpPaste, FileOpRename, FileOpTrash,
};
use crate::{LOW, NORMAL, TaskOp, TaskProg};
pub struct File {
@ -146,6 +151,26 @@ impl File {
.await??;
self.prog.send(TaskProg::Adv(task.id, 1, task.length))?;
}
FileOp::Rename(task) => {
if maybe_exists(&task.to).await && !paths_to_same_file(&task.from, &task.to).await {
let e = anyhow!("Destination already exists");
self.fail(task.id, format!("An error occurred while renaming: {e:?}"))?;
return Err(e);
}
if let Err(e) = fs::rename(&task.from, &task.to).await {
self.fail(task.id, format!("An error occurred while renaming: {e}"))?;
return Err(e.into());
}
if yazi_fs::File::from(task.to).await.is_err() {
let e = anyhow!("Failed to retrieve file info");
self.fail(task.id, format!("An error occurred while renaming: {e:?}"))?;
return Err(e);
}
self.prog.send(TaskProg::Adv(task.id, 1, 0))?;
}
}
Ok(())
}
@ -325,6 +350,14 @@ impl File {
self.succ(id)
}
pub async fn rename(&self, task: FileOpRename) -> Result<()> {
let id = task.id;
self.prog.send(TaskProg::New(id, 0))?;
self.queue(FileOp::Rename(task), NORMAL).await?;
self.succ(id)
}
#[inline]
async fn cha(path: &Path, follow: bool) -> io::Result<Cha> {
let meta = fs::symlink_metadata(path).await?;

View file

@ -8,6 +8,7 @@ pub enum FileOp {
Hardlink(FileOpHardlink),
Delete(FileOpDelete),
Trash(FileOpTrash),
Rename(FileOpRename),
}
impl FileOp {
@ -18,6 +19,7 @@ impl FileOp {
Self::Hardlink(op) => op.id,
Self::Delete(op) => op.id,
Self::Trash(op) => op.id,
Self::Rename(op) => op.id,
}
}
}
@ -105,3 +107,10 @@ pub struct FileOpTrash {
pub target: Url,
pub length: u64,
}
// --- Rename
#[derive(Clone, Debug)]
pub struct FileOpRename {
pub id: usize,
pub from: Url,
pub to: Url,
}

View file

@ -1,4 +1,4 @@
use std::{ffi::OsString, future::Future, sync::Arc, time::Duration};
use std::{ffi::OsString, future::Future, path::Path, sync::Arc, time::Duration};
use anyhow::Result;
use futures::{FutureExt, future::BoxFuture};
@ -11,7 +11,7 @@ use yazi_proxy::{MgrProxy, options::{PluginOpt, ProcessExecOpt}};
use yazi_shared::{Throttle, url::Url};
use super::{Ongoing, TaskProg, TaskStage};
use crate::{HIGH, LOW, NORMAL, TaskKind, TaskOp, file::{File, FileOpDelete, FileOpHardlink, FileOpLink, FileOpPaste, FileOpTrash}, plugin::{Plugin, PluginOpEntry}, prework::{Prework, PreworkOpFetch, PreworkOpLoad, PreworkOpSize}, process::{Process, ProcessOpBg, ProcessOpBlock, ProcessOpOrphan}};
use crate::{file::{File, FileOpDelete, FileOpHardlink, FileOpLink, FileOpPaste, FileOpRename, FileOpTrash}, plugin::{Plugin, PluginOpEntry}, prework::{Prework, PreworkOpFetch, PreworkOpLoad, PreworkOpSize}, process::{Process, ProcessOpBg, ProcessOpBlock, ProcessOpOrphan}, TaskKind, TaskOp, HIGH, LOW, NORMAL};
pub struct Scheduler {
pub file: Arc<File>,
@ -209,6 +209,18 @@ impl Scheduler {
})
}
pub fn file_rename_at(&self, root: &Path, old: &Path, new: &Path) {
let id = self.ongoing.lock().add(
TaskKind::User,
format!("Rename at {}: {} -> {} ", root.display(), old.display(), new.display()),
);
let (from, to): (Url, Url) = (root.join(old).into(), root.join(new).into());
let file = self.file.clone();
self.send_micro(id, LOW, async move { file.rename(FileOpRename { id, from, to }).await });
}
pub fn plugin_micro(&self, opt: PluginOpt) {
let id = self.ongoing.lock().add(TaskKind::User, format!("Run micro plugin `{}`", opt.id));