use std::{fs::Metadata, ops::Deref, time::{Duration, SystemTime, UNIX_EPOCH}}; use anyhow::bail; use mlua::FromLua; use serde::{Deserialize, Serialize}; use yazi_macro::{unix_either, win_either}; use yazi_shared::{strand::AsStrand, url::AsUrl}; use super::ChaKind; use crate::cha::{ChaMode, ChaType}; #[derive(Clone, Copy, Debug, Deserialize, Eq, FromLua, PartialEq, Serialize)] pub struct Cha { pub kind: ChaKind, pub mode: ChaMode, pub len: u64, pub atime: Option, pub btime: Option, pub ctime: Option, pub mtime: Option, pub dev: u64, pub uid: u32, pub gid: u32, 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, ctime: None, mtime: None, dev: 0, uid: 0, gid: 0, nlink: 0, } } } impl Cha { #[inline] pub fn new(name: T, meta: Metadata) -> Self where T: AsStrand, { Self::from_bare(&meta).attach(ChaKind::hidden(name, &meta)) } pub fn from_dummy(_url: U, r#type: Option) -> Self where U: AsUrl, { #[allow(unused_mut)] let mut kind = ChaKind::DUMMY; let mode = r#type.map(ChaMode::from_bare).unwrap_or_default(); #[cfg(unix)] if _url.as_url().urn().is_hidden() { kind |= ChaKind::HIDDEN; } Self { kind, mode, ..Default::default() } } fn from_bare(m: &Metadata) -> Self { #[cfg(unix)] use std::os::unix::fs::MetadataExt; #[cfg(unix)] let mode = { use std::os::unix::fs::PermissionsExt; ChaMode::from_bits_retain(m.permissions().mode() as u16) }; #[cfg(windows)] let mut 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() }; #[cfg(windows)] if !m.permissions().readonly() { mode |= ChaMode::U_WRITE; } Self { kind: ChaKind::empty(), mode, len: m.len(), atime: m.accessed().ok(), btime: m.created().ok(), ctime: unix_either!( UNIX_EPOCH.checked_add(Duration::new(m.ctime() as u64, m.ctime_nsec() as u32)), None ), mtime: m.modified().ok(), dev: unix_either!(m.dev(), 0) as _, uid: unix_either!(m.uid(), 0) as _, gid: unix_either!(m.gid(), 0) as _, nlink: unix_either!(m.nlink(), 0) as _, } } #[inline] pub fn hits(self, c: Self) -> bool { self.len == c.len && self.mtime == c.mtime && self.ctime == c.ctime && self.btime == c.btime && self.kind == c.kind && self.mode == c.mode } #[inline] pub fn attach(mut self, kind: ChaKind) -> Self { self.kind |= kind; self } #[inline] pub fn follow(self, followed: Option) -> Self { if !self.is_link() { return self; } let retain = self.kind & (ChaKind::HIDDEN | ChaKind::SYSTEM) | ChaKind::FOLLOW; followed.unwrap_or(self).attach(retain) } } impl Cha { #[inline] pub fn is_link(self) -> bool { self.kind.contains(ChaKind::FOLLOW) || *self.mode == ChaType::Link } #[inline] pub fn is_orphan(self) -> bool { *self.mode == ChaType::Link && self.kind.contains(ChaKind::FOLLOW) } #[inline] pub const fn is_hidden(self) -> bool { win_either!( self.kind.contains(ChaKind::HIDDEN) || self.kind.contains(ChaKind::SYSTEM), self.kind.contains(ChaKind::HIDDEN) ) } #[inline] pub const fn is_dummy(self) -> bool { self.kind.contains(ChaKind::DUMMY) } pub fn atime_dur(self) -> anyhow::Result { if let Some(atime) = self.atime { Ok(atime.duration_since(UNIX_EPOCH)?) } else { bail!("atime not available"); } } pub fn btime_dur(self) -> anyhow::Result { if let Some(btime) = self.btime { Ok(btime.duration_since(UNIX_EPOCH)?) } else { bail!("btime not available"); } } pub fn ctime_dur(self) -> anyhow::Result { if let Some(ctime) = self.ctime { Ok(ctime.duration_since(UNIX_EPOCH)?) } else { bail!("ctime not available"); } } pub fn mtime_dur(self) -> anyhow::Result { if let Some(mtime) = self.mtime { Ok(mtime.duration_since(UNIX_EPOCH)?) } else { bail!("mtime not available"); } } }