diff --git a/yazi-fs/src/provider/local/casefold.rs b/yazi-fs/src/provider/local/casefold.rs index 3e9e9c8f..c34e1c17 100644 --- a/yazi-fs/src/provider/local/casefold.rs +++ b/yazi-fs/src/provider/local/casefold.rs @@ -1,17 +1,15 @@ use std::{io, path::{Path, PathBuf}}; -#[inline] -pub async fn casefold(path: impl AsRef) -> io::Result { - let path = path.as_ref().to_owned(); - tokio::task::spawn_blocking(move || casefold_impl(path)).await? -} - -#[inline] pub async fn must_case_match(path: impl AsRef) -> bool { let path = path.as_ref(); casefold(path).await.is_ok_and(|p| p == path) } +pub(super) async fn casefold(path: impl AsRef) -> io::Result { + let path = path.as_ref().to_owned(); + tokio::task::spawn_blocking(move || casefold_impl(path)).await? +} + #[cfg(any( target_os = "macos", target_os = "netbsd", @@ -95,7 +93,7 @@ fn casefold_impl(path: PathBuf) -> io::Result { // Case-insensitive match Ok(entries.swap_remove(i)) } else { - Err(io::Error::new(io::ErrorKind::NotFound, "file not found")) + Err(io::Error::from(io::ErrorKind::NotFound)) } } diff --git a/yazi-fs/src/provider/local/local.rs b/yazi-fs/src/provider/local/local.rs index fa56955a..38e66e75 100644 --- a/yazi-fs/src/provider/local/local.rs +++ b/yazi-fs/src/provider/local/local.rs @@ -32,6 +32,13 @@ impl Provider for Local { tokio::fs::canonicalize(path).await } + async fn casefold

(&self, path: P) -> io::Result + where + P: AsRef, + { + super::casefold(path).await + } + #[inline] async fn copy(&self, from: P, to: Q, cha: Cha) -> io::Result where diff --git a/yazi-fs/src/provider/provider.rs b/yazi-fs/src/provider/provider.rs index 0be21fc2..58d6e1b6 100644 --- a/yazi-fs/src/provider/provider.rs +++ b/yazi-fs/src/provider/provider.rs @@ -67,11 +67,16 @@ pub async fn casefold<'a, U>(url: U) -> io::Result where U: Into>, { - if let Some(path) = url.into().as_path() { - local::casefold(path).await.map(Into::into) - } else { - Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem")) - } + let url: Url = url.into(); + let fold = Providers::new(url).await?.casefold(url.loc).await?; + + Ok(match url.scheme { + SchemeRef::Regular | SchemeRef::Search(_) => fold.into(), + SchemeRef::Archive(_) => { + Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem: archive"))? + } + SchemeRef::Sftp(_) => UrlBuf { loc: fold.into(), scheme: url.scheme.into() }, + }) } pub async fn copy<'a, U, V>(from: U, to: V, cha: Cha) -> io::Result @@ -134,7 +139,7 @@ where if original.scheme.covariant(link.scheme) { Providers::new(original).await?.hard_link(original.loc, link.loc).await } else { - Err(io::Error::new(io::ErrorKind::CrossesDevices, "Cross-filesystem hardlink")) + Err(io::Error::from(io::ErrorKind::CrossesDevices)) } } @@ -215,7 +220,7 @@ where if from.scheme.covariant(to.scheme) { Providers::new(from).await?.rename(from.loc, to.loc).await } else { - Err(io::Error::new(io::ErrorKind::CrossesDevices, "Cross-filesystem rename")) + Err(io::Error::from(io::ErrorKind::CrossesDevices)) } } diff --git a/yazi-fs/src/provider/providers.rs b/yazi-fs/src/provider/providers.rs index 83323f83..d81ad6a8 100644 --- a/yazi-fs/src/provider/providers.rs +++ b/yazi-fs/src/provider/providers.rs @@ -54,6 +54,16 @@ impl Provider for Providers<'_> { } } + async fn casefold

