webserver_base/assets/
modified.rs1use std::path::{Path, PathBuf};
16use std::time::SystemTime;
17
18use chrono::{DateTime, Datelike, Utc};
19use tracing::error;
20
21#[must_use]
28pub fn content_modified(directories: &[&str]) -> Option<DateTime<Utc>> {
29 content_modified_in(Path::new(""), directories)
30}
31
32pub(crate) fn content_modified_in(root: &Path, directories: &[&str]) -> Option<DateTime<Utc>> {
34 let newest: DateTime<Utc> = directories
35 .iter()
36 .filter_map(|directory| newest_mtime(&root.join(directory)))
37 .chain(current_exe_mtime())
38 .max()?;
39
40 let now: DateTime<Utc> = Utc::now();
41 if newest.year() <= 2000 || newest > now {
42 error!(
43 "content modification time is `{newest}`, which is implausible; \
44 omitting <lastmod>. Check that the build preserves file timestamps."
45 );
46 return None;
47 }
48
49 Some(newest)
50}
51
52fn newest_mtime(root: &Path) -> Option<DateTime<Utc>> {
54 if !root.is_dir() {
55 return None;
56 }
57
58 let mut newest: Option<DateTime<Utc>> = None;
59 let mut directories: Vec<std::path::PathBuf> = vec![root.to_path_buf()];
60
61 while let Some(directory) = directories.pop() {
62 let Ok(entries) = std::fs::read_dir(&directory) else {
63 continue;
64 };
65 for entry in entries.flatten() {
66 let path: PathBuf = entry.path();
67 if path.is_dir() {
68 directories.push(path);
69 continue;
70 }
71 let Ok(modified) = entry.metadata().and_then(|meta| meta.modified()) else {
72 continue;
73 };
74 let modified: DateTime<Utc> = modified.into();
75 if newest.is_none_or(|current| modified > current) {
76 newest = Some(modified);
77 }
78 }
79 }
80
81 newest
82}
83
84fn current_exe_mtime() -> Option<DateTime<Utc>> {
86 let exe: PathBuf = std::env::current_exe().ok()?;
87 let modified: SystemTime = std::fs::metadata(exe).ok()?.modified().ok()?;
88 Some(modified.into())
89}
90
91#[cfg(test)]
92mod tests {
93 use chrono::{DateTime, Utc};
94
95 use super::{content_modified, newest_mtime};
96
97 #[test]
98 fn a_directory_that_does_not_exist_contributes_nothing() {
99 let expected: Option<DateTime<Utc>> = None;
100 let actual: Option<DateTime<Utc>> =
101 newest_mtime(std::path::Path::new("/tmp/wsb-no-such-dir"));
102 assert_eq!(expected, actual);
103 }
104
105 #[test]
106 fn the_binarys_own_timestamp_is_enough_to_produce_a_plausible_date() {
107 let actual: Option<DateTime<Utc>> = content_modified(&["/tmp/wsb-no-such-dir"]);
110 assert!(actual.is_some());
111 }
112}