diff --git a/README.md b/README.md index 41d4d45..ef1ebb2 100644 --- a/README.md +++ b/README.md @@ -11,37 +11,40 @@ Install using `go get github.com/nanobox-io/golang-scribble`. ### Usage -Create a 'transaction' for scribble to transact. - -`t := scribble.Transaction{Action: "read", Collection: "records", ResourceID: "", Container: &v}` - -+ Action - the action for scribble to perform - + write - write to the scribble db - + read - read from the scribble db - + readall - read from the scribble db (all files in a collection) - + delete - remove a record from the scribble db -+ Collection - the folder scribble will create to store grouped records -+ ResourceID - the unique ID of the resource being stored (bson, uuid, etc.) -+ Container - the Struct that contains the data scribble will marshal into the store, or what it will unmarshal into from the store - - -#### Full Example ```go -// a new scribble driver, providing the directory where it will be writing to, and a qualified logger to which it can send any output. -database, err := scribble.New(dir, logger) +// a new scribble driver, providing the directory where it will be writing to, +// and a qualified logger to which it can send any output. +db, err := scribble.New(dir, logger) if err != nil { fmt.Println("Error", err) } -// this is what scribble will either marshal from when writing, or unmarshal into when reading -record := Record{} +// Write a fish to the database +fish := Fish{} +if err := db.Write("fish", "onefish", fish); err != nil { -// create a new transaction for scribble to run -t := scribble.Transaction{Action: scribble.READ, Collection: "records", ResourceID: "", Container: &record} +} + +// Read all fish from the database +fish := []Fish{} +if err := db.Read("fish", fish); err != nil { + +} + +// Read a fish from the database +fish := Fish{} +if err := db.Read("/fish/onefish", fish); err != nil { + +} + +// Delete all fish from the database +if err := db.Delete("/fish"); err != nil { + +} + +// Delete a fish from the database +if err := db.Delete("/fish/onefish"); err != nil { -// have scribble attempt to run the transaction -if err := database.Transact(t); err != nil { - fmt.Println("Error", err) } ``` @@ -54,6 +57,8 @@ Complete documentation is available on [godoc](http://godoc.org/github.com/nanob ## Todo/Doing - Tests! +- Better support for sub collections +- More methods to allow different types of reads/writes ## Contributing diff --git a/scribble.go b/scribble.go index a1e4da9..deca85a 100644 --- a/scribble.go +++ b/scribble.go @@ -5,15 +5,11 @@ // at http://mozilla.org/MPL/2.0/. // -// scribble is a tiny JSON flat file store. It uses transactions that tell it what -// actions to perform, where it is to store data, and what it is going to write -// that data from, or read the data into. It creates a very simple database -// structure under a specified directory +// scribble is a tiny JSON flat file store package scribble import ( "encoding/json" - "errors" "fmt" "io/ioutil" "os" @@ -23,7 +19,7 @@ import ( "github.com/nanobox-io/golang-hatchet" ) -const Version = "0.1.0" +const Version = "0.5.0" type ( @@ -32,24 +28,8 @@ type ( Driver struct { mutexes map[string]sync.Mutex dir string // the directory where scribble will create the database - log hatchet.Logger // the logger scirbble will log to + log hatchet.Logger // the logger scribble will log to } - - // a Transactions is what is used by a Driver to complete database operations - Transaction struct { - Action int // the action for scribble to preform - Collection string // the forlder for scribble to read/write to/from - ResourceID string // the unique ID of the record - Container interface{} // what scribble will marshal from or unmarshal into - } -) - -// -const ( - WRITE = iota - READ - READALL - DELETE ) // New creates a new scribble database at the desired directory location, and @@ -78,42 +58,24 @@ func New(dir string, logger hatchet.Logger) (*Driver, error) { return scribble, nil } -// Transact determins the type of transactions to be run, and calls the appropriate -// method to complete the operation -func (d *Driver) Transact(trans Transaction) (err error) { +// Write locks the database and attempts to write the record to the database under +// the [collection] specified with the [resource] name given +// +// Example: +// +// // write [fish] to database +// Write("/fish/onefish", fish{name:"onefish"}) +func (d *Driver) Write(collection, resource string, v interface{}) error { - // determin transaction to be run - switch trans.Action { - case WRITE: - return d.write(trans) - case READ: - return d.read(trans) - case READALL: - return d.readAll(trans) - case DELETE: - return d.delete(trans) - default: - return errors.New(fmt.Sprintf("Unsupported action %+v", trans.Action)) - } - - return -} - -// write locks the database and then proceeds to write the data represented by a -// transaction.Container. It will create a direcotry that represents the collection -// to wich the record belongs (if it doesn't already exist), and write a file -// (named by he transaction.ResourceID) to that directory -func (d *Driver) write(trans Transaction) error { - - mutex := d.getOrCreateMutex(trans.Collection) + mutex := d.getOrCreateMutex(collection) mutex.Lock() defer mutex.Unlock() // - dir := d.dir + "/" + trans.Collection + dir := d.dir + "/" + collection // - b, err := json.MarshalIndent(trans.Container, "", "\t") + b, err := json.MarshalIndent(v, "", "\t") if err != nil { return err } @@ -123,10 +85,10 @@ func (d *Driver) write(trans Transaction) error { return err } - finalPath := dir + "/" + trans.ResourceID + ".json" + finalPath := dir + "/" + resource + ".json" tmpPath := finalPath + "~" - // write marshaled data to a file, named by the resourceID + // write marshaled data to the temp file if err := ioutil.WriteFile(tmpPath, b, 0644); err != nil { return err } @@ -135,71 +97,102 @@ func (d *Driver) write(trans Transaction) error { return os.Rename(tmpPath, finalPath) } -// read does the opposite operation as write. Reading a record from the database -// (named by the transaction.resourceID, found in the transaction.Collection), and -// unmarshaling the data into the transaction.Container -func (d *Driver) read(trans Transaction) error { +// Read a record from the database +// +// Example: +// +// // read a single fish +// Read("/fish/twofish", &fish) +// +// // read all fish +// Read("/fish", &fish) +func (d *Driver) Read(path string, v interface{}) error { - dir := d.dir + "/" + trans.Collection + dir := d.dir + "/" + path - // read record from database - b, err := ioutil.ReadFile(dir + "/" + trans.ResourceID + ".json") + // + fi, err := os.Stat(path) if err != nil { return err } - // unmarshal data into the transaction.Container - return json.Unmarshal(b, trans.Container) -} + switch { -// readAll does the same operation as read, reading all the records in the specified -// transaction.Collection -func (d *Driver) readAll(trans Transaction) error { + // if the path is a directory, attempt to read all entries into v + case fi.Mode().IsDir(): - dir := d.dir + "/" + trans.Collection + // read all the files in the transaction.Collection + files, err := ioutil.ReadDir(dir) + if err != nil { + // an error here just means the collection is either empty or doesn't exist + } - // read all the files in the transaction.Collection - files, err := ioutil.ReadDir(dir) + // the files read from the database + var f []string - if err != nil { - // an error here just means an empty collection so do nothing - } + // 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(dir + "/" + file.Name()) + if err != nil { + return err + } - // the files read from the database - var f []string + // append read file + f = append(f, string(b)) + } - // 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(dir + "/" + file.Name()) + // 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().IsDir(): + // read record from database + b, err := ioutil.ReadFile(dir + ".json") if err != nil { return err } - // append read file - f = append(f, string(b)) + // unmarshal data into the transaction.Container + return json.Unmarshal(b, &v) } - // unmarhsal the read files as a comma delimeted byte array - return json.Unmarshal([]byte("["+strings.Join(f, ",")+"]"), trans.Container) + return nil } -// delete locks that database and then proceeds to remove the record (specified by -// transaction.ResourceID) from the collection -func (d *Driver) delete(trans Transaction) error { +// Delete locks that database and then attempts to remove the collection/resource +// specified by [path] +// +// Example: +// +// // delete the fish 'redfish.json' +// Delete("/fish/redfish") +// +// // delete the fish collection +// Delete("/fish") +func (d *Driver) Delete(path string) error { - mutex := d.getOrCreateMutex(trans.Collection) + mutex := d.getOrCreateMutex(path) mutex.Lock() defer mutex.Unlock() - dir := d.dir + "/" + trans.Collection + // stat the file to determine if it is a file or dir + fi, err := os.Stat(path) + if err != nil { + return err + } - // remove record from database - return os.Remove(dir + "/" + trans.ResourceID + ".json") + switch { + // remove the collection from database + case fi.Mode().IsDir(): + return os.Remove(d.dir + "/" + path) + + // remove the record from database + default: + return os.Remove(d.dir + "/" + path + ".json") + } } -// helpers - // getOrCreateMutex creates a new collection specific mutex any time a collection // is being modfied to avoid unsafe operations func (d *Driver) getOrCreateMutex(collection string) sync.Mutex {