feat: multi-path architecture (#3334)

This commit is contained in:
三咲雅 misaki masa 2025-11-18 13:56:20 +08:00 committed by GitHub
parent 577065cbd0
commit f9abe886cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
153 changed files with 4264 additions and 2417 deletions

View file

@ -1,12 +1,12 @@
use std::{borrow::Cow, collections::VecDeque, hash::{BuildHasher, Hash, Hasher}};
use std::{collections::VecDeque, 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::{skip_url, url_relative_to}, provider::{Attrs, DirReader, FileHolder, Provider, local::Local}};
use yazi_macro::ok_or_not_found;
use yazi_shared::{scheme::SchemeLike, timestamp_us, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
use yazi_shared::{path::PathCow, timestamp_us, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
use yazi_vfs::{VfsCha, copy_with_progress, maybe_exists, provider::{self, DirEntry}, unique_name};
use super::{FileInDelete, FileInHardlink, FileInLink, FileInPaste, FileInTrash};
@ -64,7 +64,7 @@ impl File {
let mut dirs = VecDeque::from([task.from.clone()]);
while let Some(src) = dirs.pop_front() {
let dest = root.join(skip_url(&src, skip));
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),
_ => Ok(()),
@ -80,7 +80,7 @@ impl File {
continue;
}
let to = dest.join(from.name().unwrap());
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);
@ -132,20 +132,21 @@ impl File {
}
pub(crate) async fn link_do(&self, task: FileInLink) -> Result<(), FileOutLink> {
let src: Cow<_> = if task.resolve {
let src: PathCow = if task.resolve {
ok_or_not_found!(
provider::read_link(&task.from).await,
return Ok(self.ops.out(task.id, FileOutLink::Succ))
)
.into()
} else if task.from.scheme.covariant(&task.to.scheme) {
task.from.loc.as_path().into()
} else if task.from.scheme().covariant(task.to.scheme()) {
task.from.loc().into()
} else {
Err(anyhow!("Source and target must be on the same filesystem: {task:?}"))?
};
let src = UrlCow::try_from((task.from.scheme(), src))?;
let src = if task.relative {
path_relative_to(provider::canonicalize(task.to.parent().unwrap()).await?.loc, &src)?
url_relative_to(provider::canonicalize(task.to.parent().unwrap()).await?, &src)?
} else {
src
};
@ -196,7 +197,7 @@ impl File {
let mut dirs = VecDeque::from([task.from.clone()]);
while let Some(src) = dirs.pop_front() {
let dest = root.join(skip_url(&src, skip));
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),
_ => Ok(()),
@ -212,7 +213,7 @@ impl File {
continue;
}
let to = dest.join(from.name().unwrap());
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);
}
@ -353,12 +354,12 @@ impl File {
while let Some(res) = it.recv().await {
match res {
Ok(0) => {
Local.remove_dir_all(&cache).await.ok();
Local::regular(&cache).remove_dir_all().await.ok();
provider::rename(cache_tmp, cache).await.context("Cannot persist downloaded file")?;
let lock = task.url.cache_lock().context("Cannot determine cache lock")?;
let hash = format!("{:x}", cha.hash_u128());
Local.write(lock, hash).await.context("Cannot lock cache")?;
Local::regular(&lock).write(hash).await.context("Cannot lock cache")?;
break;
}
@ -448,7 +449,7 @@ impl File {
pub(crate) async fn upload_do(&self, task: FileInUploadDo) -> Result<(), FileOutUploadDo> {
let lock = task.url.cache_lock().context("Cannot determine cache lock")?;
let hash = Local.read_to_string(&lock).await.context("Cannot read 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() {
Err(anyhow!("Remote file has changed since last download"))?;
@ -474,7 +475,7 @@ impl File {
let cha = Self::cha(&task.url, true, None).await.context("Cannot stat uploaded file")?;
let hash = format!("{:x}", cha.hash_u128());
Local.write(lock, hash).await.context("Cannot lock cache")?;
Local::regular(&lock).write(hash).await.context("Cannot lock cache")?;
break;
}
@ -510,7 +511,7 @@ impl File {
url.hash(&mut h);
timestamp_us().hash(&mut h);
unique_name(parent.join(format!(".{:x}.%tmp", h.finish())), async { false }).await
unique_name(parent.try_join(format!(".{:x}.%tmp", h.finish()))?, async { false }).await
}
}

View file

@ -1,4 +1,4 @@
#![allow(clippy::option_map_unit_fn, clippy::unit_arg)]
#![allow(clippy::option_map_unit_fn)]
mod macros;

View file

@ -8,7 +8,7 @@ use yazi_config::{YAZI, plugin::{Fetcher, Preloader}};
use yazi_dds::Pump;
use yazi_parser::{app::PluginOpt, tasks::ProcessOpenOpt};
use yazi_proxy::TasksProxy;
use yazi_shared::{Id, Throttle, scheme::SchemeLike, url::{UrlBuf, UrlLike}};
use yazi_shared::{Id, Throttle, url::{UrlBuf, UrlLike}};
use yazi_vfs::{must_be_dir, provider, unique_name};
use super::{Ongoing, TaskOp};
@ -79,7 +79,13 @@ impl Scheduler {
let mut ongoing = self.ongoing.lock();
let id = ongoing.add::<FileProgPaste>(format!("Cut {} to {}", from.display(), to.display()));
if to.starts_with(&from) && !to.covariant(&from) {
let Ok(prefixed) = to.try_starts_with(&from) else {
return self
.ops
.out(id, FileOutPaste::Fail("Path being cut has a different encoding".to_owned()));
};
if prefixed && !to.covariant(&from) {
return self.ops.out(id, FileOutPaste::Fail("Cannot cut directory into itself".to_owned()));
}
@ -112,7 +118,13 @@ impl Scheduler {
to.display()
));
if to.starts_with(&from) && !to.covariant(&from) {
let Ok(prefixed) = to.try_starts_with(&from) else {
return self
.ops
.out(id, FileOutPaste::Fail("Path being copied has a different encoding".to_owned()));
};
if prefixed && !to.covariant(&from) {
return self.ops.out(id, FileOutPaste::Fail("Cannot copy directory into itself".to_owned()));
}
@ -148,7 +160,14 @@ impl Scheduler {
to.display()
));
if to.starts_with(&from) && !to.covariant(&from) {
let Ok(prefixed) = to.try_starts_with(&from) else {
return self.ops.out(
id,
FileOutHardlink::Fail("Path being hardlinked has a different encoding".to_owned()),
);
};
if prefixed && !to.covariant(&from) {
return self
.ops
.out(id, FileOutHardlink::Fail("Cannot hardlink directory into itself".to_owned()));
@ -215,7 +234,7 @@ impl Scheduler {
ongoing.hooks.add_sync(id, move |canceled| _ = tx.send(canceled));
}
if !url.scheme.is_remote() {
if !url.kind().is_remote() {
return self.ops.out(id, FileOutDownload::Fail("Cannot download non-remote file".to_owned()));
};
@ -229,7 +248,7 @@ impl Scheduler {
let mut ongoing = self.ongoing.lock();
let id = ongoing.add::<FileProgUpload>(format!("Upload {}", url.display()));
if !url.scheme.is_remote() {
if !url.kind().is_remote() {
return self.ops.out(id, FileOutUpload::Fail("Cannot upload non-remote file".to_owned()));
};