feat: support displaying files with inaccessible metadata (#1275)

This commit is contained in:
Lauri Niskanen 2024-07-14 09:42:42 +03:00 committed by GitHub
parent 2cf722ed48
commit ab88588c0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 187 additions and 104 deletions

View file

@ -1 +1 @@
{"language":"en","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","subsec"],"version":"0.2"}
{"version":"0.2","language":"en","words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink"],"flagWords":[]}

View file

@ -189,6 +189,10 @@ rules = [
{ name = "*", is = "orphan", bg = "red" },
{ name = "*", is = "exec" , fg = "green" },
# Dummy files
{ name = "*", is = "dummy", bg = "red" },
{ name = "*/", is = "dummy", bg = "red" },
# Fallback
# { name = "*", fg = "white" },
{ name = "*/", fg = "blue" }
@ -771,6 +775,7 @@ conds = [
{ if = "fifo" , text = "" },
{ if = "sock" , text = "" },
{ if = "sticky", text = "" },
{ if = "dummy", text = "" },
# Fallback
{ if = "dir", text = "󰉋" },

View file

@ -29,6 +29,7 @@ impl Icons {
"hidden" => file.is_hidden(),
"link" => file.is_link(),
"orphan" => file.is_orphan(),
"dummy" => file.is_dummy(),
"block" => file.is_block(),
"char" => file.is_char(),
"fifo" => file.is_fifo(),

View file

@ -9,13 +9,14 @@ use yazi_shared::fs::Cha;
pub enum Is {
#[default]
None,
Block,
Char,
Exec,
Fifo,
Link,
Orphan,
Dummy,
Block,
Char,
Fifo,
Sock,
Exec,
Sticky,
}
@ -24,13 +25,14 @@ impl FromStr for Is {
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"block" => Self::Block,
"char" => Self::Char,
"exec" => Self::Exec,
"fifo" => Self::Fifo,
"link" => Self::Link,
"orphan" => Self::Orphan,
"dummy" => Self::Dummy,
"block" => Self::Block,
"char" => Self::Char,
"fifo" => Self::Fifo,
"sock" => Self::Sock,
"exec" => Self::Exec,
"sticky" => Self::Sticky,
_ => bail!("invalid filetype: {s}"),
})
@ -48,13 +50,14 @@ impl Is {
pub fn check(&self, cha: &Cha) -> bool {
match self {
Self::None => true,
Self::Block => cha.is_block(),
Self::Char => cha.is_char(),
Self::Exec => cha.is_exec(),
Self::Fifo => cha.is_fifo(),
Self::Link => cha.is_link(),
Self::Orphan => cha.is_orphan(),
Self::Dummy => cha.is_dummy(),
Self::Block => cha.is_block(),
Self::Char => cha.is_char(),
Self::Fifo => cha.is_fifo(),
Self::Sock => cha.is_sock(),
Self::Exec => cha.is_exec(),
Self::Sticky => cha.is_sticky(),
}
}

View file

@ -54,9 +54,11 @@ impl Files {
select! {
_ = tx.closed() => break,
result = item.metadata() => {
if let Ok(meta) = result {
tx.send(File::from_meta(Url::from(item.path()), meta).await).ok();
}
let url = Url::from(item.path());
_ = tx.send(match result {
Ok(meta) => File::from_meta(url, meta).await,
Err(_) => File::from_dummy(url, item.file_type().await.ok())
});
}
}
}
@ -76,9 +78,11 @@ impl Files {
async fn go(entities: &[DirEntry]) -> Vec<File> {
let mut files = Vec::with_capacity(entities.len() / 3 + 1);
for entry in entities {
if let Ok(meta) = entry.metadata().await {
files.push(File::from_meta(Url::from(entry.path()), meta).await);
}
let url = Url::from(entry.path());
files.push(match entry.metadata().await {
Ok(meta) => File::from_meta(url, meta).await,
Err(_) => File::from_dummy(url, entry.file_type().await.ok()),
});
}
files
}

View file

@ -33,11 +33,11 @@ impl FilesSorter {
match self.by {
SortBy::None => {}
SortBy::Modified => items.sort_unstable_by(|a, b| {
let ord = self.cmp(a.modified, b.modified, self.promote(a, b));
let ord = self.cmp(a.mtime, b.mtime, self.promote(a, b));
if ord == Ordering::Equal { by_alphabetical(a, b) } else { ord }
}),
SortBy::Created => items.sort_unstable_by(|a, b| {
let ord = self.cmp(a.created, b.created, self.promote(a, b));
let ord = self.cmp(a.ctime, b.ctime, self.promote(a, b));
if ord == Ordering::Equal { by_alphabetical(a, b) } else { ord }
}),
SortBy::Extension => items.sort_unstable_by(|a, b| {

View file

@ -30,7 +30,7 @@ impl Tab {
};
self.cd(parent.clone());
FilesOp::Creating(parent, vec![File::from_dummy(&opt.target)]).emit();
FilesOp::Creating(parent, vec![File::from_dummy(opt.target.clone(), None)]).emit();
ManagerProxy::hover(Some(opt.target));
}
}

View file

@ -102,12 +102,12 @@ impl Preview {
*url == lock.url
&& self.skip == lock.skip
&& cha.len == lock.cha.len
&& cha.modified == lock.cha.modified
&& cha.mtime == lock.cha.mtime
&& cha.kind == lock.cha.kind
&& {
#[cfg(unix)]
{
cha.permissions == lock.cha.permissions
cha.perm == lock.cha.perm
}
#[cfg(windows)]
{

View file

@ -32,11 +32,9 @@ end
function Linemode:permissions(file) return ui.Line(file.cha:permissions() or "") end
function Linemode:owner(file)
if not ya.user_name then
return ui.Line("")
else
return ui.Line(ya.user_name(file.cha.uid) .. ":" .. ya.group_name(file.cha.gid))
end
local user = file.cha.uid and ya.user_name(file.cha.uid) or file.cha.uid
local group = file.cha.gid and ya.group_name(file.cha.gid) or file.cha.gid
return ui.Line(string.format("%s:%s", user or "-", group or "-"))
end
function Linemode:render(files)

View file

@ -73,7 +73,7 @@ function Status:permissions()
for i = 1, #perm do
local c = perm:sub(i, i)
local style = THEME.status.permissions_t
if c == "-" then
if c == "-" or c == "?" then
style = THEME.status.permissions_s
elseif c == "r" then
style = THEME.status.permissions_r

View file

@ -16,6 +16,7 @@ impl Cha {
reg.add_field_method_get("is_hidden", |_, me| Ok(me.is_hidden()));
reg.add_field_method_get("is_link", |_, me| Ok(me.is_link()));
reg.add_field_method_get("is_orphan", |_, me| Ok(me.is_orphan()));
reg.add_field_method_get("is_dummy", |_, me| Ok(me.is_dummy()));
reg.add_field_method_get("is_block", |_, me| Ok(me.is_block()));
reg.add_field_method_get("is_char", |_, me| Ok(me.is_char()));
reg.add_field_method_get("is_fifo", |_, me| Ok(me.is_fifo()));
@ -25,25 +26,25 @@ impl Cha {
#[cfg(unix)]
{
reg.add_field_method_get("uid", |_, me| Ok(me.uid));
reg.add_field_method_get("gid", |_, me| Ok(me.gid));
reg.add_field_method_get("nlink", |_, me| Ok(me.nlink));
reg.add_field_method_get("uid", |_, me| Ok((!me.is_dummy()).then_some(me.uid)));
reg.add_field_method_get("gid", |_, me| Ok((!me.is_dummy()).then_some(me.gid)));
reg.add_field_method_get("nlink", |_, me| Ok((!me.is_dummy()).then_some(me.nlink)));
}
reg.add_field_method_get("length", |_, me| Ok(me.len));
reg.add_field_method_get("created", |_, me| {
Ok(me.created.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok()))
Ok(me.ctime.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok()))
});
reg.add_field_method_get("modified", |_, me| {
Ok(me.modified.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok()))
Ok(me.mtime.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok()))
});
reg.add_field_method_get("accessed", |_, me| {
Ok(me.accessed.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok()))
Ok(me.atime.and_then(|t| t.duration_since(UNIX_EPOCH).map(|d| d.as_secs_f64()).ok()))
});
reg.add_method("permissions", |_, me, ()| {
Ok(
#[cfg(unix)]
Some(yazi_shared::fs::permissions(me.permissions)),
Some(yazi_shared::fs::permissions(me.perm, me.is_dummy())),
#[cfg(windows)]
None::<String>,
)
@ -72,11 +73,11 @@ impl Cha {
Self::cast(lua, yazi_shared::fs::Cha {
kind,
len: t.raw_get("len").unwrap_or_default(),
accessed: parse_time(t.raw_get("atime").ok())?,
created: parse_time(t.raw_get("ctime").ok())?,
modified: parse_time(t.raw_get("mtime").ok())?,
atime: parse_time(t.raw_get("atime").ok())?,
ctime: parse_time(t.raw_get("ctime").ok())?,
mtime: parse_time(t.raw_get("mtime").ok())?,
#[cfg(unix)]
permissions: t.raw_get("permissions").unwrap_or_default(),
perm: t.raw_get("permissions").unwrap_or_default(),
#[cfg(unix)]
uid: t.raw_get("uid").unwrap_or_default(),
#[cfg(unix)]

View file

@ -17,7 +17,7 @@ impl Utils {
let hex = {
let mut digest = Md5::new_with_prefix(file.url.as_os_str().as_encoded_bytes());
digest.update(&format!("//{:?}//{}", file.cha.modified, t.raw_get("skip").unwrap_or(0)));
digest.update(&format!("//{:?}//{}", file.cha.mtime, t.raw_get("skip").unwrap_or(0)));
format!("{:x}", digest.finalize())
};

View file

@ -1,4 +1,4 @@
use std::{fs::Metadata, time::SystemTime};
use std::{fs::{FileType, Metadata}, time::SystemTime};
use bitflags::bitflags;
@ -11,28 +11,25 @@ bitflags! {
const LINK = 0b00000100;
const ORPHAN = 0b00001000;
const BLOCK = 0b00010000;
const CHAR = 0b00100000;
const FIFO = 0b01000000;
const SOCKET = 0b10000000;
const DUMMY = 0b00010000;
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Cha {
pub kind: ChaKind,
pub len: u64,
pub accessed: Option<SystemTime>,
pub created: Option<SystemTime>,
pub modified: Option<SystemTime>,
pub kind: ChaKind,
pub len: u64,
pub atime: Option<SystemTime>,
pub ctime: Option<SystemTime>,
pub mtime: Option<SystemTime>,
#[cfg(unix)]
pub permissions: libc::mode_t,
pub perm: libc::mode_t,
#[cfg(unix)]
pub uid: libc::uid_t,
pub uid: libc::uid_t,
#[cfg(unix)]
pub gid: libc::gid_t,
pub gid: libc::gid_t,
#[cfg(unix)]
pub nlink: libc::nlink_t,
pub nlink: libc::nlink_t,
}
impl From<Metadata> for Cha {
@ -42,47 +39,30 @@ impl From<Metadata> for Cha {
ck |= ChaKind::DIR;
}
#[cfg(unix)]
{
use std::os::unix::prelude::FileTypeExt;
if m.file_type().is_block_device() {
ck |= ChaKind::BLOCK;
}
if m.file_type().is_char_device() {
ck |= ChaKind::CHAR;
}
if m.file_type().is_fifo() {
ck |= ChaKind::FIFO;
}
if m.file_type().is_socket() {
ck |= ChaKind::SOCKET;
}
}
Self {
kind: ck,
len: m.len(),
accessed: m.accessed().ok(),
created: m.created().ok(),
modified: m.modified().ok(),
kind: ck,
len: m.len(),
atime: m.accessed().ok(),
ctime: m.created().ok(),
mtime: m.modified().ok(),
#[cfg(unix)]
permissions: {
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 _
},
@ -90,6 +70,50 @@ impl From<Metadata> for Cha {
}
}
impl From<FileType> for Cha {
fn from(t: FileType) -> Self {
let mut kind = ChaKind::DUMMY;
#[cfg(unix)]
let perm = {
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)]
perm,
..Default::default()
}
}
}
impl Cha {
#[inline]
pub fn with_kind(mut self, kind: ChaKind) -> Self {
@ -100,34 +124,25 @@ impl Cha {
impl Cha {
#[inline]
pub fn is_dir(&self) -> bool { self.kind.contains(ChaKind::DIR) }
pub const fn is_dir(&self) -> bool { self.kind.contains(ChaKind::DIR) }
#[inline]
pub fn is_hidden(&self) -> bool { self.kind.contains(ChaKind::HIDDEN) }
pub const fn is_hidden(&self) -> bool { self.kind.contains(ChaKind::HIDDEN) }
#[inline]
pub fn is_link(&self) -> bool { self.kind.contains(ChaKind::LINK) }
pub const fn is_link(&self) -> bool { self.kind.contains(ChaKind::LINK) }
#[inline]
pub fn is_orphan(&self) -> bool { self.kind.contains(ChaKind::ORPHAN) }
pub const fn is_orphan(&self) -> bool { self.kind.contains(ChaKind::ORPHAN) }
#[inline]
pub fn is_block(&self) -> bool { self.kind.contains(ChaKind::BLOCK) }
pub const fn is_dummy(&self) -> bool { self.kind.contains(ChaKind::DUMMY) }
#[inline]
pub fn is_char(&self) -> bool { self.kind.contains(ChaKind::CHAR) }
#[inline]
pub fn is_fifo(&self) -> bool { self.kind.contains(ChaKind::FIFO) }
#[inline]
pub fn is_sock(&self) -> bool { self.kind.contains(ChaKind::SOCKET) }
#[inline]
pub fn is_exec(&self) -> bool {
pub const fn is_block(&self) -> bool {
#[cfg(unix)]
{
self.permissions & libc::S_IXUSR != 0
self.perm & libc::S_IFMT == libc::S_IFBLK
}
#[cfg(windows)]
{
@ -136,10 +151,58 @@ impl Cha {
}
#[inline]
pub fn is_sticky(&self) -> bool {
pub const fn is_char(&self) -> bool {
#[cfg(unix)]
{
self.permissions & libc::S_ISVTX != 0
self.perm & libc::S_IFMT == libc::S_IFCHR
}
#[cfg(windows)]
{
false
}
}
#[inline]
pub const fn is_fifo(&self) -> bool {
#[cfg(unix)]
{
self.perm & libc::S_IFMT == libc::S_IFIFO
}
#[cfg(windows)]
{
false
}
}
#[inline]
pub const fn is_sock(&self) -> bool {
#[cfg(unix)]
{
self.perm & libc::S_IFMT == libc::S_IFSOCK
}
#[cfg(windows)]
{
false
}
}
#[inline]
pub const fn is_exec(&self) -> bool {
#[cfg(unix)]
{
self.perm & libc::S_IXUSR != 0
}
#[cfg(windows)]
{
false
}
}
#[inline]
pub const fn is_sticky(&self) -> bool {
#[cfg(unix)]
{
self.perm & libc::S_ISVTX != 0
}
#[cfg(windows)]
{

View file

@ -1,4 +1,4 @@
use std::{cell::Cell, ffi::OsStr, fs::Metadata, ops::Deref};
use std::{cell::Cell, ffi::OsStr, fs::{FileType, Metadata}, ops::Deref};
use anyhow::Result;
use tokio::fs;
@ -63,7 +63,10 @@ impl File {
}
#[inline]
pub fn from_dummy(url: &Url) -> Self { Self { url: url.to_owned(), ..Default::default() } }
pub fn from_dummy(url: Url, ft: Option<FileType>) -> Self {
// TODO: set dummy=true
Self { url: url.to_owned(), cha: ft.map_or_else(Cha::default, Cha::from), ..Default::default() }
}
}
impl File {

View file

@ -264,11 +264,11 @@ pub fn copy_with_progress(
// Convert a file mode to a string representation
#[cfg(unix)]
#[allow(clippy::collapsible_else_if)]
pub fn permissions(m: libc::mode_t) -> String {
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);
// File type
// Filetype
s.push(match m & S_IFMT {
S_IFBLK => 'b',
S_IFCHR => 'c',
@ -279,6 +279,11 @@ pub fn permissions(m: libc::mode_t) -> String {
_ => '-',
});
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 { '-' });