Compare commits

...

5 Commits

8 changed files with 568 additions and 228 deletions

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -16,28 +17,117 @@ type BoltDB struct {
buckets []BoltBucket buckets []BoltBucket
} }
type PathNode struct {
name []byte
dataType Datatype
}
/* /*
BoltBucket is just a struct representation of a Bucket in the Bolt DB BoltBucket is just a struct representation of a Bucket in the Bolt DB
*/ */
type BoltBucket struct { type BoltBucket struct {
name string name []byte
pairs []BoltPair nameDatatype Datatype
buckets []BoltBucket pairs []BoltPair
parent *BoltBucket buckets []BoltBucket
expanded bool parent *BoltBucket
errorFlag bool expanded bool
errorFlag bool
}
func NewBoltBucket(parent *BoltBucket, name []byte) *BoltBucket {
ret := new(BoltBucket)
ret.parent = parent
ret.name = name
for _, dtk := range dataTypeNameSlice {
if _, err := datatypes[dtk].ToString(name); err == nil {
ret.nameDatatype = datatypes[dtk]
break
}
}
return ret
}
func (b *BoltBucket) GetPathNode() PathNode {
return PathNode{
name: b.name,
dataType: b.nameDatatype,
}
}
func (b *BoltBucket) SetName(nm []byte) {
b.name = nm
} }
/* /*
BoltPair is just a struct representation of a Pair in the Bolt DB BoltPair is just a struct representation of a Pair in the Bolt DB
*/ */
type BoltPair struct { type BoltPair struct {
parent *BoltBucket parent *BoltBucket
key string key []byte
val string val []byte
keyDatatype Datatype
valDatatype Datatype
} }
func (bd *BoltDB) getGenericFromPath(path []string) (*BoltBucket, *BoltPair, error) { func NewBoltPair(parent *BoltBucket, key, val []byte) *BoltPair {
tp := BoltPair{
parent: parent,
key: key,
val: val,
}
for _, dtk := range dataTypeNameSlice {
if _, err := datatypes[dtk].ToString(key); err == nil {
tp.keyDatatype = datatypes[dtk]
break
}
}
for _, dtk := range dataTypeNameSlice {
if _, err := datatypes[dtk].ToString(val); err == nil {
tp.valDatatype = datatypes[dtk]
break
}
}
return &tp
}
func (p *BoltPair) GetPathNode() PathNode {
return PathNode{
name: p.key,
dataType: p.keyDatatype,
}
}
/*
GetStringKey returns the key of the pair after passing it through it's
Datatype ToString function
*/
func (p *BoltPair) GetStringKey() string {
ret, err := p.keyDatatype.ToString(p.key)
if err != nil {
return "ERROR"
}
return ret
}
/*
GetStringVal returns the val of the pair after passing it through it's
Datatype ToString function
*/
func (p *BoltPair) GetStringVal() string {
ret, err := p.valDatatype.ToString(p.val)
if err != nil {
return "ERROR"
}
return ret
}
func (bd *BoltDB) getGenericFromStringPath(path []PathNode) (*BoltBucket, *BoltPair, error) {
return bd.getGenericFromPath(path)
}
func (bd *BoltDB) getGenericFromPath(path []PathNode) (*BoltBucket, *BoltPair, error) {
// Check if 'path' leads to a pair // Check if 'path' leads to a pair
p, err := bd.getPairFromPath(path) p, err := bd.getPairFromPath(path)
if err == nil { if err == nil {
@ -52,19 +142,19 @@ func (bd *BoltDB) getGenericFromPath(path []string) (*BoltBucket, *BoltPair, err
return nil, nil, errors.New("Invalid Path") return nil, nil, errors.New("Invalid Path")
} }
func (bd *BoltDB) getBucketFromPath(path []string) (*BoltBucket, error) { func (bd *BoltDB) getBucketFromPath(path []PathNode) (*BoltBucket, error) {
if len(path) > 0 { if len(path) > 0 {
// Find the BoltBucket with a path == path // Find the BoltBucket with a path == path
var b *BoltBucket var b *BoltBucket
var err error var err error
// Find the root bucket // Find the root bucket
b, err = memBolt.getBucket(path[0]) b, err = memBolt.getBucket(path[0].name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(path) > 1 { if len(path) > 1 {
for p := 1; p < len(path); p++ { for p := 1; p < len(path); p++ {
b, err = b.getBucket(path[p]) b, err = b.getBucket(path[p].name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -75,7 +165,7 @@ func (bd *BoltDB) getBucketFromPath(path []string) (*BoltBucket, error) {
return nil, errors.New("Invalid Path") return nil, errors.New("Invalid Path")
} }
func (bd *BoltDB) getPairFromPath(path []string) (*BoltPair, error) { func (bd *BoltDB) getPairFromPath(path []PathNode) (*BoltPair, error) {
if len(path) <= 0 { if len(path) <= 0 {
return nil, errors.New("No Path") return nil, errors.New("No Path")
} }
@ -84,11 +174,11 @@ func (bd *BoltDB) getPairFromPath(path []string) (*BoltPair, error) {
return nil, err return nil, err
} }
// Found the bucket, pull out the pair // Found the bucket, pull out the pair
p, err := b.getPair(path[len(path)-1]) p, err := b.getPair(path[len(path)-1].name)
return p, err return p, err
} }
func (bd *BoltDB) getVisibleItemCount(path []string) (int, error) { func (bd *BoltDB) getVisibleItemCount(path []PathNode) (int, error) {
vis := 0 vis := 0
var retErr error var retErr error
if len(path) == 0 { if len(path) == 0 {
@ -123,12 +213,12 @@ func (bd *BoltDB) getVisibleItemCount(path []string) (int, error) {
return vis, retErr return vis, retErr
} }
func (bd *BoltDB) buildVisiblePathSlice() ([][]string, error) { func (bd *BoltDB) buildVisiblePathSlice() ([][]PathNode, error) {
var retSlice [][]string var retSlice [][]PathNode
var retErr error var retErr error
// The root path, recurse for root buckets // The root path, recurse for root buckets
for i := range bd.buckets { for i := range bd.buckets {
bktS, bktErr := bd.buckets[i].buildVisiblePathSlice([]string{}) bktS, bktErr := bd.buckets[i].buildVisiblePathSlice([]PathNode{})
if bktErr == nil { if bktErr == nil {
retSlice = append(retSlice, bktS...) retSlice = append(retSlice, bktS...)
} else { } else {
@ -139,7 +229,7 @@ func (bd *BoltDB) buildVisiblePathSlice() ([][]string, error) {
return retSlice, retErr return retSlice, retErr
} }
func (bd *BoltDB) getPrevVisiblePath(path []string) []string { func (bd *BoltDB) getPrevVisiblePath(path []PathNode) []PathNode {
visPaths, err := bd.buildVisiblePathSlice() visPaths, err := bd.buildVisiblePathSlice()
if path == nil { if path == nil {
if len(visPaths) > 0 { if len(visPaths) > 0 {
@ -151,7 +241,7 @@ func (bd *BoltDB) getPrevVisiblePath(path []string) []string {
for idx, pth := range visPaths { for idx, pth := range visPaths {
isCurPath := true isCurPath := true
for i := range path { for i := range path {
if len(pth) <= i || path[i] != pth[i] { if len(pth) <= i || !bytes.Equal(path[i].name, pth[i].name) {
isCurPath = false isCurPath = false
break break
} }
@ -163,7 +253,7 @@ func (bd *BoltDB) getPrevVisiblePath(path []string) []string {
} }
return nil return nil
} }
func (bd *BoltDB) getNextVisiblePath(path []string) []string { func (bd *BoltDB) getNextVisiblePath(path []PathNode) []PathNode {
visPaths, err := bd.buildVisiblePathSlice() visPaths, err := bd.buildVisiblePathSlice()
if path == nil { if path == nil {
if len(visPaths) > 0 { if len(visPaths) > 0 {
@ -175,7 +265,7 @@ func (bd *BoltDB) getNextVisiblePath(path []string) []string {
for idx, pth := range visPaths { for idx, pth := range visPaths {
isCurPath := true isCurPath := true
for i := range path { for i := range path {
if len(pth) <= i || path[i] != pth[i] { if len(pth) <= i || !bytes.Equal(path[i].name, pth[i].name) {
isCurPath = false isCurPath = false
break break
} }
@ -188,7 +278,7 @@ func (bd *BoltDB) getNextVisiblePath(path []string) []string {
return nil return nil
} }
func (bd *BoltDB) toggleOpenBucket(path []string) error { func (bd *BoltDB) toggleOpenBucket(path []PathNode) error {
// Find the BoltBucket with a path == path // Find the BoltBucket with a path == path
b, err := bd.getBucketFromPath(path) b, err := bd.getBucketFromPath(path)
if err == nil { if err == nil {
@ -197,7 +287,7 @@ func (bd *BoltDB) toggleOpenBucket(path []string) error {
return err return err
} }
func (bd *BoltDB) closeBucket(path []string) error { func (bd *BoltDB) closeBucket(path []PathNode) error {
// Find the BoltBucket with a path == path // Find the BoltBucket with a path == path
b, err := bd.getBucketFromPath(path) b, err := bd.getBucketFromPath(path)
if err == nil { if err == nil {
@ -206,7 +296,7 @@ func (bd *BoltDB) closeBucket(path []string) error {
return err return err
} }
func (bd *BoltDB) openBucket(path []string) error { func (bd *BoltDB) openBucket(path []PathNode) error {
// Find the BoltBucket with a path == path // Find the BoltBucket with a path == path
b, err := bd.getBucketFromPath(path) b, err := bd.getBucketFromPath(path)
if err == nil { if err == nil {
@ -215,9 +305,9 @@ func (bd *BoltDB) openBucket(path []string) error {
return err return err
} }
func (bd *BoltDB) getBucket(k string) (*BoltBucket, error) { func (bd *BoltDB) getBucket(k []byte) (*BoltBucket, error) {
for i := range bd.buckets { for i := range bd.buckets {
if bd.buckets[i].name == k { if bytes.Equal(bd.buckets[i].name, k) {
return &bd.buckets[i], nil return &bd.buckets[i], nil
} }
} }
@ -235,7 +325,7 @@ func (bd *BoltDB) syncOpenBuckets(shadow *BoltDB) {
// First test this bucket // First test this bucket
for i := range bd.buckets { for i := range bd.buckets {
for j := range shadow.buckets { for j := range shadow.buckets {
if bd.buckets[i].name == shadow.buckets[j].name { if bytes.Equal(bd.buckets[i].name, shadow.buckets[j].name) {
bd.buckets[i].syncOpenBuckets(&shadow.buckets[j]) bd.buckets[i].syncOpenBuckets(&shadow.buckets[j])
} }
} }
@ -247,9 +337,9 @@ func (bd *BoltDB) refreshDatabase() *BoltDB {
memBolt = new(BoltDB) memBolt = new(BoltDB)
db.View(func(tx *bolt.Tx) error { db.View(func(tx *bolt.Tx) error {
return tx.ForEach(func(nm []byte, b *bolt.Bucket) error { return tx.ForEach(func(nm []byte, b *bolt.Bucket) error {
bb, err := readBucket(b) bb, err := readBucket(nil, b, nm)
if err == nil { if err == nil {
bb.name = string(nm) bb.SetName(nm)
bb.expanded = false bb.expanded = false
memBolt.buckets = append(memBolt.buckets, *bb) memBolt.buckets = append(memBolt.buckets, *bb)
return nil return nil
@ -261,27 +351,41 @@ func (bd *BoltDB) refreshDatabase() *BoltDB {
} }
/* /*
GetPath returns the database path leading to this BoltBucket GetStringName returns the name of the bucket after passing it through it's
Datatype ToString function
*/ */
func (b *BoltBucket) GetPath() []string { func (b *BoltBucket) GetStringName() string {
if b.parent != nil { ret, err := b.nameDatatype.ToString(b.name)
return append(b.parent.GetPath(), b.name) if err != nil {
return "ERROR"
} }
return []string{b.name} return ret
} }
/* /*
buildVisiblePathSlice builds a slice of string slices containing all visible paths in this bucket GetPath returns the database path leading to this BoltBucket
*/
func (b *BoltBucket) GetPath() []PathNode {
bktPath := b.GetPathNode()
if b.parent != nil {
return append(b.parent.GetPath(), bktPath)
}
return []PathNode{bktPath}
}
/*
buildVisiblePathSlice builds a slice of PathNode slices containing all visible paths in this bucket
The passed prefix is the path leading to the current bucket The passed prefix is the path leading to the current bucket
*/ */
func (b *BoltBucket) buildVisiblePathSlice(prefix []string) ([][]string, error) { func (b *BoltBucket) buildVisiblePathSlice(prefix []PathNode) ([][]PathNode, error) {
var retSlice [][]string var retSlice [][]PathNode
var retErr error var retErr error
retSlice = append(retSlice, append(prefix, b.name)) bucketNode := b.GetPathNode()
retSlice = append(retSlice, append(prefix, bucketNode))
if b.expanded { if b.expanded {
// Add subbuckets // Add subbuckets
for i := range b.buckets { for i := range b.buckets {
bktS, bktErr := b.buckets[i].buildVisiblePathSlice(append(prefix, b.name)) bktS, bktErr := b.buckets[i].buildVisiblePathSlice(append(prefix, bucketNode))
if bktErr != nil { if bktErr != nil {
return retSlice, bktErr return retSlice, bktErr
} }
@ -289,7 +393,7 @@ func (b *BoltBucket) buildVisiblePathSlice(prefix []string) ([][]string, error)
} }
// Add pairs // Add pairs
for i := range b.pairs { for i := range b.pairs {
retSlice = append(retSlice, append(append(prefix, b.name), b.pairs[i].key)) retSlice = append(retSlice, append(append(prefix, bucketNode, b.pairs[i].GetPathNode())))
} }
} }
return retSlice, retErr return retSlice, retErr
@ -300,7 +404,7 @@ func (b *BoltBucket) syncOpenBuckets(shadow *BoltBucket) {
b.expanded = shadow.expanded b.expanded = shadow.expanded
for i := range b.buckets { for i := range b.buckets {
for j := range shadow.buckets { for j := range shadow.buckets {
if b.buckets[i].name == shadow.buckets[j].name { if bytes.Equal(b.buckets[i].name, shadow.buckets[j].name) {
b.buckets[i].syncOpenBuckets(&shadow.buckets[j]) b.buckets[i].syncOpenBuckets(&shadow.buckets[j])
} }
} }
@ -314,18 +418,18 @@ func (b *BoltBucket) openAllBuckets() {
} }
} }
func (b *BoltBucket) getBucket(k string) (*BoltBucket, error) { func (b *BoltBucket) getBucket(k []byte) (*BoltBucket, error) {
for i := range b.buckets { for i := range b.buckets {
if b.buckets[i].name == k { if bytes.Equal(b.buckets[i].name, k) {
return &b.buckets[i], nil return &b.buckets[i], nil
} }
} }
return nil, errors.New("Bucket Not Found") return nil, errors.New("Bucket Not Found")
} }
func (b *BoltBucket) getPair(k string) (*BoltPair, error) { func (b *BoltBucket) getPair(k []byte) (*BoltPair, error) {
for i := range b.pairs { for i := range b.pairs {
if b.pairs[i].key == k { if bytes.Equal(b.pairs[i].key, k) {
return &b.pairs[i], nil return &b.pairs[i], nil
} }
} }
@ -335,8 +439,14 @@ func (b *BoltBucket) getPair(k string) (*BoltPair, error) {
/* /*
GetPath Returns the path of the BoltPair GetPath Returns the path of the BoltPair
*/ */
func (p *BoltPair) GetPath() []string { func (p *BoltPair) GetPath() []PathNode {
return append(p.parent.GetPath(), p.key) if p.parent == nil {
return []PathNode{p.GetPathNode()}
}
return append(
p.parent.GetPath(),
p.GetPathNode(),
)
} }
/* This is a go-between function (between the boltbrowser structs /* This is a go-between function (between the boltbrowser structs
@ -346,9 +456,9 @@ func (p *BoltPair) GetPath() []string {
* Mainly used for moving a bucket from one path to another * Mainly used for moving a bucket from one path to another
* as in the 'renameBucket' function below. * as in the 'renameBucket' function below.
*/ */
func addBucketFromBoltBucket(path []string, bb *BoltBucket) error { func addBucketFromBoltBucket(path []PathNode, bb *BoltBucket) error {
if err := insertBucket(path, bb.name); err == nil { if err := insertBucket(path, bb.name); err == nil {
bucketPath := append(path, bb.name) bucketPath := append(path, bb.GetPathNode())
for i := range bb.pairs { for i := range bb.pairs {
if err = insertPair(bucketPath, bb.pairs[i].key, bb.pairs[i].val); err != nil { if err = insertPair(bucketPath, bb.pairs[i].key, bb.pairs[i].val); err != nil {
return err return err
@ -363,7 +473,7 @@ func addBucketFromBoltBucket(path []string, bb *BoltBucket) error {
return nil return nil
} }
func deleteKey(path []string) error { func deleteKey(path []PathNode) error {
if AppArgs.ReadOnly { if AppArgs.ReadOnly {
return errors.New("DB is in Read-Only Mode") return errors.New("DB is in Read-Only Mode")
} }
@ -372,13 +482,13 @@ func deleteKey(path []string) error {
// the rest are buckets leading to that key // the rest are buckets leading to that key
if len(path) == 1 { if len(path) == 1 {
// Deleting a root bucket // Deleting a root bucket
return tx.DeleteBucket([]byte(path[0])) return tx.DeleteBucket(path[0].name)
} }
b := tx.Bucket([]byte(path[0])) b := tx.Bucket(path[0].name)
if b != nil { if b != nil {
if len(path) > 1 { if len(path) > 1 {
for i := range path[1 : len(path)-1] { for i := range path[1 : len(path)-1] {
b = b.Bucket([]byte(path[i+1])) b = b.Bucket(path[i+1].name)
if b == nil { if b == nil {
return errors.New("deleteKey: Invalid Path") return errors.New("deleteKey: Invalid Path")
} }
@ -386,11 +496,11 @@ func deleteKey(path []string) error {
} }
// Now delete the last key in the path // Now delete the last key in the path
var err error var err error
if deleteBkt := b.Bucket([]byte(path[len(path)-1])); deleteBkt == nil { if deleteBkt := b.Bucket(path[len(path)-1].name); deleteBkt == nil {
// Must be a pair // Must be a pair
err = b.Delete([]byte(path[len(path)-1])) err = b.Delete(path[len(path)-1].name)
} else { } else {
err = b.DeleteBucket([]byte(path[len(path)-1])) err = b.DeleteBucket(path[len(path)-1].name)
} }
return err return err
} }
@ -399,71 +509,41 @@ func deleteKey(path []string) error {
return err return err
} }
func readBucket(b *bolt.Bucket) (*BoltBucket, error) { // Recursively read a bucket and pairs from the DB
bb := new(BoltBucket) func readBucket(parent *BoltBucket, b *bolt.Bucket, nm []byte) (*BoltBucket, error) {
bb := NewBoltBucket(parent, nm)
b.ForEach(func(k, v []byte) error { b.ForEach(func(k, v []byte) error {
if v == nil { if v == nil {
tb, err := readBucket(b.Bucket(k)) tb, err := readBucket(bb, b.Bucket(k), k)
tb.parent = bb
if err == nil { if err == nil {
tb.name = string(k)
bb.buckets = append(bb.buckets, *tb) bb.buckets = append(bb.buckets, *tb)
} }
} else { } else {
tp := BoltPair{key: string(k), val: string(v)} bb.pairs = append(bb.pairs, *NewBoltPair(bb, k, v))
tp.parent = bb
bb.pairs = append(bb.pairs, tp)
} }
return nil return nil
}) })
return bb, nil return bb, nil
} }
func renameBucket(path []string, name string) error { func renameBucket(path []PathNode, name []byte) error {
if name == path[len(path)-1] { if bytes.Equal(name, path[len(path)-1].name) {
// No change requested // No change requested
return nil return nil
} }
var bb *BoltBucket // For caching the current bucket var bb *BoltBucket // For caching the current bucket
err := db.View(func(tx *bolt.Tx) error { bb, err := memBolt.getBucketFromPath(path)
// len(b.path)-1 is the key we need to delete, if err != nil {
// the rest are buckets leading to that key return err
b := tx.Bucket([]byte(path[0])) }
if b != nil {
if len(path) > 1 {
for i := range path[1:len(path)] {
b = b.Bucket([]byte(path[i+1]))
if b == nil {
return errors.New("renameBucket: Invalid Path")
}
}
}
var err error
// Ok, cache b
bb, err = readBucket(b)
if err != nil {
return err
}
} else {
return errors.New("renameBucket: Invalid Bucket")
}
return nil
})
if err != nil {
return err
}
if bb == nil {
return errors.New("renameBucket: Couldn't find Bucket")
}
// Ok, we have the bucket cached, now delete the current instance // Ok, we have the bucket, now delete the current instance
if err = deleteKey(path); err != nil { if err = deleteKey(path); err != nil {
return err return err
} }
// Rechristen our cached bucket // Rechristen our cached bucket
bb.name = name bb.SetName(name)
// And re-add it // And re-add it
parentPath := path[:len(path)-1] parentPath := path[:len(path)-1]
if err = addBucketFromBoltBucket(parentPath, bb); err != nil { if err = addBucketFromBoltBucket(parentPath, bb); err != nil {
return err return err
@ -471,29 +551,29 @@ func renameBucket(path []string, name string) error {
return nil return nil
} }
func updatePairKey(path []string, k string) error { func updatePairKey(path []PathNode, k []byte) error {
if AppArgs.ReadOnly { if AppArgs.ReadOnly {
return errors.New("DB is in Read-Only Mode") return errors.New("DB is in Read-Only Mode")
} }
err := db.Update(func(tx *bolt.Tx) error { err := db.Update(func(tx *bolt.Tx) error {
// len(b.path)-1 is the key for the pair we're updating, // len(b.path)-1 is the key for the pair we're updating,
// the rest are buckets leading to that key // the rest are buckets leading to that key
b := tx.Bucket([]byte(path[0])) b := tx.Bucket(path[0].name)
if b != nil { if b != nil {
if len(path) > 0 { if len(path) > 0 {
for i := range path[1 : len(path)-1] { for i := range path[1 : len(path)-1] {
b = b.Bucket([]byte(path[i+1])) b = b.Bucket(path[i+1].name)
if b == nil { if b == nil {
return errors.New("updatePairValue: Invalid Path") return errors.New("updatePairValue: Invalid Path")
} }
} }
} }
bk := []byte(path[len(path)-1]) bk := path[len(path)-1]
v := b.Get(bk) v := b.Get(bk.name)
err := b.Delete(bk) err := b.Delete(bk.name)
if err == nil { if err == nil {
// Old pair has been deleted, now add the new one // Old pair has been deleted, now add the new one
err = b.Put([]byte(k), v) err = b.Put(k, v)
} }
// Now update the last key in the path // Now update the last key in the path
return err return err
@ -503,25 +583,25 @@ func updatePairKey(path []string, k string) error {
return err return err
} }
func updatePairValue(path []string, v string) error { func updatePairValue(path []PathNode, v []byte) error {
if AppArgs.ReadOnly { if AppArgs.ReadOnly {
return errors.New("DB is in Read-Only Mode") return errors.New("DB is in Read-Only Mode")
} }
err := db.Update(func(tx *bolt.Tx) error { err := db.Update(func(tx *bolt.Tx) error {
// len(b.GetPath())-1 is the key for the pair we're updating, // len(b.GetPath())-1 is the key for the pair we're updating,
// the rest are buckets leading to that key // the rest are buckets leading to that key
b := tx.Bucket([]byte(path[0])) b := tx.Bucket(path[0].name)
if b != nil { if b != nil {
if len(path) > 0 { if len(path) > 0 {
for i := range path[1 : len(path)-1] { for i := range path[1 : len(path)-1] {
b = b.Bucket([]byte(path[i+1])) b = b.Bucket(path[i+1].name)
if b == nil { if b == nil {
return errors.New("updatePairValue: Invalid Path") return errors.New("updatePairValue: Invalid Path")
} }
} }
} }
// Now update the last key in the path // Now update the last key in the path
err := b.Put([]byte(path[len(path)-1]), []byte(v)) err := b.Put(path[len(path)-1].name, v)
return err return err
} }
return errors.New("updatePairValue: Invalid Path") return errors.New("updatePairValue: Invalid Path")
@ -529,7 +609,7 @@ func updatePairValue(path []string, v string) error {
return err return err
} }
func insertBucket(path []string, n string) error { func insertBucket(path []PathNode, n []byte) error {
if AppArgs.ReadOnly { if AppArgs.ReadOnly {
return errors.New("DB is in Read-Only Mode") return errors.New("DB is in Read-Only Mode")
} }
@ -537,18 +617,18 @@ func insertBucket(path []string, n string) error {
err := db.Update(func(tx *bolt.Tx) error { err := db.Update(func(tx *bolt.Tx) error {
if len(path) == 0 { if len(path) == 0 {
// insert at root // insert at root
_, err := tx.CreateBucket([]byte(n)) _, err := tx.CreateBucket(n)
if err != nil { if err != nil {
return fmt.Errorf("insertBucket: %s", err) return fmt.Errorf("insertBucket: %s", err)
} }
} else { } else {
rootBucket, path := path[0], path[1:] rootBucket, path := path[0], path[1:]
b := tx.Bucket([]byte(rootBucket)) b := tx.Bucket(rootBucket.name)
if b != nil { if b != nil {
for len(path) > 0 { for len(path) > 0 {
tstBucket := "" var tstBucket PathNode
tstBucket, path = path[0], path[1:] tstBucket, path = path[0], path[1:]
nB := b.Bucket([]byte(tstBucket)) nB := b.Bucket(tstBucket.name)
if nB == nil { if nB == nil {
// Not a bucket, if we're out of path, just move on // Not a bucket, if we're out of path, just move on
if len(path) != 0 { if len(path) != 0 {
@ -559,7 +639,7 @@ func insertBucket(path []string, n string) error {
b = nB b = nB
} }
} }
_, err := b.CreateBucket([]byte(n)) _, err := b.CreateBucket(n)
return err return err
} }
return fmt.Errorf("insertBucket: Invalid Path %s", rootBucket) return fmt.Errorf("insertBucket: Invalid Path %s", rootBucket)
@ -569,7 +649,7 @@ func insertBucket(path []string, n string) error {
return err return err
} }
func insertPair(path []string, k string, v string) error { func insertPair(path []PathNode, k []byte, v []byte) error {
if AppArgs.ReadOnly { if AppArgs.ReadOnly {
return errors.New("DB is in Read-Only Mode") return errors.New("DB is in Read-Only Mode")
} }
@ -580,17 +660,17 @@ func insertPair(path []string, k string, v string) error {
return errors.New("insertPair: Cannot insert pair at root") return errors.New("insertPair: Cannot insert pair at root")
} }
var err error var err error
b := tx.Bucket([]byte(path[0])) b := tx.Bucket(path[0].name)
if b != nil { if b != nil {
if len(path) > 0 { if len(path) > 0 {
for i := 1; i < len(path); i++ { for i := 1; i < len(path); i++ {
b = b.Bucket([]byte(path[i])) b = b.Bucket(path[i].name)
if b == nil { if b == nil {
return fmt.Errorf("insertPair: %s", err) return fmt.Errorf("insertPair: %s", err)
} }
} }
} }
err := b.Put([]byte(k), []byte(v)) err := b.Put(k, v)
if err != nil { if err != nil {
return fmt.Errorf("insertPair: %s", err) return fmt.Errorf("insertPair: %s", err)
} }
@ -600,48 +680,80 @@ func insertPair(path []string, k string, v string) error {
return err return err
} }
func exportValue(path []string, fName string) error { func stringPathToBytePath(path []string) [][]byte {
var ret [][]byte
for _, v := range path {
ret = append(ret, []byte(v))
}
return ret
}
func bytePathToStringPath(path [][]byte) []string {
var ret []string
for _, v := range path {
ret = append(ret, string(v))
}
return ret
}
func nodePathToStringPath(path []PathNode) []string {
var ret []string
for _, v := range path {
add, err := v.dataType.ToString(v.name)
if err != nil {
add = "ERROR"
}
ret = append(ret, add)
}
return ret
}
func exportValue(path []PathNode, fName string) error {
return db.View(func(tx *bolt.Tx) error { return db.View(func(tx *bolt.Tx) error {
// len(b.path)-1 is the key whose value we want to export // len(b.path)-1 is the key whose value we want to export
// the rest are buckets leading to that key // the rest are buckets leading to that key
b := tx.Bucket([]byte(path[0])) b := tx.Bucket(path[0].name)
if b != nil { if b != nil {
if len(path) > 1 { if len(path) > 1 {
for i := range path[1 : len(path)-1] { for i := range path[1 : len(path)-1] {
b = b.Bucket([]byte(path[i+1])) b = b.Bucket(path[i+1].name)
if b == nil { if b == nil {
return errors.New("exportValue: Invalid Path: " + strings.Join(path, "/")) return errors.New("exportValue: Invalid Path: " + strings.Join(nodePathToStringPath(path), "/"))
} }
} }
} }
bk := []byte(path[len(path)-1]) bk := path[len(path)-1]
v := b.Get(bk) v := b.Get(bk.name)
return writeToFile(fName, string(v)+"\n", os.O_CREATE|os.O_WRONLY|os.O_TRUNC) return writeToFile(fName, string(v)+"\n", os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
} }
return errors.New("exportValue: Invalid Bucket") return errors.New("exportValue: Invalid Bucket")
}) })
} }
func exportJSON(path []string, fName string) error { func exportJSON(path []PathNode, fName string) error {
return db.View(func(tx *bolt.Tx) error { return db.View(func(tx *bolt.Tx) error {
// len(b.path)-1 is the key whose value we want to export // len(b.path)-1 is the key whose value we want to export
// the rest are buckets leading to that key // the rest are buckets leading to that key
b := tx.Bucket([]byte(path[0])) b := tx.Bucket(path[0].name)
if b != nil { if b != nil {
if len(path) > 1 { if len(path) > 1 {
for i := range path[1 : len(path)-1] { for i := range path[1 : len(path)-1] {
b = b.Bucket([]byte(path[i+1])) b = b.Bucket(path[i+1].name)
if b == nil { if b == nil {
return errors.New("exportValue: Invalid Path: " + strings.Join(path, "/")) return errors.New("exportValue: Invalid Path: " + strings.Join(nodePathToStringPath(path), "/"))
} }
} }
} }
bk := []byte(path[len(path)-1]) bk := path[len(path)-1]
if v := b.Get(bk); v != nil { if v := b.Get(bk.name); v != nil {
return writeToFile(fName, "{\""+string(bk)+"\":\""+string(v)+"\"}", os.O_CREATE|os.O_WRONLY|os.O_TRUNC) str, err := bk.dataType.ToString(bk.name)
if err != nil {
str = "ERROR"
}
return writeToFile(fName, "{\""+str+"\":\""+string(v)+"\"}", os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
} }
if b.Bucket(bk) != nil { if b.Bucket(bk.name) != nil {
return writeToFile(fName, genJSONString(b.Bucket(bk)), os.O_CREATE|os.O_WRONLY|os.O_TRUNC) return writeToFile(fName, genJSONString(b.Bucket(bk.name)), os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
} }
return writeToFile(fName, genJSONString(b), os.O_CREATE|os.O_WRONLY|os.O_TRUNC) return writeToFile(fName, genJSONString(b), os.O_CREATE|os.O_WRONLY|os.O_TRUNC)
} }

9
datatype.go Normal file
View File

@ -0,0 +1,9 @@
package main
// A Datatype interface knows how to convert a byte slice into a
// specific data type.
type Datatype interface {
Name() string // Type Name, for UI display
ToString([]byte) (string, error) // ToString takes a []byte from the DB and returns the string to display
FromString(string) ([]byte, error) // FromString takes a string and returns the []byte for the DB
}

39
datatype_int.go Normal file
View File

@ -0,0 +1,39 @@
package main
import (
"fmt"
"encoding/binary"
"strconv"
)
// Datatype_Int manages the int data type
type Datatype_Int64 struct{}
func (t *Datatype_Int64) Name() string {
return "int"
}
func (t *Datatype_Int64) ToString(data []byte) (string, error) {
return fmt.Sprintf("%d", t.btoi(data)), nil
}
func (t *Datatype_Int64) FromString(val string) ([]byte, error) {
v, err := strconv.ParseInt(val, 10, 64)
if err != nil {
return []byte{}, err
}
return t.itob(v), nil
}
func (t *Datatype_Int64) itob(v int64) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(v))
return b
}
func (t *Datatype_Int64) btoi(b []byte) int64 {
if len(b) < 8 {
return 0
}
return int64(binary.BigEndian.Uint64(b))
}

37
datatype_string.go Normal file
View File

@ -0,0 +1,37 @@
package main
import (
"errors"
"unicode/utf8"
)
// Datatype_String is basically the default type for
// boltbrowser (and the boltease library)
type Datatype_String struct{}
func (t *Datatype_String) Name() string {
return "string"
}
func (t *Datatype_String) ToString(data []byte) (string, error) {
if utf8.Valid(data) {
ok := true
for _, r := range string(data) {
if r < 0x20 {
ok = false
break
} else if r >= 0x7f && r <= 0x9f {
ok = false
break
}
}
if ok {
return string(data), nil
}
}
return "", errors.New("Invalid String Data")
}
func (t *Datatype_String) FromString(val string) ([]byte, error) {
return []byte(val), nil
}

37
datatype_uint.go Normal file
View File

@ -0,0 +1,37 @@
package main
import (
"encoding/binary"
"errors"
"strconv"
)
// Datatype_Uint64 manages the uint64 data type
// All conversions just use the binary package
type Datatype_Uint64 struct{}
func (t *Datatype_Uint64) Name() string {
return "uint64"
}
func (t *Datatype_Uint64) ToString(data []byte) (string, error) {
ret, bt := binary.Uvarint(data)
if bt == 0 {
return "", errors.New("Byte Buffer too small")
} else if bt < 0 {
return "", errors.New("Value larger than 64 bits")
}
return strconv.FormatUint(ret, 10), nil
}
func (t *Datatype_Uint64) FromString(val string) ([]byte, error) {
var err error
var u uint64
buf := make([]byte, binary.MaxVarintLen64)
u, err = strconv.ParseUint(val, 10, 64)
if err != nil {
return nil, err
}
binary.PutUvarint(buf, u)
return buf, nil
}

16
main.go
View File

@ -18,6 +18,9 @@ var databaseFiles []string
var db *bolt.DB var db *bolt.DB
var memBolt *BoltDB var memBolt *BoltDB
var datatypes map[string]Datatype
var dataTypeNameSlice []string
var currentFilename string var currentFilename string
const DefaultDBOpenTimeout = time.Second const DefaultDBOpenTimeout = time.Second
@ -95,6 +98,7 @@ func main() {
var err error var err error
parseArgs() parseArgs()
initDataTypes()
err = termbox.Init() err = termbox.Init()
if err != nil { if err != nil {
@ -134,3 +138,15 @@ func main() {
defer db.Close() defer db.Close()
} }
} }
func initDataTypes() {
datatypes = make(map[string]Datatype)
for _, t := range []Datatype{
&Datatype_String{},
&Datatype_Int64{},
&Datatype_Uint64{},
} {
dataTypeNameSlice = append(dataTypeNameSlice, t.Name())
datatypes[t.Name()] = t
}
}

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"strings" "strings"
@ -28,7 +29,7 @@ type BrowserScreen struct {
leftViewPort ViewPort leftViewPort ViewPort
rightViewPort ViewPort rightViewPort ViewPort
queuedCommand string queuedCommand string
currentPath []string currentPath []PathNode
currentType int currentType int
message string message string
mode BrowserMode mode BrowserMode
@ -39,6 +40,7 @@ type BrowserScreen struct {
leftPaneBuffer []Line leftPaneBuffer []Line
rightPaneBuffer []Line rightPaneBuffer []Line
} }
/* /*
@ -149,7 +151,7 @@ func (screen *BrowserScreen) handleBrowseKeyEvent(event termbox.Event) int {
screen.startInsertItemAtParent(typeBucket) screen.startInsertItemAtParent(typeBucket)
} else if event.Ch == 'e' { } else if event.Ch == 'e' {
b, p, _ := screen.db.getGenericFromPath(screen.currentPath) b, p, _ := screen.db.getGenericFromStringPath(screen.currentPath)
if b != nil { if b != nil {
screen.setMessage("Cannot edit a bucket, did you mean to (r)ename?") screen.setMessage("Cannot edit a bucket, did you mean to (r)ename?")
} else if p != nil { } else if p != nil {
@ -160,7 +162,7 @@ func (screen *BrowserScreen) handleBrowseKeyEvent(event termbox.Event) int {
screen.startRenameItem() screen.startRenameItem()
} else if event.Key == termbox.KeyEnter { } else if event.Key == termbox.KeyEnter {
b, p, _ := screen.db.getGenericFromPath(screen.currentPath) b, p, _ := screen.db.getGenericFromStringPath(screen.currentPath)
if b != nil { if b != nil {
screen.db.toggleOpenBucket(screen.currentPath) screen.db.toggleOpenBucket(screen.currentPath)
} else if p != nil { } else if p != nil {
@ -168,7 +170,7 @@ func (screen *BrowserScreen) handleBrowseKeyEvent(event termbox.Event) int {
} }
} else if event.Ch == 'l' || event.Key == termbox.KeyArrowRight { } else if event.Ch == 'l' || event.Key == termbox.KeyArrowRight {
b, p, _ := screen.db.getGenericFromPath(screen.currentPath) b, p, _ := screen.db.getGenericFromStringPath(screen.currentPath)
// Select the current item // Select the current item
if b != nil { if b != nil {
screen.db.toggleOpenBucket(screen.currentPath) screen.db.toggleOpenBucket(screen.currentPath)
@ -180,7 +182,7 @@ func (screen *BrowserScreen) handleBrowseKeyEvent(event termbox.Event) int {
} else if event.Ch == 'h' || event.Key == termbox.KeyArrowLeft { } else if event.Ch == 'h' || event.Key == termbox.KeyArrowLeft {
// If we are _on_ a bucket that's open, close it // If we are _on_ a bucket that's open, close it
b, _, e := screen.db.getGenericFromPath(screen.currentPath) b, _, e := screen.db.getGenericFromStringPath(screen.currentPath)
if e == nil && b != nil && b.expanded { if e == nil && b != nil && b.expanded {
screen.db.closeBucket(screen.currentPath) screen.db.closeBucket(screen.currentPath)
} else { } else {
@ -204,6 +206,27 @@ func (screen *BrowserScreen) handleBrowseKeyEvent(event termbox.Event) int {
} else if event.Ch == 'X' { } else if event.Ch == 'X' {
// Export Key/Value (or Bucket) as JSON // Export Key/Value (or Bucket) as JSON
screen.startExportJSON() screen.startExportJSON()
} else if event.Ch == 't' {
// TODO: change the type of the pair value being viewed
p, err := screen.db.getPairFromPath(screen.currentPath)
if err != nil {
screen.setMessage("Cannot change pair type (did you mean 'T': Change pair key/bucket name type?)")
return BrowserScreenIndex
}
p.valDatatype = findNextDatatype(p.valDatatype.Name())
} else if event.Ch == 'T' {
b, p, err := screen.db.getGenericFromStringPath(screen.currentPath)
if err != nil {
screen.setMessage("Error reading path... Not sure what to do.")
return BrowserScreenIndex
}
if b != nil {
b.nameDatatype = findNextDatatype(b.nameDatatype.Name())
} else if p != nil {
p.keyDatatype = findNextDatatype(p.keyDatatype.Name())
}
} }
return BrowserScreenIndex return BrowserScreenIndex
} }
@ -215,38 +238,53 @@ func (screen *BrowserScreen) handleInputKeyEvent(event termbox.Event) int {
} else { } else {
screen.inputModal.HandleEvent(event) screen.inputModal.HandleEvent(event)
if screen.inputModal.IsDone() { if screen.inputModal.IsDone() {
b, p, _ := screen.db.getGenericFromPath(screen.currentPath) b, p, _ := screen.db.getGenericFromStringPath(screen.currentPath)
if b != nil { if b != nil {
if screen.mode == modeChangeKey { if screen.mode == modeChangeKey {
newName := screen.inputModal.GetValue() newName := screen.inputModal.GetValue()
if renameBucket(screen.currentPath, newName) != nil { btName, err := b.nameDatatype.FromString(newName)
screen.setMessage("Error renaming bucket.") if err != nil {
} else { screen.setMessage("Error renaming bucket: " + err.Error())
b.name = newName } else {
screen.currentPath[len(screen.currentPath)-1] = b.name if renameBucket(screen.currentPath, btName) != nil {
screen.setMessage("Bucket Renamed!") screen.setMessage("Error renaming bucket.")
screen.refreshDatabase() } else {
b.name = btName
screen.currentPath[len(screen.currentPath)-1].name = b.name
screen.setMessage("Bucket Renamed!")
screen.refreshDatabase()
}
} }
} }
} else if p != nil { } else if p != nil {
if screen.mode == modeChangeKey { if screen.mode == modeChangeKey {
newKey := screen.inputModal.GetValue() newKey := screen.inputModal.GetValue()
if updatePairKey(screen.currentPath, newKey) != nil { btKey, err := p.keyDatatype.FromString(newKey)
screen.setMessage("Error occurred updating Pair.") if err != nil {
} else { screen.setMessage("Error updating pair: " + err.Error())
p.key = newKey } else {
screen.currentPath[len(screen.currentPath)-1] = p.key if updatePairKey(screen.currentPath, btKey) != nil {
screen.setMessage("Pair updated!") screen.setMessage("Error occurred updating Pair.")
screen.refreshDatabase() } else {
p.key = btKey
screen.currentPath[len(screen.currentPath)-1].name = p.key
screen.setMessage("Pair updated!")
screen.refreshDatabase()
}
} }
} else if screen.mode == modeChangeVal { } else if screen.mode == modeChangeVal {
newVal := screen.inputModal.GetValue() newVal := screen.inputModal.GetValue()
if updatePairValue(screen.currentPath, newVal) != nil { btVal, err := p.valDatatype.FromString(newVal)
screen.setMessage("Error occurred updating Pair.") if err != nil {
} else { screen.setMessage("Error updating pair: " + err.Error())
p.val = newVal } else {
screen.setMessage("Pair updated!") if updatePairValue(screen.currentPath, btVal) != nil {
screen.refreshDatabase() screen.setMessage("Error occurred updating Pair.")
} else {
p.val = btVal
screen.setMessage("Pair updated!")
screen.refreshDatabase()
}
} }
} }
} }
@ -269,7 +307,7 @@ func (screen *BrowserScreen) handleDeleteKeyEvent(event termbox.Event) int {
//found_new_path := false //found_new_path := false
if holdNextPath != nil { if holdNextPath != nil {
if len(holdNextPath) > 2 { if len(holdNextPath) > 2 {
if holdNextPath[len(holdNextPath)-2] == screen.currentPath[len(screen.currentPath)-2] { if bytes.Equal(holdNextPath[len(holdNextPath)-2].name, screen.currentPath[len(screen.currentPath)-2].name) {
screen.currentPath = holdNextPath screen.currentPath = holdNextPath
} else if holdPrevPath != nil { } else if holdPrevPath != nil {
screen.currentPath = holdPrevPath screen.currentPath = holdPrevPath
@ -305,12 +343,21 @@ func (screen *BrowserScreen) handleInsertKeyEvent(event termbox.Event) int {
screen.inputModal.HandleEvent(event) screen.inputModal.HandleEvent(event)
if screen.inputModal.IsDone() { if screen.inputModal.IsDone() {
newVal := screen.inputModal.GetValue() newVal := screen.inputModal.GetValue()
// TODO: Just default datatype to string? Or should we infer it somehow?
useType := datatypes["string"]
nodeName, err := useType.FromString(newVal)
if err != nil {
screen.setMessage("Error Inserting new item. Data did not match datatype.")
return BrowserScreenIndex
}
newNode := PathNode{name: nodeName, dataType: useType}
screen.inputModal.Clear() screen.inputModal.Clear()
var insertPath []string var insertPath []PathNode
if len(screen.currentPath) > 0 { if len(screen.currentPath) > 0 {
_, p, e := screen.db.getGenericFromPath(screen.currentPath) _, p, e := screen.db.getGenericFromStringPath(screen.currentPath)
if e != nil { if e != nil {
screen.setMessage("Error Inserting new item. Invalid Path.") screen.setMessage("Error Inserting new item. Invalid Path.")
return BrowserScreenIndex
} }
insertPath = screen.currentPath insertPath = screen.currentPath
// where are we inserting? // where are we inserting?
@ -322,14 +369,14 @@ func (screen *BrowserScreen) handleInsertKeyEvent(event termbox.Event) int {
if len(screen.currentPath) > 1 { if len(screen.currentPath) > 1 {
insertPath = screen.currentPath[:len(screen.currentPath)-1] insertPath = screen.currentPath[:len(screen.currentPath)-1]
} else { } else {
insertPath = make([]string, 0) insertPath = make([]PathNode, 0)
} }
} }
} }
parentB, _, _ := screen.db.getGenericFromPath(insertPath) parentB, _, _ := screen.db.getGenericFromStringPath(insertPath)
if screen.mode&modeInsertBucket == modeInsertBucket { if screen.mode&modeInsertBucket == modeInsertBucket {
err := insertBucket(insertPath, newVal) err := insertBucket(insertPath, newNode.name)
if err != nil { if err != nil {
screen.setMessage(fmt.Sprintf("%s => %s", err, insertPath)) screen.setMessage(fmt.Sprintf("%s => %s", err, insertPath))
} else { } else {
@ -337,13 +384,13 @@ func (screen *BrowserScreen) handleInsertKeyEvent(event termbox.Event) int {
parentB.expanded = true parentB.expanded = true
} }
} }
screen.currentPath = append(insertPath, newVal) screen.currentPath = append(insertPath, newNode)
screen.refreshDatabase() screen.refreshDatabase()
screen.mode = modeBrowse screen.mode = modeBrowse
screen.inputModal.Clear() screen.inputModal.Clear()
} else if screen.mode&modeInsertPair == modeInsertPair { } else if screen.mode&modeInsertPair == modeInsertPair {
err := insertPair(insertPath, newVal, "") err := insertPair(insertPath, []byte(newVal), []byte{})
if err != nil { if err != nil {
screen.setMessage(fmt.Sprintf("%s => %s", err, insertPath)) screen.setMessage(fmt.Sprintf("%s => %s", err, insertPath))
screen.refreshDatabase() screen.refreshDatabase()
@ -353,7 +400,7 @@ func (screen *BrowserScreen) handleInsertKeyEvent(event termbox.Event) int {
if parentB != nil { if parentB != nil {
parentB.expanded = true parentB.expanded = true
} }
screen.currentPath = append(insertPath, newVal) screen.currentPath = append(insertPath, newNode)
screen.refreshDatabase() screen.refreshDatabase()
screen.startEditItem() screen.startEditItem()
} }
@ -370,7 +417,7 @@ func (screen *BrowserScreen) handleExportKeyEvent(event termbox.Event) int {
} else { } else {
screen.inputModal.HandleEvent(event) screen.inputModal.HandleEvent(event)
if screen.inputModal.IsDone() { if screen.inputModal.IsDone() {
b, p, _ := screen.db.getGenericFromPath(screen.currentPath) b, p, _ := screen.db.getGenericFromStringPath(screen.currentPath)
fileName := screen.inputModal.GetValue() fileName := screen.inputModal.GetValue()
if screen.mode&modeExportValue == modeExportValue { if screen.mode&modeExportValue == modeExportValue {
// Exporting the value // Exporting the value
@ -406,7 +453,7 @@ func (screen *BrowserScreen) jumpCursorUp(distance int) bool {
for idx, pth := range visPaths { for idx, pth := range visPaths {
startJump := true startJump := true
for i := range pth { for i := range pth {
if len(screen.currentPath) > i && pth[i] != screen.currentPath[i] { if len(screen.currentPath) > i && !bytes.Equal(pth[i].name, screen.currentPath[i].name) {
startJump = false startJump = false
} }
} }
@ -420,7 +467,7 @@ func (screen *BrowserScreen) jumpCursorUp(distance int) bool {
} }
isCurPath := true isCurPath := true
for i := range screen.currentPath { for i := range screen.currentPath {
if screen.currentPath[i] != findPath[i] { if !bytes.Equal(screen.currentPath[i].name, findPath[i].name) {
isCurPath = false isCurPath = false
break break
} }
@ -439,7 +486,7 @@ func (screen *BrowserScreen) jumpCursorDown(distance int) bool {
startJump := true startJump := true
for i := range pth { for i := range pth {
if len(screen.currentPath) > i && pth[i] != screen.currentPath[i] { if len(screen.currentPath) > i && !bytes.Equal(pth[i].name, screen.currentPath[i].name) {
startJump = false startJump = false
} }
} }
@ -453,7 +500,7 @@ func (screen *BrowserScreen) jumpCursorDown(distance int) bool {
} }
isCurPath := true isCurPath := true
for i := range screen.currentPath { for i := range screen.currentPath {
if screen.currentPath[i] != findPath[i] { if !bytes.Equal(screen.currentPath[i].name, findPath[i].name) {
isCurPath = false isCurPath = false
break break
} }
@ -544,13 +591,13 @@ func (screen *BrowserScreen) buildLeftPane(style Style) {
for i := range screen.db.buckets { for i := range screen.db.buckets {
screen.leftPaneBuffer = append(screen.leftPaneBuffer, screen.bucketToLines(&screen.db.buckets[i], style)...) screen.leftPaneBuffer = append(screen.leftPaneBuffer, screen.bucketToLines(&screen.db.buckets[i], style)...)
} }
// Find the cursor in the leftPane // Find the cursor in the leftPane
for k, v := range screen.leftPaneBuffer { for k, v := range screen.leftPaneBuffer {
if v.Fg == style.cursorFg { if v.Fg == style.cursorFg {
screen.leftViewPort.scrollRow = k screen.leftViewPort.scrollRow = k
break break
} }
} }
} }
func (screen *BrowserScreen) drawLeftPane(style Style) { func (screen *BrowserScreen) drawLeftPane(style Style) {
@ -562,42 +609,46 @@ func (screen *BrowserScreen) drawLeftPane(style Style) {
screen.leftViewPort.bytesPerRow = w screen.leftViewPort.bytesPerRow = w
screen.leftViewPort.numberOfRows = h - 2 screen.leftViewPort.numberOfRows = h - 2
termboxUtil.FillWithChar('=', 0, 1, w, 1, style.defaultFg, style.defaultBg) termboxUtil.FillWithChar('=', 0, 1, w, 1, style.defaultFg, style.defaultBg)
startX, startY := 0, 3 startX, startY := 0, 3
screen.leftViewPort.firstRow = startY screen.leftViewPort.firstRow = startY
treeOffset := 0 treeOffset := 0
maxCursor := screen.leftViewPort.numberOfRows * 2 / 3 maxCursor := screen.leftViewPort.numberOfRows * 2 / 3
if screen.leftViewPort.scrollRow > maxCursor { if screen.leftViewPort.scrollRow > maxCursor {
treeOffset = screen.leftViewPort.scrollRow - maxCursor treeOffset = screen.leftViewPort.scrollRow - maxCursor
} }
if len(screen.leftPaneBuffer) > 0 { if len(screen.leftPaneBuffer) > 0 {
for k, v := range screen.leftPaneBuffer[treeOffset:] { for k, v := range screen.leftPaneBuffer[treeOffset:] {
termboxUtil.DrawStringAtPoint(v.Text, startX, (startY + k - 1), v.Fg, v.Bg) termboxUtil.DrawStringAtPoint(v.Text, startX, (startY + k - 1), v.Fg, v.Bg)
} }
} }
} }
func (screen *BrowserScreen) buildRightPane(style Style) { func (screen *BrowserScreen) buildRightPane(style Style) {
screen.rightPaneBuffer = nil screen.rightPaneBuffer = nil
b, p, err := screen.db.getGenericFromPath(screen.currentPath)
b, p, err := screen.db.getGenericFromStringPath(screen.currentPath)
if err == nil { if err == nil {
if b != nil { if b != nil {
screen.rightPaneBuffer = append(screen.rightPaneBuffer, screen.rightPaneBuffer = append(screen.rightPaneBuffer,
Line{fmt.Sprintf("Path: %s", strings.Join(stringifyPath(b.GetPath()), " → ")), style.defaultFg, style.defaultBg}) Line{fmt.Sprintf("Path: %s", strings.Join(nodePathToStringPath(b.GetPath()), " → ")), style.defaultFg, style.defaultBg})
screen.rightPaneBuffer = append(screen.rightPaneBuffer, screen.rightPaneBuffer = append(screen.rightPaneBuffer,
Line{fmt.Sprintf("Buckets: %d", len(b.buckets)), style.defaultFg, style.defaultBg}) Line{fmt.Sprintf("Buckets: %d", len(b.buckets)), style.defaultFg, style.defaultBg})
screen.rightPaneBuffer = append(screen.rightPaneBuffer, screen.rightPaneBuffer = append(screen.rightPaneBuffer,
Line{fmt.Sprintf("Pairs: %d", len(b.pairs)), style.defaultFg, style.defaultBg}) Line{fmt.Sprintf("Pairs: %d", len(b.pairs)), style.defaultFg, style.defaultBg})
screen.rightPaneBuffer = append(screen.rightPaneBuffer,
Line{fmt.Sprintf("Name Datatype: %s", b.nameDatatype.Name()), style.defaultFg, style.defaultBg})
} else if p != nil { } else if p != nil {
screen.rightPaneBuffer = append(screen.rightPaneBuffer, screen.rightPaneBuffer = append(screen.rightPaneBuffer,
Line{fmt.Sprintf("Path: %s", strings.Join(stringifyPath(p.GetPath()), " → ")), style.defaultFg, style.defaultBg}) Line{fmt.Sprintf("Path: %s", strings.Join(nodePathToStringPath(p.GetPath()), " → ")), style.defaultFg, style.defaultBg})
screen.rightPaneBuffer = append(screen.rightPaneBuffer, screen.rightPaneBuffer = append(screen.rightPaneBuffer,
Line{fmt.Sprintf("Key: %s", stringify([]byte(p.key))), style.defaultFg, style.defaultBg}) Line{fmt.Sprintf("Key (%s): %s", p.keyDatatype.Name(), p.GetStringKey()), style.defaultFg, style.defaultBg})
value := strings.Split(string(formatValue([]byte(p.val))), "\n") value := strings.Split(string(formatValue([]byte(p.val))), "\n")
if len(value) == 1 { if len(value) == 1 {
screen.rightPaneBuffer = append(screen.rightPaneBuffer, screen.rightPaneBuffer = append(screen.rightPaneBuffer,
Line{fmt.Sprintf("Value: %s", value[0]), style.defaultFg, style.defaultBg}) Line{fmt.Sprintf("Value (%s): %s", p.valDatatype.Name(), value[0]), style.defaultFg, style.defaultBg})
} else { } else {
screen.rightPaneBuffer = append(screen.rightPaneBuffer, screen.rightPaneBuffer = append(screen.rightPaneBuffer,
Line{"Value:", style.defaultFg, style.defaultBg}) Line{"Value:", style.defaultFg, style.defaultBg})
@ -609,10 +660,12 @@ func (screen *BrowserScreen) buildRightPane(style Style) {
} }
} else { } else {
screen.rightPaneBuffer = append(screen.rightPaneBuffer, screen.rightPaneBuffer = append(screen.rightPaneBuffer,
Line{fmt.Sprintf("Path: %s", strings.Join(stringifyPath(screen.currentPath), " → ")), style.defaultFg, style.defaultBg}) Line{fmt.Sprintf("Path: %s", strings.Join(nodePathToStringPath(screen.currentPath), " → ")), style.defaultFg, style.defaultBg})
screen.rightPaneBuffer = append(screen.rightPaneBuffer, screen.rightPaneBuffer = append(screen.rightPaneBuffer,
Line{err.Error(), termbox.ColorRed, termbox.ColorBlack}) Line{err.Error(), termbox.ColorRed, termbox.ColorBlack})
} }
screen.rightPaneBuffer = append(screen.rightPaneBuffer,
Line{"", style.defaultFg, style.defaultBg})
} }
func (screen *BrowserScreen) drawRightPane(style Style) { func (screen *BrowserScreen) drawRightPane(style Style) {
@ -674,7 +727,7 @@ func (screen *BrowserScreen) bucketToLines(bkt *BoltBucket, style Style) []Line
} }
bktPrefix := strings.Repeat(" ", len(bkt.GetPath())*2) bktPrefix := strings.Repeat(" ", len(bkt.GetPath())*2)
if bkt.expanded { if bkt.expanded {
ret = append(ret, Line{bktPrefix + "- " + stringify([]byte(bkt.name)), bfg, bbg}) ret = append(ret, Line{bktPrefix + "- " + bkt.GetStringName(), bfg, bbg})
for i := range bkt.buckets { for i := range bkt.buckets {
ret = append(ret, screen.bucketToLines(&bkt.buckets[i], style)...) ret = append(ret, screen.bucketToLines(&bkt.buckets[i], style)...)
} }
@ -684,17 +737,17 @@ func (screen *BrowserScreen) bucketToLines(bkt *BoltBucket, style Style) []Line
pfg, pbg = style.cursorFg, style.cursorBg pfg, pbg = style.cursorFg, style.cursorBg
} }
prPrefix := strings.Repeat(" ", len(bp.GetPath())*2) prPrefix := strings.Repeat(" ", len(bp.GetPath())*2)
pairString := fmt.Sprintf("%s%s: %s", prPrefix, stringify([]byte(bp.key)), stringify([]byte(bp.val))) pairString := fmt.Sprintf("%s%s: %s", prPrefix, bp.GetStringKey(), bp.GetStringVal())
ret = append(ret, Line{pairString, pfg, pbg}) ret = append(ret, Line{pairString, pfg, pbg})
} }
} else { } else {
ret = append(ret, Line{bktPrefix + "+ " + stringify([]byte(bkt.name)), bfg, bbg}) ret = append(ret, Line{bktPrefix + "+ " + bkt.GetStringName(), bfg, bbg})
} }
return ret return ret
} }
func (screen *BrowserScreen) startDeleteItem() bool { func (screen *BrowserScreen) startDeleteItem() bool {
b, p, e := screen.db.getGenericFromPath(screen.currentPath) b, p, e := screen.db.getGenericFromStringPath(screen.currentPath)
if e == nil { if e == nil {
w, h := termbox.Size() w, h := termbox.Size()
inpW, inpH := (w / 2), 6 inpW, inpH := (w / 2), 6
@ -715,7 +768,7 @@ func (screen *BrowserScreen) startDeleteItem() bool {
} }
func (screen *BrowserScreen) startEditItem() bool { func (screen *BrowserScreen) startEditItem() bool {
_, p, e := screen.db.getGenericFromPath(screen.currentPath) _, p, e := screen.db.getGenericFromStringPath(screen.currentPath)
if e == nil { if e == nil {
w, h := termbox.Size() w, h := termbox.Size()
inpW, inpH := (w / 2), 6 inpW, inpH := (w / 2), 6
@ -723,7 +776,7 @@ func (screen *BrowserScreen) startEditItem() bool {
mod := termboxUtil.CreateInputModal("", inpX, inpY, inpW, inpH, termbox.ColorWhite, termbox.ColorBlack) mod := termboxUtil.CreateInputModal("", inpX, inpY, inpW, inpH, termbox.ColorWhite, termbox.ColorBlack)
if p != nil { if p != nil {
mod.SetTitle(termboxUtil.AlignText(fmt.Sprintf("Input new value for '%s'", p.key), inpW, termboxUtil.AlignCenter)) mod.SetTitle(termboxUtil.AlignText(fmt.Sprintf("Input new value for '%s'", p.key), inpW, termboxUtil.AlignCenter))
mod.SetValue(p.val) mod.SetValue(p.GetStringVal())
} }
mod.Show() mod.Show()
screen.inputModal = mod screen.inputModal = mod
@ -734,18 +787,18 @@ func (screen *BrowserScreen) startEditItem() bool {
} }
func (screen *BrowserScreen) startRenameItem() bool { func (screen *BrowserScreen) startRenameItem() bool {
b, p, e := screen.db.getGenericFromPath(screen.currentPath) b, p, e := screen.db.getGenericFromStringPath(screen.currentPath)
if e == nil { if e == nil {
w, h := termbox.Size() w, h := termbox.Size()
inpW, inpH := (w / 2), 6 inpW, inpH := (w / 2), 6
inpX, inpY := ((w / 2) - (inpW / 2)), ((h / 2) - inpH) inpX, inpY := ((w / 2) - (inpW / 2)), ((h / 2) - inpH)
mod := termboxUtil.CreateInputModal("", inpX, inpY, inpW, inpH, termbox.ColorWhite, termbox.ColorBlack) mod := termboxUtil.CreateInputModal("", inpX, inpY, inpW, inpH, termbox.ColorWhite, termbox.ColorBlack)
if b != nil { if b != nil {
mod.SetTitle(termboxUtil.AlignText(fmt.Sprintf("Rename Bucket '%s' to:", b.name), inpW, termboxUtil.AlignCenter)) mod.SetTitle(termboxUtil.AlignText(fmt.Sprintf("Rename Bucket '%s' to:", b.GetStringName()), inpW, termboxUtil.AlignCenter))
mod.SetValue(b.name) mod.SetValue(b.GetStringName())
} else if p != nil { } else if p != nil {
mod.SetTitle(termboxUtil.AlignText(fmt.Sprintf("Rename Key '%s' to:", p.key), inpW, termboxUtil.AlignCenter)) mod.SetTitle(termboxUtil.AlignText(fmt.Sprintf("Rename Key '%s' to:", p.GetStringKey()), inpW, termboxUtil.AlignCenter))
mod.SetValue(p.key) mod.SetValue(p.GetStringKey())
} }
mod.Show() mod.Show()
screen.inputModal = mod screen.inputModal = mod
@ -774,11 +827,12 @@ func (screen *BrowserScreen) startInsertItemAtParent(tp BoltType) bool {
} }
} else { } else {
var insPath string var insPath string
_, p, e := screen.db.getGenericFromPath(screen.currentPath[:len(screen.currentPath)-1]) _, p, e := screen.db.getGenericFromStringPath(screen.currentPath[:len(screen.currentPath)-1])
stringPath := nodePathToStringPath(screen.currentPath)
if e == nil && p != nil { if e == nil && p != nil {
insPath = strings.Join(screen.currentPath[:len(screen.currentPath)-2], " → ") + " → " insPath = strings.Join(stringPath[:len(stringPath)-2], " → ") + " → "
} else { } else {
insPath = strings.Join(screen.currentPath[:len(screen.currentPath)-1], " → ") + " → " insPath = strings.Join(stringPath[:len(stringPath)-1], " → ") + " → "
} }
titlePrfx := "" titlePrfx := ""
if tp == typeBucket { if tp == typeBucket {
@ -817,11 +871,12 @@ func (screen *BrowserScreen) startInsertItem(tp BoltType) bool {
//mod.SetInputWrap(true) //mod.SetInputWrap(true)
screen.inputModal = mod screen.inputModal = mod
var insPath string var insPath string
_, p, e := screen.db.getGenericFromPath(screen.currentPath) _, p, e := screen.db.getGenericFromStringPath(screen.currentPath)
strPath := nodePathToStringPath(screen.currentPath)
if e == nil && p != nil { if e == nil && p != nil {
insPath = strings.Join(screen.currentPath[:len(screen.currentPath)-1], " → ") + " → " insPath = strings.Join(strPath[:len(strPath)-1], " → ") + " → "
} else { } else {
insPath = strings.Join(screen.currentPath, " → ") + " → " insPath = strings.Join(strPath, " → ") + " → "
} }
titlePrfx := "" titlePrfx := ""
if tp == typeBucket { if tp == typeBucket {
@ -849,7 +904,7 @@ func (screen *BrowserScreen) startInsertItem(tp BoltType) bool {
} }
func (screen *BrowserScreen) startExportValue() bool { func (screen *BrowserScreen) startExportValue() bool {
_, p, e := screen.db.getGenericFromPath(screen.currentPath) _, p, e := screen.db.getGenericFromStringPath(screen.currentPath)
if e == nil && p != nil { if e == nil && p != nil {
w, h := termbox.Size() w, h := termbox.Size()
inpW, inpH := (w / 2), 6 inpW, inpH := (w / 2), 6
@ -862,12 +917,17 @@ func (screen *BrowserScreen) startExportValue() bool {
screen.mode = modeExportValue screen.mode = modeExportValue
return true return true
} }
screen.setMessage("Couldn't do string export on " + screen.currentPath[len(screen.currentPath)-1] + "(did you mean 'X'?)") expNode := screen.currentPath[len(screen.currentPath)-1]
strVal, err := expNode.dataType.ToString(expNode.name)
if err != nil {
strVal = "ERROR"
}
screen.setMessage("Couldn't do string export on " + strVal + " (did you mean 'X'?)")
return false return false
} }
func (screen *BrowserScreen) startExportJSON() bool { func (screen *BrowserScreen) startExportJSON() bool {
b, p, e := screen.db.getGenericFromPath(screen.currentPath) b, p, e := screen.db.getGenericFromStringPath(screen.currentPath)
if e == nil { if e == nil {
w, h := termbox.Size() w, h := termbox.Size()
inpW, inpH := (w / 2), 6 inpW, inpH := (w / 2), 6
@ -914,6 +974,31 @@ func (screen *BrowserScreen) refreshDatabase() {
screen.db.syncOpenBuckets(shadowDB) screen.db.syncOpenBuckets(shadowDB)
} }
func comparePaths(p1, p2 []string) bool { func findNextDatatype(curr string) Datatype {
return strings.Join(p1, " → ") == strings.Join(p2, " → ") var first Datatype
var found bool
for k, v := range datatypes {
if first == nil {
first = v
}
if found {
return v
}
if k == curr {
found = true
}
}
return first
}
func comparePaths(p1, p2 []PathNode) bool {
if len(p1) != len(p2) {
return false
}
for k := range p1 {
if !bytes.Equal(p1[k].name, p2[k].name) {
return false
}
}
return true
} }

View File

@ -31,9 +31,14 @@ func stringify(v []byte) string {
return fmt.Sprintf("%x", v) return fmt.Sprintf("%x", v)
} }
func stringifyPath(path []string) []string { func stringifyStringPath(path []string) []string {
for k, v := range path { return stringifyPath(stringPathToBytePath(path))
path[k] = stringify([]byte(v)) }
}
return path func stringifyPath(path [][]byte) []string {
var ret []string
for _, v := range path {
ret = append(ret, stringify(v))
}
return ret
} }