use crate::error::MyResult;
use crate::fs::flags::FileFlags;
#[cfg(windows)]
use once_cell::unsync::Lazy;
#[cfg(windows)]
use std::collections::HashSet;
#[cfg(windows)]
use std::env;
#[cfg(windows)]
use std::ffi::OsStr;
use std::fs;
use std::path::Path;
use std::time::SystemTime;
#[cfg(unix)]
use uzers::{gid_t, uid_t};
#[derive(Clone)]
pub struct Metadata {
pub file_flags: FileFlags,
pub file_mode: u32,
#[cfg(unix)]
pub owner_uid: uid_t,
#[cfg(unix)]
pub owner_gid: gid_t,
pub file_size: u64,
pub file_time: SystemTime,
}
impl Metadata {
pub fn from_path(path: &Path) -> MyResult<Self> {
let metadata = fs::symlink_metadata(path).map_err(|e| (e, path))?;
Self::create_metadata(metadata, path)
}
}
#[cfg(windows)]
impl Metadata {
fn create_metadata(metadata: fs::Metadata, path: &Path) -> MyResult<Self> {
use std::os::windows::fs::MetadataExt;
let file_flags = FileFlags::from_type(metadata.file_type(), false);
let file_mode = Self::convert_mode(metadata.file_attributes(), path);
let file_size = metadata.file_size();
let file_time = metadata.modified().map_err(|e| (e, path))?;
let metadata = Self {
file_flags,
file_mode,
file_size,
file_time,
};
Ok(metadata)
}
fn convert_mode(attributes: u32, path: &Path) -> u32 {
let extensions = Lazy::new(|| {
Self::parse_extensions()
});
let mut octal = 0x04; if attributes & 0x01 == 0 {
octal |= 0x02; }
if let Some(ext) = path.extension().and_then(OsStr::to_str) {
let ext = ext.to_uppercase();
if extensions.contains(&ext) {
octal |= 0x01; }
}
octal + (octal << 3) + (octal << 6)
}
fn parse_extensions() -> HashSet<String> {
let mut extensions = HashSet::new();
if let Ok(variable) = env::var("PATH_EXT") {
for ext in variable.split(";") {
if let Some(ext) = ext.strip_prefix(".") {
extensions.insert(ext.to_uppercase());
}
}
}
extensions
}
}
#[cfg(unix)]
impl Metadata {
fn create_metadata(metadata: fs::Metadata, path: &Path) -> MyResult<Self> {
use std::os::unix::fs::MetadataExt;
let file_flags = FileFlags::from_type(metadata.file_type(), false);
let file_mode = metadata.mode();
let owner_uid = metadata.uid();
let owner_gid = metadata.gid();
let file_size = metadata.size();
let file_time = metadata.modified().map_err(|e| (e, path))?;
let metadata = Self {
file_flags,
file_mode,
owner_uid,
owner_gid,
file_size,
file_time,
};
Ok(metadata)
}
}
impl Default for Metadata {
fn default() -> Self {
Self {
file_flags: FileFlags::Dir,
file_mode: 0,
#[cfg(unix)]
owner_uid: 0,
#[cfg(unix)]
owner_gid: 0,
file_size: 0,
file_time: SystemTime::UNIX_EPOCH,
}
}
}
#[cfg(test)]
pub mod tests {
use crate::fs::flags::FileFlags;
use crate::fs::metadata::Metadata;
use chrono::{DateTime, TimeZone, Utc};
use std::time::SystemTime;
impl Metadata {
#[allow(unused_variables)]
pub fn from_fields(
file_type: char,
file_mode: u32,
owner_uid: u32, owner_gid: u32, file_size: u64,
file_year: i32,
file_month: u32,
file_day: u32,
) -> Self {
let file_flags = FileFlags::from_char(file_type);
let file_time = create_time(file_year, file_month, file_day);
let file_time = SystemTime::from(file_time);
Self {
file_flags,
file_mode,
#[cfg(unix)]
owner_uid,
#[cfg(unix)]
owner_gid,
file_size,
file_time,
}
}
}
fn create_time(year: i32, month: u32, day: u32) -> DateTime<Utc> {
Utc.with_ymd_and_hms(year, month, day, 0, 0, 0).unwrap()
}
}