navidrome/model
Deluan Quintão ffc68e29db
feat(cli): add artwork cancel to call off queued artwork work (#6006)
* feat(cli): add `artwork cancel` to call off queued artwork work

A bulk backfill had no off switch. Changing an artwork setting bumps the config
fingerprint, which enqueues every entity in the library, and the only way to stop
it was to turn agents off -- which changes the fingerprint again and enqueues a
second full backfill. The escape hatch was the trap.

`artwork cancel` deletes pending queue rows selected by --kind and/or --priority,
with the --dry-run/confirm/-y flow `reprocess` already uses. Cancelling by
priority is the point: it drops a runaway backfill while leaving the bump-priority
rows an operator queued by hand.

It only touches the queue. Resolved artwork and the item_artwork state behind
`artwork explain` are left alone, and the trace of why a cancelled item last
failed goes with its row. Preserving that trace would mean writing it to
last_failure, which `explain` prints under "Gave up after" -- reporting a
cancellation as an exhausted retry budget. The help text says the trace is
discarded instead.

Two limits the help text states, because neither is guessable: work already
dequeued is not interrupted, and an item with no artwork state yet can be queued
again by the hourly missing-artwork recheck. Cancel calls off queued work; it
does not stop the worker.

--kind validates against RefreshableKinds, not the RecheckKinds `reprocess` uses:
the queue holds media file rows, so --all has to reach them. PurgeQueued follows
the repository's naming rule -- it finds its own rows and reports how many went --
and ignores retry_at, since a row still backing off is pending work. The preview
reuses CountByKindAndPriority rather than adding a counter. reprocessConfirm
became confirmUnlessYes(yes, in, verb) now that two commands prompt.

* refactor(cli): share the artwork queue filter between the preview and the delete

Follow-up cleanup on the previous commit; no change to what the command does,
apart from --all, noted below.

The "which rows does cancel touch" predicate was written three times: once as SQL
in PurgeQueued, once in Go in cmd's matchingQueueStats, and once more in the mock.
The preview and the delete could therefore drift, and the mock would keep the
tests green while they did. persistence now has one artworkQueueFilter, shared by
PurgeQueued and a new CountQueued, and cmd does no filtering at all.

That also makes the preview cheaper. It counted the whole queue and filtered in
Go, so `artwork cancel --kind al` scanned every row of every kind to print a
handful. CountQueued pushes the filter into SQL, which the drain index serves as a
range seek. CountByKindAndPriority is gone: it is CountQueued(nil, nil).

--all now selects with an empty filter instead of enumerating RefreshableKinds.
It is what the flag help already claimed, and the enumeration was narrower than
its own documentation -- a queue row whose item_kind this build does not know
survived `--all` with no flag combination able to remove it. It also restores
SQLite's truncate path: measured with EXPLAIN QUERY PLAN, a bare DELETE plans to
nothing, while `WHERE (1=1)` -- which an empty squirrel And renders -- plans to a
full index scan. A test pins the filter's emptiness so that cannot regress
silently.

Also folded together three copies of the parse-and-dedup loop (parseAll), two
copies of the queue-stats table (printQueueStats, now shared with `artwork
status`), two copies of the stat sum (queueTotal), and four copies of the
kind-to-prefix mapping (model.KindPrefixes). The PurgeQueued specs became one
DescribeTable that asserts count and delete agree on every selection.

* docs(cli): say when `artwork cancel` evaluates its selection

The help text covered the two limits that surprise an operator after the fact, but
not the one that bites during the prompt: the count is a preview, and the filters
run again on confirm. A scan or a manual refresh landing in between is cancelled
without ever appearing in the table the operator agreed to.

Deleting only the previewed rows was considered and rejected. The exposure is one
item re-resolving on next view instead of immediately: clearing an item's artwork
state is what every recovery path selects on, so a lost Bump row from
artwork.Refresh comes back at the same priority via provisional() on the next
request, and otherwise within the hour via EnqueueAllMissing. Buying a guarantee
against that costs the truncate path on --all, the flag that exists for a
29k-item backfill.

* refactor(cli): share one set of flag targets across the artwork subcommands

reprocess and cancel each declared their own kinds/all/dry-run/yes variables, but
cobra only ever parses the one subcommand being run, so the two sets could never
hold values at the same time. backup.go already binds one backupDir across two
subcommands and one force across two more; this follows that.

Ten package-level variables become six. Each command keeps its own help string
and its own valid-kind list, so --kind still reports RecheckKinds for reprocess
and RefreshableKinds for cancel, and --source and --priority stay registered only
on the command that has them.

The priority lookup table is now knownPriorities, freeing the artworkPriorities
name for the flag. The new name also reads better against priorityName's fallback
for a value it does not know.
2026-08-21 14:03:59 -04:00
..
criteria feat(smartplaylist): add album-level fields for sorting and filtering (#5899) 2026-08-06 10:59:50 -04:00
id refactor(db): migrate all ids to a uniform canonical 128-bit base62 encoding (#5824) 2026-08-02 12:58:53 -04:00
metadata refactor(db): migrate all ids to a uniform canonical 128-bit base62 encoding (#5824) 2026-08-02 12:58:53 -04:00
request fix(scanner) artist stats not refreshing during quick scan and after missing file deletion (#4269) 2025-06-26 15:50:56 -04:00
album.go fix(artwork): re-resolve artwork when image files change on disk (#5965) 2026-08-16 13:24:07 -04:00
album_test.go fix(subsonic): avoid double brackets when appending subtitle or version (#5832) 2026-07-20 21:49:14 -04:00
annotation.go feat(subsonic): Add avgRating from subsonic spec (#4900) 2026-01-18 17:42:42 -05:00
artist.go feat(artwork): new artwork pipeline with background resolution and Low Quality Image Placeholders (#5847) 2026-08-09 15:03:27 -04:00
artist_info.go Get Similar Artists in parallel 2020-10-21 21:44:03 -04:00
artist_test.go refactor(conf): replace eager dir creation with lazy Dir type (#5495) 2026-05-13 17:44:22 -03:00
artwork.go feat(cli): add artwork cancel to call off queued artwork work (#6006) 2026-08-21 14:03:59 -04:00
artwork_id.go feat(cli): add artwork cancel to call off queued artwork work (#6006) 2026-08-21 14:03:59 -04:00
artwork_id_test.go feat(artwork): new artwork pipeline with background resolution and Low Quality Image Placeholders (#5847) 2026-08-09 15:03:27 -04:00
artwork_test.go feat(artwork): new artwork pipeline with background resolution and Low Quality Image Placeholders (#5847) 2026-08-09 15:03:27 -04:00
bookmark.go Use structs lib to map models to DB. Fix #1266 2021-08-01 12:04:45 -04:00
datastore.go feat(artwork): new artwork pipeline with background resolution and Low Quality Image Placeholders (#5847) 2026-08-09 15:03:27 -04:00
errors.go chore: remove redundant comment 2026-08-19 08:49:15 -04:00
file_types.go Fix M3U mimetype on Debian Bullseye 2024-05-09 22:26:15 -04:00
file_types_test.go Refactor file type functions 2022-12-28 15:31:56 -05:00
folder.go fix(artwork): re-resolve artwork when image files change on disk (#5965) 2026-08-16 13:24:07 -04:00
folder_test.go ci: run Go tests on Windows (#5380) 2026-04-19 13:16:47 -04:00
genre.go fix(jellyfin): resolve genre id as a MusicGenre item 2026-08-10 21:37:57 -04:00
get_entity.go feat(cli): let artwork explain/refresh accept an id without its kind (#5988) 2026-08-19 16:25:12 -04:00
get_entity_test.go feat(cli): let artwork explain/refresh accept an id without its kind (#5988) 2026-08-19 16:25:12 -04:00
image.go refactor(conf): replace eager dir creation with lazy Dir type (#5495) 2026-05-13 17:44:22 -03:00
library.go feat: Multi-library support (#4181) 2025-07-18 18:41:12 -04:00
lyrics.go refactor(lyrics): single ParseLyrics entry point + all-format plugin lyrics (#5632) 2026-06-19 18:25:35 -04:00
lyrics_benchmark_test.go fix(scanner): stop logging expected lyrics sniff misses as warnings (#5702) 2026-07-02 09:46:57 -04:00
lyrics_lrc.go feat(lyrics): support [bg:] tag and skip unknown tags in LRC files (#5966) 2026-08-16 10:18:10 -04:00
lyrics_lrc_test.go feat(lyrics): support [bg:] tag and skip unknown tags in LRC files (#5966) 2026-08-16 10:18:10 -04:00
lyrics_lyricsfile.go refactor(lyrics): single ParseLyrics entry point + all-format plugin lyrics (#5632) 2026-06-19 18:25:35 -04:00
lyrics_lyricsfile_test.go refactor(lyrics): single ParseLyrics entry point + all-format plugin lyrics (#5632) 2026-06-19 18:25:35 -04:00
lyrics_normalize.go fix(lyrics): correct TTML background-vocal cue timing and whitespace (#5672) 2026-06-27 10:37:25 -04:00
lyrics_normalize_test.go refactor(lyrics): single ParseLyrics entry point + all-format plugin lyrics (#5632) 2026-06-19 18:25:35 -04:00
lyrics_parse.go fix(scanner): stop logging expected lyrics sniff misses as warnings (#5702) 2026-07-02 09:46:57 -04:00
lyrics_parse_test.go fix(scanner): stop logging expected lyrics sniff misses as warnings (#5702) 2026-07-02 09:46:57 -04:00
lyrics_srt.go refactor(lyrics): single ParseLyrics entry point + all-format plugin lyrics (#5632) 2026-06-19 18:25:35 -04:00
lyrics_srt_test.go refactor(lyrics): single ParseLyrics entry point + all-format plugin lyrics (#5632) 2026-06-19 18:25:35 -04:00
lyrics_test.go refactor(lyrics): single ParseLyrics entry point + all-format plugin lyrics (#5632) 2026-06-19 18:25:35 -04:00
lyrics_ttml.go fix(lyrics): correct TTML background-vocal cue timing and whitespace (#5672) 2026-06-27 10:37:25 -04:00
lyrics_ttml_test.go fix(lyrics): correct TTML background-vocal cue timing and whitespace (#5672) 2026-06-27 10:37:25 -04:00
mediafile.go refactor: replace md5 with xxh3 for faster and more efficient hashing 2026-08-19 09:28:25 -04:00
mediafile_internal_test.go feat(bfr): Big Refactor: new scanner, lots of new fields and tags, improvements and DB schema changes (#2709) 2025-02-19 20:35:17 -05:00
mediafile_test.go refactor: replace md5 with xxh3 for faster and more efficient hashing 2026-08-19 09:28:25 -04:00
model_suite_test.go Rename log.LevelCritical to log.LevelFatal 2022-12-21 14:53:36 -05:00
participants.go refactor: replace md5 with xxh3 for faster and more efficient hashing 2026-08-19 09:28:25 -04:00
participants_test.go feat(bfr): Big Refactor: new scanner, lots of new fields and tags, improvements and DB schema changes (#2709) 2025-02-19 20:35:17 -05:00
player.go chore: remove more outdated TODOs 2025-03-23 11:53:43 -04:00
playlist.go fix(playlist): block track edits on synced playlists across all APIs (#5984) 2026-08-19 08:47:53 -04:00
playlist_test.go fix(playlist): block track edits on synced playlists across all APIs (#5984) 2026-08-19 08:47:53 -04:00
playqueue.go feat(server): add update and clear play queue endpoints to native API (#4215) 2025-06-11 12:02:31 -04:00
plugin.go fix(plugins): clear plugin errors on startup to allow retrying 2026-03-02 08:56:56 -05:00
properties.go Associate main entities with library 2024-05-12 21:37:42 -04:00
radio.go feat(artwork): new artwork pipeline with background resolution and Low Quality Image Placeholders (#5847) 2026-08-09 15:03:27 -04:00
radio_test.go feat(artwork): new artwork pipeline with background resolution and Low Quality Image Placeholders (#5847) 2026-08-09 15:03:27 -04:00
scanner.go fix(cli): accept absolute paths in selective scan --target (#5947) 2026-08-12 14:55:04 -04:00
scanner_test.go feat(scanner): implement selective folder scanning and file system watcher improvements (#4674) 2025-11-14 22:15:43 -05:00
scrobble.go feat(server): add scrobble history Native API (#5761) 2026-07-13 11:32:03 -04:00
scrobble_buffer.go fix(plugins): discard buffered scrobbles when a plugin is removed (#5737) 2026-07-08 12:37:17 -04:00
searchable.go feat(subsonic): sort search3 results by relevance (#5086) 2026-02-23 08:51:54 -05:00
share.go refactor: run Go modernize (#5002) 2026-02-08 09:57:30 -05:00
tag.go refactor: replace md5 with xxh3 for faster and more efficient hashing 2026-08-19 09:28:25 -04:00
tag_mappings.go feat(scanner): add ArtistSplitExceptions to protect artist names from splitting (#5701) 2026-07-02 08:29:44 -04:00
tag_mappings_test.go feat(scanner): add ArtistSplitExceptions to protect artist names from splitting (#5701) 2026-07-02 08:29:44 -04:00
tag_test.go feat(bfr): Big Refactor: new scanner, lots of new fields and tags, improvements and DB schema changes (#2709) 2025-02-19 20:35:17 -05:00
transcoding.go Replace beego/orm with dbx (#2693) 2023-12-09 13:52:17 -05:00
user.go feat(scrobbler): add per-user scrobble filter (#5964) 2026-08-15 16:10:53 -04:00
user_props.go Pass userId explicitly to UserPropsRepository methods 2021-06-25 22:21:37 -04:00
user_test.go feat: Multi-library support (#4181) 2025-07-18 18:41:12 -04:00