feat: Collection › Unmatched lists the library albums no MusicBrainz album matched, each matched by hand from a search or a link, or left off (M502 #5579)
release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m17s
release / go (push) Successful in 1m34s
release / integration (push) Failing after 5m34s
release / android (push) Successful in 5m36s
release / Build signed APK (releases and dev) (push) Successful in 5m44s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Skipped
release / Verify release artifacts (tag releases only) (push) Skipped

Like Lidarr's Unmapped Files. A local band's album, which MusicBrainz will
never have, is left off: never matched again and not counted. A re-tagged
album is matched again on the next listing. The Collection's verdict now
says when library albums need matching.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 22:12:30 -04:00
co-authored by Claude Opus 5.5
parent 10be068dcc
commit a8786abace
23 changed files with 1224 additions and 135 deletions
+65 -14
View File
@@ -271,6 +271,10 @@ SELECT COUNT(*) FILTER (WHERE fulfilled_at IS NULL AND reason <> 'missing')::big
COUNT(*) FILTER (WHERE fulfilled_at IS NULL AND reason = 'missing')::bigint AS missing,
COUNT(*) FILTER (WHERE fulfilled_at IS NOT NULL)::bigint AS held,
COUNT(DISTINCT artist_mbid)::bigint AS artists,
-- Library albums waiting to be matched by hand (#5579).
(SELECT COUNT(*) FROM library_album_matches m
WHERE m.outcome = 'unmatched'
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = m.album_id))::bigint AS unmatched,
(SELECT COUNT(*) FROM follows)::bigint AS following,
-- The Wanted view's other tabs (#5522): downloads running, those
-- waiting on a person, and releases set aside.
@@ -330,23 +334,41 @@ SELECT a.id, 'listed', 'tags', a.release_group_mbid
ON CONFLICT (album_id) DO NOTHING;
-- name: ListAlbumsToMatch :many
-- Library albums from before the cutoff that the listing has not decided,
-- or that an older matcher refused: each is matched to MusicBrainz (#5569).
-- An album row with no tracks is not an album in the library.
-- Library albums the listing has not decided, or that an older matcher
-- refused, or whose tracks changed since they were refused: each is matched
-- to MusicBrainz (#5569). A library album is one from before the cutoff, or
-- one whose tracks were: a re-tag files a track under a new album row but
-- keeps the track (#5579). An album row with no tracks is not an album in
-- the library; one a person left off stays off.
SELECT a.id, a.title, a.mbid, a.release_group_mbid, ar.name AS artist_name, ar.mbid AS artist_mbid
FROM albums a
JOIN artists ar ON ar.id = a.artist_id
WHERE a.created_at <= sqlc.arg(cutoff)::timestamptz
WHERE (a.created_at <= sqlc.arg(cutoff)::timestamptz
OR EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id AND t.added_at <= sqlc.arg(cutoff)::timestamptz))
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id)
AND NOT EXISTS (SELECT 1 FROM library_album_matches m
WHERE m.album_id = a.id AND (m.outcome = 'listed' OR m.matcher >= sqlc.arg(matcher)::int))
WHERE m.album_id = a.id
AND (m.outcome IN ('listed', 'dismissed')
OR (m.matcher >= sqlc.arg(matcher)::int
AND NOT EXISTS (SELECT 1 FROM tracks t
WHERE t.album_id = a.id AND t.updated_at > m.decided_at))))
ORDER BY a.created_at, a.id
LIMIT sqlc.arg(max_rows);
-- name: CountAlbumsToRematch :one
-- Refusals by an older matcher, which a finished listing goes back to.
SELECT COUNT(*) FROM library_album_matches
WHERE outcome = 'unmatched' AND matcher < sqlc.arg(matcher)::int;
-- name: CountAlbumsToMatch :one
-- Whether a finished listing has albums to go back to: those
-- ListAlbumsToMatch would hand it.
SELECT COUNT(*)
FROM albums a
WHERE (a.created_at <= sqlc.arg(cutoff)::timestamptz
OR EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id AND t.added_at <= sqlc.arg(cutoff)::timestamptz))
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id)
AND NOT EXISTS (SELECT 1 FROM library_album_matches m
WHERE m.album_id = a.id
AND (m.outcome IN ('listed', 'dismissed')
OR (m.matcher >= sqlc.arg(matcher)::int
AND NOT EXISTS (SELECT 1 FROM tracks t
WHERE t.album_id = a.id AND t.updated_at > m.decided_at))));
-- name: ListAlbumTracksToMatch :many
-- What an album's tracks say about themselves, to check a match against.
@@ -384,16 +406,45 @@ ON CONFLICT (album_id) DO UPDATE
-- count the albums no match was found for. The first finish is kept.
UPDATE acquisition_settings
SET library_listed_at = COALESCE(library_listed_at, now()),
library_unlisted = (SELECT COUNT(*) FROM library_album_matches WHERE outcome = 'unmatched')
library_unlisted = (SELECT COUNT(*) FROM library_album_matches m
WHERE m.outcome = 'unmatched'
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = m.album_id))
WHERE id = true
RETURNING library_listed_at, library_unlisted;
-- name: ListUnmatchedLibraryAlbums :many
-- The albums the listing could not match, for the Held tab.
SELECT m.album_id, a.title, ar.name AS artist_name, m.detail
-- The Collection's Unmatched section (#5579): the albums the listing could
-- not match, and below them the ones a person left off. An album whose
-- tracks have all moved to another row is gone, not unmatched.
SELECT m.album_id, a.title, ar.name AS artist_name, m.detail, m.outcome, m.decided_at,
(SELECT COUNT(*) FROM tracks t WHERE t.album_id = a.id AND t.missing_since IS NULL)::int AS tracks
FROM library_album_matches m
JOIN albums a ON a.id = m.album_id
JOIN artists ar ON ar.id = a.artist_id
WHERE m.outcome = 'unmatched'
ORDER BY ar.name, a.title
WHERE m.outcome IN ('unmatched', 'dismissed')
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = m.album_id)
ORDER BY m.outcome DESC, lower(ar.name), lower(a.title)
LIMIT sqlc.arg(max_rows);
-- name: RecountLibraryUnlisted :exec
-- After a person matches or leaves off an album, the count the catalogue
-- shows.
UPDATE acquisition_settings
SET library_unlisted = (SELECT COUNT(*) FROM library_album_matches m
WHERE m.outcome = 'unmatched'
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = m.album_id))
WHERE id = true;
-- name: GetAlbumToMatch :one
-- One album a person is matching by hand, as the matcher reads albums.
SELECT a.id, a.title, a.mbid, a.release_group_mbid, ar.name AS artist_name, ar.mbid AS artist_mbid
FROM albums a
JOIN artists ar ON ar.id = a.artist_id
JOIN library_album_matches m ON m.album_id = a.id AND m.outcome IN ('unmatched', 'dismissed')
WHERE a.id = $1;
-- name: SetAlbumMatchOutcome :execrows
-- Leave an unmatched album off the list, or put a left-off one back.
UPDATE library_album_matches SET outcome = sqlc.arg(outcome), decided_at = now()
WHERE album_id = sqlc.arg(album_id) AND outcome = sqlc.arg(was);