Simplify Subsonic API handler implementation

This commit is contained in:
Deluan 2022-11-21 12:57:56 -05:00
parent cd41d9a419
commit 19af11efbe
18 changed files with 280 additions and 564 deletions

View File

@ -6,7 +6,6 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/navidrome/navidrome/core/scrobbler"
"github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/server/subsonic/filter" "github.com/navidrome/navidrome/server/subsonic/filter"
@ -14,20 +13,7 @@ import (
"github.com/navidrome/navidrome/utils" "github.com/navidrome/navidrome/utils"
) )
type AlbumListController struct { func (api *Router) getAlbumList(r *http.Request) (model.Albums, int64, error) {
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) {
typ, err := requiredParamString(r, "type") typ, err := requiredParamString(r, "type")
if err != nil { if err != nil {
return nil, 0, err 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.Offset = utils.ParamInt(r, "offset", 0)
opts.Max = utils.MinInt(utils.ParamInt(r, "size", 10), 500) 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 { if err != nil {
log.Error(r, "Error retrieving albums", "error", err) log.Error(r, "Error retrieving albums", "error", err)
return nil, 0, newError(responses.ErrorGeneric, "internal error") 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 { if err != nil {
log.Error(r, "Error counting albums", "error", err) log.Error(r, "Error counting albums", "error", err)
return nil, 0, newError(responses.ErrorGeneric, "internal error") 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 return albums, count, nil
} }
func (c *AlbumListController) GetAlbumList(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (api *Router) GetAlbumList(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
albums, count, err := c.getAlbumList(r) albums, count, err := api.getAlbumList(r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -103,8 +89,8 @@ func (c *AlbumListController) GetAlbumList(w http.ResponseWriter, r *http.Reques
return response, nil return response, nil
} }
func (c *AlbumListController) GetAlbumList2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (api *Router) GetAlbumList2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
albums, pageCount, err := c.getAlbumList(r) albums, pageCount, err := api.getAlbumList(r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -116,20 +102,20 @@ func (c *AlbumListController) GetAlbumList2(w http.ResponseWriter, r *http.Reque
return response, nil 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() ctx := r.Context()
options := filter.Starred() options := filter.Starred()
artists, err := c.ds.Artist(ctx).GetAll(options) artists, err := api.ds.Artist(ctx).GetAll(options)
if err != nil { if err != nil {
log.Error(r, "Error retrieving starred artists", "error", err) log.Error(r, "Error retrieving starred artists", "error", err)
return nil, err return nil, err
} }
albums, err := c.ds.Album(ctx).GetAllWithoutGenres(options) albums, err := api.ds.Album(ctx).GetAllWithoutGenres(options)
if err != nil { if err != nil {
log.Error(r, "Error retrieving starred albums", "error", err) log.Error(r, "Error retrieving starred albums", "error", err)
return nil, err return nil, err
} }
mediaFiles, err := c.ds.MediaFile(ctx).GetAll(options) mediaFiles, err := api.ds.MediaFile(ctx).GetAll(options)
if err != nil { if err != nil {
log.Error(r, "Error retrieving starred mediaFiles", "error", err) log.Error(r, "Error retrieving starred mediaFiles", "error", err)
return nil, err return nil, err
@ -143,8 +129,8 @@ func (c *AlbumListController) GetStarred(w http.ResponseWriter, r *http.Request)
return response, nil return response, nil
} }
func (c *AlbumListController) GetStarred2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (api *Router) GetStarred2(r *http.Request) (*responses.Subsonic, error) {
resp, err := c.GetStarred(w, r) resp, err := api.GetStarred(r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -154,9 +140,9 @@ func (c *AlbumListController) GetStarred2(w http.ResponseWriter, r *http.Request
return response, nil 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() ctx := r.Context()
npInfo, err := c.scrobbler.GetNowPlaying(ctx) npInfo, err := api.scrobbler.GetNowPlaying(ctx)
if err != nil { if err != nil {
log.Error(r, "Error retrieving now playing list", "error", err) log.Error(r, "Error retrieving now playing list", "error", err)
return nil, err return nil, err
@ -166,7 +152,7 @@ func (c *AlbumListController) GetNowPlaying(w http.ResponseWriter, r *http.Reque
response.NowPlaying = &responses.NowPlaying{} response.NowPlaying = &responses.NowPlaying{}
response.NowPlaying.Entry = make([]responses.NowPlayingEntry, len(npInfo)) response.NowPlaying.Entry = make([]responses.NowPlayingEntry, len(npInfo))
for i, np := range 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 { if err != nil {
return nil, err return nil, err
} }
@ -180,13 +166,13 @@ func (c *AlbumListController) GetNowPlaying(w http.ResponseWriter, r *http.Reque
return response, nil 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) size := utils.MinInt(utils.ParamInt(r, "size", 10), 500)
genre := utils.ParamString(r, "genre") genre := utils.ParamString(r, "genre")
fromYear := utils.ParamInt(r, "fromYear", 0) fromYear := utils.ParamInt(r, "fromYear", 0)
toYear := utils.ParamInt(r, "toYear", 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 { if err != nil {
log.Error(r, "Error retrieving random songs", "error", err) log.Error(r, "Error retrieving random songs", "error", err)
return nil, err return nil, err
@ -198,12 +184,12 @@ func (c *AlbumListController) GetRandomSongs(w http.ResponseWriter, r *http.Requ
return response, nil 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) count := utils.MinInt(utils.ParamInt(r, "count", 10), 500)
offset := utils.MinInt(utils.ParamInt(r, "offset", 0), 500) offset := utils.MinInt(utils.ParamInt(r, "offset", 0), 500)
genre := utils.ParamString(r, "genre") 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 { if err != nil {
log.Error(r, "Error retrieving random songs", "error", err) log.Error(r, "Error retrieving random songs", "error", err)
return nil, err return nil, err
@ -215,8 +201,8 @@ func (c *AlbumListController) GetSongsByGenre(w http.ResponseWriter, r *http.Req
return response, nil 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.Offset = offset
opts.Max = size opts.Max = size
return c.ds.MediaFile(ctx).GetAll(opts) return api.ds.MediaFile(ctx).GetAll(opts)
} }

View File

@ -14,8 +14,8 @@ import (
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
var _ = Describe("AlbumListController", func() { var _ = Describe("Album Lists", func() {
var controller *AlbumListController var router *Router
var ds model.DataStore var ds model.DataStore
var mockRepo *tests.MockAlbumRepo var mockRepo *tests.MockAlbumRepo
var w *httptest.ResponseRecorder var w *httptest.ResponseRecorder
@ -24,7 +24,7 @@ var _ = Describe("AlbumListController", func() {
BeforeEach(func() { BeforeEach(func() {
ds = &tests.MockDataStore{} ds = &tests.MockDataStore{}
mockRepo = ds.Album(ctx).(*tests.MockAlbumRepo) 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() w = httptest.NewRecorder()
}) })
@ -34,7 +34,7 @@ var _ = Describe("AlbumListController", func() {
mockRepo.SetData(model.Albums{ mockRepo.SetData(model.Albums{
{ID: "1"}, {ID: "2"}, {ID: "1"}, {ID: "2"},
}) })
resp, err := controller.GetAlbumList(w, r) resp, err := router.GetAlbumList(w, r)
Expect(err).To(BeNil()) Expect(err).To(BeNil())
Expect(resp.AlbumList.Album[0].Id).To(Equal("1")) 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() { It("should fail if missing type parameter", func() {
r := newGetRequest() r := newGetRequest()
_, err := controller.GetAlbumList(w, r) _, err := router.GetAlbumList(w, r)
var subErr subError var subErr subError
isSubError := errors.As(err, &subErr) isSubError := errors.As(err, &subErr)
@ -59,7 +59,7 @@ var _ = Describe("AlbumListController", func() {
mockRepo.SetError(true) mockRepo.SetError(true)
r := newGetRequest("type=newest") r := newGetRequest("type=newest")
_, err := controller.GetAlbumList(w, r) _, err := router.GetAlbumList(w, r)
Expect(err).ToNot(BeNil()) Expect(err).ToNot(BeNil())
var subErr subError var subErr subError
@ -74,7 +74,7 @@ var _ = Describe("AlbumListController", func() {
mockRepo.SetData(model.Albums{ mockRepo.SetData(model.Albums{
{ID: "1"}, {ID: "2"}, {ID: "1"}, {ID: "2"},
}) })
resp, err := controller.GetAlbumList2(w, r) resp, err := router.GetAlbumList2(w, r)
Expect(err).To(BeNil()) Expect(err).To(BeNil())
Expect(resp.AlbumList2.Album[0].Id).To(Equal("1")) 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() { It("should fail if missing type parameter", func() {
r := newGetRequest() r := newGetRequest()
_, err := controller.GetAlbumList2(w, r) _, err := router.GetAlbumList2(w, r)
var subErr subError var subErr subError
errors.As(err, &subErr) errors.As(err, &subErr)
@ -99,7 +99,7 @@ var _ = Describe("AlbumListController", func() {
mockRepo.SetError(true) mockRepo.SetError(true)
r := newGetRequest("type=newest") r := newGetRequest("type=newest")
_, err := controller.GetAlbumList2(w, r) _, err := router.GetAlbumList2(w, r)
var subErr subError var subErr subError
errors.As(err, &subErr) errors.As(err, &subErr)

View File

@ -23,36 +23,37 @@ import (
const Version = "1.16.1" 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 { type Router struct {
http.Handler http.Handler
DataStore model.DataStore ds model.DataStore
Artwork core.Artwork artwork core.Artwork
Streamer core.MediaStreamer streamer core.MediaStreamer
Archiver core.Archiver archiver core.Archiver
Players core.Players players core.Players
ExternalMetadata core.ExternalMetadata externalMetadata core.ExternalMetadata
Playlists core.Playlists playlists core.Playlists
Scanner scanner.Scanner scanner scanner.Scanner
Broker events.Broker broker events.Broker
Scrobbler scrobbler.PlayTracker scrobbler scrobbler.PlayTracker
} }
func New(ds model.DataStore, artwork core.Artwork, streamer core.MediaStreamer, archiver core.Archiver, 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, players core.Players, externalMetadata core.ExternalMetadata, scanner scanner.Scanner, broker events.Broker,
playlists core.Playlists, scrobbler scrobbler.PlayTracker) *Router { playlists core.Playlists, scrobbler scrobbler.PlayTracker) *Router {
r := &Router{ r := &Router{
DataStore: ds, ds: ds,
Artwork: artwork, artwork: artwork,
Streamer: streamer, streamer: streamer,
Archiver: archiver, archiver: archiver,
Players: players, players: players,
ExternalMetadata: externalMetadata, externalMetadata: externalMetadata,
Playlists: playlists, playlists: playlists,
Scanner: scanner, scanner: scanner,
Broker: broker, broker: broker,
Scrobbler: scrobbler, scrobbler: scrobbler,
} }
r.Handler = r.routes() r.Handler = r.routes()
return r return r
@ -63,100 +64,89 @@ func (api *Router) routes() http.Handler {
r.Use(postFormToQueryParams) r.Use(postFormToQueryParams)
r.Use(checkRequiredParameters) r.Use(checkRequiredParameters)
r.Use(authenticate(api.DataStore)) r.Use(authenticate(api.ds))
// TODO Validate version // TODO Validate version
// Subsonic endpoints, grouped by controller // Subsonic endpoints, grouped by controller
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
c := initSystemController(api) r.Use(getPlayer(api.players))
withPlayer := r.With(getPlayer(api.Players)) h(r, "ping", api.Ping)
h(withPlayer, "ping", c.Ping) h(r, "getLicense", api.GetLicense)
h(withPlayer, "getLicense", c.GetLicense)
}) })
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
c := initBrowsingController(api) r.Use(getPlayer(api.players))
withPlayer := r.With(getPlayer(api.Players)) h(r, "getMusicFolders", api.GetMusicFolders)
h(withPlayer, "getMusicFolders", c.GetMusicFolders) h(r, "getIndexes", api.GetIndexes)
h(withPlayer, "getIndexes", c.GetIndexes) h(r, "getArtists", api.GetArtists)
h(withPlayer, "getArtists", c.GetArtists) h(r, "getGenres", api.GetGenres)
h(withPlayer, "getGenres", c.GetGenres) h(r, "getMusicDirectory", api.GetMusicDirectory)
h(withPlayer, "getMusicDirectory", c.GetMusicDirectory) h(r, "getArtist", api.GetArtist)
h(withPlayer, "getArtist", c.GetArtist) h(r, "getAlbum", api.GetAlbum)
h(withPlayer, "getAlbum", c.GetAlbum) h(r, "getSong", api.GetSong)
h(withPlayer, "getSong", c.GetSong) h(r, "getArtistInfo", api.GetArtistInfo)
h(withPlayer, "getArtistInfo", c.GetArtistInfo) h(r, "getArtistInfo2", api.GetArtistInfo2)
h(withPlayer, "getArtistInfo2", c.GetArtistInfo2) h(r, "getTopSongs", api.GetTopSongs)
h(withPlayer, "getTopSongs", c.GetTopSongs) h(r, "getSimilarSongs", api.GetSimilarSongs)
h(withPlayer, "getSimilarSongs", c.GetSimilarSongs) h(r, "getSimilarSongs2", api.GetSimilarSongs2)
h(withPlayer, "getSimilarSongs2", c.GetSimilarSongs2)
}) })
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
c := initAlbumListController(api) r.Use(getPlayer(api.players))
withPlayer := r.With(getPlayer(api.Players)) hr(r, "getAlbumList", api.GetAlbumList)
h(withPlayer, "getAlbumList", c.GetAlbumList) hr(r, "getAlbumList2", api.GetAlbumList2)
h(withPlayer, "getAlbumList2", c.GetAlbumList2) h(r, "getStarred", api.GetStarred)
h(withPlayer, "getStarred", c.GetStarred) h(r, "getStarred2", api.GetStarred2)
h(withPlayer, "getStarred2", c.GetStarred2) h(r, "getNowPlaying", api.GetNowPlaying)
h(withPlayer, "getNowPlaying", c.GetNowPlaying) h(r, "getRandomSongs", api.GetRandomSongs)
h(withPlayer, "getRandomSongs", c.GetRandomSongs) h(r, "getSongsByGenre", api.GetSongsByGenre)
h(withPlayer, "getSongsByGenre", c.GetSongsByGenre)
}) })
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
c := initMediaAnnotationController(api) r.Use(getPlayer(api.players))
withPlayer := r.With(getPlayer(api.Players)) h(r, "setRating", api.SetRating)
h(withPlayer, "setRating", c.SetRating) h(r, "star", api.Star)
h(withPlayer, "star", c.Star) h(r, "unstar", api.Unstar)
h(withPlayer, "unstar", c.Unstar) h(r, "scrobble", api.Scrobble)
h(withPlayer, "scrobble", c.Scrobble)
}) })
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
c := initPlaylistsController(api) r.Use(getPlayer(api.players))
withPlayer := r.With(getPlayer(api.Players)) h(r, "getPlaylists", api.GetPlaylists)
h(withPlayer, "getPlaylists", c.GetPlaylists) h(r, "getPlaylist", api.GetPlaylist)
h(withPlayer, "getPlaylist", c.GetPlaylist) h(r, "createPlaylist", api.CreatePlaylist)
h(withPlayer, "createPlaylist", c.CreatePlaylist) h(r, "deletePlaylist", api.DeletePlaylist)
h(withPlayer, "deletePlaylist", c.DeletePlaylist) h(r, "updatePlaylist", api.UpdatePlaylist)
h(withPlayer, "updatePlaylist", c.UpdatePlaylist)
}) })
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
c := initBookmarksController(api) r.Use(getPlayer(api.players))
withPlayer := r.With(getPlayer(api.Players)) h(r, "getBookmarks", api.GetBookmarks)
h(withPlayer, "getBookmarks", c.GetBookmarks) h(r, "createBookmark", api.CreateBookmark)
h(withPlayer, "createBookmark", c.CreateBookmark) h(r, "deleteBookmark", api.DeleteBookmark)
h(withPlayer, "deleteBookmark", c.DeleteBookmark) h(r, "getPlayQueue", api.GetPlayQueue)
h(withPlayer, "getPlayQueue", c.GetPlayQueue) h(r, "savePlayQueue", api.SavePlayQueue)
h(withPlayer, "savePlayQueue", c.SavePlayQueue)
}) })
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
c := initSearchingController(api) r.Use(getPlayer(api.players))
withPlayer := r.With(getPlayer(api.Players)) h(r, "search2", api.Search2)
h(withPlayer, "search2", c.Search2) h(r, "search3", api.Search3)
h(withPlayer, "search3", c.Search3)
}) })
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
c := initUsersController(api) h(r, "getUser", api.GetUser)
h(r, "getUser", c.GetUser) h(r, "getUsers", api.GetUsers)
h(r, "getUsers", c.GetUsers)
}) })
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
c := initLibraryScanningController(api) h(r, "getScanStatus", api.GetScanStatus)
h(r, "getScanStatus", c.GetScanStatus) h(r, "startScan", api.StartScan)
h(r, "startScan", c.StartScan)
}) })
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
c := initMediaRetrievalController(api)
// configure request throttling // configure request throttling
maxRequests := utils.MaxInt(2, runtime.NumCPU()) maxRequests := utils.MaxInt(2, runtime.NumCPU())
withThrottle := r.With(middleware.ThrottleBacklog(maxRequests, consts.RequestThrottleBacklogLimit, consts.RequestThrottleBacklogTimeout)) r.Use(middleware.ThrottleBacklog(maxRequests, consts.RequestThrottleBacklogLimit, consts.RequestThrottleBacklogTimeout))
h(withThrottle, "getAvatar", c.GetAvatar) hr(r, "getAvatar", api.GetAvatar)
h(withThrottle, "getCoverArt", c.GetCoverArt) hr(r, "getCoverArt", api.GetCoverArt)
h(withThrottle, "getLyrics", c.GetLyrics) h(r, "getLyrics", api.GetLyrics)
}) })
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {
c := initStreamController(api) r.Use(getPlayer(api.players))
withPlayer := r.With(getPlayer(api.Players)) hr(r, "stream", api.Stream)
h(withPlayer, "stream", c.Stream) hr(r, "download", api.Download)
h(withPlayer, "download", c.Download)
}) })
// Not Implemented (yet?) // Not Implemented (yet?)
@ -176,9 +166,9 @@ func (api *Router) routes() http.Handler {
return r return r
} }
// Add the Subsonic handler, with and without `.view` extension // Add the Subsonic handler that requires a http.ResponseWriter, with and without `.view` extension.
// Ex: if path = `ping` it will create the routes `/ping` and `/ping.view` // Ex: if path = `stream` it will create the routes `/stream` and `/stream.view`
func h(r chi.Router, path string, f handler) { func hr(r chi.Router, path string, f handlerRaw) {
handle := func(w http.ResponseWriter, r *http.Request) { handle := func(w http.ResponseWriter, r *http.Request) {
res, err := f(w, r) res, err := f(w, r)
if err != nil { if err != nil {
@ -208,6 +198,14 @@ func h(r chi.Router, path string, f handler) {
r.HandleFunc("/"+path+".view", handle) 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 // 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) { func h501(r *chi.Mux, paths ...string) {
for _, path := range paths { for _, path := range paths {

View File

@ -10,18 +10,10 @@ import (
"github.com/navidrome/navidrome/utils" "github.com/navidrome/navidrome/utils"
) )
type BookmarksController struct { func (api *Router) GetBookmarks(r *http.Request) (*responses.Subsonic, error) {
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) {
user, _ := request.UserFrom(r.Context()) user, _ := request.UserFrom(r.Context())
repo := c.ds.MediaFile(r.Context()) repo := api.ds.MediaFile(r.Context())
bmks, err := repo.GetBookmarks() bmks, err := repo.GetBookmarks()
if err != nil { if err != nil {
return nil, err return nil, err
@ -43,7 +35,7 @@ func (c *BookmarksController) GetBookmarks(w http.ResponseWriter, r *http.Reques
return response, nil 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") id, err := requiredParamString(r, "id")
if err != nil { if err != nil {
return nil, err return nil, err
@ -52,7 +44,7 @@ func (c *BookmarksController) CreateBookmark(w http.ResponseWriter, r *http.Requ
comment := utils.ParamString(r, "comment") comment := utils.ParamString(r, "comment")
position := utils.ParamInt64(r, "position", 0) position := utils.ParamInt64(r, "position", 0)
repo := c.ds.MediaFile(r.Context()) repo := api.ds.MediaFile(r.Context())
err = repo.AddBookmark(id, comment, position) err = repo.AddBookmark(id, comment, position)
if err != nil { if err != nil {
return nil, err return nil, err
@ -60,13 +52,13 @@ func (c *BookmarksController) CreateBookmark(w http.ResponseWriter, r *http.Requ
return newResponse(), nil 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") id, err := requiredParamString(r, "id")
if err != nil { if err != nil {
return nil, err return nil, err
} }
repo := c.ds.MediaFile(r.Context()) repo := api.ds.MediaFile(r.Context())
err = repo.DeleteBookmark(id) err = repo.DeleteBookmark(id)
if err != nil { if err != nil {
return nil, err return nil, err
@ -74,10 +66,10 @@ func (c *BookmarksController) DeleteBookmark(w http.ResponseWriter, r *http.Requ
return newResponse(), nil 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()) user, _ := request.UserFrom(r.Context())
repo := c.ds.PlayQueue(r.Context()) repo := api.ds.PlayQueue(r.Context())
pq, err := repo.Retrieve(user.ID) pq, err := repo.Retrieve(user.ID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -95,7 +87,7 @@ func (c *BookmarksController) GetPlayQueue(w http.ResponseWriter, r *http.Reques
return response, nil 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") ids, err := requiredParamStrings(r, "id")
if err != nil { if err != nil {
return nil, err return nil, err
@ -122,7 +114,7 @@ func (c *BookmarksController) SavePlayQueue(w http.ResponseWriter, r *http.Reque
UpdatedAt: time.Time{}, UpdatedAt: time.Time{},
} }
repo := c.ds.PlayQueue(r.Context()) repo := api.ds.PlayQueue(r.Context())
err = repo.Store(pq) err = repo.Store(pq)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -16,17 +16,8 @@ import (
"github.com/navidrome/navidrome/utils" "github.com/navidrome/navidrome/utils"
) )
type BrowsingController struct { func (api *Router) GetMusicFolders(r *http.Request) (*responses.Subsonic, error) {
ds model.DataStore mediaFolderList, _ := api.ds.MediaFolder(r.Context()).GetAll()
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()
folders := make([]responses.MusicFolder, len(mediaFolderList)) folders := make([]responses.MusicFolder, len(mediaFolderList))
for i, f := range mediaFolderList { for i, f := range mediaFolderList {
folders[i].Id = f.ID folders[i].Id = f.ID
@ -37,14 +28,14 @@ func (c *BrowsingController) GetMusicFolders(w http.ResponseWriter, r *http.Requ
return response, nil return response, nil
} }
func (c *BrowsingController) getArtistIndex(ctx context.Context, mediaFolderId int, ifModifiedSince time.Time) (*responses.Indexes, error) { func (api *Router) getArtistIndex(ctx context.Context, mediaFolderId int, ifModifiedSince time.Time) (*responses.Indexes, error) {
folder, err := c.ds.MediaFolder(ctx).Get(int32(mediaFolderId)) folder, err := api.ds.MediaFolder(ctx).Get(int32(mediaFolderId))
if err != nil { if err != nil {
log.Error(ctx, "Error retrieving MediaFolder", "id", mediaFolderId, err) log.Error(ctx, "Error retrieving MediaFolder", "id", mediaFolderId, err)
return nil, 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 { if err != nil {
log.Error(ctx, "Error retrieving LastScan property", err) log.Error(ctx, "Error retrieving LastScan property", err)
return nil, err return nil, err
@ -54,7 +45,7 @@ func (c *BrowsingController) getArtistIndex(ctx context.Context, mediaFolderId i
ms, _ := strconv.ParseInt(l, 10, 64) ms, _ := strconv.ParseInt(l, 10, 64)
lastModified := utils.ToTime(ms) lastModified := utils.ToTime(ms)
if lastModified.After(ifModifiedSince) { if lastModified.After(ifModifiedSince) {
indexes, err = c.ds.Artist(ctx).GetIndex() indexes, err = api.ds.Artist(ctx).GetIndex()
if err != nil { if err != nil {
log.Error(ctx, "Error retrieving Indexes", err) log.Error(ctx, "Error retrieving Indexes", err)
return nil, err return nil, err
@ -74,11 +65,11 @@ func (c *BrowsingController) getArtistIndex(ctx context.Context, mediaFolderId i
return res, nil 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) musicFolderId := utils.ParamInt(r, "musicFolderId", 0)
ifModifiedSince := utils.ParamTime(r, "ifModifiedSince", time.Time{}) 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 { if err != nil {
return nil, err return nil, err
} }
@ -88,9 +79,9 @@ func (c *BrowsingController) GetIndexes(w http.ResponseWriter, r *http.Request)
return response, nil 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) 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 { if err != nil {
return nil, err return nil, err
} }
@ -100,11 +91,11 @@ func (c *BrowsingController) GetArtists(w http.ResponseWriter, r *http.Request)
return response, nil 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") id := utils.ParamString(r, "id")
ctx := r.Context() 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) { if errors.Is(err, model.ErrNotFound) {
log.Error(r, "Requested ID not found ", "id", id) log.Error(r, "Requested ID not found ", "id", id)
return nil, newError(responses.ErrorDataNotFound, "Directory not found") 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) { switch v := entity.(type) {
case *model.Artist: case *model.Artist:
dir, err = c.buildArtistDirectory(ctx, v) dir, err = api.buildArtistDirectory(ctx, v)
case *model.Album: case *model.Album:
dir, err = c.buildAlbumDirectory(ctx, v) dir, err = api.buildAlbumDirectory(ctx, v)
default: default:
log.Error(r, "Requested ID of invalid type", "id", id, "entity", v) log.Error(r, "Requested ID of invalid type", "id", id, "entity", v)
return nil, newError(responses.ErrorDataNotFound, "Directory not found") 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 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") id := utils.ParamString(r, "id")
ctx := r.Context() 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) { if errors.Is(err, model.ErrNotFound) {
log.Error(ctx, "Requested ArtistID not found ", "id", id) log.Error(ctx, "Requested ArtistID not found ", "id", id)
return nil, newError(responses.ErrorDataNotFound, "Artist not found") 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 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 { if err != nil {
log.Error(ctx, "Error retrieving albums by artist", "id", id, "name", artist.Name, err) log.Error(ctx, "Error retrieving albums by artist", "id", id, "name", artist.Name, err)
return nil, err return nil, err
} }
response := newResponse() response := newResponse()
response.ArtistWithAlbumsID3 = c.buildArtist(ctx, artist, albums) response.ArtistWithAlbumsID3 = api.buildArtist(ctx, artist, albums)
return response, nil 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") id := utils.ParamString(r, "id")
ctx := r.Context() 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) { if errors.Is(err, model.ErrNotFound) {
log.Error(ctx, "Requested AlbumID not found ", "id", id) log.Error(ctx, "Requested AlbumID not found ", "id", id)
return nil, newError(responses.ErrorDataNotFound, "Album not found") 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 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 { if err != nil {
log.Error(ctx, "Error retrieving tracks from album", "id", id, "name", album.Name, err) log.Error(ctx, "Error retrieving tracks from album", "id", id, "name", album.Name, err)
return nil, err return nil, err
} }
response := newResponse() response := newResponse()
response.AlbumWithSongsID3 = c.buildAlbum(ctx, album, mfs) response.AlbumWithSongsID3 = api.buildAlbum(ctx, album, mfs)
return response, nil 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") id := utils.ParamString(r, "id")
ctx := r.Context() 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) { if errors.Is(err, model.ErrNotFound) {
log.Error(r, "Requested MediaFileID not found ", "id", id) log.Error(r, "Requested MediaFileID not found ", "id", id)
return nil, newError(responses.ErrorDataNotFound, "Song not found") 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 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() 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 { if err != nil {
log.Error(r, err) log.Error(r, err)
return nil, err return nil, err
@ -224,7 +215,7 @@ func (c *BrowsingController) GetGenres(w http.ResponseWriter, r *http.Request) (
return response, nil 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() ctx := r.Context()
id, err := requiredParamString(r, "id") id, err := requiredParamString(r, "id")
if err != nil { if err != nil {
@ -233,7 +224,7 @@ func (c *BrowsingController) GetArtistInfo(w http.ResponseWriter, r *http.Reques
count := utils.ParamInt(r, "count", 20) count := utils.ParamInt(r, "count", 20)
includeNotPresent := utils.ParamBool(r, "includeNotPresent", false) 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 { if err != nil {
return nil, err return nil, err
} }
@ -253,8 +244,8 @@ func (c *BrowsingController) GetArtistInfo(w http.ResponseWriter, r *http.Reques
return response, nil return response, nil
} }
func (c *BrowsingController) GetArtistInfo2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (api *Router) GetArtistInfo2(r *http.Request) (*responses.Subsonic, error) {
info, err := c.GetArtistInfo(w, r) info, err := api.GetArtistInfo(r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -275,7 +266,7 @@ func (c *BrowsingController) GetArtistInfo2(w http.ResponseWriter, r *http.Reque
return response, nil 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() ctx := r.Context()
id, err := requiredParamString(r, "id") id, err := requiredParamString(r, "id")
if err != nil { if err != nil {
@ -283,7 +274,7 @@ func (c *BrowsingController) GetSimilarSongs(w http.ResponseWriter, r *http.Requ
} }
count := utils.ParamInt(r, "count", 50) 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 { if err != nil {
return nil, err return nil, err
} }
@ -295,8 +286,8 @@ func (c *BrowsingController) GetSimilarSongs(w http.ResponseWriter, r *http.Requ
return response, nil return response, nil
} }
func (c *BrowsingController) GetSimilarSongs2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (api *Router) GetSimilarSongs2(r *http.Request) (*responses.Subsonic, error) {
res, err := c.GetSimilarSongs(w, r) res, err := api.GetSimilarSongs(r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -308,7 +299,7 @@ func (c *BrowsingController) GetSimilarSongs2(w http.ResponseWriter, r *http.Req
return response, nil 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() ctx := r.Context()
artist, err := requiredParamString(r, "artist") artist, err := requiredParamString(r, "artist")
if err != nil { if err != nil {
@ -316,7 +307,7 @@ func (c *BrowsingController) GetTopSongs(w http.ResponseWriter, r *http.Request)
} }
count := utils.ParamInt(r, "count", 50) 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 { if err != nil {
return nil, err return nil, err
} }
@ -328,7 +319,7 @@ func (c *BrowsingController) GetTopSongs(w http.ResponseWriter, r *http.Request)
return response, nil 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 := &responses.Directory{}
dir.Id = artist.ID dir.Id = artist.ID
dir.Name = artist.Name dir.Name = artist.Name
@ -342,7 +333,7 @@ func (c *BrowsingController) buildArtistDirectory(ctx context.Context, artist *m
dir.Starred = &artist.StarredAt 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 { if err != nil {
return nil, err return nil, err
} }
@ -351,14 +342,14 @@ func (c *BrowsingController) buildArtistDirectory(ctx context.Context, artist *m
return dir, nil 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 := &responses.ArtistWithAlbumsID3{}
a.ArtistID3 = toArtistID3(ctx, *artist) a.ArtistID3 = toArtistID3(ctx, *artist)
a.Album = childrenFromAlbums(ctx, albums) a.Album = childrenFromAlbums(ctx, albums)
return a 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 := &responses.Directory{}
dir.Id = album.ID dir.Id = album.ID
dir.Name = album.Name dir.Name = album.Name
@ -374,7 +365,7 @@ func (c *BrowsingController) buildAlbumDirectory(ctx context.Context, album *mod
dir.Starred = &album.StarredAt 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 { if err != nil {
return nil, err return nil, err
} }
@ -383,7 +374,7 @@ func (c *BrowsingController) buildAlbumDirectory(ctx context.Context, album *mod
return dir, nil 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 := &responses.AlbumWithSongsID3{}
dir.Id = album.ID dir.Id = album.ID
dir.Name = album.Name dir.Name = album.Name

View File

@ -7,24 +7,15 @@ import (
"github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model/request" "github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/scanner"
"github.com/navidrome/navidrome/server/subsonic/responses" "github.com/navidrome/navidrome/server/subsonic/responses"
"github.com/navidrome/navidrome/utils" "github.com/navidrome/navidrome/utils"
) )
type LibraryScanningController struct { func (api *Router) GetScanStatus(r *http.Request) (*responses.Subsonic, error) {
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) {
// TODO handle multiple mediafolders // TODO handle multiple mediafolders
ctx := r.Context() ctx := r.Context()
mediaFolder := conf.Server.MusicFolder mediaFolder := conf.Server.MusicFolder
status, err := c.scanner.Status(mediaFolder) status, err := api.scanner.Status(mediaFolder)
if err != nil { if err != nil {
log.Error(ctx, "Error retrieving Scanner status", err) log.Error(ctx, "Error retrieving Scanner status", err)
return nil, newError(responses.ErrorGeneric, "Internal Error") return nil, newError(responses.ErrorGeneric, "Internal Error")
@ -39,7 +30,7 @@ func (c *LibraryScanningController) GetScanStatus(w http.ResponseWriter, r *http
return response, nil 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() ctx := r.Context()
loggedUser, ok := request.UserFrom(ctx) loggedUser, ok := request.UserFrom(ctx)
if !ok { if !ok {
@ -55,7 +46,7 @@ func (c *LibraryScanningController) StartScan(w http.ResponseWriter, r *http.Req
go func() { go func() {
start := time.Now() start := time.Now()
log.Info(ctx, "Triggering manual scan", "fullScan", fullScan, "user", loggedUser.UserName) 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 { if err != nil {
log.Error(ctx, "Error scanning", err) log.Error(ctx, "Error scanning", err)
return 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)) 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)
} }

View File

@ -17,17 +17,7 @@ import (
"github.com/navidrome/navidrome/utils" "github.com/navidrome/navidrome/utils"
) )
type MediaAnnotationController struct { func (api *Router) SetRating(r *http.Request) (*responses.Subsonic, error) {
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) {
id, err := requiredParamString(r, "id") id, err := requiredParamString(r, "id")
if err != nil { if err != nil {
return nil, err 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) 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) { if errors.Is(err, model.ErrNotFound) {
log.Error(r, err) log.Error(r, err)
@ -52,23 +42,23 @@ func (c *MediaAnnotationController) SetRating(w http.ResponseWriter, r *http.Req
return newResponse(), nil 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 repo model.AnnotatedRepository
var resource string var resource string
entity, err := core.GetEntityByID(ctx, c.ds, id) entity, err := core.GetEntityByID(ctx, api.ds, id)
if err != nil { if err != nil {
return err return err
} }
switch entity.(type) { switch entity.(type) {
case *model.Artist: case *model.Artist:
repo = c.ds.Artist(ctx) repo = api.ds.Artist(ctx)
resource = "artist" resource = "artist"
case *model.Album: case *model.Album:
repo = c.ds.Album(ctx) repo = api.ds.Album(ctx)
resource = "album" resource = "album"
default: default:
repo = c.ds.MediaFile(ctx) repo = api.ds.MediaFile(ctx)
resource = "song" resource = "song"
} }
err = repo.SetRating(rating, id) err = repo.SetRating(rating, id)
@ -76,11 +66,11 @@ func (c *MediaAnnotationController) setRating(ctx context.Context, id string, ra
return err return err
} }
event := &events.RefreshResource{} event := &events.RefreshResource{}
c.broker.SendMessage(ctx, event.With(resource, id)) api.broker.SendMessage(ctx, event.With(resource, id))
return nil 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") ids := utils.ParamStrings(r, "id")
albumIds := utils.ParamStrings(r, "albumId") albumIds := utils.ParamStrings(r, "albumId")
artistIds := utils.ParamStrings(r, "artistId") 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, albumIds...)
ids = append(ids, artistIds...) ids = append(ids, artistIds...)
err := c.setStar(r.Context(), true, ids...) err := api.setStar(r.Context(), true, ids...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -98,7 +88,7 @@ func (c *MediaAnnotationController) Star(w http.ResponseWriter, r *http.Request)
return newResponse(), nil 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") ids := utils.ParamStrings(r, "id")
albumIds := utils.ParamStrings(r, "albumId") albumIds := utils.ParamStrings(r, "albumId")
artistIds := utils.ParamStrings(r, "artistId") 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, albumIds...)
ids = append(ids, artistIds...) ids = append(ids, artistIds...)
err := c.setStar(r.Context(), false, ids...) err := api.setStar(r.Context(), false, ids...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -116,7 +106,7 @@ func (c *MediaAnnotationController) Unstar(w http.ResponseWriter, r *http.Reques
return newResponse(), nil 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 { if len(ids) == 0 {
return nil return nil
} }
@ -126,7 +116,7 @@ func (c *MediaAnnotationController) setStar(ctx context.Context, star bool, ids
return nil return nil
} }
event := &events.RefreshResource{} 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 { for _, id := range ids {
exist, err := tx.Album(ctx).Exists(id) exist, err := tx.Album(ctx).Exists(id)
if err != nil { if err != nil {
@ -158,7 +148,7 @@ func (c *MediaAnnotationController) setStar(ctx context.Context, star bool, ids
} }
event = event.With("song", id) event = event.With("song", id)
} }
c.broker.SendMessage(ctx, event) api.broker.SendMessage(ctx, event)
return nil return nil
}) })
@ -173,7 +163,7 @@ func (c *MediaAnnotationController) setStar(ctx context.Context, star bool, ids
return nil 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") ids, err := requiredParamStrings(r, "id")
if err != nil { if err != nil {
return nil, err return nil, err
@ -186,12 +176,12 @@ func (c *MediaAnnotationController) Scrobble(w http.ResponseWriter, r *http.Requ
ctx := r.Context() ctx := r.Context()
if submission { if submission {
err := c.scrobblerSubmit(ctx, ids, times) err := api.scrobblerSubmit(ctx, ids, times)
if err != nil { if err != nil {
log.Error(ctx, "Error registering scrobbles", "ids", ids, "times", times, err) log.Error(ctx, "Error registering scrobbles", "ids", ids, "times", times, err)
} }
} else { } else {
err := c.scrobblerNowPlaying(ctx, ids[0]) err := api.scrobblerNowPlaying(ctx, ids[0])
if err != nil { if err != nil {
log.Error(ctx, "Error setting NowPlaying", "id", ids[0], err) 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 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 var submissions []scrobbler.Submission
log.Debug(ctx, "Scrobbling tracks", "ids", ids, "times", times) log.Debug(ctx, "Scrobbling tracks", "ids", ids, "times", times)
for i, id := range ids { 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}) 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 { func (api *Router) scrobblerNowPlaying(ctx context.Context, trackId string) error {
mf, err := c.ds.MediaFile(ctx).Get(trackId) mf, err := api.ds.MediaFile(ctx).Get(trackId)
if err != nil { if err != nil {
return err 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) 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 return err
} }

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"net/http/httptest"
"time" "time"
"github.com/navidrome/navidrome/model/request" "github.com/navidrome/navidrome/model/request"
@ -19,8 +18,7 @@ import (
) )
var _ = Describe("MediaAnnotationController", func() { var _ = Describe("MediaAnnotationController", func() {
var controller *MediaAnnotationController var router *Router
var w *httptest.ResponseRecorder
var ds model.DataStore var ds model.DataStore
var playTracker *fakePlayTracker var playTracker *fakePlayTracker
var eventBroker *fakeEventBroker var eventBroker *fakeEventBroker
@ -31,8 +29,7 @@ var _ = Describe("MediaAnnotationController", func() {
ds = &tests.MockDataStore{} ds = &tests.MockDataStore{}
playTracker = &fakePlayTracker{} playTracker = &fakePlayTracker{}
eventBroker = &fakeEventBroker{} eventBroker = &fakeEventBroker{}
controller = NewMediaAnnotationController(ds, playTracker, eventBroker) router = New(ds, nil, nil, nil, nil, nil, nil, eventBroker, nil, playTracker)
w = httptest.NewRecorder()
}) })
Describe("Scrobble", func() { Describe("Scrobble", func() {
@ -40,7 +37,7 @@ var _ = Describe("MediaAnnotationController", func() {
submissionTime := time.Now() submissionTime := time.Now()
r := newGetRequest("id=12", "id=34") r := newGetRequest("id=12", "id=34")
_, err := controller.Scrobble(w, r) _, err := router.Scrobble(r)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(playTracker.Submissions).To(HaveLen(2)) Expect(playTracker.Submissions).To(HaveLen(2))
@ -57,7 +54,7 @@ var _ = Describe("MediaAnnotationController", func() {
t2 := utils.ToMillis(time2) t2 := utils.ToMillis(time2)
r := newGetRequest("id=12", "id=34", fmt.Sprintf("time=%d", t1), fmt.Sprintf("time=%d", t2)) 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(err).ToNot(HaveOccurred())
Expect(playTracker.Submissions).To(HaveLen(2)) 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() { It("checks if number of ids match number of times", func() {
r := newGetRequest("id=12", "id=34", "time=1111") r := newGetRequest("id=12", "id=34", "time=1111")
_, err := controller.Scrobble(w, r) _, err := router.Scrobble(r)
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
Expect(playTracker.Submissions).To(BeEmpty()) Expect(playTracker.Submissions).To(BeEmpty())
@ -86,14 +83,14 @@ var _ = Describe("MediaAnnotationController", func() {
}) })
It("does not scrobble", func() { It("does not scrobble", func() {
_, err := controller.Scrobble(w, req) _, err := router.Scrobble(req)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(playTracker.Submissions).To(BeEmpty()) Expect(playTracker.Submissions).To(BeEmpty())
}) })
It("registers a NowPlaying", func() { It("registers a NowPlaying", func() {
_, err := controller.Scrobble(w, req) _, err := router.Scrobble(req)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(playTracker.Playing).To(HaveLen(1)) Expect(playTracker.Playing).To(HaveLen(1))
@ -109,7 +106,7 @@ type fakePlayTracker struct {
Error error 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 { if f.Error != nil {
return f.Error return f.Error
} }
@ -120,11 +117,11 @@ func (f *fakePlayTracker) NowPlaying(ctx context.Context, playerId string, playe
return nil 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 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 { if f.Error != nil {
return f.Error return f.Error
} }
@ -139,7 +136,7 @@ type fakeEventBroker struct {
Events []events.Event 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) f.Events = append(f.Events, event)
} }

View File

@ -8,7 +8,6 @@ import (
"github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/resources" "github.com/navidrome/navidrome/resources"
@ -18,37 +17,28 @@ import (
"github.com/navidrome/navidrome/utils/gravatar" "github.com/navidrome/navidrome/utils/gravatar"
) )
type MediaRetrievalController struct { func (api *Router) GetAvatar(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
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) {
if !conf.Server.EnableGravatar { if !conf.Server.EnableGravatar {
return c.getPlaceHolderAvatar(w, r) return api.getPlaceHolderAvatar(w, r)
} }
username, err := requiredParamString(r, "username") username, err := requiredParamString(r, "username")
if err != nil { if err != nil {
return nil, err return nil, err
} }
ctx := r.Context() ctx := r.Context()
u, err := c.ds.User(ctx).FindByUsername(username) u, err := api.ds.User(ctx).FindByUsername(username)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if u.Email == "" { if u.Email == "" {
log.Warn(ctx, "User needs an email for gravatar to work", "username", username) 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) http.Redirect(w, r, gravatar.Url(u.Email, 0), http.StatusFound)
return nil, nil 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) f, err := resources.FS().Open(consts.PlaceholderAvatar)
if err != nil { if err != nil {
log.Error(r, "Image not found", err) log.Error(r, "Image not found", err)
@ -60,13 +50,13 @@ func (c *MediaRetrievalController) getPlaceHolderAvatar(w http.ResponseWriter, r
return nil, nil 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") id := utils.ParamStringDefault(r, "id", "non-existent")
size := utils.ParamInt(r, "size", 0) size := utils.ParamInt(r, "size", 0)
w.Header().Set("cache-control", "public, max-age=315360000") 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) { if errors.Is(err, model.ErrNotFound) {
log.Error(r, "Couldn't find coverArt", "id", id, err) log.Error(r, "Couldn't find coverArt", "id", id, err)
return nil, newError(responses.ErrorDataNotFound, "Artwork not found") return nil, newError(responses.ErrorDataNotFound, "Artwork not found")
@ -92,13 +82,13 @@ func isSynced(rawLyrics string) bool {
return r.MatchString(rawLyrics) 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") artist := utils.ParamString(r, "artist")
title := utils.ParamString(r, "title") title := utils.ParamString(r, "title")
response := newResponse() response := newResponse()
lyrics := responses.Lyrics{} lyrics := responses.Lyrics{}
response.Lyrics = &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 { if err != nil {
return nil, err return nil, err

View File

@ -15,7 +15,7 @@ import (
) )
var _ = Describe("MediaRetrievalController", func() { var _ = Describe("MediaRetrievalController", func() {
var controller *MediaRetrievalController var router *Router
var ds model.DataStore var ds model.DataStore
mockRepo := &mockedMediaFile{} mockRepo := &mockedMediaFile{}
var artwork *fakeArtwork var artwork *fakeArtwork
@ -26,7 +26,7 @@ var _ = Describe("MediaRetrievalController", func() {
MockedMediaFile: mockRepo, MockedMediaFile: mockRepo,
} }
artwork = &fakeArtwork{} artwork = &fakeArtwork{}
controller = NewMediaRetrievalController(artwork, ds) router = New(ds, artwork, nil, nil, nil, nil, nil, nil, nil, nil)
w = httptest.NewRecorder() w = httptest.NewRecorder()
}) })
@ -34,7 +34,7 @@ var _ = Describe("MediaRetrievalController", func() {
It("should return data for that id", func() { It("should return data for that id", func() {
artwork.data = "image data" artwork.data = "image data"
r := newGetRequest("id=34", "size=128") r := newGetRequest("id=34", "size=128")
_, err := controller.GetCoverArt(w, r) _, err := router.GetCoverArt(w, r)
Expect(err).To(BeNil()) Expect(err).To(BeNil())
Expect(artwork.recvId).To(Equal("34")) 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() { It("should return placeholder if id parameter is missing (mimicking Subsonic)", func() {
r := newGetRequest() r := newGetRequest()
_, err := controller.GetCoverArt(w, r) _, err := router.GetCoverArt(w, r)
Expect(err).To(BeNil()) Expect(err).To(BeNil())
Expect(w.Body.String()).To(Equal(artwork.data)) 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() { It("should fail when the file is not found", func() {
artwork.err = model.ErrNotFound artwork.err = model.ErrNotFound
r := newGetRequest("id=34", "size=128") r := newGetRequest("id=34", "size=128")
_, err := controller.GetCoverArt(w, r) _, err := router.GetCoverArt(w, r)
Expect(err).To(MatchError("Artwork not found")) 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() { It("should fail when there is an unknown error", func() {
artwork.err = errors.New("weird error") artwork.err = errors.New("weird error")
r := newGetRequest("id=34", "size=128") r := newGetRequest("id=34", "size=128")
_, err := controller.GetCoverArt(w, r) _, err := router.GetCoverArt(w, r)
Expect(err).To(MatchError("weird error")) 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", 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 { if err != nil {
log.Error("You're missing something.", err) 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() { 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") r := newGetRequest("artist=Dheeraj", "title=Rinkiya+Ke+Papa")
mockRepo.SetData(model.MediaFiles{}) mockRepo.SetData(model.MediaFiles{})
response, err := controller.GetLyrics(w, r) response, err := router.GetLyrics(r)
if err != nil { if err != nil {
log.Error("You're missing something.", err) log.Error("You're missing something.", err)
} }
@ -110,7 +110,7 @@ type fakeArtwork struct {
recvSize int 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 { if c.err != nil {
return nil, c.err return nil, c.err
} }
@ -129,7 +129,7 @@ var _ = Describe("isSynced", func() {
}) })
It("returns true if lyrics contain timestamps", func() { It("returns true if lyrics contain timestamps", func() {
Expect(isSynced(`NF Real Music Expect(isSynced(`NF Real Music
[00:00] ksdjjs [00:00] First line
[00:00.85] JUST LIKE YOU [00:00.85] JUST LIKE YOU
[00:00.85] Just in case my car goes off the highway`)).To(Equal(true)) [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)) 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 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 return m.data, nil
} }

View File

@ -6,49 +6,39 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/server/subsonic/responses" "github.com/navidrome/navidrome/server/subsonic/responses"
"github.com/navidrome/navidrome/utils" "github.com/navidrome/navidrome/utils"
) )
type PlaylistsController struct { func (api *Router) GetPlaylists(r *http.Request) (*responses.Subsonic, error) {
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) {
ctx := r.Context() 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 { if err != nil {
log.Error(r, err) log.Error(r, err)
return nil, err return nil, err
} }
playlists := make([]responses.Playlist, len(allPls)) playlists := make([]responses.Playlist, len(allPls))
for i, p := range allPls { for i, p := range allPls {
playlists[i] = *c.buildPlaylist(p) playlists[i] = *api.buildPlaylist(p)
} }
response := newResponse() response := newResponse()
response.Playlists = &responses.Playlists{Playlist: playlists} response.Playlists = &responses.Playlists{Playlist: playlists}
return response, nil 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() ctx := r.Context()
id, err := requiredParamString(r, "id") id, err := requiredParamString(r, "id")
if err != nil { if err != nil {
return nil, err 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) { func (api *Router) getPlaylist(ctx context.Context, id string) (*responses.Subsonic, error) {
pls, err := c.ds.Playlist(ctx).GetWithTracks(id) pls, err := api.ds.Playlist(ctx).GetWithTracks(id)
if errors.Is(err, model.ErrNotFound) { if errors.Is(err, model.ErrNotFound) {
log.Error(ctx, err.Error(), "id", id) log.Error(ctx, err.Error(), "id", id)
return nil, newError(responses.ErrorDataNotFound, "Directory not found") 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 := newResponse()
response.Playlist = c.buildPlaylistWithSongs(ctx, pls) response.Playlist = api.buildPlaylistWithSongs(ctx, pls)
return response, nil return response, nil
} }
func (c *PlaylistsController) create(ctx context.Context, playlistId, name string, ids []string) (string, error) { func (api *Router) create(ctx context.Context, playlistId, name string, ids []string) (string, error) {
err := c.ds.WithTx(func(tx model.DataStore) error { err := api.ds.WithTx(func(tx model.DataStore) error {
owner := getUser(ctx) owner := getUser(ctx)
var pls *model.Playlist var pls *model.Playlist
var err error var err error
@ -91,7 +81,7 @@ func (c *PlaylistsController) create(ctx context.Context, playlistId, name strin
return playlistId, err 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() ctx := r.Context()
songIds := utils.ParamStrings(r, "songId") songIds := utils.ParamStrings(r, "songId")
playlistId := utils.ParamString(r, "playlistId") playlistId := utils.ParamString(r, "playlistId")
@ -99,20 +89,20 @@ func (c *PlaylistsController) CreatePlaylist(w http.ResponseWriter, r *http.Requ
if playlistId == "" && name == "" { if playlistId == "" && name == "" {
return nil, errors.New("required parameter name is missing") 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 { if err != nil {
log.Error(r, err) log.Error(r, err)
return nil, 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") id, err := requiredParamString(r, "id")
if err != nil { if err != nil {
return nil, err 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) { if errors.Is(err, model.ErrNotAuthorized) {
return nil, newError(responses.ErrorAuthorizationFail) return nil, newError(responses.ErrorAuthorizationFail)
} }
@ -123,7 +113,7 @@ func (c *PlaylistsController) DeletePlaylist(w http.ResponseWriter, r *http.Requ
return newResponse(), nil 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") playlistId, err := requiredParamString(r, "playlistId")
if err != nil { if err != nil {
return nil, err 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("-- Adding: '%v'", songsToAdd))
log.Trace(r, fmt.Sprintf("-- Removing: '%v'", songIndexesToRemove)) 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) { if errors.Is(err, model.ErrNotAuthorized) {
return nil, newError(responses.ErrorAuthorizationFail) return nil, newError(responses.ErrorAuthorizationFail)
} }
@ -162,15 +152,15 @@ func (c *PlaylistsController) UpdatePlaylist(w http.ResponseWriter, r *http.Requ
return newResponse(), nil 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{ pls := &responses.PlaylistWithSongs{
Playlist: *c.buildPlaylist(*p), Playlist: *api.buildPlaylist(*p),
} }
pls.Entry = childrenFromMediaFiles(ctx, p.MediaFiles()) pls.Entry = childrenFromMediaFiles(ctx, p.MediaFiles())
return pls return pls
} }
func (c *PlaylistsController) buildPlaylist(p model.Playlist) *responses.Playlist { func (api *Router) buildPlaylist(p model.Playlist) *responses.Playlist {
pls := &responses.Playlist{} pls := &responses.Playlist{}
pls.Id = p.ID pls.Id = p.ID
pls.Name = p.Name pls.Name = p.Name

View File

@ -1,4 +1,4 @@
//go:build linux || darwin //go:build unix
// TODO Fix snapshot tests in Windows // TODO Fix snapshot tests in Windows
// Response Snapshot tests. Only run in Linux and macOS, as they fail in Windows // Response Snapshot tests. Only run in Linux and macOS, as they fail in Windows

View File

@ -16,10 +16,6 @@ import (
"github.com/navidrome/navidrome/utils" "github.com/navidrome/navidrome/utils"
) )
type SearchingController struct {
ds model.DataStore
}
type searchParams struct { type searchParams struct {
query string query string
artistCount int artistCount int
@ -30,11 +26,7 @@ type searchParams struct {
songOffset int songOffset int
} }
func NewSearchingController(ds model.DataStore) *SearchingController { func (api *Router) getParams(r *http.Request) (*searchParams, error) {
return &SearchingController{ds: ds}
}
func (c *SearchingController) getParams(r *http.Request) (*searchParams, error) {
var err error var err error
sp := &searchParams{} sp := &searchParams{}
sp.query, err = requiredParamString(r, "query") 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 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() start := time.Now()
q := sanitize.Accents(strings.ToLower(strings.TrimSuffix(sp.query, "*"))) q := sanitize.Accents(strings.ToLower(strings.TrimSuffix(sp.query, "*")))
ctx := r.Context() ctx := r.Context()
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(3) wg.Add(3)
go func() { mediaFiles = doSearch(ctx, wg, c.ds.MediaFile(ctx).Search, q, sp.songOffset, sp.songCount) }() go func() {
go func() { albums = doSearch(ctx, wg, c.ds.Album(ctx).Search, q, sp.albumOffset, sp.albumCount) }() mediaFiles = doSearch(ctx, wg, api.ds.MediaFile(ctx).Search, q, sp.songOffset, sp.songCount)
go func() { artists = doSearch(ctx, wg, c.ds.Artist(ctx).Search, q, sp.artistOffset, sp.artistCount) }() }()
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() wg.Wait()
if ctx.Err() == nil { if ctx.Err() == nil {
@ -98,12 +94,12 @@ func (c *SearchingController) searchAll(r *http.Request, sp *searchParams) (medi
return mediaFiles, albums, artists return mediaFiles, albums, artists
} }
func (c *SearchingController) Search2(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) { func (api *Router) Search2(r *http.Request) (*responses.Subsonic, error) {
sp, err := c.getParams(r) sp, err := api.getParams(r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
mfs, als, as := c.searchAll(r, sp) mfs, als, as := api.searchAll(r, sp)
response := newResponse() response := newResponse()
searchResult2 := &responses.SearchResult2{} searchResult2 := &responses.SearchResult2{}
@ -126,13 +122,13 @@ func (c *SearchingController) Search2(w http.ResponseWriter, r *http.Request) (*
return response, nil 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() ctx := r.Context()
sp, err := c.getParams(r) sp, err := api.getParams(r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
mfs, als, as := c.searchAll(r, sp) mfs, als, as := api.searchAll(r, sp)
response := newResponse() response := newResponse()
searchResult3 := &responses.SearchResult3{} searchResult3 := &responses.SearchResult3{}

View File

@ -16,17 +16,7 @@ import (
"github.com/navidrome/navidrome/utils" "github.com/navidrome/navidrome/utils"
) )
type StreamController struct { func (api *Router) Stream(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
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) {
ctx := r.Context() ctx := r.Context()
id, err := requiredParamString(r, "id") id, err := requiredParamString(r, "id")
if err != nil { if err != nil {
@ -36,7 +26,7 @@ func (c *StreamController) Stream(w http.ResponseWriter, r *http.Request) (*resp
format := utils.ParamString(r, "format") format := utils.ParamString(r, "format")
estimateContentLength := utils.ParamBool(r, "estimateContentLength", false) 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 { if err != nil {
return nil, err return nil, err
} }
@ -82,7 +72,7 @@ func (c *StreamController) Stream(w http.ResponseWriter, r *http.Request) (*resp
return nil, nil 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() ctx := r.Context()
username, _ := request.UsernameFrom(ctx) username, _ := request.UsernameFrom(ctx)
id, err := requiredParamString(r, "id") 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") 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 { if err != nil {
return nil, err return nil, err
} }
@ -109,7 +99,7 @@ func (c *StreamController) Download(w http.ResponseWriter, r *http.Request) (*re
switch v := entity.(type) { switch v := entity.(type) {
case *model.MediaFile: case *model.MediaFile:
stream, err := c.streamer.NewStream(ctx, id, "raw", 0) stream, err := api.streamer.NewStream(ctx, id, "raw", 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -120,13 +110,13 @@ func (c *StreamController) Download(w http.ResponseWriter, r *http.Request) (*re
return nil, nil return nil, nil
case *model.Album: case *model.Album:
setHeaders(v.Name) setHeaders(v.Name)
err = c.archiver.ZipAlbum(ctx, id, w) err = api.archiver.ZipAlbum(ctx, id, w)
case *model.Artist: case *model.Artist:
setHeaders(v.Name) setHeaders(v.Name)
err = c.archiver.ZipArtist(ctx, id, w) err = api.archiver.ZipArtist(ctx, id, w)
case *model.Playlist: case *model.Playlist:
setHeaders(v.Name) setHeaders(v.Name)
err = c.archiver.ZipPlaylist(ctx, id, w) err = api.archiver.ZipPlaylist(ctx, id, w)
default: default:
err = model.ErrNotFound err = model.ErrNotFound
} }

View File

@ -12,11 +12,11 @@ func NewSystemController() *SystemController {
return &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 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 := newResponse()
response.License = &responses.License{Valid: true} response.License = &responses.License{Valid: true}
return response, nil return response, nil

View File

@ -7,14 +7,8 @@ import (
"github.com/navidrome/navidrome/server/subsonic/responses" "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 // 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()) loggedUser, ok := request.UserFrom(r.Context())
if !ok { if !ok {
return nil, newError(responses.ErrorGeneric, "Internal error") 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 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()) loggedUser, ok := request.UserFrom(r.Context())
if !ok { if !ok {
return nil, newError(responses.ErrorGeneric, "Internal error") return nil, newError(responses.ErrorGeneric, "Internal error")

View File

@ -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",
),
)

View File

@ -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))
}