diff --git a/api/get_music_directory.go b/api/get_music_directory.go index 8bdb875b3..60ee855ff 100644 --- a/api/get_music_directory.go +++ b/api/get_music_directory.go @@ -11,10 +11,12 @@ import ( type GetMusicDirectoryController struct { BaseAPIController artistRepo domain.ArtistRepository + albumRepo domain.AlbumRepository } func (c *GetMusicDirectoryController) Prepare() { inject.ExtractAssignable(utils.Graph, &c.artistRepo) + inject.ExtractAssignable(utils.Graph, &c.albumRepo) } func (c *GetMusicDirectoryController) Get() { @@ -32,10 +34,19 @@ func (c *GetMusicDirectoryController) Get() { dir := &responses.Directory{} if found { - a, _:= c.retrieveArtist(id) + a, albums := c.retrieveArtist(id) dir.Id = a.Id dir.Name = a.Name + dir.Child = make([]responses.Child, len(albums)) + for i, al := range albums { + dir.Child[i].Id = al.Id + dir.Child[i].Title = al.Name + dir.Child[i].IsDir = true + dir.Child[i].Album = al.Name + dir.Child[i].Year = al.Year + dir.Child[i].Artist = a.Name + } } else { beego.Info("Artist", id, "not found") c.SendError(responses.ERROR_DATA_NOT_FOUND, "Directory not found") @@ -54,6 +65,9 @@ func (c *GetMusicDirectoryController) retrieveArtist(id string) (a *domain.Artis c.SendError(responses.ERROR_GENERIC, "Internal Error") } - as = make([]domain.Album, 0) + if as, err = c.albumRepo.FindByArtist(id); err != nil { + beego.Error("Error reading Album from DB", err) + c.SendError(responses.ERROR_GENERIC, "Internal Error") + } return } \ No newline at end of file diff --git a/api/get_music_directory_test.go b/api/get_music_directory_test.go index cfdc593aa..90b4054f3 100644 --- a/api/get_music_directory_test.go +++ b/api/get_music_directory_test.go @@ -18,9 +18,10 @@ func TestGetMusicDirectory(t *testing.T) { utils.DefineSingleton(new(domain.ArtistRepository), func() domain.ArtistRepository { return mockArtistRepo }) - - mockArtistRepo.SetData("[]", 0) - mockArtistRepo.SetError(false) + mockAlbumRepo := mocks.CreateMockAlbumRepo() + utils.DefineSingleton(new(domain.AlbumRepository), func() domain.AlbumRepository { + return mockAlbumRepo + }) Convey("Subject: GetMusicDirectory Endpoint", t, func() { Convey("Should fail if missing Id parameter", func() { @@ -42,15 +43,27 @@ func TestGetMusicDirectory(t *testing.T) { So(w.Body, ShouldReceiveError, responses.ERROR_DATA_NOT_FOUND) }) - Convey("When id matches an artist without albums", func() { + Convey("When id matches an artist", func() { mockArtistRepo.SetData(`[{"Id":"1","Name":"The KLF"}]`, 1) - _, w := Get(AddParams("/rest/getMusicDirectory.view", "id=1"), "TestGetMusicDirectory") - So(w.Body, ShouldContainJSON, `"id":"1","name":"The KLF"`) + Convey("Without albums", func() { + _, w := Get(AddParams("/rest/getMusicDirectory.view", "id=1"), "TestGetMusicDirectory") + + So(w.Body, ShouldContainJSON, `"id":"1","name":"The KLF"`) + }) + Convey("With albums", func() { + mockAlbumRepo.SetData(`[{"Id":"A","Name":"Tardis","ArtistId":"1"}]`, 1) + _, w := Get(AddParams("/rest/getMusicDirectory.view", "id=1"), "TestGetMusicDirectory") + + So(w.Body, ShouldContainJSON, `"child":[{"album":"Tardis","artist":"The KLF","id":"A","isDir":true,"title":"Tardis"}]`) + }) }) Reset(func() { mockArtistRepo.SetData("[]", 0) mockArtistRepo.SetError(false) + + mockAlbumRepo.SetData("[]", 0) + mockAlbumRepo.SetError(false) }) }) } \ No newline at end of file diff --git a/api/responses/responses.go b/api/responses/responses.go index d361486b8..ae2522ddd 100644 --- a/api/responses/responses.go +++ b/api/responses/responses.go @@ -19,7 +19,7 @@ type JsonWrapper struct { type Error struct { Code int `xml:"code,attr" json:"code"` - Message string `xml:"message,attr" json:"message"` + Message string `xml:"message,attr" json: "message"` } type License struct { @@ -52,22 +52,22 @@ type Indexes struct { } type Child struct { - Id string `xml:"id,attr" json:"id"` - IsDir bool `xml:"isDir,attr" json:"isDir"` - Title string `xml:"title,attr" json:"title"` - Album string `xml:"album,attr" json:"album"` - Artist string `xml:"artist,attr" json:"artist"` - Track int `xml:"track,attr" json:"track"` - Year int `xml:"year,attr" json:"year"` - Genre string `xml:"genre,attr" json:"genre"` - CoverArt string `xml:"coverArt,attr" json:"coverArt"` - Size string `xml:"size,attr" json:"size"` - ContentType string `xml:"contentType,attr" json:"contentType"` - Suffix string `xml:"suffix,attr" json:"suffix"` - TranscodedContentType string `xml:"transcodedContentType,attr" json:"transcodedContentType"` - TranscodedSuffix string `xml:"transcodedSuffix,attr" json:"transcodedSuffix"` - Duration int `xml:"duration,attr" json:"duration"` - BitRate int `xml:"bitRate,attr" json:"bitRate"` + Id string `xml:"id,attr" json:"id"` + IsDir bool `xml:"isDir,attr" json:"isDir"` + Title string `xml:"title,attr" json:"title"` + Album string `xml:"album,attr,omitempty" json:"album,omitempty"` + Artist string `xml:"artist,attr,omitempty" json:"artist,omitempty"` + Track int `xml:"track,attr,omitempty" json:"track,omitempty"` + Year int `xml:"year,attr,omitempty" json:"year,omitempty"` + Genre string `xml:"genre,attr,omitempty" json:"genre,omitempty"` + CoverArt string `xml:"coverArt,attr,omitempty" json:"coverArt,omitempty"` + Size string `xml:"size,attr,omitempty" json:"size,omitempty"` + ContentType string `xml:"contentType,attr,omitempty" json:"contentType,omitempty"` + Suffix string `xml:"suffix,attr,omitempty" json:"suffix,omitempty"` + TranscodedContentType string `xml:"transcodedContentType,attr,omitempty" json:"transcodedContentType,omitempty"` + TranscodedSuffix string `xml:"transcodedSuffix,attr,omitempty" json:"transcodedSuffix,omitempty"` + Duration int `xml:"duration,attr,omitempty" json:"duration,omitempty"` + BitRate int `xml:"bitRate,attr,omitempty" json:"bitRate,omitempty"` } type Directory struct { diff --git a/api/responses/responses_test.go b/api/responses/responses_test.go index e16095e59..344f13132 100644 --- a/api/responses/responses_test.go +++ b/api/responses/responses_test.go @@ -85,7 +85,26 @@ func TestSubsonicResponses(t *testing.T) { Convey("Directory", func() { response.Directory = &Directory{Id: "1", Name: "N"} - Convey("With data", func() { + Convey("Without data", func() { + Convey("XML", func() { + So(response, ShouldMatchXML, ``) + }) + Convey("JSON", func() { + So(response, ShouldMatchJSON, `{"directory":{"id":"1","name":"N"},"status":"ok","version":"1.0.0"}`) + }) + }) + Convey("With just required data", func() { + child := make([]Child, 1) + child[0] = Child{ Id:"1", Title: "title", IsDir: false } + response.Directory.Child = child + Convey("XML", func() { + So(response, ShouldMatchXML, ``) + }) + Convey("JSON", func() { + So(response, ShouldMatchJSON, `{"directory":{"child":[{"id":"1","isDir":false,"title":"title"}],"id":"1","name":"N"},"status":"ok","version":"1.0.0"}`) + }) + }) + Convey("With all data", func() { child := make([]Child, 1) child[0] = Child{ Id:"1", IsDir: true, Title: "title", Album: "album", Artist: "artist", Track: 1, @@ -101,14 +120,6 @@ func TestSubsonicResponses(t *testing.T) { So(response, ShouldMatchJSON, `{"directory":{"child":[{"album":"album","artist":"artist","bitRate":320,"contentType":"audio/flac","coverArt":"1","duration":146,"genre":"Rock","id":"1","isDir":true,"size":"8421341","suffix":"flac","title":"title","track":1,"transcodedContentType":"audio/mpeg","transcodedSuffix":"mp3","year":1985}],"id":"1","name":"N"},"status":"ok","version":"1.0.0"}`) }) }) - Convey("Without data", func() { - Convey("XML", func() { - So(response, ShouldMatchXML, ``) - }) - Convey("JSON", func() { - So(response, ShouldMatchJSON, `{"directory":{"id":"1","name":"N"},"status":"ok","version":"1.0.0"}`) - }) - }) }) Reset(func() { diff --git a/conf/inject_definitions.go b/conf/inject_definitions.go index ecd612848..22a27e2e7 100644 --- a/conf/inject_definitions.go +++ b/conf/inject_definitions.go @@ -11,4 +11,5 @@ func init() { utils.DefineSingleton(new(domain.PropertyRepository), persistence.NewPropertyRepository) utils.DefineSingleton(new(domain.MediaFolderRepository), persistence.NewMediaFolderRepository) utils.DefineSingleton(new(domain.ArtistRepository), persistence.NewArtistRepository) + utils.DefineSingleton(new(domain.AlbumRepository), persistence.NewAlbumRepository) } diff --git a/domain/album.go b/domain/album.go index e7d9c9e91..7a424d4a2 100644 --- a/domain/album.go +++ b/domain/album.go @@ -14,4 +14,5 @@ type AlbumRepository interface { BaseRepository Put(m *Album) error Get(id string) (*Album, error) + FindByArtist(artistId string) ([]Album, error) } diff --git a/persistence/album_repository.go b/persistence/album_repository.go index d80a195a7..277e3ab73 100644 --- a/persistence/album_repository.go +++ b/persistence/album_repository.go @@ -26,3 +26,9 @@ func (r *albumRepository) Get(id string) (*domain.Album, error) { rec, err := r.readEntity(id) return rec.(*domain.Album), err } + +func (r *albumRepository) FindByArtist(artistId string) ([]domain.Album, error) { + var as = make([]domain.Album, 0) + err := r.loadChildren("artist", artistId, &as, "") + return as, err +} diff --git a/persistence/base_repository.go b/persistence/base_repository.go index 3585a67ad..d051396ab 100644 --- a/persistence/base_repository.go +++ b/persistence/base_repository.go @@ -127,25 +127,29 @@ func (r *baseRepository) toEntity(response [][]byte, entity interface{}) error { return utils.ToStruct(record, entity) } -// TODO Optimize it! Probably very slow (and confusing!) func (r *baseRepository) loadAll(entities interface{}, sortBy string) error { - total, err := r.CountAll() - if err != nil { - return err - } + setName := r.table + "s:all" + return r.loadFromSet(setName, entities, sortBy) +} +func (r* baseRepository) loadChildren(parentTable string, parentId string, entities interface{}, sortBy string) error { + setName := fmt.Sprintf("%s:%s:%ss", parentTable, parentId, r.table) + return r.loadFromSet(setName, entities, sortBy) +} + +// TODO Optimize it! Probably very slow (and confusing!) +func (r *baseRepository) loadFromSet(setName string, entities interface{}, sortBy string) error { reflected := reflect.ValueOf(entities).Elem() var sortKey []byte = nil if sortBy != "" { sortKey = []byte(fmt.Sprintf("%s:*:%s", r.table, sortBy)) } - setName := r.table + "s:all" response, err := db().XSSort([]byte(setName), 0, 0, true, false, sortKey, r.getFieldKeys("*")) if err != nil { return err } numFields := len(r.fieldNames) - for i := 0; i < total; i++ { + for i := 0; i < (len(response) / numFields); i++ { start := i * numFields entity := reflect.New(r.entityType).Interface() @@ -156,4 +160,5 @@ func (r *baseRepository) loadAll(entities interface{}, sortBy string) error { } return nil -} + +} \ No newline at end of file diff --git a/tests/mocks/mock_album_repo.go b/tests/mocks/mock_album_repo.go new file mode 100644 index 000000000..5b1db7dc9 --- /dev/null +++ b/tests/mocks/mock_album_repo.go @@ -0,0 +1,65 @@ +package mocks + +import ( + "encoding/json" + "fmt" + "github.com/deluan/gosonic/domain" +"errors" +) + +func CreateMockAlbumRepo() *MockAlbum { + return &MockAlbum{} +} + +type MockAlbum struct { + domain.AlbumRepository + data map[string]*domain.Album + err bool +} + +func (m *MockAlbum) SetError(err bool) { + m.err = err +} + +func (m *MockAlbum) SetData(j string, size int) { + m.data = make(map[string]*domain.Album) + var l = make([]domain.Album, size) + err := json.Unmarshal([]byte(j), &l) + if err != nil { + fmt.Println("ERROR: ", err) + } + for _, a := range l { + m.data[a.Id] = &a + } +} + +func (m *MockAlbum) Exists(id string) (bool, error) { + if m.err { + return false, errors.New("Error!") + } + _, found := m.data[id]; + return found, nil +} + +func (m *MockAlbum) Get(id string) (*domain.Album, error) { + if m.err { + return nil, errors.New("Error!") + } + return m.data[id], nil +} + +func (m *MockAlbum) FindByArtist(artistId string) ([]domain.Album, error) { + if m.err { + return nil, errors.New("Error!") + } + var res = make([]domain.Album, len(m.data)) + i := 0 + for _, a := range m.data { + if a.ArtistId == artistId { + res[i] = *a + i++ + } + } + + return res, nil +} \ No newline at end of file