use core::time::Duration;
use std::io;
use std::path::Path;
use crate::error::FormError;
fn is_spill_name(name: &[u8]) -> bool {
let Some(rest) = name.strip_prefix(b"upload-") else {
return false;
};
let Some(hex) = rest.strip_suffix(b".part") else {
return false;
};
hex.len() == 32
&& hex
.iter()
.all(|&b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
}
pub fn sweep(dir: &Path, older_than: Duration) -> Result<usize, FormError> {
super::temp::check_dir(dir)?;
let io = |e: io::Error| FormError::Io { kind: e.kind() };
let mut removed = 0usize;
for entry in std::fs::read_dir(dir).map_err(io)? {
let Ok(entry) = entry else { continue };
let name = entry.file_name();
if !is_spill_name(name.as_encoded_bytes()) {
continue;
}
let Ok(meta) = entry.metadata() else { continue };
if meta.is_dir() {
continue;
}
let old = meta
.modified()
.ok()
.and_then(|t| t.elapsed().ok())
.is_some_and(|age| age >= older_than);
if !old {
continue;
}
if std::fs::remove_file(entry.path()).is_ok() {
removed = removed.saturating_add(1);
}
}
Ok(removed)
}
#[cfg(test)]
mod tests {
use super::is_spill_name;
#[test]
fn test_only_form_net_names_match() {
assert!(is_spill_name(
b"upload-0123456789abcdef0123456789abcdef.part"
));
for other in [
&b"upload-.part"[..],
b"upload-0123456789ABCDEF0123456789abcdef.part",
b"upload-0123456789abcdef0123456789abcde.part",
b"upload-0123456789abcdef0123456789abcdef.part.bak",
b"x-upload-0123456789abcdef0123456789abcdef.part",
b"upload-0123456789abcdef0123456789abcdeg.part",
] {
assert!(!is_spill_name(other), "{other:?}");
}
}
}