mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +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):
|
// 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) {
|
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)
|
ia, err := s.ds.Artwork(ctx).GetItemArtwork(artID.Kind.Prefix(), artID.ID, model.ImageTypePrimary)
|
||||||
switch {
|
switch {
|
||||||
@ -99,6 +104,11 @@ func (s *service) serveEntity(ctx context.Context, artID model.ArtworkID, size i
|
|||||||
case err != nil:
|
case err != nil:
|
||||||
return nil, err
|
return nil, err
|
||||||
case ia.Hash == "":
|
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
|
return nil, ErrUnavailable
|
||||||
default:
|
default:
|
||||||
return s.serveHash(ctx, artID, ia, size, square)
|
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))
|
Expect(queueRepo.Data[primaryKey("al", "al3b")].Priority).To(Equal(model.ArtworkPriorityScan))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("returns ErrUnavailable for an absent state without enqueuing", func() {
|
It("does not re-enqueue a recently-attempted absent state", func() {
|
||||||
Expect(artRepo.PutItemArtwork(&model.ItemArtwork{ItemKind: "al", ItemID: "al4"})).To(Succeed())
|
Expect(artRepo.PutItemArtwork(&model.ItemArtwork{
|
||||||
|
ItemKind: "al", ItemID: "al4", AttemptedAt: time.Now(),
|
||||||
|
})).To(Succeed())
|
||||||
|
|
||||||
_, err := svc.Get(ctx, model.MustParseArtworkID("al-al4"), 0, false)
|
_, err := svc.Get(ctx, model.MustParseArtworkID("al-al4"), 0, false)
|
||||||
Expect(err).To(MatchError(ErrUnavailable))
|
Expect(err).To(MatchError(ErrUnavailable))
|
||||||
Expect(queueRepo.Data).To(BeEmpty())
|
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() {
|
Describe("provisional read-through", func() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user