diff --git a/.gopmfile b/.gopmfile index 9c0379330..229c4850c 100644 --- a/.gopmfile +++ b/.gopmfile @@ -4,7 +4,7 @@ path = github.com/deluan/gosonic [deps] github.com/astaxie/beego = commit:92d0b9a github.com/dhowden/itl = commit:35d15a3 -github.com/HouzuoGuo/tiedot = tag:3.2 +github.com/siddontang/ledisdb = commit:713b229 github.com/smartystreets/goconvey = commit:899ed5a [res] diff --git a/repositories/base_repository.go b/repositories/base_repository.go index 362c7f8ba..7bcb2099f 100644 --- a/repositories/base_repository.go +++ b/repositories/base_repository.go @@ -1,78 +1,15 @@ package repositories -import ( - "encoding/json" - "github.com/HouzuoGuo/tiedot/db" - "github.com/astaxie/beego" - "fmt" -) - type BaseRepository struct { - col *db.Col + key string } -func (r *BaseRepository) marshal(rec interface{}) (map[string]interface{}, error) { - // Convert to JSON... - b, err := json.Marshal(rec); - if err != nil { - return nil, err - } - // ... then convert to map - var m map[string]interface{} - err = json.Unmarshal(b, &m) - return m, err -} - -func (r*BaseRepository) query(q string, a ...interface{}) (map[int]struct{}, error) { - q = fmt.Sprintf(q, a) - - var query interface{} - json.Unmarshal([]byte(q), &query) - - queryResult := make(map[int]struct{}) - - err := db.EvalQuery(query, r.col, &queryResult) - if err != nil { - beego.Warn("Error '%s' - query='%s'", q, err) - } - return queryResult, err -} - -func (r*BaseRepository) queryFirstKey(q string, a ...interface{}) (int, error) { - result, err := r.query(q, a) - if err != nil { - return 0, err - } - for key, _ := range result { - return key, nil - } - - return 0, nil -} - -func (r *BaseRepository) saveOrUpdate(rec interface{}) error { - m, err := r.marshal(rec) - if err != nil { - return err - } - docId, err := r.queryFirstKey(`{"in": ["Id"], "eq": "%s", "limit": 1}`, m["Id"]) - if docId == 0 { - _, err = r.col.Insert(m) - return err - } - err = r.col.Update(docId, m) - if err != nil { - beego.Warn("Error updating %s[%d]: %s", r.col, docId, err) - } - return err +func (r *BaseRepository) saveOrUpdate(id string, rec interface{}) error { + return hmset(r.key + "_id_" + id, rec) } func (r *BaseRepository) Dump() { - r.col.ForEachDoc(func(id int, docContent []byte) (willMoveOn bool) { - beego.Debug("Document", id, "=", string(docContent)) - return true - }) } diff --git a/repositories/init_database.go b/repositories/init_database.go deleted file mode 100644 index 06064ba4c..000000000 --- a/repositories/init_database.go +++ /dev/null @@ -1,55 +0,0 @@ -package repositories - -import ( - "github.com/HouzuoGuo/tiedot/db" - "github.com/astaxie/beego" - "sync" -) - -var ( - _dbInstance *db.DB - once sync.Once -) - -func createCollection(name string, ix ...interface{}) *db.Col { - log := false - if dbInstance().Use(name) == nil { - if err := dbInstance().Create(name); err != nil { - beego.Error(err) - } - log = true - } - - col := dbInstance().Use(name) - - createIndex(col, []string{"Id"}, log) - for _, i := range ix { - switch i := i.(type) { - case string: - createIndex(col, []string{i}, log) - case []string: - createIndex(col, i, log) - default: - beego.Error("Trying to create an Index with an invalid type: ", i) - } - } - return col -} - -func createIndex(col *db.Col, path []string, log bool) (err error) { - if err := col.Index(path); err != nil && log { - beego.Error(path, err) - } - return err -} - -func dbInstance() *db.DB { - once.Do(func() { - instance, err := db.OpenDB(beego.AppConfig.String("dbPath")) - if err != nil { - panic(err) - } - _dbInstance = instance - }) - return _dbInstance -} \ No newline at end of file diff --git a/repositories/init_database_test.go b/repositories/init_database_test.go deleted file mode 100644 index 0d89ced18..000000000 --- a/repositories/init_database_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package repositories - -import ( - . "github.com/smartystreets/goconvey/convey" - _ "github.com/deluan/gosonic/tests" - "testing" - "github.com/deluan/gosonic/tests" -) - -const ( - testCollectionName = "TestCollection" -) - -func TestCreateCollection(t *testing.T) { - tests.Init(t, true) - - Convey("Given an empty DB", t, func() { - - Convey("When creating a new collection", func() { - newCol := createCollection(testCollectionName) - - Convey("Then it should create the collection", func() { - So(dbInstance().Use(testCollectionName), ShouldNotBeNil) - }) - Convey("And it should create a default index on Id", func() { - allIndexes := newCol.AllIndexes() - So(len(allIndexes), ShouldEqual, 1) - So(len(allIndexes[0]), ShouldEqual, 1) - So(allIndexes[0], ShouldContain, "Id") - }) - }) - - Convey("When creating a new collection with a 'Name' index", func() { - newCol := createCollection(testCollectionName, "Name") - - Convey("Then it should create a default index [Id] and an index on 'Name'", func() { - listOfIndexes := newCol.AllIndexes() - - So(len(listOfIndexes), ShouldEqual, 2) - - var allPaths = make(map[string]bool) - for _, i := range listOfIndexes { - allPaths[i[0]] = true - } - - So(allPaths, ShouldContainKey, "Id") - So(allPaths, ShouldContainKey, "Name") - }) - }) - - Reset(func() { - dbInstance().Drop(testCollectionName) - }) - }) - -} \ No newline at end of file diff --git a/repositories/ledis_database.go b/repositories/ledis_database.go new file mode 100644 index 000000000..8fcf95cf7 --- /dev/null +++ b/repositories/ledis_database.go @@ -0,0 +1,49 @@ +package repositories + +import ( + "sync" + "encoding/json" + "github.com/astaxie/beego" + "github.com/siddontang/ledisdb/ledis" + "github.com/siddontang/ledisdb/config" + "github.com/deluan/gosonic/utils" +) + +var ( + _dbInstance *ledis.DB + once sync.Once +) + +func db() *ledis.DB { + once.Do(func() { + config := config.NewConfigDefault() + config.DataDir = beego.AppConfig.String("dbPath") + l, _ := ledis.Open(config) + instance, err := l.Select(0) + if err != nil { + panic(err) + } + _dbInstance = instance + }) + return _dbInstance +} + +func hmset(key string, data interface{}) error { + h, err := utils.Flatten(data) + if err != nil { + return err + } + var fvList = make([]ledis.FVPair, len(h)) + i := 0 + for f, v := range h { + fvList[i].Field = []byte(f) + fvList[i].Value, _ = json.Marshal(v) + i++ + } + return db().HMset([]byte(key), fvList...) +} + +func hset(key, field, value string) error { + _, err := db().HSet([]byte(key), []byte(field), []byte(value)) + return err +} \ No newline at end of file diff --git a/repositories/media_file_repository.go b/repositories/media_file_repository.go index 3a60b257b..752fb31fa 100644 --- a/repositories/media_file_repository.go +++ b/repositories/media_file_repository.go @@ -12,7 +12,7 @@ type MediaFile struct { func NewMediaFileRepository() *MediaFile { r := &MediaFile{} - r.col = createCollection("MediaFiles") + r.key = "mediafile" return r } @@ -20,5 +20,5 @@ func (r *MediaFile) Add(m *models.MediaFile) error { if m.Id == "" { m.Id = fmt.Sprintf("%x", md5.Sum([]byte(m.Path))) } - return r.saveOrUpdate(m) + return r.saveOrUpdate(m.Id, m) } \ No newline at end of file diff --git a/utils/flat.go b/utils/flat.go new file mode 100644 index 000000000..129919dfa --- /dev/null +++ b/utils/flat.go @@ -0,0 +1,130 @@ +// Adapted from https://github.com/nytlabs/gojsonexplode + +package utils + +import ( + "strconv" + "encoding/json" +) + +const delimiter = "." + +func explodeList(l []interface{}, parent string, delimiter string) (map[string]interface{}, error) { + var err error + var key string + j := make(map[string]interface{}) + for k, i := range l { + if len(parent) > 0 { + key = parent + delimiter + strconv.Itoa(k) + } else { + key = strconv.Itoa(k) + } + switch v := i.(type) { + case nil: + j[key] = v + case int: + j[key] = v + case float64: + j[key] = v + case string: + j[key] = v + case bool: + j[key] = v + case []interface{}: + out := make(map[string]interface{}) + out, err = explodeList(v, key, delimiter) + if err != nil { + return nil, err + } + for newkey, value := range out { + j[newkey] = value + } + case map[string]interface{}: + out := make(map[string]interface{}) + out, err = explodeMap(v, key, delimiter) + if err != nil { + return nil, err + } + for newkey, value := range out { + j[newkey] = value + } + default: + // do nothing + } + } + return j, nil +} + +func explodeMap(m map[string]interface{}, parent string, delimiter string) (map[string]interface{}, error) { + var err error + j := make(map[string]interface{}) + for k, i := range m { + if len(parent) > 0 { + k = parent + delimiter + k + } + switch v := i.(type) { + case nil: + j[k] = v + case int: + j[k] = v + case float64: + j[k] = v + case string: + j[k] = v + case bool: + j[k] = v + case []interface{}: + out := make(map[string]interface{}) + out, err = explodeList(v, k, delimiter) + if err != nil { + return nil, err + } + for key, value := range out { + j[key] = value + } + case map[string]interface{}: + out := make(map[string]interface{}) + out, err = explodeMap(v, k, delimiter) + if err != nil { + return nil, err + } + for key, value := range out { + j[key] = value + } + default: + //nothing + } + } + return j, nil +} + +func FlattenMap(input map[string]interface{}) (map[string]interface{}, error) { + var flattened map[string]interface{} + var err error + flattened, err = explodeMap(input, "", delimiter) + if err != nil { + return nil, err + } + return flattened, nil +} + +func marshal(rec interface{}) (map[string]interface{}, error) { + // Convert to JSON... + b, err := json.Marshal(rec); + if err != nil { + return nil, err + } + + // ... then convert to map + var m map[string]interface{} + err = json.Unmarshal(b, &m) + return m, err +} + +func Flatten(input interface{}) (map[string]interface{}, error) { + m, err := marshal(input) + if err != nil { + return nil, err + } + return FlattenMap(m) +}