mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
refactor: pull directory traversal out into a function (#3411)
This commit is contained in:
parent
c569263a50
commit
d2e9e72684
12 changed files with 630 additions and 375 deletions
|
|
@ -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…"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(file out progress r#in);
|
||||
yazi_macro::mod_flat!(file out progress r#in traverse);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
208
yazi-scheduler/src/file/traverse.rs
Normal file
208
yazi-scheduler/src/file/traverse.rs
Normal 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(())
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)?,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue