fix: make every effort to carry hidden states for dummy files (#2814)

This commit is contained in:
三咲雅 misaki masa 2025-05-31 00:08:50 +08:00 committed by GitHub
parent 4076e35a2f
commit 4fdc0f120f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 175 additions and 165 deletions

View file

@ -94,7 +94,7 @@ impl Mgr {
failed.push((o, n, anyhow!("Destination already exists")));
} else if let Err(e) = fs::rename(&old, &new).await {
failed.push((o, n, e.into()));
} else if let Ok(f) = File::from(new.into()).await {
} else if let Ok(f) = File::new(new.into()).await {
succeeded.insert(Url::from(old), f);
} else {
failed.push((o, n, anyhow!("Failed to retrieve file info")));

View file

@ -1,5 +1,3 @@
use std::collections::{HashMap, HashSet};
use anyhow::Result;
use tokio::fs;
use yazi_config::popup::{ConfirmCfg, InputCfg};
@ -50,7 +48,7 @@ impl Mgr {
fs::create_dir_all(&new).await?;
} else if let Some(real) = realname(&new).await {
ok_or_not_found(fs::remove_file(&new).await)?;
FilesOp::Deleting(parent.clone(), HashSet::from_iter([UrnBuf::from(real)])).emit();
FilesOp::Deleting(parent.clone(), [UrnBuf::from(real)].into()).emit();
fs::File::create(&new).await?;
} else {
fs::create_dir_all(&parent).await.ok();
@ -58,8 +56,8 @@ impl Mgr {
fs::File::create(&new).await?;
}
if let Ok(f) = File::from(new.clone()).await {
FilesOp::Upserting(parent, HashMap::from_iter([(f.urn_owned(), f)])).emit();
if let Ok(f) = File::new(new.clone()).await {
FilesOp::Upserting(parent, [(f.urn_owned(), f)].into()).emit();
TabProxy::reveal(&new)
}
Ok(())

View file

@ -56,7 +56,7 @@ impl Mgr {
tokio::spawn(async move {
let mut files = Vec::with_capacity(todo.len());
for u in todo {
if let Ok(f) = File::from(u).await {
if let Ok(f) = File::new(u).await {
files.push(f);
}
}

View file

@ -1,4 +1,4 @@
use std::{borrow::Cow, collections::{HashMap, HashSet}};
use std::borrow::Cow;
use anyhow::Result;
use tokio::fs;
@ -80,15 +80,15 @@ impl Mgr {
if let Some(o) = overwritten {
ok_or_not_found(fs::rename(p_new.join(&o), &new).await)?;
FilesOp::Deleting(p_new.clone(), HashSet::from_iter([UrnBuf::from(o)])).emit();
FilesOp::Deleting(p_new.clone(), [UrnBuf::from(o)].into()).emit();
}
let file = File::from(new.clone()).await?;
let file = File::new(new.clone()).await?;
if p_new == p_old {
FilesOp::Upserting(p_old, HashMap::from_iter([(n_old, file)])).emit();
FilesOp::Upserting(p_old, [(n_old, file)].into()).emit();
} else {
FilesOp::Deleting(p_old, HashSet::from_iter([n_old])).emit();
FilesOp::Upserting(p_new, HashMap::from_iter([(n_new, file)])).emit();
FilesOp::Deleting(p_old, [n_old].into()).emit();
FilesOp::Upserting(p_new, [(n_new, file)].into()).emit();
}
TabProxy::reveal(&new);

View file

@ -134,8 +134,8 @@ impl Watcher {
for u in urls {
let Some((parent, urn)) = u.pair() else { continue };
let Ok(file) = File::from(u).await else {
ops.push(FilesOp::Deleting(parent, HashSet::from_iter([urn])));
let Ok(file) = File::new(u).await else {
ops.push(FilesOp::Deleting(parent, [urn].into()));
continue;
};
@ -144,11 +144,11 @@ impl Watcher {
|| realname_unchecked(u, &mut cached).await.is_ok_and(|s| urn.as_urn() == s);
if !eq {
ops.push(FilesOp::Deleting(parent, HashSet::from_iter([urn])));
ops.push(FilesOp::Deleting(parent, [urn].into()));
continue;
}
ops.push(FilesOp::Upserting(parent, HashMap::from_iter([(urn, file)])));
ops.push(FilesOp::Upserting(parent, [(urn, file)].into()));
}
FilesOp::mutate(ops);

View file

@ -1,10 +1,10 @@
use std::{mem, time::Duration};
use tokio::{fs, pin};
use tokio::pin;
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg;
use yazi_dds::Pubsub;
use yazi_fs::expand_path;
use yazi_fs::{File, FilesOp, expand_path};
use yazi_macro::render;
use yazi_proxy::{CmpProxy, InputProxy, MgrProxy, TabProxy};
use yazi_shared::{Debounce, errors::InputError, event::CmdCow, url::Url};
@ -94,16 +94,17 @@ impl Tab {
while let Some(result) = rx.next().await {
match result {
Ok(s) => {
let u = Url::from(expand_path(s));
let Ok(meta) = fs::metadata(&u).await else {
return;
};
let url = Url::from(expand_path(s));
if meta.is_dir() {
TabProxy::cd(&u);
} else {
TabProxy::reveal(&u);
let Ok(file) = File::new(url.clone()).await else { return };
if file.is_dir() {
return TabProxy::cd(&url);
}
if let Some(p) = url.parent_url() {
FilesOp::Upserting(p, [(url.urn_owned(), file)].into()).emit();
}
TabProxy::reveal(&url);
}
Err(InputError::Completed(before, ticket)) => {
CmpProxy::trigger(&before, ticket);

View file

@ -70,7 +70,7 @@ impl Tab {
while let Some(chunk) = rx.next().await {
FilesOp::Part(cwd.clone(), chunk, ticket).emit();
}
FilesOp::Done(cwd, Cha::dummy(), ticket).emit();
FilesOp::Done(cwd, Cha::default(), ticket).emit();
Ok(())
}));

View file

@ -46,7 +46,7 @@ impl Folder {
(self.cha, self.stage) = (cha, FolderStage::Loaded);
}
FilesOp::Part(_, ref files, _) if files.is_empty() => {
(self.cha, self.stage) = (Cha::dummy(), FolderStage::Loading);
(self.cha, self.stage) = (Cha::default(), FolderStage::Loading);
}
FilesOp::Part(_, _, ticket) if ticket == self.files.ticket() => {
self.stage = FolderStage::Loading;
@ -55,7 +55,7 @@ impl Folder {
(self.cha, self.stage) = (cha, FolderStage::Loaded);
}
FilesOp::IOErr(_, kind) => {
(self.cha, self.stage) = (Cha::dummy(), FolderStage::Failed(kind));
(self.cha, self.stage) = (Cha::default(), FolderStage::Failed(kind));
}
_ => {}
}

View file

@ -48,7 +48,7 @@ impl Preview {
Some(PreviewLock { url: wd.clone(), cha, mime: MIME_DIR.to_owned(), ..Default::default() });
self.folder_loader.take().map(|h| h.abort());
self.folder_loader = Some(tokio::spawn(async move {
let Some(new) = Files::assert_stale(&wd, dir.unwrap_or(Cha::dummy())).await else { return };
let Some(new) = Files::assert_stale(&wd, dir.unwrap_or_default()).await else { return };
let rx = match Files::from_dir(&wd).await {
Ok(rx) => rx,

View file

@ -1,10 +1,12 @@
use std::{fs::{FileType, Metadata}, path::Path, time::SystemTime};
use tokio::fs;
use yazi_macro::{unix_either, win_either};
use yazi_shared::url::Url;
use super::ChaKind;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Cha {
pub kind: ChaKind,
pub len: u64,
@ -25,8 +27,110 @@ pub struct Cha {
pub nlink: libc::nlink_t,
}
impl From<&Metadata> for Cha {
fn from(m: &Metadata) -> Self {
impl Default for Cha {
fn default() -> Self {
Self {
kind: ChaKind::DUMMY,
len: 0,
atime: None,
btime: None,
#[cfg(unix)]
ctime: None,
mtime: None,
#[cfg(unix)]
mode: 0,
#[cfg(unix)]
dev: 0,
#[cfg(unix)]
uid: 0,
#[cfg(unix)]
gid: 0,
#[cfg(unix)]
nlink: 0,
}
}
}
impl Cha {
#[inline]
pub fn new(path: &Path, meta: Metadata) -> Self {
Self::from_just_meta(&meta).attach(ChaKind::hidden(path, &meta))
}
#[inline]
pub async fn from_url(url: &Url) -> std::io::Result<Self> {
Ok(Self::from_follow(url, fs::symlink_metadata(url).await?).await)
}
pub async fn from_follow(path: &Path, mut meta: Metadata) -> Self {
let mut attached = ChaKind::hidden(path, &meta);
if meta.is_symlink() {
attached |= ChaKind::LINK;
meta = fs::metadata(path).await.unwrap_or(meta);
}
if meta.is_symlink() {
attached |= ChaKind::ORPHAN;
}
Self::from_just_meta(&meta).attach(attached)
}
#[inline]
pub fn from_dummy(url: &Url, ft: Option<FileType>) -> Self {
let mut me = ft.map(Self::from_half_ft).unwrap_or_default();
#[cfg(unix)]
if yazi_shared::url::Urn::new(url).is_hidden() {
me.kind |= ChaKind::HIDDEN;
}
me
}
fn from_half_ft(ft: FileType) -> Self {
let mut kind = ChaKind::DUMMY;
#[cfg(unix)]
let mode = {
use std::os::unix::fs::FileTypeExt;
if ft.is_dir() {
kind |= ChaKind::DIR;
libc::S_IFDIR
} else if ft.is_symlink() {
kind |= ChaKind::LINK;
libc::S_IFLNK
} else if ft.is_block_device() {
libc::S_IFBLK
} else if ft.is_char_device() {
libc::S_IFCHR
} else if ft.is_fifo() {
libc::S_IFIFO
} else if ft.is_socket() {
libc::S_IFSOCK
} else {
0
}
};
#[cfg(windows)]
{
if ft.is_dir() {
kind |= ChaKind::DIR;
} else if ft.is_symlink() {
kind |= ChaKind::LINK;
}
}
Self {
kind,
#[cfg(unix)]
mode,
..Default::default()
}
}
fn from_just_meta(m: &Metadata) -> Self {
#[cfg(unix)]
use std::{os::unix::{fs::MetadataExt, prelude::PermissionsExt}, time::{Duration, UNIX_EPOCH}};
let mut kind = ChaKind::empty();
if m.is_dir() {
kind |= ChaKind::DIR;
@ -40,115 +144,20 @@ impl From<&Metadata> for Cha {
atime: m.accessed().ok(),
btime: m.created().ok(),
#[cfg(unix)]
ctime: {
use std::{os::unix::fs::MetadataExt, time::{Duration, UNIX_EPOCH}};
UNIX_EPOCH.checked_add(Duration::new(m.ctime() as u64, m.ctime_nsec() as u32))
},
ctime: UNIX_EPOCH.checked_add(Duration::new(m.ctime() as u64, m.ctime_nsec() as u32)),
mtime: m.modified().ok(),
#[cfg(unix)]
mode: {
use std::os::unix::prelude::PermissionsExt;
m.permissions().mode() as _
},
mode: m.permissions().mode() as _,
#[cfg(unix)]
dev: {
use std::os::unix::fs::MetadataExt;
m.dev() as _
},
dev: m.dev() as _,
#[cfg(unix)]
uid: {
use std::os::unix::fs::MetadataExt;
m.uid() as _
},
uid: m.uid() as _,
#[cfg(unix)]
gid: {
use std::os::unix::fs::MetadataExt;
m.gid() as _
},
gid: m.gid() as _,
#[cfg(unix)]
nlink: {
use std::os::unix::fs::MetadataExt;
m.nlink() as _
},
nlink: m.nlink() as _,
}
}
}
impl From<Metadata> for Cha {
fn from(m: Metadata) -> Self { Self::from(&m) }
}
impl From<FileType> for Cha {
fn from(t: FileType) -> Self {
let mut kind = ChaKind::DUMMY;
#[cfg(unix)]
let mode = {
use std::os::unix::fs::FileTypeExt;
if t.is_dir() {
kind |= ChaKind::DIR;
libc::S_IFDIR
} else if t.is_symlink() {
kind |= ChaKind::LINK;
libc::S_IFLNK
} else if t.is_block_device() {
libc::S_IFBLK
} else if t.is_char_device() {
libc::S_IFCHR
} else if t.is_fifo() {
libc::S_IFIFO
} else if t.is_socket() {
libc::S_IFSOCK
} else {
0
}
};
#[cfg(windows)]
{
if t.is_dir() {
kind |= ChaKind::DIR;
} else if t.is_symlink() {
kind |= ChaKind::LINK;
}
}
Self {
kind,
#[cfg(unix)]
mode,
..Default::default()
}
}
}
impl Cha {
pub async fn new(path: &Path, mut meta: Metadata) -> Self {
let mut attached = ChaKind::hidden(path, &meta);
if meta.is_symlink() {
attached |= ChaKind::LINK;
meta = tokio::fs::metadata(path).await.unwrap_or(meta);
}
if meta.is_symlink() {
attached |= ChaKind::ORPHAN;
}
let mut me = Self::from(meta);
me.kind |= attached;
me
}
#[inline]
pub fn new_nofollow(_path: &Path, meta: Metadata) -> Self {
let mut me = Self::from(&meta);
me.kind |= ChaKind::hidden(_path, &meta);
me
}
#[inline]
pub fn dummy() -> Self { Self { kind: ChaKind::DUMMY, ..Default::default() } }
#[inline]
pub fn hits(self, c: Self) -> bool {
@ -159,6 +168,12 @@ impl Cha {
&& self.kind == c.kind
&& unix_either!(self.mode == c.mode, true)
}
#[inline]
fn attach(mut self, kind: ChaKind) -> Self {
self.kind |= kind;
self
}
}
impl Cha {

View file

@ -23,29 +23,25 @@ impl Deref for File {
impl File {
#[inline]
pub async fn from(url: Url) -> Result<Self> {
pub async fn new(url: Url) -> Result<Self> {
let meta = fs::symlink_metadata(&url).await?;
Ok(Self::from_meta(url, meta).await)
Ok(Self::from_follow(url, meta).await)
}
#[inline]
pub async fn from_meta(url: Url, meta: Metadata) -> Self {
pub async fn from_follow(url: Url, meta: Metadata) -> Self {
let link_to =
if meta.is_symlink() { fs::read_link(&url).await.map(Url::from).ok() } else { None };
let cha = Cha::new(&url, meta).await;
let cha = Cha::from_follow(&url, meta).await;
Self { url, cha, link_to, icon: Default::default() }
}
#[inline]
pub fn from_dummy(url: Url, ft: Option<FileType>) -> Self {
Self {
url,
cha: ft.map_or_else(Cha::dummy, Cha::from),
link_to: None,
icon: Default::default(),
}
let cha = Cha::from_dummy(&url, ft);
Self { url, cha, link_to: None, icon: Default::default() }
}
#[inline]

View file

@ -41,7 +41,7 @@ impl Files {
result = item.metadata() => {
let url = Url::from(item.path());
_ = tx.send(match result {
Ok(meta) => File::from_meta(url, meta).await,
Ok(meta) => File::from_follow(url, meta).await,
Err(_) => File::from_dummy(url, item.file_type().await.ok())
});
}
@ -65,7 +65,7 @@ impl Files {
for entry in entries {
let url = Url::from(entry.path());
files.push(match entry.metadata().await {
Ok(meta) => File::from_meta(url, meta).await,
Ok(meta) => File::from_follow(url, meta).await,
Err(_) => File::from_dummy(url, entry.file_type().await.ok()),
});
}
@ -83,7 +83,7 @@ impl Files {
pub async fn assert_stale(dir: &Url, cha: Cha) -> Option<Cha> {
use std::io::ErrorKind;
match fs::metadata(dir).await.map(Cha::from) {
match Cha::from_url(dir).await {
Ok(c) if !c.is_dir() => FilesOp::issue_error(dir, ErrorKind::NotADirectory).await,
Ok(c) if c.hits(cha) && PARTITIONS.read().heuristic(cha) => {}
Ok(c) => return Some(c),

View file

@ -125,7 +125,7 @@ impl FilesOp {
} else if maybe_exists(cwd).await {
Self::IOErr(cwd.clone(), kind).emit();
} else if let Some((p, n)) = cwd.pair() {
Self::Deleting(p, HashSet::from_iter([n])).emit();
Self::Deleting(p, [n].into()).emit();
}
}

View file

@ -4,7 +4,7 @@ use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields
use yazi_fs::cha::ChaKind;
#[derive(Clone, Copy, FromLua)]
pub struct Cha(yazi_fs::cha::Cha);
pub struct Cha(pub yazi_fs::cha::Cha);
impl Deref for Cha {
type Target = yazi_fs::cha::Cha;
@ -12,10 +12,6 @@ impl Deref for Cha {
fn deref(&self) -> &Self::Target { &self.0 }
}
impl<T: Into<yazi_fs::cha::Cha>> From<T> for Cha {
fn from(cha: T) -> Self { Self(cha.into()) }
}
impl Cha {
pub fn install(lua: &Lua) -> mlua::Result<()> {
#[inline]
@ -33,7 +29,7 @@ impl Cha {
let kind =
ChaKind::from_bits(t.raw_get("kind")?).ok_or_else(|| "Invalid kind".into_lua_err())?;
Self::from(yazi_fs::cha::Cha {
Self(yazi_fs::cha::Cha {
kind,
len: t.raw_get("len").unwrap_or_default(),
atime: parse_time(t.raw_get("atime").ok())?,

View file

@ -20,7 +20,7 @@ pub fn fd(opt: FdOpt) -> Result<UnboundedReceiver<File>> {
tokio::spawn(async move {
while let Ok(Some(line)) = it.next_line().await {
if let Ok(file) = File::from(opt.cwd.join(line)).await {
if let Ok(file) = File::new(opt.cwd.join(line)).await {
tx.send(file).ok();
}
}

View file

@ -29,7 +29,7 @@ pub fn rg(opt: RgOpt) -> Result<UnboundedReceiver<File>> {
tokio::spawn(async move {
while let Ok(Some(line)) = it.next_line().await {
if let Ok(file) = File::from(opt.cwd.join(line)).await {
if let Ok(file) = File::new(opt.cwd.join(line)).await {
tx.send(file).ok();
}
}

View file

@ -29,7 +29,7 @@ pub fn rga(opt: RgaOpt) -> Result<UnboundedReceiver<File>> {
tokio::spawn(async move {
while let Ok(Some(line)) = it.next_line().await {
if let Ok(file) = File::from(opt.cwd.join(line)).await {
if let Ok(file) = File::new(opt.cwd.join(line)).await {
tx.send(file).ok();
}
}

View file

@ -51,7 +51,7 @@ fn cha(lua: &Lua) -> mlua::Result<Function> {
};
match meta {
Ok(m) => Cha::from(m).into_lua_multi(&lua),
Ok(m) => Cha(yazi_fs::cha::Cha::new(&url, m)).into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
}
})
@ -138,7 +138,7 @@ fn read_dir(lua: &Lua) -> mlua::Result<Function> {
let file = if !resolve {
yazi_fs::File::from_dummy(url, next.file_type().await.ok())
} else if let Ok(meta) = next.metadata().await {
yazi_fs::File::from_meta(url, meta).await
yazi_fs::File::from_follow(url, meta).await
} else {
yazi_fs::File::from_dummy(url, next.file_type().await.ok())
};

View file

@ -83,7 +83,7 @@ macro_rules! impl_style_shorthands {
#[macro_export]
macro_rules! impl_file_fields {
($fields:ident) => {
yazi_binding::cached_field!($fields, cha, |_, me| Ok($crate::bindings::Cha::from(me.cha)));
yazi_binding::cached_field!($fields, cha, |_, me| Ok($crate::bindings::Cha(me.cha)));
yazi_binding::cached_field!($fields, url, |_, me| Ok(yazi_binding::Url::new(me.url_owned())));
yazi_binding::cached_field!($fields, link_to, |_, me| Ok(
me.link_to.clone().map(yazi_binding::Url::new)

View file

@ -13,7 +13,7 @@ impl TabProxy {
#[inline]
pub fn reveal(target: &Url) {
emit!(Call(Cmd::args("mgr:reveal", &[target])));
emit!(Call(Cmd::args("mgr:reveal", &[target]).with("no-dummy", true)));
}
#[inline]

View file

@ -328,15 +328,15 @@ impl File {
#[inline]
async fn cha(path: &Path, follow: bool) -> io::Result<Cha> {
let meta = fs::symlink_metadata(path).await?;
Ok(if follow { Cha::new(path, meta).await } else { Cha::new_nofollow(path, meta) })
Ok(if follow { Cha::from_follow(path, meta).await } else { Cha::new(path, meta) })
}
#[inline]
async fn cha_from(entry: DirEntry, path: &Path, follow: bool) -> io::Result<Cha> {
Ok(if follow {
Cha::new(path, entry.metadata().await?).await
Cha::from_follow(path, entry.metadata().await?).await
} else {
Cha::new_nofollow(path, entry.metadata().await?)
Cha::new(path, entry.metadata().await?)
})
}
}

View file

@ -86,6 +86,10 @@ impl Data {
}
}
impl From<bool> for Data {
fn from(value: bool) -> Self { Self::Boolean(value) }
}
impl From<usize> for Data {
fn from(value: usize) -> Self { Self::Id(value.into()) }
}