diff --git a/bolt_model.go b/bolt_model.go index ade6a29..84901e3 100644 --- a/bolt_model.go +++ b/bolt_model.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "github.com/boltdb/bolt" + "os" "strings" ) @@ -13,12 +14,13 @@ type BoltDB struct { } type BoltBucket struct { - name string - path []string - pairs []BoltPair - buckets []BoltBucket - parent *BoltBucket - expanded bool + name string + path []string + pairs []BoltPair + buckets []BoltBucket + parent *BoltBucket + expanded bool + error_flag bool } type BoltPair struct { @@ -28,23 +30,6 @@ type BoltPair struct { val string } -func (bd *BoltDB) getDepthFromPath(path []string) int { - depth := 0 - b, p, err := bd.getGenericFromPath(path) - if err != nil { - return -1 - } - if p != nil { - b = p.parent - depth += 1 - } - for b != nil { - b = b.parent - depth += 1 - } - return depth -} - func (bd *BoltDB) getGenericFromPath(path []string) (*BoltBucket, *BoltPair, error) { // Check if 'path' leads to a pair p, err := bd.getPairFromPath(path) @@ -60,38 +45,6 @@ func (bd *BoltDB) getGenericFromPath(path []string) (*BoltBucket, *BoltPair, err return nil, nil, errors.New("Invalid Path") } -func (bd *BoltDB) removeGenericAtPath(path []string) error { - b, p, err := bd.getGenericFromPath(path) - if err != nil { - return err - } - if p != nil { - pb := p.parent - for i := range pb.pairs { - if pb.pairs[i-1].key == p.key { - pb.pairs = append(pb.pairs[:i-1], pb.pairs[i+1:]...) - } - } - } - if b != nil { - pb := b.parent - if pb == nil { - for i := range bd.buckets { - if bd.buckets[i-1].name == b.name { - bd.buckets = append(bd.buckets[:i-1], bd.buckets[i+1:]...) - } - } - } else { - for i := range pb.buckets { - if pb.buckets[i-1].name == b.name { - pb.buckets = append(pb.buckets[:i-1], pb.buckets[i+1:]...) - } - } - } - } - return err -} - func (bd *BoltDB) getBucketFromPath(path []string) (*BoltBucket, error) { if len(path) > 0 { // Find the BoltBucket with a path == path @@ -104,7 +57,10 @@ func (bd *BoltDB) getBucketFromPath(path []string) (*BoltBucket, error) { } if len(path) > 1 { for p := 1; p < len(path); p++ { - return b.getBucket(path[p]) + b, err = b.getBucket(path[p]) + if err != nil { + return nil, err + } } } return b, nil @@ -113,7 +69,10 @@ func (bd *BoltDB) getBucketFromPath(path []string) (*BoltBucket, error) { } func (bd *BoltDB) getPairFromPath(path []string) (*BoltPair, error) { - b, err := memBolt.getBucketFromPath(path[:len(path)-1]) + if len(path) <= 0 { + return nil, errors.New("No Path") + } + b, err := bd.getBucketFromPath(path[:len(path)-1]) if err != nil { return nil, err } @@ -157,7 +116,24 @@ func (bd *BoltDB) getVisibleItemCount(path []string) (int, error) { return vis, ret_err } -func (bd *BoltDB) buildVisiblePathSlice(path []string) ([]string, error) { +func (bd *BoltDB) buildVisiblePathSlice() ([]string, error) { + var ret_slice []string + var ret_err error + // The root path, recurse for root buckets + for i := range bd.buckets { + bkt_s, bkt_err := bd.buckets[i].buildVisiblePathSlice([]string{}) + if bkt_err == nil { + ret_slice = append(ret_slice, bkt_s...) + } else { + // Something went wrong, set the error flag + bd.buckets[i].error_flag = true + } + } + return ret_slice, ret_err +} + +/* +func (bd *BoltDB) buildVisiblePathSlice_old(path []string) ([]string, error) { var ret_slice []string var ret_err error if len(path) == 0 { @@ -176,7 +152,7 @@ func (bd *BoltDB) buildVisiblePathSlice(path []string) ([]string, error) { // Add the bucket's path ret_slice = append(ret_slice, strings.Join(b.path, "/")) if b.expanded { - // This bucket is expanded, add up it's children + // This bucket is expanded, include it's children // * recurse for buckets for i := range b.buckets { n, err := bd.buildVisiblePathSlice(b.buckets[i].path) @@ -193,11 +169,16 @@ func (bd *BoltDB) buildVisiblePathSlice(path []string) ([]string, error) { } return ret_slice, ret_err } +*/ func (bd *BoltDB) getPrevVisiblePath(path []string) []string { - vis_paths, err := bd.buildVisiblePathSlice(nil) + vis_paths, err := bd.buildVisiblePathSlice() if path == nil { - return strings.Split(vis_paths[len(vis_paths)-1], "/") + if len(vis_paths) > 0 { + return strings.Split(vis_paths[len(vis_paths)-1], "/") + } else { + return nil + } } if err == nil { find_path := strings.Join(path, "/") @@ -210,9 +191,13 @@ func (bd *BoltDB) getPrevVisiblePath(path []string) []string { return nil } func (bd *BoltDB) getNextVisiblePath(path []string) []string { - vis_paths, err := bd.buildVisiblePathSlice(nil) + vis_paths, err := bd.buildVisiblePathSlice() if path == nil { - return strings.Split(vis_paths[0], "/") + if len(vis_paths) > 0 { + return strings.Split(vis_paths[0], "/") + } else { + return nil + } } if err == nil { find_path := strings.Join(path, "/") @@ -272,6 +257,51 @@ func (bd *BoltDB) syncOpenBuckets(shadow *BoltDB) { } } +func (bd *BoltDB) refreshDatabase() *BoltDB { + // Reload the database into memBolt + memBolt = new(BoltDB) + db.View(func(tx *bolt.Tx) error { + return tx.ForEach(func(nm []byte, b *bolt.Bucket) error { + bb, err := readBucket(b) + if err == nil { + bb.name = string(nm) + bb.setPath([]string{bb.name}) + bb.expanded = false + memBolt.buckets = append(memBolt.buckets, *bb) + //updatePaths(bb) + return nil + } + return err + }) + }) + return memBolt +} + +func (b *BoltBucket) buildVisiblePathSlice(prefix []string) ([]string, error) { + var ret_slice []string + var ret_err error + // Add this bucket to the slice + prefix = append(prefix, b.name) + ret_slice = append(ret_slice, strings.Join(prefix, "/")) + if b.expanded { + // Add subbuckets + for i := range b.buckets { + bkt_s, bkt_err := b.buckets[i].buildVisiblePathSlice(prefix) + if bkt_err == nil { + ret_slice = append(ret_slice, bkt_s...) + } else { + // Something went wrong, set the error flag + b.buckets[i].error_flag = true + } + } + // Add Pairs + for i := range b.pairs { + ret_slice = append(ret_slice, strings.Join(prefix, "/")+b.pairs[i].key) + } + } + return ret_slice, ret_err +} + func (b *BoltBucket) syncOpenBuckets(shadow *BoltBucket) { // First test this bucket b.expanded = shadow.expanded @@ -302,6 +332,16 @@ func (b *BoltBucket) getPair(k string) (*BoltPair, error) { return nil, errors.New("Pair Not Found") } +func (b *BoltBucket) setPath(p []string) { + b.path = p + for i := range b.buckets { + b.buckets[i].setPath(append(p, b.buckets[i].name)) + } + for i := range b.pairs { + b.pairs[i].path = append(p, b.pairs[i].key) + } +} + func deleteKey(path []string) error { err := db.Update(func(tx *bolt.Tx) error { // len(b.path)-1 is the key we need to delete, the rest are buckets leading to that key @@ -336,26 +376,6 @@ func deleteKey(path []string) error { return err } -func refreshDatabase() *BoltDB { - // Reload the database into memBolt - memBolt = new(BoltDB) - db.View(func(tx *bolt.Tx) error { - return tx.ForEach(func(nm []byte, b *bolt.Bucket) error { - bb, err := readBucket(b) - if err == nil { - bb.name = string(nm) - bb.path = []string{bb.name} - bb.expanded = false - memBolt.buckets = append(memBolt.buckets, *bb) - updatePaths(bb) - return nil - } - return err - }) - }) - return memBolt -} - func readBucket(b *bolt.Bucket) (*BoltBucket, error) { bb := new(BoltBucket) b.ForEach(func(k, v []byte) error { @@ -364,13 +384,11 @@ func readBucket(b *bolt.Bucket) (*BoltBucket, error) { tb.parent = bb if err == nil { tb.name = string(k) - tb.path = append(bb.path, tb.name) bb.buckets = append(bb.buckets, *tb) } } else { tp := BoltPair{key: string(k), val: string(v)} tp.parent = bb - tp.path = append(bb.path, tp.key) bb.pairs = append(bb.pairs, tp) } return nil @@ -380,6 +398,7 @@ func readBucket(b *bolt.Bucket) (*BoltBucket, error) { func updatePaths(b *BoltBucket) { for i := range b.buckets { + b.buckets[i].path = append(b.path, b.buckets[i].name) updatePaths(&b.buckets[i]) } @@ -439,29 +458,34 @@ func updatePairValue(path []string, v string) error { func insertBucket(path []string, n string) error { // Inserts a new bucket named 'n' at 'path' err := db.Update(func(tx *bolt.Tx) error { - if len(path) == 1 { + if len(path) == 0 { // insert at root _, err := tx.CreateBucket([]byte(n)) if err != nil { return fmt.Errorf("insertBucket: %s", err) } - } else if len(path) > 1 { - var err error - b := tx.Bucket([]byte(path[0])) + } else { + root_bucket, path := path[0], path[1:] + b := tx.Bucket([]byte(root_bucket)) if b != nil { - if len(path) > 2 { - for i := range path[1 : len(path)-2] { - b = b.Bucket([]byte(path[i+1])) - if b == nil { - return fmt.Errorf("insertBucket: %s", err) + for len(path) > 0 { + tst_bucket := "" + tst_bucket, path = path[0], path[1:] + n_b := b.Bucket([]byte(tst_bucket)) + if n_b == nil { + // Not a bucket, if we're out of path, just move on + if len(path) != 0 { + // Out of path, error + return errors.New("insertBucket: Invalid Path 1") } + } else { + b = n_b } } _, err := b.CreateBucket([]byte(n)) - if err != nil { - return fmt.Errorf("insertBucket: %s", err) - } + return err } + return fmt.Errorf("insertBucket: Invalid Path %s", root_bucket) } return nil }) @@ -496,3 +520,13 @@ func insertPair(path []string, k string, v string) error { }) return err } + +var f *os.File + +func logToFile(s string) { + if f == nil { + f, _ = os.Create("bolt-log") + } + f.WriteString(s + "\n") + f.Sync() +} diff --git a/boltbrowser.go b/boltbrowser.go index 18bb57d..9f06afa 100644 --- a/boltbrowser.go +++ b/boltbrowser.go @@ -57,7 +57,7 @@ func main() { } // First things first, load the database into memory - refreshDatabase() + memBolt.refreshDatabase() err = termbox.Init() if err != nil { diff --git a/screen_browser.go b/screen_browser.go index 01e4919..149168a 100644 --- a/screen_browser.go +++ b/screen_browser.go @@ -79,6 +79,9 @@ func (screen *BrowserScreen) handleBrowseKeyEvent(event termbox.Event) int { // Jump to End screen.current_path = screen.db.getPrevVisiblePath(nil) + } else if event.Key == termbox.KeyCtrlR { + screen.refreshDatabase() + } else if event.Key == termbox.KeyCtrlF { // Jump forward half a screen _, h := termbox.Size() @@ -176,6 +179,7 @@ func (screen *BrowserScreen) handleInputKeyEvent(event termbox.Event) int { } else { p.val = new_val screen.message = "Pair updated!" + screen.refreshDatabase() } } screen.mode = MODE_BROWSE @@ -192,9 +196,7 @@ func (screen *BrowserScreen) handleDeleteKeyEvent(event termbox.Event) int { hold_next_path := screen.db.getNextVisiblePath(screen.current_path) hold_prev_path := screen.db.getPrevVisiblePath(screen.current_path) if deleteKey(screen.current_path) == nil { - shadow_db := screen.db - screen.db = refreshDatabase() - screen.db.syncOpenBuckets(shadow_db) + screen.refreshDatabase() // Move the current path endpoint appropriately //found_new_path := false if hold_next_path != nil { @@ -226,37 +228,48 @@ func (screen *BrowserScreen) handleDeleteKeyEvent(event termbox.Event) int { func (screen *BrowserScreen) handleInsertKeyEvent(event termbox.Event) int { if event.Key == termbox.KeyEsc { - screen.mode = MODE_BROWSE - screen.input_modal.Clear() + if len(screen.db.buckets) == 0 { + return EXIT_SCREEN_INDEX + } else { + screen.mode = MODE_BROWSE + screen.input_modal.Clear() + } } else { screen.input_modal.HandleKeyPress(event) if screen.input_modal.IsDone() { new_val := screen.input_modal.GetValue() - b, p, e := screen.db.getGenericFromPath(screen.current_path) - if e != nil { - screen.message = "Error Inserting new item. Invalid Path." - } - insert_path := screen.current_path - // where are we inserting? - var parent *BoltBucket - if p != nil { - // If we're sitting on a pair, we have to go to it's parent - screen.mode = screen.mode | MODE_MOD_TO_PARENT - parent = p.parent - } else if b != nil { - parent = b.parent - insert_path = b.path - } - if screen.mode&MODE_MOD_TO_PARENT == MODE_MOD_TO_PARENT { - if parent != nil { - insert_path = parent.path - } else { - insert_path = screen.current_path[:0] + var insert_path []string + if len(screen.current_path) > 0 { + _, p, e := screen.db.getGenericFromPath(screen.current_path) + if e != nil { + screen.message = "Error Inserting new item. Invalid Path." + } + insert_path = screen.current_path + // where are we inserting? + //var parent *BoltBucket + if p != nil { + // If we're sitting on a pair, we have to go to it's parent + screen.mode = screen.mode | MODE_MOD_TO_PARENT + // parent = p.parent + //} else if b != nil { + // parent = b.parent + } + if screen.mode&MODE_MOD_TO_PARENT == MODE_MOD_TO_PARENT { + if len(screen.current_path) > 1 { + insert_path = screen.current_path[:len(screen.current_path)-1] + } else { + insert_path = make([]string, 0) + } } } if screen.mode&MODE_INSERT_BUCKET == MODE_INSERT_BUCKET { - insertBucket(insert_path, new_val) + screen.message = strings.Join(insert_path, "/") + err := insertBucket(insert_path, new_val) + if err != nil { + screen.message = fmt.Sprintf("%s => %s", err, insert_path) + } + screen.refreshDatabase() screen.mode = MODE_BROWSE screen.input_modal.Clear() } else if screen.mode&MODE_INSERT_PAIR == MODE_INSERT_PAIR { @@ -277,7 +290,7 @@ func (screen *BrowserScreen) handleInsertKeyEvent(event termbox.Event) int { func (screen *BrowserScreen) jumpCursorUp(distance int) bool { // Jump up 'distance' lines - vis_paths, err := screen.db.buildVisiblePathSlice(nil) + vis_paths, err := screen.db.buildVisiblePathSlice() if err == nil { find_path := strings.Join(screen.current_path, "/") start_jump := false @@ -300,7 +313,7 @@ func (screen *BrowserScreen) jumpCursorUp(distance int) bool { return true } func (screen *BrowserScreen) jumpCursorDown(distance int) bool { - vis_paths, err := screen.db.buildVisiblePathSlice(nil) + vis_paths, err := screen.db.buildVisiblePathSlice() if err == nil { find_path := strings.Join(screen.current_path, "/") start_jump := false @@ -343,6 +356,10 @@ func (screen *BrowserScreen) moveCursorDown() bool { func (screen *BrowserScreen) performLayout() {} func (screen *BrowserScreen) drawScreen(style Style) { + if len(screen.db.buckets) == 0 && screen.mode&MODE_INSERT_BUCKET != MODE_INSERT_BUCKET { + // Force a bucket insert + screen.startInsertItemAtParent(TYPE_BUCKET) + } screen.drawLeftPane(style) screen.drawRightPane(style) screen.drawHeader(style) @@ -383,7 +400,7 @@ func (screen *BrowserScreen) drawLeftPane(style Style) { // So we know how much of the tree _wants_ to be visible // we only have screen.view_port.number_of_rows of space though cur_path_spot := 0 - vis_slice, err := screen.db.buildVisiblePathSlice(nil) + vis_slice, err := screen.db.buildVisiblePathSlice() if err == nil { for i := range vis_slice { if strings.Join(screen.current_path, "/") == vis_slice[i] { @@ -425,6 +442,16 @@ func (screen *BrowserScreen) drawRightPane(style Style) { termbox_util.DrawStringAtPoint(fmt.Sprintf("Key: %s", p.key), start_x, start_y+1, style.default_fg, style.default_bg) termbox_util.DrawStringAtPoint(fmt.Sprintf("Value: %s", p.val), start_x, start_y+2, style.default_fg, style.default_bg) } + + p_str := fmt.Sprintf("PREV: %s", strings.Join(screen.db.getPrevVisiblePath(screen.current_path), "/")) + n_str := fmt.Sprintf("NEXT: %s", strings.Join(screen.db.getNextVisiblePath(screen.current_path), "/")) + termbox_util.DrawStringAtPoint(p_str, start_x, start_y+4, style.default_fg, style.default_bg) + termbox_util.DrawStringAtPoint(n_str, start_x, start_y+5, style.default_fg, style.default_bg) + + vs, _ := screen.db.buildVisiblePathSlice() + for i := range vs { + termbox_util.DrawStringAtPoint(vs[i], start_x, start_y+6+i, style.default_fg, style.default_bg) + } } } } @@ -449,7 +476,16 @@ func (screen *BrowserScreen) drawBucket(bkt *BoltBucket, style Style, y int) int bucket_bg = style.cursor_bg } - bkt_string := strings.Repeat(" ", screen.db.getDepthFromPath(bkt.path)*2) + if len(bkt.path) == 0 { + // No bucket should have a 0 length path... + if bkt.parent == nil { + bkt.path = []string{bkt.name} + } else { + bkt.path = append(bkt.parent.path, bkt.name) + } + } + + bkt_string := strings.Repeat(" ", len(bkt.path)*2) //screen.db.getDepthFromPath(bkt.path)*2) if bkt.expanded { bkt_string = bkt_string + "- " + bkt.name + " " bkt_string = fmt.Sprintf("%s%s", bkt_string, strings.Repeat(" ", (w-len(bkt_string)))) @@ -484,7 +520,7 @@ func (screen *BrowserScreen) drawPair(bp *BoltPair, style Style, y int) int { bucket_bg = style.cursor_bg } - pair_string := strings.Repeat(" ", screen.db.getDepthFromPath(bp.path)*2) + pair_string := strings.Repeat(" ", len(bp.path)*2) //screen.db.getDepthFromPath(bp.path)*2) pair_string = fmt.Sprintf("%s%s: %s", pair_string, bp.key, bp.val) pair_string = fmt.Sprintf("%s%s", pair_string, strings.Repeat(" ", (w-len(pair_string)))) termbox_util.DrawStringAtPoint(pair_string, 0, y, bucket_fg, bucket_bg) @@ -540,22 +576,23 @@ func (screen *BrowserScreen) startInsertItemAtParent(tp BoltType) bool { inp_x, inp_y := ((w / 2) - (inp_w / 2)), ((h / 2) - inp_h) mod := termbox_util.CreateInputModal("", inp_x, inp_y, inp_w, inp_h, termbox.ColorWhite, termbox.ColorBlack) screen.input_modal = mod - if len(screen.current_path) == 1 { + if len(screen.current_path) <= 0 { // in the root directory if tp == TYPE_BUCKET { - mod.SetTitle(termbox_util.AlignText("Create New Bucket", inp_w, termbox_util.ALIGN_CENTER)) + mod.SetTitle(termbox_util.AlignText("Create Root Bucket", inp_w, termbox_util.ALIGN_CENTER)) screen.mode = MODE_INSERT_BUCKET | MODE_MOD_TO_PARENT mod.Show() return true } } else { + ins_path := strings.Join(screen.current_path[:len(screen.current_path)-1], "/") + "/" if tp == TYPE_BUCKET { - mod.SetTitle(termbox_util.AlignText("Create New Bucket", inp_w, termbox_util.ALIGN_CENTER)) + mod.SetTitle(termbox_util.AlignText("Create Bucket at "+ins_path, inp_w, termbox_util.ALIGN_CENTER)) screen.mode = MODE_INSERT_BUCKET | MODE_MOD_TO_PARENT mod.Show() return true } else if tp == TYPE_PAIR { - mod.SetTitle(termbox_util.AlignText("Create New Pair", inp_w, termbox_util.ALIGN_CENTER)) + mod.SetTitle(termbox_util.AlignText("Create Pair at "+ins_path, inp_w, termbox_util.ALIGN_CENTER)) mod.SetText("New Pair Key:") mod.Show() screen.mode = MODE_INSERT_PAIR | MODE_MOD_TO_PARENT @@ -567,7 +604,7 @@ func (screen *BrowserScreen) startInsertItemAtParent(tp BoltType) bool { func (screen *BrowserScreen) startInsertItem(tp BoltType) bool { _, p, e := screen.db.getGenericFromPath(screen.current_path) - if e == nil { + if e != nil { return false } if p != nil { @@ -579,30 +616,22 @@ func (screen *BrowserScreen) startInsertItem(tp BoltType) bool { inp_x, inp_y := ((w / 2) - (inp_w / 2)), ((h / 2) - inp_h) mod := termbox_util.CreateInputModal("", inp_x, inp_y, inp_w, inp_h, termbox.ColorWhite, termbox.ColorBlack) screen.input_modal = mod - if len(screen.current_path) == 1 { - // in the root directory - if tp == TYPE_BUCKET { - mod.SetTitle(termbox_util.AlignText("Create New Bucket", inp_w, termbox_util.ALIGN_CENTER)) - mod.Show() - screen.mode = MODE_INSERT_BUCKET - return true - } - } else { - if tp == TYPE_BUCKET { - mod.SetTitle(termbox_util.AlignText("Create New Bucket", inp_w, termbox_util.ALIGN_CENTER)) - screen.mode = MODE_INSERT_BUCKET - mod.Show() - return true - } else if tp == TYPE_PAIR { - mod.SetTitle(termbox_util.AlignText("Create New Pair", inp_w, termbox_util.ALIGN_CENTER)) - screen.mode = MODE_INSERT_PAIR - mod.Show() - return true - } + ins_path := strings.Join(screen.current_path, "/") + "/" + if tp == TYPE_BUCKET { + mod.SetTitle(termbox_util.AlignText("Create Bucket at "+ins_path, inp_w, termbox_util.ALIGN_CENTER)) + screen.mode = MODE_INSERT_BUCKET + mod.Show() + return true } return false } +func (screen *BrowserScreen) refreshDatabase() { + shadowDB := screen.db + screen.db = screen.db.refreshDatabase() + screen.db.syncOpenBuckets(shadowDB) +} + func comparePaths(p1, p2 []string) bool { return strings.Join(p1, "/") == strings.Join(p2, "/") }