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
+2 -2
View File
@@ -25,9 +25,9 @@ describe('admin covers API', () => {
});
it('refetchMissingCovers POSTs to the bulk endpoint', async () => {
(api.post as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ queued: 7 });
(api.post as unknown as ReturnType<typeof vi.fn>).mockResolvedValueOnce({ started: true });
const got = await refetchMissingCovers();
expect(api.post).toHaveBeenCalledWith('/api/admin/covers/refetch-missing', {});
expect(got.queued).toBe(7);
expect(got.started).toBe(true);
});
});
+1 -1
View File
@@ -187,7 +187,7 @@ export async function refetchAlbumCover(albumId: string): Promise<RefetchAlbumCo
}
export type RefetchMissingResponse = {
queued: number;
started: boolean;
};
export async function refetchMissingCovers(): Promise<RefetchMissingResponse> {
+4 -2
View File
@@ -318,8 +318,10 @@
bulkBusy = true;
bulkResult = null;
try {
const { queued } = await refetchMissingCovers();
bulkResult = `Queued ${queued} albums for cover refetch.`;
const { started } = await refetchMissingCovers();
bulkResult = started
? 'Refetching all missing covers — local sources are fast, remote providers throttle per their limits.'
: 'Could not start the cover refetch.';
await client.invalidateQueries({ queryKey: qk.coverage() });
} catch (e) {
bulkResult = `Failed: ${errCode(e)}`;
+1 -1
View File
@@ -41,7 +41,7 @@ vi.mock('$lib/api/admin', async () => {
deleteQuarantineFile: vi.fn().mockResolvedValue({}),
deleteQuarantineViaLidarr: vi.fn().mockResolvedValue({}),
triggerScan: vi.fn().mockResolvedValue({}),
refetchMissingCovers: vi.fn().mockResolvedValue({ queued: 0 }),
refetchMissingCovers: vi.fn().mockResolvedValue({ started: true }),
researchMissingArt: vi.fn().mockResolvedValue({ version: 1 }),
createScanScheduleQuery: vi.fn(() =>
readable({