refactor: use Cha for better cross-platform consistency

This commit is contained in:
sxyazi 2025-09-17 22:27:30 +08:00
parent 6cfa92f112
commit 7a23f2a4c1
No known key found for this signature in database
16 changed files with 78 additions and 77 deletions

View file

@ -1,6 +1,6 @@
use std::{mem, ops::Deref, sync::atomic::{AtomicU64, Ordering}, time::UNIX_EPOCH}; use std::{mem, ops::Deref, sync::atomic::{AtomicU64, Ordering}, time::UNIX_EPOCH};
use anyhow::Result; use anyhow::{Result, anyhow};
use hashbrown::HashMap; use hashbrown::HashMap;
use parking_lot::RwLock; use parking_lot::RwLock;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter};
@ -103,8 +103,9 @@ impl State {
} }
async fn skip(&self) -> Result<bool> { async fn skip(&self) -> Result<bool> {
let meta = Local::symlink_metadata(BOOT.state_dir.join(".dds")).await?; let cha = Local::symlink_metadata(BOOT.state_dir.join(".dds")).await?;
let modified = meta.modified()?.duration_since(UNIX_EPOCH)?.as_micros(); let modified =
cha.mtime.ok_or_else(|| anyhow!("invalid mtime"))?.duration_since(UNIX_EPOCH)?.as_micros();
Ok(modified >= self.last.load(Ordering::Relaxed) as u128) Ok(modified >= self.last.load(Ordering::Relaxed) as u128)
} }
} }

View file

