mirror of
https://github.com/navidrome/navidrome.git
synced 2026-10-12 20:37:12 +02:00
112 lines
3.3 KiB
Go
112 lines
3.3 KiB
Go
|
|
package scanner
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"sync"
|
||
|
|
|
||
|
|
"github.com/navidrome/navidrome/consts"
|
||
|
|
"github.com/navidrome/navidrome/log"
|
||
|
|
"github.com/navidrome/navidrome/model"
|
||
|
|
)
|
||
|
|
|
||
|
|
// imageChangedFolder records a folder whose image files changed during the scan, so the
|
||
|
|
// affected albums/artists can be re-enqueued for artwork resolution at the end of phase 1.
|
||
|
|
type imageChangedFolder struct {
|
||
|
|
id string
|
||
|
|
path string
|
||
|
|
artistImage bool
|
||
|
|
}
|
||
|
|
|
||
|
|
// imageChangeCollector gathers those folders per library while phase 1 persists them, then turns
|
||
|
|
// them into artwork queue items once.
|
||
|
|
type imageChangeCollector struct {
|
||
|
|
libs map[int]model.Library
|
||
|
|
folders map[int][]imageChangedFolder
|
||
|
|
ds model.DataStore
|
||
|
|
mutex sync.Mutex
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *imageChangeCollector) record(lib model.Library, folder imageChangedFolder) {
|
||
|
|
c.mutex.Lock()
|
||
|
|
defer c.mutex.Unlock()
|
||
|
|
if c.folders == nil {
|
||
|
|
c.folders = map[int][]imageChangedFolder{}
|
||
|
|
c.libs = map[int]model.Library{}
|
||
|
|
}
|
||
|
|
c.libs[lib.ID] = lib
|
||
|
|
c.folders[lib.ID] = append(c.folders[lib.ID], folder)
|
||
|
|
}
|
||
|
|
|
||
|
|
// enqueue is best-effort: failures are logged and never fail the scan.
|
||
|
|
func (c *imageChangeCollector) enqueue(ctx context.Context) {
|
||
|
|
c.mutex.Lock()
|
||
|
|
foldersMap, libsMap := c.folders, c.libs
|
||
|
|
c.folders, c.libs = nil, nil
|
||
|
|
c.mutex.Unlock()
|
||
|
|
|
||
|
|
for libID, folders := range foldersMap {
|
||
|
|
lib := libsMap[libID]
|
||
|
|
items, err := c.queueItems(ctx, lib, folders)
|
||
|
|
if err != nil {
|
||
|
|
log.Warn(ctx, "Scanner: could not map image changes to artwork items", "lib", lib.Name, err)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if len(items) == 0 {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if err := c.ds.ArtworkQueue(ctx).Enqueue(items...); err != nil {
|
||
|
|
log.Warn(ctx, "Scanner: could not enqueue artwork for image changes", "lib", lib.Name, err)
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
log.Debug(ctx, "Scanner: Enqueued artwork resolution for image changes", "lib", lib.Name,
|
||
|
|
"changedFolders", len(folders), "items", len(items))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func (c *imageChangeCollector) queueItems(ctx context.Context, lib model.Library,
|
||
|
|
folders []imageChangedFolder,
|
||
|
|
) ([]model.ArtworkQueueItem, error) {
|
||
|
|
folderIDs := make([]string, len(folders))
|
||
|
|
var artistFolderPaths []string
|
||
|
|
for i, f := range folders {
|
||
|
|
folderIDs[i] = f.id
|
||
|
|
if f.artistImage {
|
||
|
|
artistFolderPaths = append(artistFolderPaths, f.path)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
var items []model.ArtworkQueueItem
|
||
|
|
|
||
|
|
albumIDs, err := c.ds.MediaFile(ctx).GetAlbumIDsByFolder(lib, folderIDs...)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
for _, id := range albumIDs {
|
||
|
|
items = append(items, scanArtworkItem(model.KindAlbumArtwork, id))
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(artistFolderPaths) == 0 {
|
||
|
|
return items, nil
|
||
|
|
}
|
||
|
|
// The resolver climbs to the library root, so the subtree below the folder is the affected set.
|
||
|
|
// A failure here must not discard the album items already collected.
|
||
|
|
artistIDs, err := c.ds.Album(ctx).GetSoleAlbumArtistIDsInSubtrees(lib, artistFolderPaths...)
|
||
|
|
if err != nil {
|
||
|
|
log.Warn(ctx, "Scanner: could not map image changes to artists", "lib", lib.Name, err)
|
||
|
|
return items, nil
|
||
|
|
}
|
||
|
|
for _, id := range artistIDs {
|
||
|
|
if id == "" || id == consts.UnknownArtistID || id == consts.VariousArtistsID {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
items = append(items, scanArtworkItem(model.KindArtistArtwork, id))
|
||
|
|
}
|
||
|
|
return items, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func scanArtworkItem(kind model.Kind, id string) model.ArtworkQueueItem {
|
||
|
|
return model.ArtworkQueueItem{
|
||
|
|
ItemKind: kind.Prefix(), ItemID: id, ImageType: model.ImageTypePrimary,
|
||
|
|
Priority: model.ArtworkPriorityScan,
|
||
|
|
}
|
||
|
|
}
|