use std::os::unix::fs::MetadataExt;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::time::SystemTime;
use serde::{Deserialize, Serialize};
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq)]
pub struct Metadata {
pub name: String,
pub size: u64,
pub is_dir: bool,
#[serde(with = "format_datetimes")]
pub atime: Option<SystemTime>,
#[serde(with = "format_datetimes")]
pub mtime: Option<SystemTime>,
#[serde(with = "format_datetimes")]
pub ctime: Option<SystemTime>,
pub st_mode: Option<u32>,
pub st_mode_string: Option<String>,
pub uid: Option<u32>,
pub gid: Option<u32>,
#[serde(deserialize_with = "deserialize_link::deserialize")]
pub link: Option<String>,
}
impl Metadata {
pub fn new<P: AsRef<Path>>(p: P) -> Result<Metadata, std::io::Error> {
let fpath: &Path = p.as_ref();
let name = fpath.file_name().unwrap().to_str().unwrap();
let meta = fpath.symlink_metadata()?;
let file_type = meta.file_type();
let mode = if cfg!(target_os = "linux") {
Some(meta.permissions().mode())
} else {
None
};
let (uid, gid): (Option<u32>, Option<u32>) = if cfg!(target_os = "linux") {
(Some(meta.uid()), Some(meta.gid()))
} else {
(None, None)
};
let link: Option<String> = if file_type.is_symlink() {
fpath.read_link()?.to_str().map(String::from)
} else {
None
};
Ok(Metadata {
name: name.to_string(),
size: meta.len(),
is_dir: file_type.is_dir(),
atime: meta.accessed().ok(),
mtime: meta.modified().ok(),
ctime: meta.created().ok(),
st_mode: mode,
st_mode_string: mode.map(strmode::strmode),
uid: uid,
gid: gid,
link: link,
})
}
}
mod format_datetimes {
use chrono::{DateTime, SecondsFormat, TimeZone, Utc};
use serde::{self, Deserialize, Deserializer, Serializer};
use std::time::SystemTime;
pub fn serialize<S>(system_time: &Option<SystemTime>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let timestamp = match system_time {
Some(ts) => ts
.duration_since(SystemTime::UNIX_EPOCH)
.map_err(serde::ser::Error::custom)?
.as_secs(),
None => {
return serializer.serialize_str("");
}
};
let date_time = Utc.timestamp(timestamp as i64, 0);
let dt_str = date_time.to_rfc3339_opts(SecondsFormat::Secs, true);
serializer.serialize_str(&dt_str)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<SystemTime>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s == "" {
Ok(None)
} else {
let dt = DateTime::parse_from_rfc3339(&s).map_err(serde::de::Error::custom)?;
let ts = dt.timestamp();
let st = SystemTime::UNIX_EPOCH.checked_add(std::time::Duration::from_secs(ts as u64));
st.ok_or(serde::de::Error::custom(
"Could not deserialize invalid timestamp.",
))
.map(Some)
}
}
}
mod deserialize_link {
use serde::{self, Deserialize, Deserializer};
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s == "" {
Ok(None)
} else {
Ok(Some(s))
}
}
}