diff --git a/Cargo.lock b/Cargo.lock index e93c75d7..11da3500 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4672,6 +4672,7 @@ dependencies = [ "yazi-fs", "yazi-macro", "yazi-shared", + "yazi-vfs", ] [[package]] @@ -4702,6 +4703,7 @@ dependencies = [ "yazi-fs", "yazi-macro", "yazi-shared", + "yazi-vfs", ] [[package]] @@ -4726,6 +4728,7 @@ dependencies = [ "ratatui", "regex", "serde", + "tokio", "toml 0.9.6", "tracing", "yazi-codegen", @@ -4733,6 +4736,7 @@ dependencies = [ "yazi-macro", "yazi-shared", "yazi-term", + "yazi-vfs", ] [[package]] @@ -4836,6 +4840,7 @@ dependencies = [ "yazi-proxy", "yazi-shared", "yazi-term", + "yazi-vfs", "yazi-watcher", "yazi-widgets", ] @@ -4857,6 +4862,7 @@ dependencies = [ "objc", "parking_lot", "regex", + "russh", "scopeguard", "serde", "tokio", @@ -4868,6 +4874,7 @@ dependencies = [ "yazi-macro", "yazi-sftp", "yazi-shared", + "yazi-vfs", ] [[package]] @@ -5027,6 +5034,21 @@ dependencies = [ "yazi-shared", ] +[[package]] +name = "yazi-vfs" +version = "25.9.15" +dependencies = [ + "anyhow", + "dirs", + "hashbrown 0.16.0", + "serde", + "tokio", + "toml 0.9.6", + "uzers", + "yazi-macro", + "yazi-shared", +] + [[package]] name = "yazi-watcher" version = "25.9.15" diff --git a/yazi-actor/src/cmp/trigger.rs b/yazi-actor/src/cmp/trigger.rs index 3d1c7fe0..fd2dd8dd 100644 --- a/yazi-actor/src/cmp/trigger.rs +++ b/yazi-actor/src/cmp/trigger.rs @@ -1,7 +1,7 @@ use std::{ffi::OsString, mem, path::MAIN_SEPARATOR_STR}; use anyhow::Result; -use yazi_fs::{CWD, path::expand_url, provider}; +use yazi_fs::{CWD, path::expand_url, provider::{self, DirReader, FileHolder}}; use yazi_macro::{act, render, succ}; use yazi_parser::cmp::{CmpItem, ShowOpt, TriggerOpt}; use yazi_proxy::CmpProxy; @@ -45,7 +45,7 @@ impl Actor for Trigger { cache.push(CmpItem { name: OsString::new(), is_dir: true }); } - while let Ok(Some(ent)) = dir.next_entry().await { + while let Ok(Some(ent)) = dir.next().await { if let Ok(ft) = ent.file_type().await { cache.push(CmpItem { name: ent.name().into_owned(), is_dir: ft.is_dir() }); } diff --git a/yazi-actor/src/mgr/bulk_rename.rs b/yazi-actor/src/mgr/bulk_rename.rs index 71fbdfea..8d529028 100644 --- a/yazi-actor/src/mgr/bulk_rename.rs +++ b/yazi-actor/src/mgr/bulk_rename.rs @@ -49,7 +49,7 @@ impl Actor for BulkRename { .write_all(old.join(OsStr::new("\n")).as_encoded_bytes()) .await?; - defer! { tokio::spawn(Local::remove_file(tmp.clone())); } + defer! { tokio::spawn(Local.remove_file(tmp.clone())); } TasksProxy::process_exec(Cow::Borrowed(opener), cwd, vec![ OsString::new(), tmp.to_owned().into(), @@ -60,7 +60,8 @@ impl Actor for BulkRename { defer!(AppProxy::resume()); AppProxy::stop().await; - let new: Vec<_> = Local::read_to_string(&tmp) + let new: Vec<_> = Local + .read_to_string(&tmp) .await? .lines() .take(old.len()) diff --git a/yazi-adapter/src/image.rs b/yazi-adapter/src/image.rs index 4ae10658..7f0a0c07 100644 --- a/yazi-adapter/src/image.rs +++ b/yazi-adapter/src/image.rs @@ -39,7 +39,7 @@ impl Image { }) .await??; - Ok(Local::write(cache, buf).await?) + Ok(Local.write(cache, buf).await?) } pub(super) async fn downscale(path: &Path, rect: Rect) -> Result { diff --git a/yazi-boot/Cargo.toml b/yazi-boot/Cargo.toml index 2dc20fd2..7db7890c 100644 --- a/yazi-boot/Cargo.toml +++ b/yazi-boot/Cargo.toml @@ -14,6 +14,7 @@ yazi-config = { path = "../yazi-config", version = "25.9.15" } yazi-fs = { path = "../yazi-fs", version = "25.9.15" } yazi-macro = { path = "../yazi-macro", version = "25.9.15" } yazi-shared = { path = "../yazi-shared", version = "25.9.15" } +yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" } # External dependencies clap = { workspace = true } diff --git a/yazi-boot/src/actions/clear_cache.rs b/yazi-boot/src/actions/clear_cache.rs index 90b03b46..ee864632 100644 --- a/yazi-boot/src/actions/clear_cache.rs +++ b/yazi-boot/src/actions/clear_cache.rs @@ -1,5 +1,5 @@ use yazi_config::YAZI; -use yazi_fs::Xdg; +use yazi_vfs::local::Xdg; use super::Actions; diff --git a/yazi-boot/src/boot.rs b/yazi-boot/src/boot.rs index db2e6149..8cd367a9 100644 --- a/yazi-boot/src/boot.rs +++ b/yazi-boot/src/boot.rs @@ -3,8 +3,9 @@ use std::path::PathBuf; use futures::executor::block_on; use hashbrown::HashSet; use serde::Serialize; -use yazi_fs::{CWD, Xdg, path::expand_url, provider}; +use yazi_fs::{CWD, path::expand_url, provider}; use yazi_shared::url::{UrlBuf, UrnBuf}; +use yazi_vfs::local::Xdg; #[derive(Debug, Default, Serialize)] pub struct Boot { diff --git a/yazi-cli/Cargo.toml b/yazi-cli/Cargo.toml index 6351bc46..bd4c69a3 100644 --- a/yazi-cli/Cargo.toml +++ b/yazi-cli/Cargo.toml @@ -24,6 +24,7 @@ yazi-dds = { path = "../yazi-dds", version = "25.9.15" } yazi-fs = { path = "../yazi-fs", version = "25.9.15" } yazi-macro = { path = "../yazi-macro", version = "25.9.15" } yazi-shared = { path = "../yazi-shared", version = "25.9.15" } +yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" } # External dependencies anyhow = { workspace = true } diff --git a/yazi-cli/src/package/delete.rs b/yazi-cli/src/package/delete.rs index a57428c9..e71b83ef 100644 --- a/yazi-cli/src/package/delete.rs +++ b/yazi-cli/src/package/delete.rs @@ -23,7 +23,7 @@ impl Dependency { pub(super) async fn delete_assets(&self) -> Result<()> { let assets = self.target().join("assets"); - match Local::read_dir(&assets).await { + match Local.read_dir(&assets).await { Ok(mut it) => { while let Some(entry) = it.next().await? { remove_sealed(&entry.path()) @@ -49,7 +49,7 @@ impl Dependency { .with_context(|| format!("failed to delete `{}`", path.display()))?; } - if ok_or_not_found(Local::remove_dir(&dir).await).is_ok() { + if ok_or_not_found(Local.remove_dir(&dir).await).is_ok() { outln!("Done!")?; } else { outln!( diff --git a/yazi-cli/src/package/dependency.rs b/yazi-cli/src/package/dependency.rs index 0849c63f..951d6f60 100644 --- a/yazi-cli/src/package/dependency.rs +++ b/yazi-cli/src/package/dependency.rs @@ -3,8 +3,9 @@ use std::{io::BufWriter, path::{Path, PathBuf}, str::FromStr}; use anyhow::{Result, bail}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use twox_hash::XxHash3_128; -use yazi_fs::{Xdg, provider::{DirReader, FileHolder, Provider, local::Local}}; +use yazi_fs::provider::{DirReader, FileHolder, Provider, local::Local}; use yazi_shared::BytesExt; +use yazi_vfs::local::Xdg; #[derive(Clone, Default)] pub(crate) struct Dependency { @@ -62,7 +63,7 @@ impl Dependency { } pub(super) async fn plugin_files(dir: &Path) -> std::io::Result> { - let mut it = Local::read_dir(dir).await?; + let mut it = Local.read_dir(dir).await?; let mut files: Vec = ["LICENSE", "README.md", "main.lua"].into_iter().map(Into::into).collect(); while let Some(entry) = it.next().await? { diff --git a/yazi-cli/src/package/deploy.rs b/yazi-cli/src/package/deploy.rs index 3bba466c..a48f1a73 100644 --- a/yazi-cli/src/package/deploy.rs +++ b/yazi-cli/src/package/deploy.rs @@ -20,7 +20,7 @@ impl Dependency { self.hash_check().await?; } - Local::create_dir_all(&to).await?; + Local.create_dir_all(&to).await?; self.delete_assets().await?; let res1 = Self::deploy_assets(from.join("assets"), to.join("assets")).await; @@ -40,9 +40,9 @@ impl Dependency { } async fn deploy_assets(from: PathBuf, to: PathBuf) -> Result<()> { - match Local::read_dir(&from).await { + match Local.read_dir(&from).await { Ok(mut it) => { - Local::create_dir_all(&to).await?; + Local.create_dir_all(&to).await?; while let Some(entry) = it.next().await? { let (src, dist) = (entry.path(), to.join(entry.name())); copy_and_seal(&src, &dist).await.with_context(|| { diff --git a/yazi-cli/src/package/hash.rs b/yazi-cli/src/package/hash.rs index dc24e865..1730b464 100644 --- a/yazi-cli/src/package/hash.rs +++ b/yazi-cli/src/package/hash.rs @@ -14,17 +14,17 @@ impl Dependency { for file in files { h.write(file.as_bytes()); h.write(b"VpvFw9Atb7cWGOdqhZCra634CcJJRlsRl72RbZeV0vpG1\0"); - h.write(&ok_or_not_found(Local::read(dir.join(file)).await)?); + h.write(&ok_or_not_found(Local.read(dir.join(file)).await)?); } let mut assets = vec![]; - match Local::read_dir(dir.join("assets")).await { + match Local.read_dir(dir.join("assets")).await { Ok(mut it) => { while let Some(entry) = it.next().await? { let Ok(name) = entry.name().into_owned().into_string() else { bail!("asset path is not valid UTF-8: {}", entry.path().display()); }; - assets.push((name, Local::read(entry.path()).await?)); + assets.push((name, Local.read(entry.path()).await?)); } } Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} diff --git a/yazi-cli/src/package/mod.rs b/yazi-cli/src/package/mod.rs index 7dde8fb2..5ceeedb4 100644 --- a/yazi-cli/src/package/mod.rs +++ b/yazi-cli/src/package/mod.rs @@ -3,7 +3,7 @@ yazi_macro::mod_flat!(add delete dependency deploy git hash install package upgrade); use anyhow::Context; -use yazi_fs::Xdg; +use yazi_vfs::local::Xdg; pub(super) fn init() -> anyhow::Result<()> { let root = Xdg::state_dir().join("packages"); diff --git a/yazi-cli/src/package/package.rs b/yazi-cli/src/package/package.rs index 4d8aca9b..a6d79ca0 100644 --- a/yazi-cli/src/package/package.rs +++ b/yazi-cli/src/package/package.rs @@ -2,8 +2,9 @@ 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::provider::{Provider, local::Local}; use yazi_macro::outln; +use yazi_vfs::local::Xdg; use super::Dependency; @@ -15,7 +16,7 @@ pub(crate) struct Package { impl Package { pub(crate) async fn load() -> Result { - Ok(match Local::read_to_string(Self::toml()).await { + Ok(match Local.read_to_string(Self::toml()).await { Ok(s) => toml::from_str(&s)?, Err(e) if e.kind() == std::io::ErrorKind::NotFound => Self::default(), Err(e) => Err(e)?, @@ -135,7 +136,7 @@ impl Package { async fn save(&self) -> Result<()> { let s = toml::to_string_pretty(self)?; - Local::write(Self::toml(), s).await.context("Failed to write package.toml") + Local.write(Self::toml(), s).await.context("Failed to write package.toml") } fn toml() -> PathBuf { Xdg::config_dir().join("package.toml") } diff --git a/yazi-cli/src/shared/shared.rs b/yazi-cli/src/shared/shared.rs index 76602662..23a27561 100644 --- a/yazi-cli/src/shared/shared.rs +++ b/yazi-cli/src/shared/shared.rs @@ -5,19 +5,19 @@ use yazi_fs::{ok_or_not_found, provider::{FileBuilder, Provider, local::{Gate, L #[inline] pub async fn must_exists(path: impl AsRef) -> bool { - Local::symlink_metadata(path).await.is_ok() + Local.symlink_metadata(path).await.is_ok() } #[inline] pub async fn maybe_exists(path: impl AsRef) -> bool { - match Local::symlink_metadata(path).await { + match Local.symlink_metadata(path).await { Ok(_) => true, Err(e) => e.kind() != std::io::ErrorKind::NotFound, } } pub async fn copy_and_seal(from: &Path, to: &Path) -> io::Result<()> { - let b = Local::read(from).await?; + let b = Local.read(from).await?; ok_or_not_found(remove_sealed(to).await)?; let mut file = Gate::default().create_new(true).write(true).truncate(true).open(to).await?; @@ -39,5 +39,5 @@ pub async fn remove_sealed(p: &Path) -> io::Result<()> { tokio::fs::set_permissions(p, perm).await?; } - Local::remove_file(p).await + Local.remove_file(p).await } diff --git a/yazi-config/Cargo.toml b/yazi-config/Cargo.toml index ee023832..974090af 100644 --- a/yazi-config/Cargo.toml +++ b/yazi-config/Cargo.toml @@ -14,6 +14,7 @@ yazi-fs = { path = "../yazi-fs", version = "25.9.15" } yazi-macro = { path = "../yazi-macro", version = "25.9.15" } yazi-shared = { path = "../yazi-shared", version = "25.9.15" } yazi-term = { path = "../yazi-term", version = "25.9.15" } +yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" } # External dependencies anyhow = { workspace = true } @@ -26,6 +27,7 @@ indexmap = { workspace = true } ratatui = { workspace = true } regex = { workspace = true } serde = { workspace = true } +tokio = { workspace = true } toml = { workspace = true } tracing = { workspace = true } diff --git a/yazi-config/src/keymap/keymap.rs b/yazi-config/src/keymap/keymap.rs index 79de6303..c4c2bde7 100644 --- a/yazi-config/src/keymap/keymap.rs +++ b/yazi-config/src/keymap/keymap.rs @@ -1,8 +1,9 @@ use anyhow::{Context, Result}; use serde::Deserialize; use yazi_codegen::DeserializeOver1; -use yazi_fs::{Xdg, ok_or_not_found}; +use yazi_fs::ok_or_not_found; use yazi_shared::Layer; +use yazi_vfs::local::Xdg; use super::{Chord, KeymapRules}; diff --git a/yazi-config/src/lib.rs b/yazi-config/src/lib.rs index 1d3ff20e..57f8b011 100644 --- a/yazi-config/src/lib.rs +++ b/yazi-config/src/lib.rs @@ -1,6 +1,6 @@ #![allow(clippy::module_inception)] -yazi_macro::mod_pub!(keymap mgr open opener plugin popup preview tasks theme vfs which); +yazi_macro::mod_pub!(keymap mgr open opener plugin popup preview tasks theme which); yazi_macro::mod_flat!(color icon layout pattern platform preset priority style yazi); diff --git a/yazi-config/src/preview/preview.rs b/yazi-config/src/preview/preview.rs index 0ae0a0a5..b4825a25 100644 --- a/yazi-config/src/preview/preview.rs +++ b/yazi-config/src/preview/preview.rs @@ -3,8 +3,9 @@ use std::{borrow::Cow, path::PathBuf}; use anyhow::{Context, Result, bail}; use serde::{Deserialize, Serialize}; use yazi_codegen::DeserializeOver2; -use yazi_fs::{Xdg, path::expand_url}; +use yazi_fs::path::expand_url; use yazi_shared::{SStr, timestamp_us, url::Url}; +use yazi_vfs::local::Xdg; use super::PreviewWrap; diff --git a/yazi-config/src/theme/flavor.rs b/yazi-config/src/theme/flavor.rs index bc81c60d..06f5f7e5 100644 --- a/yazi-config/src/theme/flavor.rs +++ b/yazi-config/src/theme/flavor.rs @@ -4,7 +4,7 @@ use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; use toml::Value; use yazi_codegen::DeserializeOver2; -use yazi_fs::Xdg; +use yazi_vfs::local::Xdg; #[derive(Default, Deserialize, DeserializeOver2, Serialize)] pub struct Flavor { diff --git a/yazi-config/src/theme/theme.rs b/yazi-config/src/theme/theme.rs index b3a8369f..48fc5db9 100644 --- a/yazi-config/src/theme/theme.rs +++ b/yazi-config/src/theme/theme.rs @@ -3,8 +3,9 @@ use std::path::PathBuf; use anyhow::{Context, Result, anyhow, bail}; use serde::Deserialize; use yazi_codegen::{DeserializeOver1, DeserializeOver2}; -use yazi_fs::{Xdg, ok_or_not_found, path::expand_url}; +use yazi_fs::{ok_or_not_found, path::expand_url}; use yazi_shared::url::UrlBuf; +use yazi_vfs::local::Xdg; use super::{Filetype, Flavor, Icon}; use crate::Style; diff --git a/yazi-config/src/vfs/mod.rs b/yazi-config/src/vfs/mod.rs deleted file mode 100644 index 330ec80f..00000000 --- a/yazi-config/src/vfs/mod.rs +++ /dev/null @@ -1 +0,0 @@ -yazi_macro::mod_flat!(sftp); diff --git a/yazi-config/src/vfs/sftp.rs b/yazi-config/src/vfs/sftp.rs deleted file mode 100644 index 30cb8b6a..00000000 --- a/yazi-config/src/vfs/sftp.rs +++ /dev/null @@ -1,9 +0,0 @@ -use std::path::PathBuf; - -pub struct Sftp { - pub host: String, - pub user: String, - pub port: u16, - pub password: Option, - pub key_file: Option, -} diff --git a/yazi-config/src/yazi.rs b/yazi-config/src/yazi.rs index ba346ccf..620a7834 100644 --- a/yazi-config/src/yazi.rs +++ b/yazi-config/src/yazi.rs @@ -1,7 +1,8 @@ use anyhow::{Context, Result}; use serde::Deserialize; use yazi_codegen::DeserializeOver1; -use yazi_fs::{Xdg, ok_or_not_found}; +use yazi_fs::ok_or_not_found; +use yazi_vfs::local::Xdg; use crate::{mgr, open, opener, plugin, popup, preview, tasks, which}; diff --git a/yazi-dds/src/client.rs b/yazi-dds/src/client.rs index d17a7a21..0bdccce9 100644 --- a/yazi-dds/src/client.rs +++ b/yazi-dds/src/client.rs @@ -24,7 +24,7 @@ pub struct Client { pub(super) abilities: HashSet, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct Peer { pub(super) abilities: HashSet, } diff --git a/yazi-dds/src/ember/bulk.rs b/yazi-dds/src/ember/bulk.rs index 2847db2a..c4482b55 100644 --- a/yazi-dds/src/ember/bulk.rs +++ b/yazi-dds/src/ember/bulk.rs @@ -7,7 +7,7 @@ use yazi_shared::url::UrlBuf; use super::Ember; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct EmberBulk<'a> { pub changes: HashMap, Cow<'a, UrlBuf>>, } diff --git a/yazi-dds/src/ember/bye.rs b/yazi-dds/src/ember/bye.rs index cd8efd39..2b16edd8 100644 --- a/yazi-dds/src/ember/bye.rs +++ b/yazi-dds/src/ember/bye.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use super::Ember; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct EmberBye; impl EmberBye { diff --git a/yazi-dds/src/ember/cd.rs b/yazi-dds/src/ember/cd.rs index 5bd413ec..d8219eab 100644 --- a/yazi-dds/src/ember/cd.rs +++ b/yazi-dds/src/ember/cd.rs @@ -6,7 +6,7 @@ use yazi_shared::{Id, url::UrlBuf}; use super::Ember; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct EmberCd<'a> { pub tab: Id, pub url: Cow<'a, UrlBuf>, diff --git a/yazi-dds/src/ember/delete.rs b/yazi-dds/src/ember/delete.rs index fcbe4692..c0a9ff25 100644 --- a/yazi-dds/src/ember/delete.rs +++ b/yazi-dds/src/ember/delete.rs @@ -6,7 +6,7 @@ use yazi_shared::url::UrlBuf; use super::Ember; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct EmberDelete<'a> { pub urls: Cow<'a, Vec>, } diff --git a/yazi-dds/src/ember/hey.rs b/yazi-dds/src/ember/hey.rs index cd317a7e..850fdb46 100644 --- a/yazi-dds/src/ember/hey.rs +++ b/yazi-dds/src/ember/hey.rs @@ -7,7 +7,7 @@ use super::{Ember, EmberHi}; use crate::Peer; /// Server handshake -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct EmberHey { pub peers: HashMap, pub version: SStr, diff --git a/yazi-dds/src/ember/hi.rs b/yazi-dds/src/ember/hi.rs index e62cda38..5ebe4534 100644 --- a/yazi-dds/src/ember/hi.rs +++ b/yazi-dds/src/ember/hi.rs @@ -8,7 +8,7 @@ use yazi_shared::SStr; use super::Ember; /// Client handshake -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct EmberHi<'a> { /// Kinds of events the client can handle pub abilities: HashSet>, diff --git a/yazi-dds/src/ember/hover.rs b/yazi-dds/src/ember/hover.rs index 48b1432f..ea09cbd9 100644 --- a/yazi-dds/src/ember/hover.rs +++ b/yazi-dds/src/ember/hover.rs @@ -6,7 +6,7 @@ use yazi_shared::{Id, url::UrlBuf}; use super::Ember; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct EmberHover<'a> { pub tab: Id, pub url: Option>, diff --git a/yazi-dds/src/ember/load.rs b/yazi-dds/src/ember/load.rs index f1196e67..a174d79a 100644 --- a/yazi-dds/src/ember/load.rs +++ b/yazi-dds/src/ember/load.rs @@ -7,7 +7,7 @@ use yazi_shared::{Id, url::UrlBuf}; use super::Ember; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct EmberLoad<'a> { pub tab: Id, pub url: Cow<'a, UrlBuf>, diff --git a/yazi-dds/src/ember/mount.rs b/yazi-dds/src/ember/mount.rs index 8cf86ef6..6e061299 100644 --- a/yazi-dds/src/ember/mount.rs +++ b/yazi-dds/src/ember/mount.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use super::Ember; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct EmberMount; impl EmberMount { diff --git a/yazi-dds/src/ember/move.rs b/yazi-dds/src/ember/move.rs index a08d7354..49db53e5 100644 --- a/yazi-dds/src/ember/move.rs +++ b/yazi-dds/src/ember/move.rs @@ -6,7 +6,7 @@ use yazi_shared::url::UrlBuf; use super::Ember; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct EmberMove<'a> { pub items: Cow<'a, Vec>, } @@ -34,7 +34,7 @@ impl IntoLua for EmberMove<'_> { } // --- Item -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct BodyMoveItem { pub from: UrlBuf, pub to: UrlBuf, diff --git a/yazi-dds/src/ember/rename.rs b/yazi-dds/src/ember/rename.rs index 6fa3f6c7..85810ef7 100644 --- a/yazi-dds/src/ember/rename.rs +++ b/yazi-dds/src/ember/rename.rs @@ -6,7 +6,7 @@ use yazi_shared::{Id, url::UrlBuf}; use super::Ember; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct EmberRename<'a> { pub tab: Id, pub from: Cow<'a, UrlBuf>, diff --git a/yazi-dds/src/ember/tab.rs b/yazi-dds/src/ember/tab.rs index 6e17e78c..23064421 100644 --- a/yazi-dds/src/ember/tab.rs +++ b/yazi-dds/src/ember/tab.rs @@ -4,7 +4,7 @@ use yazi_shared::Id; use super::Ember; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct EmberTab { pub id: Id, } diff --git a/yazi-dds/src/ember/trash.rs b/yazi-dds/src/ember/trash.rs index c39e548e..020dbaad 100644 --- a/yazi-dds/src/ember/trash.rs +++ b/yazi-dds/src/ember/trash.rs @@ -6,7 +6,7 @@ use yazi_shared::url::UrlBuf; use super::Ember; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct EmberTrash<'a> { pub urls: Cow<'a, Vec>, } diff --git a/yazi-dds/src/ember/yank.rs b/yazi-dds/src/ember/yank.rs index d57ee948..7ea3a894 100644 --- a/yazi-dds/src/ember/yank.rs +++ b/yazi-dds/src/ember/yank.rs @@ -8,7 +8,7 @@ use yazi_shared::url::UrlBufCov; use super::Ember; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct EmberYank<'a>(UpdateYankedOpt<'a>); impl<'a> EmberYank<'a> { diff --git a/yazi-dds/src/state.rs b/yazi-dds/src/state.rs index c33f0b6b..757bc4b4 100644 --- a/yazi-dds/src/state.rs +++ b/yazi-dds/src/state.rs @@ -58,7 +58,7 @@ impl State { return Ok(()); } - Local::create_dir_all(&BOOT.state_dir).await?; + Local.create_dir_all(&BOOT.state_dir).await?; let mut buf = BufWriter::new( Gate::default() .write(true) @@ -79,7 +79,7 @@ impl State { } async fn load(&self) -> Result<()> { - let mut file = BufReader::new(Local::open(BOOT.state_dir.join(".dds")).await?); + let mut file = BufReader::new(Local.open(BOOT.state_dir.join(".dds")).await?); let mut buf = String::new(); let mut inner = HashMap::new(); @@ -103,7 +103,7 @@ impl State { } async fn skip(&self) -> Result { - let cha = Local::symlink_metadata(BOOT.state_dir.join(".dds")).await?; + let cha = Local.symlink_metadata(BOOT.state_dir.join(".dds")).await?; let modified = cha.mtime_dur()?.as_micros(); Ok(modified >= self.last.load(Ordering::Relaxed) as u128) } diff --git a/yazi-dds/src/stream.rs b/yazi-dds/src/stream.rs index ed67add8..ede51d00 100644 --- a/yazi-dds/src/stream.rs +++ b/yazi-dds/src/stream.rs @@ -40,7 +40,7 @@ impl Stream { let p = Self::socket_file(); - yazi_fs::provider::local::Local::remove_file(&p).await.ok(); + yazi_fs::provider::local::Local.remove_file(&p).await.ok(); tokio::net::UnixListener::bind(p) } diff --git a/yazi-fm/Cargo.toml b/yazi-fm/Cargo.toml index 1ffd590b..1f20ca23 100644 --- a/yazi-fm/Cargo.toml +++ b/yazi-fm/Cargo.toml @@ -37,6 +37,7 @@ yazi-plugin = { path = "../yazi-plugin", version = "25.9.15" } yazi-proxy = { path = "../yazi-proxy", version = "25.9.15" } yazi-shared = { path = "../yazi-shared", version = "25.9.15" } yazi-term = { path = "../yazi-term", version = "25.9.15" } +yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" } yazi-watcher = { path = "../yazi-watcher", version = "25.9.15" } yazi-widgets = { path = "../yazi-widgets", version = "25.9.15" } diff --git a/yazi-fm/src/app/commands/quit.rs b/yazi-fm/src/app/commands/quit.rs index d1fd8dfb..8b0625f4 100644 --- a/yazi-fm/src/app/commands/quit.rs +++ b/yazi-fm/src/app/commands/quit.rs @@ -26,13 +26,13 @@ impl App { async fn cwd_to_file(&self, no: bool) { if let Some(p) = ARGS.cwd_file.as_ref().filter(|_| !no) { let cwd = self.core.mgr.cwd().os_str(); - Local::write(p, cwd.as_encoded_bytes()).await.ok(); + Local.write(p, cwd.as_encoded_bytes()).await.ok(); } } async fn selected_to_file(&self, selected: Option) { if let (Some(s), Some(p)) = (selected, &ARGS.chooser_file) { - Local::write(p, s.as_encoded_bytes()).await.ok(); + Local.write(p, s.as_encoded_bytes()).await.ok(); } } } diff --git a/yazi-fm/src/logs.rs b/yazi-fm/src/logs.rs index 7e4be580..48dfc8c3 100644 --- a/yazi-fm/src/logs.rs +++ b/yazi-fm/src/logs.rs @@ -4,8 +4,8 @@ use anyhow::Context; use crossterm::style::{Color, Print, ResetColor, SetForegroundColor}; use tracing_appender::non_blocking::WorkerGuard; use tracing_subscriber::EnvFilter; -use yazi_fs::Xdg; use yazi_shared::{LOG_LEVEL, RoCell}; +use yazi_vfs::local::Xdg; static _GUARD: RoCell = RoCell::new(); diff --git a/yazi-fs/Cargo.toml b/yazi-fs/Cargo.toml index 8977621d..d33df641 100644 --- a/yazi-fs/Cargo.toml +++ b/yazi-fs/Cargo.toml @@ -13,6 +13,7 @@ yazi-ffi = { path = "../yazi-ffi", version = "25.9.15" } yazi-macro = { path = "../yazi-macro", version = "25.9.15" } yazi-sftp = { path = "../yazi-sftp", version = "0.1.0" } yazi-shared = { path = "../yazi-shared", version = "25.9.15" } +yazi-vfs = { path = "../yazi-vfs", version = "25.9.15" } # External dependencies anyhow = { workspace = true } @@ -25,6 +26,7 @@ futures = { workspace = true } hashbrown = { workspace = true } parking_lot = { workspace = true } regex = { workspace = true } +russh = { workspace = true } scopeguard = { workspace = true } serde = { workspace = true } tokio = { workspace = true } diff --git a/yazi-fs/src/files.rs b/yazi-fs/src/files.rs index 005a4c38..bf4b6138 100644 --- a/yazi-fs/src/files.rs +++ b/yazi-fs/src/files.rs @@ -5,7 +5,7 @@ use tokio::{select, sync::mpsc::{self, UnboundedReceiver}}; use yazi_shared::{Id, url::{UrlBuf, Urn, UrnBuf}}; use super::{FilesSorter, Filter}; -use crate::{FILES_TICKET, File, FilesOp, SortBy, cha::Cha, mounts::PARTITIONS, provider::{self, DirEntry}}; +use crate::{FILES_TICKET, File, FilesOp, SortBy, cha::Cha, mounts::PARTITIONS, provider::{self, DirEntry, DirReader, FileHolder}}; #[derive(Default)] pub struct Files { @@ -40,7 +40,7 @@ impl Files { let (tx, rx) = mpsc::unbounded_channel(); tokio::spawn(async move { - while let Ok(Some(ent)) = it.next_entry().await { + while let Ok(Some(ent)) = it.next().await { select! { _ = tx.closed() => break, result = ent.metadata() => { @@ -59,7 +59,7 @@ impl Files { pub async fn from_dir_bulk(dir: &UrlBuf) -> std::io::Result> { let mut it = provider::read_dir(dir).await?; let mut entries = Vec::with_capacity(5000); - while let Ok(Some(entry)) = it.next_entry().await { + while let Ok(Some(entry)) = it.next().await { entries.push(entry); } diff --git a/yazi-fs/src/fns.rs b/yazi-fs/src/fns.rs index ecdb92d0..316ae137 100644 --- a/yazi-fs/src/fns.rs +++ b/yazi-fs/src/fns.rs @@ -2,7 +2,7 @@ use anyhow::Result; use tokio::{io, select, sync::{mpsc, oneshot}, time}; use yazi_shared::url::{Component, Url, UrlBuf}; -use crate::{cha::Cha, provider}; +use crate::{cha::Cha, provider::{self, DirReader, FileHolder}}; #[inline] pub async fn maybe_exists<'a>(url: impl Into>) -> bool { @@ -83,7 +83,7 @@ pub fn copy_with_progress( pub async fn remove_dir_clean(dir: &UrlBuf) { let Ok(mut it) = provider::read_dir(dir).await else { return }; - while let Ok(Some(ent)) = it.next_entry().await { + while let Ok(Some(ent)) = it.next().await { if ent.file_type().await.is_ok_and(|t| t.is_dir()) { let url = ent.url(); Box::pin(remove_dir_clean(&url)).await; diff --git a/yazi-fs/src/lib.rs b/yazi-fs/src/lib.rs index 8c80d0d4..a5dc1fa1 100644 --- a/yazi-fs/src/lib.rs +++ b/yazi-fs/src/lib.rs @@ -2,7 +2,7 @@ yazi_macro::mod_pub!(cha mounts provider path); -yazi_macro::mod_flat!(cwd file files filter fns op sorter sorting stage xdg); +yazi_macro::mod_flat!(cwd file files filter fns op sorter sorting stage); pub fn init() { CWD.init(<_>::default()); diff --git a/yazi-fs/src/provider/calculator.rs b/yazi-fs/src/provider/calculator.rs index 85920f7a..bd7adbd4 100644 --- a/yazi-fs/src/provider/calculator.rs +++ b/yazi-fs/src/provider/calculator.rs @@ -2,7 +2,7 @@ use std::{collections::VecDeque, io, time::{Duration, Instant}}; use yazi_shared::{Either, url::{Url, UrlBuf}}; -use crate::provider::{self, ReadDir}; +use crate::provider::{self, DirReader, FileHolder, ReadDir}; pub enum SizeCalculator { File(Option), @@ -65,7 +65,7 @@ impl SizeCalculator { }; } - let Ok(Some(ent)) = front.right_mut()?.next_entry().await else { + let Ok(Some(ent)) = front.right_mut()?.next().await else { pop_and_continue!(); }; diff --git a/yazi-fs/src/provider/dir_entry.rs b/yazi-fs/src/provider/dir_entry.rs index 276251ca..169c8c42 100644 --- a/yazi-fs/src/provider/dir_entry.rs +++ b/yazi-fs/src/provider/dir_entry.rs @@ -1,41 +1,56 @@ -use std::{borrow::Cow, ffi::OsStr, io}; +use std::{borrow::Cow, ffi::OsStr, io, sync::Arc}; use yazi_shared::url::UrlBuf; use crate::{cha::{Cha, ChaType}, provider::FileHolder}; pub enum DirEntry { - Local(super::local::DirEntry), + Regular(super::local::DirEntry), + Search((Arc, super::local::DirEntry)), + Sftp((Arc, super::sftp::DirEntry)), } -impl From for DirEntry { - fn from(value: super::local::DirEntry) -> Self { Self::Local(value) } +impl FileHolder for DirEntry { + fn path(&self) -> std::path::PathBuf { + match self { + Self::Regular(ent) => ent.path(), + Self::Search((_, ent)) => ent.path(), + Self::Sftp((_, ent)) => ent.path(), + } + } + + fn name(&self) -> Cow<'_, OsStr> { + match self { + Self::Regular(ent) => ent.name(), + Self::Search((_, ent)) => ent.name(), + Self::Sftp((_, ent)) => ent.name(), + } + } + + async fn metadata(&self) -> io::Result { + match self { + Self::Regular(ent) => ent.metadata().await, + Self::Search((_, ent)) => ent.metadata().await, + Self::Sftp((_, ent)) => ent.metadata().await, + } + } + + async fn file_type(&self) -> io::Result { + match self { + Self::Regular(ent) => ent.file_type().await, + Self::Search((_, ent)) => ent.file_type().await, + Self::Sftp((_, ent)) => ent.file_type().await, + } + } } impl DirEntry { #[must_use] pub fn url(&self) -> UrlBuf { match self { - Self::Local(local) => local.path().into(), - } - } - - #[must_use] - pub fn name(&self) -> Cow<'_, OsStr> { - match self { - Self::Local(local) => local.name(), - } - } - - pub async fn metadata(&self) -> io::Result { - match self { - Self::Local(local) => local.metadata().await, - } - } - - pub async fn file_type(&self) -> io::Result { - match self { - Self::Local(local) => local.file_type().await, + Self::Regular(ent) => ent.path().into(), + Self::Search((dir, ent)) => dir.join(ent.name()), + Self::Sftp((dir, ent)) => dir.join(ent.name()), } } } diff --git a/yazi-fs/src/provider/gate.rs b/yazi-fs/src/provider/gate.rs new file mode 100644 index 00000000..308ab7ee --- /dev/null +++ b/yazi-fs/src/provider/gate.rs @@ -0,0 +1,90 @@ +use std::{io, path::Path}; + +use yazi_shared::scheme::SchemeRef; + +use crate::provider::FileBuilder; + +pub enum Gate { + Local(super::local::Gate), + Sftp(super::sftp::Gate), +} + +impl From for Gate { + fn from(value: super::local::Gate) -> Self { Self::Local(value) } +} + +impl From for Gate { + fn from(value: super::sftp::Gate) -> Self { Self::Sftp(value) } +} + +impl FileBuilder for Gate { + type File = super::RwFile; + + fn append(&mut self, append: bool) -> &mut Self { + match self { + Self::Local(g) => _ = g.append(append), + Self::Sftp(g) => _ = g.append(append), + }; + self + } + + fn create(&mut self, create: bool) -> &mut Self { + match self { + Self::Local(g) => _ = g.create(create), + Self::Sftp(g) => _ = g.create(create), + }; + self + } + + fn create_new(&mut self, create_new: bool) -> &mut Self { + match self { + Self::Local(g) => _ = g.create_new(create_new), + Self::Sftp(g) => _ = g.create_new(create_new), + }; + self + } + + async fn new(scheme: SchemeRef<'_>) -> io::Result { + Ok(match scheme { + SchemeRef::Regular | SchemeRef::Search(_) => super::local::Gate::new(scheme).await?.into(), + SchemeRef::Archive(_) => { + Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem: archive"))? + } + SchemeRef::Sftp(_) => super::sftp::Gate::new(scheme).await?.into(), + }) + } + + async fn open

(&self, path: P) -> io::Result + where + P: AsRef, + { + Ok(match self { + Gate::Local(g) => g.open(path).await?.into(), + Gate::Sftp(g) => g.open(path).await?.into(), + }) + } + + fn read(&mut self, read: bool) -> &mut Self { + match self { + Self::Local(g) => _ = g.read(read), + Self::Sftp(g) => _ = g.read(read), + }; + self + } + + fn truncate(&mut self, truncate: bool) -> &mut Self { + match self { + Self::Local(g) => _ = g.truncate(truncate), + Self::Sftp(g) => _ = g.truncate(truncate), + }; + self + } + + fn write(&mut self, write: bool) -> &mut Self { + match self { + Self::Local(g) => _ = g.write(write), + Self::Sftp(g) => _ = g.write(write), + }; + self + } +} diff --git a/yazi-fs/src/provider/local/gate.rs b/yazi-fs/src/provider/local/gate.rs index 8f55cc9e..2cfac5ba 100644 --- a/yazi-fs/src/provider/local/gate.rs +++ b/yazi-fs/src/provider/local/gate.rs @@ -1,5 +1,7 @@ use std::{io, path::Path}; +use yazi_shared::scheme::SchemeRef; + use crate::provider::FileBuilder; #[derive(Default)] @@ -23,6 +25,14 @@ impl FileBuilder for Gate { self } + async fn new(scheme: SchemeRef<'_>) -> io::Result { + if scheme.is_virtual() { + Err(io::Error::new(io::ErrorKind::InvalidInput, "Not a local filesystem"))? + } else { + Ok(Self::default()) + } + } + async fn open

(&self, path: P) -> io::Result where P: AsRef, diff --git a/yazi-fs/src/provider/local/local.rs b/yazi-fs/src/provider/local/local.rs index 1547db53..d8f0ef54 100644 --- a/yazi-fs/src/provider/local/local.rs +++ b/yazi-fs/src/provider/local/local.rs @@ -2,6 +2,7 @@ use std::{io, path::{Path, PathBuf}}; use crate::{cha::Cha, provider::Provider}; +#[derive(Clone, Copy)] pub struct Local; impl Provider for Local { @@ -10,7 +11,7 @@ impl Provider for Local { type ReadDir = super::ReadDir; #[inline] - fn cache

(_: P) -> Option + fn cache

(&self, _: P) -> Option where P: AsRef, { @@ -18,7 +19,7 @@ impl Provider for Local { } #[inline] - async fn canonicalize

(path: P) -> io::Result + async fn canonicalize

(&self, path: P) -> io::Result where P: AsRef, { @@ -26,7 +27,7 @@ impl Provider for Local { } #[inline] - async fn copy(from: P, to: Q, cha: Cha) -> io::Result + async fn copy(&self, from: P, to: Q, cha: Cha) -> io::Result where P: AsRef, Q: AsRef, @@ -37,7 +38,7 @@ impl Provider for Local { } #[inline] - async fn create_dir

(path: P) -> io::Result<()> + async fn create_dir

(&self, path: P) -> io::Result<()> where P: AsRef, { @@ -45,7 +46,7 @@ impl Provider for Local { } #[inline] - async fn create_dir_all

(path: P) -> io::Result<()> + async fn create_dir_all

(&self, path: P) -> io::Result<()> where P: AsRef, { @@ -53,7 +54,10 @@ impl Provider for Local { } #[inline] - async fn hard_link(original: P, link: Q) -> io::Result<()> + async fn gate(&self) -> io::Result { Ok(Self::Gate::default()) } + + #[inline] + async fn hard_link(&self, original: P, link: Q) -> io::Result<()> where P: AsRef, Q: AsRef, @@ -62,7 +66,7 @@ impl Provider for Local { } #[inline] - async fn metadata

(path: P) -> io::Result + async fn metadata

(&self, path: P) -> io::Result where P: AsRef, { @@ -71,7 +75,7 @@ impl Provider for Local { } #[inline] - async fn read_dir

(path: P) -> io::Result + async fn read_dir

(&self, path: P) -> io::Result where P: AsRef, { @@ -79,7 +83,7 @@ impl Provider for Local { } #[inline] - async fn read_link

(path: P) -> io::Result + async fn read_link

(&self, path: P) -> io::Result where P: AsRef, { @@ -87,7 +91,7 @@ impl Provider for Local { } #[inline] - async fn remove_dir

(path: P) -> io::Result<()> + async fn remove_dir

(&self, path: P) -> io::Result<()> where P: AsRef, { @@ -95,7 +99,7 @@ impl Provider for Local { } #[inline] - async fn remove_dir_all

(path: P) -> io::Result<()> + async fn remove_dir_all

(&self, path: P) -> io::Result<()> where P: AsRef, { @@ -103,7 +107,7 @@ impl Provider for Local { } #[inline] - async fn remove_file

(path: P) -> io::Result<()> + async fn remove_file

(&self, path: P) -> io::Result<()> where P: AsRef, { @@ -111,7 +115,7 @@ impl Provider for Local { } #[inline] - async fn rename(from: P, to: Q) -> io::Result<()> + async fn rename(&self, from: P, to: Q) -> io::Result<()> where P: AsRef, Q: AsRef, @@ -120,7 +124,7 @@ impl Provider for Local { } #[inline] - async fn symlink(original: P, link: Q, _is_dir: F) -> io::Result<()> + async fn symlink(&self, original: P, link: Q, _is_dir: F) -> io::Result<()> where P: AsRef, Q: AsRef, @@ -132,14 +136,14 @@ impl Provider for Local { } #[cfg(windows)] if _is_dir().await? { - Self::symlink_dir(original, link).await + self.symlink_dir(original, link).await } else { - Self::symlink_file(original, link).await + self.symlink_file(original, link).await } } #[inline] - async fn symlink_dir(original: P, link: Q) -> io::Result<()> + async fn symlink_dir(&self, original: P, link: Q) -> io::Result<()> where P: AsRef, Q: AsRef, @@ -155,7 +159,7 @@ impl Provider for Local { } #[inline] - async fn symlink_file(original: P, link: Q) -> io::Result<()> + async fn symlink_file(&self, original: P, link: Q) -> io::Result<()> where P: AsRef, Q: AsRef, @@ -171,7 +175,7 @@ impl Provider for Local { } #[inline] - async fn symlink_metadata

(path: P) -> io::Result + async fn symlink_metadata

(&self, path: P) -> io::Result where P: AsRef, { @@ -179,7 +183,7 @@ impl Provider for Local { Ok(Cha::new(path.file_name().unwrap_or_default(), tokio::fs::symlink_metadata(path).await?)) } - async fn trash

(path: P) -> io::Result<()> + async fn trash

(&self, path: P) -> io::Result<()> where P: AsRef, { @@ -205,7 +209,7 @@ impl Provider for Local { } #[inline] - async fn write(path: P, contents: C) -> io::Result<()> + async fn write(&self, path: P, contents: C) -> io::Result<()> where P: AsRef, C: AsRef<[u8]>, @@ -264,7 +268,7 @@ impl Local { } #[inline] - pub async fn read

(path: P) -> io::Result> + pub async fn read

(&self, path: P) -> io::Result> where P: AsRef, { @@ -272,7 +276,7 @@ impl Local { } #[inline] - pub async fn read_to_string

(path: P) -> io::Result + pub async fn read_to_string

(&self, path: P) -> io::Result where P: AsRef, { diff --git a/yazi-fs/src/provider/local/read_dir.rs b/yazi-fs/src/provider/local/read_dir.rs index ab1b90d9..88790809 100644 --- a/yazi-fs/src/provider/local/read_dir.rs +++ b/yazi-fs/src/provider/local/read_dir.rs @@ -5,9 +5,9 @@ use crate::provider::DirReader; pub struct ReadDir(pub(super) tokio::fs::ReadDir); impl DirReader for ReadDir { - type Entry<'a> = super::DirEntry; + type Entry = super::DirEntry; - async fn next(&mut self) -> io::Result>> { + async fn next(&mut self) -> io::Result> { self.0.next_entry().await.map(|entry| entry.map(super::DirEntry)) } } diff --git a/yazi-fs/src/provider/mod.rs b/yazi-fs/src/provider/mod.rs index a303ec3e..4e1154a9 100644 --- a/yazi-fs/src/provider/mod.rs +++ b/yazi-fs/src/provider/mod.rs @@ -1,5 +1,5 @@ yazi_macro::mod_pub!(local sftp); -yazi_macro::mod_flat!(calculator dir_entry provider read_dir rw_file traits); +yazi_macro::mod_flat!(calculator dir_entry gate provider providers read_dir rw_file traits); pub fn init() { sftp::init(); } diff --git a/yazi-fs/src/provider/provider.rs b/yazi-fs/src/provider/provider.rs index 17a4ed86..fa3b5bc9 100644 --- a/yazi-fs/src/provider/provider.rs +++ b/yazi-fs/src/provider/provider.rs @@ -1,6 +1,6 @@ -use std::{io, path::{Path, PathBuf}}; +use std::{io, path::{Path, PathBuf}, sync::Arc}; -use yazi_shared::url::{Url, UrlBuf}; +use yazi_shared::{scheme::SchemeRef, url::{Url, UrlBuf}}; use crate::{cha::Cha, provider::{Provider, ReadDir, RwFile, local::{self, Local}}}; @@ -9,7 +9,7 @@ pub fn cache<'a, U>(url: U) -> Option where U: Into>, { - if let Some(path) = url.into().as_path() { Local::cache(path) } else { None } + if let Some(path) = url.into().as_path() { Local.cache(path) } else { None } } #[inline] @@ -31,7 +31,7 @@ where U: Into>, { if let Some(path) = url.into().as_path() { - Local::canonicalize(path).await.map(Into::into) + Local.canonicalize(path).await.map(Into::into) } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -56,7 +56,7 @@ where V: Into>, { if let (Some(from), Some(to)) = (from.into().as_path(), to.into().as_path()) { - Local::copy(from, to, cha).await + Local.copy(from, to, cha).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -68,7 +68,7 @@ where U: Into>, { if let Some(path) = url.into().as_path() { - Local::create(path).await.map(Into::into) + Local.create(path).await.map(Into::into) } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -80,7 +80,7 @@ where U: Into>, { if let Some(path) = url.into().as_path() { - Local::create_dir(path).await + Local.create_dir(path).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -92,7 +92,7 @@ where U: Into>, { if let Some(path) = url.into().as_path() { - Local::create_dir_all(path).await + Local.create_dir_all(path).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -105,7 +105,7 @@ where V: Into>, { if let (Some(original), Some(link)) = (original.into().as_path(), link.into().as_path()) { - Local::hard_link(original, link).await + Local.hard_link(original, link).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -130,7 +130,7 @@ where U: Into>, { if let Some(path) = url.into().as_path() { - Local::metadata(path).await + Local.metadata(path).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -150,11 +150,17 @@ pub async fn read_dir<'a, U>(url: U) -> io::Result where U: Into>, { - if let Some(path) = url.into().as_path() { - Local::read_dir(path).await.map(Into::into) - } else { - Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) - } + let url: Url = url.into(); + Ok(match url.scheme { + SchemeRef::Regular => ReadDir::Regular(Local.read_dir(url.loc).await?), + SchemeRef::Search(_) => { + ReadDir::Search((Arc::new(url.to_owned()), Local.read_dir(url.loc).await?)) + } + SchemeRef::Archive(_) => { + Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))? + } + SchemeRef::Sftp(_) => todo!(), + }) } #[inline] @@ -163,7 +169,7 @@ where U: Into>, { if let Some(path) = url.into().as_path() { - Local::read_link(path).await + Local.read_link(path).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -175,7 +181,7 @@ where U: Into>, { if let Some(path) = url.into().as_path() { - Local::remove_dir(path).await + Local.remove_dir(path).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -187,7 +193,7 @@ where U: Into>, { if let Some(path) = url.into().as_path() { - Local::remove_dir_all(path).await + Local.remove_dir_all(path).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -199,7 +205,7 @@ where U: Into>, { if let Some(path) = url.into().as_path() { - Local::remove_file(path).await + Local.remove_file(path).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -212,7 +218,7 @@ where V: Into>, { if let (Some(from), Some(to)) = (from.into().as_path(), to.into().as_path()) { - Local::rename(from, to).await + Local.rename(from, to).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -225,7 +231,7 @@ where F: AsyncFnOnce() -> io::Result, { if let Some(link) = link.into().as_path() { - Local::symlink(original, link, is_dir).await + Local.symlink(original, link, is_dir).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -237,7 +243,7 @@ where U: Into>, { if let Some(link) = link.into().as_path() { - Local::symlink_dir(original, link).await + Local.symlink_dir(original, link).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -249,7 +255,7 @@ where U: Into>, { if let Some(link) = link.into().as_path() { - Local::symlink_file(original, link).await + Local.symlink_file(original, link).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -261,7 +267,7 @@ where U: Into>, { if let Some(path) = url.into().as_path() { - Local::symlink_metadata(path).await + Local.symlink_metadata(path).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -273,7 +279,7 @@ where U: Into>, { if let Some(path) = url.into().as_path() { - Local::trash(path).await + Local.trash(path).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } @@ -286,7 +292,7 @@ where C: AsRef<[u8]>, { if let Some(path) = url.into().as_path() { - Local::write(path, contents).await + Local.write(path, contents).await } else { Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) } diff --git a/yazi-fs/src/provider/providers.rs b/yazi-fs/src/provider/providers.rs new file mode 100644 index 00000000..ddd4516b --- /dev/null +++ b/yazi-fs/src/provider/providers.rs @@ -0,0 +1,266 @@ +use std::{io, path::{Path, PathBuf}, sync::Arc}; + +use yazi_shared::{scheme::SchemeRef, url::Url}; +use yazi_vfs::config::{ProviderSftp, Vfs}; + +use super::local::Local; +use crate::{cha::Cha, provider::Provider}; + +pub(super) struct Providers<'a>(Inner<'a>); + +enum Inner<'a> { + Regular, + Search(Url<'a>), + Sftp((super::sftp::Sftp, Url<'a>)), +} + +impl<'a> Providers<'a> { + pub(super) async fn new(url: Url<'a>) -> io::Result { + Ok(match url.scheme { + SchemeRef::Regular => Self(Inner::Regular), + SchemeRef::Search(_) => Self(Inner::Search(url)), + SchemeRef::Archive(_) => { + Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem: archive"))? + } + SchemeRef::Sftp(name) => { + Self(Inner::Sftp((Vfs::provider::<&ProviderSftp>(name).await?.into(), url))) + } + }) + } +} + +impl Provider for Providers<'_> { + type File = super::RwFile; + type Gate = super::Gate; + type ReadDir = super::ReadDir; + + fn cache

(&self, path: P) -> Option + where + P: AsRef, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.cache(path), + Inner::Sftp((p, _)) => p.cache(path), + } + } + + async fn canonicalize

(&self, path: P) -> io::Result + where + P: AsRef, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.canonicalize(path).await, + Inner::Sftp((p, _)) => p.canonicalize(path).await, + } + } + + async fn copy(&self, from: P, to: Q, cha: Cha) -> io::Result + where + P: AsRef, + Q: AsRef, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.copy(from, to, cha).await, + Inner::Sftp((p, _)) => p.copy(from, to, cha).await, + } + } + + async fn create

(&self, path: P) -> io::Result + where + P: AsRef, + { + Ok(match self.0 { + Inner::Regular | Inner::Search(_) => Local.create(path).await?.into(), + Inner::Sftp((p, _)) => p.create(path).await?.into(), + }) + } + + async fn create_dir

(&self, path: P) -> io::Result<()> + where + P: AsRef, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.create_dir(path).await, + Inner::Sftp((p, _)) => p.create_dir(path).await, + } + } + + async fn create_dir_all

(&self, path: P) -> io::Result<()> + where + P: AsRef, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.create_dir_all(path).await, + Inner::Sftp((p, _)) => p.create_dir_all(path).await, + } + } + + async fn gate(&self) -> io::Result { + Ok(match self.0 { + Inner::Regular | Inner::Search(_) => Local.gate().await?.into(), + Inner::Sftp((p, _)) => p.gate().await?.into(), + }) + } + + async fn hard_link(&self, original: P, link: Q) -> io::Result<()> + where + P: AsRef, + Q: AsRef, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.hard_link(original, link).await, + Inner::Sftp((p, _)) => p.hard_link(original, link).await, + } + } + + async fn metadata

(&self, path: P) -> io::Result + where + P: AsRef, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.metadata(path).await, + Inner::Sftp((p, _)) => p.metadata(path).await, + } + } + + async fn open

(&self, path: P) -> io::Result + where + P: AsRef, + { + Ok(match self.0 { + Inner::Regular | Inner::Search(_) => Local.open(path).await?.into(), + Inner::Sftp((p, _)) => p.open(path).await?.into(), + }) + } + + async fn read_dir

(&self, path: P) -> io::Result + where + P: AsRef, + { + Ok(match self.0 { + Inner::Regular => Self::ReadDir::Regular(Local.read_dir(path).await?), + Inner::Search(dir) => { + Self::ReadDir::Search((Arc::new(dir.to_owned()), Local.read_dir(path).await?)) + } + Inner::Sftp((p, dir)) => { + Self::ReadDir::Sftp((Arc::new(dir.to_owned()), p.read_dir(path).await?)) + } + }) + } + + async fn read_link

(&self, path: P) -> io::Result + where + P: AsRef, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.read_link(path).await, + Inner::Sftp((p, _)) => p.read_link(path).await, + } + } + + async fn remove_dir

(&self, path: P) -> io::Result<()> + where + P: AsRef, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.remove_dir(path).await, + Inner::Sftp((p, _)) => p.remove_dir(path).await, + } + } + + async fn remove_dir_all

(&self, path: P) -> io::Result<()> + where + P: AsRef, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.remove_dir_all(path).await, + Inner::Sftp((p, _)) => p.remove_dir_all(path).await, + } + } + + async fn remove_file

