394 lines
8.0 KiB
Go
394 lines
8.0 KiB
Go
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
|
|
}
|