Halfway of getNowPlaying implementation

This commit is contained in:
Deluan 2016-03-17 09:34:32 -04:00
parent a3238ce17b
commit 68c456e188
13 changed files with 113 additions and 29 deletions

View file

@ -88,3 +88,10 @@ func (c *AlbumListController) GetStarred() {
c.SendResponse(response) c.SendResponse(response)
} }
func (c *AlbumListController) GetNowPlaying() {
response := c.NewEmpty()
response.NowPlaying = &responses.NowPlaying{}
//response.NowPlaying.Entry = make([]responses.NowPlayingEntry, 1)
c.SendResponse(response)
}

View file

@ -5,6 +5,7 @@ import (
"github.com/deluan/gosonic/api/responses" "github.com/deluan/gosonic/api/responses"
"github.com/deluan/gosonic/domain" "github.com/deluan/gosonic/domain"
"github.com/deluan/gosonic/engine"
"github.com/deluan/gosonic/persistence" "github.com/deluan/gosonic/persistence"
. "github.com/deluan/gosonic/tests" . "github.com/deluan/gosonic/tests"
"github.com/deluan/gosonic/utils" "github.com/deluan/gosonic/utils"
@ -19,6 +20,11 @@ func TestGetAlbumList(t *testing.T) {
return mockAlbumRepo return mockAlbumRepo
}) })
mockNowPlayingRepo := engine.CreateMockNowPlayingRepo()
utils.DefineSingleton(new(engine.NowPlayingRepository), func() engine.NowPlayingRepository {
return mockNowPlayingRepo
})
Convey("Subject: GetAlbumList Endpoint", t, func() { Convey("Subject: GetAlbumList Endpoint", t, func() {
mockAlbumRepo.SetData(`[ mockAlbumRepo.SetData(`[
{"Id":"A","Name":"Vagarosa","ArtistId":"2"}, {"Id":"A","Name":"Vagarosa","ArtistId":"2"},

View file

@ -23,6 +23,10 @@ func (c *MediaAnnotationController) Scrobble() {
id := c.RequiredParamString("id", "Required id parameter is missing") id := c.RequiredParamString("id", "Required id parameter is missing")
time := c.ParamTime("time", time.Now()) time := c.ParamTime("time", time.Now())
submission := c.ParamBool("submission", false) submission := c.ParamBool("submission", false)
playerName := c.Data["c"].(string)
username := c.Data["u"].(string)
if submission { if submission {
mf, err := c.scrobbler.Register(id, time) mf, err := c.scrobbler.Register(id, time)
if err != nil { if err != nil {
@ -31,7 +35,7 @@ func (c *MediaAnnotationController) Scrobble() {
} }
beego.Info(fmt.Sprintf(`Scrobbled (%s) "%s" at %v`, id, mf.Title, time)) beego.Info(fmt.Sprintf(`Scrobbled (%s) "%s" at %v`, id, mf.Title, time))
} else { } else {
mf, err := c.scrobbler.NowPlaying(id) mf, err := c.scrobbler.NowPlaying(id, username, playerName)
if err != nil { if err != nil {
beego.Error("Error setting", id, "as current song:", err) beego.Error("Error setting", id, "as current song:", err)
c.SendError(responses.ERROR_GENERIC, "Internal error") c.SendError(responses.ERROR_GENERIC, "Internal error")

View file

@ -18,8 +18,9 @@ type Subsonic struct {
AlbumList *AlbumList `xml:"albumList,omitempty" json:"albumList,omitempty"` AlbumList *AlbumList `xml:"albumList,omitempty" json:"albumList,omitempty"`
Playlists *Playlists `xml:"playlists,omitempty" json:"playlists,omitempty"` Playlists *Playlists `xml:"playlists,omitempty" json:"playlists,omitempty"`
Playlist *PlaylistWithSongs `xml:"playlist,omitempty" json:"playlist,omitempty"` Playlist *PlaylistWithSongs `xml:"playlist,omitempty" json:"playlist,omitempty"`
SearchResult2 *SearchResult2 `xml:"searchResult2,omitempty" json:"searchResult2,omitempty"` SearchResult2 *SearchResult2 `xml:"searchResult2,omitempty" json:"searchResult2,omitempty"`
Starred *Starred `xml:"starred,omitempty" json:"starred,omitempty"` Starred *Starred `xml:"starred,omitempty" json:"starred,omitempty"`
NowPlaying *NowPlaying `xml:"nowPlaying,omitempty" json:"nowPlaying,omitempty"`
} }
type JsonWrapper struct { type JsonWrapper struct {
@ -138,6 +139,18 @@ type Starred struct {
Song []Child `xml:"song" json:"song,omitempty"` Song []Child `xml:"song" json:"song,omitempty"`
} }
type NowPlayingEntry struct {
Child
UserName string `xml:"username" json:"username,omitempty"`
MinutesAgo int `xml:"minutesAgo" json:"minutesAgo,omitempty"`
PlayerId int `xml:"playerId" json:"playerId,omitempty"`
PlayerName string `xml:"playerName" json:"playerName,omitempty"`
}
type NowPlaying struct {
Entry []NowPlayingEntry `xml:"entry" json:"entry,omitempty"`
}
type User struct { type User struct {
Username string `xml:"username,attr" json:"username"` Username string `xml:"username,attr" json:"username"`
Email string `xml:"email,attr,omitempty" json:"email,omitempty"` Email string `xml:"email,attr,omitempty" json:"email,omitempty"`

View file

@ -32,6 +32,7 @@ func checkParameters(c BaseAPIController) {
logWarn(c, fmt.Sprintf(`Missing required parameter "%s"`, p)) logWarn(c, fmt.Sprintf(`Missing required parameter "%s"`, p))
abortRequest(c, responses.ERROR_MISSING_PARAMETER) abortRequest(c, responses.ERROR_MISSING_PARAMETER)
} }
c.Data[p] = c.GetString(p)
} }
} }

View file

@ -33,6 +33,7 @@ func mapEndpoints() {
beego.NSRouter("/getAlbumList.view", &api.AlbumListController{}, "*:GetAlbumList"), beego.NSRouter("/getAlbumList.view", &api.AlbumListController{}, "*:GetAlbumList"),
beego.NSRouter("/getStarred.view", &api.AlbumListController{}, "*:GetStarred"), beego.NSRouter("/getStarred.view", &api.AlbumListController{}, "*:GetStarred"),
beego.NSRouter("/getNowPlaying.view", &api.AlbumListController{}, "*:GetNowPlaying"),
beego.NSRouter("/getPlaylists.view", &api.PlaylistsController{}, "*:GetAll"), beego.NSRouter("/getPlaylists.view", &api.PlaylistsController{}, "*:GetAll"),
beego.NSRouter("/getPlaylist.view", &api.PlaylistsController{}, "*:Get"), beego.NSRouter("/getPlaylist.view", &api.PlaylistsController{}, "*:Get"),
@ -54,6 +55,7 @@ func mapFilters() {
var ValidateRequest = func(ctx *context.Context) { var ValidateRequest = func(ctx *context.Context) {
c := api.BaseAPIController{} c := api.BaseAPIController{}
c.Ctx = ctx c.Ctx = ctx
c.Data = make(map[interface{}]interface{})
api.Validate(c) api.Validate(c)
} }

View file

@ -24,6 +24,11 @@ type Entry struct {
Suffix string Suffix string
BitRate int BitRate int
ContentType string ContentType string
UserName string
MinutesAgo int
PlayerId int
PlayerName string
} }
type Entries []Entry type Entries []Entry

View file

@ -2,9 +2,12 @@ package engine
import ( import (
"math/rand" "math/rand"
"time"
"github.com/astaxie/beego"
"github.com/deluan/gosonic/domain" "github.com/deluan/gosonic/domain"
"github.com/deluan/gosonic/utils" "github.com/deluan/gosonic/utils"
"github.com/syndtr/goleveldb/leveldb/errors"
) )
// TODO Use Entries instead of Albums // TODO Use Entries instead of Albums
@ -15,14 +18,17 @@ type ListGenerator interface {
GetHighest(offset int, size int) (*domain.Albums, error) GetHighest(offset int, size int) (*domain.Albums, error)
GetRandom(offset int, size int) (*domain.Albums, error) GetRandom(offset int, size int) (*domain.Albums, error)
GetStarred() (*Entries, error) GetStarred() (*Entries, error)
GetNowPlaying() (*Entries, error)
} }
func NewListGenerator(alr domain.AlbumRepository) ListGenerator { func NewListGenerator(alr domain.AlbumRepository, mfr domain.MediaFileRepository, npr NowPlayingRepository) ListGenerator {
return listGenerator{alr} return listGenerator{alr, mfr, npr}
} }
type listGenerator struct { type listGenerator struct {
albumRepo domain.AlbumRepository albumRepo domain.AlbumRepository
mfRepository domain.MediaFileRepository
npRepo NowPlayingRepository
} }
func (g listGenerator) query(qo domain.QueryOptions, offset int, size int) (*domain.Albums, error) { func (g listGenerator) query(qo domain.QueryOptions, offset int, size int) (*domain.Albums, error) {
@ -84,3 +90,24 @@ func (g listGenerator) GetStarred() (*Entries, error) {
return &entries, nil return &entries, nil
} }
func (g listGenerator) GetNowPlaying() (*Entries, error) {
npInfo, err := g.npRepo.GetAll()
if err != nil {
return nil, err
}
entries := make(Entries, len(*npInfo))
for i, np := range *npInfo {
mf, err := g.mfRepository.Get(np.TrackId)
if err != nil {
return nil, err
}
entries[i] = FromMediaFile(mf)
entries[i].UserName = beego.AppConfig.String("user")
entries[i].MinutesAgo = int(time.Now().Sub(np.Start).Minutes())
entries[i].PlayerId = np.PlayerId
entries[i].PlayerName = np.PlayerName
}
return &entries, errors.New("Not implemented")
}

View file

@ -11,24 +11,26 @@ func CreateMockNowPlayingRepo() *MockNowPlaying {
type MockNowPlaying struct { type MockNowPlaying struct {
NowPlayingRepository NowPlayingRepository
id string info NowPlayingInfo
start time.Time err bool
err bool
} }
func (m *MockNowPlaying) SetError(err bool) { func (m *MockNowPlaying) SetError(err bool) {
m.err = err m.err = err
} }
func (m *MockNowPlaying) Set(id string) error { func (m *MockNowPlaying) Set(id, username string, playerId int, playerName string) error {
if m.err { if m.err {
return errors.New("Error!") return errors.New("Error!")
} }
m.id = id m.info.TrackId = id
m.start = time.Now() m.info.Username = username
m.info.Start = time.Now()
m.info.PlayerId = playerId
m.info.PlayerName = playerName
return nil return nil
} }
func (m *MockNowPlaying) Current() (string, time.Time) { func (m *MockNowPlaying) Current() NowPlayingInfo {
return m.id, m.start return m.info
} }

View file

@ -5,10 +5,14 @@ import "time"
const NowPlayingExpire = time.Duration(30) * time.Minute const NowPlayingExpire = time.Duration(30) * time.Minute
type NowPlayingInfo struct { type NowPlayingInfo struct {
TrackId string TrackId string
Start time.Time Start time.Time
Username string
PlayerId int
PlayerName string
} }
type NowPlayingRepository interface { type NowPlayingRepository interface {
Set(trackId string) error Set(trackId, username string, playerId int, playerName string) error
GetAll() (*[]NowPlayingInfo, error)
} }

View file

@ -10,8 +10,8 @@ import (
) )
type Scrobbler interface { type Scrobbler interface {
Register(id string, playDate time.Time) (*domain.MediaFile, error) Register(trackId string, playDate time.Time) (*domain.MediaFile, error)
NowPlaying(id string) (*domain.MediaFile, error) NowPlaying(trackId, username string, playerName string) (*domain.MediaFile, error)
} }
func NewScrobbler(itunes itunesbridge.ItunesControl, mr domain.MediaFileRepository, npr NowPlayingRepository) Scrobbler { func NewScrobbler(itunes itunesbridge.ItunesControl, mr domain.MediaFileRepository, npr NowPlayingRepository) Scrobbler {
@ -40,15 +40,15 @@ func (s *scrobbler) Register(id string, playDate time.Time) (*domain.MediaFile,
return mf, nil return mf, nil
} }
func (s *scrobbler) NowPlaying(id string) (*domain.MediaFile, error) { func (s *scrobbler) NowPlaying(trackId, username string, playerName string) (*domain.MediaFile, error) {
mf, err := s.mfRepo.Get(id) mf, err := s.mfRepo.Get(trackId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if mf == nil { if mf == nil {
return nil, errors.New(fmt.Sprintf(`Id "%s" not found`, id)) return nil, errors.New(fmt.Sprintf(`Id "%s" not found`, trackId))
} }
return mf, s.npRepo.Set(id) return mf, s.npRepo.Set(trackId, username, 1, playerName)
} }

View file

@ -54,7 +54,7 @@ func TestScrobbler(t *testing.T) {
}) })
Convey("When I inform the song that is now playing", func() { Convey("When I inform the song that is now playing", func() {
mf, err := scrobbler.NowPlaying("2") mf, err := scrobbler.NowPlaying("2", "deluan", "DSub")
Convey("Then I get the song for that id back", func() { Convey("Then I get the song for that id back", func() {
So(err, ShouldBeNil) So(err, ShouldBeNil)
@ -62,9 +62,11 @@ func TestScrobbler(t *testing.T) {
}) })
Convey("And it saves the song as the one current playing", func() { Convey("And it saves the song as the one current playing", func() {
id, start := npRepo.Current() info := npRepo.Current()
So(id, ShouldEqual, "2") So(info.TrackId, ShouldEqual, "2")
So(start, ShouldHappenBefore, time.Now()) So(info.Start, ShouldHappenBefore, time.Now())
So(info.Username, ShouldEqual, "deluan")
So(info.PlayerName, ShouldEqual, "DSub")
}) })
Convey("And iTunes is not notified", func() { Convey("And iTunes is not notified", func() {

View file

@ -22,11 +22,11 @@ func NewNowPlayingRepository() engine.NowPlayingRepository {
return r return r
} }
func (r *nowPlayingRepository) Set(id string) error { func (r *nowPlayingRepository) Set(id, username string, playerId int, playerName string) error {
if id == "" { if id == "" {
return errors.New("Id is required") return errors.New("Id is required")
} }
m := &engine.NowPlayingInfo{TrackId: id, Start: time.Now()} m := &engine.NowPlayingInfo{TrackId: id, Username: username, Start: time.Now(), PlayerId: playerId, PlayerName: playerName}
h, err := json.Marshal(m) h, err := json.Marshal(m)
if err != nil { if err != nil {
@ -35,4 +35,15 @@ func (r *nowPlayingRepository) Set(id string) error {
return Db().SetEX(nowPlayingKeyName, int64(engine.NowPlayingExpire.Seconds()), []byte(h)) return Db().SetEX(nowPlayingKeyName, int64(engine.NowPlayingExpire.Seconds()), []byte(h))
} }
func (r *nowPlayingRepository) GetAll() (*[]engine.NowPlayingInfo, error) {
val, err := Db().Get(nowPlayingKeyName)
if err != nil {
return nil, err
}
info := &engine.NowPlayingInfo{}
err = json.Unmarshal(val, info)
return &[]engine.NowPlayingInfo{*info}, err
}
var _ engine.NowPlayingRepository = (*nowPlayingRepository)(nil) var _ engine.NowPlayingRepository = (*nowPlayingRepository)(nil)