feat(server/m7-381): RunScan orchestrator chains library+backfill+enrich

Adds internal/library/scanrun.go with RunScan, which creates a scan_runs
row and sequences file-walk → MBID backfill → cover enrich, persisting
per-stage tallies as jsonb. Extends coverart.EnrichBatch to return
(processed, succeeded, failed, err) so the orchestrator can classify
outcomes. Replaces main.go's two separate boot goroutines with a single
RunScan call; passes nil scanner in the no-startup-scan branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-04 20:08:48 -04:00
parent 1ed6ca0309
commit 65a3c4892c
4 changed files with 203 additions and 33 deletions
+30 -12
View File
@@ -103,27 +103,45 @@ func (e *Enricher) EnrichAlbum(ctx context.Context, albumID pgtype.UUID) error {
// EnrichBatch drains up to limit albums whose cover_art_source is NULL.
// Iterates serially — the fetcher's rate limiter prevents bursts.
func (e *Enricher) EnrichBatch(ctx context.Context, limit int) (int, error) {
// Returns per-batch tallies for the orchestrator's scan-run record.
//
// "succeeded" means the album ended up with a non-NULL cover_art_source
// other than 'none' (i.e. we found art via sidecar or MBCAA). "failed"
// counts both EnrichAlbum errors and rows that ended at 'none'.
func (e *Enricher) EnrichBatch(ctx context.Context, limit int) (processed, succeeded, failed int, err error) {
if limit <= 0 {
return 0, nil
return 0, 0, 0, nil
}
q := dbq.New(e.pool)
rows, err := q.ListAlbumsMissingCover(ctx, int32(limit))
if err != nil {
return 0, fmt.Errorf("list missing: %w", err)
rows, qerr := q.ListAlbumsMissingCover(ctx, int32(limit))
if qerr != nil {
return 0, 0, 0, fmt.Errorf("list missing: %w", qerr)
}
processed := 0
for _, r := range rows {
if ctx.Err() != nil {
return processed, ctx.Err()
}
if err := e.EnrichAlbum(ctx, r.ID); err != nil {
e.logger.Warn("coverart: batch entry failed",
"album_id", uuidString(r.ID), "err", err)
return processed, succeeded, failed, ctx.Err()
}
processed++
if eerr := e.EnrichAlbum(ctx, r.ID); eerr != nil {
e.logger.Warn("coverart: batch entry failed",
"album_id", uuidString(r.ID), "err", eerr)
failed++
continue
}
// Re-read the source to classify the outcome.
row, rerr := q.GetAlbumWithFirstTrackPath(ctx, r.ID)
if rerr != nil {
failed++
continue
}
if row.CoverArtSource != nil &&
(*row.CoverArtSource == "sidecar" || *row.CoverArtSource == "mbcaa" || *row.CoverArtSource == "embedded") {
succeeded++
} else {
failed++
}
}
return processed, nil
return processed, succeeded, failed, nil
}
// EnrichRetryMissing drains both NULL and 'none' rows. Used by the admin