(&self, path: P) -> io::Result + where + P: AsRef, + { + match self.0 { + Inner::Regular | Inner::Search(_) => Local.casefold(path).await, + Inner::Sftp((p, _)) => p.casefold(path).await, + } + } + async fn copy(&self, from: P, to: Q, cha: Cha) -> io::Result where P: AsRef, diff --git a/yazi-fs/src/provider/sftp/metadata.rs b/yazi-fs/src/provider/sftp/metadata.rs index e9a4668b..d2b31b58 100644 --- a/yazi-fs/src/provider/sftp/metadata.rs +++ b/yazi-fs/src/provider/sftp/metadata.rs @@ -2,10 +2,10 @@ use std::{ffi::OsStr, io, time::{Duration, UNIX_EPOCH}}; use crate::cha::{Cha, ChaKind, ChaMode}; -impl TryFrom for Cha { +impl TryFrom<&yazi_sftp::fs::DirEntry> for Cha { type Error = io::Error; - fn try_from(ent: yazi_sftp::fs::DirEntry) -> Result { + fn try_from(ent: &yazi_sftp::fs::DirEntry) -> Result { let mut cha = Self::try_from((ent.name().as_ref(), ent.attrs()))?; cha.nlink = ent.nlink().unwrap_or_default(); Ok(cha) diff --git a/yazi-fs/src/provider/sftp/read_dir.rs b/yazi-fs/src/provider/sftp/read_dir.rs index 743937e1..f628e391 100644 --- a/yazi-fs/src/provider/sftp/read_dir.rs +++ b/yazi-fs/src/provider/sftp/read_dir.rs @@ -20,10 +20,7 @@ impl FileHolder for DirEntry { fn name(&self) -> Cow<'_, OsStr> { self.0.name() } - async fn metadata(&self) -> io::Result { - let name = self.name(); - (name.as_ref(), self.0.attrs()).try_into() - } + async fn metadata(&self) -> io::Result { Cha::try_from(&self.0) } async fn file_type(&self) -> io::Result { ChaMode::try_from(self.0.attrs()).map(Into::into) diff --git a/yazi-fs/src/provider/sftp/sftp.rs b/yazi-fs/src/provider/sftp/sftp.rs index 748ab505..9cca50ff 100644 --- a/yazi-fs/src/provider/sftp/sftp.rs +++ b/yazi-fs/src/provider/sftp/sftp.rs @@ -6,7 +6,7 @@ use yazi_sftp::fs::{Attrs, Flags}; use yazi_shared::{scheme::SchemeRef, url::{Url, UrlBuf, UrlCow}}; use yazi_vfs::config::ProviderSftp; -use crate::{cha::Cha, provider::{FileBuilder, Provider, local::Local}}; +use crate::{cha::Cha, provider::{DirReader, FileBuilder, FileHolder, Provider, local::Local}}; #[derive(Clone, Copy)] pub struct Sftp { @@ -44,6 +44,38 @@ impl Provider for Sftp { Ok(self.op().await?.realpath(&path).await?) } + async fn casefold

(&self, path: P) -> io::Result + where + P: AsRef, + { + let path = path.as_ref(); + let Some((parent, name)) = path.parent().zip(path.file_name()) else { + return Ok(path.to_owned()); + }; + + if !self.symlink_metadata(path).await?.is_link() { + return match self.canonicalize(path).await?.file_name() { + Some(name) => Ok(parent.join(name)), + None => Err(io::Error::other("Cannot get filename")), + }; + } + + let mut it = self.read_dir(parent).await?; + let mut similar = None; + while let Some(entry) = it.next().await? { + let s = entry.name(); + if !s.eq_ignore_ascii_case(name) { + continue; + } else if s == name { + return Ok(entry.path()); + } else if similar.is_none() { + similar = Some(s.into_owned()); + } + } + + similar.map(|n| parent.join(n)).ok_or_else(|| io::Error::from(io::ErrorKind::NotFound)) + } + async fn copy(&self, from: P, to: Q, cha: Cha) -> io::Result where P: AsRef, diff --git a/yazi-fs/src/provider/traits.rs b/yazi-fs/src/provider/traits.rs index 25d4ce1b..a6649429 100644 --- a/yazi-fs/src/provider/traits.rs +++ b/yazi-fs/src/provider/traits.rs @@ -19,6 +19,10 @@ pub trait Provider { where P: AsRef; + fn casefold

(&self, path: P) -> impl Future> + where + P: AsRef; + fn copy(&self, from: P, to: Q, cha: Cha) -> impl Future> where P: AsRef, diff --git a/yazi-sftp/src/error.rs b/yazi-sftp/src/error.rs index 84f1d273..9c1066a0 100644 --- a/yazi-sftp/src/error.rs +++ b/yazi-sftp/src/error.rs @@ -31,11 +31,21 @@ impl From for std::io::Error { fn from(err: Error) -> Self { match err { Error::IO(e) => e, - Error::Serde(_) => Self::new(std::io::ErrorKind::InvalidData, err), - Error::Status(_) => Self::other(err), - Error::Packet(_) => Self::new(std::io::ErrorKind::InvalidData, err), - Error::Timeout => Self::new(std::io::ErrorKind::TimedOut, err), - Error::Unsupported => Self::new(std::io::ErrorKind::Unsupported, err), + Error::Serde(e) => Self::new(std::io::ErrorKind::InvalidData, e), + Error::Status(status) => match status.code { + responses::StatusCode::Ok => Self::new(std::io::ErrorKind::Other, "unexpected OK"), + responses::StatusCode::Eof => Self::from(std::io::ErrorKind::UnexpectedEof), + responses::StatusCode::NoSuchFile => Self::from(std::io::ErrorKind::NotFound), + responses::StatusCode::PermissionDenied => Self::from(std::io::ErrorKind::PermissionDenied), + responses::StatusCode::Failure => Self::from(std::io::ErrorKind::Other), + responses::StatusCode::BadMessage => Self::from(std::io::ErrorKind::InvalidData), + responses::StatusCode::NoConnection => Self::from(std::io::ErrorKind::NotConnected), + responses::StatusCode::ConnectionLost => Self::from(std::io::ErrorKind::ConnectionReset), + responses::StatusCode::OpUnsupported => Self::from(std::io::ErrorKind::Unsupported), + }, + Error::Packet(e) => Self::new(std::io::ErrorKind::InvalidData, e), + Error::Timeout => Self::from(std::io::ErrorKind::TimedOut), + Error::Unsupported => Self::from(std::io::ErrorKind::Unsupported), Error::Custom(_) => Self::other(err), } } diff --git a/yazi-sftp/src/session.rs b/yazi-sftp/src/session.rs index 2bab5855..1f1c1c44 100644 --- a/yazi-sftp/src/session.rs +++ b/yazi-sftp/src/session.rs @@ -37,7 +37,7 @@ impl Session { async fn write(writer: &mut WriteHalf>, buf: Vec) -> io::Result<()> { if buf.is_empty() { - Err(io::Error::new(ErrorKind::BrokenPipe, "channel closed")) + Err(io::Error::from(ErrorKind::BrokenPipe)) } else { writer.write_all(&buf).await } diff --git a/yazi-term/src/tty/handle.rs b/yazi-term/src/tty/handle.rs index 4a4effce..b6b49b88 100644 --- a/yazi-term/src/tty/handle.rs +++ b/yazi-term/src/tty/handle.rs @@ -120,7 +120,7 @@ impl Handle { let mut b = 0; match unsafe { libc::read(self.inner, &mut b as *mut _ as *mut _, 1) } { -1 => Err(Error::last_os_error()), - 0 => Err(Error::new(ErrorKind::UnexpectedEof, "unexpected EOF")), + 0 => Err(Error::from(ErrorKind::UnexpectedEof)), _ => Ok(b), } } @@ -189,7 +189,7 @@ impl Handle { if success == 0 { return Err(Error::last_os_error()); } else if bytes == 0 { - return Err(Error::new(ErrorKind::UnexpectedEof, "unexpected EOF")); + return Err(Error::from(ErrorKind::UnexpectedEof)); } Ok(buf) } diff --git a/yazi-term/src/tty/tty.rs b/yazi-term/src/tty/tty.rs index 51da8a6c..2ce95464 100644 --- a/yazi-term/src/tty/tty.rs +++ b/yazi-term/src/tty/tty.rs @@ -38,7 +38,7 @@ impl Tty { let mut stdin = self.stdin.lock(); loop { if now.elapsed() > timeout { - return Err(Error::new(ErrorKind::TimedOut, "timed out")); + return Err(Error::from(ErrorKind::TimedOut)); } else if !stdin.poll(Duration::from_millis(30))? { continue; } diff --git a/yazi-watcher/src/watcher.rs b/yazi-watcher/src/watcher.rs index 3fc77778..66891c66 100644 --- a/yazi-watcher/src/watcher.rs +++ b/yazi-watcher/src/watcher.rs @@ -27,7 +27,7 @@ impl Watcher { } pub fn watch<'a>(&mut self, it: impl Iterator) { - self.in_tx.send(it.filter(|u| u.is_regular()).cloned().collect()).ok(); + self.in_tx.send(it.cloned().collect()).ok(); } pub fn push_files(&self, urls: Vec) { Backend::push_files(&self.out_tx, urls); }