Files
minstrel/internal/db/queries/imports.sql
T
bvandeusenandClaude Opus 5.5 c54792c51e
release / govulncheck (push) Successful in 15s
release / web (push) Successful in 1m23s
release / go (push) Successful in 1m41s
release / integration (push) Successful in 5m20s
release / android (push) Successful in 5m28s
release / Build signed APK (releases and dev) (push) Successful in 5m30s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 27s
release / Verify release artifacts (tag releases only) (push) Skipped
feat: a held album below the quality you settle for, or holding a video rip, stays wanted for a better copy, and the import replaces the old tracks with their likes and plays carried over (M502 #5524)
- acquisition_settings.upgrade_until (default High quality lossy, Lidarr's
  cutoff, note #5527; "" is off), an "Upgrade until" row under Searching.
- The wanted worker judges held albums hourly: tier per format from size per
  second (the scanner records no bitrate), the album's worst. Below the
  cutoff it is wanted for quality; a video rip among its tracks always.
- The schedule searches upgrades weekly, after open wants; a release no
  better than the copy held is refused ("not_better").
- The import matches each file to the track it would replace (recording,
  then disc/track) and replaces it when it is a rip or the file is a better
  tier: over the same path in one rename, or filed beside it and the old
  track folded in (library.ReplaceTrack: file first, then foldTrackInto).
  Nothing better: the release is set aside.
- Collection › Upgrades (Lidarr's Cutoff Unmet); Suspect sources says when a
  clean copy was looked for instead of Lidarr's search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:24:59 -04:00

166 lines
7.2 KiB
SQL

-- Import with verification (M502 #5522).
-- name: ListLiveGrabs :many
-- Every download still running or waiting on its import, oldest first.
SELECT g.*, w.release_group_mbid, w.artist_mbid, w.artist_name, w.title AS want_title,
w.reason AS want_reason, w.album_id AS want_album_id
FROM grabs g
LEFT JOIN wants w ON w.id = g.want_id
WHERE g.ended_at IS NULL
ORDER BY g.grabbed_at, g.id;
-- name: GetGrab :one
SELECT * FROM grabs WHERE id = $1;
-- name: SetGrabProgress :exec
-- What the download client says of a download not yet finished, or finished
-- and waiting for what the import needs. Leaves a claimed, decided or failed
-- import alone.
UPDATE grabs
SET progress = sqlc.arg(progress),
import_state = sqlc.arg(import_state),
import_reason = sqlc.arg(import_reason),
import_updated_at = now()
WHERE id = sqlc.arg(id) AND ended_at IS NULL
AND import_state IN ('', 'downloading', 'waiting');
-- name: ClaimGrabImport :one
-- Takes a finished download for import. The update is the lock: a second
-- pass finds nothing to claim.
UPDATE grabs
SET import_state = 'importing', import_attempts = import_attempts + 1,
import_updated_at = now(), progress = 1
WHERE id = $1 AND ended_at IS NULL AND import_state IN ('', 'downloading', 'waiting')
RETURNING *;
-- name: ReclaimStaleImports :execrows
-- An import cut short (a restart mid-copy) is tried again.
UPDATE grabs
SET import_state = 'waiting',
import_reason = 'The last import was cut short, so it is being tried again.'
WHERE ended_at IS NULL AND import_state = 'importing'
AND import_updated_at < now() - make_interval(mins => sqlc.arg(minutes)::int);
-- name: RecordImport :one
-- The import's outcome. An end reason ends the grab: imported, refused or
-- held. Without one the grab stays live: review and failed wait for a person.
UPDATE grabs
SET import_state = sqlc.arg(import_state),
import_reason = sqlc.arg(import_reason),
import_detail = sqlc.arg(import_detail),
import_updated_at = now(),
import_forced = false,
imported_at = CASE WHEN sqlc.arg(import_state)::text = 'imported' THEN now() ELSE imported_at END,
-- The files were copied, so an imported download goes on sharing
-- until the lifecycle removes it (#5523).
seeding = sqlc.arg(import_state)::text = 'imported',
imported_files = sqlc.arg(imported_files),
destination = sqlc.arg(destination),
ended_at = CASE WHEN sqlc.arg(end_reason)::text <> '' THEN now() ELSE ended_at END,
end_reason = sqlc.arg(end_reason),
end_detail = CASE WHEN sqlc.arg(end_reason)::text <> '' THEN sqlc.arg(import_reason)::text ELSE end_detail END
WHERE id = sqlc.arg(id)
RETURNING *;
-- name: ForceGrabImport :one
-- "Import anyway": the next pass imports the download whatever the check said.
UPDATE grabs
SET import_state = '', import_forced = true, import_reason = '', import_updated_at = now()
WHERE id = $1 AND ended_at IS NULL AND import_state IN ('review', 'failed')
RETURNING *;
-- name: RetryGrabImport :one
UPDATE grabs
SET import_state = '', import_reason = '', import_updated_at = now()
WHERE id = $1 AND ended_at IS NULL AND import_state = 'failed'
RETURNING *;
-- name: RehomeGrab :one
-- A download that turned out to be another wanted album is that want's grab,
-- when the want has none running.
UPDATE grabs g
SET want_id = sqlc.arg(want_id)
WHERE g.id = sqlc.arg(id) AND g.ended_at IS NULL
AND NOT EXISTS (SELECT 1 FROM grabs o WHERE o.want_id = sqlc.arg(want_id) AND o.ended_at IS NULL)
RETURNING *;
-- name: OpenWantsForGroups :many
-- Open wants for these release groups with no download running.
SELECT w.* FROM wants w
WHERE w.fulfilled_at IS NULL AND w.release_group_mbid = ANY(sqlc.arg(groups)::text[])
AND NOT EXISTS (SELECT 1 FROM grabs g WHERE g.want_id = w.id AND g.ended_at IS NULL);
-- name: MarkWantSearched :exec
-- Imported: nothing to search for until the wanted pass settles the want.
UPDATE wants SET searched_at = now() WHERE id = $1;
-- name: SearchWantAgain :exec
-- Refused: search again on the next pass.
UPDATE wants SET searched_at = NULL, search_outcome = '' WHERE id = $1;
-- name: HeldRecordings :many
-- Which of these recordings the library holds a file for.
SELECT DISTINCT mbid::text AS mbid FROM tracks
WHERE mbid = ANY(sqlc.arg(recordings)::text[]) AND missing_since IS NULL;
-- name: HeldPositions :many
-- The disc and track positions the library holds for an album of this group.
SELECT DISTINCT COALESCE(t.disc_number, 1)::int AS disc, t.track_number::int AS track
FROM tracks t
JOIN albums a ON a.id = t.album_id
WHERE a.release_group_mbid = sqlc.arg(release_group_mbid)
AND t.missing_since IS NULL AND t.track_number IS NOT NULL;
-- name: ListGrabs :many
-- The Wanted view's Activity tab: downloads waiting on a person first, then
-- the rest still running, then the ended ones newest first. Each carries
-- what is wrong with it now, if anything (#5523).
SELECT g.id, g.want_id, g.name, g.indexer, g.client, g.quality, g.origin, g.forced,
g.grabbed_at, g.ended_at, g.end_reason, g.end_detail, g.progress, g.import_state,
g.import_reason, g.import_detail, g.import_updated_at, g.imported_at, g.imported_files,
g.destination, g.seeding, g.ratio, g.seed_released_at,
w.title AS want_title, w.artist_name AS want_artist,
di.trigger AS condition, di.detail AS condition_detail, di.remediations AS condition_retries,
di.first_seen AS condition_since
FROM grabs g
LEFT JOIN wants w ON w.id = g.want_id
LEFT JOIN download_interventions di
ON di.id = (SELECT x.id FROM download_interventions x
WHERE x.grab_id = g.id AND x.outcome = 'open'
ORDER BY x.last_seen DESC LIMIT 1)
ORDER BY (g.ended_at IS NULL AND g.import_state IN ('review', 'failed')) DESC,
g.ended_at IS NULL DESC, g.grabbed_at DESC, g.id
LIMIT sqlc.arg(max_rows) OFFSET sqlc.arg(skip);
-- name: ListReleaseBlocksPage :many
SELECT b.info_hash, b.name, b.reason, b.detail, b.blocked_at, b.expires_at, w.title AS want_title,
w.artist_name AS want_artist
FROM release_blocks b
LEFT JOIN wants w ON w.id = b.want_id
WHERE b.expires_at IS NULL OR b.expires_at > now()
ORDER BY b.blocked_at DESC, b.info_hash
LIMIT sqlc.arg(max_rows) OFFSET sqlc.arg(skip);
-- name: ListUpgradeTracks :many
-- The tracks an upgrade may replace (#5524): the present tracks of the album
-- the want holds.
SELECT id, file_path, mbid, COALESCE(disc_number, 1)::int AS disc, track_number,
file_format, file_size, duration_ms, source_verdict
FROM tracks
WHERE album_id = $1 AND missing_since IS NULL;
-- name: FinishUpgrade :exec
-- An upgrade was imported: the want holds the album the new tracks are in,
-- and is judged again by the next upgrade check.
UPDATE wants
SET album_id = COALESCE(sqlc.narg(album_id), album_id),
upgrade = '',
upgrade_checked_at = NULL,
updated_at = now()
WHERE id = sqlc.arg(id);
-- name: ClearTrackSourceVerdict :exec
-- A track whose file was replaced in place is no longer the rip it was.
UPDATE tracks SET source_verdict = NULL, source_verdict_at = NULL
WHERE id = $1 AND source_verdict = 'suspect';