mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-14 19:20:37 +03:00
Reimplemented GetAlbumList&type=random and GetRandomSongs (now with filter by genres)
This commit is contained in:
parent
6cd758faa0
commit
de0816da67
@ -32,10 +32,10 @@ CloudSonic and Subsonic:
|
|||||||
| _ALBUM/SONGS LISTS_ ||
|
| _ALBUM/SONGS LISTS_ ||
|
||||||
| `getAlbumList` | `byYear` and `byGenre` are not implemented |
|
| `getAlbumList` | `byYear` and `byGenre` are not implemented |
|
||||||
| `getAlbumList2` | `byYear` and `byGenre` are not implemented |
|
| `getAlbumList2` | `byYear` and `byGenre` are not implemented |
|
||||||
| `getStarred` | Doesn't return any artists, as iTunes does not support starred (loved) artists |
|
| `getStarred` | |
|
||||||
| `getStarred2` | Doesn't return any artists, as iTunes does not support starred (loved) artists |
|
| `getStarred2` | |
|
||||||
| `getNowPlaying` | |
|
| `getNowPlaying` | |
|
||||||
| `getRandomSongs` | Ignores `genre` and `year` parameters |
|
| `getRandomSongs` | Ignores `year` parameter |
|
||||||
| ||
|
| ||
|
||||||
| _SEARCHING_ ||
|
| _SEARCHING_ ||
|
||||||
| `search2` | Doesn't support Lucene queries, only simple auto complete queries |
|
| `search2` | Doesn't support Lucene queries, only simple auto complete queries |
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cloudsonic/sonic-server/model"
|
"github.com/cloudsonic/sonic-server/model"
|
||||||
"github.com/cloudsonic/sonic-server/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ListGenerator interface {
|
type ListGenerator interface {
|
||||||
@ -19,7 +17,7 @@ type ListGenerator interface {
|
|||||||
GetStarred(offset int, size int) (Entries, error)
|
GetStarred(offset int, size int) (Entries, error)
|
||||||
GetAllStarred() (artists Entries, albums Entries, mediaFiles Entries, err error)
|
GetAllStarred() (artists Entries, albums Entries, mediaFiles Entries, err error)
|
||||||
GetNowPlaying() (Entries, error)
|
GetNowPlaying() (Entries, error)
|
||||||
GetRandomSongs(size int) (Entries, error)
|
GetRandomSongs(size int, genre string) (Entries, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewListGenerator(ds model.DataStore, npRepo NowPlayingRepository) ListGenerator {
|
func NewListGenerator(ds model.DataStore, npRepo NowPlayingRepository) ListGenerator {
|
||||||
@ -71,41 +69,31 @@ func (g *listGenerator) GetByArtist(offset int, size int) (Entries, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *listGenerator) GetRandom(offset int, size int) (Entries, error) {
|
func (g *listGenerator) GetRandom(offset int, size int) (Entries, error) {
|
||||||
ids, err := g.ds.Album().GetAllIds()
|
albums, err := g.ds.Album().GetRandom(model.QueryOptions{Max: size, Offset: offset})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
size = utils.MinInt(size, len(ids))
|
|
||||||
perm := rand.Perm(size)
|
|
||||||
r := make(Entries, size)
|
|
||||||
|
|
||||||
for i := 0; i < size; i++ {
|
r := make(Entries, len(albums))
|
||||||
v := perm[i]
|
for i, al := range albums {
|
||||||
al, err := g.ds.Album().Get((ids)[v])
|
r[i] = FromAlbum(&al)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r[i] = FromAlbum(al)
|
|
||||||
}
|
}
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *listGenerator) GetRandomSongs(size int) (Entries, error) {
|
func (g *listGenerator) GetRandomSongs(size int, genre string) (Entries, error) {
|
||||||
ids, err := g.ds.MediaFile().GetAllIds()
|
options := model.QueryOptions{Max: size}
|
||||||
|
if genre != "" {
|
||||||
|
options.Filters = map[string]interface{}{"genre": genre}
|
||||||
|
}
|
||||||
|
mediaFiles, err := g.ds.MediaFile().GetRandom(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
size = utils.MinInt(size, len(ids))
|
|
||||||
perm := rand.Perm(size)
|
|
||||||
r := make(Entries, size)
|
|
||||||
|
|
||||||
for i := 0; i < size; i++ {
|
r := make(Entries, len(mediaFiles))
|
||||||
v := perm[i]
|
for i, mf := range mediaFiles {
|
||||||
mf, err := g.ds.MediaFile().Get(ids[v])
|
r[i] = FromMediaFile(&mf)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r[i] = FromMediaFile(mf)
|
|
||||||
}
|
}
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ type AlbumRepository interface {
|
|||||||
Get(id string) (*Album, error)
|
Get(id string) (*Album, error)
|
||||||
FindByArtist(artistId string) (Albums, error)
|
FindByArtist(artistId string) (Albums, error)
|
||||||
GetAll(...QueryOptions) (Albums, error)
|
GetAll(...QueryOptions) (Albums, error)
|
||||||
GetAllIds() ([]string, error)
|
GetRandom(...QueryOptions) (Albums, error)
|
||||||
GetStarred(...QueryOptions) (Albums, error)
|
GetStarred(...QueryOptions) (Albums, error)
|
||||||
Search(q string, offset int, size int) (Albums, error)
|
Search(q string, offset int, size int) (Albums, error)
|
||||||
Refresh(ids ...string) error
|
Refresh(ids ...string) error
|
||||||
|
@ -47,7 +47,7 @@ type MediaFileRepository interface {
|
|||||||
FindByAlbum(albumId string) (MediaFiles, error)
|
FindByAlbum(albumId string) (MediaFiles, error)
|
||||||
FindByPath(path string) (MediaFiles, error)
|
FindByPath(path string) (MediaFiles, error)
|
||||||
GetStarred(options ...QueryOptions) (MediaFiles, error)
|
GetStarred(options ...QueryOptions) (MediaFiles, error)
|
||||||
GetAllIds() ([]string, error)
|
GetRandom(options ...QueryOptions) (MediaFiles, error)
|
||||||
Search(q string, offset int, size int) (MediaFiles, error)
|
Search(q string, offset int, size int) (MediaFiles, error)
|
||||||
Delete(id string) error
|
Delete(id string) error
|
||||||
DeleteByPath(path string) error
|
DeleteByPath(path string) error
|
||||||
|
@ -79,6 +79,24 @@ func (r *albumRepository) GetAll(options ...model.QueryOptions) (model.Albums, e
|
|||||||
return r.toAlbums(all), nil
|
return r.toAlbums(all), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Keep order when paginating
|
||||||
|
func (r *albumRepository) GetRandom(options ...model.QueryOptions) (model.Albums, error) {
|
||||||
|
sq := r.newRawQuery(options...)
|
||||||
|
switch r.ormer.Driver().Type() {
|
||||||
|
case orm.DRMySQL:
|
||||||
|
sq = sq.OrderBy("RAND()")
|
||||||
|
default:
|
||||||
|
sq = sq.OrderBy("RANDOM()")
|
||||||
|
}
|
||||||
|
sql, args, err := sq.ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var results []album
|
||||||
|
_, err = r.ormer.Raw(sql, args...).QueryRows(&results)
|
||||||
|
return r.toAlbums(results), err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *albumRepository) toAlbums(all []album) model.Albums {
|
func (r *albumRepository) toAlbums(all []album) model.Albums {
|
||||||
result := make(model.Albums, len(all))
|
result := make(model.Albums, len(all))
|
||||||
for i, a := range all {
|
for i, a := range all {
|
||||||
|
@ -42,12 +42,6 @@ var _ = Describe("AlbumRepository", func() {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("GetAllIds", func() {
|
|
||||||
It("returns all records", func() {
|
|
||||||
Expect(repo.GetAllIds()).To(ConsistOf("1", "2", "3"))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Describe("GetStarred", func() {
|
Describe("GetStarred", func() {
|
||||||
It("returns all starred records", func() {
|
It("returns all starred records", func() {
|
||||||
Expect(repo.GetStarred(model.QueryOptions{})).To(Equal(model.Albums{
|
Expect(repo.GetStarred(model.QueryOptions{})).To(Equal(model.Albums{
|
||||||
|
@ -127,6 +127,23 @@ func (r *mediaFileRepository) DeleteByPath(path string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mediaFileRepository) GetRandom(options ...model.QueryOptions) (model.MediaFiles, error) {
|
||||||
|
sq := r.newRawQuery(options...)
|
||||||
|
switch r.ormer.Driver().Type() {
|
||||||
|
case orm.DRMySQL:
|
||||||
|
sq = sq.OrderBy("RAND()")
|
||||||
|
default:
|
||||||
|
sq = sq.OrderBy("RANDOM()")
|
||||||
|
}
|
||||||
|
sql, args, err := sq.ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var results []mediaFile
|
||||||
|
_, err = r.ormer.Raw(sql, args...).QueryRows(&results)
|
||||||
|
return r.toMediaFiles(results), err
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mediaFileRepository) GetStarred(options ...model.QueryOptions) (model.MediaFiles, error) {
|
func (r *mediaFileRepository) GetStarred(options ...model.QueryOptions) (model.MediaFiles, error) {
|
||||||
var starred []mediaFile
|
var starred []mediaFile
|
||||||
_, err := r.newQuery(options...).Filter("starred", true).All(&starred)
|
_, err := r.newQuery(options...).Filter("starred", true).All(&starred)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package persistence
|
package persistence
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/Masterminds/squirrel"
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
"github.com/cloudsonic/sonic-server/model"
|
"github.com/cloudsonic/sonic-server/model"
|
||||||
)
|
)
|
||||||
@ -29,6 +30,29 @@ func (r *sqlRepository) newQuery(options ...model.QueryOptions) orm.QuerySeter {
|
|||||||
return q
|
return q
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *sqlRepository) newRawQuery(options ...model.QueryOptions) squirrel.SelectBuilder {
|
||||||
|
sq := squirrel.Select("*").From(r.tableName)
|
||||||
|
if len(options) > 0 {
|
||||||
|
if options[0].Max > 0 {
|
||||||
|
sq = sq.Limit(uint64(options[0].Max))
|
||||||
|
}
|
||||||
|
if options[0].Offset > 0 {
|
||||||
|
sq = sq.Offset(uint64(options[0].Max))
|
||||||
|
}
|
||||||
|
if options[0].Sort != "" {
|
||||||
|
if options[0].Order == "desc" {
|
||||||
|
sq = sq.OrderBy(options[0].Sort + " desc")
|
||||||
|
} else {
|
||||||
|
sq = sq.OrderBy(options[0].Sort)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for field, value := range options[0].Filters {
|
||||||
|
sq = sq.Where(squirrel.Like{field: value.(string) + "%"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sq
|
||||||
|
}
|
||||||
|
|
||||||
func (r *sqlRepository) CountAll() (int64, error) {
|
func (r *sqlRepository) CountAll() (int64, error) {
|
||||||
return r.newQuery().Count()
|
return r.newQuery().Count()
|
||||||
}
|
}
|
||||||
@ -38,22 +62,6 @@ func (r *sqlRepository) Exists(id string) (bool, error) {
|
|||||||
return c == 1, err
|
return c == 1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO This is used to generate random lists. Can be optimized in SQL: https://stackoverflow.com/a/19419
|
|
||||||
func (r *sqlRepository) GetAllIds() ([]string, error) {
|
|
||||||
qs := r.newQuery()
|
|
||||||
var values []orm.Params
|
|
||||||
num, err := qs.Values(&values, "id")
|
|
||||||
if num == 0 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := collectField(values, func(item interface{}) string {
|
|
||||||
return item.(orm.Params)["ID"].(string)
|
|
||||||
})
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Hack" to bypass Postgres driver limitation
|
// "Hack" to bypass Postgres driver limitation
|
||||||
func (r *sqlRepository) insert(record interface{}) error {
|
func (r *sqlRepository) insert(record interface{}) error {
|
||||||
_, err := r.ormer.Insert(record)
|
_, err := r.ormer.Insert(record)
|
||||||
|
@ -132,8 +132,9 @@ func (c *AlbumListController) GetNowPlaying(w http.ResponseWriter, r *http.Reque
|
|||||||
|
|
||||||
func (c *AlbumListController) GetRandomSongs(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
|
func (c *AlbumListController) GetRandomSongs(w http.ResponseWriter, r *http.Request) (*responses.Subsonic, error) {
|
||||||
size := utils.MinInt(ParamInt(r, "size", 10), 500)
|
size := utils.MinInt(ParamInt(r, "size", 10), 500)
|
||||||
|
genre := ParamString(r, "genre")
|
||||||
|
|
||||||
songs, err := c.listGen.GetRandomSongs(size)
|
songs, err := c.listGen.GetRandomSongs(size, 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, NewError(responses.ErrorGeneric, "Internal Error")
|
return nil, NewError(responses.ErrorGeneric, "Internal Error")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user