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())
}