use std::path::Path;
use std::time::Duration;
use crate::paths::workbenches_dir;
use crate::utils::time::now_secs;
use super::model::RoutineStore;
mod runtime;
mod session;
mod snapshot;
mod ttl;
use session::{note_forced_kill, tmux_kill_session, tmux_session_alive};
pub const CLEANUP_INTERVAL: Duration = Duration::from_secs(60 * 60);
pub(super) fn parse_workbench_name(name: &str) -> Option<(&str, u64)> {
let (slug, ts) = name.rsplit_once('-')?;
if slug.is_empty() || ts.is_empty() || !ts.bytes().all(|byte| byte.is_ascii_digit()) {
return None;
}
Some((slug, ts.parse().ok()?))
}
fn is_expired(now: u64, ts: u64, ttl: u64) -> bool {
now.saturating_sub(ts) > ttl
}
fn reap_dir(
dir: &Path,
now: u64,
ttl_for: &dyn Fn(&str) -> u64,
max_runtime_for: &dyn Fn(&str) -> u64,
is_alive: &dyn Fn(&str) -> bool,
kill: &dyn Fn(&str),
) -> usize {
let Ok(entries) = std::fs::read_dir(dir) else {
return 0;
};
let mut removed = 0;
for entry in entries.flatten() {
if !entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false) {
continue;
}
let name = entry.file_name().to_string_lossy().into_owned();
let Some((slug, ts)) = parse_workbench_name(&name) else {
continue;
};
let session = format!("moadim-{name}");
let mut alive = is_alive(&session);
if alive && is_expired(now, ts, max_runtime_for(slug)) {
kill(&session);
note_forced_kill(&entry.path());
log::warn!("cleanup: killed routine session {session:?} exceeding max runtime");
alive = false;
}
if alive {
continue;
}
if !is_expired(now, ts, ttl_for(slug)) {
continue;
}
match std::fs::remove_dir_all(entry.path()) {
Ok(()) => {
removed += 1;
log::info!("cleanup: removed expired workbench {name:?}");
}
Err(err) => log::warn!("cleanup: failed to remove workbench {name:?}: {err}"),
}
}
removed
}
pub fn cleanup_expired_workbenches(store: &RoutineStore) -> usize {
let ttls = snapshot::snapshot_ttls(store);
let max_runtimes = snapshot::snapshot_max_runtimes(store);
let ttl_for = |slug: &str| snapshot::ttl_for(&ttls, slug);
let max_runtime_for = |slug: &str| snapshot::max_runtime_for(&max_runtimes, slug);
reap_dir(
&workbenches_dir(),
now_secs(),
&ttl_for,
&max_runtime_for,
&tmux_session_alive,
&tmux_kill_session,
)
}
#[cfg(test)]
#[path = "cleanup_tests.rs"]
mod cleanup_tests;