mirror of
https://github.com/status-im/status-console-client.git
synced 2025-02-24 08:38:15 +00:00
173 lines
3.8 KiB
Go
173 lines
3.8 KiB
Go
package client
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha1"
|
|
"encoding/binary"
|
|
"encoding/gob"
|
|
"io"
|
|
"strconv"
|
|
|
|
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
|
"github.com/syndtr/goleveldb/leveldb"
|
|
"github.com/syndtr/goleveldb/leveldb/storage"
|
|
"github.com/syndtr/goleveldb/leveldb/util"
|
|
|
|
"github.com/status-im/status-console-client/protocol/v1"
|
|
)
|
|
|
|
var (
|
|
contactsListKey = []byte("contacts-list")
|
|
)
|
|
|
|
func init() {
|
|
gob.Register(&secp256k1.BitCurve{})
|
|
}
|
|
|
|
// Database is a wrapped around leveldb to provide storage
|
|
// for messenger data.
|
|
type Database struct {
|
|
db *leveldb.DB
|
|
}
|
|
|
|
// NewDatabase returns a new database creating files
|
|
// in a given path directory.
|
|
func NewDatabase(path string) (*Database, error) {
|
|
if path == "" {
|
|
// If path is not give, use in-memory storage.
|
|
storage := storage.NewMemStorage()
|
|
db, err := leveldb.Open(storage, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Database{db: db}, nil
|
|
}
|
|
|
|
db, err := leveldb.OpenFile(path, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Database{db: db}, nil
|
|
}
|
|
|
|
func (d *Database) Close() error {
|
|
return d.db.Close()
|
|
}
|
|
|
|
// Messages returns all messages for a given contact
|
|
// and between from and to timestamps.
|
|
func (d *Database) Messages(c Contact, from, to int64) (result []*protocol.Message, err error) {
|
|
start := d.keyFromContact(c, from, nil)
|
|
limit := d.keyFromContact(c, to+1, nil) // because iter is right-exclusive
|
|
|
|
iter := d.db.NewIterator(&util.Range{Start: start, Limit: limit}, nil)
|
|
for iter.Next() {
|
|
value := iter.Value()
|
|
buf := bytes.NewBuffer(value)
|
|
dec := gob.NewDecoder(buf)
|
|
|
|
var m protocol.Message
|
|
|
|
err = dec.Decode(&m)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
result = append(result, &m)
|
|
}
|
|
|
|
iter.Release()
|
|
err = iter.Error()
|
|
|
|
return
|
|
}
|
|
|
|
// SaveMessages stores messages on a disk.
|
|
func (d *Database) SaveMessages(c Contact, messages []*protocol.Message) error {
|
|
var buf bytes.Buffer
|
|
enc := gob.NewEncoder(&buf)
|
|
|
|
batch := new(leveldb.Batch)
|
|
for _, m := range messages {
|
|
// TODO(adam): incoming Timestamp is in ms
|
|
key := d.keyFromContact(c, m.Decoded.Timestamp/1000, m.Hash)
|
|
|
|
if err := enc.Encode(m); err != nil {
|
|
return err
|
|
}
|
|
|
|
data := buf.Bytes()
|
|
value := make([]byte, len(data))
|
|
copy(value, data)
|
|
// The read value needs to be copied to another slice
|
|
// because a slice returned by Bytes() is valid only until
|
|
// another write.
|
|
// As we batch writes and wait untill the loop is finished,
|
|
// slices must be available later.
|
|
batch.Put(key, value)
|
|
buf.Reset()
|
|
}
|
|
|
|
return d.db.Write(batch, nil)
|
|
}
|
|
|
|
// Contacts retrieves all saved contacts.
|
|
func (d *Database) Contacts() ([]Contact, error) {
|
|
value, err := d.db.Get(contactsListKey, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
buf := bytes.NewBuffer(value)
|
|
dec := gob.NewDecoder(buf)
|
|
|
|
var contacts []Contact
|
|
|
|
if err := dec.Decode(&contacts); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return contacts, nil
|
|
}
|
|
|
|
// SaveContacts saves all contacts on a disk.
|
|
func (d *Database) SaveContacts(contacts []Contact) error {
|
|
var buf bytes.Buffer
|
|
|
|
enc := gob.NewEncoder(&buf)
|
|
if err := enc.Encode(contacts); err != nil {
|
|
return err
|
|
}
|
|
|
|
return d.db.Put([]byte(contactsListKey), buf.Bytes(), nil)
|
|
}
|
|
|
|
const (
|
|
contactPrefixLength = sha1.Size
|
|
timeLength = 8
|
|
hashLength = 32
|
|
keyFromContactLength = contactPrefixLength + timeLength + hashLength
|
|
)
|
|
|
|
func (d *Database) prefixFromContact(c Contact) []byte {
|
|
h := sha1.New()
|
|
_, _ = io.WriteString(h, c.Name)
|
|
_, _ = io.WriteString(h, ":")
|
|
_, _ = io.WriteString(h, strconv.Itoa(int(c.Type)))
|
|
return h.Sum(nil)
|
|
}
|
|
|
|
func (d *Database) keyFromContact(c Contact, t int64, hash []byte) []byte {
|
|
var key [keyFromContactLength]byte
|
|
|
|
copy(key[:], d.prefixFromContact(c))
|
|
binary.BigEndian.PutUint64(key[contactPrefixLength:], uint64(t))
|
|
|
|
if hash != nil {
|
|
copy(key[contactPrefixLength+timeLength:], hash)
|
|
}
|
|
|
|
return key[:]
|
|
}
|