1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
use std::fs;
use std::fs::FileType;
use std::path::Path;
use std::time::SystemTime;

use walkdir::DirEntry;

#[cfg(target_family = "windows")]
use {
    std::collections::HashSet,
    std::env,
    std::ffi::OsStr,

    once_cell::unsync::Lazy,
};

use crate::error::MyResult;

pub struct Metadata {
    pub file_type: FileType,
    pub file_mode: u32,
    pub file_size: u64,
    pub file_time: SystemTime,
}

impl Metadata {
    pub fn from_entry(entry: &DirEntry) -> MyResult<Metadata> {
        Self::create_metadata(entry.metadata()?, entry.path())
    }

    pub fn from_path(path: &Path) -> MyResult<Metadata> {
        Self::create_metadata(fs::metadata(&path)?, path)
    }
}

#[cfg(target_family = "windows")]
impl Metadata {
    fn create_metadata(file_data: fs::Metadata, path: &Path) -> MyResult<Metadata> {
        use std::os::windows::fs::MetadataExt;
        let file_type = file_data.file_type();
        let file_mode = Self::convert_mode(file_data.file_attributes(), path);
        let file_size = file_data.file_size();
        let file_time = file_data.modified()?;
        let file_data = Metadata { file_type, file_mode, file_size, file_time };
        return Ok(file_data);
    }

    fn convert_mode(attributes: u32, path: &Path) -> u32 {
        let extensions = Lazy::new(|| {
            Self::parse_extensions()
        });
        let mut octal = 0x04; // (readable)
        if attributes & 0x01 == 0 {
            octal |= 0x02; // (writable)
        }
        if let Some(ext) = path.extension().and_then(OsStr::to_str) {
            let ext = ext.to_uppercase();
            if extensions.contains(&ext) {
                octal |= 0x01; // (executable)
            }
        }
        return 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());
                }
            }
        }
        return extensions;
    }
}

#[cfg(target_family = "unix")]
impl Metadata {
    fn create_metadata(file_data: fs::Metadata, _path: &Path) -> MyResult<Metadata> {
        use std::os::unix::fs::MetadataExt;
        let file_type = file_data.file_type();
        let file_mode = file_data.mode();
        let file_size = file_data.size();
        let file_time = file_data.modified()?;
        let file_data = Metadata { file_type, file_mode, file_size, file_time };
        return Ok(file_data);
    }
}