status-go/protocol/persistence.go

544 lines
11 KiB
Go

package protocol
import (
"bytes"
"context"
"database/sql"
"encoding/gob"
"encoding/hex"
"time"
"github.com/ethereum/go-ethereum/crypto"
"github.com/pkg/errors"
v1protocol "github.com/status-im/status-go/protocol/v1"
)
var (
// ErrMsgAlreadyExist returned if msg already exist.
ErrMsgAlreadyExist = errors.New("message with given ID already exist")
)
// sqlitePersistence wrapper around sql db with operations common for a client.
type sqlitePersistence struct {
db *sql.DB
}
func (db sqlitePersistence) LastMessageClock(chatID string) (int64, error) {
if chatID == "" {
return 0, errors.New("chat ID is empty")
}
var last sql.NullInt64
err := db.db.QueryRow("SELECT max(clock) FROM user_messages WHERE chat_id = ?", chatID).Scan(&last)
if err != nil {
return 0, err
}
return last.Int64, nil
}
func (db sqlitePersistence) SaveChat(chat Chat) error {
var err error
pkey := []byte{}
// For one to one chatID is an encoded public key
if chat.ChatType == ChatTypeOneToOne {
pkey, err = hex.DecodeString(chat.ID[2:])
if err != nil {
return err
}
// Safety check, make sure is well formed
_, err := crypto.UnmarshalPubkey(pkey)
if err != nil {
return err
}
}
// Encode members
var encodedMembers bytes.Buffer
memberEncoder := gob.NewEncoder(&encodedMembers)
if err := memberEncoder.Encode(chat.Members); err != nil {
return err
}
// Encode membership updates
var encodedMembershipUpdates bytes.Buffer
membershipUpdatesEncoder := gob.NewEncoder(&encodedMembershipUpdates)
if err := membershipUpdatesEncoder.Encode(chat.MembershipUpdates); err != nil {
return err
}
// Insert record
stmt, err := db.db.Prepare(`INSERT INTO chats(id, name, color, active, type, timestamp, deleted_at_clock_value, public_key, unviewed_message_count, last_clock_value, last_message_content_type, last_message_content, last_message_timestamp, last_message_clock_value, members, membership_updates)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
if err != nil {
return err
}
defer stmt.Close()
_, err = stmt.Exec(
chat.ID,
chat.Name,
chat.Color,
chat.Active,
chat.ChatType,
chat.Timestamp,
chat.DeletedAtClockValue,
pkey,
chat.UnviewedMessagesCount,
chat.LastClockValue,
chat.LastMessageContentType,
chat.LastMessageContent,
chat.LastMessageTimestamp,
chat.LastMessageClockValue,
encodedMembers.Bytes(),
encodedMembershipUpdates.Bytes(),
)
if err != nil {
return err
}
return err
}
func (db sqlitePersistence) DeleteChat(chatID string) error {
_, err := db.db.Exec("DELETE FROM chats WHERE id = ?", chatID)
return err
}
func (db sqlitePersistence) Chats() ([]*Chat, error) {
return db.chats(nil)
}
func (db sqlitePersistence) chats(tx *sql.Tx) (chats []*Chat, err error) {
if tx == nil {
tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{})
if err != nil {
return
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
// don't shadow original error
_ = tx.Rollback()
}()
}
rows, err := tx.Query(`
SELECT
id,
name,
color,
active,
type,
timestamp,
deleted_at_clock_value,
public_key,
unviewed_message_count,
last_clock_value,
last_message_content_type,
last_message_content,
last_message_timestamp,
last_message_clock_value,
members,
membership_updates
FROM chats
ORDER BY chats.timestamp DESC
`)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
var (
lastMessageContentType sql.NullString
lastMessageContent sql.NullString
lastMessageTimestamp sql.NullInt64
lastMessageClockValue sql.NullInt64
chat Chat
encodedMembers []byte
encodedMembershipUpdates []byte
pkey []byte
)
err = rows.Scan(
&chat.ID,
&chat.Name,
&chat.Color,
&chat.Active,
&chat.ChatType,
&chat.Timestamp,
&chat.DeletedAtClockValue,
&pkey,
&chat.UnviewedMessagesCount,
&chat.LastClockValue,
&lastMessageContentType,
&lastMessageContent,
&lastMessageTimestamp,
&lastMessageClockValue,
&encodedMembers,
&encodedMembershipUpdates,
)
if err != nil {
return
}
chat.LastMessageContent = lastMessageContent.String
chat.LastMessageContentType = lastMessageContentType.String
chat.LastMessageTimestamp = lastMessageTimestamp.Int64
chat.LastMessageClockValue = lastMessageClockValue.Int64
// Restore members
membersDecoder := gob.NewDecoder(bytes.NewBuffer(encodedMembers))
err = membersDecoder.Decode(&chat.Members)
if err != nil {
return
}
// Restore membership updates
membershipUpdatesDecoder := gob.NewDecoder(bytes.NewBuffer(encodedMembershipUpdates))
err = membershipUpdatesDecoder.Decode(&chat.MembershipUpdates)
if err != nil {
return
}
if len(pkey) != 0 {
chat.PublicKey, err = crypto.UnmarshalPubkey(pkey)
if err != nil {
return
}
}
chats = append(chats, &chat)
}
return
}
func (db sqlitePersistence) Contacts() ([]*Contact, error) {
rows, err := db.db.Query(`
SELECT
id,
address,
name,
alias,
identicon,
photo,
last_updated,
system_tags,
device_info,
ens_verified,
ens_verified_at,
tribute_to_talk
FROM contacts
`)
if err != nil {
return nil, err
}
defer rows.Close()
var response []*Contact
for rows.Next() {
var (
contact Contact
encodedDeviceInfo []byte
encodedSystemTags []byte
)
err := rows.Scan(
&contact.ID,
&contact.Address,
&contact.Name,
&contact.Alias,
&contact.Identicon,
&contact.Photo,
&contact.LastUpdated,
&encodedSystemTags,
&encodedDeviceInfo,
&contact.ENSVerified,
&contact.ENSVerifiedAt,
&contact.TributeToTalk,
)
if err != nil {
return nil, err
}
if encodedDeviceInfo != nil {
// Restore device info
deviceInfoDecoder := gob.NewDecoder(bytes.NewBuffer(encodedDeviceInfo))
if err := deviceInfoDecoder.Decode(&contact.DeviceInfo); err != nil {
return nil, err
}
}
if encodedSystemTags != nil {
// Restore system tags
systemTagsDecoder := gob.NewDecoder(bytes.NewBuffer(encodedSystemTags))
if err := systemTagsDecoder.Decode(&contact.SystemTags); err != nil {
return nil, err
}
}
response = append(response, &contact)
}
return response, nil
}
func (db sqlitePersistence) SetContactsENSData(contacts []Contact) error {
tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
// don't shadow original error
_ = tx.Rollback()
}()
// Ensure contacts exists
err = db.SetContactsGeneratedData(contacts, tx)
if err != nil {
return err
}
// Update ens data
for _, contact := range contacts {
_, err := tx.Exec(`UPDATE contacts SET name = ?, ens_verified = ? , ens_verified_at = ? WHERE id = ?`, contact.Name, contact.ENSVerified, contact.ENSVerifiedAt, contact.ID)
if err != nil {
return err
}
}
return nil
}
// SetContactsGeneratedData sets a contact generated data if not existing already
// in the database
func (db sqlitePersistence) SetContactsGeneratedData(contacts []Contact, tx *sql.Tx) (err error) {
if tx == nil {
tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{})
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
// don't shadow original error
_ = tx.Rollback()
}()
}
for _, contact := range contacts {
_, err = tx.Exec(`
INSERT OR IGNORE INTO contacts(
id,
address,
name,
alias,
identicon,
photo,
last_updated,
tribute_to_talk
) VALUES (?, ?, "", ?, ?, "", 0, "")`,
contact.ID,
contact.Address,
contact.Alias,
contact.Identicon,
)
if err != nil {
return
}
}
return
}
func (db sqlitePersistence) SaveContact(contact Contact, tx *sql.Tx) (err error) {
if tx == nil {
tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{})
if err != nil {
return
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
// don't shadow original error
_ = tx.Rollback()
}()
}
// Encode device info
var encodedDeviceInfo bytes.Buffer
deviceInfoEncoder := gob.NewEncoder(&encodedDeviceInfo)
err = deviceInfoEncoder.Encode(contact.DeviceInfo)
if err != nil {
return
}
// Encoded system tags
var encodedSystemTags bytes.Buffer
systemTagsEncoder := gob.NewEncoder(&encodedSystemTags)
err = systemTagsEncoder.Encode(contact.SystemTags)
if err != nil {
return
}
// Insert record
stmt, err := tx.Prepare(`
INSERT INTO contacts(
id,
address,
name,
alias,
identicon,
photo,
last_updated,
system_tags,
device_info,
ens_verified,
ens_verified_at,
tribute_to_talk
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`)
if err != nil {
return
}
defer stmt.Close()
_, err = stmt.Exec(
contact.ID,
contact.Address,
contact.Name,
contact.Alias,
contact.Identicon,
contact.Photo,
contact.LastUpdated,
encodedSystemTags.Bytes(),
encodedDeviceInfo.Bytes(),
contact.ENSVerified,
contact.ENSVerifiedAt,
contact.TributeToTalk,
)
return
}
// Messages returns messages for a given contact, in a given period. Ordered by a timestamp.
func (db sqlitePersistence) Messages(from, to time.Time) (result []*v1protocol.Message, err error) {
rows, err := db.db.Query(`
SELECT
id,
chat_id,
content_type,
message_type,
text,
clock,
timestamp,
content_chat_id,
content_text,
public_key,
flags
FROM user_messages
WHERE timestamp >= ? AND timestamp <= ?
ORDER BY timestamp`,
v1protocol.TimestampInMsFromTime(from),
v1protocol.TimestampInMsFromTime(to),
)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
msg := v1protocol.Message{
Content: v1protocol.Content{},
}
var pkey []byte
err = rows.Scan(
&msg.ID, &msg.ChatID, &msg.ContentT, &msg.MessageT, &msg.Text, &msg.Clock,
&msg.Timestamp, &msg.Content.ChatID, &msg.Content.Text, &pkey, &msg.Flags,
)
if err != nil {
return
}
if len(pkey) != 0 {
msg.SigPubKey, err = crypto.UnmarshalPubkey(pkey)
if err != nil {
return
}
}
result = append(result, &msg)
}
return
}
func (db sqlitePersistence) SaveMessages(messages []*v1protocol.Message) (last int64, err error) {
tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
if err != nil {
return
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
// don't shadow original error
_ = tx.Rollback()
}()
stmt, err := tx.Prepare(`
INSERT OR IGNORE INTO user_messages(
id,
chat_id,
content_type,
message_type,
text,
clock,
timestamp,
content_chat_id,
content_text,
public_key,
flags
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`)
if err != nil {
return
}
var rst sql.Result
for _, msg := range messages {
var pkey []byte
if msg.SigPubKey != nil {
pkey = crypto.FromECDSAPub(msg.SigPubKey)
}
rst, err = stmt.Exec(
msg.ID, msg.ChatID, msg.ContentT, msg.MessageT, msg.Text, msg.Clock, msg.Timestamp,
msg.Content.ChatID, msg.Content.Text, pkey, msg.Flags,
)
if err != nil {
return
}
last, err = rst.LastInsertId()
if err != nil {
return
}
}
return
}