use std::path::Path;
use std::time::{Duration, SystemTime};
pub(crate) fn save_temp_prefix(dest: &Path) -> String {
format!(
"{}.tmp.",
dest.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| "graph.kgl".to_string()),
)
}
const UNIDENTIFIED_TEMP_MAX_AGE: Duration = Duration::from_secs(24 * 60 * 60);
pub fn reap_stale_save_temps(path: &Path) -> usize {
let prefix = save_temp_prefix(path);
let dir = match path.parent().filter(|p| !p.as_os_str().is_empty()) {
Some(d) => d.to_path_buf(),
None => Path::new(".").to_path_buf(),
};
let entries = match std::fs::read_dir(&dir) {
Ok(entries) => entries,
Err(_) => return 0,
};
let mut reaped = 0;
for entry in entries.flatten() {
let name = entry.file_name();
let Some(name) = name.to_str() else { continue };
let Some(pid) = temp_owner_pid(name, &prefix) else {
continue;
};
if pid == std::process::id() {
continue;
}
let abandoned = match process_is_alive(pid) {
Some(alive) => !alive,
None => temp_is_older_than(&entry, UNIDENTIFIED_TEMP_MAX_AGE),
};
if abandoned && std::fs::remove_file(entry.path()).is_ok() {
reaped += 1;
}
}
reaped
}
fn temp_owner_pid(name: &str, prefix: &str) -> Option<u32> {
let rest = name.strip_prefix(prefix)?;
let (pid, nonce) = rest.split_once('.')?;
nonce.parse::<u64>().ok()?;
let pid: u32 = pid.parse().ok()?;
(pid > 0).then_some(pid)
}
fn temp_is_older_than(entry: &std::fs::DirEntry, max_age: Duration) -> bool {
entry
.metadata()
.and_then(|m| m.modified())
.ok()
.and_then(|modified| SystemTime::now().duration_since(modified).ok())
.is_some_and(|age| age > max_age)
}
#[cfg(unix)]
fn process_is_alive(pid: u32) -> Option<bool> {
if unsafe { libc::kill(pid as libc::pid_t, 0) } == 0 {
return Some(true);
}
match std::io::Error::last_os_error().raw_os_error() {
Some(libc::ESRCH) => Some(false),
Some(libc::EPERM) => Some(true),
_ => None,
}
}
#[cfg(not(unix))]
fn process_is_alive(_pid: u32) -> Option<bool> {
None
}