diff --git a/yazi-core/src/manager/watcher.rs b/yazi-core/src/manager/watcher.rs index 1e6f4172..33ebae06 100644 --- a/yazi-core/src/manager/watcher.rs +++ b/yazi-core/src/manager/watcher.rs @@ -136,7 +136,7 @@ impl Watcher { }; let u = &file.url; - let eq = (!file.is_link() && fs::canonicalize(u).await.is_ok_and(|p| p == ***u)) + let eq = (!file.is_linkable() && fs::canonicalize(u).await.is_ok_and(|p| p == ***u)) || realname_unchecked(u, &mut cached).await.is_ok_and(|s| urn.as_urn() == s); if !eq { diff --git a/yazi-plugin/src/utils/preview.rs b/yazi-plugin/src/utils/preview.rs index 1c407894..ee08fbb6 100644 --- a/yazi-plugin/src/utils/preview.rs +++ b/yazi-plugin/src/utils/preview.rs @@ -53,7 +53,6 @@ impl Utils { area, inner, wrap: if PREVIEW.wrap == PreviewWrap::Yes { WRAP } else { WRAP_NO }, - ..Default::default() })]; emit!(Call(Cmd::new("update_peeked").with_any("lock", lock), Layer::Manager)); diff --git a/yazi-scheduler/src/file/file.rs b/yazi-scheduler/src/file/file.rs index cdb2f6a3..f08aa1f1 100644 --- a/yazi-scheduler/src/file/file.rs +++ b/yazi-scheduler/src/file/file.rs @@ -1,10 +1,10 @@ -use std::{borrow::Cow, collections::VecDeque, fs::Metadata, path::{Path, PathBuf}}; +use std::{borrow::Cow, collections::VecDeque, path::{Path, PathBuf}}; use anyhow::{Result, anyhow}; -use tokio::{fs, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc}; +use tokio::{fs::{self, DirEntry}, io::{self, ErrorKind::{AlreadyExists, NotFound}}, sync::mpsc}; use tracing::warn; use yazi_config::TASKS; -use yazi_shared::fs::{Url, calculate_size, copy_with_progress, maybe_exists, ok_or_not_found, path_relative_to}; +use yazi_shared::fs::{Cha, Url, calculate_size, copy_with_progress, maybe_exists, ok_or_not_found, path_relative_to}; use super::{FileOp, FileOpDelete, FileOpHardlink, FileOpLink, FileOpPaste, FileOpTrash}; use crate::{LOW, NORMAL, TaskOp, TaskProg}; @@ -26,7 +26,7 @@ impl File { match op { FileOp::Paste(mut task) => { ok_or_not_found(fs::remove_file(&task.to).await)?; - let mut it = copy_with_progress(&task.from, &task.to, task.meta.as_ref().unwrap()); + let mut it = copy_with_progress(&task.from, &task.to, task.cha.unwrap()); while let Some(res) = it.recv().await { match res { @@ -58,14 +58,14 @@ impl File { self.prog.send(TaskProg::Adv(task.id, 1, 0))?; } FileOp::Link(task) => { - let meta = task.meta.as_ref().unwrap(); + let cha = task.cha.unwrap(); let src = if task.resolve { match fs::read_link(&task.from).await { Ok(p) => Cow::Owned(p), Err(e) if e.kind() == NotFound => { warn!("Link task partially done: {task:?}"); - return Ok(self.prog.send(TaskProg::Adv(task.id, 1, meta.len()))?); + return Ok(self.prog.send(TaskProg::Adv(task.id, 1, cha.len))?); } Err(e) => Err(e)?, } @@ -86,7 +86,7 @@ impl File { } #[cfg(windows)] { - if meta.is_dir() { + if cha.is_dir() { fs::symlink_dir(src, &task.to).await?; } else { fs::symlink_file(src, &task.to).await?; @@ -96,10 +96,10 @@ impl File { if task.delete { fs::remove_file(&task.from).await.ok(); } - self.prog.send(TaskProg::Adv(task.id, 1, meta.len()))?; + self.prog.send(TaskProg::Adv(task.id, 1, cha.len))?; } FileOp::Hardlink(task) => { - let meta = task.meta.as_ref().unwrap(); + let cha = task.cha.unwrap(); let src = if !task.follow { Cow::Borrowed(task.from.as_path()) } else if let Ok(p) = fs::canonicalize(&task.from).await { @@ -116,7 +116,7 @@ impl File { v => v?, } - self.prog.send(TaskProg::Adv(task.id, 1, meta.len()))?; + self.prog.send(TaskProg::Adv(task.id, 1, cha.len))?; } FileOp::Delete(task) => { if let Err(e) = fs::remove_file(&task.target).await { @@ -154,19 +154,19 @@ impl File { return self.succ(task.id); } - if task.meta.is_none() { - task.meta = Some(Self::metadata(&task.from, task.follow).await?); + if task.cha.is_none() { + task.cha = Some(Self::cha(&task.from, task.follow).await?); } - let meta = task.meta.as_ref().unwrap(); - if !meta.is_dir() { + let cha = task.cha.unwrap(); + if !cha.is_dir() { let id = task.id; - self.prog.send(TaskProg::New(id, meta.len()))?; + self.prog.send(TaskProg::New(id, cha.len))?; - if meta.is_file() { - self.queue(FileOp::Paste(task), LOW).await?; - } else if meta.is_symlink() { + if cha.is_linkable() { self.queue(FileOp::Link(task.into()), NORMAL).await?; + } else { + self.queue(FileOp::Paste(task), LOW).await?; } return self.succ(id); } @@ -198,20 +198,20 @@ impl File { let mut it = continue_unless_ok!(fs::read_dir(&src).await); while let Ok(Some(entry)) = it.next_entry().await { let from = Url::from(entry.path()); - let meta = continue_unless_ok!(Self::metadata(&from, task.follow).await); + let cha = continue_unless_ok!(Self::cha_from(entry, &from, task.follow).await); - if meta.is_dir() { + if cha.is_dir() { dirs.push_back(from); continue; } let to = dest.join(from.file_name().unwrap()); - self.prog.send(TaskProg::New(task.id, meta.len()))?; + self.prog.send(TaskProg::New(task.id, cha.len))?; - if meta.is_file() { - self.queue(FileOp::Paste(task.spawn(from, to, meta)), LOW).await?; - } else if meta.is_symlink() { - self.queue(FileOp::Link(task.spawn(from, to, meta).into()), NORMAL).await?; + if cha.is_linkable() { + self.queue(FileOp::Link(task.spawn(from, to, cha).into()), NORMAL).await?; + } else { + self.queue(FileOp::Paste(task.spawn(from, to, cha)), LOW).await?; } } } @@ -220,24 +220,24 @@ impl File { pub async fn link(&self, mut task: FileOpLink) -> Result<()> { let id = task.id; - if task.meta.is_none() { - task.meta = Some(fs::symlink_metadata(&task.from).await?); + if task.cha.is_none() { + task.cha = Some(Self::cha(&task.from, false).await?); } - self.prog.send(TaskProg::New(id, task.meta.as_ref().unwrap().len()))?; + self.prog.send(TaskProg::New(id, task.cha.unwrap().len))?; self.queue(FileOp::Link(task), NORMAL).await?; self.succ(id) } pub async fn hardlink(&self, mut task: FileOpHardlink) -> Result<()> { - if task.meta.is_none() { - task.meta = Some(Self::metadata(&task.from, task.follow).await?); + if task.cha.is_none() { + task.cha = Some(Self::cha(&task.from, task.follow).await?); } - let meta = task.meta.as_ref().unwrap(); - if !meta.is_dir() { + let cha = task.cha.unwrap(); + if !cha.is_dir() { let id = task.id; - self.prog.send(TaskProg::New(id, meta.len()))?; + self.prog.send(TaskProg::New(id, cha.len))?; self.queue(FileOp::Hardlink(task), NORMAL).await?; return self.succ(id); } @@ -269,16 +269,16 @@ impl File { let mut it = continue_unless_ok!(fs::read_dir(&src).await); while let Ok(Some(entry)) = it.next_entry().await { let from = Url::from(entry.path()); - let meta = continue_unless_ok!(Self::metadata(&from, task.follow).await); + let cha = continue_unless_ok!(Self::cha_from(entry, &from, task.follow).await); - if meta.is_dir() { + if cha.is_dir() { dirs.push_back(from); continue; } let to = dest.join(from.file_name().unwrap()); - self.prog.send(TaskProg::New(task.id, meta.len()))?; - self.queue(FileOp::Hardlink(task.spawn(from, to, meta)), NORMAL).await?; + self.prog.send(TaskProg::New(task.id, cha.len))?; + self.queue(FileOp::Hardlink(task.spawn(from, to, cha)), NORMAL).await?; } } self.succ(task.id) @@ -325,13 +325,18 @@ impl File { } #[inline] - async fn metadata(path: &Path, follow: bool) -> io::Result { - if !follow { - return fs::symlink_metadata(path).await; - } + async fn cha(path: &Path, follow: bool) -> io::Result { + let meta = fs::symlink_metadata(path).await?; + Ok(if follow { Cha::new(path, meta).await } else { Cha::new_nofollow(path, meta) }) + } - let meta = fs::metadata(path).await; - if meta.is_ok() { meta } else { fs::symlink_metadata(path).await } + #[inline] + async fn cha_from(entry: DirEntry, path: &Path, follow: bool) -> io::Result { + Ok(if follow { + Cha::new(path, entry.metadata().await?).await + } else { + Cha::new_nofollow(path, entry.metadata().await?) + }) } } diff --git a/yazi-scheduler/src/file/op.rs b/yazi-scheduler/src/file/op.rs index 7f898fce..b88019d7 100644 --- a/yazi-scheduler/src/file/op.rs +++ b/yazi-scheduler/src/file/op.rs @@ -1,6 +1,4 @@ -use std::fs::Metadata; - -use yazi_shared::fs::Url; +use yazi_shared::fs::{Cha, Url}; #[derive(Debug)] pub enum FileOp { @@ -29,19 +27,19 @@ pub struct FileOpPaste { pub id: usize, pub from: Url, pub to: Url, - pub meta: Option, + pub cha: Option, pub cut: bool, pub follow: bool, pub retry: u8, } impl FileOpPaste { - pub(super) fn spawn(&self, from: Url, to: Url, meta: Metadata) -> Self { + pub(super) fn spawn(&self, from: Url, to: Url, cha: Cha) -> Self { Self { id: self.id, from, to, - meta: Some(meta), + cha: Some(cha), cut: self.cut, follow: self.follow, retry: self.retry, @@ -55,7 +53,7 @@ pub struct FileOpLink { pub id: usize, pub from: Url, pub to: Url, - pub meta: Option, + pub cha: Option, pub resolve: bool, pub relative: bool, pub delete: bool, @@ -67,7 +65,7 @@ impl From for FileOpLink { id: value.id, from: value.from, to: value.to, - meta: value.meta, + cha: value.cha, resolve: true, relative: false, delete: value.cut, @@ -81,13 +79,13 @@ pub struct FileOpHardlink { pub id: usize, pub from: Url, pub to: Url, - pub meta: Option, + pub cha: Option, pub follow: bool, } impl FileOpHardlink { - pub(super) fn spawn(&self, from: Url, to: Url, meta: Metadata) -> Self { - Self { id: self.id, from, to, meta: Some(meta), follow: self.follow } + pub(super) fn spawn(&self, from: Url, to: Url, cha: Cha) -> Self { + Self { id: self.id, from, to, cha: Some(cha), follow: self.follow } } } diff --git a/yazi-scheduler/src/scheduler.rs b/yazi-scheduler/src/scheduler.rs index 027a3e17..08f958f3 100644 --- a/yazi-scheduler/src/scheduler.rs +++ b/yazi-scheduler/src/scheduler.rs @@ -71,7 +71,7 @@ impl Scheduler { pub fn file_cut(&self, from: Url, mut to: Url, force: bool) { let mut ongoing = self.ongoing.lock(); - let id = ongoing.add(TaskKind::User, format!("Cut {:?} to {:?}", from, to)); + let id = ongoing.add(TaskKind::User, format!("Cut {from} to {to}")); if to.starts_with(&from) && to != from { self.new_and_fail(id, "Cannot cut directory into itself").ok(); @@ -99,7 +99,7 @@ impl Scheduler { if !force { to = unique_name(to).await?; } - file.paste(FileOpPaste { id, from, to, meta: None, cut: true, follow: false, retry: 0 }).await + file.paste(FileOpPaste { id, from, to, cha: None, cut: true, follow: false, retry: 0 }).await }); } @@ -116,7 +116,7 @@ impl Scheduler { if !force { to = unique_name(to).await?; } - file.paste(FileOpPaste { id, from, to, meta: None, cut: false, follow, retry: 0 }).await + file.paste(FileOpPaste { id, from, to, cha: None, cut: false, follow, retry: 0 }).await }); } @@ -129,7 +129,7 @@ impl Scheduler { to = unique_name(to).await?; } file - .link(FileOpLink { id, from, to, meta: None, resolve: false, relative, delete: false }) + .link(FileOpLink { id, from, to, cha: None, resolve: false, relative, delete: false }) .await }); } @@ -147,7 +147,7 @@ impl Scheduler { if !force { to = unique_name(to).await?; } - file.hardlink(FileOpHardlink { id, from, to, meta: None, follow }).await + file.hardlink(FileOpHardlink { id, from, to, cha: None, follow }).await }); } diff --git a/yazi-shared/src/fs/cha.rs b/yazi-shared/src/fs/cha.rs index 2e26ba2e..14a08089 100644 --- a/yazi-shared/src/fs/cha.rs +++ b/yazi-shared/src/fs/cha.rs @@ -1,8 +1,10 @@ -use std::{fs::{FileType, Metadata}, time::SystemTime}; +use std::{fs::{FileType, Metadata}, path::Path, time::SystemTime}; use bitflags::bitflags; use yazi_macro::unix_either; +use super::Urn; + bitflags! { #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct ChaKind: u8 { @@ -37,40 +39,40 @@ pub struct Cha { impl From for Cha { fn from(m: Metadata) -> Self { - let mut ck = ChaKind::empty(); + let mut kind = ChaKind::empty(); if m.is_dir() { - ck |= ChaKind::DIR; + kind |= ChaKind::DIR; } Self { - kind: ck, - len: m.len(), - atime: m.accessed().ok(), - btime: m.created().ok(), + kind, + len: m.len(), + atime: m.accessed().ok(), + btime: m.created().ok(), #[cfg(unix)] - ctime: { + 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)) }, - mtime: m.modified().ok(), + mtime: m.modified().ok(), #[cfg(unix)] - perm: { + perm: { use std::os::unix::prelude::PermissionsExt; m.permissions().mode() as _ }, #[cfg(unix)] - uid: { + uid: { use std::os::unix::fs::MetadataExt; m.uid() as _ }, #[cfg(unix)] - gid: { + gid: { use std::os::unix::fs::MetadataExt; m.gid() as _ }, #[cfg(unix)] - nlink: { + nlink: { use std::os::unix::fs::MetadataExt; m.nlink() as _ }, @@ -89,7 +91,7 @@ impl From for Cha { kind |= ChaKind::DIR; libc::S_IFDIR } else if t.is_symlink() { - kind |= ChaKind::LINK; + kind |= ChaKind::ORPHAN; libc::S_IFLNK } else if t.is_block_device() { libc::S_IFBLK @@ -124,14 +126,43 @@ impl From for Cha { impl Cha { #[inline] - pub fn dummy() -> Self { Self { kind: ChaKind::DUMMY, ..Default::default() } } + pub async fn new(path: &Path, mut meta: Metadata) -> Self { + let mut attached = ChaKind::empty(); + + if meta.is_symlink() { + meta = tokio::fs::metadata(path).await.unwrap_or(meta); + attached |= if meta.is_symlink() { ChaKind::ORPHAN } else { ChaKind::LINK }; + } + + let mut cha = Self::new_nofollow(path, meta); + cha.kind |= attached; + cha + } #[inline] - pub fn with_kind(mut self, kind: ChaKind) -> Self { - self.kind |= kind; - self + pub fn new_nofollow(path: &Path, meta: Metadata) -> Self { + let mut attached = ChaKind::empty(); + + #[cfg(unix)] + if Urn::new(path).is_hidden() { + attached |= ChaKind::HIDDEN; + } + #[cfg(windows)] + { + use std::os::windows::fs::MetadataExt; + if meta.file_attributes() & 2 != 0 { + attached |= ChaKind::HIDDEN; + } + } + + let mut cha = Self::from(meta); + cha.kind |= attached; + cha } + #[inline] + pub fn dummy() -> Self { Self { kind: ChaKind::DUMMY, ..Default::default() } } + #[inline] pub fn hits(self, c: Self) -> bool { self.len == c.len @@ -156,6 +187,9 @@ impl Cha { #[inline] pub const fn is_orphan(&self) -> bool { self.kind.contains(ChaKind::ORPHAN) } + #[inline] + pub const fn is_linkable(&self) -> bool { self.is_link() || self.is_orphan() } + #[inline] pub const fn is_dummy(&self) -> bool { self.kind.contains(ChaKind::DUMMY) } diff --git a/yazi-shared/src/fs/file.rs b/yazi-shared/src/fs/file.rs index 4726b856..84a480a2 100644 --- a/yazi-shared/src/fs/file.rs +++ b/yazi-shared/src/fs/file.rs @@ -4,7 +4,7 @@ use anyhow::Result; use tokio::fs; use super::{Urn, UrnBuf}; -use crate::{fs::{Cha, ChaKind, Url}, theme::IconCache}; +use crate::{fs::{Cha, Url}, theme::IconCache}; #[derive(Clone, Debug, Default)] pub struct File { @@ -34,34 +34,13 @@ impl File { } #[inline] - pub async fn from_meta(url: Url, mut meta: Metadata) -> Self { - let mut ck = ChaKind::empty(); - let (is_link, mut link_to) = (meta.is_symlink(), None); + pub async fn from_meta(url: Url, meta: Metadata) -> Self { + let link_to = + if meta.is_symlink() { fs::read_link(&url).await.map(Url::from).ok() } else { None }; - if is_link { - meta = fs::metadata(&url).await.unwrap_or(meta); - link_to = fs::read_link(&url).await.map(Url::from).ok(); - } + let cha = Cha::new(&url, meta).await; - if is_link && meta.is_symlink() { - ck |= ChaKind::ORPHAN; - } else if is_link { - ck |= ChaKind::LINK; - } - - #[cfg(unix)] - if url.is_hidden() { - ck |= ChaKind::HIDDEN; - } - #[cfg(windows)] - { - use std::os::windows::fs::MetadataExt; - if meta.file_attributes() & 2 != 0 { - ck |= ChaKind::HIDDEN; - } - } - - Self { url, cha: Cha::from(meta).with_kind(ck), link_to, icon: Default::default() } + Self { url, cha, link_to, icon: Default::default() } } #[inline] diff --git a/yazi-shared/src/fs/fns.rs b/yazi-shared/src/fs/fns.rs index 82b2fe77..e33c3a78 100644 --- a/yazi-shared/src/fs/fns.rs +++ b/yazi-shared/src/fs/fns.rs @@ -1,8 +1,10 @@ -use std::{borrow::Cow, collections::{HashMap, HashSet, VecDeque}, ffi::{OsStr, OsString}, fs::Metadata, path::{Path, PathBuf}}; +use std::{borrow::Cow, collections::{HashMap, HashSet, VecDeque}, ffi::{OsStr, OsString}, path::{Path, PathBuf}}; use anyhow::{Result, bail}; use tokio::{fs, io, select, sync::{mpsc, oneshot}, time}; +use super::Cha; + #[inline] pub async fn must_exists(p: impl AsRef) -> bool { fs::symlink_metadata(p).await.is_ok() } @@ -175,7 +177,7 @@ pub async fn calculate_size(path: &Path) -> u64 { pub fn copy_with_progress( from: &Path, to: &Path, - meta: &Metadata, + cha: Cha, ) -> mpsc::Receiver> { let (tx, rx) = mpsc::channel(1); let (tick_tx, mut tick_rx) = oneshot::channel(); @@ -184,17 +186,17 @@ pub fn copy_with_progress( let (from, to) = (from.to_owned(), to.to_owned()); let mut ft = std::fs::FileTimes::new(); - meta.accessed().map(|t| ft = ft.set_accessed(t)).ok(); - meta.modified().map(|t| ft = ft.set_modified(t)).ok(); + cha.atime.map(|t| ft = ft.set_accessed(t)); + cha.mtime.map(|t| ft = ft.set_modified(t)); #[cfg(target_os = "macos")] { use std::os::macos::fs::FileTimesExt; - meta.created().map(|t| ft = ft.set_created(t)).ok(); + cha.btime.map(|t| ft = ft.set_created(t)); } #[cfg(windows)] { use std::os::windows::fs::FileTimesExt; - meta.created().map(|t| ft = ft.set_created(t)).ok(); + cha.btime.map(|t| ft = ft.set_created(t)); } async move { diff --git a/yazi-shared/src/fs/url.rs b/yazi-shared/src/fs/url.rs index 6d8264ca..28114467 100644 --- a/yazi-shared/src/fs/url.rs +++ b/yazi-shared/src/fs/url.rs @@ -170,10 +170,6 @@ impl Url { #[inline] pub fn pair(&self) -> Option<(Self, UrnBuf)> { Some((self.parent_url()?, self.loc.urn_owned())) } - #[cfg(unix)] - #[inline] - pub fn is_hidden(&self) -> bool { self.loc.urn().is_hidden() } - #[inline] pub fn rebase(&self, parent: &Path) -> Self { debug_assert!(self.is_regular());