(&self, path: P) -> io::Result<()> + where + P: AsRef, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.remove_file(path).await, + Inner::Sftp((p, _)) => p.remove_file(path).await, + } + } + + async fn rename(&self, from: P, to: Q) -> io::Result<()> + where + P: AsRef, + Q: AsRef, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.rename(from, to).await, + Inner::Sftp((p, _)) => p.rename(from, to).await, + } + } + + async fn symlink(&self, original: P, link: Q, is_dir: F) -> io::Result<()> + where + P: AsRef, + Q: AsRef, + F: AsyncFnOnce() -> io::Result, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.symlink(original, link, is_dir).await, + Inner::Sftp((p, _)) => p.symlink(original, link, is_dir).await, + } + } + + async fn symlink_dir(&self, original: P, link: Q) -> io::Result<()> + where + P: AsRef, + Q: AsRef, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.symlink_dir(original, link).await, + Inner::Sftp((p, _)) => p.symlink_dir(original, link).await, + } + } + + async fn symlink_file(&self, original: P, link: Q) -> io::Result<()> + where + P: AsRef, + Q: AsRef, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.symlink_file(original, link).await, + Inner::Sftp((p, _)) => p.symlink_file(original, link).await, + } + } + + async fn symlink_metadata

(&self, path: P) -> io::Result + where + P: AsRef, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.symlink_metadata(path).await, + Inner::Sftp((p, _)) => p.symlink_metadata(path).await, + } + } + + async fn trash

