mirror of
https://github.com/navidrome/navidrome.git
synced 2026-11-09 03:38:48 +01:00
Entity-level artwork queries now take a typed model.Kind instead of a bare prefix string. GetItemArtwork, DeleteForItem(s), GetInfoForItems, EnqueueStaleAbsent, hydrateItemImages, enqueueBackfillKind and artwork.Refresh convert to the prefix string only at the two real boundaries: the SQL item_kind column (kind.Prefix() inside each repo) and external string inputs (a new model.ParseKind for the nativeapi URL param, which also validates it). The Backfill/stale-absent kind slices, the resolve.go dispatch switch, and the kind→resource / kind→table lookup maps now use the Kind vars directly. The queue lifecycle methods (MarkFailed/Delete*) keep string kinds — they operate on a dequeued item's raw ItemKind column, which stays a string field, always populated via kind.Prefix(). Removes every bare "al"/"ar"/… prefix literal from non-test code (27 -> 0); behavior is unchanged.
76 lines
2.6 KiB
Go
76 lines
2.6 KiB
Go
package persistence
|
|
|
|
import (
|
|
"context"
|
|
"iter"
|
|
"slices"
|
|
|
|
. "github.com/Masterminds/squirrel"
|
|
"github.com/navidrome/navidrome/log"
|
|
"github.com/navidrome/navidrome/model"
|
|
"github.com/pocketbase/dbx"
|
|
)
|
|
|
|
// artworkChunkSize bounds each chunk fetch's id IN-list (under SQLite's bound-parameter limit); a
|
|
// whole multiple of artworkBatchSize so a page re-chunks into even hydration batches.
|
|
const artworkChunkSize = artworkBatchSize * 3
|
|
|
|
// streamByIDs yields the rows of ids in chunks, fetching each chunk through the caller's hydrating
|
|
// fetch. Resolving ids first keeps OFFSET out of the joined query (spec §6).
|
|
func streamByIDs[S ~[]T, T any](ids []string, fetch func(chunk []string) (S, error)) iter.Seq2[T, error] {
|
|
return func(yield func(T, error) bool) {
|
|
for chunk := range slices.Chunk(ids, artworkChunkSize) {
|
|
rows, err := fetch(chunk)
|
|
if err != nil {
|
|
var zero T
|
|
yield(zero, err)
|
|
return
|
|
}
|
|
for _, row := range rows {
|
|
if !yield(row, nil) {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// chunkOptions narrows the caller's options to a chunk of ids, dropping Max/Offset (the id pre-pass
|
|
// already consumed them) and reusing its Sort, which may be a seeded random expression by now.
|
|
func chunkOptions(options []model.QueryOptions, idField string) func([]string) model.QueryOptions {
|
|
var base model.QueryOptions
|
|
if len(options) > 0 {
|
|
base = model.QueryOptions{Sort: options[0].Sort, Order: options[0].Order, Filters: options[0].Filters}
|
|
}
|
|
return func(chunk []string) model.QueryOptions {
|
|
opts := base
|
|
opts.Filters = Eq{idField: chunk}
|
|
if base.Filters != nil {
|
|
opts.Filters = And{base.Filters, opts.Filters}
|
|
}
|
|
return opts
|
|
}
|
|
}
|
|
|
|
// hydrateItemImages returns per-item artwork info for a fetched page via one batched query per kind
|
|
// (never a join, see spec §6). On error it logs and returns an empty map so the page still renders.
|
|
func hydrateItemImages(ctx context.Context, db dbx.Builder, kind model.Kind, ids []string) map[string]model.ItemArtworkInfo {
|
|
if len(ids) == 0 {
|
|
return map[string]model.ItemArtworkInfo{}
|
|
}
|
|
infos, err := NewArtworkRepository(ctx, db).GetInfoForItems(kind, ids)
|
|
if err != nil {
|
|
log.Error(ctx, "Failed to hydrate artwork info onto page", "kind", kind, err)
|
|
return map[string]model.ItemArtworkInfo{}
|
|
}
|
|
return infos
|
|
}
|
|
|
|
// applyItemImage copies a hydration entry onto img; a missing entry leaves it zero (unresolved).
|
|
func applyItemImage(infos map[string]model.ItemArtworkInfo, id string, img *model.ItemImage) {
|
|
if info, ok := infos[id]; ok {
|
|
img.ImageHash = info.Hash
|
|
img.ImageAbsent = info.Absent()
|
|
img.BlurHash = info.BlurHash
|
|
}
|
|
}
|