-- AcoustID recording-id lookup (M401). Migration 0069 says what each -- lookup state means. -- name: ListTracksNeedingAcoustIDLookup :many -- The lookup worker's queue: present tracks with no tag MBID and no lookup -- since their file last changed. A looked-up id is included once its row is -- gone (the file changed), so the new bytes are checked rather than trusted. -- Keyset-paged on id, as the loudness queue is, so a pass ends even when -- every attempt is inconclusive and writes nothing. SELECT t.id, t.file_path, t.title, t.duration_ms FROM tracks t WHERE (t.mbid IS NULL OR t.mbid_source = 'acoustid') AND t.missing_since IS NULL AND t.id > sqlc.arg(after_id)::uuid AND NOT EXISTS (SELECT 1 FROM track_acoustid_lookups l WHERE l.track_id = t.id) ORDER BY t.id LIMIT sqlc.arg(batch_limit); -- name: RecordAcoustIDLookup :exec INSERT INTO track_acoustid_lookups (track_id, state, best_score, candidates, recording_mbid, detail) VALUES (sqlc.arg(track_id), sqlc.arg(state), sqlc.narg(best_score), sqlc.arg(candidates), sqlc.narg(recording_mbid), sqlc.narg(detail)) ON CONFLICT (track_id) DO UPDATE SET state = EXCLUDED.state, best_score = EXCLUDED.best_score, candidates = EXCLUDED.candidates, recording_mbid = EXCLUDED.recording_mbid, detail = EXCLUDED.detail, looked_up_at = now(); -- name: SetTrackMbidFromAcoustID :execrows -- Never over a tag id (D4): the guard is here, not only in the worker, so a -- tag the scan read between the worker's list and this write still wins. -- updated_at is left alone: it gates the scan's mtime skip, and moving it -- past a file's mtime would hide a change made just before the lookup. UPDATE tracks SET mbid = sqlc.arg(mbid), mbid_source = 'acoustid' WHERE id = sqlc.arg(id) AND (mbid IS NULL OR mbid_source = 'acoustid'); -- name: ClearTrackAcoustIDMbid :exec -- A re-lookup of changed bytes that no longer matches takes back the id an -- earlier lookup wrote. A tag id is never touched. UPDATE tracks SET mbid = NULL, mbid_source = NULL WHERE id = sqlc.arg(id) AND mbid_source = 'acoustid'; -- name: DeleteAcoustIDLookup :exec -- The scan drops the lookup of bytes that are gone, so the worker checks the -- new ones. DELETE FROM track_acoustid_lookups WHERE track_id = $1; -- name: GetMbidCoverage :one -- The admin gauge: present tracks by where their MBID came from, and the -- lookup states of those a lookup has seen. pending is what the worker's -- queue holds. SELECT count(*)::bigint AS total, count(*) FILTER (WHERE t.mbid_source = 'tag')::bigint AS from_tag, count(*) FILTER (WHERE t.mbid_source = 'acoustid')::bigint AS from_acoustid, count(*) FILTER (WHERE l.state = 'ambiguous')::bigint AS ambiguous, count(*) FILTER (WHERE l.state = 'no_match')::bigint AS no_match, count(*) FILTER (WHERE l.state = 'failed')::bigint AS failed, count(*) FILTER ( WHERE l.track_id IS NULL AND (t.mbid IS NULL OR t.mbid_source = 'acoustid') )::bigint AS pending FROM tracks t LEFT JOIN track_acoustid_lookups l ON l.track_id = t.id WHERE t.missing_since IS NULL; -- name: ListUnsettledAcoustIDLookups :many -- The admin list of tracks a lookup could not settle (ambiguous or failed), -- so they are findable rather than silently empty. SELECT t.id, t.title, t.file_path, ar.name AS artist_name, al.title AS album_title, l.state, l.best_score, l.candidates, l.detail, l.looked_up_at FROM track_acoustid_lookups l JOIN tracks t ON t.id = l.track_id JOIN artists ar ON ar.id = t.artist_id JOIN albums al ON al.id = t.album_id WHERE l.state IN ('ambiguous', 'failed') AND t.missing_since IS NULL ORDER BY ar.name, al.title, t.disc_number NULLS FIRST, t.track_number NULLS FIRST, t.title LIMIT sqlc.arg(row_limit); -- name: GetAcoustIDSettings :one SELECT * FROM acoustid_settings WHERE id = true; -- name: UpdateAcoustIDSettings :one -- The key is write-only from the admin card: set_api_key false keeps the -- stored one, and an empty api_key with set_api_key true clears it. UPDATE acoustid_settings SET enabled = sqlc.arg(enabled), min_score = sqlc.arg(min_score), api_key = CASE WHEN sqlc.arg(set_api_key)::boolean THEN NULLIF(sqlc.arg(api_key)::text, '') ELSE api_key END, updated_at = now() WHERE id = true RETURNING *;