From 7161325716d9d6fc8d39a47c98cd0b660e4542be Mon Sep 17 00:00:00 2001 From: Deluan <github@deluan.com> Date: Wed, 9 Mar 2016 10:09:15 -0500 Subject: [PATCH] Initial wiring for getPlaylists endpoint --- api/playlists.go | 34 ++++++++++++++++++ api/responses/responses.go | 28 ++++++++++++++- api/responses/responses_test.go | 30 ++++++++++++++-- conf/inject_definitions.go | 2 ++ conf/router.go | 1 + domain/playlist.go | 17 +++++++++ engine/playlists.go | 21 ++++++++++++ persistence/playlist_repository.go | 55 ++++++++++++++++++++++++++++++ 8 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 api/playlists.go create mode 100644 domain/playlist.go create mode 100644 engine/playlists.go create mode 100644 persistence/playlist_repository.go diff --git a/api/playlists.go b/api/playlists.go new file mode 100644 index 000000000..9bd4d64de --- /dev/null +++ b/api/playlists.go @@ -0,0 +1,34 @@ +package api + +import ( + "github.com/astaxie/beego" + "github.com/deluan/gosonic/api/responses" + "github.com/deluan/gosonic/engine" + "github.com/deluan/gosonic/utils" + "github.com/karlkfi/inject" +) + +type PlaylistsController struct { + BaseAPIController + pls engine.Playlists +} + +func (c *PlaylistsController) Prepare() { + inject.ExtractAssignable(utils.Graph, &c.pls) +} + +func (c *PlaylistsController) GetAll() { + allPls, err := c.pls.GetAll() + if err != nil { + beego.Error(err) + c.SendError(responses.ERROR_GENERIC, "Internal error") + } + playlists := make([]responses.Playlist, len(*allPls)) + for i, f := range *allPls { + playlists[i].Id = f.Id + playlists[i].Name = f.Name + } + response := c.NewEmpty() + response.Playlists = &responses.Playlists{Playlist: playlists} + c.SendResponse(response) +} diff --git a/api/responses/responses.go b/api/responses/responses.go index 1b6355602..6f53b1fe3 100644 --- a/api/responses/responses.go +++ b/api/responses/responses.go @@ -14,8 +14,9 @@ type Subsonic struct { MusicFolders *MusicFolders `xml:"musicFolders,omitempty" json:"musicFolders,omitempty"` Indexes *Indexes `xml:"indexes,omitempty" json:"indexes,omitempty"` Directory *Directory `xml:"directory,omitempty" json:"directory,omitempty"` - User *User `xml:"user,omitempty" json:"user,omitempty"` + User *User `xml:"user,omitempty" json:"user,omitempty"` AlbumList *AlbumList `xml:"albumList,omitempty" json:"albumList,omitempty"` + Playlists *Playlists `xml:"playlists,omitempty" json:"playlists,omitempty"` } type JsonWrapper struct { @@ -87,6 +88,31 @@ type AlbumList struct { Album []Child `xml:"album" json:"album,omitempty"` } +type Playlist struct { + Id string `xml:"id,attr" json:"id"` + Name string `xml:"name,attr" json:"name"` + /* + <xs:sequence> + <xs:element name="allowedUser" type="xs:string" minOccurs="0" maxOccurs="unbounded"/> <!--Added in 1.8.0--> + </xs:sequence> + <xs:attribute name="id" type="xs:string" use="required"/> + <xs:attribute name="name" type="xs:string" use="required"/> + <xs:attribute name="comment" type="xs:string" use="optional"/> <!--Added in 1.8.0--> + <xs:attribute name="owner" type="xs:string" use="optional"/> <!--Added in 1.8.0--> + <xs:attribute name="public" type="xs:boolean" use="optional"/> <!--Added in 1.8.0--> + <xs:attribute name="songCount" type="xs:int" use="required"/> <!--Added in 1.8.0--> + <xs:attribute name="duration" type="xs:int" use="required"/> <!--Added in 1.8.0--> + <xs:attribute name="created" type="xs:dateTime" use="required"/> <!--Added in 1.8.0--> + <xs:attribute name="changed" type="xs:dateTime" use="required"/> <!--Added in 1.13.0--> + <xs:attribute name="coverArt" type="xs:string" use="optional"/> <!--Added in 1.11.0--> + + */ +} + +type Playlists struct { + Playlist []Playlist `xml:"playlist" json:"playlist,omitempty"` +} + type User struct { Username string `xml:"username,attr" json:"username"` Email string `xml:"email,attr,omitempty" json:"email,omitempty"` diff --git a/api/responses/responses_test.go b/api/responses/responses_test.go index af7a68670..da622812d 100644 --- a/api/responses/responses_test.go +++ b/api/responses/responses_test.go @@ -1,11 +1,12 @@ package responses_test import ( + "testing" + "time" + . "github.com/deluan/gosonic/api/responses" . "github.com/deluan/gosonic/tests" . "github.com/smartystreets/goconvey/convey" - "testing" - "time" ) func TestSubsonicResponses(t *testing.T) { @@ -172,6 +173,31 @@ func TestSubsonicResponses(t *testing.T) { }) }) }) + Convey("Playlists", func() { + response.Playlists = &Playlists{} + + Convey("Without data", func() { + Convey("XML", func() { + So(response, ShouldMatchXML, `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.0.0"><playlists></playlists></subsonic-response>`) + }) + Convey("JSON", func() { + So(response, ShouldMatchJSON, `{"playlists":{},"status":"ok","version":"1.0.0"}`) + }) + }) + Convey("With data", func() { + pls := make([]Playlist, 2) + pls[0] = Playlist{Id: "111", Name: "aaa"} + pls[1] = Playlist{Id: "222", Name: "bbb"} + response.Playlists.Playlist = pls + + Convey("XML", func() { + So(response, ShouldMatchXML, `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.0.0"><playlists><playlist id="111" name="aaa"></playlist><playlist id="222" name="bbb"></playlist></playlists></subsonic-response>`) + }) + Convey("JSON", func() { + So(response, ShouldMatchJSON, `{"playlists":{"playlist":[{"id":"111","name":"aaa"},{"id":"222","name":"bbb"}]},"status":"ok","version":"1.0.0"}`) + }) + }) + }) Reset(func() { response = &Subsonic{Status: "ok", Version: "1.0.0"} diff --git a/conf/inject_definitions.go b/conf/inject_definitions.go index 131e46fca..2ef02d164 100644 --- a/conf/inject_definitions.go +++ b/conf/inject_definitions.go @@ -14,10 +14,12 @@ func init() { utils.DefineSingleton(new(domain.ArtistRepository), persistence.NewArtistRepository) utils.DefineSingleton(new(domain.AlbumRepository), persistence.NewAlbumRepository) utils.DefineSingleton(new(domain.MediaFileRepository), persistence.NewMediaFileRepository) + utils.DefineSingleton(new(domain.PlaylistRepository), persistence.NewPlaylistRepository) // Engine (Use cases) utils.DefineSingleton(new(engine.PropertyRepository), persistence.NewPropertyRepository) utils.DefineSingleton(new(engine.Browser), engine.NewBrowser) utils.DefineSingleton(new(engine.ListGenerator), engine.NewListGenerator) utils.DefineSingleton(new(engine.Cover), engine.NewCover) + utils.DefineSingleton(new(engine.Playlists), engine.NewPlaylists) } diff --git a/conf/router.go b/conf/router.go index 48b436ab7..ad38c838f 100644 --- a/conf/router.go +++ b/conf/router.go @@ -26,6 +26,7 @@ func mapEndpoints() { beego.NSRouter("/download.view", &api.StreamController{}, "*:Download"), beego.NSRouter("/getUser.view", &api.UsersController{}, "*:GetUser"), beego.NSRouter("/getAlbumList.view", &api.GetAlbumListController{}, "*:Get"), + beego.NSRouter("/getPlaylists.view", &api.PlaylistsController{}, "*:GetAll"), ) beego.AddNamespace(ns) diff --git a/domain/playlist.go b/domain/playlist.go new file mode 100644 index 000000000..7dc3c0215 --- /dev/null +++ b/domain/playlist.go @@ -0,0 +1,17 @@ +package domain + +type Playlist struct { + Id string + Name string + Tracks []string +} + +type PlaylistRepository interface { + BaseRepository + Put(m *Playlist) error + Get(id string) (*Playlist, error) + GetAll(options QueryOptions) (*Playlists, error) + PurgeInactive(active *Playlists) error +} + +type Playlists []Playlist diff --git a/engine/playlists.go b/engine/playlists.go new file mode 100644 index 000000000..ee23536f4 --- /dev/null +++ b/engine/playlists.go @@ -0,0 +1,21 @@ +package engine + +import ( + "github.com/deluan/gosonic/domain" +) + +type Playlists interface { + GetAll() (*domain.Playlists, error) +} + +type playlists struct { + plsRepo domain.PlaylistRepository +} + +func NewPlaylists(pr domain.PlaylistRepository) Playlists { + return playlists{pr} +} + +func (p playlists) GetAll() (*domain.Playlists, error) { + return p.plsRepo.GetAll(domain.QueryOptions{}) +} diff --git a/persistence/playlist_repository.go b/persistence/playlist_repository.go new file mode 100644 index 000000000..0617c8581 --- /dev/null +++ b/persistence/playlist_repository.go @@ -0,0 +1,55 @@ +package persistence + +import ( + "errors" + + "github.com/deluan/gosonic/domain" +) + +type playlistRepository struct { + ledisRepository +} + +func NewPlaylistRepository() domain.PlaylistRepository { + r := &playlistRepository{} + r.init("playlist", &domain.Playlist{}) + return r +} + +func (r *playlistRepository) Put(m *domain.Playlist) error { + if m.Id == "" { + return errors.New("Playlist Id is not set") + } + return r.saveOrUpdate(m.Id, m) +} + +func (r *playlistRepository) Get(id string) (*domain.Playlist, error) { + var rec interface{} + rec, err := r.readEntity(id) + return rec.(*domain.Playlist), err +} + +func (r *playlistRepository) GetAll(options domain.QueryOptions) (*domain.Playlists, error) { + var as = make(domain.Playlists, 0) + err := r.loadAll(&as, options) + return &as, err +} + +func (r *playlistRepository) PurgeInactive(active *domain.Playlists) error { + currentIds, err := r.getAllIds() + if err != nil { + return err + } + for _, a := range *active { + currentIds[a.Id] = false + } + inactiveIds := make(map[string]bool) + for id, inactive := range currentIds { + if inactive { + inactiveIds[id] = true + } + } + return r.DeleteAll(inactiveIds) +} + +var _ domain.PlaylistRepository = (*playlistRepository)(nil)