mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
schedule rename files
This commit is contained in:
parent
6a70ea3c57
commit
82056280b3
7 changed files with 94 additions and 66 deletions
|
|
@ -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(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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?;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue