metadata_backup/
metadata.rs1use std::os::unix::fs::MetadataExt;
16use std::os::unix::fs::PermissionsExt;
17use std::path::Path;
18use std::time::SystemTime;
19
20use serde::{Deserialize, Serialize};
21
22#[derive(Clone, Deserialize, Serialize, Debug, PartialEq)]
23pub struct Metadata {
24 pub name: String,
25 pub size: u64,
26 pub is_dir: bool,
27 #[serde(with = "format_datetimes")]
28 pub atime: Option<SystemTime>,
29 #[serde(with = "format_datetimes")]
30 pub mtime: Option<SystemTime>,
31 #[serde(with = "format_datetimes")]
32 pub ctime: Option<SystemTime>,
33 pub st_mode: Option<u32>,
34 pub st_mode_string: Option<String>,
35 pub uid: Option<u32>,
36 pub gid: Option<u32>,
37 #[serde(deserialize_with = "deserialize_link::deserialize")]
38 pub link: Option<String>,
39}
40
41impl Metadata {
42 pub fn new<P: AsRef<Path>>(p: P) -> Result<Metadata, std::io::Error> {
43 let fpath: &Path = p.as_ref();
44 let name = fpath.file_name().unwrap().to_str().unwrap();
45 let meta = fpath.symlink_metadata()?;
46 let file_type = meta.file_type();
47 let mode = if cfg!(target_os = "linux") {
48 Some(meta.permissions().mode())
49 } else {
50 None
51 };
52
53 let (uid, gid): (Option<u32>, Option<u32>) = if cfg!(target_os = "linux") {
54 (Some(meta.uid()), Some(meta.gid()))
55 } else {
56 (None, None)
57 };
58
59 let link: Option<String> = if file_type.is_symlink() {
60 fpath.read_link()?.to_str().map(String::from)
61 } else {
62 None
63 };
64
65 Ok(Metadata {
66 name: name.to_string(),
67 size: meta.len(),
68 is_dir: file_type.is_dir(),
69 atime: meta.accessed().ok(),
70 mtime: meta.modified().ok(),
71 ctime: meta.created().ok(),
72 st_mode: mode,
73 st_mode_string: mode.map(strmode::strmode),
74 uid: uid,
75 gid: gid,
76 link: link,
77 })
78 }
79}
80
81mod format_datetimes {
82 use chrono::{DateTime, SecondsFormat, TimeZone, Utc};
83 use serde::{self, Deserialize, Deserializer, Serializer};
84 use std::time::SystemTime;
85
86 pub fn serialize<S>(system_time: &Option<SystemTime>, serializer: S) -> Result<S::Ok, S::Error>
87 where
88 S: Serializer,
89 {
90 let timestamp = match system_time {
91 Some(ts) => ts
92 .duration_since(SystemTime::UNIX_EPOCH)
93 .map_err(serde::ser::Error::custom)?
94 .as_secs(),
95 None => {
96 return serializer.serialize_str("");
97 }
98 };
99
100 let date_time = Utc.timestamp(timestamp as i64, 0);
101 let dt_str = date_time.to_rfc3339_opts(SecondsFormat::Secs, true);
102 serializer.serialize_str(&dt_str)
103 }
104
105 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<SystemTime>, D::Error>
106 where
107 D: Deserializer<'de>,
108 {
109 let s = String::deserialize(deserializer)?;
110
111 if s == "" {
112 Ok(None)
113 } else {
114 let dt = DateTime::parse_from_rfc3339(&s).map_err(serde::de::Error::custom)?;
115 let ts = dt.timestamp();
116 let st = SystemTime::UNIX_EPOCH.checked_add(std::time::Duration::from_secs(ts as u64));
117
118 st.ok_or(serde::de::Error::custom(
119 "Could not deserialize invalid timestamp.",
120 ))
121 .map(Some)
122 }
123 }
124}
125
126mod deserialize_link {
132 use serde::{self, Deserialize, Deserializer};
133 pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
134 where
135 D: Deserializer<'de>,
136 {
137 let s = String::deserialize(deserializer)?;
138
139 if s == "" {
140 Ok(None)
141 } else {
142 Ok(Some(s))
143 }
144 }
145}