Merge pull request #6 from nanobox-io/update/less-weird-mkDir-wrapper

updating the mkDir wrapper to be less weird
This commit is contained in:
Steve Domino 2015-11-16 10:49:23 -07:00
commit 9831494f39
2 changed files with 201 additions and 169 deletions

View File

@ -8,11 +8,10 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"sync" "sync"
) )
const Version = "1.0.2" const Version = "1.0.3"
type ( type (
@ -43,13 +42,18 @@ func New(dir string, logger Logger) (driver *Driver, err error) {
// //
dir = filepath.Clean(dir) dir = filepath.Clean(dir)
// ensure the database location doesn't already exist (we don't want to overwrite
// any existing files/database)
if _, err := os.Stat(dir); err == nil {
fmt.Printf("Unable to create database, '%s' already exists. Please specify a different location.\n", dir)
os.Exit(1)
}
// //
if logger == nil { if logger == nil {
logger = lumber.NewConsoleLogger(lumber.INFO) logger = lumber.NewConsoleLogger(lumber.INFO)
} }
logger.Info("Creating scribble database at '%v'...\n", dir)
// //
driver = &Driver{ driver = &Driver{
dir: dir, dir: dir,
@ -57,75 +61,39 @@ func New(dir string, logger Logger) (driver *Driver, err error) {
log: logger, log: logger,
} }
logger.Info("Creating scribble database at '%v'...\n", dir)
// create database // create database
return driver, mkDir(dir) return driver, os.MkdirAll(dir, 0755)
}
// Read a record from the database
func (d *Driver) Read(collection, resource string, v interface{}) error {
//
path := filepath.Join(collection, resource)
dir := filepath.Join(d.dir, path)
//
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
case fi.Mode().IsDir():
// read all the files in the transaction.Collection; an error here just means
// the collection is either empty or doesn't exist
files, _ := ioutil.ReadDir(dir)
// the files read from the database
var f []string
// iterate over each of the files, attempting to read the file. If successful
// append the files to the collection of read files
for _, file := range files {
b, err := ioutil.ReadFile(filepath.Join(dir, file.Name()))
if err != nil {
return err
}
// append read file
f = append(f, string(b))
}
// unmarhsal the read files as a comma delimeted byte array
return json.Unmarshal([]byte("["+strings.Join(f, ",")+"]"), v)
// if the path is a file, attempt to read the single file
case fi.Mode().IsRegular():
// read record from database
b, err := ioutil.ReadFile(dir + ".json")
if err != nil {
return err
}
// unmarshal data
return json.Unmarshal(b, &v)
}
return nil
} }
// Write locks the database and attempts to write the record to the database under // Write locks the database and attempts to write the record to the database under
// the [collection] specified with the [resource] name given // the [collection] specified with the [resource] name given
func (d *Driver) Write(collection, resource string, v interface{}) error { func (d *Driver) Write(collection, resource string, v interface{}) error {
// ensure there is a place to save record
if collection == "" {
return fmt.Errorf("Missing collection - no place to save record!")
}
// ensure there is a resource (name) to save record as
if resource == "" {
return fmt.Errorf("Missing resource - unable to save record (no name)!")
}
mutex := d.getOrCreateMutex(collection) mutex := d.getOrCreateMutex(collection)
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
// //
dir := filepath.Join(d.dir, collection) dir := filepath.Join(d.dir, collection)
fnlPath := filepath.Join(dir, resource+".json")
tmpPath := fnlPath + ".tmp"
// create collection directory
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
// //
b, err := json.MarshalIndent(v, "", "\t") b, err := json.MarshalIndent(v, "", "\t")
@ -133,21 +101,84 @@ func (d *Driver) Write(collection, resource string, v interface{}) error {
return err 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 // write marshaled data to the temp file
if err := ioutil.WriteFile(tmpPath, b, 0644); err != nil { if err := ioutil.WriteFile(tmpPath, b, 0644); err != nil {
return err return err
} }
// move final file into place // move final file into place
return os.Rename(tmpPath, finalPath) return os.Rename(tmpPath, fnlPath)
}
// Read a record from the database
func (d *Driver) Read(collection, resource string, v interface{}) error {
// ensure there is a place to save record
if collection == "" {
return fmt.Errorf("Missing collection - no place to save record!")
}
// ensure there is a resource (name) to save record as
if resource == "" {
return fmt.Errorf("Missing resource - unable to save record (no name)!")
}
//
record := filepath.Join(d.dir, collection, resource)
// check to see if file exists
if _, err := stat(record); err != nil {
return err
}
// read record from database
b, err := ioutil.ReadFile(record + ".json")
if err != nil {
return err
}
// unmarshal data
return json.Unmarshal(b, &v)
}
// ReadAll records from a collection; this is returned as a slice of strings because
// there is no way of knowing what type the record is.
func (d *Driver) ReadAll(collection string) ([]string, error) {
// ensure there is a collection to read
if collection == "" {
return nil, fmt.Errorf("Missing collection - unable to record location!")
}
//
dir := filepath.Join(d.dir, collection)
// check to see if collection (directory) exists
if _, err := stat(dir); err != nil {
return nil, err
}
// read all the files in the transaction.Collection; an error here just means
// the collection is either empty or doesn't exist
files, _ := ioutil.ReadDir(dir)
// the files read from the database
var records []string
// iterate over each of the files, attempting to read the file. If successful
// append the files to the collection of read files
for _, file := range files {
b, err := ioutil.ReadFile(filepath.Join(dir, file.Name()))
if err != nil {
return nil, err
}
// append read file
records = append(records, string(b))
}
// unmarhsal the read files as a comma delimeted byte array
return records, nil
} }
// Delete locks that database and then attempts to remove the collection/resource // Delete locks that database and then attempts to remove the collection/resource
@ -208,20 +239,3 @@ func (d *Driver) getOrCreateMutex(collection string) sync.Mutex {
return m return m
} }
// mkDir is a simple wrapper that attempts to make a directory at a specified
// location
func mkDir(d string) (err error) {
//
dir, _ := os.Stat(d)
switch {
case dir == nil:
err = os.MkdirAll(d, 0755)
case !dir.IsDir():
err = os.ErrInvalid
}
return
}

View File

@ -6,146 +6,164 @@ import (
) )
// //
type Friend struct { type Fish struct {
Name string `json:"name"` Type string `json:"type"`
} }
// //
var ( var (
db *Driver db *Driver
testRoot = "./test_db" database = "./school"
friendsPath = "/friends" collection = "fish"
friend0 = Friend{} onefish = Fish{}
friend1 = Friend{Name: "wocket"} twofish = Fish{}
friend2 = Friend{Name: "wasket"} redfish = Fish{Type: "red"}
bluefish = Fish{Type: "blue"}
) )
// //
func init() { func TestMain(m *testing.M) {
startup()
}
// var err error
func startup() {
db, _ = New(testRoot, nil)
}
// // create a new scribble
func teardown() { if db, err = New(database, nil); err != nil {
os.RemoveAll(testRoot) panic(err)
}
//
func createFriend(t *testing.T) {
if err := db.Write(friendsPath, "friend1", friend1); err != nil {
t.Error("Failed to write", err)
}
}
//
func createFriends(t *testing.T) {
if err := db.Write(friendsPath, "friend1", friend1); err != nil {
t.Error("Failed to write", err)
} }
if err := db.Write(friendsPath, "friend2", friend2); err != nil { // run
t.Error("Failed to write", err) code := m.Run()
}
// cleanup
os.RemoveAll(database)
// exit
os.Exit(code)
} }
// //
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
if _, err := os.Stat(testRoot); os.IsNotExist(err) { if _, err := os.Stat(database); err != nil {
t.Error("Expected file, got none", err) t.Error("Expected file, got none")
} }
teardown()
} }
// //
func TestWrite(t *testing.T) { func TestWriteAndRead(t *testing.T) {
createFriend(t)
teardown()
}
// // add fish to database
func TestRead(t *testing.T) { if err := db.Write(collection, "redfish", redfish); err != nil {
createFriend(t) t.Error("Create fish failed: ", err.Error())
if err := db.Read(friendsPath, "friend1", &friend0); err != nil {
t.Error("Failed to read", err)
} }
if friend0.Name == "" { // read fish from database
t.Error("Expected friend, have none") if err := db.Read(collection, "redfish", &onefish); err != nil {
t.Error("Failed to read: ", err.Error())
} }
teardown() //
} if onefish.Type != "red" {
t.Error("Expected red fish, got: ", onefish.Type)
//
func TestReadEmpty(t *testing.T) {
if err := db.Read(friendsPath, "friend1", &friend0); err == nil {
t.Error("Expected nothing, found friend")
} }
teardown() destroySchool()
} }
// //
func TestReadall(t *testing.T) { func TestReadall(t *testing.T) {
createFriends(t) createSchool()
friends := []Friend{} fish, err := db.ReadAll(collection)
if err := db.Read(friendsPath, "", &friends); err != nil { if err != nil {
t.Error("Failed to read", err) t.Error("Failed to read: ", err.Error())
} }
if len(friends) <= 0 { if len(fish) <= 0 {
t.Error("Expected friends, have none") t.Error("Expected some fish, have none")
} }
teardown() destroySchool()
} }
// //
func TestReadallEmpty(t *testing.T) { func TestWriteAndReadEmpty(t *testing.T) {
friends := []Friend{} // create a fish with no home
if err := db.Read(friendsPath, "", &friends); err == nil { if err := db.Write("", "redfish", redfish); err == nil {
t.Error("Expected nothing, found friends") t.Error("Allowed write of empty resource", err.Error())
} }
teardown() // create a home with no fish
if err := db.Write(collection, "", redfish); err == nil {
t.Error("Allowed write of empty resource", err.Error())
}
// no place to read
if err := db.Read("", "redfish", onefish); err == nil {
t.Error("Allowed read of empty resource", err.Error())
}
destroySchool()
} }
// //
func TestDelete(t *testing.T) { func TestDelete(t *testing.T) {
createFriend(t)
if err := db.Delete(friendsPath, "friend1"); err != nil { // add fish to database
t.Error("Failed to delete", err) if err := db.Write(collection, "redfish", redfish); err != nil {
t.Error("Create fish failed: ", err.Error())
} }
if fi, err := os.Stat(friendsPath + "/friend1"); fi != nil { // delete the fish
t.Error("Expected nothing, have friends", err) if err := db.Delete(collection, "redfish"); err != nil {
t.Error("Failed to delete: ", err.Error())
} }
teardown() // read fish from database
if err := db.Read(collection, "redfish", &onefish); err == nil {
t.Error("Expected nothing, got fish")
}
destroySchool()
} }
// //
func TestDeleteall(t *testing.T) { func TestDeleteall(t *testing.T) {
createFriends(t) createSchool()
if err := db.Delete(friendsPath, ""); err != nil { if err := db.Delete(collection, ""); err != nil {
t.Error("Failed to delete ", err) t.Error("Failed to delete: ", err.Error())
} }
if fi, err := os.Stat(friendsPath); fi != nil { if _, err := os.Stat(collection); err == nil {
t.Error("Expected nothing, have friends", err) t.Error("Expected nothing, have fish")
} }
teardown() destroySchool()
}
//
func createFish(fish Fish) error {
return db.Write(collection, fish.Type, fish)
}
//
func createSchool() error {
for _, f := range []Fish{Fish{Type: "red"}, Fish{Type: "blue"}} {
if err := db.Write(collection, f.Type, f); err != nil {
return err
}
}
return nil
}
//
func destroyFish(name string) error {
return db.Delete(collection, name)
}
//
func destroySchool() error {
return db.Delete(collection, "")
} }