release / govulncheck (push) Successful in 18s
release / web (push) Successful in 1m30s
release / go (push) Successful in 1m46s
release / integration (push) Successful in 5m23s
release / android (push) Successful in 5m43s
release / Build signed APK (releases and dev) (push) Successful in 6m2s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
500 lines
25 KiB
SQL
500 lines
25 KiB
SQL
-- The wanted list (M502 #5520): release-type profiles, follows and wants.
|
||
|
||
-- name: ListReleaseTypeProfiles :many
|
||
SELECT p.*,
|
||
(SELECT COUNT(*) FROM follows f WHERE f.profile_id = p.id)::bigint AS follow_count
|
||
FROM release_type_profiles p
|
||
ORDER BY p.is_default DESC, p.name;
|
||
|
||
-- name: GetReleaseTypeProfile :one
|
||
SELECT * FROM release_type_profiles WHERE id = $1;
|
||
|
||
-- name: GetDefaultReleaseTypeProfile :one
|
||
SELECT * FROM release_type_profiles WHERE is_default;
|
||
|
||
-- name: CreateReleaseTypeProfile :one
|
||
INSERT INTO release_type_profiles (name, primary_types, secondary_types)
|
||
VALUES ($1, $2, $3)
|
||
RETURNING *;
|
||
|
||
-- name: UpdateReleaseTypeProfile :one
|
||
UPDATE release_type_profiles
|
||
SET name = $2,
|
||
primary_types = $3,
|
||
secondary_types = $4,
|
||
updated_at = now()
|
||
WHERE id = $1
|
||
RETURNING *;
|
||
|
||
-- name: ClearDefaultReleaseTypeProfile :exec
|
||
UPDATE release_type_profiles SET is_default = false, updated_at = now() WHERE is_default;
|
||
|
||
-- name: SetDefaultReleaseTypeProfile :execrows
|
||
UPDATE release_type_profiles SET is_default = true, updated_at = now() WHERE id = $1;
|
||
|
||
-- name: DeleteReleaseTypeProfile :execrows
|
||
-- The default stays; one in use is refused by the follows foreign key.
|
||
DELETE FROM release_type_profiles WHERE id = $1 AND NOT is_default;
|
||
|
||
-- name: UpsertFollow :one
|
||
-- Following an artist already followed changes its profile. want_existing
|
||
-- only ever turns on here; the worker turns it off once applied, and turning
|
||
-- it on again re-reads the baseline so the existing releases are judged.
|
||
INSERT INTO follows (artist_mbid, artist_name, profile_id, followed_by, want_existing)
|
||
VALUES (sqlc.arg(artist_mbid), sqlc.arg(artist_name), sqlc.arg(profile_id),
|
||
sqlc.narg(followed_by), sqlc.arg(want_existing))
|
||
ON CONFLICT (artist_mbid) DO UPDATE
|
||
SET artist_name = EXCLUDED.artist_name,
|
||
profile_id = EXCLUDED.profile_id,
|
||
want_existing = follows.want_existing OR EXCLUDED.want_existing,
|
||
baseline_at = CASE WHEN EXCLUDED.want_existing THEN NULL ELSE follows.baseline_at END,
|
||
updated_at = now()
|
||
RETURNING *;
|
||
|
||
-- name: GetFollow :one
|
||
SELECT f.*, p.name AS profile_name
|
||
FROM follows f
|
||
JOIN release_type_profiles p ON p.id = f.profile_id
|
||
WHERE f.artist_mbid = $1;
|
||
|
||
-- name: DeleteFollow :execrows
|
||
DELETE FROM follows WHERE artist_mbid = $1;
|
||
|
||
-- name: ListFollows :many
|
||
SELECT f.artist_mbid, f.artist_name, f.profile_id, f.followed_at, f.baseline_at, f.checked_at,
|
||
p.name AS profile_name,
|
||
(SELECT a.id FROM artists a WHERE a.mbid = f.artist_mbid) AS artist_id,
|
||
(SELECT COUNT(*) FROM wants w
|
||
WHERE w.artist_mbid = f.artist_mbid AND w.fulfilled_at IS NULL)::bigint AS open_wants
|
||
FROM follows f
|
||
JOIN release_type_profiles p ON p.id = f.profile_id
|
||
ORDER BY lower(f.artist_name), f.artist_mbid;
|
||
|
||
-- name: ListFollowsDue :many
|
||
-- Follows whose discography has not been applied since @before, never-read
|
||
-- first, so a new follow's baseline is not stuck behind daily re-checks.
|
||
SELECT f.*
|
||
FROM follows f
|
||
WHERE f.baseline_at IS NULL OR f.checked_at IS NULL OR f.checked_at < sqlc.arg(before)
|
||
ORDER BY f.baseline_at NULLS FIRST, f.checked_at NULLS FIRST, f.artist_mbid
|
||
LIMIT sqlc.arg(max_rows);
|
||
|
||
-- name: MarkFollowChecked :exec
|
||
UPDATE follows
|
||
SET checked_at = now(),
|
||
baseline_at = COALESCE(baseline_at, now()),
|
||
want_existing = false,
|
||
updated_at = now()
|
||
WHERE artist_mbid = $1;
|
||
|
||
-- name: ListFollowGroups :many
|
||
-- An artist's release groups from the catalogue, each with whether the
|
||
-- follow has judged it before.
|
||
SELECT g.mbid, g.title, g.primary_type, g.secondary_types, g.first_release_date,
|
||
EXISTS (SELECT 1 FROM follow_seen_groups s
|
||
WHERE s.artist_mbid = sqlc.arg(artist_mbid) AND s.release_group_mbid = g.mbid) AS seen
|
||
FROM mb_artist_release_groups ag
|
||
JOIN mb_release_groups g ON g.mbid = ag.release_group_mbid
|
||
WHERE ag.artist_mbid = sqlc.arg(artist_mbid)
|
||
ORDER BY g.first_release_date, g.title;
|
||
|
||
-- name: MarkGroupSeen :exec
|
||
INSERT INTO follow_seen_groups (artist_mbid, release_group_mbid)
|
||
VALUES ($1, $2)
|
||
ON CONFLICT DO NOTHING;
|
||
|
||
-- name: LibraryHoldsReleaseGroup :one
|
||
-- Whether the library already has the group: an album tagged with it or
|
||
-- with one of its releases, or failing that an album of the artist's with
|
||
-- the same title. Only albums with a track on disk count.
|
||
SELECT EXISTS (
|
||
SELECT 1 FROM albums a
|
||
WHERE (a.release_group_mbid = sqlc.arg(group_mbid)::text
|
||
OR a.mbid = sqlc.arg(group_mbid)
|
||
OR a.mbid IN (SELECT r.mbid FROM mb_releases r WHERE r.release_group_mbid = sqlc.arg(group_mbid))
|
||
OR (lower(a.title) = lower(sqlc.arg(title)::text)
|
||
AND a.artist_id IN (SELECT ar.id FROM artists ar WHERE ar.mbid = sqlc.arg(artist_mbid)::text)))
|
||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id AND t.missing_since IS NULL)
|
||
);
|
||
|
||
-- name: WantReleaseGroup :one
|
||
-- Wants a release group, or returns the want already recorded for it. A
|
||
-- held want is not reopened here: the library has it.
|
||
INSERT INTO wants (release_group_mbid, artist_mbid, artist_name, title, primary_type,
|
||
first_release_date, reason, created_by)
|
||
VALUES (sqlc.arg(release_group_mbid), sqlc.arg(artist_mbid), sqlc.arg(artist_name), sqlc.arg(title),
|
||
sqlc.arg(primary_type), sqlc.arg(first_release_date), sqlc.arg(reason), sqlc.narg(created_by))
|
||
ON CONFLICT (release_group_mbid) DO UPDATE SET updated_at = wants.updated_at
|
||
RETURNING *;
|
||
|
||
-- name: GetWantByReleaseGroup :one
|
||
SELECT * FROM wants WHERE release_group_mbid = $1;
|
||
|
||
-- name: ListOpenWants :many
|
||
SELECT * FROM wants
|
||
WHERE fulfilled_at IS NULL
|
||
ORDER BY wanted_at, id
|
||
LIMIT $1;
|
||
|
||
-- name: WantHoldings :many
|
||
-- Each library album that may answer a want, with what it has on disk:
|
||
-- tracks present, whether one arrived since the want, and whether a track
|
||
-- missing before the want is still missing.
|
||
SELECT a.id AS album_id,
|
||
COUNT(*) FILTER (WHERE t.missing_since IS NULL)::int AS present,
|
||
COALESCE(bool_or(t.missing_since IS NULL AND t.added_at > sqlc.arg(since)::timestamptz), false)::bool AS arrived_since,
|
||
COALESCE(bool_or(t.missing_since IS NOT NULL AND t.missing_since < sqlc.arg(since)::timestamptz), false)::bool AS missing_before,
|
||
-- Missing tracks no track on disk stands in for, by position or
|
||
-- title. A missing row whose song is back at a new path (#5245) is
|
||
-- covered; a lost bonus track is not.
|
||
COUNT(*) FILTER (WHERE t.missing_since IS NOT NULL AND NOT EXISTS (
|
||
SELECT 1 FROM tracks p
|
||
WHERE p.album_id = a.id AND p.missing_since IS NULL
|
||
AND ((p.track_number = t.track_number AND p.disc_number IS NOT DISTINCT FROM t.disc_number)
|
||
OR lower(p.title) = lower(t.title))))::int AS uncovered_missing
|
||
FROM albums a
|
||
JOIN tracks t ON t.album_id = a.id
|
||
WHERE a.release_group_mbid = sqlc.arg(group_mbid)::text
|
||
OR a.mbid = sqlc.arg(group_mbid)
|
||
OR a.id = sqlc.narg(album_id)
|
||
OR a.mbid IN (SELECT r.mbid FROM mb_releases r WHERE r.release_group_mbid = sqlc.arg(group_mbid))
|
||
GROUP BY a.id
|
||
ORDER BY present DESC, a.id;
|
||
|
||
-- name: FulfillWant :exec
|
||
UPDATE wants
|
||
SET fulfilled_at = now(),
|
||
album_id = sqlc.arg(album_id),
|
||
updated_at = now()
|
||
WHERE id = sqlc.arg(id) AND fulfilled_at IS NULL;
|
||
|
||
-- name: RepointWant :exec
|
||
-- A want recorded under a release id before #5241 is moved to its group.
|
||
UPDATE wants SET release_group_mbid = $2, updated_at = now() WHERE id = $1;
|
||
|
||
-- name: DeleteWant :execrows
|
||
DELETE FROM wants WHERE id = $1;
|
||
|
||
-- name: ListWants :many
|
||
-- The Wanted page: open wants (all but missing ones, or only missing ones),
|
||
-- the held ones, or all of them; artist_mbid narrows to one artist's page,
|
||
-- want_ids to given wants. Ordered like the library (#5572): artists A–Z by the
|
||
-- library's sort name when it holds the artist, each artist's albums by
|
||
-- release date, so the page groups them as they arrive. q narrows to an
|
||
-- artist or album title; total counts every match, past the page.
|
||
SELECT w.id, w.release_group_mbid, w.artist_mbid, w.artist_name, w.title, w.reason,
|
||
w.album_id, w.wanted_at, w.fulfilled_at,
|
||
-- Why a held album is wanted again, and the copy it has (#5524).
|
||
w.upgrade, w.held_quality,
|
||
-- A request names an album before the catalogue has read it: the
|
||
-- catalogue's type and date fill in once it has.
|
||
COALESCE(NULLIF(w.primary_type, ''), g.primary_type, '')::text AS primary_type,
|
||
COALESCE(g.secondary_types, '{}')::text[] AS secondary_types,
|
||
COALESCE(NULLIF(w.first_release_date, ''), g.first_release_date, '')::text AS first_release_date,
|
||
u.username AS requested_by,
|
||
la.id AS library_artist_id,
|
||
COUNT(*) OVER () AS total,
|
||
-- The search schedule's last look (#5521), and the download running.
|
||
w.searched_at, w.search_outcome, w.search_considered, w.best_release, w.best_verdict, w.search_error,
|
||
-- Its latest download, running or ended, and what the import made of
|
||
-- it (#5522).
|
||
gr.id AS grab_id, gr.name AS grab_name, gr.grabbed_at, gr.origin AS grab_origin,
|
||
gr.quality AS grab_quality, gr.ended_at AS grab_ended_at, gr.end_reason AS grab_end_reason,
|
||
gr.progress AS grab_progress, gr.import_state AS grab_import_state,
|
||
gr.import_reason AS grab_import_reason, gr.end_detail AS grab_end_detail, gr.imported_at AS grab_imported_at,
|
||
gr.imported_files AS grab_imported_files
|
||
FROM wants w
|
||
LEFT JOIN users u ON u.id = w.created_by
|
||
LEFT JOIN mb_release_groups g ON g.mbid = w.release_group_mbid
|
||
LEFT JOIN LATERAL (SELECT ar.id, ar.sort_name FROM artists ar WHERE ar.mbid = w.artist_mbid LIMIT 1) la ON true
|
||
LEFT JOIN grabs gr ON gr.id = (SELECT x.id FROM grabs x WHERE x.want_id = w.id
|
||
ORDER BY x.ended_at IS NULL DESC, x.grabbed_at DESC LIMIT 1)
|
||
WHERE CASE sqlc.arg(state)::text
|
||
WHEN 'all' THEN true
|
||
WHEN 'held' THEN w.fulfilled_at IS NOT NULL
|
||
WHEN 'upgrade' THEN w.fulfilled_at IS NOT NULL AND w.upgrade <> ''
|
||
WHEN 'missing' THEN w.fulfilled_at IS NULL AND w.reason = 'missing'
|
||
ELSE w.fulfilled_at IS NULL AND w.reason <> 'missing'
|
||
END
|
||
AND (sqlc.arg(artist_mbid)::text = '' OR w.artist_mbid = sqlc.arg(artist_mbid)::text)
|
||
-- want_ids narrows to given wants: the Missing page's folders (M504).
|
||
AND (COALESCE(cardinality(sqlc.arg(want_ids)::uuid[]), 0) = 0 OR w.id = ANY(sqlc.arg(want_ids)::uuid[]))
|
||
AND (sqlc.arg(q)::text = ''
|
||
OR strpos(lower(w.artist_name), lower(sqlc.arg(q)::text)) > 0
|
||
OR strpos(lower(w.title), lower(sqlc.arg(q)::text)) > 0)
|
||
ORDER BY lower(COALESCE(NULLIF(la.sort_name, ''), w.artist_name)), w.artist_mbid,
|
||
NULLIF(COALESCE(NULLIF(w.first_release_date, ''), g.first_release_date, ''), '') NULLS LAST,
|
||
lower(w.title), w.id
|
||
LIMIT sqlc.arg(max_rows) OFFSET sqlc.arg(skip);
|
||
|
||
-- name: ListWantArtists :many
|
||
-- The Wanted view's main page (#5572): every artist on the list, A–Z like
|
||
-- the library, with how many of their albums are held of how many are on
|
||
-- the list. q matches the artist's name or any of their albums' titles;
|
||
-- artist_mbid narrows to one artist, for their page's header.
|
||
WITH a AS (
|
||
SELECT w.artist_mbid,
|
||
(array_agg(w.artist_name ORDER BY w.wanted_at DESC))[1] AS artist_name,
|
||
COUNT(*) FILTER (WHERE w.fulfilled_at IS NOT NULL)::int AS held,
|
||
COUNT(*) FILTER (WHERE w.fulfilled_at IS NULL AND w.reason <> 'missing')::int AS wanted,
|
||
COUNT(*) FILTER (WHERE w.fulfilled_at IS NULL AND w.reason = 'missing')::int AS missing,
|
||
COUNT(*)::int AS albums,
|
||
bool_or(sqlc.arg(q)::text <> '' AND strpos(lower(w.title), lower(sqlc.arg(q)::text)) > 0) AS title_match
|
||
FROM wants w
|
||
WHERE sqlc.arg(artist_mbid)::text = '' OR w.artist_mbid = sqlc.arg(artist_mbid)::text
|
||
GROUP BY w.artist_mbid
|
||
)
|
||
SELECT a.artist_mbid, a.artist_name::text AS artist_name, a.held, a.wanted, a.missing, a.albums,
|
||
COALESCE(NULLIF(la.sort_name, ''), a.artist_name)::text AS sort_name,
|
||
la.id AS library_artist_id,
|
||
(f.artist_mbid IS NOT NULL)::bool AS following,
|
||
(SELECT COUNT(*) FROM grabs g JOIN wants x ON x.id = g.want_id
|
||
WHERE x.artist_mbid = a.artist_mbid AND g.ended_at IS NULL)::int AS downloading,
|
||
COUNT(*) OVER () AS total
|
||
FROM a
|
||
LEFT JOIN LATERAL (SELECT ar.id, ar.sort_name FROM artists ar WHERE ar.mbid = a.artist_mbid LIMIT 1) la ON true
|
||
LEFT JOIN follows f ON f.artist_mbid = a.artist_mbid
|
||
WHERE sqlc.arg(q)::text = ''
|
||
OR strpos(lower(a.artist_name), lower(sqlc.arg(q)::text)) > 0
|
||
OR a.title_match
|
||
ORDER BY lower(COALESCE(NULLIF(la.sort_name, ''), a.artist_name)), a.artist_mbid
|
||
LIMIT sqlc.arg(max_rows) OFFSET sqlc.arg(skip);
|
||
|
||
-- name: GetWantAlbum :one
|
||
-- What the Wanted view's track list needs of a want (#5572).
|
||
SELECT id, release_group_mbid, album_id FROM wants WHERE id = $1;
|
||
|
||
-- name: ListLibraryAlbumTracks :many
|
||
-- A held or missing album's own tracks, for the Wanted view's track list;
|
||
-- missing says its file is gone.
|
||
SELECT title, disc_number, track_number, duration_ms, (missing_since IS NOT NULL)::bool AS missing
|
||
FROM tracks
|
||
WHERE album_id = $1
|
||
ORDER BY disc_number NULLS FIRST, track_number NULLS LAST, title;
|
||
|
||
-- name: CountWants :one
|
||
SELECT COUNT(*) FILTER (WHERE fulfilled_at IS NULL AND reason <> 'missing')::bigint AS wanted,
|
||
COUNT(*) FILTER (WHERE fulfilled_at IS NULL AND reason = 'missing')::bigint AS missing,
|
||
COUNT(*) FILTER (WHERE fulfilled_at IS NOT NULL)::bigint AS held,
|
||
-- Held albums wanted for a better copy (#5524).
|
||
COUNT(*) FILTER (WHERE fulfilled_at IS NOT NULL AND upgrade <> '')::bigint AS upgrades,
|
||
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.
|
||
(SELECT COUNT(*) FROM grabs WHERE ended_at IS NULL)::bigint AS activity,
|
||
(SELECT COUNT(*) FROM grabs
|
||
WHERE ended_at IS NULL AND import_state IN ('review', 'failed'))::bigint AS attention,
|
||
(SELECT COUNT(*) FROM release_blocks b
|
||
WHERE b.expires_at IS NULL OR b.expires_at > now())::bigint AS blocked
|
||
FROM wants;
|
||
|
||
-- The one-time listing of the library (#5567). ---------------------------
|
||
|
||
-- name: GetLibraryListing :one
|
||
SELECT library_listing_started_at, library_listed_at, library_unlisted
|
||
FROM acquisition_settings WHERE id = true;
|
||
|
||
-- name: StartLibraryListing :one
|
||
-- Marks the listing begun, once; its time is the cutoff for which albums
|
||
-- count as the library's.
|
||
UPDATE acquisition_settings
|
||
SET library_listing_started_at = COALESCE(library_listing_started_at, now())
|
||
WHERE id = true
|
||
RETURNING library_listing_started_at;
|
||
|
||
-- name: ListLibraryAlbums :execrows
|
||
-- Puts library albums on the list as held: each release group once, by the
|
||
-- album holding the most tracks. Only albums the library had at the cutoff,
|
||
-- and with album_id given only that album. A group already on the list is
|
||
-- left as it is. An album with tracks already missing is held from just
|
||
-- before the first loss, so re-acquisition still fetches it back.
|
||
INSERT INTO wants (release_group_mbid, artist_mbid, artist_name, title, reason, album_id, wanted_at, fulfilled_at)
|
||
SELECT DISTINCT ON (a.release_group_mbid)
|
||
a.release_group_mbid, ar.mbid, ar.name, a.title, 'library', a.id, now(),
|
||
COALESCE((SELECT MIN(t.missing_since) FROM tracks t WHERE t.album_id = a.id) - interval '1 second', now())
|
||
FROM albums a
|
||
JOIN artists ar ON ar.id = a.artist_id
|
||
WHERE a.release_group_mbid IS NOT NULL AND a.release_group_mbid <> ''
|
||
AND ar.mbid IS NOT NULL AND ar.mbid <> ''
|
||
AND a.created_at <= sqlc.arg(cutoff)::timestamptz
|
||
AND (sqlc.narg(album_id)::uuid IS NULL OR a.id = sqlc.narg(album_id)::uuid)
|
||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id)
|
||
ORDER BY a.release_group_mbid,
|
||
(SELECT COUNT(*) FROM tracks t WHERE t.album_id = a.id AND t.missing_since IS NULL) DESC, a.id
|
||
ON CONFLICT (release_group_mbid) DO NOTHING;
|
||
|
||
-- name: RecordListedAlbums :exec
|
||
-- The albums ListLibraryAlbums takes are decided as listed by their tags,
|
||
-- so the matching that follows leaves them alone (#5569).
|
||
INSERT INTO library_album_matches (album_id, outcome, method, release_group_mbid)
|
||
SELECT a.id, 'listed', 'tags', a.release_group_mbid
|
||
FROM albums a
|
||
JOIN artists ar ON ar.id = a.artist_id
|
||
WHERE a.release_group_mbid IS NOT NULL AND a.release_group_mbid <> ''
|
||
AND ar.mbid IS NOT NULL AND ar.mbid <> ''
|
||
AND a.created_at <= sqlc.arg(cutoff)::timestamptz
|
||
AND EXISTS (SELECT 1 FROM tracks t WHERE t.album_id = a.id)
|
||
ON CONFLICT (album_id) DO NOTHING;
|
||
|
||
-- name: ListAlbumsToMatch :many
|
||
-- 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
|
||
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))))
|
||
ORDER BY a.created_at, a.id
|
||
LIMIT sqlc.arg(max_rows);
|
||
|
||
-- 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.
|
||
SELECT title, disc_number, track_number, duration_ms, mbid
|
||
FROM tracks
|
||
WHERE album_id = $1
|
||
ORDER BY disc_number NULLS FIRST, track_number NULLS LAST, title;
|
||
|
||
-- name: SetAlbumReleaseGroup :exec
|
||
-- Names a matched album's release group, filling only what nothing named.
|
||
UPDATE albums SET release_group_mbid = sqlc.arg(release_group_mbid), updated_at = now()
|
||
WHERE id = sqlc.arg(id) AND (release_group_mbid IS NULL OR release_group_mbid = '');
|
||
|
||
-- name: InsertLibraryWant :execrows
|
||
-- Puts one matched album on the list as held, from just before its first
|
||
-- lost track if it has one. A group already on the list is left as it is.
|
||
INSERT INTO wants (release_group_mbid, artist_mbid, artist_name, title, primary_type, first_release_date,
|
||
reason, album_id, wanted_at, fulfilled_at)
|
||
VALUES (sqlc.arg(release_group_mbid), sqlc.arg(artist_mbid), sqlc.arg(artist_name), sqlc.arg(title),
|
||
sqlc.arg(primary_type), sqlc.arg(first_release_date), 'library', sqlc.arg(album_id)::uuid, now(),
|
||
COALESCE((SELECT MIN(t.missing_since) FROM tracks t WHERE t.album_id = sqlc.arg(album_id)::uuid)
|
||
- interval '1 second', now()))
|
||
ON CONFLICT (release_group_mbid) DO NOTHING;
|
||
|
||
-- name: RecordAlbumMatch :exec
|
||
INSERT INTO library_album_matches (album_id, outcome, method, release_group_mbid, detail, matcher)
|
||
VALUES (sqlc.arg(album_id), sqlc.arg(outcome), sqlc.arg(method), sqlc.arg(release_group_mbid), sqlc.arg(detail),
|
||
sqlc.arg(matcher))
|
||
ON CONFLICT (album_id) DO UPDATE
|
||
SET outcome = EXCLUDED.outcome, method = EXCLUDED.method, release_group_mbid = EXCLUDED.release_group_mbid,
|
||
detail = EXCLUDED.detail, matcher = EXCLUDED.matcher, decided_at = now();
|
||
|
||
-- name: FinishLibraryListing :one
|
||
-- The listing is done, or done again after a newer matcher's retries:
|
||
-- 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 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 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 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);
|
||
|
||
|
||
-- Upgrades (#5524). -------------------------------------------------------
|
||
|
||
-- name: ListHeldWantFormats :many
|
||
-- What each held album's copy is made of, for judging whether it is worth
|
||
-- upgrading: its present tracks grouped by format, with their bytes and
|
||
-- length summed, and how many are held back as video rips. A held want whose
|
||
-- album has no track with a length yet has no rows.
|
||
SELECT w.id AS want_id, w.upgrade, w.held_tier, w.held_quality,
|
||
lower(t.file_format)::text AS file_format,
|
||
SUM(t.file_size)::bigint AS bytes,
|
||
SUM(t.duration_ms)::bigint AS duration_ms,
|
||
COUNT(*)::int AS tracks,
|
||
COUNT(*) FILTER (WHERE t.source_verdict = 'suspect')::int AS rips
|
||
FROM wants w
|
||
JOIN tracks t ON t.album_id = w.album_id
|
||
WHERE w.fulfilled_at IS NOT NULL
|
||
AND t.missing_since IS NULL AND t.duration_ms > 0
|
||
GROUP BY w.id, lower(t.file_format)
|
||
ORDER BY w.id;
|
||
|
||
-- name: SetWantUpgrades :exec
|
||
-- Writes the upgrade check's verdicts, one row per want.
|
||
UPDATE wants w
|
||
SET upgrade = v.upgrade,
|
||
held_tier = v.held_tier,
|
||
held_quality = v.held_quality,
|
||
upgrade_checked_at = now(),
|
||
updated_at = CASE WHEN w.upgrade <> v.upgrade THEN now() ELSE w.updated_at END
|
||
FROM (SELECT unnest(sqlc.arg(ids)::uuid[]) AS id,
|
||
unnest(sqlc.arg(upgrades)::text[]) AS upgrade,
|
||
unnest(sqlc.arg(held_tiers)::text[]) AS held_tier,
|
||
unnest(sqlc.arg(held_qualities)::text[]) AS held_quality) v
|
||
WHERE w.id = v.id;
|
||
|
||
-- name: ClearHeldUpgrades :execrows
|
||
-- A held want whose album has no track left to judge is not wanted again.
|
||
UPDATE wants SET upgrade = '', held_tier = '', held_quality = '', upgrade_checked_at = now()
|
||
WHERE fulfilled_at IS NOT NULL AND upgrade <> ''
|
||
AND NOT EXISTS (SELECT 1 FROM tracks t
|
||
WHERE t.album_id = wants.album_id AND t.missing_since IS NULL AND t.duration_ms > 0);
|