ex-cli 1.20.0

Command line tool to find, filter, sort and list files.
Documentation
use crate::error::{MyError, MyResult};
use crate::finder::count_components;
use crate::fs::entry::{Entry, EntryResult};
use crate::fs::file::Signature;
use crate::fs::flags::FileFlags;
#[cfg(windows)]
use crate::util::version;
use crate::zip::parent::ZipParent;
use flate2::read::GzDecoder;
use path_clean::PathClean;
use std::cell::RefCell;
use std::ffi::OsStr;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime};
use tar::{Archive, EntryType};
#[cfg(unix)]
use uzers::{gid_t, uid_t};

struct TarEntry<'a, R: Read> {
    tar_entry: RefCell<tar::Entry<'a, R>>,
    #[cfg(windows)]
    tar_buffer: RefCell<Vec<u8>>,
    file_path: PathBuf,
    file_depth: usize,
    inner_path: PathBuf,
    inner_depth: usize,
}

impl<'a, R: Read> TarEntry<'a, R> {
    fn new(
        tar_entry: tar::Entry<'a, R>,
        zip_parent: &ZipParent,
    ) -> Self {
        let inner_path = tar_entry.path().unwrap_or_default().clean();
        let tar_entry = RefCell::new(tar_entry);
        #[cfg(windows)]
        let tar_buffer = RefCell::new(Vec::new());
        let inner_depth = count_components(&inner_path);
        let file_depth = zip_parent.depth() + inner_depth;
        let file_path = zip_parent.path().join(&inner_path);
        Self {
            tar_entry,
            #[cfg(windows)]
            tar_buffer,
            file_path,
            file_depth,
            inner_path,
            inner_depth,
        }
    }

    // noinspection DuplicatedCode
    #[cfg(windows)]
    fn cache_sig(&self, data: Signature, count: usize) {
        let mut buffer = self.tar_buffer.borrow_mut();
        buffer.resize(count, 0);
        buffer.copy_from_slice(&data[0..count]);
    }
}

impl<'a, R: Read> Entry for TarEntry<'a, R> {
    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> {
        Some(&self.inner_path)
    }

    fn inner_depth(&self) -> Option<usize> {
        Some(self.inner_depth)
    }

    fn file_flags(&self) -> FileFlags {
        let entry = self.tar_entry.borrow();
        match entry.header().entry_type() {
            EntryType::Regular => FileFlags::File,
            EntryType::Directory => FileFlags::Dir,
            EntryType::Link | EntryType::Symlink => FileFlags::Link,
            _ => FileFlags::Other,
        }
    }

    #[allow(unused)]
    fn read_sig(&self) -> Option<Signature> {
        let mut data = [0; 4];
        let mut entry = self.tar_entry.borrow_mut();
        if let Ok(count) = entry.read(&mut data) {
            #[cfg(windows)]
            self.cache_sig(data, count);
            return Some(data);
        }
        None
    }

    #[cfg(windows)]
    fn read_version(&self) -> Option<String> {
        if version::inner::test_extension(&self.file_path) {
            let mut entry = self.tar_entry.borrow_mut();
            let mut buffer = self.tar_buffer.borrow_mut();
            if entry.read_to_end(&mut buffer).is_ok() {
                return version::inner::query_buffer(&mut buffer);
            }
        }
        None
    }

    fn read_link(&self) -> MyResult<Option<PathBuf>> {
        let entry = self.tar_entry.borrow();
        let link = entry.header().link_name()?;
        let link = link.map(|link| link.to_path_buf());
        Ok(link)
    }

    fn copy_metadata(&self, _other: &dyn Entry) {
    }

    fn reset_metadata(&self) {
    }

    fn file_mode(&self) -> u32 {
        let entry = self.tar_entry.borrow();
        entry.header().mode().unwrap_or_default()
    }

    #[cfg(unix)]
    fn owner_uid(&self) -> uid_t {
        let entry = self.tar_entry.borrow();
        entry.header().uid().unwrap_or_default() as uid_t
    }

    #[cfg(unix)]
    fn owner_gid(&self) -> gid_t {
        let entry = self.tar_entry.borrow();
        entry.header().gid().unwrap_or_default() as gid_t
    }

    fn file_size(&self) -> u64 {
        let entry = self.tar_entry.borrow();
        entry.size()
    }

    fn file_time(&self) -> SystemTime {
        let entry = self.tar_entry.borrow();
        let time = entry.header().mtime().unwrap_or_default();
        SystemTime::UNIX_EPOCH + Duration::from_secs(time)
    }
}

pub fn walk_entries<F: Fn(EntryResult)>(
    zip_parent: &ZipParent,
    want_gzip: bool,
    function: &F,
) -> MyResult<()> {
    let tar_path = zip_parent.path();
    match File::open(tar_path) {
        Ok(tar_file) => {
            if want_gzip {
                let tar_file = GzDecoder::new(tar_file);
                let mut tar_archive = Archive::new(tar_file);
                walk_archive(zip_parent, &mut tar_archive, function)
            } else {
                let mut tar_archive = Archive::new(tar_file);
                walk_archive(zip_parent, &mut tar_archive, function)
            }
        }
        Err(error) => {
            function(Err(MyError::from((error, tar_path))));
            Ok(())
        }
    }
}

fn walk_archive<F: Fn(EntryResult), R: Read>(
    zip_parent: &ZipParent,
    tar_archive: &mut Archive<R>,
    function: &F,
) -> MyResult<()> {
    match tar_archive.entries() {
        Ok(mut entries) => {
            while let Some(entry) = entries.next() {
                match entry {
                    Ok(entry) => {
                        if valid_entry(&entry) {
                            let entry = TarEntry::new(entry, zip_parent);
                            function(Ok(&entry));
                        }
                    }
                    Err(error) => {
                        let path = zip_parent.path();
                        function(Err(MyError::from((error, path))));
                    }
                }
            }
        }
        Err(error) => {
            let path = zip_parent.path();
            function(Err(MyError::from((error, path))));
        }
    }
    Ok(())
}

fn valid_entry<R: Read>(entry: &tar::Entry<R>) -> bool {
    if let Ok(path) = entry.path() {
        path.file_name().is_some()
    } else {
        false
    }
}