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
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>
123 lines
5.7 KiB
SQL
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;
|