refactor: replace Metadata with Cha in the file scheduler to improve interoperability with yazi-fs (#1785)

This commit is contained in:
三咲雅 · Misaki Masa 2024-10-15 20:38:34 +08:00 committed by GitHub
parent 3e8ac856cf
commit 097e5abb82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 129 additions and 116 deletions

View file

@ -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 {

View file

@ -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));

View file

@ -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<Metadata> {
if !follow {
return fs::symlink_metadata(path).await;
}
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) })
}
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<Cha> {
Ok(if follow {
Cha::new(path, entry.metadata().await?).await
} else {
Cha::new_nofollow(path, entry.metadata().await?)
})
}
}

View file

@ -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<Metadata>,
pub cha: Option<Cha>,
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<Metadata>,
pub cha: Option<Cha>,
pub resolve: bool,
pub relative: bool,
pub delete: bool,
@ -67,7 +65,7 @@ impl From<FileOpPaste> 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<Metadata>,
pub cha: Option<Cha>,
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 }
}
}

View file

@ -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
});
}

View file

@ -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<Metadata> 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<FileType> 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<FileType> 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) }

View file

@ -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]

View file

@ -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<Path>) -> 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<Result<u64, io::Error>> {
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 {

View file

@ -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());