use std::path::Path;
use std::time::{Duration, SystemTime};
use crate::error::ClusterError;
pub fn sweep_orphans(
data_dir: &Path,
max_age_secs: u64,
) -> Result<(usize, Vec<ClusterError>), ClusterError> {
let recv_dir = data_dir.join("recv_snapshots");
if !recv_dir.exists() {
return Ok((0, vec![]));
}
let entries = std::fs::read_dir(&recv_dir).map_err(|e| ClusterError::Storage {
detail: format!("read_dir recv_snapshots: {e}"),
})?;
let max_age = Duration::from_secs(max_age_secs);
let now = SystemTime::now();
let mut removed = 0usize;
let mut errors = Vec::new();
for entry_result in entries {
let entry = match entry_result {
Ok(e) => e,
Err(e) => {
errors.push(ClusterError::Storage {
detail: format!("iterate recv_snapshots: {e}"),
});
continue;
}
};
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("partial") {
continue;
}
let group_id: u64 = path
.file_stem()
.and_then(|s| s.to_str())
.and_then(|s| s.parse().ok())
.unwrap_or(0);
let age = match entry.metadata().and_then(|m| m.modified()) {
Ok(mtime) => match now.duration_since(mtime) {
Ok(d) => d,
Err(_) => Duration::ZERO, },
Err(e) => {
errors.push(ClusterError::PartialSnapshotCleanupFailed {
group_id,
detail: format!("stat {}: {e}", path.display()),
});
continue;
}
};
if age < max_age {
continue;
}
if let Err(e) = std::fs::remove_file(&path) {
errors.push(ClusterError::PartialSnapshotCleanupFailed {
group_id,
detail: format!("remove {}: {e}", path.display()),
});
} else {
removed += 1;
}
}
Ok((removed, errors))
}