ex_cli/fs/
metadata.rs

1use crate::error::MyResult;
2use crate::fs::flags::FileFlags;
3#[cfg(windows)]
4use once_cell::unsync::Lazy;
5#[cfg(windows)]
6use std::collections::HashSet;
7#[cfg(windows)]
8use std::env;
9#[cfg(windows)]
10use std::ffi::OsStr;
11use std::fs;
12use std::path::Path;
13use std::time::SystemTime;
14#[cfg(unix)]
15use uzers::{gid_t, uid_t};
16
17#[derive(Clone)]
18pub struct Metadata {
19    pub file_flags: FileFlags,
20    pub file_mode: u32,
21    #[cfg(unix)]
22    pub owner_uid: uid_t,
23    #[cfg(unix)]
24    pub owner_gid: gid_t,
25    pub file_size: u64,
26    pub file_time: SystemTime,
27}
28
29impl Metadata {
30    pub fn from_path(path: &Path) -> MyResult<Self> {
31        let metadata = fs::symlink_metadata(path).map_err(|e| (e, path))?;
32        Self::create_metadata(metadata, path)
33    }
34}
35
36#[cfg(windows)]
37impl Metadata {
38    fn create_metadata(metadata: fs::Metadata, path: &Path) -> MyResult<Self> {
39        use std::os::windows::fs::MetadataExt;
40        let file_flags = FileFlags::from_type(metadata.file_type(), false);
41        let file_mode = Self::convert_mode(metadata.file_attributes(), path);
42        let file_size = metadata.file_size();
43        let file_time = metadata.modified().map_err(|e| (e, path))?;
44        let metadata = Self {
45            file_flags,
46            file_mode,
47            file_size,
48            file_time,
49        };
50        Ok(metadata)
51    }
52
53    fn convert_mode(attributes: u32, path: &Path) -> u32 {
54        let extensions = Lazy::new(|| {
55            Self::parse_extensions()
56        });
57        let mut octal = 0x04; // (readable)
58        if attributes & 0x01 == 0 {
59            octal |= 0x02; // (writable)
60        }
61        if let Some(ext) = path.extension().and_then(OsStr::to_str) {
62            let ext = ext.to_uppercase();
63            if extensions.contains(&ext) {
64                octal |= 0x01; // (executable)
65            }
66        }
67        octal + (octal << 3) + (octal << 6)
68    }
69
70    fn parse_extensions() -> HashSet<String> {
71        let mut extensions = HashSet::new();
72        if let Ok(variable) = env::var("PATH_EXT") {
73            for ext in variable.split(";") {
74                if let Some(ext) = ext.strip_prefix(".") {
75                    extensions.insert(ext.to_uppercase());
76                }
77            }
78        }
79        extensions
80    }
81}
82
83#[cfg(unix)]
84impl Metadata {
85    fn create_metadata(metadata: fs::Metadata, path: &Path) -> MyResult<Self> {
86        use std::os::unix::fs::MetadataExt;
87        let file_flags = FileFlags::from_type(metadata.file_type(), false);
88        let file_mode = metadata.mode();
89        let owner_uid = metadata.uid();
90        let owner_gid = metadata.gid();
91        let file_size = metadata.size();
92        let file_time = metadata.modified().map_err(|e| (e, path))?;
93        let metadata = Self {
94            file_flags,
95            file_mode,
96            owner_uid,
97            owner_gid,
98            file_size,
99            file_time,
100        };
101        Ok(metadata)
102    }
103}
104
105impl Default for Metadata {
106    fn default() -> Self {
107        Self {
108            file_flags: FileFlags::Dir,
109            file_mode: 0,
110            #[cfg(unix)]
111            owner_uid: 0,
112            #[cfg(unix)]
113            owner_gid: 0,
114            file_size: 0,
115            file_time: SystemTime::UNIX_EPOCH,
116        }
117    }
118}
119
120#[cfg(test)]
121pub mod tests {
122    use crate::fs::flags::FileFlags;
123    use crate::fs::metadata::Metadata;
124    use chrono::{DateTime, TimeZone, Utc};
125    use std::time::SystemTime;
126
127    impl Metadata {
128        #[allow(unused_variables)]
129        pub fn from_fields(
130            file_type: char,
131            file_mode: u32,
132            owner_uid: u32, // uid_t
133            owner_gid: u32, // gid_t
134            file_size: u64,
135            file_year: i32,
136            file_month: u32,
137            file_day: u32,
138        ) -> Self {
139            let file_flags = FileFlags::from_char(file_type);
140            let file_time = create_time(file_year, file_month, file_day);
141            let file_time = SystemTime::from(file_time);
142            Self {
143                file_flags,
144                file_mode,
145                #[cfg(unix)]
146                owner_uid,
147                #[cfg(unix)]
148                owner_gid,
149                file_size,
150                file_time,
151            }
152        }
153    }
154
155    fn create_time(year: i32, month: u32, day: u32) -> DateTime<Utc> {
156        Utc.with_ymd_and_hms(year, month, day, 0, 0, 0).unwrap()
157    }
158}