🏡 index : quine-global/quintodrome.git

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().Enqueue(ctx, 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().GetAlbumIDsByFolder(ctx, 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().GetSoleAlbumArtistIDsInSubtrees(ctx, 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,
	}
}