diff --git a/yazi-actor/src/mgr/rename.rs b/yazi-actor/src/mgr/rename.rs index 1d97c294..19febb59 100644 --- a/yazi-actor/src/mgr/rename.rs +++ b/yazi-actor/src/mgr/rename.rs @@ -33,7 +33,7 @@ impl Actor for Rename { .chars() .rev() .position(|c| c == '.') - .filter(|_| !hovered.is_dir()) + .filter(|_| hovered.is_file()) .map(|i| name.chars().count() - i - 1) .filter(|&i| i != 0), _ => None, diff --git a/yazi-binding/src/cha.rs b/yazi-binding/src/cha.rs index fe9b5176..9d8f66e4 100644 --- a/yazi-binding/src/cha.rs +++ b/yazi-binding/src/cha.rs @@ -28,8 +28,7 @@ impl Cha { let kind = ChaKind::from_bits(t.raw_get("kind").unwrap_or_default()) .ok_or_else(|| "Invalid kind".into_lua_err())?; - let mode = - ChaMode::from_bits(t.raw_get("mode")?).ok_or_else(|| "Invalid mode".into_lua_err())?; + let mode = ChaMode::try_from(t.raw_get::("mode")?)?; Self(yazi_fs::cha::Cha { kind, diff --git a/yazi-fs/src/cha/cha.rs b/yazi-fs/src/cha/cha.rs index d6ed6b6e..4a99d192 100644 --- a/yazi-fs/src/cha/cha.rs +++ b/yazi-fs/src/cha/cha.rs @@ -1,11 +1,11 @@ -use std::{ffi::OsStr, fs::{FileType, Metadata}, ops::Deref, time::{Duration, SystemTime, UNIX_EPOCH}}; +use std::{ffi::OsStr, fs::Metadata, ops::Deref, time::{Duration, SystemTime, UNIX_EPOCH}}; use anyhow::bail; use yazi_macro::{unix_either, win_either}; use yazi_shared::url::Url; use super::ChaKind; -use crate::{cha::ChaMode, provider}; +use crate::{cha::{ChaMode, ChaType}, provider}; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Cha { @@ -63,7 +63,7 @@ impl Cha { U: Into>, { let url: Url = url.into(); - let mut retain = cha.kind & (ChaKind::HIDDEN | ChaKind::SYSTEM | ChaKind::LINK); + let mut retain = cha.kind & (ChaKind::HIDDEN | ChaKind::SYSTEM); if cha.is_link() { cha = provider::metadata(url).await.unwrap_or(cha); @@ -75,16 +75,12 @@ impl Cha { cha.attach(retain) } - pub fn from_dummy<'a, U>(_url: U, ft: Option) -> Self + pub fn from_dummy<'a, U>(_url: U, r#type: Option) -> Self where U: Into>, { - let mode = ft.map(ChaMode::from_bare).unwrap_or_default(); - let mut kind = ChaKind::DUMMY; - if mode.is_link() { - kind |= ChaKind::LINK; - } + let mode = r#type.map(ChaMode::from_bare).unwrap_or_default(); #[cfg(unix)] if _url.into().urn().is_hidden() { @@ -171,7 +167,7 @@ impl Cha { if let Some(atime) = self.atime { Ok(atime.duration_since(UNIX_EPOCH)?) } else { - bail!("atime not supported on this platform"); + bail!("atime not available"); } } @@ -179,7 +175,7 @@ impl Cha { if let Some(mtime) = self.mtime { Ok(mtime.duration_since(UNIX_EPOCH)?) } else { - bail!("mtime not supported on this platform"); + bail!("mtime not available"); } } @@ -187,7 +183,7 @@ impl Cha { if let Some(btime) = self.btime { Ok(btime.duration_since(UNIX_EPOCH)?) } else { - bail!("btime not supported on this platform"); + bail!("btime not available"); } } @@ -195,7 +191,7 @@ impl Cha { if let Some(ctime) = self.ctime { Ok(ctime.duration_since(UNIX_EPOCH)?) } else { - bail!("ctime not supported on this platform"); + bail!("ctime not available"); } } } diff --git a/yazi-fs/src/cha/kind.rs b/yazi-fs/src/cha/kind.rs index df16385a..2c02a05b 100644 --- a/yazi-fs/src/cha/kind.rs +++ b/yazi-fs/src/cha/kind.rs @@ -7,11 +7,8 @@ bitflags! { pub struct ChaKind: u8 { const HIDDEN = 0b0000_0001; const SYSTEM = 0b0000_0010; - - const LINK = 0b0000_0100; - const ORPHAN = 0b0000_1000; - - const DUMMY = 0b0001_0000; + const ORPHAN = 0b0000_0100; + const DUMMY = 0b0000_1000; } } diff --git a/yazi-fs/src/cha/mod.rs b/yazi-fs/src/cha/mod.rs index 961ce868..a72c4fb2 100644 --- a/yazi-fs/src/cha/mod.rs +++ b/yazi-fs/src/cha/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(cha kind mode); +yazi_macro::mod_flat!(cha kind mode r#type); diff --git a/yazi-fs/src/cha/mode.rs b/yazi-fs/src/cha/mode.rs index 9d3babf1..9d70e739 100644 --- a/yazi-fs/src/cha/mode.rs +++ b/yazi-fs/src/cha/mode.rs @@ -1,7 +1,10 @@ -use std::fs::FileType; +use std::ops::Deref; +use anyhow::{anyhow, bail}; use bitflags::bitflags; +use crate::cha::ChaType; + bitflags! { #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct ChaMode: u16 { @@ -36,10 +39,43 @@ bitflags! { } } -impl ChaMode { - #[inline] - pub fn r#type(self) -> Self { self & Self::T_MASK } +impl Deref for ChaMode { + type Target = ChaType; + #[inline] + fn deref(&self) -> &Self::Target { + match *self & Self::T_MASK { + Self::T_FILE => &ChaType::File, + Self::T_DIR => &ChaType::Dir, + Self::T_LINK => &ChaType::Link, + Self::T_BLOCK => &ChaType::Block, + Self::T_CHAR => &ChaType::Char, + Self::T_SOCK => &ChaType::Sock, + Self::T_FIFO => &ChaType::FIFO, + _ => &ChaType::Unknown, + } + } +} + +impl TryFrom for ChaMode { + type Error = anyhow::Error; + + fn try_from(value: u16) -> Result { + let me = Self::from_bits(value).ok_or_else(|| anyhow!("invalid file mode: {value:04o}"))?; + match me & Self::T_MASK { + Self::T_FILE + | Self::T_DIR + | Self::T_LINK + | Self::T_BLOCK + | Self::T_CHAR + | Self::T_SOCK + | Self::T_FIFO => Ok(me), + _ => bail!("invalid file type: {value:04o}"), + } + } +} + +impl ChaMode { // Convert a file mode to a string representation #[cfg(unix)] #[allow(clippy::collapsible_else_if)] @@ -47,13 +83,13 @@ impl ChaMode { let mut s = *b"-?????????"; // File type - s[0] = match self.r#type() { - Self::T_DIR => b'd', - Self::T_LINK => b'l', - Self::T_BLOCK => b'b', - Self::T_CHAR => b'c', - Self::T_SOCK => b's', - Self::T_FIFO => b'p', + s[0] = match *self { + ChaType::Dir => b'd', + ChaType::Link => b'l', + ChaType::Block => b'b', + ChaType::Char => b'c', + ChaType::Sock => b's', + ChaType::FIFO => b'p', _ => b'-', }; if dummy { @@ -90,69 +126,25 @@ impl ChaMode { s } - pub(super) fn from_bare(ft: FileType) -> Self { - #[cfg(unix)] - { - use std::os::unix::fs::FileTypeExt; - if ft.is_file() { - Self::T_FILE - } else if ft.is_dir() { - Self::T_DIR - } else if ft.is_symlink() { - Self::T_LINK - } else if ft.is_block_device() { - Self::T_BLOCK - } else if ft.is_char_device() { - Self::T_CHAR - } else if ft.is_socket() { - Self::T_SOCK - } else if ft.is_fifo() { - Self::T_FIFO - } else { - Self::empty() - } - } - #[cfg(windows)] - { - if ft.is_file() { - Self::T_FILE - } else if ft.is_dir() { - Self::T_DIR - } else if ft.is_symlink() { - Self::T_LINK - } else { - Self::empty() - } + pub(super) fn from_bare(r#type: ChaType) -> Self { + match r#type { + ChaType::File => Self::T_FILE, + ChaType::Dir => Self::T_DIR, + ChaType::Link => Self::T_LINK, + ChaType::Block => Self::T_BLOCK, + ChaType::Char => Self::T_CHAR, + ChaType::Sock => Self::T_SOCK, + ChaType::FIFO => Self::T_FIFO, + ChaType::Unknown => Self::empty(), } } } impl ChaMode { - #[inline] - pub const fn is_file(self) -> bool { self.contains(Self::T_FILE) } - - #[inline] - pub const fn is_dir(self) -> bool { self.contains(Self::T_DIR) } - - #[inline] - pub const fn is_link(&self) -> bool { self.contains(Self::T_LINK) } - - #[inline] - pub const fn is_block(&self) -> bool { self.contains(Self::T_BLOCK) } - - #[inline] - pub const fn is_char(&self) -> bool { self.contains(Self::T_CHAR) } - - #[inline] - pub const fn is_sock(&self) -> bool { self.contains(Self::T_SOCK) } - - #[inline] - pub const fn is_fifo(&self) -> bool { self.contains(Self::T_FIFO) } - // TODO: deprecate #[inline] - pub const fn is_exec(&self) -> bool { self.contains(Self::U_EXEC) } + pub const fn is_exec(self) -> bool { self.contains(Self::U_EXEC) } #[inline] - pub const fn is_sticky(&self) -> bool { self.contains(Self::S_STICKY) } + pub const fn is_sticky(self) -> bool { self.contains(Self::S_STICKY) } } diff --git a/yazi-fs/src/cha/type.rs b/yazi-fs/src/cha/type.rs new file mode 100644 index 00000000..9941d6ac --- /dev/null +++ b/yazi-fs/src/cha/type.rs @@ -0,0 +1,80 @@ +use std::fs::FileType; + +use crate::cha::ChaMode; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ChaType { + File, + Dir, + Link, + Block, + Char, + Sock, + FIFO, + Unknown, +} + +impl From for ChaType { + fn from(value: ChaMode) -> Self { *value } +} + +impl From for ChaType { + fn from(value: FileType) -> Self { + #[cfg(unix)] + { + use std::os::unix::fs::FileTypeExt; + if value.is_file() { + Self::File + } else if value.is_dir() { + Self::Dir + } else if value.is_symlink() { + Self::Link + } else if value.is_block_device() { + Self::Block + } else if value.is_char_device() { + Self::Char + } else if value.is_socket() { + Self::Sock + } else if value.is_fifo() { + Self::FIFO + } else { + Self::Unknown + } + } + #[cfg(windows)] + { + if value.is_file() { + Self::File + } else if value.is_dir() { + Self::Dir + } else if value.is_symlink() { + Self::Link + } else { + Self::Unknown + } + } + } +} + +impl ChaType { + #[inline] + pub fn is_file(self) -> bool { self == Self::File } + + #[inline] + pub fn is_dir(self) -> bool { self == Self::Dir } + + #[inline] + pub fn is_link(self) -> bool { self == Self::Link } + + #[inline] + pub fn is_block(self) -> bool { self == Self::Block } + + #[inline] + pub fn is_char(self) -> bool { self == Self::Char } + + #[inline] + pub fn is_sock(self) -> bool { self == Self::Sock } + + #[inline] + pub fn is_fifo(self) -> bool { self == Self::FIFO } +} diff --git a/yazi-fs/src/file.rs b/yazi-fs/src/file.rs index 2576d613..b9b75814 100644 --- a/yazi-fs/src/file.rs +++ b/yazi-fs/src/file.rs @@ -1,9 +1,9 @@ -use std::{ffi::OsStr, fs::FileType, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::{Path, PathBuf}}; +use std::{ffi::OsStr, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::{Path, PathBuf}}; use anyhow::Result; use yazi_shared::url::{Uri, UrlBuf, UrlCow, Urn}; -use crate::{cha::Cha, provider}; +use crate::{cha::{Cha, ChaType}, provider}; #[derive(Clone, Debug, Default)] pub struct File { @@ -36,9 +36,9 @@ impl File { } #[inline] - pub fn from_dummy(url: impl Into, ft: Option) -> Self { + pub fn from_dummy(url: impl Into, r#type: Option) -> Self { let url = url.into(); - let cha = Cha::from_dummy(&url, ft); + let cha = Cha::from_dummy(&url, r#type); Self { url, cha, link_to: None } } diff --git a/yazi-fs/src/files.rs b/yazi-fs/src/files.rs index 09de81c0..005a4c38 100644 --- a/yazi-fs/src/files.rs +++ b/yazi-fs/src/files.rs @@ -40,14 +40,14 @@ impl Files { let (tx, rx) = mpsc::unbounded_channel(); tokio::spawn(async move { - while let Ok(Some(item)) = it.next_entry().await { + while let Ok(Some(ent)) = it.next_entry().await { select! { _ = tx.closed() => break, - result = item.metadata() => { - let url = item.url(); + result = ent.metadata() => { + let url = ent.url(); _ = tx.send(match result { Ok(cha) => File::from_follow(url, cha).await, - Err(_) => File::from_dummy(url, item.file_type().await.ok()) + Err(_) => File::from_dummy(url, ent.file_type().await.ok()) }); } } @@ -67,11 +67,11 @@ impl Files { let (second, third) = rest.split_at(entries.len() / 3); async fn go(entries: &[DirEntry]) -> Vec { let mut files = Vec::with_capacity(entries.len()); - for entry in entries { - let url = entry.url(); - files.push(match entry.metadata().await { + for ent in entries { + let url = ent.url(); + files.push(match ent.metadata().await { Ok(cha) => File::from_follow(url, cha).await, - Err(_) => File::from_dummy(url, entry.file_type().await.ok()), + Err(_) => File::from_dummy(url, ent.file_type().await.ok()), }); } files diff --git a/yazi-fs/src/fns.rs b/yazi-fs/src/fns.rs index 7c1f6c87..ecdb92d0 100644 --- a/yazi-fs/src/fns.rs +++ b/yazi-fs/src/fns.rs @@ -83,9 +83,9 @@ pub fn copy_with_progress( pub async fn remove_dir_clean(dir: &UrlBuf) { let Ok(mut it) = provider::read_dir(dir).await else { return }; - while let Ok(Some(entry)) = it.next_entry().await { - if entry.file_type().await.is_ok_and(|t| t.is_dir()) { - let url = entry.url(); + while let Ok(Some(ent)) = it.next_entry().await { + if ent.file_type().await.is_ok_and(|t| t.is_dir()) { + let url = ent.url(); Box::pin(remove_dir_clean(&url)).await; provider::remove_dir(&url).await.ok(); } diff --git a/yazi-fs/src/provider/dir_entry.rs b/yazi-fs/src/provider/dir_entry.rs index c5eabe75..276251ca 100644 --- a/yazi-fs/src/provider/dir_entry.rs +++ b/yazi-fs/src/provider/dir_entry.rs @@ -2,7 +2,7 @@ use std::{borrow::Cow, ffi::OsStr, io}; use yazi_shared::url::UrlBuf; -use crate::{cha::Cha, provider::FileHolder}; +use crate::{cha::{Cha, ChaType}, provider::FileHolder}; pub enum DirEntry { Local(super::local::DirEntry), @@ -33,7 +33,7 @@ impl DirEntry { } } - pub async fn file_type(&self) -> io::Result { + pub async fn file_type(&self) -> io::Result { match self { Self::Local(local) => local.file_type().await, } diff --git a/yazi-fs/src/provider/local/dir_entry.rs b/yazi-fs/src/provider/local/dir_entry.rs index 8f9ecffd..d8985e5b 100644 --- a/yazi-fs/src/provider/local/dir_entry.rs +++ b/yazi-fs/src/provider/local/dir_entry.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, ffi::OsStr, io, path::PathBuf}; -use crate::{cha::Cha, provider::FileHolder}; +use crate::{cha::{Cha, ChaType}, provider::FileHolder}; pub struct DirEntry(pub(super) tokio::fs::DirEntry); @@ -14,5 +14,5 @@ impl FileHolder for DirEntry { Ok(Cha::new(&name, self.0.metadata().await?)) } - async fn file_type(&self) -> io::Result { self.0.file_type().await } + async fn file_type(&self) -> io::Result { self.0.file_type().await.map(Into::into) } } diff --git a/yazi-fs/src/provider/sftp/metadata.rs b/yazi-fs/src/provider/sftp/metadata.rs new file mode 100644 index 00000000..b9c66964 --- /dev/null +++ b/yazi-fs/src/provider/sftp/metadata.rs @@ -0,0 +1,59 @@ +use std::{ffi::OsStr, io, time::{Duration, UNIX_EPOCH}}; + +use crate::cha::{Cha, ChaKind, ChaMode}; + +impl TryFrom> 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()))?; + cha.nlink = ent.nlink().unwrap_or_default(); + Ok(cha) + } +} + +impl TryFrom<(&OsStr, &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() }; + + Ok(Cha { + kind, + mode: attrs.try_into()?, + len: attrs.size.unwrap_or(0), + atime: attrs.atime.and_then(|t| UNIX_EPOCH.checked_add(Duration::from_secs(t as u64))), + btime: None, + ctime: None, + mtime: attrs.mtime.and_then(|t| UNIX_EPOCH.checked_add(Duration::from_secs(t as u64))), + dev: 0, + uid: attrs.uid.unwrap_or(0), + gid: attrs.gid.unwrap_or(0), + nlink: 0, + }) + } +} + +impl TryFrom<&yazi_sftp::fs::Attrs> for ChaMode { + type Error = io::Error; + + fn try_from(attrs: &yazi_sftp::fs::Attrs) -> Result { + ChaMode::try_from(attrs.perm.unwrap_or_default() as u16) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) + } +} + +impl From for yazi_sftp::fs::Attrs { + fn from(cha: Cha) -> Self { + Self { + size: Some(cha.len), + uid: Some(cha.uid), + gid: Some(cha.gid), + perm: Some(cha.mode.bits() as u32), + atime: cha.atime_dur().ok().map(|d| d.as_secs() as u32), + mtime: cha.mtime_dur().ok().map(|d| d.as_secs() as u32), + extended: Default::default(), + } + } +} diff --git a/yazi-fs/src/provider/sftp/mod.rs b/yazi-fs/src/provider/sftp/mod.rs index b88864b7..f719af7d 100644 --- a/yazi-fs/src/provider/sftp/mod.rs +++ b/yazi-fs/src/provider/sftp/mod.rs @@ -1,4 +1,4 @@ -yazi_macro::mod_flat!(gate read_dir sftp); +yazi_macro::mod_flat!(gate metadata read_dir sftp); pub(super) static CONN: yazi_shared::RoCell> = yazi_shared::RoCell::new(); diff --git a/yazi-fs/src/provider/sftp/read_dir.rs b/yazi-fs/src/provider/sftp/read_dir.rs index b55d5d39..2954d1ab 100644 --- a/yazi-fs/src/provider/sftp/read_dir.rs +++ b/yazi-fs/src/provider/sftp/read_dir.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, ffi::OsStr, io, path::PathBuf}; -use crate::{cha::Cha, provider::{DirReader, FileHolder}}; +use crate::{cha::{Cha, ChaMode, ChaType}, provider::{DirReader, FileHolder}}; pub struct ReadDir(pub(super) yazi_sftp::fs::ReadDir); @@ -20,7 +20,12 @@ impl FileHolder for DirEntry<'_> { fn name(&self) -> Cow<'_, OsStr> { self.0.name() } - async fn metadata(&self) -> io::Result { todo!() } + async fn metadata(&self) -> io::Result { + let name = self.name(); + (name.as_ref(), self.0.attrs()).try_into() + } - async fn file_type(&self) -> io::Result { todo!() } + 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 f2924716..8e4a083e 100644 --- a/yazi-fs/src/provider/sftp/sftp.rs +++ b/yazi-fs/src/provider/sftp/sftp.rs @@ -30,19 +30,9 @@ impl Provider for Sftp { P: AsRef, Q: AsRef, { - // FIXME: pull this out to a From for Attrs impl - let attrs = Attrs { - size: Some(cha.len), - uid: Some(cha.uid), - gid: Some(cha.gid), - perm: Some(cha.mode.bits() as _), - atime: cha.atime_dur().ok().map(|d| d.as_secs() as u32), - mtime: cha.mtime_dur().ok().map(|d| d.as_secs() as u32), - extended: Default::default(), - }; + 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?; @@ -68,7 +58,9 @@ impl Provider for Sftp { where P: AsRef, { - todo!() + let path = path.as_ref(); + let attrs = Self::op().await?.stat(path).await?; + (path.file_name().unwrap_or_default(), &attrs).try_into() } async fn read_dir

(path: P) -> io::Result @@ -120,7 +112,9 @@ impl Provider for Sftp { where P: AsRef, { - todo!() + let path = path.as_ref(); + let attrs = Self::op().await?.lstat(path).await?; + (path.file_name().unwrap_or_default(), &attrs).try_into() } async fn trash

(_path: P) -> io::Result<()> diff --git a/yazi-fs/src/provider/traits.rs b/yazi-fs/src/provider/traits.rs index 72a89518..31273dad 100644 --- a/yazi-fs/src/provider/traits.rs +++ b/yazi-fs/src/provider/traits.rs @@ -3,7 +3,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 crate::cha::Cha; +use crate::cha::{Cha, ChaType}; pub trait Provider { type File: AsyncRead + AsyncWrite + Unpin; @@ -189,7 +189,7 @@ pub trait FileHolder { fn metadata(&self) -> impl Future>; - fn file_type(&self) -> impl Future>; + fn file_type(&self) -> impl Future>; } // --- FileOpener