mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: SFTP connection pool (#3198)
This commit is contained in:
parent
9a804bac9c
commit
86f0885737
31 changed files with 570 additions and 337 deletions
92
Cargo.lock
generated
92
Cargo.lock
generated
|
|
@ -150,9 +150,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.99"
|
||||
version = "1.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
|
|
@ -459,9 +459,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.37"
|
||||
version = "1.2.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44"
|
||||
checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
|
|
@ -527,9 +527,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.47"
|
||||
version = "4.5.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7eac00902d9d136acd712710d71823fb8ac8004ca445a89e73a41d45aa712931"
|
||||
checksum = "e2134bb3ea021b78629caa971416385309e0131b351b25e01dc16fb54e1b5fae"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
|
@ -537,9 +537,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.47"
|
||||
version = "4.5.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ad9bbf750e73b5884fb8a211a9424a1906c1e156724260fdae972f31d70e1d6"
|
||||
checksum = "c2ba64afa3c0a6df7fa517765e31314e983f51dda798ffba27b988194fb65dc9"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
|
@ -919,9 +919,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.3"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d630bccd429a5bb5a64b5e94f693bfc48c9f8566418fda4c494cc94f911f87cc"
|
||||
checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
|
@ -1243,9 +1243,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d"
|
||||
checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
|
|
@ -1613,18 +1613,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "imgref"
|
||||
version = "1.11.0"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408"
|
||||
checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.11.3"
|
||||
version = "2.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3"
|
||||
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.5",
|
||||
"hashbrown 0.16.0",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
|
@ -1824,9 +1824,9 @@ checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.175"
|
||||
version = "0.2.176"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
|
|
@ -2685,9 +2685,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.23"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f55f4fedc84ed39cb7a489322318976425e42a147e2be79d8f878e2884f94e84"
|
||||
checksum = "83f9b339b02259ada5c0f4a389b7fb472f933aa17ce176fd2ad98f28bb401fde"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
|
@ -3203,9 +3203,9 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.225"
|
||||
version = "1.0.226"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d"
|
||||
checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
|
|
@ -3223,18 +3223,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.225"
|
||||
version = "1.0.226"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383"
|
||||
checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.225"
|
||||
version = "1.0.226"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516"
|
||||
checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -3265,9 +3265,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "1.0.1"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2789234a13a53fc4be1b51ea1bab45a3c338bdb884862a257d10e5a74ae009e6"
|
||||
checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
|
@ -3617,11 +3617,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.43"
|
||||
version = "0.3.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031"
|
||||
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"libc",
|
||||
"num-conv",
|
||||
"num_threads",
|
||||
|
|
@ -3716,14 +3717,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.9.6"
|
||||
version = "0.9.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae2a4cf385da23d1d53bc15cdfa5c2109e93d8d362393c801e87da2f72f0e201"
|
||||
checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde_core",
|
||||
"serde_spanned 1.0.1",
|
||||
"toml_datetime 0.7.1",
|
||||
"serde_spanned 1.0.2",
|
||||
"toml_datetime 0.7.2",
|
||||
"toml_parser",
|
||||
"toml_writer",
|
||||
"winnow",
|
||||
|
|
@ -3740,9 +3741,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.7.1"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a197c0ec7d131bfc6f7e82c8442ba1595aeab35da7adbf05b6b73cd06a16b6be"
|
||||
checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
|
@ -3762,18 +3763,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "toml_parser"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
|
||||
checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627"
|
||||
dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_writer"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
|
||||
checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
|
|
@ -4695,7 +4696,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"toml 0.9.6",
|
||||
"toml 0.9.7",
|
||||
"twox-hash",
|
||||
"vergen-gitcl",
|
||||
"yazi-boot",
|
||||
|
|
@ -4728,8 +4729,7 @@ dependencies = [
|
|||
"ratatui",
|
||||
"regex",
|
||||
"serde",
|
||||
"tokio",
|
||||
"toml 0.9.6",
|
||||
"toml 0.9.7",
|
||||
"tracing",
|
||||
"yazi-codegen",
|
||||
"yazi-fs",
|
||||
|
|
@ -4868,6 +4868,7 @@ dependencies = [
|
|||
"tokio",
|
||||
"tracing",
|
||||
"trash",
|
||||
"twox-hash",
|
||||
"uzers",
|
||||
"windows-sys 0.61.0",
|
||||
"yazi-ffi",
|
||||
|
|
@ -5038,12 +5039,11 @@ dependencies = [
|
|||
name = "yazi-vfs"
|
||||
version = "25.9.15"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dirs",
|
||||
"hashbrown 0.16.0",
|
||||
"serde",
|
||||
"tokio",
|
||||
"toml 0.9.6",
|
||||
"toml 0.9.7",
|
||||
"uzers",
|
||||
"yazi-macro",
|
||||
"yazi-shared",
|
||||
|
|
|
|||
12
Cargo.toml
12
Cargo.toml
|
|
@ -21,10 +21,10 @@ lto = false
|
|||
|
||||
[workspace.dependencies]
|
||||
ansi-to-tui = "7.0.0"
|
||||
anyhow = "1.0.99"
|
||||
anyhow = "1.0.100"
|
||||
base64 = "0.22.1"
|
||||
bitflags = { version = "2.9.4", features = [ "serde" ] }
|
||||
clap = { version = "4.5.47", features = [ "derive" ] }
|
||||
clap = { version = "4.5.48", features = [ "derive" ] }
|
||||
core-foundation-sys = "0.8.7"
|
||||
crossterm = { version = "0.29.0", features = [ "event-stream" ] }
|
||||
dirs = "6.0.0"
|
||||
|
|
@ -32,8 +32,8 @@ foldhash = "0.2.0"
|
|||
futures = "0.3.31"
|
||||
globset = "0.4.16"
|
||||
hashbrown = { version = "0.16.0", features = [ "serde" ] }
|
||||
indexmap = { version = "2.11.3", features = [ "serde" ] }
|
||||
libc = "0.2.175"
|
||||
indexmap = { version = "2.11.4", features = [ "serde" ] }
|
||||
libc = "0.2.176"
|
||||
lru = "0.16.1"
|
||||
mlua = { version = "0.11.3", features = [ "anyhow", "async", "error-send", "lua54", "macros", "serde" ] }
|
||||
objc = "0.2.7"
|
||||
|
|
@ -44,13 +44,13 @@ ratatui = { version = "0.29.0", features = [ "unstable-rendered-line
|
|||
regex = "1.11.2"
|
||||
russh = { version = "0.54.3", default-features = false, features = [ "ring", "rsa" ] }
|
||||
scopeguard = "1.2.0"
|
||||
serde = { version = "1.0.225", features = [ "derive" ] }
|
||||
serde = { version = "1.0.226", features = [ "derive" ] }
|
||||
serde_json = "1.0.145"
|
||||
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
||||
tokio = { version = "1.47.1", features = [ "full" ] }
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-util = "0.7.16"
|
||||
toml = { version = "0.9.6" }
|
||||
toml = { version = "0.9.7" }
|
||||
tracing = { version = "0.1.41", features = [ "max_level_debug", "release_max_level_debug" ] }
|
||||
unicode-width = { version = "0.2.0", default-features = false }
|
||||
twox-hash = { version = "2.1.2", default-features = false, features = [ "std", "random", "xxhash3_128" ] }
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use futures::executor::block_on;
|
|||
use hashbrown::HashSet;
|
||||
use serde::Serialize;
|
||||
use yazi_fs::{CWD, path::expand_url, provider};
|
||||
use yazi_shared::url::{UrlBuf, UrnBuf};
|
||||
use yazi_shared::url::{UrlBuf, UrlCow, UrnBuf};
|
||||
use yazi_vfs::local::Xdg;
|
||||
|
||||
#[derive(Debug, Default, Serialize)]
|
||||
|
|
@ -27,7 +27,12 @@ impl Boot {
|
|||
return (vec![CWD.load().as_ref().clone()], vec![UrnBuf::default()]);
|
||||
}
|
||||
|
||||
async fn go(entry: UrlBuf) -> (UrlBuf, UrnBuf) {
|
||||
async fn go(entry: &UrlBuf) -> (UrlBuf, UrnBuf) {
|
||||
let mut entry = expand_url(entry);
|
||||
if let Ok(u @ UrlCow::Owned { .. }) = provider::absolute(&entry).await {
|
||||
entry = u.into_owned();
|
||||
}
|
||||
|
||||
let Some((parent, child)) = entry.pair() else {
|
||||
return (entry, UrnBuf::default());
|
||||
};
|
||||
|
|
@ -39,7 +44,7 @@ impl Boot {
|
|||
}
|
||||
}
|
||||
|
||||
futures::future::join_all(entries.iter().map(expand_url).map(go)).await.into_iter().unzip()
|
||||
futures::future::join_all(entries.iter().map(go)).await.into_iter().unzip()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ indexmap = { workspace = true }
|
|||
ratatui = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ scopeguard = { workspace = true }
|
|||
serde = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
twox-hash = { workspace = true }
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
libc = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ use std::{env::{current_dir, set_current_dir}, ops::Deref, path::PathBuf, sync::
|
|||
use arc_swap::ArcSwap;
|
||||
use yazi_shared::{RoCell, url::UrlBuf};
|
||||
|
||||
use crate::provider;
|
||||
|
||||
pub static CWD: RoCell<Cwd> = RoCell::new();
|
||||
|
||||
pub struct Cwd(ArcSwap<UrlBuf>);
|
||||
|
|
@ -26,16 +28,20 @@ impl Default for Cwd {
|
|||
}
|
||||
|
||||
impl Cwd {
|
||||
pub fn path(&self) -> PathBuf {
|
||||
let url = self.0.load();
|
||||
provider::cache(url.as_ref()).unwrap_or_else(|| url.loc.to_path())
|
||||
}
|
||||
|
||||
pub fn set(&self, url: &UrlBuf) -> bool {
|
||||
if self.load().as_ref() == url {
|
||||
if !url.is_absolute() {
|
||||
return false;
|
||||
} else if self.0.load().as_ref() == url {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.store(Arc::new(url.clone()));
|
||||
if let Some(p) = url.as_path() {
|
||||
unsafe { std::env::set_var("PWD", p) };
|
||||
Self::sync_cwd();
|
||||
}
|
||||
self.0.store(Arc::new(url.clone()));
|
||||
Self::sync_cwd();
|
||||
|
||||
true
|
||||
}
|
||||
|
|
@ -47,17 +53,20 @@ impl Cwd {
|
|||
}
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
if let Some(p) = CWD.load().as_path() {
|
||||
_ = set_current_dir(p);
|
||||
}
|
||||
let path = CWD.path();
|
||||
std::fs::create_dir_all(&path).ok();
|
||||
|
||||
_ = set_current_dir(&path);
|
||||
let cur = current_dir().unwrap_or_default();
|
||||
|
||||
unsafe { std::env::set_var("PWD", path) }
|
||||
SYNCING.store(false, Ordering::Relaxed);
|
||||
|
||||
if let Some(p) = CWD.load().as_path()
|
||||
&& cur != p
|
||||
{
|
||||
set_current_dir(p).ok();
|
||||
let path = CWD.path();
|
||||
if cur != path {
|
||||
std::fs::create_dir_all(&path).ok();
|
||||
set_current_dir(&path).ok();
|
||||
unsafe { std::env::set_var("PWD", path) }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,11 +62,13 @@ fn expand_variables(p: &Path) -> Cow<'_, Path> {
|
|||
}
|
||||
}
|
||||
|
||||
fn absolute_url<'a>(url: Url<'a>) -> UrlCow<'a> {
|
||||
let b = url.loc.as_os_str().as_encoded_bytes();
|
||||
let local = !url.scheme.is_virtual();
|
||||
pub fn absolute_url<'a>(url: Url<'a>) -> UrlCow<'a> {
|
||||
if url.scheme.is_virtual() {
|
||||
return url.into();
|
||||
}
|
||||
|
||||
if cfg!(windows) && local && b.len() == 2 && b[1] == b':' && b[0].is_ascii_alphabetic() {
|
||||
let b = url.loc.as_os_str().as_encoded_bytes();
|
||||
if cfg!(windows) && b.len() == 2 && b[1] == b':' && b[0].is_ascii_alphabetic() {
|
||||
let loc = LocBuf::with(
|
||||
format!(r"{}:\", b[0].to_ascii_uppercase() as char).into(),
|
||||
if url.has_base() { 0 } else { 2 },
|
||||
|
|
@ -74,8 +76,7 @@ fn absolute_url<'a>(url: Url<'a>) -> UrlCow<'a> {
|
|||
)
|
||||
.expect("Failed to create Loc from drive letter");
|
||||
UrlBuf { loc, scheme: url.scheme.into() }.into()
|
||||
} else if local
|
||||
&& let Ok(rest) = url.loc.strip_prefix("~/")
|
||||
} else if let Ok(rest) = url.loc.strip_prefix("~/")
|
||||
&& let Some(home) = dirs::home_dir()
|
||||
&& home.is_absolute()
|
||||
{
|
||||
|
|
@ -88,10 +89,10 @@ fn absolute_url<'a>(url: Url<'a>) -> UrlCow<'a> {
|
|||
.expect("Failed to create Loc from home directory");
|
||||
UrlBuf { loc, scheme: url.scheme.into() }.into()
|
||||
} else if !url.is_absolute() {
|
||||
let cwd = CWD.load();
|
||||
let loc = LocBuf::with(cwd.loc.join(url.loc), url.uri().count(), url.urn().count())
|
||||
let cwd = CWD.path();
|
||||
let loc = LocBuf::with(cwd.join(url.loc), url.uri().count(), url.urn().count())
|
||||
.expect("Failed to create Loc from relative path");
|
||||
UrlBuf { loc, scheme: cwd.scheme.clone() }.into()
|
||||
UrlBuf { loc, scheme: url.scheme.into() }.into()
|
||||
} else {
|
||||
url.into()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::{io, path::Path};
|
|||
|
||||
use yazi_shared::scheme::SchemeRef;
|
||||
|
||||
use crate::provider::FileBuilder;
|
||||
use crate::{cha::Cha, provider::FileBuilder};
|
||||
|
||||
pub enum Gate {
|
||||
Local(super::local::Gate),
|
||||
|
|
@ -28,6 +28,14 @@ impl FileBuilder for Gate {
|
|||
self
|
||||
}
|
||||
|
||||
fn cha(&mut self, cha: Cha) -> &mut Self {
|
||||
match self {
|
||||
Self::Local(g) => _ = g.cha(cha),
|
||||
Self::Sftp(g) => _ = g.cha(cha),
|
||||
};
|
||||
self
|
||||
}
|
||||
|
||||
fn create(&mut self, create: bool) -> &mut Self {
|
||||
match self {
|
||||
Self::Local(g) => _ = g.create(create),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::{io, path::Path};
|
|||
|
||||
use yazi_shared::scheme::SchemeRef;
|
||||
|
||||
use crate::provider::FileBuilder;
|
||||
use crate::{cha::Cha, provider::FileBuilder};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Gate(tokio::fs::OpenOptions);
|
||||
|
|
@ -15,6 +15,12 @@ impl FileBuilder for Gate {
|
|||
self
|
||||
}
|
||||
|
||||
fn cha(&mut self, cha: Cha) -> &mut Self {
|
||||
#[cfg(unix)]
|
||||
self.0.mode(cha.mode.bits() as _);
|
||||
self
|
||||
}
|
||||
|
||||
fn create(&mut self, create: bool) -> &mut Self {
|
||||
self.0.create(create);
|
||||
self
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
use std::{io, path::{Path, PathBuf}};
|
||||
|
||||
use crate::{cha::Cha, provider::Provider};
|
||||
use yazi_shared::url::{Url, UrlCow};
|
||||
|
||||
use crate::{cha::Cha, path::absolute_url, provider::Provider};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Local;
|
||||
|
|
@ -10,12 +12,16 @@ impl Provider for Local {
|
|||
type Gate = super::Gate;
|
||||
type ReadDir = super::ReadDir;
|
||||
|
||||
#[inline]
|
||||
fn cache<P>(&self, _: P) -> Option<PathBuf>
|
||||
async fn absolute<'a, U>(&self, url: U) -> io::Result<UrlCow<'a>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
None
|
||||
let url: Url = url.into();
|
||||
if url.scheme.is_virtual() {
|
||||
Err(io::Error::new(io::ErrorKind::InvalidInput, "Not a local URL"))
|
||||
} else {
|
||||
Ok(absolute_url(url))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -220,23 +226,9 @@ impl Provider for Local {
|
|||
|
||||
impl Local {
|
||||
async fn copy_impl(from: PathBuf, to: PathBuf, cha: Cha) -> io::Result<u64> {
|
||||
let mut ft = std::fs::FileTimes::new();
|
||||
cha.atime.map(|t| ft = ft.set_accessed(t));
|
||||
cha.mtime.map(|t| ft = ft.set_modified(t));
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use std::os::macos::fs::FileTimesExt;
|
||||
cha.btime.map(|t| ft = ft.set_created(t));
|
||||
}
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::fs::FileTimesExt;
|
||||
cha.btime.map(|t| ft = ft.set_created(t));
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||
{
|
||||
use std::os::{fd::AsRawFd, unix::fs::OpenOptionsExt};
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut reader = std::fs::File::open(from)?;
|
||||
|
|
@ -248,8 +240,8 @@ impl Local {
|
|||
.open(to)?;
|
||||
|
||||
let written = std::io::copy(&mut reader, &mut writer)?;
|
||||
unsafe { libc::fchmod(writer.as_raw_fd(), cha.mode.bits() as _) };
|
||||
writer.set_times(ft).ok();
|
||||
writer.set_permissions(cha.into()).ok();
|
||||
writer.set_times(cha.into()).ok();
|
||||
|
||||
Ok(written)
|
||||
})
|
||||
|
|
@ -260,7 +252,11 @@ impl Local {
|
|||
{
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let written = std::fs::copy(from, &to)?;
|
||||
std::fs::File::options().write(true).open(to).and_then(|f| f.set_times(ft)).ok();
|
||||
|
||||
if let Ok(file) = std::fs::File::options().write(true).open(to) {
|
||||
file.set_times(cha.into()).ok();
|
||||
}
|
||||
|
||||
Ok(written)
|
||||
})
|
||||
.await?
|
||||
|
|
|
|||
40
yazi-fs/src/provider/local/metadata.rs
Normal file
40
yazi-fs/src/provider/local/metadata.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use std::fs::{FileTimes, Permissions};
|
||||
|
||||
use crate::cha::Cha;
|
||||
|
||||
impl From<Cha> for FileTimes {
|
||||
fn from(cha: Cha) -> Self {
|
||||
let mut t = std::fs::FileTimes::new();
|
||||
|
||||
if let Some(atime) = cha.atime {
|
||||
t = t.set_accessed(atime);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Some(btime) = cha.btime {
|
||||
use std::os::macos::fs::FileTimesExt;
|
||||
t = t.set_created(btime);
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
if let Some(btime) = cha.btime {
|
||||
use std::os::windows::fs::FileTimesExt;
|
||||
t = t.set_created(btime);
|
||||
}
|
||||
|
||||
if let Some(mtime) = cha.mtime {
|
||||
t = t.set_modified(mtime);
|
||||
}
|
||||
|
||||
t
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl From<Cha> for Permissions {
|
||||
fn from(cha: Cha) -> Self {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
Permissions::from_mode(cha.mode.bits() as _)
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(calculator casefold dir_entry gate identical local read_dir);
|
||||
yazi_macro::mod_flat!(calculator casefold dir_entry gate identical local metadata read_dir);
|
||||
|
|
|
|||
|
|
@ -1,18 +1,40 @@
|
|||
use std::{io, path::{Path, PathBuf}, sync::Arc};
|
||||
use std::{io, path::{Path, PathBuf}};
|
||||
|
||||
use yazi_shared::{scheme::SchemeRef, url::{Url, UrlBuf}};
|
||||
use tokio::io::{BufReader, BufWriter};
|
||||
use twox_hash::XxHash3_128;
|
||||
use yazi_shared::{scheme::SchemeRef, url::{Url, UrlBuf, UrlCow}};
|
||||
use yazi_vfs::local::Xdg;
|
||||
|
||||
use crate::{cha::Cha, provider::{Provider, ReadDir, RwFile, local::{self, Local}}};
|
||||
use crate::{cha::Cha, provider::{Provider, Providers, ReadDir, RwFile, local::{self, Local}}};
|
||||
|
||||
pub async fn absolute<'a, U>(url: U) -> io::Result<UrlCow<'a>>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
let url: Url = url.into();
|
||||
Providers::new(url).await?.absolute(url).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
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 }
|
||||
let url: Url = url.into();
|
||||
match url.scheme {
|
||||
SchemeRef::Regular | SchemeRef::Search(_) => None,
|
||||
SchemeRef::Archive(name) => Some(
|
||||
Xdg::cache_dir()
|
||||
.join(format!("archive-{}", yazi_shared::url::Encode::domain(name)))
|
||||
.join(format!("{:x}", XxHash3_128::oneshot(url.loc.bytes()))),
|
||||
),
|
||||
SchemeRef::Sftp(name) => Some(
|
||||
Xdg::cache_dir()
|
||||
.join(format!("sftp-{}", yazi_shared::url::Encode::domain(name)))
|
||||
.join(format!("{:x}", XxHash3_128::oneshot(url.loc.bytes()))),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn calculate<'a, U>(url: U) -> io::Result<u64>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
|
|
@ -25,19 +47,22 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn canonicalize<'a, U>(url: U) -> io::Result<UrlBuf>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
if let Some(path) = url.into().as_path() {
|
||||
Local.canonicalize(path).await.map(Into::into)
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
let url: Url = url.into();
|
||||
let canon = Providers::new(url).await?.canonicalize(url.loc).await?;
|
||||
|
||||
Ok(match url.scheme {
|
||||
SchemeRef::Regular | SchemeRef::Search(_) => canon.into(),
|
||||
SchemeRef::Archive(_) => {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem: archive"))?
|
||||
}
|
||||
SchemeRef::Sftp(_) => UrlBuf { loc: canon.into(), scheme: url.scheme.into() },
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn casefold<'a, U>(url: U) -> io::Result<UrlBuf>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
|
|
@ -49,69 +74,70 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn copy<'a, U, V>(from: U, to: V, cha: Cha) -> io::Result<u64>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
V: Into<Url<'a>>,
|
||||
{
|
||||
if let (Some(from), Some(to)) = (from.into().as_path(), to.into().as_path()) {
|
||||
Local.copy(from, to, cha).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
let (from, to): (Url, Url) = (from.into(), to.into());
|
||||
|
||||
match (from.as_path(), to.as_path()) {
|
||||
(Some(from), Some(to)) => Local.copy(from, to, cha).await,
|
||||
(None, None) if from.scheme.covariant(to.scheme) => {
|
||||
Providers::new(from).await?.copy(from.loc, to.loc, cha).await
|
||||
}
|
||||
(Some(_), None) | (None, Some(_)) | (None, None) => {
|
||||
let src = Providers::new(from).await?.open(from.loc).await?;
|
||||
let dist = Providers::new(to).await?.create(to.loc).await?;
|
||||
|
||||
let mut reader = BufReader::with_capacity(524288, src);
|
||||
let mut writer = BufWriter::with_capacity(524288, dist);
|
||||
|
||||
let written = tokio::io::copy(&mut reader, &mut writer).await?;
|
||||
writer.into_inner().set_cha(cha).await.ok();
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn create<'a, U>(url: U) -> io::Result<RwFile>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
if let Some(path) = url.into().as_path() {
|
||||
Local.create(path).await.map(Into::into)
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
let url: Url = url.into();
|
||||
Providers::new(url).await?.create(url.loc).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn create_dir<'a, U>(url: U) -> io::Result<()>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
if let Some(path) = url.into().as_path() {
|
||||
Local.create_dir(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
let url: Url = url.into();
|
||||
Providers::new(url).await?.create_dir(url.loc).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn create_dir_all<'a, U>(url: U) -> io::Result<()>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
if let Some(path) = url.into().as_path() {
|
||||
Local.create_dir_all(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
let url: Url = url.into();
|
||||
Providers::new(url).await?.create_dir_all(url.loc).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn hard_link<'a, U, V>(original: U, link: V) -> io::Result<()>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
V: Into<Url<'a>>,
|
||||
{
|
||||
if let (Some(original), Some(link)) = (original.into().as_path(), link.into().as_path()) {
|
||||
Local.hard_link(original, link).await
|
||||
let (original, link): (Url, Url) = (original.into(), link.into());
|
||||
if original.scheme.covariant(link.scheme) {
|
||||
Providers::new(original).await?.hard_link(original.loc, link.loc).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
Err(io::Error::new(io::ErrorKind::CrossesDevices, "Cross-filesystem hardlink"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn identical<'a, U, V>(a: U, b: V) -> io::Result<bool>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
|
|
@ -124,19 +150,14 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn metadata<'a, U>(url: U) -> io::Result<Cha>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
if let Some(path) = url.into().as_path() {
|
||||
Local.metadata(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
let url: Url = url.into();
|
||||
Providers::new(url).await?.metadata(url.loc).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn must_identical<'a, U, V>(a: U, b: V) -> bool
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
|
|
@ -145,155 +166,105 @@ where
|
|||
identical(a, b).await.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn read_dir<'a, U>(url: U) -> io::Result<ReadDir>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
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!(),
|
||||
})
|
||||
Providers::new(url).await?.read_dir(url.loc).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn read_link<'a, U>(url: U) -> io::Result<PathBuf>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
if let Some(path) = url.into().as_path() {
|
||||
Local.read_link(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
let url: Url = url.into();
|
||||
Providers::new(url).await?.read_link(url.loc).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn remove_dir<'a, U>(url: U) -> io::Result<()>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
if let Some(path) = url.into().as_path() {
|
||||
Local.remove_dir(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
let url: Url = url.into();
|
||||
Providers::new(url).await?.remove_dir(url.loc).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn remove_dir_all<'a, U>(url: U) -> io::Result<()>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
if let Some(path) = url.into().as_path() {
|
||||
Local.remove_dir_all(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
let url: Url = url.into();
|
||||
Providers::new(url).await?.remove_dir_all(url.loc).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn remove_file<'a, U>(url: U) -> io::Result<()>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
if let Some(path) = url.into().as_path() {
|
||||
Local.remove_file(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
let url: Url = url.into();
|
||||
Providers::new(url).await?.remove_file(url.loc).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn rename<'a, U, V>(from: U, to: V) -> io::Result<()>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
V: Into<Url<'a>>,
|
||||
{
|
||||
if let (Some(from), Some(to)) = (from.into().as_path(), to.into().as_path()) {
|
||||
Local.rename(from, to).await
|
||||
let (from, to): (Url, Url) = (from.into(), to.into());
|
||||
if from.scheme.covariant(to.scheme) {
|
||||
Providers::new(from).await?.rename(from.loc, to.loc).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
Err(io::Error::new(io::ErrorKind::CrossesDevices, "Cross-filesystem rename"))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn symlink<'a, U, F>(original: &Path, link: U, is_dir: F) -> io::Result<()>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
F: AsyncFnOnce() -> io::Result<bool>,
|
||||
{
|
||||
if let Some(link) = link.into().as_path() {
|
||||
Local.symlink(original, link, is_dir).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
let link: Url = link.into();
|
||||
Providers::new(link).await?.symlink(original, link.loc, is_dir).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn symlink_dir<'a, U>(original: &Path, link: U) -> io::Result<()>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
if let Some(link) = link.into().as_path() {
|
||||
Local.symlink_dir(original, link).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
let link: Url = link.into();
|
||||
Providers::new(link).await?.symlink_dir(original, link.loc).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn symlink_file<'a, U>(original: &Path, link: U) -> io::Result<()>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
if let Some(link) = link.into().as_path() {
|
||||
Local.symlink_file(original, link).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
let link: Url = link.into();
|
||||
Providers::new(link).await?.symlink_file(original, link.loc).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn symlink_metadata<'a, U>(url: U) -> io::Result<Cha>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
if let Some(path) = url.into().as_path() {
|
||||
Local.symlink_metadata(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
let url: Url = url.into();
|
||||
Providers::new(url).await?.symlink_metadata(url.loc).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn trash<'a, U>(url: U) -> io::Result<()>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
if let Some(path) = url.into().as_path() {
|
||||
Local.trash(path).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
let url: Url = url.into();
|
||||
Providers::new(url).await?.trash(url.loc).await
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub async fn write<'a, U, C>(url: U, contents: C) -> io::Result<()>
|
||||
where
|
||||
U: Into<Url<'a>>,
|
||||
C: AsRef<[u8]>,
|
||||
{
|
||||
if let Some(path) = url.into().as_path() {
|
||||
Local.write(path, contents).await
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
|
||||
}
|
||||
let url: Url = url.into();
|
||||
Providers::new(url).await?.write(url.loc, contents).await
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::{io, path::{Path, PathBuf}, sync::Arc};
|
||||
|
||||
use yazi_shared::{scheme::SchemeRef, url::Url};
|
||||
use yazi_shared::{scheme::SchemeRef, url::{Url, UrlCow}};
|
||||
use yazi_vfs::config::{ProviderSftp, Vfs};
|
||||
|
||||
use super::local::Local;
|
||||
|
|
@ -34,13 +34,13 @@ impl Provider for Providers<'_> {
|
|||
type Gate = super::Gate;
|
||||
type ReadDir = super::ReadDir;
|
||||
|
||||
fn cache<P>(&self, path: P) -> Option<PathBuf>
|
||||
async fn absolute<'a, U>(&self, url: U) -> io::Result<UrlCow<'a>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
match self.0 {
|
||||
Inner::Regular | Inner::Search(_) => Local.cache(path),
|
||||
Inner::Sftp((p, _)) => p.cache(path),
|
||||
Inner::Regular | Inner::Search(_) => Local.absolute(url).await,
|
||||
Inner::Sftp((p, _)) => p.absolute(url).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
use std::pin::Pin;
|
||||
use std::{io, pin::Pin};
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use crate::cha::Cha;
|
||||
|
||||
pub enum RwFile {
|
||||
Tokio(tokio::fs::File),
|
||||
Sftp(Box<yazi_sftp::fs::File>),
|
||||
|
|
@ -15,13 +17,34 @@ impl From<yazi_sftp::fs::File> for RwFile {
|
|||
fn from(f: yazi_sftp::fs::File) -> Self { Self::Sftp(Box::new(f)) }
|
||||
}
|
||||
|
||||
impl RwFile {
|
||||
pub async fn set_cha(&self, cha: Cha) -> io::Result<()> {
|
||||
match self {
|
||||
Self::Tokio(f) => {
|
||||
let std = f.try_clone().await?.into_std().await;
|
||||
tokio::task::spawn_blocking(move || {
|
||||
#[cfg(unix)]
|
||||
std.set_permissions(cha.into()).ok();
|
||||
std.set_times(cha.into()).ok();
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
Self::Sftp(f) => {
|
||||
f.fsetstat(&cha.into()).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for RwFile {
|
||||
#[inline]
|
||||
fn poll_read(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &mut tokio::io::ReadBuf<'_>,
|
||||
) -> std::task::Poll<std::io::Result<()>> {
|
||||
) -> std::task::Poll<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),
|
||||
|
|
@ -35,7 +58,7 @@ impl AsyncWrite for RwFile {
|
|||
mut self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> std::task::Poll<Result<usize, std::io::Error>> {
|
||||
) -> std::task::Poll<Result<usize, 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),
|
||||
|
|
@ -46,7 +69,7 @@ impl AsyncWrite for RwFile {
|
|||
fn poll_flush(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||
) -> std::task::Poll<Result<(), io::Error>> {
|
||||
match &mut *self {
|
||||
Self::Tokio(f) => Pin::new(f).poll_flush(cx),
|
||||
Self::Sftp(f) => Pin::new(f).poll_flush(cx),
|
||||
|
|
@ -57,7 +80,7 @@ impl AsyncWrite for RwFile {
|
|||
fn poll_shutdown(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Result<(), std::io::Error>> {
|
||||
) -> std::task::Poll<Result<(), io::Error>> {
|
||||
match &mut *self {
|
||||
Self::Tokio(f) => Pin::new(f).poll_shutdown(cx),
|
||||
Self::Sftp(f) => Pin::new(f).poll_shutdown(cx),
|
||||
|
|
@ -68,8 +91,8 @@ impl AsyncWrite for RwFile {
|
|||
fn poll_write_vectored(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
bufs: &[std::io::IoSlice<'_>],
|
||||
) -> std::task::Poll<Result<usize, std::io::Error>> {
|
||||
bufs: &[io::IoSlice<'_>],
|
||||
) -> std::task::Poll<Result<usize, 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),
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@ use yazi_sftp::fs::{Attrs, Flags};
|
|||
use yazi_shared::scheme::SchemeRef;
|
||||
use yazi_vfs::config::{ProviderSftp, Vfs};
|
||||
|
||||
use crate::provider::FileBuilder;
|
||||
use crate::{cha::Cha, provider::FileBuilder};
|
||||
|
||||
pub struct Gate {
|
||||
sftp: super::Sftp,
|
||||
|
||||
append: bool,
|
||||
cha: Option<Cha>,
|
||||
create: bool,
|
||||
create_new: bool,
|
||||
read: bool,
|
||||
|
|
@ -25,6 +26,11 @@ impl FileBuilder for Gate {
|
|||
self
|
||||
}
|
||||
|
||||
fn cha(&mut self, cha: Cha) -> &mut Self {
|
||||
self.cha = Some(cha);
|
||||
self
|
||||
}
|
||||
|
||||
fn create(&mut self, create: bool) -> &mut Self {
|
||||
self.create = create;
|
||||
self
|
||||
|
|
@ -45,6 +51,7 @@ impl FileBuilder for Gate {
|
|||
sftp,
|
||||
|
||||
append: false,
|
||||
cha: None,
|
||||
create: false,
|
||||
create_new: false,
|
||||
read: false,
|
||||
|
|
@ -77,7 +84,9 @@ impl FileBuilder for Gate {
|
|||
flags |= Flags::WRITE;
|
||||
}
|
||||
|
||||
Ok(self.sftp.op().await?.open(&path, flags, Attrs::default()).await?)
|
||||
let attrs = self.cha.map_or(Attrs::default(), Attrs::from);
|
||||
|
||||
Ok(self.sftp.op().await?.open(&path, flags, &attrs).await?)
|
||||
}
|
||||
|
||||
fn read(&mut self, read: bool) -> &mut Self {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
use std::{io, path::{Path, PathBuf}, sync::Arc};
|
||||
|
||||
use russh::keys::PrivateKeyWithHashAlg;
|
||||
use tokio::io::{BufReader, BufWriter};
|
||||
use yazi_sftp::fs::{Attrs, Flags};
|
||||
use yazi_shared::scheme::SchemeRef;
|
||||
use yazi_shared::{scheme::SchemeRef, url::{Url, UrlBuf, UrlCow}};
|
||||
use yazi_vfs::config::ProviderSftp;
|
||||
|
||||
use crate::{cha::Cha, provider::{FileBuilder, Provider}};
|
||||
use crate::{cha::Cha, provider::{FileBuilder, Provider, local::Local}};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Sftp {
|
||||
|
|
@ -21,11 +23,18 @@ impl Provider for Sftp {
|
|||
type Gate = super::Gate;
|
||||
type ReadDir = super::ReadDir;
|
||||
|
||||
fn cache<P>(&self, _: P) -> Option<PathBuf>
|
||||
async fn absolute<'a, U>(&self, url: U) -> io::Result<UrlCow<'a>>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
U: Into<Url<'a>>,
|
||||
{
|
||||
todo!()
|
||||
let url: Url = url.into();
|
||||
Ok(if url.is_absolute() {
|
||||
url.into()
|
||||
} else if let SchemeRef::Sftp(_) = url.scheme {
|
||||
UrlBuf { loc: self.canonicalize(url.loc).await?.into(), scheme: url.scheme.into() }.into()
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::InvalidInput, "Not an SFTP URL"))?
|
||||
})
|
||||
}
|
||||
|
||||
async fn canonicalize<P>(&self, path: P) -> io::Result<PathBuf>
|
||||
|
|
@ -43,10 +52,16 @@ impl Provider for Sftp {
|
|||
let attrs = Attrs::from(cha);
|
||||
|
||||
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?;
|
||||
let from = op.open(&from, Flags::READ, &Attrs::default()).await?;
|
||||
let to = op.open(&to, Flags::WRITE | Flags::CREATE | Flags::TRUNCATE, &attrs).await?;
|
||||
|
||||
tokio::io::copy(&mut from, &mut to).await
|
||||
let mut reader = BufReader::with_capacity(524288, from);
|
||||
let mut writer = BufWriter::with_capacity(524288, to);
|
||||
|
||||
let written = tokio::io::copy(&mut reader, &mut writer).await?;
|
||||
writer.into_inner().fsetstat(&attrs).await.ok();
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
async fn create_dir<P>(&self, path: P) -> io::Result<()>
|
||||
|
|
@ -144,7 +159,7 @@ impl Sftp {
|
|||
use deadpool::managed::PoolError;
|
||||
|
||||
let pool = *super::CONN.lock().entry(self.config).or_insert_with(|| {
|
||||
Box::leak(Box::new(deadpool::managed::Pool::builder(self).build().unwrap()))
|
||||
Box::leak(Box::new(deadpool::managed::Pool::builder(self).max_size(5).build().unwrap()))
|
||||
});
|
||||
|
||||
pool.get().await.map_err(|e| match e {
|
||||
|
|
@ -172,45 +187,134 @@ impl deadpool::managed::Manager for Sftp {
|
|||
type Error = io::Error;
|
||||
type Type = yazi_sftp::Operator;
|
||||
|
||||
// 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 channel = self.connect().await.map_err(|e| {
|
||||
io::Error::other(format!("Failed to connect to SFTP server `{}`: {e}", self.name))
|
||||
})?;
|
||||
|
||||
// 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()))
|
||||
let mut op = yazi_sftp::Operator::make(channel.into_stream());
|
||||
op.init().await?;
|
||||
Ok(op)
|
||||
}
|
||||
|
||||
async fn recycle(
|
||||
&self,
|
||||
obj: &mut Self::Type,
|
||||
metrics: &deadpool::managed::Metrics,
|
||||
_metrics: &deadpool::managed::Metrics,
|
||||
) -> deadpool::managed::RecycleResult<Self::Error> {
|
||||
// FIXME
|
||||
Ok(())
|
||||
if obj.is_closed() {
|
||||
Err(deadpool::managed::RecycleError::Message("Channel closed".into()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sftp {
|
||||
async fn connect(self) -> Result<russh::Channel<russh::client::Msg>, russh::Error> {
|
||||
let pref = Arc::new(russh::client::Config {
|
||||
inactivity_timeout: Some(std::time::Duration::from_secs(30)),
|
||||
keepalive_interval: Some(std::time::Duration::from_secs(10)),
|
||||
nodelay: true,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let session = if self.config.password.is_some() {
|
||||
self.connect_by_password(pref).await
|
||||
} else if self.config.key_file.is_some() {
|
||||
self.connect_by_key(pref).await
|
||||
} else {
|
||||
self.connect_by_agent(pref).await
|
||||
}?;
|
||||
|
||||
let channel = session.channel_open_session().await?;
|
||||
channel.request_subsystem(true, "sftp").await?;
|
||||
Ok(channel)
|
||||
}
|
||||
|
||||
async fn connect_by_password(
|
||||
self,
|
||||
pref: Arc<russh::client::Config>,
|
||||
) -> Result<russh::client::Handle<Self>, russh::Error> {
|
||||
let Some(password) = &self.config.password else {
|
||||
return Err(russh::Error::InvalidConfig("Password not provided".to_owned()));
|
||||
};
|
||||
|
||||
let mut session =
|
||||
russh::client::connect(pref, (self.config.host.as_str(), self.config.port), self).await?;
|
||||
|
||||
if session.authenticate_password(&self.config.user, password).await?.success() {
|
||||
Ok(session)
|
||||
} else {
|
||||
Err(russh::Error::InvalidConfig("Password authentication failed".to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
async fn connect_by_key(
|
||||
self,
|
||||
pref: Arc<russh::client::Config>,
|
||||
) -> Result<russh::client::Handle<Self>, russh::Error> {
|
||||
let Some(key_file) = &self.config.key_file else {
|
||||
return Err(russh::Error::InvalidConfig("Key file not provided".to_owned()));
|
||||
};
|
||||
|
||||
let key = Local
|
||||
.read_to_string(key_file)
|
||||
.await
|
||||
.map_err(|e| russh::Error::InvalidConfig(format!("Failed to read key file: {e}")))?;
|
||||
|
||||
let key = russh::keys::decode_secret_key(&key, self.config.key_passphrase.as_deref())?;
|
||||
|
||||
let mut session =
|
||||
russh::client::connect(pref, (self.config.host.as_str(), self.config.port), self).await?;
|
||||
|
||||
let result = session
|
||||
.authenticate_publickey(
|
||||
&self.config.user,
|
||||
PrivateKeyWithHashAlg::new(
|
||||
Arc::new(key),
|
||||
session.best_supported_rsa_hash().await?.flatten(),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
|
||||
if result.success() {
|
||||
Ok(session)
|
||||
} else {
|
||||
Err(russh::Error::InvalidConfig("Public key authentication failed".to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
async fn connect_by_agent(
|
||||
self,
|
||||
pref: Arc<russh::client::Config>,
|
||||
) -> Result<russh::client::Handle<Self>, russh::Error> {
|
||||
let Some(identity_agent) = &self.config.identity_agent else {
|
||||
return Err(russh::Error::InvalidConfig("Identity agent not provided".to_owned()));
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
let mut agent = russh::keys::agent::client::AgentClient::connect_uds(identity_agent).await?;
|
||||
#[cfg(windows)]
|
||||
let mut agent =
|
||||
russh::keys::agent::client::AgentClient::connect_named_pipe(identity_agent).await?;
|
||||
|
||||
let keys = agent.request_identities().await?;
|
||||
if keys.is_empty() {
|
||||
return Err(russh::Error::InvalidConfig("No keys found in SSH agent".to_owned()));
|
||||
}
|
||||
|
||||
let mut session =
|
||||
russh::client::connect(pref, (self.config.host.as_str(), self.config.port), self).await?;
|
||||
|
||||
for key in keys {
|
||||
match session.authenticate_publickey_with(&self.config.user, key, None, &mut agent).await {
|
||||
Ok(result) if result.success() => return Ok(session),
|
||||
Ok(_) => {}
|
||||
Err(e) => tracing::error!("Identity agent authentication error: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
Err(russh::Error::InvalidConfig("Public key authentication via agent failed".to_owned()))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +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 yazi_shared::{scheme::SchemeRef, url::{Url, UrlCow}};
|
||||
|
||||
use crate::cha::{Cha, ChaType};
|
||||
|
||||
|
|
@ -11,9 +11,9 @@ pub trait Provider {
|
|||
type Gate: FileBuilder<File = Self::File>;
|
||||
type ReadDir: DirReader;
|
||||
|
||||
fn cache<P>(&self, path: P) -> Option<PathBuf>
|
||||
fn absolute<'a, U>(&self, url: U) -> impl Future<Output = io::Result<UrlCow<'a>>>
|
||||
where
|
||||
P: AsRef<Path>;
|
||||
U: Into<Url<'a>>;
|
||||
|
||||
fn canonicalize<P>(&self, path: P) -> impl Future<Output = io::Result<PathBuf>>
|
||||
where
|
||||
|
|
@ -40,26 +40,37 @@ pub trait Provider {
|
|||
P: AsRef<Path>,
|
||||
{
|
||||
async move {
|
||||
let path = path.as_ref();
|
||||
let mut path = path.as_ref();
|
||||
if path == Path::new("") {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
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(e) => return Err(e),
|
||||
let mut stack = Vec::new();
|
||||
loop {
|
||||
match self.create_dir(path).await {
|
||||
Ok(()) => break,
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => {
|
||||
if let Some(parent) = path.parent() {
|
||||
stack.push(path);
|
||||
path = parent;
|
||||
} else {
|
||||
return Err(io::Error::other("failed to create whole tree"));
|
||||
}
|
||||
}
|
||||
Err(_) if self.metadata(path).await.is_ok_and(|m| m.is_dir()) => break,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
match path.parent() {
|
||||
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 {
|
||||
Ok(()) => Ok(()),
|
||||
Err(_) if self.metadata(path).await.is_ok_and(|m| m.is_dir()) => Ok(()),
|
||||
Err(e) => Err(e),
|
||||
|
||||
while let Some(p) = stack.pop() {
|
||||
match self.create_dir(p).await {
|
||||
Ok(()) => {}
|
||||
Err(_) if self.metadata(p).await.is_ok_and(|m| m.is_dir()) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +116,7 @@ pub trait Provider {
|
|||
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(me, &child.path()).await
|
||||
Box::pin(remove_dir_all_impl(me, &child.path())).await
|
||||
} else {
|
||||
me.remove_file(&child.path()).await
|
||||
};
|
||||
|
|
@ -206,6 +217,8 @@ pub trait FileBuilder {
|
|||
|
||||
fn append(&mut self, append: bool) -> &mut Self;
|
||||
|
||||
fn cha(&mut self, cha: Cha) -> &mut Self;
|
||||
|
||||
fn create(&mut self, create: bool) -> &mut Self;
|
||||
|
||||
fn create_new(&mut self, create_new: bool) -> &mut Self;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::{collections::HashMap, fmt};
|
|||
|
||||
use serde::{Deserialize, Deserializer, Serialize, de::Visitor, ser::SerializeStruct};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Attrs {
|
||||
pub size: Option<u64>,
|
||||
pub uid: Option<u32>,
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ impl File {
|
|||
Operator::from(&self.session).fstat(&self.handle).await
|
||||
}
|
||||
|
||||
pub async fn fsetstat(&self, attrs: Attrs) -> Result<(), Error> {
|
||||
pub async fn fsetstat(&self, attrs: &Attrs) -> Result<(), Error> {
|
||||
Operator::from(&self.session).fsetstat(&self.handle, attrs).await
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ impl Operator {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn open<'a, P>(&self, path: P, flags: Flags, attrs: Attrs) -> Result<File, Error>
|
||||
pub async fn open<'a, P>(&self, path: P, flags: Flags, attrs: &'a Attrs) -> Result<File, Error>
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
|
|
@ -78,7 +78,7 @@ impl Operator {
|
|||
status.into()
|
||||
}
|
||||
|
||||
pub async fn fsetstat(&self, handle: &str, attrs: Attrs) -> Result<(), Error> {
|
||||
pub async fn fsetstat<'a>(&self, handle: &str, attrs: &'a Attrs) -> Result<(), Error> {
|
||||
let status: responses::Status = self.send(requests::FSetStat::new(handle, attrs)).await?;
|
||||
status.into()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -259,6 +259,7 @@ pub fn from_bytes(mut bytes: &[u8]) -> Result<Packet<'static>, Error> {
|
|||
102 => Packet::Handle(Deserializer::once(bytes)?),
|
||||
103 => Packet::Data(Deserializer::once(bytes)?),
|
||||
104 => Packet::Name(Deserializer::once(bytes)?),
|
||||
105 => Packet::Attrs(Deserializer::once(bytes)?),
|
||||
201 => Packet::ExtendedReply(Deserializer::once(bytes)?),
|
||||
_ => return Err(Error::Packet("unknown packet kind")),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{ByteStr, fs::{Attrs, Flags}};
|
||||
|
|
@ -7,15 +9,15 @@ pub struct Open<'a> {
|
|||
pub id: u32,
|
||||
pub path: ByteStr<'a>,
|
||||
pub flags: Flags,
|
||||
pub attrs: Attrs,
|
||||
pub attrs: Cow<'a, Attrs>,
|
||||
}
|
||||
|
||||
impl<'a> Open<'a> {
|
||||
pub fn new<P>(path: P, flags: Flags, attrs: Attrs) -> Self
|
||||
pub fn new<P>(path: P, flags: Flags, attrs: &'a Attrs) -> Self
|
||||
where
|
||||
P: Into<ByteStr<'a>>,
|
||||
{
|
||||
Self { id: 0, path: path.into(), flags, attrs }
|
||||
Self { id: 0, path: path.into(), flags, attrs: Cow::Borrowed(attrs) }
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
|
|
|
|||
|
|
@ -23,12 +23,12 @@ impl<'a> SetStat<'a> {
|
|||
pub struct FSetStat<'a> {
|
||||
pub id: u32,
|
||||
pub handle: Cow<'a, str>,
|
||||
pub attrs: Attrs,
|
||||
pub attrs: Cow<'a, Attrs>,
|
||||
}
|
||||
|
||||
impl<'a> FSetStat<'a> {
|
||||
pub fn new(handle: impl Into<Cow<'a, str>>, attrs: Attrs) -> Self {
|
||||
Self { id: 0, handle: handle.into(), attrs }
|
||||
pub fn new(handle: impl Into<Cow<'a, str>>, attrs: &'a Attrs) -> Self {
|
||||
Self { id: 0, handle: handle.into(), attrs: Cow::Borrowed(attrs) }
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.handle.len() + self.attrs.len() }
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
use std::{any::TypeId, collections::HashMap, io::ErrorKind, sync::Arc};
|
||||
use std::{any::TypeId, collections::HashMap, io::{self, ErrorKind}, sync::Arc};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use russh::{ChannelStream, client::Msg};
|
||||
use serde::Serialize;
|
||||
use tokio::{io::{AsyncReadExt, AsyncWriteExt}, select, sync::{mpsc, oneshot}};
|
||||
use tokio::{io::{AsyncReadExt, AsyncWriteExt, ReadHalf, WriteHalf}, select, sync::{mpsc, oneshot}};
|
||||
|
||||
use crate::{Error, Id, Packet, responses};
|
||||
|
||||
|
|
@ -14,6 +14,10 @@ pub struct Session {
|
|||
pub(super) extensions: Mutex<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl Drop for Session {
|
||||
fn drop(&mut self) { self.tx.send(vec![]).ok(); }
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub(super) fn make(stream: ChannelStream<Msg>) -> Arc<Self> {
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
|
|
@ -24,15 +28,30 @@ impl Session {
|
|||
extensions: Default::default(),
|
||||
});
|
||||
|
||||
async fn read(reader: &mut ReadHalf<ChannelStream<Msg>>) -> io::Result<Vec<u8>> {
|
||||
let len = reader.read_u32().await?;
|
||||
let mut buf = vec![0; len as usize];
|
||||
reader.read_exact(&mut buf).await?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
async fn write(writer: &mut WriteHalf<ChannelStream<Msg>>, buf: Vec<u8>) -> io::Result<()> {
|
||||
if buf.is_empty() {
|
||||
Err(io::Error::new(ErrorKind::BrokenPipe, "channel closed"))
|
||||
} else {
|
||||
writer.write_all(&buf).await
|
||||
}
|
||||
}
|
||||
|
||||
let (mut reader, mut writer) = tokio::io::split(stream);
|
||||
tokio::spawn(async move {
|
||||
while let Some(data) = rx.recv().await {
|
||||
if data.is_empty() {
|
||||
if let Err(e) = write(&mut writer, data).await
|
||||
&& e.kind() == ErrorKind::BrokenPipe
|
||||
{
|
||||
rx.close();
|
||||
writer.shutdown().await.ok();
|
||||
break;
|
||||
} else {
|
||||
writer.write_all(&data).await.ok();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -41,12 +60,16 @@ impl Session {
|
|||
tokio::spawn(async move {
|
||||
loop {
|
||||
select! {
|
||||
Ok(len) = reader.read_u32() => {
|
||||
let mut buf = vec![0; len as usize];
|
||||
if let Err(e) = reader.read_exact(&mut buf).await && e.kind() == ErrorKind::UnexpectedEof {
|
||||
me_.tx.send(vec![]).ok();
|
||||
break;
|
||||
}
|
||||
result = read(&mut reader) => {
|
||||
let buf = match result {
|
||||
Ok(b) => b,
|
||||
Err(e) if e.kind() == ErrorKind::UnexpectedEof => {
|
||||
me_.tx.send(vec![]).ok();
|
||||
break;
|
||||
},
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if let Ok(packet) = crate::from_bytes(&buf)
|
||||
&& let Some(cb) = me_.callback.lock().remove(&packet.id())
|
||||
{
|
||||
|
|
@ -89,4 +112,6 @@ impl Session {
|
|||
|
||||
Ok(rx)
|
||||
}
|
||||
|
||||
pub fn is_closed(&self) -> bool { self.tx.is_closed() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,11 +53,11 @@ impl From<String> for LogLevel {
|
|||
impl AsRef<str> for LogLevel {
|
||||
fn as_ref(&self) -> &str {
|
||||
match self {
|
||||
Self::None => "NONE",
|
||||
Self::Error => "ERROR",
|
||||
Self::Warn => "WARN",
|
||||
Self::Info => "INFO",
|
||||
Self::Debug => "DEBUG",
|
||||
Self::None => "yazi=NONE",
|
||||
Self::Error => "yazi=ERROR",
|
||||
Self::Warn => "yazi=WARN",
|
||||
Self::Info => "yazi=INFO",
|
||||
Self::Debug => "yazi=DEBUG",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,5 +205,5 @@ impl<'a> Loc<'a> {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn bytes(self) -> &'a [u8] { self.inner.as_os_str().as_encoded_bytes() }
|
||||
pub fn bytes(self) -> &'a [u8] { self.inner.as_os_str().as_encoded_bytes() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ impl<'a> Encode<'a> {
|
|||
pub(super) fn new(loc: Loc<'a>, scheme: SchemeRef<'a>) -> Self { Self { loc, scheme } }
|
||||
|
||||
#[inline]
|
||||
fn domain<'s>(s: &'s str) -> PercentEncode<'s> {
|
||||
pub fn domain<'s>(s: &'s str) -> PercentEncode<'s> {
|
||||
const SET: &AsciiSet = &CONTROLS.add(b'/').add(b':');
|
||||
percent_encode(s.as_bytes(), SET)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ 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 }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::path::PathBuf;
|
||||
use std::{io, path::PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -18,12 +18,33 @@ impl TryFrom<&'static Provider> for &'static ProviderSftp {
|
|||
}
|
||||
}
|
||||
|
||||
impl Provider {
|
||||
pub(super) fn reshape(self) -> io::Result<Self> {
|
||||
match self {
|
||||
Self::Sftp(p) => p.reshape().map(Self::Sftp),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- 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>,
|
||||
pub host: String,
|
||||
pub user: String,
|
||||
pub port: u16,
|
||||
pub password: Option<String>,
|
||||
pub key_file: Option<PathBuf>,
|
||||
pub key_passphrase: Option<String>,
|
||||
// FIXME: set default: $SSH_AUTH_SOCK
|
||||
pub identity_agent: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl ProviderSftp {
|
||||
fn reshape(self) -> io::Result<Self> {
|
||||
// FIXME: expand the path
|
||||
// if let Some(key_file) = self.key_file {}
|
||||
// if let Some(identity_agent) = self.identity_agent {}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,14 +40,14 @@ impl Vfs {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn read() -> io::Result<String> {
|
||||
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> {
|
||||
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")))?;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue