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>
859 lines
23 KiB
Go
859 lines
23 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: imports.sql
|
|
|
|
package dbq
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const claimGrabImport = `-- name: ClaimGrabImport :one
|
|
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 id, want_id, info_hash, name, key, indexer, source, client, save_path, quality, origin, forced, grabbed_by, grabbed_at, ended_at, end_reason, end_detail, progress, import_state, import_reason, import_detail, import_forced, import_attempts, import_updated_at, imported_at, imported_files, destination, seeding, ratio, seed_released_at
|
|
`
|
|
|
|
// Takes a finished download for import. The update is the lock: a second
|
|
// pass finds nothing to claim.
|
|
func (q *Queries) ClaimGrabImport(ctx context.Context, id pgtype.UUID) (Grab, error) {
|
|
row := q.db.QueryRow(ctx, claimGrabImport, id)
|
|
var i Grab
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WantID,
|
|
&i.InfoHash,
|
|
&i.Name,
|
|
&i.Key,
|
|
&i.Indexer,
|
|
&i.Source,
|
|
&i.Client,
|
|
&i.SavePath,
|
|
&i.Quality,
|
|
&i.Origin,
|
|
&i.Forced,
|
|
&i.GrabbedBy,
|
|
&i.GrabbedAt,
|
|
&i.EndedAt,
|
|
&i.EndReason,
|
|
&i.EndDetail,
|
|
&i.Progress,
|
|
&i.ImportState,
|
|
&i.ImportReason,
|
|
&i.ImportDetail,
|
|
&i.ImportForced,
|
|
&i.ImportAttempts,
|
|
&i.ImportUpdatedAt,
|
|
&i.ImportedAt,
|
|
&i.ImportedFiles,
|
|
&i.Destination,
|
|
&i.Seeding,
|
|
&i.Ratio,
|
|
&i.SeedReleasedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const clearTrackSourceVerdict = `-- name: ClearTrackSourceVerdict :exec
|
|
UPDATE tracks SET source_verdict = NULL, source_verdict_at = NULL
|
|
WHERE id = $1 AND source_verdict = 'suspect'
|
|
`
|
|
|
|
// A track whose file was replaced in place is no longer the rip it was.
|
|
func (q *Queries) ClearTrackSourceVerdict(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, clearTrackSourceVerdict, id)
|
|
return err
|
|
}
|
|
|
|
const finishUpgrade = `-- name: FinishUpgrade :exec
|
|
UPDATE wants
|
|
SET album_id = COALESCE($1, album_id),
|
|
upgrade = '',
|
|
upgrade_checked_at = NULL,
|
|
updated_at = now()
|
|
WHERE id = $2
|
|
`
|
|
|
|
type FinishUpgradeParams struct {
|
|
AlbumID pgtype.UUID
|
|
ID pgtype.UUID
|
|
}
|
|
|
|
// An upgrade was imported: the want holds the album the new tracks are in,
|
|
// and is judged again by the next upgrade check.
|
|
func (q *Queries) FinishUpgrade(ctx context.Context, arg FinishUpgradeParams) error {
|
|
_, err := q.db.Exec(ctx, finishUpgrade, arg.AlbumID, arg.ID)
|
|
return err
|
|
}
|
|
|
|
const forceGrabImport = `-- name: ForceGrabImport :one
|
|
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 id, want_id, info_hash, name, key, indexer, source, client, save_path, quality, origin, forced, grabbed_by, grabbed_at, ended_at, end_reason, end_detail, progress, import_state, import_reason, import_detail, import_forced, import_attempts, import_updated_at, imported_at, imported_files, destination, seeding, ratio, seed_released_at
|
|
`
|
|
|
|
// "Import anyway": the next pass imports the download whatever the check said.
|
|
func (q *Queries) ForceGrabImport(ctx context.Context, id pgtype.UUID) (Grab, error) {
|
|
row := q.db.QueryRow(ctx, forceGrabImport, id)
|
|
var i Grab
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WantID,
|
|
&i.InfoHash,
|
|
&i.Name,
|
|
&i.Key,
|
|
&i.Indexer,
|
|
&i.Source,
|
|
&i.Client,
|
|
&i.SavePath,
|
|
&i.Quality,
|
|
&i.Origin,
|
|
&i.Forced,
|
|
&i.GrabbedBy,
|
|
&i.GrabbedAt,
|
|
&i.EndedAt,
|
|
&i.EndReason,
|
|
&i.EndDetail,
|
|
&i.Progress,
|
|
&i.ImportState,
|
|
&i.ImportReason,
|
|
&i.ImportDetail,
|
|
&i.ImportForced,
|
|
&i.ImportAttempts,
|
|
&i.ImportUpdatedAt,
|
|
&i.ImportedAt,
|
|
&i.ImportedFiles,
|
|
&i.Destination,
|
|
&i.Seeding,
|
|
&i.Ratio,
|
|
&i.SeedReleasedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getGrab = `-- name: GetGrab :one
|
|
SELECT id, want_id, info_hash, name, key, indexer, source, client, save_path, quality, origin, forced, grabbed_by, grabbed_at, ended_at, end_reason, end_detail, progress, import_state, import_reason, import_detail, import_forced, import_attempts, import_updated_at, imported_at, imported_files, destination, seeding, ratio, seed_released_at FROM grabs WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetGrab(ctx context.Context, id pgtype.UUID) (Grab, error) {
|
|
row := q.db.QueryRow(ctx, getGrab, id)
|
|
var i Grab
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WantID,
|
|
&i.InfoHash,
|
|
&i.Name,
|
|
&i.Key,
|
|
&i.Indexer,
|
|
&i.Source,
|
|
&i.Client,
|
|
&i.SavePath,
|
|
&i.Quality,
|
|
&i.Origin,
|
|
&i.Forced,
|
|
&i.GrabbedBy,
|
|
&i.GrabbedAt,
|
|
&i.EndedAt,
|
|
&i.EndReason,
|
|
&i.EndDetail,
|
|
&i.Progress,
|
|
&i.ImportState,
|
|
&i.ImportReason,
|
|
&i.ImportDetail,
|
|
&i.ImportForced,
|
|
&i.ImportAttempts,
|
|
&i.ImportUpdatedAt,
|
|
&i.ImportedAt,
|
|
&i.ImportedFiles,
|
|
&i.Destination,
|
|
&i.Seeding,
|
|
&i.Ratio,
|
|
&i.SeedReleasedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const heldPositions = `-- name: HeldPositions :many
|
|
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 = $1
|
|
AND t.missing_since IS NULL AND t.track_number IS NOT NULL
|
|
`
|
|
|
|
type HeldPositionsRow struct {
|
|
Disc int32
|
|
Track int32
|
|
}
|
|
|
|
// The disc and track positions the library holds for an album of this group.
|
|
func (q *Queries) HeldPositions(ctx context.Context, releaseGroupMbid *string) ([]HeldPositionsRow, error) {
|
|
rows, err := q.db.Query(ctx, heldPositions, releaseGroupMbid)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []HeldPositionsRow
|
|
for rows.Next() {
|
|
var i HeldPositionsRow
|
|
if err := rows.Scan(&i.Disc, &i.Track); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const heldRecordings = `-- name: HeldRecordings :many
|
|
SELECT DISTINCT mbid::text AS mbid FROM tracks
|
|
WHERE mbid = ANY($1::text[]) AND missing_since IS NULL
|
|
`
|
|
|
|
// Which of these recordings the library holds a file for.
|
|
func (q *Queries) HeldRecordings(ctx context.Context, recordings []string) ([]string, error) {
|
|
rows, err := q.db.Query(ctx, heldRecordings, recordings)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []string
|
|
for rows.Next() {
|
|
var mbid string
|
|
if err := rows.Scan(&mbid); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, mbid)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listGrabs = `-- name: ListGrabs :many
|
|
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 $2 OFFSET $1
|
|
`
|
|
|
|
type ListGrabsParams struct {
|
|
Skip int32
|
|
MaxRows int32
|
|
}
|
|
|
|
type ListGrabsRow struct {
|
|
ID pgtype.UUID
|
|
WantID pgtype.UUID
|
|
Name string
|
|
Indexer string
|
|
Client string
|
|
Quality string
|
|
Origin string
|
|
Forced bool
|
|
GrabbedAt pgtype.Timestamptz
|
|
EndedAt pgtype.Timestamptz
|
|
EndReason string
|
|
EndDetail string
|
|
Progress float32
|
|
ImportState string
|
|
ImportReason string
|
|
ImportDetail []byte
|
|
ImportUpdatedAt pgtype.Timestamptz
|
|
ImportedAt pgtype.Timestamptz
|
|
ImportedFiles int32
|
|
Destination string
|
|
Seeding bool
|
|
Ratio float32
|
|
SeedReleasedAt pgtype.Timestamptz
|
|
WantTitle *string
|
|
WantArtist *string
|
|
Condition *string
|
|
ConditionDetail *string
|
|
ConditionRetries *int32
|
|
ConditionSince pgtype.Timestamptz
|
|
}
|
|
|
|
// 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).
|
|
func (q *Queries) ListGrabs(ctx context.Context, arg ListGrabsParams) ([]ListGrabsRow, error) {
|
|
rows, err := q.db.Query(ctx, listGrabs, arg.Skip, arg.MaxRows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListGrabsRow
|
|
for rows.Next() {
|
|
var i ListGrabsRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.WantID,
|
|
&i.Name,
|
|
&i.Indexer,
|
|
&i.Client,
|
|
&i.Quality,
|
|
&i.Origin,
|
|
&i.Forced,
|
|
&i.GrabbedAt,
|
|
&i.EndedAt,
|
|
&i.EndReason,
|
|
&i.EndDetail,
|
|
&i.Progress,
|
|
&i.ImportState,
|
|
&i.ImportReason,
|
|
&i.ImportDetail,
|
|
&i.ImportUpdatedAt,
|
|
&i.ImportedAt,
|
|
&i.ImportedFiles,
|
|
&i.Destination,
|
|
&i.Seeding,
|
|
&i.Ratio,
|
|
&i.SeedReleasedAt,
|
|
&i.WantTitle,
|
|
&i.WantArtist,
|
|
&i.Condition,
|
|
&i.ConditionDetail,
|
|
&i.ConditionRetries,
|
|
&i.ConditionSince,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listLiveGrabs = `-- name: ListLiveGrabs :many
|
|
|
|
SELECT g.id, g.want_id, g.info_hash, g.name, g.key, g.indexer, g.source, g.client, g.save_path, g.quality, g.origin, g.forced, g.grabbed_by, 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_forced, g.import_attempts, g.import_updated_at, g.imported_at, g.imported_files, g.destination, g.seeding, g.ratio, g.seed_released_at, 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
|
|
`
|
|
|
|
type ListLiveGrabsRow struct {
|
|
ID pgtype.UUID
|
|
WantID pgtype.UUID
|
|
InfoHash string
|
|
Name string
|
|
Key string
|
|
Indexer string
|
|
Source string
|
|
Client string
|
|
SavePath string
|
|
Quality string
|
|
Origin string
|
|
Forced bool
|
|
GrabbedBy pgtype.UUID
|
|
GrabbedAt pgtype.Timestamptz
|
|
EndedAt pgtype.Timestamptz
|
|
EndReason string
|
|
EndDetail string
|
|
Progress float32
|
|
ImportState string
|
|
ImportReason string
|
|
ImportDetail []byte
|
|
ImportForced bool
|
|
ImportAttempts int32
|
|
ImportUpdatedAt pgtype.Timestamptz
|
|
ImportedAt pgtype.Timestamptz
|
|
ImportedFiles int32
|
|
Destination string
|
|
Seeding bool
|
|
Ratio float32
|
|
SeedReleasedAt pgtype.Timestamptz
|
|
ReleaseGroupMbid *string
|
|
ArtistMbid *string
|
|
ArtistName *string
|
|
WantTitle *string
|
|
WantReason *string
|
|
WantAlbumID pgtype.UUID
|
|
}
|
|
|
|
// Import with verification (M502 #5522).
|
|
// Every download still running or waiting on its import, oldest first.
|
|
func (q *Queries) ListLiveGrabs(ctx context.Context) ([]ListLiveGrabsRow, error) {
|
|
rows, err := q.db.Query(ctx, listLiveGrabs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListLiveGrabsRow
|
|
for rows.Next() {
|
|
var i ListLiveGrabsRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.WantID,
|
|
&i.InfoHash,
|
|
&i.Name,
|
|
&i.Key,
|
|
&i.Indexer,
|
|
&i.Source,
|
|
&i.Client,
|
|
&i.SavePath,
|
|
&i.Quality,
|
|
&i.Origin,
|
|
&i.Forced,
|
|
&i.GrabbedBy,
|
|
&i.GrabbedAt,
|
|
&i.EndedAt,
|
|
&i.EndReason,
|
|
&i.EndDetail,
|
|
&i.Progress,
|
|
&i.ImportState,
|
|
&i.ImportReason,
|
|
&i.ImportDetail,
|
|
&i.ImportForced,
|
|
&i.ImportAttempts,
|
|
&i.ImportUpdatedAt,
|
|
&i.ImportedAt,
|
|
&i.ImportedFiles,
|
|
&i.Destination,
|
|
&i.Seeding,
|
|
&i.Ratio,
|
|
&i.SeedReleasedAt,
|
|
&i.ReleaseGroupMbid,
|
|
&i.ArtistMbid,
|
|
&i.ArtistName,
|
|
&i.WantTitle,
|
|
&i.WantReason,
|
|
&i.WantAlbumID,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listReleaseBlocksPage = `-- 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 $2 OFFSET $1
|
|
`
|
|
|
|
type ListReleaseBlocksPageParams struct {
|
|
Skip int32
|
|
MaxRows int32
|
|
}
|
|
|
|
type ListReleaseBlocksPageRow struct {
|
|
InfoHash string
|
|
Name string
|
|
Reason string
|
|
Detail string
|
|
BlockedAt pgtype.Timestamptz
|
|
ExpiresAt pgtype.Timestamptz
|
|
WantTitle *string
|
|
WantArtist *string
|
|
}
|
|
|
|
func (q *Queries) ListReleaseBlocksPage(ctx context.Context, arg ListReleaseBlocksPageParams) ([]ListReleaseBlocksPageRow, error) {
|
|
rows, err := q.db.Query(ctx, listReleaseBlocksPage, arg.Skip, arg.MaxRows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListReleaseBlocksPageRow
|
|
for rows.Next() {
|
|
var i ListReleaseBlocksPageRow
|
|
if err := rows.Scan(
|
|
&i.InfoHash,
|
|
&i.Name,
|
|
&i.Reason,
|
|
&i.Detail,
|
|
&i.BlockedAt,
|
|
&i.ExpiresAt,
|
|
&i.WantTitle,
|
|
&i.WantArtist,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listUpgradeTracks = `-- name: ListUpgradeTracks :many
|
|
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
|
|
`
|
|
|
|
type ListUpgradeTracksRow struct {
|
|
ID pgtype.UUID
|
|
FilePath string
|
|
Mbid *string
|
|
Disc int32
|
|
TrackNumber *int32
|
|
FileFormat string
|
|
FileSize int64
|
|
DurationMs int32
|
|
SourceVerdict *string
|
|
}
|
|
|
|
// The tracks an upgrade may replace (#5524): the present tracks of the album
|
|
// the want holds.
|
|
func (q *Queries) ListUpgradeTracks(ctx context.Context, albumID pgtype.UUID) ([]ListUpgradeTracksRow, error) {
|
|
rows, err := q.db.Query(ctx, listUpgradeTracks, albumID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []ListUpgradeTracksRow
|
|
for rows.Next() {
|
|
var i ListUpgradeTracksRow
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.FilePath,
|
|
&i.Mbid,
|
|
&i.Disc,
|
|
&i.TrackNumber,
|
|
&i.FileFormat,
|
|
&i.FileSize,
|
|
&i.DurationMs,
|
|
&i.SourceVerdict,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const markWantSearched = `-- name: MarkWantSearched :exec
|
|
UPDATE wants SET searched_at = now() WHERE id = $1
|
|
`
|
|
|
|
// Imported: nothing to search for until the wanted pass settles the want.
|
|
func (q *Queries) MarkWantSearched(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, markWantSearched, id)
|
|
return err
|
|
}
|
|
|
|
const openWantsForGroups = `-- name: OpenWantsForGroups :many
|
|
SELECT w.id, w.release_group_mbid, w.artist_mbid, w.artist_name, w.title, w.primary_type, w.first_release_date, w.reason, w.album_id, w.created_by, w.wanted_at, w.fulfilled_at, w.created_at, w.updated_at, w.searched_at, w.search_outcome, w.search_considered, w.best_release, w.best_verdict, w.search_error, w.upgrade, w.held_tier, w.held_quality, w.upgrade_checked_at FROM wants w
|
|
WHERE w.fulfilled_at IS NULL AND w.release_group_mbid = ANY($1::text[])
|
|
AND NOT EXISTS (SELECT 1 FROM grabs g WHERE g.want_id = w.id AND g.ended_at IS NULL)
|
|
`
|
|
|
|
// Open wants for these release groups with no download running.
|
|
func (q *Queries) OpenWantsForGroups(ctx context.Context, groups []string) ([]Want, error) {
|
|
rows, err := q.db.Query(ctx, openWantsForGroups, groups)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var items []Want
|
|
for rows.Next() {
|
|
var i Want
|
|
if err := rows.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,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const reclaimStaleImports = `-- name: ReclaimStaleImports :execrows
|
|
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 => $1::int)
|
|
`
|
|
|
|
// An import cut short (a restart mid-copy) is tried again.
|
|
func (q *Queries) ReclaimStaleImports(ctx context.Context, minutes int32) (int64, error) {
|
|
result, err := q.db.Exec(ctx, reclaimStaleImports, minutes)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected(), nil
|
|
}
|
|
|
|
const recordImport = `-- name: RecordImport :one
|
|
UPDATE grabs
|
|
SET import_state = $1,
|
|
import_reason = $2,
|
|
import_detail = $3,
|
|
import_updated_at = now(),
|
|
import_forced = false,
|
|
imported_at = CASE WHEN $1::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 = $1::text = 'imported',
|
|
imported_files = $4,
|
|
destination = $5,
|
|
ended_at = CASE WHEN $6::text <> '' THEN now() ELSE ended_at END,
|
|
end_reason = $6,
|
|
end_detail = CASE WHEN $6::text <> '' THEN $2::text ELSE end_detail END
|
|
WHERE id = $7
|
|
RETURNING id, want_id, info_hash, name, key, indexer, source, client, save_path, quality, origin, forced, grabbed_by, grabbed_at, ended_at, end_reason, end_detail, progress, import_state, import_reason, import_detail, import_forced, import_attempts, import_updated_at, imported_at, imported_files, destination, seeding, ratio, seed_released_at
|
|
`
|
|
|
|
type RecordImportParams struct {
|
|
ImportState string
|
|
ImportReason string
|
|
ImportDetail []byte
|
|
ImportedFiles int32
|
|
Destination string
|
|
EndReason string
|
|
ID pgtype.UUID
|
|
}
|
|
|
|
// 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.
|
|
func (q *Queries) RecordImport(ctx context.Context, arg RecordImportParams) (Grab, error) {
|
|
row := q.db.QueryRow(ctx, recordImport,
|
|
arg.ImportState,
|
|
arg.ImportReason,
|
|
arg.ImportDetail,
|
|
arg.ImportedFiles,
|
|
arg.Destination,
|
|
arg.EndReason,
|
|
arg.ID,
|
|
)
|
|
var i Grab
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WantID,
|
|
&i.InfoHash,
|
|
&i.Name,
|
|
&i.Key,
|
|
&i.Indexer,
|
|
&i.Source,
|
|
&i.Client,
|
|
&i.SavePath,
|
|
&i.Quality,
|
|
&i.Origin,
|
|
&i.Forced,
|
|
&i.GrabbedBy,
|
|
&i.GrabbedAt,
|
|
&i.EndedAt,
|
|
&i.EndReason,
|
|
&i.EndDetail,
|
|
&i.Progress,
|
|
&i.ImportState,
|
|
&i.ImportReason,
|
|
&i.ImportDetail,
|
|
&i.ImportForced,
|
|
&i.ImportAttempts,
|
|
&i.ImportUpdatedAt,
|
|
&i.ImportedAt,
|
|
&i.ImportedFiles,
|
|
&i.Destination,
|
|
&i.Seeding,
|
|
&i.Ratio,
|
|
&i.SeedReleasedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const rehomeGrab = `-- name: RehomeGrab :one
|
|
UPDATE grabs g
|
|
SET want_id = $1
|
|
WHERE g.id = $2 AND g.ended_at IS NULL
|
|
AND NOT EXISTS (SELECT 1 FROM grabs o WHERE o.want_id = $1 AND o.ended_at IS NULL)
|
|
RETURNING id, want_id, info_hash, name, key, indexer, source, client, save_path, quality, origin, forced, grabbed_by, grabbed_at, ended_at, end_reason, end_detail, progress, import_state, import_reason, import_detail, import_forced, import_attempts, import_updated_at, imported_at, imported_files, destination, seeding, ratio, seed_released_at
|
|
`
|
|
|
|
type RehomeGrabParams struct {
|
|
WantID pgtype.UUID
|
|
ID pgtype.UUID
|
|
}
|
|
|
|
// A download that turned out to be another wanted album is that want's grab,
|
|
// when the want has none running.
|
|
func (q *Queries) RehomeGrab(ctx context.Context, arg RehomeGrabParams) (Grab, error) {
|
|
row := q.db.QueryRow(ctx, rehomeGrab, arg.WantID, arg.ID)
|
|
var i Grab
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WantID,
|
|
&i.InfoHash,
|
|
&i.Name,
|
|
&i.Key,
|
|
&i.Indexer,
|
|
&i.Source,
|
|
&i.Client,
|
|
&i.SavePath,
|
|
&i.Quality,
|
|
&i.Origin,
|
|
&i.Forced,
|
|
&i.GrabbedBy,
|
|
&i.GrabbedAt,
|
|
&i.EndedAt,
|
|
&i.EndReason,
|
|
&i.EndDetail,
|
|
&i.Progress,
|
|
&i.ImportState,
|
|
&i.ImportReason,
|
|
&i.ImportDetail,
|
|
&i.ImportForced,
|
|
&i.ImportAttempts,
|
|
&i.ImportUpdatedAt,
|
|
&i.ImportedAt,
|
|
&i.ImportedFiles,
|
|
&i.Destination,
|
|
&i.Seeding,
|
|
&i.Ratio,
|
|
&i.SeedReleasedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const retryGrabImport = `-- 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 id, want_id, info_hash, name, key, indexer, source, client, save_path, quality, origin, forced, grabbed_by, grabbed_at, ended_at, end_reason, end_detail, progress, import_state, import_reason, import_detail, import_forced, import_attempts, import_updated_at, imported_at, imported_files, destination, seeding, ratio, seed_released_at
|
|
`
|
|
|
|
func (q *Queries) RetryGrabImport(ctx context.Context, id pgtype.UUID) (Grab, error) {
|
|
row := q.db.QueryRow(ctx, retryGrabImport, id)
|
|
var i Grab
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.WantID,
|
|
&i.InfoHash,
|
|
&i.Name,
|
|
&i.Key,
|
|
&i.Indexer,
|
|
&i.Source,
|
|
&i.Client,
|
|
&i.SavePath,
|
|
&i.Quality,
|
|
&i.Origin,
|
|
&i.Forced,
|
|
&i.GrabbedBy,
|
|
&i.GrabbedAt,
|
|
&i.EndedAt,
|
|
&i.EndReason,
|
|
&i.EndDetail,
|
|
&i.Progress,
|
|
&i.ImportState,
|
|
&i.ImportReason,
|
|
&i.ImportDetail,
|
|
&i.ImportForced,
|
|
&i.ImportAttempts,
|
|
&i.ImportUpdatedAt,
|
|
&i.ImportedAt,
|
|
&i.ImportedFiles,
|
|
&i.Destination,
|
|
&i.Seeding,
|
|
&i.Ratio,
|
|
&i.SeedReleasedAt,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const searchWantAgain = `-- name: SearchWantAgain :exec
|
|
UPDATE wants SET searched_at = NULL, search_outcome = '' WHERE id = $1
|
|
`
|
|
|
|
// Refused: search again on the next pass.
|
|
func (q *Queries) SearchWantAgain(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, searchWantAgain, id)
|
|
return err
|
|
}
|
|
|
|
const setGrabProgress = `-- name: SetGrabProgress :exec
|
|
UPDATE grabs
|
|
SET progress = $1,
|
|
import_state = $2,
|
|
import_reason = $3,
|
|
import_updated_at = now()
|
|
WHERE id = $4 AND ended_at IS NULL
|
|
AND import_state IN ('', 'downloading', 'waiting')
|
|
`
|
|
|
|
type SetGrabProgressParams struct {
|
|
Progress float32
|
|
ImportState string
|
|
ImportReason string
|
|
ID pgtype.UUID
|
|
}
|
|
|
|
// 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.
|
|
func (q *Queries) SetGrabProgress(ctx context.Context, arg SetGrabProgressParams) error {
|
|
_, err := q.db.Exec(ctx, setGrabProgress,
|
|
arg.Progress,
|
|
arg.ImportState,
|
|
arg.ImportReason,
|
|
arg.ID,
|
|
)
|
|
return err
|
|
}
|