From 2214e4bd4f20a9ca1e4d47e7b690412bbd3eaa1c Mon Sep 17 00:00:00 2001 From: Deluan Date: Wed, 9 Mar 2016 18:28:11 -0500 Subject: [PATCH] Playlists working --- README.md | 6 ++-- api/playlists.go | 47 +++++++++++++++++++++++++ api/responses/responses.go | 28 +++++++++------ api/responses/responses_test.go | 24 +++++++++++++ conf/router.go | 1 + engine/browser.go | 23 ------------ engine/common.go | 29 +++++++++++++++ engine/playlists.go | 62 ++++++++++++++++++++++++++++++--- 8 files changed, 179 insertions(+), 41 deletions(-) create mode 100644 engine/common.go diff --git a/README.md b/README.md index 52246ac78..67e6750d5 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,11 @@ The project's main goals are: * Learning Go ;) [![Gopher](https://blog.golang.org/favicon.ico)](https://golang.org) -### Supported Subsonic API version: +### Supported Subsonic API version -[(Almost) up to date compatibility chart](https://github.com/deluan/gosonic/wiki/Compatibility) +I'm currently trying to implement all functionality from API v1.2.0, with some exceptions. + +Check the (almost) up to date [compatibility chart](https://github.com/deluan/gosonic/wiki/Compatibility) for what is working. ### Development Environment diff --git a/api/playlists.go b/api/playlists.go index 9bd4d64de..26f83abc8 100644 --- a/api/playlists.go +++ b/api/playlists.go @@ -32,3 +32,50 @@ func (c *PlaylistsController) GetAll() { response.Playlists = &responses.Playlists{Playlist: playlists} c.SendResponse(response) } + +func (c *PlaylistsController) Get() { + id := c.RequiredParamString("id", "id parameter required") + + pinfo, err := c.pls.Get(id) + switch { + case err == engine.DataNotFound: + beego.Error(err, "Id:", id) + c.SendError(responses.ERROR_DATA_NOT_FOUND, "Directory not found") + case err != nil: + beego.Error(err) + c.SendError(responses.ERROR_GENERIC, "Internal Error") + } + + response := c.NewEmpty() + response.Playlist = c.buildPlaylist(pinfo) + c.SendResponse(response) +} + +func (c *PlaylistsController) buildPlaylist(d *engine.PlaylistInfo) *responses.PlaylistWithSongs { + pls := &responses.PlaylistWithSongs{} + pls.Id = d.Id + pls.Name = d.Name + + pls.Entry = make([]responses.Child, len(d.Entries)) + for i, child := range d.Entries { + pls.Entry[i].Id = child.Id + pls.Entry[i].Title = child.Title + pls.Entry[i].IsDir = child.IsDir + pls.Entry[i].Parent = child.Parent + pls.Entry[i].Album = child.Album + pls.Entry[i].Year = child.Year + pls.Entry[i].Artist = child.Artist + pls.Entry[i].Genre = child.Genre + pls.Entry[i].CoverArt = child.CoverArt + pls.Entry[i].Track = child.Track + pls.Entry[i].Duration = child.Duration + pls.Entry[i].Size = child.Size + pls.Entry[i].Suffix = child.Suffix + pls.Entry[i].BitRate = child.BitRate + pls.Entry[i].ContentType = child.ContentType + if !child.Starred.IsZero() { + pls.Entry[i].Starred = &child.Starred + } + } + return pls +} diff --git a/api/responses/responses.go b/api/responses/responses.go index c6bf9e4a8..f89583933 100644 --- a/api/responses/responses.go +++ b/api/responses/responses.go @@ -6,17 +6,18 @@ import ( ) type Subsonic struct { - XMLName xml.Name `xml:"http://subsonic.org/restapi subsonic-response" json:"-"` - Status string `xml:"status,attr" json:"status"` - Version string `xml:"version,attr" json:"version"` - Error *Error `xml:"error,omitempty" json:"error,omitempty"` - License *License `xml:"license,omitempty" json:"license,omitempty"` - 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"` - AlbumList *AlbumList `xml:"albumList,omitempty" json:"albumList,omitempty"` - Playlists *Playlists `xml:"playlists,omitempty" json:"playlists,omitempty"` + XMLName xml.Name `xml:"http://subsonic.org/restapi subsonic-response" json:"-"` + Status string `xml:"status,attr" json:"status"` + Version string `xml:"version,attr" json:"version"` + Error *Error `xml:"error,omitempty" json:"error,omitempty"` + License *License `xml:"license,omitempty" json:"license,omitempty"` + 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"` + AlbumList *AlbumList `xml:"albumList,omitempty" json:"albumList,omitempty"` + Playlists *Playlists `xml:"playlists,omitempty" json:"playlists,omitempty"` + Playlist *PlaylistWithSongs `xml:"playlist,omitempty" json:"playlist,omitempty"` } type JsonWrapper struct { @@ -111,6 +112,11 @@ type Playlists struct { Playlist []Playlist `xml:"playlist" json:"playlist,omitempty"` } +type PlaylistWithSongs struct { + Playlist + Entry []Child `xml:"entry" json:"entry,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 da622812d..559fd959a 100644 --- a/api/responses/responses_test.go +++ b/api/responses/responses_test.go @@ -199,6 +199,30 @@ func TestSubsonicResponses(t *testing.T) { }) }) + Convey("Playlist", func() { + response.Playlist = &PlaylistWithSongs{} + response.Playlist.Id = "1" + response.Playlist.Name = "My Playlist" + Convey("Without data", func() { + Convey("XML", func() { + So(response, ShouldMatchXML, ``) + }) + Convey("JSON", func() { + So(response, ShouldMatchJSON, `{"playlist":{"id":"1","name":"My Playlist"},"status":"ok","version":"1.0.0"}`) + }) + }) + Convey("With just required data", func() { + entry := make([]Child, 1) + entry[0] = Child{Id: "1", Title: "title", IsDir: false} + response.Playlist.Entry = entry + Convey("XML", func() { + So(response, ShouldMatchXML, ``) + }) + Convey("JSON", func() { + So(response, ShouldMatchJSON, `{"playlist":{"entry":[{"id":"1","isDir":false,"title":"title"}],"id":"1","name":"My Playlist"},"status":"ok","version":"1.0.0"}`) + }) + }) + }) Reset(func() { response = &Subsonic{Status: "ok", Version: "1.0.0"} }) diff --git a/conf/router.go b/conf/router.go index 0e56a7b82..c821d5e0f 100644 --- a/conf/router.go +++ b/conf/router.go @@ -30,6 +30,7 @@ func mapEndpoints() { beego.NSRouter("/getAlbumList.view", &api.GetAlbumListController{}, "*:Get"), beego.NSRouter("/getPlaylists.view", &api.PlaylistsController{}, "*:GetAll"), + beego.NSRouter("/getPlaylist.view", &api.PlaylistsController{}, "*:Get"), beego.NSRouter("/getUser.view", &api.UsersController{}, "*:GetUser"), ) diff --git a/engine/browser.go b/engine/browser.go index bdf5cd829..6a682d7d2 100644 --- a/engine/browser.go +++ b/engine/browser.go @@ -12,10 +12,6 @@ import ( "github.com/deluan/gosonic/utils" ) -var ( - DataNotFound = errors.New("Data Not Found") -) - type Browser interface { MediaFolders() (*domain.MediaFolders, error) Indexes(ifModifiedSince time.Time) (*domain.ArtistIndexes, time.Time, error) @@ -57,25 +53,6 @@ func (b browser) Indexes(ifModifiedSince time.Time) (*domain.ArtistIndexes, time return &domain.ArtistIndexes{}, lastModified, nil } -type Child struct { - Id string - Title string - IsDir bool - Parent string - Album string - Year int - Artist string - Genre string - CoverArt string - Starred time.Time - Track int - Duration int - Size string - Suffix string - BitRate int - ContentType string -} - type DirectoryInfo struct { Id string Name string diff --git a/engine/common.go b/engine/common.go new file mode 100644 index 000000000..9ac485845 --- /dev/null +++ b/engine/common.go @@ -0,0 +1,29 @@ +package engine + +import ( + "errors" + "time" +) + +type Child struct { + Id string + Title string + IsDir bool + Parent string + Album string + Year int + Artist string + Genre string + CoverArt string + Starred time.Time + Track int + Duration int + Size string + Suffix string + BitRate int + ContentType string +} + +var ( + DataNotFound = errors.New("Data Not Found") +) diff --git a/engine/playlists.go b/engine/playlists.go index ee23536f4..d65cdffc9 100644 --- a/engine/playlists.go +++ b/engine/playlists.go @@ -6,16 +6,68 @@ import ( type Playlists interface { GetAll() (*domain.Playlists, error) + Get(id string) (*PlaylistInfo, error) +} + +func NewPlaylists(pr domain.PlaylistRepository, mr domain.MediaFileRepository) Playlists { + return playlists{pr, mr} } type playlists struct { - plsRepo domain.PlaylistRepository -} - -func NewPlaylists(pr domain.PlaylistRepository) Playlists { - return playlists{pr} + plsRepo domain.PlaylistRepository + mfileRepo domain.MediaFileRepository } func (p playlists) GetAll() (*domain.Playlists, error) { return p.plsRepo.GetAll(domain.QueryOptions{}) } + +type PlaylistInfo struct { + Id string + Name string + Entries []Child +} + +func (p playlists) Get(id string) (*PlaylistInfo, error) { + pl, err := p.plsRepo.Get(id) + if err != nil { + return nil, err + } + + if pl == nil { + return nil, DataNotFound + } + + pinfo := &PlaylistInfo{Id: pl.Id, Name: pl.Name} + pinfo.Entries = make([]Child, len(pl.Tracks)) + + // TODO Optimize: Get all tracks at once + for i, mfId := range pl.Tracks { + mf, err := p.mfileRepo.Get(mfId) + if err != nil { + return nil, err + } + pinfo.Entries[i].Id = mf.Id + pinfo.Entries[i].Title = mf.Title + pinfo.Entries[i].IsDir = false + pinfo.Entries[i].Parent = mf.AlbumId + pinfo.Entries[i].Album = mf.Album + pinfo.Entries[i].Year = mf.Year + pinfo.Entries[i].Artist = mf.Artist + pinfo.Entries[i].Genre = mf.Genre + //pinfo.Entries[i].Track = mf.TrackNumber + pinfo.Entries[i].Duration = mf.Duration + pinfo.Entries[i].Size = mf.Size + pinfo.Entries[i].Suffix = mf.Suffix + pinfo.Entries[i].BitRate = mf.BitRate + if mf.Starred { + pinfo.Entries[i].Starred = mf.UpdatedAt + } + if mf.HasCoverArt { + pinfo.Entries[i].CoverArt = mf.Id + } + pinfo.Entries[i].ContentType = mf.ContentType() + } + + return pinfo, nil +}