1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
package store
import (
"bytes"
"encoding/binary"
bolt "go.etcd.io/bbolt"
. "src.elv.sh/pkg/store/storedefs"
)
func init() {
initDB["initialize command history table"] = func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists([]byte(bucketCmd))
return err
}
}
// NextCmdSeq returns the next sequence number of the command history.
func (s *dbStore) NextCmdSeq() (int, error) {
var seq uint64
err := s.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(bucketCmd))
seq = b.Sequence() + 1
return nil
})
return int(seq), err
}
// AddCmd adds a new command to the command history.
func (s *dbStore) AddCmd(cmd string) (int, error) {
var (
seq uint64
err error
)
err = s.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(bucketCmd))
seq, err = b.NextSequence()
if err != nil {
return err
}
return b.Put(marshalSeq(seq), []byte(cmd))
})
return int(seq), err
}
// DelCmd deletes a command history item with the given sequence number.
func (s *dbStore) DelCmd(seq int) error {
return s.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(bucketCmd))
return b.Delete(marshalSeq(uint64(seq)))
})
}
// Cmd queries the command history item with the specified sequence number.
func (s *dbStore) Cmd(seq int) (string, error) {
var cmd string
err := s.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(bucketCmd))
v := b.Get(marshalSeq(uint64(seq)))
if v == nil {
return ErrNoMatchingCmd
}
cmd = string(v)
return nil
})
return cmd, err
}
// IterateCmds iterates all the commands in the specified range, and calls the
// callback with the content of each command sequentially.
func (s *dbStore) IterateCmds(from, upto int, f func(Cmd)) error {
return s.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(bucketCmd))
c := b.Cursor()
for k, v := c.Seek(marshalSeq(uint64(from))); k != nil && unmarshalSeq(k) < uint64(upto); k, v = c.Next() {
f(Cmd{Text: string(v), Seq: int(unmarshalSeq(k))})
}
return nil
})
}
// CmdsWithSeq returns all commands within the specified range.
func (s *dbStore) CmdsWithSeq(from, upto int) ([]Cmd, error) {
var cmds []Cmd
err := s.IterateCmds(from, upto, func(cmd Cmd) {
cmds = append(cmds, cmd)
})
return cmds, err
}
// NextCmd finds the first command after the given sequence number (inclusive)
// with the given prefix.
func (s *dbStore) NextCmd(from int, prefix string) (Cmd, error) {
var cmd Cmd
err := s.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(bucketCmd))
c := b.Cursor()
p := []byte(prefix)
for k, v := c.Seek(marshalSeq(uint64(from))); k != nil; k, v = c.Next() {
if bytes.HasPrefix(v, p) {
cmd = Cmd{Text: string(v), Seq: int(unmarshalSeq(k))}
return nil
}
}
return ErrNoMatchingCmd
})
return cmd, err
}
// PrevCmd finds the last command before the given sequence number (exclusive)
// with the given prefix.
func (s *dbStore) PrevCmd(upto int, prefix string) (Cmd, error) {
var cmd Cmd
err := s.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(bucketCmd))
c := b.Cursor()
p := []byte(prefix)
var v []byte
k, _ := c.Seek(marshalSeq(uint64(upto)))
if k == nil { // upto > LAST
k, v = c.Last()
if k == nil {
return ErrNoMatchingCmd
}
} else {
k, v = c.Prev() // upto exists, find the previous one
}
for ; k != nil; k, v = c.Prev() {
if bytes.HasPrefix(v, p) {
cmd = Cmd{Text: string(v), Seq: int(unmarshalSeq(k))}
return nil
}
}
return ErrNoMatchingCmd
})
return cmd, err
}
func marshalSeq(seq uint64) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, seq)
return b
}
func unmarshalSeq(key []byte) uint64 {
return binary.BigEndian.Uint64(key)
}
|