mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-31 07:30:32 +00:00
Merge d4eca9cc9af1f2fb7705995f726a0125726d0d7f into 600ea5482c36d3705fbca1d9ab749d9bdafd6f80
This commit is contained in:
commit
90df7fb304
@ -2,6 +2,7 @@ package artworke2e_test
|
||||
|
||||
import (
|
||||
"testing/fstest"
|
||||
"time"
|
||||
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
@ -466,4 +467,19 @@ var _ = Describe("Album artwork resolution", func() {
|
||||
Expect(readArtwork(al.CoverArtID())).To(Equal(imageBytes("cover")))
|
||||
})
|
||||
})
|
||||
|
||||
// https://github.com/navidrome/navidrome/issues/5469
|
||||
It("updates cover art after an image-only quick scan", func() {
|
||||
conf.Server.CoverArtPriority = defaultCoverPriority
|
||||
setLayout(fstest.MapFS{"Artist/Album/01 - Track.mp3": trackFile(1, "Track")})
|
||||
scan()
|
||||
alBefore := firstAlbum()
|
||||
|
||||
fakeFS.Add("Artist/Album/cover.jpg", imageFile("new-cover"), alBefore.UpdatedAt.Add(time.Hour))
|
||||
scanQuick()
|
||||
|
||||
alAfter := firstAlbum()
|
||||
Expect(alAfter.ImportedAt.After(alBefore.ImportedAt)).To(BeTrue())
|
||||
Expect(readArtwork(alAfter.CoverArtID())).To(Equal(imageBytes("new-cover")))
|
||||
})
|
||||
})
|
||||
|
||||
@ -98,6 +98,14 @@ func scan() {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
func scanQuick() {
|
||||
GinkgoHelper()
|
||||
s := scanner.New(ctx, ds, artwork.NoopCacheWarmer(), events.NoopBroker(),
|
||||
playlists.NewPlaylists(ds, core.NewImageUploadService()), metrics.NewNoopInstance())
|
||||
_, err := s.ScanAll(ctx, false)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
|
||||
func firstAlbum() model.Album {
|
||||
GinkgoHelper()
|
||||
albums, err := ds.Album(ctx).GetAll(model.QueryOptions{})
|
||||
|
||||
@ -112,10 +112,14 @@ func ParseDiscArtworkID(id string) (albumID string, discNumber int, err error) {
|
||||
}
|
||||
|
||||
func artworkIDFromAlbum(al Album) ArtworkID {
|
||||
lastUpdate := al.UpdatedAt
|
||||
if al.ImportedAt.After(lastUpdate) {
|
||||
lastUpdate = al.ImportedAt
|
||||
}
|
||||
return ArtworkID{
|
||||
Kind: KindAlbumArtwork,
|
||||
ID: al.ID,
|
||||
LastUpdate: al.UpdatedAt,
|
||||
LastUpdate: lastUpdate,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -342,6 +342,9 @@ func (p *phaseFolders) persistChanges(entry *folderEntry) (*folderEntry, error)
|
||||
albumRepo := tx.Album(p.ctx)
|
||||
mfRepo := tx.MediaFile(p.ctx)
|
||||
|
||||
prevFolder, _ := folderRepo.Get(entry.id)
|
||||
imagesChanged := folderImagesChanged(prevFolder, entry)
|
||||
|
||||
// Save folder to DB
|
||||
folder := entry.toFolder()
|
||||
err := folderRepo.Put(folder)
|
||||
@ -415,6 +418,13 @@ func (p *phaseFolders) persistChanges(entry *folderEntry) (*folderEntry, error)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// #5469: bump ImportedAt when sidecar images change so cover URLs invalidate.
|
||||
if imagesChanged {
|
||||
if err := p.touchAlbumsForFolderImages(mfRepo, albumRepo, entry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, "scanner: persist changes")
|
||||
if err != nil {
|
||||
@ -431,6 +441,34 @@ func (p *phaseFolders) persistChanges(entry *folderEntry) (*folderEntry, error)
|
||||
return entry, err
|
||||
}
|
||||
|
||||
func folderImagesChanged(prev *model.Folder, entry *folderEntry) bool {
|
||||
if prev == nil {
|
||||
return len(entry.imageFiles) > 0
|
||||
}
|
||||
newFiles := slices.Collect(maps.Keys(entry.imageFiles))
|
||||
slices.Sort(newFiles)
|
||||
prevFiles := slices.Clone(prev.ImageFiles)
|
||||
slices.Sort(prevFiles)
|
||||
return !slices.Equal(prevFiles, newFiles) || !prev.ImagesUpdatedAt.Equal(entry.imagesUpdatedAt)
|
||||
}
|
||||
|
||||
func (p *phaseFolders) touchAlbumsForFolderImages(
|
||||
mfRepo model.MediaFileRepository,
|
||||
albumRepo model.AlbumRepository,
|
||||
entry *folderEntry,
|
||||
) error {
|
||||
mfs, err := mfRepo.GetAll(model.QueryOptions{
|
||||
Filters: squirrel.And{
|
||||
squirrel.Eq{"folder_id": entry.id},
|
||||
squirrel.Eq{"missing": false},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return albumRepo.Touch(slice.Unique(slice.Map(mfs, func(mf model.MediaFile) string { return mf.AlbumID }))...)
|
||||
}
|
||||
|
||||
// persistAlbum persists the given album to the database, and reassigns annotations from the previous album ID
|
||||
func (p *phaseFolders) persistAlbum(repo model.AlbumRepository, a *model.Album, idMap map[string]string) error {
|
||||
prevID := idMap[a.ID]
|
||||
|
||||
@ -81,8 +81,10 @@ const getAvatarUrl = (username, size) =>
|
||||
)
|
||||
|
||||
const getCoverArtUrl = (record, size, square) => {
|
||||
const dates = [record?.updatedAt, record?.importedAt].filter(Boolean).sort()
|
||||
const bust = dates[dates.length - 1]
|
||||
const options = {
|
||||
...(record.updatedAt && { _: record.updatedAt }),
|
||||
...(bust && { _: bust }),
|
||||
...(size && { size }),
|
||||
...(square && { square }),
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user