mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-25 02:37:22 +02:00
feat(cli): add artwork refresh
This commit is contained in:
parent
ab3e6fe949
commit
4e07b292a3
2 changed files with 103 additions and 0 deletions
|
|
@ -5,6 +5,8 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
|
@ -28,6 +30,7 @@ func init() {
|
|||
artworkExplainCmd.Flags().BoolVar(&explainLive, "live", false,
|
||||
"perform real external lookups instead of reporting what would be tried")
|
||||
artworkCmd.AddCommand(artworkExplainCmd)
|
||||
artworkCmd.AddCommand(artworkRefreshCmd)
|
||||
rootCmd.AddCommand(artworkCmd)
|
||||
}
|
||||
|
||||
|
|
@ -49,6 +52,48 @@ var artworkExplainCmd = &cobra.Command{
|
|||
},
|
||||
}
|
||||
|
||||
var artworkRefreshCmd = &cobra.Command{
|
||||
Use: "refresh <kind> <id>...",
|
||||
Short: "Clear an item's artwork state and re-resolve it",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
kind, err := parseArtworkKind(args[0])
|
||||
if err != nil {
|
||||
log.Fatal(cmd.Context(), err)
|
||||
}
|
||||
runRefresh(cmd.Context(), kind, args[1:])
|
||||
},
|
||||
}
|
||||
|
||||
func runRefresh(ctx context.Context, kind model.Kind, ids []string) {
|
||||
defer db.Init(ctx)()
|
||||
ds, ctx := getAdminContext(ctx)
|
||||
|
||||
if failed := refreshItems(ctx, ds, kind, ids, os.Stdout); failed > 0 {
|
||||
log.Fatal(ctx, "Failed to refresh artwork", "kind", kind, "failed", failed, "total", len(ids))
|
||||
}
|
||||
}
|
||||
|
||||
// refreshItems keeps going after a failure — the ids are independent — and returns how many failed.
|
||||
func refreshItems(ctx context.Context, ds model.DataStore, kind model.Kind, ids []string, out io.Writer) int {
|
||||
var failed int
|
||||
for _, id := range ids {
|
||||
// artwork.Refresh would happily queue an id that does not exist, orphaning a queue row.
|
||||
if _, err := artworkItemName(ctx, ds, kind, id); err != nil {
|
||||
log.Error(ctx, "Item not found", "kind", kind, "id", id, err)
|
||||
failed++
|
||||
continue
|
||||
}
|
||||
if err := artwork.Refresh(ctx, ds, kind, id); err != nil {
|
||||
log.Error(ctx, "Error refreshing artwork", "kind", kind, "id", id, err)
|
||||
failed++
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(out, "%s/%s: queued\n", kind.Prefix(), id)
|
||||
}
|
||||
return failed
|
||||
}
|
||||
|
||||
func parseArtworkKind(s string) (model.Kind, error) {
|
||||
kind, ok := model.ParseKind(s)
|
||||
if ok {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
|
@ -180,3 +183,58 @@ var _ = Describe("formatExplain", func() {
|
|||
"the priority chain config does not govern this kind")
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("artwork refresh command", func() {
|
||||
It("requires at least a kind and one id", func() {
|
||||
Expect(artworkRefreshCmd.Args(artworkRefreshCmd, []string{"ar"})).To(HaveOccurred())
|
||||
Expect(artworkRefreshCmd.Args(artworkRefreshCmd, []string{"ar", "id1"})).ToNot(HaveOccurred())
|
||||
Expect(artworkRefreshCmd.Args(artworkRefreshCmd, []string{"ar", "id1", "id2"})).ToNot(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("refreshItems", func() {
|
||||
var ds *tests.MockDataStore
|
||||
var queue *tests.MockArtworkQueueRepo
|
||||
var art *tests.MockArtworkRepo
|
||||
var out strings.Builder
|
||||
ctx := context.Background()
|
||||
|
||||
BeforeEach(func() {
|
||||
albums := tests.CreateMockAlbumRepo()
|
||||
albums.SetData(model.Albums{{ID: "al-1"}, {ID: "al-3"}})
|
||||
ds = &tests.MockDataStore{MockedAlbum: albums}
|
||||
art = ds.Artwork(ctx).(*tests.MockArtworkRepo)
|
||||
queue = ds.ArtworkQueue(ctx).(*tests.MockArtworkQueueRepo)
|
||||
out.Reset()
|
||||
})
|
||||
|
||||
It("clears the stored state and queues each id at Bump priority", func() {
|
||||
Expect(art.PutItemArtwork(&model.ItemArtwork{ItemKind: model.KindAlbumArtwork.Prefix(),
|
||||
ItemID: "al-1", ImageType: model.ImageTypePrimary, Hash: "abc123"})).To(Succeed())
|
||||
|
||||
Expect(refreshItems(ctx, ds, model.KindAlbumArtwork, []string{"al-1", "al-3"}, &out)).To(BeZero())
|
||||
|
||||
_, err := art.GetItemArtwork(model.KindAlbumArtwork, "al-1", model.ImageTypePrimary)
|
||||
Expect(err).To(MatchError(model.ErrNotFound))
|
||||
queued, err := queue.Get(model.KindAlbumArtwork, "al-1", model.ImageTypePrimary)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(queued.Priority).To(Equal(model.ArtworkPriorityBump))
|
||||
Expect(out.String()).To(Equal("al/al-1: queued\nal/al-3: queued\n"))
|
||||
})
|
||||
|
||||
It("skips an id that does not exist instead of queuing it", func() {
|
||||
Expect(refreshItems(ctx, ds, model.KindAlbumArtwork, []string{"al-2"}, &out)).To(Equal(1))
|
||||
|
||||
_, err := queue.Get(model.KindAlbumArtwork, "al-2", model.ImageTypePrimary)
|
||||
Expect(err).To(MatchError(model.ErrNotFound), "a typo must not leave an orphan queue row")
|
||||
Expect(out.String()).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("continues past a failing id and counts the failures", func() {
|
||||
Expect(refreshItems(ctx, ds, model.KindAlbumArtwork,
|
||||
[]string{"al-1", "al-2", "al-3"}, &out)).To(Equal(1))
|
||||
|
||||
Expect(out.String()).To(Equal("al/al-1: queued\nal/al-3: queued\n"),
|
||||
"the ids after a failure are still refreshed")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue