use anyhow::{Result, ensure};
use elaborate::std::{fs::MetadataContext, path::PathContext};
use std::{
collections::{HashMap, HashSet},
path::{Path, PathBuf},
sync::atomic::{AtomicBool, Ordering},
time::SystemTime,
};
#[derive(Debug)]
pub struct Value {
parent_mtime: SystemTime,
mtime: SystemTime,
}
impl Value {
fn new(path: &Path) -> Result<Self> {
let parent = path.parent_wc()?;
let parent_mtime = parent
.metadata_wc()
.and_then(|metadata| metadata.modified_wc())?;
let mtime = path
.metadata_wc()
.and_then(|metadata| metadata.modified_wc())?;
Ok(Self {
parent_mtime,
mtime,
})
}
}
#[derive(Debug)]
pub struct Timestamps {
map: HashMap<PathBuf, Value>,
compared: AtomicBool,
}
impl Timestamps {
pub fn new(stdout: &str) -> Result<Self> {
let map = Self::map_from_stdout(stdout)?;
Ok(Self {
map,
compared: AtomicBool::new(false),
})
}
fn map_from_stdout(stdout: &str) -> Result<HashMap<PathBuf, Value>> {
let mut map = HashMap::new();
for line in stdout.lines().filter(|line| {
line.replace('\\', "/")
.ends_with("/nested_workspace.timestamp")
}) {
let index = line.rfind('=').unwrap();
let path = PathBuf::from(&line[index + 1..]);
let value = Value::new(&path)?;
map.insert(path, value);
}
Ok(map)
}
pub fn get(&self) -> &HashMap<PathBuf, Value> {
&self.map
}
pub fn rescan(&self) -> Result<Self> {
let map = self
.map
.keys()
.map(|path| Value::new(path).map(|contents| (path.clone(), contents)))
.collect::<Result<HashMap<_, _>>>()?;
Ok(Self {
map,
compared: AtomicBool::new(false),
})
}
pub fn compare(&self, other: &Self) -> Result<()> {
self.compared.store(true, Ordering::SeqCst);
other.compared.store(true, Ordering::SeqCst);
let keys_self = self.map.keys().collect::<HashSet<_>>();
let keys_other = other.map.keys().collect::<HashSet<_>>();
ensure!(keys_self == keys_other, "keys differ");
for (path, value_self) in &self.map {
let value_other = other.map.get(path).unwrap();
ensure!(value_self.parent_mtime <= value_other.parent_mtime);
ensure!(value_self.mtime < value_other.mtime);
}
Ok(())
}
}
impl Drop for Timestamps {
fn drop(&mut self) {
if !self.compared.load(Ordering::SeqCst) {
let other = self.rescan().unwrap();
self.compare(&other).unwrap();
}
}
}