mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-04 21:17:37 +03:00
Playlists working
This commit is contained in:
parent
4bb4fc0cb8
commit
2214e4bd4f
8 changed files with 179 additions and 41 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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, `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.0.0"><playlist id="1" name="My Playlist"></playlist></subsonic-response>`)
|
||||
})
|
||||
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, `<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.0.0"><playlist id="1" name="My Playlist"><entry id="1" isDir="false" title="title"></entry></playlist></subsonic-response>`)
|
||||
})
|
||||
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"}
|
||||
})
|
||||
|
|
|
@ -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"),
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
29
engine/common.go
Normal file
29
engine/common.go
Normal file
|
@ -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")
|
||||
)
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue