From 329c80cbee5bb9e527ffa475bc0df2477f71ed93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20misaki=20masa?= Date: Wed, 26 Nov 2025 17:27:52 +0800 Subject: [PATCH] feat: support Unix path backend for the SFTP provider (#3371) --- Cargo.lock | 2 + yazi-fs/src/file.rs | 2 +- yazi-fs/src/path/expand.rs | 5 +- yazi-sftp/Cargo.toml | 1 + yazi-sftp/src/byte_str.rs | 114 ------------------------- yazi-sftp/src/fs/dir_entry.rs | 40 +++------ yazi-sftp/src/fs/read_dir.rs | 12 +-- yazi-sftp/src/lib.rs | 6 +- yazi-sftp/src/operator.rs | 79 ++++++++--------- yazi-sftp/src/path.rs | 83 ++++++++++++++++++ yazi-sftp/src/requests/extended.rs | 26 +++--- yazi-sftp/src/requests/lstat.rs | 10 +-- yazi-sftp/src/requests/mkdir.rs | 10 +-- yazi-sftp/src/requests/open.rs | 10 +-- yazi-sftp/src/requests/open_dir.rs | 10 +-- yazi-sftp/src/requests/readlink.rs | 10 +-- yazi-sftp/src/requests/realpath.rs | 10 +-- yazi-sftp/src/requests/remove.rs | 10 +-- yazi-sftp/src/requests/rename.rs | 14 +-- yazi-sftp/src/requests/rmdir.rs | 10 +-- yazi-sftp/src/requests/set_stat.rs | 10 +-- yazi-sftp/src/requests/stat.rs | 10 +-- yazi-sftp/src/requests/symlink.rs | 14 +-- yazi-sftp/src/responses/extended.rs | 2 +- yazi-sftp/src/responses/name.rs | 8 +- yazi-sftp/src/wtf.rs | 87 ------------------- yazi-shared/src/loc/able.rs | 62 ++++++++++++++ yazi-shared/src/loc/buf.rs | 16 ++-- yazi-shared/src/loc/loc.rs | 4 +- yazi-shared/src/path/buf.rs | 18 ++++ yazi-shared/src/path/conversion.rs | 8 ++ yazi-shared/src/path/kind.rs | 2 +- yazi-shared/src/path/like.rs | 2 + yazi-shared/src/path/path.rs | 8 ++ yazi-shared/src/path/view.rs | 8 ++ yazi-shared/src/strand/conversion.rs | 8 ++ yazi-shared/src/strand/cow.rs | 7 ++ yazi-shared/src/strand/kind.rs | 2 +- yazi-shared/src/strand/view.rs | 12 +++ yazi-shared/src/url/buf.rs | 9 +- yazi-shared/src/url/components.rs | 2 +- yazi-shared/src/url/cow.rs | 12 +-- yazi-shared/src/url/url.rs | 14 +-- yazi-vfs/Cargo.toml | 1 + yazi-vfs/src/provider/sftp/metadata.rs | 11 ++- yazi-vfs/src/provider/sftp/sftp.rs | 10 +-- 46 files changed, 410 insertions(+), 401 deletions(-) delete mode 100644 yazi-sftp/src/byte_str.rs create mode 100644 yazi-sftp/src/path.rs delete mode 100644 yazi-sftp/src/wtf.rs diff --git a/Cargo.lock b/Cargo.lock index 40b612b2..ec94c648 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5065,6 +5065,7 @@ dependencies = [ "russh", "serde", "tokio", + "typed-path", ] [[package]] @@ -5123,6 +5124,7 @@ dependencies = [ "russh", "tokio", "tracing", + "typed-path", "yazi-config", "yazi-fs", "yazi-macro", diff --git a/yazi-fs/src/file.rs b/yazi-fs/src/file.rs index 17d46159..399ca4c0 100644 --- a/yazi-fs/src/file.rs +++ b/yazi-fs/src/file.rs @@ -60,7 +60,7 @@ impl File { Url::Archive { loc: Loc::saturated(to.as_os().ok()?, kind), domain } } UrlBuf::Sftp { domain, .. } => { - Url::Sftp { loc: Loc::saturated(to.as_os().ok()?, kind), domain } + Url::Sftp { loc: Loc::saturated(to.as_unix().ok()?, kind), domain } } }) } diff --git a/yazi-fs/src/path/expand.rs b/yazi-fs/src/path/expand.rs index f5bde9ff..cec4b1fd 100644 --- a/yazi-fs/src/path/expand.rs +++ b/yazi-fs/src/path/expand.rs @@ -36,7 +36,10 @@ fn expand_url_impl<'a>(url: Url<'a>) -> UrlCow<'a> { Url::Regular(_) => UrlBuf::Regular(loc), Url::Search { domain, .. } => UrlBuf::Search { loc, domain: domain.intern() }, Url::Archive { domain, .. } => UrlBuf::Archive { loc, domain: domain.intern() }, - Url::Sftp { domain, .. } => UrlBuf::Sftp { loc, domain: domain.intern() }, + Url::Sftp { domain, .. } => { + todo!(); + // UrlBuf::Sftp { loc, domain: domain.intern() } + } }; absolute_url(expanded) diff --git a/yazi-sftp/Cargo.toml b/yazi-sftp/Cargo.toml index 20939e46..b9f2606a 100644 --- a/yazi-sftp/Cargo.toml +++ b/yazi-sftp/Cargo.toml @@ -17,3 +17,4 @@ parking_lot = { workspace = true } russh = { workspace = true } serde = { workspace = true } tokio = { workspace = true } +typed-path = { workspace = true } diff --git a/yazi-sftp/src/byte_str.rs b/yazi-sftp/src/byte_str.rs deleted file mode 100644 index 97f2d156..00000000 --- a/yazi-sftp/src/byte_str.rs +++ /dev/null @@ -1,114 +0,0 @@ -use std::{borrow::Cow, ffi::{OsStr, OsString}, ops::Deref, path::{Path, PathBuf}}; - -use serde::{Deserialize, Serialize}; - -use crate::Error; - -#[derive(Debug, Default, Deserialize, Serialize)] -pub struct ByteStr<'a>(Cow<'a, [u8]>); - -impl Deref for ByteStr<'_> { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { &self.0 } -} - -impl<'a> From<&'a str> for ByteStr<'a> { - fn from(value: &'a str) -> Self { ByteStr(Cow::Borrowed(value.as_bytes())) } -} - -impl<'a> From<&'a Self> for ByteStr<'a> { - fn from(value: &'a ByteStr) -> Self { ByteStr(Cow::Borrowed(&value.0)) } -} - -impl PartialEq<&str> for ByteStr<'_> { - fn eq(&self, other: &&str) -> bool { self.0 == other.as_bytes() } -} - -impl<'a> ByteStr<'a> { - pub fn to_os_str(&self) -> Cow<'_, OsStr> { - #[cfg(unix)] - { - use std::os::unix::ffi::OsStrExt; - OsStr::from_bytes(&self.0).into() - } - #[cfg(windows)] - { - super::wtf::bytes_to_wide(self.0.as_ref()) - } - } - - pub fn into_os_string(self) -> OsString { - #[cfg(unix)] - { - use std::os::unix::ffi::OsStringExt; - OsString::from_vec(self.0.into_owned()) - } - #[cfg(windows)] - { - match super::wtf::bytes_to_wide(self.0.as_ref()) { - Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(self.0.into_owned()) }.into(), - Cow::Owned(s) => s, - } - } - } - - pub fn to_path(&self) -> Cow<'_, Path> { - match self.to_os_str() { - Cow::Borrowed(s) => Path::new(s).into(), - Cow::Owned(s) => PathBuf::from(s).into(), - } - } - - pub fn into_path(self) -> PathBuf { - match self.0 { - Cow::Borrowed(_) => self.to_path().into_owned(), - Cow::Owned(_) => self.into_os_string().into(), - } - } - - pub fn join(&self, other: impl Into) -> PathBuf { - let other = other.into(); - match self.to_path() { - Cow::Borrowed(p) => p.join(other.to_path()), - Cow::Owned(mut p) => { - p.push(other.to_path()); - p - } - } - } - - pub fn into_owned(self) -> ByteStr<'static> { ByteStr(Cow::Owned(self.0.into_owned())) } - - pub(super) unsafe fn from_str_bytes_unchecked(bytes: &'a [u8]) -> Self { - Self(Cow::Borrowed(bytes)) - } -} - -// --- Traits -pub trait ToByteStr<'a> { - fn to_byte_str(self) -> Result, Error>; -} - -impl<'a, T> ToByteStr<'a> for &'a T -where - T: AsRef + ?Sized, -{ - fn to_byte_str(self) -> Result, Error> { - #[cfg(unix)] - { - use std::os::unix::ffi::OsStrExt; - Ok(ByteStr(Cow::Borrowed(self.as_ref().as_os_str().as_bytes()))) - } - #[cfg(windows)] - { - super::wtf::wide_to_bytes(self.as_ref().as_os_str()) - .ok_or(Error::custom("failed to convert wide path to bytes")) - .map(ByteStr) - } - } -} - -impl<'a> ToByteStr<'a> for &'a ByteStr<'a> { - fn to_byte_str(self) -> Result, Error> { Ok(ByteStr(Cow::Borrowed(&self.0))) } -} diff --git a/yazi-sftp/src/fs/dir_entry.rs b/yazi-sftp/src/fs/dir_entry.rs index 7ae8cb85..66ea6a02 100644 --- a/yazi-sftp/src/fs/dir_entry.rs +++ b/yazi-sftp/src/fs/dir_entry.rs @@ -1,47 +1,31 @@ -use std::{borrow::Cow, ffi::OsStr, path::PathBuf, sync::Arc}; +use std::sync::Arc; -use crate::{ByteStr, fs::Attrs}; +use typed_path::UnixPathBuf; + +use crate::fs::Attrs; pub struct DirEntry { - pub(super) dir: Arc>, - pub(super) name: ByteStr<'static>, - pub(super) long_name: ByteStr<'static>, + pub(super) dir: Arc, + pub(super) name: Vec, + pub(super) long_name: Vec, pub(super) attrs: Attrs, } impl DirEntry { #[must_use] - pub fn path(&self) -> PathBuf { self.dir.join(&self.name) } + pub fn path(&self) -> UnixPathBuf { self.dir.join(&self.name) } - #[must_use] - pub fn name(&self) -> Cow<'_, OsStr> { self.name.to_os_str() } + pub fn name(&self) -> &[u8] { &self.name } - #[must_use] - pub fn long_name(&self) -> Cow<'_, OsStr> { self.long_name.to_os_str() } + pub fn long_name(&self) -> &[u8] { &self.long_name } pub fn attrs(&self) -> &Attrs { &self.attrs } pub fn nlink(&self) -> Option { str::from_utf8(self.long_name_field(1)?).ok()?.parse().ok() } - pub fn user(&self) -> Option> { - let b = self.long_name_field(2)?; - Some(unsafe { - match ByteStr::from_str_bytes_unchecked(b).to_os_str() { - Cow::Borrowed(_) => OsStr::from_encoded_bytes_unchecked(b).into(), - Cow::Owned(s) => s.into(), - } - }) - } + pub fn user(&self) -> Option<&[u8]> { self.long_name_field(2) } - pub fn group(&self) -> Option> { - let b = self.long_name_field(3)?; - Some(unsafe { - match ByteStr::from_str_bytes_unchecked(b).to_os_str() { - Cow::Borrowed(_) => OsStr::from_encoded_bytes_unchecked(b).into(), - Cow::Owned(s) => s.into(), - } - }) - } + pub fn group(&self) -> Option<&[u8]> { self.long_name_field(3) } fn long_name_field(&self, n: usize) -> Option<&[u8]> { self.long_name.split(|b| b.is_ascii_whitespace()).filter(|s| !s.is_empty()).nth(n) diff --git a/yazi-sftp/src/fs/read_dir.rs b/yazi-sftp/src/fs/read_dir.rs index 8386f428..22e0a354 100644 --- a/yazi-sftp/src/fs/read_dir.rs +++ b/yazi-sftp/src/fs/read_dir.rs @@ -1,10 +1,10 @@ use std::{mem, sync::Arc}; -use crate::{ByteStr, Error, Session, fs::DirEntry, requests, responses}; +use crate::{Error, Session, SftpPath, fs::DirEntry, requests, responses}; pub struct ReadDir { session: Arc, - dir: Arc>, + dir: Arc, handle: String, name: responses::Name<'static>, @@ -13,7 +13,7 @@ pub struct ReadDir { } impl ReadDir { - pub(crate) fn new(session: &Arc, dir: ByteStr, handle: String) -> Self { + pub(crate) fn new(session: &Arc, dir: SftpPath, handle: String) -> Self { Self { session: session.clone(), dir: Arc::new(dir.into_owned()), @@ -33,11 +33,11 @@ impl ReadDir { }; self.cursor += 1; - if item.name != "." && item.name != ".." { + if &*item.name != b"." && &*item.name != b".." { return Ok(Some(DirEntry { dir: self.dir.clone(), - name: item.name, - long_name: item.long_name, + name: item.name.into_owned(), + long_name: item.long_name.into_owned(), attrs: item.attrs, })); } diff --git a/yazi-sftp/src/lib.rs b/yazi-sftp/src/lib.rs index e7e7b9d2..9a0e87cc 100644 --- a/yazi-sftp/src/lib.rs +++ b/yazi-sftp/src/lib.rs @@ -4,23 +4,21 @@ pub mod fs; pub mod requests; pub mod responses; -mod byte_str; mod de; mod error; mod id; mod macros; mod operator; mod packet; +mod path; mod ser; mod session; -#[cfg(windows)] -mod wtf; -pub use byte_str::*; pub(crate) use de::*; pub use error::*; pub(crate) use id::*; pub use operator::*; pub use packet::*; +pub use path::*; pub(crate) use ser::*; pub use session::*; diff --git a/yazi-sftp/src/operator.rs b/yazi-sftp/src/operator.rs index 1620d4f4..b28cb181 100644 --- a/yazi-sftp/src/operator.rs +++ b/yazi-sftp/src/operator.rs @@ -1,9 +1,10 @@ -use std::{ops::Deref, path::PathBuf, sync::Arc}; +use std::{ops::Deref, sync::Arc}; use russh::{ChannelStream, client::Msg}; use tokio::sync::oneshot; +use typed_path::UnixPathBuf; -use crate::{ByteStr, Error, Packet, Session, ToByteStr, fs::{Attrs, File, Flags, ReadDir}, requests, responses}; +use crate::{AsSftpPath, Error, Packet, Session, SftpPath, fs::{Attrs, File, Flags, ReadDir}, requests, responses}; pub struct Operator(Arc); @@ -28,9 +29,9 @@ impl Operator { pub async fn open<'a, P>(&self, path: P, flags: Flags, attrs: &'a Attrs) -> Result where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - let handle: responses::Handle = self.send(requests::Open::new(path, flags, attrs)?).await?; + let handle: responses::Handle = self.send(requests::Open::new(path, flags, attrs)).await?; Ok(File::new(&self.0, handle.handle)) } @@ -59,9 +60,9 @@ impl Operator { pub async fn lstat<'a, P>(&self, path: P) -> Result where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - let attrs: responses::Attrs = self.send(requests::Lstat::new(path)?).await?; + let attrs: responses::Attrs = self.send(requests::Lstat::new(path)).await?; Ok(attrs.attrs) } @@ -72,9 +73,9 @@ impl Operator { pub async fn setstat<'a, P>(&self, path: P, attrs: Attrs) -> Result<(), Error> where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - let status: responses::Status = self.send(requests::SetStat::new(path, attrs)?).await?; + let status: responses::Status = self.send(requests::SetStat::new(path, attrs)).await?; status.into() } @@ -85,100 +86,100 @@ impl Operator { pub async fn read_dir<'a, P>(&'a self, dir: P) -> Result where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - let dir: ByteStr = dir.to_byte_str()?; - let handle: responses::Handle = self.send(requests::OpenDir::new(&dir)?).await?; + let dir: SftpPath = dir.as_sftp_path(); + let handle: responses::Handle = self.send(requests::OpenDir::new(&dir)).await?; Ok(ReadDir::new(&self.0, dir, handle.handle)) } pub async fn remove<'a, P>(&self, path: P) -> Result<(), Error> where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - let status: responses::Status = self.send(requests::Remove::new(path)?).await?; + let status: responses::Status = self.send(requests::Remove::new(path)).await?; status.into() } pub async fn mkdir<'a, P>(&self, path: P, attrs: Attrs) -> Result<(), Error> where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - let status: responses::Status = self.send(requests::Mkdir::new(path, attrs)?).await?; + let status: responses::Status = self.send(requests::Mkdir::new(path, attrs)).await?; status.into() } pub async fn rmdir<'a, P>(&self, path: P) -> Result<(), Error> where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - let status: responses::Status = self.send(requests::Rmdir::new(path)?).await?; + let status: responses::Status = self.send(requests::Rmdir::new(path)).await?; status.into() } - pub async fn realpath<'a, P>(&self, path: P) -> Result + pub async fn realpath<'a, P>(&self, path: P) -> Result where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - let mut name: responses::Name = self.send(requests::Realpath::new(path)?).await?; + let mut name: responses::Name = self.send(requests::Realpath::new(path)).await?; if name.items.is_empty() { Err(Error::custom("realpath returned no names")) } else { - Ok(name.items.swap_remove(0).name.into_path()) + Ok(name.items.swap_remove(0).name.into_owned().into()) } } pub async fn stat<'a, P>(&self, path: P) -> Result where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - let attrs: responses::Attrs = self.send(requests::Stat::new(path)?).await?; + let attrs: responses::Attrs = self.send(requests::Stat::new(path)).await?; Ok(attrs.attrs) } pub async fn rename<'a, F, T>(&self, from: F, to: T) -> Result<(), Error> where - F: ToByteStr<'a>, - T: ToByteStr<'a>, + F: AsSftpPath<'a>, + T: AsSftpPath<'a>, { - let status: responses::Status = self.send(requests::Rename::new(from, to)?).await?; + let status: responses::Status = self.send(requests::Rename::new(from, to)).await?; status.into() } pub async fn rename_posix<'a, F, T>(&self, from: F, to: T) -> Result<(), Error> where - F: ToByteStr<'a>, - T: ToByteStr<'a>, + F: AsSftpPath<'a>, + T: AsSftpPath<'a>, { if self.extensions.lock().get("posix-rename@openssh.com").is_none_or(|s| s != "1") { return Err(Error::Unsupported); } - let data = requests::ExtendedRename::new(from, to)?; + let data = requests::ExtendedRename::new(from, to); let status: responses::Status = self.send(requests::Extended::new("posix-rename@openssh.com", data)).await?; status.into() } - pub async fn readlink<'a, P>(&self, path: P) -> Result + pub async fn readlink<'a, P>(&self, path: P) -> Result where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - let mut name: responses::Name = self.send(requests::Readlink::new(path)?).await?; + let mut name: responses::Name = self.send(requests::Readlink::new(path)).await?; if name.items.is_empty() { Err(Error::custom("readlink returned no names")) } else { - Ok(name.items.swap_remove(0).name.into_path()) + Ok(name.items.swap_remove(0).name.into_owned().into()) } } pub async fn symlink<'a, L, O>(&self, original: O, link: L) -> Result<(), Error> where - O: ToByteStr<'a>, - L: ToByteStr<'a>, + O: AsSftpPath<'a>, + L: AsSftpPath<'a>, { - let status: responses::Status = self.send(requests::Symlink::new(original, link)?).await?; + let status: responses::Status = self.send(requests::Symlink::new(original, link)).await?; status.into() } @@ -193,14 +194,14 @@ impl Operator { pub async fn hardlink<'a, O, L>(&self, original: O, link: L) -> Result<(), Error> where - O: ToByteStr<'a>, - L: ToByteStr<'a>, + O: AsSftpPath<'a>, + L: AsSftpPath<'a>, { if self.extensions.lock().get("hardlink@openssh.com").is_none_or(|s| s != "1") { return Err(Error::Unsupported); } - let data = requests::ExtendedHardlink::new(original, link)?; + let data = requests::ExtendedHardlink::new(original, link); let status: responses::Status = self.send(requests::Extended::new("hardlink@openssh.com", data)).await?; status.into() diff --git a/yazi-sftp/src/path.rs b/yazi-sftp/src/path.rs new file mode 100644 index 00000000..39f20d7c --- /dev/null +++ b/yazi-sftp/src/path.rs @@ -0,0 +1,83 @@ +use std::{borrow::Cow, ops::Deref}; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug)] +pub enum SftpPath<'a> { + Borrowed(&'a typed_path::UnixPath), + Owned(typed_path::UnixPathBuf), +} + +impl Deref for SftpPath<'_> { + type Target = typed_path::UnixPath; + + fn deref(&self) -> &Self::Target { + match self { + SftpPath::Borrowed(p) => p, + SftpPath::Owned(p) => p.as_path(), + } + } +} + +impl Default for SftpPath<'_> { + fn default() -> Self { SftpPath::Borrowed(typed_path::UnixPath::new("")) } +} + +impl<'a> From<&'a Self> for SftpPath<'a> { + fn from(value: &'a SftpPath) -> Self { SftpPath::Borrowed(value) } +} + +impl<'a> From> for SftpPath<'a> { + fn from(value: Cow<'a, [u8]>) -> Self { + match value { + Cow::Borrowed(b) => Self::Borrowed(typed_path::UnixPath::new(b)), + Cow::Owned(b) => SftpPath::Owned(typed_path::UnixPathBuf::from(b)), + } + } +} + +impl Serialize for SftpPath<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_bytes(self.as_bytes()) + } +} + +impl<'de> Deserialize<'de> for SftpPath<'_> { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let cow = >::deserialize(deserializer)?; + Ok(Self::Owned(cow.into_owned().into())) + } +} + +impl<'a> SftpPath<'a> { + pub fn len(&self) -> usize { self.as_bytes().len() } + + pub fn into_owned(self) -> typed_path::UnixPathBuf { + match self { + SftpPath::Borrowed(p) => p.to_owned(), + SftpPath::Owned(p) => p, + } + } +} + +// --- Traits +pub trait AsSftpPath<'a> { + fn as_sftp_path(self) -> SftpPath<'a>; +} + +impl<'a, T> AsSftpPath<'a> for &'a T +where + T: ?Sized + AsRef, +{ + fn as_sftp_path(self) -> SftpPath<'a> { SftpPath::Borrowed(self.as_ref()) } +} + +impl<'a> AsSftpPath<'a> for &'a SftpPath<'a> { + fn as_sftp_path(self) -> SftpPath<'a> { SftpPath::Borrowed(self) } +} diff --git a/yazi-sftp/src/requests/extended.rs b/yazi-sftp/src/requests/extended.rs index 7b310b03..de29a665 100644 --- a/yazi-sftp/src/requests/extended.rs +++ b/yazi-sftp/src/requests/extended.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, fmt::Debug}; use serde::{Deserialize, Serialize}; -use crate::{ByteStr, Error, ToByteStr}; +use crate::{AsSftpPath, SftpPath}; #[derive(Debug, Deserialize, Serialize)] pub struct Extended<'a, D> { @@ -30,17 +30,17 @@ pub trait ExtendedData: Debug + Serialize + for<'de> Deserialize<'de> { // --- POSIX Rename #[derive(Debug, Deserialize, Serialize)] pub struct ExtendedRename<'a> { - pub from: ByteStr<'a>, - pub to: ByteStr<'a>, + pub from: SftpPath<'a>, + pub to: SftpPath<'a>, } impl<'a> ExtendedRename<'a> { - pub fn new(from: F, to: T) -> Result + pub fn new(from: F, to: T) -> Self where - F: ToByteStr<'a>, - T: ToByteStr<'a>, + F: AsSftpPath<'a>, + T: AsSftpPath<'a>, { - Ok(Self { from: from.to_byte_str()?, to: to.to_byte_str()? }) + Self { from: from.as_sftp_path(), to: to.as_sftp_path() } } } @@ -65,17 +65,17 @@ impl ExtendedData for ExtendedFsync<'_> { // --- Hardlink #[derive(Debug, Deserialize, Serialize)] pub struct ExtendedHardlink<'a> { - pub original: ByteStr<'a>, - pub link: ByteStr<'a>, + pub original: SftpPath<'a>, + pub link: SftpPath<'a>, } impl<'a> ExtendedHardlink<'a> { - pub fn new(original: O, link: L) -> Result + pub fn new(original: O, link: L) -> Self where - O: ToByteStr<'a>, - L: ToByteStr<'a>, + O: AsSftpPath<'a>, + L: AsSftpPath<'a>, { - Ok(Self { original: original.to_byte_str()?, link: link.to_byte_str()? }) + Self { original: original.as_sftp_path(), link: link.as_sftp_path() } } } diff --git a/yazi-sftp/src/requests/lstat.rs b/yazi-sftp/src/requests/lstat.rs index 55f87891..ae6539f7 100644 --- a/yazi-sftp/src/requests/lstat.rs +++ b/yazi-sftp/src/requests/lstat.rs @@ -1,19 +1,19 @@ use serde::{Deserialize, Serialize}; -use crate::{ByteStr, Error, ToByteStr}; +use crate::{AsSftpPath, SftpPath}; #[derive(Debug, Deserialize, Serialize)] pub struct Lstat<'a> { pub id: u32, - pub path: ByteStr<'a>, + pub path: SftpPath<'a>, } impl Lstat<'_> { - pub fn new<'a, P>(path: P) -> Result, Error> + pub fn new<'a, P>(path: P) -> Lstat<'a> where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - Ok(Lstat { id: 0, path: path.to_byte_str()? }) + Lstat { id: 0, path: path.as_sftp_path() } } pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() } diff --git a/yazi-sftp/src/requests/mkdir.rs b/yazi-sftp/src/requests/mkdir.rs index 4d37563e..0dd8de51 100644 --- a/yazi-sftp/src/requests/mkdir.rs +++ b/yazi-sftp/src/requests/mkdir.rs @@ -1,20 +1,20 @@ use serde::{Deserialize, Serialize}; -use crate::{ByteStr, Error, ToByteStr, fs::Attrs}; +use crate::{AsSftpPath, SftpPath, fs::Attrs}; #[derive(Debug, Deserialize, Serialize)] pub struct Mkdir<'a> { pub id: u32, - pub path: ByteStr<'a>, + pub path: SftpPath<'a>, pub attrs: Attrs, } impl<'a> Mkdir<'a> { - pub fn new

(path: P, attrs: Attrs) -> Result + pub fn new

(path: P, attrs: Attrs) -> Self where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - Ok(Self { id: 0, path: path.to_byte_str()?, attrs }) + Self { id: 0, path: path.as_sftp_path(), attrs } } pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() + self.attrs.len() } diff --git a/yazi-sftp/src/requests/open.rs b/yazi-sftp/src/requests/open.rs index 22f22861..55b2eacd 100644 --- a/yazi-sftp/src/requests/open.rs +++ b/yazi-sftp/src/requests/open.rs @@ -2,22 +2,22 @@ use std::borrow::Cow; use serde::{Deserialize, Serialize}; -use crate::{ByteStr, Error, ToByteStr, fs::{Attrs, Flags}}; +use crate::{AsSftpPath, SftpPath, fs::{Attrs, Flags}}; #[derive(Debug, Deserialize, Serialize)] pub struct Open<'a> { pub id: u32, - pub path: ByteStr<'a>, + pub path: SftpPath<'a>, pub flags: Flags, pub attrs: Cow<'a, Attrs>, } impl<'a> Open<'a> { - pub fn new

(path: P, flags: Flags, attrs: &'a Attrs) -> Result + pub fn new

(path: P, flags: Flags, attrs: &'a Attrs) -> Self where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - Ok(Self { id: 0, path: path.to_byte_str()?, flags, attrs: Cow::Borrowed(attrs) }) + Self { id: 0, path: path.as_sftp_path(), flags, attrs: Cow::Borrowed(attrs) } } pub fn len(&self) -> usize { diff --git a/yazi-sftp/src/requests/open_dir.rs b/yazi-sftp/src/requests/open_dir.rs index 3d29b54f..71eaf8a8 100644 --- a/yazi-sftp/src/requests/open_dir.rs +++ b/yazi-sftp/src/requests/open_dir.rs @@ -1,19 +1,19 @@ use serde::{Deserialize, Serialize}; -use crate::{ByteStr, Error, ToByteStr}; +use crate::{AsSftpPath, SftpPath}; #[derive(Debug, Deserialize, Serialize)] pub struct OpenDir<'a> { pub id: u32, - pub path: ByteStr<'a>, + pub path: SftpPath<'a>, } impl<'a> OpenDir<'a> { - pub fn new

(path: P) -> Result + pub fn new

(path: P) -> Self where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - Ok(Self { id: 0, path: path.to_byte_str()? }) + Self { id: 0, path: path.as_sftp_path() } } pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() } diff --git a/yazi-sftp/src/requests/readlink.rs b/yazi-sftp/src/requests/readlink.rs index 9f9ed259..dfdc34f2 100644 --- a/yazi-sftp/src/requests/readlink.rs +++ b/yazi-sftp/src/requests/readlink.rs @@ -1,19 +1,19 @@ use serde::{Deserialize, Serialize}; -use crate::{ByteStr, Error, ToByteStr}; +use crate::{AsSftpPath, SftpPath}; #[derive(Debug, Deserialize, Serialize)] pub struct Readlink<'a> { pub id: u32, - pub path: ByteStr<'a>, + pub path: SftpPath<'a>, } impl<'a> Readlink<'a> { - pub fn new

(path: P) -> Result + pub fn new

(path: P) -> Self where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - Ok(Self { id: 0, path: path.to_byte_str()? }) + Self { id: 0, path: path.as_sftp_path() } } pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() } diff --git a/yazi-sftp/src/requests/realpath.rs b/yazi-sftp/src/requests/realpath.rs index bad53344..05760f98 100644 --- a/yazi-sftp/src/requests/realpath.rs +++ b/yazi-sftp/src/requests/realpath.rs @@ -1,19 +1,19 @@ use serde::{Deserialize, Serialize}; -use crate::{ByteStr, Error, ToByteStr}; +use crate::{AsSftpPath, SftpPath}; #[derive(Debug, Deserialize, Serialize)] pub struct Realpath<'a> { pub id: u32, - pub path: ByteStr<'a>, + pub path: SftpPath<'a>, } impl<'a> Realpath<'a> { - pub fn new

