mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
refactor(artwork): move ImageUploadService to artwork.Uploader
Relocate the image-upload service from core to core/artwork as artwork.Uploader, co-locating it with the resolver/worker/serving that own the artwork state it invalidates. MaxImageUploadSize moves too — its only callers are the two image-upload handlers — which lets core/image_upload.go be deleted entirely. Extract the shared "clear resolved state + re-queue at Bump" invalidation into artwork.Refresh and fold nativeapi's refreshArtwork handler onto it, removing the duplicated DeleteForItem+Enqueue block that had drifted into three places. The wire provider moves from core's set to artwork's; the playlists.ImageUploadService binding moves to the top-level injector so core/artwork stays unaware of playlists. Behavior is unchanged.
This commit is contained in:
parent
8a0ff264f1
commit
53babc7a2a
@ -12,7 +12,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/core/playlists"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
@ -260,7 +260,7 @@ func runImport(ctx context.Context, files []string) {
|
||||
ctx = request.WithUser(ctx, *user)
|
||||
}
|
||||
|
||||
pls := playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
pls := playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
for _, file := range files {
|
||||
absPath, err := filepath.Abs(file)
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/core/playlists"
|
||||
"github.com/navidrome/navidrome/db"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
@ -82,7 +82,7 @@ func runScanner(ctx context.Context) {
|
||||
sqlDB := db.Db()
|
||||
defer db.Db().Close()
|
||||
ds := persistence.New(sqlDB)
|
||||
pls := playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
pls := playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
// Parse targets from command line or file
|
||||
var scanTargets []model.ScanTarget
|
||||
|
||||
@ -65,8 +65,8 @@ func CreateNativeAPIRouter(ctx context.Context) *nativeapi.Router {
|
||||
sqlDB := db.Db()
|
||||
dataStore := persistence.New(sqlDB)
|
||||
share := core.NewShare(dataStore)
|
||||
imageUploadService := core.NewImageUploadService(dataStore)
|
||||
playlistsPlaylists := playlists.NewPlaylists(dataStore, imageUploadService)
|
||||
uploader := artwork.NewUploader(dataStore)
|
||||
playlistsPlaylists := playlists.NewPlaylists(dataStore, uploader)
|
||||
insights := metrics.GetInstance(dataStore)
|
||||
broker := events.GetBroker()
|
||||
metricsMetrics := metrics.GetPrometheusInstance(dataStore)
|
||||
@ -76,7 +76,7 @@ func CreateNativeAPIRouter(ctx context.Context) *nativeapi.Router {
|
||||
library := core.NewLibrary(dataStore, modelScanner, watcher, broker, manager)
|
||||
user := core.NewUser(dataStore, manager)
|
||||
maintenance := core.NewMaintenance(dataStore)
|
||||
router := nativeapi.New(dataStore, share, playlistsPlaylists, insights, library, user, maintenance, manager, imageUploadService)
|
||||
router := nativeapi.New(dataStore, share, playlistsPlaylists, insights, library, user, maintenance, manager, uploader)
|
||||
return router
|
||||
}
|
||||
|
||||
@ -98,8 +98,8 @@ func CreateSubsonicAPIRouter(ctx context.Context) *subsonic.Router {
|
||||
agentsAgents := agents.GetAgents(dataStore, manager)
|
||||
matcherMatcher := matcher.New(dataStore)
|
||||
provider := external.NewProvider(dataStore, agentsAgents, matcherMatcher)
|
||||
imageUploadService := core.NewImageUploadService(dataStore)
|
||||
playlistsPlaylists := playlists.NewPlaylists(dataStore, imageUploadService)
|
||||
uploader := artwork.NewUploader(dataStore)
|
||||
playlistsPlaylists := playlists.NewPlaylists(dataStore, uploader)
|
||||
modelScanner := scanner.New(ctx, dataStore, broker, playlistsPlaylists, metricsMetrics)
|
||||
playTracker := scrobbler.GetPlayTracker(dataStore, broker, manager)
|
||||
playbackServer := playback.GetInstance(dataStore)
|
||||
@ -125,8 +125,8 @@ func CreateJellyfinAPIRouter(ctx context.Context) *jellyfin.Router {
|
||||
metricsMetrics := metrics.GetPrometheusInstance(dataStore)
|
||||
manager := plugins.GetManager(dataStore, broker, metricsMetrics)
|
||||
playTracker := scrobbler.GetPlayTracker(dataStore, broker, manager)
|
||||
imageUploadService := core.NewImageUploadService(dataStore)
|
||||
playlistsPlaylists := playlists.NewPlaylists(dataStore, imageUploadService)
|
||||
uploader := artwork.NewUploader(dataStore)
|
||||
playlistsPlaylists := playlists.NewPlaylists(dataStore, uploader)
|
||||
agentsAgents := agents.GetAgents(dataStore, manager)
|
||||
matcherMatcher := matcher.New(dataStore)
|
||||
provider := external.NewProvider(dataStore, agentsAgents, matcherMatcher)
|
||||
@ -183,8 +183,8 @@ func CreateScanner(ctx context.Context) model.Scanner {
|
||||
sqlDB := db.Db()
|
||||
dataStore := persistence.New(sqlDB)
|
||||
broker := events.GetBroker()
|
||||
imageUploadService := core.NewImageUploadService(dataStore)
|
||||
playlistsPlaylists := playlists.NewPlaylists(dataStore, imageUploadService)
|
||||
uploader := artwork.NewUploader(dataStore)
|
||||
playlistsPlaylists := playlists.NewPlaylists(dataStore, uploader)
|
||||
metricsMetrics := metrics.GetPrometheusInstance(dataStore)
|
||||
modelScanner := scanner.New(ctx, dataStore, broker, playlistsPlaylists, metricsMetrics)
|
||||
return modelScanner
|
||||
@ -194,8 +194,8 @@ func CreateScanWatcher(ctx context.Context) scanner.Watcher {
|
||||
sqlDB := db.Db()
|
||||
dataStore := persistence.New(sqlDB)
|
||||
broker := events.GetBroker()
|
||||
imageUploadService := core.NewImageUploadService(dataStore)
|
||||
playlistsPlaylists := playlists.NewPlaylists(dataStore, imageUploadService)
|
||||
uploader := artwork.NewUploader(dataStore)
|
||||
playlistsPlaylists := playlists.NewPlaylists(dataStore, uploader)
|
||||
metricsMetrics := metrics.GetPrometheusInstance(dataStore)
|
||||
modelScanner := scanner.New(ctx, dataStore, broker, playlistsPlaylists, metricsMetrics)
|
||||
watcher := scanner.GetWatcher(dataStore, modelScanner)
|
||||
@ -234,7 +234,7 @@ func getPluginManager() *plugins.Manager {
|
||||
|
||||
// wire_injectors.go:
|
||||
|
||||
var allProviders = wire.NewSet(core.Set, artwork.Set, server.New, subsonic.New, jellyfin.New, nativeapi.New, public.New, persistence.New, lastfm.NewRouter, listenbrainz.NewRouter, events.GetBroker, scanner.New, scanner.GetWatcher, metrics.GetPrometheusInstance, db.Db, plugins.GetManager, sonic.New, wire.Bind(new(agents.PluginLoader), new(*plugins.Manager)), wire.Bind(new(scrobbler.PluginLoader), new(*plugins.Manager)), wire.Bind(new(lyrics.PluginLoader), new(*plugins.Manager)), wire.Bind(new(sonic.PluginLoader), new(*plugins.Manager)), wire.Bind(new(sonic.Engine), new(*sonic.Sonic)), wire.Bind(new(nativeapi.PluginManager), new(*plugins.Manager)), wire.Bind(new(core.PluginUnloader), new(*plugins.Manager)), wire.Bind(new(plugins.PluginMetricsRecorder), new(metrics.Metrics)), wire.Bind(new(core.Watcher), new(scanner.Watcher)))
|
||||
var allProviders = wire.NewSet(core.Set, artwork.Set, server.New, subsonic.New, jellyfin.New, nativeapi.New, public.New, persistence.New, lastfm.NewRouter, listenbrainz.NewRouter, events.GetBroker, scanner.New, scanner.GetWatcher, metrics.GetPrometheusInstance, db.Db, plugins.GetManager, sonic.New, wire.Bind(new(agents.PluginLoader), new(*plugins.Manager)), wire.Bind(new(scrobbler.PluginLoader), new(*plugins.Manager)), wire.Bind(new(lyrics.PluginLoader), new(*plugins.Manager)), wire.Bind(new(sonic.PluginLoader), new(*plugins.Manager)), wire.Bind(new(sonic.Engine), new(*sonic.Sonic)), wire.Bind(new(nativeapi.PluginManager), new(*plugins.Manager)), wire.Bind(new(core.PluginUnloader), new(*plugins.Manager)), wire.Bind(new(plugins.PluginMetricsRecorder), new(metrics.Metrics)), wire.Bind(new(core.Watcher), new(scanner.Watcher)), wire.Bind(new(playlists.ImageUploadService), new(artwork.Uploader)))
|
||||
|
||||
func GetPluginManager(ctx context.Context) *plugins.Manager {
|
||||
manager := getPluginManager()
|
||||
|
||||
@ -14,6 +14,7 @@ import (
|
||||
"github.com/navidrome/navidrome/core/lyrics"
|
||||
"github.com/navidrome/navidrome/core/metrics"
|
||||
"github.com/navidrome/navidrome/core/playback"
|
||||
"github.com/navidrome/navidrome/core/playlists"
|
||||
"github.com/navidrome/navidrome/core/scrobbler"
|
||||
"github.com/navidrome/navidrome/core/sonic"
|
||||
"github.com/navidrome/navidrome/db"
|
||||
@ -56,6 +57,7 @@ var allProviders = wire.NewSet(
|
||||
wire.Bind(new(core.PluginUnloader), new(*plugins.Manager)),
|
||||
wire.Bind(new(plugins.PluginMetricsRecorder), new(metrics.Metrics)),
|
||||
wire.Bind(new(core.Watcher), new(scanner.Watcher)),
|
||||
wire.Bind(new(playlists.ImageUploadService), new(artwork.Uploader)),
|
||||
)
|
||||
|
||||
func CreateDataStore() model.DataStore {
|
||||
|
||||
21
core/artwork/refresh.go
Normal file
21
core/artwork/refresh.go
Normal file
@ -0,0 +1,21 @@
|
||||
package artwork
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/navidrome/navidrome/model"
|
||||
)
|
||||
|
||||
// Refresh clears an item's resolved artwork state and re-queues it at Bump priority, so a
|
||||
// deliberate refresh (image upload or manual re-resolve) drops the current pick and re-resolves it.
|
||||
func Refresh(ctx context.Context, ds model.DataStore, kind, id string) error {
|
||||
if err := ds.Artwork(ctx).DeleteForItem(kind, id); err != nil {
|
||||
return fmt.Errorf("clearing artwork state: %w", err)
|
||||
}
|
||||
item := model.ArtworkQueueItem{ItemKind: kind, ItemID: id, ImageType: model.ImageTypePrimary, Priority: model.ArtworkPriorityBump}
|
||||
if err := ds.ArtworkQueue(ctx).Enqueue(item); err != nil {
|
||||
return fmt.Errorf("enqueuing artwork refresh: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package core
|
||||
package artwork
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -15,14 +15,6 @@ import (
|
||||
"github.com/navidrome/navidrome/utils"
|
||||
)
|
||||
|
||||
type ImageUploadService interface {
|
||||
SetImage(ctx context.Context, entityType string, entityID string, name string, oldPath string, reader io.Reader, ext string) (filename string, err error)
|
||||
RemoveImage(ctx context.Context, path string) error
|
||||
// EnqueueArtwork clears an item's resolved state and re-queues it at Bump priority. Callers
|
||||
// must invoke it AFTER persisting the new filename, so the worker never resolves the old one.
|
||||
EnqueueArtwork(ctx context.Context, entityType, entityID string)
|
||||
}
|
||||
|
||||
// MaxImageUploadSize returns the configured MaxImageUploadSize in bytes, or the built-in default
|
||||
// when it's unset/invalid. Shared by every API that accepts image uploads.
|
||||
func MaxImageUploadSize() int64 {
|
||||
@ -33,6 +25,16 @@ func MaxImageUploadSize() int64 {
|
||||
return int64(size)
|
||||
}
|
||||
|
||||
// Uploader stores a user-uploaded entity image and invalidates that entity's artwork state so the
|
||||
// upload becomes the served cover.
|
||||
type Uploader interface {
|
||||
SetImage(ctx context.Context, entityType string, entityID string, name string, oldPath string, reader io.Reader, ext string) (filename string, err error)
|
||||
RemoveImage(ctx context.Context, path string) error
|
||||
// EnqueueArtwork clears an item's resolved state and re-queues it at Bump priority. Callers
|
||||
// must invoke it AFTER persisting the new filename, so the worker never resolves the old one.
|
||||
EnqueueArtwork(ctx context.Context, entityType, entityID string)
|
||||
}
|
||||
|
||||
// uploadEntityKind maps an upload's entity type to its artwork kind prefix, so a
|
||||
// successful upload can clear and re-queue that item's artwork state.
|
||||
var uploadEntityKind = map[string]string{
|
||||
@ -41,15 +43,15 @@ var uploadEntityKind = map[string]string{
|
||||
consts.EntityRadio: model.KindRadioArtwork.Prefix(),
|
||||
}
|
||||
|
||||
type imageUploadService struct {
|
||||
type uploader struct {
|
||||
ds model.DataStore
|
||||
}
|
||||
|
||||
func NewImageUploadService(ds model.DataStore) ImageUploadService {
|
||||
return &imageUploadService{ds: ds}
|
||||
func NewUploader(ds model.DataStore) Uploader {
|
||||
return &uploader{ds: ds}
|
||||
}
|
||||
|
||||
func (s *imageUploadService) SetImage(ctx context.Context, entityType string, entityID string, name string, oldPath string, reader io.Reader, ext string) (string, error) {
|
||||
func (s *uploader) SetImage(ctx context.Context, entityType string, entityID string, name string, oldPath string, reader io.Reader, ext string) (string, error) {
|
||||
filename := imageFilename(entityID, name, ext)
|
||||
absPath := model.UploadedImagePath(entityType, filename)
|
||||
|
||||
@ -79,22 +81,17 @@ func (s *imageUploadService) SetImage(ctx context.Context, entityType string, en
|
||||
|
||||
// EnqueueArtwork clears the item's resolved state and re-queues it at Bump priority: the
|
||||
// upload is now the top-priority source, so the worker re-resolves and the UI swaps.
|
||||
func (s *imageUploadService) EnqueueArtwork(ctx context.Context, entityType, id string) {
|
||||
func (s *uploader) EnqueueArtwork(ctx context.Context, entityType, id string) {
|
||||
kind, ok := uploadEntityKind[entityType]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := s.ds.Artwork(ctx).DeleteForItem(kind, id); err != nil {
|
||||
log.Warn(ctx, "Could not clear artwork state after upload", "kind", kind, "id", id, err)
|
||||
}
|
||||
item := model.ArtworkQueueItem{ItemKind: kind, ItemID: id, ImageType: model.ImageTypePrimary,
|
||||
Priority: model.ArtworkPriorityBump}
|
||||
if err := s.ds.ArtworkQueue(ctx).Enqueue(item); err != nil {
|
||||
log.Warn(ctx, "Could not enqueue artwork after upload", "kind", kind, "id", id, err)
|
||||
if err := Refresh(ctx, s.ds, kind, id); err != nil {
|
||||
log.Warn(ctx, "Could not refresh artwork after upload", "kind", kind, "id", id, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *imageUploadService) RemoveImage(ctx context.Context, path string) error {
|
||||
func (s *uploader) RemoveImage(ctx context.Context, path string) error {
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package core_test
|
||||
package artwork
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -9,15 +9,14 @@ import (
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/conf/configtest"
|
||||
"github.com/navidrome/navidrome/consts"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("ImageUploadService", func() {
|
||||
var svc core.ImageUploadService
|
||||
var _ = Describe("Uploader", func() {
|
||||
var svc Uploader
|
||||
var tmpDir string
|
||||
var artRepo *tests.MockArtworkRepo
|
||||
var queueRepo *tests.MockArtworkQueueRepo
|
||||
@ -29,7 +28,7 @@ var _ = Describe("ImageUploadService", func() {
|
||||
artRepo = tests.CreateMockArtworkRepo()
|
||||
queueRepo = tests.CreateMockArtworkQueueRepo()
|
||||
ds := &tests.MockDataStore{MockedArtwork: artRepo, MockedArtworkQueue: queueRepo}
|
||||
svc = core.NewImageUploadService(ds)
|
||||
svc = NewUploader(ds)
|
||||
})
|
||||
|
||||
Describe("SetImage", func() {
|
||||
@ -155,21 +154,21 @@ var _ = Describe("MaxImageUploadSize", func() {
|
||||
|
||||
It("returns the configured size when valid", func() {
|
||||
conf.Server.MaxImageUploadSize = "20MB"
|
||||
Expect(core.MaxImageUploadSize()).To(Equal(int64(20_000_000)))
|
||||
Expect(MaxImageUploadSize()).To(Equal(int64(20_000_000)))
|
||||
})
|
||||
|
||||
It("returns the default size when config is empty", func() {
|
||||
conf.Server.MaxImageUploadSize = ""
|
||||
Expect(core.MaxImageUploadSize()).To(Equal(int64(10_000_000)))
|
||||
Expect(MaxImageUploadSize()).To(Equal(int64(10_000_000)))
|
||||
})
|
||||
|
||||
It("returns the default size when config is invalid", func() {
|
||||
conf.Server.MaxImageUploadSize = "not-a-size"
|
||||
Expect(core.MaxImageUploadSize()).To(Equal(int64(10_000_000)))
|
||||
Expect(MaxImageUploadSize()).To(Equal(int64(10_000_000)))
|
||||
})
|
||||
|
||||
It("parses raw byte values", func() {
|
||||
conf.Server.MaxImageUploadSize = "52428800"
|
||||
Expect(core.MaxImageUploadSize()).To(Equal(int64(52_428_800)))
|
||||
Expect(MaxImageUploadSize()).To(Equal(int64(52_428_800)))
|
||||
})
|
||||
})
|
||||
@ -9,4 +9,5 @@ var Set = wire.NewSet(
|
||||
GetImageCache,
|
||||
NewWorker,
|
||||
ProvideImageStore,
|
||||
NewUploader,
|
||||
)
|
||||
|
||||
@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/conf/configtest"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/core/playlists"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/criteria"
|
||||
@ -43,7 +43,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
var folder *model.Folder
|
||||
BeforeEach(func() {
|
||||
DeferCleanup(configtest.SetupConfig())
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
ds.MockedMediaFile = &mockedMediaFileRepo{}
|
||||
libPath, _ := os.Getwd()
|
||||
// Set up library with the actual library path that matches the folder
|
||||
@ -118,7 +118,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
|
||||
mockLibRepo.SetData([]model.Library{{ID: 1, Path: tmpDir}})
|
||||
ds.MockedMediaFile = &mockedMediaFileFromListRepo{data: []string{"test.mp3", "test.ogg"}}
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
plsFolder := &model.Folder{ID: "1", LibraryID: 1, LibraryPath: tmpDir, Path: "", Name: ""}
|
||||
pls, err := ps.ImportFromFolder(ctx, plsFolder, "test.m3u")
|
||||
@ -136,7 +136,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
|
||||
mockLibRepo.SetData([]model.Library{{ID: 1, Path: tmpDir}})
|
||||
ds.MockedMediaFile = &mockedMediaFileFromListRepo{data: []string{"test.mp3"}}
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
plsFolder := &model.Folder{ID: "1", LibraryID: 1, LibraryPath: tmpDir, Path: "", Name: ""}
|
||||
pls, err := ps.ImportFromFolder(ctx, plsFolder, "test.m3u")
|
||||
@ -155,7 +155,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
|
||||
mockLibRepo.SetData([]model.Library{{ID: 1, Path: tmpDir}})
|
||||
ds.MockedMediaFile = &mockedMediaFileFromListRepo{data: []string{"test.mp3"}}
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
plsFolder := &model.Folder{ID: "1", LibraryID: 1, LibraryPath: tmpDir, Path: "", Name: ""}
|
||||
pls, err := ps.ImportFromFolder(ctx, plsFolder, "test.m3u")
|
||||
@ -174,7 +174,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
|
||||
mockLibRepo.SetData([]model.Library{{ID: 1, Path: tmpDir}})
|
||||
ds.MockedMediaFile = &mockedMediaFileFromListRepo{data: []string{"test.mp3"}}
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
plsFolder := &model.Folder{ID: "1", LibraryID: 1, LibraryPath: tmpDir, Path: "", Name: ""}
|
||||
pls, err := ps.ImportFromFolder(ctx, plsFolder, "test.m3u")
|
||||
@ -192,7 +192,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
|
||||
mockLibRepo.SetData([]model.Library{{ID: 1, Path: tmpDir}})
|
||||
ds.MockedMediaFile = &mockedMediaFileFromListRepo{data: []string{"test.mp3"}}
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
plsFolder := &model.Folder{ID: "1", LibraryID: 1, LibraryPath: tmpDir, Path: "", Name: ""}
|
||||
pls, err := ps.ImportFromFolder(ctx, plsFolder, "test.m3u")
|
||||
@ -209,7 +209,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
|
||||
mockLibRepo.SetData([]model.Library{{ID: 1, Path: tmpDir}})
|
||||
ds.MockedMediaFile = &mockedMediaFileFromListRepo{data: []string{"test.mp3"}}
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
plsFolder := &model.Folder{ID: "1", LibraryID: 1, LibraryPath: tmpDir, Path: "", Name: ""}
|
||||
pls, err := ps.ImportFromFolder(ctx, plsFolder, "test.m3u")
|
||||
@ -226,7 +226,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
|
||||
mockLibRepo.SetData([]model.Library{{ID: 1, Path: tmpDir}})
|
||||
ds.MockedMediaFile = &mockedMediaFileFromListRepo{data: []string{"test.mp3"}}
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
plsFolder := &model.Folder{ID: "1", LibraryID: 1, LibraryPath: tmpDir, Path: "", Name: ""}
|
||||
pls, err := ps.ImportFromFolder(ctx, plsFolder, "test.m3u")
|
||||
@ -244,7 +244,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
|
||||
mockLibRepo.SetData([]model.Library{{ID: 1, Path: tmpDir}})
|
||||
ds.MockedMediaFile = &mockedMediaFileFromListRepo{data: []string{"test.mp3"}}
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
plsFolder := &model.Folder{ID: "1", LibraryID: 1, LibraryPath: tmpDir, Path: "", Name: ""}
|
||||
pls, err := ps.ImportFromFolder(ctx, plsFolder, "test.m3u")
|
||||
@ -258,7 +258,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
tmpDir := GinkgoT().TempDir()
|
||||
mockLibRepo.SetData([]model.Library{{ID: 1, Path: tmpDir}})
|
||||
ds.MockedMediaFile = &mockedMediaFileFromListRepo{data: []string{"test.mp3"}}
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
m3u := "#EXTALBUMARTURL:https://example.com/new-cover.jpg\ntest.mp3\n"
|
||||
plsFile := filepath.Join(tmpDir, "test.m3u")
|
||||
@ -285,7 +285,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
tmpDir := GinkgoT().TempDir()
|
||||
mockLibRepo.SetData([]model.Library{{ID: 1, Path: tmpDir}})
|
||||
ds.MockedMediaFile = &mockedMediaFileFromListRepo{data: []string{"test.mp3"}}
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
plsFile := filepath.Join(tmpDir, "test.m3u")
|
||||
Expect(os.WriteFile(plsFile, []byte("test.mp3\n"), 0600)).To(Succeed())
|
||||
@ -311,7 +311,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
tmpDir := GinkgoT().TempDir()
|
||||
mockLibRepo.SetData([]model.Library{{ID: 1, Path: tmpDir}})
|
||||
ds.MockedMediaFile = &mockedMediaFileFromListRepo{data: []string{"test.mp3"}}
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
m3u := "test.mp3\n"
|
||||
plsFile := filepath.Join(tmpDir, "test.m3u")
|
||||
@ -388,7 +388,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
tmpDir := GinkgoT().TempDir()
|
||||
mockLibRepo.SetData([]model.Library{{ID: 1, Path: tmpDir}})
|
||||
ds.MockedMediaFile = &mockedMediaFileFromListRepo{data: []string{}}
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
// Create the playlist file on disk with the filesystem's normalization form
|
||||
plsFile := tmpDir + "/" + filesystemName + ".m3u"
|
||||
@ -448,7 +448,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
"def.mp3", // This is playlists/def.mp3 relative to plsDir
|
||||
},
|
||||
}
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
})
|
||||
|
||||
It("handles relative paths that reference files in other libraries", func() {
|
||||
@ -604,7 +604,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
},
|
||||
}
|
||||
// Recreate playlists service to pick up new mock
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
// Create playlist in music library that references both tracks
|
||||
plsContent := "#PLAYLIST:Same Path Test\nalbum/track.mp3\n../classical/album/track.mp3"
|
||||
@ -662,7 +662,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
},
|
||||
}
|
||||
ds.MockedFolder = mockFolderRepo
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
plsContent := "#PLAYLIST:My Playlist\ntest.mp3\ntest.ogg\n"
|
||||
plsFile := filepath.Join(tmpDir, "my-playlist.m3u")
|
||||
@ -681,7 +681,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
libDir := filepath.Join(tmpDir, "music")
|
||||
Expect(os.Mkdir(libDir, 0755)).To(Succeed())
|
||||
mockLibRepo.SetData([]model.Library{{ID: 1, Path: libDir}})
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
plsContent := "#PLAYLIST:External Playlist\n" + libDir + "/test.mp3\n"
|
||||
plsFile := filepath.Join(tmpDir, "external.m3u")
|
||||
@ -704,7 +704,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
},
|
||||
}
|
||||
ds.MockedFolder = mockFolderRepo
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
plsFile := filepath.Join(tmpDir, "test.m3u")
|
||||
Expect(os.WriteFile(plsFile, []byte("test.mp3\n"), 0600)).To(Succeed())
|
||||
@ -724,7 +724,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
},
|
||||
}
|
||||
ds.MockedFolder = mockFolderRepo
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
plsFile := filepath.Join(tmpDir, "test.m3u")
|
||||
Expect(os.WriteFile(plsFile, []byte("test.mp3\n"), 0600)).To(Succeed())
|
||||
@ -744,7 +744,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
},
|
||||
}
|
||||
ds.MockedFolder = mockFolderRepo
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
|
||||
plsFile := filepath.Join(tmpDir, "test.m3u")
|
||||
Expect(os.WriteFile(plsFile, []byte("test.mp3\n"), 0600)).To(Succeed())
|
||||
@ -767,7 +767,7 @@ var _ = Describe("Playlists - Import", func() {
|
||||
BeforeEach(func() {
|
||||
repo = &mockedMediaFileFromListRepo{}
|
||||
ds.MockedMediaFile = repo
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
mockLibRepo.SetData([]model.Library{{ID: 1, Path: "/music"}, {ID: 2, Path: "/new"}})
|
||||
ctx = request.WithUser(ctx, model.User{ID: "123"})
|
||||
})
|
||||
|
||||
@ -52,8 +52,8 @@ type Playlists interface {
|
||||
TracksRepository(ctx context.Context, playlistId string, refreshSmartPlaylist bool) rest.Repository
|
||||
}
|
||||
|
||||
// ImageUploadService is a local interface satisfied by core.ImageUploadService.
|
||||
// Defined here to avoid an import cycle between core and core/playlists.
|
||||
// ImageUploadService is a local interface satisfied by artwork.Uploader.
|
||||
// Defined here to avoid an import cycle between core/artwork and core/playlists.
|
||||
type ImageUploadService interface {
|
||||
SetImage(ctx context.Context, entityType string, entityID string, name string, oldPath string, reader io.Reader, ext string) (filename string, err error)
|
||||
RemoveImage(ctx context.Context, path string) error
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/conf/configtest"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/core/playlists"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/criteria"
|
||||
@ -42,7 +42,7 @@ var _ = Describe("Playlists", func() {
|
||||
"pls-1": {ID: "pls-1", Name: "My Playlist", OwnerID: "user-1"},
|
||||
}
|
||||
mockPlsRepo.TracksRepo = mockTracks
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
})
|
||||
|
||||
It("allows owner to delete their playlist", func() {
|
||||
@ -82,7 +82,7 @@ var _ = Describe("Playlists", func() {
|
||||
"pls-1": {ID: "pls-1", Name: "My Playlist", OwnerID: "user-1"},
|
||||
}
|
||||
mockPlsRepo.TracksRepo = mockTracks
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
})
|
||||
|
||||
It("returns the playlist's track repository", func() {
|
||||
@ -103,7 +103,7 @@ var _ = Describe("Playlists", func() {
|
||||
"pls-smart": {ID: "pls-smart", Name: "Smart", OwnerID: "user-1",
|
||||
Rules: &criteria.Criteria{Expression: criteria.Contains{"title": "test"}}},
|
||||
}
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
})
|
||||
|
||||
It("creates a new playlist with owner set from context", func() {
|
||||
@ -161,7 +161,7 @@ var _ = Describe("Playlists", func() {
|
||||
Rules: &criteria.Criteria{Expression: criteria.Contains{"title": "test"}}},
|
||||
}
|
||||
mockPlsRepo.TracksRepo = mockTracks
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
})
|
||||
|
||||
It("allows owner to update their playlist", func() {
|
||||
@ -219,7 +219,7 @@ var _ = Describe("Playlists", func() {
|
||||
"pls-other": {ID: "pls-other", Name: "Other's", OwnerID: "other-user"},
|
||||
}
|
||||
mockPlsRepo.TracksRepo = mockTracks
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
})
|
||||
|
||||
It("allows owner to add tracks", func() {
|
||||
@ -267,7 +267,7 @@ var _ = Describe("Playlists", func() {
|
||||
Rules: &criteria.Criteria{Expression: criteria.Contains{"title": "test"}}},
|
||||
}
|
||||
mockPlsRepo.TracksRepo = mockTracks
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
})
|
||||
|
||||
It("allows owner to remove tracks", func() {
|
||||
@ -301,7 +301,7 @@ var _ = Describe("Playlists", func() {
|
||||
Rules: &criteria.Criteria{Expression: criteria.Contains{"title": "test"}}},
|
||||
}
|
||||
mockPlsRepo.TracksRepo = mockTracks
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
})
|
||||
|
||||
It("allows owner to reorder", func() {
|
||||
@ -330,7 +330,7 @@ var _ = Describe("Playlists", func() {
|
||||
"pls-1": {ID: "pls-1", Name: "My Playlist", OwnerID: "user-1"},
|
||||
"pls-other": {ID: "pls-other", Name: "Other's", OwnerID: "other-user"},
|
||||
}
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
})
|
||||
|
||||
It("saves image file and updates UploadedImage", func() {
|
||||
@ -400,7 +400,7 @@ var _ = Describe("Playlists", func() {
|
||||
"pls-empty": {ID: "pls-empty", Name: "No Cover", OwnerID: "user-1"},
|
||||
"pls-other": {ID: "pls-other", Name: "Other's", OwnerID: "other-user"},
|
||||
}
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
})
|
||||
|
||||
It("removes file and clears UploadedImage", func() {
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/deluan/rest"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/core/playlists"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/criteria"
|
||||
@ -37,7 +37,7 @@ var _ = Describe("REST Adapter", func() {
|
||||
mockPlsRepo.Data = map[string]*model.Playlist{
|
||||
"pls-1": {ID: "pls-1", Name: "My Playlist", OwnerID: "user-1"},
|
||||
}
|
||||
ps = playlists.NewPlaylists(ds, core.NewImageUploadService(ds))
|
||||
ps = playlists.NewPlaylists(ds, artwork.NewUploader(ds))
|
||||
})
|
||||
|
||||
Describe("Save", func() {
|
||||
|
||||
@ -24,8 +24,6 @@ var Set = wire.NewSet(
|
||||
NewLibrary,
|
||||
NewUser,
|
||||
NewMaintenance,
|
||||
NewImageUploadService,
|
||||
wire.Bind(new(playlists.ImageUploadService), new(ImageUploadService)),
|
||||
stream.NewTranscodeDecider,
|
||||
agents.GetAgents,
|
||||
external.NewProvider,
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/conf/configtest"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/core/metrics"
|
||||
"github.com/navidrome/navidrome/core/playlists"
|
||||
"github.com/navidrome/navidrome/core/storage/storagetest"
|
||||
@ -275,7 +275,7 @@ var _ = BeforeSuite(func() {
|
||||
|
||||
buildTestFS()
|
||||
s := scanner.New(ctx, initDS, events.NoopBroker(),
|
||||
playlists.NewPlaylists(initDS, core.NewImageUploadService(initDS)), metrics.NewNoopInstance())
|
||||
playlists.NewPlaylists(initDS, artwork.NewUploader(initDS)), metrics.NewNoopInstance())
|
||||
_, err = s.ScanAll(ctx, true)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/navidrome/navidrome/conf/configtest"
|
||||
"github.com/navidrome/navidrome/consts"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/core/metrics"
|
||||
"github.com/navidrome/navidrome/core/playlists"
|
||||
"github.com/navidrome/navidrome/db"
|
||||
@ -31,7 +31,7 @@ var _ = Describe("Controller", func() {
|
||||
DeferCleanup(configtest.SetupConfig())
|
||||
ds = &tests.MockDataStore{RealDS: persistence.New(db.Db())}
|
||||
ds.MockedProperty = &tests.MockedPropertyRepo{}
|
||||
ctrl = scanner.New(ctx, ds, events.NoopBroker(), playlists.NewPlaylists(ds, core.NewImageUploadService(ds)), metrics.NewNoopInstance())
|
||||
ctrl = scanner.New(ctx, ds, events.NoopBroker(), playlists.NewPlaylists(ds, artwork.NewUploader(ds)), metrics.NewNoopInstance())
|
||||
})
|
||||
|
||||
It("includes last scan error", func() {
|
||||
|
||||
@ -12,7 +12,7 @@ import (
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/google/uuid"
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/core/metrics"
|
||||
"github.com/navidrome/navidrome/core/playlists"
|
||||
"github.com/navidrome/navidrome/core/storage/storagetest"
|
||||
@ -40,7 +40,7 @@ func BenchmarkScan(b *testing.B) {
|
||||
ds := persistence.New(db.Db())
|
||||
conf.Server.DevExternalScanner = false
|
||||
s := scanner.New(context.Background(), ds, events.NoopBroker(),
|
||||
playlists.NewPlaylists(ds, core.NewImageUploadService(ds)), metrics.NewNoopInstance())
|
||||
playlists.NewPlaylists(ds, artwork.NewUploader(ds)), metrics.NewNoopInstance())
|
||||
|
||||
fs := storagetest.FakeFS{}
|
||||
storagetest.Register("fake", &fs)
|
||||
|
||||
@ -11,7 +11,7 @@ import (
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/conf/configtest"
|
||||
"github.com/navidrome/navidrome/consts"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/core/metrics"
|
||||
"github.com/navidrome/navidrome/core/playlists"
|
||||
"github.com/navidrome/navidrome/core/storage/storagetest"
|
||||
@ -78,7 +78,7 @@ var _ = Describe("Scanner - Multi-Library", Ordered, func() {
|
||||
Expect(ds.User(ctx).Put(&adminUser)).To(Succeed())
|
||||
|
||||
s = scanner.New(ctx, ds, events.NoopBroker(),
|
||||
playlists.NewPlaylists(ds, core.NewImageUploadService(ds)), metrics.NewNoopInstance())
|
||||
playlists.NewPlaylists(ds, artwork.NewUploader(ds)), metrics.NewNoopInstance())
|
||||
|
||||
// Create two test libraries (let DB auto-assign IDs)
|
||||
lib1 = model.Library{Name: "Rock Collection", Path: "rock:///music"}
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/conf/configtest"
|
||||
"github.com/navidrome/navidrome/consts"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/core/metrics"
|
||||
"github.com/navidrome/navidrome/core/playlists"
|
||||
"github.com/navidrome/navidrome/core/storage/storagetest"
|
||||
@ -66,7 +66,7 @@ var _ = Describe("ScanFolders", Ordered, func() {
|
||||
Expect(ds.User(ctx).Put(&adminUser)).To(Succeed())
|
||||
|
||||
s = scanner.New(ctx, ds, events.NoopBroker(),
|
||||
playlists.NewPlaylists(ds, core.NewImageUploadService(ds)), metrics.NewNoopInstance())
|
||||
playlists.NewPlaylists(ds, artwork.NewUploader(ds)), metrics.NewNoopInstance())
|
||||
|
||||
lib = model.Library{ID: 1, Name: "Fake Library", Path: "fake:///music"}
|
||||
Expect(ds.Library(ctx).Put(&lib)).To(Succeed())
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/conf/configtest"
|
||||
"github.com/navidrome/navidrome/consts"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/core/metrics"
|
||||
"github.com/navidrome/navidrome/core/playlists"
|
||||
"github.com/navidrome/navidrome/core/storage/storagetest"
|
||||
@ -86,7 +86,7 @@ var _ = Describe("Scanner", Ordered, func() {
|
||||
Expect(ds.User(ctx).Put(&adminUser)).To(Succeed())
|
||||
|
||||
s = scanner.New(ctx, ds, events.NoopBroker(),
|
||||
playlists.NewPlaylists(ds, core.NewImageUploadService(ds)), metrics.NewNoopInstance())
|
||||
playlists.NewPlaylists(ds, artwork.NewUploader(ds)), metrics.NewNoopInstance())
|
||||
|
||||
lib = model.Library{ID: 1, Name: "Fake Library", Path: "fake:///music"}
|
||||
Expect(ds.Library(ctx).Put(&lib)).To(Succeed())
|
||||
|
||||
@ -326,7 +326,7 @@ func setupTestDB() {
|
||||
decider,
|
||||
core.NewPlayers(ds),
|
||||
scrobbler.NewPlayTracker(ds, events.NoopBroker(), nil),
|
||||
playlists.NewPlaylists(ds, core.NewImageUploadService(ds)),
|
||||
playlists.NewPlaylists(ds, artwork.NewUploader(ds)),
|
||||
providerFake,
|
||||
sonicSvc,
|
||||
lyrics.NewLyrics(ds, nil),
|
||||
|
||||
@ -16,7 +16,7 @@ import (
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
@ -98,7 +98,7 @@ func (api *Router) postItemImage(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
// The limit caps the decoded image (native endpoint semantics); Jellyfin clients base64-encode
|
||||
// the wire body (4/3 bigger), so the read cap allows for inflation.
|
||||
limit := core.MaxImageUploadSize()
|
||||
limit := artwork.MaxImageUploadSize()
|
||||
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, limit*4/3+4))
|
||||
if err != nil {
|
||||
log.Warn(ctx, "Jellyfin API: cover upload rejected: body exceeds MaxImageUploadSize",
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
)
|
||||
@ -32,15 +33,8 @@ func (api *Router) refreshArtwork() http.HandlerFunc {
|
||||
http.Error(w, "invalid artwork kind", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if err := api.ds.Artwork(ctx).DeleteForItem(kind, id); err != nil {
|
||||
log.Error(ctx, "Error clearing artwork state", "kind", kind, "id", id, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
item := model.ArtworkQueueItem{ItemKind: kind, ItemID: id, ImageType: model.ImageTypePrimary,
|
||||
Priority: model.ArtworkPriorityBump}
|
||||
if err := api.ds.ArtworkQueue(ctx).Enqueue(item); err != nil {
|
||||
log.Error(ctx, "Error enqueuing artwork refresh", "kind", kind, "id", id, err)
|
||||
if err := artwork.Refresh(ctx, api.ds, kind, id); err != nil {
|
||||
log.Error(ctx, "Error refreshing artwork", "kind", kind, "id", id, err)
|
||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
@ -31,7 +31,7 @@ func checkImageUploadPermission(w http.ResponseWriter, r *http.Request) bool {
|
||||
}
|
||||
|
||||
func handleImageUpload(saveFn func(ctx context.Context, reader io.Reader, ext string) error) http.HandlerFunc {
|
||||
maxImageSize := core.MaxImageUploadSize()
|
||||
maxImageSize := artwork.MaxImageUploadSize()
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
if !checkImageUploadPermission(w, r) {
|
||||
|
||||
@ -13,6 +13,7 @@ import (
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/core/metrics"
|
||||
playlistsvc "github.com/navidrome/navidrome/core/playlists"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
@ -44,10 +45,10 @@ type Router struct {
|
||||
users core.User
|
||||
maintenance core.Maintenance
|
||||
pluginManager PluginManager
|
||||
imgUpload core.ImageUploadService
|
||||
imgUpload artwork.Uploader
|
||||
}
|
||||
|
||||
func New(ds model.DataStore, share core.Share, playlists playlistsvc.Playlists, insights metrics.Insights, libraryService core.Library, userService core.User, maintenance core.Maintenance, pluginManager PluginManager, imgUpload core.ImageUploadService) *Router {
|
||||
func New(ds model.DataStore, share core.Share, playlists playlistsvc.Playlists, insights metrics.Insights, libraryService core.Library, userService core.User, maintenance core.Maintenance, pluginManager PluginManager, imgUpload artwork.Uploader) *Router {
|
||||
r := &Router{ds: ds, share: share, playlists: playlists, insights: insights, libs: libraryService, users: userService, maintenance: maintenance, pluginManager: pluginManager, imgUpload: imgUpload}
|
||||
r.Handler = r.routes()
|
||||
return r
|
||||
|
||||
@ -409,7 +409,7 @@ func setupTestDB() {
|
||||
streamerSpy = &harness.SpyStreamer{}
|
||||
decider := stream.NewTranscodeDecider(ds, harness.NoopFFmpeg{})
|
||||
s := scanner.New(ctx, ds, events.NoopBroker(),
|
||||
playlists.NewPlaylists(ds, core.NewImageUploadService(ds)), metrics.NewNoopInstance())
|
||||
playlists.NewPlaylists(ds, artwork.NewUploader(ds)), metrics.NewNoopInstance())
|
||||
router = subsonic.New(
|
||||
ds,
|
||||
noopArtwork{},
|
||||
@ -419,7 +419,7 @@ func setupTestDB() {
|
||||
noopProvider{},
|
||||
s,
|
||||
events.NoopBroker(),
|
||||
playlists.NewPlaylists(ds, core.NewImageUploadService(ds)),
|
||||
playlists.NewPlaylists(ds, artwork.NewUploader(ds)),
|
||||
scrobbler.NewPlayTracker(ds, events.NoopBroker(), nil),
|
||||
core.NewShare(ds),
|
||||
playback.PlaybackServer(nil),
|
||||
|
||||
@ -114,7 +114,7 @@ var _ = Describe("Artwork Serving", Ordered, func() {
|
||||
Expect(ds.User(ctx).SetUserLibraries(adminUser.ID, []int{artLib.ID})).To(Succeed())
|
||||
|
||||
s := scanner.New(ctx, ds, events.NoopBroker(),
|
||||
playlists.NewPlaylists(ds, core.NewImageUploadService(ds)), metrics.NewNoopInstance())
|
||||
playlists.NewPlaylists(ds, artwork.NewUploader(ds)), metrics.NewNoopInstance())
|
||||
_, err := s.ScanAll(ctx, true)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
@ -228,10 +228,10 @@ var _ = Describe("Artwork Serving", Ordered, func() {
|
||||
func buildArtworkRouter(art artwork.Service) *subsonic.Router {
|
||||
decider := stream.NewTranscodeDecider(ds, harness.NoopFFmpeg{})
|
||||
s := scanner.New(ctx, ds, events.NoopBroker(),
|
||||
playlists.NewPlaylists(ds, core.NewImageUploadService(ds)), metrics.NewNoopInstance())
|
||||
playlists.NewPlaylists(ds, artwork.NewUploader(ds)), metrics.NewNoopInstance())
|
||||
return subsonic.New(
|
||||
ds, art, streamerSpy, noopArchiver{}, core.NewPlayers(ds), noopProvider{}, s,
|
||||
events.NoopBroker(), playlists.NewPlaylists(ds, core.NewImageUploadService(ds)),
|
||||
events.NoopBroker(), playlists.NewPlaylists(ds, artwork.NewUploader(ds)),
|
||||
scrobbler.NewPlayTracker(ds, events.NoopBroker(), nil), core.NewShare(ds),
|
||||
playback.PlaybackServer(nil), metrics.NewNoopInstance(), lyrics.NewLyrics(ds, nil), decider, nil,
|
||||
)
|
||||
|
||||
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/core/metrics"
|
||||
"github.com/navidrome/navidrome/core/playlists"
|
||||
"github.com/navidrome/navidrome/core/storage/storagetest"
|
||||
@ -53,7 +53,7 @@ var _ = Describe("Multi-Library Support", Ordered, func() {
|
||||
|
||||
// Run incremental scan to import lib2 content (lib1 files unchanged → skipped)
|
||||
s := scanner.New(ctx, ds, events.NoopBroker(),
|
||||
playlists.NewPlaylists(ds, core.NewImageUploadService(ds)), metrics.NewNoopInstance())
|
||||
playlists.NewPlaylists(ds, artwork.NewUploader(ds)), metrics.NewNoopInstance())
|
||||
_, err = s.ScanAll(ctx, false)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/agents"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/core/lyrics"
|
||||
"github.com/navidrome/navidrome/core/matcher"
|
||||
"github.com/navidrome/navidrome/core/metrics"
|
||||
@ -43,7 +44,7 @@ func buildSonicRouter(provider sonic.Provider) *subsonic.Router {
|
||||
noopProvider{},
|
||||
nil, // scanner
|
||||
events.NoopBroker(),
|
||||
playlists.NewPlaylists(ds, core.NewImageUploadService(ds)),
|
||||
playlists.NewPlaylists(ds, artwork.NewUploader(ds)),
|
||||
scrobbler.NewPlayTracker(ds, events.NoopBroker(), nil),
|
||||
core.NewShare(ds),
|
||||
playback.PlaybackServer(nil),
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
"testing/fstest"
|
||||
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/artwork"
|
||||
"github.com/navidrome/navidrome/core/auth"
|
||||
"github.com/navidrome/navidrome/core/ffmpeg"
|
||||
"github.com/navidrome/navidrome/core/metrics"
|
||||
@ -74,7 +74,7 @@ func SetupDB(ctx context.Context, users ...*model.User) *DB {
|
||||
}
|
||||
|
||||
s := scanner.New(ctx, ds, events.NoopBroker(),
|
||||
playlists.NewPlaylists(ds, core.NewImageUploadService(ds)), metrics.NewNoopInstance())
|
||||
playlists.NewPlaylists(ds, artwork.NewUploader(ds)), metrics.NewNoopInstance())
|
||||
_, err := s.ScanAll(ctx, true)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
|
||||
@ -38,7 +38,9 @@ describe('CoverImage', () => {
|
||||
|
||||
it('shows neither a broken <img> nor a canvas while loading a record with no blurhash', () => {
|
||||
useImageUrl.mockReturnValue({ imgUrl: null, loading: true })
|
||||
const { container } = render(<CoverImage record={{ id: 'al-2', name: 'X' }} />)
|
||||
const { container } = render(
|
||||
<CoverImage record={{ id: 'al-2', name: 'X' }} />,
|
||||
)
|
||||
expect(container.querySelector('img')).toBeNull()
|
||||
expect(container.querySelector('canvas')).toBeNull()
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user