use std::path::Path;
use std::time::Duration;
use chrono::Utc;
use sqlx::SqlitePool;
use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;
use crate::db::{audit, emails, settings};
use crate::error::Result;
use crate::pipeline::ingest::transcript_path_for;
async fn remove_email_artifacts(raw_path: &str) {
let _ = tokio::fs::remove_file(raw_path).await;
let _ = tokio::fs::remove_file(transcript_path_for(std::path::Path::new(raw_path))).await;
}
pub async fn cap_per_mailbox(
pool: &SqlitePool,
mailbox_id: &str,
keep_max: i64,
_raw_dir: &Path,
) -> Result<u64> {
if keep_max <= 0 {
return Ok(0);
}
let victims = emails::trim_mailbox(pool, mailbox_id, keep_max).await?;
if victims.is_empty() {
return Ok(0);
}
let ids: Vec<String> = victims.iter().map(|(id, _)| id.clone()).collect();
emails::delete_by_ids(pool, &ids).await?;
for (_, raw_path) in &victims {
remove_email_artifacts(raw_path).await;
}
Ok(victims.len() as u64)
}
pub fn spawn_periodic(
pool: SqlitePool,
cancel: CancellationToken,
interval: Duration,
) -> JoinHandle<()> {
tokio::spawn(async move {
let mut tick = tokio::time::interval(interval);
tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
loop {
tokio::select! {
_ = cancel.cancelled() => return,
_ = tick.tick() => {
if let Err(e) = run_once(&pool).await {
tracing::warn!(target: "postcrate::retention",
error = %e, "retention sweep failed");
}
}
}
}
})
}
async fn run_once(pool: &SqlitePool) -> Result<()> {
let s = settings::load_all(pool).await?;
if s.inbox.auto_clear_after_days > 0 {
let cutoff = Utc::now().timestamp_millis()
- (i64::from(s.inbox.auto_clear_after_days) * 86_400_000);
let victims = emails::list_older_than(pool, cutoff).await?;
if !victims.is_empty() {
let ids: Vec<String> = victims.iter().map(|(id, _, _)| id.clone()).collect();
emails::delete_by_ids(pool, &ids).await?;
for (_, _, raw_path) in &victims {
remove_email_artifacts(raw_path).await;
}
tracing::info!(target: "postcrate::retention",
count = victims.len(), "age-purged emails");
}
}
if s.advanced.audit_retain_days > 0 {
let pruned = audit::prune_older_than(pool, s.advanced.audit_retain_days).await?;
if pruned > 0 {
tracing::info!(target: "postcrate::retention", count = pruned, "pruned audit rows");
}
}
Ok(())
}