SeaORM: enclosures, downloads and the reaper
Twelve enclosure functions move to SeaORM: recording, the download queue, marking done or failed, requeueing, and what the reaper may delete. INSERT OR IGNORE becomes ON CONFLICT DO NOTHING; the reaper's read verdict is true or false rather than 1 or 0, which Postgres would type as a 32-bit integer and refuse to read as an i64; `read = 1` and `flagged = 1` test the booleans themselves. retention::run and its callers (reap, rm, retire_group, retire_stranded) become async. The reaper deletes files, so it was checked on a copy of production against the old SQL on the same file: all 2,195 candidates, identical and in the same order. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,25 +45,25 @@ pub fn aged(candidates: &[Candidate], cutoff: i64) -> Vec<Candidate> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn run(cfg: &Config, db: &Db, dry_run: bool) -> Result<Report> {
|
||||
pub async fn run(cfg: &Config, db: &Db, dry_run: bool) -> Result<Report> {
|
||||
let mut report = Report::default();
|
||||
|
||||
// Someone may have deleted a file by hand; the row must stop claiming it exists.
|
||||
for (id, path) in db.missing_files()? {
|
||||
for (id, path) in db.missing_files().await? {
|
||||
if !dry_run {
|
||||
db.mark_reaped(id)?;
|
||||
db.mark_reaped(id).await?;
|
||||
}
|
||||
tracing::debug!(path, "file gone, row reaped");
|
||||
report.reconciled += 1;
|
||||
}
|
||||
|
||||
let candidates = db.reap_candidates()?;
|
||||
let candidates = db.reap_candidates().await?;
|
||||
|
||||
if cfg.general.max_age_days > 0 {
|
||||
let cutoff = now() - (cfg.general.max_age_days * 86_400) as i64;
|
||||
report.aged_out = aged(&candidates, cutoff);
|
||||
for c in &report.aged_out {
|
||||
report.bytes_freed += remove(db, c, dry_run)?;
|
||||
report.bytes_freed += remove(db, c, dry_run).await?;
|
||||
}
|
||||
if !dry_run {
|
||||
report.entries_pruned = db.prune_entries(cutoff)?;
|
||||
@@ -81,14 +81,14 @@ pub fn run(cfg: &Config, db: &Db, dry_run: bool) -> Result<Report> {
|
||||
let total: u64 = remaining.iter().map(|c| c.bytes.max(0) as u64).sum();
|
||||
report.over_quota = pick(&remaining, total, limit);
|
||||
for c in &report.over_quota {
|
||||
report.bytes_freed += remove(db, c, dry_run)?;
|
||||
report.bytes_freed += remove(db, c, dry_run).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(report)
|
||||
}
|
||||
|
||||
fn remove(db: &Db, c: &Candidate, dry_run: bool) -> Result<u64> {
|
||||
async fn remove(db: &Db, c: &Candidate, dry_run: bool) -> Result<u64> {
|
||||
if dry_run {
|
||||
return Ok(c.bytes.max(0) as u64);
|
||||
}
|
||||
@@ -100,7 +100,7 @@ fn remove(db: &Db, c: &Candidate, dry_run: bool) -> Result<u64> {
|
||||
tracing::warn!(path = c.path, error = %e, "could not delete");
|
||||
return Ok(0);
|
||||
}
|
||||
db.mark_reaped(c.id)?;
|
||||
db.mark_reaped(c.id).await?;
|
||||
Ok(size)
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let got: Vec<i64> = db.reap_candidates().unwrap().iter().map(|c| c.id).collect();
|
||||
let got: Vec<i64> = db.reap_candidates().await.unwrap().iter().map(|c| c.id).collect();
|
||||
assert_eq!(
|
||||
got,
|
||||
vec![4, 2, 3],
|
||||
|
||||
Reference in New Issue
Block a user