From 19af11efbe68d133f1913adb43d5c5ea4b01f20e Mon Sep 17 00:00:00 2001 From: Deluan Date: Mon, 21 Nov 2022 12:57:56 -0500 Subject: [PATCH] Simplify Subsonic API handler implementation --- server/subsonic/album_lists.go | 58 +++---- server/subsonic/album_lists_test.go | 18 +- server/subsonic/api.go | 182 ++++++++++---------- server/subsonic/bookmarks.go | 28 ++- server/subsonic/browsing.go | 93 +++++----- server/subsonic/library_scanning.go | 19 +- server/subsonic/media_annotation.go | 56 +++--- server/subsonic/media_annotation_test.go | 25 ++- server/subsonic/media_retrieval.go | 28 +-- server/subsonic/media_retrieval_test.go | 22 +-- server/subsonic/playlists.go | 50 +++--- server/subsonic/responses/responses_test.go | 2 +- server/subsonic/searching.go | 34 ++-- server/subsonic/stream.go | 26 +-- server/subsonic/system.go | 4 +- server/subsonic/users.go | 10 +- server/subsonic/wire_gen.go | 112 ------------ server/subsonic/wire_injectors.go | 77 --------- 18 files changed, 280 insertions(+), 564 deletions(-) delete mode 100644 server/subsonic/wire_gen.go delete mode 100644 server/subsonic/wire_injectors.go diff --git a/server/subsonic/album_lists.go b/server/subsonic/album_lists.go index 7344510a..3b3ef455 100644 --- a/server/subsonic/album_lists.go +++ b/server/subsonic/album_lists.go @@ -6,7 +6,6 @@ import ( "strconv" "time" - "github.com/navidrome/navidrome/core/scrobbler" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/server/subsonic/filter" @@ -14,20 +13,7 @@ import ( "github.com/navidrome/navidrome/utils" ) -type AlbumListController struct { - ds model.DataStore - scrobbler scrobbler.PlayTracker -} - -func NewAlbumListController(ds model.DataStore, scrobbler scrobbler.PlayTracker) *AlbumListController { - c := &AlbumListController{ - ds: ds, - scrobbler: scrobbler, - } - return c -} - -func (c *AlbumListController) getAlbumList(r *http.Request) (model.Albums, int64, error) { +func (api *Router) getAlbumList(r *http.Request) (model.Albums, int64, error) { typ, err := requiredParamString(r, "type") if err != nil { return nil, 0, err @@ -74,14 +60,14 @@ func (c *AlbumListController) getAlbumList(r *http.Request) (model.Albums, int64 opts.Offset = utils.ParamInt(r, "offset", 0) opts.Max = utils.MinInt(utils.ParamInt(r, "size", 10), 500) - albums, err := c.ds.Album(r.Context()).GetAllWithoutGenres(opts) + albums, err := api.ds.Album(r.Context()).GetAllWithoutGenres(opts) if err != nil { log.Error(r, "Error retrieving albums", "error", err) return nil, 0, newError(responses.ErrorGeneric, "internal error") } - count, err := c.ds.Album(r.Context()).CountAll(opts) + count, err := api.ds.Album(r.Context()).CountAll(opts) if err != nil { log.Error(r, "Error counting albums", "error", err) return nil, 0, newError(responses.ErrorGeneric, "internal error") @@ -90,8 +76,8 @@ func (c *AlbumListController) getAlbumList(r *http.Request) (model.Albums, int64 return albums, count, nil } -func (c *AlbumListController) GetAlbumList(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - albums, count, err := c.getAlbumList(r) +func (api *Router) GetAlbumList(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { + albums, count, err := api.getAlbumList(r) if err != nil { return nil, err } @@ -103,8 +89,8 @@ func (c *AlbumListController) GetAlbumList(w http.ResponseWriter, r *http.Reques return response, nil } -func (c *AlbumListController) GetAlbumList2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - albums, pageCount, err := c.getAlbumList(r) +func (api *Router) GetAlbumList2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { + albums, pageCount, err := api.getAlbumList(r) if err != nil { return nil, err } @@ -116,20 +102,20 @@ func (c *AlbumListController) GetAlbumList2(w http.ResponseWriter, r *http.Reque return response, nil } -func (c *AlbumListController) GetStarred(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetStarred(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() options := filter.Starred() - artists, err := c.ds.Artist(ctx).GetAll(options) + artists, err := api.ds.Artist(ctx).GetAll(options) if err != nil { log.Error(r, "Error retrieving starred artists", "error", err) return nil, err } - albums, err := c.ds.Album(ctx).GetAllWithoutGenres(options) + albums, err := api.ds.Album(ctx).GetAllWithoutGenres(options) if err != nil { log.Error(r, "Error retrieving starred albums", "error", err) return nil, err } - mediaFiles, err := c.ds.MediaFile(ctx).GetAll(options) + mediaFiles, err := api.ds.MediaFile(ctx).GetAll(options) if err != nil { log.Error(r, "Error retrieving starred mediaFiles", "error", err) return nil, err @@ -143,8 +129,8 @@ func (c *AlbumListController) GetStarred(w http.ResponseWriter, r *http.Request) return response, nil } -func (c *AlbumListController) GetStarred2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - resp, err := c.GetStarred(w, r) +func (api *Router) GetStarred2(r *http.Request) (*responses.Subsonic, error) { + resp, err := api.GetStarred(r) if err != nil { return nil, err } @@ -154,9 +140,9 @@ func (c *AlbumListController) GetStarred2(w http.ResponseWriter, r *http.Request return response, nil } -func (c *AlbumListController) GetNowPlaying(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetNowPlaying(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() - npInfo, err := c.scrobbler.GetNowPlaying(ctx) + npInfo, err := api.scrobbler.GetNowPlaying(ctx) if err != nil { log.Error(r, "Error retrieving now playing list", "error", err) return nil, err @@ -166,7 +152,7 @@ func (c *AlbumListController) GetNowPlaying(w http.ResponseWriter, r *http.Reque response.NowPlaying = &responses.NowPlaying{} response.NowPlaying.Entry = make([]responses.NowPlayingEntry, len(npInfo)) for i, np := range npInfo { - mf, err := c.ds.MediaFile(ctx).Get(np.TrackID) + mf, err := api.ds.MediaFile(ctx).Get(np.TrackID) if err != nil { return nil, err } @@ -180,13 +166,13 @@ func (c *AlbumListController) GetNowPlaying(w http.ResponseWriter, r *http.Reque return response, nil } -func (c *AlbumListController) GetRandomSongs(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetRandomSongs(r *http.Request) (*responses.Subsonic, error) { size := utils.MinInt(utils.ParamInt(r, "size", 10), 500) genre := utils.ParamString(r, "genre") fromYear := utils.ParamInt(r, "fromYear", 0) toYear := utils.ParamInt(r, "toYear", 0) - songs, err := c.getSongs(r.Context(), 0, size, filter.SongsByRandom(genre, fromYear, toYear)) + songs, err := api.getSongs(r.Context(), 0, size, filter.SongsByRandom(genre, fromYear, toYear)) if err != nil { log.Error(r, "Error retrieving random songs", "error", err) return nil, err @@ -198,12 +184,12 @@ func (c *AlbumListController) GetRandomSongs(w http.ResponseWriter, r *http.Requ return response, nil } -func (c *AlbumListController) GetSongsByGenre(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetSongsByGenre(r *http.Request) (*responses.Subsonic, error) { count := utils.MinInt(utils.ParamInt(r, "count", 10), 500) offset := utils.MinInt(utils.ParamInt(r, "offset", 0), 500) genre := utils.ParamString(r, "genre") - songs, err := c.getSongs(r.Context(), offset, count, filter.SongsByGenre(genre)) + songs, err := api.getSongs(r.Context(), offset, count, filter.SongsByGenre(genre)) if err != nil { log.Error(r, "Error retrieving random songs", "error", err) return nil, err @@ -215,8 +201,8 @@ func (c *AlbumListController) GetSongsByGenre(w http.ResponseWriter, r *http.Req return response, nil } -func (c *AlbumListController) getSongs(ctx context.Context, offset, size int, opts filter.Options) (model.MediaFiles, error) { +func (api *Router) getSongs(ctx context.Context, offset, size int, opts filter.Options) (model.MediaFiles, error) { opts.Offset = offset opts.Max = size - return c.ds.MediaFile(ctx).GetAll(opts) + return api.ds.MediaFile(ctx).GetAll(opts) } diff --git a/server/subsonic/album_lists_test.go b/server/subsonic/album_lists_test.go index 95bce271..fc684210 100644 --- a/server/subsonic/album_lists_test.go +++ b/server/subsonic/album_lists_test.go @@ -14,8 +14,8 @@ import ( . "github.com/onsi/gomega" ) -var _ = Describe("AlbumListController", func() { - var controller *AlbumListController +var _ = Describe("Album Lists", func() { + var router *Router var ds model.DataStore var mockRepo *tests.MockAlbumRepo var w *httptest.ResponseRecorder @@ -24,7 +24,7 @@ var _ = Describe("AlbumListController", func() { BeforeEach(func() { ds = &tests.MockDataStore{} mockRepo = ds.Album(ctx).(*tests.MockAlbumRepo) - controller = NewAlbumListController(ds, nil) + router = New(ds, nil, nil, nil, nil, nil, nil, nil, nil, nil) w = httptest.NewRecorder() }) @@ -34,7 +34,7 @@ var _ = Describe("AlbumListController", func() { mockRepo.SetData(model.Albums{ {ID: "1"}, {ID: "2"}, }) - resp, err := controller.GetAlbumList(w, r) + resp, err := router.GetAlbumList(w, r) Expect(err).To(BeNil()) Expect(resp.AlbumList.Album[0].Id).To(Equal("1")) @@ -46,7 +46,7 @@ var _ = Describe("AlbumListController", func() { It("should fail if missing type parameter", func() { r := newGetRequest() - _, err := controller.GetAlbumList(w, r) + _, err := router.GetAlbumList(w, r) var subErr subError isSubError := errors.As(err, &subErr) @@ -59,7 +59,7 @@ var _ = Describe("AlbumListController", func() { mockRepo.SetError(true) r := newGetRequest("type=newest") - _, err := controller.GetAlbumList(w, r) + _, err := router.GetAlbumList(w, r) Expect(err).ToNot(BeNil()) var subErr subError @@ -74,7 +74,7 @@ var _ = Describe("AlbumListController", func() { mockRepo.SetData(model.Albums{ {ID: "1"}, {ID: "2"}, }) - resp, err := controller.GetAlbumList2(w, r) + resp, err := router.GetAlbumList2(w, r) Expect(err).To(BeNil()) Expect(resp.AlbumList2.Album[0].Id).To(Equal("1")) @@ -86,7 +86,7 @@ var _ = Describe("AlbumListController", func() { It("should fail if missing type parameter", func() { r := newGetRequest() - _, err := controller.GetAlbumList2(w, r) + _, err := router.GetAlbumList2(w, r) var subErr subError errors.As(err, &subErr) @@ -99,7 +99,7 @@ var _ = Describe("AlbumListController", func() { mockRepo.SetError(true) r := newGetRequest("type=newest") - _, err := controller.GetAlbumList2(w, r) + _, err := router.GetAlbumList2(w, r) var subErr subError errors.As(err, &subErr) diff --git a/server/subsonic/api.go b/server/subsonic/api.go index bc454930..cca2933c 100644 --- a/server/subsonic/api.go +++ b/server/subsonic/api.go @@ -23,36 +23,37 @@ import ( const Version = "1.16.1" -type handler = func(http.ResponseWriter, *http.Request) (*responses.Subsonic, error) +type handler = func(*http.Request) (*responses.Subsonic, error) +type handlerRaw = func(http.ResponseWriter, *http.Request) (*responses.Subsonic, error) type Router struct { http.Handler - DataStore model.DataStore - Artwork core.Artwork - Streamer core.MediaStreamer - Archiver core.Archiver - Players core.Players - ExternalMetadata core.ExternalMetadata - Playlists core.Playlists - Scanner scanner.Scanner - Broker events.Broker - Scrobbler scrobbler.PlayTracker + ds model.DataStore + artwork core.Artwork + streamer core.MediaStreamer + archiver core.Archiver + players core.Players + externalMetadata core.ExternalMetadata + playlists core.Playlists + scanner scanner.Scanner + broker events.Broker + scrobbler scrobbler.PlayTracker } func New(ds model.DataStore, artwork core.Artwork, streamer core.MediaStreamer, archiver core.Archiver, players core.Players, externalMetadata core.ExternalMetadata, scanner scanner.Scanner, broker events.Broker, playlists core.Playlists, scrobbler scrobbler.PlayTracker) *Router { r := &Router{ - DataStore: ds, - Artwork: artwork, - Streamer: streamer, - Archiver: archiver, - Players: players, - ExternalMetadata: externalMetadata, - Playlists: playlists, - Scanner: scanner, - Broker: broker, - Scrobbler: scrobbler, + ds: ds, + artwork: artwork, + streamer: streamer, + archiver: archiver, + players: players, + externalMetadata: externalMetadata, + playlists: playlists, + scanner: scanner, + broker: broker, + scrobbler: scrobbler, } r.Handler = r.routes() return r @@ -63,100 +64,89 @@ func (api *Router) routes() http.Handler { r.Use(postFormToQueryParams) r.Use(checkRequiredParameters) - r.Use(authenticate(api.DataStore)) + r.Use(authenticate(api.ds)) // TODO Validate version // Subsonic endpoints, grouped by controller r.Group(func(r chi.Router) { - c := initSystemController(api) - withPlayer := r.With(getPlayer(api.Players)) - h(withPlayer, "ping", c.Ping) - h(withPlayer, "getLicense", c.GetLicense) + r.Use(getPlayer(api.players)) + h(r, "ping", api.Ping) + h(r, "getLicense", api.GetLicense) }) r.Group(func(r chi.Router) { - c := initBrowsingController(api) - withPlayer := r.With(getPlayer(api.Players)) - h(withPlayer, "getMusicFolders", c.GetMusicFolders) - h(withPlayer, "getIndexes", c.GetIndexes) - h(withPlayer, "getArtists", c.GetArtists) - h(withPlayer, "getGenres", c.GetGenres) - h(withPlayer, "getMusicDirectory", c.GetMusicDirectory) - h(withPlayer, "getArtist", c.GetArtist) - h(withPlayer, "getAlbum", c.GetAlbum) - h(withPlayer, "getSong", c.GetSong) - h(withPlayer, "getArtistInfo", c.GetArtistInfo) - h(withPlayer, "getArtistInfo2", c.GetArtistInfo2) - h(withPlayer, "getTopSongs", c.GetTopSongs) - h(withPlayer, "getSimilarSongs", c.GetSimilarSongs) - h(withPlayer, "getSimilarSongs2", c.GetSimilarSongs2) + r.Use(getPlayer(api.players)) + h(r, "getMusicFolders", api.GetMusicFolders) + h(r, "getIndexes", api.GetIndexes) + h(r, "getArtists", api.GetArtists) + h(r, "getGenres", api.GetGenres) + h(r, "getMusicDirectory", api.GetMusicDirectory) + h(r, "getArtist", api.GetArtist) + h(r, "getAlbum", api.GetAlbum) + h(r, "getSong", api.GetSong) + h(r, "getArtistInfo", api.GetArtistInfo) + h(r, "getArtistInfo2", api.GetArtistInfo2) + h(r, "getTopSongs", api.GetTopSongs) + h(r, "getSimilarSongs", api.GetSimilarSongs) + h(r, "getSimilarSongs2", api.GetSimilarSongs2) }) r.Group(func(r chi.Router) { - c := initAlbumListController(api) - withPlayer := r.With(getPlayer(api.Players)) - h(withPlayer, "getAlbumList", c.GetAlbumList) - h(withPlayer, "getAlbumList2", c.GetAlbumList2) - h(withPlayer, "getStarred", c.GetStarred) - h(withPlayer, "getStarred2", c.GetStarred2) - h(withPlayer, "getNowPlaying", c.GetNowPlaying) - h(withPlayer, "getRandomSongs", c.GetRandomSongs) - h(withPlayer, "getSongsByGenre", c.GetSongsByGenre) + r.Use(getPlayer(api.players)) + hr(r, "getAlbumList", api.GetAlbumList) + hr(r, "getAlbumList2", api.GetAlbumList2) + h(r, "getStarred", api.GetStarred) + h(r, "getStarred2", api.GetStarred2) + h(r, "getNowPlaying", api.GetNowPlaying) + h(r, "getRandomSongs", api.GetRandomSongs) + h(r, "getSongsByGenre", api.GetSongsByGenre) }) r.Group(func(r chi.Router) { - c := initMediaAnnotationController(api) - withPlayer := r.With(getPlayer(api.Players)) - h(withPlayer, "setRating", c.SetRating) - h(withPlayer, "star", c.Star) - h(withPlayer, "unstar", c.Unstar) - h(withPlayer, "scrobble", c.Scrobble) + r.Use(getPlayer(api.players)) + h(r, "setRating", api.SetRating) + h(r, "star", api.Star) + h(r, "unstar", api.Unstar) + h(r, "scrobble", api.Scrobble) }) r.Group(func(r chi.Router) { - c := initPlaylistsController(api) - withPlayer := r.With(getPlayer(api.Players)) - h(withPlayer, "getPlaylists", c.GetPlaylists) - h(withPlayer, "getPlaylist", c.GetPlaylist) - h(withPlayer, "createPlaylist", c.CreatePlaylist) - h(withPlayer, "deletePlaylist", c.DeletePlaylist) - h(withPlayer, "updatePlaylist", c.UpdatePlaylist) + r.Use(getPlayer(api.players)) + h(r, "getPlaylists", api.GetPlaylists) + h(r, "getPlaylist", api.GetPlaylist) + h(r, "createPlaylist", api.CreatePlaylist) + h(r, "deletePlaylist", api.DeletePlaylist) + h(r, "updatePlaylist", api.UpdatePlaylist) }) r.Group(func(r chi.Router) { - c := initBookmarksController(api) - withPlayer := r.With(getPlayer(api.Players)) - h(withPlayer, "getBookmarks", c.GetBookmarks) - h(withPlayer, "createBookmark", c.CreateBookmark) - h(withPlayer, "deleteBookmark", c.DeleteBookmark) - h(withPlayer, "getPlayQueue", c.GetPlayQueue) - h(withPlayer, "savePlayQueue", c.SavePlayQueue) + r.Use(getPlayer(api.players)) + h(r, "getBookmarks", api.GetBookmarks) + h(r, "createBookmark", api.CreateBookmark) + h(r, "deleteBookmark", api.DeleteBookmark) + h(r, "getPlayQueue", api.GetPlayQueue) + h(r, "savePlayQueue", api.SavePlayQueue) }) r.Group(func(r chi.Router) { - c := initSearchingController(api) - withPlayer := r.With(getPlayer(api.Players)) - h(withPlayer, "search2", c.Search2) - h(withPlayer, "search3", c.Search3) + r.Use(getPlayer(api.players)) + h(r, "search2", api.Search2) + h(r, "search3", api.Search3) }) r.Group(func(r chi.Router) { - c := initUsersController(api) - h(r, "getUser", c.GetUser) - h(r, "getUsers", c.GetUsers) + h(r, "getUser", api.GetUser) + h(r, "getUsers", api.GetUsers) }) r.Group(func(r chi.Router) { - c := initLibraryScanningController(api) - h(r, "getScanStatus", c.GetScanStatus) - h(r, "startScan", c.StartScan) + h(r, "getScanStatus", api.GetScanStatus) + h(r, "startScan", api.StartScan) }) r.Group(func(r chi.Router) { - c := initMediaRetrievalController(api) // configure request throttling maxRequests := utils.MaxInt(2, runtime.NumCPU()) - withThrottle := r.With(middleware.ThrottleBacklog(maxRequests, consts.RequestThrottleBacklogLimit, consts.RequestThrottleBacklogTimeout)) - h(withThrottle, "getAvatar", c.GetAvatar) - h(withThrottle, "getCoverArt", c.GetCoverArt) - h(withThrottle, "getLyrics", c.GetLyrics) + r.Use(middleware.ThrottleBacklog(maxRequests, consts.RequestThrottleBacklogLimit, consts.RequestThrottleBacklogTimeout)) + hr(r, "getAvatar", api.GetAvatar) + hr(r, "getCoverArt", api.GetCoverArt) + h(r, "getLyrics", api.GetLyrics) }) r.Group(func(r chi.Router) { - c := initStreamController(api) - withPlayer := r.With(getPlayer(api.Players)) - h(withPlayer, "stream", c.Stream) - h(withPlayer, "download", c.Download) + r.Use(getPlayer(api.players)) + hr(r, "stream", api.Stream) + hr(r, "download", api.Download) }) // Not Implemented (yet?) @@ -176,9 +166,9 @@ func (api *Router) routes() http.Handler { return r } -// Add the Subsonic handler, with and without `.view` extension -// Ex: if path = `ping` it will create the routes `/ping` and `/ping.view` -func h(r chi.Router, path string, f handler) { +// Add the Subsonic handler that requires a http.ResponseWriter, with and without `.view` extension. +// Ex: if path = `stream` it will create the routes `/stream` and `/stream.view` +func hr(r chi.Router, path string, f handlerRaw) { handle := func(w http.ResponseWriter, r *http.Request) { res, err := f(w, r) if err != nil { @@ -208,6 +198,14 @@ func h(r chi.Router, path string, f handler) { r.HandleFunc("/"+path+".view", handle) } +// Add the Subsonic handler, with and without `.view` extension +// Ex: if path = `ping` it will create the routes `/ping` and `/ping.view` +func h(r chi.Router, path string, f handler) { + hr(r, path, func(_ http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { + return f(r) + }) +} + // Add a handler that returns 501 - Not implemented. Used to signal that an endpoint is not implemented yet func h501(r *chi.Mux, paths ...string) { for _, path := range paths { diff --git a/server/subsonic/bookmarks.go b/server/subsonic/bookmarks.go index c6ed7c2e..e2dd3daf 100644 --- a/server/subsonic/bookmarks.go +++ b/server/subsonic/bookmarks.go @@ -10,18 +10,10 @@ import ( "github.com/navidrome/navidrome/utils" ) -type BookmarksController struct { - ds model.DataStore -} - -func NewBookmarksController(ds model.DataStore) *BookmarksController { - return &BookmarksController{ds: ds} -} - -func (c *BookmarksController) GetBookmarks(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetBookmarks(r *http.Request) (*responses.Subsonic, error) { user, _ := request.UserFrom(r.Context()) - repo := c.ds.MediaFile(r.Context()) + repo := api.ds.MediaFile(r.Context()) bmks, err := repo.GetBookmarks() if err != nil { return nil, err @@ -43,7 +35,7 @@ func (c *BookmarksController) GetBookmarks(w http.ResponseWriter, r *http.Reques return response, nil } -func (c *BookmarksController) CreateBookmark(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) CreateBookmark(r *http.Request) (*responses.Subsonic, error) { id, err := requiredParamString(r, "id") if err != nil { return nil, err @@ -52,7 +44,7 @@ func (c *BookmarksController) CreateBookmark(w http.ResponseWriter, r *http.Requ comment := utils.ParamString(r, "comment") position := utils.ParamInt64(r, "position", 0) - repo := c.ds.MediaFile(r.Context()) + repo := api.ds.MediaFile(r.Context()) err = repo.AddBookmark(id, comment, position) if err != nil { return nil, err @@ -60,13 +52,13 @@ func (c *BookmarksController) CreateBookmark(w http.ResponseWriter, r *http.Requ return newResponse(), nil } -func (c *BookmarksController) DeleteBookmark(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) DeleteBookmark(r *http.Request) (*responses.Subsonic, error) { id, err := requiredParamString(r, "id") if err != nil { return nil, err } - repo := c.ds.MediaFile(r.Context()) + repo := api.ds.MediaFile(r.Context()) err = repo.DeleteBookmark(id) if err != nil { return nil, err @@ -74,10 +66,10 @@ func (c *BookmarksController) DeleteBookmark(w http.ResponseWriter, r *http.Requ return newResponse(), nil } -func (c *BookmarksController) GetPlayQueue(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetPlayQueue(r *http.Request) (*responses.Subsonic, error) { user, _ := request.UserFrom(r.Context()) - repo := c.ds.PlayQueue(r.Context()) + repo := api.ds.PlayQueue(r.Context()) pq, err := repo.Retrieve(user.ID) if err != nil { return nil, err @@ -95,7 +87,7 @@ func (c *BookmarksController) GetPlayQueue(w http.ResponseWriter, r *http.Reques return response, nil } -func (c *BookmarksController) SavePlayQueue(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) SavePlayQueue(r *http.Request) (*responses.Subsonic, error) { ids, err := requiredParamStrings(r, "id") if err != nil { return nil, err @@ -122,7 +114,7 @@ func (c *BookmarksController) SavePlayQueue(w http.ResponseWriter, r *http.Reque UpdatedAt: time.Time{}, } - repo := c.ds.PlayQueue(r.Context()) + repo := api.ds.PlayQueue(r.Context()) err = repo.Store(pq) if err != nil { return nil, err diff --git a/server/subsonic/browsing.go b/server/subsonic/browsing.go index a5b72827..c6ecc447 100644 --- a/server/subsonic/browsing.go +++ b/server/subsonic/browsing.go @@ -16,17 +16,8 @@ import ( "github.com/navidrome/navidrome/utils" ) -type BrowsingController struct { - ds model.DataStore - em core.ExternalMetadata -} - -func NewBrowsingController(ds model.DataStore, em core.ExternalMetadata) *BrowsingController { - return &BrowsingController{ds: ds, em: em} -} - -func (c *BrowsingController) GetMusicFolders(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - mediaFolderList, _ := c.ds.MediaFolder(r.Context()).GetAll() +func (api *Router) GetMusicFolders(r *http.Request) (*responses.Subsonic, error) { + mediaFolderList, _ := api.ds.MediaFolder(r.Context()).GetAll() folders := make([]responses.MusicFolder, len(mediaFolderList)) for i, f := range mediaFolderList { folders[i].Id = f.ID @@ -37,14 +28,14 @@ func (c *BrowsingController) GetMusicFolders(w http.ResponseWriter, r *http.Requ return response, nil } -func (c *BrowsingController) getArtistIndex(ctx context.Context, mediaFolderId int, ifModifiedSince time.Time) (*responses.Indexes, error) { - folder, err := c.ds.MediaFolder(ctx).Get(int32(mediaFolderId)) +func (api *Router) getArtistIndex(ctx context.Context, mediaFolderId int, ifModifiedSince time.Time) (*responses.Indexes, error) { + folder, err := api.ds.MediaFolder(ctx).Get(int32(mediaFolderId)) if err != nil { log.Error(ctx, "Error retrieving MediaFolder", "id", mediaFolderId, err) return nil, err } - l, err := c.ds.Property(ctx).DefaultGet(model.PropLastScan+"-"+folder.Path, "-1") + l, err := api.ds.Property(ctx).DefaultGet(model.PropLastScan+"-"+folder.Path, "-1") if err != nil { log.Error(ctx, "Error retrieving LastScan property", err) return nil, err @@ -54,7 +45,7 @@ func (c *BrowsingController) getArtistIndex(ctx context.Context, mediaFolderId i ms, _ := strconv.ParseInt(l, 10, 64) lastModified := utils.ToTime(ms) if lastModified.After(ifModifiedSince) { - indexes, err = c.ds.Artist(ctx).GetIndex() + indexes, err = api.ds.Artist(ctx).GetIndex() if err != nil { log.Error(ctx, "Error retrieving Indexes", err) return nil, err @@ -74,11 +65,11 @@ func (c *BrowsingController) getArtistIndex(ctx context.Context, mediaFolderId i return res, nil } -func (c *BrowsingController) GetIndexes(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetIndexes(r *http.Request) (*responses.Subsonic, error) { musicFolderId := utils.ParamInt(r, "musicFolderId", 0) ifModifiedSince := utils.ParamTime(r, "ifModifiedSince", time.Time{}) - res, err := c.getArtistIndex(r.Context(), musicFolderId, ifModifiedSince) + res, err := api.getArtistIndex(r.Context(), musicFolderId, ifModifiedSince) if err != nil { return nil, err } @@ -88,9 +79,9 @@ func (c *BrowsingController) GetIndexes(w http.ResponseWriter, r *http.Request) return response, nil } -func (c *BrowsingController) GetArtists(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetArtists(r *http.Request) (*responses.Subsonic, error) { musicFolderId := utils.ParamInt(r, "musicFolderId", 0) - res, err := c.getArtistIndex(r.Context(), musicFolderId, time.Time{}) + res, err := api.getArtistIndex(r.Context(), musicFolderId, time.Time{}) if err != nil { return nil, err } @@ -100,11 +91,11 @@ func (c *BrowsingController) GetArtists(w http.ResponseWriter, r *http.Request) return response, nil } -func (c *BrowsingController) GetMusicDirectory(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetMusicDirectory(r *http.Request) (*responses.Subsonic, error) { id := utils.ParamString(r, "id") ctx := r.Context() - entity, err := core.GetEntityByID(ctx, c.ds, id) + entity, err := core.GetEntityByID(ctx, api.ds, id) if errors.Is(err, model.ErrNotFound) { log.Error(r, "Requested ID not found ", "id", id) return nil, newError(responses.ErrorDataNotFound, "Directory not found") @@ -118,9 +109,9 @@ func (c *BrowsingController) GetMusicDirectory(w http.ResponseWriter, r *http.Re switch v := entity.(type) { case *model.Artist: - dir, err = c.buildArtistDirectory(ctx, v) + dir, err = api.buildArtistDirectory(ctx, v) case *model.Album: - dir, err = c.buildAlbumDirectory(ctx, v) + dir, err = api.buildAlbumDirectory(ctx, v) default: log.Error(r, "Requested ID of invalid type", "id", id, "entity", v) return nil, newError(responses.ErrorDataNotFound, "Directory not found") @@ -136,11 +127,11 @@ func (c *BrowsingController) GetMusicDirectory(w http.ResponseWriter, r *http.Re return response, nil } -func (c *BrowsingController) GetArtist(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetArtist(r *http.Request) (*responses.Subsonic, error) { id := utils.ParamString(r, "id") ctx := r.Context() - artist, err := c.ds.Artist(ctx).Get(id) + artist, err := api.ds.Artist(ctx).Get(id) if errors.Is(err, model.ErrNotFound) { log.Error(ctx, "Requested ArtistID not found ", "id", id) return nil, newError(responses.ErrorDataNotFound, "Artist not found") @@ -150,22 +141,22 @@ func (c *BrowsingController) GetArtist(w http.ResponseWriter, r *http.Request) ( return nil, err } - albums, err := c.ds.Album(ctx).GetAllWithoutGenres(filter.AlbumsByArtistID(id)) + albums, err := api.ds.Album(ctx).GetAllWithoutGenres(filter.AlbumsByArtistID(id)) if err != nil { log.Error(ctx, "Error retrieving albums by artist", "id", id, "name", artist.Name, err) return nil, err } response := newResponse() - response.ArtistWithAlbumsID3 = c.buildArtist(ctx, artist, albums) + response.ArtistWithAlbumsID3 = api.buildArtist(ctx, artist, albums) return response, nil } -func (c *BrowsingController) GetAlbum(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetAlbum(r *http.Request) (*responses.Subsonic, error) { id := utils.ParamString(r, "id") ctx := r.Context() - album, err := c.ds.Album(ctx).Get(id) + album, err := api.ds.Album(ctx).Get(id) if errors.Is(err, model.ErrNotFound) { log.Error(ctx, "Requested AlbumID not found ", "id", id) return nil, newError(responses.ErrorDataNotFound, "Album not found") @@ -175,22 +166,22 @@ func (c *BrowsingController) GetAlbum(w http.ResponseWriter, r *http.Request) (* return nil, err } - mfs, err := c.ds.MediaFile(ctx).GetAll(filter.SongsByAlbum(id)) + mfs, err := api.ds.MediaFile(ctx).GetAll(filter.SongsByAlbum(id)) if err != nil { log.Error(ctx, "Error retrieving tracks from album", "id", id, "name", album.Name, err) return nil, err } response := newResponse() - response.AlbumWithSongsID3 = c.buildAlbum(ctx, album, mfs) + response.AlbumWithSongsID3 = api.buildAlbum(ctx, album, mfs) return response, nil } -func (c *BrowsingController) GetSong(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetSong(r *http.Request) (*responses.Subsonic, error) { id := utils.ParamString(r, "id") ctx := r.Context() - mf, err := c.ds.MediaFile(ctx).Get(id) + mf, err := api.ds.MediaFile(ctx).Get(id) if errors.Is(err, model.ErrNotFound) { log.Error(r, "Requested MediaFileID not found ", "id", id) return nil, newError(responses.ErrorDataNotFound, "Song not found") @@ -206,9 +197,9 @@ func (c *BrowsingController) GetSong(w http.ResponseWriter, r *http.Request) (*r return response, nil } -func (c *BrowsingController) GetGenres(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetGenres(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() - genres, err := c.ds.Genre(ctx).GetAll(model.QueryOptions{Sort: "song_count, album_count, name desc", Order: "desc"}) + genres, err := api.ds.Genre(ctx).GetAll(model.QueryOptions{Sort: "song_count, album_count, name desc", Order: "desc"}) if err != nil { log.Error(r, err) return nil, err @@ -224,7 +215,7 @@ func (c *BrowsingController) GetGenres(w http.ResponseWriter, r *http.Request) ( return response, nil } -func (c *BrowsingController) GetArtistInfo(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetArtistInfo(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() id, err := requiredParamString(r, "id") if err != nil { @@ -233,7 +224,7 @@ func (c *BrowsingController) GetArtistInfo(w http.ResponseWriter, r *http.Reques count := utils.ParamInt(r, "count", 20) includeNotPresent := utils.ParamBool(r, "includeNotPresent", false) - artist, err := c.em.UpdateArtistInfo(ctx, id, count, includeNotPresent) + artist, err := api.externalMetadata.UpdateArtistInfo(ctx, id, count, includeNotPresent) if err != nil { return nil, err } @@ -253,8 +244,8 @@ func (c *BrowsingController) GetArtistInfo(w http.ResponseWriter, r *http.Reques return response, nil } -func (c *BrowsingController) GetArtistInfo2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - info, err := c.GetArtistInfo(w, r) +func (api *Router) GetArtistInfo2(r *http.Request) (*responses.Subsonic, error) { + info, err := api.GetArtistInfo(r) if err != nil { return nil, err } @@ -275,7 +266,7 @@ func (c *BrowsingController) GetArtistInfo2(w http.ResponseWriter, r *http.Reque return response, nil } -func (c *BrowsingController) GetSimilarSongs(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetSimilarSongs(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() id, err := requiredParamString(r, "id") if err != nil { @@ -283,7 +274,7 @@ func (c *BrowsingController) GetSimilarSongs(w http.ResponseWriter, r *http.Requ } count := utils.ParamInt(r, "count", 50) - songs, err := c.em.SimilarSongs(ctx, id, count) + songs, err := api.externalMetadata.SimilarSongs(ctx, id, count) if err != nil { return nil, err } @@ -295,8 +286,8 @@ func (c *BrowsingController) GetSimilarSongs(w http.ResponseWriter, r *http.Requ return response, nil } -func (c *BrowsingController) GetSimilarSongs2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - res, err := c.GetSimilarSongs(w, r) +func (api *Router) GetSimilarSongs2(r *http.Request) (*responses.Subsonic, error) { + res, err := api.GetSimilarSongs(r) if err != nil { return nil, err } @@ -308,7 +299,7 @@ func (c *BrowsingController) GetSimilarSongs2(w http.ResponseWriter, r *http.Req return response, nil } -func (c *BrowsingController) GetTopSongs(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetTopSongs(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() artist, err := requiredParamString(r, "artist") if err != nil { @@ -316,7 +307,7 @@ func (c *BrowsingController) GetTopSongs(w http.ResponseWriter, r *http.Request) } count := utils.ParamInt(r, "count", 50) - songs, err := c.em.TopSongs(ctx, artist, count) + songs, err := api.externalMetadata.TopSongs(ctx, artist, count) if err != nil { return nil, err } @@ -328,7 +319,7 @@ func (c *BrowsingController) GetTopSongs(w http.ResponseWriter, r *http.Request) return response, nil } -func (c *BrowsingController) buildArtistDirectory(ctx context.Context, artist *model.Artist) (*responses.Directory, error) { +func (api *Router) buildArtistDirectory(ctx context.Context, artist *model.Artist) (*responses.Directory, error) { dir := &responses.Directory{} dir.Id = artist.ID dir.Name = artist.Name @@ -342,7 +333,7 @@ func (c *BrowsingController) buildArtistDirectory(ctx context.Context, artist *m dir.Starred = &artist.StarredAt } - albums, err := c.ds.Album(ctx).GetAllWithoutGenres(filter.AlbumsByArtistID(artist.ID)) + albums, err := api.ds.Album(ctx).GetAllWithoutGenres(filter.AlbumsByArtistID(artist.ID)) if err != nil { return nil, err } @@ -351,14 +342,14 @@ func (c *BrowsingController) buildArtistDirectory(ctx context.Context, artist *m return dir, nil } -func (c *BrowsingController) buildArtist(ctx context.Context, artist *model.Artist, albums model.Albums) *responses.ArtistWithAlbumsID3 { +func (api *Router) buildArtist(ctx context.Context, artist *model.Artist, albums model.Albums) *responses.ArtistWithAlbumsID3 { a := &responses.ArtistWithAlbumsID3{} a.ArtistID3 = toArtistID3(ctx, *artist) a.Album = childrenFromAlbums(ctx, albums) return a } -func (c *BrowsingController) buildAlbumDirectory(ctx context.Context, album *model.Album) (*responses.Directory, error) { +func (api *Router) buildAlbumDirectory(ctx context.Context, album *model.Album) (*responses.Directory, error) { dir := &responses.Directory{} dir.Id = album.ID dir.Name = album.Name @@ -374,7 +365,7 @@ func (c *BrowsingController) buildAlbumDirectory(ctx context.Context, album *mod dir.Starred = &album.StarredAt } - mfs, err := c.ds.MediaFile(ctx).GetAll(filter.SongsByAlbum(album.ID)) + mfs, err := api.ds.MediaFile(ctx).GetAll(filter.SongsByAlbum(album.ID)) if err != nil { return nil, err } @@ -383,7 +374,7 @@ func (c *BrowsingController) buildAlbumDirectory(ctx context.Context, album *mod return dir, nil } -func (c *BrowsingController) buildAlbum(ctx context.Context, album *model.Album, mfs model.MediaFiles) *responses.AlbumWithSongsID3 { +func (api *Router) buildAlbum(ctx context.Context, album *model.Album, mfs model.MediaFiles) *responses.AlbumWithSongsID3 { dir := &responses.AlbumWithSongsID3{} dir.Id = album.ID dir.Name = album.Name diff --git a/server/subsonic/library_scanning.go b/server/subsonic/library_scanning.go index 58066a70..dc0838a9 100644 --- a/server/subsonic/library_scanning.go +++ b/server/subsonic/library_scanning.go @@ -7,24 +7,15 @@ import ( "github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model/request" - "github.com/navidrome/navidrome/scanner" "github.com/navidrome/navidrome/server/subsonic/responses" "github.com/navidrome/navidrome/utils" ) -type LibraryScanningController struct { - scanner scanner.Scanner -} - -func NewLibraryScanningController(scanner scanner.Scanner) *LibraryScanningController { - return &LibraryScanningController{scanner: scanner} -} - -func (c *LibraryScanningController) GetScanStatus(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetScanStatus(r *http.Request) (*responses.Subsonic, error) { // TODO handle multiple mediafolders ctx := r.Context() mediaFolder := conf.Server.MusicFolder - status, err := c.scanner.Status(mediaFolder) + status, err := api.scanner.Status(mediaFolder) if err != nil { log.Error(ctx, "Error retrieving Scanner status", err) return nil, newError(responses.ErrorGeneric, "Internal Error") @@ -39,7 +30,7 @@ func (c *LibraryScanningController) GetScanStatus(w http.ResponseWriter, r *http return response, nil } -func (c *LibraryScanningController) StartScan(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) StartScan(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() loggedUser, ok := request.UserFrom(ctx) if !ok { @@ -55,7 +46,7 @@ func (c *LibraryScanningController) StartScan(w http.ResponseWriter, r *http.Req go func() { start := time.Now() log.Info(ctx, "Triggering manual scan", "fullScan", fullScan, "user", loggedUser.UserName) - err := c.scanner.RescanAll(ctx, fullScan) + err := api.scanner.RescanAll(ctx, fullScan) if err != nil { log.Error(ctx, "Error scanning", err) return @@ -63,5 +54,5 @@ func (c *LibraryScanningController) StartScan(w http.ResponseWriter, r *http.Req log.Info(ctx, "Manual scan complete", "user", loggedUser.UserName, "elapsed", time.Since(start).Round(100*time.Millisecond)) }() - return c.GetScanStatus(w, r) + return api.GetScanStatus(r) } diff --git a/server/subsonic/media_annotation.go b/server/subsonic/media_annotation.go index b1ffb0bb..10c36804 100644 --- a/server/subsonic/media_annotation.go +++ b/server/subsonic/media_annotation.go @@ -17,17 +17,7 @@ import ( "github.com/navidrome/navidrome/utils" ) -type MediaAnnotationController struct { - ds model.DataStore - playTracker scrobbler.PlayTracker - broker events.Broker -} - -func NewMediaAnnotationController(ds model.DataStore, playTracker scrobbler.PlayTracker, broker events.Broker) *MediaAnnotationController { - return &MediaAnnotationController{ds: ds, playTracker: playTracker, broker: broker} -} - -func (c *MediaAnnotationController) SetRating(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) SetRating(r *http.Request) (*responses.Subsonic, error) { id, err := requiredParamString(r, "id") if err != nil { return nil, err @@ -38,7 +28,7 @@ func (c *MediaAnnotationController) SetRating(w http.ResponseWriter, r *http.Req } log.Debug(r, "Setting rating", "rating", rating, "id", id) - err = c.setRating(r.Context(), id, rating) + err = api.setRating(r.Context(), id, rating) if errors.Is(err, model.ErrNotFound) { log.Error(r, err) @@ -52,23 +42,23 @@ func (c *MediaAnnotationController) SetRating(w http.ResponseWriter, r *http.Req return newResponse(), nil } -func (c *MediaAnnotationController) setRating(ctx context.Context, id string, rating int) error { +func (api *Router) setRating(ctx context.Context, id string, rating int) error { var repo model.AnnotatedRepository var resource string - entity, err := core.GetEntityByID(ctx, c.ds, id) + entity, err := core.GetEntityByID(ctx, api.ds, id) if err != nil { return err } switch entity.(type) { case *model.Artist: - repo = c.ds.Artist(ctx) + repo = api.ds.Artist(ctx) resource = "artist" case *model.Album: - repo = c.ds.Album(ctx) + repo = api.ds.Album(ctx) resource = "album" default: - repo = c.ds.MediaFile(ctx) + repo = api.ds.MediaFile(ctx) resource = "song" } err = repo.SetRating(rating, id) @@ -76,11 +66,11 @@ func (c *MediaAnnotationController) setRating(ctx context.Context, id string, ra return err } event := &events.RefreshResource{} - c.broker.SendMessage(ctx, event.With(resource, id)) + api.broker.SendMessage(ctx, event.With(resource, id)) return nil } -func (c *MediaAnnotationController) Star(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) Star(r *http.Request) (*responses.Subsonic, error) { ids := utils.ParamStrings(r, "id") albumIds := utils.ParamStrings(r, "albumId") artistIds := utils.ParamStrings(r, "artistId") @@ -90,7 +80,7 @@ func (c *MediaAnnotationController) Star(w http.ResponseWriter, r *http.Request) ids = append(ids, albumIds...) ids = append(ids, artistIds...) - err := c.setStar(r.Context(), true, ids...) + err := api.setStar(r.Context(), true, ids...) if err != nil { return nil, err } @@ -98,7 +88,7 @@ func (c *MediaAnnotationController) Star(w http.ResponseWriter, r *http.Request) return newResponse(), nil } -func (c *MediaAnnotationController) Unstar(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) Unstar(r *http.Request) (*responses.Subsonic, error) { ids := utils.ParamStrings(r, "id") albumIds := utils.ParamStrings(r, "albumId") artistIds := utils.ParamStrings(r, "artistId") @@ -108,7 +98,7 @@ func (c *MediaAnnotationController) Unstar(w http.ResponseWriter, r *http.Reques ids = append(ids, albumIds...) ids = append(ids, artistIds...) - err := c.setStar(r.Context(), false, ids...) + err := api.setStar(r.Context(), false, ids...) if err != nil { return nil, err } @@ -116,7 +106,7 @@ func (c *MediaAnnotationController) Unstar(w http.ResponseWriter, r *http.Reques return newResponse(), nil } -func (c *MediaAnnotationController) setStar(ctx context.Context, star bool, ids ...string) error { +func (api *Router) setStar(ctx context.Context, star bool, ids ...string) error { if len(ids) == 0 { return nil } @@ -126,7 +116,7 @@ func (c *MediaAnnotationController) setStar(ctx context.Context, star bool, ids return nil } event := &events.RefreshResource{} - err := c.ds.WithTx(func(tx model.DataStore) error { + err := api.ds.WithTx(func(tx model.DataStore) error { for _, id := range ids { exist, err := tx.Album(ctx).Exists(id) if err != nil { @@ -158,7 +148,7 @@ func (c *MediaAnnotationController) setStar(ctx context.Context, star bool, ids } event = event.With("song", id) } - c.broker.SendMessage(ctx, event) + api.broker.SendMessage(ctx, event) return nil }) @@ -173,7 +163,7 @@ func (c *MediaAnnotationController) setStar(ctx context.Context, star bool, ids return nil } -func (c *MediaAnnotationController) Scrobble(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) Scrobble(r *http.Request) (*responses.Subsonic, error) { ids, err := requiredParamStrings(r, "id") if err != nil { return nil, err @@ -186,12 +176,12 @@ func (c *MediaAnnotationController) Scrobble(w http.ResponseWriter, r *http.Requ ctx := r.Context() if submission { - err := c.scrobblerSubmit(ctx, ids, times) + err := api.scrobblerSubmit(ctx, ids, times) if err != nil { log.Error(ctx, "Error registering scrobbles", "ids", ids, "times", times, err) } } else { - err := c.scrobblerNowPlaying(ctx, ids[0]) + err := api.scrobblerNowPlaying(ctx, ids[0]) if err != nil { log.Error(ctx, "Error setting NowPlaying", "id", ids[0], err) } @@ -200,7 +190,7 @@ func (c *MediaAnnotationController) Scrobble(w http.ResponseWriter, r *http.Requ return newResponse(), nil } -func (c *MediaAnnotationController) scrobblerSubmit(ctx context.Context, ids []string, times []time.Time) error { +func (api *Router) scrobblerSubmit(ctx context.Context, ids []string, times []time.Time) error { var submissions []scrobbler.Submission log.Debug(ctx, "Scrobbling tracks", "ids", ids, "times", times) for i, id := range ids { @@ -213,11 +203,11 @@ func (c *MediaAnnotationController) scrobblerSubmit(ctx context.Context, ids []s submissions = append(submissions, scrobbler.Submission{TrackID: id, Timestamp: t}) } - return c.playTracker.Submit(ctx, submissions) + return api.scrobbler.Submit(ctx, submissions) } -func (c *MediaAnnotationController) scrobblerNowPlaying(ctx context.Context, trackId string) error { - mf, err := c.ds.MediaFile(ctx).Get(trackId) +func (api *Router) scrobblerNowPlaying(ctx context.Context, trackId string) error { + mf, err := api.ds.MediaFile(ctx).Get(trackId) if err != nil { return err } @@ -234,6 +224,6 @@ func (c *MediaAnnotationController) scrobblerNowPlaying(ctx context.Context, tra } log.Info(ctx, "Now Playing", "title", mf.Title, "artist", mf.Artist, "user", username, "player", player.Name) - err = c.playTracker.NowPlaying(ctx, clientId, client, trackId) + err = api.scrobbler.NowPlaying(ctx, clientId, client, trackId) return err } diff --git a/server/subsonic/media_annotation_test.go b/server/subsonic/media_annotation_test.go index 7fd790da..7b7f2848 100644 --- a/server/subsonic/media_annotation_test.go +++ b/server/subsonic/media_annotation_test.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "net/http" - "net/http/httptest" "time" "github.com/navidrome/navidrome/model/request" @@ -19,8 +18,7 @@ import ( ) var _ = Describe("MediaAnnotationController", func() { - var controller *MediaAnnotationController - var w *httptest.ResponseRecorder + var router *Router var ds model.DataStore var playTracker *fakePlayTracker var eventBroker *fakeEventBroker @@ -31,8 +29,7 @@ var _ = Describe("MediaAnnotationController", func() { ds = &tests.MockDataStore{} playTracker = &fakePlayTracker{} eventBroker = &fakeEventBroker{} - controller = NewMediaAnnotationController(ds, playTracker, eventBroker) - w = httptest.NewRecorder() + router = New(ds, nil, nil, nil, nil, nil, nil, eventBroker, nil, playTracker) }) Describe("Scrobble", func() { @@ -40,7 +37,7 @@ var _ = Describe("MediaAnnotationController", func() { submissionTime := time.Now() r := newGetRequest("id=12", "id=34") - _, err := controller.Scrobble(w, r) + _, err := router.Scrobble(r) Expect(err).ToNot(HaveOccurred()) Expect(playTracker.Submissions).To(HaveLen(2)) @@ -57,7 +54,7 @@ var _ = Describe("MediaAnnotationController", func() { t2 := utils.ToMillis(time2) r := newGetRequest("id=12", "id=34", fmt.Sprintf("time=%d", t1), fmt.Sprintf("time=%d", t2)) - _, err := controller.Scrobble(w, r) + _, err := router.Scrobble(r) Expect(err).ToNot(HaveOccurred()) Expect(playTracker.Submissions).To(HaveLen(2)) @@ -70,7 +67,7 @@ var _ = Describe("MediaAnnotationController", func() { It("checks if number of ids match number of times", func() { r := newGetRequest("id=12", "id=34", "time=1111") - _, err := controller.Scrobble(w, r) + _, err := router.Scrobble(r) Expect(err).To(HaveOccurred()) Expect(playTracker.Submissions).To(BeEmpty()) @@ -86,14 +83,14 @@ var _ = Describe("MediaAnnotationController", func() { }) It("does not scrobble", func() { - _, err := controller.Scrobble(w, req) + _, err := router.Scrobble(req) Expect(err).ToNot(HaveOccurred()) Expect(playTracker.Submissions).To(BeEmpty()) }) It("registers a NowPlaying", func() { - _, err := controller.Scrobble(w, req) + _, err := router.Scrobble(req) Expect(err).ToNot(HaveOccurred()) Expect(playTracker.Playing).To(HaveLen(1)) @@ -109,7 +106,7 @@ type fakePlayTracker struct { Error error } -func (f *fakePlayTracker) NowPlaying(ctx context.Context, playerId string, playerName string, trackId string) error { +func (f *fakePlayTracker) NowPlaying(_ context.Context, playerId string, _ string, trackId string) error { if f.Error != nil { return f.Error } @@ -120,11 +117,11 @@ func (f *fakePlayTracker) NowPlaying(ctx context.Context, playerId string, playe return nil } -func (f *fakePlayTracker) GetNowPlaying(ctx context.Context) ([]scrobbler.NowPlayingInfo, error) { +func (f *fakePlayTracker) GetNowPlaying(_ context.Context) ([]scrobbler.NowPlayingInfo, error) { return nil, f.Error } -func (f *fakePlayTracker) Submit(ctx context.Context, submissions []scrobbler.Submission) error { +func (f *fakePlayTracker) Submit(_ context.Context, submissions []scrobbler.Submission) error { if f.Error != nil { return f.Error } @@ -139,7 +136,7 @@ type fakeEventBroker struct { Events []events.Event } -func (f *fakeEventBroker) SendMessage(ctx context.Context, event events.Event) { +func (f *fakeEventBroker) SendMessage(_ context.Context, event events.Event) { f.Events = append(f.Events, event) } diff --git a/server/subsonic/media_retrieval.go b/server/subsonic/media_retrieval.go index ca250d7f..5f9bfa61 100644 --- a/server/subsonic/media_retrieval.go +++ b/server/subsonic/media_retrieval.go @@ -8,7 +8,6 @@ import ( "github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/consts" - "github.com/navidrome/navidrome/core" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/resources" @@ -18,37 +17,28 @@ import ( "github.com/navidrome/navidrome/utils/gravatar" ) -type MediaRetrievalController struct { - artwork core.Artwork - ds model.DataStore -} - -func NewMediaRetrievalController(artwork core.Artwork, ds model.DataStore) *MediaRetrievalController { - return &MediaRetrievalController{artwork: artwork, ds: ds} -} - -func (c *MediaRetrievalController) GetAvatar(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetAvatar(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { if !conf.Server.EnableGravatar { - return c.getPlaceHolderAvatar(w, r) + return api.getPlaceHolderAvatar(w, r) } username, err := requiredParamString(r, "username") if err != nil { return nil, err } ctx := r.Context() - u, err := c.ds.User(ctx).FindByUsername(username) + u, err := api.ds.User(ctx).FindByUsername(username) if err != nil { return nil, err } if u.Email == "" { log.Warn(ctx, "User needs an email for gravatar to work", "username", username) - return c.getPlaceHolderAvatar(w, r) + return api.getPlaceHolderAvatar(w, r) } http.Redirect(w, r, gravatar.Url(u.Email, 0), http.StatusFound) return nil, nil } -func (c *MediaRetrievalController) getPlaceHolderAvatar(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) getPlaceHolderAvatar(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { f, err := resources.FS().Open(consts.PlaceholderAvatar) if err != nil { log.Error(r, "Image not found", err) @@ -60,13 +50,13 @@ func (c *MediaRetrievalController) getPlaceHolderAvatar(w http.ResponseWriter, r return nil, nil } -func (c *MediaRetrievalController) GetCoverArt(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetCoverArt(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { id := utils.ParamStringDefault(r, "id", "non-existent") size := utils.ParamInt(r, "size", 0) w.Header().Set("cache-control", "public, max-age=315360000") - imgReader, err := c.artwork.Get(r.Context(), id, size) + imgReader, err := api.artwork.Get(r.Context(), id, size) if errors.Is(err, model.ErrNotFound) { log.Error(r, "Couldn't find coverArt", "id", id, err) return nil, newError(responses.ErrorDataNotFound, "Artwork not found") @@ -92,13 +82,13 @@ func isSynced(rawLyrics string) bool { return r.MatchString(rawLyrics) } -func (c *MediaRetrievalController) GetLyrics(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetLyrics(r *http.Request) (*responses.Subsonic, error) { artist := utils.ParamString(r, "artist") title := utils.ParamString(r, "title") response := newResponse() lyrics := responses.Lyrics{} response.Lyrics = &lyrics - media_files, err := c.ds.MediaFile(r.Context()).GetAll(filter.SongsWithLyrics(artist, title)) + media_files, err := api.ds.MediaFile(r.Context()).GetAll(filter.SongsWithLyrics(artist, title)) if err != nil { return nil, err diff --git a/server/subsonic/media_retrieval_test.go b/server/subsonic/media_retrieval_test.go index e85961ec..0d284336 100644 --- a/server/subsonic/media_retrieval_test.go +++ b/server/subsonic/media_retrieval_test.go @@ -15,7 +15,7 @@ import ( ) var _ = Describe("MediaRetrievalController", func() { - var controller *MediaRetrievalController + var router *Router var ds model.DataStore mockRepo := &mockedMediaFile{} var artwork *fakeArtwork @@ -26,7 +26,7 @@ var _ = Describe("MediaRetrievalController", func() { MockedMediaFile: mockRepo, } artwork = &fakeArtwork{} - controller = NewMediaRetrievalController(artwork, ds) + router = New(ds, artwork, nil, nil, nil, nil, nil, nil, nil, nil) w = httptest.NewRecorder() }) @@ -34,7 +34,7 @@ var _ = Describe("MediaRetrievalController", func() { It("should return data for that id", func() { artwork.data = "image data" r := newGetRequest("id=34", "size=128") - _, err := controller.GetCoverArt(w, r) + _, err := router.GetCoverArt(w, r) Expect(err).To(BeNil()) Expect(artwork.recvId).To(Equal("34")) @@ -44,7 +44,7 @@ var _ = Describe("MediaRetrievalController", func() { It("should return placeholder if id parameter is missing (mimicking Subsonic)", func() { r := newGetRequest() - _, err := controller.GetCoverArt(w, r) + _, err := router.GetCoverArt(w, r) Expect(err).To(BeNil()) Expect(w.Body.String()).To(Equal(artwork.data)) @@ -53,7 +53,7 @@ var _ = Describe("MediaRetrievalController", func() { It("should fail when the file is not found", func() { artwork.err = model.ErrNotFound r := newGetRequest("id=34", "size=128") - _, err := controller.GetCoverArt(w, r) + _, err := router.GetCoverArt(w, r) Expect(err).To(MatchError("Artwork not found")) }) @@ -61,7 +61,7 @@ var _ = Describe("MediaRetrievalController", func() { It("should fail when there is an unknown error", func() { artwork.err = errors.New("weird error") r := newGetRequest("id=34", "size=128") - _, err := controller.GetCoverArt(w, r) + _, err := router.GetCoverArt(w, r) Expect(err).To(MatchError("weird error")) }) @@ -78,7 +78,7 @@ var _ = Describe("MediaRetrievalController", func() { Lyrics: "[00:18.80]We're no strangers to love\n[00:22.80]You know the rules and so do I", }, }) - response, err := controller.GetLyrics(w, r) + response, err := router.GetLyrics(r) if err != nil { log.Error("You're missing something.", err) } @@ -90,7 +90,7 @@ var _ = Describe("MediaRetrievalController", func() { It("should return empty subsonic response if the record corresponding to the given artist & title is not found", func() { r := newGetRequest("artist=Dheeraj", "title=Rinkiya+Ke+Papa") mockRepo.SetData(model.MediaFiles{}) - response, err := controller.GetLyrics(w, r) + response, err := router.GetLyrics(r) if err != nil { log.Error("You're missing something.", err) } @@ -110,7 +110,7 @@ type fakeArtwork struct { recvSize int } -func (c *fakeArtwork) Get(ctx context.Context, id string, size int) (io.ReadCloser, error) { +func (c *fakeArtwork) Get(_ context.Context, id string, size int) (io.ReadCloser, error) { if c.err != nil { return nil, c.err } @@ -129,7 +129,7 @@ var _ = Describe("isSynced", func() { }) It("returns true if lyrics contain timestamps", func() { Expect(isSynced(`NF Real Music - [00:00] ksdjjs + [00:00] First line [00:00.85] JUST LIKE YOU [00:00.85] Just in case my car goes off the highway`)).To(Equal(true)) Expect(isSynced("[04:02:50.85] Never gonna give you up")).To(Equal(true)) @@ -148,6 +148,6 @@ func (m *mockedMediaFile) SetData(mfs model.MediaFiles) { m.data = mfs } -func (m *mockedMediaFile) GetAll(options ...model.QueryOptions) (model.MediaFiles, error) { +func (m *mockedMediaFile) GetAll(...model.QueryOptions) (model.MediaFiles, error) { return m.data, nil } diff --git a/server/subsonic/playlists.go b/server/subsonic/playlists.go index 56b02352..d78944fd 100644 --- a/server/subsonic/playlists.go +++ b/server/subsonic/playlists.go @@ -6,49 +6,39 @@ import ( "fmt" "net/http" - "github.com/navidrome/navidrome/core" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/server/subsonic/responses" "github.com/navidrome/navidrome/utils" ) -type PlaylistsController struct { - ds model.DataStore - pls core.Playlists -} - -func NewPlaylistsController(ds model.DataStore, pls core.Playlists) *PlaylistsController { - return &PlaylistsController{ds: ds, pls: pls} -} - -func (c *PlaylistsController) GetPlaylists(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetPlaylists(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() - allPls, err := c.ds.Playlist(ctx).GetAll(model.QueryOptions{Sort: "name"}) + allPls, err := api.ds.Playlist(ctx).GetAll(model.QueryOptions{Sort: "name"}) if err != nil { log.Error(r, err) return nil, err } playlists := make([]responses.Playlist, len(allPls)) for i, p := range allPls { - playlists[i] = *c.buildPlaylist(p) + playlists[i] = *api.buildPlaylist(p) } response := newResponse() response.Playlists = &responses.Playlists{Playlist: playlists} return response, nil } -func (c *PlaylistsController) GetPlaylist(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetPlaylist(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() id, err := requiredParamString(r, "id") if err != nil { return nil, err } - return c.getPlaylist(ctx, id) + return api.getPlaylist(ctx, id) } -func (c *PlaylistsController) getPlaylist(ctx context.Context, id string) (*responses.Subsonic, error) { - pls, err := c.ds.Playlist(ctx).GetWithTracks(id) +func (api *Router) getPlaylist(ctx context.Context, id string) (*responses.Subsonic, error) { + pls, err := api.ds.Playlist(ctx).GetWithTracks(id) if errors.Is(err, model.ErrNotFound) { log.Error(ctx, err.Error(), "id", id) return nil, newError(responses.ErrorDataNotFound, "Directory not found") @@ -59,12 +49,12 @@ func (c *PlaylistsController) getPlaylist(ctx context.Context, id string) (*resp } response := newResponse() - response.Playlist = c.buildPlaylistWithSongs(ctx, pls) + response.Playlist = api.buildPlaylistWithSongs(ctx, pls) return response, nil } -func (c *PlaylistsController) create(ctx context.Context, playlistId, name string, ids []string) (string, error) { - err := c.ds.WithTx(func(tx model.DataStore) error { +func (api *Router) create(ctx context.Context, playlistId, name string, ids []string) (string, error) { + err := api.ds.WithTx(func(tx model.DataStore) error { owner := getUser(ctx) var pls *model.Playlist var err error @@ -91,7 +81,7 @@ func (c *PlaylistsController) create(ctx context.Context, playlistId, name strin return playlistId, err } -func (c *PlaylistsController) CreatePlaylist(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) CreatePlaylist(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() songIds := utils.ParamStrings(r, "songId") playlistId := utils.ParamString(r, "playlistId") @@ -99,20 +89,20 @@ func (c *PlaylistsController) CreatePlaylist(w http.ResponseWriter, r *http.Requ if playlistId == "" && name == "" { return nil, errors.New("required parameter name is missing") } - id, err := c.create(ctx, playlistId, name, songIds) + id, err := api.create(ctx, playlistId, name, songIds) if err != nil { log.Error(r, err) return nil, err } - return c.getPlaylist(ctx, id) + return api.getPlaylist(ctx, id) } -func (c *PlaylistsController) DeletePlaylist(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) DeletePlaylist(r *http.Request) (*responses.Subsonic, error) { id, err := requiredParamString(r, "id") if err != nil { return nil, err } - err = c.ds.Playlist(r.Context()).Delete(id) + err = api.ds.Playlist(r.Context()).Delete(id) if errors.Is(err, model.ErrNotAuthorized) { return nil, newError(responses.ErrorAuthorizationFail) } @@ -123,7 +113,7 @@ func (c *PlaylistsController) DeletePlaylist(w http.ResponseWriter, r *http.Requ return newResponse(), nil } -func (c *PlaylistsController) UpdatePlaylist(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) UpdatePlaylist(r *http.Request) (*responses.Subsonic, error) { playlistId, err := requiredParamString(r, "playlistId") if err != nil { return nil, err @@ -151,7 +141,7 @@ func (c *PlaylistsController) UpdatePlaylist(w http.ResponseWriter, r *http.Requ log.Trace(r, fmt.Sprintf("-- Adding: '%v'", songsToAdd)) log.Trace(r, fmt.Sprintf("-- Removing: '%v'", songIndexesToRemove)) - err = c.pls.Update(r.Context(), playlistId, plsName, comment, public, songsToAdd, songIndexesToRemove) + err = api.playlists.Update(r.Context(), playlistId, plsName, comment, public, songsToAdd, songIndexesToRemove) if errors.Is(err, model.ErrNotAuthorized) { return nil, newError(responses.ErrorAuthorizationFail) } @@ -162,15 +152,15 @@ func (c *PlaylistsController) UpdatePlaylist(w http.ResponseWriter, r *http.Requ return newResponse(), nil } -func (c *PlaylistsController) buildPlaylistWithSongs(ctx context.Context, p *model.Playlist) *responses.PlaylistWithSongs { +func (api *Router) buildPlaylistWithSongs(ctx context.Context, p *model.Playlist) *responses.PlaylistWithSongs { pls := &responses.PlaylistWithSongs{ - Playlist: *c.buildPlaylist(*p), + Playlist: *api.buildPlaylist(*p), } pls.Entry = childrenFromMediaFiles(ctx, p.MediaFiles()) return pls } -func (c *PlaylistsController) buildPlaylist(p model.Playlist) *responses.Playlist { +func (api *Router) buildPlaylist(p model.Playlist) *responses.Playlist { pls := &responses.Playlist{} pls.Id = p.ID pls.Name = p.Name diff --git a/server/subsonic/responses/responses_test.go b/server/subsonic/responses/responses_test.go index 7b5c920f..b6549112 100644 --- a/server/subsonic/responses/responses_test.go +++ b/server/subsonic/responses/responses_test.go @@ -1,4 +1,4 @@ -//go:build linux || darwin +//go:build unix // TODO Fix snapshot tests in Windows // Response Snapshot tests. Only run in Linux and macOS, as they fail in Windows diff --git a/server/subsonic/searching.go b/server/subsonic/searching.go index 9fca9c4c..ba72d8e6 100644 --- a/server/subsonic/searching.go +++ b/server/subsonic/searching.go @@ -16,10 +16,6 @@ import ( "github.com/navidrome/navidrome/utils" ) -type SearchingController struct { - ds model.DataStore -} - type searchParams struct { query string artistCount int @@ -30,11 +26,7 @@ type searchParams struct { songOffset int } -func NewSearchingController(ds model.DataStore) *SearchingController { - return &SearchingController{ds: ds} -} - -func (c *SearchingController) getParams(r *http.Request) (*searchParams, error) { +func (api *Router) getParams(r *http.Request) (*searchParams, error) { var err error sp := &searchParams{} sp.query, err = requiredParamString(r, "query") @@ -78,15 +70,19 @@ func doSearch[T any](ctx context.Context, wg *sync.WaitGroup, s searchFunc[T], q return res } -func (c *SearchingController) searchAll(r *http.Request, sp *searchParams) (mediaFiles model.MediaFiles, albums model.Albums, artists model.Artists) { +func (api *Router) searchAll(r *http.Request, sp *searchParams) (mediaFiles model.MediaFiles, albums model.Albums, artists model.Artists) { start := time.Now() q := sanitize.Accents(strings.ToLower(strings.TrimSuffix(sp.query, "*"))) ctx := r.Context() wg := &sync.WaitGroup{} wg.Add(3) - go func() { mediaFiles = doSearch(ctx, wg, c.ds.MediaFile(ctx).Search, q, sp.songOffset, sp.songCount) }() - go func() { albums = doSearch(ctx, wg, c.ds.Album(ctx).Search, q, sp.albumOffset, sp.albumCount) }() - go func() { artists = doSearch(ctx, wg, c.ds.Artist(ctx).Search, q, sp.artistOffset, sp.artistCount) }() + go func() { + mediaFiles = doSearch(ctx, wg, api.ds.MediaFile(ctx).Search, q, sp.songOffset, sp.songCount) + }() + go func() { albums = doSearch(ctx, wg, api.ds.Album(ctx).Search, q, sp.albumOffset, sp.albumCount) }() + go func() { + artists = doSearch(ctx, wg, api.ds.Artist(ctx).Search, q, sp.artistOffset, sp.artistCount) + }() wg.Wait() if ctx.Err() == nil { @@ -98,12 +94,12 @@ func (c *SearchingController) searchAll(r *http.Request, sp *searchParams) (medi return mediaFiles, albums, artists } -func (c *SearchingController) Search2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { - sp, err := c.getParams(r) +func (api *Router) Search2(r *http.Request) (*responses.Subsonic, error) { + sp, err := api.getParams(r) if err != nil { return nil, err } - mfs, als, as := c.searchAll(r, sp) + mfs, als, as := api.searchAll(r, sp) response := newResponse() searchResult2 := &responses.SearchResult2{} @@ -126,13 +122,13 @@ func (c *SearchingController) Search2(w http.ResponseWriter, r *http.Request) (* return response, nil } -func (c *SearchingController) Search3(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) Search3(r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() - sp, err := c.getParams(r) + sp, err := api.getParams(r) if err != nil { return nil, err } - mfs, als, as := c.searchAll(r, sp) + mfs, als, as := api.searchAll(r, sp) response := newResponse() searchResult3 := &responses.SearchResult3{} diff --git a/server/subsonic/stream.go b/server/subsonic/stream.go index 77afe5c8..6f3601cf 100644 --- a/server/subsonic/stream.go +++ b/server/subsonic/stream.go @@ -16,17 +16,7 @@ import ( "github.com/navidrome/navidrome/utils" ) -type StreamController struct { - streamer core.MediaStreamer - archiver core.Archiver - ds model.DataStore -} - -func NewStreamController(streamer core.MediaStreamer, archiver core.Archiver, ds model.DataStore) *StreamController { - return &StreamController{streamer: streamer, archiver: archiver, ds: ds} -} - -func (c *StreamController) Stream(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) Stream(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() id, err := requiredParamString(r, "id") if err != nil { @@ -36,7 +26,7 @@ func (c *StreamController) Stream(w http.ResponseWriter, r *http.Request) (*resp format := utils.ParamString(r, "format") estimateContentLength := utils.ParamBool(r, "estimateContentLength", false) - stream, err := c.streamer.NewStream(ctx, id, format, maxBitRate) + stream, err := api.streamer.NewStream(ctx, id, format, maxBitRate) if err != nil { return nil, err } @@ -82,7 +72,7 @@ func (c *StreamController) Stream(w http.ResponseWriter, r *http.Request) (*resp return nil, nil } -func (c *StreamController) Download(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) Download(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { ctx := r.Context() username, _ := request.UsernameFrom(ctx) id, err := requiredParamString(r, "id") @@ -95,7 +85,7 @@ func (c *StreamController) Download(w http.ResponseWriter, r *http.Request) (*re return nil, newError(responses.ErrorAuthorizationFail, "downloads are disabled") } - entity, err := core.GetEntityByID(ctx, c.ds, id) + entity, err := core.GetEntityByID(ctx, api.ds, id) if err != nil { return nil, err } @@ -109,7 +99,7 @@ func (c *StreamController) Download(w http.ResponseWriter, r *http.Request) (*re switch v := entity.(type) { case *model.MediaFile: - stream, err := c.streamer.NewStream(ctx, id, "raw", 0) + stream, err := api.streamer.NewStream(ctx, id, "raw", 0) if err != nil { return nil, err } @@ -120,13 +110,13 @@ func (c *StreamController) Download(w http.ResponseWriter, r *http.Request) (*re return nil, nil case *model.Album: setHeaders(v.Name) - err = c.archiver.ZipAlbum(ctx, id, w) + err = api.archiver.ZipAlbum(ctx, id, w) case *model.Artist: setHeaders(v.Name) - err = c.archiver.ZipArtist(ctx, id, w) + err = api.archiver.ZipArtist(ctx, id, w) case *model.Playlist: setHeaders(v.Name) - err = c.archiver.ZipPlaylist(ctx, id, w) + err = api.archiver.ZipPlaylist(ctx, id, w) default: err = model.ErrNotFound } diff --git a/server/subsonic/system.go b/server/subsonic/system.go index e51a1f78..006ec99b 100644 --- a/server/subsonic/system.go +++ b/server/subsonic/system.go @@ -12,11 +12,11 @@ func NewSystemController() *SystemController { return &SystemController{} } -func (c *SystemController) Ping(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) Ping(_ *http.Request) (*responses.Subsonic, error) { return newResponse(), nil } -func (c *SystemController) GetLicense(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetLicense(_ *http.Request) (*responses.Subsonic, error) { response := newResponse() response.License = &responses.License{Valid: true} return response, nil diff --git a/server/subsonic/users.go b/server/subsonic/users.go index a79b7196..f4cf90ae 100644 --- a/server/subsonic/users.go +++ b/server/subsonic/users.go @@ -7,14 +7,8 @@ import ( "github.com/navidrome/navidrome/server/subsonic/responses" ) -type UsersController struct{} - -func NewUsersController() *UsersController { - return &UsersController{} -} - // TODO This is a placeholder. The real one has to read this info from a config file or the database -func (c *UsersController) GetUser(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetUser(r *http.Request) (*responses.Subsonic, error) { loggedUser, ok := request.UserFrom(r.Context()) if !ok { return nil, newError(responses.ErrorGeneric, "Internal error") @@ -30,7 +24,7 @@ func (c *UsersController) GetUser(w http.ResponseWriter, r *http.Request) (*resp return response, nil } -func (c *UsersController) GetUsers(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { +func (api *Router) GetUsers(r *http.Request) (*responses.Subsonic, error) { loggedUser, ok := request.UserFrom(r.Context()) if !ok { return nil, newError(responses.ErrorGeneric, "Internal error") diff --git a/server/subsonic/wire_gen.go b/server/subsonic/wire_gen.go deleted file mode 100644 index 30aca1db..00000000 --- a/server/subsonic/wire_gen.go +++ /dev/null @@ -1,112 +0,0 @@ -// Code generated by Wire. DO NOT EDIT. - -//go:generate go run github.com/google/wire/cmd/wire -//go:build !wireinject -// +build !wireinject - -package subsonic - -import ( - "github.com/google/wire" -) - -// Injectors from wire_injectors.go: - -func initSystemController(router *Router) *SystemController { - systemController := NewSystemController() - return systemController -} - -func initBrowsingController(router *Router) *BrowsingController { - dataStore := router.DataStore - externalMetadata := router.ExternalMetadata - browsingController := NewBrowsingController(dataStore, externalMetadata) - return browsingController -} - -func initAlbumListController(router *Router) *AlbumListController { - dataStore := router.DataStore - playTracker := router.Scrobbler - albumListController := NewAlbumListController(dataStore, playTracker) - return albumListController -} - -func initMediaAnnotationController(router *Router) *MediaAnnotationController { - dataStore := router.DataStore - playTracker := router.Scrobbler - broker := router.Broker - mediaAnnotationController := NewMediaAnnotationController(dataStore, playTracker, broker) - return mediaAnnotationController -} - -func initPlaylistsController(router *Router) *PlaylistsController { - dataStore := router.DataStore - playlists := router.Playlists - playlistsController := NewPlaylistsController(dataStore, playlists) - return playlistsController -} - -func initSearchingController(router *Router) *SearchingController { - dataStore := router.DataStore - searchingController := NewSearchingController(dataStore) - return searchingController -} - -func initUsersController(router *Router) *UsersController { - usersController := NewUsersController() - return usersController -} - -func initMediaRetrievalController(router *Router) *MediaRetrievalController { - artwork := router.Artwork - dataStore := router.DataStore - mediaRetrievalController := NewMediaRetrievalController(artwork, dataStore) - return mediaRetrievalController -} - -func initStreamController(router *Router) *StreamController { - mediaStreamer := router.Streamer - archiver := router.Archiver - dataStore := router.DataStore - streamController := NewStreamController(mediaStreamer, archiver, dataStore) - return streamController -} - -func initBookmarksController(router *Router) *BookmarksController { - dataStore := router.DataStore - bookmarksController := NewBookmarksController(dataStore) - return bookmarksController -} - -func initLibraryScanningController(router *Router) *LibraryScanningController { - scanner := router.Scanner - libraryScanningController := NewLibraryScanningController(scanner) - return libraryScanningController -} - -// wire_injectors.go: - -var allProviders = wire.NewSet( - NewSystemController, - NewBrowsingController, - NewAlbumListController, - NewMediaAnnotationController, - NewPlaylistsController, - NewSearchingController, - NewUsersController, - NewMediaRetrievalController, - NewStreamController, - NewBookmarksController, - NewLibraryScanningController, wire.FieldsOf( - new(*Router), - "DataStore", - "Artwork", - "Streamer", - "Archiver", - "ExternalMetadata", - "Scanner", - "Broker", - "Scrobbler", - "Playlists", - ), -) diff --git a/server/subsonic/wire_injectors.go b/server/subsonic/wire_injectors.go deleted file mode 100644 index 7d262751..00000000 --- a/server/subsonic/wire_injectors.go +++ /dev/null @@ -1,77 +0,0 @@ -//go:build wireinject - -package subsonic - -import ( - "github.com/google/wire" -) - -var allProviders = wire.NewSet( - NewSystemController, - NewBrowsingController, - NewAlbumListController, - NewMediaAnnotationController, - NewPlaylistsController, - NewSearchingController, - NewUsersController, - NewMediaRetrievalController, - NewStreamController, - NewBookmarksController, - NewLibraryScanningController, - wire.FieldsOf( - new(*Router), - "DataStore", - "Artwork", - "Streamer", - "Archiver", - "ExternalMetadata", - "Scanner", - "Broker", - "Scrobbler", - "Playlists", - ), -) - -func initSystemController(router *Router) *SystemController { - panic(wire.Build(allProviders)) -} - -func initBrowsingController(router *Router) *BrowsingController { - panic(wire.Build(allProviders)) -} - -func initAlbumListController(router *Router) *AlbumListController { - panic(wire.Build(allProviders)) -} - -func initMediaAnnotationController(router *Router) *MediaAnnotationController { - panic(wire.Build(allProviders)) -} - -func initPlaylistsController(router *Router) *PlaylistsController { - panic(wire.Build(allProviders)) -} - -func initSearchingController(router *Router) *SearchingController { - panic(wire.Build(allProviders)) -} - -func initUsersController(router *Router) *UsersController { - panic(wire.Build(allProviders)) -} - -func initMediaRetrievalController(router *Router) *MediaRetrievalController { - panic(wire.Build(allProviders)) -} - -func initStreamController(router *Router) *StreamController { - panic(wire.Build(allProviders)) -} - -func initBookmarksController(router *Router) *BookmarksController { - panic(wire.Build(allProviders)) -} - -func initLibraryScanningController(router *Router) *LibraryScanningController { - panic(wire.Build(allProviders)) -}