@ -1,4 +1,4 @@
use std::{fs::{FileType, Metadata}, ops::Deref, time::SystemTime}; use std::{ffi::OsStr, fs::{FileType, Metadata}, ops::Deref, time::SystemTime};
use yazi_macro::{unix_either, win_either}; use yazi_macro::{unix_either, win_either};
use yazi_shared::url::Url; use yazi_shared::url::Url;
@ -47,11 +47,8 @@ impl Default for Cha {
impl Cha { impl Cha {
#[inline] #[inline]
pub fn new<'a, U>(url: U, meta: Metadata) -> Self pub fn new(name: &OsStr, meta: Metadata) -> Self {
where Self::from_bare(&meta).attach(ChaKind::hidden(name, &meta))
U: Into<Url<'a>>,
{
Self::from_bare(&meta).attach(ChaKind::hidden(url, &meta))
} }
#[inline] #[inline]
@ -60,22 +57,21 @@ impl Cha {
Ok(Self::from_follow(url, provider::symlink_metadata(url).await?).await) Ok(Self::from_follow(url, provider::symlink_metadata(url).await?).await)
} }
pub async fn from_follow<'a, U>(url: U, mut meta: Metadata) -> Self pub async fn from_follow<'a, U>(url: U, mut cha: Self) -> Self
where where
U: Into<Url<'a>>, U: Into<Url<'a>>,
{ {
let url = url.into(); let url: Url = url.into();
let mut attached = ChaKind::hidden(url, &meta); let mut attached = cha.kind & (ChaKind::HIDDEN | ChaKind::SYSTEM | ChaKind::LINK);
if meta.is_symlink() { if cha.is_link() {
attached |= ChaKind::LINK; cha = provider::metadata(url).await.unwrap_or(cha);
meta = provider::metadata(url).await.unwrap_or(meta);
} }
if meta.is_symlink() { if cha.is_link() {
attached |= ChaKind::ORPHAN; attached |= ChaKind::ORPHAN;
} }
Self::from_bare(&meta).attach(attached) cha.attach(attached)
} }
pub fn from_dummy<'a, U>(_url: U, ft: Option<FileType>) -> Self pub fn from_dummy<'a, U>(_url: U, ft: Option<FileType>) -> Self

View file

@ -1,7 +1,6 @@
use std::fs::Metadata; use std::{ffi::OsStr, fs::Metadata};
use bitflags::bitflags; use bitflags::bitflags;
use yazi_shared::url::Url;
bitflags! { bitflags! {
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
@ -18,15 +17,15 @@ bitflags! {
impl ChaKind { impl ChaKind {
#[inline] #[inline]
pub(super) fn hidden<'a, U>(_url: U, _meta: &Metadata) -> Self pub(super) fn hidden(_name: &OsStr, _meta: &Metadata) -> Self {
where
U: Into<Url<'a>>,
{
let mut me = Self::empty(); let mut me = Self::empty();
#[cfg(unix)] #[cfg(unix)]
if _url.into().urn().is_hidden() { {
me |= Self::HIDDEN; use std::os::unix::ffi::OsStrExt;
if _name.as_bytes().starts_with(b".") {
me |= Self::HIDDEN;
}
} }
#[cfg(windows)] #[cfg(windows)]
{ {

View file

@ -1,4 +1,4 @@
use std::{ffi::OsStr, fs::{FileType, Metadata}, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::{Path, PathBuf}}; use std::{ffi::OsStr, fs::FileType, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::{Path, PathBuf}};
use anyhow::Result; use anyhow::Result;
use yazi_shared::url::{Uri, UrlBuf, UrlCow, Urn}; use yazi_shared::url::{Uri, UrlBuf, UrlCow, Urn};
@ -22,15 +22,15 @@ impl File {
#[inline] #[inline]
pub async fn new(url: impl Into<UrlCow<'_>>) -> Result<Self> { pub async fn new(url: impl Into<UrlCow<'_>>) -> Result<Self> {
let url = url.into(); let url = url.into();
let meta = provider::symlink_metadata(&url).await?; let cha = provider::symlink_metadata(&url).await?;
Ok(Self::from_follow(url.into_owned(), meta).await) Ok(Self::from_follow(url.into_owned(), cha).await)
} }
#[inline] #[inline]
pub async fn from_follow(url: UrlBuf, meta: Metadata) -> Self { pub async fn from_follow(url: UrlBuf, cha: Cha) -> Self {
let link_to = if meta.is_symlink() { provider::read_link(&url).await.ok() } else { None }; let link_to = if cha.is_link() { provider::read_link(&url).await.ok() } else { None };
let cha = Cha::from_follow(&url, meta).await; let cha = Cha::from_follow(&url, cha).await;
Self { url, cha, link_to } Self { url, cha, link_to }
} }

View file

@ -46,7 +46,7 @@ impl Files {
result = item.metadata() => { result = item.metadata() => {
let url = item.url(); let url = item.url();
_ = tx.send(match result { _ = tx.send(match result {
Ok(meta) => File::from_follow(url, meta).await, Ok(cha) => File::from_follow(url, cha).await,
Err(_) => File::from_dummy(url, item.file_type().await.ok()) Err(_) => File::from_dummy(url, item.file_type().await.ok())
}); });
} }
@ -70,7 +70,7 @@ impl Files {
for entry in entries { for entry in entries {
let url = entry.url(); let url = entry.url();
files.push(match entry.metadata().await { files.push(match entry.metadata().await {
Ok(meta) => File::from_follow(url, meta).await, Ok(cha) => File::from_follow(url, cha).await,
Err(_) => File::from_dummy(url, entry.file_type().await.ok()), Err(_) => File::from_dummy(url, entry.file_type().await.ok()),
}); });
} }

View file

@ -68,7 +68,7 @@ pub fn copy_with_progress(
None => {} None => {}
} }
let len = provider::symlink_metadata(&to).await.map(|m| m.len()).unwrap_or(0); let len = provider::symlink_metadata(&to).await.map(|m| m.len).unwrap_or(0);
if len > last { if len > last {
prog_tx.send(Ok(len - last)).await.ok(); prog_tx.send(Ok(len - last)).await.ok();
last = len; last = len;

View file

@ -15,11 +15,11 @@ impl SizeCalculator {
U: Into<Url<'a>>, U: Into<Url<'a>>,
{ {
let url: Url = url.into(); let url: Url = url.into();
let meta = provider::symlink_metadata(url).await?; let cha = provider::symlink_metadata(url).await?;
Ok(if meta.is_dir() { Ok(if cha.is_dir() {
Self::Dir(VecDeque::from([Either::Left(url.to_owned())])) Self::Dir(VecDeque::from([Either::Left(url.to_owned())]))
} else { } else {
Self::File(Some(meta.len())) Self::File(Some(cha.len))
}) })
} }
@ -72,8 +72,8 @@ impl SizeCalculator {
let Ok(ft) = ent.file_type().await else { continue }; let Ok(ft) = ent.file_type().await else { continue };
if ft.is_dir() { if ft.is_dir() {
buf.push_back(Either::Left(ent.url())); buf.push_back(Either::Left(ent.url()));
} else if let Ok(meta) = ent.metadata().await { } else if let Ok(cha) = ent.metadata().await {
size += meta.len(); size += cha.len;
} }
} }
Some(size) Some(size)

View file

@ -2,7 +2,7 @@ use std::{borrow::Cow, ffi::OsStr, io};
use yazi_shared::url::UrlBuf; use yazi_shared::url::UrlBuf;
use crate::provider::FileHolder; use crate::{cha::Cha, provider::FileHolder};
pub enum DirEntry { pub enum DirEntry {
Local(super::local::DirEntry), Local(super::local::DirEntry),
@ -27,7 +27,7 @@ impl DirEntry {
} }
} }
pub async fn metadata(&self) -> io::Result<std::fs::Metadata> { pub async fn metadata(&self) -> io::Result<Cha> {
match self { match self {
Self::Local(local) => local.metadata().await, Self::Local(local) => local.metadata().await,
} }

View file

@ -1,6 +1,6 @@
use std::{borrow::Cow, ffi::OsStr, io, path::PathBuf}; use std::{borrow::Cow, ffi::OsStr, io, path::PathBuf};
use crate::provider::FileHolder; use crate::{cha::Cha, provider::FileHolder};
pub struct DirEntry(pub(super) tokio::fs::DirEntry); pub struct DirEntry(pub(super) tokio::fs::DirEntry);
@ -9,7 +9,10 @@ impl FileHolder for DirEntry {
fn name(&self) -> Cow<'_, OsStr> { self.0.file_name().into() } fn name(&self) -> Cow<'_, OsStr> { self.0.file_name().into() }
async fn metadata(&self) -> io::Result<std::fs::Metadata> { self.0.metadata().await } async fn metadata(&self) -> io::Result<Cha> {
let name = self.name(); // TODO: use `file_name_os_str` when stabilized
Ok(Cha::new(&name, self.0.metadata().await?))
}
async fn file_type(&self) -> io::Result<std::fs::FileType> { self.0.file_type().await } async fn file_type(&self) -> io::Result<std::fs::FileType> { self.0.file_type().await }
} }

View file

@ -62,11 +62,12 @@ impl Provider for Local {
} }
#[inline] #[inline]
async fn metadata<P>(path: P) -> io::Result<std::fs::Metadata> async fn metadata<P>(path: P) -> io::Result<Cha>
where where
P: AsRef<Path>, P: AsRef<Path>,
{ {
tokio::fs::metadata(path).await let path = path.as_ref();
Ok(Cha::new(path.file_name().unwrap_or_default(), tokio::fs::metadata(path).await?))
} }
#[inline] #[inline]
@ -170,11 +171,12 @@ impl Provider for Local {
} }
#[inline] #[inline]
async fn symlink_metadata<P>(path: P) -> io::Result<std::fs::Metadata> async fn symlink_metadata<P>(path: P) -> io::Result<Cha>
where where
P: AsRef<Path>, P: AsRef<Path>,
{ {
tokio::fs::symlink_metadata(path).await let path = path.as_ref();
Ok(Cha::new(path.file_name().unwrap_or_default(), tokio::fs::symlink_metadata(path).await?))
} }
async fn trash<P>(path: P) -> io::Result<()> async fn trash<P>(path: P) -> io::Result<()>

View file

@ -125,7 +125,7 @@ where
} }
#[inline] #[inline]
pub async fn metadata<'a, U>(url: U) -> io::Result<std::fs::Metadata> pub async fn metadata<'a, U>(url: U) -> io::Result<Cha>
where where
U: Into<Url<'a>>, U: Into<Url<'a>>,
{ {
@ -256,7 +256,7 @@ where
} }
#[inline] #[inline]
pub async fn symlink_metadata<'a, U>(url: U) -> io::Result<std::fs::Metadata> pub async fn symlink_metadata<'a, U>(url: U) -> io::Result<Cha>
where where
U: Into<Url<'a>>, U: Into<Url<'a>>,
{ {

View file

@ -1,6 +1,6 @@
use std::{borrow::Cow, ffi::OsStr, io, path::PathBuf}; use std::{borrow::Cow, ffi::OsStr, io, path::PathBuf};
use crate::provider::{DirReader, FileHolder}; use crate::{cha::Cha, provider::{DirReader, FileHolder}};
pub struct ReadDir(pub(super) yazi_sftp::fs::ReadDir); pub struct ReadDir(pub(super) yazi_sftp::fs::ReadDir);
@ -20,7 +20,7 @@ impl FileHolder for DirEntry<'_> {
fn name(&self) -> Cow<'_, OsStr> { self.0.name() } fn name(&self) -> Cow<'_, OsStr> { self.0.name() }
async fn metadata(&self) -> io::Result<std::fs::Metadata> { todo!() } async fn metadata(&self) -> io::Result<Cha> { todo!() }
async fn file_type(&self) -> io::Result<std::fs::FileType> { todo!() } async fn file_type(&self) -> io::Result<std::fs::FileType> { todo!() }
} }

View file

@ -70,7 +70,7 @@ impl Provider for Sftp {
Ok(Self::op().await?.hardlink(&original, &link).await?) Ok(Self::op().await?.hardlink(&original, &link).await?)
} }
async fn metadata<P>(path: P) -> io::Result<std::fs::Metadata> async fn metadata<P>(path: P) -> io::Result<Cha>
where where
P: AsRef<Path>, P: AsRef<Path>,
{ {
@ -122,7 +122,7 @@ impl Provider for Sftp {
Ok(Self::op().await?.symlink(&original, &link).await?) Ok(Self::op().await?.symlink(&original, &link).await?)
} }
async fn symlink_metadata<P>(path: P) -> io::Result<std::fs::Metadata> async fn symlink_metadata<P>(path: P) -> io::Result<Cha>
where where
P: AsRef<Path>, P: AsRef<Path>,
{ {

View file

@ -67,7 +67,7 @@ pub trait Provider {
P: AsRef<Path>, P: AsRef<Path>,
Q: AsRef<Path>; Q: AsRef<Path>;
fn metadata<P>(path: P) -> impl Future<Output = io::Result<std::fs::Metadata>> fn metadata<P>(path: P) -> impl Future<Output = io::Result<Cha>>
where where
P: AsRef<Path>; P: AsRef<Path>;
@ -115,8 +115,8 @@ pub trait Provider {
async move { async move {
let path = path.as_ref(); let path = path.as_ref();
let ft = ok_or_not_found!(Self::symlink_metadata(path).await, return Ok(())); let cha = ok_or_not_found!(Self::symlink_metadata(path).await, return Ok(()));
if ft.is_symlink() { if cha.is_link() {
Self::remove_file(path).await Self::remove_file(path).await
} else { } else {
remove_dir_all_impl::<Self>(path).await remove_dir_all_impl::<Self>(path).await
@ -155,7 +155,7 @@ pub trait Provider {
Self::symlink(original, link, async || Ok(false)) Self::symlink(original, link, async || Ok(false))
} }
fn symlink_metadata<P>(path: P) -> impl Future<Output = io::Result<std::fs::Metadata>> fn symlink_metadata<P>(path: P) -> impl Future<Output = io::Result<Cha>>
where where
P: AsRef<Path>; P: AsRef<Path>;
@ -187,7 +187,7 @@ pub trait FileHolder {
fn name(&self) -> Cow<'_, OsStr>; fn name(&self) -> Cow<'_, OsStr>;
fn metadata(&self) -> impl Future<Output = io::Result<std::fs::Metadata>>; fn metadata(&self) -> impl Future<Output = io::Result<Cha>>;
fn file_type(&self) -> impl Future<Output = io::Result<std::fs::FileType>>; fn file_type(&self) -> impl Future<Output = io::Result<std::fs::FileType>>;
} }

View file

@ -50,14 +50,14 @@ fn cwd(lua: &Lua) -> mlua::Result<Function> {
fn cha(lua: &Lua) -> mlua::Result<Function> { fn cha(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, (url, follow): (UrlRef, Option<bool>)| async move { lua.create_async_function(|lua, (url, follow): (UrlRef, Option<bool>)| async move {
let meta = if follow.unwrap_or(false) { let cha = if follow.unwrap_or(false) {
provider::metadata(&*url).await provider::metadata(&*url).await
} else { } else {
provider::symlink_metadata(&*url).await provider::symlink_metadata(&*url).await
}; };
match meta { match cha {
Ok(m) => Cha(yazi_fs::cha::Cha::new(&*url, m)).into_lua_multi(&lua), Ok(c) => Cha(c).into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua), Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
} }
}) })
@ -129,8 +129,8 @@ fn read_dir(lua: &Lua) -> mlua::Result<Function> {
let file = if !resolve { let file = if !resolve {
yazi_fs::File::from_dummy(url, next.file_type().await.ok()) yazi_fs::File::from_dummy(url, next.file_type().await.ok())
} else if let Ok(meta) = next.metadata().await { } else if let Ok(cha) = next.metadata().await {
yazi_fs::File::from_follow(url, meta).await yazi_fs::File::from_follow(url, cha).await
} else { } else {
yazi_fs::File::from_dummy(url, next.file_type().await.ok()) yazi_fs::File::from_dummy(url, next.file_type().await.ok())
}; };

View file

@ -71,7 +71,7 @@ impl File {
let mut it = continue_unless_ok!(provider::read_dir(&src).await); let mut it = continue_unless_ok!(provider::read_dir(&src).await);
while let Ok(Some(entry)) = it.next_entry().await { while let Ok(Some(entry)) = it.next_entry().await {
let from = entry.url(); let from = entry.url();
let cha = continue_unless_ok!(Self::cha_from(entry, &from, task.follow).await); let cha = continue_unless_ok!(Self::entry_cha(entry, &from, task.follow).await);
if cha.is_dir() { if cha.is_dir() {
dirs.push_back(from); dirs.push_back(from);
@ -205,7 +205,7 @@ impl File {
let mut it = continue_unless_ok!(provider::read_dir(&src).await); let mut it = continue_unless_ok!(provider::read_dir(&src).await);
while let Ok(Some(entry)) = it.next_entry().await { while let Ok(Some(entry)) = it.next_entry().await {
let from = entry.url(); let from = entry.url();
let cha = continue_unless_ok!(Self::cha_from(entry, &from, task.follow).await); let cha = continue_unless_ok!(Self::entry_cha(entry, &from, task.follow).await);
if cha.is_dir() { if cha.is_dir() {
dirs.push_back(from); dirs.push_back(from);
@ -237,11 +237,11 @@ impl File {
} }
pub(crate) async fn delete(&self, mut task: FileInDelete) -> Result<(), FileOutDelete> { pub(crate) async fn delete(&self, mut task: FileInDelete) -> Result<(), FileOutDelete> {
let meta = provider::symlink_metadata(&task.target).await?; let cha = provider::symlink_metadata(&task.target).await?;
if !meta.is_dir() { if !cha.is_dir() {
let id = task.id; let id = task.id;
task.length = meta.len(); task.length = cha.len;
self.ops.out(id, FileOutDelete::New(meta.len())); self.ops.out(id, FileOutDelete::New(cha.len));
self.queue(task, NORMAL); self.queue(task, NORMAL);
self.ops.out(id, FileOutDelete::Init); self.ops.out(id, FileOutDelete::Init);
return Ok(()); return Ok(());
@ -252,16 +252,16 @@ impl File {
let Ok(mut it) = provider::read_dir(&target).await else { continue }; let Ok(mut it) = provider::read_dir(&target).await else { continue };
while let Ok(Some(entry)) = it.next_entry().await { while let Ok(Some(entry)) = it.next_entry().await {
let Ok(meta) = entry.metadata().await else { continue }; let Ok(cha) = entry.metadata().await else { continue };
if meta.is_dir() { if cha.is_dir() {
dirs.push_front(entry.url()); dirs.push_front(entry.url());
continue; continue;
} }
task.target = entry.url(); task.target = entry.url();
task.length = meta.len(); task.length = cha.len;
self.ops.out(task.id, FileOutDelete::New(meta.len())); self.ops.out(task.id, FileOutDelete::New(cha.len));
self.queue(task.clone(), NORMAL); self.queue(task.clone(), NORMAL);
} }
} }
@ -291,16 +291,16 @@ impl File {
#[inline] #[inline]
async fn cha<'a>(url: impl Into<Url<'a>>, follow: bool) -> io::Result<Cha> { async fn cha<'a>(url: impl Into<Url<'a>>, follow: bool) -> io::Result<Cha> {
let url = url.into(); let url = url.into();
let meta = provider::symlink_metadata(url).await?; let cha = provider::symlink_metadata(url).await?;
Ok(if follow { Cha::from_follow(url, meta).await } else { Cha::new(url, meta) }) Ok(if follow { Cha::from_follow(url, cha).await } else { cha })
} }
#[inline] #[inline]
async fn cha_from(entry: DirEntry, url: &UrlBuf, follow: bool) -> io::Result<Cha> { async fn entry_cha(entry: DirEntry, url: &UrlBuf, follow: bool) -> io::Result<Cha> {
Ok(if follow { Ok(if follow {
Cha::from_follow(url, entry.metadata().await?).await Cha::from_follow(url, entry.metadata().await?).await
} else { } else {
Cha::new(url, entry.metadata().await?) entry.metadata().await?
}) })
} }
} }