diff --git a/yazi-plugin/preset/components/tasks.lua b/yazi-plugin/preset/components/tasks.lua index 9ee4f185..11b5ec4e 100644 --- a/yazi-plugin/preset/components/tasks.lua +++ b/yazi-plugin/preset/components/tasks.lua @@ -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…" diff --git a/yazi-scheduler/src/file/file.rs b/yazi-scheduler/src/file/file.rs index 496e54c8..9b7f8df8 100644 --- a/yazi-scheduler/src/file/file.rs +++ b/yazi-scheduler/src/file/file.rs @@ -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::( + 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::( + 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::( + 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::( + 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::( + 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::( + 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(url: U, follow: bool, entry: Option) -> io::Result + pub(super) async fn cha(url: U, follow: bool, entry: Option) -> io::Result where U: AsUrl, { diff --git a/yazi-scheduler/src/file/in.rs b/yazi-scheduler/src/file/in.rs index f56b07c3..0a447094 100644 --- a/yazi-scheduler/src/file/in.rs +++ b/yazi-scheduler/src/file/in.rs @@ -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, - 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, + 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, } // --- 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, + pub(crate) cache: Option, } diff --git a/yazi-scheduler/src/file/mod.rs b/yazi-scheduler/src/file/mod.rs index 9ae8d171..2a1a30ce 100644 --- a/yazi-scheduler/src/file/mod.rs +++ b/yazi-scheduler/src/file/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(file out progress r#in); +yazi_macro::mod_flat!(file out progress r#in traverse); diff --git a/yazi-scheduler/src/file/out.rs b/yazi-scheduler/src/file/out.rs index 5994103c..6e0cdb1d 100644 --- a/yazi-scheduler/src/file/out.rs +++ b/yazi-scheduler/src/file/out.rs @@ -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 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 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 for FileOutPaste { +impl From 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 for FileOutPasteDo { +impl From 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; diff --git a/yazi-scheduler/src/file/progress.rs b/yazi-scheduler/src/file/progress.rs index 2238d922..709ece50 100644 --- a/yazi-scheduler/src/file/progress.rs +++ b/yazi-scheduler/src/file/progress.rs @@ -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, - pub cleaned: bool, } -impl From for TaskSummary { - fn from(value: FileProgPaste) -> Self { +impl From for TaskSummary { + fn from(value: FileProgCopy) -> Self { Self { total: value.total_files, success: value.success_files, @@ -24,7 +23,56 @@ impl From 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 { + 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, + pub cleaned: bool, +} + +impl From 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 } diff --git a/yazi-scheduler/src/file/traverse.rs b/yazi-scheduler/src/file/traverse.rs new file mode 100644 index 00000000..0336da0e --- /dev/null +++ b/yazi-scheduler/src/file/traverse.rs @@ -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; + + fn follow(&self) -> bool; + + fn from(&self) -> Url<'_>; + + async fn init(&mut self) -> io::Result { + 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, cha: Cha) -> Self; + + fn to(&self) -> Option>; +} + +impl Traverse for FileInCopy { + fn cha(&mut self) -> &mut Option { &mut self.cha } + + fn follow(&self) -> bool { self.follow } + + fn from(&self) -> Url<'_> { self.from.as_url() } + + fn spawn(&self, from: UrlBuf, to: Option, cha: Cha) -> Self { + Self { + id: self.id, + from, + to: to.unwrap(), + cha: Some(cha), + follow: self.follow, + retry: self.retry, + } + } + + fn to(&self) -> Option> { Some(self.to.as_url()) } +} + +impl Traverse for FileInCut { + fn cha(&mut self) -> &mut Option { &mut self.cha } + + fn follow(&self) -> bool { self.follow } + + fn from(&self) -> Url<'_> { self.from.as_url() } + + fn spawn(&self, from: UrlBuf, to: Option, cha: Cha) -> Self { + Self { + id: self.id, + from, + to: to.unwrap(), + cha: Some(cha), + follow: self.follow, + retry: self.retry, + } + } + + fn to(&self) -> Option> { Some(self.to.as_url()) } +} + +impl Traverse for FileInHardlink { + fn cha(&mut self) -> &mut Option { &mut self.cha } + + fn follow(&self) -> bool { self.follow } + + fn from(&self) -> Url<'_> { self.from.as_url() } + + fn spawn(&self, from: UrlBuf, to: Option, cha: Cha) -> Self { + Self { id: self.id, from, to: to.unwrap(), cha: Some(cha), follow: self.follow } + } + + fn to(&self) -> Option> { Some(self.to.as_url()) } +} + +impl Traverse for FileInDelete { + fn cha(&mut self) -> &mut Option { &mut self.cha } + + fn follow(&self) -> bool { false } + + fn from(&self) -> Url<'_> { self.target.as_url() } + + fn spawn(&self, from: UrlBuf, _to: Option, cha: Cha) -> Self { + Self { id: self.id, target: from, cha: Some(cha) } + } + + fn to(&self) -> Option> { None } +} + +impl Traverse for FileInDownload { + fn cha(&mut self) -> &mut Option { &mut self.cha } + + fn follow(&self) -> bool { true } + + fn from(&self) -> Url<'_> { self.url.as_url() } + + fn spawn(&self, from: UrlBuf, _to: Option, cha: Cha) -> Self { + Self { id: self.id, url: from, cha: Some(cha), retry: self.retry } + } + + fn to(&self) -> Option> { None } +} + +impl Traverse for FileInUpload { + fn cha(&mut self) -> &mut Option { &mut self.cha } + + fn follow(&self) -> bool { true } + + fn from(&self) -> Url<'_> { self.url.as_url() } + + async fn init(&mut self) -> io::Result { + 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, cha: Cha) -> Self { + Self { id: self.id, cha: Some(cha), cache: from.cache(), url: from } + } + + fn to(&self) -> Option> { None } +} + +#[allow(private_bounds)] +pub(super) async fn traverse( + mut task: T, + on_dir: D, + on_file: FC, + on_error: E, +) -> Result<(), R> +where + R: Debug + From, + T: Traverse, + D: AsyncFn(Url) -> Result<(), R>, + FC: Fn(T, Cha) -> FR, + FR: Future>, + 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(()) +} diff --git a/yazi-scheduler/src/in.rs b/yazi-scheduler/src/in.rs index 3ba780cc..d008d1c8 100644 --- a/yazi-scheduler/src/in.rs +++ b/yazi-scheduler/src/in.rs @@ -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 diff --git a/yazi-scheduler/src/out.rs b/yazi-scheduler/src/out.rs index cd4b687f..b428a7e3 100644 --- a/yazi-scheduler/src/out.rs +++ b/yazi-scheduler/src/out.rs @@ -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), diff --git a/yazi-scheduler/src/progress.rs b/yazi-scheduler/src/progress.rs index 10abc5fc..05ade88f 100644 --- a/yazi-scheduler/src/progress.rs +++ b/yazi-scheduler/src/progress.rs @@ -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 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 { 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, diff --git a/yazi-scheduler/src/scheduler.rs b/yazi-scheduler/src/scheduler.rs index f14547ec..464b90a0 100644 --- a/yazi-scheduler/src/scheduler.rs +++ b/yazi-scheduler/src/scheduler.rs @@ -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, @@ -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::(format!("Cut {} to {}", from.display(), to.display())); + let id = ongoing.add::(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::(format!( + let id = self.ongoing.lock().add::(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 diff --git a/yazi-vfs/src/provider/sftp/sftp.rs b/yazi-vfs/src/provider/sftp/sftp.rs index 00e1797d..db44f0a5 100644 --- a/yazi-vfs/src/provider/sftp/sftp.rs +++ b/yazi-vfs/src/provider/sftp/sftp.rs @@ -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)?,