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
- 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>
270 lines
8.4 KiB
Go
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
|
|
}
|