use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime};
use supercode_interchange::session::{ResidueSegment, Session, SessionFormat};
const MAX_AGE: Duration = Duration::from_secs(90 * 24 * 60 * 60);
const MAX_BYTES: u64 = 4 * 1024 * 1024 * 1024;
const PRUNE_EVERY: Duration = Duration::from_secs(24 * 60 * 60);
fn store_root() -> PathBuf {
crate::agent::global_instructions_dir().join("residue")
}
fn key_dir(format: SessionFormat, key: &str) -> PathBuf {
store_root()
.join(format!("{format:?}").to_lowercase())
.join(key.get(..2).unwrap_or("00"))
.join(key)
}
pub(crate) fn store_segments(session: &Session) {
let segments = session.residue_segments();
if segments.is_empty() {
return;
}
prune_if_due(&store_root());
for keyed in segments {
let path = key_dir(keyed.format, &keyed.key).join(format!("{}.json", keyed.segment.source));
if path.exists() {
continue;
}
let Some(parent) = path.parent() else {
continue;
};
let Ok(json) = serde_json::to_vec(&keyed.segment) else {
continue;
};
let temp = path.with_extension("json.tmp");
if std::fs::create_dir_all(parent).is_ok() && std::fs::write(&temp, json).is_ok() {
let _ = std::fs::rename(&temp, &path);
}
}
}
pub(crate) fn lookup(format: SessionFormat, key: &str) -> Vec<ResidueSegment> {
let Ok(entries) = std::fs::read_dir(key_dir(format, key)) else {
return Vec::new();
};
let mut segments = Vec::new();
for entry in entries.flatten() {
let path = entry.path();
if path.extension().and_then(|e| e.to_str()) != Some("json") {
continue;
}
let Some(segment) = std::fs::read(&path)
.ok()
.and_then(|bytes| serde_json::from_slice::<ResidueSegment>(&bytes).ok())
else {
continue;
};
if let Ok(file) = std::fs::File::options().write(true).open(&path) {
let _ = file.set_modified(SystemTime::now());
}
segments.push(segment);
}
segments
}
fn prune_if_due(root: &Path) {
let marker = root.join(".pruned");
let due = std::fs::metadata(&marker)
.and_then(|metadata| metadata.modified())
.map(|at| at.elapsed().map(|age| age >= PRUNE_EVERY).unwrap_or(true))
.unwrap_or(true);
if !due || std::fs::create_dir_all(root).is_err() || std::fs::write(&marker, b"").is_err() {
return;
}
let mut segments = Vec::new();
let mut pending = vec![root.to_path_buf()];
while let Some(dir) = pending.pop() {
let Ok(entries) = std::fs::read_dir(&dir) else {
continue;
};
for entry in entries.flatten() {
let path = entry.path();
let Ok(metadata) = entry.metadata() else {
continue;
};
if metadata.is_dir() {
pending.push(path);
} else if path.extension().and_then(|e| e.to_str()) == Some("json") {
let modified = metadata.modified().unwrap_or(SystemTime::UNIX_EPOCH);
segments.push((modified, metadata.len(), path));
}
}
}
segments.sort_by_key(|(modified, _, _)| *modified);
let mut total: u64 = segments.iter().map(|(_, size, _)| size).sum();
for (modified, size, path) in segments {
let stale = modified.elapsed().map(|age| age > MAX_AGE).unwrap_or(false);
if !stale && total <= MAX_BYTES {
break;
}
if std::fs::remove_file(&path).is_ok() {
total -= size;
}
}
}