(path: P) -> Result + pub fn new

(path: P) -> Self where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - Ok(Self { id: 0, path: path.to_byte_str()? }) + Self { id: 0, path: path.as_sftp_path() } } pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() } diff --git a/yazi-sftp/src/requests/remove.rs b/yazi-sftp/src/requests/remove.rs index db9ea2d5..ffc7122f 100644 --- a/yazi-sftp/src/requests/remove.rs +++ b/yazi-sftp/src/requests/remove.rs @@ -1,19 +1,19 @@ use serde::{Deserialize, Serialize}; -use crate::{ByteStr, Error, ToByteStr}; +use crate::{AsSftpPath, SftpPath}; #[derive(Debug, Deserialize, Serialize)] pub struct Remove<'a> { pub id: u32, - pub path: ByteStr<'a>, + pub path: SftpPath<'a>, } impl<'a> Remove<'a> { - pub fn new

(path: P) -> Result + pub fn new

(path: P) -> Self where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - Ok(Self { id: 0, path: path.to_byte_str()? }) + Self { id: 0, path: path.as_sftp_path() } } pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() } diff --git a/yazi-sftp/src/requests/rename.rs b/yazi-sftp/src/requests/rename.rs index 749495f0..9b734630 100644 --- a/yazi-sftp/src/requests/rename.rs +++ b/yazi-sftp/src/requests/rename.rs @@ -1,21 +1,21 @@ use serde::{Deserialize, Serialize}; -use crate::{ByteStr, Error, ToByteStr}; +use crate::{AsSftpPath, SftpPath}; #[derive(Debug, Deserialize, Serialize)] pub struct Rename<'a> { pub id: u32, - pub from: ByteStr<'a>, - pub to: ByteStr<'a>, + pub from: SftpPath<'a>, + pub to: SftpPath<'a>, } impl<'a> Rename<'a> { - pub fn new(from: F, to: T) -> Result + pub fn new(from: F, to: T) -> Self where - F: ToByteStr<'a>, - T: ToByteStr<'a>, + F: AsSftpPath<'a>, + T: AsSftpPath<'a>, { - Ok(Self { id: 0, from: from.to_byte_str()?, to: to.to_byte_str()? }) + Self { id: 0, from: from.as_sftp_path(), to: to.as_sftp_path() } } pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.from.len() + 4 + self.to.len() } diff --git a/yazi-sftp/src/requests/rmdir.rs b/yazi-sftp/src/requests/rmdir.rs index 3d9a26c6..13fb990b 100644 --- a/yazi-sftp/src/requests/rmdir.rs +++ b/yazi-sftp/src/requests/rmdir.rs @@ -1,19 +1,19 @@ use serde::{Deserialize, Serialize}; -use crate::{ByteStr, Error, ToByteStr}; +use crate::{AsSftpPath, SftpPath}; #[derive(Debug, Deserialize, Serialize)] pub struct Rmdir<'a> { pub id: u32, - pub path: ByteStr<'a>, + pub path: SftpPath<'a>, } impl<'a> Rmdir<'a> { - pub fn new

