feat: lazy retention sweep on /web/cases
New module retention::sweep_user_retention is invoked at the top of handle_my_cases, using the current user's retention policy. Per case: step 1 auto-closes when the newest .m4a mtime is older than auto_close_days; step 2 auto-purges when closed_at is older than auto_delete_days. Order matters - a case auto-closed in the same sweep has closed_at ~= now and naturally survives the purge check, preserving the full grace period after a vacation. Race guard: purge re-reads the close marker immediately before remove_dir_all so a concurrent upload that reopens the case cancels the purge. Every automatic action emits an info! line with reason and threshold; failures warn but never abort the sweep. Seven integration tests cover both threshold paths, the disable-via-0 escape hatch, the newest-recording rule, the vacation scenario, and the open-case skip. Tests age the comparison objects (mtime via filetime, closed_at as handwritten RFC3339) instead of faking "now" - no clock abstraction needed.
This commit is contained in:
@@ -312,6 +312,15 @@ pub async fn handle_my_cases(
|
||||
auto_trigger::try_enqueue_all_for_user(&user_root, &pipeline.analyze_tx, &config, &events_tx)
|
||||
.await;
|
||||
|
||||
// Lazy retention sweep: at most one disk scan per /web/cases visit,
|
||||
// reusing the user's retention policy from users.toml. Runs before
|
||||
// the listing scan so any auto-close/auto-purge actions are
|
||||
// reflected in the same response.
|
||||
if let Some(u) = config.users.iter().find(|u| u.slug == user.slug) {
|
||||
crate::retention::sweep_user_retention(&user_root, &user.slug, &u.retention, &events_tx)
|
||||
.await;
|
||||
}
|
||||
|
||||
let busy = pipeline.analyze_busy.0.load(Ordering::Acquire);
|
||||
let cases = scan_user_cases(&config, &user.slug, busy).await;
|
||||
let total = cases.len();
|
||||
|
||||
Reference in New Issue
Block a user