diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go index ad8287df..10deceef 100644 --- a/cmd/wire_gen.go +++ b/cmd/wire_gen.go @@ -44,8 +44,6 @@ func CreateSubsonicAPIRouter() *subsonic.Router { dataStore := persistence.New() artworkCache := core.NewImageCache() artwork := core.NewArtwork(dataStore, artworkCache) - nowPlayingRepository := engine.NewNowPlayingRepository() - listGenerator := engine.NewListGenerator(dataStore, nowPlayingRepository) playlists := engine.NewPlaylists(dataStore) transcoderTranscoder := transcoder.New() transcodingCache := core.NewTranscodingCache() @@ -55,7 +53,7 @@ func CreateSubsonicAPIRouter() *subsonic.Router { client := core.LastFMNewClient() spotifyClient := core.SpotifyNewClient() externalInfo := core.NewExternalInfo(dataStore, client, spotifyClient) - router := subsonic.New(artwork, listGenerator, playlists, mediaStreamer, archiver, players, externalInfo, dataStore) + router := subsonic.New(artwork, playlists, mediaStreamer, archiver, players, externalInfo, dataStore) return router } diff --git a/server/subsonic/engine/nowplaying.go b/core/nowplaying.go similarity index 70% rename from server/subsonic/engine/nowplaying.go rename to core/nowplaying.go index 643ad029..7431505d 100644 --- a/server/subsonic/engine/nowplaying.go +++ b/core/nowplaying.go @@ -1,4 +1,4 @@ -package engine +package core import ( "container/list" @@ -17,22 +17,10 @@ type NowPlayingInfo struct { } // This repo must have the semantics of a FIFO queue, for each playerId -type NowPlayingRepository interface { +type NowPlaying interface { // Insert at the head of the queue Enqueue(*NowPlayingInfo) error - // Removes and returns the element at the end of the queue - Dequeue(playerId int) (*NowPlayingInfo, error) - - // Returns the element at the head of the queue (last inserted one) - Head(playerId int) (*NowPlayingInfo, error) - - // Returns the element at the end of the queue (first inserted one) - Tail(playerId int) (*NowPlayingInfo, error) - - // Size of the queue for the playerId - Count(playerId int) (int64, error) - // Returns all heads from all playerIds GetAll() ([]*NowPlayingInfo, error) } @@ -41,55 +29,17 @@ var playerMap = sync.Map{} type nowPlayingRepository struct{} -func NewNowPlayingRepository() NowPlayingRepository { +func NewNowPlayingRepository() NowPlaying { r := &nowPlayingRepository{} return r } -func (r *nowPlayingRepository) getList(id int) *list.List { - l, _ := playerMap.LoadOrStore(id, list.New()) - return l.(*list.List) -} - func (r *nowPlayingRepository) Enqueue(info *NowPlayingInfo) error { l := r.getList(info.PlayerId) l.PushFront(info) return nil } -func (r *nowPlayingRepository) Dequeue(playerId int) (*NowPlayingInfo, error) { - l := r.getList(playerId) - e := checkExpired(l, l.Back) - if e == nil { - return nil, nil - } - l.Remove(e) - return e.Value.(*NowPlayingInfo), nil -} - -func (r *nowPlayingRepository) Head(playerId int) (*NowPlayingInfo, error) { - l := r.getList(playerId) - e := checkExpired(l, l.Front) - if e == nil { - return nil, nil - } - return e.Value.(*NowPlayingInfo), nil -} - -func (r *nowPlayingRepository) Tail(playerId int) (*NowPlayingInfo, error) { - l := r.getList(playerId) - e := checkExpired(l, l.Back) - if e == nil { - return nil, nil - } - return e.Value.(*NowPlayingInfo), nil -} - -func (r *nowPlayingRepository) Count(playerId int) (int64, error) { - l := r.getList(playerId) - return int64(l.Len()), nil -} - func (r *nowPlayingRepository) GetAll() ([]*NowPlayingInfo, error) { var all []*NowPlayingInfo playerMap.Range(func(playerId, l interface{}) bool { @@ -103,6 +53,44 @@ func (r *nowPlayingRepository) GetAll() ([]*NowPlayingInfo, error) { return all, nil } +func (r *nowPlayingRepository) getList(id int) *list.List { + l, _ := playerMap.LoadOrStore(id, list.New()) + return l.(*list.List) +} + +func (r *nowPlayingRepository) dequeue(playerId int) (*NowPlayingInfo, error) { + l := r.getList(playerId) + e := checkExpired(l, l.Back) + if e == nil { + return nil, nil + } + l.Remove(e) + return e.Value.(*NowPlayingInfo), nil +} + +func (r *nowPlayingRepository) head(playerId int) (*NowPlayingInfo, error) { + l := r.getList(playerId) + e := checkExpired(l, l.Front) + if e == nil { + return nil, nil + } + return e.Value.(*NowPlayingInfo), nil +} + +func (r *nowPlayingRepository) tail(playerId int) (*NowPlayingInfo, error) { + l := r.getList(playerId) + e := checkExpired(l, l.Back) + if e == nil { + return nil, nil + } + return e.Value.(*NowPlayingInfo), nil +} + +func (r *nowPlayingRepository) count(playerId int) (int64, error) { + l := r.getList(playerId) + return int64(l.Len()), nil +} + func checkExpired(l *list.List, f func() *list.Element) *list.Element { for { e := f() diff --git a/server/subsonic/engine/nowplaying_test.go b/core/nowplaying_test.go similarity index 69% rename from server/subsonic/engine/nowplaying_test.go rename to core/nowplaying_test.go index acbb113d..f33e910f 100644 --- a/server/subsonic/engine/nowplaying_test.go +++ b/core/nowplaying_test.go @@ -1,4 +1,4 @@ -package engine +package core import ( "sync" @@ -8,27 +8,27 @@ import ( . "github.com/onsi/gomega" ) -var _ = Describe("NowPlayingRepository", func() { - var repo NowPlayingRepository +var _ = Describe("NowPlaying", func() { + var repo *nowPlayingRepository var now = time.Now() var past = time.Time{} BeforeEach(func() { playerMap = sync.Map{} - repo = NewNowPlayingRepository() + repo = NewNowPlayingRepository().(*nowPlayingRepository) }) It("enqueues and dequeues records", func() { Expect(repo.Enqueue(&NowPlayingInfo{PlayerId: 1, TrackID: "AAA", Start: now})).To(BeNil()) Expect(repo.Enqueue(&NowPlayingInfo{PlayerId: 1, TrackID: "BBB", Start: now})).To(BeNil()) - Expect(repo.Tail(1)).To(Equal(&NowPlayingInfo{PlayerId: 1, TrackID: "AAA", Start: now})) - Expect(repo.Head(1)).To(Equal(&NowPlayingInfo{PlayerId: 1, TrackID: "BBB", Start: now})) + Expect(repo.tail(1)).To(Equal(&NowPlayingInfo{PlayerId: 1, TrackID: "AAA", Start: now})) + Expect(repo.head(1)).To(Equal(&NowPlayingInfo{PlayerId: 1, TrackID: "BBB", Start: now})) - Expect(repo.Count(1)).To(Equal(int64(2))) + Expect(repo.count(1)).To(Equal(int64(2))) - Expect(repo.Dequeue(1)).To(Equal(&NowPlayingInfo{PlayerId: 1, TrackID: "AAA", Start: now})) - Expect(repo.Count(1)).To(Equal(int64(1))) + Expect(repo.dequeue(1)).To(Equal(&NowPlayingInfo{PlayerId: 1, TrackID: "AAA", Start: now})) + Expect(repo.count(1)).To(Equal(int64(1))) }) It("handles multiple players", func() { @@ -43,11 +43,11 @@ var _ = Describe("NowPlayingRepository", func() { {PlayerId: 2, TrackID: "DDD", Start: now}, })) - Expect(repo.Count(2)).To(Equal(int64(2))) - Expect(repo.Count(2)).To(Equal(int64(2))) + Expect(repo.count(2)).To(Equal(int64(2))) + Expect(repo.count(2)).To(Equal(int64(2))) - Expect(repo.Tail(1)).To(Equal(&NowPlayingInfo{PlayerId: 1, TrackID: "AAA", Start: now})) - Expect(repo.Head(2)).To(Equal(&NowPlayingInfo{PlayerId: 2, TrackID: "DDD", Start: now})) + Expect(repo.tail(1)).To(Equal(&NowPlayingInfo{PlayerId: 1, TrackID: "AAA", Start: now})) + Expect(repo.head(2)).To(Equal(&NowPlayingInfo{PlayerId: 2, TrackID: "DDD", Start: now})) }) It("handles expired items", func() { diff --git a/core/wire_providers.go b/core/wire_providers.go index 217fa56c..69199823 100644 --- a/core/wire_providers.go +++ b/core/wire_providers.go @@ -16,6 +16,7 @@ var Set = wire.NewSet( NewTranscodingCache, NewImageCache, NewArchiver, + NewNowPlayingRepository, NewExternalInfo, NewCacheWarmer, LastFMNewClient, diff --git a/server/subsonic/album_lists.go b/server/subsonic/album_lists.go index dbe86044..efa6aa22 100644 --- a/server/subsonic/album_lists.go +++ b/server/subsonic/album_lists.go @@ -4,24 +4,25 @@ import ( "context" "errors" "net/http" + "time" + "github.com/deluan/navidrome/core" "github.com/deluan/navidrome/log" "github.com/deluan/navidrome/model" - "github.com/deluan/navidrome/server/subsonic/engine" "github.com/deluan/navidrome/server/subsonic/filter" "github.com/deluan/navidrome/server/subsonic/responses" "github.com/deluan/navidrome/utils" ) type AlbumListController struct { - ds model.DataStore - listGen engine.ListGenerator + ds model.DataStore + nowPlaying core.NowPlaying } -func NewAlbumListController(ds model.DataStore, listGen engine.ListGenerator) *AlbumListController { +func NewAlbumListController(ds model.DataStore, nowPlaying core.NowPlaying) *AlbumListController { c := &AlbumListController{ - ds: ds, - listGen: listGen, + ds: ds, + nowPlaying: nowPlaying, } return c } @@ -132,7 +133,8 @@ func (c *AlbumListController) GetStarred2(w http.ResponseWriter, r *http.Request } func (c *AlbumListController) GetNowPlaying(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - npInfos, err := c.listGen.GetNowPlaying(r.Context()) + ctx := r.Context() + npInfo, err := c.nowPlaying.GetAll() if err != nil { log.Error(r, "Error retrieving now playing list", "error", err) return nil, newError(responses.ErrorGeneric, "Internal Error") @@ -140,13 +142,18 @@ func (c *AlbumListController) GetNowPlaying(w http.ResponseWriter, r *http.Reque response := newResponse() response.NowPlaying = &responses.NowPlaying{} - response.NowPlaying.Entry = make([]responses.NowPlayingEntry, len(npInfos)) - for i, entry := range npInfos { - response.NowPlaying.Entry[i].Child = toChild(r.Context(), entry) - response.NowPlaying.Entry[i].UserName = entry.UserName - response.NowPlaying.Entry[i].MinutesAgo = entry.MinutesAgo - response.NowPlaying.Entry[i].PlayerId = entry.PlayerId - response.NowPlaying.Entry[i].PlayerName = entry.PlayerName + response.NowPlaying.Entry = make([]responses.NowPlayingEntry, len(npInfo)) + for i, np := range npInfo { + mf, err := c.ds.MediaFile(ctx).Get(np.TrackID) + if err != nil { + return nil, newError(responses.ErrorGeneric, "Internal Error") + } + + response.NowPlaying.Entry[i].Child = childFromMediaFile(ctx, *mf) + response.NowPlaying.Entry[i].UserName = np.Username + response.NowPlaying.Entry[i].MinutesAgo = int(time.Since(np.Start).Minutes()) + response.NowPlaying.Entry[i].PlayerId = np.PlayerId + response.NowPlaying.Entry[i].PlayerName = np.PlayerName } return response, nil } diff --git a/server/subsonic/api.go b/server/subsonic/api.go index 365806c0..9017f1ee 100644 --- a/server/subsonic/api.go +++ b/server/subsonic/api.go @@ -23,22 +23,21 @@ const Version = "1.13.0" type Handler = func(http.ResponseWriter, *http.Request) (*responses.Subsonic, error) type Router struct { - Artwork core.Artwork - ListGenerator engine.ListGenerator - Playlists engine.Playlists - Streamer core.MediaStreamer - Archiver core.Archiver - Players engine.Players - ExternalInfo core.ExternalInfo - DataStore model.DataStore + Artwork core.Artwork + Playlists engine.Playlists + Streamer core.MediaStreamer + Archiver core.Archiver + Players engine.Players + ExternalInfo core.ExternalInfo + DataStore model.DataStore mux http.Handler } -func New(artwork core.Artwork, listGenerator engine.ListGenerator, +func New(artwork core.Artwork, playlists engine.Playlists, streamer core.MediaStreamer, archiver core.Archiver, players engine.Players, externalInfo core.ExternalInfo, ds model.DataStore) *Router { - r := &Router{Artwork: artwork, ListGenerator: listGenerator, Playlists: playlists, + r := &Router{Artwork: artwork, Playlists: playlists, Streamer: streamer, Archiver: archiver, Players: players, ExternalInfo: externalInfo, DataStore: ds} r.mux = r.routes() return r diff --git a/server/subsonic/engine/list_generator.go b/server/subsonic/engine/list_generator.go deleted file mode 100644 index b263f3d2..00000000 --- a/server/subsonic/engine/list_generator.go +++ /dev/null @@ -1,41 +0,0 @@ -package engine - -import ( - "context" - "time" - - "github.com/deluan/navidrome/model" -) - -type ListGenerator interface { - GetNowPlaying(ctx context.Context) (Entries, error) -} - -func NewListGenerator(ds model.DataStore, npRepo NowPlayingRepository) ListGenerator { - return &listGenerator{ds, npRepo} -} - -type listGenerator struct { - ds model.DataStore - npRepo NowPlayingRepository -} - -func (g *listGenerator) GetNowPlaying(ctx context.Context) (Entries, error) { - npInfo, err := g.npRepo.GetAll() - if err != nil { - return nil, err - } - entries := make(Entries, len(npInfo)) - for i, np := range npInfo { - mf, err := g.ds.MediaFile(ctx).Get(np.TrackID) - if err != nil { - return nil, err - } - entries[i] = FromMediaFile(mf) - entries[i].UserName = np.Username - entries[i].MinutesAgo = int(time.Since(np.Start).Minutes()) - entries[i].PlayerId = np.PlayerId - entries[i].PlayerName = np.PlayerName - } - return entries, nil -} diff --git a/server/subsonic/engine/wire_providers.go b/server/subsonic/engine/wire_providers.go index 818d45a9..dd0c0585 100644 --- a/server/subsonic/engine/wire_providers.go +++ b/server/subsonic/engine/wire_providers.go @@ -5,8 +5,6 @@ import ( ) var Set = wire.NewSet( - NewListGenerator, NewPlaylists, - NewNowPlayingRepository, NewPlayers, ) diff --git a/server/subsonic/media_annotation.go b/server/subsonic/media_annotation.go index ead75859..ded173b5 100644 --- a/server/subsonic/media_annotation.go +++ b/server/subsonic/media_annotation.go @@ -6,20 +6,20 @@ import ( "net/http" "time" + "github.com/deluan/navidrome/core" "github.com/deluan/navidrome/log" "github.com/deluan/navidrome/model" "github.com/deluan/navidrome/model/request" - "github.com/deluan/navidrome/server/subsonic/engine" "github.com/deluan/navidrome/server/subsonic/responses" "github.com/deluan/navidrome/utils" ) type MediaAnnotationController struct { ds model.DataStore - npRepo engine.NowPlayingRepository + npRepo core.NowPlaying } -func NewMediaAnnotationController(ds model.DataStore, npr engine.NowPlayingRepository) *MediaAnnotationController { +func NewMediaAnnotationController(ds model.DataStore, npr core.NowPlaying) *MediaAnnotationController { return &MediaAnnotationController{ds: ds, npRepo: npr} } @@ -176,7 +176,7 @@ func (c *MediaAnnotationController) scrobblerNowPlaying(ctx context.Context, pla log.Info("Now Playing", "title", mf.Title, "artist", mf.Artist, "user", username) - info := &engine.NowPlayingInfo{TrackID: trackId, Username: username, Start: time.Now(), PlayerId: playerId, PlayerName: playerName} + info := &core.NowPlayingInfo{TrackID: trackId, Username: username, Start: time.Now(), PlayerId: playerId, PlayerName: playerName} return mf, c.npRepo.Enqueue(info) } diff --git a/server/subsonic/wire_gen.go b/server/subsonic/wire_gen.go index 83c06d59..79941213 100644 --- a/server/subsonic/wire_gen.go +++ b/server/subsonic/wire_gen.go @@ -6,7 +6,7 @@ package subsonic import ( - "github.com/deluan/navidrome/server/subsonic/engine" + "github.com/deluan/navidrome/core" "github.com/google/wire" ) @@ -26,15 +26,15 @@ func initBrowsingController(router *Router) *BrowsingController { func initAlbumListController(router *Router) *AlbumListController { dataStore := router.DataStore - listGenerator := router.ListGenerator - albumListController := NewAlbumListController(dataStore, listGenerator) + nowPlaying := core.NewNowPlayingRepository() + albumListController := NewAlbumListController(dataStore, nowPlaying) return albumListController } func initMediaAnnotationController(router *Router) *MediaAnnotationController { dataStore := router.DataStore - nowPlayingRepository := engine.NewNowPlayingRepository() - mediaAnnotationController := NewMediaAnnotationController(dataStore, nowPlayingRepository) + nowPlaying := core.NewNowPlayingRepository() + mediaAnnotationController := NewMediaAnnotationController(dataStore, nowPlaying) return mediaAnnotationController } @@ -87,5 +87,5 @@ var allProviders = wire.NewSet( NewUsersController, NewMediaRetrievalController, NewStreamController, - NewBookmarksController, engine.NewNowPlayingRepository, wire.FieldsOf(new(*Router), "Artwork", "ListGenerator", "Playlists", "Streamer", "Archiver", "DataStore", "ExternalInfo"), + NewBookmarksController, core.NewNowPlayingRepository, wire.FieldsOf(new(*Router), "Artwork", "Playlists", "Streamer", "Archiver", "DataStore", "ExternalInfo"), ) diff --git a/server/subsonic/wire_injectors.go b/server/subsonic/wire_injectors.go index a19b3675..7bf9cba8 100644 --- a/server/subsonic/wire_injectors.go +++ b/server/subsonic/wire_injectors.go @@ -3,7 +3,7 @@ package subsonic import ( - "github.com/deluan/navidrome/server/subsonic/engine" + "github.com/deluan/navidrome/core" "github.com/google/wire" ) @@ -18,8 +18,8 @@ var allProviders = wire.NewSet( NewMediaRetrievalController, NewStreamController, NewBookmarksController, - engine.NewNowPlayingRepository, - wire.FieldsOf(new(*Router), "Artwork", "ListGenerator", "Playlists", "Streamer", "Archiver", "DataStore", "ExternalInfo"), + core.NewNowPlayingRepository, + wire.FieldsOf(new(*Router), "Artwork", "Playlists", "Streamer", "Archiver", "DataStore", "ExternalInfo"), ) func initSystemController(router *Router) *SystemController {