package store import ( "bytes" "encoding/binary" bolt "go.etcd.io/bbolt" ) 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)) if v := b.Get(marshalSeq(uint64(seq))); v == nil { return ErrNoMatchingCmd } else { 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 }) } // Cmds returns the contents of all commands within the specified range. // // NOTE: Deprecated as of 0.13. Delete after release of 0.14. func (s *dbStore) Cmds(from, upto int) ([]string, error) { var cmds []string err := s.IterateCmds(from, upto, func(cmd Cmd) { cmds = append(cmds, cmd.Text) }) return cmds, err } // 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) }