diff --git a/api/get_indexes.go b/api/get_indexes.go index 1a5b59c76..39bb1d39c 100644 --- a/api/get_indexes.go +++ b/api/get_indexes.go @@ -6,15 +6,18 @@ import ( "github.com/deluan/gosonic/utils" "github.com/karlkfi/inject" "github.com/deluan/gosonic/api/responses" + "github.com/deluan/gosonic/consts" ) type GetIndexesController struct { beego.Controller repo repositories.ArtistIndex + properties repositories.Property } func (c *GetIndexesController) Prepare() { inject.ExtractAssignable(utils.Graph, &c.repo) + inject.ExtractAssignable(utils.Graph, &c.properties) } func (c *GetIndexesController) Get() { @@ -23,7 +26,15 @@ func (c *GetIndexesController) Get() { beego.Error("Error retrieving Indexes:", err) c.CustomAbort(200, string(responses.NewError(responses.ERROR_GENERIC, "Internal Error"))) } - res := &responses.ArtistIndex{IgnoredArticles: beego.AppConfig.String("ignoredArticles")} + res := &responses.ArtistIndex{} + + res.LastModified, err = c.properties.Get(consts.LastScan) + if err != nil { + beego.Error("Error retrieving LastScan property:", err) + c.CustomAbort(200, string(responses.NewError(responses.ERROR_GENERIC, "Internal Error"))) + } + + res.IgnoredArticles = beego.AppConfig.String("ignoredArticles") res.Index = make([]responses.IdxIndex, len(indexes)) for i, idx := range indexes { res.Index[i].Name = idx.Id diff --git a/api/get_indexes_test.go b/api/get_indexes_test.go index 4693c6179..e391366f4 100644 --- a/api/get_indexes_test.go +++ b/api/get_indexes_test.go @@ -6,25 +6,39 @@ import ( "github.com/deluan/gosonic/utils" . "github.com/smartystreets/goconvey/convey" "github.com/deluan/gosonic/tests" - "github.com/deluan/gosonic/models" "github.com/deluan/gosonic/repositories" "encoding/xml" - "encoding/json" - "fmt" - "errors" "github.com/deluan/gosonic/api/responses" + "github.com/deluan/gosonic/consts" + "github.com/deluan/gosonic/tests/mocks" +) + +const ( + emptyResponse = `` ) func TestGetIndexes(t *testing.T) { tests.Init(t, false) - mockRepo := &mockArtistIndex{} + mockRepo := mocks.CreateMockArtistIndexRepo() utils.DefineSingleton(new(repositories.ArtistIndex), func() repositories.ArtistIndex { return mockRepo }) + propRepo := mocks.CreateMockPropertyRepo() + utils.DefineSingleton(new(repositories.Property), func() repositories.Property { + return propRepo + }) Convey("Subject: GetIndexes Endpoint", t, func() { - Convey("Return fail on DB error", func() { - mockRepo.err = true + Convey("Return fail on Index Table error", func() { + mockRepo.SetError(true) + _, w := Get(AddParams("/rest/getIndexes.view"), "TestGetIndexes") + + v := responses.Subsonic{} + xml.Unmarshal(w.Body.Bytes(), &v) + So(v.Status, ShouldEqual, "fail") + }) + Convey("Return fail on Property Table error", func() { + propRepo.SetError(true) _, w := Get(AddParams("/rest/getIndexes.view"), "TestGetIndexes") v := responses.Subsonic{} @@ -43,45 +57,26 @@ func TestGetIndexes(t *testing.T) { So(err, ShouldBeNil) }) Convey("Then it should return an empty collection", func() { - So(w.Body.String(), ShouldContainSubstring, ``) + So(w.Body.String(), ShouldContainSubstring, emptyResponse) }) }) Convey("When the index is not empty", func() { - mockRepo.data = makeMockData(`[{"Id": "A","Artists": [ + mockRepo.SetData(`[{"Id": "A","Artists": [ {"ArtistId": "21", "Artist": "Afrolicious"} ]}]`, 2) - _, w := Get(AddParams("/rest/getIndexes.view"), "TestGetIndexes") Convey("Then it should return the the items in the response", func() { + _, w := Get(AddParams("/rest/getIndexes.view"), "TestGetIndexes") + So(w.Body.String(), ShouldContainSubstring, - ``) + ``) }) }) Reset(func() { - mockRepo.data = make([]models.ArtistIndex, 0) - mockRepo.err = false + mockRepo.SetData("[]", 0) + mockRepo.SetError(false) + propRepo.Put(consts.LastScan, "1") + propRepo.SetError(false) }) }) -} - -func makeMockData(j string, length int) []models.ArtistIndex { - data := make([]models.ArtistIndex, length) - err := json.Unmarshal([]byte(j), &data) - if err != nil { - fmt.Println("ERROR: ", err) - } - return data -} - -type mockArtistIndex struct { - repositories.ArtistIndexImpl - data []models.ArtistIndex - err bool -} - -func (m *mockArtistIndex) GetAll() ([]models.ArtistIndex, error) { - if m.err { - return nil, errors.New("Error!") - } - return m.data, nil } \ No newline at end of file diff --git a/api/responses/indexes.go b/api/responses/indexes.go index c8e793a1c..bfefa8ab6 100644 --- a/api/responses/indexes.go +++ b/api/responses/indexes.go @@ -17,6 +17,7 @@ type IdxIndex struct { type ArtistIndex struct { XMLName xml.Name `xml:"indexes"` Index []IdxIndex `xml:"indexes"` + LastModified string `xml:"lastModified,attr"` IgnoredArticles string `xml:"ignoredArticles,attr"` } diff --git a/conf/inject_definitions.go b/conf/inject_definitions.go index eff8e885d..e06db431e 100644 --- a/conf/inject_definitions.go +++ b/conf/inject_definitions.go @@ -7,4 +7,5 @@ import ( func init () { utils.DefineSingleton(new(repositories.ArtistIndex), repositories.NewArtistIndexRepository) + utils.DefineSingleton(new(repositories.Property), repositories.NewPropertyRepository) } diff --git a/consts/properties.go b/consts/properties.go new file mode 100644 index 000000000..37f0baebe --- /dev/null +++ b/consts/properties.go @@ -0,0 +1,5 @@ +package consts + +const ( + LastScan = "LastScan" +) diff --git a/models/property.go b/models/property.go new file mode 100644 index 000000000..f460eefad --- /dev/null +++ b/models/property.go @@ -0,0 +1,6 @@ +package models + +type Property struct { + Id string + Value string +} diff --git a/repositories/property_repository.go b/repositories/property_repository.go new file mode 100644 index 000000000..85d6235b3 --- /dev/null +++ b/repositories/property_repository.go @@ -0,0 +1,35 @@ +package repositories + +import ( + "github.com/deluan/gosonic/models" +"errors" +) + +type Property interface { + Put(id string, value string) error + Get(id string) (string, error) +} + +type PropertyImpl struct { + BaseRepository +} + +func NewPropertyRepository() *PropertyImpl { + r := &PropertyImpl{} + r.init("property", &models.Property{}) + return r +} + +func (r *PropertyImpl) Put(id string, value string) error { + m := &models.Property{Id: id, Value: value} + if m.Id == "" { + return errors.New("Id is required") + } + return r.saveOrUpdate(m.Id, m) +} + +func (r *PropertyImpl) Get(id string) (string, error) { + var rec interface{} + rec, err := r.readEntity(id) + return rec.(*models.Property).Value, err +} diff --git a/scanner/scanner.go b/scanner/scanner.go index e0c6115aa..79a2b6e81 100644 --- a/scanner/scanner.go +++ b/scanner/scanner.go @@ -6,6 +6,9 @@ import ( "github.com/deluan/gosonic/models" "strings" "github.com/deluan/gosonic/utils" + "github.com/deluan/gosonic/consts" + "time" + "fmt" ) type Scanner interface { @@ -26,7 +29,7 @@ func doImport(mediaFolder string, scanner Scanner) { beego.Info("Finished importing", len(files), "files") } -func importLibrary(files []Track) { +func importLibrary(files []Track) (err error){ mfRepo := repositories.NewMediaFileRepository() albumRepo := repositories.NewAlbumRepository() artistRepo := repositories.NewArtistRepository() @@ -38,7 +41,7 @@ func importLibrary(files []Track) { collectIndex(artist, artistIndex) } - if err := saveIndex(artistIndex); err != nil { + if err = saveIndex(artistIndex); err != nil { beego.Error(err) } @@ -48,6 +51,15 @@ func importLibrary(files []Track) { beego.Info("Total Albums in database:", c) c, _ = mfRepo.CountAll() beego.Info("Total MediaFiles in database:", c) + + if err == nil { + propertyRepo := repositories.NewPropertyRepository() + millis := time.Now().UnixNano() / 1000000 + propertyRepo.Put(consts.LastScan, fmt.Sprint(millis)) + beego.Info("LastScan timestamp:", millis) + } + + return err } func parseTrack(t *Track) (*models.MediaFile, *models.Album, *models.Artist) { diff --git a/tests/mocks/mock_index_repo_tests.go b/tests/mocks/mock_index_repo_tests.go new file mode 100644 index 000000000..4256205ee --- /dev/null +++ b/tests/mocks/mock_index_repo_tests.go @@ -0,0 +1,38 @@ +package mocks + +import ( + "github.com/deluan/gosonic/models" + "fmt" + "encoding/json" + "github.com/deluan/gosonic/repositories" + "errors" +) + +func CreateMockArtistIndexRepo() *MockArtistIndex { + return &MockArtistIndex{} +} + +type MockArtistIndex struct { + repositories.ArtistIndexImpl + data []models.ArtistIndex + err bool +} + +func (m *MockArtistIndex) SetError(err bool) { + m.err = err +} + +func (m *MockArtistIndex) SetData(j string, length int) { + m.data = make([]models.ArtistIndex, length) + err := json.Unmarshal([]byte(j), &m.data) + if err != nil { + fmt.Println("ERROR: ", err) + } +} + +func (m *MockArtistIndex) GetAll() ([]models.ArtistIndex, error) { + if m.err { + return nil, errors.New("Error!") + } + return m.data, nil +} diff --git a/tests/mocks/mock_property_repo_tests.go b/tests/mocks/mock_property_repo_tests.go new file mode 100644 index 000000000..5946a3528 --- /dev/null +++ b/tests/mocks/mock_property_repo_tests.go @@ -0,0 +1,36 @@ +package mocks + +import ( + "github.com/deluan/gosonic/repositories" + "errors" +) + +func CreateMockPropertyRepo() *MockProperty { + return &MockProperty{data: make(map[string]string)} +} + +type MockProperty struct { + repositories.PropertyImpl + data map[string]string + err bool +} + +func (m *MockProperty) SetError(err bool) { + m.err = err +} + +func (m *MockProperty) Put(id string, value string) error { + if (m.err) { + return errors.New("Error!") + } + m.data[id] = value + return nil +} + +func (m *MockProperty) Get(id string) (string, error) { + if (m.err) { + return "", errors.New("Error!") + } else { + return m.data[id], nil + } +} \ No newline at end of file