use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
pub const BACKUP_KEEP: usize = 10;
pub fn backup_file(path: &Path, dir: &Path, name: &str) -> Result<Option<PathBuf>, String> {
let millis = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0, |d| d.as_millis());
backup_file_at(path, dir, name, millis, BACKUP_KEEP)
}
pub(super) fn backup_file_at(
path: &Path,
dir: &Path,
name: &str,
stamp: u128,
keep: usize,
) -> Result<Option<PathBuf>, String> {
if !path.is_file() {
return Ok(None);
}
std::fs::create_dir_all(dir).map_err(|e| format!("cannot create {}: {e}", dir.display()))?;
let extension = path.extension().and_then(|e| e.to_str()).unwrap_or("bak");
let copy = dir.join(format!("{name}-{stamp:020}.{extension}"));
std::fs::copy(path, ©).map_err(|e| format!("cannot back up {}: {e}", path.display()))?;
prune(dir, name, keep);
Ok(Some(copy))
}
fn prune(dir: &Path, name: &str, keep: usize) {
let prefix = format!("{name}-");
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
let mut copies: Vec<PathBuf> = entries
.flatten()
.map(|entry| entry.path())
.filter(|path| {
path.file_name()
.and_then(|n| n.to_str())
.is_some_and(|n| n.starts_with(&prefix))
})
.collect();
copies.sort();
let surplus = copies.len().saturating_sub(keep);
for old in &copies[..surplus] {
let _ = std::fs::remove_file(old);
}
}