mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: new yazi-vfs crate (#3187)
This commit is contained in:
parent
30c0279570
commit
4e3ac54c8e
88 changed files with 930 additions and 286 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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() });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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<DynamicImage> {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use yazi_config::YAZI;
|
||||
use yazi_fs::Xdg;
|
||||
use yazi_vfs::local::Xdg;
|
||||
|
||||
use super::Actions;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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!(
|
||||
|
|
|
|||
|
|
@ -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<Vec<String>> {
|
||||
let mut it = Local::read_dir(dir).await?;
|
||||
let mut it = Local.read_dir(dir).await?;
|
||||
let mut files: Vec<String> =
|
||||
["LICENSE", "README.md", "main.lua"].into_iter().map(Into::into).collect();
|
||||
while let Some(entry) = it.next().await? {
|
||||
|
|
|
|||
|
|
@ -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(|| {
|
||||
|
|
|
|||
|
|
@ -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 => {}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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<Self> {
|
||||
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") }
|
||||
|
|
|
|||
|
|
@ -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<Path>) -> bool {
|
||||
Local::symlink_metadata(path).await.is_ok()
|
||||
Local.symlink_metadata(path).await.is_ok()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn maybe_exists(path: impl AsRef<Path>) -> 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
yazi_macro::mod_flat!(sftp);
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
pub struct Sftp {
|
||||
pub host: String,
|
||||
pub user: String,
|
||||
pub port: u16,
|
||||
pub password: Option<String>,
|
||||
pub key_file: Option<PathBuf>,
|
||||
}
|
||||
|
|
@ -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};
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ pub struct Client {
|
|||
pub(super) abilities: HashSet<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Peer {
|
||||
pub(super) abilities: HashSet<String>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>, Cow<'a, UrlBuf>>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use super::Ember;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct EmberBye;
|
||||
|
||||
impl EmberBye {
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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<UrlBuf>>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Id, Peer>,
|
||||
pub version: SStr,
|
||||
|
|
|
|||
|
|
@ -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<Cow<'a, str>>,
|
||||
|
|
|
|||
|
|
@ -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<Cow<'a, UrlBuf>>,
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use super::Ember;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct EmberMount;
|
||||
|
||||
impl EmberMount {
|
||||
|
|
|
|||
|
|
@ -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<BodyMoveItem>>,
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<UrlBuf>>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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<bool> {
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
||||
|
|
|
|||
|
|
@ -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<OsString>) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<WorkerGuard> = RoCell::new();
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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<Vec<File>> {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Url<'a>>) -> 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;
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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<u64>),
|
||||
|
|
@ -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!();
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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<UrlBuf>, super::local::DirEntry)),
|
||||
Sftp((Arc<UrlBuf>, super::sftp::DirEntry)),
|
||||
}
|
||||
|
||||
impl From<super::local::DirEntry> 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<Cha> {
|
||||
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<ChaType> {
|
||||
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<Cha> {
|
||||
match self {
|
||||
Self::Local(local) => local.metadata().await,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn file_type(&self) -> io::Result<ChaType> {
|
||||
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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
90
yazi-fs/src/provider/gate.rs
Normal file
90
yazi-fs/src/provider/gate.rs
Normal file
|
|
@ -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<super::local::Gate> for Gate {
|
||||
fn from(value: super::local::Gate) -> Self { Self::Local(value) }
|
||||
}
|
||||
|
||||
impl From<super::sftp::Gate> 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<Self> {
|
||||
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<P>(&self, path: P) -> io::Result<Self::File>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Self> {
|
||||
if scheme.is_virtual() {
|
||||
Err(io::Error::new(io::ErrorKind::InvalidInput, "Not a local filesystem"))?
|
||||
} else {
|
||||
Ok(Self::default())
|
||||
}
|
||||
}
|
||||
|
||||
async fn open<P>(&self, path: P) -> io::Result<Self::File>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
|
|
|
|||
|
|
@ -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>(_: P) -> Option<PathBuf>
|
||||
fn cache<P>(&self, _: P) -> Option<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
@ -18,7 +19,7 @@ impl Provider for Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
async fn canonicalize<P>(path: P) -> io::Result<PathBuf>
|
||||
async fn canonicalize<P>(&self, path: P) -> io::Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
@ -26,7 +27,7 @@ impl Provider for Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
async fn copy<P, Q>(from: P, to: Q, cha: Cha) -> io::Result<u64>
|
||||
async fn copy<P, Q>(&self, from: P, to: Q, cha: Cha) -> io::Result<u64>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
|
|
@ -37,7 +38,7 @@ impl Provider for Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
async fn create_dir<P>(path: P) -> io::Result<()>
|
||||
async fn create_dir<P>(&self, path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
@ -45,7 +46,7 @@ impl Provider for Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
async fn create_dir_all<P>(path: P) -> io::Result<()>
|
||||
async fn create_dir_all<P>(&self, path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
@ -53,7 +54,10 @@ impl Provider for Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
async fn hard_link<P, Q>(original: P, link: Q) -> io::Result<()>
|
||||
async fn gate(&self) -> io::Result<Self::Gate> { Ok(Self::Gate::default()) }
|
||||
|
||||
#[inline]
|
||||
async fn hard_link<P, Q>(&self, original: P, link: Q) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
|
|
@ -62,7 +66,7 @@ impl Provider for Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
async fn metadata<P>(path: P) -> io::Result<Cha>
|
||||
async fn metadata<P>(&self, path: P) -> io::Result<Cha>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
@ -71,7 +75,7 @@ impl Provider for Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
async fn read_dir<P>(path: P) -> io::Result<Self::ReadDir>
|
||||
async fn read_dir<P>(&self, path: P) -> io::Result<Self::ReadDir>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
@ -79,7 +83,7 @@ impl Provider for Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
async fn read_link<P>(path: P) -> io::Result<PathBuf>
|
||||
async fn read_link<P>(&self, path: P) -> io::Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
@ -87,7 +91,7 @@ impl Provider for Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
async fn remove_dir<P>(path: P) -> io::Result<()>
|
||||
async fn remove_dir<P>(&self, path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
@ -95,7 +99,7 @@ impl Provider for Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
async fn remove_dir_all<P>(path: P) -> io::Result<()>
|
||||
async fn remove_dir_all<P>(&self, path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
@ -103,7 +107,7 @@ impl Provider for Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
async fn remove_file<P>(path: P) -> io::Result<()>
|
||||
async fn remove_file<P>(&self, path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
@ -111,7 +115,7 @@ impl Provider for Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
async fn rename<P, Q>(from: P, to: Q) -> io::Result<()>
|
||||
async fn rename<P, Q>(&self, from: P, to: Q) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
|
|
@ -120,7 +124,7 @@ impl Provider for Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
async fn symlink<P, Q, F>(original: P, link: Q, _is_dir: F) -> io::Result<()>
|
||||
async fn symlink<P, Q, F>(&self, original: P, link: Q, _is_dir: F) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
|
|
@ -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<P, Q>(original: P, link: Q) -> io::Result<()>
|
||||
async fn symlink_dir<P, Q>(&self, original: P, link: Q) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
|
|
@ -155,7 +159,7 @@ impl Provider for Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
async fn symlink_file<P, Q>(original: P, link: Q) -> io::Result<()>
|
||||
async fn symlink_file<P, Q>(&self, original: P, link: Q) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
|
|
@ -171,7 +175,7 @@ impl Provider for Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
async fn symlink_metadata<P>(path: P) -> io::Result<Cha>
|
||||
async fn symlink_metadata<P>(&self, path: P) -> io::Result<Cha>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
@ -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<P>(path: P) -> io::Result<()>
|
||||
async fn trash<P>(&self, path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
@ -205,7 +209,7 @@ impl Provider for Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
async fn write<P, C>(path: P, contents: C) -> io::Result<()>
|
||||
async fn write<P, C>(&self, path: P, contents: C) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
C: AsRef<[u8]>,
|
||||
|
|
@ -264,7 +268,7 @@ impl Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn read<P>(path: P) -> io::Result<Vec<u8>>
|
||||
pub async fn read<P>(&self, path: P) -> io::Result<Vec<u8>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
@ -272,7 +276,7 @@ impl Local {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn read_to_string<P>(path: P) -> io::Result<String>
|
||||
pub async fn read_to_string<P>(&self, path: P) -> io::Result<String>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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<Option<Self::Entry<'_>>> {
|
||||
async fn next(&mut self) -> io::Result<Option<Self::Entry>> {
|
||||
self.0.next_entry().await.map(|entry| entry.map(super::DirEntry))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(); }
|
||||
|
|
|
|||
|
|
@ -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<PathBuf>
|
|||
where
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
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<Url<'a>>,
|
||||
{
|
||||
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<Url<'a>>,
|
||||
{
|
||||
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<Url<'a>>,
|
||||
{
|
||||
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<Url<'a>>,
|
||||
{
|
||||
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<Url<'a>>,
|
||||
{
|
||||
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<Url<'a>>,
|
||||
{
|
||||
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<Url<'a>>,
|
||||
{
|
||||
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<ReadDir>
|
|||
where
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
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<Url<'a>>,
|
||||
{
|
||||
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<Url<'a>>,
|
||||
{
|
||||
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<Url<'a>>,
|
||||
{
|
||||
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<Url<'a>>,
|
||||
{
|
||||
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<Url<'a>>,
|
||||
{
|
||||
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<bool>,
|
||||
{
|
||||
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<Url<'a>>,
|
||||
{
|
||||
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<Url<'a>>,
|
||||
{
|
||||
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<Url<'a>>,
|
||||
{
|
||||
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<Url<'a>>,
|
||||
{
|
||||
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"))
|
||||
}
|
||||
|
|
|
|||
266
yazi-fs/src/provider/providers.rs
Normal file
266
yazi-fs/src/provider/providers.rs
Normal file
|
|
@ -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<Self> {
|
||||
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<P>(&self, path: P) -> Option<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
match self.0 {
|
||||
Inner::Regular | Inner::Search(_) => Local.cache(path),
|
||||
Inner::Sftp((p, _)) => p.cache(path),
|
||||
}
|
||||
}
|
||||
|
||||
async fn canonicalize<P>(&self, path: P) -> io::Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
match self.0 {
|
||||
Inner::Regular | Inner::Search(_) => Local.canonicalize(path).await,
|
||||
Inner::Sftp((p, _)) => p.canonicalize(path).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn copy<P, Q>(&self, from: P, to: Q, cha: Cha) -> io::Result<u64>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
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<P>(&self, path: P) -> io::Result<Self::File>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
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<P>(&self, path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
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<P>(&self, path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
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<Self::Gate> {
|
||||
Ok(match self.0 {
|
||||
Inner::Regular | Inner::Search(_) => Local.gate().await?.into(),
|
||||
Inner::Sftp((p, _)) => p.gate().await?.into(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn hard_link<P, Q>(&self, original: P, link: Q) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
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<P>(&self, path: P) -> io::Result<Cha>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
match self.0 {
|
||||
Inner::Regular | Inner::Search(_) => Local.metadata(path).await,
|
||||
Inner::Sftp((p, _)) => p.metadata(path).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn open<P>(&self, path: P) -> io::Result<Self::File>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
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<P>(&self, path: P) -> io::Result<Self::ReadDir>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
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<P>(&self, path: P) -> io::Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
match self.0 {
|
||||
Inner::Regular | Inner::Search(_) => Local.read_link(path).await,
|
||||
Inner::Sftp((p, _)) => p.read_link(path).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn remove_dir<P>(&self, path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
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<P>(&self, path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
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<P>(&self, path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
match self.0 {
|
||||
Inner::Regular | Inner::Search(_) => Local.remove_file(path).await,
|
||||
Inner::Sftp((p, _)) => p.remove_file(path).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn rename<P, Q>(&self, from: P, to: Q) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
match self.0 {
|
||||
Inner::Regular | Inner::Search(_) => Local.rename(from, to).await,
|
||||
Inner::Sftp((p, _)) => p.rename(from, to).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn symlink<P, Q, F>(&self, original: P, link: Q, is_dir: F) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
F: AsyncFnOnce() -> io::Result<bool>,
|
||||
{
|
||||
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<P, Q>(&self, original: P, link: Q) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
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<P, Q>(&self, original: P, link: Q) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
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<P>(&self, path: P) -> io::Result<Cha>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
match self.0 {
|
||||
Inner::Regular | Inner::Search(_) => Local.symlink_metadata(path).await,
|
||||
Inner::Sftp((p, _)) => p.symlink_metadata(path).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn trash<P>(&self, path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
match self.0 {
|
||||
Inner::Regular | Inner::Search(_) => Local.trash(path).await,
|
||||
Inner::Sftp((p, _)) => p.trash(path).await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn write<P, C>(&self, path: P, contents: C) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
C: AsRef<[u8]>,
|
||||
{
|
||||
match self.0 {
|
||||
Inner::Regular | Inner::Search(_) => Local.write(path, contents).await,
|
||||
Inner::Sftp((p, _)) => p.write(path, contents).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UrlBuf>, super::local::ReadDir)),
|
||||
Sftp((Arc<UrlBuf>, super::sftp::ReadDir)),
|
||||
}
|
||||
|
||||
impl From<super::local::ReadDir> 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<Option<DirEntry>> {
|
||||
match self {
|
||||
Self::Local(local) => local.next().await.map(|entry| entry.map(Into::into)),
|
||||
}
|
||||
async fn next(&mut self) -> io::Result<Option<Self::Entry>> {
|
||||
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)))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use tokio::io::{AsyncRead, AsyncWrite};
|
|||
|
||||
pub enum RwFile {
|
||||
Tokio(tokio::fs::File),
|
||||
SFTP(Box<yazi_sftp::fs::File>),
|
||||
Sftp(Box<yazi_sftp::fs::File>),
|
||||
}
|
||||
|
||||
impl From<tokio::fs::File> for RwFile {
|
||||
|
|
@ -12,7 +12,7 @@ impl From<tokio::fs::File> for RwFile {
|
|||
}
|
||||
|
||||
impl From<yazi_sftp::fs::File> 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<std::io::Result<()>> {
|
||||
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<Result<usize, std::io::Error>> {
|
||||
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<Result<(), std::io::Error>> {
|
||||
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<Result<(), std::io::Error>> {
|
||||
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<Result<usize, std::io::Error>> {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Self> {
|
||||
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<P>(&self, path: P) -> io::Result<Self::File>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ use std::{ffi::OsStr, io, time::{Duration, UNIX_EPOCH}};
|
|||
|
||||
use crate::cha::{Cha, ChaKind, ChaMode};
|
||||
|
||||
impl TryFrom<yazi_sftp::fs::DirEntry<'_>> for Cha {
|
||||
impl TryFrom<yazi_sftp::fs::DirEntry> for Cha {
|
||||
type Error = io::Error;
|
||||
|
||||
fn try_from(ent: yazi_sftp::fs::DirEntry<'_>) -> Result<Self, Self::Error> {
|
||||
fn try_from(ent: yazi_sftp::fs::DirEntry) -> Result<Self, Self::Error> {
|
||||
let mut cha = Self::try_from((ent.name().as_ref(), ent.attrs()))?;
|
||||
cha.nlink = ent.nlink().unwrap_or_default();
|
||||
Ok(cha)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
yazi_macro::mod_flat!(gate metadata read_dir sftp);
|
||||
|
||||
pub(super) static CONN: yazi_shared::RoCell<deadpool::managed::Pool<Sftp>> =
|
||||
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<Sftp>,
|
||||
>,
|
||||
>,
|
||||
> = 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()); }
|
||||
|
|
|
|||
|
|
@ -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<Option<Self::Entry<'_>>> {
|
||||
async fn next(&mut self) -> io::Result<Option<Self::Entry>> {
|
||||
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() }
|
||||
|
|
|
|||
|
|
@ -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>(_: P) -> Option<PathBuf>
|
||||
fn cache<P>(&self, _: P) -> Option<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn canonicalize<P>(path: P) -> io::Result<PathBuf>
|
||||
async fn canonicalize<P>(&self, path: P) -> io::Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Ok(Self::op().await?.realpath(&path).await?)
|
||||
Ok(self.op().await?.realpath(&path).await?)
|
||||
}
|
||||
|
||||
async fn copy<P, Q>(from: P, to: Q, cha: Cha) -> io::Result<u64>
|
||||
async fn copy<P, Q>(&self, from: P, to: Q, cha: Cha) -> io::Result<u64>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
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<P>(path: P) -> io::Result<()>
|
||||
async fn create_dir<P>(&self, path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Ok(Self::op().await?.mkdir(&path, Attrs::default()).await?)
|
||||
Ok(self.op().await?.mkdir(&path, Attrs::default()).await?)
|
||||
}
|
||||
|
||||
async fn hard_link<P, Q>(original: P, link: Q) -> io::Result<()>
|
||||
async fn gate(&self) -> io::Result<Self::Gate> {
|
||||
super::Gate::new(SchemeRef::Sftp(self.name)).await
|
||||
}
|
||||
|
||||
async fn hard_link<P, Q>(&self, original: P, link: Q) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
Ok(Self::op().await?.hardlink(&original, &link).await?)
|
||||
Ok(self.op().await?.hardlink(&original, &link).await?)
|
||||
}
|
||||
|
||||
async fn metadata<P>(path: P) -> io::Result<Cha>
|
||||
async fn metadata<P>(&self, path: P) -> io::Result<Cha>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
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<P>(path: P) -> io::Result<Self::ReadDir>
|
||||
async fn read_dir<P>(&self, path: P) -> io::Result<Self::ReadDir>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Ok(super::ReadDir(Self::op().await?.read_dir(&path).await?))
|
||||
Ok(super::ReadDir(self.op().await?.read_dir(&path).await?))
|
||||
}
|
||||
|
||||
async fn read_link<P>(path: P) -> io::Result<PathBuf>
|
||||
async fn read_link<P>(&self, path: P) -> io::Result<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Ok(Self::op().await?.readlink(&path).await?)
|
||||
Ok(self.op().await?.readlink(&path).await?)
|
||||
}
|
||||
|
||||
async fn remove_dir<P>(path: P) -> io::Result<()>
|
||||
async fn remove_dir<P>(&self, path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Ok(Self::op().await?.rmdir(&path).await?)
|
||||
Ok(self.op().await?.rmdir(&path).await?)
|
||||
}
|
||||
|
||||
async fn remove_file<P>(path: P) -> io::Result<()>
|
||||
async fn remove_file<P>(&self, path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
Ok(Self::op().await?.remove(&path).await?)
|
||||
Ok(self.op().await?.remove(&path).await?)
|
||||
}
|
||||
|
||||
async fn rename<P, Q>(from: P, to: Q) -> io::Result<()>
|
||||
async fn rename<P, Q>(&self, from: P, to: Q) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
Ok(Self::op().await?.rename(&from, &to).await?)
|
||||
Ok(self.op().await?.rename(&from, &to).await?)
|
||||
}
|
||||
|
||||
async fn symlink<P, Q, F>(original: P, link: Q, _is_dir: F) -> io::Result<()>
|
||||
async fn symlink<P, Q, F>(&self, original: P, link: Q, _is_dir: F) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
F: AsyncFnOnce() -> io::Result<bool>,
|
||||
{
|
||||
Ok(Self::op().await?.symlink(&original, &link).await?)
|
||||
Ok(self.op().await?.symlink(&original, &link).await?)
|
||||
}
|
||||
|
||||
async fn symlink_metadata<P>(path: P) -> io::Result<Cha>
|
||||
async fn symlink_metadata<P>(&self, path: P) -> io::Result<Cha>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
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<P>(_path: P) -> io::Result<()>
|
||||
async fn trash<P>(&self, _path: P) -> io::Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
@ -126,10 +140,14 @@ impl Provider for Sftp {
|
|||
}
|
||||
|
||||
impl Sftp {
|
||||
pub(super) async fn op() -> io::Result<deadpool::managed::Object<Sftp>> {
|
||||
pub(super) async fn op(self) -> io::Result<deadpool::managed::Object<Sftp>> {
|
||||
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<bool, Self::Error> {
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl deadpool::managed::Manager for Sftp {
|
||||
type Error = io::Error;
|
||||
type Type = yazi_sftp::Operator;
|
||||
|
||||
async fn create(&self) -> Result<Self::Type, Self::Error> { todo!() }
|
||||
// FIXME: remove the hardcoded test values
|
||||
async fn create(&self) -> Result<Self::Type, Self::Error> {
|
||||
todo!()
|
||||
// async fn inner(sftp: Sftp) -> anyhow::Result<yazi_sftp::Operator> {
|
||||
// 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<Self::Error> {
|
||||
todo!()
|
||||
// FIXME
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn detach(&self, _obj: &mut Self::Type) { todo!() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<File = Self::File>;
|
||||
type ReadDir: DirReader;
|
||||
|
||||
fn cache<P>(_: P) -> Option<PathBuf>
|
||||
fn cache<P>(&self, path: P) -> Option<PathBuf>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn canonicalize<P>(path: P) -> impl Future<Output = io::Result<PathBuf>>
|
||||
fn canonicalize<P>(&self, path: P) -> impl Future<Output = io::Result<PathBuf>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn copy<P, Q>(from: P, to: Q, cha: Cha) -> impl Future<Output = io::Result<u64>>
|
||||
fn copy<P, Q>(&self, from: P, to: Q, cha: Cha) -> impl Future<Output = io::Result<u64>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>;
|
||||
|
||||
fn create<P>(path: P) -> impl Future<Output = io::Result<Self::File>>
|
||||
fn create<P>(&self, path: P) -> impl Future<Output = io::Result<Self::File>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
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<P>(path: P) -> impl Future<Output = io::Result<()>>
|
||||
fn create_dir<P>(&self, path: P) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn create_dir_all<P>(path: P) -> impl Future<Output = io::Result<()>>
|
||||
fn create_dir_all<P>(&self, path: P) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
|
@ -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<P, Q>(original: P, link: Q) -> impl Future<Output = io::Result<()>>
|
||||
fn gate(&self) -> impl Future<Output = io::Result<Self::Gate>>;
|
||||
|
||||
fn hard_link<P, Q>(&self, original: P, link: Q) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>;
|
||||
|
||||
fn metadata<P>(path: P) -> impl Future<Output = io::Result<Cha>>
|
||||
fn metadata<P>(&self, path: P) -> impl Future<Output = io::Result<Cha>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn open<P>(path: P) -> impl Future<Output = io::Result<Self::File>>
|
||||
fn open<P>(&self, path: P) -> impl Future<Output = io::Result<Self::File>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
async move { Self::Gate::default().read(true).open(path).await }
|
||||
async move { self.gate().await?.read(true).open(path).await }
|
||||
}
|
||||
|
||||
fn read_dir<P>(path: P) -> impl Future<Output = io::Result<Self::ReadDir>>
|
||||
fn read_dir<P>(&self, path: P) -> impl Future<Output = io::Result<Self::ReadDir>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn read_link<P>(path: P) -> impl Future<Output = io::Result<PathBuf>>
|
||||
fn read_link<P>(&self, path: P) -> impl Future<Output = io::Result<PathBuf>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn remove_dir<P>(path: P) -> impl Future<Output = io::Result<()>>
|
||||
fn remove_dir<P>(&self, path: P) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn remove_dir_all<P>(path: P) -> impl Future<Output = io::Result<()>>
|
||||
fn remove_dir_all<P>(&self, path: P) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
async fn remove_dir_all_impl<S>(path: &Path) -> io::Result<()>
|
||||
async fn remove_dir_all_impl<P>(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::<S>(&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::<Self>(path).await
|
||||
remove_dir_all_impl(self, path).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_file<P>(path: P) -> impl Future<Output = io::Result<()>>
|
||||
fn remove_file<P>(&self, path: P) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn rename<P, Q>(from: P, to: Q) -> impl Future<Output = io::Result<()>>
|
||||
fn rename<P, Q>(&self, from: P, to: Q) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>;
|
||||
|
||||
fn symlink<P, Q, F>(original: P, link: Q, _is_dir: F) -> impl Future<Output = io::Result<()>>
|
||||
fn symlink<P, Q, F>(
|
||||
&self,
|
||||
original: P,
|
||||
link: Q,
|
||||
_is_dir: F,
|
||||
) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
F: AsyncFnOnce() -> io::Result<bool>;
|
||||
|
||||
fn symlink_dir<P, Q>(original: P, link: Q) -> impl Future<Output = io::Result<()>>
|
||||
fn symlink_dir<P, Q>(&self, original: P, link: Q) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
Self::symlink(original, link, async || Ok(true))
|
||||
self.symlink(original, link, async || Ok(true))
|
||||
}
|
||||
|
||||
fn symlink_file<P, Q>(original: P, link: Q) -> impl Future<Output = io::Result<()>>
|
||||
fn symlink_file<P, Q>(&self, original: P, link: Q) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
Self::symlink(original, link, async || Ok(false))
|
||||
self.symlink(original, link, async || Ok(false))
|
||||
}
|
||||
|
||||
fn symlink_metadata<P>(path: P) -> impl Future<Output = io::Result<Cha>>
|
||||
fn symlink_metadata<P>(&self, path: P) -> impl Future<Output = io::Result<Cha>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn trash<P>(path: P) -> impl Future<Output = io::Result<()>>
|
||||
fn trash<P>(&self, path: P) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
||||
fn write<P, C>(path: P, contents: C) -> impl Future<Output = io::Result<()>>
|
||||
fn write<P, C>(&self, path: P, contents: C) -> impl Future<Output = io::Result<()>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
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<Output = io::Result<Option<Self::Entry<'_>>>>;
|
||||
fn next(&mut self) -> impl Future<Output = io::Result<Option<Self::Entry>>>;
|
||||
}
|
||||
|
||||
// --- FileHolder
|
||||
pub trait FileHolder {
|
||||
#[must_use]
|
||||
fn path(&self) -> PathBuf;
|
||||
|
||||
#[must_use]
|
||||
fn name(&self) -> Cow<'_, OsStr>;
|
||||
|
||||
fn metadata(&self) -> impl Future<Output = io::Result<Cha>>;
|
||||
|
|
@ -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<Output = io::Result<Self>>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn open<P>(&self, path: P) -> impl Future<Output = io::Result<Self::File>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,6 @@ macro_rules! ok_or_not_found {
|
|||
}
|
||||
};
|
||||
($result:expr) => {
|
||||
ok_or_not_found!($result, ())
|
||||
ok_or_not_found!($result, Default::default())
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<UrlBufCov>>,
|
||||
|
|
|
|||
2
yazi-plugin/src/external/highlighter.rs
vendored
2
yazi-plugin/src/external/highlighter.rs
vendored
|
|
@ -39,7 +39,7 @@ impl Highlighter {
|
|||
pub fn abort() { INCR.next(); }
|
||||
|
||||
pub async fn highlight(&self, skip: usize, size: Size) -> Result<Text<'static>, 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();
|
||||
|
|
|
|||
|
|
@ -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<Function> {
|
|||
};
|
||||
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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<ByteStr<'static>>,
|
||||
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) }
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::{ByteStr, Error, Session, fs::DirEntry, requests, responses};
|
|||
|
||||
pub struct ReadDir {
|
||||
session: Arc<Session>,
|
||||
dir: ByteStr<'static>,
|
||||
dir: Arc<ByteStr<'static>>,
|
||||
handle: String,
|
||||
|
||||
name: responses::Name<'static>,
|
||||
|
|
@ -16,7 +16,7 @@ impl ReadDir {
|
|||
pub(crate) fn new(session: &Arc<Session>, 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<Option<DirEntry<'_>>, Error> {
|
||||
pub async fn next(&mut self) -> Result<Option<DirEntry>, 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,
|
||||
|
|
|
|||
|
|
@ -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<Session>> for Operator {
|
|||
}
|
||||
|
||||
impl Operator {
|
||||
pub fn make(stream: ChannelStream<Msg>) -> 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;
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ pub struct Session {
|
|||
}
|
||||
|
||||
impl Session {
|
||||
pub fn make(stream: ChannelStream<Msg>) -> Arc<Self> {
|
||||
pub(super) fn make(stream: ChannelStream<Msg>) -> Arc<Self> {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
let me = Arc::new(Self {
|
||||
tx,
|
||||
|
|
|
|||
|
|
@ -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<bool> for Data {
|
|||
}
|
||||
|
||||
// --- Key
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum DataKey {
|
||||
Nil,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
24
yazi-vfs/Cargo.toml
Normal file
24
yazi-vfs/Cargo.toml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "yazi-vfs"
|
||||
version = "25.9.15"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
authors = [ "sxyazi <sxyazi@gmail.com>" ]
|
||||
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 }
|
||||
1
yazi-vfs/src/config/mod.rs
Normal file
1
yazi-vfs/src/config/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
yazi_macro::mod_flat!(provider vfs);
|
||||
29
yazi-vfs/src/config/provider.rs
Normal file
29
yazi-vfs/src/config/provider.rs
Normal file
|
|
@ -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<Self, Self::Error> {
|
||||
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<String>,
|
||||
pub key_file: Option<PathBuf>,
|
||||
}
|
||||
61
yazi-vfs/src/config/vfs.rs
Normal file
61
yazi-vfs/src/config/vfs.rs
Normal file
|
|
@ -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<String, Provider>,
|
||||
}
|
||||
|
||||
impl Vfs {
|
||||
pub async fn load() -> io::Result<&'static Self> {
|
||||
pub static LOADED: OnceCell<Vfs> = OnceCell::const_new();
|
||||
|
||||
async fn init() -> io::Result<Vfs> {
|
||||
tokio::task::spawn_blocking(|| {
|
||||
toml::from_str::<Vfs>(&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<String> {
|
||||
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<Self> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
1
yazi-vfs/src/lib.rs
Normal file
1
yazi-vfs/src/lib.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
yazi_macro::mod_pub!(config local);
|
||||
1
yazi-vfs/src/local/mod.rs
Normal file
1
yazi-vfs/src/local/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
yazi_macro::mod_flat!(xdg);
|
||||
Loading…
Add table
Reference in a new issue