mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
fix: bust song/disc artwork ids and disc thumbnails on cover changes
Song coverArt ids emitted to Subsonic clients embedded no timestamp for the disc/album fallback paths (always `_0`), so clients that cache by coverArt id kept stale art after an album cover upload or delete. Fold the song's CoverArtUpdatedAt into DiscCoverArtID and AlbumCoverArtID; ids are unchanged when no cover was ever uploaded. The web UI disc-header thumbnails had the same gap: getDiscCoverArtUrl only used the song's updatedAt. It now takes the record and picks the newest of updatedAt/coverArtUpdatedAt, sharing the cache-key helper with getCoverArtUrl.
This commit is contained in:
parent
e8946b8810
commit
223efdf020
@ -132,13 +132,13 @@ func (mf MediaFile) CoverArtID() ArtworkID {
|
||||
// otherwise it returns the album artwork ID.
|
||||
func (mf MediaFile) DiscCoverArtID() ArtworkID {
|
||||
if mf.DiscNumber > 0 {
|
||||
return NewArtworkID(KindDiscArtwork, DiscArtworkID(mf.AlbumID, mf.DiscNumber), nil)
|
||||
return NewArtworkID(KindDiscArtwork, DiscArtworkID(mf.AlbumID, mf.DiscNumber), mf.CoverArtUpdatedAt)
|
||||
}
|
||||
return mf.AlbumCoverArtID()
|
||||
}
|
||||
|
||||
func (mf MediaFile) AlbumCoverArtID() ArtworkID {
|
||||
return artworkIDFromAlbum(Album{ID: mf.AlbumID})
|
||||
return artworkIDFromAlbum(Album{ID: mf.AlbumID, CoverArtUpdatedAt: mf.CoverArtUpdatedAt})
|
||||
}
|
||||
|
||||
func (mf MediaFile) StructuredLyrics() (LyricList, error) {
|
||||
|
||||
@ -549,6 +549,19 @@ var _ = Describe("MediaFile", func() {
|
||||
Expect(id.Kind).To(Equal(KindAlbumArtwork))
|
||||
Expect(id.ID).To(Equal(mf.AlbumID))
|
||||
})
|
||||
It("folds CoverArtUpdatedAt into disc and album fallback ids", func() {
|
||||
stamp := time.Date(2026, 7, 17, 0, 0, 0, 0, time.UTC)
|
||||
disc := MediaFile{ID: "111", AlbumID: "1", DiscNumber: 2}
|
||||
album := MediaFile{ID: "111", AlbumID: "1"}
|
||||
Expect(disc.CoverArtID().String()).To(HaveSuffix("_0"))
|
||||
Expect(album.CoverArtID().String()).To(HaveSuffix("_0"))
|
||||
disc.CoverArtUpdatedAt = &stamp
|
||||
album.CoverArtUpdatedAt = &stamp
|
||||
Expect(disc.CoverArtID().LastUpdate).To(Equal(stamp))
|
||||
Expect(disc.CoverArtID().String()).ToNot(HaveSuffix("_0"))
|
||||
Expect(album.CoverArtID().LastUpdate).To(Equal(stamp))
|
||||
Expect(album.CoverArtID().String()).ToNot(HaveSuffix("_0"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("AudioCodec", func() {
|
||||
|
||||
@ -96,18 +96,9 @@ const DiscSubtitleRow = forwardRef(
|
||||
onClick(discNumber)
|
||||
}
|
||||
|
||||
const coverArtUrl = subsonic.getDiscCoverArtUrl(
|
||||
record.albumId,
|
||||
record.discNumber,
|
||||
record.updatedAt,
|
||||
96,
|
||||
)
|
||||
const coverArtUrl = subsonic.getDiscCoverArtUrl(record, 96)
|
||||
|
||||
const fullImageUrl = subsonic.getDiscCoverArtUrl(
|
||||
record.albumId,
|
||||
record.discNumber,
|
||||
record.updatedAt,
|
||||
)
|
||||
const fullImageUrl = subsonic.getDiscCoverArtUrl(record)
|
||||
|
||||
const handleOpenLightbox = useCallback(
|
||||
(e) => {
|
||||
|
||||
@ -80,13 +80,15 @@ const getAvatarUrl = (username, size) =>
|
||||
}),
|
||||
)
|
||||
|
||||
const getCoverArtUrl = (record, size, square) => {
|
||||
// Bust the cache on the newest of updatedAt / coverArtUpdatedAt (album cover
|
||||
// uploads bump the latter).
|
||||
const cacheKey = [record.updatedAt, record.coverArtUpdatedAt]
|
||||
// Newest of updatedAt / coverArtUpdatedAt (album cover uploads bump the latter).
|
||||
const artCacheKey = (record) =>
|
||||
[record.updatedAt, record.coverArtUpdatedAt]
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => new Date(a) - new Date(b))
|
||||
.pop()
|
||||
|
||||
const getCoverArtUrl = (record, size, square) => {
|
||||
const cacheKey = artCacheKey(record)
|
||||
const options = {
|
||||
...(cacheKey && { _: cacheKey }),
|
||||
...(size && { size }),
|
||||
@ -109,13 +111,18 @@ const getCoverArtUrl = (record, size, square) => {
|
||||
}
|
||||
}
|
||||
|
||||
const getDiscCoverArtUrl = (albumId, discNumber, updatedAt, size) => {
|
||||
const getDiscCoverArtUrl = (record, size) => {
|
||||
const cacheKey = artCacheKey(record)
|
||||
const options = {
|
||||
...(updatedAt && { _: updatedAt }),
|
||||
...(cacheKey && { _: cacheKey }),
|
||||
...(size && { size }),
|
||||
}
|
||||
return baseUrl(
|
||||
url('getCoverArt', 'dc-' + albumId + ':' + discNumber, options),
|
||||
url(
|
||||
'getCoverArt',
|
||||
'dc-' + record.albumId + ':' + record.discNumber,
|
||||
options,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -154,9 +154,11 @@ describe('getDiscCoverArtUrl', () => {
|
||||
|
||||
it('should construct URL with dc-albumId:discNumber format, size, and cache param', () => {
|
||||
const url = subsonic.getDiscCoverArtUrl(
|
||||
'album-123',
|
||||
2,
|
||||
'2023-01-01T00:00:00Z',
|
||||
{
|
||||
albumId: 'album-123',
|
||||
discNumber: 2,
|
||||
updatedAt: '2023-01-01T00:00:00Z',
|
||||
},
|
||||
48,
|
||||
)
|
||||
|
||||
@ -167,7 +169,10 @@ describe('getDiscCoverArtUrl', () => {
|
||||
})
|
||||
|
||||
it('should handle missing updatedAt', () => {
|
||||
const url = subsonic.getDiscCoverArtUrl('album-123', 1, undefined, 48)
|
||||
const url = subsonic.getDiscCoverArtUrl(
|
||||
{ albumId: 'album-123', discNumber: 1 },
|
||||
48,
|
||||
)
|
||||
|
||||
expect(url).toContain('id=dc-album-123%3A1')
|
||||
expect(url).toContain('size=48')
|
||||
@ -175,16 +180,29 @@ describe('getDiscCoverArtUrl', () => {
|
||||
})
|
||||
|
||||
it('should handle missing size', () => {
|
||||
const url = subsonic.getDiscCoverArtUrl(
|
||||
'album-123',
|
||||
1,
|
||||
'2023-01-01T00:00:00Z',
|
||||
)
|
||||
const url = subsonic.getDiscCoverArtUrl({
|
||||
albumId: 'album-123',
|
||||
discNumber: 1,
|
||||
updatedAt: '2023-01-01T00:00:00Z',
|
||||
})
|
||||
|
||||
expect(url).toContain('id=dc-album-123%3A1')
|
||||
expect(url).toContain('_=2023-01-01T00%3A00%3A00Z')
|
||||
expect(url).not.toContain('size=')
|
||||
})
|
||||
|
||||
it('should bust the cache on coverArtUpdatedAt when it is newer', () => {
|
||||
const url = subsonic.getDiscCoverArtUrl({
|
||||
albumId: 'album-123',
|
||||
discNumber: 1,
|
||||
updatedAt: '2023-01-01T00:00:00Z',
|
||||
coverArtUpdatedAt: '2024-06-01T00:00:00Z',
|
||||
})
|
||||
|
||||
expect(url).toContain('id=dc-album-123%3A1')
|
||||
expect(url).toContain('_=2024-06-01T00%3A00%3A00Z')
|
||||
expect(url).not.toContain('_=2023-01-01')
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAvatarUrl', () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user