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 ;) [](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
+}