refactor: pull directory traversal out into a function (#3411)

This commit is contained in:
三咲雅 misaki masa 2025-12-07 01:28:45 +08:00 committed by GitHub
parent c569263a50
commit d2e9e72684
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 630 additions and 375 deletions

View file

@ -62,8 +62,10 @@ function Tasks:redraw()
end
function Tasks:icon(snap)
if snap.prog.kind == "FilePaste" then
return snap.name:find("Copy ", 1, true) == 1 and "" or ""
if snap.prog.kind == "FileCopy" then
return ""
elseif snap.prog.kind == "FileCut" then
return ""
elseif snap.prog.kind == "FileDelete" then
return ""
elseif snap.prog.kind == "FileDownload" then
@ -77,7 +79,13 @@ end
function Tasks:progress_redraw(snap, y)
local kind = snap.prog.kind
if kind == "FilePaste" or kind == "FileDelete" or kind == "FileDownload" or kind == "FileUpload" then
if
kind == "FileCopy"
or kind == "FileCut"
or kind == "FileDelete"
or kind == "FileDownload"
or kind == "FileUpload"
then
local percent
if snap.success then
percent = "Cleaning…"

View file

@ -1,16 +1,16 @@
use std::{collections::VecDeque, hash::{BuildHasher, Hash, Hasher}};
use std::hash::{BuildHasher, Hash, Hasher};
use anyhow::{Context, Result, anyhow};
use tokio::{io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc};
use tracing::warn;
use yazi_config::YAZI;
use yazi_fs::{Cwd, FsHash128, FsUrl, cha::Cha, ok_or_not_found, path::{path_relative_to, skip_url}, provider::{Attrs, DirReader, FileHolder, Provider, local::Local}};
use yazi_fs::{Cwd, FsHash128, FsUrl, cha::Cha, ok_or_not_found, path::path_relative_to, provider::{Attrs, FileHolder, Provider, local::Local}};
use yazi_macro::ok_or_not_found;
use yazi_shared::{path::PathCow, timestamp_us, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
use yazi_vfs::{VfsCha, maybe_exists, provider::{self, DirEntry}, unique_name};
use super::{FileInDelete, FileInHardlink, FileInLink, FileInPaste, FileInTrash};
use crate::{LOW, NORMAL, TaskIn, TaskOp, TaskOps, file::{FileInDownload, FileInUpload, FileInUploadDo, FileOutDelete, FileOutDeleteDo, FileOutDownload, FileOutDownloadDo, FileOutHardlink, FileOutHardlinkDo, FileOutLink, FileOutPaste, FileOutPasteDo, FileOutTrash, FileOutUpload, FileOutUploadDo}};
use super::{FileInCopy, FileInDelete, FileInHardlink, FileInLink, FileInTrash};
use crate::{LOW, NORMAL, TaskIn, TaskOp, TaskOps, file::{FileInCut, FileInDownload, FileInUpload, FileOutCopy, FileOutCopyDo, FileOutCut, FileOutCutDo, FileOutDelete, FileOutDeleteDo, FileOutDownload, FileOutDownloadDo, FileOutHardlink, FileOutHardlinkDo, FileOutLink, FileOutTrash, FileOutUpload, FileOutUploadDo}};
pub(crate) struct File {
ops: TaskOps,
@ -25,103 +25,44 @@ impl File {
Self { ops: tx.into(), r#macro: r#macro.clone() }
}
pub(crate) async fn paste(&self, mut task: FileInPaste) -> Result<(), FileOutPaste> {
if task.cut
&& !task.follow
&& ok_or_not_found(provider::rename(&task.from, &task.to).await).is_ok()
{
return Ok(self.ops.out(task.id, FileOutPaste::Succ));
}
pub(crate) async fn copy(&self, task: FileInCopy) -> Result<(), FileOutCopy> {
let id = task.id;
if task.cha.is_none() {
task.cha = Some(Self::cha(&task.from, task.follow, None).await?);
}
let cha = task.cha.unwrap();
if !cha.is_dir() {
let id = task.id;
if cha.is_orphan() || (cha.is_link() && !task.follow) {
self.ops.out(id, FileOutPaste::New(0));
self.queue(task.into_link(), NORMAL);
} else {
self.ops.out(id, FileOutPaste::New(cha.len));
self.queue(task, LOW);
}
return Ok(self.ops.out(id, FileOutPaste::Succ));
}
macro_rules! continue_unless_ok {
($result:expr) => {
match $result {
Ok(v) => v,
Err(e) => {
self.ops.out(task.id, FileOutPaste::Deform(e.to_string()));
continue;
}
}
};
}
let root = &task.to;
let skip = task.from.components().count();
let mut dirs = VecDeque::from([task.from.clone()]);
while let Some(src) = dirs.pop_front() {
let dest = continue_unless_ok!(root.try_join(skip_url(&src, skip)));
continue_unless_ok!(match provider::create_dir(&dest).await {
Err(e) if e.kind() != AlreadyExists => Err(e),
super::traverse::<FileOutCopy, _, _, _, _, _>(
task,
async |dir| match provider::create_dir(dir).await {
Err(e) if e.kind() != AlreadyExists => Err(e)?,
_ => Ok(()),
});
let mut it = continue_unless_ok!(provider::read_dir(&src).await);
while let Ok(Some(entry)) = it.next().await {
let from = entry.url();
let cha = continue_unless_ok!(Self::cha(&from, task.follow, Some(entry)).await);
if cha.is_dir() {
dirs.push_back(from);
continue;
}
let to = continue_unless_ok!(dest.try_join(from.name().unwrap()));
if cha.is_orphan() || (cha.is_link() && !task.follow) {
self.ops.out(task.id, FileOutPaste::New(0));
self.queue(task.spawn(from, to, cha).into_link(), NORMAL);
},
async |task, cha| {
Ok(if cha.is_orphan() || (cha.is_link() && !task.follow) {
self.ops.out(id, FileOutCopy::New(0));
self.queue(task.into_link(), NORMAL);
} else {
self.ops.out(task.id, FileOutPaste::New(cha.len));
self.queue(task.spawn(from, to, cha), LOW);
}
}
}
self.ops.out(id, FileOutCopy::New(cha.len));
self.queue(task, LOW);
})
},
|err| {
self.ops.out(id, FileOutCopy::Deform(err));
},
)
.await?;
Ok(self.ops.out(task.id, FileOutPaste::Succ))
Ok(self.ops.out(id, FileOutCopy::Succ))
}
pub(crate) async fn paste_do(&self, mut task: FileInPaste) -> Result<(), FileOutPasteDo> {
ok_or_not_found!(provider::remove_file(&task.to).await);
pub(crate) async fn copy_do(&self, mut task: FileInCopy) -> Result<(), FileOutCopyDo> {
ok_or_not_found!(provider::remove_file(&task.to).await); // FIXME: integrate into copy_with_progress
let mut it = provider::copy_with_progress(&task.from, &task.to, task.cha.unwrap()).await?;
while let Some(res) = it.recv().await {
match res {
Ok(0) => {
if task.cut {
provider::remove_file(&task.from).await.ok();
}
break;
}
Ok(n) => self.ops.out(task.id, FileOutPasteDo::Adv(n)),
Ok(0) => break,
Ok(n) => self.ops.out(task.id, FileOutCopyDo::Adv(n)),
Err(e) if e.kind() == NotFound => {
let Ok(cha) = Self::cha(&task.from, task.follow, None).await else {
warn!("Paste task partially done: {task:?}");
break;
};
if cha.is_orphan() || (cha.is_link() && !task.follow) {
task.cha = Some(cha);
return Ok(self.queue(task.into_link(), NORMAL));
}
Err(e)?
warn!("Copy task partially done: {task:?}");
break;
}
// Operation not permitted (os error 1)
// Attribute not found (os error 93)
@ -130,13 +71,75 @@ impl File {
&& matches!(e.raw_os_error(), Some(1) | Some(93)) =>
{
task.retry += 1;
self.ops.out(task.id, FileOutPasteDo::Log(format!("Retrying due to error: {e}")));
self.ops.out(task.id, FileOutCopyDo::Log(format!("Retrying due to error: {e}")));
return Ok(self.queue(task, LOW));
}
Err(e) => Err(e)?,
}
}
Ok(self.ops.out(task.id, FileOutPasteDo::Succ))
Ok(self.ops.out(task.id, FileOutCopyDo::Succ))
}
pub(crate) async fn cut(&self, task: FileInCut) -> Result<(), FileOutCut> {
let id = task.id;
if !task.follow && ok_or_not_found(provider::rename(&task.from, &task.to).await).is_ok() {
return Ok(self.ops.out(id, FileOutCut::Succ));
}
super::traverse::<FileOutCut, _, _, _, _, _>(
task,
async |dir| match provider::create_dir(dir).await {
Err(e) if e.kind() != AlreadyExists => Err(e)?,
_ => Ok(()),
},
async |task, cha| {
Ok(if cha.is_orphan() || (cha.is_link() && !task.follow) {
self.ops.out(id, FileOutCut::New(0));
self.queue(task.into_link(), NORMAL);
} else {
self.ops.out(id, FileOutCut::New(cha.len));
self.queue(task, LOW);
})
},
|err| {
self.ops.out(id, FileOutCut::Deform(err));
},
)
.await?;
Ok(self.ops.out(id, FileOutCut::Succ))
}
pub(crate) async fn cut_do(&self, mut task: FileInCut) -> Result<(), FileOutCutDo> {
ok_or_not_found!(provider::remove_file(&task.to).await); // FIXME: integrate into copy_with_progress
let mut it = provider::copy_with_progress(&task.from, &task.to, task.cha.unwrap()).await?;
while let Some(res) = it.recv().await {
match res {
Ok(0) => {
provider::remove_file(&task.from).await.ok();
break;
}
Ok(n) => self.ops.out(task.id, FileOutCutDo::Adv(n)),
Err(e) if e.kind() == NotFound => {
warn!("Cut task partially done: {task:?}");
break;
}
// Operation not permitted (os error 1)
// Attribute not found (os error 93)
Err(e)
if task.retry < YAZI.tasks.bizarre_retry
&& matches!(e.raw_os_error(), Some(1) | Some(93)) =>
{
task.retry += 1;
self.ops.out(task.id, FileOutCutDo::Log(format!("Retrying due to error: {e}")));
return Ok(self.queue(task, LOW));
}
Err(e) => Err(e)?,
}
}
Ok(self.ops.out(task.id, FileOutCutDo::Succ))
}
pub(crate) fn link(&self, task: FileInLink) -> Result<(), FileOutLink> {
@ -174,60 +177,26 @@ impl File {
Ok(self.ops.out(task.id, FileOutLink::Succ))
}
pub(crate) async fn hardlink(&self, mut task: FileInHardlink) -> Result<(), FileOutHardlink> {
if task.cha.is_none() {
task.cha = Some(Self::cha(&task.from, task.follow, None).await?);
}
pub(crate) async fn hardlink(&self, task: FileInHardlink) -> Result<(), FileOutHardlink> {
let id = task.id;
let cha = task.cha.unwrap();
if !cha.is_dir() {
let id = task.id;
self.ops.out(id, FileOutHardlink::New);
self.queue(task, NORMAL);
self.ops.out(id, FileOutHardlink::Succ);
return Ok(());
}
macro_rules! continue_unless_ok {
($result:expr) => {
match $result {
Ok(v) => v,
Err(e) => {
self.ops.out(task.id, FileOutHardlink::Deform(e.to_string()));
continue;
}
}
};
}
let root = &task.to;
let skip = task.from.components().count();
let mut dirs = VecDeque::from([task.from.clone()]);
while let Some(src) = dirs.pop_front() {
let dest = continue_unless_ok!(root.try_join(skip_url(&src, skip)));
continue_unless_ok!(match provider::create_dir(&dest).await {
Err(e) if e.kind() != AlreadyExists => Err(e),
super::traverse::<FileOutHardlink, _, _, _, _, _>(
task,
async |dir| match provider::create_dir(dir).await {
Err(e) if e.kind() != AlreadyExists => Err(e)?,
_ => Ok(()),
});
},
async |task, _cha| {
self.ops.out(id, FileOutHardlink::New);
Ok(self.queue(task, NORMAL))
},
|err| {
self.ops.out(id, FileOutHardlink::Deform(err));
},
)
.await?;
let mut it = continue_unless_ok!(provider::read_dir(&src).await);
while let Ok(Some(entry)) = it.next().await {
let from = entry.url();
let cha = continue_unless_ok!(Self::cha(&from, task.follow, Some(entry)).await);
if cha.is_dir() {
dirs.push_back(from);
continue;
}
let to = continue_unless_ok!(dest.try_join(from.name().unwrap()));
self.ops.out(task.id, FileOutHardlink::New);
self.queue(task.spawn(from, to, cha), NORMAL);
}
}
Ok(self.ops.out(task.id, FileOutHardlink::Succ))
Ok(self.ops.out(id, FileOutHardlink::Succ))
}
pub(crate) async fn hardlink_do(&self, task: FileInHardlink) -> Result<(), FileOutHardlinkDo> {
@ -245,37 +214,21 @@ impl File {
Ok(self.ops.out(task.id, FileOutHardlinkDo::Succ))
}
pub(crate) async fn delete(&self, mut task: FileInDelete) -> Result<(), FileOutDelete> {
let cha = provider::symlink_metadata(&task.target).await?;
if !cha.is_dir() {
let id = task.id;
task.length = cha.len;
self.ops.out(id, FileOutDelete::New(cha.len));
self.queue(task, NORMAL);
self.ops.out(id, FileOutDelete::Succ);
return Ok(());
}
pub(crate) async fn delete(&self, task: FileInDelete) -> Result<(), FileOutDelete> {
let id = task.id;
let mut dirs = VecDeque::from([task.target]);
while let Some(target) = dirs.pop_front() {
let Ok(mut it) = provider::read_dir(&target).await else { continue };
super::traverse::<FileOutDelete, _, _, _, _, _>(
task,
async |_dir| Ok(()),
async |task, cha| {
self.ops.out(id, FileOutDelete::New(cha.len));
Ok(self.queue(task, NORMAL))
},
|_err| {},
)
.await?;
while let Ok(Some(entry)) = it.next().await {
let Ok(cha) = entry.metadata().await else { continue };
if cha.is_dir() {
dirs.push_front(entry.url());
continue;
}
task.target = entry.url();
task.length = cha.len;
self.ops.out(task.id, FileOutDelete::New(cha.len));
self.queue(task.clone(), NORMAL);
}
}
Ok(self.ops.out(task.id, FileOutDelete::Succ))
Ok(self.ops.out(id, FileOutDelete::Succ))
}
pub(crate) async fn delete_do(&self, task: FileInDelete) -> Result<(), FileOutDeleteDo> {
@ -285,7 +238,7 @@ impl File {
Err(_) if !maybe_exists(&task.target).await => {}
Err(e) => Err(e)?,
}
Ok(self.ops.out(task.id, FileOutDeleteDo::Succ(task.length)))
Ok(self.ops.out(task.id, FileOutDeleteDo::Succ(task.cha.unwrap().len)))
}
pub(crate) fn trash(&self, task: FileInTrash) -> Result<(), FileOutTrash> {
@ -297,56 +250,31 @@ impl File {
Ok(self.ops.out(task.id, FileOutTrash::Succ))
}
pub(crate) async fn download(&self, mut task: FileInDownload) -> Result<(), FileOutDownload> {
if task.cha.is_none() {
task.cha = Some(Self::cha(&task.url, true, None).await?);
}
pub(crate) async fn download(&self, task: FileInDownload) -> Result<(), FileOutDownload> {
let id = task.id;
let cha = task.cha.unwrap();
if cha.is_orphan() {
Err(io::Error::new(NotFound, "Source of symlink doesn't exist"))?;
}
if !cha.is_dir() {
let id = task.id;
self.ops.out(id, FileOutDownload::New(cha.len));
self.queue(task, LOW);
return Ok(self.ops.out(id, FileOutDownload::Succ));
}
macro_rules! continue_unless_ok {
($result:expr) => {
match $result {
Ok(v) => v,
Err(e) => {
self.ops.out(task.id, FileOutDownload::Deform(e.to_string()));
continue;
}
}
};
}
let mut dirs = VecDeque::from([task.url.clone()]);
while let Some(src) = dirs.pop_front() {
let mut it = continue_unless_ok!(provider::read_dir(&src).await);
tokio::task::spawn_blocking(move || _ = Cwd::ensure(src.as_url())).await.ok();
while let Ok(Some(entry)) = it.next().await {
let from = entry.url();
let cha = continue_unless_ok!(Self::cha(&from, true, Some(entry)).await);
if cha.is_orphan() {
continue_unless_ok!(Err("Source of symlink doesn't exist"));
} else if cha.is_dir() {
dirs.push_back(from);
super::traverse::<FileOutDownload, _, _, _, _, _>(
task,
async |dir| {
let dir = dir.to_owned();
tokio::task::spawn_blocking(move || _ = Cwd::ensure(dir.as_url())).await.ok();
Ok(())
},
async |task, cha| {
Ok(if cha.is_orphan() {
Err(io::Error::new(NotFound, "Source of symlink doesn't exist"))?;
} else {
self.ops.out(task.id, FileOutDownload::New(cha.len));
self.queue(task.spawn(from, cha), LOW);
}
}
}
self.ops.out(id, FileOutDownload::New(cha.len));
self.queue(task, LOW)
})
},
|err| {
self.ops.out(id, FileOutDownload::Deform(err));
},
)
.await?;
Ok(self.ops.out(task.id, FileOutDownload::Succ))
Ok(self.ops.out(id, FileOutDownload::Succ))
}
pub(crate) async fn download_do(
@ -393,79 +321,47 @@ impl File {
}
pub(crate) async fn upload(&self, task: FileInUpload) -> Result<(), FileOutUpload> {
let cha = Self::cha(&task.url, true, None).await?;
if cha.is_orphan() {
Err(anyhow!("Source of symlink doesn't exist"))?;
}
let id = task.id;
if !cha.is_dir() {
let cache = task.url.cache().context("Cannot determine cache path")?;
match Self::cha(&cache, true, None).await {
Ok(c) if c.mtime == cha.mtime => {}
Ok(c) => {
self.ops.out(task.id, FileOutUpload::New(c.len));
self.queue(FileInUploadDo { id: task.id, url: task.url, cha, cache }, LOW);
}
Err(e) if e.kind() == NotFound => {}
Err(e) => Err(e)?,
};
return Ok(self.ops.out(task.id, FileOutUpload::Succ));
}
super::traverse::<FileOutUpload, _, _, _, _, _>(
task,
async |_dir| Ok(()),
async |task, cha| {
let cache = task.cache.as_ref().context("Cannot determine cache path")?;
macro_rules! continue_unless_ok {
($result:expr) => {
match $result {
Ok(v) => v,
Err(e) => {
self.ops.out(task.id, FileOutUpload::Deform(e.to_string()));
continue;
Ok(match Self::cha(cache, true, None).await {
Ok(c) if c.mtime == cha.mtime => {}
Ok(c) => {
self.ops.out(id, FileOutUpload::New(c.len));
self.queue(task, LOW);
}
}
};
}
Err(e) if e.kind() == NotFound => {}
Err(e) => Err(e)?,
})
},
|err| {
self.ops.out(id, FileOutUpload::Deform(err));
},
)
.await?;
let mut dirs = VecDeque::from([task.url]);
while let Some(src) = dirs.pop_front() {
let mut it = continue_unless_ok!(provider::read_dir(&src).await);
while let Ok(Some(entry)) = it.next().await {
let from = entry.url();
let cha = continue_unless_ok!(Self::cha(&from, true, Some(entry)).await);
if cha.is_orphan() {
continue_unless_ok!(Err("Source of symlink doesn't exist"));
} else if cha.is_dir() {
dirs.push_back(from);
continue;
}
let cache = continue_unless_ok!(from.cache().ok_or("Cannot determine cache path"));
let cache_cha = continue_unless_ok!(match Self::cha(&cache, true, None).await {
Ok(c) if c.mtime == cha.mtime => continue,
Ok(c) => Ok(c),
Err(e) if e.kind() == NotFound => continue,
Err(e) => Err(e),
});
self.ops.out(task.id, FileOutUpload::New(cache_cha.len));
self.queue(FileInUploadDo { id: task.id, url: from, cha, cache }, LOW);
}
}
Ok(self.ops.out(task.id, FileOutUpload::Succ))
Ok(self.ops.out(id, FileOutUpload::Succ))
}
pub(crate) async fn upload_do(&self, task: FileInUploadDo) -> Result<(), FileOutUploadDo> {
pub(crate) async fn upload_do(&self, task: FileInUpload) -> Result<(), FileOutUploadDo> {
let cha = task.cha.unwrap();
let cache = task.cache.context("Cannot determine cache path")?;
let lock = task.url.cache_lock().context("Cannot determine cache lock")?;
let hash = Local::regular(&lock).read_to_string().await.context("Cannot read cache lock")?;
let hash = u128::from_str_radix(&hash, 16).context("Cannot parse hash from lock")?;
if hash != task.cha.hash_u128() {
if hash != cha.hash_u128() {
Err(anyhow!("Remote file has changed since last download"))?;
}
let tmp = Self::tmp(&task.url).await.context("Cannot determine temporary upload path")?;
let mut it = provider::copy_with_progress(&task.cache, &tmp, Attrs {
mode: Some(task.cha.mode),
let mut it = provider::copy_with_progress(&cache, &tmp, Attrs {
mode: Some(cha.mode),
atime: None,
btime: None,
mtime: None,
@ -495,7 +391,7 @@ impl File {
Ok(self.ops.out(task.id, FileOutUploadDo::Succ))
}
async fn cha<U>(url: U, follow: bool, entry: Option<DirEntry>) -> io::Result<Cha>
pub(super) async fn cha<U>(url: U, follow: bool, entry: Option<DirEntry>) -> io::Result<Cha>
where
U: AsUrl,
{

View file

@ -3,31 +3,18 @@ use std::path::PathBuf;
use yazi_fs::cha::Cha;
use yazi_shared::{Id, url::UrlBuf};
// --- Paste
// --- Copy
#[derive(Clone, Debug)]
pub(crate) struct FileInPaste {
pub(crate) struct FileInCopy {
pub(crate) id: Id,
pub(crate) from: UrlBuf,
pub(crate) to: UrlBuf,
pub(crate) cha: Option<Cha>,
pub(crate) cut: bool,
pub(crate) follow: bool,
pub(crate) retry: u8,
}
impl FileInPaste {
pub(super) fn spawn(&self, from: UrlBuf, to: UrlBuf, cha: Cha) -> Self {
Self {
id: self.id,
from,
to,
cha: Some(cha),
cut: self.cut,
follow: self.follow,
retry: self.retry,
}
}
impl FileInCopy {
pub(super) fn into_link(self) -> FileInLink {
FileInLink {
id: self.id,
@ -36,7 +23,32 @@ impl FileInPaste {
cha: self.cha,
resolve: true,
relative: false,
delete: self.cut,
delete: false,
}
}
}
// --- Cut
#[derive(Clone, Debug)]
pub(crate) struct FileInCut {
pub(crate) id: Id,
pub(crate) from: UrlBuf,
pub(crate) to: UrlBuf,
pub(crate) cha: Option<Cha>,
pub(crate) follow: bool,
pub(crate) retry: u8,
}
impl FileInCut {
pub(super) fn into_link(self) -> FileInLink {
FileInLink {
id: self.id,
from: self.from,
to: self.to,
cha: self.cha,
resolve: true,
relative: false,
delete: true,
}
}
}
@ -63,18 +75,12 @@ pub(crate) struct FileInHardlink {
pub(crate) follow: bool,
}
impl FileInHardlink {
pub(super) fn spawn(&self, from: UrlBuf, to: UrlBuf, cha: Cha) -> Self {
Self { id: self.id, from, to, cha: Some(cha), follow: self.follow }
}
}
// --- Delete
#[derive(Clone, Debug)]
pub(crate) struct FileInDelete {
pub(crate) id: Id,
pub(crate) target: UrlBuf,
pub(crate) length: u64,
pub(crate) cha: Option<Cha>,
}
// --- Trash
@ -93,24 +99,11 @@ pub(crate) struct FileInDownload {
pub(crate) retry: u8,
}
impl FileInDownload {
pub(super) fn spawn(&self, url: UrlBuf, cha: Cha) -> Self {
Self { id: self.id, url, cha: Some(cha), retry: self.retry }
}
}
// --- Upload
#[derive(Clone, Debug)]
pub(crate) struct FileInUpload {
pub(crate) id: Id,
pub(crate) url: UrlBuf,
}
// --- UploadDo
#[derive(Clone, Debug)]
pub(crate) struct FileInUploadDo {
pub(crate) id: Id,
pub(crate) url: UrlBuf,
pub(crate) cha: Cha,
pub(crate) cache: PathBuf,
pub(crate) cha: Option<Cha>,
pub(crate) cache: Option<PathBuf>,
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(file out progress r#in);
yazi_macro::mod_flat!(file out progress r#in traverse);

View file

@ -1,8 +1,79 @@
use crate::{Task, TaskProg};
// --- Paste
// --- Copy
#[derive(Debug)]
pub(crate) enum FileOutPaste {
pub(crate) enum FileOutCopy {
New(u64),
Deform(String),
Succ,
Fail(String),
}
impl From<std::io::Error> for FileOutCopy {
fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl FileOutCopy {
pub(crate) fn reduce(self, task: &mut Task) {
let TaskProg::FileCopy(prog) = &mut task.prog else { return };
match self {
Self::New(bytes) => {
prog.total_files += 1;
prog.total_bytes += bytes;
}
Self::Deform(reason) => {
prog.total_files += 1;
prog.failed_files += 1;
task.log(reason);
}
Self::Succ => {
prog.collected = Some(true);
}
Self::Fail(reason) => {
prog.collected = Some(false);
task.log(reason);
}
}
}
}
// --- CopyDo
#[derive(Debug)]
pub(crate) enum FileOutCopyDo {
Adv(u64),
Log(String),
Succ,
Fail(String),
}
impl From<std::io::Error> for FileOutCopyDo {
fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl FileOutCopyDo {
pub(crate) fn reduce(self, task: &mut Task) {
let TaskProg::FileCopy(prog) = &mut task.prog else { return };
match self {
Self::Adv(size) => {
prog.processed_bytes += size;
}
Self::Log(line) => {
task.log(line);
}
Self::Succ => {
prog.success_files += 1;
}
Self::Fail(reason) => {
prog.failed_files += 1;
task.log(reason);
}
}
}
}
// --- Cut
#[derive(Debug)]
pub(crate) enum FileOutCut {
New(u64),
Deform(String),
Succ,
@ -10,13 +81,13 @@ pub(crate) enum FileOutPaste {
Clean,
}
impl From<std::io::Error> for FileOutPaste {
impl From<std::io::Error> for FileOutCut {
fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl FileOutPaste {
impl FileOutCut {
pub(crate) fn reduce(self, task: &mut Task) {
let TaskProg::FilePaste(prog) = &mut task.prog else { return };
let TaskProg::FileCut(prog) = &mut task.prog else { return };
match self {
Self::New(bytes) => {
prog.total_files += 1;
@ -41,22 +112,22 @@ impl FileOutPaste {
}
}
// --- PasteDo
// --- CutDo
#[derive(Debug)]
pub(crate) enum FileOutPasteDo {
pub(crate) enum FileOutCutDo {
Adv(u64),
Log(String),
Succ,
Fail(String),
}
impl From<std::io::Error> for FileOutPasteDo {
impl From<std::io::Error> for FileOutCutDo {
fn from(value: std::io::Error) -> Self { Self::Fail(format!("{value:?}")) }
}
impl FileOutPasteDo {
impl FileOutCutDo {
pub(crate) fn reduce(self, task: &mut Task) {
let TaskProg::FilePaste(prog) = &mut task.prog else { return };
let TaskProg::FileCut(prog) = &mut task.prog else { return };
match self {
Self::Adv(size) => {
prog.processed_bytes += size;
@ -102,7 +173,17 @@ impl FileOutLink {
task.log(reason);
}
}
} else if let TaskProg::FilePaste(prog) = &mut task.prog {
} else if let TaskProg::FileCopy(prog) = &mut task.prog {
match self {
Self::Succ => {
prog.success_files += 1;
}
Self::Fail(reason) => {
prog.failed_files += 1;
task.log(reason);
}
}
} else if let TaskProg::FileCut(prog) = &mut task.prog {
match self {
Self::Succ => {
prog.success_files += 1;

View file

@ -1,20 +1,19 @@
use serde::Serialize;
use yazi_parser::app::TaskSummary;
// --- Paste
// --- Copy
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
pub struct FileProgPaste {
pub struct FileProgCopy {
pub total_files: u32,
pub success_files: u32,
pub failed_files: u32,
pub total_bytes: u64,
pub processed_bytes: u64,
pub collected: Option<bool>,
pub cleaned: bool,
}
impl From<FileProgPaste> for TaskSummary {
fn from(value: FileProgPaste) -> Self {
impl From<FileProgCopy> for TaskSummary {
fn from(value: FileProgCopy) -> Self {
Self {
total: value.total_files,
success: value.success_files,
@ -24,7 +23,56 @@ impl From<FileProgPaste> for TaskSummary {
}
}
impl FileProgPaste {
impl FileProgCopy {
pub fn running(self) -> bool {
self.collected.is_none() || self.success_files + self.failed_files != self.total_files
}
pub fn success(self) -> bool {
self.collected == Some(true) && self.success_files == self.total_files
}
pub fn failed(self) -> bool { self.collected == Some(false) }
pub fn cleaned(self) -> bool { false }
pub fn percent(self) -> Option<f32> {
Some(if self.success() {
100.0
} else if self.failed() {
0.0
} else if self.total_bytes != 0 {
99.99f32.min(self.processed_bytes as f32 / self.total_bytes as f32 * 100.0)
} else {
99.99
})
}
}
// --- Cut
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize)]
pub struct FileProgCut {
pub total_files: u32,
pub success_files: u32,
pub failed_files: u32,
pub total_bytes: u64,
pub processed_bytes: u64,
pub collected: Option<bool>,
pub cleaned: bool,
}
impl From<FileProgCut> for TaskSummary {
fn from(value: FileProgCut) -> Self {
Self {
total: value.total_files,
success: value.success_files,
failed: value.failed_files,
percent: value.percent().map(Into::into),
}
}
}
impl FileProgCut {
pub fn running(self) -> bool {
self.collected.is_none() || self.success_files + self.failed_files != self.total_files
}

View file

@ -0,0 +1,208 @@
use std::{collections::VecDeque, fmt::Debug, io};
use yazi_fs::{FsUrl, cha::Cha, path::skip_url, provider::{DirReader, FileHolder}};
use yazi_shared::{strand::StrandLike, url::{AsUrl, Url, UrlBuf, UrlLike}};
use yazi_vfs::provider::{self};
use crate::file::{FileInCopy, FileInCut, FileInDelete, FileInDownload, FileInHardlink, FileInUpload};
trait Traverse {
fn cha(&mut self) -> &mut Option<Cha>;
fn follow(&self) -> bool;
fn from(&self) -> Url<'_>;
async fn init(&mut self) -> io::Result<Cha> {
if self.cha().is_none() {
*self.cha() = Some(super::File::cha(self.from(), self.follow(), None).await?)
}
Ok(self.cha().unwrap())
}
fn spawn(&self, from: UrlBuf, to: Option<UrlBuf>, cha: Cha) -> Self;
fn to(&self) -> Option<Url<'_>>;
}
impl Traverse for FileInCopy {
fn cha(&mut self) -> &mut Option<Cha> { &mut self.cha }
fn follow(&self) -> bool { self.follow }
fn from(&self) -> Url<'_> { self.from.as_url() }
fn spawn(&self, from: UrlBuf, to: Option<UrlBuf>, cha: Cha) -> Self {
Self {
id: self.id,
from,
to: to.unwrap(),
cha: Some(cha),
follow: self.follow,
retry: self.retry,
}
}
fn to(&self) -> Option<Url<'_>> { Some(self.to.as_url()) }
}
impl Traverse for FileInCut {
fn cha(&mut self) -> &mut Option<Cha> { &mut self.cha }
fn follow(&self) -> bool { self.follow }
fn from(&self) -> Url<'_> { self.from.as_url() }
fn spawn(&self, from: UrlBuf, to: Option<UrlBuf>, cha: Cha) -> Self {
Self {
id: self.id,
from,
to: to.unwrap(),
cha: Some(cha),
follow: self.follow,
retry: self.retry,
}
}
fn to(&self) -> Option<Url<'_>> { Some(self.to.as_url()) }
}
impl Traverse for FileInHardlink {
fn cha(&mut self) -> &mut Option<Cha> { &mut self.cha }
fn follow(&self) -> bool { self.follow }
fn from(&self) -> Url<'_> { self.from.as_url() }
fn spawn(&self, from: UrlBuf, to: Option<UrlBuf>, cha: Cha) -> Self {
Self { id: self.id, from, to: to.unwrap(), cha: Some(cha), follow: self.follow }
}
fn to(&self) -> Option<Url<'_>> { Some(self.to.as_url()) }
}
impl Traverse for FileInDelete {
fn cha(&mut self) -> &mut Option<Cha> { &mut self.cha }
fn follow(&self) -> bool { false }
fn from(&self) -> Url<'_> { self.target.as_url() }
fn spawn(&self, from: UrlBuf, _to: Option<UrlBuf>, cha: Cha) -> Self {
Self { id: self.id, target: from, cha: Some(cha) }
}
fn to(&self) -> Option<Url<'_>> { None }
}
impl Traverse for FileInDownload {
fn cha(&mut self) -> &mut Option<Cha> { &mut self.cha }
fn follow(&self) -> bool { true }
fn from(&self) -> Url<'_> { self.url.as_url() }
fn spawn(&self, from: UrlBuf, _to: Option<UrlBuf>, cha: Cha) -> Self {
Self { id: self.id, url: from, cha: Some(cha), retry: self.retry }
}
fn to(&self) -> Option<Url<'_>> { None }
}
impl Traverse for FileInUpload {
fn cha(&mut self) -> &mut Option<Cha> { &mut self.cha }
fn follow(&self) -> bool { true }
fn from(&self) -> Url<'_> { self.url.as_url() }
async fn init(&mut self) -> io::Result<Cha> {
if self.cha.is_none() {
self.cha = Some(super::File::cha(self.from(), self.follow(), None).await?)
}
if self.cache.is_none() {
self.cache = self.url.cache();
}
Ok(self.cha.unwrap())
}
fn spawn(&self, from: UrlBuf, _to: Option<UrlBuf>, cha: Cha) -> Self {
Self { id: self.id, cha: Some(cha), cache: from.cache(), url: from }
}
fn to(&self) -> Option<Url<'_>> { None }
}
#[allow(private_bounds)]
pub(super) async fn traverse<R, T, D, FC, FR, E>(
mut task: T,
on_dir: D,
on_file: FC,
on_error: E,
) -> Result<(), R>
where
R: Debug + From<io::Error>,
T: Traverse,
D: AsyncFn(Url) -> Result<(), R>,
FC: Fn(T, Cha) -> FR,
FR: Future<Output = Result<(), R>>,
E: Fn(String),
{
let cha = task.init().await?;
if !cha.is_dir() {
return on_file(task, cha).await;
}
let root = task.to();
let skip = task.from().components().count();
let mut dirs = VecDeque::from([task.from().to_owned()]);
macro_rules! err {
($result:expr, $($args:tt)*) => {
match $result {
Ok(v) => v,
Err(e) => {
on_error(format!("{}: {e:?}", format_args!($($args)*)));
continue;
}
}
};
}
while let Some(src) = dirs.pop_front() {
let mut it = err!(provider::read_dir(&src).await, "Cannot read directory {src:?}");
let dest = if let Some(root) = root {
let s = skip_url(&src, skip);
err!(root.try_join(&s), "Cannot join {root:?} with {}", s.display())
} else {
src
};
() = err!(on_dir(dest.as_url()).await, "Cannot process directory {dest:?}");
while let Ok(Some(entry)) = it.next().await {
let from = entry.url();
let cha = err!(
super::File::cha(&from, task.follow(), Some(entry)).await,
"Cannot get metadata for {from:?}"
);
if cha.is_dir() {
dirs.push_back(from);
continue;
}
let to = if root.is_some() {
let name = from.name().unwrap();
Some(err!(dest.try_join(name), "Cannot join {dest:?} with {}", name.display()))
} else {
None
};
err!(on_file(task.spawn(from, to, cha), cha).await, "Cannot process file");
}
}
Ok(())
}

View file

@ -1,17 +1,18 @@
use yazi_shared::Id;
use crate::{file::{FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInPaste, FileInTrash, FileInUploadDo}, impl_from_in, plugin::PluginInEntry, prework::{PreworkInFetch, PreworkInLoad, PreworkInSize}};
use crate::{file::{FileInCopy, FileInCut, FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInTrash, FileInUpload}, impl_from_in, plugin::PluginInEntry, prework::{PreworkInFetch, PreworkInLoad, PreworkInSize}};
#[derive(Debug)]
pub(crate) enum TaskIn {
// File
FilePaste(FileInPaste),
FileCopy(FileInCopy),
FileCut(FileInCut),
FileLink(FileInLink),
FileHardlink(FileInHardlink),
FileDelete(FileInDelete),
FileTrash(FileInTrash),
FileDownload(FileInDownload),
FileUploadDo(FileInUploadDo),
FileUpload(FileInUpload),
// Plugin
PluginEntry(PluginInEntry),
// Prework
@ -22,7 +23,7 @@ pub(crate) enum TaskIn {
impl_from_in! {
// File
FilePaste(FileInPaste), FileLink(FileInLink), FileHardlink(FileInHardlink), FileDelete(FileInDelete), FileTrash(FileInTrash), FileDownload(FileInDownload), FileUploadDo(FileInUploadDo),
FileCopy(FileInCopy), FileCut(FileInCut), FileLink(FileInLink), FileHardlink(FileInHardlink), FileDelete(FileInDelete), FileTrash(FileInTrash), FileDownload(FileInDownload), FileUpload(FileInUpload),
// Plugin
PluginEntry(PluginInEntry),
// Prework
@ -33,13 +34,14 @@ impl TaskIn {
pub fn id(&self) -> Id {
match self {
// File
Self::FilePaste(r#in) => r#in.id,
Self::FileCopy(r#in) => r#in.id,
Self::FileCut(r#in) => r#in.id,
Self::FileLink(r#in) => r#in.id,
Self::FileHardlink(r#in) => r#in.id,
Self::FileDelete(r#in) => r#in.id,
Self::FileTrash(r#in) => r#in.id,
Self::FileDownload(r#in) => r#in.id,
Self::FileUploadDo(r#in) => r#in.id,
Self::FileUpload(r#in) => r#in.id,
// Plugin
Self::PluginEntry(r#in) => r#in.id,
// Prework

View file

@ -1,10 +1,12 @@
use crate::{Task, file::{FileOutDelete, FileOutDeleteDo, FileOutDownload, FileOutDownloadDo, FileOutHardlink, FileOutHardlinkDo, FileOutLink, FileOutPaste, FileOutPasteDo, FileOutTrash, FileOutUpload, FileOutUploadDo}, impl_from_out, plugin::PluginOutEntry, prework::{PreworkOutFetch, PreworkOutLoad, PreworkOutSize}, process::{ProcessOutBg, ProcessOutBlock, ProcessOutOrphan}};
use crate::{Task, file::{FileOutCopy, FileOutCopyDo, FileOutCut, FileOutCutDo, FileOutDelete, FileOutDeleteDo, FileOutDownload, FileOutDownloadDo, FileOutHardlink, FileOutHardlinkDo, FileOutLink, FileOutTrash, FileOutUpload, FileOutUploadDo}, impl_from_out, plugin::PluginOutEntry, prework::{PreworkOutFetch, PreworkOutLoad, PreworkOutSize}, process::{ProcessOutBg, ProcessOutBlock, ProcessOutOrphan}};
#[derive(Debug)]
pub(super) enum TaskOut {
// File
FilePaste(FileOutPaste),
FilePasteDo(FileOutPasteDo),
FileCopy(FileOutCopy),
FileCopyDo(FileOutCopyDo),
FileCut(FileOutCut),
FileCutDo(FileOutCutDo),
FileLink(FileOutLink),
FileHardlink(FileOutHardlink),
FileHardlinkDo(FileOutHardlinkDo),
@ -32,7 +34,7 @@ pub(super) enum TaskOut {
impl_from_out! {
// File
FilePaste(FileOutPaste), FilePasteDo(FileOutPasteDo), FileLink(FileOutLink), FileHardlink(FileOutHardlink), FileHardlinkDo(FileOutHardlinkDo), FileDelete(FileOutDelete), FileDeleteDo(FileOutDeleteDo), FileTrash(FileOutTrash), FileDownload(FileOutDownload), FileDownloadDo(FileOutDownloadDo), FileUpload(FileOutUpload), FileUploadDo(FileOutUploadDo),
FileCopy(FileOutCopy), FileCopyDo(FileOutCopyDo), FileCut(FileOutCut), FileCutDo(FileOutCutDo), FileLink(FileOutLink), FileHardlink(FileOutHardlink), FileHardlinkDo(FileOutHardlinkDo), FileDelete(FileOutDelete), FileDeleteDo(FileOutDeleteDo), FileTrash(FileOutTrash), FileDownload(FileOutDownload), FileDownloadDo(FileOutDownloadDo), FileUpload(FileOutUpload), FileUploadDo(FileOutUploadDo),
// Plugin
PluginEntry(PluginOutEntry),
// Prework
@ -45,8 +47,10 @@ impl TaskOut {
pub(crate) fn reduce(self, task: &mut Task) {
match self {
// File
Self::FilePaste(out) => out.reduce(task),
Self::FilePasteDo(out) => out.reduce(task),
Self::FileCopy(out) => out.reduce(task),
Self::FileCopyDo(out) => out.reduce(task),
Self::FileCut(out) => out.reduce(task),
Self::FileCutDo(out) => out.reduce(task),
Self::FileLink(out) => out.reduce(task),
Self::FileHardlink(out) => out.reduce(task),
Self::FileHardlinkDo(out) => out.reduce(task),

View file

@ -1,13 +1,14 @@
use serde::Serialize;
use yazi_parser::app::TaskSummary;
use crate::{file::{FileProgDelete, FileProgDownload, FileProgHardlink, FileProgLink, FileProgPaste, FileProgTrash, FileProgUpload}, impl_from_prog, plugin::PluginProgEntry, prework::{PreworkProgFetch, PreworkProgLoad, PreworkProgSize}, process::{ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}};
use crate::{file::{FileProgCopy, FileProgCut, FileProgDelete, FileProgDownload, FileProgHardlink, FileProgLink, FileProgTrash, FileProgUpload}, impl_from_prog, plugin::PluginProgEntry, prework::{PreworkProgFetch, PreworkProgLoad, PreworkProgSize}, process::{ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(tag = "kind")]
pub enum TaskProg {
// File
FilePaste(FileProgPaste),
FileCopy(FileProgCopy),
FileCut(FileProgCut),
FileLink(FileProgLink),
FileHardlink(FileProgHardlink),
FileDelete(FileProgDelete),
@ -28,7 +29,7 @@ pub enum TaskProg {
impl_from_prog! {
// File
FilePaste(FileProgPaste), FileLink(FileProgLink), FileHardlink(FileProgHardlink), FileDelete(FileProgDelete), FileTrash(FileProgTrash), FileDownload(FileProgDownload), FileUpload(FileProgUpload),
FileCopy(FileProgCopy), FileCut(FileProgCut), FileLink(FileProgLink), FileHardlink(FileProgHardlink), FileDelete(FileProgDelete), FileTrash(FileProgTrash), FileDownload(FileProgDownload), FileUpload(FileProgUpload),
// Plugin
PluginEntry(PluginProgEntry),
// Prework
@ -41,7 +42,8 @@ impl From<TaskProg> for TaskSummary {
fn from(value: TaskProg) -> Self {
match value {
// File
TaskProg::FilePaste(p) => p.into(),
TaskProg::FileCopy(p) => p.into(),
TaskProg::FileCut(p) => p.into(),
TaskProg::FileLink(p) => p.into(),
TaskProg::FileHardlink(p) => p.into(),
TaskProg::FileDelete(p) => p.into(),
@ -66,7 +68,8 @@ impl TaskProg {
pub fn running(self) -> bool {
match self {
// File
Self::FilePaste(p) => p.running(),
Self::FileCopy(p) => p.running(),
Self::FileCut(p) => p.running(),
Self::FileLink(p) => p.running(),
Self::FileHardlink(p) => p.running(),
Self::FileDelete(p) => p.running(),
@ -89,7 +92,8 @@ impl TaskProg {
pub fn success(self) -> bool {
match self {
// File
Self::FilePaste(p) => p.success(),
Self::FileCopy(p) => p.success(),
Self::FileCut(p) => p.success(),
Self::FileLink(p) => p.success(),
Self::FileHardlink(p) => p.success(),
Self::FileDelete(p) => p.success(),
@ -112,7 +116,8 @@ impl TaskProg {
pub fn failed(self) -> bool {
match self {
// File
Self::FilePaste(p) => p.failed(),
Self::FileCopy(p) => p.failed(),
Self::FileCut(p) => p.failed(),
Self::FileLink(p) => p.failed(),
Self::FileHardlink(p) => p.failed(),
Self::FileDelete(p) => p.failed(),
@ -135,7 +140,8 @@ impl TaskProg {
pub fn cleaned(self) -> bool {
match self {
// File
Self::FilePaste(p) => p.cleaned(),
Self::FileCopy(p) => p.cleaned(),
Self::FileCut(p) => p.cleaned(),
Self::FileLink(p) => p.cleaned(),
Self::FileHardlink(p) => p.cleaned(),
Self::FileDelete(p) => p.cleaned(),
@ -158,7 +164,8 @@ impl TaskProg {
pub fn percent(self) -> Option<f32> {
match self {
// File
Self::FilePaste(p) => p.percent(),
Self::FileCopy(p) => p.percent(),
Self::FileCut(p) => p.percent(),
Self::FileLink(p) => p.percent(),
Self::FileHardlink(p) => p.percent(),
Self::FileDelete(p) => p.percent(),
@ -181,7 +188,8 @@ impl TaskProg {
pub(crate) fn is_user(self) -> bool {
match self {
// File
Self::FilePaste(_) => true,
Self::FileCopy(_) => true,
Self::FileCut(_) => true,
Self::FileLink(_) => true,
Self::FileHardlink(_) => true,
Self::FileDelete(_) => true,

View file

@ -12,7 +12,7 @@ use yazi_shared::{Id, Throttle, url::{UrlBuf, UrlLike}};
use yazi_vfs::{must_be_dir, provider, unique_name};
use super::{Ongoing, TaskOp};
use crate::{HIGH, LOW, NORMAL, TaskIn, TaskOps, TaskOut, file::{File, FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInPaste, FileInTrash, FileInUpload, FileOutDelete, FileOutDownload, FileOutHardlink, FileOutPaste, FileOutUpload, FileProgDelete, FileProgDownload, FileProgHardlink, FileProgLink, FileProgPaste, FileProgTrash, FileProgUpload}, plugin::{Plugin, PluginInEntry, PluginProgEntry}, prework::{Prework, PreworkInFetch, PreworkInLoad, PreworkInSize, PreworkProgFetch, PreworkProgLoad, PreworkProgSize}, process::{Process, ProcessInBg, ProcessInBlock, ProcessInOrphan, ProcessOutBg, ProcessOutBlock, ProcessOutOrphan, ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}};
use crate::{HIGH, LOW, NORMAL, TaskIn, TaskOps, TaskOut, file::{File, FileInCopy, FileInCut, FileInDelete, FileInDownload, FileInHardlink, FileInLink, FileInTrash, FileInUpload, FileOutCopy, FileOutCut, FileOutDelete, FileOutDownload, FileOutHardlink, FileOutUpload, FileProgCopy, FileProgCut, FileProgDelete, FileProgDownload, FileProgHardlink, FileProgLink, FileProgTrash, FileProgUpload}, plugin::{Plugin, PluginInEntry, PluginProgEntry}, prework::{Prework, PreworkInFetch, PreworkInLoad, PreworkInSize, PreworkProgFetch, PreworkProgLoad, PreworkProgSize}, process::{Process, ProcessInBg, ProcessInBlock, ProcessInOrphan, ProcessOutBg, ProcessOutBlock, ProcessOutOrphan, ProcessProgBg, ProcessProgBlock, ProcessProgOrphan}};
pub struct Scheduler {
file: Arc<File>,
@ -77,10 +77,10 @@ impl Scheduler {
pub fn file_cut(&self, from: UrlBuf, mut to: UrlBuf, force: bool) {
let mut ongoing = self.ongoing.lock();
let id = ongoing.add::<FileProgPaste>(format!("Cut {} to {}", from.display(), to.display()));
let id = ongoing.add::<FileProgCut>(format!("Cut {} to {}", from.display(), to.display()));
if to.try_starts_with(&from).unwrap_or(false) && !to.covariant(&from) {
return self.ops.out(id, FileOutPaste::Fail("Cannot cut directory into itself".to_owned()));
return self.ops.out(id, FileOutCut::Fail("Cannot cut directory into itself".to_owned()));
}
ongoing.hooks.add_async(id, {
@ -92,7 +92,7 @@ impl Scheduler {
provider::remove_dir_clean(&from).await.ok();
Pump::push_move(from, to);
}
ops.out(id, FileOutPaste::Clean);
ops.out(id, FileOutCut::Clean);
}
});
@ -102,19 +102,19 @@ impl Scheduler {
if !force {
to = unique_name(to, must_be_dir(&from)).await?;
}
file.paste(FileInPaste { id, from, to, cha: None, cut: true, follow, retry: 0 }).await
file.cut(FileInCut { id, from, to, cha: None, follow, retry: 0 }).await
});
}
pub fn file_copy(&self, from: UrlBuf, mut to: UrlBuf, force: bool, follow: bool) {
let id = self.ongoing.lock().add::<FileProgPaste>(format!(
let id = self.ongoing.lock().add::<FileProgCopy>(format!(
"Copy {} to {}",
from.display(),
to.display()
));
if to.try_starts_with(&from).unwrap_or(false) && !to.covariant(&from) {
return self.ops.out(id, FileOutPaste::Fail("Cannot copy directory into itself".to_owned()));
return self.ops.out(id, FileOutCopy::Fail("Cannot copy directory into itself".to_owned()));
}
let file = self.file.clone();
@ -123,7 +123,7 @@ impl Scheduler {
if !force {
to = unique_name(to, must_be_dir(&from)).await?;
}
file.paste(FileInPaste { id, from, to, cha: None, cut: false, follow, retry: 0 }).await
file.copy(FileInCopy { id, from, to, cha: None, follow, retry: 0 }).await
});
}
@ -193,7 +193,7 @@ impl Scheduler {
self.send_micro(
id,
LOW,
async move { file.delete(FileInDelete { id, target, length: 0 }).await },
async move { file.delete(FileInDelete { id, target, cha: None }).await },
);
}
@ -242,7 +242,9 @@ impl Scheduler {
};
let file = self.file.clone();
self.send_micro(id, LOW, async move { file.upload(FileInUpload { id, url }).await });
self.send_micro(id, LOW, async move {
file.upload(FileInUpload { id, url, cha: None, cache: None }).await
});
}
pub fn plugin_micro(&self, opt: PluginOpt) {
@ -411,13 +413,14 @@ impl Scheduler {
let result: Result<_, TaskOut> = match r#in {
// File
TaskIn::FilePaste(r#in) => file.paste_do(r#in).await.map_err(Into::into),
TaskIn::FileCopy(r#in) => file.copy_do(r#in).await.map_err(Into::into),
TaskIn::FileCut(r#in) => file.cut_do(r#in).await.map_err(Into::into),
TaskIn::FileLink(r#in) => file.link_do(r#in).await.map_err(Into::into),
TaskIn::FileHardlink(r#in) => file.hardlink_do(r#in).await.map_err(Into::into),
TaskIn::FileDelete(r#in) => file.delete_do(r#in).await.map_err(Into::into),
TaskIn::FileTrash(r#in) => file.trash_do(r#in).await.map_err(Into::into),
TaskIn::FileDownload(r#in) => file.download_do(r#in).await.map_err(Into::into),
TaskIn::FileUploadDo(r#in) => file.upload_do(r#in).await.map_err(Into::into),
TaskIn::FileUpload(r#in) => file.upload_do(r#in).await.map_err(Into::into),
// Plugin
TaskIn::PluginEntry(r#in) => plugin.macro_do(r#in).await.map_err(Into::into),
// Prework

View file

@ -167,7 +167,11 @@ impl<'a> Provider for Sftp<'a> {
match op.rename_posix(self.path, &to).await {
Ok(()) => {}
Err(yazi_sftp::Error::Unsupported) => {
op.remove(&to).await?;
match op.remove(&to).await.map_err(io::Error::from) {
Ok(()) => {}
Err(e) if e.kind() == io::ErrorKind::NotFound => {}
Err(e) => Err(e)?,
}
op.rename(self.path, &to).await?;
}
Err(e) => Err(e)?,