ex-cli 1.21.0

Command line tool to find, filter, sort and list files.
Documentation
use crate::error::{MyError, MyResult};
use crate::fs::file::Signature;
use crate::fs::flags::FileFlags;
use crate::fs::metadata::Metadata;
#[cfg(windows)]
use crate::util::version;
use crc32fast::Hasher;
use std::cell::RefCell;
use std::ffi::OsStr;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
#[cfg(unix)]
use uzers::{gid_t, uid_t};
use walkdir::DirEntry;

pub type EntryResult<'a> = MyResult<&'a dyn Entry>;

pub trait Entry {
    fn file_path(&self) -> &Path;

    fn file_name(&self) -> &OsStr;

    fn file_depth(&self) -> usize;

    fn inner_path(&self) -> Option<&Path>;

    fn inner_depth(&self) -> Option<usize>;

    fn file_flags(&self) -> FileFlags;

    fn read_crc(&self) -> u32;

    fn read_sig(&self) -> Option<Signature>;

    #[cfg(windows)]
    fn read_version(&self) -> Option<String>;

    fn read_link(&self) -> MyResult<Option<PathBuf>>;

    fn copy_metadata(&self, other: &dyn Entry);

    fn reset_metadata(&self);

    fn file_mode(&self) -> u32;

    #[cfg(unix)]
    fn owner_uid(&self) -> uid_t;

    #[cfg(unix)]
    fn owner_gid(&self) -> gid_t;

    fn file_size(&self) -> u64;

    fn file_time(&self) -> SystemTime;
}

#[derive(Clone)]
pub struct FileEntry {
    file_path: PathBuf,
    file_depth: usize,
    zip_archive: bool,
    file_flags: FileFlags,
    file_metadata: RefCell<Option<Metadata>>,
}

impl FileEntry {
    pub fn from_entry(dir_entry: DirEntry, zip_archive: bool) -> Box<dyn Entry> {
        let file_flags = FileFlags::from_type(dir_entry.file_type(), zip_archive);
        let file_depth = dir_entry.depth();
        let file_path = dir_entry.into_path();
        let file_metadata = RefCell::new(None);
        let entry = Self {
            file_path,
            file_depth,
            zip_archive,
            file_flags,
            file_metadata,
        };
        Box::new(entry)
    }

    pub fn from_path(path: &Path) -> MyResult<Box<dyn Entry>> {
        let file_path = PathBuf::from(path);
        let file_metadata = Metadata::from_path(path)?;
        let file_flags = file_metadata.file_flags;
        let file_metadata = RefCell::new(Some(file_metadata));
        let entry = Self {
            file_path,
            file_depth: 0,
            zip_archive: false,
            file_flags,
            file_metadata,
        };
        Ok(Box::new(entry))
    }

    #[cfg(test)]
    pub fn from_fields(
        file_path: PathBuf,
        file_depth: usize,
        file_type: char,
        file_metadata: Metadata,
    ) -> Self {
        let file_flags = FileFlags::from_char(file_type);
        let file_metadata = RefCell::new(Some(file_metadata));
        Self {
            file_path,
            file_depth,
            zip_archive: false,
            file_flags,
            file_metadata,
        }
    }

    #[cfg(test)]
    pub fn subtract_depth(&self, depth: usize) -> Option<Self> {
        if self.file_depth >= depth {
            let mut entry = self.clone();
            entry.file_depth -= depth;
            return Some(entry);
        }
        None
    }

    fn get_metadata(&self) -> Metadata {
        Metadata::from_path(&self.file_path).unwrap_or_default()
    }
}

impl Entry for FileEntry {
    fn file_path(&self) -> &Path {
        &self.file_path
    }

    fn file_name(&self) -> &OsStr {
        self.file_path.file_name().unwrap_or_default()
    }

    fn file_depth(&self) -> usize {
        self.file_depth
    }

    fn inner_path(&self) -> Option<&Path> {
        None
    }

    fn inner_depth(&self) -> Option<usize> {
        self.zip_archive.then_some(0)
    }

    fn file_flags(&self) -> FileFlags {
        self.file_flags
    }

    fn read_crc(&self) -> u32 {
        crc32_file(&self.file_path).unwrap_or_default()
    }

    fn read_sig(&self) -> Option<Signature> {
        let mut data = [0; 4];
        match File::open(&self.file_path) {
            Ok(mut file) => file.read(&mut data).map(|_| data).ok(),
            Err(_) => None,
        }
    }

    #[cfg(windows)]
    fn read_version(&self) -> Option<String> {
        if version::inner::test_extension(&self.file_path) {
            version::inner::query_file(&self.file_path)
        } else {
            None
        }
    }

    fn read_link(&self) -> MyResult<Option<PathBuf>> {
        match self.file_path.read_link() {
            Ok(link) => Ok(Some(link)),
            Err(error) => Err(MyError::from((error, self.file_path.as_path()))),
        }
    }

    #[cfg(unix)]
    fn copy_metadata(&self, other: &dyn Entry) {
        let mut metadata = self.file_metadata.borrow_mut();
        let metadata = metadata.get_or_insert_with(|| self.get_metadata());
        metadata.file_mode = other.file_mode();
        metadata.owner_uid = other.owner_uid();
        metadata.owner_gid = other.owner_gid();
        metadata.file_size = other.file_size();
        metadata.file_time = other.file_time();
    }

    #[cfg(not(unix))]
    fn copy_metadata(&self, other: &dyn Entry) {
        let mut metadata = self.file_metadata.borrow_mut();
        let metadata = metadata.get_or_insert_with(|| self.get_metadata());
        metadata.file_mode = other.file_mode();
        metadata.file_size = other.file_size();
        metadata.file_time = other.file_time();
    }

    fn reset_metadata(&self) {
        let mut metadata = self.file_metadata.borrow_mut();
        let metadata = metadata.get_or_insert_with(|| self.get_metadata());
        metadata.file_size = 0;
        metadata.file_time = SystemTime::UNIX_EPOCH;
    }

    fn file_mode(&self) -> u32 {
        let mut metadata = self.file_metadata.borrow_mut();
        let metadata = metadata.get_or_insert_with(|| self.get_metadata());
        metadata.file_mode
    }

    #[cfg(unix)]
    fn owner_uid(&self) -> uid_t {
        let mut metadata = self.file_metadata.borrow_mut();
        let metadata = metadata.get_or_insert_with(|| self.get_metadata());
        metadata.owner_uid
    }

    #[cfg(unix)]
    fn owner_gid(&self) -> gid_t {
        let mut metadata = self.file_metadata.borrow_mut();
        let metadata = metadata.get_or_insert_with(|| self.get_metadata());
        metadata.owner_gid
    }

    fn file_size(&self) -> u64 {
        let mut metadata = self.file_metadata.borrow_mut();
        let metadata = metadata.get_or_insert_with(|| self.get_metadata());
        metadata.file_size
    }

    fn file_time(&self) -> SystemTime {
        let mut metadata = self.file_metadata.borrow_mut();
        let metadata = metadata.get_or_insert_with(|| self.get_metadata());
        metadata.file_time
    }
}

fn crc32_file(path: &Path) -> MyResult<u32> {
    let mut file = File::open(path)?;
    let mut hasher = Hasher::new();
    let mut buffer = [0u8; 8192];
    loop {
        let count = file.read(&mut buffer)?;
        if count == 0 {
            break;
        }
        hasher.update(&buffer[..count]);
    }
    Ok(hasher.finalize())
}