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:
commit
9831494f39
186
scribble.go
186
scribble.go
@ -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
|
|
||||||
}
|
|
||||||
|
184
scribble_test.go
184
scribble_test.go
@ -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, "")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user