(&self, path: P) -> io::Result<()> + where + P: AsRef, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.trash(path).await, + Inner::Sftp((p, _)) => p.trash(path).await, + } + } + + async fn write(&self, path: P, contents: C) -> io::Result<()> + where + P: AsRef, + C: AsRef<[u8]>, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.write(path, contents).await, + Inner::Sftp((p, _)) => p.write(path, contents).await, + } + } +} diff --git a/yazi-fs/src/provider/read_dir.rs b/yazi-fs/src/provider/read_dir.rs index 5e008e19..3e6a1f50 100644 --- a/yazi-fs/src/provider/read_dir.rs +++ b/yazi-fs/src/provider/read_dir.rs @@ -1,20 +1,27 @@ -use std::io; +use std::{io, sync::Arc}; + +use yazi_shared::url::UrlBuf; -use super::DirEntry; use crate::provider::DirReader; pub enum ReadDir { - Local(super::local::ReadDir), + Regular(super::local::ReadDir), + Search((Arc, super::local::ReadDir)), + Sftp((Arc, super::sftp::ReadDir)), } -impl From for ReadDir { - fn from(value: super::local::ReadDir) -> Self { Self::Local(value) } -} +impl DirReader for ReadDir { + type Entry = super::DirEntry; -impl ReadDir { - pub async fn next_entry(&mut self) -> io::Result> { - match self { - Self::Local(local) => local.next().await.map(|entry| entry.map(Into::into)), - } + async fn next(&mut self) -> io::Result> { + Ok(match self { + Self::Regular(reader) => reader.next().await?.map(Self::Entry::Regular), + Self::Search((dir, reader)) => { + reader.next().await?.map(|ent| Self::Entry::Search((dir.clone(), ent))) + } + Self::Sftp((dir, reader)) => { + reader.next().await?.map(|ent| Self::Entry::Sftp((dir.clone(), ent))) + } + }) } } diff --git a/yazi-fs/src/provider/rw_file.rs b/yazi-fs/src/provider/rw_file.rs index 69a74571..499c7d36 100644 --- a/yazi-fs/src/provider/rw_file.rs +++ b/yazi-fs/src/provider/rw_file.rs @@ -4,7 +4,7 @@ use tokio::io::{AsyncRead, AsyncWrite}; pub enum RwFile { Tokio(tokio::fs::File), - SFTP(Box), + Sftp(Box), } impl From for RwFile { @@ -12,7 +12,7 @@ impl From for RwFile { } impl From for RwFile { - fn from(f: yazi_sftp::fs::File) -> Self { Self::SFTP(Box::new(f)) } + fn from(f: yazi_sftp::fs::File) -> Self { Self::Sftp(Box::new(f)) } } impl AsyncRead for RwFile { @@ -24,7 +24,7 @@ impl AsyncRead for RwFile { ) -> std::task::Poll> { match &mut *self { Self::Tokio(f) => Pin::new(f).poll_read(cx, buf), - Self::SFTP(f) => Pin::new(f).poll_read(cx, buf), + Self::Sftp(f) => Pin::new(f).poll_read(cx, buf), } } } @@ -38,7 +38,7 @@ impl AsyncWrite for RwFile { ) -> std::task::Poll> { match &mut *self { Self::Tokio(f) => Pin::new(f).poll_write(cx, buf), - Self::SFTP(f) => Pin::new(f).poll_write(cx, buf), + Self::Sftp(f) => Pin::new(f).poll_write(cx, buf), } } @@ -49,7 +49,7 @@ impl AsyncWrite for RwFile { ) -> std::task::Poll> { match &mut *self { Self::Tokio(f) => Pin::new(f).poll_flush(cx), - Self::SFTP(f) => Pin::new(f).poll_flush(cx), + Self::Sftp(f) => Pin::new(f).poll_flush(cx), } } @@ -60,7 +60,7 @@ impl AsyncWrite for RwFile { ) -> std::task::Poll> { match &mut *self { Self::Tokio(f) => Pin::new(f).poll_shutdown(cx), - Self::SFTP(f) => Pin::new(f).poll_shutdown(cx), + Self::Sftp(f) => Pin::new(f).poll_shutdown(cx), } } @@ -72,7 +72,7 @@ impl AsyncWrite for RwFile { ) -> std::task::Poll> { 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::Sftp(f) => Pin::new(f).poll_write_vectored(cx, bufs), } } @@ -80,7 +80,7 @@ impl AsyncWrite for RwFile { fn is_write_vectored(&self) -> bool { match self { Self::Tokio(f) => f.is_write_vectored(), - Self::SFTP(f) => f.is_write_vectored(), + Self::Sftp(f) => f.is_write_vectored(), } } } diff --git a/yazi-fs/src/provider/sftp/gate.rs b/yazi-fs/src/provider/sftp/gate.rs index d35b17ac..6a345b76 100644 --- a/yazi-fs/src/provider/sftp/gate.rs +++ b/yazi-fs/src/provider/sftp/gate.rs @@ -1,11 +1,14 @@ use std::{io, path::Path}; use yazi_sftp::fs::{Attrs, Flags}; +use yazi_shared::scheme::SchemeRef; +use yazi_vfs::config::{ProviderSftp, Vfs}; use crate::provider::FileBuilder; -#[derive(Default)] pub struct Gate { + sftp: super::Sftp, + append: bool, create: bool, create_new: bool, @@ -32,6 +35,24 @@ impl FileBuilder for Gate { self } + async fn new(scheme: SchemeRef<'_>) -> io::Result { + let sftp: super::Sftp = match scheme { + SchemeRef::Sftp(name) => Vfs::provider::<&ProviderSftp>(name).await?.into(), + _ => Err(io::Error::new(io::ErrorKind::InvalidInput, "Not an SFTP URL"))?, + }; + + Ok(Self { + sftp, + + append: false, + create: false, + create_new: false, + read: false, + truncate: false, + write: false, + }) + } + async fn open

(&self, path: P) -> io::Result where P: AsRef, @@ -55,7 +76,8 @@ impl FileBuilder for Gate { if self.write { flags |= Flags::WRITE; } - Ok(super::Sftp::op().await?.open(&path, flags, Attrs::default()).await?) + + Ok(self.sftp.op().await?.open(&path, flags, Attrs::default()).await?) } fn read(&mut self, read: bool) -> &mut Self { diff --git a/yazi-fs/src/provider/sftp/metadata.rs b/yazi-fs/src/provider/sftp/metadata.rs index b9c66964..e9a4668b 100644 --- a/yazi-fs/src/provider/sftp/metadata.rs +++ b/yazi-fs/src/provider/sftp/metadata.rs @@ -2,10 +2,10 @@ use std::{ffi::OsStr, io, time::{Duration, UNIX_EPOCH}}; use crate::cha::{Cha, ChaKind, ChaMode}; -impl TryFrom> for Cha { +impl TryFrom for Cha { type Error = io::Error; - fn try_from(ent: yazi_sftp::fs::DirEntry<'_>) -> Result { + fn try_from(ent: yazi_sftp::fs::DirEntry) -> Result { let mut cha = Self::try_from((ent.name().as_ref(), ent.attrs()))?; cha.nlink = ent.nlink().unwrap_or_default(); Ok(cha) diff --git a/yazi-fs/src/provider/sftp/mod.rs b/yazi-fs/src/provider/sftp/mod.rs index f719af7d..a94405a8 100644 --- a/yazi-fs/src/provider/sftp/mod.rs +++ b/yazi-fs/src/provider/sftp/mod.rs @@ -1,6 +1,12 @@ yazi_macro::mod_flat!(gate metadata read_dir sftp); -pub(super) static CONN: yazi_shared::RoCell> = - yazi_shared::RoCell::new(); +pub(super) static CONN: yazi_shared::RoCell< + parking_lot::Mutex< + hashbrown::HashMap< + &'static yazi_vfs::config::ProviderSftp, + &'static deadpool::managed::Pool, + >, + >, +> = yazi_shared::RoCell::new(); -pub(super) fn init() { CONN.init(deadpool::managed::Pool::builder(Sftp).build().unwrap()); } +pub(super) fn init() { CONN.init(Default::default()); } diff --git a/yazi-fs/src/provider/sftp/read_dir.rs b/yazi-fs/src/provider/sftp/read_dir.rs index 2954d1ab..743937e1 100644 --- a/yazi-fs/src/provider/sftp/read_dir.rs +++ b/yazi-fs/src/provider/sftp/read_dir.rs @@ -5,17 +5,17 @@ use crate::{cha::{Cha, ChaMode, ChaType}, provider::{DirReader, FileHolder}}; pub struct ReadDir(pub(super) yazi_sftp::fs::ReadDir); impl DirReader for ReadDir { - type Entry<'a> = DirEntry<'a>; + type Entry = DirEntry; - async fn next(&mut self) -> io::Result>> { + async fn next(&mut self) -> io::Result> { Ok(self.0.next().await?.map(DirEntry)) } } // --- Entry -pub struct DirEntry<'a>(yazi_sftp::fs::DirEntry<'a>); +pub struct DirEntry(yazi_sftp::fs::DirEntry); -impl FileHolder for DirEntry<'_> { +impl FileHolder for DirEntry { fn path(&self) -> PathBuf { self.0.path() } fn name(&self) -> Cow<'_, OsStr> { self.0.name() } diff --git a/yazi-fs/src/provider/sftp/sftp.rs b/yazi-fs/src/provider/sftp/sftp.rs index 8e4a083e..a354e142 100644 --- a/yazi-fs/src/provider/sftp/sftp.rs +++ b/yazi-fs/src/provider/sftp/sftp.rs @@ -1,123 +1,137 @@ -use std::{io, path::{Path, PathBuf}}; +use std::{io, path::{Path, PathBuf}, sync::Arc}; use yazi_sftp::fs::{Attrs, Flags}; +use yazi_shared::scheme::SchemeRef; +use yazi_vfs::config::ProviderSftp; -use crate::{cha::Cha, provider::Provider}; +use crate::{cha::Cha, provider::{FileBuilder, Provider}}; -pub struct Sftp; +#[derive(Clone, Copy)] +pub struct Sftp { + name: &'static str, + config: &'static ProviderSftp, +} + +impl From<(&'static str, &'static ProviderSftp)> for Sftp { + fn from((name, config): (&'static str, &'static ProviderSftp)) -> Self { Self { name, config } } +} impl Provider for Sftp { type File = yazi_sftp::fs::File; type Gate = super::Gate; type ReadDir = super::ReadDir; - fn cache

(_: P) -> Option + fn cache

(&self, _: P) -> Option where P: AsRef, { todo!() } - async fn canonicalize

(path: P) -> io::Result + async fn canonicalize

(&self, path: P) -> io::Result where P: AsRef, { - Ok(Self::op().await?.realpath(&path).await?) + Ok(self.op().await?.realpath(&path).await?) } - async fn copy(from: P, to: Q, cha: Cha) -> io::Result + async fn copy(&self, from: P, to: Q, cha: Cha) -> io::Result where P: AsRef, Q: AsRef, { let attrs = Attrs::from(cha); - let op = Self::op().await?; + let op = self.op().await?; let mut from = op.open(&from, Flags::READ, Attrs::default()).await?; let mut to = op.open(&to, Flags::WRITE | Flags::CREATE | Flags::TRUNCATE, attrs).await?; tokio::io::copy(&mut from, &mut to).await } - async fn create_dir

(path: P) -> io::Result<()> + async fn create_dir

(&self, path: P) -> io::Result<()> where P: AsRef, { - Ok(Self::op().await?.mkdir(&path, Attrs::default()).await?) + Ok(self.op().await?.mkdir(&path, Attrs::default()).await?) } - async fn hard_link(original: P, link: Q) -> io::Result<()> + async fn gate(&self) -> io::Result { + super::Gate::new(SchemeRef::Sftp(self.name)).await + } + + async fn hard_link(&self, original: P, link: Q) -> io::Result<()> where P: AsRef, Q: AsRef, { - Ok(Self::op().await?.hardlink(&original, &link).await?) + Ok(self.op().await?.hardlink(&original, &link).await?) } - async fn metadata

(path: P) -> io::Result + async fn metadata

(&self, path: P) -> io::Result where P: AsRef, { let path = path.as_ref(); - let attrs = Self::op().await?.stat(path).await?; + let attrs = self.op().await?.stat(path).await?; (path.file_name().unwrap_or_default(), &attrs).try_into() } - async fn read_dir

(path: P) -> io::Result + async fn read_dir

(&self, path: P) -> io::Result where P: AsRef, { - Ok(super::ReadDir(Self::op().await?.read_dir(&path).await?)) + Ok(super::ReadDir(self.op().await?.read_dir(&path).await?)) } - async fn read_link

(path: P) -> io::Result + async fn read_link

(&self, path: P) -> io::Result where P: AsRef, { - Ok(Self::op().await?.readlink(&path).await?) + Ok(self.op().await?.readlink(&path).await?) } - async fn remove_dir

(path: P) -> io::Result<()> + async fn remove_dir

(&self, path: P) -> io::Result<()> where P: AsRef, { - Ok(Self::op().await?.rmdir(&path).await?) + Ok(self.op().await?.rmdir(&path).await?) } - async fn remove_file

(path: P) -> io::Result<()> + async fn remove_file

(&self, path: P) -> io::Result<()> where P: AsRef, { - Ok(Self::op().await?.remove(&path).await?) + Ok(self.op().await?.remove(&path).await?) } - async fn rename(from: P, to: Q) -> io::Result<()> + async fn rename(&self, from: P, to: Q) -> io::Result<()> where P: AsRef, Q: AsRef, { - Ok(Self::op().await?.rename(&from, &to).await?) + Ok(self.op().await?.rename(&from, &to).await?) } - async fn symlink(original: P, link: Q, _is_dir: F) -> io::Result<()> + async fn symlink(&self, original: P, link: Q, _is_dir: F) -> io::Result<()> where P: AsRef, Q: AsRef, F: AsyncFnOnce() -> io::Result, { - Ok(Self::op().await?.symlink(&original, &link).await?) + Ok(self.op().await?.symlink(&original, &link).await?) } - async fn symlink_metadata

(path: P) -> io::Result + async fn symlink_metadata

(&self, path: P) -> io::Result where P: AsRef, { let path = path.as_ref(); - let attrs = Self::op().await?.lstat(path).await?; + let attrs = self.op().await?.lstat(path).await?; (path.file_name().unwrap_or_default(), &attrs).try_into() } - async fn trash

(_path: P) -> io::Result<()> + async fn trash

(&self, _path: P) -> io::Result<()> where P: AsRef, { @@ -126,10 +140,14 @@ impl Provider for Sftp { } impl Sftp { - pub(super) async fn op() -> io::Result> { + pub(super) async fn op(self) -> io::Result> { use deadpool::managed::PoolError; - super::CONN.get().await.map_err(|e| match e { + let pool = *super::CONN.lock().entry(self.config).or_insert_with(|| { + Box::leak(Box::new(deadpool::managed::Pool::builder(self).build().unwrap())) + }); + + pool.get().await.map_err(|e| match e { PoolError::Timeout(_) => io::Error::new(io::ErrorKind::TimedOut, e.to_string()), PoolError::Backend(e) => e, PoolError::Closed | PoolError::NoRuntimeSpecified | PoolError::PostCreateHook(_) => { @@ -139,19 +157,60 @@ impl Sftp { } } +impl russh::client::Handler for Sftp { + type Error = russh::Error; + + async fn check_server_key( + &mut self, + _server_public_key: &russh::keys::PublicKey, + ) -> Result { + Ok(true) + } +} + impl deadpool::managed::Manager for Sftp { type Error = io::Error; type Type = yazi_sftp::Operator; - async fn create(&self) -> Result { todo!() } + // FIXME: remove the hardcoded test values + async fn create(&self) -> Result { + todo!() + // async fn inner(sftp: Sftp) -> anyhow::Result { + // let config = Arc::new(russh::client::Config::default()); + // let mut session = russh::client::connect(config, ("127.0.0.1", 22), + // sftp).await?; + + // let mut agent = russh::keys::agent::client::AgentClient::connect_uds( + // "/Users/ika/Library/Group + // Containers/2BUA8C4S2C.com.1password/t/agent.sock", ) + // .await?; + + // let mut keys = agent.request_identities().await?; + // if !session + // .authenticate_publickey_with("root", keys.remove(0), None, &mut agent) + // .await? + // .success() + // { + // panic!("auth failed"); + // } + + // let channel = session.channel_open_session().await?; + // channel.request_subsystem(true, "sftp").await?; + + // let mut op = yazi_sftp::Operator::make(channel.into_stream()); + // op.init().await?; + // Ok(op) + // } + + // inner(*self).await.map_err(|e| io::Error::other(e.to_string())) + } async fn recycle( &self, obj: &mut Self::Type, metrics: &deadpool::managed::Metrics, ) -> deadpool::managed::RecycleResult { - todo!() + // FIXME + Ok(()) } - - fn detach(&self, _obj: &mut Self::Type) { todo!() } } diff --git a/yazi-fs/src/provider/traits.rs b/yazi-fs/src/provider/traits.rs index 31273dad..72db8ee1 100644 --- a/yazi-fs/src/provider/traits.rs +++ b/yazi-fs/src/provider/traits.rs @@ -2,6 +2,7 @@ use std::{borrow::Cow, ffi::OsStr, io, path::{Path, PathBuf}}; use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt}; use yazi_macro::ok_or_not_found; +use yazi_shared::scheme::SchemeRef; use crate::cha::{Cha, ChaType}; @@ -10,31 +11,31 @@ pub trait Provider { type Gate: FileBuilder; type ReadDir: DirReader; - fn cache

(_: P) -> Option + fn cache

(&self, path: P) -> Option where P: AsRef; - fn canonicalize

(path: P) -> impl Future> + fn canonicalize

(&self, path: P) -> impl Future> where P: AsRef; - fn copy(from: P, to: Q, cha: Cha) -> impl Future> + fn copy(&self, from: P, to: Q, cha: Cha) -> impl Future> where P: AsRef, Q: AsRef; - fn create

(path: P) -> impl Future> + fn create

(&self, path: P) -> impl Future> where P: AsRef, { - async move { Self::Gate::default().write(true).create(true).truncate(true).open(path).await } + async move { self.gate().await?.write(true).create(true).truncate(true).open(path).await } } - fn create_dir

(path: P) -> impl Future> + fn create_dir

(&self, path: P) -> impl Future> where P: AsRef; - fn create_dir_all

(path: P) -> impl Future> + fn create_dir_all

(&self, path: P) -> impl Future> where P: AsRef, { @@ -44,147 +45,154 @@ pub trait Provider { return Ok(()); } - match Self::create_dir(path).await { + match self.create_dir(path).await { Ok(()) => return Ok(()), Err(e) if e.kind() == io::ErrorKind::NotFound => {} - Err(_) if Self::metadata(path).await.is_ok_and(|m| m.is_dir()) => return Ok(()), + Err(_) if self.metadata(path).await.is_ok_and(|m| m.is_dir()) => return Ok(()), Err(e) => return Err(e), } match path.parent() { - Some(p) => Self::create_dir_all(p).await?, + Some(p) => self.create_dir_all(p).await?, None => return Err(io::Error::other("failed to create whole tree")), } - match Self::create_dir(path).await { + match self.create_dir(path).await { Ok(()) => Ok(()), - Err(_) if Self::metadata(path).await.is_ok_and(|m| m.is_dir()) => Ok(()), + Err(_) if self.metadata(path).await.is_ok_and(|m| m.is_dir()) => Ok(()), Err(e) => Err(e), } } } - fn hard_link(original: P, link: Q) -> impl Future> + fn gate(&self) -> impl Future>; + + fn hard_link(&self, original: P, link: Q) -> impl Future> where P: AsRef, Q: AsRef; - fn metadata

(path: P) -> impl Future> + fn metadata

(&self, path: P) -> impl Future> where P: AsRef; - fn open

(path: P) -> impl Future> + fn open

(&self, path: P) -> impl Future> where P: AsRef, { - async move { Self::Gate::default().read(true).open(path).await } + async move { self.gate().await?.read(true).open(path).await } } - fn read_dir

(path: P) -> impl Future> + fn read_dir

(&self, path: P) -> impl Future> where P: AsRef; - fn read_link

(path: P) -> impl Future> + fn read_link

(&self, path: P) -> impl Future> where P: AsRef; - fn remove_dir

(path: P) -> impl Future> + fn remove_dir

(&self, path: P) -> impl Future> where P: AsRef; - fn remove_dir_all

(path: P) -> impl Future> + fn remove_dir_all

(&self, path: P) -> impl Future> where P: AsRef, { - async fn remove_dir_all_impl(path: &Path) -> io::Result<()> + async fn remove_dir_all_impl

(me: &P, path: &Path) -> io::Result<()> where - S: Provider + ?Sized, + P: Provider + ?Sized, { - let mut it = ok_or_not_found!(S::read_dir(path).await, return Ok(())); + let mut it = ok_or_not_found!(me.read_dir(path).await, return Ok(())); while let Some(child) = it.next().await? { let ft = ok_or_not_found!(child.file_type().await, continue); let result = if ft.is_dir() { - remove_dir_all_impl::(&child.path()).await + remove_dir_all_impl(me, &child.path()).await } else { - S::remove_file(&child.path()).await + me.remove_file(&child.path()).await }; () = ok_or_not_found!(result); } - Ok(ok_or_not_found!(S::remove_dir(path).await)) + Ok(ok_or_not_found!(me.remove_dir(path).await)) } async move { let path = path.as_ref(); - let cha = ok_or_not_found!(Self::symlink_metadata(path).await, return Ok(())); + let cha = ok_or_not_found!(self.symlink_metadata(path).await, return Ok(())); if cha.is_link() { - Self::remove_file(path).await + self.remove_file(path).await } else { - remove_dir_all_impl::(path).await + remove_dir_all_impl(self, path).await } } } - fn remove_file

(path: P) -> impl Future> + fn remove_file

(&self, path: P) -> impl Future> where P: AsRef; - fn rename(from: P, to: Q) -> impl Future> + fn rename(&self, from: P, to: Q) -> impl Future> where P: AsRef, Q: AsRef; - fn symlink(original: P, link: Q, _is_dir: F) -> impl Future> + fn symlink( + &self, + original: P, + link: Q, + _is_dir: F, + ) -> impl Future> where P: AsRef, Q: AsRef, F: AsyncFnOnce() -> io::Result; - fn symlink_dir(original: P, link: Q) -> impl Future> + fn symlink_dir(&self, original: P, link: Q) -> impl Future> where P: AsRef, Q: AsRef, { - Self::symlink(original, link, async || Ok(true)) + self.symlink(original, link, async || Ok(true)) } - fn symlink_file(original: P, link: Q) -> impl Future> + fn symlink_file(&self, original: P, link: Q) -> impl Future> where P: AsRef, Q: AsRef, { - Self::symlink(original, link, async || Ok(false)) + self.symlink(original, link, async || Ok(false)) } - fn symlink_metadata

(path: P) -> impl Future> + fn symlink_metadata

(&self, path: P) -> impl Future> where P: AsRef; - fn trash

(path: P) -> impl Future> + fn trash

(&self, path: P) -> impl Future> where P: AsRef; - fn write(path: P, contents: C) -> impl Future> + fn write(&self, path: P, contents: C) -> impl Future> where P: AsRef, C: AsRef<[u8]>, { - async move { Self::create(path).await?.write_all(contents.as_ref()).await } + async move { self.create(path).await?.write_all(contents.as_ref()).await } } } // --- DirReader pub trait DirReader { - type Entry<'a>: FileHolder - where - Self: 'a; + type Entry: FileHolder; - fn next(&mut self) -> impl Future>>>; + fn next(&mut self) -> impl Future>>; } // --- FileHolder pub trait FileHolder { + #[must_use] fn path(&self) -> PathBuf; + #[must_use] fn name(&self) -> Cow<'_, OsStr>; fn metadata(&self) -> impl Future>; @@ -193,7 +201,7 @@ pub trait FileHolder { } // --- FileOpener -pub trait FileBuilder: Default { +pub trait FileBuilder { type File: AsyncRead + AsyncWrite + Unpin; fn append(&mut self, append: bool) -> &mut Self; @@ -202,6 +210,10 @@ pub trait FileBuilder: Default { fn create_new(&mut self, create_new: bool) -> &mut Self; + fn new(scheme: SchemeRef) -> impl Future> + where + Self: Sized; + fn open

(&self, path: P) -> impl Future> where P: AsRef; diff --git a/yazi-macro/src/fs.rs b/yazi-macro/src/fs.rs index a4ab093d..5c282ad3 100644 --- a/yazi-macro/src/fs.rs +++ b/yazi-macro/src/fs.rs @@ -8,6 +8,6 @@ macro_rules! ok_or_not_found { } }; ($result:expr) => { - ok_or_not_found!($result, ()) + ok_or_not_found!($result, Default::default()) }; } diff --git a/yazi-parser/src/mgr/quit.rs b/yazi-parser/src/mgr/quit.rs index 21f30a48..e2c9ef9c 100644 --- a/yazi-parser/src/mgr/quit.rs +++ b/yazi-parser/src/mgr/quit.rs @@ -2,7 +2,7 @@ use mlua::{FromLua, IntoLua, Lua, LuaSerdeExt, Value}; use serde::{Deserialize, Serialize}; use yazi_shared::event::{CmdCow, Data, EventQuit}; -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Debug, Default, Deserialize, Serialize)] pub struct QuitOpt { pub code: i32, pub no_cwd_file: bool, diff --git a/yazi-parser/src/mgr/update_yanked.rs b/yazi-parser/src/mgr/update_yanked.rs index 46cb36e8..41288723 100644 --- a/yazi-parser/src/mgr/update_yanked.rs +++ b/yazi-parser/src/mgr/update_yanked.rs @@ -12,7 +12,7 @@ type Iter = yazi_binding::Iter< yazi_binding::Url, >; -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct UpdateYankedOpt<'a> { pub cut: bool, pub urls: Cow<'a, HashSet>, diff --git a/yazi-plugin/src/external/highlighter.rs b/yazi-plugin/src/external/highlighter.rs index b88b45b3..8d1927e6 100644 --- a/yazi-plugin/src/external/highlighter.rs +++ b/yazi-plugin/src/external/highlighter.rs @@ -39,7 +39,7 @@ impl Highlighter { pub fn abort() { INCR.next(); } pub async fn highlight(&self, skip: usize, size: Size) -> Result, PeekError> { - let mut reader = BufReader::new(Local::open(&self.path).await?); + let mut reader = BufReader::new(Local.open(&self.path).await?); let syntax = Self::find_syntax(&self.path, &mut reader).await; let mut plain = syntax.is_err(); diff --git a/yazi-plugin/src/fs/fs.rs b/yazi-plugin/src/fs/fs.rs index bffa46bb..0ba2cd83 100644 --- a/yazi-plugin/src/fs/fs.rs +++ b/yazi-plugin/src/fs/fs.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use mlua::{ExternalError, Function, IntoLua, IntoLuaMulti, Lua, Table, Value}; use yazi_binding::{Cha, Composer, ComposerGet, ComposerSet, Error, File, Url, UrlRef}; use yazi_config::Pattern; -use yazi_fs::{mounts::PARTITIONS, provider, remove_dir_clean}; +use yazi_fs::{mounts::PARTITIONS, provider::{self, DirReader, FileHolder}, remove_dir_clean}; use yazi_shared::url::UrlCow; use crate::bindings::SizeCalculator; @@ -121,7 +121,7 @@ fn read_dir(lua: &Lua) -> mlua::Result { }; let mut files = vec![]; - while let Ok(Some(next)) = it.next_entry().await { + while let Ok(Some(next)) = it.next().await { let url = next.url(); if pat.as_ref().is_some_and(|p| !p.match_url(&url, p.is_dir)) { continue; diff --git a/yazi-plugin/src/loader/loader.rs b/yazi-plugin/src/loader/loader.rs index 6e6d599b..8fe1390e 100644 --- a/yazi-plugin/src/loader/loader.rs +++ b/yazi-plugin/src/loader/loader.rs @@ -62,7 +62,7 @@ impl Loader { let p = BOOT.plugin_dir.join(format!("{plugin}.yazi/{entry}.lua")); let chunk = - Local::read(&p).await.with_context(|| format!("Failed to load plugin from {p:?}"))?.into(); + Local.read(&p).await.with_context(|| format!("Failed to load plugin from {p:?}"))?.into(); let result = Self::compatible_or_error(id, &chunk); let inspect = f(&chunk); diff --git a/yazi-scheduler/src/file/file.rs b/yazi-scheduler/src/file/file.rs index d60e8f41..6be89955 100644 --- a/yazi-scheduler/src/file/file.rs +++ b/yazi-scheduler/src/file/file.rs @@ -4,7 +4,7 @@ use anyhow::{Result, anyhow}; use tokio::{io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc}; use tracing::warn; use yazi_config::YAZI; -use yazi_fs::{cha::Cha, copy_with_progress, maybe_exists, ok_or_not_found, path::{path_relative_to, skip_url}, provider::{self, DirEntry}}; +use yazi_fs::{cha::Cha, copy_with_progress, maybe_exists, ok_or_not_found, path::{path_relative_to, skip_url}, provider::{self, DirEntry, DirReader, FileHolder}}; use yazi_shared::url::{Url, UrlBuf, UrlCow}; use super::{FileInDelete, FileInHardlink, FileInLink, FileInPaste, FileInTrash}; @@ -69,7 +69,7 @@ impl File { }); let mut it = continue_unless_ok!(provider::read_dir(&src).await); - while let Ok(Some(entry)) = it.next_entry().await { + while let Ok(Some(entry)) = it.next().await { let from = entry.url(); let cha = continue_unless_ok!(Self::entry_cha(entry, &from, task.follow).await); @@ -203,7 +203,7 @@ impl File { }); let mut it = continue_unless_ok!(provider::read_dir(&src).await); - while let Ok(Some(entry)) = it.next_entry().await { + while let Ok(Some(entry)) = it.next().await { let from = entry.url(); let cha = continue_unless_ok!(Self::entry_cha(entry, &from, task.follow).await); @@ -251,7 +251,7 @@ impl File { while let Some(target) = dirs.pop_front() { let Ok(mut it) = provider::read_dir(&target).await else { continue }; - while let Ok(Some(entry)) = it.next_entry().await { + while let Ok(Some(entry)) = it.next().await { let Ok(cha) = entry.metadata().await else { continue }; if cha.is_dir() { diff --git a/yazi-sftp/src/fs/dir_entry.rs b/yazi-sftp/src/fs/dir_entry.rs index 218f728d..7ae8cb85 100644 --- a/yazi-sftp/src/fs/dir_entry.rs +++ b/yazi-sftp/src/fs/dir_entry.rs @@ -1,15 +1,15 @@ -use std::{borrow::Cow, ffi::OsStr, path::PathBuf}; +use std::{borrow::Cow, ffi::OsStr, path::PathBuf, sync::Arc}; use crate::{ByteStr, fs::Attrs}; -pub struct DirEntry<'a> { - pub(super) dir: ByteStr<'a>, - pub(super) name: ByteStr<'a>, - pub(super) long_name: ByteStr<'a>, +pub struct DirEntry { + pub(super) dir: Arc>, + pub(super) name: ByteStr<'static>, + pub(super) long_name: ByteStr<'static>, pub(super) attrs: Attrs, } -impl<'a> DirEntry<'a> { +impl DirEntry { #[must_use] pub fn path(&self) -> PathBuf { self.dir.join(&self.name) } diff --git a/yazi-sftp/src/fs/read_dir.rs b/yazi-sftp/src/fs/read_dir.rs index bed6e1a3..8386f428 100644 --- a/yazi-sftp/src/fs/read_dir.rs +++ b/yazi-sftp/src/fs/read_dir.rs @@ -4,7 +4,7 @@ use crate::{ByteStr, Error, Session, fs::DirEntry, requests, responses}; pub struct ReadDir { session: Arc, - dir: ByteStr<'static>, + dir: Arc>, handle: String, name: responses::Name<'static>, @@ -16,7 +16,7 @@ impl ReadDir { pub(crate) fn new(session: &Arc, dir: ByteStr, handle: String) -> Self { Self { session: session.clone(), - dir: dir.into_owned(), + dir: Arc::new(dir.into_owned()), handle, name: Default::default(), @@ -25,7 +25,7 @@ impl ReadDir { } } - pub async fn next(&mut self) -> Result>, Error> { + pub async fn next(&mut self) -> Result, Error> { loop { self.fetch().await?; let Some(item) = self.name.items.get_mut(self.cursor).map(mem::take) else { @@ -35,7 +35,7 @@ impl ReadDir { self.cursor += 1; if item.name != "." && item.name != ".." { return Ok(Some(DirEntry { - dir: ByteStr::from(&self.dir), + dir: self.dir.clone(), name: item.name, long_name: item.long_name, attrs: item.attrs, diff --git a/yazi-sftp/src/operator.rs b/yazi-sftp/src/operator.rs index adfbf2bb..bbfd7196 100644 --- a/yazi-sftp/src/operator.rs +++ b/yazi-sftp/src/operator.rs @@ -1,5 +1,6 @@ use std::{ops::Deref, path::PathBuf, sync::Arc}; +use russh::{ChannelStream, client::Msg}; use tokio::sync::oneshot; use crate::{ByteStr, Error, Packet, Session, fs::{Attrs, File, Flags, ReadDir}, requests, responses}; @@ -17,6 +18,8 @@ impl From<&Arc> for Operator { } impl Operator { + pub fn make(stream: ChannelStream) -> Self { Self(Session::make(stream)) } + pub async fn init(&mut self) -> Result<(), Error> { let version: responses::Version = self.send(requests::Init::default()).await?; *self.extensions.lock() = version.extensions; diff --git a/yazi-sftp/src/requests/extended.rs b/yazi-sftp/src/requests/extended.rs index 69e25d1f..a10601dd 100644 --- a/yazi-sftp/src/requests/extended.rs +++ b/yazi-sftp/src/requests/extended.rs @@ -42,7 +42,7 @@ impl ExtendedData for ExtendedFsync<'_> { } // --- Hardlink -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct ExtendedHardlink<'a> { pub original: ByteStr<'a>, pub link: ByteStr<'a>, diff --git a/yazi-sftp/src/requests/fstat.rs b/yazi-sftp/src/requests/fstat.rs index d077562d..eea99a3c 100644 --- a/yazi-sftp/src/requests/fstat.rs +++ b/yazi-sftp/src/requests/fstat.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use serde::{Deserialize, Serialize}; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct Fstat<'a> { pub id: u32, pub handle: Cow<'a, str>, diff --git a/yazi-sftp/src/requests/set_stat.rs b/yazi-sftp/src/requests/set_stat.rs index 6096cb83..fd426d7c 100644 --- a/yazi-sftp/src/requests/set_stat.rs +++ b/yazi-sftp/src/requests/set_stat.rs @@ -19,7 +19,7 @@ impl<'a> SetStat<'a> { pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() + self.attrs.len() } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct FSetStat<'a> { pub id: u32, pub handle: Cow<'a, str>, diff --git a/yazi-sftp/src/session.rs b/yazi-sftp/src/session.rs index 4e14d683..4df83f5e 100644 --- a/yazi-sftp/src/session.rs +++ b/yazi-sftp/src/session.rs @@ -15,7 +15,7 @@ pub struct Session { } impl Session { - pub fn make(stream: ChannelStream) -> Arc { + pub(super) fn make(stream: ChannelStream) -> Arc { let (tx, mut rx) = mpsc::unbounded_channel(); let me = Arc::new(Self { tx, diff --git a/yazi-shared/src/event/data.rs b/yazi-shared/src/event/data.rs index 59c1bdab..17d1300b 100644 --- a/yazi-shared/src/event/data.rs +++ b/yazi-shared/src/event/data.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize, de}; use crate::{Id, SStr, url::{UrlBuf, UrlCow, UrnBuf}}; // --- Data -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] #[serde(untagged)] pub enum Data { Nil, @@ -154,7 +154,7 @@ impl PartialEq for Data { } // --- Key -#[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] #[serde(untagged)] pub enum DataKey { Nil, diff --git a/yazi-shared/src/id.rs b/yazi-shared/src/id.rs index ffb67b2e..0abaa021 100644 --- a/yazi-shared/src/id.rs +++ b/yazi-shared/src/id.rs @@ -3,7 +3,7 @@ use std::{fmt::Display, str::FromStr, sync::atomic::{AtomicU64, Ordering}}; use serde::{Deserialize, Serialize}; #[derive( - Clone, Copy, Debug, Default, Hash, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize, + Clone, Copy, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, )] pub struct Id(pub u64); diff --git a/yazi-vfs/Cargo.toml b/yazi-vfs/Cargo.toml new file mode 100644 index 00000000..ba14c226 --- /dev/null +++ b/yazi-vfs/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "yazi-vfs" +version = "25.9.15" +edition = "2024" +license = "MIT" +authors = [ "sxyazi " ] +description = "Yazi virtual file system" +homepage = "https://yazi-rs.github.io" +repository = "https://github.com/sxyazi/yazi" + +[dependencies] +yazi-macro = { path = "../yazi-macro", version = "25.9.15" } +yazi-shared = { path = "../yazi-shared", version = "25.9.15" } + +# External dependencies +anyhow = { workspace = true } +dirs = { workspace = true } +hashbrown = { workspace = true } +serde = { workspace = true } +tokio = { workspace = true } +toml = { workspace = true } + +[target."cfg(unix)".dependencies] +uzers = { workspace = true } diff --git a/yazi-vfs/src/config/mod.rs b/yazi-vfs/src/config/mod.rs new file mode 100644 index 00000000..b27c77fc --- /dev/null +++ b/yazi-vfs/src/config/mod.rs @@ -0,0 +1 @@ +yazi_macro::mod_flat!(provider vfs); diff --git a/yazi-vfs/src/config/provider.rs b/yazi-vfs/src/config/provider.rs new file mode 100644 index 00000000..ab3d4fe9 --- /dev/null +++ b/yazi-vfs/src/config/provider.rs @@ -0,0 +1,29 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize)] +#[serde(tag = "type", rename_all = "kebab-case")] +pub enum Provider { + Sftp(ProviderSftp), +} + +impl TryFrom<&'static Provider> for &'static ProviderSftp { + type Error = &'static str; + + fn try_from(value: &'static Provider) -> Result { + match value { + Provider::Sftp(p) => Ok(p), + } + } +} + +// --- SFTP +#[derive(Deserialize, Hash, Serialize, Eq, PartialEq)] +pub struct ProviderSftp { + pub host: String, + pub user: String, + pub port: u16, + pub password: Option, + pub key_file: Option, +} diff --git a/yazi-vfs/src/config/vfs.rs b/yazi-vfs/src/config/vfs.rs new file mode 100644 index 00000000..70cb3283 --- /dev/null +++ b/yazi-vfs/src/config/vfs.rs @@ -0,0 +1,61 @@ +use std::io; + +use hashbrown::HashMap; +use serde::{Deserialize, Serialize}; +use tokio::sync::OnceCell; +use yazi_macro::ok_or_not_found; + +use super::Provider; +use crate::local::Xdg; + +#[derive(Deserialize, Serialize)] +pub struct Vfs { + pub providers: HashMap, +} + +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(|| { + toml::from_str::(&Vfs::read()?).map_err(io::Error::other)?.reshape() + }) + .await? + } + + LOADED.get_or_try_init(init).await + } + + pub async fn provider<'a, P>(name: &str) -> io::Result<(&'a str, P)> + where + P: TryFrom<&'a Provider, Error = &'static str>, + { + let Some((key, value)) = Self::load().await?.providers.get_key_value(name) else { + return Err(io::Error::other(format!("No such VFS provider: {name}"))); + }; + match value.try_into() { + Ok(p) => Ok((key.as_str(), p)), + Err(e) => Err(io::Error::other(format!("VFS provider `{key}` has wrong type: {e}"))), + } + } + + pub(crate) fn read() -> io::Result { + let p = Xdg::config_dir().join("vfs.toml"); + Ok(ok_or_not_found!(std::fs::read_to_string(&p).map_err(|e| { + std::io::Error::new(e.kind(), format!("Failed to read VFS config {p:?}: {e}")) + }))) + } + + pub(crate) fn reshape(self) -> io::Result { + for name in self.providers.keys() { + if name.is_empty() || name.len() > 20 { + Err(io::Error::other(format!("VFS name `{name}` must be between 1 and 20 characters")))?; + } else if !name.bytes().all(|b| matches!(b, b'0'..=b'9' | b'a'..=b'z' | b'-')) { + Err(io::Error::other(format!("VFS name `{name}` must be in kebab-case")))?; + } + } + + Ok(self) + } +} diff --git a/yazi-vfs/src/lib.rs b/yazi-vfs/src/lib.rs new file mode 100644 index 00000000..b899e780 --- /dev/null +++ b/yazi-vfs/src/lib.rs @@ -0,0 +1 @@ +yazi_macro::mod_pub!(config local); diff --git a/yazi-vfs/src/local/mod.rs b/yazi-vfs/src/local/mod.rs new file mode 100644 index 00000000..f680801c --- /dev/null +++ b/yazi-vfs/src/local/mod.rs @@ -0,0 +1 @@ +yazi_macro::mod_flat!(xdg); diff --git a/yazi-fs/src/xdg.rs b/yazi-vfs/src/local/xdg.rs similarity index 100% rename from yazi-fs/src/xdg.rs rename to yazi-vfs/src/local/xdg.rs