diff --git a/api/get_album_list.go b/api/album_list.go similarity index 74% rename from api/get_album_list.go rename to api/album_list.go index 6a5636e68..d443ad23e 100644 --- a/api/get_album_list.go +++ b/api/album_list.go @@ -10,7 +10,7 @@ import ( "github.com/deluan/gosonic/utils" ) -type GetAlbumListController struct { +type AlbumListController struct { BaseAPIController listGen engine.ListGenerator types map[string]strategy @@ -18,7 +18,7 @@ type GetAlbumListController struct { type strategy func(offset int, size int) (*domain.Albums, error) -func (c *GetAlbumListController) Prepare() { +func (c *AlbumListController) Prepare() { utils.ResolveDependencies(&c.listGen) c.types = map[string]strategy{ @@ -30,12 +30,12 @@ func (c *GetAlbumListController) Prepare() { } } -func (c *GetAlbumListController) Get() { +func (c *AlbumListController) GetAlbumList() { typ := c.RequiredParamString("type", "Required string parameter 'type' is not present") method, found := c.types[typ] if !found { - beego.Error("getAlbumList type", typ, "not implemented!") + beego.Error("albumList type", typ, "not implemented!") c.SendError(responses.ERROR_GENERIC, "Not implemented!") } @@ -70,3 +70,21 @@ func (c *GetAlbumListController) Get() { response.AlbumList = &responses.AlbumList{Album: albumList} c.SendResponse(response) } + +func (c *AlbumListController) GetStarred() { + albums, err := c.listGen.GetStarred() + if err != nil { + beego.Error("Error retrieving starred albums:", err) + c.SendError(responses.ERROR_GENERIC, "Internal Error") + } + + response := c.NewEmpty() + response.Starred = &responses.Starred{} + response.Starred.Album = make([]responses.Child, len(*albums)) + + for i, entry := range *albums { + response.Starred.Album[i] = c.ToChild(entry) + } + + c.SendResponse(response) +} diff --git a/api/get_album_list_test.go b/api/album_list_test.go similarity index 100% rename from api/get_album_list_test.go rename to api/album_list_test.go diff --git a/api/responses/responses.go b/api/responses/responses.go index 8bd446b4e..41a506e80 100644 --- a/api/responses/responses.go +++ b/api/responses/responses.go @@ -19,6 +19,7 @@ type Subsonic struct { Playlists *Playlists `xml:"playlists,omitempty" json:"playlists,omitempty"` Playlist *PlaylistWithSongs `xml:"playlist,omitempty" json:"playlist,omitempty"` SearchResult2 *SearchResult2 `xml:"searchResult2,omitempty" json:"searchResult2,omitempty"` + Starred *Starred `xml:"starred,omitempty" json:"starred,omitempty"` } type JsonWrapper struct { @@ -131,6 +132,12 @@ type SearchResult2 struct { Song []Child `xml:"song" json:"song,omitempty"` } +type Starred struct { + Artist []Artist `xml:"artist" json:"artist,omitempty"` + Album []Child `xml:"album" json:"album,omitempty"` + Song []Child `xml:"song" json:"song,omitempty"` +} + type User struct { Username string `xml:"username,attr" json:"username"` Email string `xml:"email,attr,omitempty" json:"email,omitempty"` diff --git a/conf/router.go b/conf/router.go index 41ea60d7d..d7f233353 100644 --- a/conf/router.go +++ b/conf/router.go @@ -31,7 +31,8 @@ func mapEndpoints() { beego.NSRouter("/scrobble.view", &api.MediaAnnotationController{}, "*:Scrobble"), - beego.NSRouter("/getAlbumList.view", &api.GetAlbumListController{}, "*:Get"), + beego.NSRouter("/getAlbumList.view", &api.AlbumListController{}, "*:GetAlbumList"), + beego.NSRouter("/getStarred.view", &api.AlbumListController{}, "*:GetStarred"), beego.NSRouter("/getPlaylists.view", &api.PlaylistsController{}, "*:GetAll"), beego.NSRouter("/getPlaylist.view", &api.PlaylistsController{}, "*:Get"), diff --git a/domain/album.go b/domain/album.go index d1137cc82..1097baebc 100644 --- a/domain/album.go +++ b/domain/album.go @@ -31,4 +31,5 @@ type AlbumRepository interface { GetAll(QueryOptions) (*Albums, error) PurgeInactive(active *Albums) error GetAllIds() (*[]string, error) + GetStarred(QueryOptions) (*Albums, error) } diff --git a/engine/common.go b/engine/common.go index 1821c8a29..2232915a7 100644 --- a/engine/common.go +++ b/engine/common.go @@ -26,6 +26,8 @@ type Entry struct { ContentType string } +type Entries []Entry + var ( ErrDataNotFound = errors.New("Data Not Found") ) diff --git a/engine/list_generator.go b/engine/list_generator.go index 6dc328b60..51a4b1b8a 100644 --- a/engine/list_generator.go +++ b/engine/list_generator.go @@ -14,6 +14,7 @@ type ListGenerator interface { GetFrequent(offset int, size int) (*domain.Albums, error) GetHighest(offset int, size int) (*domain.Albums, error) GetRandom(offset int, size int) (*domain.Albums, error) + GetStarred() (*Entries, error) } func NewListGenerator(alr domain.AlbumRepository) ListGenerator { @@ -69,3 +70,17 @@ func (g listGenerator) GetRandom(offset int, size int) (*domain.Albums, error) { } return &r, nil } + +func (g listGenerator) GetStarred() (*Entries, error) { + albums, err := g.albumRepo.GetStarred(domain.QueryOptions{}) + if err != nil { + return nil, err + } + entries := make(Entries, len(*albums)) + + for i, al := range *albums { + entries[i] = FromAlbum(&al) + } + + return &entries, nil +} diff --git a/persistence/album_repository.go b/persistence/album_repository.go index 171c4fa7e..def92fa1b 100644 --- a/persistence/album_repository.go +++ b/persistence/album_repository.go @@ -74,4 +74,10 @@ func (r *albumRepository) PurgeInactive(active *domain.Albums) error { return r.DeleteAll(inactiveIds) } +func (r *albumRepository) GetStarred(options domain.QueryOptions) (*domain.Albums, error) { + var as = make(domain.Albums, 0) + err := r.loadRange("Starred", true, true, &as, options) + return &as, err +} + var _ domain.AlbumRepository = (*albumRepository)(nil) diff --git a/persistence/ledis_repository.go b/persistence/ledis_repository.go index 64dbf9c05..d8f098e0f 100644 --- a/persistence/ledis_repository.go +++ b/persistence/ledis_repository.go @@ -167,23 +167,25 @@ func (r *ledisRepository) saveOrUpdate(id string, entity interface{}) error { } func calcScore(entity interface{}, fieldName string) int64 { - var score int64 - dv := reflect.ValueOf(entity).Elem() v := dv.FieldByName(fieldName) - switch v.Interface().(type) { + return toScore(v.Interface()) +} + +func toScore(value interface{}) int64 { + switch v := value.(type) { case int: - score = v.Int() + return int64(v) case bool: - if v.Bool() { - score = 1 + if v { + return 1 } case time.Time: - score = utils.ToMillis(v.Interface().(time.Time)) + return utils.ToMillis(v) } - return score + return 0 } func (r *ledisRepository) getParentRelationKey(entity interface{}) string { @@ -243,6 +245,36 @@ func (r *ledisRepository) toEntity(response [][]byte, entity interface{}) error return utils.ToStruct(record, entity) } +func (r *ledisRepository) loadRange(idxName string, min interface{}, max interface{}, entities interface{}, qo ...domain.QueryOptions) error { + o := domain.QueryOptions{} + if len(qo) > 0 { + o = qo[0] + } + if o.Size == 0 { + o.Size = -1 + } + + minS := toScore(min) + maxS := toScore(max) + + idxKey := fmt.Sprintf("%s:idx:%s", r.table, idxName) + resp, err := Db().ZRangeByScore([]byte(idxKey), minS, maxS, o.Offset, o.Size) + if err != nil { + return err + } + + reflected := reflect.ValueOf(entities).Elem() + for _, pair := range resp { + e, err := r.readEntity(string(pair.Member)) + if err != nil { + return err + } + reflected.Set(reflect.Append(reflected, reflect.ValueOf(e).Elem())) + } + + return nil +} + func (r *ledisRepository) loadAll(entities interface{}, qo ...domain.QueryOptions) error { setName := r.table + "s:all" return r.loadFromSet(setName, entities, qo...)