(path: P) -> Result + pub fn new

(path: P) -> Self where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - Ok(Self { id: 0, path: path.to_byte_str()? }) + Self { id: 0, path: path.as_sftp_path() } } pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() } diff --git a/yazi-sftp/src/requests/set_stat.rs b/yazi-sftp/src/requests/set_stat.rs index 4e618489..9734f3fc 100644 --- a/yazi-sftp/src/requests/set_stat.rs +++ b/yazi-sftp/src/requests/set_stat.rs @@ -2,21 +2,21 @@ use std::borrow::Cow; use serde::{Deserialize, Serialize}; -use crate::{ByteStr, Error, ToByteStr, fs::Attrs}; +use crate::{AsSftpPath, SftpPath, fs::Attrs}; #[derive(Debug, Deserialize, Serialize)] pub struct SetStat<'a> { pub id: u32, - pub path: ByteStr<'a>, + pub path: SftpPath<'a>, pub attrs: Attrs, } impl<'a> SetStat<'a> { - pub fn new

(path: P, attrs: Attrs) -> Result + pub fn new

(path: P, attrs: Attrs) -> Self where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - Ok(Self { id: 0, path: path.to_byte_str()?, attrs }) + Self { id: 0, path: path.as_sftp_path(), attrs } } pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() + self.attrs.len() } diff --git a/yazi-sftp/src/requests/stat.rs b/yazi-sftp/src/requests/stat.rs index 58addd47..dfbcdf04 100644 --- a/yazi-sftp/src/requests/stat.rs +++ b/yazi-sftp/src/requests/stat.rs @@ -1,19 +1,19 @@ use serde::{Deserialize, Serialize}; -use crate::{ByteStr, Error, ToByteStr}; +use crate::{AsSftpPath, SftpPath}; #[derive(Debug, Deserialize, Serialize)] pub struct Stat<'a> { pub id: u32, - pub path: ByteStr<'a>, + pub path: SftpPath<'a>, } impl<'a> Stat<'a> { - pub fn new

