220 lines
4.5 KiB
Go
220 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"os"
|
|
"time"
|
|
|
|
bolt "go.etcd.io/bbolt"
|
|
)
|
|
|
|
func Bolt_Open(readOnly bool, path string) (*bolt.DB, error) {
|
|
opts := *bolt.DefaultOptions
|
|
opts.Timeout = 10 * time.Second
|
|
opts.ReadOnly = readOnly
|
|
|
|
return bolt.Open(path, os.FileMode(0644), &opts)
|
|
}
|
|
|
|
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(db *bolt.DB, browse []string, fn func(tx *bolt.Tx, bucket *bolt.Bucket) error) error {
|
|
if len(browse) == 0 {
|
|
// not a bucket
|
|
return errors.New("No bucket selected")
|
|
}
|
|
|
|
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(tx, bucket)
|
|
|
|
})
|
|
}
|
|
|
|
func Bolt_CreateBucket(db *bolt.DB, browse []string, newBucket string) 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
|
|
}
|
|
})
|
|
}
|
|
|
|
func Bolt_DeleteBucket(db *bolt.DB, browse []string, delBucket string) 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))
|
|
}
|
|
})
|
|
}
|
|
|
|
func Bolt_SetItem(db *bolt.DB, browse []string, key, val string) error {
|
|
if len(browse) == 0 {
|
|
return errors.New("Can't create top-level items")
|
|
}
|
|
|
|
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))
|
|
})
|
|
}
|
|
|
|
func Bolt_DeleteItem(db *bolt.DB, browse []string, key string) error {
|
|
if len(browse) == 0 {
|
|
return errors.New("Can't create top-level items")
|
|
}
|
|
|
|
return db.Update(func(tx *bolt.Tx) error {
|
|
|
|
bucket, err := walkBuckets(tx, browse)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return bucket.Delete([]byte(key))
|
|
})
|
|
}
|
|
|
|
func Bolt_DBStats(db *bolt.DB) (string, error) {
|
|
jBytes, err := json.MarshalIndent(db.Stats(), "", " ")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(jBytes), nil
|
|
}
|
|
|
|
func Bolt_BucketStats(db *bolt.DB, browse []string) (string, error) {
|
|
var stats bolt.BucketStats
|
|
|
|
err := withBrowse_ReadOnly(db, browse, func(tx *bolt.Tx, bucket *bolt.Bucket) error {
|
|
stats = bucket.Stats()
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
jBytes, err := json.MarshalIndent(stats, "", " ")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return string(jBytes), err
|
|
}
|
|
|
|
func Bolt_ListBuckets(db *bolt.DB, browse []string, cb func(b string)) error {
|
|
|
|
if len(browse) == 0 {
|
|
// root mode
|
|
return db.View(func(tx *bolt.Tx) error {
|
|
return tx.ForEach(func(k []byte, _ *bolt.Bucket) error {
|
|
cb(string(k))
|
|
return nil
|
|
})
|
|
})
|
|
|
|
}
|
|
|
|
// Nested-mode
|
|
return withBrowse_ReadOnly(db, browse, func(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 {
|
|
cb(string(k))
|
|
}
|
|
return nil
|
|
})
|
|
})
|
|
}
|
|
|
|
type ListItemInfo struct {
|
|
Name string
|
|
DataLen int64
|
|
}
|
|
|
|
func Bolt_ListItems(db *bolt.DB, browse []string, cb func(ListItemInfo) error) error {
|
|
|
|
if len(browse) == 0 {
|
|
return errors.New("No bucket specified")
|
|
}
|
|
|
|
// Nested-mode
|
|
return withBrowse_ReadOnly(db, browse, func(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
|
|
}
|
|
|
|
return cb(ListItemInfo{string(k), int64(len(v))})
|
|
})
|
|
})
|
|
}
|
|
|
|
func Bolt_GetItem(db *bolt.DB, browse []string, key string) (string, error) {
|
|
var ret string
|
|
|
|
err := withBrowse_ReadOnly(db, browse, func(tx *bolt.Tx, bucket *bolt.Bucket) error {
|
|
d := bucket.Get([]byte(key))
|
|
ret = string(d)
|
|
return nil
|
|
})
|
|
return ret, err
|
|
}
|
|
|
|
func Bolt_Close(db *bolt.DB) error {
|
|
return db.Close()
|
|
}
|