cleaned up some logic, using filepath library when working with paths to have better support for windows (per @chrhlnd pull request). added tests to test looking for empty files/directories
This commit is contained in:
parent
a117d7d71e
commit
9d6df7eff7
141
scribble.go
141
scribble.go
|
@ -13,13 +13,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/nanobox-io/golang-hatchet"
|
"github.com/nanobox-io/golang-hatchet"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Version = "0.5.0"
|
const Version = "1.0.0"
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|
||||||
|
@ -35,13 +36,17 @@ type (
|
||||||
// New creates a new scribble database at the desired directory location, and
|
// New creates a new scribble database at the desired directory location, and
|
||||||
// returns a *Driver to then use for interacting with the database
|
// returns a *Driver to then use for interacting with the database
|
||||||
func New(dir string, logger hatchet.Logger) (*Driver, error) {
|
func New(dir string, logger hatchet.Logger) (*Driver, error) {
|
||||||
fmt.Printf("Creating database directory at '%v'...\n", dir)
|
|
||||||
|
//
|
||||||
|
dir = filepath.Clean(dir)
|
||||||
|
|
||||||
//
|
//
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
logger = hatchet.DevNullLogger{}
|
logger = hatchet.DevNullLogger{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Info("Creating database directory at '%v'...\n", dir)
|
||||||
|
|
||||||
//
|
//
|
||||||
d := &Driver{
|
d := &Driver{
|
||||||
dir: dir,
|
dir: dir,
|
||||||
|
@ -58,56 +63,25 @@ func New(dir string, logger hatchet.Logger) (*Driver, error) {
|
||||||
return d, nil
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write locks the database and attempts to write the record to the database under
|
|
||||||
// the [collection] specified with the [resource] name given
|
|
||||||
func (d *Driver) Write(collection, resource string, v interface{}) error {
|
|
||||||
|
|
||||||
mutex := d.getOrCreateMutex(collection)
|
|
||||||
mutex.Lock()
|
|
||||||
defer mutex.Unlock()
|
|
||||||
|
|
||||||
//
|
|
||||||
dir := d.dir + collection
|
|
||||||
|
|
||||||
//
|
|
||||||
b, err := json.MarshalIndent(v, "", "\t")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// create collection directory
|
|
||||||
if err := mkDir(dir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
finalPath := dir + "/" + resource + ".json"
|
|
||||||
tmpPath := finalPath + "~"
|
|
||||||
|
|
||||||
// write marshaled data to the temp file
|
|
||||||
if err := ioutil.WriteFile(tmpPath, b, 0644); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// move final file into place
|
|
||||||
return os.Rename(tmpPath, finalPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read a record from the database
|
// Read a record from the database
|
||||||
func (d *Driver) Read(path string, v interface{}) error {
|
func (d *Driver) Read(path string, v interface{}) error {
|
||||||
|
|
||||||
//
|
//
|
||||||
m, p := modePath(d.dir + path)
|
dir := filepath.Join(d.dir, path)
|
||||||
|
|
||||||
switch m {
|
//
|
||||||
|
switch fi, err := stat(dir); {
|
||||||
|
|
||||||
|
// if fi is nil or error is not nil return
|
||||||
|
case fi == nil, err != nil:
|
||||||
|
return fmt.Errorf("Unable to find file or directory named %v\n", path)
|
||||||
|
|
||||||
// if the path is a directory, attempt to read all entries into v
|
// if the path is a directory, attempt to read all entries into v
|
||||||
case "dir":
|
case fi.Mode().IsDir():
|
||||||
|
|
||||||
// read all the files in the transaction.Collection
|
// read all the files in the transaction.Collection; an error here just means
|
||||||
files, err := ioutil.ReadDir(p)
|
// the collection is either empty or doesn't exist
|
||||||
if err != nil {
|
files, _ := ioutil.ReadDir(dir)
|
||||||
// an error here just means the collection is either empty or doesn't exist
|
|
||||||
}
|
|
||||||
|
|
||||||
// the files read from the database
|
// the files read from the database
|
||||||
var f []string
|
var f []string
|
||||||
|
@ -115,7 +89,7 @@ func (d *Driver) Read(path string, v interface{}) error {
|
||||||
// iterate over each of the files, attempting to read the file. If successful
|
// iterate over each of the files, attempting to read the file. If successful
|
||||||
// append the files to the collection of read files
|
// append the files to the collection of read files
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
b, err := ioutil.ReadFile(p + "/" + file.Name())
|
b, err := ioutil.ReadFile(filepath.Join(dir, file.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -128,51 +102,94 @@ func (d *Driver) Read(path string, v interface{}) error {
|
||||||
return json.Unmarshal([]byte("["+strings.Join(f, ",")+"]"), v)
|
return json.Unmarshal([]byte("["+strings.Join(f, ",")+"]"), v)
|
||||||
|
|
||||||
// if the path is a file, attempt to read the single file
|
// if the path is a file, attempt to read the single file
|
||||||
case "file":
|
case fi.Mode().IsRegular():
|
||||||
|
|
||||||
// read record from database
|
// read record from database
|
||||||
b, err := ioutil.ReadFile(p)
|
b, err := ioutil.ReadFile(dir + ".json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// unmarshal data into the transaction.Container
|
// unmarshal data
|
||||||
return json.Unmarshal(b, &v)
|
return json.Unmarshal(b, &v)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write locks the database and attempts to write the record to the database under
|
||||||
|
// the [collection] specified with the [resource] name given
|
||||||
|
func (d *Driver) Write(collection, resource string, v interface{}) error {
|
||||||
|
|
||||||
|
mutex := d.getOrCreateMutex(collection)
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
|
||||||
|
//
|
||||||
|
dir := filepath.Join(d.dir, collection)
|
||||||
|
|
||||||
|
//
|
||||||
|
b, err := json.MarshalIndent(v, "", "\t")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create collection directory
|
||||||
|
if err := mkDir(dir); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
finalPath := filepath.Join(dir, resource+".json")
|
||||||
|
tmpPath := finalPath + "~"
|
||||||
|
|
||||||
|
// write marshaled data to the temp file
|
||||||
|
if err := ioutil.WriteFile(tmpPath, b, 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// move final file into place
|
||||||
|
return os.Rename(tmpPath, finalPath)
|
||||||
|
}
|
||||||
|
|
||||||
// Delete locks that database and then attempts to remove the collection/resource
|
// Delete locks that database and then attempts to remove the collection/resource
|
||||||
// specified by [path]
|
// specified by [path]
|
||||||
func (d *Driver) Delete(path string) error {
|
func (d *Driver) Delete(path string) error {
|
||||||
|
|
||||||
|
//
|
||||||
mutex := d.getOrCreateMutex(path)
|
mutex := d.getOrCreateMutex(path)
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
|
|
||||||
//
|
//
|
||||||
_, p := modePath(d.dir + path)
|
dir := filepath.Join(d.dir, path)
|
||||||
|
|
||||||
//
|
switch fi, err := stat(dir); {
|
||||||
return os.RemoveAll(p)
|
|
||||||
|
// if fi is nil or error is not nil return
|
||||||
|
case fi == nil, err != nil:
|
||||||
|
return fmt.Errorf("Unable to find file or directory named %v\n", path)
|
||||||
|
|
||||||
|
// remove directory and all contents
|
||||||
|
case fi.Mode().IsDir():
|
||||||
|
return os.RemoveAll(dir)
|
||||||
|
|
||||||
|
// remove file
|
||||||
|
case fi.Mode().IsRegular():
|
||||||
|
return os.RemoveAll(dir + ".json")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
func modePath(path string) (m, p string) {
|
func stat(path string) (fi os.FileInfo, err error) {
|
||||||
|
|
||||||
// check for dir
|
// check for dir, if path isn't a directory check to see if it's a file
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
if fi, err = os.Stat(path); os.IsNotExist(err) {
|
||||||
|
fi, err = os.Stat(path + ".json")
|
||||||
// check for file
|
|
||||||
if _, err := os.Stat(path + ".json"); os.IsNotExist(err) {
|
|
||||||
fmt.Printf("No file or directory found at '%v'\n", path+".json")
|
|
||||||
}
|
|
||||||
|
|
||||||
return "file", path + ".json"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "dir", path
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// getOrCreateMutex creates a new collection specific mutex any time a collection
|
// getOrCreateMutex creates a new collection specific mutex any time a collection
|
||||||
|
|
|
@ -65,6 +65,7 @@ func TestNew(t *testing.T) {
|
||||||
//
|
//
|
||||||
func TestWrite(t *testing.T) {
|
func TestWrite(t *testing.T) {
|
||||||
createFriend(t)
|
createFriend(t)
|
||||||
|
teardown()
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -78,6 +79,18 @@ func TestRead(t *testing.T) {
|
||||||
if friend0.Name == "" {
|
if friend0.Name == "" {
|
||||||
t.Error("Expected friend, have none")
|
t.Error("Expected friend, have none")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
teardown()
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
func TestReadEmpty(t *testing.T) {
|
||||||
|
|
||||||
|
if err := db.Read(friendsPath+"/friend1", &friend0); err == nil {
|
||||||
|
t.Error("Expected nothing, found friend")
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown()
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -96,6 +109,17 @@ func TestReadall(t *testing.T) {
|
||||||
teardown()
|
teardown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
func TestReadallEmpty(t *testing.T) {
|
||||||
|
|
||||||
|
friends := []Friend{}
|
||||||
|
if err := db.Read(friendsPath, &friends); err == nil {
|
||||||
|
t.Error("Expected nothing, found friends")
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown()
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
func TestDelete(t *testing.T) {
|
func TestDelete(t *testing.T) {
|
||||||
createFriend(t)
|
createFriend(t)
|
||||||
|
|
Loading…
Reference in New Issue