2019-11-21 16:19:22 +00:00
package protocol
2019-07-17 22:25:42 +00:00
import (
2019-07-30 18:39:16 +00:00
"bytes"
2019-07-17 22:25:42 +00:00
"context"
"database/sql"
2019-07-30 18:39:16 +00:00
"encoding/gob"
2020-07-30 20:54:33 +00:00
"encoding/json"
2020-10-28 13:18:24 +00:00
"time"
2019-07-17 22:25:42 +00:00
"github.com/pkg/errors"
2020-01-15 11:36:49 +00:00
2020-01-10 18:59:01 +00:00
"github.com/status-im/status-go/eth-node/crypto"
2020-12-15 15:28:05 +00:00
"github.com/status-im/status-go/images"
2020-07-22 07:41:40 +00:00
"github.com/status-im/status-go/protocol/common"
2020-12-15 14:43:41 +00:00
"github.com/status-im/status-go/protocol/protobuf"
2019-07-17 22:25:42 +00:00
)
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 {
2021-03-09 12:48:15 +00:00
* common . RawMessagesPersistence
2019-07-17 22:25:42 +00:00
db * sql . DB
}
2021-03-09 12:48:15 +00:00
func NewSQLitePersistence ( db * sql . DB ) * sqlitePersistence {
return & sqlitePersistence { common . NewRawMessagesPersistence ( db ) , db }
}
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
func ( db sqlitePersistence ) SaveChat ( chat Chat ) error {
2020-02-07 11:56:30 +00:00
err := chat . Validate ( )
if err != nil {
return err
}
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
return db . saveChat ( nil , chat )
}
2019-07-17 22:25:42 +00:00
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
func ( db sqlitePersistence ) SaveChats ( chats [ ] * Chat ) error {
tx , err := db . db . BeginTx ( context . Background ( ) , & sql . TxOptions { } )
2020-06-17 18:55:49 +00:00
if err != nil {
return err
}
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
// don't shadow original error
_ = tx . Rollback ( )
} ( )
for _ , chat := range chats {
err := db . saveChat ( tx , * chat )
if err != nil {
return err
}
2019-07-17 22:25:42 +00:00
}
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
return nil
2019-07-17 22:25:42 +00:00
}
2019-12-02 15:34:05 +00:00
func ( db sqlitePersistence ) SaveContacts ( contacts [ ] * Contact ) error {
tx , err := db . db . BeginTx ( context . Background ( ) , & sql . TxOptions { } )
2020-06-17 18:55:49 +00:00
if err != nil {
return err
}
2019-12-02 15:34:05 +00:00
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
// don't shadow original error
_ = tx . Rollback ( )
} ( )
for _ , contact := range contacts {
err := db . SaveContact ( contact , tx )
if err != nil {
return err
}
}
return nil
}
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
func ( db sqlitePersistence ) saveChat ( tx * sql . Tx , chat Chat ) error {
2019-07-30 18:39:16 +00:00
var err error
Move to protobuf for Message type (#1706)
* Use a single Message type `v1/message.go` and `message.go` are the same now, and they embed `protobuf.ChatMessage`
* Use `SendChatMessage` for sending chat messages, this is basically the old `Send` but a bit more flexible so we can send different message types (stickers,commands), and not just text.
* Remove dedup from services/shhext. Because now we process in status-protocol, dedup makes less sense, as those messages are going to be processed anyway, so removing for now, we can re-evaluate if bringing it to status-go or not.
* Change the various retrieveX method to a single one:
`RetrieveAll` will be processing those messages that it can process (Currently only `Message`), and return the rest in `RawMessages` (still transit). The format for the response is:
`Chats`: -> The chats updated by receiving the message
`Messages`: -> The messages retrieved (already matched to a chat)
`Contacts`: -> The contacts updated by the messages
`RawMessages` -> Anything else that can't be parsed, eventually as we move everything to status-protocol-go this will go away.
2019-12-05 16:25:34 +00:00
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 ( )
} ( )
}
2019-07-30 18:39:16 +00:00
// 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
}
2020-07-30 20:54:33 +00:00
// encode last message
var encodedLastMessage [ ] byte
if chat . LastMessage != nil {
encodedLastMessage , err = json . Marshal ( chat . LastMessage )
if err != nil {
return err
}
}
2019-07-30 18:39:16 +00:00
// Insert record
2021-06-01 12:13:17 +00:00
stmt , err := tx . Prepare ( ` INSERT INTO chats ( id , name , color , active , type , timestamp , deleted_at_clock_value , unviewed_message_count , unviewed_mentions_count , last_clock_value , last_message , members , membership_updates , muted , invitation_admin , profile , community_id , joined , synced_from , synced_to , description )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? ) ` )
2019-07-30 18:39:16 +00:00
if err != nil {
return err
}
defer stmt . Close ( )
_ , err = stmt . Exec (
2019-08-20 11:20:25 +00:00
chat . ID ,
2019-07-30 18:39:16 +00:00
chat . Name ,
chat . Color ,
chat . Active ,
chat . ChatType ,
chat . Timestamp ,
chat . DeletedAtClockValue ,
chat . UnviewedMessagesCount ,
2021-05-26 06:38:25 +00:00
chat . UnviewedMentionsCount ,
2019-07-30 18:39:16 +00:00
chat . LastClockValue ,
2020-07-30 20:54:33 +00:00
encodedLastMessage ,
2019-07-30 18:39:16 +00:00
encodedMembers . Bytes ( ) ,
encodedMembershipUpdates . Bytes ( ) ,
2020-06-26 07:46:14 +00:00
chat . Muted ,
2020-08-07 13:49:37 +00:00
chat . InvitationAdmin ,
2020-10-20 15:10:28 +00:00
chat . Profile ,
2020-11-18 09:16:51 +00:00
chat . CommunityID ,
2021-05-18 08:48:42 +00:00
chat . Joined ,
2021-05-14 10:55:42 +00:00
chat . SyncedFrom ,
chat . SyncedTo ,
2021-06-01 12:13:17 +00:00
chat . Description ,
2019-07-30 18:39:16 +00:00
)
2020-10-20 15:10:28 +00:00
2019-07-30 18:39:16 +00:00
if err != nil {
return err
}
return err
}
2021-03-25 15:15:22 +00:00
func ( db sqlitePersistence ) SetSyncTimestamps ( syncedFrom , syncedTo uint32 , chatID string ) error {
_ , err := db . db . Exec ( ` UPDATE chats SET synced_from = ?, synced_to = ? WHERE id = ? ` , syncedFrom , syncedTo , chatID )
return err
}
2020-12-22 15:39:05 +00:00
func ( db sqlitePersistence ) DeleteChat ( chatID string ) ( err error ) {
var tx * sql . Tx
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 ( )
} ( )
_ , err = tx . Exec ( "DELETE FROM chats WHERE id = ?" , chatID )
if err != nil {
return
}
2020-12-22 16:20:12 +00:00
_ , err = tx . Exec ( ` DELETE FROM user_messages WHERE local_chat_id = ? ` , chatID )
2020-12-22 15:39:05 +00:00
return
2019-07-30 18:39:16 +00:00
}
2020-06-26 07:46:14 +00:00
func ( db sqlitePersistence ) MuteChat ( chatID string ) error {
_ , err := db . db . Exec ( "UPDATE chats SET muted = 1 WHERE id = ?" , chatID )
return err
}
func ( db sqlitePersistence ) UnmuteChat ( chatID string ) error {
_ , err := db . db . Exec ( "UPDATE chats SET muted = 0 WHERE id = ?" , chatID )
return err
}
2019-08-29 06:33:46 +00:00
func ( db sqlitePersistence ) Chats ( ) ( [ ] * Chat , error ) {
return db . chats ( nil )
2019-08-20 11:20:25 +00:00
}
2019-07-30 18:39:16 +00:00
2019-11-15 08:52:28 +00:00
func ( db sqlitePersistence ) chats ( tx * sql . Tx ) ( chats [ ] * Chat , err error ) {
2019-08-20 11:20:25 +00:00
if tx == nil {
tx , err = db . db . BeginTx ( context . Background ( ) , & sql . TxOptions { } )
if err != nil {
2019-11-15 08:52:28 +00:00
return
2019-08-20 11:20:25 +00:00
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
// don't shadow original error
_ = tx . Rollback ( )
} ( )
}
2019-11-15 08:52:28 +00:00
rows , err := tx . Query ( `
SELECT
2020-05-20 12:16:12 +00:00
chats . id ,
chats . name ,
chats . color ,
chats . active ,
chats . type ,
chats . timestamp ,
chats . deleted_at_clock_value ,
chats . unviewed_message_count ,
2021-05-26 06:38:25 +00:00
chats . unviewed_mentions_count ,
2020-05-20 12:16:12 +00:00
chats . last_clock_value ,
chats . last_message ,
chats . members ,
chats . membership_updates ,
2020-06-26 07:46:14 +00:00
chats . muted ,
2020-08-07 13:49:37 +00:00
chats . invitation_admin ,
2020-10-20 15:10:28 +00:00
chats . profile ,
2020-11-18 09:16:51 +00:00
chats . community_id ,
2021-03-25 15:15:22 +00:00
chats . joined ,
chats . synced_from ,
chats . synced_to ,
2021-06-01 12:13:17 +00:00
chats . description ,
2020-05-20 12:16:12 +00:00
contacts . identicon ,
contacts . alias
FROM chats LEFT JOIN contacts ON chats . id = contacts . id
2019-11-15 08:52:28 +00:00
ORDER BY chats . timestamp DESC
` )
2019-07-30 18:39:16 +00:00
if err != nil {
2019-11-15 08:52:28 +00:00
return
2019-07-30 18:39:16 +00:00
}
defer rows . Close ( )
for rows . Next ( ) {
2019-11-15 08:52:28 +00:00
var (
2020-05-20 12:16:12 +00:00
alias sql . NullString
identicon sql . NullString
2020-08-07 13:49:37 +00:00
invitationAdmin sql . NullString
2020-10-20 15:10:28 +00:00
profile sql . NullString
2021-03-25 15:15:22 +00:00
syncedFrom sql . NullInt64
syncedTo sql . NullInt64
2019-11-15 08:52:28 +00:00
chat Chat
encodedMembers [ ] byte
encodedMembershipUpdates [ ] byte
2020-07-30 20:54:33 +00:00
lastMessageBytes [ ] byte
2019-11-15 08:52:28 +00:00
)
err = rows . Scan (
2019-07-30 18:39:16 +00:00
& chat . ID ,
& chat . Name ,
& chat . Color ,
& chat . Active ,
& chat . ChatType ,
& chat . Timestamp ,
& chat . DeletedAtClockValue ,
& chat . UnviewedMessagesCount ,
2021-05-26 06:38:25 +00:00
& chat . UnviewedMentionsCount ,
2019-07-30 18:39:16 +00:00
& chat . LastClockValue ,
2020-07-30 20:54:33 +00:00
& lastMessageBytes ,
2019-07-30 18:39:16 +00:00
& encodedMembers ,
& encodedMembershipUpdates ,
2020-06-26 07:46:14 +00:00
& chat . Muted ,
2020-08-07 13:49:37 +00:00
& invitationAdmin ,
2020-10-20 15:10:28 +00:00
& profile ,
2020-11-18 09:16:51 +00:00
& chat . CommunityID ,
2021-05-18 08:48:42 +00:00
& chat . Joined ,
2021-03-25 15:15:22 +00:00
& syncedFrom ,
& syncedTo ,
2021-06-01 12:13:17 +00:00
& chat . Description ,
2020-05-20 12:16:12 +00:00
& identicon ,
& alias ,
2019-07-30 18:39:16 +00:00
)
2020-08-07 13:49:37 +00:00
2019-07-30 18:39:16 +00:00
if err != nil {
2019-11-15 08:52:28 +00:00
return
2019-07-30 18:39:16 +00:00
}
2020-08-07 13:49:37 +00:00
if invitationAdmin . Valid {
chat . InvitationAdmin = invitationAdmin . String
}
2020-10-20 15:10:28 +00:00
if profile . Valid {
chat . Profile = profile . String
}
2019-07-30 18:39:16 +00:00
// Restore members
membersDecoder := gob . NewDecoder ( bytes . NewBuffer ( encodedMembers ) )
2019-11-15 08:52:28 +00:00
err = membersDecoder . Decode ( & chat . Members )
if err != nil {
return
2019-07-30 18:39:16 +00:00
}
// Restore membership updates
membershipUpdatesDecoder := gob . NewDecoder ( bytes . NewBuffer ( encodedMembershipUpdates ) )
2019-11-15 08:52:28 +00:00
err = membershipUpdatesDecoder . Decode ( & chat . MembershipUpdates )
if err != nil {
return
2019-07-30 18:39:16 +00:00
}
2021-03-25 15:15:22 +00:00
if syncedFrom . Valid {
chat . SyncedFrom = uint32 ( syncedFrom . Int64 )
}
if syncedTo . Valid {
chat . SyncedTo = uint32 ( syncedTo . Int64 )
}
2020-07-30 20:54:33 +00:00
// Restore last message
if lastMessageBytes != nil {
2020-09-01 13:27:01 +00:00
message := & common . Message { }
2020-07-30 20:54:33 +00:00
if err = json . Unmarshal ( lastMessageBytes , message ) ; err != nil {
return
}
chat . LastMessage = message
}
2020-05-20 12:16:12 +00:00
chat . Alias = alias . String
chat . Identicon = identicon . String
2019-11-15 08:52:28 +00:00
chats = append ( chats , & chat )
2019-07-30 18:39:16 +00:00
}
2019-11-15 08:52:28 +00:00
return
2019-07-30 18:39:16 +00:00
}
2019-12-02 15:34:05 +00:00
func ( db sqlitePersistence ) Chat ( chatID string ) ( * Chat , error ) {
var (
chat Chat
encodedMembers [ ] byte
encodedMembershipUpdates [ ] byte
2020-08-17 06:37:18 +00:00
lastMessageBytes [ ] byte
2020-08-07 13:49:37 +00:00
invitationAdmin sql . NullString
2020-10-20 15:10:28 +00:00
profile sql . NullString
2019-12-02 15:34:05 +00:00
)
err := db . db . QueryRow ( `
SELECT
id ,
name ,
color ,
active ,
type ,
timestamp ,
deleted_at_clock_value ,
unviewed_message_count ,
2021-05-26 06:38:25 +00:00
unviewed_mentions_count ,
2019-12-02 15:34:05 +00:00
last_clock_value ,
last_message ,
members ,
2020-06-26 07:46:14 +00:00
membership_updates ,
2020-08-07 13:49:37 +00:00
muted ,
2020-10-20 15:10:28 +00:00
invitation_admin ,
2020-11-18 09:16:51 +00:00
profile ,
2021-05-18 08:48:42 +00:00
community_id ,
2021-06-01 12:13:17 +00:00
joined ,
description
2019-12-02 15:34:05 +00:00
FROM chats
WHERE id = ?
` , chatID ) . Scan ( & chat . ID ,
& chat . Name ,
& chat . Color ,
& chat . Active ,
& chat . ChatType ,
& chat . Timestamp ,
& chat . DeletedAtClockValue ,
& chat . UnviewedMessagesCount ,
2021-05-26 06:38:25 +00:00
& chat . UnviewedMentionsCount ,
2019-12-02 15:34:05 +00:00
& chat . LastClockValue ,
2020-08-17 06:37:18 +00:00
& lastMessageBytes ,
2019-12-02 15:34:05 +00:00
& encodedMembers ,
& encodedMembershipUpdates ,
2020-06-26 07:46:14 +00:00
& chat . Muted ,
2020-08-07 13:49:37 +00:00
& invitationAdmin ,
2020-10-20 15:10:28 +00:00
& profile ,
2020-11-18 09:16:51 +00:00
& chat . CommunityID ,
2021-05-18 08:48:42 +00:00
& chat . Joined ,
2021-06-01 12:13:17 +00:00
& chat . Description ,
2019-12-02 15:34:05 +00:00
)
switch err {
case sql . ErrNoRows :
return nil , nil
case nil :
2020-08-07 13:49:37 +00:00
if invitationAdmin . Valid {
chat . InvitationAdmin = invitationAdmin . String
}
2020-10-20 15:10:28 +00:00
if profile . Valid {
chat . Profile = profile . String
}
2019-12-02 15:34:05 +00:00
// Restore members
membersDecoder := gob . NewDecoder ( bytes . NewBuffer ( encodedMembers ) )
err = membersDecoder . Decode ( & chat . Members )
if err != nil {
return nil , err
}
// Restore membership updates
membershipUpdatesDecoder := gob . NewDecoder ( bytes . NewBuffer ( encodedMembershipUpdates ) )
err = membershipUpdatesDecoder . Decode ( & chat . MembershipUpdates )
if err != nil {
return nil , err
}
2020-08-17 06:37:18 +00:00
// Restore last message
if lastMessageBytes != nil {
2020-09-01 13:27:01 +00:00
message := & common . Message { }
2020-08-17 06:37:18 +00:00
if err = json . Unmarshal ( lastMessageBytes , message ) ; err != nil {
return nil , err
}
chat . LastMessage = message
}
2019-12-02 15:34:05 +00:00
return & chat , nil
}
return nil , err
}
2019-07-30 18:39:16 +00:00
func ( db sqlitePersistence ) Contacts ( ) ( [ ] * Contact , error ) {
2020-12-15 15:28:05 +00:00
allContacts := make ( map [ string ] * Contact )
2019-11-15 08:52:28 +00:00
rows , err := db . db . Query ( `
SELECT
2020-12-15 15:28:05 +00:00
c . id ,
c . address ,
2021-01-11 10:32:51 +00:00
v . name ,
v . verified ,
2020-12-15 15:28:05 +00:00
c . alias ,
c . identicon ,
c . last_updated ,
c . system_tags ,
c . device_info ,
c . local_nickname ,
i . image_type ,
i . payload
2021-01-11 10:32:51 +00:00
FROM contacts c LEFT JOIN chat_identity_contacts i ON c . id = i . contact_id LEFT JOIN ens_verification_records v ON c . id = v . public_key
2019-11-15 08:52:28 +00:00
` )
2019-07-30 18:39:16 +00:00
if err != nil {
return nil , err
}
defer rows . Close ( )
for rows . Next ( ) {
2020-12-15 15:28:05 +00:00
2019-11-15 08:52:28 +00:00
var (
contact Contact
encodedDeviceInfo [ ] byte
encodedSystemTags [ ] byte
2020-08-20 14:06:38 +00:00
nickname sql . NullString
2020-12-15 15:28:05 +00:00
imageType sql . NullString
2021-01-11 10:32:51 +00:00
ensName sql . NullString
ensVerified sql . NullBool
2020-12-15 15:28:05 +00:00
imagePayload [ ] byte
2019-11-15 08:52:28 +00:00
)
2020-12-15 15:28:05 +00:00
contact . Images = make ( map [ string ] images . IdentityImage )
2019-07-30 18:39:16 +00:00
err := rows . Scan (
& contact . ID ,
& contact . Address ,
2021-01-11 10:32:51 +00:00
& ensName ,
& ensVerified ,
2019-09-26 07:01:17 +00:00
& contact . Alias ,
& contact . Identicon ,
2019-07-30 18:39:16 +00:00
& contact . LastUpdated ,
& encodedSystemTags ,
& encodedDeviceInfo ,
2020-08-20 14:06:38 +00:00
& nickname ,
2020-12-15 15:28:05 +00:00
& imageType ,
& imagePayload ,
2019-07-30 18:39:16 +00:00
)
if err != nil {
return nil , err
}
2020-08-20 14:06:38 +00:00
if nickname . Valid {
contact . LocalNickname = nickname . String
}
2021-01-11 10:32:51 +00:00
if ensName . Valid {
contact . Name = ensName . String
}
if ensVerified . Valid {
contact . ENSVerified = ensVerified . Bool
}
2019-09-26 07:01:17 +00:00
if encodedDeviceInfo != nil {
// Restore device info
deviceInfoDecoder := gob . NewDecoder ( bytes . NewBuffer ( encodedDeviceInfo ) )
if err := deviceInfoDecoder . Decode ( & contact . DeviceInfo ) ; err != nil {
return nil , err
}
2019-07-30 18:39:16 +00:00
}
2019-09-26 07:01:17 +00:00
if encodedSystemTags != nil {
// Restore system tags
systemTagsDecoder := gob . NewDecoder ( bytes . NewBuffer ( encodedSystemTags ) )
if err := systemTagsDecoder . Decode ( & contact . SystemTags ) ; err != nil {
return nil , err
}
2019-07-30 18:39:16 +00:00
}
2020-12-15 15:28:05 +00:00
previousContact , ok := allContacts [ contact . ID ]
if ! ok {
if imageType . Valid {
contact . Images [ imageType . String ] = images . IdentityImage { Name : imageType . String , Payload : imagePayload }
}
allContacts [ contact . ID ] = & contact
} else if imageType . Valid {
previousContact . Images [ imageType . String ] = images . IdentityImage { Name : imageType . String , Payload : imagePayload }
allContacts [ contact . ID ] = previousContact
}
2019-07-30 18:39:16 +00:00
}
2020-12-15 15:28:05 +00:00
var response [ ] * Contact
for key := range allContacts {
response = append ( response , allContacts [ key ] )
}
2019-07-30 18:39:16 +00:00
return response , nil
}
2020-12-15 15:28:05 +00:00
func ( db sqlitePersistence ) SaveContactChatIdentity ( contactID string , chatIdentity * protobuf . ChatIdentity ) ( updated bool , err error ) {
if chatIdentity . Clock == 0 {
return false , errors . New ( "clock value unset" )
}
tx , err := db . db . BeginTx ( context . Background ( ) , & sql . TxOptions { } )
if err != nil {
return false , err
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
// don't shadow original error
_ = tx . Rollback ( )
} ( )
for imageType , image := range chatIdentity . Images {
var exists bool
err := tx . QueryRow ( ` SELECT EXISTS(SELECT 1 FROM chat_identity_contacts WHERE contact_id = ? AND image_type = ? AND clock_value >= ?) ` , contactID , imageType , chatIdentity . Clock ) . Scan ( & exists )
if err != nil {
return false , err
}
if exists {
continue
}
stmt , err := tx . Prepare ( ` INSERT INTO chat_identity_contacts (contact_id, image_type, clock_value, payload) VALUES (?, ?, ?, ?) ` )
if err != nil {
return false , err
}
defer stmt . Close ( )
if image . Payload == nil {
continue
}
// Validate image URI to make sure it's serializable
_ , err = images . GetPayloadDataURI ( image . Payload )
if err != nil {
return false , err
}
_ , err = stmt . Exec (
contactID ,
imageType ,
chatIdentity . Clock ,
image . Payload ,
)
if err != nil {
return false , err
}
updated = true
}
return
}
2020-12-15 14:43:41 +00:00
func ( db sqlitePersistence ) ExpiredEmojiReactionsIDs ( maxSendCount int ) ( [ ] string , error ) {
ids := [ ] string { }
rows , err := db . db . Query ( `
SELECT
id
FROM
raw_messages
WHERE
message_type = ? AND sent = ? AND send_count <= ? ` ,
protobuf . ApplicationMetadataMessage_EMOJI_REACTION , false , maxSendCount )
if err != nil {
return ids , err
}
defer rows . Close ( )
for rows . Next ( ) {
var id string
if err := rows . Scan ( & id ) ; err != nil {
return ids , err
}
ids = append ( ids , id )
}
return ids , nil
}
2019-12-02 15:34:05 +00:00
func ( db sqlitePersistence ) SaveContact ( contact * Contact , tx * sql . Tx ) ( err error ) {
2019-08-20 11:20:25 +00:00
if tx == nil {
tx , err = db . db . BeginTx ( context . Background ( ) , & sql . TxOptions { } )
if err != nil {
2019-11-15 08:52:28 +00:00
return
2019-08-20 11:20:25 +00:00
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
// don't shadow original error
_ = tx . Rollback ( )
} ( )
}
2019-07-30 18:39:16 +00:00
// Encode device info
var encodedDeviceInfo bytes . Buffer
deviceInfoEncoder := gob . NewEncoder ( & encodedDeviceInfo )
2019-11-15 08:52:28 +00:00
err = deviceInfoEncoder . Encode ( contact . DeviceInfo )
if err != nil {
return
2019-07-30 18:39:16 +00:00
}
// Encoded system tags
var encodedSystemTags bytes . Buffer
systemTagsEncoder := gob . NewEncoder ( & encodedSystemTags )
2019-11-15 08:52:28 +00:00
err = systemTagsEncoder . Encode ( contact . SystemTags )
if err != nil {
return
2019-07-30 18:39:16 +00:00
}
// Insert record
2021-01-11 10:32:51 +00:00
// NOTE: tribute_to_talk is not used anymore, but it's not nullable
// Removing it requires copying over the table which might be expensive
// when there are many contacts, so best avoiding it
2019-11-15 08:52:28 +00:00
stmt , err := tx . Prepare ( `
INSERT INTO contacts (
id ,
address ,
name ,
alias ,
identicon ,
last_updated ,
system_tags ,
device_info ,
2020-12-15 15:28:05 +00:00
local_nickname ,
2021-01-11 10:32:51 +00:00
photo ,
tribute_to_talk
) VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , "" )
2019-11-15 08:52:28 +00:00
` )
2019-07-30 18:39:16 +00:00
if err != nil {
2019-11-15 08:52:28 +00:00
return
2019-07-30 18:39:16 +00:00
}
defer stmt . Close ( )
_ , err = stmt . Exec (
contact . ID ,
contact . Address ,
contact . Name ,
2019-09-26 07:01:17 +00:00
contact . Alias ,
contact . Identicon ,
2019-07-30 18:39:16 +00:00
contact . LastUpdated ,
encodedSystemTags . Bytes ( ) ,
encodedDeviceInfo . Bytes ( ) ,
2020-08-20 14:06:38 +00:00
contact . LocalNickname ,
2020-12-15 15:28:05 +00:00
// Photo is not used anymore but constrained to be NOT NULL
// we set it to blank for now to avoid a migration of the table
"" ,
2019-07-30 18:39:16 +00:00
)
2019-11-15 08:52:28 +00:00
return
2019-07-30 18:39:16 +00:00
}
2020-01-10 18:59:01 +00:00
func ( db sqlitePersistence ) SaveTransactionToValidate ( transaction * TransactionToValidate ) error {
compressedKey := crypto . CompressPubkey ( transaction . From )
_ , err := db . db . Exec ( ` INSERT INTO messenger_transactions_to_validate (
command_id ,
message_id ,
transaction_hash ,
retry_count ,
first_seen ,
public_key ,
signature ,
to_validate )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? ) ` ,
transaction . CommandID ,
transaction . MessageID ,
transaction . TransactionHash ,
transaction . RetryCount ,
transaction . FirstSeen ,
compressedKey ,
transaction . Signature ,
transaction . Validate ,
)
return err
}
func ( db sqlitePersistence ) UpdateTransactionToValidate ( transaction * TransactionToValidate ) error {
_ , err := db . db . Exec ( ` UPDATE messenger_transactions_to_validate
SET retry_count = ? , to_validate = ?
WHERE transaction_hash = ? ` ,
transaction . RetryCount ,
transaction . Validate ,
transaction . TransactionHash ,
)
return err
}
func ( db sqlitePersistence ) TransactionsToValidate ( ) ( [ ] * TransactionToValidate , error ) {
var transactions [ ] * TransactionToValidate
rows , err := db . db . Query ( `
SELECT
command_id ,
message_id ,
transaction_hash ,
retry_count ,
first_seen ,
public_key ,
signature ,
to_validate
FROM messenger_transactions_to_validate
WHERE to_validate = 1 ;
` )
if err != nil {
return nil , err
}
defer rows . Close ( )
for rows . Next ( ) {
var t TransactionToValidate
var pkBytes [ ] byte
err = rows . Scan (
& t . CommandID ,
& t . MessageID ,
& t . TransactionHash ,
& t . RetryCount ,
& t . FirstSeen ,
& pkBytes ,
& t . Signature ,
& t . Validate ,
)
if err != nil {
return nil , err
}
publicKey , err := crypto . DecompressPubkey ( pkBytes )
if err != nil {
return nil , err
}
t . From = publicKey
transactions = append ( transactions , & t )
}
return transactions , nil
}
2020-10-28 13:18:24 +00:00
2020-12-16 18:28:34 +00:00
func ( db sqlitePersistence ) GetWhenChatIdentityLastPublished ( chatID string ) ( t int64 , hash [ ] byte , err error ) {
2020-12-10 10:12:51 +00:00
rows , err := db . db . Query ( "SELECT clock_value, hash FROM chat_identity_last_published WHERE chat_id = ?" , chatID )
2020-10-28 13:18:24 +00:00
if err != nil {
2020-12-16 18:28:34 +00:00
return t , nil , err
2020-10-28 13:18:24 +00:00
}
2020-12-16 18:17:38 +00:00
defer func ( ) {
err = rows . Close ( )
} ( )
2020-10-28 13:18:24 +00:00
for rows . Next ( ) {
2020-12-16 18:28:34 +00:00
err = rows . Scan ( & t , & hash )
2020-10-28 13:18:24 +00:00
if err != nil {
2020-12-16 18:28:34 +00:00
return t , nil , err
2020-10-28 13:18:24 +00:00
}
}
2020-12-16 18:17:38 +00:00
return t , hash , nil
2020-10-28 13:18:24 +00:00
}
2020-12-16 18:17:38 +00:00
func ( db sqlitePersistence ) SaveWhenChatIdentityLastPublished ( chatID string , hash [ ] byte ) ( err error ) {
2020-10-28 13:18:24 +00:00
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 ( )
} ( )
2020-12-10 10:12:51 +00:00
stmt , err := tx . Prepare ( "INSERT INTO chat_identity_last_published (chat_id, clock_value, hash) VALUES (?, ?, ?)" )
2020-10-28 13:18:24 +00:00
if err != nil {
return err
}
defer stmt . Close ( )
2020-12-10 10:12:51 +00:00
_ , err = stmt . Exec ( chatID , time . Now ( ) . Unix ( ) , hash )
2020-10-28 13:18:24 +00:00
if err != nil {
return err
}
return nil
}