(path: P) -> Result + pub fn new

(path: P) -> Self where - P: ToByteStr<'a>, + P: AsSftpPath<'a>, { - Ok(Self { id: 0, path: path.to_byte_str()? }) + Self { id: 0, path: path.as_sftp_path() } } pub fn len(&self) -> usize { size_of_val(&self.id) + 4 + self.path.len() } diff --git a/yazi-sftp/src/requests/symlink.rs b/yazi-sftp/src/requests/symlink.rs index 4f88ce2a..7dffd335 100644 --- a/yazi-sftp/src/requests/symlink.rs +++ b/yazi-sftp/src/requests/symlink.rs @@ -1,21 +1,21 @@ use serde::{Deserialize, Serialize}; -use crate::{ByteStr, Error, ToByteStr}; +use crate::{AsSftpPath, SftpPath}; #[derive(Debug, Deserialize, Serialize)] pub struct Symlink<'a> { pub id: u32, - pub link: ByteStr<'a>, - pub original: ByteStr<'a>, + pub link: SftpPath<'a>, + pub original: SftpPath<'a>, } impl<'a> Symlink<'a> { - pub fn new(link: L, original: O) -> Result + pub fn new(link: L, original: O) -> Self where - L: ToByteStr<'a>, - O: ToByteStr<'a>, + L: AsSftpPath<'a>, + O: AsSftpPath<'a>, { - Ok(Self { id: 0, link: link.to_byte_str()?, original: original.to_byte_str()? }) + Self { id: 0, link: link.as_sftp_path(), original: original.as_sftp_path() } } pub fn len(&self) -> usize { diff --git a/yazi-sftp/src/responses/extended.rs b/yazi-sftp/src/responses/extended.rs index 8be4fafc..6f772b14 100644 --- a/yazi-sftp/src/responses/extended.rs +++ b/yazi-sftp/src/responses/extended.rs @@ -30,7 +30,7 @@ impl Serialize for ExtendedData<'_> { S: serde::Serializer, { let mut seq = serializer.serialize_seq(None)?; - for b in self.0.as_ref() { + for b in &*self.0 { seq.serialize_element(b)?; } seq.end() diff --git a/yazi-sftp/src/responses/name.rs b/yazi-sftp/src/responses/name.rs index 6126cd22..6982e02d 100644 --- a/yazi-sftp/src/responses/name.rs +++ b/yazi-sftp/src/responses/name.rs @@ -1,6 +1,8 @@ +use std::borrow::Cow; + use serde::{Deserialize, Serialize}; -use crate::{ByteStr, fs::Attrs}; +use crate::fs::Attrs; #[derive(Debug, Default, Deserialize, Serialize)] pub struct Name<'a> { @@ -10,8 +12,8 @@ pub struct Name<'a> { #[derive(Debug, Default, Deserialize, Serialize)] pub struct NameItem<'a> { - pub name: ByteStr<'a>, - pub long_name: ByteStr<'a>, + pub name: Cow<'a, [u8]>, + pub long_name: Cow<'a, [u8]>, pub attrs: Attrs, } diff --git a/yazi-sftp/src/wtf.rs b/yazi-sftp/src/wtf.rs deleted file mode 100644 index bc097032..00000000 --- a/yazi-sftp/src/wtf.rs +++ /dev/null @@ -1,87 +0,0 @@ -use std::{borrow::Cow, ffi::{OsStr, OsString}, os::windows::ffi::{OsStrExt, OsStringExt}}; - -pub(super) fn bytes_to_wide(mut bytes: &[u8]) -> Cow<'_, OsStr> { - let mut wide: Option> = None; - while !bytes.is_empty() { - match (str::from_utf8(bytes), &mut wide) { - (Ok(valid), None) => { - return OsStr::new(valid).into(); - } - (Ok(valid), Some(wide)) => { - for ch in valid.chars() { - wide.extend(ch.encode_utf16(&mut [0u16; 2]).iter()); - } - break; - } - (Err(err), _) => { - let wide = wide.get_or_insert_with(|| Vec::with_capacity(bytes.len())); - - let valid = unsafe { str::from_utf8_unchecked(&bytes[..err.valid_up_to()]) }; - for c in valid.chars() { - wide.extend(c.encode_utf16(&mut [0u16; 2]).iter()); - } - bytes = &bytes[valid.len()..]; - - let invalid = err.error_len().unwrap_or(bytes.len()); - for &b in &bytes[..invalid] { - wide.push(0xdc00 + b as u16); - } - bytes = &bytes[invalid..]; - } - } - } - OsString::from_wide(&wide.unwrap_or_default()).into() -} - -pub(super) fn wide_to_bytes(wide: &OsStr) -> Option> { - if let Some(s) = wide.to_str() { - return Some(s.as_bytes().into()); - } - - let mut it = wide.encode_wide(); - let mut out = Vec::with_capacity(wide.len()); - - while let Some(w) = it.next() { - if (0xdc00..=0xdcff).contains(&w) { - out.push((w - 0xdc00) as u8); - } else if (0xd800..=0xdbff).contains(&w) { - let x = it.next().filter(|x| (0xdc00..=0xdfff).contains(x))?; - let c = char::from_u32(0x10000 + (((w as u32 - 0xd800) << 10) | (x as u32 - 0xdc00)))?; - out.extend_from_slice(c.encode_utf8(&mut [0u8; 4]).as_bytes()); - } else if (0xdc00..=0xdfff).contains(&w) { - return None; - } else { - let c = char::from_u32(w as u32)?; - out.extend_from_slice(c.encode_utf8(&mut [0u8; 4]).as_bytes()); - } - } - - Some(out.into()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn wtf8_roundtrip() { - let b = &[ - b'\0', // NUL - 0xff, // 0xFF - b'a', b'b', b'c', // abc - 0xf0, 0x9f, 0x98, 0x80, // 😀 - 0xc3, 0x28, // illegal UTF-8 - ]; - assert_eq!(&*wide_to_bytes(&bytes_to_wide(b)).unwrap(), b); - } - - #[test] - #[cfg(windows)] - fn low_surrogates_for_non_utf8() { - use std::os::windows::ffi::OsStrExt; - - let os = bytes_to_wide(b"\xFF"); - let wide: Vec = os.encode_wide().collect(); - assert_eq!(wide, vec![0xdc00 + 0xff]); - } -} diff --git a/yazi-shared/src/loc/able.rs b/yazi-shared/src/loc/able.rs index 4b5b2cd2..2421fdec 100644 --- a/yazi-shared/src/loc/able.rs +++ b/yazi-shared/src/loc/able.rs @@ -18,6 +18,12 @@ impl<'p> LocAble<'p> for &'p std::path::Path { type Strand<'a> = &'a OsStr; } +impl<'p> LocAble<'p> for &'p typed_path::UnixPath { + type Components<'a> = typed_path::UnixComponents<'a>; + type Owned = typed_path::UnixPathBuf; + type Strand<'a> = &'a [u8]; +} + // --- LocBufAble pub trait LocBufAble where @@ -36,11 +42,18 @@ impl LocBufAble for std::path::PathBuf { type Strand<'a> = &'a OsStr; } +impl LocBufAble for typed_path::UnixPathBuf { + type Borrowed<'a> = &'a typed_path::UnixPath; + type Strand<'a> = &'a [u8]; +} + // --- StrandAble pub trait StrandAble<'a>: Copy {} impl<'a> StrandAble<'a> for &'a OsStr {} +impl<'a> StrandAble<'a> for &'a [u8] {} + // --- LocAbleImpl pub(super) trait LocAbleImpl<'p>: LocAble<'p> { fn as_encoded_bytes(self) -> &'p [u8]; @@ -96,6 +109,36 @@ impl<'p> LocAbleImpl<'p> for &'p std::path::Path { fn to_path_buf(self) -> Self::Owned { self.to_path_buf() } } +impl<'p> LocAbleImpl<'p> for &'p typed_path::UnixPath { + fn as_encoded_bytes(self) -> &'p [u8] { self.as_bytes() } + + fn components(self) -> Self::Components<'p> { self.components() } + + fn empty() -> Self { typed_path::UnixPath::new("") } + + fn file_name(self) -> Option> { self.file_name() } + + unsafe fn from_encoded_bytes_unchecked(bytes: &'p [u8]) -> Self { + typed_path::UnixPath::new(bytes) + } + + fn join<'a, T>(self, path: T) -> Self::Owned + where + T: AsStrandView<'a, Self::Strand<'a>>, + { + self.join(path.as_strand_view()) + } + + fn strip_prefix<'a, T>(self, base: T) -> Option + where + T: AsStrandView<'a, Self::Strand<'a>>, + { + self.strip_prefix(base.as_strand_view()).ok() + } + + fn to_path_buf(self) -> Self::Owned { self.to_path_buf() } +} + // --- LocBufAbleImpl pub(super) trait LocBufAbleImpl: LocBufAble { fn as_encoded_bytes(&self) -> &[u8] { self.borrow().as_encoded_bytes() } @@ -130,6 +173,21 @@ impl LocBufAbleImpl for std::path::PathBuf { } } +impl LocBufAbleImpl for typed_path::UnixPathBuf { + fn borrow(&self) -> Self::Borrowed<'_> { self.as_path() } + + unsafe fn from_encoded_bytes_unchecked(bytes: Vec) -> Self { bytes.into() } + + fn into_encoded_bytes(self) -> Vec { self.into_vec() } + + fn set_file_name<'a, T>(&mut self, name: T) + where + T: AsStrandView<'a, Self::Strand<'a>>, + { + self.set_file_name(name.as_strand_view()) + } +} + // --- StrandAbleImpl pub(super) trait StrandAbleImpl<'a>: StrandAble<'a> { fn as_encoded_bytes(self) -> &'a [u8]; @@ -140,3 +198,7 @@ pub(super) trait StrandAbleImpl<'a>: StrandAble<'a> { impl<'a> StrandAbleImpl<'a> for &'a OsStr { fn as_encoded_bytes(self) -> &'a [u8] { self.as_encoded_bytes() } } + +impl<'a> StrandAbleImpl<'a> for &'a [u8] { + fn as_encoded_bytes(self) -> &'a [u8] { self } +} diff --git a/yazi-shared/src/loc/buf.rs b/yazi-shared/src/loc/buf.rs index 171f9247..029866a2 100644 --- a/yazi-shared/src/loc/buf.rs +++ b/yazi-shared/src/loc/buf.rs @@ -1,11 +1,11 @@ -use std::{cmp, ffi::OsStr, fmt::{self, Debug, Formatter}, hash::{Hash, Hasher}, marker::PhantomData, mem, ops::Deref, path::PathBuf}; +use std::{cmp, ffi::OsStr, fmt::{self, Debug, Formatter}, hash::{Hash, Hasher}, marker::PhantomData, mem, ops::Deref}; use anyhow::Result; use crate::{loc::{Loc, LocAble, LocAbleImpl, LocBufAble, LocBufAbleImpl}, path::{AsPath, AsPathView, PathDyn, SetNameError}, scheme::SchemeKind, strand::AsStrandView}; #[derive(Clone, Default, Eq, PartialEq)] -pub struct LocBuf

