mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 14:51:03 +00:00
feat!: custom VFS provider (#4118)
This commit is contained in:
parent
dbb0cc0d55
commit
6d84921e70
173 changed files with 2665 additions and 1852 deletions
|
|
@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
- Make help menu a command palette ([#4074])
|
||||
- Input history ([#4104])
|
||||
- Experimental `%y`, `%Y`, `%t`, `%T`, `%yN`, `%YN`, `%tN`, `%TN` shell formatting parameters ([#4108])
|
||||
- Custom VFS provider ([#4118])
|
||||
- Make visual mode support wraparound scrolling ([#4101])
|
||||
- H/M/L Vim-like motion for moving cursor relative to viewport ([#3970])
|
||||
- Context-aware icons for inputs ([#4080])
|
||||
|
|
@ -31,6 +32,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
### Changed
|
||||
|
||||
- Rename `<BackTab>` to `<S-Tab>` ([#3989])
|
||||
- Rename `type` field in `vfs.toml` to `kind`, leaving `type` available for future custom VFS parameters ([#4118])
|
||||
- Remove `Url.is_archive` so `archive://` can be registered as a custom scheme ([#4118])
|
||||
- Make `mgr::Yanked`, `tab::Selected`, and the `@yank` DDS event return `File` instead of `Url` from `__pairs()` ([#4096])
|
||||
- Remove `help:filter` action since the filter input is now always available ([#4074])
|
||||
- `[help]` of `theme.toml`: supersede `on` with `chord`, supersede `run` and `desc` with `action`, remove `footer` ([#4074])
|
||||
|
|
@ -40,6 +43,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
- Deprecate `backward --far` and `forward --far` in favor of `backward wide` and `forward wide`, respectively ([#4012])
|
||||
- Deprecate `tab::Mode.is_visual` in favor of the new `tab::Mode.is_normal` ([#4101])
|
||||
- Deprecate `Url.is_regular`, `Url.is_search`, and `Url.domain` in favor of `Url.spec.is_regular`, `Url.spec.is_search`, and `Url.spec.domain`, respectively ([#4118])
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
@ -1775,3 +1779,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
[#4101]: https://github.com/sxyazi/yazi/pull/4101
|
||||
[#4104]: https://github.com/sxyazi/yazi/pull/4104
|
||||
[#4108]: https://github.com/sxyazi/yazi/pull/4108
|
||||
[#4118]: https://github.com/sxyazi/yazi/pull/4118
|
||||
|
|
|
|||
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -5197,6 +5197,7 @@ dependencies = [
|
|||
"hashbrown 0.17.1",
|
||||
"mlua",
|
||||
"parking_lot",
|
||||
"strum",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
|
|
@ -5381,15 +5382,14 @@ dependencies = [
|
|||
"mlua",
|
||||
"parking_lot",
|
||||
"russh",
|
||||
"serde",
|
||||
"tokio",
|
||||
"toml",
|
||||
"tracing",
|
||||
"typed-path",
|
||||
"yazi-binding",
|
||||
"yazi-codegen",
|
||||
"yazi-config",
|
||||
"yazi-fs",
|
||||
"yazi-macro",
|
||||
"yazi-runner",
|
||||
"yazi-sftp",
|
||||
"yazi-shared",
|
||||
"yazi-shim",
|
||||
|
|
@ -5436,7 +5436,6 @@ dependencies = [
|
|||
"tracing",
|
||||
"unicode-width",
|
||||
"yazi-binding",
|
||||
"yazi-dds",
|
||||
"yazi-macro",
|
||||
"yazi-shared",
|
||||
"yazi-shim",
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::process;
|
|||
|
||||
use anyhow::Result;
|
||||
use yazi_boot::ARGS;
|
||||
use yazi_fs::provider::{Provider, local::Local};
|
||||
use yazi_fs::engine::{Engine, local::Local};
|
||||
use yazi_parser::app::QuitForm;
|
||||
use yazi_shared::{data::Data, strand::{StrandBuf, StrandLike, ToStrand}};
|
||||
use yazi_tui::Raterm;
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ use std::{io, mem};
|
|||
|
||||
use anyhow::Result;
|
||||
use yazi_core::cmp::{CmpItem, CmpOpt};
|
||||
use yazi_fs::{path::clean_url, provider::{DirReader, FileHolder}};
|
||||
use yazi_fs::{engine::{DirReader, FileHolder}, path::clean_url};
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::cmp::TriggerForm;
|
||||
use yazi_proxy::CmpProxy;
|
||||
use yazi_shared::{AnyAsciiChar, BytePredictor, data::Data, natsort, path::{AsPath, PathBufDyn, PathLike}, scheme::{SchemeCow, SchemeLike}, strand::{AsStrand, StrandLike}, url::{UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_vfs::provider;
|
||||
use yazi_shared::{AnyAsciiChar, BytePredictor, data::Data, natsort, path::{AsPath, PathBufDyn, PathLike}, spec::Spec, strand::{AsStrand, StrandLike}, url::{UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_vfs::engine;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ impl Actor for Trigger {
|
|||
}
|
||||
|
||||
cx.cmp.handle = Some(tokio::spawn(async move {
|
||||
let mut dir = provider::read_dir(&parent).await?;
|
||||
let mut dir = engine::read_dir(&parent).await?;
|
||||
let mut cache = vec![];
|
||||
|
||||
// "/" is both a directory separator and the root directory per se
|
||||
|
|
@ -66,14 +66,14 @@ impl Actor for Trigger {
|
|||
|
||||
impl Trigger {
|
||||
fn split_url(s: &str) -> Option<(UrlBuf, PathBufDyn)> {
|
||||
let (scheme, path) = SchemeCow::parse(s.as_bytes()).ok()?;
|
||||
let (spec, path) = Spec::parse(s.as_bytes()).ok()?;
|
||||
if path.is_empty() && !AnyAsciiChar::SEP.predicate(s.bytes().last()?) {
|
||||
return None; // We don't complete a `sftp://test`, but `sftp://test/`
|
||||
}
|
||||
|
||||
// Scheme
|
||||
let scheme = scheme.zeroed();
|
||||
if scheme.is_local() && path.as_strand() == "~" {
|
||||
// Spec
|
||||
let spec = spec.zeroed();
|
||||
if spec.kind.is_local() && path.as_strand() == "~" {
|
||||
return None; // We don't complete a `~`, but `~/`
|
||||
}
|
||||
|
||||
|
|
@ -81,11 +81,11 @@ impl Trigger {
|
|||
let child = path.rsplit_pred(AnyAsciiChar::SEP).map_or(path.as_path(), |(_, c)| c).to_owned();
|
||||
|
||||
// Parent
|
||||
let url = UrlCow::try_from((scheme.clone().zeroed(), path)).ok()?;
|
||||
let abs = if let Some(u) = provider::try_absolute(&url) { u } else { url };
|
||||
let url = UrlCow::try_from((spec.clone().zeroed(), path)).ok()?;
|
||||
let abs = if let Some(u) = engine::try_absolute(&url) { u } else { url };
|
||||
let parent = abs.loc().try_strip_suffix(&child).ok()?;
|
||||
|
||||
Some((clean_url(UrlCow::try_from((scheme, parent)).ok()?), child))
|
||||
Some((clean_url(UrlCow::try_from((spec, parent)).ok()?), child))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,10 +108,11 @@ mod tests {
|
|||
#[test]
|
||||
fn test_split() {
|
||||
yazi_shared::init_tests();
|
||||
yazi_config::init_tests();
|
||||
yazi_fs::init();
|
||||
|
||||
assert_eq!(Trigger::split_url(""), None);
|
||||
assert_eq!(Trigger::split_url("sftp://test"), None);
|
||||
assert_eq!(Trigger::split_url("sftp://vps"), None);
|
||||
compare(" ", "", " ");
|
||||
|
||||
compare("/", "/", "");
|
||||
|
|
@ -127,16 +128,18 @@ mod tests {
|
|||
compare("/foo/bar", "/foo/", "bar");
|
||||
compare("///foo/bar", "/foo/", "bar");
|
||||
|
||||
CWD.set(&"sftp://test".parse::<UrlBuf>().unwrap(), || {});
|
||||
compare("sftp://test/a", "sftp://test/.", "a");
|
||||
compare("sftp://test//a", "sftp://test//", "a");
|
||||
compare("sftp://test2/a", "sftp://test2/.", "a");
|
||||
compare("sftp://test2//a", "sftp://test2//", "a");
|
||||
CWD.set(&"sftp://vps".parse::<UrlBuf>().unwrap(), || {});
|
||||
compare("sftp://vps/a", "sftp://vps/.", "a");
|
||||
compare("sftp://vps//a", "sftp://vps//", "a");
|
||||
compare("test-scope://aws/a", "test-scope://aws/.", "a");
|
||||
compare("test-scope://aws//a", "test-scope://aws//", "a");
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn test_split() {
|
||||
yazi_shared::init_tests();
|
||||
yazi_config::init_tests();
|
||||
yazi_fs::init();
|
||||
compare("foo", "", "foo");
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::{Result, anyhow};
|
|||
use scopeguard::defer;
|
||||
use yazi_binding::Permit;
|
||||
use yazi_config::{YAZI, opener::OpenerRuleArc};
|
||||
use yazi_fs::{FilesOp, Splatter, file::File, provider::{Provider, local::Local}};
|
||||
use yazi_fs::{FilesOp, Splatter, engine::{Engine, local::Local}, file::File};
|
||||
use yazi_macro::{succ, writef};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_proxy::TasksProxy;
|
||||
|
|
@ -13,7 +13,7 @@ use yazi_shared::{data::Data, strand::Strand, url::{AsUrl, UrlBuf, UrlLike}};
|
|||
use yazi_shim::path::CROSS_SEPARATOR;
|
||||
use yazi_term::YIELD_TO_SUBPROCESS;
|
||||
use yazi_tty::{TTY, sequence::EraseScreen};
|
||||
use yazi_vfs::{VfsFile, provider};
|
||||
use yazi_vfs::{VfsFile, engine};
|
||||
use yazi_watcher::WATCHER;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -31,7 +31,7 @@ impl Actor for BulkCreate {
|
|||
let cwd = cx.cwd().clone();
|
||||
tokio::spawn(async move {
|
||||
let tmp = YAZI.preview.tmpfile("bulk-create");
|
||||
provider::create_new(&tmp).await?;
|
||||
engine::create_new(&tmp).await?;
|
||||
|
||||
defer! {
|
||||
let tmp = tmp.clone();
|
||||
|
|
@ -76,10 +76,10 @@ impl BulkCreate {
|
|||
};
|
||||
|
||||
let result: io::Result<()> = if entry.is_dir {
|
||||
provider::create_dir_all(&dist).await
|
||||
engine::create_dir_all(&dist).await
|
||||
} else if let Some(parent) = dist.parent() {
|
||||
provider::create_dir_all(parent).await.ok();
|
||||
provider::create_new(&dist).await.map(|_| ())
|
||||
engine::create_dir_all(parent).await.ok();
|
||||
engine::create_new(&dist).await.map(|_| ())
|
||||
} else {
|
||||
Err(io::Error::other("No parent directory"))
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use tokio::io::AsyncWriteExt;
|
|||
use yazi_binding::Permit;
|
||||
use yazi_config::{YAZI, opener::OpenerRuleArc};
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_fs::{FilesOp, Splatter, file::File, max_common_root, path::skip_url, provider::{FileBuilder, Provider, local::{Gate, Local}}};
|
||||
use yazi_fs::{FilesOp, Splatter, engine::{Engine, FileBuilder, local::{Demand, Local}}, file::File, max_common_root, path::skip_url};
|
||||
use yazi_macro::{err, succ, writef};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_proxy::TasksProxy;
|
||||
|
|
@ -15,7 +15,7 @@ use yazi_scheduler::{AppProxy, NotifyProxy, process::ShellOpt};
|
|||
use yazi_shared::{data::Data, path::PathDyn, strand::{AsStrand, AsStrandJoin, Strand, StrandBuf, StrandLike}, url::{AsUrl, UrlBuf, UrlLike}};
|
||||
use yazi_term::YIELD_TO_SUBPROCESS;
|
||||
use yazi_tty::{TTY, sequence::EraseScreen};
|
||||
use yazi_vfs::{VfsFile, maybe_exists, provider};
|
||||
use yazi_vfs::{VfsFile, engine, maybe_exists};
|
||||
use yazi_watcher::WATCHER;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -46,7 +46,7 @@ impl Actor for BulkRename {
|
|||
tokio::spawn(async move {
|
||||
let tmp = YAZI.preview.tmpfile("bulk-rename");
|
||||
|
||||
Gate::default()
|
||||
Demand::default()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(&tmp)
|
||||
|
|
@ -127,9 +127,9 @@ impl BulkRename {
|
|||
continue;
|
||||
};
|
||||
|
||||
if maybe_exists(&new).await && !provider::must_identical(&old, &new).await {
|
||||
if maybe_exists(&new).await && !engine::must_identical(&old, &new).await {
|
||||
failed.push((o, n, anyhow!("Destination already exists")));
|
||||
} else if let Err(e) = provider::rename(&old, &new).await {
|
||||
} else if let Err(e) = engine::rename(&old, &new).await {
|
||||
failed.push((o, n, e.into()));
|
||||
} else if let Ok(f) = File::new(new).await {
|
||||
succeeded.insert(old, f);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use yazi_macro::{act, err, input, render, succ};
|
|||
use yazi_parser::mgr::CdForm;
|
||||
use yazi_proxy::{CmpProxy, MgrProxy};
|
||||
use yazi_shared::{Debounce, data::Data, url::{AsUrl, UrlBuf, UrlLike}};
|
||||
use yazi_vfs::{VfsFile, provider};
|
||||
use yazi_vfs::{VfsFile, engine};
|
||||
use yazi_widgets::input::InputEvent;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -73,7 +73,7 @@ impl Cd {
|
|||
match result {
|
||||
InputEvent::Submit(s) => {
|
||||
let Ok(url) = UrlBuf::try_from(s).map(expand_url) else { return };
|
||||
let Ok(url) = provider::absolute(&url).await else { return };
|
||||
let Ok(url) = engine::absolute(&url).await else { return };
|
||||
let url = clean_url(url);
|
||||
|
||||
let Ok(file) = File::new(&url).await else { return };
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use yazi_macro::{input, ok_or_not_found, succ};
|
|||
use yazi_parser::mgr::CreateForm;
|
||||
use yazi_proxy::{ConfirmProxy, MgrProxy};
|
||||
use yazi_shared::{AnyAsciiChar, BytePredictor, data::Data, strand::{StrandBuf, StrandLike}, url::{UrlBuf, UrlLike}};
|
||||
use yazi_vfs::{VfsFile, provider};
|
||||
use yazi_vfs::{VfsFile, engine};
|
||||
use yazi_watcher::WATCHER;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -62,22 +62,22 @@ impl Create {
|
|||
let _permit = WATCHER.acquire().await.unwrap();
|
||||
|
||||
if dir {
|
||||
provider::create_dir_all(&new).await?;
|
||||
} else if let Ok(real) = provider::casefold(&new).await
|
||||
engine::create_dir_all(&new).await?;
|
||||
} else if let Ok(real) = engine::casefold(&new).await
|
||||
&& let Some((parent, urn)) = real.pair()
|
||||
{
|
||||
ok_or_not_found!(provider::remove_file(&new).await);
|
||||
ok_or_not_found!(engine::remove_file(&new).await);
|
||||
FilesOp::Deleting(parent.into(), [urn.into()].into()).emit();
|
||||
provider::create(&new).await?;
|
||||
engine::create(&new).await?;
|
||||
} else if let Some(parent) = new.parent() {
|
||||
provider::create_dir_all(parent).await.ok();
|
||||
ok_or_not_found!(provider::remove_file(&new).await);
|
||||
provider::create(&new).await?;
|
||||
engine::create_dir_all(parent).await.ok();
|
||||
ok_or_not_found!(engine::remove_file(&new).await);
|
||||
engine::create(&new).await?;
|
||||
} else {
|
||||
bail!("Cannot create file at root");
|
||||
}
|
||||
|
||||
if let Ok(real) = provider::casefold(&new).await
|
||||
if let Ok(real) = engine::casefold(&new).await
|
||||
&& let Some((parent, urn)) = real.pair()
|
||||
{
|
||||
let file = File::new(&real).await?;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use yazi_macro::succ;
|
|||
use yazi_parser::VoidForm;
|
||||
use yazi_proxy::MgrProxy;
|
||||
use yazi_shared::{data::Data, url::UrlLike};
|
||||
use yazi_vfs::provider;
|
||||
use yazi_vfs::engine;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ impl Actor for Displace {
|
|||
let from = cx.cwd().to_owned();
|
||||
tokio::spawn(async move {
|
||||
MgrProxy::displace_do(tab, DisplaceOpt {
|
||||
to: provider::canonicalize(&from).await.map_err(Into::into),
|
||||
to: engine::canonicalize(&from).await.map_err(Into::into),
|
||||
from,
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
|||
use futures::{StreamExt, stream::FuturesUnordered};
|
||||
use hashbrown::HashSet;
|
||||
use yazi_core::mgr::OpenOpt;
|
||||
use yazi_fs::{FsScheme, file::File, provider::{Provider, local::Local}};
|
||||
use yazi_fs::{FsSpec, engine::{Engine, local::Local}, file::File};
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::mgr::DownloadForm;
|
||||
use yazi_proxy::MgrProxy;
|
||||
|
|
@ -74,7 +74,7 @@ impl Actor for Download {
|
|||
|
||||
impl Download {
|
||||
async fn prepare(urls: &[UrlBuf]) {
|
||||
let roots: HashSet<_> = urls.iter().filter_map(|u| u.scheme().cache()).collect();
|
||||
let roots: HashSet<_> = urls.iter().filter_map(|u| u.auth().cache()).collect();
|
||||
for mut root in roots {
|
||||
root.push("%lock");
|
||||
Local::regular(&root).create_dir_all().await.ok();
|
||||
|
|
|
|||
|
|
@ -52,11 +52,8 @@ impl Refresh {
|
|||
}
|
||||
}
|
||||
|
||||
let futs: Vec<_> = folders
|
||||
.iter()
|
||||
.filter(|&f| f.url.is_absolute() && f.url.is_internal())
|
||||
.map(|&f| go(f.url.clone(), f.cha))
|
||||
.collect();
|
||||
let futs: Vec<_> =
|
||||
folders.iter().filter(|&f| f.url.is_absolute()).map(|&f| go(f.url.clone(), f.cha)).collect();
|
||||
|
||||
if !futs.is_empty() {
|
||||
tokio::spawn(futures::future::join_all(futs));
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use yazi_macro::{act, err, input, ok_or_not_found, succ};
|
|||
use yazi_parser::mgr::RenameForm;
|
||||
use yazi_proxy::{ConfirmProxy, MgrProxy};
|
||||
use yazi_shared::{data::Data, id::Id, url::{UrlBuf, UrlLike}};
|
||||
use yazi_vfs::{VfsFile, provider};
|
||||
use yazi_vfs::{VfsFile, engine};
|
||||
use yazi_watcher::WATCHER;
|
||||
use yazi_widgets::input::InputEvent;
|
||||
|
||||
|
|
@ -70,18 +70,18 @@ impl Rename {
|
|||
let Some(_) = new.pair() else { return Ok(()) };
|
||||
let _permit = WATCHER.acquire().await.unwrap();
|
||||
|
||||
let overwritten = provider::casefold(&new).await;
|
||||
provider::rename(&old, &new).await?;
|
||||
let overwritten = engine::casefold(&new).await;
|
||||
engine::rename(&old, &new).await?;
|
||||
|
||||
if let Ok(u) = overwritten
|
||||
&& u != new
|
||||
&& let Some((parent, urn)) = u.pair()
|
||||
{
|
||||
ok_or_not_found!(provider::rename(&u, &new).await);
|
||||
ok_or_not_found!(engine::rename(&u, &new).await);
|
||||
FilesOp::Deleting(parent.to_owned(), [urn.into()].into()).emit();
|
||||
}
|
||||
|
||||
let new = provider::casefold(&new).await?;
|
||||
let new = engine::casefold(&new).await?;
|
||||
let Some((new_p, new_n)) = new.pair() else { return Ok(()) };
|
||||
|
||||
let file = File::new(&new).await?;
|
||||
|
|
@ -103,7 +103,7 @@ impl Rename {
|
|||
};
|
||||
|
||||
Ok(
|
||||
provider::must_identical(old, new).await
|
||||
engine::must_identical(old, new).await
|
||||
|| ConfirmProxy::show(ConfirmCfg::overwrite(&file)).await,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ impl Actor for Stash {
|
|||
const NAME: &str = "stash";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
if form.target.is_absolute() && form.target.is_internal() {
|
||||
if form.target.is_absolute() && !form.target.is_search() {
|
||||
cx.tab_mut().backstack.push(form.target.as_url());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use image::{DynamicImage, ImageDecoder, ImageError, ImageReader, Limits, codecs:
|
|||
use ratatui_core::layout::Rect;
|
||||
use yazi_config::YAZI;
|
||||
use yazi_emulator::Dimension;
|
||||
use yazi_fs::provider::{Provider, local::Local};
|
||||
use yazi_fs::engine::{Engine, local::Local};
|
||||
|
||||
use crate::Icc;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,24 @@ use yazi_codegen::FromLuaOwned;
|
|||
use crate::Error;
|
||||
|
||||
#[derive(FromLuaOwned)]
|
||||
pub struct MpscTx<T: FromLua + 'static>(pub tokio::sync::mpsc::Sender<T>);
|
||||
pub struct MpscTx<T: FromLua + 'static, U: 'static = T> {
|
||||
tx: tokio::sync::mpsc::Sender<U>,
|
||||
f: fn(T) -> U,
|
||||
}
|
||||
pub struct MpscRx<T: IntoLua + 'static>(pub tokio::sync::mpsc::Receiver<T>);
|
||||
|
||||
impl<T: FromLua> UserData for MpscTx<T> {
|
||||
impl<T: FromLua> MpscTx<T> {
|
||||
pub fn new(tx: tokio::sync::mpsc::Sender<T>) -> Self { Self { tx, f: |v| v } }
|
||||
}
|
||||
|
||||
impl<T: FromLua, U> MpscTx<T, U> {
|
||||
pub fn map(tx: tokio::sync::mpsc::Sender<U>, f: fn(T) -> U) -> Self { Self { tx, f } }
|
||||
}
|
||||
|
||||
impl<T: FromLua, U: 'static> UserData for MpscTx<T, U> {
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods.add_async_method("send", |lua, me, value: Value| async move {
|
||||
match me.0.send(T::from_lua(value, &lua)?).await {
|
||||
match me.tx.send((me.f)(T::from_lua(value, &lua)?)).await {
|
||||
Ok(()) => true.into_lua_multi(&lua),
|
||||
Err(e) => (false, Error::custom(e.to_string())).into_lua_multi(&lua),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{borrow::Cow, fmt::Display};
|
||||
use std::{borrow::Cow, fmt::Display, io};
|
||||
|
||||
use mlua::{ExternalError, Lua, LuaString, MetaMethod, UserData, UserDataFields, UserDataMethods, Value};
|
||||
use yazi_codegen::FromLuaOwned;
|
||||
|
|
@ -12,6 +12,17 @@ pub enum Error {
|
|||
Custom(SStr),
|
||||
}
|
||||
|
||||
impl From<Error> for io::Error {
|
||||
fn from(value: Error) -> Self {
|
||||
match value {
|
||||
Error::Io(e) => e,
|
||||
Error::Fs(e) => e.into(),
|
||||
Error::Serde(e) => Self::other(e),
|
||||
Error::Custom(s) => Self::other(s.into_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
let custom = lua.create_function(|_, msg: String| Ok(Self::custom(msg)))?;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use futures::executor::block_on;
|
|||
use hashbrown::HashSet;
|
||||
use yazi_fs::{CWD, path::clean_url};
|
||||
use yazi_shared::{strand::StrandBuf, url::{UrlBuf, UrlLike}};
|
||||
use yazi_vfs::provider;
|
||||
use yazi_vfs::engine;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Boot {
|
||||
|
|
@ -22,7 +22,7 @@ impl Boot {
|
|||
async fn go(entry: &UrlBuf) -> (UrlBuf, StrandBuf) {
|
||||
let mut entry = clean_url(entry);
|
||||
|
||||
if let Ok(u) = provider::absolute(&entry).await
|
||||
if let Ok(u) = engine::absolute(&entry).await
|
||||
&& u.is_owned()
|
||||
{
|
||||
entry = u.into_owned();
|
||||
|
|
@ -32,7 +32,7 @@ impl Boot {
|
|||
return (entry, Default::default());
|
||||
};
|
||||
|
||||
if provider::metadata(&entry).await.is_ok_and(|m| m.is_file()) {
|
||||
if engine::metadata(&entry).await.is_ok_and(|m| m.is_file()) {
|
||||
(parent.into(), child.into())
|
||||
} else {
|
||||
(entry, Default::default())
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use anyhow::{Context, Result};
|
||||
use yazi_fs::{ok_or_not_found, provider::{Provider, local::Local}};
|
||||
use yazi_fs::{engine::{Engine, local::Local}, ok_or_not_found};
|
||||
use yazi_macro::outln;
|
||||
|
||||
use super::Dependency;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use yazi_fs::provider::{Provider, local::Local};
|
||||
use yazi_fs::engine::{Engine, local::Local};
|
||||
use yazi_macro::outln;
|
||||
|
||||
use super::Dependency;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::{Context, Result, bail};
|
||||
use twox_hash::XxHash3_128;
|
||||
use yazi_fs::provider::local::Local;
|
||||
use yazi_fs::engine::local::Local;
|
||||
use yazi_macro::ok_or_not_found;
|
||||
|
||||
use super::Dependency;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::{path::PathBuf, str::FromStr};
|
|||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use yazi_fs::{Xdg, provider::{Provider, local::Local}};
|
||||
use yazi_fs::{Xdg, engine::{Engine, local::Local}};
|
||||
use yazi_macro::{ok_or_not_found, outln};
|
||||
|
||||
use super::Dependency;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::{io, path::Path};
|
||||
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use yazi_fs::provider::{FileBuilder, Provider, local::{Gate, Local}};
|
||||
use yazi_fs::engine::{Engine, FileBuilder, local::{Demand, Local}};
|
||||
use yazi_macro::ok_or_not_found;
|
||||
|
||||
#[inline]
|
||||
|
|
@ -21,7 +21,7 @@ pub async fn copy_and_seal(from: &Path, to: &Path) -> io::Result<()> {
|
|||
let b = Local::regular(from).read().await?;
|
||||
ok_or_not_found!(remove_sealed(to).await);
|
||||
|
||||
let mut file = Gate::default().create_new(true).write(true).truncate(true).open(to).await?;
|
||||
let mut file = Demand::default().create_new(true).write(true).truncate(true).open(to).await?;
|
||||
file.write_all(&b).await?;
|
||||
|
||||
let mut perm = file.metadata().await?.permissions();
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ pub fn deserialize_over1(input: TokenStream) -> TokenStream {
|
|||
impl #impl_generics yazi_shim::toml::DeserializeOverWith for #ident #ty_generics #where_clause {
|
||||
fn deserialize_over_with<'__de, __D: serde::Deserializer<'__de>>(self, de: __D) -> Result<Self, __D::Error> {
|
||||
use serde::de::{Error, IgnoredAny, MapAccess, Visitor};
|
||||
use yazi_shared::KebabCasedString;
|
||||
use yazi_shared::KebabCasedKey;
|
||||
use yazi_shim::{serde::single_map_entry, toml::{DeserializeOverHook, DeserializeOverSeed, DeserializeOverWith}};
|
||||
|
||||
struct V #impl_generics (#ident #ty_generics) #where_clause;
|
||||
|
|
@ -67,7 +67,7 @@ pub fn deserialize_over1(input: TokenStream) -> TokenStream {
|
|||
}
|
||||
|
||||
fn visit_map<__M: MapAccess<'__de>>(mut self, mut map: __M) -> Result<Self::Value, __M::Error> {
|
||||
while let Some(key) = map.next_key::<KebabCasedString>()? {
|
||||
while let Some(key) = map.next_key::<KebabCasedKey>()? {
|
||||
match key.as_ref() {
|
||||
#(#normal_arms,)*
|
||||
#flatten_arm
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
yazi_macro::mod_pub!(keymap mgr open opener plugin popup preview tasks theme which);
|
||||
yazi_macro::mod_pub!(keymap mgr open opener plugin popup preview tasks theme vfs which);
|
||||
|
||||
yazi_macro::mod_flat!(icon inject layout mixing pattern platform preset priority selectable selector yazi);
|
||||
yazi_macro::mod_flat!(icon inject layout mixing pattern platform preset priority selectable selector tests yazi);
|
||||
|
||||
use std::io::{Read, Write};
|
||||
|
||||
|
|
@ -11,6 +11,7 @@ use yazi_tty::{TTY, sequence::SetSgr};
|
|||
pub static YAZI: RoCell<yazi::Yazi> = RoCell::new();
|
||||
pub static KEYMAP: RoCell<keymap::Keymap> = RoCell::new();
|
||||
pub static THEME: RoCell<theme::Theme> = RoCell::new();
|
||||
pub static VFS: RoCell<vfs::Vfs> = RoCell::new();
|
||||
pub static LAYOUT: SyncCell<Layout> = SyncCell::new(Layout::default());
|
||||
|
||||
pub fn init() -> anyhow::Result<()> {
|
||||
|
|
@ -24,14 +25,17 @@ pub fn init() -> anyhow::Result<()> {
|
|||
fn try_init(merge: bool) -> anyhow::Result<()> {
|
||||
let mut yazi = Preset::yazi()?;
|
||||
let mut keymap = Preset::keymap()?;
|
||||
let mut vfs = Preset::vfs()?;
|
||||
|
||||
if merge {
|
||||
yazi = yazi.deserialize_over(&yazi::Yazi::read()?)?;
|
||||
keymap = keymap.deserialize_over(&keymap::Keymap::read()?)?;
|
||||
vfs = vfs.deserialize_over(&vfs::Vfs::read()?)?;
|
||||
}
|
||||
|
||||
YAZI.init(yazi);
|
||||
KEYMAP.init(keymap);
|
||||
VFS.init(vfs);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::{fmt::Debug, str::FromStr};
|
|||
use anyhow::{Result, bail};
|
||||
use globset::{Candidate, GlobBuilder};
|
||||
use serde::Deserialize;
|
||||
use yazi_shared::{scheme::SchemeKind, url::AsUrl};
|
||||
use yazi_shared::{auth::Auth, url::AsUrl};
|
||||
|
||||
use crate::Mixable;
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ impl Pattern {
|
|||
|
||||
if is_dir != self.is_dir {
|
||||
return false;
|
||||
} else if !self.scheme.matches(url.kind()) {
|
||||
} else if !self.scheme.matches(url.auth()) {
|
||||
return false;
|
||||
} else if self.is_star {
|
||||
return true;
|
||||
|
|
@ -112,59 +112,43 @@ impl Mixable for Pattern {
|
|||
}
|
||||
|
||||
// --- Scheme
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
enum PatternScheme {
|
||||
Any,
|
||||
Local,
|
||||
Remote,
|
||||
Virtual,
|
||||
|
||||
Regular,
|
||||
Search,
|
||||
Archive,
|
||||
Sftp,
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
impl PatternScheme {
|
||||
fn parse(s: &str) -> Result<(Self, usize)> {
|
||||
let Some((protocol, _)) = s.split_once("://") else {
|
||||
let Some((s, _)) = s.split_once("://") else {
|
||||
return Ok((Self::Any, 0));
|
||||
};
|
||||
|
||||
let scheme = match protocol {
|
||||
let scheme = match s {
|
||||
"*" => Self::Any,
|
||||
"local" => Self::Local,
|
||||
"remote" => Self::Remote,
|
||||
"virtual" => Self::Virtual,
|
||||
|
||||
"regular" => Self::Regular,
|
||||
"search" => Self::Search,
|
||||
"archive" => Self::Archive,
|
||||
"sftp" => Self::Sftp,
|
||||
|
||||
"" => bail!("Invalid URL pattern: protocol is empty"),
|
||||
_ => bail!("Unknown protocol in URL pattern: {protocol}"),
|
||||
"" => bail!("Invalid URL pattern: scheme is empty"),
|
||||
other => Self::Custom(other.to_owned()),
|
||||
};
|
||||
|
||||
Ok((scheme, protocol.len() + 3))
|
||||
Ok((scheme, s.len() + 3))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn matches(self, kind: SchemeKind) -> bool {
|
||||
use SchemeKind as K;
|
||||
|
||||
match (self, kind) {
|
||||
(Self::Any, _) => true,
|
||||
(Self::Local, s) => s.is_local(),
|
||||
(Self::Remote, s) => s.is_remote(),
|
||||
(Self::Virtual, s) => s.is_virtual(),
|
||||
|
||||
(Self::Regular, K::Regular) => true,
|
||||
(Self::Search, K::Search) => true,
|
||||
(Self::Archive, K::Archive) => true,
|
||||
(Self::Sftp, K::Sftp) => true,
|
||||
|
||||
_ => false,
|
||||
fn matches(&self, auth: &Auth) -> bool {
|
||||
match self {
|
||||
Self::Any => true,
|
||||
Self::Local => auth.kind.is_local(),
|
||||
Self::Remote => auth.kind.is_remote(),
|
||||
Self::Virtual => auth.kind.is_virtual(),
|
||||
Self::Custom(name) => auth.scheme == name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -183,6 +167,8 @@ mod tests {
|
|||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_unix() {
|
||||
yazi_shared::init_tests();
|
||||
|
||||
// Wildcard
|
||||
assert!(matches("*", "/foo"));
|
||||
assert!(matches("*", "/foo/bar"));
|
||||
|
|
@ -217,6 +203,8 @@ mod tests {
|
|||
#[cfg(windows)]
|
||||
#[test]
|
||||
fn test_windows() {
|
||||
yazi_shared::init_tests();
|
||||
|
||||
// Wildcard
|
||||
assert!(matches("*", r#"C:\foo"#));
|
||||
assert!(matches("*", r#"C:\foo\bar"#));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use serde::Deserialize;
|
||||
use yazi_binding::position::{Offset, Origin, Position};
|
||||
use yazi_codegen::{DeserializeOver, DeserializeOver2};
|
||||
use yazi_shared::{scheme::Encode as EncodeScheme, url::Url};
|
||||
use yazi_shared::{spec::Encode as EncodeSpec, url::Url};
|
||||
use yazi_widgets::input::InputOpt;
|
||||
|
||||
#[derive(Deserialize, DeserializeOver, DeserializeOver2)]
|
||||
|
|
@ -49,7 +49,7 @@ impl Input {
|
|||
InputOpt {
|
||||
name: "cd".to_owned(),
|
||||
title: self.cd_title.clone(),
|
||||
value: if cwd.kind().is_local() { String::new() } else { EncodeScheme(cwd).to_string() },
|
||||
value: if cwd.kind().is_local() { String::new() } else { EncodeSpec(cwd).to_string() },
|
||||
history: "shared".to_owned(),
|
||||
position: Position::new(self.cd_origin, self.cd_offset),
|
||||
completion: true,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{Yazi, keymap::Keymap, theme::Theme};
|
||||
use crate::{Yazi, keymap::Keymap, theme::Theme, vfs::Vfs};
|
||||
|
||||
pub(crate) struct Preset;
|
||||
|
||||
|
|
@ -11,6 +11,10 @@ impl Preset {
|
|||
toml::from_str(&yazi_macro::config_preset!("keymap"))
|
||||
}
|
||||
|
||||
pub(super) fn vfs() -> Result<Vfs, toml::de::Error> {
|
||||
toml::from_str(&yazi_macro::config_preset!("vfs"))
|
||||
}
|
||||
|
||||
pub(super) fn theme(light: bool) -> Result<Theme, toml::de::Error> {
|
||||
toml::from_str(&if light {
|
||||
yazi_macro::theme_preset!("light")
|
||||
|
|
|
|||
9
yazi-config/src/tests.rs
Normal file
9
yazi-config/src/tests.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use std::sync::OnceLock;
|
||||
|
||||
use crate::{Preset, VFS};
|
||||
|
||||
pub fn init_tests() {
|
||||
static INIT: OnceLock<()> = OnceLock::new();
|
||||
|
||||
INIT.get_or_init(|| VFS.init(Preset::vfs().unwrap()));
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ use arc_swap::ArcSwap;
|
|||
use hashbrown::{HashMap, hash_map};
|
||||
use serde::{Deserialize, Deserializer, de::{MapAccess, Visitor}};
|
||||
use yazi_codegen::{DeserializeOver, Overlay};
|
||||
use yazi_shared::{KebabCasedString, SnakeCasedString};
|
||||
use yazi_shared::{KebabCasedKey, SnakeCasedString};
|
||||
use yazi_shim::{arc_swap::IntoPointee, toml::DeserializeOverWith};
|
||||
|
||||
use crate::theme::CustomSection;
|
||||
|
|
@ -39,7 +39,7 @@ impl<'de> Deserialize<'de> for Custom {
|
|||
|
||||
fn visit_map<M: MapAccess<'de>>(self, mut map: M) -> Result<Self::Value, M::Error> {
|
||||
let mut sections = HashMap::with_capacity(map.size_hint().unwrap_or(0));
|
||||
while let Some(key) = map.next_key::<KebabCasedString>()? {
|
||||
while let Some(key) = map.next_key::<KebabCasedKey>()? {
|
||||
let section = map.next_value::<CustomSection>()?;
|
||||
if !section.load().is_empty() {
|
||||
sections.insert(key.into_snake_cased(), section);
|
||||
|
|
@ -64,7 +64,7 @@ impl DeserializeOverWith for Custom {
|
|||
|
||||
fn visit_map<M: MapAccess<'de>>(self, mut map: M) -> Result<Custom, M::Error> {
|
||||
let mut sections = self.0.unwrap_unchecked();
|
||||
while let Some(key) = map.next_key::<KebabCasedString>()? {
|
||||
while let Some(key) = map.next_key::<KebabCasedKey>()? {
|
||||
let (key, new) = (key.into_snake_cased(), map.next_value::<CustomSection>()?);
|
||||
match sections.entry(key) {
|
||||
hash_map::Entry::Occupied(mut oe) => {
|
||||
|
|
|
|||
17
yazi-config/src/vfs/lua.rs
Normal file
17
yazi-config/src/vfs/lua.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
use serde::Deserialize;
|
||||
use yazi_shared::{auth::Auth, event::Cmd};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ServiceLua {
|
||||
#[serde(flatten)]
|
||||
pub auth: Arc<Auth>,
|
||||
pub run: Cmd,
|
||||
}
|
||||
|
||||
impl Deref for ServiceLua {
|
||||
type Target = Auth;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.auth }
|
||||
}
|
||||
1
yazi-config/src/vfs/mod.rs
Normal file
1
yazi-config/src/vfs/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
yazi_macro::mod_flat!(lua service services sftp vfs);
|
||||
64
yazi-config/src/vfs/service.rs
Normal file
64
yazi-config/src/vfs/service.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use serde::Deserialize;
|
||||
use yazi_shared::auth::{Auth, AuthKind};
|
||||
|
||||
use super::{ServiceLua, ServiceSftp};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "kind", rename_all = "kebab-case")]
|
||||
pub enum Service {
|
||||
Sftp(ServiceSftp),
|
||||
Mount(ServiceLua),
|
||||
Scope(ServiceLua),
|
||||
}
|
||||
|
||||
impl TryFrom<&'static Service> for &'static ServiceSftp {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: &'static Service) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Service::Sftp(p) => Ok(p),
|
||||
Service::Mount(_) | Service::Scope(_) => {
|
||||
Err("expected an SFTP service, got a custom VFS service")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&'static Service> for &'static ServiceLua {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: &'static Service) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Service::Sftp(_) => Err("expected a custom VFS service, got an SFTP service"),
|
||||
Service::Mount(lua) | Service::Scope(lua) => Ok(lua),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Service {
|
||||
pub fn kind(&self) -> AuthKind {
|
||||
match self {
|
||||
Self::Sftp(_) => AuthKind::Sftp,
|
||||
Self::Mount(_) => AuthKind::Mount,
|
||||
Self::Scope(_) => AuthKind::Scope,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn auth(&self) -> &Arc<Auth> {
|
||||
match self {
|
||||
Self::Sftp(sftp) => &sftp.auth,
|
||||
Self::Mount(lua) => &lua.auth,
|
||||
Self::Scope(lua) => &lua.auth,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn auth_mut(&mut self) -> &mut Arc<Auth> {
|
||||
match self {
|
||||
Self::Sftp(sftp) => &mut sftp.auth,
|
||||
Self::Mount(lua) => &mut lua.auth,
|
||||
Self::Scope(lua) => &mut lua.auth,
|
||||
}
|
||||
}
|
||||
}
|
||||
47
yazi-config/src/vfs/services.rs
Normal file
47
yazi-config/src/vfs/services.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use std::{ops::{Deref, DerefMut}, sync::Arc};
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use yazi_shared::KebabCasedKey;
|
||||
use yazi_shim::toml::{DeserializeOverHook, DeserializeOverWith};
|
||||
|
||||
use super::Service;
|
||||
|
||||
pub struct Services(HashMap<KebabCasedKey, Service>);
|
||||
|
||||
impl Deref for Services {
|
||||
type Target = HashMap<KebabCasedKey, Service>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl DerefMut for Services {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Services {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
Self(HashMap::deserialize(deserializer)?)
|
||||
.deserialize_over_hook()
|
||||
.map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl DeserializeOverWith for Services {
|
||||
fn deserialize_over_with<'de, D: Deserializer<'de>>(self, de: D) -> Result<Self, D::Error> {
|
||||
Ok(Self(self.0.deserialize_over_with(de)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl DeserializeOverHook for Services {
|
||||
fn deserialize_over_hook(mut self) -> Result<Self, toml::de::Error> {
|
||||
for (domain, service) in &mut self.0 {
|
||||
let kind = service.kind();
|
||||
let auth = Arc::get_mut(service.auth_mut()).expect("unique auth arc");
|
||||
|
||||
auth.kind = kind;
|
||||
auth.domain = domain.to_string().into();
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
use std::{env, path::PathBuf};
|
||||
use std::{env, ops::Deref, path::PathBuf, sync::Arc};
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, de};
|
||||
use yazi_fs::path::sanitize_path;
|
||||
use yazi_shared::auth::{Auth, AuthKind, Scheme};
|
||||
|
||||
#[derive(Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct ServiceSftp {
|
||||
#[serde(skip, default = "default_auth")]
|
||||
pub auth: Arc<Auth>,
|
||||
pub host: String,
|
||||
pub user: String,
|
||||
pub port: u16,
|
||||
|
|
@ -20,6 +23,14 @@ pub struct ServiceSftp {
|
|||
pub identity_agent: PathBuf,
|
||||
}
|
||||
|
||||
impl Deref for ServiceSftp {
|
||||
type Target = Auth;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.auth }
|
||||
}
|
||||
|
||||
fn default_auth() -> Arc<Auth> { Auth::new(AuthKind::Sftp, Scheme::Sftp, "") }
|
||||
|
||||
fn deserialize_path<'de, D>(deserializer: D) -> Result<PathBuf, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
53
yazi-config/src/vfs/vfs.rs
Normal file
53
yazi-config/src/vfs/vfs.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
use std::io;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::Deserialize;
|
||||
use yazi_codegen::{DeserializeOver, DeserializeOver1};
|
||||
use yazi_fs::{Xdg, ok_or_not_found};
|
||||
use yazi_shared::auth::AuthInventory;
|
||||
|
||||
use super::{Service, Services};
|
||||
use crate::VFS;
|
||||
|
||||
#[derive(Deserialize, DeserializeOver, DeserializeOver1)]
|
||||
pub struct Vfs {
|
||||
pub services: Services,
|
||||
}
|
||||
|
||||
impl Vfs {
|
||||
pub fn service<P>(domain: &str) -> io::Result<P>
|
||||
where
|
||||
P: TryFrom<&'static Service, Error = &'static str>,
|
||||
{
|
||||
let Some((key, value)) = VFS.services.get_key_value(domain) else {
|
||||
return Err(io::Error::other(format!("No such VFS service: {domain}")));
|
||||
};
|
||||
|
||||
match value.try_into() {
|
||||
Ok(p) => Ok(p),
|
||||
Err(e) => Err(io::Error::other(format!("VFS service `{key}` has wrong kind: {e}"))),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn read() -> Result<String> {
|
||||
let p = Xdg::config_dir().join("vfs.toml");
|
||||
ok_or_not_found(std::fs::read_to_string(&p))
|
||||
.with_context(|| format!("Failed to read config {p:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
// --- Inject
|
||||
inventory::submit! {
|
||||
AuthInventory {
|
||||
get: |scheme, domain| {
|
||||
VFS.services.get(domain).and_then(|service| {
|
||||
let auth = service.auth();
|
||||
if auth.scheme == scheme {
|
||||
Some(auth.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ impl Splatable for MgrSnap {
|
|||
.checked_sub(1)
|
||||
.and_then(|tab| self.tabs.get(tab))
|
||||
.map_or_else(|| &[][..], |s| &s.selected)
|
||||
.into_iter()
|
||||
.iter()
|
||||
.skip(idx.unwrap_or(0))
|
||||
.take(if idx.is_some() { 1 } else { usize::MAX })
|
||||
.map(AsUrl::as_url)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use yazi_config::{LAYOUT, YAZI};
|
|||
use yazi_fs::{Entries, FilesOp, cha::Cha, file::File};
|
||||
use yazi_macro::render;
|
||||
use yazi_runner::{RUNNER, previewer::{PeekError, PeekJob}};
|
||||
use yazi_shared::{pool::Symbol, url::{UrlBuf, UrlLike}};
|
||||
use yazi_shared::{pool::Symbol, url::UrlBuf};
|
||||
use yazi_vfs::{VfsEntries, VfsFilesOp};
|
||||
|
||||
use crate::{AppProxy, Highlighter, MgrProxy, tab::PreviewLock};
|
||||
|
|
@ -48,9 +48,7 @@ impl Preview {
|
|||
}
|
||||
|
||||
pub fn go_folder(&mut self, file: File, dir: Option<Cha>, mime: Symbol<str>, force: bool) {
|
||||
if !file.url.is_internal() {
|
||||
return self.go(file, mime, force);
|
||||
} else if self.folder_lock.as_ref() == Some(&file.url) {
|
||||
if self.folder_lock.as_ref() == Some(&file.url) {
|
||||
return self.go(file, mime, force);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
|||
use hashbrown::HashMap;
|
||||
use parking_lot::RwLock;
|
||||
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter};
|
||||
use yazi_fs::{Xdg, provider::{FileBuilder, Provider, local::{Gate, Local}}};
|
||||
use yazi_fs::{Xdg, engine::{Engine, FileBuilder, local::{Demand, Local}}};
|
||||
use yazi_shared::timestamp_us;
|
||||
use yazi_shim::cell::RoCell;
|
||||
|
||||
|
|
@ -63,7 +63,12 @@ impl State {
|
|||
Local::regular(&state_dir).create_dir_all().await?;
|
||||
|
||||
let mut buf = BufWriter::new(
|
||||
Gate::default().write(true).create(true).truncate(true).open(state_dir.join(".dds")).await?,
|
||||
Demand::default()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.open(state_dir.join(".dds"))
|
||||
.await?,
|
||||
);
|
||||
|
||||
let mut state = inner.into_iter().collect::<Vec<_>>();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::{io, path::PathBuf};
|
||||
|
||||
use tokio::{io::{AsyncBufReadExt, BufReader, Lines, ReadHalf, WriteHalf}, sync::OnceCell};
|
||||
use yazi_fs::{Xdg, create_owned_dir, provider::{Provider, local::Local}};
|
||||
use yazi_fs::{Xdg, create_owned_dir, engine::{Engine, local::Local}};
|
||||
|
||||
pub struct Stream;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,12 +16,14 @@ async fn main() -> anyhow::Result<()> {
|
|||
|
||||
yazi_fs::init();
|
||||
|
||||
yazi_vfs::init();
|
||||
|
||||
yazi_tty::init();
|
||||
|
||||
yazi_config::init()?;
|
||||
|
||||
yazi_vfs::init();
|
||||
|
||||
yazi_runner::init(yazi_plugin::slim_lua);
|
||||
|
||||
yazi_boot::init();
|
||||
|
||||
yazi_term::init()?;
|
||||
|
|
@ -34,8 +36,6 @@ async fn main() -> anyhow::Result<()> {
|
|||
|
||||
yazi_watcher::init();
|
||||
|
||||
yazi_runner::init(yazi_plugin::slim_lua);
|
||||
|
||||
yazi_plugin::init()?;
|
||||
|
||||
yazi_dds::serve();
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use std::ops::Deref;
|
|||
|
||||
use anyhow::{anyhow, bail};
|
||||
use bitflags::bitflags;
|
||||
use mlua::{IntoLua, Lua, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::cha::ChaType;
|
||||
|
|
@ -158,3 +159,7 @@ impl ChaMode {
|
|||
#[inline]
|
||||
pub const fn is_sticky(self) -> bool { self.contains(Self::S_STICKY) }
|
||||
}
|
||||
|
||||
impl IntoLua for ChaMode {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { self.bits().into_lua(lua) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use mlua::{IntoLua, Lua, Value};
|
||||
|
||||
use crate::cha::{Cha, ChaMode};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
|
|
@ -63,6 +65,8 @@ impl TryFrom<Attrs> for std::fs::Permissions {
|
|||
}
|
||||
|
||||
impl Attrs {
|
||||
pub fn mode(mode: ChaMode) -> Self { Self { mode: Some(mode), ..Default::default() } }
|
||||
|
||||
pub fn has_times(self) -> bool {
|
||||
self.atime.is_some() || self.btime.is_some() || self.mtime.is_some()
|
||||
}
|
||||
|
|
@ -73,3 +77,16 @@ impl Attrs {
|
|||
|
||||
pub fn mtime_dur(self) -> Option<Duration> { self.mtime?.duration_since(UNIX_EPOCH).ok() }
|
||||
}
|
||||
|
||||
impl IntoLua for Attrs {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
lua
|
||||
.create_table_from([
|
||||
("mode", self.mode.map(|m| m.bits()).into_lua(lua)?),
|
||||
("atime", self.atime_dur().map(|d| d.as_secs_f64()).into_lua(lua)?),
|
||||
("btime", self.btime_dur().map(|d| d.as_secs_f64()).into_lua(lua)?),
|
||||
("mtime", self.mtime_dur().map(|d| d.as_secs_f64()).into_lua(lua)?),
|
||||
])?
|
||||
.into_lua(lua)
|
||||
}
|
||||
}
|
||||
22
yazi-fs/src/engine/capabilities.rs
Normal file
22
yazi-fs/src/engine/capabilities.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use mlua::{FromLua, Lua, Table, Value};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Capabilities {
|
||||
pub symlink: bool,
|
||||
pub hard_link: bool,
|
||||
pub trash: bool,
|
||||
pub copy_progressive: bool,
|
||||
}
|
||||
|
||||
impl FromLua for Capabilities {
|
||||
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
|
||||
let t = Table::from_lua(value, lua)?;
|
||||
|
||||
Ok(Self {
|
||||
symlink: t.raw_get("symlink")?,
|
||||
hard_link: t.raw_get("hard_link")?,
|
||||
trash: t.raw_get("trash")?,
|
||||
copy_progressive: t.raw_get("copy_progressive")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
55
yazi-fs/src/engine/demand.rs
Normal file
55
yazi-fs/src/engine/demand.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use mlua::{IntoLua, Lua, Value};
|
||||
|
||||
use super::{Attrs, FileBuilder};
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct Demand {
|
||||
pub attrs: Attrs,
|
||||
pub append: bool,
|
||||
pub create: bool,
|
||||
pub create_new: bool,
|
||||
pub read: bool,
|
||||
pub truncate: bool,
|
||||
pub write: bool,
|
||||
}
|
||||
|
||||
impl Demand {
|
||||
pub fn build<T: FileBuilder>(self) -> T {
|
||||
let mut demand = T::default();
|
||||
demand.attrs(self.attrs);
|
||||
|
||||
if self.append {
|
||||
demand.append(true);
|
||||
}
|
||||
if self.create {
|
||||
demand.create(true);
|
||||
}
|
||||
if self.create_new {
|
||||
demand.create_new(true);
|
||||
}
|
||||
if self.read {
|
||||
demand.read(true);
|
||||
}
|
||||
if self.truncate {
|
||||
demand.truncate(true);
|
||||
}
|
||||
if self.write {
|
||||
demand.write(true);
|
||||
}
|
||||
demand
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for Demand {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
let t = lua.create_table_from([
|
||||
("append", self.append),
|
||||
("create", self.create),
|
||||
("create_new", self.create_new),
|
||||
("read", self.read),
|
||||
("truncate", self.truncate),
|
||||
("write", self.write),
|
||||
])?;
|
||||
t.into_lua(lua)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use yazi_shared::{loc::LocBuf, pool::InternStr, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_shared::{loc::LocBuf, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}};
|
||||
|
||||
use crate::CWD;
|
||||
|
||||
|
|
@ -50,7 +50,7 @@ fn try_absolute_impl<'a>(url: UrlCow<'a>) -> Option<UrlCow<'a>> {
|
|||
|
||||
Some(match url.as_url() {
|
||||
Url::Regular(_) => UrlBuf::Regular(loc).into(),
|
||||
Url::Search { domain, .. } => UrlBuf::Search { loc, domain: domain.intern() }.into(),
|
||||
Url::Archive { .. } | Url::Sftp { .. } => None?,
|
||||
Url::Search { auth, .. } => UrlBuf::Search { loc, auth: auth.clone() }.into(),
|
||||
Url::Mount { .. } | Url::Scope { .. } | Url::Sftp { .. } => None?,
|
||||
})
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ use std::{io, path::PathBuf};
|
|||
|
||||
use tokio::{select, sync::{mpsc, oneshot}};
|
||||
|
||||
use crate::provider::Attrs;
|
||||
use crate::engine::Attrs;
|
||||
|
||||
pub(super) async fn copy_impl(from: PathBuf, to: PathBuf, attrs: Attrs) -> io::Result<u64> {
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
|
|
@ -48,7 +48,7 @@ pub(super) async fn copy_impl(from: PathBuf, to: PathBuf, attrs: Attrs) -> io::R
|
|||
}
|
||||
}
|
||||
|
||||
pub(super) fn copy_with_progress_impl(
|
||||
pub(super) fn copy_progressive_impl(
|
||||
from: PathBuf,
|
||||
to: PathBuf,
|
||||
attrs: Attrs,
|
||||
|
|
@ -2,12 +2,12 @@ use std::io;
|
|||
|
||||
use yazi_shared::url::AsUrl;
|
||||
|
||||
use crate::provider::{Attrs, FileBuilder};
|
||||
use crate::engine::{Attrs, FileBuilder};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Gate(tokio::fs::OpenOptions);
|
||||
pub struct Demand(tokio::fs::OpenOptions);
|
||||
|
||||
impl FileBuilder for Gate {
|
||||
impl FileBuilder for Demand {
|
||||
type File = tokio::fs::File;
|
||||
|
||||
fn append(&mut self, append: bool) -> &mut Self {
|
||||
|
|
@ -2,7 +2,7 @@ use std::{io, sync::Arc};
|
|||
|
||||
use yazi_shared::{path::PathBufDyn, strand::StrandCow, url::{UrlBuf, UrlLike}};
|
||||
|
||||
use crate::{cha::{Cha, ChaType}, provider::FileHolder};
|
||||
use crate::{cha::{Cha, ChaType}, engine::FileHolder};
|
||||
|
||||
pub enum DirEntry {
|
||||
Regular(tokio::fs::DirEntry),
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
use std::{io, path::Path, sync::Arc};
|
||||
use std::{fs::FileTimes, io, path::Path, sync::Arc};
|
||||
|
||||
use tokio::sync::mpsc;
|
||||
use yazi_shared::{path::{AsPath, PathBufDyn}, scheme::SchemeKind, strand::AsStrand, url::{Url, UrlBuf, UrlCow}};
|
||||
use yazi_shared::{auth::AuthKind, path::{AsPath, PathBufDyn}, strand::AsStrand, url::{Url, UrlBuf, UrlCow}};
|
||||
|
||||
use crate::{cha::{Cha, ChaMode}, provider::{Attrs, Capabilities, Provider}};
|
||||
use crate::{cha::{Cha, ChaMode}, engine::{Attrs, Capabilities, Engine}};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Local<'a> {
|
||||
|
|
@ -11,9 +11,9 @@ pub struct Local<'a> {
|
|||
path: &'a Path,
|
||||
}
|
||||
|
||||
impl<'a> Provider for Local<'a> {
|
||||
impl<'a> Engine for Local<'a> {
|
||||
type Demand = super::Demand;
|
||||
type File = tokio::fs::File;
|
||||
type Gate = super::Gate;
|
||||
type Me<'b> = Local<'b>;
|
||||
type ReadDir = super::ReadDir;
|
||||
type UrlCow = UrlCow<'a>;
|
||||
|
|
@ -29,7 +29,14 @@ impl<'a> Provider for Local<'a> {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn capabilities(&self) -> Capabilities { Capabilities { symlink: true } }
|
||||
async fn capabilities(&self) -> io::Result<Capabilities> {
|
||||
Ok(Capabilities {
|
||||
symlink: true,
|
||||
hard_link: true,
|
||||
trash: true,
|
||||
copy_progressive: true,
|
||||
})
|
||||
}
|
||||
|
||||
async fn casefold(&self) -> io::Result<UrlBuf> {
|
||||
super::casefold(self.path).await.map(Into::into)
|
||||
|
|
@ -45,14 +52,14 @@ impl<'a> Provider for Local<'a> {
|
|||
super::copy_impl(from, to, attrs).await
|
||||
}
|
||||
|
||||
fn copy_with_progress<P, A>(&self, to: P, attrs: A) -> io::Result<mpsc::Receiver<io::Result<u64>>>
|
||||
fn copy_progressive<P, A>(&self, to: P, attrs: A) -> io::Result<mpsc::Receiver<io::Result<u64>>>
|
||||
where
|
||||
P: AsPath,
|
||||
A: Into<Attrs>,
|
||||
{
|
||||
let to = to.as_path().to_os_owned()?;
|
||||
let from = self.path.to_owned();
|
||||
Ok(super::copy_with_progress_impl(from, to, attrs.into()))
|
||||
Ok(super::copy_progressive_impl(from, to, attrs.into()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -80,7 +87,7 @@ impl<'a> Provider for Local<'a> {
|
|||
async fn new<'b>(url: Url<'b>) -> io::Result<Self::Me<'b>> {
|
||||
match url {
|
||||
Url::Regular(loc) | Url::Search { loc, .. } => Ok(Self::Me { url, path: loc.as_inner() }),
|
||||
Url::Archive { .. } | Url::Sftp { .. } => {
|
||||
Url::Mount { .. } | Url::Scope { .. } | Url::Sftp { .. } => {
|
||||
Err(io::Error::new(io::ErrorKind::InvalidInput, format!("Not a local URL: {url:?}")))
|
||||
}
|
||||
}
|
||||
|
|
@ -89,12 +96,12 @@ impl<'a> Provider for Local<'a> {
|
|||
#[inline]
|
||||
async fn read_dir(self) -> io::Result<Self::ReadDir> {
|
||||
Ok(match self.url.kind() {
|
||||
SchemeKind::Regular => Self::ReadDir::Regular(tokio::fs::read_dir(self.path).await?),
|
||||
SchemeKind::Search => Self::ReadDir::Others {
|
||||
AuthKind::Regular => Self::ReadDir::Regular(tokio::fs::read_dir(self.path).await?),
|
||||
AuthKind::Search => Self::ReadDir::Others {
|
||||
reader: tokio::fs::read_dir(self.path).await?,
|
||||
dir: Arc::new(self.url.to_owned()),
|
||||
},
|
||||
SchemeKind::Archive | SchemeKind::Sftp => Err(io::Error::new(
|
||||
AuthKind::Mount | AuthKind::Scope | AuthKind::Sftp => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("Not a local URL: {:?}", self.url),
|
||||
))?,
|
||||
|
|
@ -125,24 +132,19 @@ impl<'a> Provider for Local<'a> {
|
|||
tokio::fs::rename(self.path, to).await
|
||||
}
|
||||
|
||||
async fn set_mode(&self, mode: ChaMode) -> io::Result<()> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
return tokio::fs::set_permissions(self.path, mode.into()).await;
|
||||
async fn set_attrs(&self, attrs: Attrs) -> io::Result<()> {
|
||||
let (mode, times) = (attrs.mode, attrs.try_into());
|
||||
if mode.is_none() && times.is_err() {
|
||||
return Ok(());
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
|
||||
let path: Vec<u16> = self.path.as_os_str().encode_wide().chain(Some(0)).collect();
|
||||
let perm = if mode.contains(ChaMode::U_WRITE) { libc::S_IWRITE } else { libc::S_IREAD };
|
||||
|
||||
return tokio::task::spawn_blocking(move || {
|
||||
let result = unsafe { libc::wchmod(path.as_ptr(), perm) };
|
||||
if result == 0 { Ok(()) } else { Err(io::Error::last_os_error()) }
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
let path = self.path.to_owned();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let a = mode.map_or(Ok(()), |mode| Self::set_mode(&path, mode));
|
||||
let b = times.map_or(Ok(()), |times| Self::set_times(&path, times));
|
||||
a.and(b)
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -256,4 +258,26 @@ impl<'a> Local<'a> {
|
|||
{
|
||||
Self { url: Url::regular(path), path: path.as_ref() }
|
||||
}
|
||||
|
||||
fn set_mode(path: &Path, mode: ChaMode) -> io::Result<()> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
std::fs::set_permissions(path, mode.into())
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::ffi::OsStrExt;
|
||||
|
||||
let path: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
|
||||
let perm = if mode.contains(ChaMode::U_WRITE) { libc::S_IWRITE } else { libc::S_IREAD };
|
||||
|
||||
let result = unsafe { libc::wchmod(path.as_ptr(), perm) };
|
||||
if result == 0 { Ok(()) } else { Err(io::Error::last_os_error()) }
|
||||
}
|
||||
}
|
||||
|
||||
fn set_times(path: &Path, times: FileTimes) -> io::Result<()> {
|
||||
std::fs::File::open(path)?.set_times(times)
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(absolute calculator casefold copier dir_entry gate identical local read_dir);
|
||||
yazi_macro::mod_flat!(absolute calculator casefold copier dir_entry demand identical local read_dir);
|
||||
|
|
@ -2,7 +2,7 @@ use std::{io, sync::Arc};
|
|||
|
||||
use yazi_shared::url::UrlBuf;
|
||||
|
||||
use crate::provider::DirReader;
|
||||
use crate::engine::DirReader;
|
||||
|
||||
pub enum ReadDir {
|
||||
Regular(tokio::fs::ReadDir),
|
||||
3
yazi-fs/src/engine/mod.rs
Normal file
3
yazi-fs/src/engine/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
yazi_macro::mod_pub!(local);
|
||||
|
||||
yazi_macro::mod_flat!(attrs capabilities demand traits);
|
||||
|
|
@ -4,20 +4,20 @@ use tokio::{io::{AsyncRead, AsyncSeek, AsyncWrite, AsyncWriteExt}, sync::mpsc};
|
|||
use yazi_macro::ok_or_not_found;
|
||||
use yazi_shared::{path::{AsPath, PathBufDyn}, strand::{AsStrand, StrandCow}, url::{AsUrl, Url, UrlBuf}};
|
||||
|
||||
use crate::{cha::{Cha, ChaMode, ChaType}, provider::{Attrs, Capabilities}};
|
||||
use crate::{cha::{Cha, ChaType}, engine::{Attrs, Capabilities}};
|
||||
|
||||
pub trait Provider: Sized {
|
||||
pub trait Engine: Sized {
|
||||
type File: AsyncRead + AsyncSeek + AsyncWrite + Unpin;
|
||||
type Gate: FileBuilder<File = Self::File>;
|
||||
type Demand: FileBuilder<File = Self::File>;
|
||||
type ReadDir: DirReader + 'static;
|
||||
type UrlCow;
|
||||
type Me<'a>: Provider;
|
||||
type Me<'a>: Engine;
|
||||
|
||||
fn absolute(&self) -> impl Future<Output = io::Result<Self::UrlCow>>;
|
||||
|
||||
fn canonicalize(&self) -> impl Future<Output = io::Result<UrlBuf>>;
|
||||
|
||||
fn capabilities(&self) -> Capabilities;
|
||||
fn capabilities(&self) -> impl Future<Output = io::Result<Capabilities>>;
|
||||
|
||||
fn casefold(&self) -> impl Future<Output = io::Result<UrlBuf>>;
|
||||
|
||||
|
|
@ -25,17 +25,13 @@ pub trait Provider: Sized {
|
|||
where
|
||||
P: AsPath;
|
||||
|
||||
fn copy_with_progress<P, A>(
|
||||
&self,
|
||||
to: P,
|
||||
attrs: A,
|
||||
) -> io::Result<mpsc::Receiver<io::Result<u64>>>
|
||||
fn copy_progressive<P, A>(&self, to: P, attrs: A) -> io::Result<mpsc::Receiver<io::Result<u64>>>
|
||||
where
|
||||
P: AsPath,
|
||||
A: Into<Attrs>;
|
||||
|
||||
fn create(&self) -> impl Future<Output = io::Result<Self::File>> {
|
||||
async move { self.gate().write(true).create(true).truncate(true).open(self.url()).await }
|
||||
async move { self.demand().write(true).create(true).truncate(true).open(self.url()).await }
|
||||
}
|
||||
|
||||
fn create_dir(&self) -> impl Future<Output = io::Result<()>>;
|
||||
|
|
@ -77,10 +73,10 @@ pub trait Provider: Sized {
|
|||
}
|
||||
|
||||
fn create_new(&self) -> impl Future<Output = io::Result<Self::File>> {
|
||||
async move { self.gate().write(true).create_new(true).open(self.url()).await }
|
||||
async move { self.demand().write(true).create_new(true).open(self.url()).await }
|
||||
}
|
||||
|
||||
fn gate(&self) -> Self::Gate { Self::Gate::default() }
|
||||
fn demand(&self) -> Self::Demand { Self::Demand::default() }
|
||||
|
||||
fn hard_link<P>(&self, to: P) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
|
|
@ -91,7 +87,7 @@ pub trait Provider: Sized {
|
|||
fn new<'a>(url: Url<'a>) -> impl Future<Output = io::Result<Self::Me<'a>>>;
|
||||
|
||||
fn open(&self) -> impl Future<Output = io::Result<Self::File>> {
|
||||
async move { self.gate().read(true).open(self.url()).await }
|
||||
async move { self.demand().read(true).open(self.url()).await }
|
||||
}
|
||||
|
||||
fn read_dir(self) -> impl Future<Output = io::Result<Self::ReadDir>>;
|
||||
|
|
@ -103,7 +99,7 @@ pub trait Provider: Sized {
|
|||
fn remove_dir_all(&self) -> impl Future<Output = io::Result<()>> {
|
||||
async fn remove_dir_all_impl<P>(url: Url<'_>) -> io::Result<()>
|
||||
where
|
||||
P: Provider,
|
||||
P: Engine,
|
||||
{
|
||||
let mut it = ok_or_not_found!(P::new(url).await?.read_dir().await, return Ok(()));
|
||||
while let Some(child) = it.next().await? {
|
||||
|
|
@ -138,13 +134,13 @@ pub trait Provider: Sized {
|
|||
let mut result = Ok(());
|
||||
|
||||
while let Some((dir, visited)) = stack.pop() {
|
||||
let Ok(provider) = Self::new(dir.as_url()).await else {
|
||||
let Ok(engine) = Self::new(dir.as_url()).await else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if visited {
|
||||
result = provider.remove_dir().await;
|
||||
} else if let Ok(mut it) = provider.read_dir().await {
|
||||
result = engine.remove_dir().await;
|
||||
} else if let Ok(mut it) = engine.read_dir().await {
|
||||
stack.push((dir, true));
|
||||
while let Ok(Some(ent)) = it.next().await {
|
||||
if ent.file_type().await.is_ok_and(|t| t.is_dir()) {
|
||||
|
|
@ -163,7 +159,7 @@ pub trait Provider: Sized {
|
|||
where
|
||||
P: AsPath;
|
||||
|
||||
fn set_mode(&self, mode: ChaMode) -> impl Future<Output = io::Result<()>>;
|
||||
fn set_attrs(&self, attrs: Attrs) -> impl Future<Output = io::Result<()>>;
|
||||
|
||||
fn symlink<S, F>(&self, original: S, _is_dir: F) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use mlua::{FromLua, Lua, Value};
|
||||
use yazi_shared::url::UrlBuf;
|
||||
|
||||
use crate::file::File;
|
||||
|
|
@ -28,3 +29,9 @@ impl From<Files> for Vec<File> {
|
|||
impl From<Files> for Vec<UrlBuf> {
|
||||
fn from(value: Files) -> Self { value.0.into_iter().map(|f| f.url).collect() }
|
||||
}
|
||||
|
||||
impl FromLua for Files {
|
||||
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
|
||||
Vec::<File>::from_lua(value, lua).map(Self)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ pub fn max_common_root(urls: &[UrlBuf]) -> usize {
|
|||
.components()
|
||||
.zip(parent.components())
|
||||
.take_while(|(a, b)| match (a, b) {
|
||||
(Component::Scheme(a), Component::Scheme(b)) => a.covariant(*b),
|
||||
(Component::Auth(a), Component::Auth(b)) => a.covariant(b),
|
||||
(a, b) => a == b,
|
||||
})
|
||||
.count()
|
||||
|
|
@ -53,6 +53,8 @@ pub fn max_common_root(urls: &[UrlBuf]) -> usize {
|
|||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn test_max_common_root() {
|
||||
yazi_shared::init_tests();
|
||||
|
||||
fn assert(input: &[&str], expected: &str) {
|
||||
use std::{ffi::OsStr, str::FromStr};
|
||||
let urls: Vec<_> =
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
extern crate self as yazi_fs;
|
||||
|
||||
yazi_macro::mod_pub!(cha file mounts path provider);
|
||||
yazi_macro::mod_pub!(cha file mounts path engine);
|
||||
|
||||
yazi_macro::mod_flat!(cwd entries filter fns hash op scheme sorter sorting splatter stage url xdg);
|
||||
yazi_macro::mod_flat!(cwd spec entries filter fns hash op sorter sorting splatter stage url xdg);
|
||||
|
||||
pub fn init() {
|
||||
CWD.init(<_>::default());
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ pub fn clean_url<'a>(url: impl Into<UrlCow<'a>>) -> UrlBuf {
|
|||
cow.trail().components().count() - 1,
|
||||
);
|
||||
|
||||
let scheme = cow.into_scheme().into_owned().with_ports(uri, urn);
|
||||
(scheme, path).try_into().expect("UrlBuf from cleaned path")
|
||||
let spec = cow.into_spec().with_ports(uri, urn);
|
||||
(spec, path).try_into().expect("UrlBuf from cleaned path")
|
||||
}
|
||||
|
||||
fn clean_path_impl(path: PathDyn, base: usize, trail: usize) -> (PathBufDyn, usize, usize) {
|
||||
|
|
@ -76,19 +76,25 @@ mod tests {
|
|||
yazi_shared::init_tests();
|
||||
let cases = [
|
||||
// CurDir
|
||||
("archive://:3//./tmp/test.zip/foo/bar", "archive://:3//tmp/test.zip/foo/bar"),
|
||||
("archive://:3//tmp/./test.zip/foo/bar", "archive://:3//tmp/test.zip/foo/bar"),
|
||||
("archive://:3//tmp/./test.zip/./foo/bar", "archive://:3//tmp/test.zip/foo/bar"),
|
||||
("archive://:3//tmp/./test.zip/./foo/./bar/.", "archive://:3//tmp/test.zip/foo/bar"),
|
||||
("test-mount://7z:3//./tmp/test.zip/foo/bar", "test-mount://7z:3//tmp/test.zip/foo/bar"),
|
||||
("test-mount://7z:3//tmp/./test.zip/foo/bar", "test-mount://7z:3//tmp/test.zip/foo/bar"),
|
||||
("test-mount://7z:3//tmp/./test.zip/./foo/bar", "test-mount://7z:3//tmp/test.zip/foo/bar"),
|
||||
(
|
||||
"test-mount://7z:3//tmp/./test.zip/./foo/./bar/.",
|
||||
"test-mount://7z:3//tmp/test.zip/foo/bar",
|
||||
),
|
||||
// ParentDir
|
||||
("archive://:3:2//../../tmp/test.zip/foo/bar", "archive://:3:2//tmp/test.zip/foo/bar"),
|
||||
("archive://:3:2//tmp/../../test.zip/foo/bar", "archive://:3:2//test.zip/foo/bar"),
|
||||
("archive://:4:2//tmp/test.zip/../../foo/bar", "archive://:2:2//foo/bar"),
|
||||
("archive://:5:2//tmp/test.zip/../../foo/bar", "archive://:2:2//foo/bar"),
|
||||
("archive://:4:4//tmp/test.zip/foo/bar/../../", "archive:////tmp/test.zip"),
|
||||
("archive://:5:4//tmp/test.zip/foo/bar/../../", "archive://:1//tmp/test.zip"),
|
||||
("archive://:4:4//tmp/test.zip/foo/bar/../../../", "archive:////tmp"),
|
||||
("sftp://test//root/.config/yazi/../../Downloads", "sftp://test//root/Downloads"),
|
||||
(
|
||||
"test-mount://7z:3:2//../../tmp/test.zip/foo/bar",
|
||||
"test-mount://7z:3:2//tmp/test.zip/foo/bar",
|
||||
),
|
||||
("test-mount://7z:3:2//tmp/../../test.zip/foo/bar", "test-mount://7z:3:2//test.zip/foo/bar"),
|
||||
("test-mount://7z:4:2//tmp/test.zip/../../foo/bar", "test-mount://7z:2:2//foo/bar"),
|
||||
("test-mount://7z:5:2//tmp/test.zip/../../foo/bar", "test-mount://7z:2:2//foo/bar"),
|
||||
("test-mount://7z:4:4//tmp/test.zip/foo/bar/../../", "test-mount://7z//tmp/test.zip"),
|
||||
("test-mount://7z:5:4//tmp/test.zip/foo/bar/../../", "test-mount://7z:1//tmp/test.zip"),
|
||||
("test-mount://7z:4:4//tmp/test.zip/foo/bar/../../../", "test-mount://7z//tmp"),
|
||||
("sftp://vps//root/.config/yazi/../../Downloads", "sftp://vps//root/Downloads"),
|
||||
];
|
||||
|
||||
for (input, expected) in cases {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use yazi_shared::{loc::LocBuf, path::{PathBufDyn, PathCow, PathKind, PathLike}, pool::InternStr, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_shared::{loc::LocBuf, path::{PathBufDyn, PathCow, PathKind, PathLike}, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_shim::wtf8::FromWtf8Vec;
|
||||
|
||||
#[inline]
|
||||
|
|
@ -32,17 +32,21 @@ fn expand_url_impl(url: UrlCow) -> UrlCow {
|
|||
Url::Regular(_) => UrlBuf::Regular(
|
||||
LocBuf::<std::path::PathBuf>::with(path.into_os().unwrap(), uri, urn).unwrap(),
|
||||
),
|
||||
Url::Search { domain, .. } => UrlBuf::Search {
|
||||
loc: LocBuf::<std::path::PathBuf>::with(path.into_os().unwrap(), uri, urn).unwrap(),
|
||||
domain: domain.intern(),
|
||||
Url::Search { auth, .. } => UrlBuf::Search {
|
||||
loc: LocBuf::<std::path::PathBuf>::with(path.into_os().unwrap(), uri, urn).unwrap(),
|
||||
auth: auth.clone(),
|
||||
},
|
||||
Url::Archive { domain, .. } => UrlBuf::Archive {
|
||||
loc: LocBuf::<std::path::PathBuf>::with(path.into_os().unwrap(), uri, urn).unwrap(),
|
||||
domain: domain.intern(),
|
||||
Url::Mount { auth, .. } => UrlBuf::Mount {
|
||||
loc: LocBuf::<std::path::PathBuf>::with(path.into_os().unwrap(), uri, urn).unwrap(),
|
||||
auth: auth.clone(),
|
||||
},
|
||||
Url::Sftp { domain, .. } => UrlBuf::Sftp {
|
||||
loc: LocBuf::<typed_path::UnixPathBuf>::with(path.into_unix().unwrap(), uri, urn).unwrap(),
|
||||
domain: domain.intern(),
|
||||
Url::Scope { auth, .. } => UrlBuf::Scope {
|
||||
loc: LocBuf::<typed_path::UnixPathBuf>::with(path.into_unix().unwrap(), uri, urn).unwrap(),
|
||||
auth: auth.clone(),
|
||||
},
|
||||
Url::Sftp { auth, .. } => UrlBuf::Sftp {
|
||||
loc: LocBuf::<typed_path::UnixPathBuf>::with(path.into_unix().unwrap(), uri, urn).unwrap(),
|
||||
auth: auth.clone(),
|
||||
},
|
||||
}
|
||||
.into()
|
||||
|
|
@ -94,39 +98,48 @@ mod tests {
|
|||
|
||||
let cases = [
|
||||
// Zero extra component expanded
|
||||
("archive:////tmp/test.zip/$FOO/bar", "archive:////tmp/test.zip/foo/bar"),
|
||||
("archive://:1//tmp/test.zip/$FOO/bar", "archive://:1//tmp/test.zip/foo/bar"),
|
||||
("archive://:2//tmp/test.zip/bar/$FOO", "archive://:2//tmp/test.zip/bar/foo"),
|
||||
("archive://:3//tmp/test.zip/$FOO/bar", "archive://:3//tmp/test.zip/foo/bar"),
|
||||
("archive://:3:1//tmp/test.zip/bar/$FOO", "archive://:3:1//tmp/test.zip/bar/foo"),
|
||||
("archive://:3:2//tmp/test.zip/$FOO/bar", "archive://:3:2//tmp/test.zip/foo/bar"),
|
||||
("archive://:3:3//tmp/test.zip/bar/$FOO", "archive://:3:3//tmp/test.zip/bar/foo"),
|
||||
("test-mount://7z//tmp/test.zip/$FOO/bar", "test-mount://7z//tmp/test.zip/foo/bar"),
|
||||
("test-mount://7z:1//tmp/test.zip/$FOO/bar", "test-mount://7z:1//tmp/test.zip/foo/bar"),
|
||||
("test-mount://7z:2//tmp/test.zip/bar/$FOO", "test-mount://7z:2//tmp/test.zip/bar/foo"),
|
||||
("test-mount://7z:3//tmp/test.zip/$FOO/bar", "test-mount://7z:3//tmp/test.zip/foo/bar"),
|
||||
("test-mount://7z:3:1//tmp/test.zip/bar/$FOO", "test-mount://7z:3:1//tmp/test.zip/bar/foo"),
|
||||
("test-mount://7z:3:2//tmp/test.zip/$FOO/bar", "test-mount://7z:3:2//tmp/test.zip/foo/bar"),
|
||||
("test-mount://7z:3:3//tmp/test.zip/bar/$FOO", "test-mount://7z:3:3//tmp/test.zip/bar/foo"),
|
||||
// +1 component
|
||||
("archive:////tmp/test.zip/$BAR_BAZ", "archive:////tmp/test.zip/bar/baz"),
|
||||
("archive://:1//tmp/test.zip/$BAR_BAZ", "archive://:2//tmp/test.zip/bar/baz"),
|
||||
("archive://:2//$BAR_BAZ/tmp/test.zip", "archive://:2//bar/baz/tmp/test.zip"),
|
||||
("archive://:2:1//tmp/test.zip/$BAR_BAZ", "archive://:3:2//tmp/test.zip/bar/baz"),
|
||||
("archive://:2:2//tmp/$BAR_BAZ/test.zip", "archive://:3:3//tmp/bar/baz/test.zip"),
|
||||
("archive://:2:2//$BAR_BAZ/tmp/test.zip", "archive://:2:2//bar/baz/tmp/test.zip"),
|
||||
("test-mount://7z//tmp/test.zip/$BAR_BAZ", "test-mount://7z//tmp/test.zip/bar/baz"),
|
||||
("test-mount://7z:1//tmp/test.zip/$BAR_BAZ", "test-mount://7z:2//tmp/test.zip/bar/baz"),
|
||||
("test-mount://7z:2//$BAR_BAZ/tmp/test.zip", "test-mount://7z:2//bar/baz/tmp/test.zip"),
|
||||
("test-mount://7z:2:1//tmp/test.zip/$BAR_BAZ", "test-mount://7z:3:2//tmp/test.zip/bar/baz"),
|
||||
("test-mount://7z:2:2//tmp/$BAR_BAZ/test.zip", "test-mount://7z:3:3//tmp/bar/baz/test.zip"),
|
||||
("test-mount://7z:2:2//$BAR_BAZ/tmp/test.zip", "test-mount://7z:2:2//bar/baz/tmp/test.zip"),
|
||||
// -1 component
|
||||
("archive:////tmp/test.zip/${BAR/BAZ}", "archive:////tmp/test.zip/bar_baz"),
|
||||
("archive://:1//tmp/test.zip/${BAR/BAZ}", "archive://:1//tmp/test.zip/${BAR/BAZ}"),
|
||||
("archive://:1//tmp/${BAR/BAZ}/test.zip", "archive://:1//tmp/bar_baz/test.zip"),
|
||||
("archive://:2//tmp/test.zip/${BAR/BAZ}", "archive://:1//tmp/test.zip/bar_baz"),
|
||||
("archive://:2//tmp/${BAR/BAZ}/test.zip", "archive://:2//tmp/${BAR/BAZ}/test.zip"),
|
||||
("archive://:2:1//tmp/test.zip/${BAR/BAZ}", "archive://:2:1//tmp/test.zip/${BAR/BAZ}"),
|
||||
("archive://:2:1//tmp/${BAR/BAZ}/test.zip", "archive://:2:1//tmp/${BAR/BAZ}/test.zip"),
|
||||
("archive://:2:1//${BAR/BAZ}/tmp/test.zip", "archive://:2:1//bar_baz/tmp/test.zip"),
|
||||
("archive://:3:2//tmp/test.zip/${BAR/BAZ}", "archive://:2:1//tmp/test.zip/bar_baz"),
|
||||
("archive://:3:2//tmp/${BAR/BAZ}/test.zip", "archive://:3:2//tmp/${BAR/BAZ}/test.zip"),
|
||||
("archive://:3:3//tmp/test.zip/${BAR/BAZ}", "archive://:2:2//tmp/test.zip/bar_baz"),
|
||||
("archive://:3:3//tmp/${BAR/BAZ}/test.zip", "archive://:2:2//tmp/bar_baz/test.zip"),
|
||||
("test-mount://7z//tmp/test.zip/${BAR/BAZ}", "test-mount://7z//tmp/test.zip/bar_baz"),
|
||||
("test-mount://7z:1//tmp/test.zip/${BAR/BAZ}", "test-mount://7z:1//tmp/test.zip/${BAR/BAZ}"),
|
||||
("test-mount://7z:1//tmp/${BAR/BAZ}/test.zip", "test-mount://7z:1//tmp/bar_baz/test.zip"),
|
||||
("test-mount://7z:2//tmp/test.zip/${BAR/BAZ}", "test-mount://7z:1//tmp/test.zip/bar_baz"),
|
||||
("test-mount://7z:2//tmp/${BAR/BAZ}/test.zip", "test-mount://7z:2//tmp/${BAR/BAZ}/test.zip"),
|
||||
(
|
||||
"test-mount://7z:2:1//tmp/test.zip/${BAR/BAZ}",
|
||||
"test-mount://7z:2:1//tmp/test.zip/${BAR/BAZ}",
|
||||
),
|
||||
(
|
||||
"test-mount://7z:2:1//tmp/${BAR/BAZ}/test.zip",
|
||||
"test-mount://7z:2:1//tmp/${BAR/BAZ}/test.zip",
|
||||
),
|
||||
("test-mount://7z:2:1//${BAR/BAZ}/tmp/test.zip", "test-mount://7z:2:1//bar_baz/tmp/test.zip"),
|
||||
("test-mount://7z:3:2//tmp/test.zip/${BAR/BAZ}", "test-mount://7z:2:1//tmp/test.zip/bar_baz"),
|
||||
(
|
||||
"test-mount://7z:3:2//tmp/${BAR/BAZ}/test.zip",
|
||||
"test-mount://7z:3:2//tmp/${BAR/BAZ}/test.zip",
|
||||
),
|
||||
("test-mount://7z:3:3//tmp/test.zip/${BAR/BAZ}", "test-mount://7z:2:2//tmp/test.zip/bar_baz"),
|
||||
("test-mount://7z:3:3//tmp/${BAR/BAZ}/test.zip", "test-mount://7z:2:2//tmp/bar_baz/test.zip"),
|
||||
// Zeros all components
|
||||
("archive:////${EM/PT/Y}", "archive:////"),
|
||||
("archive://:1//${EM/PT/Y}", "archive://:1//${EM/PT/Y}"),
|
||||
("archive://:2//${EM/PT/Y}", "archive://:2//${EM/PT/Y}"),
|
||||
("archive://:3//${EM/PT/Y}", "archive:////"),
|
||||
("archive://:4//${EM/PT/Y}", "archive://:1//"),
|
||||
("test-mount://7z//${EM/PT/Y}", "test-mount://7z//"),
|
||||
("test-mount://7z:1//${EM/PT/Y}", "test-mount://7z:1//${EM/PT/Y}"),
|
||||
("test-mount://7z:2//${EM/PT/Y}", "test-mount://7z:2//${EM/PT/Y}"),
|
||||
("test-mount://7z:3//${EM/PT/Y}", "test-mount://7z//"),
|
||||
("test-mount://7z:4//${EM/PT/Y}", "test-mount://7z:1//"),
|
||||
];
|
||||
|
||||
for (input, expected) in cases {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use yazi_shared::url::UrlBuf;
|
|||
use crate::path::{clean_url, expand_url};
|
||||
|
||||
pub fn sanitize_path(path: PathBuf) -> Option<PathBuf> {
|
||||
clean_url(yazi_fs::provider::local::try_absolute(expand_url(UrlBuf::from(path)))?)
|
||||
clean_url(yazi_fs::engine::local::try_absolute(expand_url(UrlBuf::from(path)))?)
|
||||
.into_loc()
|
||||
.into_os()
|
||||
.ok()
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Capabilities {
|
||||
pub symlink: bool,
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
yazi_macro::mod_pub!(local);
|
||||
|
||||
yazi_macro::mod_flat!(attrs capabilities traits);
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use yazi_shared::{path::PathBufDyn, scheme::{AsScheme, Scheme, SchemeInventory, SchemeRef}};
|
||||
use yazi_shim::mlua::UserDataFieldsExt;
|
||||
|
||||
use crate::Xdg;
|
||||
|
||||
pub trait FsScheme {
|
||||
fn cache(&self) -> Option<PathBuf>;
|
||||
}
|
||||
|
||||
impl FsScheme for SchemeRef<'_> {
|
||||
fn cache(&self) -> Option<PathBuf> {
|
||||
match self {
|
||||
Self::Regular { .. } | Self::Search { .. } => None,
|
||||
Self::Archive { domain, .. } => Some(
|
||||
Xdg::temp_dir().join(format!("archive-{}", yazi_shared::scheme::Encode::domain(domain))),
|
||||
),
|
||||
Self::Sftp { domain, .. } => {
|
||||
Some(Xdg::temp_dir().join(format!("sftp-{}", yazi_shared::scheme::Encode::domain(domain))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FsScheme for Scheme {
|
||||
fn cache(&self) -> Option<PathBuf> { self.as_scheme().cache() }
|
||||
}
|
||||
|
||||
// --- Inject
|
||||
inventory::submit! {
|
||||
SchemeInventory {
|
||||
register: |registry| {
|
||||
registry.add_cached_field("cache", |_, me| Ok(me.cache().map(PathBufDyn::from)));
|
||||
}
|
||||
}
|
||||
}
|
||||
33
yazi-fs/src/spec.rs
Normal file
33
yazi-fs/src/spec.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use yazi_shared::{auth::{Auth, AuthKind, Encode}, path::PathBufDyn, spec::SpecInventory};
|
||||
use yazi_shim::{mlua::UserDataFieldsExt, strum::IntoStr};
|
||||
|
||||
use crate::Xdg;
|
||||
|
||||
pub trait FsSpec {
|
||||
fn cache(&self) -> Option<PathBuf>;
|
||||
}
|
||||
|
||||
impl FsSpec for Auth {
|
||||
fn cache(&self) -> Option<PathBuf> {
|
||||
match self.kind {
|
||||
AuthKind::Regular | AuthKind::Search => None,
|
||||
AuthKind::Mount | AuthKind::Scope | AuthKind::Sftp => Some(Xdg::temp_dir().join(format!(
|
||||
"{}_{}_{}",
|
||||
self.kind.into_str(),
|
||||
self.scheme,
|
||||
Encode::domain(&self.domain)
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Inject
|
||||
inventory::submit! {
|
||||
SpecInventory {
|
||||
register: |registry| {
|
||||
registry.add_cached_field("cache", |_, me| Ok(me.cache().map(PathBufDyn::from)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
use std::{borrow::Cow, ffi::OsStr, path::{Path, PathBuf}};
|
||||
|
||||
use yazi_shared::{path::{AsPath, PathDyn}, url::{AsUrl, Url, UrlBuf, UrlBufInventory, UrlCow}};
|
||||
use mlua::UserDataFields;
|
||||
use yazi_shared::{path::{AsPath, PathDyn}, url::{AsUrl, Url, UrlBuf, UrlBufInventory, UrlCow, UrlLike}};
|
||||
use yazi_shim::mlua::UserDataFieldsExt;
|
||||
|
||||
use crate::{FsHash128, FsScheme, path::PercentEncoding};
|
||||
use crate::{FsHash128, FsSpec, path::PercentEncoding};
|
||||
|
||||
pub trait FsUrl<'a> {
|
||||
fn cache(&self) -> Option<PathBuf>;
|
||||
|
|
@ -36,11 +37,11 @@ impl<'a> FsUrl<'a> for Url<'a> {
|
|||
root
|
||||
}
|
||||
|
||||
self.scheme().cache().map(|root| with_loc(self.loc(), root))
|
||||
self.auth().cache().map(|root| with_loc(self.loc(), root))
|
||||
}
|
||||
|
||||
fn cache_lock(&self) -> Option<PathBuf> {
|
||||
self.scheme().cache().map(|mut root| {
|
||||
self.auth().cache().map(|mut root| {
|
||||
root.push("%lock");
|
||||
root.push(format!("{:x}", self.hash_u128()));
|
||||
root
|
||||
|
|
@ -50,7 +51,7 @@ impl<'a> FsUrl<'a> for Url<'a> {
|
|||
fn unified_path(self) -> Cow<'a, Path> {
|
||||
match self {
|
||||
Self::Regular(loc) | Self::Search { loc, .. } => loc.as_inner().into(),
|
||||
Self::Archive { .. } | Self::Sftp { .. } => {
|
||||
Self::Mount { .. } | Self::Scope { .. } | Self::Sftp { .. } => {
|
||||
self.cache().expect("non-local URL should have a cache path").into()
|
||||
}
|
||||
}
|
||||
|
|
@ -65,7 +66,7 @@ impl FsUrl<'_> for UrlBuf {
|
|||
fn unified_path(self) -> Cow<'static, Path> {
|
||||
match self {
|
||||
Self::Regular(loc) | Self::Search { loc, .. } => loc.into_inner().into(),
|
||||
Self::Archive { .. } | Self::Sftp { .. } => {
|
||||
Self::Mount { .. } | Self::Scope { .. } | Self::Sftp { .. } => {
|
||||
self.cache().expect("non-local URL should have a cache path").into()
|
||||
}
|
||||
}
|
||||
|
|
@ -80,7 +81,7 @@ impl<'a> FsUrl<'a> for UrlCow<'a> {
|
|||
fn unified_path(self) -> Cow<'a, Path> {
|
||||
match self {
|
||||
Self::Regular(loc) | Self::Search { loc, .. } => loc.into_inner(),
|
||||
Self::Archive { .. } | Self::Sftp { .. } => {
|
||||
Self::Mount { .. } | Self::Scope { .. } | Self::Sftp { .. } => {
|
||||
self.cache().expect("non-local URL should have a cache path").into()
|
||||
}
|
||||
}
|
||||
|
|
@ -92,6 +93,22 @@ inventory::submit! {
|
|||
UrlBufInventory {
|
||||
register: |registry| {
|
||||
registry.add_cached_field("cache", |_, me| Ok(me.cache()));
|
||||
registry.add_cached_field("domain", |lua, me| {
|
||||
yazi_binding::deprecate!(lua, "{}: `Url.domain` is deprecated, use `Url.spec.domain` instead.");
|
||||
lua.create_string(&*me.spec().domain)
|
||||
});
|
||||
registry.add_field_method_get("is_regular", |lua, me| {
|
||||
yazi_binding::deprecate!(lua, "{}: `Url.is_regular` is deprecated, use `Url.spec.is_regular` instead.");
|
||||
Ok(me.is_regular())
|
||||
});
|
||||
registry.add_field_method_get("is_search", |lua, me| {
|
||||
yazi_binding::deprecate!(lua, "{}: `Url.is_search` is deprecated, use `Url.spec.is_search` instead.");
|
||||
Ok(me.is_search())
|
||||
});
|
||||
registry.add_cached_field("scheme", |lua, me| {
|
||||
yazi_binding::deprecate!(lua, "{}: `Url.scheme` is deprecated, use `Url.spec` instead.");
|
||||
Ok(me.spec())
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use serde::Deserialize;
|
|||
use yazi_core::mgr::CdSource;
|
||||
use yazi_fs::path::{clean_url, expand_url};
|
||||
use yazi_shared::{event::ActionCow, url::{Url, UrlBuf}};
|
||||
use yazi_vfs::provider;
|
||||
use yazi_vfs::engine;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CdForm {
|
||||
|
|
@ -27,7 +27,7 @@ impl TryFrom<ActionCow> for CdForm {
|
|||
me.target = expand_url(me.target).into_owned();
|
||||
}
|
||||
|
||||
if let Some(u) = provider::try_absolute(&me.target)
|
||||
if let Some(u) = engine::try_absolute(&me.target)
|
||||
&& u.is_owned()
|
||||
{
|
||||
me.target = u.into_owned();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use serde::Deserialize;
|
|||
use yazi_core::mgr::CdSource;
|
||||
use yazi_fs::path::{clean_url, expand_url};
|
||||
use yazi_shared::{event::ActionCow, url::UrlBuf};
|
||||
use yazi_vfs::provider;
|
||||
use yazi_vfs::engine;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct RevealForm {
|
||||
|
|
@ -27,7 +27,7 @@ impl TryFrom<ActionCow> for RevealForm {
|
|||
me.target = expand_url(me.target).into_owned();
|
||||
}
|
||||
|
||||
if let Some(u) = provider::try_absolute(&me.target)
|
||||
if let Some(u) = engine::try_absolute(&me.target)
|
||||
&& u.is_owned()
|
||||
{
|
||||
me.target = u.into_owned();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use serde::Deserialize;
|
|||
use yazi_boot::BOOT;
|
||||
use yazi_fs::path::{clean_url, expand_url};
|
||||
use yazi_shared::{event::ActionCow, url::UrlBuf};
|
||||
use yazi_vfs::provider;
|
||||
use yazi_vfs::engine;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct TabCreateForm {
|
||||
|
|
@ -30,7 +30,7 @@ impl TryFrom<ActionCow> for TabCreateForm {
|
|||
target = expand_url(target).into_owned();
|
||||
}
|
||||
|
||||
if let Some(u) = provider::try_absolute(&target)
|
||||
if let Some(u) = engine::try_absolute(&target)
|
||||
&& u.is_owned()
|
||||
{
|
||||
target = u.into_owned();
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ function Header:flags()
|
|||
local finder = self._tab.finder
|
||||
|
||||
local t = {}
|
||||
if cwd.is_search then
|
||||
t[#t + 1] = string.format("search: %s", cwd.domain)
|
||||
if cwd.spec.is_search then
|
||||
t[#t + 1] = string.format("search: %s", cwd.spec.domain)
|
||||
end
|
||||
if filter then
|
||||
t[#t + 1] = string.format("filter: %s", filter)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ function M:entry()
|
|||
ya.emit("escape", { visual = true })
|
||||
|
||||
local cwd, selected = state()
|
||||
if cwd.scheme.is_virtual then
|
||||
if cwd.spec.is_virtual then
|
||||
return ya.notify { title = "Fzf", content = "Not supported under virtual filesystems", timeout = 5, level = "warn" }
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
local function fetch(_, job)
|
||||
local updates = {}
|
||||
for _, file in ipairs(job.files) do
|
||||
if file.url.scheme.is_virtual then
|
||||
if file.url.spec.is_virtual then
|
||||
updates[file.url] = "folder/remote"
|
||||
else
|
||||
updates[file.url] = "folder/local"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ local M = {}
|
|||
|
||||
local function stale_cache(file)
|
||||
local url = file.url
|
||||
local lock = url.scheme.cache:join(string.format("%%lock/%s", url:hash(true)))
|
||||
local lock = url.spec.cache:join(string.format("%%lock/%s", url:hash(true)))
|
||||
|
||||
local fd = fs.access():read(true):open(Url(lock))
|
||||
if not fd then
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ use mlua::{IntoLuaMulti, UserData, UserDataFields, UserDataMethods, Value};
|
|||
use yazi_binding::Error;
|
||||
|
||||
pub enum SizeCalculator {
|
||||
Local(yazi_fs::provider::local::SizeCalculator),
|
||||
Remote(yazi_vfs::provider::SizeCalculator),
|
||||
Local(yazi_fs::engine::local::SizeCalculator),
|
||||
Remote(yazi_vfs::engine::SizeCalculator),
|
||||
}
|
||||
|
||||
impl UserData for SizeCalculator {
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ use std::str::FromStr;
|
|||
use mlua::{ExternalError, Function, IntoLua, IntoLuaMulti, Lua, LuaString, Table, Value};
|
||||
use yazi_binding::{Composer, ComposerGet, ComposerSet, Error};
|
||||
use yazi_config::Pattern;
|
||||
use yazi_fs::{file::File, mounts::PARTITIONS, provider::{Attrs, DirReader, FileHolder}};
|
||||
use yazi_fs::{engine::{Attrs, DirReader, FileHolder}, file::File, mounts::PARTITIONS};
|
||||
use yazi_shared::url::{UrlBuf, UrlCow, UrlLike, UrlRef};
|
||||
use yazi_vfs::{VfsFile, provider};
|
||||
use yazi_vfs::{VfsFile, engine};
|
||||
|
||||
use crate::fs::SizeCalculator;
|
||||
|
||||
|
|
@ -38,15 +38,15 @@ pub fn compose() -> Composer<ComposerGet, ComposerSet> {
|
|||
}
|
||||
|
||||
fn access(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|_, ()| Ok(yazi_vfs::provider::Gate::default()))
|
||||
lua.create_function(|_, ()| Ok(yazi_vfs::engine::Demand::default()))
|
||||
}
|
||||
|
||||
fn calc_size(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_async_function(|lua, url: UrlRef| async move {
|
||||
let it = if let Some(path) = url.as_local() {
|
||||
yazi_fs::provider::local::SizeCalculator::new(path).await.map(SizeCalculator::Local)
|
||||
yazi_fs::engine::local::SizeCalculator::new(path).await.map(SizeCalculator::Local)
|
||||
} else {
|
||||
yazi_vfs::provider::SizeCalculator::new(&*url).await.map(SizeCalculator::Remote)
|
||||
yazi_vfs::engine::SizeCalculator::new(&*url).await.map(SizeCalculator::Remote)
|
||||
};
|
||||
|
||||
match it {
|
||||
|
|
@ -59,9 +59,9 @@ fn calc_size(lua: &Lua) -> mlua::Result<Function> {
|
|||
fn cha(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_async_function(|lua, (url, follow): (UrlRef, Option<bool>)| async move {
|
||||
let cha = if follow.unwrap_or(false) {
|
||||
provider::metadata(&*url).await
|
||||
engine::metadata(&*url).await
|
||||
} else {
|
||||
provider::symlink_metadata(&*url).await
|
||||
engine::symlink_metadata(&*url).await
|
||||
};
|
||||
|
||||
match cha {
|
||||
|
|
@ -73,7 +73,7 @@ fn cha(lua: &Lua) -> mlua::Result<Function> {
|
|||
|
||||
fn copy(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_async_function(|lua, (from, to): (UrlRef, UrlRef)| async move {
|
||||
match provider::copy(&*from, &*to, Attrs::default()).await {
|
||||
match engine::copy(&*from, &*to, Attrs::default()).await {
|
||||
Ok(len) => len.into_lua_multi(&lua),
|
||||
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
|
||||
}
|
||||
|
|
@ -83,8 +83,8 @@ fn copy(lua: &Lua) -> mlua::Result<Function> {
|
|||
fn create(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_async_function(|lua, (r#type, url): (LuaString, UrlRef)| async move {
|
||||
let result = match &*r#type.as_bytes() {
|
||||
b"dir" => provider::create_dir(&*url).await,
|
||||
b"dir_all" => provider::create_dir_all(&*url).await,
|
||||
b"dir" => engine::create_dir(&*url).await,
|
||||
b"dir_all" => engine::create_dir_all(&*url).await,
|
||||
_ => Err("Creation type must be 'dir' or 'dir_all'".into_lua_err())?,
|
||||
};
|
||||
|
||||
|
|
@ -171,7 +171,7 @@ fn read_dir(lua: &Lua) -> mlua::Result<Function> {
|
|||
let limit = options.raw_get("limit").unwrap_or(usize::MAX);
|
||||
let resolve = options.raw_get::<bool>("resolve")?;
|
||||
|
||||
let mut it = match provider::read_dir(&*dir).await {
|
||||
let mut it = match engine::read_dir(&*dir).await {
|
||||
Ok(it) => it,
|
||||
Err(e) => return (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
|
||||
};
|
||||
|
|
@ -204,10 +204,10 @@ fn read_dir(lua: &Lua) -> mlua::Result<Function> {
|
|||
fn remove(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_async_function(|lua, (r#type, url): (LuaString, UrlRef)| async move {
|
||||
let result = match &*r#type.as_bytes() {
|
||||
b"file" => provider::remove_file(&*url).await,
|
||||
b"dir" => provider::remove_dir(&*url).await,
|
||||
b"dir_all" => provider::remove_dir_all(&*url).await,
|
||||
b"dir_clean" => provider::remove_dir_clean(&*url).await,
|
||||
b"file" => engine::remove_file(&*url).await,
|
||||
b"dir" => engine::remove_dir(&*url).await,
|
||||
b"dir_all" => engine::remove_dir_all(&*url).await,
|
||||
b"dir_clean" => engine::remove_dir_clean(&*url).await,
|
||||
_ => Err("Removal type must be 'file', 'dir', 'dir_all', or 'dir_clean'".into_lua_err())?,
|
||||
};
|
||||
|
||||
|
|
@ -220,7 +220,7 @@ fn remove(lua: &Lua) -> mlua::Result<Function> {
|
|||
|
||||
fn rename(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_async_function(|lua, (from, to): (UrlRef, UrlRef)| async move {
|
||||
match provider::rename(&*from, &*to).await {
|
||||
match engine::rename(&*from, &*to).await {
|
||||
Ok(()) => true.into_lua_multi(&lua),
|
||||
Err(e) => (false, Error::Io(e)).into_lua_multi(&lua),
|
||||
}
|
||||
|
|
@ -244,7 +244,7 @@ fn unique(lua: &Lua) -> mlua::Result<Function> {
|
|||
|
||||
fn write(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_async_function(|lua, (url, data): (UrlRef, LuaString)| async move {
|
||||
match provider::write(&*url, data.as_bytes()).await {
|
||||
match engine::write(&*url, data.as_bytes()).await {
|
||||
Ok(()) => true.into_lua_multi(&lua),
|
||||
Err(e) => (false, Error::Io(e)).into_lua_multi(&lua),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
use mlua::{IntoLua, Lua, Value};
|
||||
use yazi_binding::{Composer, ComposerGet, ComposerSet, elements::{Align, Bar, Border, Color, Constraint, Edge, Fill, Gauge, Layout, Line, List, Pad, Rect, Row, Span, Table, Text, Wrap}, position::Position, style::Style};
|
||||
use yazi_config::THEME;
|
||||
use yazi_widgets::{clear::Clear, input::InputArc};
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_macro::err;
|
||||
use yazi_shim::strum::IntoStr;
|
||||
use yazi_widgets::{clear::Clear, input::{InputArc, InputEvent}};
|
||||
|
||||
pub fn compose() -> Composer<ComposerGet, ComposerSet> {
|
||||
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
|
||||
|
|
@ -16,7 +19,7 @@ pub fn compose() -> Composer<ComposerGet, ComposerSet> {
|
|||
b"Edge" => Edge::compose(lua)?,
|
||||
b"Fill" => Fill::compose(lua)?,
|
||||
b"Gauge" => Gauge::compose(lua)?,
|
||||
b"Input" => InputArc::compose(lua, (&THEME.input).into())?,
|
||||
b"Input" => InputArc::compose(lua, (&THEME.input).into(), publish_input)?,
|
||||
b"Layout" => Layout::compose(lua)?,
|
||||
b"Line" => Line::compose(lua)?,
|
||||
b"List" => List::compose(lua)?,
|
||||
|
|
@ -48,3 +51,7 @@ pub fn compose() -> Composer<ComposerGet, ComposerSet> {
|
|||
|
||||
Composer::new(get, set)
|
||||
}
|
||||
|
||||
fn publish_input(event: InputEvent) {
|
||||
err!(Pubsub::pub_after_input((&event).into_str(), event.value()));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::any::TypeId;
|
|||
|
||||
use mlua::{AnyUserData, ExternalError, Function, Lua, LuaString};
|
||||
use tokio::process::{ChildStderr, ChildStdin, ChildStdout};
|
||||
use yazi_vfs::provider::RwFile;
|
||||
use yazi_vfs::engine::RwFile;
|
||||
|
||||
use super::Utils;
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ impl Utils {
|
|||
}
|
||||
(b"mpsc", Some(buffer)) => {
|
||||
let (tx, rx) = tokio::sync::mpsc::channel::<Value>(buffer);
|
||||
(MpscTx(tx), MpscRx(rx)).into_lua_multi(lua)
|
||||
(MpscTx::new(tx), MpscRx(rx)).into_lua_multi(lua)
|
||||
}
|
||||
(b"mpsc", None) => {
|
||||
let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<Value>();
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ anyhow = { workspace = true }
|
|||
hashbrown = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-util = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
yazi_macro::mod_pub!(entry fetcher loader preloader previewer);
|
||||
yazi_macro::mod_pub!(entry fetcher loader preloader previewer provider);
|
||||
|
||||
yazi_macro::mod_flat!(runner spot);
|
||||
|
||||
|
|
@ -6,6 +6,5 @@ pub static RUNNER: yazi_shim::cell::RoCell<Runner> = yazi_shim::cell::RoCell::ne
|
|||
|
||||
pub fn init(setter: fn(&mlua::Lua) -> mlua::Result<()>) {
|
||||
crate::loader::init();
|
||||
|
||||
RUNNER.init(Runner { setter });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::{Context, Result, bail, ensure};
|
|||
use hashbrown::HashMap;
|
||||
use mlua::{ExternalError, Lua, Table, chunk::ChunkMode};
|
||||
use parking_lot::RwLock;
|
||||
use yazi_fs::{Xdg, provider::local::Local};
|
||||
use yazi_fs::{Xdg, engine::local::Local};
|
||||
use yazi_macro::plugin_preset as preset;
|
||||
use yazi_shared::{BytesExt, LOG_LEVEL};
|
||||
use yazi_shim::cell::RoCell;
|
||||
|
|
|
|||
162
yazi-runner/src/provider/job.rs
Normal file
162
yazi-runner/src/provider/job.rs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
use std::io;
|
||||
|
||||
use mlua::{IntoLua, Lua, Value};
|
||||
use strum::AsRefStr;
|
||||
use yazi_binding::MpscTx;
|
||||
use yazi_fs::engine::{Attrs, Demand};
|
||||
use yazi_shared::{path::PathBufDyn, url::UrlBuf};
|
||||
|
||||
#[derive(AsRefStr)]
|
||||
#[strum(serialize_all = "PascalCase")]
|
||||
pub enum ProviderJob {
|
||||
Capabilities,
|
||||
Absolute {
|
||||
url: UrlBuf,
|
||||
},
|
||||
Canonicalize {
|
||||
url: UrlBuf,
|
||||
},
|
||||
Casefold {
|
||||
url: UrlBuf,
|
||||
},
|
||||
SymlinkMetadata {
|
||||
url: UrlBuf,
|
||||
},
|
||||
Metadata {
|
||||
url: UrlBuf,
|
||||
},
|
||||
ReadDir {
|
||||
url: UrlBuf,
|
||||
},
|
||||
Open {
|
||||
url: UrlBuf,
|
||||
attrs: Attrs,
|
||||
demand: Demand,
|
||||
},
|
||||
CreateDir {
|
||||
url: UrlBuf,
|
||||
},
|
||||
HardLink {
|
||||
from: UrlBuf,
|
||||
to: PathBufDyn,
|
||||
},
|
||||
ReadLink {
|
||||
url: UrlBuf,
|
||||
},
|
||||
RemoveDir {
|
||||
url: UrlBuf,
|
||||
},
|
||||
RemoveFile {
|
||||
url: UrlBuf,
|
||||
},
|
||||
Rename {
|
||||
from: UrlBuf,
|
||||
to: PathBufDyn,
|
||||
},
|
||||
Symlink {
|
||||
original: Vec<u8>,
|
||||
url: UrlBuf,
|
||||
is_dir: bool,
|
||||
},
|
||||
Trash {
|
||||
url: UrlBuf,
|
||||
},
|
||||
Read {
|
||||
url: UrlBuf,
|
||||
offset: u64,
|
||||
len: usize,
|
||||
},
|
||||
Write {
|
||||
url: UrlBuf,
|
||||
offset: u64,
|
||||
bytes: Vec<u8>,
|
||||
},
|
||||
Copy {
|
||||
from: UrlBuf,
|
||||
to: PathBufDyn,
|
||||
attrs: Attrs,
|
||||
},
|
||||
CopyProgressive {
|
||||
from: UrlBuf,
|
||||
to: PathBufDyn,
|
||||
attrs: Attrs,
|
||||
tx: MpscTx<u64, io::Result<u64>>,
|
||||
},
|
||||
SetLen {
|
||||
url: UrlBuf,
|
||||
size: u64,
|
||||
},
|
||||
SetAttrs {
|
||||
url: UrlBuf,
|
||||
attrs: Attrs,
|
||||
},
|
||||
}
|
||||
|
||||
impl IntoLua for ProviderJob {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||
let t = lua.create_table()?;
|
||||
t.raw_set("op", self.as_ref())?;
|
||||
|
||||
match self {
|
||||
Self::Capabilities => {}
|
||||
|
||||
Self::Absolute { url }
|
||||
| Self::Canonicalize { url }
|
||||
| Self::Casefold { url }
|
||||
| Self::SymlinkMetadata { url }
|
||||
| Self::Metadata { url }
|
||||
| Self::ReadDir { url }
|
||||
| Self::CreateDir { url }
|
||||
| Self::ReadLink { url }
|
||||
| Self::RemoveDir { url }
|
||||
| Self::RemoveFile { url }
|
||||
| Self::Trash { url } => t.raw_set("url", url)?,
|
||||
|
||||
Self::Open { url, attrs, demand } => {
|
||||
t.raw_set("url", url)?;
|
||||
t.raw_set("attrs", attrs)?;
|
||||
t.raw_set("demand", demand)?;
|
||||
}
|
||||
Self::HardLink { from, to } | Self::Rename { from, to } => {
|
||||
t.raw_set("from", from)?;
|
||||
t.raw_set("to", to)?;
|
||||
}
|
||||
Self::Symlink { original, url, is_dir } => {
|
||||
t.raw_set("original", lua.create_external_string(original)?)?;
|
||||
t.raw_set("url", url)?;
|
||||
t.raw_set("is_dir", is_dir)?;
|
||||
}
|
||||
Self::Read { url, offset, len } => {
|
||||
t.raw_set("url", url)?;
|
||||
t.raw_set("offset", offset)?;
|
||||
t.raw_set("len", len)?;
|
||||
}
|
||||
Self::Write { url, offset, bytes } => {
|
||||
t.raw_set("url", url)?;
|
||||
t.raw_set("offset", offset)?;
|
||||
t.raw_set("bytes", lua.create_external_string(bytes)?)?;
|
||||
}
|
||||
Self::Copy { from, to, attrs } => {
|
||||
t.raw_set("from", from)?;
|
||||
t.raw_set("to", to)?;
|
||||
t.raw_set("attrs", attrs)?;
|
||||
}
|
||||
Self::CopyProgressive { from, to, attrs, tx } => {
|
||||
t.raw_set("from", from)?;
|
||||
t.raw_set("to", to)?;
|
||||
t.raw_set("attrs", attrs)?;
|
||||
t.raw_set("tx", tx)?;
|
||||
}
|
||||
Self::SetLen { url, size } => {
|
||||
t.raw_set("url", url)?;
|
||||
t.raw_set("size", size)?;
|
||||
}
|
||||
Self::SetAttrs { url, attrs } => {
|
||||
t.raw_set("url", url)?;
|
||||
t.raw_set("attrs", attrs)?;
|
||||
}
|
||||
}
|
||||
|
||||
t.into_lua(lua)
|
||||
}
|
||||
}
|
||||
1
yazi-runner/src/provider/mod.rs
Normal file
1
yazi-runner/src/provider/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
yazi_macro::mod_flat!(job result provider);
|
||||
42
yazi-runner/src/provider/provider.rs
Normal file
42
yazi-runner/src/provider/provider.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use mlua::{ExternalError, FromLua, FromLuaMulti, IntoLua, ObjectLike, Value};
|
||||
use tokio::runtime::Handle;
|
||||
use yazi_shared::{data::Sendable, event::Cmd};
|
||||
|
||||
use crate::{Runner, loader::LOADER, provider::{ProvideResult, ProviderJob}};
|
||||
|
||||
impl Runner {
|
||||
pub async fn provide<T>(&'static self, run: &'static Cmd, job: ProviderJob) -> ProvideResult<T>
|
||||
where
|
||||
T: FromLua + Send + 'static,
|
||||
{
|
||||
match LOADER.ensure(&run.name, |_| ()).await {
|
||||
Ok(()) => self.provide_do(run, job).await,
|
||||
Err(e) => yazi_binding::Error::custom(e.to_string()).into(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn provide_do<T>(&'static self, run: &'static Cmd, job: ProviderJob) -> ProvideResult<T>
|
||||
where
|
||||
T: FromLua + Send + 'static,
|
||||
{
|
||||
match tokio::task::spawn_blocking(move || {
|
||||
let lua = self.spawn(&run.name)?;
|
||||
|
||||
Handle::current().block_on(async {
|
||||
let Value::Table(job) = job.into_lua(&lua)? else {
|
||||
return Err("ProviderJob should be a table".into_lua_err());
|
||||
};
|
||||
job.raw_set("args", Sendable::args_to_table_ref(&lua, &run.args)?)?;
|
||||
|
||||
let values = LOADER.load(&lua, &run.name).await?.call_async_method("provide", job).await?;
|
||||
ProvideResult::from_lua_multi(values, &lua)
|
||||
})
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(Ok(result)) => result,
|
||||
Ok(Err(error)) => error.into(),
|
||||
Err(error) => error.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
43
yazi-runner/src/provider/result.rs
Normal file
43
yazi-runner/src/provider/result.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
use mlua::{FromLua, FromLuaMulti, Lua, MultiValue, Value};
|
||||
|
||||
pub struct ProvideResult<T>(pub Result<T, yazi_binding::Error>);
|
||||
|
||||
impl<T> From<yazi_binding::Error> for ProvideResult<T> {
|
||||
fn from(value: yazi_binding::Error) -> Self { Self(Err(value)) }
|
||||
}
|
||||
|
||||
impl<T> From<mlua::Error> for ProvideResult<T> {
|
||||
fn from(value: mlua::Error) -> Self { yazi_binding::Error::custom(value.to_string()).into() }
|
||||
}
|
||||
|
||||
impl<T> From<tokio::task::JoinError> for ProvideResult<T> {
|
||||
fn from(value: tokio::task::JoinError) -> Self {
|
||||
yazi_binding::Error::custom(value.to_string()).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromLua> FromLuaMulti for ProvideResult<T> {
|
||||
fn from_lua_multi(mut values: MultiValue, lua: &Lua) -> mlua::Result<Self> {
|
||||
let value = values.pop_front().unwrap_or(Value::Nil);
|
||||
let error = values.pop_front().unwrap_or(Value::Nil);
|
||||
|
||||
Ok(Self(if error.is_nil() {
|
||||
T::from_lua(value, lua).map_err(|e| yazi_binding::Error::custom(e.to_string()))
|
||||
} else {
|
||||
Err(
|
||||
yazi_binding::Error::from_lua(error, lua)
|
||||
.unwrap_or_else(|e| yazi_binding::Error::custom(e.to_string())),
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl ProvideResult<bool> {
|
||||
pub fn ok(self) -> Result<(), yazi_binding::Error> {
|
||||
if self.0? {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(yazi_binding::Error::custom("Lua VFS returned false without an Error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,9 +4,9 @@ use anyhow::{Context, Result, anyhow};
|
|||
use tokio::{io::{self, ErrorKind::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, provider::{Attrs, FileHolder, Provider, local::Local}};
|
||||
use yazi_fs::{Cwd, FsHash128, FsUrl, cha::Cha, engine::{Attrs, Engine, FileHolder, local::Local}, ok_or_not_found, path::path_relative_to};
|
||||
use yazi_shared::{path::PathCow, url::{AsUrl, UrlCow, UrlLike}};
|
||||
use yazi_vfs::{VfsCha, maybe_exists, provider::{self, DirEntry}, unique_file};
|
||||
use yazi_vfs::{VfsCha, engine::{self, DirEntry}, maybe_exists, unique_file};
|
||||
|
||||
use super::{FileInCopy, FileInDelete, FileInHardlink, FileInLink, FileInTrash};
|
||||
use crate::{LOW, NORMAL, TaskOp, TaskOps, TasksProxy, ctx, file::{FileIn, FileInCut, FileInDownload, FileInUpload, FileOutCopy, FileOutCopyDo, FileOutCut, FileOutCutDo, FileOutDelete, FileOutDeleteDo, FileOutDownload, FileOutDownloadDo, FileOutHardlink, FileOutHardlinkDo, FileOutLink, FileOutTrash, FileOutUpload, FileOutUploadDo, Transaction, Traverse}, hook::{HookInOutCopy, HookInOutCut, HookInOutHardlink, HookInOutLink}, ok_or_not_found};
|
||||
|
|
@ -38,7 +38,7 @@ impl File {
|
|||
|
||||
super::traverse::<FileOutCopy, _, _, _, _, _>(
|
||||
task,
|
||||
async |dir| match provider::create_dir(dir).await {
|
||||
async |dir| match engine::create_dir(dir).await {
|
||||
Err(e) if e.kind() != io::ErrorKind::AlreadyExists => Err(e)?,
|
||||
_ => Ok(()),
|
||||
},
|
||||
|
|
@ -63,7 +63,7 @@ impl File {
|
|||
pub(crate) async fn copy_do(&self, mut task: FileInCopy) -> Result<(), FileOutCopyDo> {
|
||||
ok_or_not_found!(task, Transaction::unlink(&task.to).await);
|
||||
let mut rx =
|
||||
ctx!(task, provider::copy_with_progress(&task.from, &task.to, task.cha.unwrap()).await)?;
|
||||
ctx!(task, engine::copy_progressive(&task.from, &task.to, task.cha.unwrap()).await)?;
|
||||
|
||||
loop {
|
||||
match rx.recv().await.unwrap_or(Ok(0)) {
|
||||
|
|
@ -101,16 +101,16 @@ impl File {
|
|||
self.ops.out(id, HookInOutCut::new(&task.from, &task.to));
|
||||
TasksProxy::update_succeed(id, [&task.to], true);
|
||||
|
||||
if !task.follow && ok_or_not_found(provider::rename(&task.from, &task.to).await).is_ok() {
|
||||
if !task.follow && ok_or_not_found(engine::rename(&task.from, &task.to).await).is_ok() {
|
||||
return Ok(self.ops.out(id, FileOutCut::Succ));
|
||||
}
|
||||
|
||||
let (mut links, mut files) = (vec![], vec![]);
|
||||
let reorder = task.follow && ctx!(task, provider::capabilities(&task.from).await)?.symlink;
|
||||
let reorder = task.follow && ctx!(task, engine::capabilities(&task.from).await)?.symlink;
|
||||
|
||||
super::traverse::<FileOutCut, _, _, _, _, _>(
|
||||
task,
|
||||
async |dir| match provider::create_dir(dir).await {
|
||||
async |dir| match engine::create_dir(dir).await {
|
||||
Err(e) if e.kind() != io::ErrorKind::AlreadyExists => Err(e)?,
|
||||
_ => Ok(()),
|
||||
},
|
||||
|
|
@ -155,12 +155,12 @@ impl File {
|
|||
pub(crate) async fn cut_do(&self, mut task: FileInCut) -> Result<(), FileOutCutDo> {
|
||||
ok_or_not_found!(task, Transaction::unlink(&task.to).await);
|
||||
let mut rx =
|
||||
ctx!(task, provider::copy_with_progress(&task.from, &task.to, task.cha.unwrap()).await)?;
|
||||
ctx!(task, engine::copy_progressive(&task.from, &task.to, task.cha.unwrap()).await)?;
|
||||
|
||||
loop {
|
||||
match rx.recv().await.unwrap_or(Ok(0)) {
|
||||
Ok(0) => {
|
||||
provider::remove_file(&task.from).await.ok();
|
||||
engine::remove_file(&task.from).await.ok();
|
||||
break;
|
||||
}
|
||||
Ok(n) => self.ops.out(task.id, FileOutCutDo::Adv(n)),
|
||||
|
|
@ -199,7 +199,7 @@ impl File {
|
|||
let mut src: PathCow = if task.resolve {
|
||||
ok_or_not_found!(
|
||||
task,
|
||||
provider::read_link(&task.from).await,
|
||||
engine::read_link(&task.from).await,
|
||||
return Ok(self.ops.out(task.id, FileOutLink::Succ))
|
||||
)
|
||||
.into()
|
||||
|
|
@ -208,14 +208,14 @@ impl File {
|
|||
};
|
||||
|
||||
if task.relative {
|
||||
let canon = ctx!(task, provider::canonicalize(task.to.parent().unwrap()).await)?;
|
||||
let canon = ctx!(task, engine::canonicalize(task.to.parent().unwrap()).await)?;
|
||||
src = ctx!(task, path_relative_to(canon.loc(), src))?;
|
||||
}
|
||||
|
||||
ok_or_not_found!(task, provider::remove_file(&task.to).await);
|
||||
ok_or_not_found!(task, engine::remove_file(&task.to).await);
|
||||
ctx!(
|
||||
task,
|
||||
provider::symlink(&task.to, src, async || {
|
||||
engine::symlink(&task.to, src, async || {
|
||||
Ok(match task.cha {
|
||||
Some(cha) => cha.is_dir(),
|
||||
None => Self::cha(&task.from, task.resolve, None).await?.is_dir(),
|
||||
|
|
@ -225,7 +225,7 @@ impl File {
|
|||
)?;
|
||||
|
||||
if task.delete {
|
||||
provider::remove_file(&task.from).await.ok();
|
||||
engine::remove_file(&task.from).await.ok();
|
||||
}
|
||||
Ok(self.ops.out(task.id, FileOutLink::Succ))
|
||||
}
|
||||
|
|
@ -241,7 +241,7 @@ impl File {
|
|||
self.ops.out(task.id, HookInOutHardlink::new(&task.from, &task.to));
|
||||
super::traverse::<FileOutHardlink, _, _, _, _, _>(
|
||||
task,
|
||||
async |dir| match provider::create_dir(dir).await {
|
||||
async |dir| match engine::create_dir(dir).await {
|
||||
Err(e) if e.kind() != io::ErrorKind::AlreadyExists => Err(e)?,
|
||||
_ => Ok(()),
|
||||
},
|
||||
|
|
@ -261,14 +261,14 @@ impl File {
|
|||
pub(crate) async fn hardlink_do(&self, task: FileInHardlink) -> Result<(), FileOutHardlinkDo> {
|
||||
let src = if !task.follow {
|
||||
UrlCow::from(&task.from)
|
||||
} else if let Ok(p) = provider::canonicalize(&task.from).await {
|
||||
} else if let Ok(p) = engine::canonicalize(&task.from).await {
|
||||
UrlCow::from(p)
|
||||
} else {
|
||||
UrlCow::from(&task.from)
|
||||
};
|
||||
|
||||
ok_or_not_found!(task, provider::remove_file(&task.to).await);
|
||||
ok_or_not_found!(task, provider::hard_link(&src, &task.to).await);
|
||||
ok_or_not_found!(task, engine::remove_file(&task.to).await);
|
||||
ok_or_not_found!(task, engine::hard_link(&src, &task.to).await);
|
||||
|
||||
Ok(self.ops.out(task.id, FileOutHardlinkDo::Succ))
|
||||
}
|
||||
|
|
@ -291,7 +291,7 @@ impl File {
|
|||
}
|
||||
|
||||
pub(crate) async fn delete_do(&self, task: FileInDelete) -> Result<(), FileOutDeleteDo> {
|
||||
match provider::remove_file(&task.target).await {
|
||||
match engine::remove_file(&task.target).await {
|
||||
Ok(()) => {}
|
||||
Err(e) if e.kind() == NotFound => {}
|
||||
Err(_) if !maybe_exists(&task.target).await => {}
|
||||
|
|
@ -305,7 +305,7 @@ impl File {
|
|||
}
|
||||
|
||||
pub(crate) async fn trash_do(&self, task: FileInTrash) -> Result<(), FileOutTrash> {
|
||||
ctx!(task, provider::trash(&task.target).await)?;
|
||||
ctx!(task, engine::trash(&task.target).await)?;
|
||||
Ok(self.ops.out(task.id, FileOutTrash::Succ))
|
||||
}
|
||||
|
||||
|
|
@ -345,12 +345,12 @@ impl File {
|
|||
let cache = ctx!(task, task.target.cache(), "Cannot determine cache path")?;
|
||||
let cache_tmp = ctx!(task, Transaction::tmp(&cache).await, "Cannot determine download cache")?;
|
||||
|
||||
let mut rx = ctx!(task, provider::copy_with_progress(&task.target, &cache_tmp, cha).await)?;
|
||||
let mut rx = ctx!(task, engine::copy_progressive(&task.target, &cache_tmp, cha).await)?;
|
||||
loop {
|
||||
match rx.recv().await.unwrap_or(Ok(0)) {
|
||||
Ok(0) => {
|
||||
Local::regular(&cache).remove_dir_all().await.ok();
|
||||
ctx!(task, provider::rename(cache_tmp, cache).await, "Cannot persist downloaded file")?;
|
||||
ctx!(task, engine::rename(cache_tmp, cache).await, "Cannot persist downloaded file")?;
|
||||
|
||||
let lock = ctx!(task, task.target.cache_lock(), "Cannot determine cache lock")?;
|
||||
let hash = format!("{:x}", cha.hash_u128());
|
||||
|
|
@ -422,7 +422,7 @@ impl File {
|
|||
ctx!(task, Transaction::tmp(&task.target).await, "Cannot determine temporary upload path")?;
|
||||
let mut rx = ctx!(
|
||||
task,
|
||||
provider::copy_with_progress(cache, &tmp, Attrs {
|
||||
engine::copy_progressive(cache, &tmp, Attrs {
|
||||
mode: Some(cha.mode),
|
||||
atime: None,
|
||||
btime: None,
|
||||
|
|
@ -440,7 +440,7 @@ impl File {
|
|||
Err(anyhow!("Failed to work on: {task:?}: remote file has changed during upload"))?;
|
||||
}
|
||||
|
||||
ctx!(task, provider::rename(&tmp, &task.target).await, "Cannot persist uploaded file")?;
|
||||
ctx!(task, engine::rename(&tmp, &task.target).await, "Cannot persist uploaded file")?;
|
||||
|
||||
let cha =
|
||||
ctx!(task, Self::cha(&task.target, true, None).await, "Cannot stat uploaded file")?;
|
||||
|
|
@ -463,7 +463,7 @@ impl File {
|
|||
let cha = if let Some(entry) = entry {
|
||||
entry.metadata().await?
|
||||
} else {
|
||||
provider::symlink_metadata(url.as_url()).await?
|
||||
engine::symlink_metadata(url.as_url()).await?
|
||||
};
|
||||
Ok(if follow { Cha::from_follow(url, cha).await } else { cha })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ impl Drop for FileInCut {
|
|||
impl FileInCut {
|
||||
pub fn new(from: UrlBuf, to: UrlBuf, force: bool) -> Self {
|
||||
Self {
|
||||
follow: !from.scheme().covariant(to.scheme()),
|
||||
follow: !from.auth().covariant(to.auth()),
|
||||
id: Id::ZERO,
|
||||
from,
|
||||
to,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use std::{hash::{BuildHasher, Hash, Hasher}, io};
|
||||
|
||||
use yazi_fs::cha::ChaMode;
|
||||
use yazi_fs::{cha::ChaMode, engine::Attrs};
|
||||
use yazi_macro::ok_or_not_found;
|
||||
use yazi_shared::{timestamp_us, url::{AsUrl, Url, UrlBuf}};
|
||||
use yazi_vfs::{provider, unique_file};
|
||||
use yazi_vfs::{engine, unique_file};
|
||||
|
||||
pub(super) struct Transaction;
|
||||
|
||||
|
|
@ -33,11 +33,11 @@ impl Transaction {
|
|||
{
|
||||
let url = url.as_url();
|
||||
|
||||
let cha = ok_or_not_found!(provider::symlink_metadata(url).await, return Ok(()));
|
||||
let cha = ok_or_not_found!(engine::symlink_metadata(url).await, return Ok(()));
|
||||
if cha.is_link() {
|
||||
provider::rename(Self::tmp(url).await?, url).await?;
|
||||
engine::rename(Self::tmp(url).await?, url).await?;
|
||||
} else if !cha.contains(ChaMode::U_WRITE) {
|
||||
provider::set_mode(url, cha.mode | ChaMode::U_WRITE).await?;
|
||||
engine::set_attrs(url, Attrs::mode(cha.mode | ChaMode::U_WRITE)).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::{collections::VecDeque, fmt::Debug};
|
||||
|
||||
use yazi_fs::{FsUrl, cha::Cha, path::skip_url, provider::{DirReader, FileHolder}};
|
||||
use yazi_fs::{FsUrl, cha::Cha, engine::{DirReader, FileHolder}, path::skip_url};
|
||||
use yazi_shared::{strand::StrandLike, url::{AsUrl, Url, UrlBuf, UrlLike}};
|
||||
use yazi_vfs::provider::{self};
|
||||
use yazi_vfs::engine::{self};
|
||||
|
||||
use crate::{ctx, file::{FileInCopy, FileInCut, FileInDelete, FileInDownload, FileInHardlink, FileInUpload}};
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ where
|
|||
}
|
||||
|
||||
while let Some(src) = dirs.pop_front() {
|
||||
let mut it = err!(provider::read_dir(&src).await, "Cannot read directory {src:?}");
|
||||
let mut it = err!(engine::read_dir(&src).await, "Cannot read directory {src:?}");
|
||||
|
||||
let dest = if let Some(root) = root {
|
||||
let s = skip_url(&src, skip);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use parking_lot::Mutex;
|
|||
use tokio::sync::mpsc;
|
||||
use yazi_dds::Pump;
|
||||
use yazi_fs::ok_or_not_found;
|
||||
use yazi_vfs::provider;
|
||||
use yazi_vfs::engine;
|
||||
|
||||
use crate::{Ongoing, TaskOp, TaskOps, TasksProxy, file::{FileOutCopy, FileOutCut, FileOutDelete, FileOutDownload, FileOutHardlink, FileOutLink, FileOutTrash, FileOutUpload}, hook::{HookIn, HookInDelete, HookInDownload, HookInOutCopy, HookInOutCut, HookInOutHardlink, HookInOutLink, HookInPreload, HookInTrash, HookInUpload}, preload::{Preload, PreloadOut}};
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ impl Hook {
|
|||
return self.ops.out(task.id, FileOutCut::Clean(Ok(())));
|
||||
}
|
||||
|
||||
let result = ok_or_not_found(provider::remove_dir_clean(&task.from).await);
|
||||
let result = ok_or_not_found(engine::remove_dir_clean(&task.from).await);
|
||||
TasksProxy::update_succeed(task.id, [&task.to, &task.from], true);
|
||||
Pump::push_move(task.from, task.to);
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ impl Hook {
|
|||
return self.ops.out(task.id, FileOutDelete::Clean(Ok(())));
|
||||
}
|
||||
|
||||
let result = ok_or_not_found(provider::remove_dir_all(&task.target).await);
|
||||
let result = ok_or_not_found(engine::remove_dir_all(&task.target).await);
|
||||
TasksProxy::update_succeed(task.id, [&task.target], false);
|
||||
Pump::push_delete(task.target);
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ impl Scheduler {
|
|||
}
|
||||
|
||||
pub fn file_copy(&self, from: UrlBuf, to: UrlBuf, force: bool, follow: bool) {
|
||||
let follow = follow || !from.scheme().covariant(to.scheme());
|
||||
let follow = follow || !from.auth().covariant(to.auth());
|
||||
let mut r#in = FileInCopy { id: Id::ZERO, from, to, force, cha: None, follow, retry: 0 };
|
||||
|
||||
self.add(&mut r#in, |_| ());
|
||||
|
|
@ -109,7 +109,7 @@ impl Scheduler {
|
|||
let mut r#in = FileInHardlink { id: Id::ZERO, from, to, force, cha: None, follow };
|
||||
self.add(&mut r#in, |_| ());
|
||||
|
||||
if !r#in.from.scheme().covariant(r#in.to.scheme()) {
|
||||
if !r#in.from.auth().covariant(r#in.to.auth()) {
|
||||
return self
|
||||
.ops
|
||||
.out(r#in.id, FileOutHardlink::Fail("Cannot hardlink cross filesystem".to_owned()));
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use parking_lot::RwLock;
|
|||
use tokio::sync::mpsc;
|
||||
use yazi_fs::FilesOp;
|
||||
use yazi_shared::url::{UrlBuf, UrlLike};
|
||||
use yazi_vfs::provider;
|
||||
use yazi_vfs::engine;
|
||||
|
||||
use super::SizeIn;
|
||||
use crate::{TaskOp, TaskOps, size::SizeOut};
|
||||
|
|
@ -25,7 +25,7 @@ impl Size {
|
|||
}
|
||||
|
||||
pub(crate) async fn size(&self, task: SizeIn) -> Result<(), SizeOut> {
|
||||
let length = provider::calculate(&task.target).await.unwrap_or(0);
|
||||
let length = engine::calculate(&task.target).await.unwrap_or(0);
|
||||
task.throttle.done((task.target, length), |buf| {
|
||||
{
|
||||
let mut loading = self.sizing.write();
|
||||
|
|
|
|||
65
yazi-shared/src/auth/auth.rs
Normal file
65
yazi-shared/src/auth/auth.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
use std::{fmt, sync::Arc};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use percent_encoding::percent_decode_str;
|
||||
use serde::Deserialize;
|
||||
use yazi_shim::{SStr, cell::RoCell};
|
||||
|
||||
use crate::auth::{AuthInventory, AuthKind, Encode, Scheme};
|
||||
|
||||
pub(super) static DEFAULT_ARC: RoCell<Arc<Auth>> = RoCell::new();
|
||||
|
||||
#[derive(Debug, Deserialize, Eq, Hash, PartialEq)]
|
||||
pub struct Auth {
|
||||
#[serde(default)]
|
||||
pub kind: AuthKind,
|
||||
pub scheme: Scheme,
|
||||
#[serde(default)]
|
||||
pub domain: SStr,
|
||||
}
|
||||
|
||||
impl Default for Auth {
|
||||
fn default() -> Self { Self::DEFAULT }
|
||||
}
|
||||
|
||||
impl fmt::Display for Auth {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { Encode(self, false).fmt(f) }
|
||||
}
|
||||
|
||||
impl Auth {
|
||||
pub const DEFAULT: Self =
|
||||
Self { kind: AuthKind::Regular, scheme: Scheme::Regular, domain: SStr::Borrowed("") };
|
||||
|
||||
pub fn default_arc() -> Arc<Self> { DEFAULT_ARC.clone() }
|
||||
|
||||
pub fn new(kind: AuthKind, scheme: Scheme, domain: impl Into<SStr>) -> Arc<Self> {
|
||||
Arc::new(Self { kind, scheme, domain: domain.into() })
|
||||
}
|
||||
|
||||
pub fn get(scheme: &Scheme, domain: &str) -> Option<Arc<Self>> {
|
||||
match scheme {
|
||||
Scheme::Regular => Some(Self::default_arc()),
|
||||
Scheme::Search => Some(Self::search(domain)),
|
||||
_ => inventory::iter::<AuthInventory>().find_map(|entry| (entry.get)(scheme, domain)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn search(query: impl Into<String>) -> Arc<Self> {
|
||||
Self::new(AuthKind::Search, Scheme::Search, query.into())
|
||||
}
|
||||
|
||||
pub fn covariant(&self, other: &Self) -> bool {
|
||||
!self.kind.is_virtual() && !other.kind.is_virtual() || self == other
|
||||
}
|
||||
|
||||
pub fn parse_cache(cache: &str) -> Result<Arc<Self>> {
|
||||
let (kind, rest) = cache.split_once('_').ok_or_else(|| anyhow!("invalid cache: {cache}"))?;
|
||||
let (scheme, domain) = rest.split_once('_').ok_or_else(|| anyhow!("invalid cache: {cache}"))?;
|
||||
|
||||
Ok(Arc::new(Self {
|
||||
kind: kind.parse()?,
|
||||
scheme: scheme.parse()?,
|
||||
domain: percent_decode_str(domain).decode_utf8()?.into_owned().into(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
26
yazi-shared/src/auth/encode.rs
Normal file
26
yazi-shared/src/auth/encode.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use std::fmt;
|
||||
|
||||
use percent_encoding::{AsciiSet, CONTROLS, PercentEncode, percent_encode};
|
||||
|
||||
use super::Auth;
|
||||
|
||||
pub struct Encode<'a>(pub &'a Auth, pub bool);
|
||||
|
||||
impl Encode<'_> {
|
||||
pub fn domain(s: &str) -> PercentEncode<'_> {
|
||||
const SET: &AsciiSet = &CONTROLS.add(b'/').add(b':');
|
||||
percent_encode(s.as_bytes(), SET)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Encode<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}{}://{}",
|
||||
self.0.scheme,
|
||||
if self.1 { "~" } else { "" },
|
||||
Self::domain(&self.0.domain)
|
||||
)
|
||||
}
|
||||
}
|
||||
9
yazi-shared/src/auth/inventory.rs
Normal file
9
yazi-shared/src/auth/inventory.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::auth::{Auth, Scheme};
|
||||
|
||||
pub struct AuthInventory {
|
||||
pub get: fn(&Scheme, &str) -> Option<Arc<Auth>>,
|
||||
}
|
||||
|
||||
inventory::collect!(AuthInventory);
|
||||
42
yazi-shared/src/auth/kind.rs
Normal file
42
yazi-shared/src/auth/kind.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use serde::Deserialize;
|
||||
use strum::{EnumString, IntoStaticStr};
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, Default, Deserialize, EnumString, Eq, Hash, IntoStaticStr, PartialEq,
|
||||
)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum AuthKind {
|
||||
#[default]
|
||||
Regular,
|
||||
Search,
|
||||
Mount,
|
||||
Scope,
|
||||
Sftp,
|
||||
}
|
||||
|
||||
impl AuthKind {
|
||||
#[inline]
|
||||
pub fn is_local(self) -> bool {
|
||||
match self {
|
||||
Self::Regular | Self::Search => true,
|
||||
Self::Mount | Self::Scope | Self::Sftp => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_remote(self) -> bool {
|
||||
match self {
|
||||
Self::Regular | Self::Search | Self::Mount => false,
|
||||
Self::Scope | Self::Sftp => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_virtual(self) -> bool {
|
||||
match self {
|
||||
Self::Regular | Self::Search => false,
|
||||
Self::Mount | Self::Scope | Self::Sftp => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
3
yazi-shared/src/auth/mod.rs
Normal file
3
yazi-shared/src/auth/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
yazi_macro::mod_flat!(auth encode inventory kind scheme);
|
||||
|
||||
pub(super) fn init() { DEFAULT_ARC.with(Default::default); }
|
||||
63
yazi-shared/src/auth/scheme.rs
Normal file
63
yazi-shared/src/auth/scheme.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
use std::{fmt, str::FromStr};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use serde_with::DeserializeFromStr;
|
||||
|
||||
use crate::{BytesExt, pool::{InternStr, Symbol}};
|
||||
|
||||
#[derive(Clone, Debug, DeserializeFromStr, Eq, Hash, PartialEq)]
|
||||
pub enum Scheme {
|
||||
Regular,
|
||||
Search,
|
||||
Sftp,
|
||||
Custom(Symbol<str>),
|
||||
}
|
||||
|
||||
impl AsRef<str> for Scheme {
|
||||
fn as_ref(&self) -> &str { self.as_str() }
|
||||
}
|
||||
|
||||
impl PartialEq<str> for Scheme {
|
||||
fn eq(&self, other: &str) -> bool { self.as_str() == other }
|
||||
}
|
||||
|
||||
impl PartialEq<&str> for Scheme {
|
||||
fn eq(&self, other: &&str) -> bool { self == *other }
|
||||
}
|
||||
|
||||
impl PartialEq<&String> for Scheme {
|
||||
fn eq(&self, other: &&String) -> bool { self == other.as_str() }
|
||||
}
|
||||
|
||||
impl PartialEq<&Self> for Scheme {
|
||||
fn eq(&self, other: &&Self) -> bool { self == *other }
|
||||
}
|
||||
|
||||
impl fmt::Display for Scheme {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.as_str().fmt(f) }
|
||||
}
|
||||
|
||||
impl FromStr for Scheme {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
Ok(match s {
|
||||
"regular" => Self::Regular,
|
||||
"search" => Self::Search,
|
||||
"sftp" => Self::Sftp,
|
||||
_ if !s.is_empty() && s.as_bytes().kebab_cased() => Self::Custom(s.intern()),
|
||||
_ => bail!("scheme must be kebab-case and non-empty, got: {s}"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Scheme {
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Self::Regular => "regular",
|
||||
Self::Search => "search",
|
||||
Self::Sftp => "sftp",
|
||||
Self::Custom(s) => s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,10 +6,12 @@ use crate::{BytesExt, SnakeCasedString};
|
|||
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct KebabCasedString(String);
|
||||
pub struct KebabCasedKey(String);
|
||||
|
||||
impl KebabCasedString {
|
||||
pub fn new(s: String) -> Option<Self> { s.as_bytes().kebab_cased().then_some(Self(s)) }
|
||||
impl KebabCasedKey {
|
||||
pub fn new(s: String) -> Option<Self> {
|
||||
(!s.is_empty() && s.len() < 20 && s.as_bytes().kebab_cased()).then_some(Self(s))
|
||||
}
|
||||
|
||||
pub fn into_snake_cased(self) -> SnakeCasedString {
|
||||
let mut b = self.0.into_bytes();
|
||||
|
|
@ -22,50 +24,52 @@ impl KebabCasedString {
|
|||
}
|
||||
}
|
||||
|
||||
impl Deref for KebabCasedString {
|
||||
impl Deref for KebabCasedKey {
|
||||
type Target = str;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl Borrow<str> for KebabCasedString {
|
||||
impl Borrow<str> for KebabCasedKey {
|
||||
#[inline]
|
||||
fn borrow(&self) -> &str { &self.0 }
|
||||
}
|
||||
|
||||
impl Borrow<String> for KebabCasedString {
|
||||
impl Borrow<String> for KebabCasedKey {
|
||||
#[inline]
|
||||
fn borrow(&self) -> &String { &self.0 }
|
||||
}
|
||||
|
||||
impl AsRef<str> for KebabCasedString {
|
||||
impl AsRef<str> for KebabCasedKey {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &str { &self.0 }
|
||||
}
|
||||
|
||||
impl AsRef<OsStr> for KebabCasedString {
|
||||
impl AsRef<OsStr> for KebabCasedKey {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &OsStr { self.0.as_ref() }
|
||||
}
|
||||
|
||||
impl Display for KebabCasedString {
|
||||
impl Display for KebabCasedKey {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { Display::fmt(&self.0, f) }
|
||||
}
|
||||
|
||||
impl From<KebabCasedString> for String {
|
||||
impl From<KebabCasedKey> for String {
|
||||
#[inline]
|
||||
fn from(value: KebabCasedString) -> Self { value.0 }
|
||||
fn from(value: KebabCasedKey) -> Self { value.0 }
|
||||
}
|
||||
|
||||
impl From<KebabCasedString> for Cow<'_, str> {
|
||||
impl From<KebabCasedKey> for Cow<'_, str> {
|
||||
#[inline]
|
||||
fn from(value: KebabCasedString) -> Self { Cow::Owned(value.0) }
|
||||
fn from(value: KebabCasedKey) -> Self { Cow::Owned(value.0) }
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for KebabCasedString {
|
||||
impl<'de> Deserialize<'de> for KebabCasedKey {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let value = String::deserialize(deserializer)?;
|
||||
Self::new(value).ok_or_else(|| serde::de::Error::custom("must be a kebab-cased string"))
|
||||
Self::new(value).ok_or_else(|| {
|
||||
serde::de::Error::custom("must be a non-empty kebab-cased key shorter than 20 characters")
|
||||
})
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue