diff --git a/api/album_lists.go b/api/album_lists.go index 8e7646d54..13aca9ae0 100644 --- a/api/album_lists.go +++ b/api/album_lists.go @@ -40,7 +40,7 @@ func (c *AlbumListController) GetAlbumList() { } offset := c.ParamInt("offset", 0) - size := utils.MinInt(c.ParamInt("size", 0), 500) + size := utils.MinInt(c.ParamInt("size", 10), 500) albums, err := method(offset, size) if err != nil { @@ -63,7 +63,7 @@ func (c *AlbumListController) GetAlbumList2() { } offset := c.ParamInt("offset", 0) - size := utils.MinInt(c.ParamInt("size", 0), 500) + size := utils.MinInt(c.ParamInt("size", 10), 500) albums, err := method(offset, size) if err != nil { @@ -125,3 +125,21 @@ func (c *AlbumListController) GetNowPlaying() { } c.SendResponse(response) } + +func (c *AlbumListController) GetRandomSongs() { + size := utils.MinInt(c.ParamInt("size", 10), 500) + + songs, err := c.listGen.GetRandomSongs(size) + if err != nil { + beego.Error("Error retrieving random songs:", err) + c.SendError(responses.ErrorGeneric, "Internal Error") + } + + response := c.NewEmpty() + response.RandomSongs = &responses.Songs{} + response.RandomSongs.Songs = make([]responses.Child, len(songs)) + for i, entry := range songs { + response.RandomSongs.Songs[i] = c.ToChild(entry) + } + c.SendResponse(response) +} diff --git a/api/base_api_controller.go b/api/base_api_controller.go index dbb36f2e6..3a4cac8d7 100644 --- a/api/base_api_controller.go +++ b/api/base_api_controller.go @@ -44,10 +44,10 @@ func (c *BaseAPIController) ParamStrings(param string) []string { } func (c *BaseAPIController) ParamTime(param string, def time.Time) time.Time { - var value int64 if c.Input().Get(param) == "" { return def } + var value int64 c.Ctx.Input.Bind(&value, param) return utils.ToTime(value) } @@ -73,7 +73,10 @@ func (c *BaseAPIController) RequiredParamInt(param string, msg string) int { } func (c *BaseAPIController) ParamInt(param string, def int) int { - value := def + if c.Input().Get(param) == "" { + return def + } + var value int c.Ctx.Input.Bind(&value, param) return value } @@ -91,10 +94,10 @@ func (c *BaseAPIController) ParamInts(param string) []int { } func (c *BaseAPIController) ParamBool(param string, def bool) bool { - value := def if c.Input().Get(param) == "" { return def } + var value bool c.Ctx.Input.Bind(&value, param) return value } diff --git a/api/responses/responses.go b/api/responses/responses.go index 734ef09f6..a699f76d7 100644 --- a/api/responses/responses.go +++ b/api/responses/responses.go @@ -25,6 +25,7 @@ type Subsonic struct { Starred2 *Starred `xml:"starred2,omitempty" json:"starred2,omitempty"` NowPlaying *NowPlaying `xml:"nowPlaying,omitempty" json:"nowPlaying,omitempty"` Song *Child `xml:"song,omitempty" json:"song,omitempty"` + RandomSongs *Songs `xml:"randomSongs,omitempty" json:"randomSongs,omitempty"` // ID3 Artist *Indexes `xml:"artists,omitempty" json:"artists,omitempty"` @@ -114,6 +115,10 @@ type Child struct { */ } +type Songs struct { + Songs []Child `xml:"song" json:"song,omitempty"` +} + type Directory struct { Child []Child `xml:"child" json:"child,omitempty"` Id string `xml:"id,attr" json:"id"` diff --git a/conf/router.go b/conf/router.go index 2aa6d3850..09ff1bf7c 100644 --- a/conf/router.go +++ b/conf/router.go @@ -46,6 +46,7 @@ func mapEndpoints() { beego.NSRouter("/getStarred.view", &api.AlbumListController{}, "*:GetStarred"), beego.NSRouter("/getStarred2.view", &api.AlbumListController{}, "*:GetStarred2"), beego.NSRouter("/getNowPlaying.view", &api.AlbumListController{}, "*:GetNowPlaying"), + beego.NSRouter("/getRandomSongs.view", &api.AlbumListController{}, "*:GetRandomSongs"), beego.NSRouter("/getPlaylists.view", &api.PlaylistsController{}, "*:GetPlaylists"), beego.NSRouter("/getPlaylist.view", &api.PlaylistsController{}, "*:GetPlaylist"), diff --git a/domain/mediafile.go b/domain/mediafile.go index ee2ce3aa4..ac45f2bbc 100644 --- a/domain/mediafile.go +++ b/domain/mediafile.go @@ -52,4 +52,5 @@ type MediaFileRepository interface { FindByAlbum(albumId string) (MediaFiles, error) GetStarred(options QueryOptions) (MediaFiles, error) PurgeInactive(active MediaFiles) ([]string, error) + GetAllIds() ([]string, error) } diff --git a/engine/list_generator.go b/engine/list_generator.go index c6287dcef..320463321 100644 --- a/engine/list_generator.go +++ b/engine/list_generator.go @@ -19,6 +19,7 @@ type ListGenerator interface { GetStarred(offset int, size int) (Entries, error) GetAllStarred() (albums Entries, mediaFiles Entries, err error) GetNowPlaying() (Entries, error) + GetRandomSongs(size int) (Entries, error) } func NewListGenerator(alr domain.AlbumRepository, mfr domain.MediaFileRepository, npr NowPlayingRepository) ListGenerator { @@ -89,6 +90,26 @@ func (g *listGenerator) GetRandom(offset int, size int) (Entries, error) { return r, nil } +func (g *listGenerator) GetRandomSongs(size int) (Entries, error) { + ids, err := g.mfRepository.GetAllIds() + if err != nil { + return nil, err + } + size = utils.MinInt(size, len(ids)) + perm := rand.Perm(size) + r := make(Entries, size) + + for i := 0; i < size; i++ { + v := perm[i] + mf, err := g.mfRepository.Get(ids[v]) + if err != nil { + return nil, err + } + r[i] = FromMediaFile(mf) + } + return r, nil +} + func (g *listGenerator) GetStarred(offset int, size int) (Entries, error) { qo := domain.QueryOptions{Offset: offset, Size: size, Desc: true} albums, err := g.albumRepo.GetStarred(qo) diff --git a/persistence/mediafile_repository.go b/persistence/mediafile_repository.go index 921e765f6..1e99a844d 100644 --- a/persistence/mediafile_repository.go +++ b/persistence/mediafile_repository.go @@ -52,6 +52,22 @@ func (r *mediaFileRepository) GetStarred(options domain.QueryOptions) (domain.Me return mfs, err } +func (r *mediaFileRepository) GetAllIds() ([]string, error) { + idMap, err := r.getAllIds() + if err != nil { + return nil, err + } + ids := make([]string, len(idMap)) + + i := 0 + for id := range idMap { + ids[i] = id + i++ + } + + return ids, nil +} + func (r *mediaFileRepository) PurgeInactive(active domain.MediaFiles) ([]string, error) { return r.purgeInactive(active, func(e interface{}) string { return e.(domain.MediaFile).Id