use std::{
collections::HashSet,
fs,
path::{Path, PathBuf},
};
use super::sidecar::sidecar_path_for_backup;
pub(crate) fn find_latest_backup_and_sidecar(
target: &Path,
tag: &str,
) -> Option<(Option<PathBuf>, PathBuf)> {
let name = target.file_name()?.to_str()?; let parent = target.parent().unwrap_or_else(|| Path::new("."));
let rd = fs::read_dir(parent).ok()?;
let mut best: Option<(u128, PathBuf)> = None;
for entry in rd.flatten() {
let fname = entry.file_name();
let Some(s) = fname.to_str() else { continue };
let (rest_opt, wildcard) = if tag.is_empty() {
let pre = format!(".{name}.");
(s.strip_prefix(&pre), true)
} else {
let pre = format!(".{name}.{tag}.");
(s.strip_prefix(&pre), false)
};
let Some(rest) = rest_opt else { continue };
let core = if let Some(core) = rest.strip_suffix(".bak") {
core
} else if let Some(core) = rest.strip_suffix(".bak.meta.json") {
core
} else {
continue;
};
let ts_part = if wildcard {
core.rsplit('.').next().unwrap_or(core)
} else {
core
};
let Ok(ts) = ts_part.parse::<u128>() else {
continue;
};
let base = if s.ends_with(".bak.meta.json") {
parent.join(&s[..s.len() - ".meta.json".len()])
} else {
parent.join(s)
};
let is_better = match best.as_ref() {
None => true,
Some((cur, _)) => ts > *cur,
};
if is_better {
best = Some((ts, base));
}
}
let (_, base) = best?;
let sidecar = sidecar_path_for_backup(&base);
let backup_present = if base.exists() { Some(base) } else { None };
Some((backup_present, sidecar))
}
pub(crate) fn find_previous_backup_and_sidecar(
target: &Path,
tag: &str,
) -> Option<(Option<PathBuf>, PathBuf)> {
let name = target.file_name()?.to_str()?;
let parent = target.parent().unwrap_or_else(|| Path::new("."));
let mut seen = HashSet::<u128>::new();
let mut stamps: Vec<(u128, PathBuf)> = fs::read_dir(parent)
.ok()?
.filter_map(Result::ok)
.filter_map(|e| e.file_name().into_string().ok())
.filter_map(|s| {
let ok_prefix = if tag.is_empty() {
s.strip_prefix(&format!(".{name}.")).is_some()
} else {
s.strip_prefix(&format!(".{name}.{tag}.")).is_some()
};
if !ok_prefix {
return None;
}
let rest_opt = s
.strip_suffix(".bak")
.or_else(|| s.strip_suffix(".bak.meta.json"))
.map(ToString::to_string);
let core = rest_opt?;
let ts_s = core.rsplit('.').next().unwrap_or("");
let Ok(ts) = ts_s.parse::<u128>() else {
return None;
};
if !seen.insert(ts) {
return None;
}
let base = if s.ends_with(".bak.meta.json") {
parent.join(&s[..s.len() - ".meta.json".len()])
} else {
parent.join(&s)
};
Some((ts, base))
})
.collect();
if stamps.len() < 2 {
return None;
}
stamps.sort_unstable_by_key(|(ts, _)| *ts);
let (_ts, base) = stamps.get(stamps.len() - 2)?.clone();
let sidecar = sidecar_path_for_backup(&base);
let backup_present = if base.exists() { Some(base) } else { None };
Some((backup_present, sidecar))
}