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:
@@ -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
|
||||
|
||||
@@ -219,7 +219,7 @@ func TestEnrichBatch_DrainsNullSourceOnly(t *testing.T) {
|
||||
}
|
||||
|
||||
e := NewEnricher(pool, discardLogger(), nil, false)
|
||||
processed, err := e.EnrichBatch(context.Background(), 100)
|
||||
processed, _, _, err := e.EnrichBatch(context.Background(), 100)
|
||||
if err != nil {
|
||||
t.Fatalf("batch: %v", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user