package main import "C" import ( "encoding/binary" "encoding/json" "errors" "os" "github.com/boltdb/bolt" ) const ( ERROR_AND_STOP_CALLING int64 = 100 ERROR_AND_KEEP_CALLING = 101 FINISHED_OK = 102 REAL_MESSAGE = 103 ) const Magic int64 = 0x10203040 //export GetMagic func GetMagic() int64 { return Magic } //export Bolt_Open func Bolt_Open(path string) (ObjectReference, *C.char, int) { ptrDB, err := bolt.Open(path, os.FileMode(0644), bolt.DefaultOptions) if err != nil { errMsg := err.Error() return 0, C.CString(errMsg), len(errMsg) } dbRef := gms.Put(ptrDB) return dbRef, nil, 0 } func withBoltDBReference(b ObjectReference, fn func(db *bolt.DB) error) error { dbIFC, ok := gms.Get(b) if !ok { return NullObjectReference } ptrDB, ok := dbIFC.(*bolt.DB) if !ok { return NullObjectReference } return fn(ptrDB) } func walkBuckets(tx *bolt.Tx, browse []string) (*bolt.Bucket, error) { bucket := tx.Bucket([]byte(browse[0])) if bucket == nil { return nil, errors.New("Unknown bucket") } for i := 1; i < len(browse); i += 1 { bucket = bucket.Bucket([]byte(browse[i])) if bucket == nil { return nil, errors.New("Unknown bucket") } } return bucket, nil } func withBrowse_ReadOnly(b_ref ObjectReference, browse []string, fn func(db *bolt.DB, tx *bolt.Tx, bucket *bolt.Bucket) error) error { if len(browse) == 0 { // not a bucket return errors.New("No bucket selected") } return withBoltDBReference(b_ref, func(db *bolt.DB) error { return db.View(func(tx *bolt.Tx) error { bucket, err := walkBuckets(tx, browse) if err != nil { return err } // Walked the bucket chain, now run the user callback return fn(db, tx, bucket) }) }) } func err2triple(err error) (int64, *C.char, int) { if err != nil { msg := err.Error() return ERROR_AND_STOP_CALLING, C.CString(msg), len(msg) } return FINISHED_OK, nil, 0 } //export Bolt_CreateBucket func Bolt_CreateBucket(b_ref ObjectReference, browse []string, newBucket string) (int64, *C.char, int) { err := withBoltDBReference(b_ref, func(db *bolt.DB) error { return db.Update(func(tx *bolt.Tx) error { if len(browse) == 0 { // Top-level bucket _, err := tx.CreateBucket([]byte(newBucket)) return err } else { // Deeper bucket bucket, err := walkBuckets(tx, browse) if err != nil { return err } // Walked the bucket chain, now create the new bucket _, err = bucket.CreateBucket([]byte(newBucket)) return err } }) }) return err2triple(err) } //export Bolt_DeleteBucket func Bolt_DeleteBucket(b_ref ObjectReference, browse []string, delBucket string) (int64, *C.char, int) { err := withBoltDBReference(b_ref, func(db *bolt.DB) error { return db.Update(func(tx *bolt.Tx) error { if len(browse) == 0 { // Top-level bucket return tx.DeleteBucket([]byte(delBucket)) } else { // Deeper bucket bucket, err := walkBuckets(tx, browse) if err != nil { return err } // Walked the bucket chain, now delete the selected bucket return bucket.DeleteBucket([]byte(delBucket)) } }) }) return err2triple(err) } //export Bolt_SetItem func Bolt_SetItem(b_ref ObjectReference, browse []string, key, val string) (int64, *C.char, int) { if len(browse) == 0 { return err2triple(errors.New("Can't create top-level items")) } err := withBoltDBReference(b_ref, func(db *bolt.DB) error { return db.Update(func(tx *bolt.Tx) error { bucket, err := walkBuckets(tx, browse) if err != nil { return err } return bucket.Put([]byte(key), []byte(val)) }) }) return err2triple(err) } //export Bolt_DeleteItem func Bolt_DeleteItem(b_ref ObjectReference, browse []string, key string) (int64, *C.char, int) { if len(browse) == 0 { return err2triple(errors.New("Can't create top-level items")) } err := withBoltDBReference(b_ref, func(db *bolt.DB) error { return db.Update(func(tx *bolt.Tx) error { bucket, err := walkBuckets(tx, browse) if err != nil { return err } return bucket.Delete([]byte(key)) }) }) return err2triple(err) } type CallResponse struct { s string e error } //export Bolt_DBStats func Bolt_DBStats(b ObjectReference) (int64, *C.char, int) { var stats bolt.Stats err := withBoltDBReference(b, func(db *bolt.DB) error { stats = db.Stats() return nil }) jBytes, err := json.Marshal(stats) if err != nil { return err2triple(err) } return REAL_MESSAGE, C.CString(string(jBytes)), len(jBytes) } //export Bolt_BucketStats func Bolt_BucketStats(b ObjectReference, browse []string) (int64, *C.char, int) { var stats bolt.BucketStats err := withBrowse_ReadOnly(b, browse, func(db *bolt.DB, tx *bolt.Tx, bucket *bolt.Bucket) error { stats = bucket.Stats() return nil }) if err != nil { return err2triple(err) } jBytes, err := json.Marshal(stats) if err != nil { return err2triple(err) } return REAL_MESSAGE, C.CString(string(jBytes)), len(jBytes) } type NextCall struct { content chan CallResponse } //export Bolt_ListBuckets func Bolt_ListBuckets(b ObjectReference, browse []string) ObjectReference { pNC := &NextCall{ content: make(chan CallResponse, 0), } pNC_Ref := gms.Put(pNC) go func() { var err error if len(browse) == 0 { // root mode err = withBoltDBReference(b, func(db *bolt.DB) error { return db.View(func(tx *bolt.Tx) error { return tx.ForEach(func(k []byte, _ *bolt.Bucket) error { pNC.content <- CallResponse{s: string(k)} return nil }) }) }) } else { // Nested-mode err = withBrowse_ReadOnly(b, browse, func(db *bolt.DB, tx *bolt.Tx, bucket *bolt.Bucket) error { return bucket.ForEach(func(k, v []byte) error { // non-nil v means it's a data item if v == nil { pNC.content <- CallResponse{s: string(k)} } return nil }) }) } if err != nil { pNC.content <- CallResponse{e: err} } close(pNC.content) }() return pNC_Ref } //export Bolt_ListItems func Bolt_ListItems(b ObjectReference, browse []string) ObjectReference { pNC := &NextCall{ content: make(chan CallResponse, 0), } pNC_Ref := gms.Put(pNC) go func() { var err error if len(browse) == 0 { err = errors.New("No bucket specified") } else { // Nested-mode err = withBrowse_ReadOnly(b, browse, func(db *bolt.DB, tx *bolt.Tx, bucket *bolt.Bucket) error { return bucket.ForEach(func(k, v []byte) error { if v == nil { return nil // nil v means it's a bucket, skip } itemLength := make([]byte, 8) binary.LittleEndian.PutUint64(itemLength, uint64(len(v))) pNC.content <- CallResponse{s: string(itemLength) + string(k)} return nil }) }) } if err != nil { pNC.content <- CallResponse{e: err} } close(pNC.content) }() return pNC_Ref } //export Bolt_GetItem func Bolt_GetItem(b ObjectReference, browse []string, key string) (int64, *C.char, int) { var ret *C.char = nil var ret_len = 0 err := withBrowse_ReadOnly(b, browse, func(db *bolt.DB, tx *bolt.Tx, bucket *bolt.Bucket) error { d := bucket.Get([]byte(key)) ret = C.CString(string(d)) ret_len = len(d) return nil }) if err != nil { return err2triple(err) } return REAL_MESSAGE, ret, ret_len } //export GetNext func GetNext(oRef ObjectReference) (int64, *C.char, int) { pNC_Iface, ok := gms.Get(oRef) if !ok { return err2triple(NullObjectReference) } pNC, ok := pNC_Iface.(*NextCall) if !ok { return err2triple(NullObjectReference) } cr, ok := <-pNC.content if !ok { gms.Delete(oRef) return err2triple(nil) } if cr.e != nil { msg := cr.e.Error() return ERROR_AND_KEEP_CALLING, C.CString(msg), len(msg) } return REAL_MESSAGE, C.CString(cr.s), len(cr.s) } //export Bolt_ListBucketsAtRoot func Bolt_ListBucketsAtRoot(b ObjectReference) ObjectReference { return Bolt_ListBuckets(b, nil) } //export Bolt_Close func Bolt_Close(b ObjectReference) (*C.char, int) { err := withBoltDBReference(b, func(db *bolt.DB) error { return db.Close() }) if err != nil { msg := err.Error() return C.CString(msg), len(msg) } gms.Delete(b) return nil, 0 } func main() { // virtual }