Files
minstrel/internal/db/dbq/reacquisition.sql.go
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

270 lines
8.4 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: reacquisition.sql
package dbq
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const countAlbumsMissingWithoutMbid = `-- name: CountAlbumsMissingWithoutMbid :one
SELECT COUNT(DISTINCT albums.id)::bigint
FROM albums
JOIN artists ON artists.id = albums.artist_id
JOIN tracks ON tracks.album_id = albums.id
WHERE tracks.missing_since IS NOT NULL
AND (albums.mbid IS NULL OR artists.mbid IS NULL)
`
// Albums with missing files that can never be wanted automatically because
// nothing identifies them to MusicBrainz. Surfaced on the admin card so the
// gap is visible: silently doing nothing for these would read as broken.
func (q *Queries) CountAlbumsMissingWithoutMbid(ctx context.Context) (int64, error) {
row := q.db.QueryRow(ctx, countAlbumsMissingWithoutMbid)
var column_1 int64
err := row.Scan(&column_1)
return column_1, err
}
const getReacquisitionSettings = `-- name: GetReacquisitionSettings :one
SELECT id, enabled, grace_hours, max_per_pass FROM reacquisition_settings WHERE id = true
`
// Re-acquisition of missing files (milestone #290, reworked for M502 #5520).
// The unit is the album: an album whose files went missing becomes a want for
// its release group, and the search schedule (#5521) looks for it.
func (q *Queries) GetReacquisitionSettings(ctx context.Context) (ReacquisitionSetting, error) {
row := q.db.QueryRow(ctx, getReacquisitionSettings)
var i ReacquisitionSetting
err := row.Scan(
&i.ID,
&i.Enabled,
&i.GraceHours,
&i.MaxPerPass,
)
return i, err
}
const getWantsForAlbums = `-- name: GetWantsForAlbums :many
SELECT a.id AS album_id, w.id AS want_id, w.wanted_at, w.fulfilled_at
FROM albums a
JOIN wants w ON w.album_id = a.id OR w.release_group_mbid = a.release_group_mbid
WHERE a.id = ANY($1::uuid[])
ORDER BY w.wanted_at DESC
`
type GetWantsForAlbumsRow struct {
AlbumID pgtype.UUID
WantID pgtype.UUID
WantedAt pgtype.Timestamptz
FulfilledAt pgtype.Timestamptz
}
// What the wanted list says about each album on the missing-files page.
func (q *Queries) GetWantsForAlbums(ctx context.Context, albumIds []pgtype.UUID) ([]GetWantsForAlbumsRow, error) {
rows, err := q.db.Query(ctx, getWantsForAlbums, albumIds)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetWantsForAlbumsRow
for rows.Next() {
var i GetWantsForAlbumsRow
if err := rows.Scan(
&i.AlbumID,
&i.WantID,
&i.WantedAt,
&i.FulfilledAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listAlbumsDueReacquisition = `-- name: ListAlbumsDueReacquisition :many
SELECT albums.id AS album_id,
albums.title AS album_title,
albums.mbid AS album_mbid,
albums.release_group_mbid AS album_release_group_mbid,
artists.name AS artist_name,
artists.mbid AS artist_mbid,
COUNT(tracks.id)::bigint AS missing_track_count,
MAX(tracks.missing_since)::timestamptz AS last_missing_since
FROM albums
JOIN artists ON artists.id = albums.artist_id
JOIN tracks ON tracks.album_id = albums.id
WHERE tracks.missing_since IS NOT NULL
AND tracks.missing_since <= now() - make_interval(hours => $1::int)
AND albums.mbid IS NOT NULL
AND artists.mbid IS NOT NULL
GROUP BY albums.id, albums.title, albums.mbid, albums.release_group_mbid,
artists.name, artists.mbid
HAVING EXISTS (
SELECT 1 FROM wants w
WHERE w.album_id = albums.id OR w.release_group_mbid = albums.release_group_mbid)
AND NOT EXISTS (
SELECT 1 FROM wants w
WHERE (w.album_id = albums.id OR w.release_group_mbid = albums.release_group_mbid)
AND (w.fulfilled_at IS NULL OR w.fulfilled_at > MAX(tracks.missing_since)))
ORDER BY MIN(tracks.missing_since), albums.sort_title
LIMIT $2
`
type ListAlbumsDueReacquisitionParams struct {
GraceHours int32
PageLimit int32
}
type ListAlbumsDueReacquisitionRow struct {
AlbumID pgtype.UUID
AlbumTitle string
AlbumMbid *string
AlbumReleaseGroupMbid *string
ArtistName string
ArtistMbid *string
MissingTrackCount int64
LastMissingSince pgtype.Timestamptz
}
// The sweeper's selection. An album qualifies when:
//
// - it still has at least one track whose file has been missing longer than
// the grace window. Measured on missing_since, which reconcile never
// re-stamps (#2523), so it is a genuine "gone since" clock;
// - MusicBrainz can name it: the album and its artist have MBIDs. Albums
// failing this are counted separately (CountAlbumsMissingWithoutMbid);
// - it is on the wanted list (#5567): an album taken off the list, or
// dropped into the library by hand, is not fetched back;
// - no want already covers it: none open, and none held since its files
// went missing. A want held after the loss means the library has a full
// copy despite the missing rows (#5245), so asking again would loop.
func (q *Queries) ListAlbumsDueReacquisition(ctx context.Context, arg ListAlbumsDueReacquisitionParams) ([]ListAlbumsDueReacquisitionRow, error) {
rows, err := q.db.Query(ctx, listAlbumsDueReacquisition, arg.GraceHours, arg.PageLimit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []ListAlbumsDueReacquisitionRow
for rows.Next() {
var i ListAlbumsDueReacquisitionRow
if err := rows.Scan(
&i.AlbumID,
&i.AlbumTitle,
&i.AlbumMbid,
&i.AlbumReleaseGroupMbid,
&i.ArtistName,
&i.ArtistMbid,
&i.MissingTrackCount,
&i.LastMissingSince,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateReacquisitionSettings = `-- name: UpdateReacquisitionSettings :one
UPDATE reacquisition_settings
SET enabled = $1,
grace_hours = $2,
max_per_pass = $3
WHERE id = true
RETURNING id, enabled, grace_hours, max_per_pass
`
type UpdateReacquisitionSettingsParams struct {
Enabled bool
GraceHours int32
MaxPerPass int32
}
// Whole-row write from the admin card; the CHECKs in migration 0056 are the
// validation, so a bad value fails loudly rather than being clamped silently.
func (q *Queries) UpdateReacquisitionSettings(ctx context.Context, arg UpdateReacquisitionSettingsParams) (ReacquisitionSetting, error) {
row := q.db.QueryRow(ctx, updateReacquisitionSettings, arg.Enabled, arg.GraceHours, arg.MaxPerPass)
var i ReacquisitionSetting
err := row.Scan(
&i.ID,
&i.Enabled,
&i.GraceHours,
&i.MaxPerPass,
)
return i, err
}
const wantMissingAlbum = `-- name: WantMissingAlbum :one
INSERT INTO wants (release_group_mbid, artist_mbid, artist_name, title, reason, album_id)
VALUES ($1, $2, $3,
$4, 'missing', $5)
ON CONFLICT (release_group_mbid) DO UPDATE
SET reason = 'missing',
album_id = EXCLUDED.album_id,
wanted_at = now(),
fulfilled_at = NULL,
updated_at = now()
WHERE wants.fulfilled_at IS NOT NULL
RETURNING id, release_group_mbid, artist_mbid, artist_name, title, primary_type, first_release_date, reason, album_id, created_by, wanted_at, fulfilled_at, created_at, updated_at, searched_at, search_outcome, search_considered, best_release, best_verdict, search_error, upgrade, held_tier, held_quality, upgrade_checked_at
`
type WantMissingAlbumParams struct {
ReleaseGroupMbid string
ArtistMbid string
ArtistName string
Title string
AlbumID pgtype.UUID
}
// Records an album with missing files as wanted. A held want for the same
// group is reopened; an open one is left as it is, and no row comes back.
func (q *Queries) WantMissingAlbum(ctx context.Context, arg WantMissingAlbumParams) (Want, error) {
row := q.db.QueryRow(ctx, wantMissingAlbum,
arg.ReleaseGroupMbid,
arg.ArtistMbid,
arg.ArtistName,
arg.Title,
arg.AlbumID,
)
var i Want
err := row.Scan(
&i.ID,
&i.ReleaseGroupMbid,
&i.ArtistMbid,
&i.ArtistName,
&i.Title,
&i.PrimaryType,
&i.FirstReleaseDate,
&i.Reason,
&i.AlbumID,
&i.CreatedBy,
&i.WantedAt,
&i.FulfilledAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.SearchedAt,
&i.SearchOutcome,
&i.SearchConsidered,
&i.BestRelease,
&i.BestVerdict,
&i.SearchError,
&i.Upgrade,
&i.HeldTier,
&i.HeldQuality,
&i.UpgradeCheckedAt,
)
return i, err
}