Reimplemented GetAlbumList&type=random and GetRandomSongs (now with filter by genres)

This commit is contained in:
Deluan 2020-01-21 08:49:43 -05:00
parent 6cd758faa0
commit de0816da67
9 changed files with 80 additions and 54 deletions

View file

@ -32,10 +32,10 @@ CloudSonic and Subsonic:
| _ALBUM/SONGS LISTS_ ||
| `getAlbumList` | `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 |
| `getStarred2` | Doesn't return any artists, as iTunes does not support starred (loved) artists |
| `getStarred` | |
| `getStarred2` | |
| `getNowPlaying` | |
| `getRandomSongs` | Ignores `genre` and `year` parameters |
| `getRandomSongs` | Ignores `year` parameter |
| ||
| _SEARCHING_ ||
| `search2` | Doesn't support Lucene queries, only simple auto complete queries |

View file

@ -1,11 +1,9 @@
package engine
import (
"math/rand"
"time"
"github.com/cloudsonic/sonic-server/model"
"github.com/cloudsonic/sonic-server/utils"
)
type ListGenerator interface {
@ -19,7 +17,7 @@ type ListGenerator interface {
GetStarred(offset int, size int) (Entries, error)
GetAllStarred() (artists Entries, albums Entries, mediaFiles Entries, err error)
GetNowPlaying() (Entries, error)
GetRandomSongs(size int) (Entries, error)
GetRandomSongs(size int, genre string) (Entries, error)
}
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) {
ids, err := g.ds.Album().GetAllIds()
albums, err := g.ds.Album().GetRandom(model.QueryOptions{Max: size, Offset: offset})
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]
al, err := g.ds.Album().Get((ids)[v])
if err != nil {
return nil, err
}
r[i] = FromAlbum(al)
r := make(Entries, len(albums))
for i, al := range albums {
r[i] = FromAlbum(&al)
}
return r, nil
}
func (g *listGenerator) GetRandomSongs(size int) (Entries, error) {
ids, err := g.ds.MediaFile().GetAllIds()
func (g *listGenerator) GetRandomSongs(size int, genre string) (Entries, error) {
options := model.QueryOptions{Max: size}
if genre != "" {
options.Filters = map[string]interface{}{"genre": genre}
}
mediaFiles, err := g.ds.MediaFile().GetRandom(options)
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.ds.MediaFile().Get(ids[v])
if err != nil {
return nil, err
}
r[i] = FromMediaFile(mf)
r := make(Entries, len(mediaFiles))
for i, mf := range mediaFiles {
r[i] = FromMediaFile(&mf)
}
return r, nil
}

View file

@ -33,7 +33,7 @@ type AlbumRepository interface {
Get(id string) (*Album, error)
FindByArtist(artistId string) (Albums, error)
GetAll(...QueryOptions) (Albums, error)
GetAllIds() ([]string, error)
GetRandom(...QueryOptions) (Albums, error)
GetStarred(...QueryOptions) (Albums, error)
Search(q string, offset int, size int) (Albums, error)
Refresh(ids ...string) error

View file

@ -47,7 +47,7 @@ type MediaFileRepository interface {
FindByAlbum(albumId string) (MediaFiles, error)
FindByPath(path string) (MediaFiles, error)
GetStarred(options ...QueryOptions) (MediaFiles, error)
GetAllIds() ([]string, error)
GetRandom(options ...QueryOptions) (MediaFiles, error)
Search(q string, offset int, size int) (MediaFiles, error)
Delete(id string) error
DeleteByPath(path string) error

View file

@ -79,6 +79,24 @@ func (r *albumRepository) GetAll(options ...model.QueryOptions) (model.Albums, e
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 {
result := make(model.Albums, len(all))
for i, a := range all {

View file

@ -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() {
It("returns all starred records", func() {
Expect(repo.GetStarred(model.QueryOptions{})).To(Equal(model.Albums{

View file

@ -127,6 +127,23 @@ func (r *mediaFileRepository) DeleteByPath(path string) error {
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) {
var starred []mediaFile
_, err := r.newQuery(options...).Filter("starred", true).All(&starred)

View file

@ -1,6 +1,7 @@
package persistence
import (
"github.com/Masterminds/squirrel"
"github.com/astaxie/beego/orm"
"github.com/cloudsonic/sonic-server/model"
)
@ -29,6 +30,29 @@ func (r *sqlRepository) newQuery(options ...model.QueryOptions) orm.QuerySeter {
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) {
return r.newQuery().Count()
}
@ -38,22 +62,6 @@ func (r *sqlRepository) Exists(id string) (bool, error) {
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
func (r *sqlRepository) insert(record interface{}) error {
_, err := r.ormer.Insert(record)

View file

@ -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) {
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 {
log.Error(r, "Error retrieving random songs", "error", err)
return nil, NewError(responses.ErrorGeneric, "Internal Error")