feat: size calculator for remote file systems

This commit is contained in:
sxyazi 2025-09-15 21:12:12 +08:00
parent a3fc9a0ec7
commit 0abfc3c5f1
No known key found for this signature in database
28 changed files with 476 additions and 348 deletions

View file

@ -1,7 +1,7 @@
use std::{ops::Deref, time::{Duration, SystemTime, UNIX_EPOCH}};
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, UserData, UserDataFields, UserDataMethods};
use yazi_fs::cha::ChaKind;
use yazi_fs::cha::{ChaKind, ChaMode};
#[derive(Clone, Copy, FromLua)]
pub struct Cha(pub yazi_fs::cha::Cha);
@ -25,11 +25,15 @@ impl Cha {
lua.globals().raw_set(
"Cha",
lua.create_function(|lua, t: Table| {
let kind =
ChaKind::from_bits(t.raw_get("kind")?).ok_or_else(|| "Invalid kind".into_lua_err())?;
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())?;
Self(yazi_fs::cha::Cha {
kind,
mode,
len: t.raw_get("len").unwrap_or_default(),
atime: parse_time(t.raw_get("atime").ok())?,
btime: parse_time(t.raw_get("btime").ok())?,
@ -37,8 +41,6 @@ impl Cha {
ctime: parse_time(t.raw_get("ctime").ok())?,
mtime: parse_time(t.raw_get("mtime").ok())?,
#[cfg(unix)]
mode: t.raw_get("mode").unwrap_or_default(),
#[cfg(unix)]
dev: t.raw_get("dev").unwrap_or_default(),
#[cfg(unix)]
uid: t.raw_get("uid").unwrap_or_default(),
@ -55,6 +57,7 @@ impl Cha {
impl UserData for Cha {
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("mode", |_, me| Ok(me.mode.bits()));
fields.add_field_method_get("is_dir", |_, me| Ok(me.is_dir()));
fields.add_field_method_get("is_hidden", |_, me| Ok(me.is_hidden()));
fields.add_field_method_get("is_link", |_, me| Ok(me.is_link()));
@ -70,7 +73,6 @@ impl UserData for Cha {
#[cfg(unix)]
{
use std::ops::Not;
fields.add_field_method_get("mode", |_, me| Ok(me.is_dummy().not().then_some(me.mode)));
fields.add_field_method_get("dev", |_, me| Ok(me.is_dummy().not().then_some(me.dev)));
fields.add_field_method_get("uid", |_, me| Ok(me.is_dummy().not().then_some(me.uid)));
fields.add_field_method_get("gid", |_, me| Ok(me.is_dummy().not().then_some(me.gid)));
@ -94,12 +96,12 @@ impl UserData for Cha {
}
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("perm", |_, _me, ()| {
methods.add_method("perm", |lua, _me, ()| {
Ok(
#[cfg(unix)]
Some(yazi_fs::permissions(_me.mode, _me.is_dummy())),
lua.create_string(_me.mode.permissions(_me.is_dummy())),
#[cfg(windows)]
None::<String>,
Ok(mlua::Value::Nil),
)
});
}

View file

@ -3,7 +3,7 @@ use std::{mem, ops::Deref, sync::atomic::{AtomicU64, Ordering}, time::UNIX_EPOCH
use anyhow::Result;
use hashbrown::HashMap;
use parking_lot::RwLock;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufWriter};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter};
use yazi_boot::BOOT;
use yazi_fs::provider::local::{Gate, Local};
use yazi_shared::{RoCell, timestamp_us};
@ -79,7 +79,7 @@ impl State {
}
async fn load(&self) -> Result<()> {
let mut file = Local::open(BOOT.state_dir.join(".dds")).await?.reader();
let mut file = BufReader::new(Local::open(BOOT.state_dir.join(".dds")).await?);
let mut buf = String::new();
let mut inner = HashMap::new();

View file

@ -1,14 +1,15 @@
use std::{fs::{FileType, Metadata}, time::SystemTime};
use std::{fs::{FileType, Metadata}, ops::Deref, time::SystemTime};
use yazi_macro::{unix_either, win_either};
use yazi_shared::url::{Url, UrlBuf};
use super::ChaKind;
use crate::provider;
use crate::{cha::ChaMode, provider};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Cha {
pub kind: ChaKind,
pub mode: ChaMode,
pub len: u64,
pub atime: Option<SystemTime>,
pub btime: Option<SystemTime>,
@ -16,21 +17,26 @@ pub struct Cha {
pub ctime: Option<SystemTime>,
pub mtime: Option<SystemTime>,
#[cfg(unix)]
pub mode: libc::mode_t,
#[cfg(unix)]
pub dev: libc::dev_t,
#[cfg(unix)]
pub uid: libc::uid_t,
pub uid: u32,
#[cfg(unix)]
pub gid: libc::gid_t,
pub gid: u32,
#[cfg(unix)]
pub nlink: libc::nlink_t,
pub nlink: u64,
}
impl Deref for Cha {
type Target = ChaMode;
fn deref(&self) -> &Self::Target { &self.mode }
}
impl Default for Cha {
fn default() -> Self {
Self {
kind: ChaKind::DUMMY,
mode: ChaMode::empty(),
len: 0,
atime: None,
btime: None,
@ -38,8 +44,6 @@ impl Default for Cha {
ctime: None,
mtime: None,
#[cfg(unix)]
mode: 0,
#[cfg(unix)]
dev: 0,
#[cfg(unix)]
uid: 0,
@ -54,7 +58,7 @@ impl Default for Cha {
impl Cha {
#[inline]
pub fn new<'a>(url: impl Into<Url<'a>>, meta: Metadata) -> Self {
Self::from_just_meta(&meta).attach(ChaKind::hidden(url, &meta))
Self::from_bare_meta(&meta).attach(ChaKind::hidden(url, &meta))
}
#[inline]
@ -75,12 +79,12 @@ impl Cha {
attached |= ChaKind::ORPHAN;
}
Self::from_just_meta(&meta).attach(attached)
Self::from_bare_meta(&meta).attach(attached)
}
#[inline]
pub fn from_dummy(_url: &UrlBuf, ft: Option<FileType>) -> Self {
let mut me = ft.map(Self::from_half_ft).unwrap_or_default();
let mut me = ft.map(Self::from_bare_ft).unwrap_or_default();
#[cfg(unix)]
if _url.urn().is_hidden() {
me.kind |= ChaKind::HIDDEN;
@ -88,61 +92,74 @@ impl Cha {
me
}
fn from_half_ft(ft: FileType) -> Self {
let mut kind = ChaKind::DUMMY;
fn from_bare_ft(ft: FileType) -> Self {
#[cfg(unix)]
let mode = {
use std::os::unix::fs::FileTypeExt;
if ft.is_dir() {
kind |= ChaKind::DIR;
libc::S_IFDIR
if ft.is_file() {
ChaMode::T_FILE
} else if ft.is_dir() {
ChaMode::T_DIR
} else if ft.is_symlink() {
kind |= ChaKind::LINK;
libc::S_IFLNK
ChaMode::T_LINK
} else if ft.is_block_device() {
libc::S_IFBLK
ChaMode::T_BLOCK
} else if ft.is_char_device() {
libc::S_IFCHR
} else if ft.is_fifo() {
libc::S_IFIFO
ChaMode::T_CHAR
} else if ft.is_socket() {
libc::S_IFSOCK
ChaMode::T_SOCK
} else if ft.is_fifo() {
ChaMode::T_FIFO
} else {
0
ChaMode::empty()
}
};
#[cfg(windows)]
{
if ft.is_dir() {
kind |= ChaKind::DIR;
let mode = {
if ft.is_file() {
ChaMode::T_FILE
} else if ft.is_dir() {
ChaMode::T_DIR
} else if ft.is_symlink() {
kind |= ChaKind::LINK;
ChaMode::T_LINK
} else {
ChaMode::empty()
}
}
};
Self {
kind,
#[cfg(unix)]
mode,
..Default::default()
}
let kind = ChaKind::DUMMY
| if mode.contains(ChaMode::T_LINK) { ChaKind::LINK } else { ChaKind::empty() };
Self { kind, mode, ..Default::default() }
}
fn from_just_meta(m: &Metadata) -> Self {
fn from_bare_meta(m: &Metadata) -> Self {
#[cfg(unix)]
use std::{os::unix::{fs::MetadataExt, prelude::PermissionsExt}, time::{Duration, UNIX_EPOCH}};
use std::{os::unix::fs::MetadataExt, time::{Duration, UNIX_EPOCH}};
let mut kind = ChaKind::empty();
if m.is_dir() {
kind |= ChaKind::DIR;
} else if m.is_symlink() {
kind |= ChaKind::LINK;
}
#[cfg(unix)]
let mode = {
use std::os::unix::fs::PermissionsExt;
ChaMode::from_bits_retain(m.permissions().mode() as u16)
};
#[cfg(windows)]
let mode = {
if m.is_file() {
ChaMode::T_FILE
} else if m.is_dir() {
ChaMode::T_DIR
} else if m.is_symlink() {
ChaMode::T_LINK
} else {
ChaMode::empty()
}
};
Self {
kind,
kind: ChaKind::empty(),
mode,
len: m.len(),
atime: m.accessed().ok(),
btime: m.created().ok(),
@ -150,8 +167,6 @@ impl Cha {
ctime: UNIX_EPOCH.checked_add(Duration::new(m.ctime() as u64, m.ctime_nsec() as u32)),
mtime: m.modified().ok(),
#[cfg(unix)]
mode: m.permissions().mode() as _,
#[cfg(unix)]
dev: m.dev() as _,
#[cfg(unix)]
uid: m.uid() as _,
@ -180,9 +195,6 @@ impl Cha {
}
impl Cha {
#[inline]
pub const fn is_dir(&self) -> bool { self.kind.contains(ChaKind::DIR) }
#[inline]
pub const fn is_hidden(&self) -> bool {
win_either!(
@ -191,38 +203,9 @@ impl Cha {
)
}
#[inline]
pub const fn is_link(&self) -> bool { self.kind.contains(ChaKind::LINK) }
#[inline]
pub const fn is_orphan(&self) -> bool { self.kind.contains(ChaKind::ORPHAN) }
#[inline]
pub const fn is_dummy(&self) -> bool { self.kind.contains(ChaKind::DUMMY) }
#[inline]
pub const fn is_block(&self) -> bool {
unix_either!(self.mode & libc::S_IFMT == libc::S_IFBLK, false)
}
#[inline]
pub const fn is_char(&self) -> bool {
unix_either!(self.mode & libc::S_IFMT == libc::S_IFCHR, false)
}
#[inline]
pub const fn is_fifo(&self) -> bool {
unix_either!(self.mode & libc::S_IFMT == libc::S_IFIFO, false)
}
#[inline]
pub const fn is_sock(&self) -> bool {
unix_either!(self.mode & libc::S_IFMT == libc::S_IFSOCK, false)
}
#[inline]
pub const fn is_exec(&self) -> bool { unix_either!(self.mode & libc::S_IXUSR != 0, false) }
#[inline]
pub const fn is_sticky(&self) -> bool { unix_either!(self.mode & libc::S_ISVTX != 0, false) }
}

View file

@ -6,15 +6,13 @@ use yazi_shared::url::Url;
bitflags! {
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ChaKind: u8 {
const DIR = 0b00000001;
const HIDDEN = 0b0000_0001;
const SYSTEM = 0b0000_0010;
const HIDDEN = 0b00000010;
const LINK = 0b00000100;
const ORPHAN = 0b00001000;
const LINK = 0b0000_0100;
const ORPHAN = 0b0000_1000;
const DUMMY = 0b00010000;
#[cfg(windows)]
const SYSTEM = 0b00100000;
const DUMMY = 0b0001_0000;
}
}

View file

@ -1 +1 @@
yazi_macro::mod_flat!(cha kind);
yazi_macro::mod_flat!(cha kind mode);

120
yazi-fs/src/cha/mode.rs Normal file
View file

@ -0,0 +1,120 @@
use bitflags::bitflags;
bitflags! {
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ChaMode: u16 {
// File type
const T_MASK = 0b1111_0000_0000_0000;
const T_SOCK = 0b1100_0000_0000_0000;
const T_LINK = 0b1010_0000_0000_0000;
const T_FILE = 0b1000_0000_0000_0000;
const T_BLOCK = 0b0110_0000_0000_0000;
const T_DIR = 0b0100_0000_0000_0000;
const T_CHAR = 0b0010_0000_0000_0000;
const T_FIFO = 0b0001_0000_0000_0000;
// Special
const S_SUID = 0b0000_1000_0000_0000;
const S_SGID = 0b0000_0100_0000_0000;
const S_STICKY = 0b0000_0010_0000_0000;
// User
const U_MASK = 0b0000_0001_1100_0000;
const U_READ = 0b0000_0001_0000_0000;
const U_WRITE = 0b0000_0000_1000_0000;
const U_EXEC = 0b0000_0000_0100_0000;
// Group
const G_MASK = 0b0000_0000_0011_1000;
const G_READ = 0b0000_0000_0010_0000;
const G_WRITE = 0b0000_0000_0001_0000;
const G_EXEC = 0b0000_0000_0000_1000;
// Others
const O_MASK = 0b0000_0000_0000_0111;
const O_READ = 0b0000_0000_0000_0100;
const O_WRITE = 0b0000_0000_0000_0010;
const O_EXEC = 0b0000_0000_0000_0001;
}
}
impl ChaMode {
#[inline]
pub fn r#type(self) -> Self { self & Self::T_MASK }
// Convert a file mode to a string representation
#[cfg(unix)]
#[allow(clippy::collapsible_else_if)]
pub fn permissions(self, dummy: bool) -> [u8; 10] {
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',
_ => b'-',
};
if dummy {
return s;
}
// User
s[1] = if self.contains(Self::U_READ) { b'r' } else { b'-' };
s[2] = if self.contains(Self::U_WRITE) { b'w' } else { b'-' };
s[3] = if self.contains(Self::U_EXEC) {
if self.contains(Self::S_SUID) { b's' } else { b'x' }
} else {
if self.contains(Self::S_SUID) { b'S' } else { b'-' }
};
// Group
s[4] = if self.contains(Self::G_READ) { b'r' } else { b'-' };
s[5] = if self.contains(Self::G_WRITE) { b'w' } else { b'-' };
s[6] = if self.contains(Self::G_EXEC) {
if self.contains(Self::S_SGID) { b's' } else { b'x' }
} else {
if self.contains(Self::S_SGID) { b'S' } else { b'-' }
};
// Others
s[7] = if self.contains(Self::O_READ) { b'r' } else { b'-' };
s[8] = if self.contains(Self::O_WRITE) { b'w' } else { b'-' };
s[9] = if self.contains(Self::O_EXEC) {
if self.contains(Self::S_STICKY) { b't' } else { b'x' }
} else {
if self.contains(Self::S_STICKY) { b'T' } else { b'-' }
};
s
}
}
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) }
#[inline]
pub const fn is_sticky(&self) -> bool { self.contains(Self::S_STICKY) }
}

View file

@ -94,59 +94,6 @@ pub async fn remove_dir_clean(dir: &UrlBuf) {
provider::remove_dir(dir).await.ok();
}
// Convert a file mode to a string representation
#[cfg(unix)]
#[allow(clippy::collapsible_else_if)]
pub fn permissions(m: libc::mode_t, dummy: bool) -> String {
use libc::{S_IFBLK, S_IFCHR, S_IFDIR, S_IFIFO, S_IFLNK, S_IFMT, S_IFSOCK, S_IRGRP, S_IROTH, S_IRUSR, S_ISGID, S_ISUID, S_ISVTX, S_IWGRP, S_IWOTH, S_IWUSR, S_IXGRP, S_IXOTH, S_IXUSR};
let mut s = String::with_capacity(10);
// Filetype
s.push(match m & S_IFMT {
S_IFBLK => 'b',
S_IFCHR => 'c',
S_IFDIR => 'd',
S_IFIFO => 'p',
S_IFLNK => 'l',
S_IFSOCK => 's',
_ => '-',
});
if dummy {
s.push_str("?????????");
return s;
}
// Owner
s.push(if m & S_IRUSR != 0 { 'r' } else { '-' });
s.push(if m & S_IWUSR != 0 { 'w' } else { '-' });
s.push(if m & S_IXUSR != 0 {
if m & S_ISUID != 0 { 's' } else { 'x' }
} else {
if m & S_ISUID != 0 { 'S' } else { '-' }
});
// Group
s.push(if m & S_IRGRP != 0 { 'r' } else { '-' });
s.push(if m & S_IWGRP != 0 { 'w' } else { '-' });
s.push(if m & S_IXGRP != 0 {
if m & S_ISGID != 0 { 's' } else { 'x' }
} else {
if m & S_ISGID != 0 { 'S' } else { '-' }
});
// Other
s.push(if m & S_IROTH != 0 { 'r' } else { '-' });
s.push(if m & S_IWOTH != 0 { 'w' } else { '-' });
s.push(if m & S_IXOTH != 0 {
if m & S_ISVTX != 0 { 't' } else { 'x' }
} else {
if m & S_ISVTX != 0 { 'T' } else { '-' }
});
s
}
// Find the max common root in a list of urls
// e.g. /a/b/c, /a/b/d -> /a/b
// /aa/bb/cc, /aa/dd/ee -> /aa

View file

@ -2,7 +2,7 @@
yazi_macro::mod_pub!(cha mounts provider path);
yazi_macro::mod_flat!(calculator cwd file files filter fns op sorter sorting stage xdg);
yazi_macro::mod_flat!(cwd file files filter fns op sorter sorting stage xdg);
pub fn init() {
CWD.init(<_>::default());

View file

@ -1,9 +0,0 @@
// --- BufRead
pub trait BufRead: tokio::io::AsyncRead + Send {}
impl<T: tokio::io::AsyncRead + Send> BufRead for T {}
// --- BufReadSync
pub trait BufReadSync: std::io::BufRead + std::io::Seek + Send {}
impl<T: std::io::BufRead + std::io::Seek + Send> BufReadSync for T {}

View file

@ -0,0 +1,81 @@
use std::{collections::VecDeque, io, time::{Duration, Instant}};
use yazi_shared::{Either, url::{Url, UrlBuf}};
use crate::provider::{self, ReadDir};
pub enum SizeCalculator {
File(Option<u64>),
Dir(VecDeque<Either<UrlBuf, ReadDir>>),
}
impl SizeCalculator {
pub async fn new<'a, U>(url: U) -> io::Result<Self>
where
U: Into<Url<'a>>,
{
let url: Url = url.into();
let meta = provider::symlink_metadata(url).await?;
Ok(if meta.is_dir() {
Self::Dir(VecDeque::from([Either::Left(url.to_owned())]))
} else {
Self::File(Some(meta.len()))
})
}
pub async fn total<'a, U>(url: U) -> io::Result<u64>
where
U: Into<Url<'a>>,
{
let mut it = Self::new(url).await?;
let mut total = 0;
while let Some(n) = it.next().await? {
total += n;
}
Ok(total)
}
pub async fn next(&mut self) -> io::Result<Option<u64>> {
Ok(match self {
Self::File(size) => size.take(),
Self::Dir(buf) => Self::next_chunk(buf).await,
})
}
async fn next_chunk(buf: &mut VecDeque<Either<UrlBuf, ReadDir>>) -> Option<u64> {
let (mut i, mut size, now) = (0, 0, Instant::now());
macro_rules! pop_and_continue {
() => {{
buf.pop_front();
if buf.is_empty() {
return Some(size);
}
continue;
}};
}
while i < 2000 && now.elapsed() < Duration::from_millis(100) {
i += 1;
let front = buf.front_mut()?;
if let Either::Left(p) = front {
*front = match provider::read_dir(p).await {
Ok(it) => Either::Right(it),
Err(_) => pop_and_continue!(),
};
}
let Ok(Some(ent)) = front.right_mut()?.next_entry().await else {
pop_and_continue!();
};
let Ok(ft) = ent.file_type().await else { continue };
if ft.is_dir() {
buf.push_back(Either::Left(ent.url()));
} else if let Ok(meta) = ent.metadata().await {
size += meta.len();
}
}
Some(size)
}
}

View file

@ -33,36 +33,3 @@ impl DirEntry {
}
}
}
// --- DirEntrySync
pub enum DirEntrySync {
Local(super::local::DirEntrySync),
}
impl DirEntrySync {
#[must_use]
pub fn url(&self) -> UrlBuf {
match self {
Self::Local(local) => local.url(),
}
}
#[must_use]
pub fn file_name(&self) -> OsString {
match self {
Self::Local(local) => local.file_name(),
}
}
pub fn metadata(&self) -> io::Result<std::fs::Metadata> {
match self {
Self::Local(local) => local.metadata(),
}
}
pub fn file_type(&self) -> io::Result<std::fs::FileType> {
match self {
Self::Local(local) => local.file_type(),
}
}
}

View file

@ -1,11 +1,9 @@
use std::{collections::VecDeque, future::poll_fn, io, mem, pin::Pin, task::{Poll, ready}, time::{Duration, Instant}};
use std::{collections::VecDeque, future::poll_fn, io, mem, path::{Path, PathBuf}, pin::Pin, task::{Poll, ready}, time::{Duration, Instant}};
use tokio::task::JoinHandle;
use yazi_shared::{Either, url::UrlBuf};
use yazi_shared::Either;
use crate::provider::{self, ReadDirSync};
type Task = Either<UrlBuf, ReadDirSync>;
type Task = Either<PathBuf, std::fs::ReadDir>;
pub enum SizeCalculator {
Idle((VecDeque<Task>, Option<u64>)),
@ -13,23 +11,23 @@ pub enum SizeCalculator {
}
impl SizeCalculator {
pub async fn new(url: &UrlBuf) -> io::Result<Self> {
let u = url.to_owned();
pub async fn new(path: &Path) -> io::Result<Self> {
let p = path.to_owned();
tokio::task::spawn_blocking(move || {
let meta = provider::symlink_metadata_sync(&u)?;
let meta = std::fs::symlink_metadata(&p)?;
if !meta.is_dir() {
return Ok(Self::Idle((VecDeque::new(), Some(meta.len()))));
}
let mut buf = VecDeque::from([Either::Right(provider::read_dir_sync(&u)?)]);
let mut buf = VecDeque::from([Either::Right(std::fs::read_dir(&p)?)]);
let size = Self::next_chunk(&mut buf);
Ok(Self::Idle((buf, size)))
})
.await?
}
pub async fn total(url: &UrlBuf) -> io::Result<u64> {
let mut it = Self::new(url).await?;
pub async fn total(path: &Path) -> io::Result<u64> {
let mut it = Self::new(path).await?;
let mut total = 0;
while let Some(n) = it.next().await? {
total += n;
@ -63,7 +61,7 @@ impl SizeCalculator {
.await
}
fn next_chunk(buf: &mut VecDeque<Either<UrlBuf, ReadDirSync>>) -> Option<u64> {
fn next_chunk(buf: &mut VecDeque<Either<PathBuf, std::fs::ReadDir>>) -> Option<u64> {
let (mut i, mut size, now) = (0, 0, Instant::now());
macro_rules! pop_and_continue {
() => {{
@ -79,8 +77,8 @@ impl SizeCalculator {
i += 1;
let front = buf.front_mut()?;
if let Either::Left(u) = front {
*front = match provider::read_dir_sync(u) {
if let Either::Left(p) = front {
*front = match std::fs::read_dir(p) {
Ok(it) => Either::Right(it),
Err(_) => pop_and_continue!(),
};
@ -93,7 +91,7 @@ impl SizeCalculator {
let Ok(ent) = next else { continue };
let Ok(ft) = ent.file_type() else { continue };
if ft.is_dir() {
buf.push_back(Either::Left(ent.url()));
buf.push_back(Either::Left(ent.path()));
} else if let Ok(meta) = ent.metadata() {
size += meta.len();
}

View file

@ -22,25 +22,3 @@ impl DirEntry {
#[must_use]
pub fn url(&self) -> UrlBuf { self.0.path().into() }
}
// --- DirEntrySync
pub struct DirEntrySync(std::fs::DirEntry);
impl Deref for DirEntrySync {
type Target = std::fs::DirEntry;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl From<std::fs::DirEntry> for DirEntrySync {
fn from(value: std::fs::DirEntry) -> Self { Self(value) }
}
impl From<DirEntrySync> for crate::provider::DirEntrySync {
fn from(value: DirEntrySync) -> Self { Self::Local(value) }
}
impl DirEntrySync {
#[must_use]
pub fn url(&self) -> UrlBuf { self.0.path().into() }
}

View file

@ -1,6 +1,6 @@
use std::{io, path::{Path, PathBuf}};
use crate::{cha::Cha, provider::local::{Gate, ReadDir, ReadDirSync, RwFile}};
use crate::{cha::Cha, provider::local::{Gate, ReadDir, RwFile}};
pub struct Local;
@ -145,14 +145,6 @@ impl Local {
tokio::fs::read_dir(path).await.map(Into::into)
}
#[inline]
pub fn read_dir_sync<P>(path: P) -> io::Result<ReadDirSync>
where
P: AsRef<Path>,
{
std::fs::read_dir(path).map(Into::into)
}
#[inline]
pub async fn read_link<P>(path: P) -> io::Result<PathBuf>
where
@ -261,14 +253,6 @@ impl Local {
tokio::fs::symlink_metadata(path).await
}
#[inline]
pub fn symlink_metadata_sync<P>(path: P) -> io::Result<std::fs::Metadata>
where
P: AsRef<Path>,
{
std::fs::symlink_metadata(path)
}
pub async fn trash<P>(path: P) -> io::Result<()>
where
P: AsRef<Path>,

View file

@ -1 +1 @@
yazi_macro::mod_flat!(casefold dir_entry gate identical local read_dir rw_file);
yazi_macro::mod_flat!(calculator casefold dir_entry gate identical local read_dir rw_file);

View file

@ -1,6 +1,6 @@
use std::io;
use super::{DirEntry, DirEntrySync};
use super::DirEntry;
pub struct ReadDir(tokio::fs::ReadDir);
@ -17,22 +17,3 @@ impl ReadDir {
self.0.next_entry().await.map(|entry| entry.map(Into::into))
}
}
// --- ReadDirSync
pub struct ReadDirSync(std::fs::ReadDir);
impl From<std::fs::ReadDir> for ReadDirSync {
fn from(value: std::fs::ReadDir) -> Self { Self(value) }
}
impl From<ReadDirSync> for crate::provider::ReadDirSync {
fn from(value: ReadDirSync) -> Self { Self::Local(value) }
}
impl Iterator for ReadDirSync {
type Item = io::Result<DirEntrySync>;
fn next(&mut self) -> Option<io::Result<DirEntrySync>> {
self.0.next().map(|result| result.map(Into::into))
}
}

View file

@ -1,3 +1,7 @@
use std::{pin::Pin, task::Poll};
use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite};
pub struct RwFile(tokio::fs::File);
impl From<tokio::fs::File> for RwFile {
@ -8,11 +12,67 @@ impl From<RwFile> for crate::provider::RwFile {
fn from(value: RwFile) -> Self { Self::Local(value) }
}
impl From<tokio::fs::File> for crate::provider::RwFile {
fn from(value: tokio::fs::File) -> Self { RwFile(value).into() }
impl AsyncRead for RwFile {
#[inline]
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> Poll<std::io::Result<()>> {
Pin::new(&mut self.0).poll_read(cx, buf)
}
}
impl RwFile {
impl AsyncSeek for RwFile {
#[inline]
pub fn reader(self) -> tokio::io::BufReader<tokio::fs::File> { tokio::io::BufReader::new(self.0) }
fn start_seek(mut self: Pin<&mut Self>, position: std::io::SeekFrom) -> std::io::Result<()> {
Pin::new(&mut self.0).start_seek(position)
}
#[inline]
fn poll_complete(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<std::io::Result<u64>> {
Pin::new(&mut self.0).poll_complete(cx)
}
}
impl AsyncWrite for RwFile {
#[inline]
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, std::io::Error>> {
Pin::new(&mut self.0).poll_write(cx, buf)
}
#[inline]
fn poll_flush(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Result<(), std::io::Error>> {
Pin::new(&mut self.0).poll_flush(cx)
}
#[inline]
fn poll_shutdown(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Result<(), std::io::Error>> {
Pin::new(&mut self.0).poll_shutdown(cx)
}
#[inline]
fn poll_write_vectored(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
bufs: &[std::io::IoSlice<'_>],
) -> Poll<Result<usize, std::io::Error>> {
Pin::new(&mut self.0).poll_write_vectored(cx, bufs)
}
#[inline]
fn is_write_vectored(&self) -> bool { self.0.is_write_vectored() }
}

View file

@ -1,3 +1,3 @@
yazi_macro::mod_pub!(local sftp);
yazi_macro::mod_flat!(buffer dir_entry provider read_dir rw_file);
yazi_macro::mod_flat!(calculator dir_entry provider read_dir rw_file);

View file

@ -2,7 +2,7 @@ use std::{io, path::{Path, PathBuf}};
use yazi_shared::url::{Url, UrlBuf};
use crate::{cha::Cha, provider::{ReadDir, ReadDirSync, RwFile, local::{self, Local}}};
use crate::{cha::Cha, provider::{ReadDir, RwFile, local::{self, Local}}};
#[inline]
pub fn cache<'a, U>(url: U) -> Option<PathBuf>
@ -12,6 +12,19 @@ where
if let Some(path) = url.into().as_path() { Local::cache(path) } else { None }
}
#[inline]
pub async fn calculate<'a, U>(url: U) -> io::Result<u64>
where
U: Into<Url<'a>>,
{
let url: Url = url.into();
if let Some(path) = url.as_path() {
local::SizeCalculator::total(path).await
} else {
super::SizeCalculator::total(url).await
}
}
#[inline]
pub async fn canonicalize<'a, U>(url: U) -> io::Result<UrlBuf>
where
@ -156,18 +169,6 @@ where
}
}
#[inline]
pub fn read_dir_sync<'a, U>(url: U) -> io::Result<ReadDirSync>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() {
Local::read_dir_sync(path).map(Into::into)
} else {
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
}
}
#[inline]
pub async fn read_link<'a, U>(url: U) -> io::Result<PathBuf>
where
@ -278,18 +279,6 @@ where
}
}
#[inline]
pub fn symlink_metadata_sync<'a, U>(url: U) -> io::Result<std::fs::Metadata>
where
U: Into<Url<'a>>,
{
if let Some(path) = url.into().as_path() {
Local::symlink_metadata_sync(path)
} else {
Err(io::Error::new(io::ErrorKind::Unsupported, "Unsupported filesystem"))
}
}
#[inline]
pub async fn trash<'a, U>(url: U) -> io::Result<()>
where

View file

@ -1,6 +1,6 @@
use std::io;
use super::{DirEntry, DirEntrySync};
use super::DirEntry;
pub enum ReadDir {
Local(super::local::ReadDir),
@ -13,18 +13,3 @@ impl ReadDir {
}
}
}
// --- ReadDirSync
pub enum ReadDirSync {
Local(super::local::ReadDirSync),
}
impl Iterator for ReadDirSync {
type Item = io::Result<DirEntrySync>;
fn next(&mut self) -> Option<io::Result<DirEntrySync>> {
match self {
Self::Local(local) => local.next().map(|result| result.map(Into::into)),
}
}
}

View file

@ -1,13 +1,71 @@
use crate::provider::BufRead;
use std::pin::Pin;
use tokio::io::{AsyncRead, AsyncWrite};
pub enum RwFile {
Local(super::local::RwFile),
}
impl RwFile {
pub fn reader(self) -> Box<dyn BufRead> {
match self {
Self::Local(local) => Box::new(local.reader()),
impl AsyncRead for RwFile {
#[inline]
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
match &mut *self {
Self::Local(f) => Pin::new(f).poll_read(cx, buf),
}
}
}
impl AsyncWrite for RwFile {
#[inline]
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
buf: &[u8],
) -> std::task::Poll<Result<usize, std::io::Error>> {
match &mut *self {
Self::Local(f) => Pin::new(f).poll_write(cx, buf),
}
}
#[inline]
fn poll_flush(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
match &mut *self {
Self::Local(f) => Pin::new(f).poll_flush(cx),
}
}
#[inline]
fn poll_shutdown(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), std::io::Error>> {
match &mut *self {
Self::Local(f) => Pin::new(f).poll_shutdown(cx),
}
}
#[inline]
fn poll_write_vectored(
mut self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
bufs: &[std::io::IoSlice<'_>],
) -> std::task::Poll<Result<usize, std::io::Error>> {
match &mut *self {
Self::Local(f) => Pin::new(f).poll_write_vectored(cx, bufs),
}
}
#[inline]
fn is_write_vectored(&self) -> bool {
match self {
Self::Local(f) => f.is_write_vectored(),
}
}
}

View file

@ -84,13 +84,6 @@ impl Sftp {
todo!()
}
pub fn read_dir_sync<P>(path: P) -> io::Result<()>
where
P: AsRef<Path>,
{
todo!()
}
pub async fn read_link<P>(path: P) -> io::Result<PathBuf>
where
P: AsRef<Path>,
@ -166,13 +159,6 @@ impl Sftp {
todo!()
}
pub fn symlink_metadata_sync<P>(path: P) -> io::Result<std::fs::Metadata>
where
P: AsRef<Path>,
{
todo!()
}
pub async fn trash<P>(path: P) -> io::Result<()>
where
P: AsRef<Path>,

View file

@ -16,7 +16,7 @@ function M:peek(job)
for _, f in ipairs(files) do
local icon = File({
url = Url(f.path),
cha = Cha { kind = f.attr:sub(1, 1) == "D" and 1 or 0 },
cha = Cha { mode = tonumber(f.attr:sub(1, 1) == "D" and "40700" or "100644", 8) },
}):icon()
if f.size > 0 then

View file

@ -1,12 +1,20 @@
use mlua::{IntoLuaMulti, UserData, UserDataMethods, Value};
use yazi_binding::Error;
pub struct SizeCalculator(pub yazi_fs::SizeCalculator);
pub enum SizeCalculator {
Local(yazi_fs::provider::local::SizeCalculator),
Remote(yazi_fs::provider::SizeCalculator),
}
impl UserData for SizeCalculator {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_async_method_mut("recv", |lua, mut me, ()| async move {
match me.0.next().await {
let next = match &mut *me {
Self::Local(it) => it.next().await,
Self::Remote(it) => it.next().await,
};
match next {
Ok(value) => value.into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
}

View file

@ -3,9 +3,9 @@ use std::{borrow::Cow, io::Cursor, mem, path::{Path, PathBuf}, sync::OnceLock};
use anyhow::{Result, anyhow};
use ratatui::{layout::Size, text::{Line, Span, Text}};
use syntect::{LoadingError, dumps, easy::HighlightLines, highlighting::{self, Theme, ThemeSet}, parsing::{SyntaxReference, SyntaxSet}};
use tokio::io::AsyncBufReadExt;
use tokio::io::{AsyncBufReadExt, AsyncSeekExt, BufReader};
use yazi_config::{THEME, YAZI, preview::PreviewWrap};
use yazi_fs::provider::local::Local;
use yazi_fs::provider::local::{self, Local};
use yazi_shared::{Ids, errors::PeekError, replace_to_printable};
static INCR: Ids = Ids::new();
@ -39,9 +39,9 @@ impl Highlighter {
pub fn abort() { INCR.next(); }
pub async fn highlight(&self, skip: usize, size: Size) -> Result<Text<'static>, PeekError> {
let mut reader = Local::open(&self.path).await?.reader();
let mut reader = BufReader::new(Local::open(&self.path).await?);
let syntax = Self::find_syntax(&self.path).await;
let syntax = Self::find_syntax(&self.path, &mut reader).await;
let mut plain = syntax.is_err();
let mut before = Vec::with_capacity(if plain { 0 } else { skip });
@ -130,7 +130,10 @@ impl Highlighter {
.await?
}
async fn find_syntax(path: &Path) -> Result<&'static SyntaxReference> {
async fn find_syntax(
path: &Path,
reader: &mut BufReader<local::RwFile>,
) -> Result<&'static SyntaxReference> {
let (_, syntaxes) = Self::init();
let name = path.file_name().map(|n| n.to_string_lossy()).unwrap_or_default();
if let Some(s) = syntaxes.find_syntax_by_extension(&name) {
@ -143,8 +146,8 @@ impl Highlighter {
}
let mut line = String::new();
let mut reader = Local::open(&path).await?.reader();
reader.read_line(&mut line).await?;
reader.rewind().await?;
syntaxes.find_syntax_by_first_line(&line).ok_or_else(|| anyhow!("No syntax found"))
}

View file

@ -147,8 +147,14 @@ fn read_dir(lua: &Lua) -> mlua::Result<Function> {
fn calc_size(lua: &Lua) -> mlua::Result<Function> {
lua.create_async_function(|lua, url: UrlRef| async move {
match yazi_fs::SizeCalculator::new(&url).await {
Ok(it) => SizeCalculator(it).into_lua_multi(&lua),
let it = if let Some(path) = url.as_path() {
provider::local::SizeCalculator::new(path).await.map(SizeCalculator::Local)
} else {
provider::SizeCalculator::new(&*url).await.map(SizeCalculator::Remote)
};
match it {
Ok(it) => it.into_lua_multi(&lua),
Err(e) => (Value::Nil, Error::Io(e)).into_lua_multi(&lua),
}
})

View file

@ -8,7 +8,7 @@ use tokio::sync::mpsc;
use tokio_util::sync::CancellationToken;
use tracing::error;
use yazi_config::Priority;
use yazi_fs::{FilesOp, SizeCalculator};
use yazi_fs::{FilesOp, provider};
use yazi_plugin::isolate;
use yazi_shared::{event::CmdCow, url::UrlBuf};
@ -93,7 +93,7 @@ impl Prework {
}
pub(crate) async fn size_do(&self, task: PreworkInSize) -> Result<(), PreworkOutSize> {
let length = SizeCalculator::total(&task.target).await.unwrap_or(0);
let length = provider::calculate(&task.target).await.unwrap_or(0);
task.throttle.done((task.target, length), |buf| {
{
let mut loading = self.sizing.write();

View file

@ -10,10 +10,13 @@ pub struct DirEntry<'a> {
}
impl<'a> DirEntry<'a> {
#[must_use]
pub fn path(&self) -> PathBuf { self.dir.join(&self.name) }
#[must_use]
pub fn name(&self) -> Cow<'_, OsStr> { self.name.to_os_str() }
#[must_use]
pub fn long_name(&self) -> Cow<'_, OsStr> { self.long_name.to_os_str() }
pub fn attrs(&self) -> &Attrs { &self.attrs }