{ +pub struct LocBuf

{ pub(super) inner: P, pub(super) uri: usize, pub(super) urn: usize, @@ -21,7 +21,7 @@ where } // FIXME: remove -impl AsRef for LocBuf { +impl AsRef for LocBuf { fn as_ref(&self) -> &std::path::Path { self.inner.as_ref() } } @@ -93,8 +93,8 @@ where } } -impl> From<&T> for LocBuf { - fn from(value: &T) -> Self { Self::from(PathBuf::from(value)) } +impl> From<&T> for LocBuf { + fn from(value: &T) -> Self { Self::from(std::path::PathBuf::from(value)) } } impl

LocBuf

@@ -249,13 +249,13 @@ where pub fn has_trail(&self) -> bool { self.as_loc().has_trail() } } -impl LocBuf { - pub const fn empty() -> Self { Self { inner: PathBuf::new(), uri: 0, urn: 0 } } +impl LocBuf { + pub const fn empty() -> Self { Self { inner: std::path::PathBuf::new(), uri: 0, urn: 0 } } } #[cfg(test)] mod tests { - use std::path::Path; + use std::path::{Path, PathBuf}; use super::*; use crate::url::{UrlBuf, UrlLike}; diff --git a/yazi-shared/src/loc/loc.rs b/yazi-shared/src/loc/loc.rs index 182284d7..6d9c27c6 100644 --- a/yazi-shared/src/loc/loc.rs +++ b/yazi-shared/src/loc/loc.rs @@ -1,4 +1,4 @@ -use std::{hash::{Hash, Hasher}, marker::PhantomData, ops::Deref, path::Path}; +use std::{hash::{Hash, Hasher}, marker::PhantomData, ops::Deref}; use anyhow::{Result, bail}; @@ -6,7 +6,7 @@ use super::LocAbleImpl; use crate::{loc::{LocAble, LocBuf, LocBufAble, StrandAbleImpl}, path::{AsPath, AsPathView, PathDyn}, scheme::SchemeKind, strand::AsStrandView}; #[derive(Clone, Copy, Debug)] -pub struct Loc<'p, P = &'p Path> { +pub struct Loc<'p, P = &'p std::path::Path> { pub(super) inner: P, pub(super) uri: usize, pub(super) urn: usize, diff --git a/yazi-shared/src/path/buf.rs b/yazi-shared/src/path/buf.rs index 9d42ca69..4f282cf8 100644 --- a/yazi-shared/src/path/buf.rs +++ b/yazi-shared/src/path/buf.rs @@ -15,6 +15,10 @@ impl From for PathBufDyn { fn from(value: std::path::PathBuf) -> Self { Self::Os(value) } } +impl From for PathBufDyn { + fn from(value: typed_path::UnixPathBuf) -> Self { Self::Unix(value) } +} + impl From> for PathBufDyn { fn from(value: PathDyn<'_>) -> Self { value.to_owned() } } @@ -25,6 +29,12 @@ impl TryFrom for std::path::PathBuf { fn try_from(value: PathBufDyn) -> Result { value.into_os() } } +impl TryFrom for typed_path::UnixPathBuf { + type Error = PathDynError; + + fn try_from(value: PathBufDyn) -> Result { value.into_unix() } +} + impl PartialEq> for PathBufDyn { fn eq(&self, other: &PathDyn<'_>) -> bool { self.as_path() == *other } } @@ -76,6 +86,14 @@ impl PathBufDyn { }) } + #[inline] + pub fn into_unix(self) -> Result { + Ok(match self { + Self::Os(_) => Err(PathDynError::AsUnix)?, + Self::Unix(p) => p, + }) + } + #[inline] pub fn new(kind: PathKind) -> Self { match kind { diff --git a/yazi-shared/src/path/conversion.rs b/yazi-shared/src/path/conversion.rs index 9dbccb4d..5478b1ff 100644 --- a/yazi-shared/src/path/conversion.rs +++ b/yazi-shared/src/path/conversion.rs @@ -14,6 +14,14 @@ impl AsPath for std::path::PathBuf { fn as_path(&self) -> PathDyn<'_> { PathDyn::Os(self) } } +impl AsPath for typed_path::UnixPath { + fn as_path(&self) -> PathDyn<'_> { PathDyn::Unix(self) } +} + +impl AsPath for typed_path::UnixPathBuf { + fn as_path(&self) -> PathDyn<'_> { PathDyn::Unix(self) } +} + impl AsPath for PathDyn<'_> { fn as_path(&self) -> PathDyn<'_> { *self } } diff --git a/yazi-shared/src/path/kind.rs b/yazi-shared/src/path/kind.rs index a0dd82ba..c66781cd 100644 --- a/yazi-shared/src/path/kind.rs +++ b/yazi-shared/src/path/kind.rs @@ -11,7 +11,7 @@ impl From for PathKind { SchemeKind::Regular => Self::Os, SchemeKind::Search => Self::Os, SchemeKind::Archive => Self::Os, - SchemeKind::Sftp => Self::Os, // FIXME + SchemeKind::Sftp => Self::Unix, } } } diff --git a/yazi-shared/src/path/like.rs b/yazi-shared/src/path/like.rs index 0c8ca02c..bf50a271 100644 --- a/yazi-shared/src/path/like.rs +++ b/yazi-shared/src/path/like.rs @@ -7,6 +7,8 @@ use crate::{Utf8BytePredictor, path::{AsPath, Components, Display, EndsWithError pub trait PathLike: AsPath { fn as_os(&self) -> Result<&std::path::Path, PathDynError> { self.as_path().as_os() } + fn as_unix(&self) -> Result<&typed_path::UnixPath, PathDynError> { self.as_path().as_unix() } + fn components(&self) -> Components<'_> { self.as_path().components() } fn display(&self) -> Display<'_> { self.as_path().display() } diff --git a/yazi-shared/src/path/path.rs b/yazi-shared/src/path/path.rs index eb895296..6ed324d7 100644 --- a/yazi-shared/src/path/path.rs +++ b/yazi-shared/src/path/path.rs @@ -58,6 +58,14 @@ impl<'p> PathDyn<'p> { } } + #[inline] + pub fn as_unix(self) -> Result<&'p typed_path::UnixPath, PathDynError> { + match self { + Self::Os(_) => Err(PathDynError::AsUnix), + Self::Unix(p) => Ok(p), + } + } + pub fn components(self) -> Components<'p> { match self { Self::Os(p) => Components::Os(p.components()), diff --git a/yazi-shared/src/path/view.rs b/yazi-shared/src/path/view.rs index 601682a5..31a2fdf0 100644 --- a/yazi-shared/src/path/view.rs +++ b/yazi-shared/src/path/view.rs @@ -10,3 +10,11 @@ impl<'a> AsPathView<'a, &'a std::path::Path> for &'a std::path::Path { impl<'a> AsPathView<'a, &'a std::path::Path> for &'a std::path::PathBuf { fn as_path_view(self) -> &'a std::path::Path { self } } + +impl<'a> AsPathView<'a, &'a typed_path::UnixPath> for &'a typed_path::UnixPath { + fn as_path_view(self) -> &'a typed_path::UnixPath { self } +} + +impl<'a> AsPathView<'a, &'a typed_path::UnixPath> for &'a typed_path::UnixPathBuf { + fn as_path_view(self) -> &'a typed_path::UnixPath { self } +} diff --git a/yazi-shared/src/strand/conversion.rs b/yazi-shared/src/strand/conversion.rs index 35258cfa..b527c5c7 100644 --- a/yazi-shared/src/strand/conversion.rs +++ b/yazi-shared/src/strand/conversion.rs @@ -11,6 +11,10 @@ impl AsStrand for [u8] { fn as_strand(&self) -> Strand<'_> { Strand::Bytes(self) } } +impl AsStrand for &[u8] { + fn as_strand(&self) -> Strand<'_> { Strand::Bytes(self) } +} + impl AsStrand for str { fn as_strand(&self) -> Strand<'_> { Strand::Utf8(self) } } @@ -43,6 +47,10 @@ impl AsStrand for &std::path::Path { fn as_strand(&self) -> Strand<'_> { Strand::Os(self.as_os_str()) } } +impl AsStrand for &typed_path::UnixPath { + fn as_strand(&self) -> Strand<'_> { Strand::Bytes(self.as_bytes()) } +} + impl AsStrand for crate::path::Components<'_> { fn as_strand(&self) -> Strand<'_> { self.strand() } } diff --git a/yazi-shared/src/strand/cow.rs b/yazi-shared/src/strand/cow.rs index 74d6b906..4a2de3fe 100644 --- a/yazi-shared/src/strand/cow.rs +++ b/yazi-shared/src/strand/cow.rs @@ -34,6 +34,13 @@ impl From for StrandCow<'_> { fn from(value: StrandBuf) -> Self { Self::Owned(value) } } +impl<'a, T> From<&'a T> for StrandCow<'a> +where + T: ?Sized + AsStrand, +{ + fn from(value: &'a T) -> Self { Self::Borrowed(value.as_strand()) } +} + impl PartialEq> for StrandCow<'_> { fn eq(&self, other: &Strand) -> bool { self.as_strand() == *other } } diff --git a/yazi-shared/src/strand/kind.rs b/yazi-shared/src/strand/kind.rs index e1034715..103afc20 100644 --- a/yazi-shared/src/strand/kind.rs +++ b/yazi-shared/src/strand/kind.rs @@ -22,7 +22,7 @@ impl From for StrandKind { SchemeKind::Regular => Self::Os, SchemeKind::Search => Self::Os, SchemeKind::Archive => Self::Os, - SchemeKind::Sftp => Self::Os, // FIXME + SchemeKind::Sftp => Self::Bytes, } } } diff --git a/yazi-shared/src/strand/view.rs b/yazi-shared/src/strand/view.rs index 3a3e7a35..9303486a 100644 --- a/yazi-shared/src/strand/view.rs +++ b/yazi-shared/src/strand/view.rs @@ -16,3 +16,15 @@ impl<'a> AsStrandView<'a, &'a OsStr> for &'a std::path::Path { impl<'a> AsStrandView<'a, &'a OsStr> for std::path::Components<'a> { fn as_strand_view(self) -> &'a OsStr { self.as_path().as_os_str() } } + +impl<'a> AsStrandView<'a, &'a [u8]> for &'a [u8] { + fn as_strand_view(self) -> &'a [u8] { self } +} + +impl<'a> AsStrandView<'a, &'a [u8]> for &'a typed_path::UnixPath { + fn as_strand_view(self) -> &'a [u8] { self.as_bytes() } +} + +impl<'a> AsStrandView<'a, &'a [u8]> for typed_path::UnixComponents<'a> { + fn as_strand_view(self) -> &'a [u8] { self.as_path::().as_bytes() } +} diff --git a/yazi-shared/src/url/buf.rs b/yazi-shared/src/url/buf.rs index 9986ab46..4811d866 100644 --- a/yazi-shared/src/url/buf.rs +++ b/yazi-shared/src/url/buf.rs @@ -10,7 +10,7 @@ pub enum UrlBuf { Regular(LocBuf), Search { loc: LocBuf, domain: Symbol }, Archive { loc: LocBuf, domain: Symbol }, - Sftp { loc: LocBuf, domain: Symbol }, + Sftp { loc: LocBuf, domain: Symbol }, } // FIXME: remove @@ -128,7 +128,7 @@ impl UrlBuf { Self::Regular(loc) => loc.try_set_name(name.as_os()?)?, Self::Search { loc, .. } => loc.try_set_name(name.as_os()?)?, Self::Archive { loc, .. } => loc.try_set_name(name.as_os()?)?, - Self::Sftp { loc, .. } => loc.try_set_name(name.as_os()?)?, + Self::Sftp { loc, .. } => loc.try_set_name(name.encoded_bytes())?, }) } @@ -141,7 +141,10 @@ impl UrlBuf { Self::Archive { loc, domain } => { Self::Archive { loc: loc.rebase(base), domain: domain.clone() } } - Self::Sftp { loc, domain } => Self::Sftp { loc: loc.rebase(base), domain: domain.clone() }, + Self::Sftp { loc, domain } => { + todo!(); + // Self::Sftp { loc: loc.rebase(base), domain: domain.clone() } + } } } } diff --git a/yazi-shared/src/url/components.rs b/yazi-shared/src/url/components.rs index 987bbf53..3c164c56 100644 --- a/yazi-shared/src/url/components.rs +++ b/yazi-shared/src/url/components.rs @@ -87,7 +87,7 @@ impl<'a> Components<'a> { Url::Archive { loc: Loc::with(path.as_os().unwrap(), uri, urn).unwrap(), domain } } Url::Sftp { domain, .. } => { - Url::Sftp { loc: Loc::with(path.as_os().unwrap(), uri, urn).unwrap(), domain } + Url::Sftp { loc: Loc::with(path.as_unix().unwrap(), uri, urn).unwrap(), domain } } } } diff --git a/yazi-shared/src/url/cow.rs b/yazi-shared/src/url/cow.rs index 99fbadad..2abf6bf6 100644 --- a/yazi-shared/src/url/cow.rs +++ b/yazi-shared/src/url/cow.rs @@ -10,12 +10,12 @@ pub enum UrlCow<'a> { Regular(LocBuf), Search { loc: LocBuf, domain: SymbolCow<'a, str> }, Archive { loc: LocBuf, domain: SymbolCow<'a, str> }, - Sftp { loc: LocBuf, domain: SymbolCow<'a, str> }, + Sftp { loc: LocBuf, domain: SymbolCow<'a, str> }, RegularRef(Loc<'a>), SearchRef { loc: Loc<'a>, domain: SymbolCow<'a, str> }, ArchiveRef { loc: Loc<'a>, domain: SymbolCow<'a, str> }, - SftpRef { loc: Loc<'a>, domain: SymbolCow<'a, str> }, + SftpRef { loc: Loc<'a, &'a typed_path::UnixPath>, domain: SymbolCow<'a, str> }, } // FIXME: remove @@ -148,7 +148,7 @@ impl<'a> TryFrom<(SchemeCow<'a>, PathDyn<'a>)> for UrlCow<'a> { domain: domain.ok_or_else(|| anyhow!("missing domain for archive scheme"))?, }, SchemeKind::Sftp => Self::SftpRef { - loc: Loc::with(path.as_os()?, uri, urn)?, + loc: Loc::with(path.as_unix()?, uri, urn)?, domain: domain.ok_or_else(|| anyhow!("missing domain for sftp scheme"))?, }, }) @@ -165,15 +165,15 @@ impl<'a> TryFrom<(SchemeCow<'a>, PathBufDyn)> for UrlCow<'a> { Ok(match kind { SchemeKind::Regular => Self::Regular(path.into_os()?.into()), SchemeKind::Search => Self::Search { - loc: LocBuf::::with(path.try_into()?, uri, urn)?, + loc: LocBuf::::with(path.try_into()?, uri, urn)?, domain: domain.ok_or_else(|| anyhow!("missing domain for search scheme"))?, }, SchemeKind::Archive => Self::Archive { - loc: LocBuf::::with(path.try_into()?, uri, urn)?, + loc: LocBuf::::with(path.try_into()?, uri, urn)?, domain: domain.ok_or_else(|| anyhow!("missing domain for archive scheme"))?, }, SchemeKind::Sftp => Self::Sftp { - loc: LocBuf::::with(path.try_into()?, uri, urn)?, + loc: LocBuf::::with(path.try_into()?, uri, urn)?, domain: domain.ok_or_else(|| anyhow!("missing domain for sftp scheme"))?, }, }) diff --git a/yazi-shared/src/url/url.rs b/yazi-shared/src/url/url.rs index 86795dbf..bd971a85 100644 --- a/yazi-shared/src/url/url.rs +++ b/yazi-shared/src/url/url.rs @@ -12,7 +12,7 @@ pub enum Url<'a> { Regular(Loc<'a>), Search { loc: Loc<'a>, domain: &'a str }, Archive { loc: Loc<'a>, domain: &'a str }, - Sftp { loc: Loc<'a>, domain: &'a str }, + Sftp { loc: Loc<'a, &'a typed_path::UnixPath>, domain: &'a str }, } // --- Eq @@ -253,7 +253,7 @@ impl<'a> Url<'a> { domain: domain.intern(), }, Self::Sftp { domain, .. } => { - UrlBuf::Sftp { loc: joined.into_os()?.into(), domain: domain.intern() } + UrlBuf::Sftp { loc: joined.into_unix()?.into(), domain: domain.intern() } } }) } @@ -282,24 +282,24 @@ impl<'a> Url<'a> { domain: domain.intern(), }, Self::Archive { loc, domain } if path.try_starts_with(loc.trail())? => UrlBuf::Archive { - loc: LocBuf::::new(path.into_os()?, loc.base(), loc.trail()), + loc: LocBuf::::new(path.into_os()?, loc.base(), loc.trail()), domain: domain.intern(), }, Self::Sftp { loc, domain } if path.try_starts_with(loc.trail())? => UrlBuf::Sftp { - loc: LocBuf::::new(path.into_os()?, loc.base(), loc.trail()), + loc: LocBuf::::new(path.into_unix()?, loc.base(), loc.trail()), domain: domain.intern(), }, Self::Search { domain, .. } => UrlBuf::Search { - loc: LocBuf::::saturated(path.into_os()?, self.kind()), + loc: LocBuf::::saturated(path.into_os()?, self.kind()), domain: domain.intern(), }, Self::Archive { domain, .. } => UrlBuf::Archive { - loc: LocBuf::::saturated(path.into_os()?, self.kind()), + loc: LocBuf::::saturated(path.into_os()?, self.kind()), domain: domain.intern(), }, Self::Sftp { domain, .. } => UrlBuf::Sftp { - loc: LocBuf::::saturated(path.into_os()?, self.kind()), + loc: LocBuf::::saturated(path.into_unix()?, self.kind()), domain: domain.intern(), }, }; diff --git a/yazi-vfs/Cargo.toml b/yazi-vfs/Cargo.toml index 3bed8acd..53c524b0 100644 --- a/yazi-vfs/Cargo.toml +++ b/yazi-vfs/Cargo.toml @@ -27,3 +27,4 @@ parking_lot = { workspace = true } russh = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } +typed-path = { workspace = true } diff --git a/yazi-vfs/src/provider/sftp/metadata.rs b/yazi-vfs/src/provider/sftp/metadata.rs index 23c2ee9e..82473e94 100644 --- a/yazi-vfs/src/provider/sftp/metadata.rs +++ b/yazi-vfs/src/provider/sftp/metadata.rs @@ -1,4 +1,4 @@ -use std::{ffi::OsStr, io, time::{Duration, UNIX_EPOCH}}; +use std::{io, time::{Duration, UNIX_EPOCH}}; use yazi_fs::cha::ChaKind; @@ -26,18 +26,17 @@ impl TryFrom<&yazi_sftp::fs::DirEntry> for Cha { type Error = io::Error; fn try_from(ent: &yazi_sftp::fs::DirEntry) -> Result { - let mut cha = Self::try_from((ent.name().as_ref(), ent.attrs()))?; + let mut cha = Self::try_from((ent.name(), ent.attrs()))?; cha.0.nlink = ent.nlink().unwrap_or_default(); Ok(cha) } } -impl TryFrom<(&OsStr, &yazi_sftp::fs::Attrs)> for Cha { +impl TryFrom<(&[u8], &yazi_sftp::fs::Attrs)> for Cha { type Error = io::Error; - fn try_from((name, attrs): (&OsStr, &yazi_sftp::fs::Attrs)) -> Result { - let kind = - if name.as_encoded_bytes().starts_with(b".") { ChaKind::HIDDEN } else { ChaKind::empty() }; + fn try_from((name, attrs): (&[u8], &yazi_sftp::fs::Attrs)) -> Result { + let kind = if name.starts_with(b".") { ChaKind::HIDDEN } else { ChaKind::empty() }; Ok(Self(yazi_fs::cha::Cha { kind, diff --git a/yazi-vfs/src/provider/sftp/sftp.rs b/yazi-vfs/src/provider/sftp/sftp.rs index 531edc75..e31fbc76 100644 --- a/yazi-vfs/src/provider/sftp/sftp.rs +++ b/yazi-vfs/src/provider/sftp/sftp.rs @@ -12,7 +12,7 @@ use crate::provider::sftp::Conn; #[derive(Clone)] pub struct Sftp<'a> { url: Url<'a>, - path: &'a Path, + path: &'a typed_path::UnixPath, name: &'static str, config: &'static ProviderSftp, @@ -71,7 +71,7 @@ impl<'a> Provider for Sftp<'a> { where P: AsPath, { - let to = to.as_path().as_os()?; + let to = to.as_path().as_unix()?; let attrs = Attrs::from(super::Attrs(attrs)); let op = self.op().await?; @@ -96,7 +96,7 @@ impl<'a> Provider for Sftp<'a> { where P: AsPath, { - let to = to.as_path().as_os()?; + let to = to.as_path().as_unix()?; Ok(self.op().await?.hardlink(self.path, to).await?) } @@ -137,7 +137,7 @@ impl<'a> Provider for Sftp<'a> { where P: AsPath, { - let to = to.as_path().as_os()?; + let to = to.as_path().as_unix()?; let op = self.op().await?; match op.rename_posix(self.path, &to).await { @@ -156,7 +156,7 @@ impl<'a> Provider for Sftp<'a> { P: AsPath, F: AsyncFnOnce() -> io::Result, { - let original = original.as_path().as_os()?; + let original = original.as_path().as_unix()?; Ok(self.op().await?.symlink(&original, self.path).await?) }