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