Files
minstrel/internal/db/queries/search.sql
T
bvandeusenandClaude Opus 5.5 ebbc49b902
release / govulncheck (push) Successful in 14s
release / web (push) Failing after 15s
release / go (push) Failing after 1m16s
release / integration (push) Failing after 4m49s
release / android (push) Successful in 4m59s
release / Build signed APK (releases and dev) (push) Successful in 5m14s
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
feat: import with verification, and a Wanted view of its own (M502 #5522)
A finished download is checked against MusicBrainz before anything is
filed: the files are paired with an edition's tracks by position or path,
and AcoustID, tag ids, lengths and titles say whether it is the album.
What is the album is copied into the library as
Artist/Album (Year)/Artist - Album - NN - Title, tagged with Picard's
MusicBrainz ids, and scanned; the download keeps seeding. What is not is
refused: set aside, removed from the client, and the album searched for
again. A download the check cannot settle waits for a person, who imports
it anyway or refuses it. Tracks the library already holds are skipped.

The wanted list leaves Admin for a top-level Wanted view, admin-only and
web-only, with tabs for Wanted, Activity (downloads and their imports,
the ones needing a person first), Missing files, Held, Following (with the
release-type profiles, moved off Integrations) and Blocked. The search
settings gain "File new music into" when the library has several folders.

The search pass logs why each failed search failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 10:36:21 -04:00

123 lines
5.7 KiB
SQL

-- Searching for wants and grabbing releases (M502 #5521).
-- name: GetAcquisitionSettings :one
SELECT * FROM acquisition_settings WHERE id = true;
-- name: UpdateAcquisitionSettings :one
UPDATE acquisition_settings
SET auto_grab = sqlc.arg(auto_grab),
search_interval_hours = sqlc.arg(search_interval_hours),
allowed_qualities = sqlc.arg(allowed_qualities),
import_root = sqlc.arg(import_root),
updated_at = now()
WHERE id = true
RETURNING *;
-- name: RecordSearchPass :exec
UPDATE acquisition_settings SET last_pass_at = now(), last_pass = sqlc.arg(report) WHERE id = true;
-- name: ListWantsDueForSearch :many
-- Open wants with no download running that were never searched or not since
-- @hours ago: never-searched first, then requests, then missing files, then
-- followed artists' releases.
SELECT w.id, w.release_group_mbid, w.artist_name, w.title, w.reason,
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
FROM wants w
LEFT JOIN mb_release_groups g ON g.mbid = w.release_group_mbid
WHERE w.fulfilled_at IS NULL
AND NOT EXISTS (SELECT 1 FROM grabs gr WHERE gr.want_id = w.id AND gr.ended_at IS NULL)
AND (w.searched_at IS NULL OR w.searched_at < now() - make_interval(hours => sqlc.arg(hours)::int))
ORDER BY w.searched_at IS NOT NULL,
CASE w.reason WHEN 'request' THEN 0 WHEN 'missing' THEN 1 ELSE 2 END,
w.searched_at NULLS FIRST, w.wanted_at, w.id
LIMIT sqlc.arg(max_rows);
-- name: GetWantForSearch :one
SELECT w.id, w.release_group_mbid, w.artist_mbid, w.artist_name, w.title, w.reason, w.fulfilled_at,
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
FROM wants w
LEFT JOIN mb_release_groups g ON g.mbid = w.release_group_mbid
WHERE w.id = $1;
-- name: EditionTrackSpan :one
-- The fewest tracks an official edition of the group has (any edition when
-- none is official), and the most any has. Zero when none is catalogued.
SELECT COALESCE(MIN(track_count) FILTER (WHERE status = 'Official'), MIN(track_count), 0)::int AS min_tracks,
COALESCE(MAX(track_count), 0)::int AS max_tracks
FROM mb_releases
WHERE release_group_mbid = $1 AND track_count > 0;
-- name: RecordWantSearch :exec
UPDATE wants
SET searched_at = now(),
search_outcome = sqlc.arg(outcome),
search_considered = sqlc.arg(considered),
best_release = sqlc.arg(best_release),
best_verdict = sqlc.arg(best_verdict),
search_error = sqlc.arg(search_error),
updated_at = now()
WHERE id = sqlc.arg(id);
-- name: DeleteWantCandidates :exec
DELETE FROM want_candidates WHERE want_id = $1;
-- name: InsertWantCandidate :exec
INSERT INTO want_candidates (want_id, key, name, info_hash, indexer, source, size_bytes, seeders,
files_count, format, quality, year, verdict, reason, reasons, folded_from, rank)
VALUES (sqlc.arg(want_id), sqlc.arg(key), sqlc.arg(name), sqlc.arg(info_hash), sqlc.arg(indexer),
sqlc.arg(source), sqlc.arg(size_bytes), sqlc.arg(seeders), sqlc.arg(files_count), sqlc.arg(format),
sqlc.arg(quality), sqlc.arg(year), sqlc.arg(verdict), sqlc.arg(reason), sqlc.arg(reasons),
sqlc.arg(folded_from), sqlc.arg(rank))
ON CONFLICT (want_id, key) DO NOTHING;
-- name: ListWantCandidates :many
SELECT * FROM want_candidates WHERE want_id = $1 ORDER BY rank, key;
-- name: GetWantCandidate :one
SELECT * FROM want_candidates WHERE want_id = $1 AND key = $2;
-- name: MarkCandidatesBlocked :exec
UPDATE want_candidates
SET verdict = 'blocked', reason = sqlc.arg(reason),
reasons = jsonb_build_array(jsonb_build_object('kind', 'hard', 'code', 'blocked', 'text', sqlc.arg(reason)::text))
WHERE info_hash = sqlc.arg(info_hash);
-- name: MarkCandidatesUnblocked :exec
UPDATE want_candidates
SET verdict = 'review', reason = sqlc.arg(reason),
reasons = jsonb_build_array(jsonb_build_object('kind', 'soft', 'code', 'unblocked', 'text', sqlc.arg(reason)::text))
WHERE info_hash = sqlc.arg(info_hash) AND verdict = 'blocked';
-- name: GetLiveGrab :one
SELECT * FROM grabs WHERE want_id = $1 AND ended_at IS NULL;
-- name: InsertGrab :one
INSERT INTO grabs (want_id, info_hash, name, key, indexer, source, client, save_path, quality,
origin, forced, grabbed_by)
VALUES (sqlc.narg(want_id), sqlc.arg(info_hash), sqlc.arg(name), sqlc.arg(key), sqlc.arg(indexer),
sqlc.arg(source), sqlc.arg(client), sqlc.arg(save_path), sqlc.arg(quality),
sqlc.arg(origin), sqlc.arg(forced), sqlc.narg(grabbed_by))
RETURNING *;
-- name: EndGrab :exec
UPDATE grabs
SET ended_at = now(), end_reason = sqlc.arg(end_reason), end_detail = sqlc.arg(end_detail)
WHERE id = sqlc.arg(id) AND ended_at IS NULL;
-- name: ListReleaseBlocks :many
SELECT * FROM release_blocks ORDER BY blocked_at DESC;
-- name: BlockRelease :exec
INSERT INTO release_blocks (info_hash, name, reason, detail, want_id, blocked_by)
VALUES (sqlc.arg(info_hash), sqlc.arg(name), sqlc.arg(reason), sqlc.arg(detail),
sqlc.narg(want_id), sqlc.narg(blocked_by))
ON CONFLICT (info_hash) DO UPDATE
SET reason = EXCLUDED.reason, detail = EXCLUDED.detail, blocked_at = now();
-- name: UnblockRelease :execrows
DELETE FROM release_blocks WHERE info_hash = $1;