use std::path::Path;
use serde::Deserialize;
use crate::config::config_dir;
use crate::store::Store;
pub const DEFAULT_RETENTION_SECS: i64 = 604_800;
#[derive(Deserialize)]
struct ConfigFile {
store: Option<StoreSection>,
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct StoreSection {
retention: Option<u64>,
}
pub(crate) fn parse_retention(content: &str) -> Result<i64, String> {
let cf: ConfigFile = toml::from_str(content).map_err(|e| format!("cannot parse: {e}"))?;
match cf.store.and_then(|s| s.retention) {
None => Ok(DEFAULT_RETENTION_SECS),
Some(n) => i64::try_from(n).map_err(|_| format!("[store] retention {n} is too large")),
}
}
fn read_retention(path: &Path) -> Result<i64, String> {
let content = match std::fs::read_to_string(path) {
Ok(c) => c,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(DEFAULT_RETENTION_SECS),
Err(e) => return Err(format!("{}: {e}", path.display())),
};
parse_retention(&content).map_err(|e| format!("{}: {e}", path.display()))
}
pub(crate) fn load_retention() -> i64 {
load_retention_from(&config_dir().join("config.toml"))
}
pub(crate) fn load_retention_from(path: &Path) -> i64 {
read_retention(path).unwrap_or_else(|reason| {
eprintln!("oo: warning: {reason}; using default retention {DEFAULT_RETENTION_SECS}s");
DEFAULT_RETENTION_SECS
})
}
pub(crate) fn purge_stale(store: &mut dyn Store, project_id: &str) {
purge_with(store, project_id, load_retention());
}
pub(crate) fn purge_with(store: &mut dyn Store, project_id: &str, retention: i64) {
if retention == 0 {
return;
}
if let Err(e) = store.cleanup_stale(project_id, retention) {
eprintln!("oo: warning: retention purge failed: {e}");
}
}
#[cfg(test)]
#[path = "retention_tests.rs"]
mod retention_tests;