mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
refactor(artwork): deduplicate purge loop, backfill table, and extGate alias
This commit is contained in:
parent
c57496d50d
commit
87095fab08
@ -11,6 +11,7 @@ import (
|
||||
"github.com/navidrome/navidrome/consts"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/utils/slice"
|
||||
)
|
||||
|
||||
// FingerprintPropertyKey is the model.PropertyRepository key Backfill compares against
|
||||
@ -46,36 +47,36 @@ func Backfill(ctx context.Context, ds model.DataStore) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
artists, err := ds.Artist(ctx).GetAll()
|
||||
if err != nil {
|
||||
return false, err
|
||||
// Artists first: few entities, most external-dependent, so they get queue headstart.
|
||||
kinds := []struct {
|
||||
kind string
|
||||
fetch func() ([]string, error)
|
||||
}{
|
||||
{"ar", func() ([]string, error) {
|
||||
as, err := ds.Artist(ctx).GetAll()
|
||||
return slice.Map(as, func(a model.Artist) string { return a.ID }), err
|
||||
}},
|
||||
{"al", func() ([]string, error) {
|
||||
as, err := ds.Album(ctx).GetAll()
|
||||
return slice.Map(as, func(a model.Album) string { return a.ID }), err
|
||||
}},
|
||||
{"pl", func() ([]string, error) {
|
||||
ps, err := ds.Playlist(ctx).GetAll()
|
||||
return slice.Map(ps, func(p model.Playlist) string { return p.ID }), err
|
||||
}},
|
||||
{"ra", func() ([]string, error) {
|
||||
rs, err := ds.Radio(ctx).GetAll()
|
||||
return slice.Map(rs, func(r model.Radio) string { return r.ID }), err
|
||||
}},
|
||||
}
|
||||
if err := enqueueBackfillKind(ctx, ds, "ar", idsOf(artists, func(a model.Artist) string { return a.ID })); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
albums, err := ds.Album(ctx).GetAll()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := enqueueBackfillKind(ctx, ds, "al", idsOf(albums, func(a model.Album) string { return a.ID })); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
playlists, err := ds.Playlist(ctx).GetAll()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := enqueueBackfillKind(ctx, ds, "pl", idsOf(playlists, func(p model.Playlist) string { return p.ID })); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
radios, err := ds.Radio(ctx).GetAll()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := enqueueBackfillKind(ctx, ds, "ra", idsOf(radios, func(r model.Radio) string { return r.ID })); err != nil {
|
||||
return false, err
|
||||
for _, k := range kinds {
|
||||
ids, err := k.fetch()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := enqueueBackfillKind(ctx, ds, k.kind, ids); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := props.Put(FingerprintPropertyKey, current); err != nil {
|
||||
@ -98,14 +99,6 @@ func enqueueBackfillKind(ctx context.Context, ds model.DataStore, kind string, i
|
||||
return ds.ArtworkQueue(ctx).Enqueue(items...)
|
||||
}
|
||||
|
||||
func idsOf[T any](items []T, id func(T) string) []string {
|
||||
ids := make([]string, len(items))
|
||||
for i, it := range items {
|
||||
ids[i] = id(it)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// EnqueueStaleAbsentAll requeues absent-state entries older than staleAbsentAge, across
|
||||
// every artwork-bearing kind, for the periodic recheck job.
|
||||
func EnqueueStaleAbsentAll(ctx context.Context, ds model.DataStore) error {
|
||||
|
||||
@ -38,7 +38,7 @@ type workerDeps struct {
|
||||
store *ImageStore
|
||||
prov external.Provider
|
||||
ffmpeg ffmpeg.FFmpeg
|
||||
extGate func(func() (io.ReadCloser, string, error)) (io.ReadCloser, string, error)
|
||||
extGate extGateFunc
|
||||
}
|
||||
|
||||
// processItem resolves one queue item end to end: find an image, hash/decode/
|
||||
|
||||
@ -39,7 +39,7 @@ func passthroughExtGate(f func() (io.ReadCloser, string, error)) (io.ReadCloser,
|
||||
}
|
||||
|
||||
// resolveItem walks the kind's priority chain and returns the first hit.
|
||||
func resolveItem(ctx context.Context, ds model.DataStore, prov external.Provider, ffmpeg ffmpeg.FFmpeg, item model.ArtworkQueueItem, extGate func(func() (io.ReadCloser, string, error)) (io.ReadCloser, string, error)) (resolution, error) {
|
||||
func resolveItem(ctx context.Context, ds model.DataStore, prov external.Provider, ffmpeg ffmpeg.FFmpeg, item model.ArtworkQueueItem, extGate extGateFunc) (resolution, error) {
|
||||
if extGate == nil {
|
||||
extGate = passthroughExtGate
|
||||
}
|
||||
@ -290,12 +290,10 @@ func resolveEmbedded(ctx context.Context, lib libraryView, ffm ffmpeg.FFmpeg, em
|
||||
return resolution{}, false
|
||||
}
|
||||
abs := lib.Abs(embedRel)
|
||||
mtime := mtimeViaFS(lib.FS, embedRel)
|
||||
if r, _, _ := fromTag(ctx, lib.FS, embedRel)(); r != nil {
|
||||
return resolution{reader: r, source: "embedded", sourcePath: abs, refMtime: mtime}, true
|
||||
}
|
||||
if r, _, _ := fromFFmpegTag(ctx, ffm, abs)(); r != nil {
|
||||
return resolution{reader: r, source: "embedded", sourcePath: abs, refMtime: mtime}, true
|
||||
for _, sf := range []sourceFunc{fromTag(ctx, lib.FS, embedRel), fromFFmpegTag(ctx, ffm, abs)} {
|
||||
if r, _, _ := sf(); r != nil {
|
||||
return resolution{reader: r, source: "embedded", sourcePath: abs, refMtime: mtimeViaFS(lib.FS, embedRel)}, true
|
||||
}
|
||||
}
|
||||
return resolution{}, false
|
||||
}
|
||||
|
||||
@ -78,19 +78,7 @@ func (r *artworkQueueRepository) DeleteIfUnchanged(kind, id, imageType string, r
|
||||
|
||||
// PurgeDangling removes queue rows whose entity no longer exists, per kind.
|
||||
func (r *artworkQueueRepository) PurgeDangling() (int64, error) {
|
||||
var total int64
|
||||
for kind, table := range danglingItemArtworkKinds {
|
||||
del := Delete(r.tableName).Where(And{
|
||||
Eq{"item_kind": kind},
|
||||
Expr("item_id NOT IN (SELECT id FROM " + table + ")"),
|
||||
})
|
||||
c, err := r.executeSQL(del)
|
||||
if err != nil {
|
||||
return total, err
|
||||
}
|
||||
total += c
|
||||
}
|
||||
return total, nil
|
||||
return purgeDangling(r.executeSQL, r.tableName)
|
||||
}
|
||||
|
||||
func (r *artworkQueueRepository) Count() (int64, error) {
|
||||
|
||||
@ -122,14 +122,15 @@ var danglingItemArtworkKinds = map[string]string{
|
||||
"ra": "radio",
|
||||
}
|
||||
|
||||
func (r *artworkRepository) PurgeDanglingItemArtwork() (int64, error) {
|
||||
// purgeDangling deletes rows in table whose owning entity is gone, one statement per kind.
|
||||
func purgeDangling(execute func(Sqlizer) (int64, error), table string) (int64, error) {
|
||||
var total int64
|
||||
for kind, table := range danglingItemArtworkKinds {
|
||||
del := Delete(itemArtworkTable).Where(And{
|
||||
for kind, entityTable := range danglingItemArtworkKinds {
|
||||
del := Delete(table).Where(And{
|
||||
Eq{"item_kind": kind},
|
||||
Expr("item_id NOT IN (SELECT id FROM " + table + ")"),
|
||||
Expr("item_id NOT IN (SELECT id FROM " + entityTable + ")"),
|
||||
})
|
||||
c, err := r.items.executeSQL(del)
|
||||
c, err := execute(del)
|
||||
if err != nil {
|
||||
return total, err
|
||||
}
|
||||
@ -138,6 +139,10 @@ func (r *artworkRepository) PurgeDanglingItemArtwork() (int64, error) {
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (r *artworkRepository) PurgeDanglingItemArtwork() (int64, error) {
|
||||
return purgeDangling(r.items.executeSQL, itemArtworkTable)
|
||||
}
|
||||
|
||||
func (r *artworkRepository) GetItemArtwork(kind, id, imageType string) (*model.ItemArtwork, error) {
|
||||
sel := Select("*").From(itemArtworkTable).
|
||||
Where(Eq{"item_kind": kind, "item_id": id, "image_type": imageType})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user