Step 5: quota and age retention
Oldest-first reaper with the original's 50 MB headroom pad, plus a reconcile pass for files deleted by hand and pruning of stale entries. The Python meant to reap only read, unflagged episodes but a missing import and a typo made that filter throw on every candidate. Requiring read=1 would be equally dead headless, so flagged is the keep-forever marker and read only decides ordering. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RPyeapneuXrCdojsaiXGbe
This commit is contained in:
35
src/main.rs
35
src/main.rs
@@ -2,6 +2,7 @@ mod config;
|
||||
mod db;
|
||||
mod download;
|
||||
mod feed;
|
||||
mod retention;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
@@ -22,6 +23,12 @@ struct Cli {
|
||||
enum Command {
|
||||
/// Show configured feeds and their state
|
||||
List,
|
||||
/// Delete old or over-quota downloads
|
||||
Reap {
|
||||
/// Show what would go, delete nothing
|
||||
#[arg(long)]
|
||||
dry_run: bool,
|
||||
},
|
||||
/// Scan feeds for new entries
|
||||
Fetch {
|
||||
/// Only this feed id
|
||||
@@ -41,7 +48,15 @@ async fn main() -> Result<()> {
|
||||
|
||||
match cli.command {
|
||||
Command::List => list(&cfg, &db, &config_path),
|
||||
Command::Fetch { feed, force } => fetch(&cfg, &db, feed.as_deref(), force).await,
|
||||
Command::Fetch { feed, force } => {
|
||||
// Make room before pulling more down, as the original did per download.
|
||||
report_reap(&retention::run(&cfg, &db, false)?, false);
|
||||
fetch(&cfg, &db, feed.as_deref(), force).await
|
||||
}
|
||||
Command::Reap { dry_run } => {
|
||||
report_reap(&retention::run(&cfg, &db, dry_run)?, true);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,6 +269,24 @@ async fn fetch_one(
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn report_reap(r: &retention::Report, verbose: bool) {
|
||||
for c in r.aged_out.iter().chain(r.over_quota.iter()) {
|
||||
println!("reap {} ({:.1} MB)", c.path, c.bytes.max(0) as f64 / 1_048_576.0);
|
||||
}
|
||||
if r.reconciled > 0 {
|
||||
println!("{} row(s) pointed at files that were already gone", r.reconciled);
|
||||
}
|
||||
let total = r.aged_out.len() + r.over_quota.len();
|
||||
if total > 0 || verbose {
|
||||
println!(
|
||||
"reaped {total} file(s), {:.1} MB, {} stale entr{} pruned",
|
||||
r.bytes_freed as f64 / 1_048_576.0,
|
||||
r.entries_pruned,
|
||||
if r.entries_pruned == 1 { "y" } else { "ies" }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn ago(t: Option<i64>) -> String {
|
||||
let Some(t) = t else { return "never".into() };
|
||||
format!("{} ago", duration((db::now() - t).max(0) as u64))
|
||||
|
||||
Reference in New Issue
Block a user