diff --git a/README.md b/README.md index 67517b7..8aca170 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,18 @@ if err := db.Delete("fish", ""); err != nil { } ``` +Also supports automatically generating integer IDs. + +``` +// Write a fish to the database +fish := Fish{} +id, err := db.WriteAutoId("fish", fish); if err != nil { + fmt.Println("Error", err) +} + +// ID == 1 +``` + ## Documentation - Complete documentation is available on [godoc](http://godoc.org/github.com/nanobox-io/golang-scribble). - Coverage Report is available on [gocover](https://gocover.io/github.com/nanobox-io/golang-scribble) diff --git a/scribble.go b/scribble.go index 65844ce..90e3fd5 100644 --- a/scribble.go +++ b/scribble.go @@ -10,13 +10,15 @@ import ( "sync" "github.com/jcelliott/lumber" + "path" + "strings" + "strconv" ) // Version is the current version of the project const Version = "1.0.4" type ( - // Logger is a generic logger interface Logger interface { Fatal(string, ...interface{}) @@ -98,6 +100,11 @@ func (d *Driver) Write(collection, resource string, v interface{}) error { mutex.Lock() defer mutex.Unlock() + return d.writeFile(collection, resource, v) +} + +// Writes a file to disk +func (d *Driver) writeFile(collection string, resource string, v interface{}) error { // dir := filepath.Join(d.dir, collection) fnlPath := filepath.Join(dir, resource+".json") @@ -123,6 +130,51 @@ func (d *Driver) Write(collection, resource string, v interface{}) error { return os.Rename(tmpPath, fnlPath) } +// Locks the database, gets the next available integer resource ID and attempts to write the +// record to the database under the [collection] specified with the generated [resource] ID +func (d *Driver) WriteAutoId(collection string, v interface{}) (resourceId int64, err error) { + resourceId = -1 + + // ensure there is a place to save record + if collection == "" { + return resourceId, fmt.Errorf("Missing collection - no place to save record!") + } + + mutex := d.getOrCreateMutex(collection) + mutex.Lock() + defer mutex.Unlock() + + // list the directory, sort it, take the last entry then parse and increment the last ID + files, err := ioutil.ReadDir(d.dir) + if err != nil { + fmt.Printf("Error listing directory %s: %s", d.dir, err.Error()) + return resourceId, err + } + + if len(files) == 0 { + resourceId = 1 + } else { + lastFile := files[len(files)-1] + + ext := path.Ext(lastFile.Name()) + + baseName := strings.TrimSuffix(lastFile.Name(), ext) + + resourceId, err := strconv.ParseInt(baseName, 10, 8) + if err != nil { + fmt.Printf("Error parsing string '%s' as integer", baseName, err.Error()) + return resourceId, err + } + } + + stringResourceId := fmt.Sprintf("%d", resourceId) + + fmt.Printf("Writing resource under auto-generated ID '%s'\n", stringResourceId) + err = d.writeFile(collection, stringResourceId, v) + + return resourceId, err +} + // Read a record from the database func (d *Driver) Read(collection, resource string, v interface{}) error { @@ -212,11 +264,11 @@ func (d *Driver) Delete(collection, resource string) error { case fi == nil, err != nil: return fmt.Errorf("Unable to find file or directory named %v\n", path) - // remove directory and all contents + // remove directory and all contents case fi.Mode().IsDir(): return os.RemoveAll(dir) - // remove file + // remove file case fi.Mode().IsRegular(): return os.RemoveAll(dir + ".json") } diff --git a/scribble_test.go b/scribble_test.go index 1e4e546..58eff03 100644 --- a/scribble_test.go +++ b/scribble_test.go @@ -3,6 +3,7 @@ package scribble import ( "os" "testing" + "fmt" ) // @@ -85,6 +86,36 @@ func TestWriteAndRead(t *testing.T) { destroySchool() } +// +func TestWriteAutoIdAndRead(t *testing.T) { + + createDB() + + // add fish to database + id, err := db.WriteAutoId(collection, redfish); + if err != nil { + t.Error("Create fish failed: ", err.Error()) + } + + if id <= 0 { + t.Error("Auto-generated ID should have been > 0") + } + + stringId := fmt.Sprintf("%d", id) + + // read fish from database + if err := db.Read(collection, stringId, &onefish); err != nil { + t.Error("Failed to read: ", err.Error()) + } + + // + if onefish.Type != "red" { + t.Error("Expected red fish, got: ", onefish.Type) + } + + destroySchool() +} + // func TestReadall(t *testing.T) {