feat(coverart): #388 remove global cover-art backfill cap

Operator feedback (2026-05-09): the 500-album cap meant cover art rolled
in over many nightly runs even when local sources (sidecar/embedded)
could finish in minutes. Remove the global cap; rely on the existing
per-provider HTTP throttle (coverart httpClient MinInterval) so local
art is disk-speed and remote providers stay TOS-friendly.

- enricher.go / artist_enricher.go: EnrichBatch, EnrichArtistBatch,
  EnrichRetryMissing now treat limit<0 as unbounded (0 still = stage
  disabled). The cap was a SQL LIMIT; unbounded uses max int32.
- main.go: RunScanConfig EnrichCap/ArtistEnrichCap = -1 (unbounded).
- Drop LibraryConfig.CoverArtBackfillCap + the
  MINSTREL_LIBRARY_COVERART_BACKFILL_CAP env var.
- Drop the now-dead coverBackfillCap param threaded through
  server.New + api.Mount + the handlers struct.
- Admin bulk refetch (/api/admin/covers/refetch-missing) now drains
  unbounded; response {queued:int} → {started:bool} (the count is
  unknowable synchronously for a fire-and-forget drain). Web copy +
  client type + Go/web tests updated to match.

No doc refs existed (config.example.yaml / docker-compose / README
never documented the env var).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 18:41:21 -04:00
parent 4fca0e66cb
commit 005965d6de
14 changed files with 107 additions and 93 deletions
+8 -2
View File
@@ -144,15 +144,21 @@ func (e *Enricher) recordArtistArt(ctx context.Context, artistID pgtype.UUID, th
// errored). The JSON tally written to scan_runs keeps the existing
// processed/succeeded/failed shape for backwards compat with the
// admin overview UI; the log line is the operator's diagnostic.
// limit: 0 = stage disabled; >0 = bounded; <0 = unbounded (#388 —
// no global cap; remote providers self-throttle per provider).
func (e *Enricher) EnrichArtistBatch(ctx context.Context, limit int, dataDir string,
progressCb func(processed, succeeded, failed int)) (processed, succeeded, failed int, err error) {
if limit <= 0 {
if limit == 0 {
return 0, 0, 0, nil
}
queryLimit := int32(limit)
if limit < 0 {
queryLimit = 1<<31 - 1 // unbounded
}
q := dbq.New(e.pool)
rows, qerr := q.ListArtistsMissingArt(ctx, dbq.ListArtistsMissingArtParams{
ArtistArtSourcesVersion: e.settings.CurrentVersion(),
Limit: int32(limit),
Limit: queryLimit,
})
if qerr != nil {
return 0, 0, 0, fmt.Errorf("list missing artist art: %w", qerr)
+18 -4
View File
@@ -213,15 +213,23 @@ func (e *Enricher) recordAlbumCover(ctx context.Context, albumID pgtype.UUID, pa
// JSON tally written to scan_runs keeps the existing
// processed/succeeded/failed shape for backwards compat with the
// admin overview UI; the log line is the operator's diagnostic.
// limit semantics: 0 = stage disabled (skip); >0 = bounded batch;
// <0 = unbounded — walk every album needing art. The global cap was
// removed (#388); external providers self-throttle via their
// per-provider httpClient MinInterval, local sources run at disk speed.
func (e *Enricher) EnrichBatch(ctx context.Context, limit int,
progressCb func(processed, succeeded, failed int)) (processed, succeeded, failed int, err error) {
if limit <= 0 {
if limit == 0 {
return 0, 0, 0, nil
}
queryLimit := int32(limit)
if limit < 0 {
queryLimit = 1<<31 - 1 // unbounded
}
q := dbq.New(e.pool)
rows, qerr := q.ListAlbumsMissingCover(ctx, dbq.ListAlbumsMissingCoverParams{
CoverArtSourcesVersion: e.settings.CurrentVersion(),
Limit: int32(limit),
Limit: queryLimit,
})
if qerr != nil {
return 0, 0, 0, fmt.Errorf("list missing: %w", qerr)
@@ -293,12 +301,18 @@ func (e *Enricher) logBatchSummary(stage string, eligible, processed, succeeded,
// EnrichRetryMissing drains both NULL and 'none' rows. Used by the
// admin bulk-retry endpoint. Clears 'none' to NULL first so
// EnrichAlbum picks them up.
// limit: 0 = no-op; >0 = bounded; <0 = unbounded (retry every
// missing/none album). Admin bulk-retry passes <0 post-#388.
func (e *Enricher) EnrichRetryMissing(ctx context.Context, limit int) (int, error) {
if limit <= 0 {
if limit == 0 {
return 0, nil
}
queryLimit := int32(limit)
if limit < 0 {
queryLimit = 1<<31 - 1 // unbounded
}
q := dbq.New(e.pool)
rows, err := q.ListAlbumsRetryMissing(ctx, int32(limit))
rows, err := q.ListAlbumsRetryMissing(ctx, queryLimit)
if err != nil {
return 0, fmt.Errorf("list retry missing: %w", err)
}