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
+22 -20
View File
@@ -83,32 +83,34 @@ func run() error {
})
coverEnricher := coverart.NewEnricher(pool, logger.With("component", "coverart"), coverFetcher, cfg.Library.CoverArtFromMBCAA)
// One-shot MBID backfill: walks albums with NULL mbid, reads tags from
// one track per album, persists the MBID. Filling in MBIDs unblocks the
// cover enricher for libraries that were imported before scanner-side
// MBID extraction landed (M7 #380).
go func() {
if _, err := library.BackfillMBIDs(ctx, pool, logger.With("component", "mbid_backfill"), 5000); err != nil {
logger.Warn("mbid backfill failed", "err", err)
}
}()
// One unified scan chain: library walk → MBID backfill → cover enrich.
// Boot-time scan and manual-trigger scans share this path; results land
// in scan_runs for the admin overview.
if cfg.Library.ScanOnStartup && len(cfg.Library.ScanPaths) > 0 {
go func() {
if _, err := scanner.Scan(ctx); err != nil {
logger.Error("startup scan failed", "err", err)
}
// Enrich any newly-imported albums (and resume any partial backlog).
if _, err := coverEnricher.EnrichBatch(ctx, cfg.Library.CoverArtBackfillCap); err != nil {
logger.Warn("post-scan cover enrichment failed", "err", err)
if _, err := library.RunScan(ctx, pool, scanner, coverEnricher,
logger.With("component", "scan_run"),
library.RunScanConfig{
BackfillCap: 5000,
EnrichCap: cfg.Library.CoverArtBackfillCap,
},
); err != nil {
logger.Warn("startup scan run failed", "err", err)
}
}()
} else {
// No startup scan — still run the boot backfill once for any rows
// the previous run didn't get to.
// No startup file walk, but still run backfill + enrich so cover
// progress doesn't stall just because the operator disabled
// scan-on-startup.
go func() {
if _, err := coverEnricher.EnrichBatch(ctx, cfg.Library.CoverArtBackfillCap); err != nil {
logger.Warn("boot cover enrichment failed", "err", err)
if _, err := library.RunScan(ctx, pool, nil, coverEnricher,
logger.With("component", "scan_run"),
library.RunScanConfig{
BackfillCap: 5000,
EnrichCap: cfg.Library.CoverArtBackfillCap,
},
); err != nil {
logger.Warn("boot-only scan run failed", "err", err)
}
}()
}