mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
feat(artwork): re-queue an absent cover when its page is viewed
serveEntity now schedules a Bump recheck for an entity whose art was recorded absent, so viewing a missing cover re-triggers resolution (e.g. after an external source that was down during the scan comes back), matching the request-time bump that already covers never-resolved entities. Throttled by attempted_at against requestRecheckAge (1h) so repeatedly opening a genuinely-absent page can't hammer external services. EnqueueBump preserves an existing failed-state backoff via MAX(priority,...) and inserts a fresh, immediately-eligible recheck for a settled-absent row (whose queue row was already deleted).
This commit is contained in:
parent
d3739a4cf1
commit
8a0ff264f1
@ -89,8 +89,13 @@ func (s *service) Get(ctx context.Context, artID model.ArtworkID, size int, squa
|
||||
}
|
||||
}
|
||||
|
||||
// requestRecheckAge throttles view-triggered rechecks of an absent entity so repeatedly opening a
|
||||
// genuinely-absent page can't hammer external services; below staleAbsentAge to catch younger absences.
|
||||
const requestRecheckAge = time.Hour
|
||||
|
||||
// serveEntity serves an entity whose state the worker owns (album/artist/playlist/radio):
|
||||
// found row serves its hash, absent row is unavailable, missing row reads through provisionally.
|
||||
// found row serves its hash, absent row is unavailable (promoting a stale recheck on view),
|
||||
// missing row reads through provisionally.
|
||||
func (s *service) serveEntity(ctx context.Context, artID model.ArtworkID, size int, square bool) (*Image, error) {
|
||||
ia, err := s.ds.Artwork(ctx).GetItemArtwork(artID.Kind.Prefix(), artID.ID, model.ImageTypePrimary)
|
||||
switch {
|
||||
@ -99,6 +104,11 @@ func (s *service) serveEntity(ctx context.Context, artID model.ArtworkID, size i
|
||||
case err != nil:
|
||||
return nil, err
|
||||
case ia.Hash == "":
|
||||
// EnqueueBump preserves an existing backoff row's retry_at; for a settled absent row
|
||||
// (no queue row) it inserts a fresh, immediately-eligible recheck.
|
||||
if time.Since(ia.AttemptedAt) > requestRecheckAge {
|
||||
s.enqueue(ctx, artID, model.ArtworkPriorityBump)
|
||||
}
|
||||
return nil, ErrUnavailable
|
||||
default:
|
||||
return s.serveHash(ctx, artID, ia, size, square)
|
||||
|
||||
@ -183,13 +183,29 @@ var _ = Describe("Service", func() {
|
||||
Expect(queueRepo.Data[primaryKey("al", "al3b")].Priority).To(Equal(model.ArtworkPriorityScan))
|
||||
})
|
||||
|
||||
It("returns ErrUnavailable for an absent state without enqueuing", func() {
|
||||
Expect(artRepo.PutItemArtwork(&model.ItemArtwork{ItemKind: "al", ItemID: "al4"})).To(Succeed())
|
||||
It("does not re-enqueue a recently-attempted absent state", func() {
|
||||
Expect(artRepo.PutItemArtwork(&model.ItemArtwork{
|
||||
ItemKind: "al", ItemID: "al4", AttemptedAt: time.Now(),
|
||||
})).To(Succeed())
|
||||
|
||||
_, err := svc.Get(ctx, model.MustParseArtworkID("al-al4"), 0, false)
|
||||
Expect(err).To(MatchError(ErrUnavailable))
|
||||
Expect(queueRepo.Data).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("promotes a stale absent state at Bump priority on view", func() {
|
||||
Expect(artRepo.PutItemArtwork(&model.ItemArtwork{
|
||||
ItemKind: "al", ItemID: "al4b", AttemptedAt: time.Now().Add(-2 * requestRecheckAge),
|
||||
})).To(Succeed())
|
||||
|
||||
_, err := svc.Get(ctx, model.MustParseArtworkID("al-al4b"), 0, false)
|
||||
Expect(err).To(MatchError(ErrUnavailable))
|
||||
Expect(queueRepo.Data[primaryKey("al", "al4b")].Priority).To(Equal(model.ArtworkPriorityBump))
|
||||
// The absent state row is left intact; only a recheck is scheduled.
|
||||
ia, err := artRepo.GetItemArtwork("al", "al4b", model.ImageTypePrimary)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(ia.Hash).To(BeEmpty())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("provisional read-through", func() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user