diff --git a/CHANGELOG.md b/CHANGELOG.md index 15e56815..deded357 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 `` to `` ([#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 diff --git a/Cargo.lock b/Cargo.lock index dba80645..5ba1d704 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/yazi-actor/src/app/quit.rs b/yazi-actor/src/app/quit.rs index 2c81556c..429e95c5 100644 --- a/yazi-actor/src/app/quit.rs +++ b/yazi-actor/src/app/quit.rs @@ -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; diff --git a/yazi-actor/src/cmp/trigger.rs b/yazi-actor/src/cmp/trigger.rs index bb4a631e..f53c8cba 100644 --- a/yazi-actor/src/cmp/trigger.rs +++ b/yazi-actor/src/cmp/trigger.rs @@ -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::().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::().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"); diff --git a/yazi-actor/src/mgr/bulk_create.rs b/yazi-actor/src/mgr/bulk_create.rs index ba294eb3..ea054392 100644 --- a/yazi-actor/src/mgr/bulk_create.rs +++ b/yazi-actor/src/mgr/bulk_create.rs @@ -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")) }; diff --git a/yazi-actor/src/mgr/bulk_rename.rs b/yazi-actor/src/mgr/bulk_rename.rs index 4f42978a..9e2865f5 100644 --- a/yazi-actor/src/mgr/bulk_rename.rs +++ b/yazi-actor/src/mgr/bulk_rename.rs @@ -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); diff --git a/yazi-actor/src/mgr/cd.rs b/yazi-actor/src/mgr/cd.rs index 11da268d..ab852086 100644 --- a/yazi-actor/src/mgr/cd.rs +++ b/yazi-actor/src/mgr/cd.rs @@ -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 }; diff --git a/yazi-actor/src/mgr/create.rs b/yazi-actor/src/mgr/create.rs index 9128720b..0613a212 100644 --- a/yazi-actor/src/mgr/create.rs +++ b/yazi-actor/src/mgr/create.rs @@ -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?; diff --git a/yazi-actor/src/mgr/displace.rs b/yazi-actor/src/mgr/displace.rs index c6339beb..97189b38 100644 --- a/yazi-actor/src/mgr/displace.rs +++ b/yazi-actor/src/mgr/displace.rs @@ -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, }); }); diff --git a/yazi-actor/src/mgr/download.rs b/yazi-actor/src/mgr/download.rs index 61b02d8a..1a6da9ee 100644 --- a/yazi-actor/src/mgr/download.rs +++ b/yazi-actor/src/mgr/download.rs @@ -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(); diff --git a/yazi-actor/src/mgr/refresh.rs b/yazi-actor/src/mgr/refresh.rs index 5d4ae16f..c6692aaa 100644 --- a/yazi-actor/src/mgr/refresh.rs +++ b/yazi-actor/src/mgr/refresh.rs @@ -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)); diff --git a/yazi-actor/src/mgr/rename.rs b/yazi-actor/src/mgr/rename.rs index b654e33c..423640de 100644 --- a/yazi-actor/src/mgr/rename.rs +++ b/yazi-actor/src/mgr/rename.rs @@ -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, ) } diff --git a/yazi-actor/src/mgr/stash.rs b/yazi-actor/src/mgr/stash.rs index 89b68a7f..cba5fb97 100644 --- a/yazi-actor/src/mgr/stash.rs +++ b/yazi-actor/src/mgr/stash.rs @@ -13,7 +13,7 @@ impl Actor for Stash { const NAME: &str = "stash"; fn act(cx: &mut Ctx, form: Self::Form) -> Result { - 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()); } diff --git a/yazi-adapter/src/image.rs b/yazi-adapter/src/image.rs index 5afeff56..a917f8ff 100644 --- a/yazi-adapter/src/image.rs +++ b/yazi-adapter/src/image.rs @@ -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; diff --git a/yazi-binding/src/chan.rs b/yazi-binding/src/chan.rs index 651f0bc2..91116377 100644 --- a/yazi-binding/src/chan.rs +++ b/yazi-binding/src/chan.rs @@ -4,13 +4,24 @@ use yazi_codegen::FromLuaOwned; use crate::Error; #[derive(FromLuaOwned)] -pub struct MpscTx(pub tokio::sync::mpsc::Sender); +pub struct MpscTx { + tx: tokio::sync::mpsc::Sender, + f: fn(T) -> U, +} pub struct MpscRx(pub tokio::sync::mpsc::Receiver); -impl UserData for MpscTx { +impl MpscTx { + pub fn new(tx: tokio::sync::mpsc::Sender) -> Self { Self { tx, f: |v| v } } +} + +impl MpscTx { + pub fn map(tx: tokio::sync::mpsc::Sender, f: fn(T) -> U) -> Self { Self { tx, f } } +} + +impl UserData for MpscTx { fn add_methods>(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), } diff --git a/yazi-binding/src/error.rs b/yazi-binding/src/error.rs index b13103f6..e20e43f8 100644 --- a/yazi-binding/src/error.rs +++ b/yazi-binding/src/error.rs @@ -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 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)))?; diff --git a/yazi-boot/src/boot.rs b/yazi-boot/src/boot.rs index 867644e6..a3def6ae 100644 --- a/yazi-boot/src/boot.rs +++ b/yazi-boot/src/boot.rs @@ -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()) diff --git a/yazi-cli/src/package/delete.rs b/yazi-cli/src/package/delete.rs index 9f548c1f..113484ca 100644 --- a/yazi-cli/src/package/delete.rs +++ b/yazi-cli/src/package/delete.rs @@ -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; diff --git a/yazi-cli/src/package/deploy.rs b/yazi-cli/src/package/deploy.rs index ffbe5f5e..c1d428cc 100644 --- a/yazi-cli/src/package/deploy.rs +++ b/yazi-cli/src/package/deploy.rs @@ -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; diff --git a/yazi-cli/src/package/hash.rs b/yazi-cli/src/package/hash.rs index 39a4e62b..378dbc28 100644 --- a/yazi-cli/src/package/hash.rs +++ b/yazi-cli/src/package/hash.rs @@ -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; diff --git a/yazi-cli/src/package/package.rs b/yazi-cli/src/package/package.rs index 40deb14b..159f00d3 100644 --- a/yazi-cli/src/package/package.rs +++ b/yazi-cli/src/package/package.rs @@ -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; diff --git a/yazi-cli/src/shared/shared.rs b/yazi-cli/src/shared/shared.rs index 387b5ccb..d276bc33 100644 --- a/yazi-cli/src/shared/shared.rs +++ b/yazi-cli/src/shared/shared.rs @@ -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(); diff --git a/yazi-codegen/src/lib.rs b/yazi-codegen/src/lib.rs index d3b247b3..0c10562c 100644 --- a/yazi-codegen/src/lib.rs +++ b/yazi-codegen/src/lib.rs @@ -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 { 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 { - while let Some(key) = map.next_key::()? { + while let Some(key) = map.next_key::()? { match key.as_ref() { #(#normal_arms,)* #flatten_arm diff --git a/yazi-config/src/lib.rs b/yazi-config/src/lib.rs index 7468d153..6a42d274 100644 --- a/yazi-config/src/lib.rs +++ b/yazi-config/src/lib.rs @@ -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 = RoCell::new(); pub static KEYMAP: RoCell = RoCell::new(); pub static THEME: RoCell = RoCell::new(); +pub static VFS: RoCell = RoCell::new(); pub static LAYOUT: SyncCell = 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(()) } diff --git a/yazi-config/src/pattern.rs b/yazi-config/src/pattern.rs index 37fd6dc3..a632b0a8 100644 --- a/yazi-config/src/pattern.rs +++ b/yazi-config/src/pattern.rs @@ -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"#)); diff --git a/yazi-config/src/popup/input.rs b/yazi-config/src/popup/input.rs index 8abebacf..bb4d1659 100644 --- a/yazi-config/src/popup/input.rs +++ b/yazi-config/src/popup/input.rs @@ -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, diff --git a/yazi-config/src/preset.rs b/yazi-config/src/preset.rs index 06cc992f..f4d7bdb0 100644 --- a/yazi-config/src/preset.rs +++ b/yazi-config/src/preset.rs @@ -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 { + toml::from_str(&yazi_macro::config_preset!("vfs")) + } + pub(super) fn theme(light: bool) -> Result { toml::from_str(&if light { yazi_macro::theme_preset!("light") diff --git a/yazi-config/src/tests.rs b/yazi-config/src/tests.rs new file mode 100644 index 00000000..4f955f41 --- /dev/null +++ b/yazi-config/src/tests.rs @@ -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())); +} diff --git a/yazi-config/src/theme/custom.rs b/yazi-config/src/theme/custom.rs index c6e3fd29..da7c734f 100644 --- a/yazi-config/src/theme/custom.rs +++ b/yazi-config/src/theme/custom.rs @@ -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>(self, mut map: M) -> Result { let mut sections = HashMap::with_capacity(map.size_hint().unwrap_or(0)); - while let Some(key) = map.next_key::()? { + while let Some(key) = map.next_key::()? { let section = map.next_value::()?; if !section.load().is_empty() { sections.insert(key.into_snake_cased(), section); @@ -64,7 +64,7 @@ impl DeserializeOverWith for Custom { fn visit_map>(self, mut map: M) -> Result { let mut sections = self.0.unwrap_unchecked(); - while let Some(key) = map.next_key::()? { + while let Some(key) = map.next_key::()? { let (key, new) = (key.into_snake_cased(), map.next_value::()?); match sections.entry(key) { hash_map::Entry::Occupied(mut oe) => { diff --git a/yazi-config/src/vfs/lua.rs b/yazi-config/src/vfs/lua.rs new file mode 100644 index 00000000..f94e2d05 --- /dev/null +++ b/yazi-config/src/vfs/lua.rs @@ -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, + pub run: Cmd, +} + +impl Deref for ServiceLua { + type Target = Auth; + + fn deref(&self) -> &Self::Target { &self.auth } +} diff --git a/yazi-config/src/vfs/mod.rs b/yazi-config/src/vfs/mod.rs new file mode 100644 index 00000000..4481f651 --- /dev/null +++ b/yazi-config/src/vfs/mod.rs @@ -0,0 +1 @@ +yazi_macro::mod_flat!(lua service services sftp vfs); diff --git a/yazi-config/src/vfs/service.rs b/yazi-config/src/vfs/service.rs new file mode 100644 index 00000000..9c5604fa --- /dev/null +++ b/yazi-config/src/vfs/service.rs @@ -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 { + 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 { + 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 { + 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 { + match self { + Self::Sftp(sftp) => &mut sftp.auth, + Self::Mount(lua) => &mut lua.auth, + Self::Scope(lua) => &mut lua.auth, + } + } +} diff --git a/yazi-config/src/vfs/services.rs b/yazi-config/src/vfs/services.rs new file mode 100644 index 00000000..46c1d1c8 --- /dev/null +++ b/yazi-config/src/vfs/services.rs @@ -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); + +impl Deref for Services { + type Target = HashMap; + + 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>(deserializer: D) -> Result { + 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 { + Ok(Self(self.0.deserialize_over_with(de)?)) + } +} + +impl DeserializeOverHook for Services { + fn deserialize_over_hook(mut self) -> Result { + 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) + } +} diff --git a/yazi-vfs/src/config/sftp.rs b/yazi-config/src/vfs/sftp.rs similarity index 80% rename from yazi-vfs/src/config/sftp.rs rename to yazi-config/src/vfs/sftp.rs index 81d67e88..6f3a5615 100644 --- a/yazi-vfs/src/config/sftp.rs +++ b/yazi-config/src/vfs/sftp.rs @@ -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, 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::new(AuthKind::Sftp, Scheme::Sftp, "") } + fn deserialize_path<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, diff --git a/yazi-config/src/vfs/vfs.rs b/yazi-config/src/vfs/vfs.rs new file mode 100644 index 00000000..7d936147 --- /dev/null +++ b/yazi-config/src/vfs/vfs.rs @@ -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

(domain: &str) -> io::Result

+ 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 { + 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 + } + }) + }, + } +} diff --git a/yazi-core/src/mgr/snap.rs b/yazi-core/src/mgr/snap.rs index f726bd56..fecb4de4 100644 --- a/yazi-core/src/mgr/snap.rs +++ b/yazi-core/src/mgr/snap.rs @@ -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) diff --git a/yazi-core/src/tab/preview.rs b/yazi-core/src/tab/preview.rs index a05ecff5..da4a2f0b 100644 --- a/yazi-core/src/tab/preview.rs +++ b/yazi-core/src/tab/preview.rs @@ -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, mime: Symbol, 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); } diff --git a/yazi-dds/src/state.rs b/yazi-dds/src/state.rs index c740921a..1488a16e 100644 --- a/yazi-dds/src/state.rs +++ b/yazi-dds/src/state.rs @@ -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::>(); diff --git a/yazi-dds/src/stream.rs b/yazi-dds/src/stream.rs index 5aba44bd..51eac132 100644 --- a/yazi-dds/src/stream.rs +++ b/yazi-dds/src/stream.rs @@ -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; diff --git a/yazi-fm/src/main.rs b/yazi-fm/src/main.rs index f6710142..9db4c7ab 100644 --- a/yazi-fm/src/main.rs +++ b/yazi-fm/src/main.rs @@ -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(); diff --git a/yazi-fs/src/cha/mode.rs b/yazi-fs/src/cha/mode.rs index fd2d2db7..0dd5a0b0 100644 --- a/yazi-fs/src/cha/mode.rs +++ b/yazi-fs/src/cha/mode.rs @@ -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 { self.bits().into_lua(lua) } +} diff --git a/yazi-fs/src/provider/attrs.rs b/yazi-fs/src/engine/attrs.rs similarity index 75% rename from yazi-fs/src/provider/attrs.rs rename to yazi-fs/src/engine/attrs.rs index 5e25f9b0..47fc9a19 100644 --- a/yazi-fs/src/provider/attrs.rs +++ b/yazi-fs/src/engine/attrs.rs @@ -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 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 { self.mtime?.duration_since(UNIX_EPOCH).ok() } } + +impl IntoLua for Attrs { + fn into_lua(self, lua: &Lua) -> mlua::Result { + 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) + } +} diff --git a/yazi-fs/src/engine/capabilities.rs b/yazi-fs/src/engine/capabilities.rs new file mode 100644 index 00000000..e6c2d2ee --- /dev/null +++ b/yazi-fs/src/engine/capabilities.rs @@ -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 { + 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")?, + }) + } +} diff --git a/yazi-fs/src/engine/demand.rs b/yazi-fs/src/engine/demand.rs new file mode 100644 index 00000000..7aa85be0 --- /dev/null +++ b/yazi-fs/src/engine/demand.rs @@ -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(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 { + 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) + } +} diff --git a/yazi-fs/src/provider/local/absolute.rs b/yazi-fs/src/engine/local/absolute.rs similarity index 86% rename from yazi-fs/src/provider/local/absolute.rs rename to yazi-fs/src/engine/local/absolute.rs index d22cceda..091c2d84 100644 --- a/yazi-fs/src/provider/local/absolute.rs +++ b/yazi-fs/src/engine/local/absolute.rs @@ -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> { 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?, }) } diff --git a/yazi-fs/src/provider/local/calculator.rs b/yazi-fs/src/engine/local/calculator.rs similarity index 100% rename from yazi-fs/src/provider/local/calculator.rs rename to yazi-fs/src/engine/local/calculator.rs diff --git a/yazi-fs/src/provider/local/casefold.rs b/yazi-fs/src/engine/local/casefold.rs similarity index 100% rename from yazi-fs/src/provider/local/casefold.rs rename to yazi-fs/src/engine/local/casefold.rs diff --git a/yazi-fs/src/provider/local/copier.rs b/yazi-fs/src/engine/local/copier.rs similarity index 97% rename from yazi-fs/src/provider/local/copier.rs rename to yazi-fs/src/engine/local/copier.rs index 528227c0..256138be 100644 --- a/yazi-fs/src/provider/local/copier.rs +++ b/yazi-fs/src/engine/local/copier.rs @@ -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 { #[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, diff --git a/yazi-fs/src/provider/local/gate.rs b/yazi-fs/src/engine/local/demand.rs similarity index 90% rename from yazi-fs/src/provider/local/gate.rs rename to yazi-fs/src/engine/local/demand.rs index 2136b2ec..f0cb61d3 100644 --- a/yazi-fs/src/provider/local/gate.rs +++ b/yazi-fs/src/engine/local/demand.rs @@ -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 { diff --git a/yazi-fs/src/provider/local/dir_entry.rs b/yazi-fs/src/engine/local/dir_entry.rs similarity index 95% rename from yazi-fs/src/provider/local/dir_entry.rs rename to yazi-fs/src/engine/local/dir_entry.rs index aa126a85..fb494640 100644 --- a/yazi-fs/src/provider/local/dir_entry.rs +++ b/yazi-fs/src/engine/local/dir_entry.rs @@ -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), diff --git a/yazi-fs/src/provider/local/identical.rs b/yazi-fs/src/engine/local/identical.rs similarity index 100% rename from yazi-fs/src/provider/local/identical.rs rename to yazi-fs/src/engine/local/identical.rs diff --git a/yazi-fs/src/provider/local/local.rs b/yazi-fs/src/engine/local/local.rs similarity index 74% rename from yazi-fs/src/provider/local/local.rs rename to yazi-fs/src/engine/local/local.rs index 6ec0557d..32e476d3 100644 --- a/yazi-fs/src/provider/local/local.rs +++ b/yazi-fs/src/engine/local/local.rs @@ -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 { + Ok(Capabilities { + symlink: true, + hard_link: true, + trash: true, + copy_progressive: true, + }) + } async fn casefold(&self) -> io::Result { 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(&self, to: P, attrs: A) -> io::Result>> + fn copy_progressive(&self, to: P, attrs: A) -> io::Result>> where P: AsPath, A: Into, { 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> { 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 { 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 = 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 = 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) + } } diff --git a/yazi-fs/src/provider/local/mod.rs b/yazi-fs/src/engine/local/mod.rs similarity index 62% rename from yazi-fs/src/provider/local/mod.rs rename to yazi-fs/src/engine/local/mod.rs index a7d3e2e3..d837d4c3 100644 --- a/yazi-fs/src/provider/local/mod.rs +++ b/yazi-fs/src/engine/local/mod.rs @@ -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); diff --git a/yazi-fs/src/provider/local/read_dir.rs b/yazi-fs/src/engine/local/read_dir.rs similarity index 94% rename from yazi-fs/src/provider/local/read_dir.rs rename to yazi-fs/src/engine/local/read_dir.rs index 78ee07c9..4a3332e8 100644 --- a/yazi-fs/src/provider/local/read_dir.rs +++ b/yazi-fs/src/engine/local/read_dir.rs @@ -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), diff --git a/yazi-fs/src/engine/mod.rs b/yazi-fs/src/engine/mod.rs new file mode 100644 index 00000000..15c19b54 --- /dev/null +++ b/yazi-fs/src/engine/mod.rs @@ -0,0 +1,3 @@ +yazi_macro::mod_pub!(local); + +yazi_macro::mod_flat!(attrs capabilities demand traits); diff --git a/yazi-fs/src/provider/traits.rs b/yazi-fs/src/engine/traits.rs similarity index 86% rename from yazi-fs/src/provider/traits.rs rename to yazi-fs/src/engine/traits.rs index 0c9bb0d6..c26ee590 100644 --- a/yazi-fs/src/provider/traits.rs +++ b/yazi-fs/src/engine/traits.rs @@ -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; + type Demand: FileBuilder; type ReadDir: DirReader + 'static; type UrlCow; - type Me<'a>: Provider; + type Me<'a>: Engine; fn absolute(&self) -> impl Future>; fn canonicalize(&self) -> impl Future>; - fn capabilities(&self) -> Capabilities; + fn capabilities(&self) -> impl Future>; fn casefold(&self) -> impl Future>; @@ -25,17 +25,13 @@ pub trait Provider: Sized { where P: AsPath; - fn copy_with_progress( - &self, - to: P, - attrs: A, - ) -> io::Result>> + fn copy_progressive(&self, to: P, attrs: A) -> io::Result>> where P: AsPath, A: Into; fn create(&self) -> impl Future> { - 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>; @@ -77,10 +73,10 @@ pub trait Provider: Sized { } fn create_new(&self) -> impl Future> { - 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

(&self, to: P) -> impl Future> where @@ -91,7 +87,7 @@ pub trait Provider: Sized { fn new<'a>(url: Url<'a>) -> impl Future>>; fn open(&self) -> impl Future> { - 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>; @@ -103,7 +99,7 @@ pub trait Provider: Sized { fn remove_dir_all(&self) -> impl Future> { async fn remove_dir_all_impl

(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>; + fn set_attrs(&self, attrs: Attrs) -> impl Future>; fn symlink(&self, original: S, _is_dir: F) -> impl Future> where diff --git a/yazi-fs/src/file/files.rs b/yazi-fs/src/file/files.rs index 62434be8..9e48b615 100644 --- a/yazi-fs/src/file/files.rs +++ b/yazi-fs/src/file/files.rs @@ -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 for Vec { impl From for Vec { 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 { + Vec::::from_lua(value, lua).map(Self) + } +} diff --git a/yazi-fs/src/fns.rs b/yazi-fs/src/fns.rs index 4772769f..61239627 100644 --- a/yazi-fs/src/fns.rs +++ b/yazi-fs/src/fns.rs @@ -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<_> = diff --git a/yazi-fs/src/lib.rs b/yazi-fs/src/lib.rs index d6532551..666cd11b 100644 --- a/yazi-fs/src/lib.rs +++ b/yazi-fs/src/lib.rs @@ -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()); diff --git a/yazi-fs/src/path/clean.rs b/yazi-fs/src/path/clean.rs index 8fa4085a..2fef639f 100644 --- a/yazi-fs/src/path/clean.rs +++ b/yazi-fs/src/path/clean.rs @@ -8,8 +8,8 @@ pub fn clean_url<'a>(url: impl Into>) -> 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 { diff --git a/yazi-fs/src/path/expand.rs b/yazi-fs/src/path/expand.rs index b5a49027..18d95794 100644 --- a/yazi-fs/src/path/expand.rs +++ b/yazi-fs/src/path/expand.rs @@ -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::::with(path.into_os().unwrap(), uri, urn).unwrap(), ), - Url::Search { domain, .. } => UrlBuf::Search { - loc: LocBuf::::with(path.into_os().unwrap(), uri, urn).unwrap(), - domain: domain.intern(), + Url::Search { auth, .. } => UrlBuf::Search { + loc: LocBuf::::with(path.into_os().unwrap(), uri, urn).unwrap(), + auth: auth.clone(), }, - Url::Archive { domain, .. } => UrlBuf::Archive { - loc: LocBuf::::with(path.into_os().unwrap(), uri, urn).unwrap(), - domain: domain.intern(), + Url::Mount { auth, .. } => UrlBuf::Mount { + loc: LocBuf::::with(path.into_os().unwrap(), uri, urn).unwrap(), + auth: auth.clone(), }, - Url::Sftp { domain, .. } => UrlBuf::Sftp { - loc: LocBuf::::with(path.into_unix().unwrap(), uri, urn).unwrap(), - domain: domain.intern(), + Url::Scope { auth, .. } => UrlBuf::Scope { + loc: LocBuf::::with(path.into_unix().unwrap(), uri, urn).unwrap(), + auth: auth.clone(), + }, + Url::Sftp { auth, .. } => UrlBuf::Sftp { + loc: LocBuf::::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 { diff --git a/yazi-fs/src/path/normalize.rs b/yazi-fs/src/path/normalize.rs index 13cb152c..a76ed93f 100644 --- a/yazi-fs/src/path/normalize.rs +++ b/yazi-fs/src/path/normalize.rs @@ -5,7 +5,7 @@ use yazi_shared::url::UrlBuf; use crate::path::{clean_url, expand_url}; pub fn sanitize_path(path: PathBuf) -> Option { - 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() diff --git a/yazi-fs/src/provider/capabilities.rs b/yazi-fs/src/provider/capabilities.rs deleted file mode 100644 index 323bbf95..00000000 --- a/yazi-fs/src/provider/capabilities.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[derive(Clone, Copy, Debug)] -pub struct Capabilities { - pub symlink: bool, -} diff --git a/yazi-fs/src/provider/mod.rs b/yazi-fs/src/provider/mod.rs deleted file mode 100644 index 1a96a542..00000000 --- a/yazi-fs/src/provider/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -yazi_macro::mod_pub!(local); - -yazi_macro::mod_flat!(attrs capabilities traits); diff --git a/yazi-fs/src/scheme.rs b/yazi-fs/src/scheme.rs deleted file mode 100644 index 415a0252..00000000 --- a/yazi-fs/src/scheme.rs +++ /dev/null @@ -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; -} - -impl FsScheme for SchemeRef<'_> { - fn cache(&self) -> Option { - 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 { self.as_scheme().cache() } -} - -// --- Inject -inventory::submit! { - SchemeInventory { - register: |registry| { - registry.add_cached_field("cache", |_, me| Ok(me.cache().map(PathBufDyn::from))); - } - } -} diff --git a/yazi-fs/src/spec.rs b/yazi-fs/src/spec.rs new file mode 100644 index 00000000..1af5029a --- /dev/null +++ b/yazi-fs/src/spec.rs @@ -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; +} + +impl FsSpec for Auth { + fn cache(&self) -> Option { + 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))); + } + } +} diff --git a/yazi-fs/src/url.rs b/yazi-fs/src/url.rs index 80d17a9a..32f14201 100644 --- a/yazi-fs/src/url.rs +++ b/yazi-fs/src/url.rs @@ -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; @@ -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 { - 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()) + }); } } } diff --git a/yazi-parser/src/mgr/cd.rs b/yazi-parser/src/mgr/cd.rs index 9b310531..484d2bb2 100644 --- a/yazi-parser/src/mgr/cd.rs +++ b/yazi-parser/src/mgr/cd.rs @@ -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 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(); diff --git a/yazi-parser/src/mgr/reveal.rs b/yazi-parser/src/mgr/reveal.rs index 133f9896..93f8c003 100644 --- a/yazi-parser/src/mgr/reveal.rs +++ b/yazi-parser/src/mgr/reveal.rs @@ -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 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(); diff --git a/yazi-parser/src/mgr/tab_create.rs b/yazi-parser/src/mgr/tab_create.rs index 20c7090f..1956266e 100644 --- a/yazi-parser/src/mgr/tab_create.rs +++ b/yazi-parser/src/mgr/tab_create.rs @@ -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 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(); diff --git a/yazi-plugin/preset/components/header.lua b/yazi-plugin/preset/components/header.lua index 494d06a4..8ae0018b 100644 --- a/yazi-plugin/preset/components/header.lua +++ b/yazi-plugin/preset/components/header.lua @@ -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) diff --git a/yazi-plugin/preset/plugins/fzf.lua b/yazi-plugin/preset/plugins/fzf.lua index 8d5552e3..6cd4fb8e 100644 --- a/yazi-plugin/preset/plugins/fzf.lua +++ b/yazi-plugin/preset/plugins/fzf.lua @@ -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 diff --git a/yazi-plugin/preset/plugins/mime-dir.lua b/yazi-plugin/preset/plugins/mime-dir.lua index 38abab94..6bb204b4 100644 --- a/yazi-plugin/preset/plugins/mime-dir.lua +++ b/yazi-plugin/preset/plugins/mime-dir.lua @@ -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" diff --git a/yazi-plugin/preset/plugins/mime-remote.lua b/yazi-plugin/preset/plugins/mime-remote.lua index 4654940f..2bd474d8 100644 --- a/yazi-plugin/preset/plugins/mime-remote.lua +++ b/yazi-plugin/preset/plugins/mime-remote.lua @@ -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 diff --git a/yazi-plugin/src/fs/calculator.rs b/yazi-plugin/src/fs/calculator.rs index 9c9a5b83..294c01ab 100644 --- a/yazi-plugin/src/fs/calculator.rs +++ b/yazi-plugin/src/fs/calculator.rs @@ -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 { diff --git a/yazi-plugin/src/fs/fs.rs b/yazi-plugin/src/fs/fs.rs index 404b6b2b..5f6ca642 100644 --- a/yazi-plugin/src/fs/fs.rs +++ b/yazi-plugin/src/fs/fs.rs @@ -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 { } fn access(lua: &Lua) -> mlua::Result { - 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 { 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 { fn cha(lua: &Lua) -> mlua::Result { lua.create_async_function(|lua, (url, follow): (UrlRef, Option)| 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 { fn copy(lua: &Lua) -> mlua::Result { 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 { fn create(lua: &Lua) -> mlua::Result { 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 { let limit = options.raw_get("limit").unwrap_or(usize::MAX); let resolve = options.raw_get::("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 { fn remove(lua: &Lua) -> mlua::Result { 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 { fn rename(lua: &Lua) -> mlua::Result { 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 { fn write(lua: &Lua) -> mlua::Result { 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), } diff --git a/yazi-plugin/src/ui/ui.rs b/yazi-plugin/src/ui/ui.rs index c8a8c010..dc81a2f2 100644 --- a/yazi-plugin/src/ui/ui.rs +++ b/yazi-plugin/src/ui/ui.rs @@ -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 { fn get(lua: &Lua, key: &[u8]) -> mlua::Result { @@ -16,7 +19,7 @@ pub fn compose() -> Composer { 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 { Composer::new(get, set) } + +fn publish_input(event: InputEvent) { + err!(Pubsub::pub_after_input((&event).into_str(), event.value())); +} diff --git a/yazi-plugin/src/utils/app.rs b/yazi-plugin/src/utils/app.rs index d3d0a431..926e606f 100644 --- a/yazi-plugin/src/utils/app.rs +++ b/yazi-plugin/src/utils/app.rs @@ -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; diff --git a/yazi-plugin/src/utils/sync.rs b/yazi-plugin/src/utils/sync.rs index 0de4dfa1..ea155bb4 100644 --- a/yazi-plugin/src/utils/sync.rs +++ b/yazi-plugin/src/utils/sync.rs @@ -95,7 +95,7 @@ impl Utils { } (b"mpsc", Some(buffer)) => { let (tx, rx) = tokio::sync::mpsc::channel::(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::(); diff --git a/yazi-runner/Cargo.toml b/yazi-runner/Cargo.toml index 29506f35..951ad92f 100644 --- a/yazi-runner/Cargo.toml +++ b/yazi-runner/Cargo.toml @@ -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 } diff --git a/yazi-runner/src/lib.rs b/yazi-runner/src/lib.rs index 82e8db1d..9251a61e 100644 --- a/yazi-runner/src/lib.rs +++ b/yazi-runner/src/lib.rs @@ -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 = yazi_shim::cell::RoCell::ne pub fn init(setter: fn(&mlua::Lua) -> mlua::Result<()>) { crate::loader::init(); - RUNNER.init(Runner { setter }); } diff --git a/yazi-runner/src/loader/loader.rs b/yazi-runner/src/loader/loader.rs index 6d118786..e4593b46 100644 --- a/yazi-runner/src/loader/loader.rs +++ b/yazi-runner/src/loader/loader.rs @@ -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; diff --git a/yazi-runner/src/provider/job.rs b/yazi-runner/src/provider/job.rs new file mode 100644 index 00000000..79abdc58 --- /dev/null +++ b/yazi-runner/src/provider/job.rs @@ -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, + url: UrlBuf, + is_dir: bool, + }, + Trash { + url: UrlBuf, + }, + Read { + url: UrlBuf, + offset: u64, + len: usize, + }, + Write { + url: UrlBuf, + offset: u64, + bytes: Vec, + }, + Copy { + from: UrlBuf, + to: PathBufDyn, + attrs: Attrs, + }, + CopyProgressive { + from: UrlBuf, + to: PathBufDyn, + attrs: Attrs, + tx: MpscTx>, + }, + SetLen { + url: UrlBuf, + size: u64, + }, + SetAttrs { + url: UrlBuf, + attrs: Attrs, + }, +} + +impl IntoLua for ProviderJob { + fn into_lua(self, lua: &Lua) -> mlua::Result { + 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) + } +} diff --git a/yazi-runner/src/provider/mod.rs b/yazi-runner/src/provider/mod.rs new file mode 100644 index 00000000..70b63fea --- /dev/null +++ b/yazi-runner/src/provider/mod.rs @@ -0,0 +1 @@ +yazi_macro::mod_flat!(job result provider); diff --git a/yazi-runner/src/provider/provider.rs b/yazi-runner/src/provider/provider.rs new file mode 100644 index 00000000..71642884 --- /dev/null +++ b/yazi-runner/src/provider/provider.rs @@ -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(&'static self, run: &'static Cmd, job: ProviderJob) -> ProvideResult + 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(&'static self, run: &'static Cmd, job: ProviderJob) -> ProvideResult + 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(), + } + } +} diff --git a/yazi-runner/src/provider/result.rs b/yazi-runner/src/provider/result.rs new file mode 100644 index 00000000..f8ef92e6 --- /dev/null +++ b/yazi-runner/src/provider/result.rs @@ -0,0 +1,43 @@ +use mlua::{FromLua, FromLuaMulti, Lua, MultiValue, Value}; + +pub struct ProvideResult(pub Result); + +impl From for ProvideResult { + fn from(value: yazi_binding::Error) -> Self { Self(Err(value)) } +} + +impl From for ProvideResult { + fn from(value: mlua::Error) -> Self { yazi_binding::Error::custom(value.to_string()).into() } +} + +impl From for ProvideResult { + fn from(value: tokio::task::JoinError) -> Self { + yazi_binding::Error::custom(value.to_string()).into() + } +} + +impl FromLuaMulti for ProvideResult { + fn from_lua_multi(mut values: MultiValue, lua: &Lua) -> mlua::Result { + 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 { + 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")) + } + } +} diff --git a/yazi-scheduler/src/file/file.rs b/yazi-scheduler/src/file/file.rs index 500dce13..5141f54c 100644 --- a/yazi-scheduler/src/file/file.rs +++ b/yazi-scheduler/src/file/file.rs @@ -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::( 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::( 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::( 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 }) } diff --git a/yazi-scheduler/src/file/in.rs b/yazi-scheduler/src/file/in.rs index 2a92ef16..17cd8e47 100644 --- a/yazi-scheduler/src/file/in.rs +++ b/yazi-scheduler/src/file/in.rs @@ -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, diff --git a/yazi-scheduler/src/file/transaction.rs b/yazi-scheduler/src/file/transaction.rs index f812af7b..ddf7db60 100644 --- a/yazi-scheduler/src/file/transaction.rs +++ b/yazi-scheduler/src/file/transaction.rs @@ -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(()) diff --git a/yazi-scheduler/src/file/traverse.rs b/yazi-scheduler/src/file/traverse.rs index 4c47c7ad..866f9571 100644 --- a/yazi-scheduler/src/file/traverse.rs +++ b/yazi-scheduler/src/file/traverse.rs @@ -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); diff --git a/yazi-scheduler/src/hook/hook.rs b/yazi-scheduler/src/hook/hook.rs index b4a23837..3f82a4c6 100644 --- a/yazi-scheduler/src/hook/hook.rs +++ b/yazi-scheduler/src/hook/hook.rs @@ -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); diff --git a/yazi-scheduler/src/scheduler.rs b/yazi-scheduler/src/scheduler.rs index b8dae4c3..67e5ac9b 100644 --- a/yazi-scheduler/src/scheduler.rs +++ b/yazi-scheduler/src/scheduler.rs @@ -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())); diff --git a/yazi-scheduler/src/size/size.rs b/yazi-scheduler/src/size/size.rs index bed30a91..bcd01646 100644 --- a/yazi-scheduler/src/size/size.rs +++ b/yazi-scheduler/src/size/size.rs @@ -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(); diff --git a/yazi-shared/src/auth/auth.rs b/yazi-shared/src/auth/auth.rs new file mode 100644 index 00000000..3a6efe2f --- /dev/null +++ b/yazi-shared/src/auth/auth.rs @@ -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> = 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 { DEFAULT_ARC.clone() } + + pub fn new(kind: AuthKind, scheme: Scheme, domain: impl Into) -> Arc { + Arc::new(Self { kind, scheme, domain: domain.into() }) + } + + pub fn get(scheme: &Scheme, domain: &str) -> Option> { + match scheme { + Scheme::Regular => Some(Self::default_arc()), + Scheme::Search => Some(Self::search(domain)), + _ => inventory::iter::().find_map(|entry| (entry.get)(scheme, domain)), + } + } + + pub fn search(query: impl Into) -> Arc { + 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> { + 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(), + })) + } +} diff --git a/yazi-shared/src/auth/encode.rs b/yazi-shared/src/auth/encode.rs new file mode 100644 index 00000000..043da080 --- /dev/null +++ b/yazi-shared/src/auth/encode.rs @@ -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) + ) + } +} diff --git a/yazi-shared/src/auth/inventory.rs b/yazi-shared/src/auth/inventory.rs new file mode 100644 index 00000000..a722d68d --- /dev/null +++ b/yazi-shared/src/auth/inventory.rs @@ -0,0 +1,9 @@ +use std::sync::Arc; + +use crate::auth::{Auth, Scheme}; + +pub struct AuthInventory { + pub get: fn(&Scheme, &str) -> Option>, +} + +inventory::collect!(AuthInventory); diff --git a/yazi-shared/src/auth/kind.rs b/yazi-shared/src/auth/kind.rs new file mode 100644 index 00000000..ed794c77 --- /dev/null +++ b/yazi-shared/src/auth/kind.rs @@ -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, + } + } +} diff --git a/yazi-shared/src/auth/mod.rs b/yazi-shared/src/auth/mod.rs new file mode 100644 index 00000000..55f581e7 --- /dev/null +++ b/yazi-shared/src/auth/mod.rs @@ -0,0 +1,3 @@ +yazi_macro::mod_flat!(auth encode inventory kind scheme); + +pub(super) fn init() { DEFAULT_ARC.with(Default::default); } diff --git a/yazi-shared/src/auth/scheme.rs b/yazi-shared/src/auth/scheme.rs new file mode 100644 index 00000000..2e4fd3c3 --- /dev/null +++ b/yazi-shared/src/auth/scheme.rs @@ -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), +} + +impl AsRef for Scheme { + fn as_ref(&self) -> &str { self.as_str() } +} + +impl PartialEq 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 { + 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, + } + } +} diff --git a/yazi-shared/src/kebab_cased_string.rs b/yazi-shared/src/kebab_cased_key.rs similarity index 57% rename from yazi-shared/src/kebab_cased_string.rs rename to yazi-shared/src/kebab_cased_key.rs index 01ef687b..99d5b496 100644 --- a/yazi-shared/src/kebab_cased_string.rs +++ b/yazi-shared/src/kebab_cased_key.rs @@ -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 { s.as_bytes().kebab_cased().then_some(Self(s)) } +impl KebabCasedKey { + pub fn new(s: String) -> Option { + (!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 for KebabCasedString { +impl Borrow for KebabCasedKey { #[inline] fn borrow(&self) -> &str { &self.0 } } -impl Borrow for KebabCasedString { +impl Borrow for KebabCasedKey { #[inline] fn borrow(&self) -> &String { &self.0 } } -impl AsRef for KebabCasedString { +impl AsRef for KebabCasedKey { #[inline] fn as_ref(&self) -> &str { &self.0 } } -impl AsRef for KebabCasedString { +impl AsRef 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 for String { +impl From for String { #[inline] - fn from(value: KebabCasedString) -> Self { value.0 } + fn from(value: KebabCasedKey) -> Self { value.0 } } -impl From for Cow<'_, str> { +impl From 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>(deserializer: D) -> Result { 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") + }) } } diff --git a/yazi-shared/src/lib.rs b/yazi-shared/src/lib.rs index c9e663d8..452728a9 100644 --- a/yazi-shared/src/lib.rs +++ b/yazi-shared/src/lib.rs @@ -1,8 +1,8 @@ extern crate self as yazi_shared; -yazi_macro::mod_pub!(any_data data event id loc path pool scheme shell strand translit url); +yazi_macro::mod_pub!(any_data auth data spec event id loc path pool shell strand translit url); -yazi_macro::mod_flat!(bytes chars completion_token condition debounce env kebab_cased_string last_value layer localset natsort non_empty_string os predictor snake_cased_string source tests throttle time); +yazi_macro::mod_flat!(bytes chars completion_token condition debounce env kebab_cased_key last_value layer localset natsort non_empty_string os predictor snake_cased_string source tests throttle time); pub fn init() { LOCAL_SET.with(tokio::task::LocalSet::new); @@ -13,5 +13,6 @@ pub fn init() { USERS_CACHE.with(<_>::default); pool::init(); + auth::init(); event::Event::init(); } diff --git a/yazi-shared/src/loc/buf.rs b/yazi-shared/src/loc/buf.rs index 52d1cfbf..f00c77eb 100644 --- a/yazi-shared/src/loc/buf.rs +++ b/yazi-shared/src/loc/buf.rs @@ -2,7 +2,7 @@ use std::{cmp, ffi::OsStr, fmt::{self, Debug, Formatter}, hash::{Hash, Hasher}, use anyhow::Result; -use crate::{loc::{Loc, LocAble, LocAbleImpl, LocBufAble, LocBufAbleImpl}, path::{AsPath, AsPathView, PathDyn, SetNameError}, scheme::SchemeKind, strand::AsStrandView}; +use crate::{auth::AuthKind, loc::{Loc, LocAble, LocAbleImpl, LocBufAble, LocBufAbleImpl}, path::{AsPath, AsPathView, PathDyn, SetNameError}, strand::AsStrandView}; #[derive(Clone, Default, Eq, PartialEq)] pub struct LocBuf

{ @@ -146,7 +146,7 @@ where Self { inner: loc.inner, uri, urn } } - pub fn saturated(path: P, kind: SchemeKind) -> Self { + pub fn saturated(path: P, kind: AuthKind) -> Self { let loc = Self::from(path); let Loc { inner, uri, urn, _phantom } = Loc::saturated(&loc.inner, kind); @@ -332,18 +332,18 @@ mod tests { // Regular ("/", "a", "/a"), ("/a/b", "c", "/a/c"), - // Archive - ("archive:////", "a.zip", "archive:////a.zip"), - ("archive:////a.zip/b", "c", "archive:////a.zip/c"), - ("archive://:2//a.zip/b", "c", "archive://:2//a.zip/c"), - ("archive://:2:1//a.zip/b", "c", "archive://:2:1//a.zip/c"), + // Mount + ("test-mount://7z//", "a.zip", "test-mount://7z//a.zip"), + ("test-mount://7z//a.zip/b", "c", "test-mount://7z//a.zip/c"), + ("test-mount://7z:2//a.zip/b", "c", "test-mount://7z:2//a.zip/c"), + ("test-mount://7z:2:1//a.zip/b", "c", "test-mount://7z:2:1//a.zip/c"), // Empty ("/a", "", "/"), - ("archive:////a.zip", "", "archive:////"), - ("archive:////a.zip/b", "", "archive:////a.zip"), - ("archive://:1:1//a.zip", "", "archive:////"), - ("archive://:2//a.zip/b", "", "archive://:1//a.zip"), - ("archive://:2:2//a.zip/b", "", "archive://:1:1//a.zip"), + ("test-mount://7z//a.zip", "", "test-mount://7z//"), + ("test-mount://7z//a.zip/b", "", "test-mount://7z//a.zip"), + ("test-mount://7z:1:1//a.zip", "", "test-mount://7z//"), + ("test-mount://7z:2//a.zip/b", "", "test-mount://7z:1//a.zip"), + ("test-mount://7z:2:2//a.zip/b", "", "test-mount://7z:1:1//a.zip"), ]; for (input, name, expected) in cases { diff --git a/yazi-shared/src/loc/loc.rs b/yazi-shared/src/loc/loc.rs index a1824ee8..533b7c2a 100644 --- a/yazi-shared/src/loc/loc.rs +++ b/yazi-shared/src/loc/loc.rs @@ -3,7 +3,7 @@ use std::{hash::{Hash, Hasher}, marker::PhantomData, ops::Deref}; use anyhow::{Result, bail}; use super::LocAbleImpl; -use crate::{loc::{LocAble, LocBuf, LocBufAble, StrandAbleImpl}, path::{AsPath, AsPathView, PathDyn}, scheme::SchemeKind, strand::AsStrandView}; +use crate::{auth::AuthKind, loc::{LocAble, LocBuf, LocBufAble, StrandAbleImpl}, path::{AsPath, AsPathView, PathDyn}, strand::AsStrandView}; #[derive(Clone, Copy, Debug)] pub struct Loc<'p, P = &'p std::path::Path> { @@ -147,15 +147,16 @@ where self.inner.parent().filter(|p| !p.as_encoded_bytes().is_empty()) } - pub fn saturated<'a, T>(path: T, kind: SchemeKind) -> Self + pub fn saturated<'a, T>(path: T, kind: AuthKind) -> Self where T: AsPathView<'p, P>, { match kind { - SchemeKind::Regular => Self::bare(path), - SchemeKind::Search => Self::zeroed(path), - SchemeKind::Archive => Self::zeroed(path), - SchemeKind::Sftp => Self::bare(path), + AuthKind::Regular => Self::bare(path), + AuthKind::Search => Self::zeroed(path), + AuthKind::Mount => Self::zeroed(path), + AuthKind::Scope => Self::bare(path), + AuthKind::Sftp => Self::bare(path), } } diff --git a/yazi-shared/src/path/kind.rs b/yazi-shared/src/path/kind.rs index fb2c9a1f..68b59865 100644 --- a/yazi-shared/src/path/kind.rs +++ b/yazi-shared/src/path/kind.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::scheme::SchemeKind; +use crate::auth::AuthKind; #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] #[serde(rename_all = "lowercase")] @@ -9,13 +9,14 @@ pub enum PathKind { Unix, } -impl From for PathKind { - fn from(value: SchemeKind) -> Self { +impl From for PathKind { + fn from(value: AuthKind) -> Self { match value { - SchemeKind::Regular => Self::Os, - SchemeKind::Search => Self::Os, - SchemeKind::Archive => Self::Os, - SchemeKind::Sftp => Self::Unix, + AuthKind::Regular => Self::Os, + AuthKind::Search => Self::Os, + AuthKind::Mount => Self::Os, + AuthKind::Scope => Self::Unix, + AuthKind::Sftp => Self::Unix, } } } diff --git a/yazi-shared/src/pool/mod.rs b/yazi-shared/src/pool/mod.rs index 4aa99859..396f37f2 100644 --- a/yazi-shared/src/pool/mod.rs +++ b/yazi-shared/src/pool/mod.rs @@ -4,7 +4,7 @@ static SYMBOLS: yazi_shim::cell::RoCell< parking_lot::Mutex>, > = yazi_shim::cell::RoCell::new(); -pub(super) fn init() { SYMBOLS.with(<_>::default); } +pub(super) fn init() { SYMBOLS.with(Default::default); } #[inline] pub(super) fn compute_hash(value: T) -> u64 { diff --git a/yazi-shared/src/scheme/cow.rs b/yazi-shared/src/scheme/cow.rs deleted file mode 100644 index 30a4eea0..00000000 --- a/yazi-shared/src/scheme/cow.rs +++ /dev/null @@ -1,232 +0,0 @@ -use std::borrow::Cow; - -use anyhow::{Result, ensure}; -use percent_encoding::percent_decode; - -use crate::{path::{PathCow, PathLike}, pool::{InternStr, SymbolCow}, scheme::{AsScheme, Scheme, SchemeKind, SchemeRef}, url::Url}; - -#[derive(Clone, Debug)] -pub enum SchemeCow<'a> { - Borrowed(SchemeRef<'a>), - Owned(Scheme), -} - -impl<'a> From> for SchemeCow<'a> { - fn from(value: SchemeRef<'a>) -> Self { Self::Borrowed(value) } -} - -impl<'a, T> From<&'a T> for SchemeCow<'a> -where - T: AsScheme + ?Sized, -{ - fn from(value: &'a T) -> Self { Self::Borrowed(value.as_scheme()) } -} - -impl From for SchemeCow<'_> { - fn from(value: Scheme) -> Self { Self::Owned(value) } -} - -impl From> for Scheme { - fn from(value: SchemeCow<'_>) -> Self { value.into_owned() } -} - -impl PartialEq> for SchemeCow<'_> { - fn eq(&self, other: &SchemeRef) -> bool { self.as_scheme() == *other } -} - -impl<'a> SchemeCow<'a> { - pub fn regular(uri: usize, urn: usize) -> Self { SchemeRef::Regular { uri, urn }.into() } - - pub fn search(domain: T, uri: usize, urn: usize) -> Self - where - T: Into>, - { - match domain.into() { - Cow::Borrowed(domain) => SchemeRef::Search { domain, uri, urn }.into(), - Cow::Owned(domain) => Scheme::Search { domain: domain.intern(), uri, urn }.into(), - } - } - - pub fn archive(domain: T, uri: usize, urn: usize) -> Self - where - T: Into>, - { - match domain.into() { - Cow::Borrowed(domain) => SchemeRef::Archive { domain, uri, urn }.into(), - Cow::Owned(domain) => Scheme::Archive { domain: domain.intern(), uri, urn }.into(), - } - } - - pub fn sftp(domain: T, uri: usize, urn: usize) -> Self - where - T: Into>, - { - match domain.into() { - Cow::Borrowed(domain) => SchemeRef::Sftp { domain, uri, urn }.into(), - Cow::Owned(domain) => Scheme::Sftp { domain: domain.intern(), uri, urn }.into(), - } - } - - pub fn parse(bytes: &'a [u8]) -> Result<(Self, PathCow<'a>)> { - let Some((kind, tilde)) = SchemeKind::parse(bytes)? else { - let path = Self::decode_path(SchemeKind::Regular, false, bytes)?; - let (uri, urn) = Self::normalize_ports(SchemeKind::Regular, None, None, &path)?; - return Ok((Self::regular(uri, urn), path)); - }; - - // Decode domain and ports - let mut skip = kind.offset(tilde); - let (domain, uri, urn) = match kind { - SchemeKind::Regular => ("".into(), None, None), - SchemeKind::Search => Self::decode_param(&bytes[skip..], &mut skip)?, - SchemeKind::Archive => Self::decode_param(&bytes[skip..], &mut skip)?, - SchemeKind::Sftp => Self::decode_param(&bytes[skip..], &mut skip)?, - }; - - // Decode path - let path = Self::decode_path(kind, tilde, &bytes[skip..])?; - - // Build scheme - let (uri, urn) = Self::normalize_ports(kind, uri, urn, &path)?; - let scheme = match kind { - SchemeKind::Regular => Self::regular(uri, urn), - SchemeKind::Search => Self::search(domain, uri, urn), - SchemeKind::Archive => Self::archive(domain, uri, urn), - SchemeKind::Sftp => Self::sftp(domain, uri, urn), - }; - - Ok((scheme, path)) - } - - fn decode_param( - bytes: &'a [u8], - skip: &mut usize, - ) -> Result<(Cow<'a, str>, Option, Option)> { - let mut len = bytes.iter().copied().take_while(|&b| b != b'/').count(); - let slash = bytes.get(len).is_some_and(|&b| b == b'/'); - *skip += len + slash as usize; - - let (uri, urn) = Self::decode_ports(&bytes[..len], &mut len)?; - let domain = match Cow::from(percent_decode(&bytes[..len])) { - Cow::Borrowed(b) => str::from_utf8(b)?.into(), - Cow::Owned(b) => String::from_utf8(b)?.into(), - }; - - Ok((domain, uri, urn)) - } - - fn decode_ports(bytes: &[u8], skip: &mut usize) -> Result<(Option, Option)> { - let Some(a_idx) = bytes.iter().rposition(|&b| b == b':') else { return Ok((None, None)) }; - let a_len = bytes.len() - a_idx; - *skip -= a_len; - let a = if a_len == 1 { None } else { Some(str::from_utf8(&bytes[a_idx + 1..])?.parse()?) }; - - let Some(b_idx) = bytes[..a_idx].iter().rposition(|&b| b == b':') else { - return Ok((a, None)); - }; - let b_len = bytes[..a_idx].len() - b_idx; - *skip -= b_len; - let b = - if b_len == 1 { None } else { Some(str::from_utf8(&bytes[b_idx + 1..a_idx])?.parse()?) }; - - Ok((b, a)) - } - - fn decode_path(kind: SchemeKind, tilde: bool, bytes: &'a [u8]) -> Result> { - let bytes: Cow<_> = if tilde { percent_decode(bytes).into() } else { bytes.into() }; - PathCow::with(kind, bytes) - } - - fn normalize_ports( - kind: SchemeKind, - uri: Option, - urn: Option, - path: &PathCow, - ) -> Result<(usize, usize)> { - Ok(match kind { - SchemeKind::Regular => { - ensure!(uri.is_none() && urn.is_none(), "Regular scheme cannot have ports"); - (path.name().is_some() as usize, path.name().is_some() as usize) - } - SchemeKind::Search => { - let (uri, urn) = (uri.unwrap_or(0), urn.unwrap_or(0)); - ensure!(uri == urn, "Search scheme requires URI and URN to be equal"); - (uri, urn) - } - SchemeKind::Archive => (uri.unwrap_or(0), urn.unwrap_or(0)), - SchemeKind::Sftp => { - let uri = uri.unwrap_or(path.name().is_some() as usize); - let urn = urn.unwrap_or(path.name().is_some() as usize); - (uri, urn) - } - }) - } - - pub fn retrieve_ports(url: Url) -> (usize, usize) { - match url { - Url::Regular(loc) => (loc.file_name().is_some() as usize, loc.file_name().is_some() as usize), - Url::Search { loc, .. } => (loc.uri().components().count(), loc.urn().components().count()), - Url::Archive { loc, .. } => (loc.uri().components().count(), loc.urn().components().count()), - Url::Sftp { loc, .. } => (loc.uri().components().count(), loc.urn().components().count()), - } - } -} - -impl<'a> SchemeCow<'a> { - #[inline] - pub fn into_domain(self) -> Option> { - Some(match self { - SchemeCow::Borrowed(s) => s.domain()?.into(), - SchemeCow::Owned(s) => s.into_domain()?.into(), - }) - } - - #[inline] - pub fn into_owned(self) -> Scheme { - match self { - Self::Borrowed(s) => s.to_owned(), - Self::Owned(s) => s, - } - } - - pub fn with_ports(self, uri: usize, urn: usize) -> Self { - match self { - Self::Borrowed(s) => s.with_ports(uri, urn).into(), - Self::Owned(s) => s.with_ports(uri, urn).into(), - } - } - - #[inline] - pub fn zeroed(self) -> Self { self.with_ports(0, 0) } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_decode_ports() -> Result<()> { - fn assert(s: &str, len: usize, uri: Option, urn: Option) -> Result<()> { - let mut n = usize::MAX; - let port = SchemeCow::decode_ports(s.as_bytes(), &mut n)?; - assert_eq!((usize::MAX - n, port.0, port.1), (len, uri, urn)); - Ok(()) - } - - // Zeros - assert("", 0, None, None)?; - assert(":", 1, None, None)?; - assert("::", 2, None, None)?; - - // URI - assert(":2", 2, Some(2), None)?; - assert(":2:", 3, Some(2), None)?; - assert(":22:", 4, Some(22), None)?; - - // URN - assert("::1", 3, None, Some(1))?; - assert(":2:1", 4, Some(2), Some(1))?; - assert(":22:11", 6, Some(22), Some(11))?; - Ok(()) - } -} diff --git a/yazi-shared/src/scheme/inventory.rs b/yazi-shared/src/scheme/inventory.rs deleted file mode 100644 index dcc850d0..00000000 --- a/yazi-shared/src/scheme/inventory.rs +++ /dev/null @@ -1,9 +0,0 @@ -use mlua::UserDataRegistry; - -use crate::scheme::Scheme; - -pub struct SchemeInventory { - pub register: fn(&mut UserDataRegistry), -} - -inventory::collect!(SchemeInventory); diff --git a/yazi-shared/src/scheme/kind.rs b/yazi-shared/src/scheme/kind.rs deleted file mode 100644 index c5ddc3dc..00000000 --- a/yazi-shared/src/scheme/kind.rs +++ /dev/null @@ -1,84 +0,0 @@ -use anyhow::{Result, bail}; -use strum::IntoStaticStr; - -use crate::{BytesExt, scheme::{AsScheme, SchemeRef}}; - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, IntoStaticStr)] -#[strum(serialize_all = "kebab-case")] -pub enum SchemeKind { - Regular, - Search, - Archive, - Sftp, -} - -impl From for SchemeKind -where - T: AsScheme, -{ - fn from(value: T) -> Self { - match value.as_scheme() { - SchemeRef::Regular { .. } => Self::Regular, - SchemeRef::Search { .. } => Self::Search, - SchemeRef::Archive { .. } => Self::Archive, - SchemeRef::Sftp { .. } => Self::Sftp, - } - } -} - -impl TryFrom<&[u8]> for SchemeKind { - type Error = anyhow::Error; - - fn try_from(value: &[u8]) -> Result { - match value { - b"regular" => Ok(Self::Regular), - b"search" => Ok(Self::Search), - b"archive" => Ok(Self::Archive), - b"sftp" => Ok(Self::Sftp), - _ => bail!("invalid scheme kind: {}", String::from_utf8_lossy(value)), - } - } -} - -impl SchemeKind { - #[inline] - pub fn is_local(self) -> bool { - match self { - Self::Regular | Self::Search => true, - Self::Archive | Self::Sftp => false, - } - } - - #[inline] - pub fn is_remote(self) -> bool { - match self { - Self::Regular | Self::Search | Self::Archive => false, - Self::Sftp => true, - } - } - - #[inline] - pub fn is_virtual(self) -> bool { - match self { - Self::Regular | Self::Search => false, - Self::Archive | Self::Sftp => true, - } - } - - #[inline] - pub(super) fn offset(self, tilde: bool) -> usize { - 3 + Into::<&str>::into(self).len() + tilde as usize - } - - pub fn parse(bytes: &[u8]) -> Result> { - let Some((kind, _)) = bytes.split_seq_once(b"://") else { - return Ok(None); - }; - - Ok(Some(if let Some(stripped) = kind.strip_suffix(b"~") { - (Self::try_from(stripped)?, true) - } else { - (Self::try_from(kind)?, false) - })) - } -} diff --git a/yazi-shared/src/scheme/lua.rs b/yazi-shared/src/scheme/lua.rs deleted file mode 100644 index 5764a70b..00000000 --- a/yazi-shared/src/scheme/lua.rs +++ /dev/null @@ -1,20 +0,0 @@ -use mlua::{UserData, UserDataFields, UserDataRegistry}; -use yazi_shim::{mlua::UserDataFieldsExt, strum::IntoStr}; - -use crate::scheme::{Scheme, SchemeInventory, SchemeLike}; - -impl UserData for Scheme { - fn add_fields>(fields: &mut F) { - fields.add_cached_field("kind", |_, me| Ok(me.kind().into_str())); - fields.add_field_method_get("is_virtual", |_, me| Ok(me.is_virtual())); - } - - fn register(registry: &mut UserDataRegistry) { - Self::add_fields(registry); - Self::add_methods(registry); - - for inv in inventory::iter::() { - (inv.register)(registry); - } - } -} diff --git a/yazi-shared/src/scheme/mod.rs b/yazi-shared/src/scheme/mod.rs deleted file mode 100644 index 465e626b..00000000 --- a/yazi-shared/src/scheme/mod.rs +++ /dev/null @@ -1 +0,0 @@ -yazi_macro::mod_flat!(cow encode inventory kind lua r#ref scheme traits); diff --git a/yazi-shared/src/scheme/ref.rs b/yazi-shared/src/scheme/ref.rs deleted file mode 100644 index 6259f699..00000000 --- a/yazi-shared/src/scheme/ref.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::{hash::Hash, ops::Deref}; - -use crate::{pool::InternStr, scheme::{AsScheme, Scheme, SchemeKind}}; - -#[derive(Clone, Copy, Debug)] -pub enum SchemeRef<'a> { - Regular { uri: usize, urn: usize }, - Search { domain: &'a str, uri: usize, urn: usize }, - Archive { domain: &'a str, uri: usize, urn: usize }, - Sftp { domain: &'a str, uri: usize, urn: usize }, -} - -impl Deref for SchemeRef<'_> { - type Target = SchemeKind; - - fn deref(&self) -> &Self::Target { - match self { - Self::Regular { .. } => &SchemeKind::Regular, - Self::Search { .. } => &SchemeKind::Search, - Self::Archive { .. } => &SchemeKind::Archive, - Self::Sftp { .. } => &SchemeKind::Sftp, - } - } -} - -impl Hash for SchemeRef<'_> { - fn hash(&self, state: &mut H) { - self.kind().hash(state); - self.domain().hash(state); - } -} - -impl PartialEq> for SchemeRef<'_> { - fn eq(&self, other: &SchemeRef) -> bool { - self.kind() == other.kind() && self.domain() == other.domain() - } -} - -impl From> for Scheme { - fn from(value: SchemeRef) -> Self { value.to_owned() } -} - -impl<'a> SchemeRef<'a> { - pub fn covariant(self, other: impl AsScheme) -> bool { - let other = other.as_scheme(); - if self.is_virtual() || other.is_virtual() { self == other } else { true } - } - - pub const fn domain(self) -> Option<&'a str> { - match self { - Self::Regular { .. } => None, - Self::Search { domain, .. } | Self::Archive { domain, .. } | Self::Sftp { domain, .. } => { - Some(domain) - } - } - } - - pub const fn kind(self) -> SchemeKind { - match self { - Self::Regular { .. } => SchemeKind::Regular, - Self::Search { .. } => SchemeKind::Search, - Self::Archive { .. } => SchemeKind::Archive, - Self::Sftp { .. } => SchemeKind::Sftp, - } - } - - pub const fn ports(self) -> (usize, usize) { - match self { - Self::Regular { uri, urn } => (uri, urn), - Self::Search { uri, urn, .. } => (uri, urn), - Self::Archive { uri, urn, .. } => (uri, urn), - Self::Sftp { uri, urn, .. } => (uri, urn), - } - } - - pub fn to_owned(self) -> Scheme { - match self { - Self::Regular { uri, urn } => Scheme::Regular { uri, urn }, - Self::Search { domain, uri, urn } => Scheme::Search { domain: domain.intern(), uri, urn }, - Self::Archive { domain, uri, urn } => Scheme::Archive { domain: domain.intern(), uri, urn }, - Self::Sftp { domain, uri, urn } => Scheme::Sftp { domain: domain.intern(), uri, urn }, - } - } - - pub const fn with_ports(self, uri: usize, urn: usize) -> Self { - match self { - Self::Regular { .. } => Self::Regular { uri, urn }, - Self::Search { domain, .. } => Self::Search { domain, uri, urn }, - Self::Archive { domain, .. } => Self::Archive { domain, uri, urn }, - Self::Sftp { domain, .. } => Self::Sftp { domain, uri, urn }, - } - } - - pub const fn zeroed(self) -> Self { self.with_ports(0, 0) } -} diff --git a/yazi-shared/src/scheme/scheme.rs b/yazi-shared/src/scheme/scheme.rs deleted file mode 100644 index 8148a431..00000000 --- a/yazi-shared/src/scheme/scheme.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::hash::{Hash, Hasher}; - -use serde::Deserialize; - -use crate::{pool::Symbol, scheme::{AsScheme, SchemeRef}}; - -#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] -#[serde(tag = "kind", rename_all = "kebab-case")] -pub enum Scheme { - Regular { uri: usize, urn: usize }, - Search { domain: Symbol, uri: usize, urn: usize }, - Archive { domain: Symbol, uri: usize, urn: usize }, - Sftp { domain: Symbol, uri: usize, urn: usize }, -} - -impl Hash for Scheme { - fn hash(&self, state: &mut H) { self.as_scheme().hash(state); } -} - -impl PartialEq> for Scheme { - fn eq(&self, other: &SchemeRef<'_>) -> bool { self.as_scheme() == *other } -} - -impl Scheme { - #[inline] - pub fn into_domain(self) -> Option> { - match self { - Self::Regular { .. } => None, - Self::Search { domain, .. } | Self::Archive { domain, .. } | Self::Sftp { domain, .. } => { - Some(domain) - } - } - } - - #[inline] - pub fn with_ports(self, uri: usize, urn: usize) -> Self { - match self { - Self::Regular { .. } => Self::Regular { uri, urn }, - Self::Search { domain, .. } => Self::Search { domain, uri, urn }, - Self::Archive { domain, .. } => Self::Archive { domain, uri, urn }, - Self::Sftp { domain, .. } => Self::Sftp { domain, uri, urn }, - } - } - - #[inline] - pub fn zeroed(self) -> Self { self.with_ports(0, 0) } -} diff --git a/yazi-shared/src/scheme/traits.rs b/yazi-shared/src/scheme/traits.rs deleted file mode 100644 index b36cbc56..00000000 --- a/yazi-shared/src/scheme/traits.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::scheme::{Scheme, SchemeCow, SchemeKind, SchemeRef}; - -pub trait AsScheme { - fn as_scheme(&self) -> SchemeRef<'_>; -} - -impl AsScheme for SchemeRef<'_> { - #[inline] - fn as_scheme(&self) -> SchemeRef<'_> { *self } -} - -impl AsScheme for Scheme { - #[inline] - fn as_scheme(&self) -> SchemeRef<'_> { - match *self { - Self::Regular { uri, urn } => SchemeRef::Regular { uri, urn }, - Self::Search { ref domain, uri, urn } => SchemeRef::Search { domain, uri, urn }, - Self::Archive { ref domain, uri, urn } => SchemeRef::Archive { domain, uri, urn }, - Self::Sftp { ref domain, uri, urn } => SchemeRef::Sftp { domain, uri, urn }, - } - } -} - -impl AsScheme for &Scheme { - #[inline] - fn as_scheme(&self) -> SchemeRef<'_> { (**self).as_scheme() } -} - -impl AsScheme for SchemeCow<'_> { - #[inline] - fn as_scheme(&self) -> SchemeRef<'_> { - match self { - SchemeCow::Borrowed(s) => *s, - SchemeCow::Owned(s) => s.as_scheme(), - } - } -} - -impl AsScheme for &SchemeCow<'_> { - #[inline] - fn as_scheme(&self) -> SchemeRef<'_> { (**self).as_scheme() } -} - -// --- SchemeLike -pub trait SchemeLike -where - Self: AsScheme + Sized, -{ - fn kind(&self) -> SchemeKind { *self.as_scheme() } - - fn ports(&self) -> (usize, usize) { self.as_scheme().ports() } - - fn domain(&self) -> Option<&str> { self.as_scheme().domain() } - - fn covariant(&self, other: impl AsScheme) -> bool { self.as_scheme().covariant(other) } - - fn is_local(&self) -> bool { self.as_scheme().is_local() } - - fn is_remote(&self) -> bool { self.as_scheme().is_remote() } - - fn is_virtual(&self) -> bool { self.as_scheme().is_virtual() } -} - -impl SchemeLike for Scheme {} -impl SchemeLike for SchemeCow<'_> {} diff --git a/yazi-shared/src/scheme/encode.rs b/yazi-shared/src/spec/encode.rs similarity index 58% rename from yazi-shared/src/scheme/encode.rs rename to yazi-shared/src/spec/encode.rs index c51e28ea..79728da1 100644 --- a/yazi-shared/src/scheme/encode.rs +++ b/yazi-shared/src/spec/encode.rs @@ -1,8 +1,6 @@ use std::fmt::{self, Display}; -use percent_encoding::{AsciiSet, CONTROLS, PercentEncode, percent_encode}; - -use crate::{scheme::SchemeKind, url::Url}; +use crate::{auth::{AuthKind, Encode as EncodeAuth}, url::Url}; #[derive(Clone, Copy)] pub struct Encode<'a>(pub Url<'a>); @@ -12,12 +10,6 @@ impl<'a> From> for Encode<'a> { } impl<'a> Encode<'a> { - #[inline] - pub fn domain<'s>(s: &'s str) -> PercentEncode<'s> { - const SET: &AsciiSet = &CONTROLS.add(b'/').add(b':'); - percent_encode(s.as_bytes(), SET) - } - pub(crate) fn ports(self) -> impl Display { struct D<'a>(Encode<'a>); @@ -25,7 +17,7 @@ impl<'a> Encode<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { macro_rules! w { ($default_uri:expr, $default_urn:expr) => {{ - let (uri, urn) = self.0.0.scheme().ports(); + let (uri, urn) = self.0.0.spec().ports(); match (uri != $default_uri, urn != $default_urn) { (true, true) => write!(f, ":{uri}:{urn}"), (true, false) => write!(f, ":{uri}"), @@ -36,9 +28,9 @@ impl<'a> Encode<'a> { } match self.0.0.kind() { - SchemeKind::Regular => Ok(()), - SchemeKind::Search | SchemeKind::Archive => w!(0, 0), - SchemeKind::Sftp => { + AuthKind::Regular => Ok(()), + AuthKind::Search | AuthKind::Mount => w!(0, 0), + AuthKind::Scope | AuthKind::Sftp => { w!(self.0.0.loc().name().is_some() as usize, self.0.0.loc().name().is_some() as usize) } } @@ -53,11 +45,12 @@ impl Display for Encode<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0 { Url::Regular(_) => write!(f, "regular://"), - Url::Search { domain, .. } => write!(f, "search://{}{}/", Self::domain(domain), self.ports()), - Url::Archive { domain, .. } => { - write!(f, "archive://{}{}/", Self::domain(domain), self.ports()) + Url::Search { auth, .. } + | Url::Mount { auth, .. } + | Url::Scope { auth, .. } + | Url::Sftp { auth, .. } => { + write!(f, "{}{}/", EncodeAuth(auth, false), self.ports()) } - Url::Sftp { domain, .. } => write!(f, "sftp://{}{}/", Self::domain(domain), self.ports()), } } } diff --git a/yazi-shared/src/spec/inventory.rs b/yazi-shared/src/spec/inventory.rs new file mode 100644 index 00000000..613ed014 --- /dev/null +++ b/yazi-shared/src/spec/inventory.rs @@ -0,0 +1,9 @@ +use mlua::UserDataRegistry; + +use crate::spec::Spec; + +pub struct SpecInventory { + pub register: fn(&mut UserDataRegistry), +} + +inventory::collect!(SpecInventory); diff --git a/yazi-shared/src/spec/lua.rs b/yazi-shared/src/spec/lua.rs new file mode 100644 index 00000000..7c5f34cf --- /dev/null +++ b/yazi-shared/src/spec/lua.rs @@ -0,0 +1,23 @@ +use mlua::{UserData, UserDataFields, UserDataRegistry}; +use yazi_shim::{mlua::UserDataFieldsExt, strum::IntoStr}; + +use crate::{auth::AuthKind, spec::{Spec, SpecInventory}}; + +impl UserData for Spec { + fn add_fields>(fields: &mut F) { + fields.add_cached_field("kind", |_, me| Ok(me.kind.into_str())); + fields.add_cached_field("domain", |lua, me| lua.create_string(&*me.domain)); + fields.add_field_method_get("is_regular", |_, me| Ok(me.kind == AuthKind::Regular)); + fields.add_field_method_get("is_search", |_, me| Ok(me.kind == AuthKind::Search)); + fields.add_field_method_get("is_virtual", |_, me| Ok(me.kind.is_virtual())); + } + + fn register(registry: &mut UserDataRegistry) { + Self::add_fields(registry); + Self::add_methods(registry); + + for inv in inventory::iter::() { + (inv.register)(registry); + } + } +} diff --git a/yazi-shared/src/spec/mod.rs b/yazi-shared/src/spec/mod.rs new file mode 100644 index 00000000..22ef313e --- /dev/null +++ b/yazi-shared/src/spec/mod.rs @@ -0,0 +1 @@ +yazi_macro::mod_flat!(spec encode inventory lua parsed); diff --git a/yazi-shared/src/spec/parsed.rs b/yazi-shared/src/spec/parsed.rs new file mode 100644 index 00000000..aaf272d1 --- /dev/null +++ b/yazi-shared/src/spec/parsed.rs @@ -0,0 +1,43 @@ +use std::fmt; + +use anyhow::Result; + +use crate::{BytesExt, auth::Scheme}; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ParsedSpec<'a> { + bytes: &'a [u8], + skip: usize, + pub scheme: Scheme, + pub tilde: bool, +} + +impl fmt::Display for ParsedSpec<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}{}", self.scheme, if self.tilde { "~" } else { "" }) + } +} + +impl<'a> ParsedSpec<'a> { + pub fn parse(bytes: &'a [u8]) -> Result { + let Some((scheme, _)) = bytes.split_seq_once(b"://") else { + return Ok(Self { bytes, skip: 0, scheme: Scheme::Regular, tilde: false }); + }; + + let (scheme, tilde) = if let Some(stripped) = scheme.strip_suffix(b"~") { + (stripped, true) + } else { + (scheme, false) + }; + + let scheme: Scheme = str::from_utf8(scheme)?.parse()?; + let skip = 3 + scheme.as_str().len() + tilde as usize; + Ok(Self { bytes, skip, scheme, tilde }) + } + + #[inline] + pub fn has_scheme(&self) -> bool { self.skip > 0 } + + #[inline] + pub fn rest(&self) -> &'a [u8] { &self.bytes[self.skip..] } +} diff --git a/yazi-shared/src/spec/spec.rs b/yazi-shared/src/spec/spec.rs new file mode 100644 index 00000000..b33ad015 --- /dev/null +++ b/yazi-shared/src/spec/spec.rs @@ -0,0 +1,129 @@ +use std::{borrow::Cow, ops::Deref, sync::Arc}; + +use anyhow::{Result, anyhow, ensure}; +use percent_encoding::percent_decode; +use serde::Deserialize; + +use crate::{auth::{Auth, AuthKind, Encode, Scheme}, path::{PathCow, PathLike}, spec::ParsedSpec, url::Url}; + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +pub struct Spec { + #[serde(flatten)] + pub auth: Arc, + pub uri: usize, + pub urn: usize, +} + +impl Deref for Spec { + type Target = Auth; + + fn deref(&self) -> &Self::Target { &self.auth } +} + +impl Spec { + pub fn parse<'a>(bytes: &'a [u8]) -> Result<(Self, PathCow<'a>)> { + let parsed = ParsedSpec::parse(bytes)?; + let rest = parsed.rest(); + + // Decode domain and ports + let mut skip = 0; + let (domain, uri, urn) = match &parsed.scheme { + Scheme::Regular => (Cow::Borrowed(""), None, None), + _ => Self::decode_param(rest, &mut skip)?, + }; + + // Resolve authority + let auth = Auth::get(&parsed.scheme, &domain) + .ok_or_else(|| anyhow!("unknown VFS authority: {parsed}://{}", Encode::domain(&domain)))?; + + // Decode path + let path = Self::decode_path(auth.kind, parsed.tilde, &rest[skip..])?; + let (uri, urn) = Self::normalize_ports(auth.kind, uri, urn, &path)?; + + Ok((Self { auth, uri, urn }, path)) + } + + pub const fn ports(&self) -> (usize, usize) { (self.uri, self.urn) } + + #[inline] + pub fn with_ports(self, uri: usize, urn: usize) -> Self { Self { uri, urn, ..self } } + + #[inline] + pub fn zeroed(self) -> Self { self.with_ports(0, 0) } + + fn decode_param<'a>( + bytes: &'a [u8], + skip: &mut usize, + ) -> Result<(Cow<'a, str>, Option, Option)> { + let mut len = bytes.iter().copied().take_while(|&b| b != b'/').count(); + let slash = bytes.get(len).is_some_and(|&b| b == b'/'); + *skip += len + slash as usize; + + let (uri, urn) = Self::decode_ports(&bytes[..len], &mut len)?; + let domain = match Cow::from(percent_decode(&bytes[..len])) { + Cow::Borrowed(b) => str::from_utf8(b)?.into(), + Cow::Owned(b) => String::from_utf8(b)?.into(), + }; + + Ok((domain, uri, urn)) + } + + fn decode_ports(bytes: &[u8], skip: &mut usize) -> Result<(Option, Option)> { + let Some(a_idx) = bytes.iter().rposition(|&b| b == b':') else { return Ok((None, None)) }; + let a_len = bytes.len() - a_idx; + *skip -= a_len; + let a = if a_len == 1 { None } else { Some(str::from_utf8(&bytes[a_idx + 1..])?.parse()?) }; + + let Some(b_idx) = bytes[..a_idx].iter().rposition(|&b| b == b':') else { + return Ok((a, None)); + }; + let b_len = bytes[..a_idx].len() - b_idx; + *skip -= b_len; + let b = + if b_len == 1 { None } else { Some(str::from_utf8(&bytes[b_idx + 1..a_idx])?.parse()?) }; + + Ok((b, a)) + } + + fn decode_path<'a>(kind: AuthKind, tilde: bool, bytes: &'a [u8]) -> Result> { + let bytes: Cow<_> = if tilde { percent_decode(bytes).into() } else { bytes.into() }; + PathCow::with(kind, bytes) + } + + fn normalize_ports( + kind: AuthKind, + uri: Option, + urn: Option, + path: &PathCow, + ) -> Result<(usize, usize)> { + Ok(match kind { + AuthKind::Regular => { + ensure!(uri.is_none() && urn.is_none(), "Regular scheme cannot have ports"); + (path.name().is_some() as usize, path.name().is_some() as usize) + } + AuthKind::Search => { + let (uri, urn) = (uri.unwrap_or(0), urn.unwrap_or(0)); + ensure!(uri == urn, "Search scheme requires URI and URN to be equal"); + (uri, urn) + } + AuthKind::Mount => (uri.unwrap_or(0), urn.unwrap_or(0)), + AuthKind::Scope | AuthKind::Sftp => { + let uri = uri.unwrap_or(path.name().is_some() as usize); + let urn = urn.unwrap_or(path.name().is_some() as usize); + (uri, urn) + } + }) + } + + pub fn retrieve_ports(url: Url) -> (usize, usize) { + match url { + Url::Regular(loc) => (loc.file_name().is_some() as usize, loc.file_name().is_some() as usize), + Url::Search { loc, .. } | Url::Mount { loc, .. } => { + (loc.uri().components().count(), loc.urn().components().count()) + } + Url::Scope { loc, .. } | Url::Sftp { loc, .. } => { + (loc.uri().components().count(), loc.urn().components().count()) + } + } + } +} diff --git a/yazi-shared/src/strand/kind.rs b/yazi-shared/src/strand/kind.rs index 103afc20..7ed18e20 100644 --- a/yazi-shared/src/strand/kind.rs +++ b/yazi-shared/src/strand/kind.rs @@ -1,4 +1,4 @@ -use crate::{path::PathKind, scheme::SchemeKind}; +use crate::{auth::AuthKind, path::PathKind}; #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] pub enum StrandKind { @@ -16,13 +16,14 @@ impl From for StrandKind { } } -impl From for StrandKind { - fn from(value: SchemeKind) -> Self { +impl From for StrandKind { + fn from(value: AuthKind) -> Self { match value { - SchemeKind::Regular => Self::Os, - SchemeKind::Search => Self::Os, - SchemeKind::Archive => Self::Os, - SchemeKind::Sftp => Self::Bytes, + AuthKind::Regular => Self::Os, + AuthKind::Search => Self::Os, + AuthKind::Mount => Self::Os, + AuthKind::Scope => Self::Bytes, + AuthKind::Sftp => Self::Bytes, } } } diff --git a/yazi-shared/src/tests.rs b/yazi-shared/src/tests.rs index 0dedad2b..0ac25d82 100644 --- a/yazi-shared/src/tests.rs +++ b/yazi-shared/src/tests.rs @@ -1,7 +1,20 @@ -pub fn init_tests() { - static INIT: std::sync::OnceLock<()> = std::sync::OnceLock::new(); +use std::sync::OnceLock; - INIT.get_or_init(|| { - crate::init(); - }); +use crate::auth::{Auth, AuthInventory, AuthKind}; + +pub fn init_tests() { + static INIT: OnceLock<()> = OnceLock::new(); + + INIT.get_or_init(crate::init); +} + +inventory::submit! { + AuthInventory { + get: |scheme, domain| match (scheme.as_str(), domain) { + ("test-mount", "7z") => Some(Auth::new(AuthKind::Mount, scheme.clone(), "7z")), + ("test-scope", "aws") => Some(Auth::new(AuthKind::Scope, scheme.clone(), "aws")), + ("sftp", "vps") => Some(Auth::new(AuthKind::Sftp, scheme.clone(), "vps")), + _ => None, + }, + } } diff --git a/yazi-shared/src/url/buf.rs b/yazi-shared/src/url/buf.rs index ef9b2e05..b86f4f4f 100644 --- a/yazi-shared/src/url/buf.rs +++ b/yazi-shared/src/url/buf.rs @@ -1,18 +1,19 @@ -use std::{borrow::Cow, fmt::{Debug, Formatter}, hash::{Hash, Hasher}, path::{Path, PathBuf}, str::FromStr}; +use std::{borrow::Cow, fmt::{Debug, Formatter}, hash::{Hash, Hasher}, path::{Path, PathBuf}, str::FromStr, sync::Arc}; use anyhow::Result; use serde::{Deserialize, Serialize, de::{self, IntoDeserializer}}; use yazi_codegen::FromLuaOwned; use yazi_macro::impl_data_any; -use crate::{loc::LocBuf, path::{PathBufDyn, PathDynError, SetNameError}, pool::{InternStr, Pool, Symbol}, scheme::{Scheme, SchemeLike}, strand::AsStrand, url::{AsUrl, Url, UrlCow, UrlDeserializer, UrlLike}}; +use crate::{auth::Auth, loc::LocBuf, path::{PathBufDyn, PathDynError, SetNameError}, spec::Spec, strand::AsStrand, url::{AsUrl, Url, UrlCow, UrlDeserializer, UrlLike}}; #[derive(Clone, Eq, FromLuaOwned)] pub enum UrlBuf { Regular(LocBuf), - Search { loc: LocBuf, domain: Symbol }, - Archive { loc: LocBuf, domain: Symbol }, - Sftp { loc: LocBuf, domain: Symbol }, + Search { loc: LocBuf, auth: Arc }, + Mount { loc: LocBuf, auth: Arc }, + Scope { loc: LocBuf, auth: Arc }, + Sftp { loc: LocBuf, auth: Arc }, } impl_data_any!(UrlBuf); @@ -30,9 +31,10 @@ impl From> for UrlBuf { fn from(url: Url<'_>) -> Self { match url { Url::Regular(loc) => Self::Regular(loc.into()), - Url::Search { loc, domain } => Self::Search { loc: loc.into(), domain: domain.intern() }, - Url::Archive { loc, domain } => Self::Archive { loc: loc.into(), domain: domain.intern() }, - Url::Sftp { loc, domain } => Self::Sftp { loc: loc.into(), domain: domain.intern() }, + Url::Search { loc, auth } => Self::Search { loc: loc.into(), auth: auth.clone() }, + Url::Mount { loc, auth } => Self::Mount { loc: loc.into(), auth: auth.clone() }, + Url::Scope { loc, auth } => Self::Scope { loc: loc.into(), auth: auth.clone() }, + Url::Sftp { loc, auth } => Self::Sftp { loc: loc.into(), auth: auth.clone() }, } } } @@ -71,10 +73,10 @@ impl TryFrom for UrlBuf { } } -impl TryFrom<(Scheme, PathBufDyn)> for UrlBuf { +impl TryFrom<(Spec, PathBufDyn)> for UrlBuf { type Error = anyhow::Error; - fn try_from(value: (Scheme, PathBufDyn)) -> Result { + fn try_from(value: (Spec, PathBufDyn)) -> Result { Ok(UrlCow::try_from(value)?.into_owned()) } } @@ -137,7 +139,8 @@ impl UrlBuf { match self { Self::Regular(loc) => loc.into_inner().into(), Self::Search { loc, .. } => loc.into_inner().into(), - Self::Archive { loc, .. } => loc.into_inner().into(), + Self::Mount { loc, .. } => loc.into_inner().into(), + Self::Scope { loc, .. } => loc.into_inner().into(), Self::Sftp { loc, .. } => loc.into_inner().into(), } } @@ -152,7 +155,8 @@ impl UrlBuf { Ok(match self { Self::Regular(loc) => loc.try_set_name(name.as_os()?)?, Self::Search { loc, .. } => loc.try_set_name(name.as_os()?)?, - Self::Archive { loc, .. } => loc.try_set_name(name.as_os()?)?, + Self::Mount { loc, .. } => loc.try_set_name(name.as_os()?)?, + Self::Scope { loc, .. } => loc.try_set_name(name.encoded_bytes())?, Self::Sftp { loc, .. } => loc.try_set_name(name.encoded_bytes())?, }) } @@ -160,16 +164,10 @@ impl UrlBuf { pub fn rebase(&self, base: &Path) -> Self { match self { Self::Regular(loc) => Self::Regular(loc.rebase(base)), - Self::Search { loc, domain } => { - Self::Search { loc: loc.rebase(base), domain: domain.clone() } - } - Self::Archive { loc, domain } => { - Self::Archive { loc: loc.rebase(base), domain: domain.clone() } - } - Self::Sftp { loc, domain } => { - todo!(); - // Self::Sftp { loc: loc.rebase(base), domain: domain.clone() } - } + Self::Search { loc, auth } => Self::Search { loc: loc.rebase(base), auth: auth.clone() }, + Self::Mount { loc, auth } => Self::Mount { loc: loc.rebase(base), auth: auth.clone() }, + Self::Scope { .. } => todo!(), + Self::Sftp { .. } => todo!(), } } } @@ -184,10 +182,10 @@ impl UrlBuf { } #[inline] - pub fn into_search(self, domain: impl AsRef) -> Result { + pub fn into_search(self, query: impl AsRef) -> Result { Ok(Self::Search { - loc: LocBuf::::zeroed(self.into_loc().into_os()?), - domain: Pool::::intern(domain), + loc: LocBuf::::zeroed(self.into_loc().into_os()?), + auth: Auth::search(query.as_ref()), }) } } @@ -237,14 +235,14 @@ impl<'de> Deserialize<'de> for UrlBuf { #[derive(Deserialize)] struct Shadow { #[serde(flatten)] - scheme: Scheme, - path: Vec, + spec: Spec, + path: Vec, } - let Shadow { scheme, path } = Deserialize::deserialize(deserializer)?; - let path = PathBufDyn::with(scheme.kind(), path).map_err(de::Error::custom)?; + let Shadow { spec, path } = Deserialize::deserialize(deserializer)?; + let path = PathBufDyn::with(spec.kind, path).map_err(de::Error::custom)?; - UrlBuf::try_from((scheme, path)).map_err(de::Error::custom) + UrlBuf::try_from((spec, path)).map_err(de::Error::custom) } } @@ -281,13 +279,13 @@ mod tests { // Search ("search://kw//a", "b/c", "search://kw:2:2//a/b/c"), ("search://kw:2:2//a/b/c", "d/e", "search://kw:4:4//a/b/c/d/e"), - // Archive - ("archive:////a/b.zip", "c/d", "archive://:2:1//a/b.zip/c/d"), - ("archive://:2:1//a/b.zip/c/d", "e/f", "archive://:4:1//a/b.zip/c/d/e/f"), - ("archive://:2:2//a/b.zip/c/d", "e/f", "archive://:4:1//a/b.zip/c/d/e/f"), + // Mount + ("test-mount://7z//a/b.zip", "c/d", "test-mount://7z:2:1//a/b.zip/c/d"), + ("test-mount://7z:2:1//a/b.zip/c/d", "e/f", "test-mount://7z:4:1//a/b.zip/c/d/e/f"), + ("test-mount://7z:2:2//a/b.zip/c/d", "e/f", "test-mount://7z:4:1//a/b.zip/c/d/e/f"), // SFTP - ("sftp://remote//a", "b/c", "sftp://remote//a/b/c"), - ("sftp://remote:1:1//a/b/c", "d/e", "sftp://remote//a/b/c/d/e"), + ("sftp://vps//a", "b/c", "sftp://vps//a/b/c"), + ("sftp://vps:1:1//a/b/c", "d/e", "sftp://vps//a/b/c/d/e"), // Relative ("search://kw", "b/c", "search://kw:2:2/b/c"), ("search://kw/", "b/c", "search://kw:2:2/b/c"), @@ -318,17 +316,17 @@ mod tests { ("search://kw:2:2//a/b/c", Some("search://kw:1:1//a/b")), ("search://kw:1:1//a/b", Some("search://kw//a")), ("search://kw//a", Some("/")), - // Archive - ("archive://:2:1//a/b.zip/c/d", Some("archive://:1:1//a/b.zip/c")), - ("archive://:1:1//a/b.zip/c", Some("archive:////a/b.zip")), - ("archive:////a/b.zip", Some("/a")), + // Mount + ("test-mount://7z:2:1//a/b.zip/c/d", Some("test-mount://7z:1:1//a/b.zip/c")), + ("test-mount://7z:1:1//a/b.zip/c", Some("test-mount://7z//a/b.zip")), + ("test-mount://7z//a/b.zip", Some("/a")), // SFTP - ("sftp://remote:3:1//a/b", Some("sftp://remote//a")), - ("sftp://remote:2:1//a", Some("sftp://remote//")), - ("sftp://remote:1:1//a", Some("sftp://remote//")), - ("sftp://remote//a", Some("sftp://remote//")), - ("sftp://remote:1//", None), - ("sftp://remote//", None), + ("sftp://vps:3:1//a/b", Some("sftp://vps//a")), + ("sftp://vps:2:1//a", Some("sftp://vps//")), + ("sftp://vps:1:1//a", Some("sftp://vps//")), + ("sftp://vps//a", Some("sftp://vps//")), + ("sftp://vps:1//", None), + ("sftp://vps//", None), // Relative ("search://kw:2:2/a/b", Some("search://kw:1:1/a")), ("search://kw:1:1/a", None), diff --git a/yazi-shared/src/url/component.rs b/yazi-shared/src/url/component.rs index 37835a61..6277737b 100644 --- a/yazi-shared/src/url/component.rs +++ b/yazi-shared/src/url/component.rs @@ -2,11 +2,11 @@ use std::path::PrefixComponent; use anyhow::Result; -use crate::{path::{self, PathDynError}, scheme::SchemeRef, strand::Strand}; +use crate::{auth::Auth, path::{self, PathDynError}, strand::Strand}; #[derive(Clone, Copy, Debug, PartialEq)] pub enum Component<'a> { - Scheme(SchemeRef<'a>), + Auth(&'a Auth), Prefix(PrefixComponent<'a>), RootDir, CurDir, @@ -29,7 +29,7 @@ impl<'a> From> for Component<'a> { impl<'a> Component<'a> { pub fn downgrade(self) -> Option> { Some(match self { - Self::Scheme(_) => None?, + Self::Auth(_) => None?, Self::Prefix(p) => path::Component::Prefix(p), Self::RootDir => path::Component::RootDir, Self::CurDir => path::Component::CurDir, @@ -44,7 +44,7 @@ impl<'a> Component<'a> { // let mut buf = PathBuf::new(); // let mut scheme = None; // iter.into_iter().for_each(|c| match c { -// Component::Scheme(s) => scheme = Some(s), +// Component::Auth(s) => scheme = Some(s), // Component::Prefix(p) => buf.push(path::Component::Prefix(p)), // Component::RootDir => buf.push(path::Component::RootDir), // Component::CurDir => buf.push(path::Component::CurDir), diff --git a/yazi-shared/src/url/components.rs b/yazi-shared/src/url/components.rs index 50778f81..ed7f2cfe 100644 --- a/yazi-shared/src/url/components.rs +++ b/yazi-shared/src/url/components.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, ffi::{OsStr, OsString}, iter::FusedIterator, ops::Not}; -use crate::{loc::Loc, path, scheme::{Encode as EncodeScheme, SchemeCow, SchemeRef}, strand::{StrandBuf, StrandCow}, url::{Component, Encode as EncodeUrl, Url}}; +use crate::{auth::Auth, loc::Loc, path, spec::{Encode as EncodeSpec, Spec}, strand::{StrandBuf, StrandCow}, url::{Component, Encode as EncodeUrl, Url}}; #[derive(Clone)] pub struct Components<'a> { @@ -25,7 +25,7 @@ impl<'a> Components<'a> { pub fn covariant(&self, other: &Self) -> bool { match (self.scheme_yielded, other.scheme_yielded) { (true, true) => {} - (false, false) if self.scheme().covariant(other.scheme()) => {} + (false, false) if self.auth().covariant(other.auth()) => {} _ => return false, } self.inner == other.inner @@ -40,27 +40,24 @@ impl<'a> Components<'a> { return os.into(); } - let mut s = OsString::from(EncodeScheme(self.url()).to_string()); + let mut s = OsString::from(EncodeSpec(self.url()).to_string()); s.reserve_exact(os.len()); s.push(os); s.into() } - pub fn scheme(&self) -> SchemeRef<'a> { + pub fn auth(&self) -> &'a Auth { self.url.auth() } + + fn ports(&self) -> (usize, usize) { let left = self.inner.clone().count(); - let (uri, urn) = SchemeCow::retrieve_ports(self.url); + let (uri, urn) = Spec::retrieve_ports(self.url); let (uri, urn) = ( uri.saturating_sub(self.back_yields).min(left), urn.saturating_sub(self.back_yields).min(left), ); - match self.url { - Url::Regular(_) => SchemeRef::Regular { uri, urn }, - Url::Search { domain, .. } => SchemeRef::Search { domain, uri, urn }, - Url::Archive { domain, .. } => SchemeRef::Archive { domain, uri, urn }, - Url::Sftp { domain, .. } => SchemeRef::Sftp { domain, uri, urn }, - } + (uri, urn) } pub fn strand(&self) -> StrandCow<'a> { @@ -69,7 +66,7 @@ impl<'a> Components<'a> { return s.into(); } - let mut buf = StrandBuf::with_str(s.kind(), EncodeScheme(self.url()).to_string()); + let mut buf = StrandBuf::with_str(s.kind(), EncodeSpec(self.url()).to_string()); buf.reserve_exact(s.len()); buf.try_push(s).expect("strand with same kind"); buf.into() @@ -77,17 +74,20 @@ impl<'a> Components<'a> { pub fn url(&self) -> Url<'a> { let path = self.inner.path(); - let (uri, urn) = self.scheme().ports(); + let (uri, urn) = self.ports(); match self.url { Url::Regular(_) => Url::Regular(Loc::with(path.as_os().unwrap(), uri, urn).unwrap()), - Url::Search { domain, .. } => { - Url::Search { loc: Loc::with(path.as_os().unwrap(), uri, urn).unwrap(), domain } + Url::Search { auth, .. } => { + Url::Search { loc: Loc::with(path.as_os().unwrap(), uri, urn).unwrap(), auth } } - Url::Archive { domain, .. } => { - Url::Archive { loc: Loc::with(path.as_os().unwrap(), uri, urn).unwrap(), domain } + Url::Mount { auth, .. } => { + Url::Mount { loc: Loc::with(path.as_os().unwrap(), uri, urn).unwrap(), auth } } - Url::Sftp { domain, .. } => { - Url::Sftp { loc: Loc::with(path.as_unix().unwrap(), uri, urn).unwrap(), domain } + Url::Scope { auth, .. } => { + Url::Scope { loc: Loc::with(path.as_unix().unwrap(), uri, urn).unwrap(), auth } + } + Url::Sftp { auth, .. } => { + Url::Sftp { loc: Loc::with(path.as_unix().unwrap(), uri, urn).unwrap(), auth } } } } @@ -99,7 +99,7 @@ impl<'a> Iterator for Components<'a> { fn next(&mut self) -> Option { if !self.scheme_yielded { self.scheme_yielded = true; - Some(Component::Scheme(self.scheme())) + Some(Component::Auth(self.auth())) } else { self.inner.next().map(Into::into) } @@ -120,7 +120,7 @@ impl<'a> DoubleEndedIterator for Components<'a> { Some(c.into()) } else if !self.scheme_yielded { self.scheme_yielded = true; - Some(Component::Scheme(self.scheme())) + Some(Component::Auth(self.auth())) } else { None } @@ -136,7 +136,7 @@ impl<'a> PartialEq for Components<'a> { } match (self.scheme_yielded, other.scheme_yielded) { (true, true) => true, - (false, false) if self.scheme() == other.scheme() => true, + (false, false) if self.auth() == other.auth() => true, _ => false, } } @@ -147,49 +147,49 @@ impl<'a> PartialEq for Components<'a> { mod tests { use anyhow::Result; - use crate::{scheme::SchemeRef, url::{Component, UrlBuf, UrlLike}}; + use crate::{auth::Auth as A, spec::Spec as D, url::{Component, UrlBuf, UrlLike}}; #[test] fn test_url() -> Result<()> { use Component::*; - use SchemeRef as S; crate::init_tests(); + let s = |uri, urn| D { auth: A::search("keyword"), uri, urn }; let search: UrlBuf = "search://keyword//root/projects/yazi".parse()?; assert_eq!(search.uri(), ""); - assert_eq!(search.scheme(), S::Search { domain: "keyword", uri: 0, urn: 0 }); + assert_eq!(search.spec(), s(0, 0)); let src = search.try_join("src")?; assert_eq!(src.uri(), "src"); - assert_eq!(src.scheme(), S::Search { domain: "keyword", uri: 1, urn: 1 }); + assert_eq!(src.spec(), s(1, 1)); let main = src.try_join("main.rs")?; assert_eq!(main.urn(), "src/main.rs"); - assert_eq!(main.scheme(), S::Search { domain: "keyword", uri: 2, urn: 2 }); + assert_eq!(main.spec(), s(2, 2)); let mut it = main.components(); - assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 2, urn: 2 }); + assert_eq!(it.url().spec(), s(2, 2)); assert_eq!(it.next_back(), Some(Normal("main.rs".into()))); - assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 1, urn: 1 }); + assert_eq!(it.url().spec(), s(1, 1)); assert_eq!(it.next_back(), Some(Normal("src".into()))); - assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 0, urn: 0 }); + assert_eq!(it.url().spec(), s(0, 0)); assert_eq!(it.next_back(), Some(Normal("yazi".into()))); - assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 0, urn: 0 }); + assert_eq!(it.url().spec(), s(0, 0)); let mut it = main.components(); - assert_eq!(it.next(), Some(Scheme(S::Search { domain: "keyword", uri: 2, urn: 2 }))); + assert_eq!(it.next(), Some(Auth(&A::search("keyword")))); assert_eq!(it.next(), Some(RootDir)); assert_eq!(it.next(), Some(Normal("root".into()))); assert_eq!(it.next(), Some(Normal("projects".into()))); assert_eq!(it.next(), Some(Normal("yazi".into()))); - assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 2, urn: 2 }); + assert_eq!(it.url().spec(), s(2, 2)); assert_eq!(it.next(), Some(Normal("src".into()))); - assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 1, urn: 1 }); + assert_eq!(it.url().spec(), s(1, 1)); assert_eq!(it.next_back(), Some(Normal("main.rs".into()))); - assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 0, urn: 0 }); + assert_eq!(it.url().spec(), s(0, 0)); assert_eq!(it.next(), None); - assert_eq!(it.url().scheme(), S::Search { domain: "keyword", uri: 0, urn: 0 }); + assert_eq!(it.url().spec(), s(0, 0)); Ok(()) } diff --git a/yazi-shared/src/url/cov.rs b/yazi-shared/src/url/cov.rs index 7b77c24b..b88e17af 100644 --- a/yazi-shared/src/url/cov.rs +++ b/yazi-shared/src/url/cov.rs @@ -34,7 +34,7 @@ impl Hash for UrlCov<'_> { fn hash(&self, state: &mut H) { self.0.loc().hash(state); if self.0.kind().is_virtual() { - self.0.scheme().hash(state); + self.0.auth().hash(state); } } } diff --git a/yazi-shared/src/url/cow.rs b/yazi-shared/src/url/cow.rs index 323e34fb..4b09a528 100644 --- a/yazi-shared/src/url/cow.rs +++ b/yazi-shared/src/url/cow.rs @@ -1,31 +1,28 @@ -use std::{borrow::Cow, hash::{Hash, Hasher}, path::PathBuf}; +use std::{borrow::Cow, hash::{Hash, Hasher}, path::PathBuf, sync::Arc}; -use anyhow::{Result, anyhow}; +use anyhow::Result; use serde::{Deserialize, Deserializer, Serialize}; use typed_path::{UnixPath, UnixPathBuf}; -use crate::{loc::{Loc, LocBuf, LocCow}, path::{PathBufDyn, PathCow, PathDyn}, pool::SymbolCow, scheme::{AsScheme, Scheme, SchemeCow, SchemeKind, SchemeRef}, url::{AsUrl, Url, UrlBuf}}; +use crate::{auth::{Auth, AuthKind}, loc::{Loc, LocBuf, LocCow}, path::{PathBufDyn, PathCow, PathDyn}, spec::Spec, url::{AsUrl, Url, UrlBuf}}; #[derive(Clone, Debug)] pub enum UrlCow<'a> { Regular(LocCow<'a>), - Search { loc: LocCow<'a>, domain: SymbolCow<'a, str> }, - Archive { loc: LocCow<'a>, domain: SymbolCow<'a, str> }, - Sftp { loc: LocCow<'a, &'a UnixPath, UnixPathBuf>, domain: SymbolCow<'a, str> }, -} - -// FIXME: remove -impl Default for UrlCow<'_> { - fn default() -> Self { Self::Regular(Default::default()) } + Search { loc: LocCow<'a>, auth: Arc }, + Mount { loc: LocCow<'a>, auth: Arc }, + Scope { loc: LocCow<'a, &'a UnixPath, UnixPathBuf>, auth: Arc }, + Sftp { loc: LocCow<'a, &'a UnixPath, UnixPathBuf>, auth: Arc }, } impl<'a> From> for UrlCow<'a> { fn from(value: Url<'a>) -> Self { match value { Url::Regular(loc) => Self::Regular(loc.into()), - Url::Search { loc, domain } => Self::Search { loc: loc.into(), domain: domain.into() }, - Url::Archive { loc, domain } => Self::Archive { loc: loc.into(), domain: domain.into() }, - Url::Sftp { loc, domain } => Self::Sftp { loc: loc.into(), domain: domain.into() }, + Url::Search { loc, auth } => Self::Search { loc: loc.into(), auth: auth.clone() }, + Url::Mount { loc, auth } => Self::Mount { loc: loc.into(), auth: auth.clone() }, + Url::Scope { loc, auth } => Self::Scope { loc: loc.into(), auth: auth.clone() }, + Url::Sftp { loc, auth } => Self::Sftp { loc: loc.into(), auth: auth.clone() }, } } } @@ -41,11 +38,10 @@ impl From for UrlCow<'_> { fn from(value: UrlBuf) -> Self { match value { UrlBuf::Regular(loc) => Self::Regular(loc.into()), - UrlBuf::Search { loc, domain } => Self::Search { loc: loc.into(), domain: domain.into() }, - UrlBuf::Archive { loc, domain } => { - Self::Archive { loc: loc.into(), domain: domain.into() } - } - UrlBuf::Sftp { loc, domain } => Self::Sftp { loc: loc.into(), domain: domain.into() }, + UrlBuf::Search { loc, auth } => Self::Search { loc: loc.into(), auth }, + UrlBuf::Mount { loc, auth } => Self::Mount { loc: loc.into(), auth }, + UrlBuf::Scope { loc, auth } => Self::Scope { loc: loc.into(), auth }, + UrlBuf::Sftp { loc, auth } => Self::Sftp { loc: loc.into(), auth }, } } } @@ -65,7 +61,7 @@ impl From<&UrlCow<'_>> for UrlBuf { impl<'a> TryFrom<&'a [u8]> for UrlCow<'a> { type Error = anyhow::Error; - fn try_from(value: &'a [u8]) -> Result { SchemeCow::parse(value)?.try_into() } + fn try_from(value: &'a [u8]) -> Result { Spec::parse(value)?.try_into() } } impl TryFrom> for UrlCow<'_> { @@ -101,89 +97,55 @@ impl<'a> TryFrom> for UrlCow<'a> { } } -impl<'a> TryFrom<(SchemeRef<'a>, PathDyn<'a>)> for UrlCow<'a> { +impl<'a> TryFrom<(Spec, PathCow<'a>)> for UrlCow<'a> { type Error = anyhow::Error; - fn try_from((scheme, path): (SchemeRef<'a>, PathDyn<'a>)) -> Result { - (SchemeCow::Borrowed(scheme), path).try_into() - } -} - -impl<'a> TryFrom<(SchemeRef<'a>, PathCow<'a>)> for UrlCow<'a> { - type Error = anyhow::Error; - - fn try_from((scheme, path): (SchemeRef<'a>, PathCow<'a>)) -> Result { - (SchemeCow::Borrowed(scheme), path).try_into() - } -} - -impl TryFrom<(Scheme, PathBufDyn)> for UrlCow<'_> { - type Error = anyhow::Error; - - fn try_from((scheme, path): (Scheme, PathBufDyn)) -> Result { - (SchemeCow::Owned(scheme), path).try_into() - } -} - -impl<'a> TryFrom<(SchemeCow<'a>, PathCow<'a>)> for UrlCow<'a> { - type Error = anyhow::Error; - - fn try_from((scheme, path): (SchemeCow<'a>, PathCow<'a>)) -> Result { + fn try_from((spec, path): (Spec, PathCow<'a>)) -> Result { match path { - PathCow::Borrowed(path) => (scheme, path).try_into(), - PathCow::Owned(path) => (scheme, path).try_into(), + PathCow::Borrowed(path) => (spec, path).try_into(), + PathCow::Owned(path) => (spec, path).try_into(), } } } -impl<'a> TryFrom<(SchemeCow<'a>, PathDyn<'a>)> for UrlCow<'a> { +impl<'a> TryFrom<(Spec, PathDyn<'a>)> for UrlCow<'a> { type Error = anyhow::Error; - fn try_from((scheme, path): (SchemeCow<'a>, PathDyn<'a>)) -> Result { - let kind = scheme.as_scheme().kind(); - let (uri, urn) = scheme.as_scheme().ports(); - let domain = scheme.into_domain(); - Ok(match kind { - SchemeKind::Regular => Self::Regular(Loc::bare(path.as_os()?).into()), - SchemeKind::Search => Self::Search { - loc: Loc::with(path.as_os()?, uri, urn)?.into(), - domain: domain.ok_or_else(|| anyhow!("missing domain for search scheme"))?, - }, - SchemeKind::Archive => Self::Archive { - loc: Loc::with(path.as_os()?, uri, urn)?.into(), - domain: domain.ok_or_else(|| anyhow!("missing domain for archive scheme"))?, - }, - SchemeKind::Sftp => Self::Sftp { - loc: Loc::with(path.as_unix()?, uri, urn)?.into(), - domain: domain.ok_or_else(|| anyhow!("missing domain for sftp scheme"))?, - }, + fn try_from((spec, path): (Spec, PathDyn<'a>)) -> Result { + let Spec { auth, uri, urn } = spec; + Ok(match auth.kind { + AuthKind::Regular => Self::Regular(Loc::bare(path.as_os()?).into()), + AuthKind::Search => Self::Search { loc: Loc::with(path.as_os()?, uri, urn)?.into(), auth }, + AuthKind::Mount => Self::Mount { loc: Loc::with(path.as_os()?, uri, urn)?.into(), auth }, + AuthKind::Scope => Self::Scope { loc: Loc::with(path.as_unix()?, uri, urn)?.into(), auth }, + AuthKind::Sftp => Self::Sftp { loc: Loc::with(path.as_unix()?, uri, urn)?.into(), auth }, }) } } -impl<'a> TryFrom<(SchemeCow<'a>, PathBufDyn)> for UrlCow<'a> { +impl<'a> TryFrom<(Spec, PathBufDyn)> for UrlCow<'a> { type Error = anyhow::Error; - fn try_from((scheme, path): (SchemeCow<'a>, PathBufDyn)) -> Result { - let kind = scheme.as_scheme().kind(); - let (uri, urn) = scheme.as_scheme().ports(); - let domain = scheme.into_domain(); - Ok(match kind { - SchemeKind::Regular => { + fn try_from((spec, path): (Spec, PathBufDyn)) -> Result { + let Spec { auth, uri, urn } = spec; + Ok(match auth.kind { + AuthKind::Regular => { Self::Regular(LocBuf::::from(path.into_os()?).into()) } - SchemeKind::Search => Self::Search { - loc: LocBuf::::with(path.try_into()?, uri, urn)?.into(), - domain: domain.ok_or_else(|| anyhow!("missing domain for search scheme"))?, + AuthKind::Search => Self::Search { + loc: LocBuf::::with(path.try_into()?, uri, urn)?.into(), + auth, }, - SchemeKind::Archive => Self::Archive { - loc: LocBuf::::with(path.try_into()?, uri, urn)?.into(), - domain: domain.ok_or_else(|| anyhow!("missing domain for archive scheme"))?, - }, - SchemeKind::Sftp => Self::Sftp { - loc: LocBuf::::with(path.try_into()?, uri, urn)?.into(), - domain: domain.ok_or_else(|| anyhow!("missing domain for sftp scheme"))?, + AuthKind::Mount => Self::Mount { + loc: LocBuf::::with(path.try_into()?, uri, urn)?.into(), + auth, }, + AuthKind::Scope => { + Self::Scope { loc: LocBuf::::with(path.try_into()?, uri, urn)?.into(), auth } + } + AuthKind::Sftp => { + Self::Sftp { loc: LocBuf::::with(path.try_into()?, uri, urn)?.into(), auth } + } }) } } @@ -209,7 +171,8 @@ impl<'a> UrlCow<'a> { match self { Self::Regular(loc) => loc.is_owned(), Self::Search { loc, .. } => loc.is_owned(), - Self::Archive { loc, .. } => loc.is_owned(), + Self::Mount { loc, .. } => loc.is_owned(), + Self::Scope { loc, .. } => loc.is_owned(), Self::Sftp { loc, .. } => loc.is_owned(), } } @@ -217,47 +180,24 @@ impl<'a> UrlCow<'a> { pub fn into_owned(self) -> UrlBuf { match self { Self::Regular(loc) => UrlBuf::Regular(loc.into_owned()), - Self::Search { loc, domain } => { - UrlBuf::Search { loc: loc.into_owned(), domain: domain.into() } - } - Self::Archive { loc, domain } => { - UrlBuf::Archive { loc: loc.into_owned(), domain: domain.into() } - } - Self::Sftp { loc, domain } => { - UrlBuf::Sftp { loc: loc.into_owned(), domain: domain.into() } - } + Self::Search { loc, auth } => UrlBuf::Search { loc: loc.into_owned(), auth }, + Self::Mount { loc, auth } => UrlBuf::Mount { loc: loc.into_owned(), auth }, + Self::Scope { loc, auth } => UrlBuf::Scope { loc: loc.into_owned(), auth }, + Self::Sftp { loc, auth } => UrlBuf::Sftp { loc: loc.into_owned(), auth }, } } - pub fn into_pair(self) -> (SchemeCow<'a>, PathCow<'a>) { - let (uri, urn) = self.as_url().scheme().ports(); - match self { - Self::Regular(loc) => match loc { - LocCow::Borrowed(_) => (SchemeRef::Regular { uri, urn }.into(), loc.into_path()), - LocCow::Owned(_) => (Scheme::Regular { uri, urn }.into(), loc.into_path()), - }, - Self::Search { loc, domain } => match domain { - SymbolCow::Borrowed(domain) => { - (SchemeRef::Search { domain, uri, urn }.into(), loc.into_path()) - } - SymbolCow::Owned(domain) => (Scheme::Search { domain, uri, urn }.into(), loc.into_path()), - }, - Self::Archive { loc, domain } => match domain { - SymbolCow::Borrowed(domain) => { - (SchemeRef::Archive { domain, uri, urn }.into(), loc.into_path()) - } - SymbolCow::Owned(domain) => (Scheme::Archive { domain, uri, urn }.into(), loc.into_path()), - }, - Self::Sftp { loc, domain } => match domain { - SymbolCow::Borrowed(domain) => { - (SchemeRef::Sftp { domain, uri, urn }.into(), loc.into_path()) - } - SymbolCow::Owned(domain) => (Scheme::Sftp { domain, uri, urn }.into(), loc.into_path()), - }, - } + pub fn into_pair(self) -> (Spec, PathCow<'a>) { + let (uri, urn) = Spec::retrieve_ports(self.as_url()); + let (auth, path) = match self { + Self::Regular(loc) => (Auth::default_arc(), loc.into_path()), + Self::Search { loc, auth } | Self::Mount { loc, auth } => (auth, loc.into_path()), + Self::Scope { loc, auth } | Self::Sftp { loc, auth } => (auth, loc.into_path()), + }; + (Spec { auth, uri, urn }, path) } - pub fn into_scheme(self) -> SchemeCow<'a> { self.into_pair().0 } + pub fn into_spec(self) -> Spec { self.into_pair().0 } pub fn into_path(self) -> PathCow<'a> { self.into_pair().1 } @@ -289,6 +229,8 @@ mod tests { #[test] fn test_parse() -> Result<()> { + crate::init_tests(); + struct Case { url: &'static str, urn: &'static str, @@ -322,29 +264,29 @@ mod tests { trail: "search://keyword//root/Documents/reports/", base: "search://keyword//root/Documents/reports/", }, - // Archive portal + // Mount portal Case { - url: "archive://domain//root/Downloads/images.zip", + url: "test-mount://7z//root/Downloads/images.zip", urn: "", uri: "", - trail: "archive://domain//root/Downloads/images.zip", - base: "archive://domain//root/Downloads/images.zip", + trail: "test-mount://7z//root/Downloads/images.zip", + base: "test-mount://7z//root/Downloads/images.zip", }, - // Archive item + // Mount item Case { - url: "archive://domain:2:1//root/Downloads/images.zip/2025/city.jpg", + url: "test-mount://7z:2:1//root/Downloads/images.zip/2025/city.jpg", urn: "city.jpg", uri: "2025/city.jpg", - trail: "archive://domain:1:1//root/Downloads/images.zip/2025/", - base: "archive://domain//root/Downloads/images.zip/", + trail: "test-mount://7z:1:1//root/Downloads/images.zip/2025/", + base: "test-mount://7z//root/Downloads/images.zip/", }, // SFTP Case { - url: "sftp://my-server//root/docs/report.pdf", + url: "sftp://vps//root/docs/report.pdf", urn: "report.pdf", uri: "report.pdf", - trail: "sftp://my-server//root/docs/", - base: "sftp://my-server//root/docs/", + trail: "sftp://vps//root/docs/", + base: "sftp://vps//root/docs/", }, ]; diff --git a/yazi-shared/src/url/de.rs b/yazi-shared/src/url/de.rs index 306b0f0d..d3449501 100644 --- a/yazi-shared/src/url/de.rs +++ b/yazi-shared/src/url/de.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use serde::{Deserializer, de::{self, IntoDeserializer, MapAccess}}; -use crate::{data::BytesDeserializer, pool::SymbolCow, scheme::SchemeLike, url::UrlCow}; +use crate::{data::BytesDeserializer, pool::{InternStr, Symbol}, url::UrlCow}; pub struct UrlDeserializer<'a>(pub(super) UrlCow<'a>); @@ -32,7 +32,8 @@ impl<'de, 'a: 'de> Deserializer<'de> for UrlDeserializer<'a> { // --- Map struct MapDeserializer<'a> { kind: Option<&'static str>, - domain: Option>, + scheme: Option>, + domain: Option>, uri: Option, urn: Option, path: Option>, @@ -40,13 +41,13 @@ struct MapDeserializer<'a> { impl<'a> MapDeserializer<'a> { fn new(url: UrlCow<'a>) -> Self { - let (scheme, path) = url.into_pair(); - let kind: &'static str = scheme.kind().into(); - let (uri, urn) = scheme.ports(); + let (spec, path) = url.into_pair(); + let (uri, urn) = spec.ports(); Self { - kind: Some(kind), - domain: scheme.into_domain(), + kind: Some(spec.kind.into()), + scheme: Some(spec.scheme.intern()), + domain: Some(spec.domain.intern()), uri: Some(uri), urn: Some(urn), path: Some(path.into_encoded_bytes()), @@ -63,6 +64,8 @@ impl<'de, 'a: 'de> MapAccess<'de> for MapDeserializer<'a> { { let key = if self.kind.is_some() { Some("kind") + } else if self.scheme.is_some() { + Some("scheme") } else if self.domain.is_some() { Some("domain") } else if self.uri.is_some() { @@ -85,8 +88,11 @@ impl<'de, 'a: 'de> MapAccess<'de> for MapDeserializer<'a> { if let Some(kind) = self.kind.take() { return seed.deserialize(kind.into_deserializer()); } + if let Some(scheme) = self.scheme.take() { + return seed.deserialize(scheme.into_deserializer()); + } if let Some(domain) = self.domain.take() { - return seed.deserialize(domain.as_ref().into_deserializer()); + return seed.deserialize(domain.into_deserializer()); } if let Some(uri) = self.uri.take() { return seed.deserialize(uri.into_deserializer()); @@ -104,6 +110,7 @@ impl<'de, 'a: 'de> MapAccess<'de> for MapDeserializer<'a> { fn size_hint(&self) -> Option { Some( self.kind.is_some() as usize + + self.scheme.is_some() as usize + self.domain.is_some() as usize + self.uri.is_some() as usize + self.urn.is_some() as usize diff --git a/yazi-shared/src/url/display.rs b/yazi-shared/src/url/display.rs index ad797709..85e43a76 100644 --- a/yazi-shared/src/url/display.rs +++ b/yazi-shared/src/url/display.rs @@ -1,4 +1,4 @@ -use crate::{scheme::Encode, url::Url}; +use crate::{spec::Encode, url::Url}; pub struct Display<'a>(pub Url<'a>); diff --git a/yazi-shared/src/url/encode.rs b/yazi-shared/src/url/encode.rs index 1695e50e..8969547d 100644 --- a/yazi-shared/src/url/encode.rs +++ b/yazi-shared/src/url/encode.rs @@ -2,26 +2,23 @@ use std::fmt::{self, Display}; use percent_encoding::{CONTROLS, percent_encode}; -use crate::url::Url; +use crate::{auth::Encode as EncodeAuth, url::Url}; #[derive(Clone, Copy)] pub struct Encode<'a>(pub Url<'a>); impl Display for Encode<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use crate::scheme::Encode as E; + use crate::spec::Encode as E; let loc = percent_encode(self.0.loc().encoded_bytes(), CONTROLS); match self.0 { Url::Regular(_) => write!(f, "regular~://{loc}"), - Url::Search { domain, .. } => { - write!(f, "search~://{}{}/{loc}", E::domain(domain), E::ports((*self).into())) - } - Url::Archive { domain, .. } => { - write!(f, "archive~://{}{}/{loc}", E::domain(domain), E::ports((*self).into())) - } - Url::Sftp { domain, .. } => { - write!(f, "sftp~://{}{}/{loc}", E::domain(domain), E::ports((*self).into())) + Url::Search { auth, .. } + | Url::Mount { auth, .. } + | Url::Scope { auth, .. } + | Url::Sftp { auth, .. } => { + write!(f, "{}{}/{loc}", EncodeAuth(auth, true), E::ports((*self).into())) } } } diff --git a/yazi-shared/src/url/like.rs b/yazi-shared/src/url/like.rs index 48e7b847..471c808b 100644 --- a/yazi-shared/src/url/like.rs +++ b/yazi-shared/src/url/like.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, ffi::OsStr, path::Path}; use anyhow::Result; -use crate::{path::{AsPathRef, EndsWithError, JoinError, PathDyn, StartsWithError, StripPrefixError}, scheme::{SchemeKind, SchemeRef}, strand::{AsStrand, Strand}, url::{AsUrl, Components, Display, Url, UrlBuf, UrlCow}}; +use crate::{auth::{Auth, AuthKind}, path::{AsPathRef, EndsWithError, JoinError, PathDyn, StartsWithError, StripPrefixError}, spec::Spec, strand::{AsStrand, Strand}, url::{AsUrl, Components, Display, Url, UrlBuf, UrlCow}}; pub trait UrlLike where @@ -10,6 +10,8 @@ where { fn as_local(&self) -> Option<&Path> { self.as_url().as_local() } + fn auth(&self) -> &Auth { self.as_url().auth() } + fn base(&self) -> Url<'_> { self.as_url().base() } fn components(&self) -> Components<'_> { self.as_url().into() } @@ -28,15 +30,11 @@ where fn is_absolute(&self) -> bool { self.as_url().is_absolute() } - fn is_archive(&self) -> bool { self.as_url().is_archive() } - - fn is_internal(&self) -> bool { self.as_url().is_internal() } - fn is_regular(&self) -> bool { self.as_url().is_regular() } fn is_search(&self) -> bool { self.as_url().is_search() } - fn kind(&self) -> SchemeKind { self.as_url().kind() } + fn kind(&self) -> AuthKind { self.as_url().kind() } fn loc(&self) -> PathDyn<'_> { self.as_url().loc() } @@ -48,7 +46,7 @@ where fn parent(&self) -> Option> { self.as_url().parent() } - fn scheme(&self) -> SchemeRef<'_> { self.as_url().scheme() } + fn spec(&self) -> Spec { self.as_url().spec() } fn stem(&self) -> Option> { self.as_url().stem() } diff --git a/yazi-shared/src/url/lua.rs b/yazi-shared/src/url/lua.rs index 559a5fff..65ef2086 100644 --- a/yazi-shared/src/url/lua.rs +++ b/yazi-shared/src/url/lua.rs @@ -1,7 +1,7 @@ use mlua::{AnyUserData, ExternalError, ExternalResult, IntoLua, Lua, LuaString, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, UserDataRegistry, Value}; use yazi_shim::mlua::UserDataFieldsExt; -use crate::{LOG_LEVEL, path::{PathBufDyn, PathLike, StripPrefixError}, scheme::{SchemeCow, SchemeLike}, strand::{StrandLike, ToStrand}, url::{UrlBuf, UrlBufInventory, UrlCow, UrlLike}}; +use crate::{LOG_LEVEL, path::{PathBufDyn, PathLike, StripPrefixError}, spec::Spec, strand::{StrandLike, ToStrand}, url::{UrlBuf, UrlBufInventory, UrlCow, UrlLike}}; pub type UrlRef = UserDataRef; @@ -41,16 +41,16 @@ impl UrlBuf { match other { Value::String(s) => { let b = s.as_bytes(); - let (scheme, path) = SchemeCow::parse(&b)?; - if scheme.covariant(self.scheme()) { + let (spec, path) = Spec::parse(&b)?; + if spec.covariant(self.auth()) { self.try_join(path).into_lua_err()?.into_lua(lua) } else { - UrlCow::try_from((scheme, path))?.into_owned().into_lua(lua) + UrlCow::try_from((spec, path))?.into_owned().into_lua(lua) } } Value::UserData(ref ud) => { if let Ok(url) = ud.borrow::() { - if url.scheme().covariant(self.scheme()) { + if url.auth().covariant(self.auth()) { self.try_join(url.loc()).into_lua_err()?.into_lua(lua) } else { Ok(other) @@ -106,14 +106,8 @@ impl UserData for UrlBuf { }); fields.add_cached_field("parent", |_, me| Ok(me.parent().map(Self::from))); - fields.add_cached_field("scheme", |_, me| Ok(me.scheme().to_owned())); - fields.add_cached_field("domain", |lua, me| { - me.scheme().domain().map(|s| lua.create_string(s)).transpose() - }); + fields.add_cached_field("spec", |_, me| Ok(me.spec())); - fields.add_field_method_get("is_regular", |_, me| Ok(me.is_regular())); - fields.add_field_method_get("is_search", |_, me| Ok(me.is_search())); - fields.add_field_method_get("is_archive", |_, me| Ok(me.is_archive())); fields.add_field_method_get("is_absolute", |_, me| Ok(me.is_absolute())); fields.add_field_method_get("has_root", |_, me| Ok(me.has_root())); } diff --git a/yazi-shared/src/url/traits.rs b/yazi-shared/src/url/traits.rs index c1ea5387..547b5824 100644 --- a/yazi-shared/src/url/traits.rs +++ b/yazi-shared/src/url/traits.rs @@ -40,9 +40,10 @@ impl AsUrl for UrlBuf { fn as_url(&self) -> Url<'_> { match self { Self::Regular(loc) => Url::Regular(loc.as_loc()), - Self::Search { loc, domain } => Url::Search { loc: loc.as_loc(), domain }, - Self::Archive { loc, domain } => Url::Archive { loc: loc.as_loc(), domain }, - Self::Sftp { loc, domain } => Url::Sftp { loc: loc.as_loc(), domain }, + Self::Search { loc, auth } => Url::Search { loc: loc.as_loc(), auth }, + Self::Mount { loc, auth } => Url::Mount { loc: loc.as_loc(), auth }, + Self::Scope { loc, auth } => Url::Scope { loc: loc.as_loc(), auth }, + Self::Sftp { loc, auth } => Url::Sftp { loc: loc.as_loc(), auth }, } } } @@ -61,9 +62,10 @@ impl AsUrl for UrlCow<'_> { fn as_url(&self) -> Url<'_> { match self { Self::Regular(loc) => Url::Regular(loc.as_loc()), - Self::Search { loc, domain } => Url::Search { loc: loc.as_loc(), domain }, - Self::Archive { loc, domain } => Url::Archive { loc: loc.as_loc(), domain }, - Self::Sftp { loc, domain } => Url::Sftp { loc: loc.as_loc(), domain }, + Self::Search { loc, auth } => Url::Search { loc: loc.as_loc(), auth }, + Self::Mount { loc, auth } => Url::Mount { loc: loc.as_loc(), auth }, + Self::Scope { loc, auth } => Url::Scope { loc: loc.as_loc(), auth }, + Self::Sftp { loc, auth } => Url::Sftp { loc: loc.as_loc(), auth }, } } } diff --git a/yazi-shared/src/url/url.rs b/yazi-shared/src/url/url.rs index a17abc9a..07fdbb8c 100644 --- a/yazi-shared/src/url/url.rs +++ b/yazi-shared/src/url/url.rs @@ -1,18 +1,19 @@ -use std::{borrow::Cow, ffi::OsStr, fmt::{Debug, Formatter}, path::{Path, PathBuf}}; +use std::{borrow::Cow, ffi::OsStr, fmt::{Debug, Formatter}, path::{Path, PathBuf}, sync::Arc}; use anyhow::Result; use hashbrown::Equivalent; use serde::Serialize; use super::Encode as EncodeUrl; -use crate::{loc::{Loc, LocBuf}, path::{AsPath, AsPathRef, EndsWithError, JoinError, PathBufDyn, PathDyn, PathDynError, PathLike, StartsWithError, StripPrefixError, StripSuffixError}, pool::{InternStr, Pool}, scheme::{Encode as EncodeScheme, SchemeCow, SchemeKind, SchemeRef}, strand::{AsStrand, Strand}, url::{AsUrl, Components, UrlBuf, UrlCow}}; +use crate::{auth::{Auth, AuthKind}, loc::{Loc, LocBuf}, path::{AsPath, AsPathRef, EndsWithError, JoinError, PathBufDyn, PathDyn, PathDynError, PathLike, StartsWithError, StripPrefixError, StripSuffixError}, spec::{Encode as EncodeSpec, ParsedSpec, Spec}, strand::{AsStrand, Strand}, url::{AsUrl, Components, UrlBuf, UrlCow}}; #[derive(Clone, Copy, Eq, Hash, PartialEq)] pub enum Url<'a> { Regular(Loc<'a>), - Search { loc: Loc<'a>, domain: &'a str }, - Archive { loc: Loc<'a>, domain: &'a str }, - Sftp { loc: Loc<'a, &'a typed_path::UnixPath>, domain: &'a str }, + Search { loc: Loc<'a>, auth: &'a Arc }, + Mount { loc: Loc<'a>, auth: &'a Arc }, + Scope { loc: Loc<'a, &'a typed_path::UnixPath>, auth: &'a Arc }, + Sftp { loc: Loc<'a, &'a typed_path::UnixPath>, auth: &'a Arc }, } // --- Eq @@ -31,7 +32,7 @@ impl Debug for Url<'_> { if self.is_regular() { write!(f, "{}", self.loc().display()) } else { - write!(f, "{}{}", EncodeScheme(*self), self.loc().display()) + write!(f, "{}{}", EncodeSpec(*self), self.loc().display()) } } } @@ -39,9 +40,9 @@ impl Debug for Url<'_> { impl Serialize for Url<'_> { fn serialize(&self, serializer: S) -> Result { let (kind, loc) = (self.kind(), self.loc()); - match (kind == SchemeKind::Regular, loc.to_str()) { + match (kind == AuthKind::Regular, loc.to_str()) { (true, Ok(s)) => serializer.serialize_str(s), - (false, Ok(s)) => serializer.serialize_str(&format!("{}{s}", EncodeScheme(*self))), + (false, Ok(s)) => serializer.serialize_str(&format!("{}{s}", EncodeSpec(*self))), (_, Err(_)) => serializer.collect_str(&EncodeUrl(*self)), } } @@ -53,6 +54,17 @@ impl<'a> Url<'a> { self.loc().as_os().ok().filter(|_| self.kind().is_local()) } + #[inline] + pub fn auth(self) -> &'a Auth { + match self { + Self::Regular(_) => &Auth::DEFAULT, + Self::Search { auth, .. } + | Self::Mount { auth, .. } + | Self::Scope { auth, .. } + | Self::Sftp { auth, .. } => auth, + } + } + #[inline] pub fn as_regular(self) -> Result { Ok(Self::Regular(Loc::bare(self.loc().as_os()?))) @@ -61,9 +73,10 @@ impl<'a> Url<'a> { pub fn base(self) -> Self { match self { Self::Regular(loc) => Self::Regular(Loc::bare(loc.base())), - Self::Search { loc, domain } => Self::Search { loc: Loc::zeroed(loc.base()), domain }, - Self::Archive { loc, domain } => Self::Archive { loc: Loc::zeroed(loc.base()), domain }, - Self::Sftp { loc, domain } => Self::Sftp { loc: Loc::bare(loc.base()), domain }, + Self::Search { loc, auth } => Self::Search { loc: Loc::zeroed(loc.base()), auth }, + Self::Mount { loc, auth } => Self::Mount { loc: Loc::zeroed(loc.base()), auth }, + Self::Scope { loc, auth } => Self::Scope { loc: Loc::bare(loc.base()), auth }, + Self::Sftp { loc, auth } => Self::Sftp { loc: Loc::bare(loc.base()), auth }, } } @@ -73,7 +86,7 @@ impl<'a> Url<'a> { #[inline] pub fn covariant(self, other: impl AsUrl) -> bool { let other = other.as_url(); - self.loc() == other.loc() && self.scheme().covariant(other.scheme()) + self.loc() == other.loc() && self.auth().covariant(other.auth()) } #[inline] @@ -81,7 +94,8 @@ impl<'a> Url<'a> { Some(match self { Self::Regular(loc) => loc.extension()?.as_strand(), Self::Search { loc, .. } => loc.extension()?.as_strand(), - Self::Archive { loc, .. } => loc.extension()?.as_strand(), + Self::Mount { loc, .. } => loc.extension()?.as_strand(), + Self::Scope { loc, .. } => loc.extension()?.as_strand(), Self::Sftp { loc, .. } => loc.extension()?.as_strand(), }) } @@ -91,7 +105,8 @@ impl<'a> Url<'a> { match self { Self::Regular(loc) => loc.has_base(), Self::Search { loc, .. } => loc.has_base(), - Self::Archive { loc, .. } => loc.has_base(), + Self::Mount { loc, .. } => loc.has_base(), + Self::Scope { loc, .. } => loc.has_base(), Self::Sftp { loc, .. } => loc.has_base(), } } @@ -104,7 +119,8 @@ impl<'a> Url<'a> { match self { Self::Regular(loc) => loc.has_trail(), Self::Search { loc, .. } => loc.has_trail(), - Self::Archive { loc, .. } => loc.has_trail(), + Self::Mount { loc, .. } => loc.has_trail(), + Self::Scope { loc, .. } => loc.has_trail(), Self::Sftp { loc, .. } => loc.has_trail(), } } @@ -112,18 +128,6 @@ impl<'a> Url<'a> { #[inline] pub fn is_absolute(self) -> bool { self.loc().is_absolute() } - #[inline] - pub fn is_archive(self) -> bool { matches!(self, Self::Archive { .. }) } - - #[inline] - pub fn is_internal(self) -> bool { - match self { - Self::Regular(_) | Self::Sftp { .. } => true, - Self::Search { .. } => !self.uri().is_empty(), - Self::Archive { .. } => false, - } - } - #[inline] pub fn is_regular(self) -> bool { matches!(self, Self::Regular(_)) } @@ -131,21 +135,15 @@ impl<'a> Url<'a> { pub fn is_search(self) -> bool { matches!(self, Self::Search { .. }) } #[inline] - pub fn kind(self) -> SchemeKind { - match self { - Self::Regular(_) => SchemeKind::Regular, - Self::Search { .. } => SchemeKind::Search, - Self::Archive { .. } => SchemeKind::Archive, - Self::Sftp { .. } => SchemeKind::Sftp, - } - } + pub fn kind(self) -> AuthKind { self.auth().kind } #[inline] pub fn loc(self) -> PathDyn<'a> { match self { Self::Regular(loc) => loc.as_path(), Self::Search { loc, .. } => loc.as_path(), - Self::Archive { loc, .. } => loc.as_path(), + Self::Mount { loc, .. } => loc.as_path(), + Self::Scope { loc, .. } => loc.as_path(), Self::Sftp { loc, .. } => loc.as_path(), } } @@ -155,7 +153,8 @@ impl<'a> Url<'a> { Some(match self { Self::Regular(loc) => loc.file_name()?.as_strand(), Self::Search { loc, .. } => loc.file_name()?.as_strand(), - Self::Archive { loc, .. } => loc.file_name()?.as_strand(), + Self::Mount { loc, .. } => loc.file_name()?.as_strand(), + Self::Scope { loc, .. } => loc.file_name()?.as_strand(), Self::Sftp { loc, .. } => loc.file_name()?.as_strand(), }) } @@ -175,21 +174,24 @@ impl<'a> Url<'a> { // Search Self::Search { loc, .. } if uri.is_empty() => Self::regular(loc.parent()?), - Self::Search { loc, domain } => { - Self::Search { loc: Loc::new(loc.parent()?, loc.base(), loc.base()), domain } + Self::Search { loc, auth } => { + Self::Search { loc: Loc::new(loc.parent()?, loc.base(), loc.base()), auth } } - // Archive - Self::Archive { loc, .. } if uri.is_empty() => Self::regular(loc.parent()?), - Self::Archive { loc, domain } if uri.components().nth(1).is_none() => { - Self::Archive { loc: Loc::zeroed(loc.parent()?), domain } + // Mount + Self::Mount { loc, .. } if uri.is_empty() => Self::regular(loc.parent()?), + Self::Mount { loc, auth } if uri.components().nth(1).is_none() => { + Self::Mount { loc: Loc::zeroed(loc.parent()?), auth } } - Self::Archive { loc, domain } => { - Self::Archive { loc: Loc::floated(loc.parent()?, loc.base()), domain } + Self::Mount { loc, auth } => { + Self::Mount { loc: Loc::floated(loc.parent()?, loc.base()), auth } } + // Scope + Self::Scope { loc, auth } => Self::Scope { loc: Loc::bare(loc.parent()?), auth }, + // SFTP - Self::Sftp { loc, domain } => Self::Sftp { loc: Loc::bare(loc.parent()?), domain }, + Self::Sftp { loc, auth } => Self::Sftp { loc: Loc::bare(loc.parent()?), auth }, }) } @@ -198,15 +200,17 @@ impl<'a> Url<'a> { Self::Regular(Loc::bare(path.as_ref())) } - #[inline] - pub fn scheme(self) -> SchemeRef<'a> { - let (uri, urn) = SchemeCow::retrieve_ports(self); - match self { - Self::Regular(_) => SchemeRef::Regular { uri, urn }, - Self::Search { domain, .. } => SchemeRef::Search { domain, uri, urn }, - Self::Archive { domain, .. } => SchemeRef::Archive { domain, uri, urn }, - Self::Sftp { domain, .. } => SchemeRef::Sftp { domain, uri, urn }, - } + pub fn spec(self) -> Spec { + let auth = match self { + Self::Regular(_) => Auth::default_arc(), + Self::Search { auth, .. } + | Self::Mount { auth, .. } + | Self::Scope { auth, .. } + | Self::Sftp { auth, .. } => auth.clone(), + }; + + let (uri, urn) = Spec::retrieve_ports(self); + Spec { auth, uri, urn } } #[inline] @@ -214,7 +218,8 @@ impl<'a> Url<'a> { Some(match self { Self::Regular(loc) => loc.file_stem()?.as_strand(), Self::Search { loc, .. } => loc.file_stem()?.as_strand(), - Self::Archive { loc, .. } => loc.file_stem()?.as_strand(), + Self::Mount { loc, .. } => loc.file_stem()?.as_strand(), + Self::Scope { loc, .. } => loc.file_stem()?.as_strand(), Self::Sftp { loc, .. } => loc.file_stem()?.as_strand(), }) } @@ -222,10 +227,10 @@ impl<'a> Url<'a> { #[inline] pub fn to_owned(self) -> UrlBuf { self.into() } - pub fn to_search(self, domain: impl AsRef) -> Result { + pub fn to_search(self, query: impl AsRef) -> Result { Ok(UrlBuf::Search { - loc: LocBuf::::zeroed(self.loc().to_os_owned()?), - domain: Pool::::intern(domain), + loc: LocBuf::::zeroed(self.loc().to_os_owned()?), + auth: Auth::search(query.as_ref()), }) } @@ -234,31 +239,33 @@ impl<'a> Url<'a> { match self { Self::Regular(loc) => Self::Regular(Loc::bare(loc.trail())), - Self::Search { loc, domain } if uri.is_empty() => { - Self::Search { loc: Loc::zeroed(loc.trail()), domain } + Self::Search { loc, auth } if uri.is_empty() => { + Self::Search { loc: Loc::zeroed(loc.trail()), auth } } - Self::Search { loc, domain } => { - Self::Search { loc: Loc::new(loc.trail(), loc.base(), loc.base()), domain } + Self::Search { loc, auth } => { + Self::Search { loc: Loc::new(loc.trail(), loc.base(), loc.base()), auth } } - Self::Archive { loc, domain } if uri.is_empty() => { - Self::Archive { loc: Loc::zeroed(loc.trail()), domain } + Self::Mount { loc, auth } if uri.is_empty() => { + Self::Mount { loc: Loc::zeroed(loc.trail()), auth } } - Self::Archive { loc, domain } => { - Self::Archive { loc: Loc::new(loc.trail(), loc.base(), loc.base()), domain } + Self::Mount { loc, auth } => { + Self::Mount { loc: Loc::new(loc.trail(), loc.base(), loc.base()), auth } } - Self::Sftp { loc, domain } => Self::Sftp { loc: Loc::bare(loc.trail()), domain }, + Self::Scope { loc, auth } => Self::Scope { loc: Loc::bare(loc.trail()), auth }, + + Self::Sftp { loc, auth } => Self::Sftp { loc: Loc::bare(loc.trail()), auth }, } } pub fn triple(self) -> (PathDyn<'a>, PathDyn<'a>, PathDyn<'a>) { match self { - Self::Regular(loc) | Self::Search { loc, .. } | Self::Archive { loc, .. } => { + Self::Regular(loc) | Self::Search { loc, .. } | Self::Mount { loc, .. } => { let (base, rest, urn) = loc.triple(); (base.as_path(), rest.as_path(), urn.as_path()) } - Self::Sftp { loc, .. } => { + Self::Scope { loc, .. } | Self::Sftp { loc, .. } => { let (base, rest, urn) = loc.triple(); (base.as_path(), rest.as_path(), urn.as_path()) } @@ -268,7 +275,7 @@ impl<'a> Url<'a> { #[inline] pub fn try_ends_with(self, child: impl AsUrl) -> Result { let child = child.as_url(); - Ok(self.loc().try_ends_with(child.loc())? && self.scheme().covariant(child.scheme())) + Ok(self.loc().try_ends_with(child.loc())? && self.auth().covariant(child.auth())) } pub fn try_join(self, path: impl AsStrand) -> Result { @@ -277,26 +284,28 @@ impl<'a> Url<'a> { Ok(match self { Self::Regular(_) => UrlBuf::Regular(joined.into_os()?.into()), - Self::Search { loc, domain } if joined.try_starts_with(loc.base())? => UrlBuf::Search { - loc: LocBuf::::new(joined.try_into()?, loc.base(), loc.base()), - domain: domain.intern(), - }, - Self::Search { domain, .. } => UrlBuf::Search { - loc: LocBuf::::zeroed(joined.into_os()?), - domain: domain.intern(), + Self::Search { loc, auth } if joined.try_starts_with(loc.base())? => UrlBuf::Search { + loc: LocBuf::::new(joined.try_into()?, loc.base(), loc.base()), + auth: auth.clone(), }, + Self::Search { auth, .. } => { + UrlBuf::Search { loc: LocBuf::::zeroed(joined.into_os()?), auth: auth.clone() } + } - Self::Archive { loc, domain } if joined.try_starts_with(loc.base())? => UrlBuf::Archive { - loc: LocBuf::::floated(joined.try_into()?, loc.base()), - domain: domain.intern(), - }, - Self::Archive { domain, .. } => UrlBuf::Archive { - loc: LocBuf::::zeroed(joined.into_os()?), - domain: domain.intern(), + Self::Mount { loc, auth } if joined.try_starts_with(loc.base())? => UrlBuf::Mount { + loc: LocBuf::::floated(joined.try_into()?, loc.base()), + auth: auth.clone(), }, + Self::Mount { auth, .. } => { + UrlBuf::Mount { loc: LocBuf::::zeroed(joined.into_os()?), auth: auth.clone() } + } - Self::Sftp { domain, .. } => { - UrlBuf::Sftp { loc: joined.into_unix()?.into(), domain: domain.intern() } + Self::Scope { auth, .. } => { + UrlBuf::Scope { loc: joined.into_unix()?.into(), auth: auth.clone() } + } + + Self::Sftp { auth, .. } => { + UrlBuf::Sftp { loc: joined.into_unix()?.into(), auth: auth.clone() } } }) } @@ -309,7 +318,7 @@ impl<'a> Url<'a> { let b = rep.encoded_bytes(); if take == 0 { return UrlCow::try_from(b); - } else if SchemeKind::parse(b)?.is_some() { + } else if ParsedSpec::parse(b)?.has_scheme() { return UrlCow::try_from(b); } @@ -320,30 +329,38 @@ impl<'a> Url<'a> { let url = match self { Self::Regular(_) => UrlBuf::from(path.into_os()?), - Self::Search { loc, domain } if path.try_starts_with(loc.trail())? => UrlBuf::Search { - loc: LocBuf::::new(path.into_os()?, loc.base(), loc.trail()), - domain: domain.intern(), + Self::Search { loc, auth } if path.try_starts_with(loc.trail())? => UrlBuf::Search { + loc: LocBuf::::new(path.into_os()?, loc.base(), loc.trail()), + auth: auth.clone(), }, - Self::Archive { loc, domain } if path.try_starts_with(loc.trail())? => UrlBuf::Archive { - loc: LocBuf::::new(path.into_os()?, loc.base(), loc.trail()), - domain: domain.intern(), + Self::Mount { loc, auth } if path.try_starts_with(loc.trail())? => UrlBuf::Mount { + loc: LocBuf::::new(path.into_os()?, loc.base(), loc.trail()), + auth: auth.clone(), }, - Self::Sftp { loc, domain } if path.try_starts_with(loc.trail())? => UrlBuf::Sftp { - loc: LocBuf::::new(path.into_unix()?, loc.base(), loc.trail()), - domain: domain.intern(), + Self::Scope { loc, auth } if path.try_starts_with(loc.trail())? => UrlBuf::Scope { + loc: LocBuf::::new(path.into_unix()?, loc.base(), loc.trail()), + auth: auth.clone(), + }, + Self::Sftp { loc, auth } if path.try_starts_with(loc.trail())? => UrlBuf::Sftp { + loc: LocBuf::::new(path.into_unix()?, loc.base(), loc.trail()), + auth: auth.clone(), }, - Self::Search { domain, .. } => UrlBuf::Search { - loc: LocBuf::::saturated(path.into_os()?, self.kind()), - domain: domain.intern(), + Self::Search { auth, .. } => UrlBuf::Search { + loc: LocBuf::::saturated(path.into_os()?, self.kind()), + auth: auth.clone(), }, - Self::Archive { domain, .. } => UrlBuf::Archive { - loc: LocBuf::::saturated(path.into_os()?, self.kind()), - domain: domain.intern(), + Self::Mount { auth, .. } => UrlBuf::Mount { + loc: LocBuf::::saturated(path.into_os()?, self.kind()), + auth: auth.clone(), }, - Self::Sftp { domain, .. } => UrlBuf::Sftp { - loc: LocBuf::::saturated(path.into_unix()?, self.kind()), - domain: domain.intern(), + Self::Scope { auth, .. } => UrlBuf::Scope { + loc: LocBuf::::saturated(path.into_unix()?, self.kind()), + auth: auth.clone(), + }, + Self::Sftp { auth, .. } => UrlBuf::Sftp { + loc: LocBuf::::saturated(path.into_unix()?, self.kind()), + auth: auth.clone(), }, }; @@ -353,7 +370,7 @@ impl<'a> Url<'a> { #[inline] pub fn try_starts_with(self, base: impl AsUrl) -> Result { let base = base.as_url(); - Ok(self.loc().try_starts_with(base.loc())? && self.scheme().covariant(base.scheme())) + Ok(self.loc().try_starts_with(base.loc())? && self.auth().covariant(base.auth())) } pub fn try_strip_prefix(self, base: impl AsUrl) -> Result, StripPrefixError> { @@ -362,43 +379,19 @@ impl<'a> Url<'a> { let base = base.as_url(); let prefix = self.loc().try_strip_prefix(base.loc())?; + if self.auth().covariant(base.auth()) { + return Ok(prefix); + } match (self, base) { - // Same scheme - (U::Regular(_), U::Regular(_)) => Ok(prefix), - (U::Search { .. }, U::Search { .. }) => Ok(prefix), - (U::Archive { domain: a, .. }, U::Archive { domain: b, .. }) => { - (a == b).then_some(prefix).ok_or(Exotic) - } - (U::Sftp { domain: a, .. }, U::Sftp { domain: b, .. }) => { - (a == b).then_some(prefix).ok_or(Exotic) - } - - // Both are local files - (U::Regular(_), U::Search { .. }) => Ok(prefix), - (U::Search { .. }, U::Regular(_)) => Ok(prefix), - - // Only the entry of archives is a local file - (U::Regular(_), U::Archive { .. }) => { + // A mount portal is the local source file until it gains an inner URI. + (U::Regular(_) | U::Search { .. }, U::Mount { .. }) => { base.uri().is_empty().then_some(prefix).ok_or(NotPrefix) } - (U::Search { .. }, U::Archive { .. }) => { - base.uri().is_empty().then_some(prefix).ok_or(NotPrefix) - } - (U::Archive { .. }, U::Regular(_)) => { + (U::Mount { .. }, U::Regular(_) | U::Search { .. }) => { self.uri().is_empty().then_some(prefix).ok_or(NotPrefix) } - (U::Archive { .. }, U::Search { .. }) => { - self.uri().is_empty().then_some(prefix).ok_or(NotPrefix) - } - - // Independent virtual file space - (U::Regular(_), U::Sftp { .. }) => Err(Exotic), - (U::Search { .. }, U::Sftp { .. }) => Err(Exotic), - (U::Archive { .. }, U::Sftp { .. }) => Err(Exotic), - (U::Sftp { .. }, U::Regular(_)) => Err(Exotic), - (U::Sftp { .. }, U::Search { .. }) => Err(Exotic), - (U::Sftp { .. }, U::Archive { .. }) => Err(Exotic), + _ => Err(Exotic), } } @@ -408,43 +401,19 @@ impl<'a> Url<'a> { let other = other.as_url(); let suffix = self.loc().try_strip_suffix(other.loc())?; + if self.auth().covariant(other.auth()) { + return Ok(suffix); + } match (self, other) { - // Same scheme - (U::Regular(_), U::Regular(_)) => Ok(suffix), - (U::Search { .. }, U::Search { .. }) => Ok(suffix), - (U::Archive { domain: a, .. }, U::Archive { domain: b, .. }) => { - (a == b).then_some(suffix).ok_or(Exotic) - } - (U::Sftp { domain: a, .. }, U::Sftp { domain: b, .. }) => { - (a == b).then_some(suffix).ok_or(Exotic) - } - - // Both are local files - (U::Regular(_), U::Search { .. }) => Ok(suffix), - (U::Search { .. }, U::Regular(_)) => Ok(suffix), - - // Only the entry of archives is a local file - (U::Regular(_), U::Archive { .. }) => { + // A mount portal is the local source file until it gains an inner URI. + (U::Regular(_) | U::Search { .. }, U::Mount { .. }) => { other.uri().is_empty().then_some(suffix).ok_or(NotSuffix) } - (U::Search { .. }, U::Archive { .. }) => { - other.uri().is_empty().then_some(suffix).ok_or(NotSuffix) - } - (U::Archive { .. }, U::Regular(_)) => { + (U::Mount { .. }, U::Regular(_) | U::Search { .. }) => { self.uri().is_empty().then_some(suffix).ok_or(NotSuffix) } - (U::Archive { .. }, U::Search { .. }) => { - self.uri().is_empty().then_some(suffix).ok_or(NotSuffix) - } - - // Independent virtual file space - (U::Regular(_), U::Sftp { .. }) => Err(Exotic), - (U::Search { .. }, U::Sftp { .. }) => Err(Exotic), - (U::Archive { .. }, U::Sftp { .. }) => Err(Exotic), - (U::Sftp { .. }, U::Regular(_)) => Err(Exotic), - (U::Sftp { .. }, U::Search { .. }) => Err(Exotic), - (U::Sftp { .. }, U::Archive { .. }) => Err(Exotic), + _ => Err(Exotic), } } @@ -453,7 +422,8 @@ impl<'a> Url<'a> { match self { Self::Regular(loc) => loc.uri().as_path(), Self::Search { loc, .. } => loc.uri().as_path(), - Self::Archive { loc, .. } => loc.uri().as_path(), + Self::Mount { loc, .. } => loc.uri().as_path(), + Self::Scope { loc, .. } => loc.uri().as_path(), Self::Sftp { loc, .. } => loc.uri().as_path(), } } @@ -463,7 +433,8 @@ impl<'a> Url<'a> { match self { Self::Regular(loc) => loc.urn().as_path(), Self::Search { loc, .. } => loc.urn().as_path(), - Self::Archive { loc, .. } => loc.urn().as_path(), + Self::Mount { loc, .. } => loc.urn().as_path(), + Self::Scope { loc, .. } => loc.urn().as_path(), Self::Sftp { loc, .. } => loc.urn().as_path(), } } diff --git a/yazi-shim/src/fs/error.rs b/yazi-shim/src/fs/error.rs index 55e7796a..40eb0e94 100644 --- a/yazi-shim/src/fs/error.rs +++ b/yazi-shim/src/fs/error.rs @@ -31,6 +31,16 @@ impl From for Error { fn from(kind: io::ErrorKind) -> Self { Self::Kind(kind) } } +impl From for io::Error { + fn from(value: Error) -> Self { + match value { + Error::Kind(kind) => Self::from(kind), + Error::Raw(code) => Self::from_raw_os_error(code), + Error::Custom { kind, message, .. } => Self::new(kind, message.to_string()), + } + } +} + impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { diff --git a/yazi-vfs/Cargo.toml b/yazi-vfs/Cargo.toml index 4bbbe40c..c36d1a4b 100644 --- a/yazi-vfs/Cargo.toml +++ b/yazi-vfs/Cargo.toml @@ -14,9 +14,10 @@ workspace = true [dependencies] yazi-binding = { path = "../yazi-binding", version = "26.5.6" } -yazi-codegen = { path = "../yazi-codegen", version = "26.5.6" } +yazi-config = { path = "../yazi-config", version = "26.5.6" } yazi-fs = { path = "../yazi-fs", version = "26.5.6" } yazi-macro = { path = "../yazi-macro", version = "26.5.6" } +yazi-runner = { path = "../yazi-runner", version = "26.5.6" } yazi-sftp = { path = "../yazi-sftp", version = "26.5.6" } yazi-shared = { path = "../yazi-shared", version = "26.5.6" } yazi-shim = { path = "../yazi-shim", version = "26.5.6" } @@ -31,8 +32,6 @@ hashbrown = { workspace = true } mlua = { workspace = true } parking_lot = { workspace = true } russh = { workspace = true } -serde = { workspace = true } tokio = { workspace = true } -toml = { workspace = true } tracing = { workspace = true } typed-path = { workspace = true } diff --git a/yazi-vfs/src/cha.rs b/yazi-vfs/src/cha.rs index d93dcb8c..639b430d 100644 --- a/yazi-vfs/src/cha.rs +++ b/yazi-vfs/src/cha.rs @@ -3,7 +3,7 @@ use std::io; use yazi_fs::cha::{Cha, ChaKind}; use yazi_shared::url::AsUrl; -use crate::provider; +use crate::engine; pub trait VfsCha: Sized { fn from_url(url: impl AsUrl) -> impl Future>; @@ -17,7 +17,7 @@ impl VfsCha for Cha { #[inline] async fn from_url(url: impl AsUrl) -> io::Result { let url = url.as_url(); - Ok(Self::from_follow(url, provider::symlink_metadata(url).await?).await) + Ok(Self::from_follow(url, engine::symlink_metadata(url).await?).await) } async fn from_follow(url: U, mut cha: Self) -> Self @@ -29,7 +29,7 @@ impl VfsCha for Cha { if cha.is_link() { retain |= ChaKind::FOLLOW; - cha = provider::metadata(url).await.unwrap_or(cha); + cha = engine::metadata(url).await.unwrap_or(cha); } cha.attach(retain) diff --git a/yazi-vfs/src/config/mod.rs b/yazi-vfs/src/config/mod.rs deleted file mode 100644 index e35a3ea7..00000000 --- a/yazi-vfs/src/config/mod.rs +++ /dev/null @@ -1 +0,0 @@ -yazi_macro::mod_flat!(service services sftp vfs); diff --git a/yazi-vfs/src/config/service.rs b/yazi-vfs/src/config/service.rs deleted file mode 100644 index 283f7db7..00000000 --- a/yazi-vfs/src/config/service.rs +++ /dev/null @@ -1,19 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::config::ServiceSftp; - -#[derive(Deserialize, Serialize)] -#[serde(tag = "type", rename_all = "kebab-case")] -pub enum Service { - Sftp(ServiceSftp), -} - -impl TryFrom<&'static Service> for &'static ServiceSftp { - type Error = &'static str; - - fn try_from(value: &'static Service) -> Result { - match value { - Service::Sftp(p) => Ok(p), - } - } -} diff --git a/yazi-vfs/src/config/services.rs b/yazi-vfs/src/config/services.rs deleted file mode 100644 index 1264c0ff..00000000 --- a/yazi-vfs/src/config/services.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::ops::Deref; - -use anyhow::Result; -use hashbrown::HashMap; -use serde::{Deserialize, Deserializer, Serialize, de, de::{MapAccess, Visitor}}; -use yazi_codegen::DeserializeOver; -use yazi_shim::toml::DeserializeOverWith; - -use crate::config::Service; - -#[derive(Serialize, Deserialize, DeserializeOver)] -pub struct Services(HashMap); - -impl Deref for Services { - type Target = HashMap; - - fn deref(&self) -> &Self::Target { &self.0 } -} - -impl DeserializeOverWith for Services { - fn deserialize_over_with<'de, D: Deserializer<'de>>(self, de: D) -> Result { - struct V(Services); - - impl<'de> Visitor<'de> for V { - type Value = Services; - - fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.write_str("a map of VFS services") - } - - fn visit_map>(mut self, mut map: M) -> Result { - while let Some(key) = map.next_key::()? { - if key.is_empty() || key.len() > 20 { - return Err(de::Error::custom(format!( - "VFS name `{key}` must be between 1 and 20 characters" - ))); - } else if !key.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-')) { - return Err(de::Error::custom(format!("VFS name `{key}` must be in kebab-case"))); - } - self.0.0.insert(key, map.next_value()?); - } - Ok(self.0) - } - } - - de.deserialize_map(V(self)) - } -} diff --git a/yazi-vfs/src/config/vfs.rs b/yazi-vfs/src/config/vfs.rs deleted file mode 100644 index 2b90389b..00000000 --- a/yazi-vfs/src/config/vfs.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::io; - -use anyhow::{Context, Result}; -use serde::{Deserialize, Serialize}; -use tokio::sync::OnceCell; -use yazi_codegen::{DeserializeOver, DeserializeOver1}; -use yazi_fs::{Xdg, ok_or_not_found}; -use yazi_shim::toml::DeserializeOver; - -use super::Service; -use crate::config::Services; - -#[derive(Deserialize, Serialize, DeserializeOver, DeserializeOver1)] -pub struct Vfs { - pub services: Services, -} - -impl Vfs { - pub async fn load() -> io::Result<&'static Self> { - pub static LOADED: OnceCell = OnceCell::const_new(); - - async fn init() -> io::Result { - tokio::task::spawn_blocking(|| -> anyhow::Result { - let vfs: Vfs = toml::from_str(&yazi_macro::config_preset!("vfs"))?; - Ok(vfs.deserialize_over(&Vfs::read()?)?) - }) - .await? - .map_err(io::Error::other) - } - - LOADED.get_or_try_init(init).await - } - - pub async fn service<'a, P>(name: &str) -> io::Result<(&'a str, P)> - where - P: TryFrom<&'a Service, Error = &'static str>, - { - let Some((key, value)) = Self::load().await?.services.get_key_value(name) else { - return Err(io::Error::other(format!("No such VFS service: {name}"))); - }; - match value.try_into() { - Ok(p) => Ok((key.as_str(), p)), - Err(e) => Err(io::Error::other(format!("VFS service `{key}` has wrong type: {e}"))), - } - } - - fn read() -> Result { - 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:?}")) - } -} diff --git a/yazi-vfs/src/provider/sftp/absolute.rs b/yazi-vfs/src/engine/absolute.rs similarity index 53% rename from yazi-vfs/src/provider/sftp/absolute.rs rename to yazi-vfs/src/engine/absolute.rs index 567d804b..53515425 100644 --- a/yazi-vfs/src/provider/sftp/absolute.rs +++ b/yazi-vfs/src/engine/absolute.rs @@ -1,18 +1,15 @@ use yazi_fs::CWD; use yazi_shared::url::{UrlCow, UrlLike}; -pub fn try_absolute<'a, U>(url: U) -> Option> +pub(crate) fn try_absolute_impl<'a, U>(url: U) -> Option> where U: Into>, { - try_absolute_impl(url.into()) -} - -fn try_absolute_impl<'a>(url: UrlCow<'a>) -> Option> { + let url = url.into(); if url.is_absolute() { Some(url) } else if let cwd = CWD.load() - && cwd.scheme().covariant(url.scheme()) + && cwd.auth().covariant(url.auth()) { Some(cwd.try_join(url.loc()).ok()?.into()) } else { diff --git a/yazi-vfs/src/provider/calculator.rs b/yazi-vfs/src/engine/calculator.rs similarity index 96% rename from yazi-vfs/src/provider/calculator.rs rename to yazi-vfs/src/engine/calculator.rs index e3aba5d0..bf5cfa5d 100644 --- a/yazi-vfs/src/provider/calculator.rs +++ b/yazi-vfs/src/engine/calculator.rs @@ -1,7 +1,7 @@ use std::{collections::VecDeque, io, time::{Duration, Instant}}; use either::Either; -use yazi_fs::{cha::Cha, provider::{DirReader, FileHolder}}; +use yazi_fs::{cha::Cha, engine::{DirReader, FileHolder}}; use yazi_shared::url::{AsUrl, UrlBuf}; use super::ReadDir; diff --git a/yazi-vfs/src/provider/copier.rs b/yazi-vfs/src/engine/copier.rs similarity index 90% rename from yazi-vfs/src/provider/copier.rs rename to yazi-vfs/src/engine/copier.rs index 358c1b64..1b952fa7 100644 --- a/yazi-vfs/src/provider/copier.rs +++ b/yazi-vfs/src/engine/copier.rs @@ -2,17 +2,17 @@ use std::{io::{self, SeekFrom}, sync::{Arc, atomic::{AtomicU64, Ordering}}}; use futures::{StreamExt, TryStreamExt}; use tokio::{io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt, BufReader, BufWriter}, select, sync::{mpsc, oneshot}}; -use yazi_fs::{cha::Cha, provider::{Attrs, FileBuilder}}; +use yazi_fs::{cha::Cha, engine::{Attrs, FileBuilder}}; use yazi_shared::url::{Url, UrlBuf}; -use crate::provider::{self, Gate, RwFile}; +use crate::engine::{self, Demand, RwFile}; const BUF_SIZE: usize = 512 * 1024; const PER_CHUNK: u64 = 8 * 1024 * 1024; pub(super) async fn copy_impl(from: Url<'_>, to: Url<'_>, attrs: Attrs) -> io::Result { - let src = provider::open(from).await?; - let dist = provider::create(to).await?; + let src = engine::open(from).await?; + let dist = engine::create(to).await?; let mut reader = BufReader::with_capacity(BUF_SIZE, src); let mut writer = BufWriter::with_capacity(BUF_SIZE, dist); @@ -24,7 +24,7 @@ pub(super) async fn copy_impl(from: Url<'_>, to: Url<'_>, attrs: Attrs) -> io::R Ok(written) } -pub(super) fn copy_with_progress_impl( +pub(super) fn copy_progressive_impl( from: UrlBuf, to: UrlBuf, attrs: Attrs, @@ -65,10 +65,10 @@ impl ProgressiveCopier { } async fn init(&self) -> io::Result<(Cha, RwFile, RwFile)> { - let src = provider::open(&self.from).await?; + let src = engine::open(&self.from).await?; let cha = src.metadata().await?; - let dist = provider::create(&self.to).await?; + let dist = engine::create(&self.to).await?; dist.set_len(cha.len).await?; Ok((cha, src, dist)) } @@ -122,11 +122,11 @@ impl ProgressiveCopier { let mut src = BufReader::with_capacity(BUF_SIZE, match src { Some(f) => f, - None => provider::open(&self.from).await?, + None => engine::open(&self.from).await?, }); let mut dist = BufWriter::with_capacity(BUF_SIZE, match dist { Some(f) => f, - None => Gate::default().write(true).open(&self.to).await?, + None => Demand::default().write(true).open(&self.to).await?, }); src.seek(SeekFrom::Start(offset)).await?; diff --git a/yazi-vfs/src/provider/gate.rs b/yazi-vfs/src/engine/demand.rs similarity index 59% rename from yazi-vfs/src/provider/gate.rs rename to yazi-vfs/src/engine/demand.rs index 3210dd16..ecbbf140 100644 --- a/yazi-vfs/src/provider/gate.rs +++ b/yazi-vfs/src/engine/demand.rs @@ -2,40 +2,32 @@ use std::io; use mlua::{AnyUserData, IntoLuaMulti, UserData, UserDataMethods, Value}; use yazi_binding::Error; -use yazi_fs::provider::{Attrs, FileBuilder}; -use yazi_shared::{scheme::SchemeKind, url::{AsUrl, UrlRef}}; +use yazi_fs::engine::{Attrs, FileBuilder}; +use yazi_shared::{auth::AuthKind, url::{AsUrl, UrlRef}}; #[derive(Clone, Copy, Default)] -pub struct Gate { - pub(super) append: bool, - pub(super) attrs: Attrs, - pub(super) create: bool, - pub(super) create_new: bool, - pub(super) read: bool, - pub(super) truncate: bool, - pub(super) write: bool, -} +pub struct Demand(yazi_fs::engine::Demand); -impl FileBuilder for Gate { +impl FileBuilder for Demand { type File = super::RwFile; fn append(&mut self, append: bool) -> &mut Self { - self.append = append; + self.0.append = append; self } fn attrs(&mut self, attrs: Attrs) -> &mut Self { - self.attrs = attrs; + self.0.attrs = attrs; self } fn create(&mut self, create: bool) -> &mut Self { - self.create = create; + self.0.create = create; self } fn create_new(&mut self, create_new: bool) -> &mut Self { - self.create_new = create_new; + self.0.create_new = create_new; self } @@ -45,59 +37,33 @@ impl FileBuilder for Gate { { let url = url.as_url(); Ok(match url.kind() { - SchemeKind::Regular | SchemeKind::Search => { - self.build::().open(url).await?.into() + AuthKind::Regular | AuthKind::Search => { + self.0.build::().open(url).await?.into() } - SchemeKind::Archive => { - Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem: archive"))? + AuthKind::Mount | AuthKind::Scope => { + self.0.build::().open(url).await?.into() } - SchemeKind::Sftp => self.build::().open(url).await?.into(), + AuthKind::Sftp => self.0.build::().open(url).await?.into(), }) } fn read(&mut self, read: bool) -> &mut Self { - self.read = read; + self.0.read = read; self } fn truncate(&mut self, truncate: bool) -> &mut Self { - self.truncate = truncate; + self.0.truncate = truncate; self } fn write(&mut self, write: bool) -> &mut Self { - self.write = write; + self.0.write = write; self } } -impl Gate { - fn build(self) -> T { - let mut gate = T::default(); - if self.append { - gate.append(true); - } - gate.attrs(self.attrs); - if self.create { - gate.create(true); - } - if self.create_new { - gate.create_new(true); - } - if self.read { - gate.read(true); - } - if self.truncate { - gate.truncate(true); - } - if self.write { - gate.write(true); - } - gate - } -} - -impl UserData for Gate { +impl UserData for Demand { fn add_methods>(methods: &mut M) { methods.add_function("append", |_, (ud, append): (AnyUserData, bool)| { ud.borrow_mut::()?.append(append); diff --git a/yazi-vfs/src/provider/dir_entry.rs b/yazi-vfs/src/engine/dir_entry.rs similarity index 73% rename from yazi-vfs/src/provider/dir_entry.rs rename to yazi-vfs/src/engine/dir_entry.rs index 40b75e64..2a8b8a86 100644 --- a/yazi-vfs/src/provider/dir_entry.rs +++ b/yazi-vfs/src/engine/dir_entry.rs @@ -1,10 +1,11 @@ use std::io; -use yazi_fs::{cha::{Cha, ChaType}, provider::FileHolder}; +use yazi_fs::{cha::{Cha, ChaType}, engine::FileHolder}; use yazi_shared::{path::PathBufDyn, strand::StrandCow, url::UrlBuf}; pub enum DirEntry { - Local(yazi_fs::provider::local::DirEntry), + Local(yazi_fs::engine::local::DirEntry), + Lua(super::lua::DirEntry), Sftp(super::sftp::DirEntry), } @@ -12,6 +13,7 @@ impl FileHolder for DirEntry { async fn file_type(&self) -> io::Result { match self { Self::Local(entry) => entry.file_type().await, + Self::Lua(entry) => entry.file_type().await, Self::Sftp(entry) => entry.file_type().await, } } @@ -19,6 +21,7 @@ impl FileHolder for DirEntry { async fn metadata(&self) -> io::Result { match self { Self::Local(entry) => entry.metadata().await, + Self::Lua(entry) => entry.metadata().await, Self::Sftp(entry) => entry.metadata().await, } } @@ -26,6 +29,7 @@ impl FileHolder for DirEntry { fn name(&self) -> StrandCow<'_> { match self { Self::Local(entry) => entry.name(), + Self::Lua(entry) => entry.name(), Self::Sftp(entry) => entry.name(), } } @@ -33,6 +37,7 @@ impl FileHolder for DirEntry { fn path(&self) -> PathBufDyn { match self { Self::Local(entry) => entry.path(), + Self::Lua(entry) => entry.path(), Self::Sftp(entry) => entry.path(), } } @@ -40,6 +45,7 @@ impl FileHolder for DirEntry { fn url(&self) -> UrlBuf { match self { Self::Local(entry) => entry.url(), + Self::Lua(entry) => entry.url(), Self::Sftp(entry) => entry.url(), } } diff --git a/yazi-vfs/src/provider/provider.rs b/yazi-vfs/src/engine/engine.rs similarity index 59% rename from yazi-vfs/src/provider/provider.rs rename to yazi-vfs/src/engine/engine.rs index 3535bcb0..9a9ce8e9 100644 --- a/yazi-vfs/src/provider/provider.rs +++ b/yazi-vfs/src/engine/engine.rs @@ -1,16 +1,16 @@ use std::io; use tokio::sync::mpsc; -use yazi_fs::{cha::{Cha, ChaMode}, provider::{Attrs, Capabilities, Provider, local::Local}}; +use yazi_fs::{cha::Cha, engine::{Attrs, Capabilities, Engine, local::Local}}; use yazi_shared::{path::PathBufDyn, strand::AsStrand, url::{AsUrl, Url, UrlBuf, UrlCow}}; -use super::{Providers, ReadDir, RwFile}; +use super::{Engines, ReadDir, RwFile}; pub async fn absolute<'a, U>(url: &'a U) -> io::Result> where U: AsUrl, { - Providers::new(url.as_url()).await?.absolute().await + Engines::new(url.as_url()).await?.absolute().await } pub async fn calculate(url: U) -> io::Result @@ -19,7 +19,7 @@ where { let url = url.as_url(); if let Some(path) = url.as_local() { - yazi_fs::provider::local::SizeCalculator::total(path).await + yazi_fs::engine::local::SizeCalculator::total(path).await } else { super::SizeCalculator::total(url).await } @@ -29,21 +29,21 @@ pub async fn canonicalize(url: U) -> io::Result where U: AsUrl, { - Providers::new(url.as_url()).await?.canonicalize().await + Engines::new(url.as_url()).await?.canonicalize().await } pub async fn capabilities(url: U) -> io::Result where U: AsUrl, { - Ok(Providers::new(url.as_url()).await?.capabilities()) + Engines::new(url.as_url()).await?.capabilities().await } pub async fn casefold(url: U) -> io::Result where U: AsUrl, { - Providers::new(url.as_url()).await?.casefold().await + Engines::new(url.as_url()).await?.casefold().await } pub async fn copy(from: U, to: V, attrs: Attrs) -> io::Result @@ -55,14 +55,14 @@ where match (from.kind().is_local(), to.kind().is_local()) { (true, true) => Local::new(from).await?.copy(to.loc(), attrs).await, - (false, false) if from.scheme().covariant(to.scheme()) => { - Providers::new(from).await?.copy(to.loc(), attrs).await + (false, false) if from.auth().covariant(to.auth()) => { + Engines::new(from).await?.copy(to.loc(), attrs).await } (true, false) | (false, true) | (false, false) => super::copy_impl(from, to, attrs).await, } } -pub async fn copy_with_progress( +pub async fn copy_progressive( from: U, to: V, attrs: A, @@ -73,44 +73,44 @@ where A: Into, { let (from, to) = (from.as_url(), to.as_url()); + let attrs = attrs.into(); - match (from.kind().is_local(), to.kind().is_local()) { - (true, true) => Local::new(from).await?.copy_with_progress(to.loc(), attrs), - (false, false) if from.scheme().covariant(to.scheme()) => { - Providers::new(from).await?.copy_with_progress(to.loc(), attrs) - } - (true, false) | (false, true) | (false, false) => { - Ok(super::copy_with_progress_impl(from.to_owned(), to.to_owned(), attrs.into())) + if from.auth().covariant(to.auth()) { + let engine = Engines::new(from).await?; + if engine.capabilities().await?.copy_progressive { + return engine.copy_progressive(to.loc(), attrs); } } + + Ok(super::copy_progressive_impl(from.to_owned(), to.to_owned(), attrs)) } pub async fn create(url: U) -> io::Result where U: AsUrl, { - Providers::new(url.as_url()).await?.create().await + Engines::new(url.as_url()).await?.create().await } pub async fn create_dir(url: U) -> io::Result<()> where U: AsUrl, { - Providers::new(url.as_url()).await?.create_dir().await + Engines::new(url.as_url()).await?.create_dir().await } pub async fn create_dir_all(url: U) -> io::Result<()> where U: AsUrl, { - Providers::new(url.as_url()).await?.create_dir_all().await + Engines::new(url.as_url()).await?.create_dir_all().await } pub async fn create_new(url: U) -> io::Result where U: AsUrl, { - Providers::new(url.as_url()).await?.create_new().await + Engines::new(url.as_url()).await?.create_new().await } pub async fn hard_link(original: U, link: V) -> io::Result<()> @@ -119,8 +119,8 @@ where V: AsUrl, { let (original, link) = (original.as_url(), link.as_url()); - if original.scheme().covariant(link.scheme()) { - Providers::new(original).await?.hard_link(link.loc()).await + if original.auth().covariant(link.auth()) { + Engines::new(original).await?.hard_link(link.loc()).await } else { Err(io::Error::from(io::ErrorKind::CrossesDevices)) } @@ -132,7 +132,7 @@ where V: AsUrl, { if let (Some(a), Some(b)) = (a.as_url().as_local(), b.as_url().as_local()) { - yazi_fs::provider::local::identical(a, b).await + yazi_fs::engine::local::identical(a, b).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -142,7 +142,7 @@ pub async fn metadata(url: U) -> io::Result where U: AsUrl, { - Providers::new(url.as_url()).await?.metadata().await + Engines::new(url.as_url()).await?.metadata().await } pub async fn must_identical(a: U, b: V) -> bool @@ -157,49 +157,49 @@ pub async fn open(url: U) -> io::Result where U: AsUrl, { - Providers::new(url.as_url()).await?.open().await + Engines::new(url.as_url()).await?.open().await } pub async fn read_dir(url: U) -> io::Result where U: AsUrl, { - Providers::new(url.as_url()).await?.read_dir().await + Engines::new(url.as_url()).await?.read_dir().await } pub async fn read_link(url: U) -> io::Result where U: AsUrl, { - Providers::new(url.as_url()).await?.read_link().await + Engines::new(url.as_url()).await?.read_link().await } pub async fn remove_dir(url: U) -> io::Result<()> where U: AsUrl, { - Providers::new(url.as_url()).await?.remove_dir().await + Engines::new(url.as_url()).await?.remove_dir().await } pub async fn remove_dir_all(url: U) -> io::Result<()> where U: AsUrl, { - Providers::new(url.as_url()).await?.remove_dir_all().await + Engines::new(url.as_url()).await?.remove_dir_all().await } pub async fn remove_dir_clean(url: U) -> io::Result<()> where U: AsUrl, { - Providers::new(url.as_url()).await?.remove_dir_clean().await + Engines::new(url.as_url()).await?.remove_dir_clean().await } pub async fn remove_file(url: U) -> io::Result<()> where U: AsUrl, { - Providers::new(url.as_url()).await?.remove_file().await + Engines::new(url.as_url()).await?.remove_file().await } pub async fn rename(from: U, to: V) -> io::Result<()> @@ -208,18 +208,18 @@ where V: AsUrl, { let (from, to) = (from.as_url(), to.as_url()); - if from.scheme().covariant(to.scheme()) { - Providers::new(from).await?.rename(to.loc()).await + if from.auth().covariant(to.auth()) { + Engines::new(from).await?.rename(to.loc()).await } else { Err(io::Error::from(io::ErrorKind::CrossesDevices)) } } -pub async fn set_mode(url: U, mode: ChaMode) -> io::Result<()> +pub async fn set_attrs(url: U, attrs: Attrs) -> io::Result<()> where U: AsUrl, { - Providers::new(url.as_url()).await?.set_mode(mode).await + Engines::new(url.as_url()).await?.set_attrs(attrs).await } pub async fn symlink(link: U, original: S, is_dir: F) -> io::Result<()> @@ -228,7 +228,7 @@ where S: AsStrand, F: AsyncFnOnce() -> io::Result, { - Providers::new(link.as_url()).await?.symlink(original, is_dir).await + Engines::new(link.as_url()).await?.symlink(original, is_dir).await } pub async fn symlink_dir(link: U, original: S) -> io::Result<()> @@ -236,7 +236,7 @@ where U: AsUrl, S: AsStrand, { - Providers::new(link.as_url()).await?.symlink_dir(original).await + Engines::new(link.as_url()).await?.symlink_dir(original).await } pub async fn symlink_file(link: U, original: S) -> io::Result<()> @@ -244,21 +244,21 @@ where U: AsUrl, S: AsStrand, { - Providers::new(link.as_url()).await?.symlink_file(original).await + Engines::new(link.as_url()).await?.symlink_file(original).await } pub async fn symlink_metadata(url: U) -> io::Result where U: AsUrl, { - Providers::new(url.as_url()).await?.symlink_metadata().await + Engines::new(url.as_url()).await?.symlink_metadata().await } pub async fn trash(url: U) -> io::Result<()> where U: AsUrl, { - Providers::new(url.as_url()).await?.trash().await + Engines::new(url.as_url()).await?.trash().await } pub fn try_absolute<'a, U>(url: U) -> Option> @@ -267,9 +267,8 @@ where { let url = url.into(); match url.as_url() { - Url::Regular(_) | Url::Search { .. } => yazi_fs::provider::local::try_absolute(url), - Url::Archive { .. } => None, // TODO - Url::Sftp { .. } => crate::provider::sftp::try_absolute(url), + Url::Regular(_) | Url::Search { .. } => yazi_fs::engine::local::try_absolute(url), + Url::Mount { .. } | Url::Scope { .. } | Url::Sftp { .. } => super::try_absolute_impl(url), } } @@ -278,5 +277,5 @@ where U: AsUrl, C: AsRef<[u8]>, { - Providers::new(url.as_url()).await?.write(contents).await + Engines::new(url.as_url()).await?.write(contents).await } diff --git a/yazi-vfs/src/provider/providers.rs b/yazi-vfs/src/engine/engines.rs similarity index 68% rename from yazi-vfs/src/provider/providers.rs rename to yazi-vfs/src/engine/engines.rs index 5c1d30f5..cace26f6 100644 --- a/yazi-vfs/src/provider/providers.rs +++ b/yazi-vfs/src/engine/engines.rs @@ -1,25 +1,27 @@ use std::io; use tokio::sync::mpsc; -use yazi_fs::{cha::{Cha, ChaMode}, provider::{Attrs, Capabilities, Provider}}; +use yazi_fs::{cha::Cha, engine::{Attrs, Capabilities, Engine}}; use yazi_shared::{path::{AsPath, PathBufDyn}, strand::AsStrand, url::{Url, UrlBuf, UrlCow}}; #[derive(Clone)] -pub(super) enum Providers<'a> { - Local(yazi_fs::provider::local::Local<'a>), +pub(super) enum Engines<'a> { + Local(yazi_fs::engine::local::Local<'a>), + Lua(super::lua::Lua<'a>), Sftp(super::sftp::Sftp<'a>), } -impl<'a> Provider for Providers<'a> { +impl<'a> Engine for Engines<'a> { + type Demand = super::Demand; type File = super::RwFile; - type Gate = super::Gate; - type Me<'b> = Providers<'b>; + type Me<'b> = Engines<'b>; type ReadDir = super::ReadDir; type UrlCow = UrlCow<'a>; async fn absolute(&self) -> io::Result { match self { Self::Local(p) => p.absolute().await, + Self::Lua(p) => p.absolute().await, Self::Sftp(p) => p.absolute().await, } } @@ -27,20 +29,23 @@ impl<'a> Provider for Providers<'a> { async fn canonicalize(&self) -> io::Result { match self { Self::Local(p) => p.canonicalize().await, + Self::Lua(p) => p.canonicalize().await, Self::Sftp(p) => p.canonicalize().await, } } - fn capabilities(&self) -> Capabilities { + async fn capabilities(&self) -> io::Result { match self { - Self::Local(p) => p.capabilities(), - Self::Sftp(p) => p.capabilities(), + Self::Local(p) => p.capabilities().await, + Self::Lua(p) => p.capabilities().await, + Self::Sftp(p) => p.capabilities().await, } } async fn casefold(&self) -> io::Result { match self { Self::Local(p) => p.casefold().await, + Self::Lua(p) => p.casefold().await, Self::Sftp(p) => p.casefold().await, } } @@ -51,24 +56,27 @@ impl<'a> Provider for Providers<'a> { { match self { Self::Local(p) => p.copy(to, attrs).await, + Self::Lua(p) => p.copy(to, attrs).await, Self::Sftp(p) => p.copy(to, attrs).await, } } - fn copy_with_progress(&self, to: P, attrs: A) -> io::Result>> + fn copy_progressive(&self, to: P, attrs: A) -> io::Result>> where P: AsPath, A: Into, { match self { - Self::Local(p) => p.copy_with_progress(to, attrs), - Self::Sftp(p) => p.copy_with_progress(to, attrs), + Self::Local(p) => p.copy_progressive(to, attrs), + Self::Lua(p) => p.copy_progressive(to, attrs), + Self::Sftp(p) => p.copy_progressive(to, attrs), } } async fn create(&self) -> io::Result { Ok(match self { Self::Local(p) => p.create().await?.into(), + Self::Lua(p) => p.create().await?.into(), Self::Sftp(p) => p.create().await?.into(), }) } @@ -76,6 +84,7 @@ impl<'a> Provider for Providers<'a> { async fn create_dir(&self) -> io::Result<()> { match self { Self::Local(p) => p.create_dir().await, + Self::Lua(p) => p.create_dir().await, Self::Sftp(p) => p.create_dir().await, } } @@ -83,6 +92,7 @@ impl<'a> Provider for Providers<'a> { async fn create_dir_all(&self) -> io::Result<()> { match self { Self::Local(p) => p.create_dir_all().await, + Self::Lua(p) => p.create_dir_all().await, Self::Sftp(p) => p.create_dir_all().await, } } @@ -90,6 +100,7 @@ impl<'a> Provider for Providers<'a> { async fn create_new(&self) -> io::Result { Ok(match self { Self::Local(p) => p.create_new().await?.into(), + Self::Lua(p) => p.create_new().await?.into(), Self::Sftp(p) => p.create_new().await?.into(), }) } @@ -100,6 +111,7 @@ impl<'a> Provider for Providers<'a> { { match self { Self::Local(p) => p.hard_link(to).await, + Self::Lua(p) => p.hard_link(to).await, Self::Sftp(p) => p.hard_link(to).await, } } @@ -107,18 +119,17 @@ impl<'a> Provider for Providers<'a> { async fn metadata(&self) -> io::Result { match self { Self::Local(p) => p.metadata().await, + Self::Lua(p) => p.metadata().await, Self::Sftp(p) => p.metadata().await, } } async fn new<'b>(url: Url<'b>) -> io::Result> { - use yazi_shared::scheme::SchemeKind as K; + use yazi_shared::auth::AuthKind as K; Ok(match url.kind() { - K::Regular | K::Search => Self::Me::Local(yazi_fs::provider::local::Local::new(url).await?), - K::Archive => { - Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem: archive"))? - } + K::Regular | K::Search => Self::Me::Local(yazi_fs::engine::local::Local::new(url).await?), + K::Mount | K::Scope => Self::Me::Lua(super::lua::Lua::new(url).await?), K::Sftp => Self::Me::Sftp(super::sftp::Sftp::new(url).await?), }) } @@ -126,6 +137,7 @@ impl<'a> Provider for Providers<'a> { async fn open(&self) -> io::Result { Ok(match self { Self::Local(p) => p.open().await?.into(), + Self::Lua(p) => p.open().await?.into(), Self::Sftp(p) => p.open().await?.into(), }) } @@ -133,6 +145,7 @@ impl<'a> Provider for Providers<'a> { async fn read_dir(self) -> io::Result { Ok(match self { Self::Local(p) => Self::ReadDir::Local(p.read_dir().await?), + Self::Lua(p) => Self::ReadDir::Lua(p.read_dir().await?), Self::Sftp(p) => Self::ReadDir::Sftp(p.read_dir().await?), }) } @@ -140,6 +153,7 @@ impl<'a> Provider for Providers<'a> { async fn read_link(&self) -> io::Result { match self { Self::Local(p) => p.read_link().await, + Self::Lua(p) => p.read_link().await, Self::Sftp(p) => p.read_link().await, } } @@ -147,6 +161,7 @@ impl<'a> Provider for Providers<'a> { async fn remove_dir(&self) -> io::Result<()> { match self { Self::Local(p) => p.remove_dir().await, + Self::Lua(p) => p.remove_dir().await, Self::Sftp(p) => p.remove_dir().await, } } @@ -154,6 +169,7 @@ impl<'a> Provider for Providers<'a> { async fn remove_dir_all(&self) -> io::Result<()> { match self { Self::Local(p) => p.remove_dir_all().await, + Self::Lua(p) => p.remove_dir_all().await, Self::Sftp(p) => p.remove_dir_all().await, } } @@ -161,6 +177,7 @@ impl<'a> Provider for Providers<'a> { async fn remove_file(&self) -> io::Result<()> { match self { Self::Local(p) => p.remove_file().await, + Self::Lua(p) => p.remove_file().await, Self::Sftp(p) => p.remove_file().await, } } @@ -171,14 +188,16 @@ impl<'a> Provider for Providers<'a> { { match self { Self::Local(p) => p.rename(to).await, + Self::Lua(p) => p.rename(to).await, Self::Sftp(p) => p.rename(to).await, } } - async fn set_mode(&self, mode: ChaMode) -> io::Result<()> { + async fn set_attrs(&self, attrs: Attrs) -> io::Result<()> { match self { - Self::Local(p) => p.set_mode(mode).await, - Self::Sftp(p) => p.set_mode(mode).await, + Self::Local(p) => p.set_attrs(attrs).await, + Self::Lua(p) => p.set_attrs(attrs).await, + Self::Sftp(p) => p.set_attrs(attrs).await, } } @@ -189,6 +208,7 @@ impl<'a> Provider for Providers<'a> { { match self { Self::Local(p) => p.symlink(original, is_dir).await, + Self::Lua(p) => p.symlink(original, is_dir).await, Self::Sftp(p) => p.symlink(original, is_dir).await, } } @@ -199,6 +219,7 @@ impl<'a> Provider for Providers<'a> { { match self { Self::Local(p) => p.symlink_dir(original).await, + Self::Lua(p) => p.symlink_dir(original).await, Self::Sftp(p) => p.symlink_dir(original).await, } } @@ -209,6 +230,7 @@ impl<'a> Provider for Providers<'a> { { match self { Self::Local(p) => p.symlink_file(original).await, + Self::Lua(p) => p.symlink_file(original).await, Self::Sftp(p) => p.symlink_file(original).await, } } @@ -216,6 +238,7 @@ impl<'a> Provider for Providers<'a> { async fn symlink_metadata(&self) -> io::Result { match self { Self::Local(p) => p.symlink_metadata().await, + Self::Lua(p) => p.symlink_metadata().await, Self::Sftp(p) => p.symlink_metadata().await, } } @@ -223,6 +246,7 @@ impl<'a> Provider for Providers<'a> { async fn trash(&self) -> io::Result<()> { match self { Self::Local(p) => p.trash().await, + Self::Lua(p) => p.trash().await, Self::Sftp(p) => p.trash().await, } } @@ -230,6 +254,7 @@ impl<'a> Provider for Providers<'a> { fn url(&self) -> Url<'_> { match self { Self::Local(p) => p.url(), + Self::Lua(p) => p.url(), Self::Sftp(p) => p.url(), } } @@ -240,6 +265,7 @@ impl<'a> Provider for Providers<'a> { { match self { Self::Local(p) => p.write(contents).await, + Self::Lua(p) => p.write(contents).await, Self::Sftp(p) => p.write(contents).await, } } diff --git a/yazi-vfs/src/engine/lua/demand.rs b/yazi-vfs/src/engine/lua/demand.rs new file mode 100644 index 00000000..fee5dd6a --- /dev/null +++ b/yazi-vfs/src/engine/lua/demand.rs @@ -0,0 +1,61 @@ +use std::io; + +use yazi_fs::engine::{Attrs, Engine, FileBuilder}; +use yazi_runner::provider::ProviderJob; +use yazi_shared::url::AsUrl; + +use crate::engine::lua::{File, Lua}; + +#[derive(Clone, Copy, Default)] +pub struct Demand(yazi_fs::engine::Demand); + +impl FileBuilder for Demand { + type File = File; + + fn append(&mut self, append: bool) -> &mut Self { + self.0.append = append; + self + } + + fn attrs(&mut self, attrs: Attrs) -> &mut Self { + self.0.attrs = attrs; + self + } + + fn create(&mut self, create: bool) -> &mut Self { + self.0.create = create; + self + } + + fn create_new(&mut self, create_new: bool) -> &mut Self { + self.0.create_new = create_new; + self + } + + async fn open(&self, url: U) -> io::Result + where + U: AsUrl, + { + let engine = Lua::new(url.as_url()).await?; + let job = + ProviderJob::Open { url: engine.url.to_owned(), attrs: self.0.attrs, demand: self.0 }; + + let pos = engine.call(job).await?.0?; + Ok(File::new(engine.url, engine.run, pos, self.0)) + } + + fn read(&mut self, read: bool) -> &mut Self { + self.0.read = read; + self + } + + fn truncate(&mut self, truncate: bool) -> &mut Self { + self.0.truncate = truncate; + self + } + + fn write(&mut self, write: bool) -> &mut Self { + self.0.write = write; + self + } +} diff --git a/yazi-vfs/src/engine/lua/file.rs b/yazi-vfs/src/engine/lua/file.rs new file mode 100644 index 00000000..b503ddc7 --- /dev/null +++ b/yazi-vfs/src/engine/lua/file.rs @@ -0,0 +1,197 @@ +use std::{io::{self, SeekFrom}, pin::Pin, task::{Context, Poll, ready}}; + +use mlua::BString; +use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf}; +use yazi_fs::{cha::Cha, engine::Demand}; +use yazi_runner::{RUNNER, provider::ProviderJob}; +use yazi_shared::{event::Cmd, url::UrlBuf}; + +type Fut = Pin> + Send + Sync + 'static>>; + +pub struct File { + url: UrlBuf, + pos: u64, + run: &'static Cmd, + demand: Demand, + pending_read: Option>, + pending_seek: Option, + pending_write: Option>, +} + +enum SeekState { + NonBlocking(u64), + Blocking(i64, Fut), +} + +impl File { + pub(super) fn new(url: impl Into, run: &'static Cmd, pos: u64, demand: Demand) -> Self { + Self { + url: url.into(), + pos, + run, + demand, + pending_read: None, + pending_seek: None, + pending_write: None, + } + } + + pub async fn set_len(&self, size: u64) -> io::Result<()> { + let url = self.url.clone(); + Ok(RUNNER.provide(self.run, ProviderJob::SetLen { url, size }).await.ok()?) + } + + pub async fn set_attrs(&self, attrs: yazi_fs::engine::Attrs) -> io::Result<()> { + let url = self.url.clone(); + Ok(RUNNER.provide(self.run, ProviderJob::SetAttrs { url, attrs }).await.ok()?) + } + + pub(crate) async fn metadata(&self) -> io::Result { + let url = self.url.clone(); + Ok(RUNNER.provide(self.run, ProviderJob::Metadata { url }).await.0?) + } + + fn write_impl( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bytes: impl FnOnce() -> Vec, + ) -> Poll> { + let me = self.get_mut(); + if !me.demand.write { + return Poll::Ready(Err(io::ErrorKind::PermissionDenied.into())); + } + + let bytes = bytes(); + if bytes.is_empty() { + return Poll::Ready(Ok(0)); + } + + if me.pending_write.is_none() { + let (run, len) = (me.run, bytes.len()); + let job = ProviderJob::Write { url: me.url.clone(), offset: me.pos, bytes }; + me.pending_write = Some(Box::pin(async move { + RUNNER.provide(run, job).await.ok()?; + Ok(len) + })); + } + + let result = ready!(me.pending_write.as_mut().unwrap().as_mut().poll(cx)); + me.pending_write = None; + + Poll::Ready(result.inspect(|&n| { + me.pos = me.pos.checked_add(n as u64).expect("offset overflow"); + })) + } +} + +impl AsyncRead for File { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let me = self.get_mut(); + if !me.demand.read { + return Poll::Ready(Err(io::ErrorKind::PermissionDenied.into())); + } else if buf.remaining() == 0 { + return Poll::Ready(Ok(())); + } + + if me.pending_read.is_none() { + let run = me.run; + let job = + ProviderJob::Read { url: me.url.clone(), offset: me.pos, len: buf.remaining() }; + me.pending_read = Some(Box::pin(async move { Ok(RUNNER.provide(run, job).await.0?) })); + } + + let result = ready!(me.pending_read.as_mut().unwrap().as_mut().poll(cx)); + me.pending_read = None; + + Poll::Ready(result.map(|bytes| { + let n = bytes.len().min(buf.remaining()); + buf.put_slice(&bytes[..n]); + me.pos = me.pos.checked_add(n as u64).expect("offset overflow"); + })) + } +} + +impl AsyncSeek for File { + fn start_seek(self: Pin<&mut Self>, position: SeekFrom) -> io::Result<()> { + let me = self.get_mut(); + if me.pending_seek.is_some() { + return Err(io::Error::other("call poll_complete before start_seek")); + } + + me.pending_seek = Some(match position { + SeekFrom::Start(n) => SeekState::NonBlocking(n), + SeekFrom::Current(n) => me + .pos + .checked_add_signed(n) + .map(SeekState::NonBlocking) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "seek overflow"))?, + SeekFrom::End(n) => { + let run = me.run; + let job = ProviderJob::Metadata { url: me.url.clone() }; + SeekState::Blocking( + n, + Box::pin(async move { Ok(RUNNER.provide::(run, job).await.0?.len) }), + ) + } + }); + + Ok(()) + } + + fn poll_complete(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let me = self.get_mut(); + let Some(state) = &mut me.pending_seek else { + return Poll::Ready(Ok(me.pos)); + }; + + let result = match state { + SeekState::NonBlocking(n) => Ok(*n), + SeekState::Blocking(n, fut) => ready!(fut.as_mut().poll(cx)).and_then(|len| { + len + .checked_add_signed(*n) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "seek overflow")) + }), + }; + if let Ok(n) = result { + me.pos = n; + } + + me.pending_seek = None; + Poll::Ready(result) + } +} + +impl AsyncWrite for File { + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { + self.write_impl(cx, || buf.to_vec()) + } + + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + let len = bufs.iter().map(|b| b.len()).sum(); + self.write_impl(cx, || { + let mut bytes = Vec::with_capacity(len); + for b in bufs { + bytes.extend_from_slice(b); + } + bytes + }) + } + + fn is_write_vectored(&self) -> bool { true } +} diff --git a/yazi-vfs/src/engine/lua/lua.rs b/yazi-vfs/src/engine/lua/lua.rs new file mode 100644 index 00000000..cb4cdf65 --- /dev/null +++ b/yazi-vfs/src/engine/lua/lua.rs @@ -0,0 +1,205 @@ +use std::io; + +use mlua::FromLua; +use tokio::sync::mpsc; +use yazi_binding::MpscTx; +use yazi_config::vfs::{ServiceLua, Vfs}; +use yazi_fs::{cha::Cha, engine::{Attrs, Capabilities, Engine}, file::Files}; +use yazi_runner::{RUNNER, provider::{ProvideResult, ProviderJob}}; +use yazi_shared::{event::Cmd, path::{AsPath, PathBufDyn}, strand::AsStrand, url::{AsUrl, Url, UrlBuf, UrlCow}}; + +use crate::engine::lua::ReadDir; + +#[derive(Clone)] +pub struct Lua<'a> { + pub(super) url: Url<'a>, + + pub(super) run: &'static Cmd, +} + +impl<'a> Engine for Lua<'a> { + type Demand = super::Demand; + type File = super::File; + type Me<'b> = Lua<'b>; + type ReadDir = ReadDir; + type UrlCow = UrlCow<'static>; + + async fn absolute(&self) -> io::Result { + let url = self.url.to_owned(); + + Ok(self.call::(ProviderJob::Absolute { url }).await?.0?.into()) + } + + async fn canonicalize(&self) -> io::Result { + let url = self.url.to_owned(); + + Ok(self.call(ProviderJob::Canonicalize { url }).await?.0?) + } + + async fn capabilities(&self) -> io::Result { + Ok(self.call(ProviderJob::Capabilities).await?.0?) + } + + async fn casefold(&self) -> io::Result { + let url = self.url.to_owned(); + + Ok(self.call(ProviderJob::Casefold { url }).await?.0?) + } + + async fn copy

(&self, to: P, attrs: Attrs) -> io::Result + where + P: AsPath, + { + let from = self.url.to_owned(); + let to = to.as_path().to_owned(); + + Ok(self.call(ProviderJob::Copy { from, to, attrs }).await?.0?) + } + + fn copy_progressive(&self, to: P, attrs: A) -> io::Result>> + where + P: AsPath, + A: Into, + { + let (tx, rx) = mpsc::channel(20); + let job = ProviderJob::CopyProgressive { + from: self.url.to_owned(), + to: to.as_path().to_owned(), + attrs: attrs.into(), + tx: MpscTx::map(tx.clone(), Ok), + }; + + let run = self.run; + tokio::spawn(async move { + match RUNNER.provide(run, job).await.ok() { + Ok(()) => tx.send(Ok(0)).await.ok(), + Err(e) => tx.send(Err(e.into())).await.ok(), + }; + }); + + Ok(rx) + } + + async fn create_dir(&self) -> io::Result<()> { + let url = self.url.to_owned(); + + Ok(self.call(ProviderJob::CreateDir { url }).await?.ok()?) + } + + async fn hard_link

(&self, to: P) -> io::Result<()> + where + P: AsPath, + { + let from = self.url.to_owned(); + let to = to.as_path().to_owned(); + + Ok(self.call(ProviderJob::HardLink { from, to }).await?.ok()?) + } + + async fn metadata(&self) -> io::Result { + let url = self.url.to_owned(); + + Ok(self.call(ProviderJob::Metadata { url }).await?.0?) + } + + async fn new<'b>(url: Url<'b>) -> io::Result> { + let (Url::Mount { auth, .. } | Url::Scope { auth, .. }) = url else { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + format!("Not a custom VFS URL: {url:?}"), + )); + }; + + let service = Vfs::service::<&ServiceLua>(&auth.domain)?; + if service.scheme != auth.scheme { + return Err(io::Error::other(format!("No such custom VFS authority: {auth}"))); + } + + Ok(Self::Me { url, run: &service.run }) + } + + async fn read_dir(self) -> io::Result { + let url = self.url.to_owned(); + let files: Files = self.call(ProviderJob::ReadDir { url }).await?.0?; + + Ok(ReadDir { files: files.0.into_iter() }) + } + + async fn read_link(&self) -> io::Result { + let url = self.url.to_owned(); + + Ok(self.call(ProviderJob::ReadLink { url }).await?.0?) + } + + async fn remove_dir(&self) -> io::Result<()> { + let url = self.url.to_owned(); + + Ok(self.call(ProviderJob::RemoveDir { url }).await?.ok()?) + } + + async fn remove_file(&self) -> io::Result<()> { + let url = self.url.to_owned(); + + Ok(self.call(ProviderJob::RemoveFile { url }).await?.ok()?) + } + + async fn rename

(&self, to: P) -> io::Result<()> + where + P: AsPath, + { + let from = self.url.to_owned(); + let to = to.as_path().to_owned(); + + Ok(self.call(ProviderJob::Rename { from, to }).await?.ok()?) + } + + async fn set_attrs(&self, attrs: Attrs) -> io::Result<()> { + let url = self.url.to_owned(); + + Ok(self.call(ProviderJob::SetAttrs { url, attrs }).await?.ok()?) + } + + async fn symlink(&self, original: S, is_dir: F) -> io::Result<()> + where + S: AsStrand, + F: AsyncFnOnce() -> io::Result, + { + let original = original.as_strand().encoded_bytes().to_vec(); + let url = self.url.to_owned(); + + Ok(self.call(ProviderJob::Symlink { original, url, is_dir: is_dir().await? }).await?.ok()?) + } + + async fn symlink_metadata(&self) -> io::Result { + let url = self.url.to_owned(); + + Ok(self.call(ProviderJob::SymlinkMetadata { url }).await?.0?) + } + + async fn trash(&self) -> io::Result<()> { + let url = self.url.to_owned(); + + Ok(self.call(ProviderJob::Trash { url }).await?.ok()?) + } + + fn url(&self) -> Url<'_> { self.url.as_url() } + + async fn write(&self, contents: C) -> io::Result<()> + where + C: AsRef<[u8]>, + { + let url = self.url.to_owned(); + let bytes = contents.as_ref().to_vec(); + + Ok(self.call(ProviderJob::Write { url, offset: 0, bytes }).await?.ok()?) + } +} + +impl<'a> Lua<'a> { + pub(super) async fn call(&self, job: ProviderJob) -> io::Result> + where + T: FromLua + Send + 'static, + { + Ok(RUNNER.provide(self.run, job).await) + } +} diff --git a/yazi-vfs/src/engine/lua/mod.rs b/yazi-vfs/src/engine/lua/mod.rs new file mode 100644 index 00000000..292c8cb8 --- /dev/null +++ b/yazi-vfs/src/engine/lua/mod.rs @@ -0,0 +1 @@ +yazi_macro::mod_flat!(file demand lua read_dir); diff --git a/yazi-vfs/src/engine/lua/read_dir.rs b/yazi-vfs/src/engine/lua/read_dir.rs new file mode 100644 index 00000000..86a99dea --- /dev/null +++ b/yazi-vfs/src/engine/lua/read_dir.rs @@ -0,0 +1,33 @@ +use std::{io, vec}; + +use yazi_fs::{cha::{Cha, ChaType}, engine::{DirReader, FileHolder}}; +use yazi_shared::{path::PathBufDyn, strand::StrandCow, url::{UrlBuf, UrlLike}}; + +pub struct ReadDir { + pub(super) files: vec::IntoIter, +} + +impl DirReader for ReadDir { + type Entry = DirEntry; + + async fn next(&mut self) -> io::Result> { + Ok(self.files.next().map(|file| DirEntry { file })) + } +} + +// --- Entry +pub struct DirEntry { + file: yazi_fs::file::File, +} + +impl FileHolder for DirEntry { + async fn file_type(&self) -> io::Result { Ok(**self.file.cha) } + + async fn metadata(&self) -> io::Result { Ok(self.file.cha) } + + fn name(&self) -> StrandCow<'_> { self.file.name().unwrap_or_default().into() } + + fn path(&self) -> PathBufDyn { self.file.url.loc().into() } + + fn url(&self) -> UrlBuf { self.file.url.clone() } +} diff --git a/yazi-vfs/src/engine/mod.rs b/yazi-vfs/src/engine/mod.rs new file mode 100644 index 00000000..40539902 --- /dev/null +++ b/yazi-vfs/src/engine/mod.rs @@ -0,0 +1,5 @@ +yazi_macro::mod_pub!(lua sftp); + +yazi_macro::mod_flat!(absolute calculator copier dir_entry demand engine engines read_dir rw_file); + +pub(super) fn init() { sftp::init(); } diff --git a/yazi-vfs/src/provider/read_dir.rs b/yazi-vfs/src/engine/read_dir.rs similarity index 68% rename from yazi-vfs/src/provider/read_dir.rs rename to yazi-vfs/src/engine/read_dir.rs index 55e63159..ba06a59d 100644 --- a/yazi-vfs/src/provider/read_dir.rs +++ b/yazi-vfs/src/engine/read_dir.rs @@ -1,9 +1,10 @@ use std::io; -use yazi_fs::provider::DirReader; +use yazi_fs::engine::DirReader; pub enum ReadDir { - Local(yazi_fs::provider::local::ReadDir), + Local(yazi_fs::engine::local::ReadDir), + Lua(super::lua::ReadDir), Sftp(super::sftp::ReadDir), } @@ -13,6 +14,7 @@ impl DirReader for ReadDir { async fn next(&mut self) -> io::Result> { Ok(match self { Self::Local(reader) => reader.next().await?.map(Self::Entry::Local), + Self::Lua(reader) => reader.next().await?.map(Self::Entry::Lua), Self::Sftp(reader) => reader.next().await?.map(Self::Entry::Sftp), }) } diff --git a/yazi-vfs/src/provider/rw_file.rs b/yazi-vfs/src/engine/rw_file.rs similarity index 67% rename from yazi-vfs/src/provider/rw_file.rs rename to yazi-vfs/src/engine/rw_file.rs index 0b4e6e0d..20809cf5 100644 --- a/yazi-vfs/src/provider/rw_file.rs +++ b/yazi-vfs/src/engine/rw_file.rs @@ -1,11 +1,14 @@ use std::{io, pin::Pin}; -use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite}; -use yazi_fs::provider::Attrs; +use mlua::{IntoLuaMulti, LuaString, UserData, UserDataMethods, Value}; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncSeek, AsyncWrite, AsyncWriteExt}; +use yazi_binding::Error; +use yazi_fs::engine::Attrs; pub enum RwFile { Tokio(tokio::fs::File), Sftp(Box), + Lua(super::lua::File), } impl From for RwFile { @@ -16,12 +19,17 @@ impl From for RwFile { fn from(f: yazi_sftp::fs::File) -> Self { Self::Sftp(Box::new(f)) } } +impl From for RwFile { + fn from(f: super::lua::File) -> Self { Self::Lua(f) } +} + impl RwFile { // FIXME: path pub async fn metadata(&self) -> io::Result { Ok(match self { Self::Tokio(f) => yazi_fs::cha::Cha::new("// FIXME", f.metadata().await?), Self::Sftp(f) => super::sftp::Cha::try_from(("// FIXME".as_bytes(), &f.fstat().await?))?.0, + Self::Lua(f) => f.metadata().await?, }) } @@ -45,6 +53,7 @@ impl RwFile { f.fsetstat(&attrs).await?; } } + Self::Lua(f) => f.set_attrs(attrs).await?, } Ok(()) @@ -56,6 +65,7 @@ impl RwFile { Self::Sftp(f) => { f.fsetstat(&yazi_sftp::fs::Attrs { size: Some(size), ..Default::default() }).await? } + Self::Lua(f) => f.set_len(size).await?, }) } } @@ -70,6 +80,7 @@ impl AsyncRead for RwFile { match &mut *self { Self::Tokio(f) => Pin::new(f).poll_read(cx, buf), Self::Sftp(f) => Pin::new(f).poll_read(cx, buf), + Self::Lua(f) => Pin::new(f).poll_read(cx, buf), } } } @@ -80,6 +91,7 @@ impl AsyncSeek for RwFile { match &mut *self { Self::Tokio(f) => Pin::new(f).start_seek(position), Self::Sftp(f) => Pin::new(f).start_seek(position), + Self::Lua(f) => Pin::new(f).start_seek(position), } } @@ -91,6 +103,7 @@ impl AsyncSeek for RwFile { match &mut *self { Self::Tokio(f) => Pin::new(f).poll_complete(cx), Self::Sftp(f) => Pin::new(f).poll_complete(cx), + Self::Lua(f) => Pin::new(f).poll_complete(cx), } } } @@ -105,6 +118,7 @@ impl AsyncWrite for RwFile { match &mut *self { Self::Tokio(f) => Pin::new(f).poll_write(cx, buf), Self::Sftp(f) => Pin::new(f).poll_write(cx, buf), + Self::Lua(f) => Pin::new(f).poll_write(cx, buf), } } @@ -116,6 +130,7 @@ impl AsyncWrite for RwFile { match &mut *self { Self::Tokio(f) => Pin::new(f).poll_flush(cx), Self::Sftp(f) => Pin::new(f).poll_flush(cx), + Self::Lua(f) => Pin::new(f).poll_flush(cx), } } @@ -127,6 +142,7 @@ impl AsyncWrite for RwFile { match &mut *self { Self::Tokio(f) => Pin::new(f).poll_shutdown(cx), Self::Sftp(f) => Pin::new(f).poll_shutdown(cx), + Self::Lua(f) => Pin::new(f).poll_shutdown(cx), } } @@ -139,6 +155,7 @@ impl AsyncWrite for RwFile { match &mut *self { Self::Tokio(f) => Pin::new(f).poll_write_vectored(cx, bufs), Self::Sftp(f) => Pin::new(f).poll_write_vectored(cx, bufs), + Self::Lua(f) => Pin::new(f).poll_write_vectored(cx, bufs), } } @@ -147,6 +164,34 @@ impl AsyncWrite for RwFile { match self { Self::Tokio(f) => f.is_write_vectored(), Self::Sftp(f) => f.is_write_vectored(), + Self::Lua(f) => f.is_write_vectored(), } } } + +impl UserData for RwFile { + fn add_methods>(methods: &mut M) { + methods.add_async_method_mut("flush", |lua, mut me, ()| async move { + match me.flush().await { + Ok(()) => true.into_lua_multi(&lua), + Err(e) => (false, Error::Io(e)).into_lua_multi(&lua), + } + }); + methods.add_async_method_mut("read", |lua, mut me, len: usize| async move { + let mut buf = vec![0; len]; + match me.read(&mut buf).await { + Ok(n) => { + buf.truncate(n); + lua.create_external_string(buf)?.into_lua_multi(&lua) + } + Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua), + } + }); + methods.add_async_method_mut("write_all", |lua, mut me, src: LuaString| async move { + match me.write_all(&src.as_bytes()).await { + Ok(()) => true.into_lua_multi(&lua), + Err(e) => (false, Error::Io(e)).into_lua_multi(&lua), + } + }); + } +} diff --git a/yazi-vfs/src/provider/sftp/conn.rs b/yazi-vfs/src/engine/sftp/conn.rs similarity index 98% rename from yazi-vfs/src/provider/sftp/conn.rs rename to yazi-vfs/src/engine/sftp/conn.rs index 413f41db..bd0519d9 100644 --- a/yazi-vfs/src/provider/sftp/conn.rs +++ b/yazi-vfs/src/engine/sftp/conn.rs @@ -2,13 +2,11 @@ use std::{io, sync::Arc, time::{Duration, SystemTime}}; use chrono::DateTime; use russh::keys::{PrivateKeyWithHashAlg, agent::AgentIdentity}; -use yazi_fs::provider::local::Local; - -use crate::config::ServiceSftp; +use yazi_config::vfs::ServiceSftp; +use yazi_fs::engine::local::Local; #[derive(Clone, Copy)] pub(super) struct Conn { - pub(super) name: &'static str, pub(super) config: &'static ServiceSftp, } @@ -35,7 +33,7 @@ impl deadpool::managed::Manager for Conn { async fn create(&self) -> Result { let channel = self.connect().await.map_err(|e| { - io::Error::other(format!("Failed to connect to SFTP server `{}`: {e}", self.name)) + io::Error::other(format!("Failed to connect to SFTP server `{}`: {e}", self.config.domain)) })?; let mut op = yazi_sftp::Operator::make(channel.into_stream()); diff --git a/yazi-vfs/src/provider/sftp/gate.rs b/yazi-vfs/src/engine/sftp/demand.rs similarity index 55% rename from yazi-vfs/src/provider/sftp/gate.rs rename to yazi-vfs/src/engine/sftp/demand.rs index 5f4a4b7d..3bcf43e9 100644 --- a/yazi-vfs/src/provider/sftp/gate.rs +++ b/yazi-vfs/src/engine/sftp/demand.rs @@ -1,59 +1,59 @@ use std::io; -use yazi_fs::provider::{Attrs, FileBuilder}; +use yazi_fs::engine::{Attrs, Engine, FileBuilder}; use yazi_sftp::fs::Flags; -use yazi_shared::url::{AsUrl, Url}; +use yazi_shared::url::AsUrl; -use crate::{config::{ServiceSftp, Vfs}, provider::sftp::Conn}; +use crate::engine::sftp::Sftp; #[derive(Clone, Copy, Default)] -pub struct Gate(crate::provider::Gate); +pub struct Demand(yazi_fs::engine::Demand); -impl From for Flags { - fn from(Gate(g): Gate) -> Self { +impl From for Flags { + fn from(Demand(demand): Demand) -> Self { let mut flags = Self::empty(); - if g.append { + if demand.append { flags |= Self::APPEND; } - if g.create { + if demand.create { flags |= Self::CREATE; } - if g.create_new { + if demand.create_new { flags |= Self::CREATE | Self::EXCLUDE; } - if g.read { + if demand.read { flags |= Self::READ; } - if g.truncate { + if demand.truncate { flags |= Self::TRUNCATE; } - if g.write { + if demand.write { flags |= Self::WRITE; } flags } } -impl FileBuilder for Gate { +impl FileBuilder for Demand { type File = yazi_sftp::fs::File; fn append(&mut self, append: bool) -> &mut Self { - self.0.append(append); + self.0.append = append; self } fn attrs(&mut self, attrs: Attrs) -> &mut Self { - self.0.attrs(attrs); + self.0.attrs = attrs; self } fn create(&mut self, create: bool) -> &mut Self { - self.0.create(create); + self.0.create = create; self } fn create_new(&mut self, create_new: bool) -> &mut Self { - self.0.create_new(create_new); + self.0.create_new = create_new; self } @@ -61,21 +61,17 @@ impl FileBuilder for Gate { where U: AsUrl, { - let url = url.as_url(); - let (path, (name, config)) = match url { - Url::Sftp { loc, domain } => (*loc, Vfs::service::<&ServiceSftp>(domain).await?), - _ => Err(io::Error::new(io::ErrorKind::InvalidInput, format!("Not an SFTP URL: {url:?}")))?, - }; + let engine = Sftp::new(url.as_url()).await?; + let conn = engine.op().await?; - let conn = Conn { name, config }.roll().await?; let flags = Flags::from(*self); let attrs = super::Attrs(self.0.attrs).try_into().unwrap_or_default(); + let result = conn.open(engine.path, flags, &attrs).await; - let result = conn.open(path, flags, &attrs).await; if self.0.create_new && let Err(yazi_sftp::Error::Status(status)) = &result && status.is_failure() - && conn.lstat(path).await.is_ok() + && conn.lstat(engine.path).await.is_ok() { return Err(io::Error::from(io::ErrorKind::AlreadyExists)); } @@ -84,17 +80,17 @@ impl FileBuilder for Gate { } fn read(&mut self, read: bool) -> &mut Self { - self.0.read(read); + self.0.read = read; self } fn truncate(&mut self, truncate: bool) -> &mut Self { - self.0.truncate(truncate); + self.0.truncate = truncate; self } fn write(&mut self, write: bool) -> &mut Self { - self.0.write(write); + self.0.write = write; self } } diff --git a/yazi-vfs/src/provider/sftp/metadata.rs b/yazi-vfs/src/engine/sftp/metadata.rs similarity index 96% rename from yazi-vfs/src/provider/sftp/metadata.rs rename to yazi-vfs/src/engine/sftp/metadata.rs index 4f6cbfc2..1d82c4dd 100644 --- a/yazi-vfs/src/provider/sftp/metadata.rs +++ b/yazi-vfs/src/engine/sftp/metadata.rs @@ -3,7 +3,7 @@ use std::{io, time::{Duration, UNIX_EPOCH}}; use yazi_fs::cha::ChaKind; // --- Attrs -pub(crate) struct Attrs(pub(crate) yazi_fs::provider::Attrs); +pub(crate) struct Attrs(pub(crate) yazi_fs::engine::Attrs); impl TryFrom for yazi_sftp::fs::Attrs { type Error = (); diff --git a/yazi-vfs/src/engine/sftp/mod.rs b/yazi-vfs/src/engine/sftp/mod.rs new file mode 100644 index 00000000..49e56591 --- /dev/null +++ b/yazi-vfs/src/engine/sftp/mod.rs @@ -0,0 +1,12 @@ +yazi_macro::mod_flat!(conn demand metadata read_dir sftp); + +static CONN: yazi_shim::cell::RoCell< + parking_lot::Mutex< + hashbrown::HashMap< + &'static yazi_config::vfs::ServiceSftp, + &'static deadpool::managed::Pool, + >, + >, +> = yazi_shim::cell::RoCell::new(); + +pub(super) fn init() { CONN.init(Default::default()); } diff --git a/yazi-vfs/src/provider/sftp/read_dir.rs b/yazi-vfs/src/engine/sftp/read_dir.rs similarity index 95% rename from yazi-vfs/src/provider/sftp/read_dir.rs rename to yazi-vfs/src/engine/sftp/read_dir.rs index de55a16d..0d5b0100 100644 --- a/yazi-vfs/src/provider/sftp/read_dir.rs +++ b/yazi-vfs/src/engine/sftp/read_dir.rs @@ -1,6 +1,6 @@ use std::{io, sync::Arc}; -use yazi_fs::provider::{DirReader, FileHolder}; +use yazi_fs::engine::{DirReader, FileHolder}; use yazi_shared::{path::PathBufDyn, strand::StrandCow, url::{UrlBuf, UrlLike}}; use super::{Cha, ChaMode}; diff --git a/yazi-vfs/src/provider/sftp/sftp.rs b/yazi-vfs/src/engine/sftp/sftp.rs similarity index 75% rename from yazi-vfs/src/provider/sftp/sftp.rs rename to yazi-vfs/src/engine/sftp/sftp.rs index a12690b2..40820533 100644 --- a/yazi-vfs/src/provider/sftp/sftp.rs +++ b/yazi-vfs/src/engine/sftp/sftp.rs @@ -1,31 +1,31 @@ use std::{io, sync::Arc}; use tokio::{io::{AsyncWriteExt, BufReader, BufWriter}, sync::mpsc::Receiver}; -use yazi_fs::{cha::ChaMode, provider::{Capabilities, DirReader, FileHolder, Provider}}; +use yazi_config::vfs::{ServiceSftp, Vfs}; +use yazi_fs::engine::{Capabilities, DirReader, Engine, FileHolder}; use yazi_sftp::fs::{Attrs, Flags}; -use yazi_shared::{loc::LocBuf, path::{AsPath, PathBufDyn}, pool::InternStr, scheme::SchemeKind, strand::AsStrand, url::{Url, UrlBuf, UrlCow, UrlLike}}; +use yazi_shared::{auth::AuthKind, loc::LocBuf, path::{AsPath, PathBufDyn}, strand::AsStrand, url::{Url, UrlBuf, UrlCow, UrlLike}}; use super::Cha; -use crate::{config::{ServiceSftp, Vfs}, provider::sftp::Conn}; +use crate::engine::sftp::Conn; #[derive(Clone)] pub struct Sftp<'a> { - url: Url<'a>, - path: &'a typed_path::UnixPath, + url: Url<'a>, + pub(super) path: &'a typed_path::UnixPath, - name: &'static str, config: &'static ServiceSftp, } -impl<'a> Provider for Sftp<'a> { +impl<'a> Engine for Sftp<'a> { + type Demand = super::Demand; type File = yazi_sftp::fs::File; - type Gate = super::Gate; type Me<'b> = Sftp<'b>; type ReadDir = super::ReadDir; type UrlCow = UrlCow<'a>; async fn absolute(&self) -> io::Result { - Ok(if let Some(u) = super::try_absolute(self.url) { + Ok(if let Some(u) = crate::engine::try_absolute_impl(self.url) { u } else { self.canonicalize().await?.into() @@ -34,12 +34,19 @@ impl<'a> Provider for Sftp<'a> { async fn canonicalize(&self) -> io::Result { Ok(UrlBuf::Sftp { - loc: self.op().await?.realpath(self.path).await?.into(), - domain: self.name.intern(), + loc: self.op().await?.realpath(self.path).await?.into(), + auth: self.config.auth.clone(), }) } - fn capabilities(&self) -> Capabilities { Capabilities { symlink: true } } + async fn capabilities(&self) -> io::Result { + Ok(Capabilities { + symlink: true, + hard_link: true, + copy_progressive: true, + ..Default::default() + }) + } async fn casefold(&self) -> io::Result { let Some((parent, name)) = self.url.parent().zip(self.url.name()) else { @@ -74,7 +81,7 @@ impl<'a> Provider for Sftp<'a> { .ok_or_else(|| io::Error::from(io::ErrorKind::NotFound)) } - async fn copy

(&self, to: P, attrs: yazi_fs::provider::Attrs) -> io::Result + async fn copy

(&self, to: P, attrs: yazi_fs::engine::Attrs) -> io::Result where P: AsPath, { @@ -98,21 +105,21 @@ impl<'a> Provider for Sftp<'a> { Ok(written) } - fn copy_with_progress(&self, to: P, attrs: A) -> io::Result>> + fn copy_progressive(&self, to: P, attrs: A) -> io::Result>> where P: AsPath, - A: Into, + A: Into, { let to = UrlBuf::Sftp { - loc: LocBuf::::saturated( + loc: LocBuf::::saturated( to.as_path().to_unix_owned()?, - SchemeKind::Sftp, + AuthKind::Sftp, ), - domain: self.name.intern(), + auth: self.config.auth.clone(), }; let from = self.url.to_owned(); - Ok(crate::provider::copy_with_progress_impl(from, to, attrs.into())) + Ok(crate::engine::copy_progressive_impl(from, to, attrs.into())) } async fn create_dir(&self) -> io::Result<()> { @@ -144,15 +151,12 @@ impl<'a> Provider for Sftp<'a> { } async fn new<'b>(url: Url<'b>) -> io::Result> { - match url { - Url::Regular(_) | Url::Search { .. } | Url::Archive { .. } => { - Err(io::Error::new(io::ErrorKind::InvalidInput, format!("Not a SFTP URL: {url:?}"))) - } - Url::Sftp { loc, domain } => { - let (name, config) = Vfs::service::<&ServiceSftp>(domain).await?; - Ok(Self::Me { url, path: loc.as_inner(), name, config }) - } - } + let Url::Sftp { loc, auth } = url else { + return Err(io::Error::new(io::ErrorKind::InvalidInput, format!("Not a SFTP URL: {url:?}"))); + }; + + let config = Vfs::service::<&ServiceSftp>(&auth.domain)?; + Ok(Self::Me { url, path: loc.as_inner(), config }) } async fn read_dir(self) -> io::Result { @@ -192,10 +196,10 @@ impl<'a> Provider for Sftp<'a> { Ok(()) } - async fn set_mode(&self, mode: ChaMode) -> io::Result<()> { - let attrs = super::Attrs(yazi_fs::provider::Attrs { mode: Some(mode), ..Default::default() }) + async fn set_attrs(&self, attrs: yazi_fs::engine::Attrs) -> io::Result<()> { + let attrs = super::Attrs(attrs) .try_into() - .map_err(|()| io::Error::new(io::ErrorKind::InvalidInput, "Cannot convert mode"))?; + .map_err(|()| io::Error::new(io::ErrorKind::InvalidInput, "Cannot convert attributes"))?; Ok(self.op().await?.setstat(self.path, attrs).await?) } @@ -226,6 +230,6 @@ impl<'a> Provider for Sftp<'a> { impl<'a> Sftp<'a> { #[inline] pub(super) async fn op(&self) -> io::Result> { - Conn { name: self.name, config: self.config }.roll().await + Conn { config: self.config }.roll().await } } diff --git a/yazi-vfs/src/entries.rs b/yazi-vfs/src/entries.rs index da47837b..290841a6 100644 --- a/yazi-vfs/src/entries.rs +++ b/yazi-vfs/src/entries.rs @@ -1,10 +1,10 @@ use std::io; use tokio::{select, sync::mpsc::{self, UnboundedReceiver}}; -use yazi_fs::{Entries, FilesOp, cha::Cha, file::File, mounts::PARTITIONS, provider::{DirReader, FileHolder}}; +use yazi_fs::{Entries, FilesOp, cha::Cha, engine::{DirReader, FileHolder}, file::File, mounts::PARTITIONS}; use yazi_shared::url::UrlBuf; -use crate::{VfsCha, VfsFile, VfsFilesOp, provider::{self, DirEntry}}; +use crate::{VfsCha, VfsFile, VfsFilesOp, engine::{self, DirEntry}}; pub trait VfsEntries { fn from_dir(dir: &UrlBuf) -> impl Future>>; @@ -16,7 +16,7 @@ pub trait VfsEntries { impl VfsEntries for Entries { async fn from_dir(dir: &UrlBuf) -> std::io::Result> { - let mut it = provider::read_dir(dir).await?; + let mut it = engine::read_dir(dir).await?; let (tx, rx) = mpsc::unbounded_channel(); tokio::spawn(async move { @@ -37,7 +37,7 @@ impl VfsEntries for Entries { } async fn from_dir_bulk(dir: &UrlBuf) -> std::io::Result> { - let mut it = provider::read_dir(dir).await?; + let mut it = engine::read_dir(dir).await?; let mut entries = Vec::new(); while let Ok(Some(entry)) = it.next().await { entries.push(entry); diff --git a/yazi-vfs/src/file.rs b/yazi-vfs/src/file.rs index 26323a2d..8b4a7d72 100644 --- a/yazi-vfs/src/file.rs +++ b/yazi-vfs/src/file.rs @@ -3,7 +3,7 @@ use std::io; use yazi_fs::{cha::Cha, file::File}; use yazi_shared::url::{UrlBuf, UrlCow}; -use crate::{VfsCha, provider}; +use crate::{VfsCha, engine}; pub trait VfsFile: Sized { fn new<'a>(url: impl Into>) -> impl Future>; @@ -16,13 +16,13 @@ pub trait VfsFile: Sized { impl VfsFile for File { async fn new<'a>(url: impl Into>) -> io::Result { let url = url.into(); - let cha = provider::symlink_metadata(&url).await?; + let cha = engine::symlink_metadata(&url).await?; Ok(Self::from_follow(url.into_owned(), cha).await) } async fn maybe_new<'a>(url: impl Into>) -> io::Result> { let url = url.into(); - let cha = match provider::symlink_metadata(&url).await { + let cha = match engine::symlink_metadata(&url).await { Ok(cha) => cha, Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(None), Err(e) => return Err(e), @@ -31,7 +31,7 @@ impl VfsFile for File { } async fn from_follow(url: UrlBuf, cha: Cha) -> Self { - let link_to = if cha.is_link() { provider::read_link(&url).await.ok() } else { None }; + let link_to = if cha.is_link() { engine::read_link(&url).await.ok() } else { None }; let cha = Cha::from_follow(&url, cha).await; diff --git a/yazi-vfs/src/fns.rs b/yazi-vfs/src/fns.rs index 445b635f..733fc5b5 100644 --- a/yazi-vfs/src/fns.rs +++ b/yazi-vfs/src/fns.rs @@ -2,21 +2,18 @@ use std::io::{self}; use yazi_shared::{strand::{StrandBuf, StrandLike}, url::{AsUrl, UrlBuf, UrlLike}}; -use crate::provider; +use crate::engine; pub async fn maybe_exists(url: impl AsUrl) -> bool { - match provider::symlink_metadata(url).await { + match engine::symlink_metadata(url).await { Ok(_) => true, Err(e) => e.kind() != io::ErrorKind::NotFound, } } pub async fn unique_file(u: UrlBuf, is_dir: bool) -> io::Result { - let result = if is_dir { - provider::create_dir(&u).await - } else { - provider::create_new(&u).await.map(|_| ()) - }; + let result = + if is_dir { engine::create_dir(&u).await } else { engine::create_new(&u).await.map(|_| ()) }; match result { Ok(()) => Ok(u), @@ -55,9 +52,9 @@ async fn _unique_file(mut url: UrlBuf, is_dir: bool) -> io::Result { url.try_set_name(&name)?; let result = if is_dir { - provider::create_dir(&url).await + engine::create_dir(&url).await } else { - provider::create_new(&url).await.map(|_| ()) + engine::create_new(&url).await.map(|_| ()) }; match result { diff --git a/yazi-vfs/src/lib.rs b/yazi-vfs/src/lib.rs index 79d5c69a..fb81f98b 100644 --- a/yazi-vfs/src/lib.rs +++ b/yazi-vfs/src/lib.rs @@ -1,5 +1,5 @@ -yazi_macro::mod_pub!(config provider); +yazi_macro::mod_pub!(engine); yazi_macro::mod_flat!(cha entries file fns op); -pub fn init() { provider::init(); } +pub fn init() { engine::init(); } diff --git a/yazi-vfs/src/provider/lua.rs b/yazi-vfs/src/provider/lua.rs deleted file mode 100644 index e61ba9bd..00000000 --- a/yazi-vfs/src/provider/lua.rs +++ /dev/null @@ -1,32 +0,0 @@ -use mlua::{IntoLuaMulti, LuaString, UserData, UserDataMethods, Value}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use yazi_binding::Error; - -use crate::provider::RwFile; - -impl UserData for RwFile { - fn add_methods>(methods: &mut M) { - methods.add_async_method_mut("flush", |lua, mut me, ()| async move { - match me.flush().await { - Ok(()) => true.into_lua_multi(&lua), - Err(e) => (false, Error::Io(e)).into_lua_multi(&lua), - } - }); - methods.add_async_method_mut("read", |lua, mut me, len: usize| async move { - let mut buf = vec![0; len]; - match me.read(&mut buf).await { - Ok(n) => { - buf.truncate(n); - lua.create_external_string(buf)?.into_lua_multi(&lua) - } - Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua), - } - }); - methods.add_async_method_mut("write_all", |lua, mut me, src: LuaString| async move { - match me.write_all(&src.as_bytes()).await { - Ok(()) => true.into_lua_multi(&lua), - Err(e) => (false, Error::Io(e)).into_lua_multi(&lua), - } - }); - } -} diff --git a/yazi-vfs/src/provider/mod.rs b/yazi-vfs/src/provider/mod.rs deleted file mode 100644 index b8afef8f..00000000 --- a/yazi-vfs/src/provider/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -yazi_macro::mod_pub!(sftp); - -yazi_macro::mod_flat!(calculator copier dir_entry gate lua provider providers read_dir rw_file); - -pub(super) fn init() { sftp::init(); } diff --git a/yazi-vfs/src/provider/sftp/mod.rs b/yazi-vfs/src/provider/sftp/mod.rs deleted file mode 100644 index 8bf7b524..00000000 --- a/yazi-vfs/src/provider/sftp/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -yazi_macro::mod_flat!(absolute conn gate metadata read_dir sftp); - -static CONN: yazi_shim::cell::RoCell< - parking_lot::Mutex< - hashbrown::HashMap<&'static crate::config::ServiceSftp, &'static deadpool::managed::Pool>, - >, -> = yazi_shim::cell::RoCell::new(); - -pub(super) fn init() { CONN.init(Default::default()); } diff --git a/yazi-watcher/src/local/local.rs b/yazi-watcher/src/local/local.rs index 5392c22f..6c817473 100644 --- a/yazi-watcher/src/local/local.rs +++ b/yazi-watcher/src/local/local.rs @@ -5,7 +5,7 @@ use notify::{PollWatcher, RecommendedWatcher, RecursiveMode, Result, Watcher}; use tokio::{pin, sync::mpsc::{self, UnboundedReceiver}}; use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream}; use tracing::error; -use yazi_fs::{FilesOp, file::File, mounts::PARTITIONS, provider::{self, Provider}}; +use yazi_fs::{FilesOp, engine::{self, Engine}, file::File, mounts::PARTITIONS}; use yazi_shared::url::{UrlBuf, UrlLike}; use yazi_vfs::VfsFile; @@ -79,7 +79,7 @@ impl Local { return true; } - match provider::local::Local::regular(path).metadata().await { + match engine::local::Local::regular(path).metadata().await { Ok(cha) => PARTITIONS.read().soundless(cha), Err(_) => true, } @@ -104,7 +104,7 @@ impl Local { }; if let Some(p) = file.url.as_local() - && !provider::local::match_name_case(p).await + && !engine::local::match_name_case(p).await { ops.push(FilesOp::Deleting(parent.into(), [urn.into()].into())); continue; diff --git a/yazi-watcher/src/reporter.rs b/yazi-watcher/src/reporter.rs index 7d74b1a3..1c4c5336 100644 --- a/yazi-watcher/src/reporter.rs +++ b/yazi-watcher/src/reporter.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use percent_encoding::percent_decode; use tokio::sync::mpsc; -use yazi_shared::{scheme::SchemeKind, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}}; +use yazi_shared::{auth::AuthKind, url::{AsUrl, Url, UrlBuf, UrlCow, UrlLike}}; use crate::{WATCHED, local::LINKED}; @@ -20,9 +20,9 @@ impl Reporter { { for url in urls.into_iter().map(Into::into) { match url.as_url().kind() { - SchemeKind::Regular | SchemeKind::Search => self.report_local(url), - SchemeKind::Archive => {} - SchemeKind::Sftp => self.report_remote(url), + AuthKind::Regular | AuthKind::Search => self.report_local(url), + AuthKind::Mount => {} // TODO: mounted VFS cache invalidation + AuthKind::Scope | AuthKind::Sftp => self.report_remote(url), } } } diff --git a/yazi-watcher/src/watched.rs b/yazi-watcher/src/watched.rs index f74f7a47..6628191b 100644 --- a/yazi-watcher/src/watched.rs +++ b/yazi-watcher/src/watched.rs @@ -1,9 +1,8 @@ use std::{ops::{Deref, DerefMut}, path::Path}; use hashbrown::HashSet; -use percent_encoding::percent_decode_str; use yazi_fs::{Xdg, path::PercentEncoding}; -use yazi_shared::{path::{Component, PathBufDyn, PathDyn, PathLike}, pool::InternStr, scheme::SchemeKind, url::{AsUrl, UrlBuf}}; +use yazi_shared::{auth::{Auth, AuthKind}, path::{Component, PathBufDyn, PathDyn, PathLike}, url::{AsUrl, UrlBuf}}; use crate::Watchee; @@ -42,21 +41,26 @@ impl Watched { pub(super) fn find_by_cache(&self, cache: PathDyn) -> Option { let mut it = cache.try_strip_prefix(Xdg::temp_dir()).ok()?.components(); - // Parse domain - let domain = it.next()?.as_normal()?.to_str().ok()?; - let domain = percent_decode_str(domain.strip_prefix("sftp-")?).decode_utf8().ok()?.intern(); + // Parse authority + let cache = it.next()?.as_normal()?.to_str().ok()?; + let auth = Auth::parse_cache(cache).ok()?; // Parse path let (path, abs) = if let Ok(p) = it.path().try_strip_prefix(".%2F") { (p, false) } else { (it.path(), true) }; - let path = path.percent_decode(SchemeKind::Sftp).ok()?; + let path = path.percent_decode(auth.kind).ok()?; let path = PathBufDyn::from_components( - SchemeKind::Sftp, + auth.kind, abs.then_some(Component::RootDir).into_iter().chain(path.components()), ) .ok()?; - let url = UrlBuf::Sftp { loc: path.into_unix().ok()?.into(), domain }; + let url = match auth.kind { + AuthKind::Mount => UrlBuf::Mount { loc: path.into_os().ok()?.into(), auth }, + AuthKind::Scope => UrlBuf::Scope { loc: path.into_unix().ok()?.into(), auth }, + AuthKind::Sftp => UrlBuf::Sftp { loc: path.into_unix().ok()?.into(), auth }, + AuthKind::Regular | AuthKind::Search => return None, + }; if self.contains_url(&url) { Some(url) } else { None } } } diff --git a/yazi-widgets/Cargo.toml b/yazi-widgets/Cargo.toml index 0e7dee39..538983f1 100644 --- a/yazi-widgets/Cargo.toml +++ b/yazi-widgets/Cargo.toml @@ -18,7 +18,6 @@ vendored-lua = [ "mlua/vendored" ] [dependencies] yazi-binding = { path = "../yazi-binding", version = "26.5.6" } -yazi-dds = { path = "../yazi-dds", version = "26.5.6" } yazi-macro = { path = "../yazi-macro", version = "26.5.6" } yazi-shared = { path = "../yazi-shared", version = "26.5.6" } yazi-shim = { path = "../yazi-shim", version = "26.5.6" } diff --git a/yazi-widgets/src/input/input_arc.rs b/yazi-widgets/src/input/input_arc.rs index 71f132ef..8ac68321 100644 --- a/yazi-widgets/src/input/input_arc.rs +++ b/yazi-widgets/src/input/input_arc.rs @@ -4,11 +4,8 @@ use mlua::{AnyUserData, IntoLua, Lua, MetaMethod, Table, UserData, UserDataMetho use parking_lot::Mutex; use ratatui_core::widgets::Widget; use yazi_binding::{elements::{Area, Spatial}, impl_area_method}; -use yazi_dds::Pubsub; -use yazi_macro::err; -use yazi_shim::strum::IntoStr; -use crate::input::{Input, InputOpt, InputStyles}; +use crate::input::{Input, InputEvent, InputOpt, InputStyles}; #[derive(Clone, Debug, Default)] pub struct InputArc { @@ -25,13 +22,12 @@ impl Deref for InputArc { } impl InputArc { - pub fn compose(lua: &Lua, styles: InputStyles) -> mlua::Result { + pub fn compose(lua: &Lua, styles: InputStyles, cb: fn(InputEvent)) -> mlua::Result { let new = lua.create_function(move |_, (_, mut opt): (Table, InputOpt)| { opt.styles.normal = opt.styles.normal.or(styles.normal); opt.styles.selected = opt.styles.selected.or(styles.selected); opt.styles.blink = opt.styles.blink.or(styles.blink); - opt = opt.with_cb(|e| err!(Pubsub::pub_after_input((&e).into_str(), e.value()))); - Ok(Self::from(Input::new(opt)?)) + Ok(Self::from(Input::new(opt.with_cb(cb))?)) })?; let input = lua.create_table()?;