use std::path::{Path, PathBuf};
use std::time::SystemTime;
use chrono::{DateTime, Datelike, Utc};
use tracing::error;
#[must_use]
pub fn content_modified(directories: &[&str]) -> Option<DateTime<Utc>> {
let newest: DateTime<Utc> = directories
.iter()
.filter_map(|directory| newest_mtime(Path::new(directory)))
.chain(current_exe_mtime())
.max()?;
let now: DateTime<Utc> = Utc::now();
if newest.year() <= 2000 || newest > now {
error!(
"content modification time is `{newest}`, which is implausible; \
omitting <lastmod>. Check that the build preserves file timestamps."
);
return None;
}
Some(newest)
}
fn newest_mtime(root: &Path) -> Option<DateTime<Utc>> {
if !root.is_dir() {
return None;
}
let mut newest: Option<DateTime<Utc>> = None;
let mut directories: Vec<std::path::PathBuf> = vec![root.to_path_buf()];
while let Some(directory) = directories.pop() {
let Ok(entries) = std::fs::read_dir(&directory) else {
continue;
};
for entry in entries.flatten() {
let path: PathBuf = entry.path();
if path.is_dir() {
directories.push(path);
continue;
}
let Ok(modified) = entry.metadata().and_then(|meta| meta.modified()) else {
continue;
};
let modified: DateTime<Utc> = modified.into();
if newest.is_none_or(|current| modified > current) {
newest = Some(modified);
}
}
}
newest
}
fn current_exe_mtime() -> Option<DateTime<Utc>> {
let exe: PathBuf = std::env::current_exe().ok()?;
let modified: SystemTime = std::fs::metadata(exe).ok()?.modified().ok()?;
Some(modified.into())
}
#[cfg(test)]
mod tests {
use chrono::{DateTime, Utc};
use super::{content_modified, newest_mtime};
#[test]
fn a_directory_that_does_not_exist_contributes_nothing() {
let expected: Option<DateTime<Utc>> = None;
let actual: Option<DateTime<Utc>> =
newest_mtime(std::path::Path::new("/tmp/wsb-no-such-dir"));
assert_eq!(expected, actual);
}
#[test]
fn the_binarys_own_timestamp_is_enough_to_produce_a_plausible_date() {
let actual: Option<DateTime<Utc>> = content_modified(&["/tmp/wsb-no-such-dir"]);
assert!(actual.is_some());
}
}