feat: data lifecycle rewrite, download clients, wanted list, and central catalog index
Build & publish Arch package / arch-package (push) Successful in 2m12s
Search index maintenance / maintain-index (push) Successful in 2h22m28s

Ships the fresh-start schema cleanup: rebuilt explore catalog index
pipeline (dump import, artifact fetch/build, incremental listen-count
refresh), a new download subsystem (Lidarr/Prowlarr/qBittorrent/SABnzbd/
slskd/yt-dlp providers, staging, reconciliation, wanted list), and the
supporting schema/query/store changes across backend and frontend.

Also includes two smaller follow-ups: bump the central index's
rebuild-after cadence from 90 to 180 days, and remove the Explore
"library only" online/offline toggle entirely (frontend-only, no
backend counterpart) rather than carry unused UI/state.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y2Agd9af5hE7qzti2ackiS
This commit is contained in:
2026-08-06 17:12:01 -04:00
co-authored by Claude Sonnet 5
parent d0d86f85d5
commit e190fd75b9
165 changed files with 31088 additions and 5192 deletions
+64 -17
View File
@@ -35,7 +35,7 @@ func (q *Queries) CountAudioFilesByLibrary(ctx context.Context, libraryID int64)
const createAudioFile = `-- name: CreateAudioFile :one
INSERT INTO audio_files (file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
RETURNING id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id, play_count, last_played, tag_status, group_key
RETURNING id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id, play_count, last_played, tag_status, group_key, modified_at
`
type CreateAudioFileParams struct {
@@ -84,6 +84,7 @@ func (q *Queries) CreateAudioFile(ctx context.Context, arg CreateAudioFileParams
&i.LastPlayed,
&i.TagStatus,
&i.GroupKey,
&i.ModifiedAt,
)
return i, err
}
@@ -92,9 +93,9 @@ const createAudioFileWithGroupKey = `-- name: CreateAudioFileWithGroupKey :one
INSERT INTO audio_files (
file_path, length_milliseconds, file_type_id, recording_id,
sample_rate, bit_depth, channels, bitrate, file_size, basename,
library_id, group_key, tag_status
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
RETURNING id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id, play_count, last_played, tag_status, group_key
library_id, group_key, tag_status, modified_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
RETURNING id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id, play_count, last_played, tag_status, group_key, modified_at
`
type CreateAudioFileWithGroupKeyParams struct {
@@ -111,6 +112,7 @@ type CreateAudioFileWithGroupKeyParams struct {
LibraryID int64
GroupKey string
TagStatus string
ModifiedAt int64
}
func (q *Queries) CreateAudioFileWithGroupKey(ctx context.Context, arg CreateAudioFileWithGroupKeyParams) (AudioFile, error) {
@@ -128,6 +130,7 @@ func (q *Queries) CreateAudioFileWithGroupKey(ctx context.Context, arg CreateAud
arg.LibraryID,
arg.GroupKey,
arg.TagStatus,
arg.ModifiedAt,
)
var i AudioFile
err := row.Scan(
@@ -147,6 +150,7 @@ func (q *Queries) CreateAudioFileWithGroupKey(ctx context.Context, arg CreateAud
&i.LastPlayed,
&i.TagStatus,
&i.GroupKey,
&i.ModifiedAt,
)
return i, err
}
@@ -203,7 +207,7 @@ func (q *Queries) GetAllAudioFilePaths(ctx context.Context) ([]GetAllAudioFilePa
}
const getAllAudioFiles = `-- name: GetAllAudioFiles :many
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id, play_count, last_played, tag_status, group_key FROM audio_files
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id, play_count, last_played, tag_status, group_key, modified_at FROM audio_files
`
func (q *Queries) GetAllAudioFiles(ctx context.Context) ([]AudioFile, error) {
@@ -232,6 +236,7 @@ func (q *Queries) GetAllAudioFiles(ctx context.Context) ([]AudioFile, error) {
&i.LastPlayed,
&i.TagStatus,
&i.GroupKey,
&i.ModifiedAt,
); err != nil {
return nil, err
}
@@ -527,7 +532,7 @@ func (q *Queries) GetAllTracksWithFullMetadataByLibrary(ctx context.Context, lib
}
const getAudioFile = `-- name: GetAudioFile :one
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id, play_count, last_played, tag_status, group_key FROM audio_files
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id, play_count, last_played, tag_status, group_key, modified_at FROM audio_files
WHERE id = ? LIMIT 1
`
@@ -551,12 +556,13 @@ func (q *Queries) GetAudioFile(ctx context.Context, id int64) (AudioFile, error)
&i.LastPlayed,
&i.TagStatus,
&i.GroupKey,
&i.ModifiedAt,
)
return i, err
}
const getAudioFileByPath = `-- name: GetAudioFileByPath :one
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id, play_count, last_played, tag_status, group_key FROM audio_files
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id, play_count, last_played, tag_status, group_key, modified_at FROM audio_files
WHERE file_path = ? LIMIT 1
`
@@ -580,6 +586,7 @@ func (q *Queries) GetAudioFileByPath(ctx context.Context, filePath string) (Audi
&i.LastPlayed,
&i.TagStatus,
&i.GroupKey,
&i.ModifiedAt,
)
return i, err
}
@@ -597,7 +604,7 @@ func (q *Queries) GetAudioFileGroupKey(ctx context.Context, id int64) (string, e
}
const getAudioFilesByLibrary = `-- name: GetAudioFilesByLibrary :many
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id, play_count, last_played, tag_status, group_key FROM audio_files WHERE library_id = ?
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id, play_count, last_played, tag_status, group_key, modified_at FROM audio_files WHERE library_id = ?
`
func (q *Queries) GetAudioFilesByLibrary(ctx context.Context, libraryID int64) ([]AudioFile, error) {
@@ -626,6 +633,7 @@ func (q *Queries) GetAudioFilesByLibrary(ctx context.Context, libraryID int64) (
&i.LastPlayed,
&i.TagStatus,
&i.GroupKey,
&i.ModifiedAt,
); err != nil {
return nil, err
}
@@ -854,7 +862,7 @@ func (q *Queries) GetAudioFilesByReleaseGroupByLibrary(ctx context.Context, arg
}
const getAudioFilesNeedingMetadata = `-- name: GetAudioFilesNeedingMetadata :many
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id, play_count, last_played, tag_status, group_key FROM audio_files
SELECT id, file_path, length_milliseconds, file_type_id, recording_id, sample_rate, bit_depth, channels, bitrate, file_size, basename, library_id, play_count, last_played, tag_status, group_key, modified_at FROM audio_files
WHERE recording_id = 0
`
@@ -884,6 +892,7 @@ func (q *Queries) GetAudioFilesNeedingMetadata(ctx context.Context) ([]AudioFile
&i.LastPlayed,
&i.TagStatus,
&i.GroupKey,
&i.ModifiedAt,
); err != nil {
return nil, err
}
@@ -898,6 +907,20 @@ func (q *Queries) GetAudioFilesNeedingMetadata(ctx context.Context) ([]AudioFile
return items, nil
}
const getLibraryMaxModifiedAt = `-- name: GetLibraryMaxModifiedAt :one
SELECT CAST(COALESCE(MAX(modified_at), 0) AS INTEGER) FROM audio_files
WHERE library_id = ?
`
// Newest recorded mtime in a library, for the startup soft scan. 0 when
// the library is empty or no row has a baseline yet.
func (q *Queries) GetLibraryMaxModifiedAt(ctx context.Context, libraryID int64) (int64, error) {
row := q.db.QueryRowContext(ctx, getLibraryMaxModifiedAt, libraryID)
var column_1 int64
err := row.Scan(&column_1)
return column_1, err
}
const getRandomAudioFilePath = `-- name: GetRandomAudioFilePath :one
SELECT file_path FROM audio_files
ORDER BY RANDOM()
@@ -1139,18 +1162,20 @@ func (q *Queries) UpdateAudioFile(ctx context.Context, arg UpdateAudioFileParams
const updateAudioFileRecording = `-- name: UpdateAudioFileRecording :exec
UPDATE audio_files
SET recording_id = ?, sample_rate = ?, bit_depth = ?, channels = ?, bitrate = ?, file_size = ?
SET recording_id = ?, sample_rate = ?, bit_depth = ?, channels = ?, bitrate = ?, file_size = ?, length_milliseconds = ?, modified_at = ?
WHERE id = ?
`
type UpdateAudioFileRecordingParams struct {
RecordingID int64
SampleRate int64
BitDepth int64
Channels int64
Bitrate int64
FileSize int64
ID int64
RecordingID int64
SampleRate int64
BitDepth int64
Channels int64
Bitrate int64
FileSize int64
LengthMilliseconds int64
ModifiedAt int64
ID int64
}
func (q *Queries) UpdateAudioFileRecording(ctx context.Context, arg UpdateAudioFileRecordingParams) error {
@@ -1161,7 +1186,29 @@ func (q *Queries) UpdateAudioFileRecording(ctx context.Context, arg UpdateAudioF
arg.Channels,
arg.Bitrate,
arg.FileSize,
arg.LengthMilliseconds,
arg.ModifiedAt,
arg.ID,
)
return err
}
const updateAudioFileStat = `-- name: UpdateAudioFileStat :exec
UPDATE audio_files
SET modified_at = ?, file_size = ?
WHERE id = ?
`
type UpdateAudioFileStatParams struct {
ModifiedAt int64
FileSize int64
ID int64
}
// Records the on-disk mtime/size without re-reading tags. Used to
// backfill the staleness baseline for files the scan skipped, and to
// re-baseline after YellowJacket's own tag writer rewrites a file.
func (q *Queries) UpdateAudioFileStat(ctx context.Context, arg UpdateAudioFileStatParams) error {
_, err := q.db.ExecContext(ctx, updateAudioFileStat, arg.ModifiedAt, arg.FileSize, arg.ID)
return err
}
@@ -0,0 +1,959 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: download.sql
package sqlcgen
import (
"context"
"database/sql"
)
const createDownloadItem = `-- name: CreateDownloadItem :exec
INSERT INTO download_items (
id, request_id, provider_id, transport_id, external_id,
candidate, state, staging_dir, bytes_total
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`
type CreateDownloadItemParams struct {
ID string
RequestID string
ProviderID int64
TransportID sql.NullInt64
ExternalID string
Candidate string
State string
StagingDir string
BytesTotal int64
}
func (q *Queries) CreateDownloadItem(ctx context.Context, arg CreateDownloadItemParams) error {
_, err := q.db.ExecContext(ctx, createDownloadItem,
arg.ID,
arg.RequestID,
arg.ProviderID,
arg.TransportID,
arg.ExternalID,
arg.Candidate,
arg.State,
arg.StagingDir,
arg.BytesTotal,
)
return err
}
const createDownloadProvider = `-- name: CreateDownloadProvider :one
INSERT INTO download_providers (kind, name, enabled, priority, settings)
VALUES (?, ?, ?, ?, ?)
RETURNING id
`
type CreateDownloadProviderParams struct {
Kind string
Name string
Enabled int64
Priority int64
Settings string
}
func (q *Queries) CreateDownloadProvider(ctx context.Context, arg CreateDownloadProviderParams) (int64, error) {
row := q.db.QueryRowContext(ctx, createDownloadProvider,
arg.Kind,
arg.Name,
arg.Enabled,
arg.Priority,
arg.Settings,
)
var id int64
err := row.Scan(&id)
return id, err
}
const createDownloadRequest = `-- name: CreateDownloadRequest :exec
INSERT INTO download_requests (
id, library_id, source, want_id, release_mbid, release_group_mbid,
recording_mbid, artist, album, query, expected, state
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`
type CreateDownloadRequestParams struct {
ID string
LibraryID int64
Source string
WantID sql.NullInt64
ReleaseMbid sql.NullString
ReleaseGroupMbid sql.NullString
RecordingMbid sql.NullString
Artist string
Album string
Query string
Expected string
State string
}
func (q *Queries) CreateDownloadRequest(ctx context.Context, arg CreateDownloadRequestParams) error {
_, err := q.db.ExecContext(ctx, createDownloadRequest,
arg.ID,
arg.LibraryID,
arg.Source,
arg.WantID,
arg.ReleaseMbid,
arg.ReleaseGroupMbid,
arg.RecordingMbid,
arg.Artist,
arg.Album,
arg.Query,
arg.Expected,
arg.State,
)
return err
}
const deleteDownloadProvider = `-- name: DeleteDownloadProvider :exec
DELETE FROM download_providers
WHERE id = ?
`
func (q *Queries) DeleteDownloadProvider(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, deleteDownloadProvider, id)
return err
}
const deleteDownloadRequest = `-- name: DeleteDownloadRequest :exec
DELETE FROM download_requests
WHERE id = ?
`
func (q *Queries) DeleteDownloadRequest(ctx context.Context, id string) error {
_, err := q.db.ExecContext(ctx, deleteDownloadRequest, id)
return err
}
const deleteDownloadWant = `-- name: DeleteDownloadWant :exec
DELETE FROM download_wants WHERE id = ?
`
func (q *Queries) DeleteDownloadWant(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, deleteDownloadWant, id)
return err
}
const deleteFinishedDownloadRequests = `-- name: DeleteFinishedDownloadRequests :exec
DELETE FROM download_requests
WHERE state IN ('complete', 'cancelled', 'failed')
`
func (q *Queries) DeleteFinishedDownloadRequests(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, deleteFinishedDownloadRequests)
return err
}
const deleteSatisfiedDownloadWants = `-- name: DeleteSatisfiedDownloadWants :exec
DELETE FROM download_wants WHERE state = 'satisfied'
`
func (q *Queries) DeleteSatisfiedDownloadWants(ctx context.Context) error {
_, err := q.db.ExecContext(ctx, deleteSatisfiedDownloadWants)
return err
}
const getDownloadItem = `-- name: GetDownloadItem :one
SELECT id, request_id, provider_id, transport_id, external_id, candidate,
state, staging_dir, bytes_done, bytes_total, imported_paths,
error, created_at, updated_at
FROM download_items
WHERE id = ?
`
func (q *Queries) GetDownloadItem(ctx context.Context, id string) (DownloadItem, error) {
row := q.db.QueryRowContext(ctx, getDownloadItem, id)
var i DownloadItem
err := row.Scan(
&i.ID,
&i.RequestID,
&i.ProviderID,
&i.TransportID,
&i.ExternalID,
&i.Candidate,
&i.State,
&i.StagingDir,
&i.BytesDone,
&i.BytesTotal,
&i.ImportedPaths,
&i.Error,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getDownloadProvider = `-- name: GetDownloadProvider :one
SELECT id, kind, name, enabled, priority, settings, created_at
FROM download_providers
WHERE id = ?
`
func (q *Queries) GetDownloadProvider(ctx context.Context, id int64) (DownloadProvider, error) {
row := q.db.QueryRowContext(ctx, getDownloadProvider, id)
var i DownloadProvider
err := row.Scan(
&i.ID,
&i.Kind,
&i.Name,
&i.Enabled,
&i.Priority,
&i.Settings,
&i.CreatedAt,
)
return i, err
}
const getDownloadRequest = `-- name: GetDownloadRequest :one
SELECT id, library_id, source, want_id, release_mbid, release_group_mbid,
recording_mbid, artist, album, query, expected, state, error,
created_at, updated_at
FROM download_requests
WHERE id = ?
`
func (q *Queries) GetDownloadRequest(ctx context.Context, id string) (DownloadRequest, error) {
row := q.db.QueryRowContext(ctx, getDownloadRequest, id)
var i DownloadRequest
err := row.Scan(
&i.ID,
&i.LibraryID,
&i.Source,
&i.WantID,
&i.ReleaseMbid,
&i.ReleaseGroupMbid,
&i.RecordingMbid,
&i.Artist,
&i.Album,
&i.Query,
&i.Expected,
&i.State,
&i.Error,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getDownloadWant = `-- name: GetDownloadWant :one
SELECT id, mbid, entity, library_id, artist, title, scope, secondary, state, parent_id, attempts, last_error, last_tried_at, next_try_at, external_ids, created_at, updated_at FROM download_wants WHERE id = ?
`
func (q *Queries) GetDownloadWant(ctx context.Context, id int64) (DownloadWant, error) {
row := q.db.QueryRowContext(ctx, getDownloadWant, id)
var i DownloadWant
err := row.Scan(
&i.ID,
&i.Mbid,
&i.Entity,
&i.LibraryID,
&i.Artist,
&i.Title,
&i.Scope,
&i.Secondary,
&i.State,
&i.ParentID,
&i.Attempts,
&i.LastError,
&i.LastTriedAt,
&i.NextTryAt,
&i.ExternalIds,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getDownloadWantByMBID = `-- name: GetDownloadWantByMBID :one
SELECT id, mbid, entity, library_id, artist, title, scope, secondary, state, parent_id, attempts, last_error, last_tried_at, next_try_at, external_ids, created_at, updated_at FROM download_wants WHERE mbid = ? AND library_id = ?
`
type GetDownloadWantByMBIDParams struct {
Mbid string
LibraryID int64
}
func (q *Queries) GetDownloadWantByMBID(ctx context.Context, arg GetDownloadWantByMBIDParams) (DownloadWant, error) {
row := q.db.QueryRowContext(ctx, getDownloadWantByMBID, arg.Mbid, arg.LibraryID)
var i DownloadWant
err := row.Scan(
&i.ID,
&i.Mbid,
&i.Entity,
&i.LibraryID,
&i.Artist,
&i.Title,
&i.Scope,
&i.Secondary,
&i.State,
&i.ParentID,
&i.Attempts,
&i.LastError,
&i.LastTriedAt,
&i.NextTryAt,
&i.ExternalIds,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const listChildDownloadWants = `-- name: ListChildDownloadWants :many
SELECT id, mbid, entity, library_id, artist, title, scope, secondary, state, parent_id, attempts, last_error, last_tried_at, next_try_at, external_ids, created_at, updated_at FROM download_wants WHERE parent_id = ? ORDER BY id
`
func (q *Queries) ListChildDownloadWants(ctx context.Context, parentID sql.NullInt64) ([]DownloadWant, error) {
rows, err := q.db.QueryContext(ctx, listChildDownloadWants, parentID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []DownloadWant
for rows.Next() {
var i DownloadWant
if err := rows.Scan(
&i.ID,
&i.Mbid,
&i.Entity,
&i.LibraryID,
&i.Artist,
&i.Title,
&i.Scope,
&i.Secondary,
&i.State,
&i.ParentID,
&i.Attempts,
&i.LastError,
&i.LastTriedAt,
&i.NextTryAt,
&i.ExternalIds,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listDownloadItemsForRequest = `-- name: ListDownloadItemsForRequest :many
SELECT id, request_id, provider_id, transport_id, external_id, candidate,
state, staging_dir, bytes_done, bytes_total, imported_paths,
error, created_at, updated_at
FROM download_items
WHERE request_id = ?
ORDER BY created_at
`
func (q *Queries) ListDownloadItemsForRequest(ctx context.Context, requestID string) ([]DownloadItem, error) {
rows, err := q.db.QueryContext(ctx, listDownloadItemsForRequest, requestID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []DownloadItem
for rows.Next() {
var i DownloadItem
if err := rows.Scan(
&i.ID,
&i.RequestID,
&i.ProviderID,
&i.TransportID,
&i.ExternalID,
&i.Candidate,
&i.State,
&i.StagingDir,
&i.BytesDone,
&i.BytesTotal,
&i.ImportedPaths,
&i.Error,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listDownloadProviders = `-- name: ListDownloadProviders :many
SELECT id, kind, name, enabled, priority, settings, created_at
FROM download_providers
ORDER BY priority DESC, name
`
func (q *Queries) ListDownloadProviders(ctx context.Context) ([]DownloadProvider, error) {
rows, err := q.db.QueryContext(ctx, listDownloadProviders)
if err != nil {
return nil, err
}
defer rows.Close()
var items []DownloadProvider
for rows.Next() {
var i DownloadProvider
if err := rows.Scan(
&i.ID,
&i.Kind,
&i.Name,
&i.Enabled,
&i.Priority,
&i.Settings,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listDownloadRequests = `-- name: ListDownloadRequests :many
SELECT id, library_id, source, want_id, release_mbid, release_group_mbid,
recording_mbid, artist, album, query, expected, state, error,
created_at, updated_at
FROM download_requests
ORDER BY created_at DESC
LIMIT ?
`
func (q *Queries) ListDownloadRequests(ctx context.Context, limit int64) ([]DownloadRequest, error) {
rows, err := q.db.QueryContext(ctx, listDownloadRequests, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []DownloadRequest
for rows.Next() {
var i DownloadRequest
if err := rows.Scan(
&i.ID,
&i.LibraryID,
&i.Source,
&i.WantID,
&i.ReleaseMbid,
&i.ReleaseGroupMbid,
&i.RecordingMbid,
&i.Artist,
&i.Album,
&i.Query,
&i.Expected,
&i.State,
&i.Error,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listDownloadWants = `-- name: ListDownloadWants :many
SELECT id, mbid, entity, library_id, artist, title, scope, secondary, state, parent_id, attempts, last_error, last_tried_at, next_try_at, external_ids, created_at, updated_at FROM download_wants
ORDER BY
CASE state WHEN 'wanted' THEN 0 WHEN 'paused' THEN 1 ELSE 2 END,
artist, title
`
func (q *Queries) ListDownloadWants(ctx context.Context) ([]DownloadWant, error) {
rows, err := q.db.QueryContext(ctx, listDownloadWants)
if err != nil {
return nil, err
}
defer rows.Close()
var items []DownloadWant
for rows.Next() {
var i DownloadWant
if err := rows.Scan(
&i.ID,
&i.Mbid,
&i.Entity,
&i.LibraryID,
&i.Artist,
&i.Title,
&i.Scope,
&i.Secondary,
&i.State,
&i.ParentID,
&i.Attempts,
&i.LastError,
&i.LastTriedAt,
&i.NextTryAt,
&i.ExternalIds,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listDownloadWantsByEntity = `-- name: ListDownloadWantsByEntity :many
SELECT id, mbid, entity, library_id, artist, title, scope, secondary, state, parent_id, attempts, last_error, last_tried_at, next_try_at, external_ids, created_at, updated_at FROM download_wants
WHERE entity = ? AND state = ?
ORDER BY id
`
type ListDownloadWantsByEntityParams struct {
Entity string
State string
}
func (q *Queries) ListDownloadWantsByEntity(ctx context.Context, arg ListDownloadWantsByEntityParams) ([]DownloadWant, error) {
rows, err := q.db.QueryContext(ctx, listDownloadWantsByEntity, arg.Entity, arg.State)
if err != nil {
return nil, err
}
defer rows.Close()
var items []DownloadWant
for rows.Next() {
var i DownloadWant
if err := rows.Scan(
&i.ID,
&i.Mbid,
&i.Entity,
&i.LibraryID,
&i.Artist,
&i.Title,
&i.Scope,
&i.Secondary,
&i.State,
&i.ParentID,
&i.Attempts,
&i.LastError,
&i.LastTriedAt,
&i.NextTryAt,
&i.ExternalIds,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listDueDownloadWants = `-- name: ListDueDownloadWants :many
SELECT id, mbid, entity, library_id, artist, title, scope, secondary, state, parent_id, attempts, last_error, last_tried_at, next_try_at, external_ids, created_at, updated_at FROM download_wants
WHERE state = 'wanted'
AND entity <> 'artist'
AND (next_try_at IS NULL OR next_try_at <= CURRENT_TIMESTAMP)
ORDER BY attempts, created_at
LIMIT ?
`
// Everything the reconciler should act on this pass: wanted, not an
// artist subscription (those expand rather than download), and either
// never tried or past its backoff.
func (q *Queries) ListDueDownloadWants(ctx context.Context, limit int64) ([]DownloadWant, error) {
rows, err := q.db.QueryContext(ctx, listDueDownloadWants, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var items []DownloadWant
for rows.Next() {
var i DownloadWant
if err := rows.Scan(
&i.ID,
&i.Mbid,
&i.Entity,
&i.LibraryID,
&i.Artist,
&i.Title,
&i.Scope,
&i.Secondary,
&i.State,
&i.ParentID,
&i.Attempts,
&i.LastError,
&i.LastTriedAt,
&i.NextTryAt,
&i.ExternalIds,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listLiveDownloadItems = `-- name: ListLiveDownloadItems :many
SELECT id, request_id, provider_id, transport_id, external_id, candidate,
state, staging_dir, bytes_done, bytes_total, imported_paths,
error, created_at, updated_at
FROM download_items
WHERE state NOT IN ('complete', 'cancelled', 'failed')
ORDER BY created_at
`
func (q *Queries) ListLiveDownloadItems(ctx context.Context) ([]DownloadItem, error) {
rows, err := q.db.QueryContext(ctx, listLiveDownloadItems)
if err != nil {
return nil, err
}
defer rows.Close()
var items []DownloadItem
for rows.Next() {
var i DownloadItem
if err := rows.Scan(
&i.ID,
&i.RequestID,
&i.ProviderID,
&i.TransportID,
&i.ExternalID,
&i.Candidate,
&i.State,
&i.StagingDir,
&i.BytesDone,
&i.BytesTotal,
&i.ImportedPaths,
&i.Error,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listLiveDownloadRequests = `-- name: ListLiveDownloadRequests :many
SELECT id, library_id, source, want_id, release_mbid, release_group_mbid,
recording_mbid, artist, album, query, expected, state, error,
created_at, updated_at
FROM download_requests
WHERE state NOT IN ('complete', 'cancelled', 'failed')
ORDER BY created_at
`
func (q *Queries) ListLiveDownloadRequests(ctx context.Context) ([]DownloadRequest, error) {
rows, err := q.db.QueryContext(ctx, listLiveDownloadRequests)
if err != nil {
return nil, err
}
defer rows.Close()
var items []DownloadRequest
for rows.Next() {
var i DownloadRequest
if err := rows.Scan(
&i.ID,
&i.LibraryID,
&i.Source,
&i.WantID,
&i.ReleaseMbid,
&i.ReleaseGroupMbid,
&i.RecordingMbid,
&i.Artist,
&i.Album,
&i.Query,
&i.Expected,
&i.State,
&i.Error,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const recordDownloadWantAttempt = `-- name: RecordDownloadWantAttempt :exec
UPDATE download_wants
SET attempts = attempts + 1,
last_error = ?,
last_tried_at = CURRENT_TIMESTAMP,
next_try_at = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`
type RecordDownloadWantAttemptParams struct {
LastError string
NextTryAt sql.NullTime
ID int64
}
func (q *Queries) RecordDownloadWantAttempt(ctx context.Context, arg RecordDownloadWantAttemptParams) error {
_, err := q.db.ExecContext(ctx, recordDownloadWantAttempt, arg.LastError, arg.NextTryAt, arg.ID)
return err
}
const satisfyDownloadWant = `-- name: SatisfyDownloadWant :exec
UPDATE download_wants
SET state = 'satisfied', last_error = '', next_try_at = NULL,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`
func (q *Queries) SatisfyDownloadWant(ctx context.Context, id int64) error {
_, err := q.db.ExecContext(ctx, satisfyDownloadWant, id)
return err
}
const setDownloadItemExternalID = `-- name: SetDownloadItemExternalID :exec
UPDATE download_items
SET external_id = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`
type SetDownloadItemExternalIDParams struct {
ExternalID string
ID string
}
func (q *Queries) SetDownloadItemExternalID(ctx context.Context, arg SetDownloadItemExternalIDParams) error {
_, err := q.db.ExecContext(ctx, setDownloadItemExternalID, arg.ExternalID, arg.ID)
return err
}
const setDownloadItemImported = `-- name: SetDownloadItemImported :exec
UPDATE download_items
SET imported_paths = ?, state = 'complete', error = '',
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`
type SetDownloadItemImportedParams struct {
ImportedPaths string
ID string
}
func (q *Queries) SetDownloadItemImported(ctx context.Context, arg SetDownloadItemImportedParams) error {
_, err := q.db.ExecContext(ctx, setDownloadItemImported, arg.ImportedPaths, arg.ID)
return err
}
const setDownloadItemProgress = `-- name: SetDownloadItemProgress :exec
UPDATE download_items
SET bytes_done = ?, bytes_total = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`
type SetDownloadItemProgressParams struct {
BytesDone int64
BytesTotal int64
ID string
}
func (q *Queries) SetDownloadItemProgress(ctx context.Context, arg SetDownloadItemProgressParams) error {
_, err := q.db.ExecContext(ctx, setDownloadItemProgress, arg.BytesDone, arg.BytesTotal, arg.ID)
return err
}
const setDownloadItemState = `-- name: SetDownloadItemState :exec
UPDATE download_items
SET state = ?, error = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`
type SetDownloadItemStateParams struct {
State string
Error string
ID string
}
func (q *Queries) SetDownloadItemState(ctx context.Context, arg SetDownloadItemStateParams) error {
_, err := q.db.ExecContext(ctx, setDownloadItemState, arg.State, arg.Error, arg.ID)
return err
}
const setDownloadRequestState = `-- name: SetDownloadRequestState :exec
UPDATE download_requests
SET state = ?, error = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`
type SetDownloadRequestStateParams struct {
State string
Error string
ID string
}
func (q *Queries) SetDownloadRequestState(ctx context.Context, arg SetDownloadRequestStateParams) error {
_, err := q.db.ExecContext(ctx, setDownloadRequestState, arg.State, arg.Error, arg.ID)
return err
}
const setDownloadWantExternalIDs = `-- name: SetDownloadWantExternalIDs :exec
UPDATE download_wants
SET external_ids = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`
type SetDownloadWantExternalIDsParams struct {
ExternalIds string
ID int64
}
func (q *Queries) SetDownloadWantExternalIDs(ctx context.Context, arg SetDownloadWantExternalIDsParams) error {
_, err := q.db.ExecContext(ctx, setDownloadWantExternalIDs, arg.ExternalIds, arg.ID)
return err
}
const setDownloadWantState = `-- name: SetDownloadWantState :exec
UPDATE download_wants
SET state = ?, last_error = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`
type SetDownloadWantStateParams struct {
State string
LastError string
ID int64
}
func (q *Queries) SetDownloadWantState(ctx context.Context, arg SetDownloadWantStateParams) error {
_, err := q.db.ExecContext(ctx, setDownloadWantState, arg.State, arg.LastError, arg.ID)
return err
}
const updateDownloadProvider = `-- name: UpdateDownloadProvider :exec
UPDATE download_providers
SET name = ?, enabled = ?, priority = ?, settings = ?
WHERE id = ?
`
type UpdateDownloadProviderParams struct {
Name string
Enabled int64
Priority int64
Settings string
ID int64
}
func (q *Queries) UpdateDownloadProvider(ctx context.Context, arg UpdateDownloadProviderParams) error {
_, err := q.db.ExecContext(ctx, updateDownloadProvider,
arg.Name,
arg.Enabled,
arg.Priority,
arg.Settings,
arg.ID,
)
return err
}
const upsertDownloadWant = `-- name: UpsertDownloadWant :one
INSERT INTO download_wants (
mbid, entity, library_id, artist, title, scope, secondary,
parent_id, next_try_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT(mbid, library_id) DO UPDATE SET
artist = CASE WHEN excluded.artist <> '' THEN excluded.artist
ELSE download_wants.artist END,
title = CASE WHEN excluded.title <> '' THEN excluded.title
ELSE download_wants.title END,
scope = excluded.scope,
secondary = excluded.secondary,
updated_at = CURRENT_TIMESTAMP
RETURNING id
`
type UpsertDownloadWantParams struct {
Mbid string
Entity string
LibraryID int64
Artist string
Title string
Scope string
Secondary int64
ParentID sql.NullInt64
}
// ---------------------------------------------------------------------
// Wants
// ---------------------------------------------------------------------
// Adding something already wanted is not an error and must not reset
// the retry clock, so the conflict path only refreshes display text and
// un-pauses nothing. scope and secondary are updated because asking
// again with a wider scope is a real change of intent.
func (q *Queries) UpsertDownloadWant(ctx context.Context, arg UpsertDownloadWantParams) (int64, error) {
row := q.db.QueryRowContext(ctx, upsertDownloadWant,
arg.Mbid,
arg.Entity,
arg.LibraryID,
arg.Artist,
arg.Title,
arg.Scope,
arg.Secondary,
arg.ParentID,
)
var id int64
err := row.Scan(&id)
return id, err
}
+146 -1
View File
@@ -26,6 +26,20 @@ type ArtistCreditArtist struct {
CreditID int64
}
type ArtistImage struct {
ID int64
ArtistMbid string
Source string
SourceUrl string
FilePath string
IsPrimary int64
SortOrder int64
Width sql.NullInt64
Height sql.NullInt64
FileSize sql.NullInt64
CreatedAt time.Time
}
type ArtistMetadatum struct {
Mbid string
Source string
@@ -50,6 +64,7 @@ type AudioFile struct {
LastPlayed sql.NullTime
TagStatus string
GroupKey string
ModifiedAt int64
}
type CoverArt struct {
@@ -59,6 +74,116 @@ type CoverArt struct {
MimeType string
}
type DownloadItem struct {
ID string
RequestID string
ProviderID int64
TransportID sql.NullInt64
ExternalID string
Candidate string
State string
StagingDir string
BytesDone int64
BytesTotal int64
ImportedPaths string
Error string
CreatedAt time.Time
UpdatedAt time.Time
}
type DownloadProvider struct {
ID int64
Kind string
Name string
Enabled int64
Priority int64
Settings string
CreatedAt time.Time
}
type DownloadRequest struct {
ID string
LibraryID int64
Source string
WantID sql.NullInt64
ReleaseMbid sql.NullString
ReleaseGroupMbid sql.NullString
RecordingMbid sql.NullString
Artist string
Album string
Query string
Expected string
State string
Error string
CreatedAt time.Time
UpdatedAt time.Time
}
type DownloadWant struct {
ID int64
Mbid string
Entity string
LibraryID int64
Artist string
Title string
Scope string
Secondary int64
State string
ParentID sql.NullInt64
Attempts int64
LastError string
LastTriedAt sql.NullTime
NextTryAt sql.NullTime
ExternalIds string
CreatedAt time.Time
UpdatedAt time.Time
}
type ExploreChampionFt struct {
Title string
ArtistName string
Aliases string
}
type ExploreIndex struct {
ID int64
EntityType string
Mbid string
Title string
ArtistName string
ArtistMbid string
Aliases string
Popularity int64
ListenerCount int64
Duration int64
CaaReleaseMbid string
ReleaseName string
PrimaryType string
SecondaryTypes string
ReleaseDate string
ArtistType string
Country string
Disambiguation string
SortName string
InLibrary int64
IsSimilar int64
LocalArtistID sql.NullInt64
LocalReleaseGroupID sql.NullInt64
LocalRecordingID sql.NullInt64
DiscogFetched int64
}
type ExploreIndexFt struct {
Title string
ArtistName string
Aliases string
}
type ExploreIndexMetum struct {
Key string
Value string
}
type FileType struct {
ID int64
Extension string
@@ -176,10 +301,10 @@ type ReleaseGroup struct {
CoverArtID sql.NullInt64
AlbumArtistCreditID sql.NullInt64
Year sql.NullInt64
OriginalYear sql.NullInt64
TotalTracks sql.NullInt64
TotalDiscs sql.NullInt64
Mbid sql.NullString
OriginalYear sql.NullInt64
}
type ReleaseGroupRecording struct {
@@ -190,6 +315,19 @@ type ReleaseGroupRecording struct {
DiscNumber sql.NullInt64
}
type ReleaseToRg struct {
ReleaseMbid string
RgMbid string
}
type SearchClick struct {
Query string
EntityMbid string
EntityType string
ClickCount int64
LastClicked time.Time
}
type SearchIndex struct {
FilePath string
Title string
@@ -197,6 +335,13 @@ type SearchIndex struct {
Album string
}
type SimilarArtistMap struct {
SourceArtistMbid string
SimilarArtistMbid string
SimilarArtistName string
Score int64
}
type TaggingCandidate struct {
GroupKey string
Candidates string
@@ -23,7 +23,7 @@ func (q *Queries) CountReleaseGroupRecordings(ctx context.Context, releaseGroupI
const createReleaseGroup = `-- name: CreateReleaseGroup :one
INSERT INTO release_groups (name) VALUES (?)
RETURNING id, name, cover_art_id, album_artist_credit_id, year, original_year, total_tracks, total_discs, mbid
RETURNING id, name, cover_art_id, album_artist_credit_id, year, total_tracks, total_discs, mbid, original_year
`
func (q *Queries) CreateReleaseGroup(ctx context.Context, name string) (ReleaseGroup, error) {
@@ -35,10 +35,10 @@ func (q *Queries) CreateReleaseGroup(ctx context.Context, name string) (ReleaseG
&i.CoverArtID,
&i.AlbumArtistCreditID,
&i.Year,
&i.OriginalYear,
&i.TotalTracks,
&i.TotalDiscs,
&i.Mbid,
&i.OriginalYear,
)
return i, err
}
@@ -47,7 +47,7 @@ const createReleaseGroupFull = `-- name: CreateReleaseGroupFull :one
INSERT INTO release_groups (
name, cover_art_id, album_artist_credit_id, year, total_tracks, total_discs
) VALUES (?, ?, ?, ?, ?, ?)
RETURNING id, name, cover_art_id, album_artist_credit_id, year, original_year, total_tracks, total_discs, mbid
RETURNING id, name, cover_art_id, album_artist_credit_id, year, total_tracks, total_discs, mbid, original_year
`
type CreateReleaseGroupFullParams struct {
@@ -75,10 +75,10 @@ func (q *Queries) CreateReleaseGroupFull(ctx context.Context, arg CreateReleaseG
&i.CoverArtID,
&i.AlbumArtistCreditID,
&i.Year,
&i.OriginalYear,
&i.TotalTracks,
&i.TotalDiscs,
&i.Mbid,
&i.OriginalYear,
)
return i, err
}
@@ -432,7 +432,7 @@ func (q *Queries) GetAllAlbumsWithDetailsByLibrary(ctx context.Context, libraryI
}
const getAllReleaseGroups = `-- name: GetAllReleaseGroups :many
SELECT id, name, cover_art_id, album_artist_credit_id, year, original_year, total_tracks, total_discs, mbid FROM release_groups
SELECT id, name, cover_art_id, album_artist_credit_id, year, total_tracks, total_discs, mbid, original_year FROM release_groups
ORDER BY name
`
@@ -451,10 +451,10 @@ func (q *Queries) GetAllReleaseGroups(ctx context.Context) ([]ReleaseGroup, erro
&i.CoverArtID,
&i.AlbumArtistCreditID,
&i.Year,
&i.OriginalYear,
&i.TotalTracks,
&i.TotalDiscs,
&i.Mbid,
&i.OriginalYear,
); err != nil {
return nil, err
}
@@ -470,7 +470,7 @@ func (q *Queries) GetAllReleaseGroups(ctx context.Context) ([]ReleaseGroup, erro
}
const getReleaseGroup = `-- name: GetReleaseGroup :one
SELECT id, name, cover_art_id, album_artist_credit_id, year, original_year, total_tracks, total_discs, mbid FROM release_groups
SELECT id, name, cover_art_id, album_artist_credit_id, year, total_tracks, total_discs, mbid, original_year FROM release_groups
WHERE id = ? LIMIT 1
`
@@ -483,16 +483,16 @@ func (q *Queries) GetReleaseGroup(ctx context.Context, id int64) (ReleaseGroup,
&i.CoverArtID,
&i.AlbumArtistCreditID,
&i.Year,
&i.OriginalYear,
&i.TotalTracks,
&i.TotalDiscs,
&i.Mbid,
&i.OriginalYear,
)
return i, err
}
const getReleaseGroupByNameAndArtist = `-- name: GetReleaseGroupByNameAndArtist :one
SELECT id, name, cover_art_id, album_artist_credit_id, year, original_year, total_tracks, total_discs, mbid FROM release_groups
SELECT id, name, cover_art_id, album_artist_credit_id, year, total_tracks, total_discs, mbid, original_year FROM release_groups
WHERE name = ? AND album_artist_credit_id = ? LIMIT 1
`
@@ -510,10 +510,10 @@ func (q *Queries) GetReleaseGroupByNameAndArtist(ctx context.Context, arg GetRel
&i.CoverArtID,
&i.AlbumArtistCreditID,
&i.Year,
&i.OriginalYear,
&i.TotalTracks,
&i.TotalDiscs,
&i.Mbid,
&i.OriginalYear,
)
return i, err
}
@@ -574,7 +574,7 @@ VALUES (?, ?, ?)
ON CONFLICT(name, album_artist_credit_id) DO UPDATE SET
album_artist_credit_id = COALESCE(excluded.album_artist_credit_id, release_groups.album_artist_credit_id),
year = COALESCE(excluded.year, release_groups.year)
RETURNING id, name, cover_art_id, album_artist_credit_id, year, original_year, total_tracks, total_discs, mbid
RETURNING id, name, cover_art_id, album_artist_credit_id, year, total_tracks, total_discs, mbid, original_year
`
type UpsertReleaseGroupParams struct {
@@ -592,10 +592,10 @@ func (q *Queries) UpsertReleaseGroup(ctx context.Context, arg UpsertReleaseGroup
&i.CoverArtID,
&i.AlbumArtistCreditID,
&i.Year,
&i.OriginalYear,
&i.TotalTracks,
&i.TotalDiscs,
&i.Mbid,
&i.OriginalYear,
)
return i, err
}