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"
2024-01-24 20:09:28 +00:00
"strings"
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
2022-06-08 18:39:53 +00:00
"github.com/ethereum/go-ethereum/log"
"github.com/mat/besticon/besticon"
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"
2022-08-08 15:22:22 +00:00
userimage "github.com/status-im/status-go/images"
2020-07-22 07:41:40 +00:00
"github.com/status-im/status-go/protocol/common"
2022-08-02 12:56:26 +00:00
"github.com/status-im/status-go/protocol/identity"
2020-12-15 14:43:41 +00:00
"github.com/status-im/status-go/protocol/protobuf"
2022-06-08 18:39:53 +00:00
"github.com/status-im/status-go/services/browsers"
2019-07-17 22:25:42 +00:00
)
var (
// ErrMsgAlreadyExist returned if msg already exist.
ErrMsgAlreadyExist = errors . New ( "message with given ID already exist" )
2021-10-28 15:21:28 +00:00
HoursInTwoWeeks = 336
2019-07-17 22:25:42 +00:00
)
// 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
}
2022-03-28 10:10:40 +00:00
func newSQLitePersistence ( db * sql . DB ) * sqlitePersistence {
2021-03-09 12:48:15 +00:00
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
2023-04-16 15:06:00 +00:00
stmt , err := tx . Prepare ( ` INSERT INTO chats ( id , name , color , emoji , active , type , timestamp , deleted_at_clock_value , unviewed_message_count , unviewed_mentions_count , last_clock_value , last_message , members , membership_updates , muted , muted_till , invitation_admin , profile , community_id , joined , synced_from , synced_to , first_message_timestamp , description , highlight , read_messages_at_clock_value , received_invitation_admin , image_payload )
VALUES ( ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? , ? ) ` )
2019-07-30 18:39:16 +00:00
if err != nil {
return err
}
defer stmt . Close ( )
2022-08-08 15:22:22 +00:00
var imagePayload [ ] byte
if len ( chat . Base64Image ) > 0 {
imagePayload , err = userimage . GetPayloadFromURI ( chat . Base64Image )
if err != nil {
return err
}
}
2019-07-30 18:39:16 +00:00
_ , 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 ,
2021-10-04 13:02:25 +00:00
chat . Emoji ,
2019-07-30 18:39:16 +00:00
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 ,
2023-04-16 15:06:00 +00:00
chat . MuteTill ,
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 ,
2022-09-02 08:36:07 +00:00
chat . FirstMessageTimestamp ,
2021-06-01 12:13:17 +00:00
chat . Description ,
2021-10-21 17:04:56 +00:00
chat . Highlight ,
2021-10-12 10:33:32 +00:00
chat . ReadMessagesAtClockValue ,
2021-11-25 15:21:42 +00:00
chat . ReceivedInvitationAdmin ,
2022-08-08 15:22:22 +00:00
imagePayload ,
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
}
2023-04-16 15:06:00 +00:00
func ( db sqlitePersistence ) MuteChat ( chatID string , mutedTill time . Time ) error {
mutedTillFormatted := mutedTill . Format ( time . RFC3339 )
_ , err := db . db . Exec ( "UPDATE chats SET muted = 1, muted_till = ? WHERE id = ?" , mutedTillFormatted , chatID )
2020-06-26 07:46:14 +00:00
return err
}
func ( db sqlitePersistence ) UnmuteChat ( chatID string ) error {
2023-04-16 15:06:00 +00:00
_ , err := db . db . Exec ( "UPDATE chats SET muted = 0, muted_till = 0 WHERE id = ?" , chatID )
2020-06-26 07:46:14 +00:00
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 ,
2021-10-04 13:02:25 +00:00
chats . emoji ,
2020-05-20 12:16:12 +00:00
chats . active ,
chats . type ,
chats . timestamp ,
chats . deleted_at_clock_value ,
2022-08-08 15:22:22 +00:00
chats . read_messages_at_clock_value ,
2020-05-20 12:16:12 +00:00
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 ,
2023-04-16 15:06:00 +00:00
chats . muted_till ,
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 ,
2022-09-02 08:36:07 +00:00
chats . first_message_timestamp ,
2021-06-01 12:13:17 +00:00
chats . description ,
2021-10-21 17:04:56 +00:00
contacts . alias ,
2022-08-08 15:22:22 +00:00
chats . highlight ,
chats . received_invitation_admin ,
chats . image_payload
2020-05-20 12:16:12 +00:00
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
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
2022-09-02 08:36:07 +00:00
firstMessageTimestamp sql . NullInt64
2023-04-16 15:06:00 +00:00
MuteTill sql . NullTime
2019-11-15 08:52:28 +00:00
chat Chat
encodedMembers [ ] byte
encodedMembershipUpdates [ ] byte
2020-07-30 20:54:33 +00:00
lastMessageBytes [ ] byte
2022-08-08 15:22:22 +00:00
imagePayload [ ] 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 ,
2021-10-04 13:02:25 +00:00
& chat . Emoji ,
2019-07-30 18:39:16 +00:00
& chat . Active ,
& chat . ChatType ,
& chat . Timestamp ,
& chat . DeletedAtClockValue ,
2021-10-12 10:33:32 +00:00
& chat . ReadMessagesAtClockValue ,
2019-07-30 18:39:16 +00:00
& 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 ,
2023-04-16 15:06:00 +00:00
& MuteTill ,
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 ,
2022-09-02 08:36:07 +00:00
& firstMessageTimestamp ,
2021-06-01 12:13:17 +00:00
& chat . Description ,
2020-05-20 12:16:12 +00:00
& alias ,
2021-10-21 17:04:56 +00:00
& chat . Highlight ,
2021-11-25 15:21:42 +00:00
& chat . ReceivedInvitationAdmin ,
2022-08-08 15:22:22 +00:00
& imagePayload ,
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 )
}
2022-09-02 08:36:07 +00:00
if firstMessageTimestamp . Valid {
chat . FirstMessageTimestamp = uint32 ( firstMessageTimestamp . Int64 )
}
2022-08-08 15:22:22 +00:00
if imagePayload != nil {
base64Image , err := userimage . GetPayloadDataURI ( imagePayload )
if err == nil {
chat . Base64Image = base64Image
}
}
2020-07-30 20:54:33 +00:00
// Restore last message
if lastMessageBytes != nil {
2023-08-18 11:39:59 +00:00
message := common . NewMessage ( )
2020-07-30 20:54:33 +00:00
if err = json . Unmarshal ( lastMessageBytes , message ) ; err != nil {
return
}
chat . LastMessage = message
}
2023-04-16 15:06:00 +00:00
if MuteTill . Valid {
chat . MuteTill = MuteTill . Time
}
2020-05-20 12:16:12 +00:00
chat . Alias = alias . 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
2021-12-15 08:34:20 +00:00
syncedFrom sql . NullInt64
syncedTo sql . NullInt64
2022-09-02 08:36:07 +00:00
firstMessageTimestamp sql . NullInt64
2023-04-16 15:06:00 +00:00
MuteTill sql . NullTime
2022-08-08 15:22:22 +00:00
imagePayload [ ] byte
2019-12-02 15:34:05 +00:00
)
2023-05-16 16:11:52 +00:00
var unviewedMessagesCount int
var unviewedMentionsCount int
2019-12-02 15:34:05 +00:00
err := db . db . QueryRow ( `
SELECT
id ,
name ,
color ,
2021-10-04 13:02:25 +00:00
emoji ,
2019-12-02 15:34:05 +00:00
active ,
type ,
timestamp ,
2021-10-12 10:33:32 +00:00
read_messages_at_clock_value ,
2019-12-02 15:34:05 +00:00
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 ,
2023-04-16 15:06:00 +00:00
muted_till ,
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 ,
2022-08-08 15:22:22 +00:00
joined ,
description ,
highlight ,
received_invitation_admin ,
synced_from ,
synced_to ,
2022-09-02 08:36:07 +00:00
first_message_timestamp ,
2022-08-08 15:22:22 +00:00
image_payload
2019-12-02 15:34:05 +00:00
FROM chats
WHERE id = ?
` , chatID ) . Scan ( & chat . ID ,
& chat . Name ,
& chat . Color ,
2021-10-04 13:02:25 +00:00
& chat . Emoji ,
2019-12-02 15:34:05 +00:00
& chat . Active ,
& chat . ChatType ,
& chat . Timestamp ,
2021-10-12 10:33:32 +00:00
& chat . ReadMessagesAtClockValue ,
2019-12-02 15:34:05 +00:00
& chat . DeletedAtClockValue ,
2023-05-16 16:11:52 +00:00
& unviewedMessagesCount ,
& 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 ,
2023-04-16 15:06:00 +00:00
& MuteTill ,
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 ,
2021-10-21 17:04:56 +00:00
& chat . Highlight ,
2021-11-25 15:21:42 +00:00
& chat . ReceivedInvitationAdmin ,
2021-12-15 08:34:20 +00:00
& syncedFrom ,
& syncedTo ,
2022-09-02 08:36:07 +00:00
& firstMessageTimestamp ,
2022-08-08 15:22:22 +00:00
& imagePayload ,
2019-12-02 15:34:05 +00:00
)
switch err {
case sql . ErrNoRows :
return nil , nil
case nil :
2021-12-15 08:34:20 +00:00
if syncedFrom . Valid {
chat . SyncedFrom = uint32 ( syncedFrom . Int64 )
}
if syncedTo . Valid {
chat . SyncedTo = uint32 ( syncedTo . Int64 )
}
2022-09-02 08:36:07 +00:00
if firstMessageTimestamp . Valid {
chat . FirstMessageTimestamp = uint32 ( firstMessageTimestamp . Int64 )
}
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
}
2023-05-16 16:11:52 +00:00
// Set UnviewedCounts and make sure they are above 0
// Since Chat's UnviewedMessagesCount is uint and the SQL column is INT, it can create a discrepancy
if unviewedMessagesCount < 0 {
unviewedMessagesCount = 0
}
if unviewedMentionsCount < 0 {
unviewedMentionsCount = 0
}
chat . UnviewedMessagesCount = uint ( unviewedMessagesCount )
chat . UnviewedMentionsCount = uint ( unviewedMentionsCount )
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 {
2023-08-18 11:39:59 +00:00
message := common . NewMessage ( )
2020-08-17 06:37:18 +00:00
if err = json . Unmarshal ( lastMessageBytes , message ) ; err != nil {
return nil , err
}
chat . LastMessage = message
}
2022-08-08 15:22:22 +00:00
if imagePayload != nil {
base64Image , err := userimage . GetPayloadDataURI ( imagePayload )
if err == nil {
chat . Base64Image = base64Image
}
}
2023-04-16 15:06:00 +00:00
if MuteTill . Valid {
chat . MuteTill = MuteTill . Time
}
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 ,
2022-02-17 15:13:10 +00:00
c . display_name ,
2020-12-15 15:28:05 +00:00
c . identicon ,
c . last_updated ,
2021-10-22 14:20:42 +00:00
c . last_updated_locally ,
2021-03-24 08:04:03 +00:00
c . blocked ,
2021-10-22 14:20:42 +00:00
c . removed ,
2020-12-15 15:28:05 +00:00
c . local_nickname ,
2022-08-02 12:56:26 +00:00
c . contact_request_state ,
2023-01-20 14:28:30 +00:00
c . contact_request_local_clock ,
c . contact_request_remote_state ,
c . contact_request_remote_clock ,
2020-12-15 15:28:05 +00:00
i . image_type ,
2022-07-05 19:49:44 +00:00
i . payload ,
2022-07-06 15:19:02 +00:00
i . clock_value ,
2022-07-05 19:49:44 +00:00
COALESCE ( c . verification_status , 0 ) as verification_status ,
COALESCE ( t . trust_status , 0 ) as trust_status
2022-08-02 12:56:26 +00:00
FROM contacts c
LEFT JOIN chat_identity_contacts i ON c . id = i . contact_id
2022-07-05 19:49:44 +00:00
LEFT JOIN ens_verification_records v ON c . id = v . public_key
LEFT JOIN trusted_users t ON c . id = t . id ;
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 (
2023-01-20 14:28:30 +00:00
contact Contact
nickname sql . NullString
contactRequestLocalState sql . NullInt64
contactRequestLocalClock sql . NullInt64
contactRequestRemoteState sql . NullInt64
contactRequestRemoteClock sql . NullInt64
displayName sql . NullString
imageType sql . NullString
ensName sql . NullString
ensVerified sql . NullBool
blocked sql . NullBool
removed sql . NullBool
lastUpdatedLocally sql . NullInt64
identityImageClock sql . NullInt64
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 ,
2022-02-17 15:13:10 +00:00
& displayName ,
2019-09-26 07:01:17 +00:00
& contact . Identicon ,
2019-07-30 18:39:16 +00:00
& contact . LastUpdated ,
2021-10-22 14:20:42 +00:00
& lastUpdatedLocally ,
2021-03-24 08:04:03 +00:00
& blocked ,
2021-10-22 14:20:42 +00:00
& removed ,
2020-08-20 14:06:38 +00:00
& nickname ,
2023-01-20 14:28:30 +00:00
& contactRequestLocalState ,
& contactRequestLocalClock ,
& contactRequestRemoteState ,
& contactRequestRemoteClock ,
2020-12-15 15:28:05 +00:00
& imageType ,
& imagePayload ,
2022-08-07 07:35:54 +00:00
& identityImageClock ,
2022-07-05 19:49:44 +00:00
& contact . VerificationStatus ,
& contact . TrustStatus ,
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
}
2023-01-20 14:28:30 +00:00
if contactRequestLocalState . Valid {
contact . ContactRequestLocalState = ContactRequestState ( contactRequestLocalState . Int64 )
2022-01-18 16:31:34 +00:00
}
2023-01-20 14:28:30 +00:00
if contactRequestLocalClock . Valid {
contact . ContactRequestLocalClock = uint64 ( contactRequestLocalClock . Int64 )
}
if contactRequestRemoteState . Valid {
contact . ContactRequestRemoteState = ContactRequestState ( contactRequestRemoteState . Int64 )
}
if contactRequestRemoteClock . Valid {
contact . ContactRequestRemoteClock = uint64 ( contactRequestRemoteClock . Int64 )
2022-01-18 16:31:34 +00:00
}
2022-02-17 15:13:10 +00:00
if displayName . Valid {
contact . DisplayName = displayName . String
}
2021-01-11 10:32:51 +00:00
if ensName . Valid {
2022-02-17 15:13:10 +00:00
contact . EnsName = ensName . String
2021-01-11 10:32:51 +00:00
}
if ensVerified . Valid {
contact . ENSVerified = ensVerified . Bool
}
2021-03-24 08:04:03 +00:00
if blocked . Valid {
contact . Blocked = blocked . Bool
2019-07-30 18:39:16 +00:00
}
2021-10-22 14:20:42 +00:00
if removed . Valid {
contact . Removed = removed . Bool
}
if lastUpdatedLocally . Valid {
contact . LastUpdatedLocally = uint64 ( lastUpdatedLocally . Int64 )
}
2020-12-15 15:28:05 +00:00
previousContact , ok := allContacts [ contact . ID ]
if ! ok {
if imageType . Valid {
2022-08-07 07:35:54 +00:00
contact . Images [ imageType . String ] = images . IdentityImage { Name : imageType . String , Payload : imagePayload , Clock : uint64 ( identityImageClock . Int64 ) }
2020-12-15 15:28:05 +00:00
}
allContacts [ contact . ID ] = & contact
} else if imageType . Valid {
2022-08-07 07:35:54 +00:00
previousContact . Images [ imageType . String ] = images . IdentityImage { Name : imageType . String , Payload : imagePayload , Clock : uint64 ( identityImageClock . Int64 ) }
2020-12-15 15:28:05 +00:00
allContacts [ contact . ID ] = previousContact
}
2019-07-30 18:39:16 +00:00
}
2022-08-02 12:56:26 +00:00
// Read social links
for _ , contact := range allContacts {
rows , err := db . db . Query ( ` SELECT link_text, link_url FROM chat_identity_social_links WHERE chat_id = ? ` , contact . ID )
if err != nil {
return nil , err
}
defer rows . Close ( )
for rows . Next ( ) {
var (
text sql . NullString
url sql . NullString
)
err := rows . Scan (
& text , & url ,
)
if err != nil {
return nil , err
}
2023-06-05 11:10:26 +00:00
link := & identity . SocialLink { }
2022-08-02 12:56:26 +00:00
if text . Valid {
link . Text = text . String
}
if url . Valid {
link . URL = url . String
}
contact . SocialLinks = append ( contact . SocialLinks , link )
}
}
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
}
2024-01-24 20:09:28 +00:00
func extractImageTypes ( images map [ string ] * protobuf . IdentityImage ) [ ] string {
uniqueImageTypesMap := make ( map [ string ] struct { } )
for key := range images {
uniqueImageTypesMap [ key ] = struct { } { }
}
var uniqueImageTypes [ ] string
for key := range uniqueImageTypesMap {
uniqueImageTypes = append ( uniqueImageTypes , key )
}
return uniqueImageTypes
}
func generatePlaceholders ( count int ) string {
placeholders := make ( [ ] string , count )
for i := 0 ; i < count ; i ++ {
placeholders [ i ] = "?"
}
return strings . Join ( placeholders , ", " )
}
func ( db sqlitePersistence ) UpdateContactChatIdentity ( contactID string , chatIdentity * protobuf . ChatIdentity ) ( clockUpdated , imagesUpdated bool , err error ) {
2020-12-15 15:28:05 +00:00
if chatIdentity . Clock == 0 {
2022-08-02 12:56:26 +00:00
return false , false , errors . New ( "clock value unset" )
2020-12-15 15:28:05 +00:00
}
tx , err := db . db . BeginTx ( context . Background ( ) , & sql . TxOptions { } )
if err != nil {
2022-08-02 12:56:26 +00:00
return false , false , err
2020-12-15 15:28:05 +00:00
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
// don't shadow original error
_ = tx . Rollback ( )
} ( )
2024-01-24 20:09:28 +00:00
extractedImageTypes := extractImageTypes ( chatIdentity . Images )
query := "DELETE FROM chat_identity_contacts WHERE contact_id = ?"
if len ( extractedImageTypes ) > 0 {
query += " AND image_type NOT IN (" + generatePlaceholders ( len ( extractedImageTypes ) ) + ")"
}
stmt , err := tx . Prepare ( query )
if err != nil {
return false , false , err
}
defer stmt . Close ( )
args := make ( [ ] interface { } , len ( extractedImageTypes ) + 1 )
args [ 0 ] = contactID
for i , v := range extractedImageTypes {
args [ i + 1 ] = v
}
result , err := stmt . Exec ( args ... )
if err != nil {
return false , false , err
}
imagesUpdated = false
if rowsAffected , err := result . RowsAffected ( ) ; err == nil && rowsAffected > 0 {
imagesUpdated = true
}
2022-08-02 12:56:26 +00:00
updateClock := func ( ) ( updated bool , err error ) {
var newerClockEntryExists bool
err = tx . QueryRow ( ` SELECT EXISTS(SELECT 1 FROM chat_identity_last_received WHERE chat_id = ? AND clock_value >= ?) ` , contactID , chatIdentity . Clock ) . Scan ( & newerClockEntryExists )
if err != nil {
return false , err
}
if newerClockEntryExists {
return false , nil
}
stmt , err := tx . Prepare ( "INSERT INTO chat_identity_last_received (chat_id, clock_value) VALUES (?, ?)" )
if err != nil {
return false , err
}
defer stmt . Close ( )
_ , err = stmt . Exec (
contactID ,
chatIdentity . Clock ,
)
if err != nil {
return false , err
}
return true , nil
}
clockUpdated , err = updateClock ( )
if err != nil {
return false , false , err
}
2020-12-15 15:28:05 +00:00
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 {
2022-08-02 12:56:26 +00:00
return clockUpdated , false , err
2020-12-15 15:28:05 +00:00
}
if exists {
continue
}
stmt , err := tx . Prepare ( ` INSERT INTO chat_identity_contacts (contact_id, image_type, clock_value, payload) VALUES (?, ?, ?, ?) ` )
if err != nil {
2022-08-02 12:56:26 +00:00
return clockUpdated , false , err
2020-12-15 15:28:05 +00:00
}
defer stmt . Close ( )
if image . Payload == nil {
continue
}
2021-02-17 23:14:48 +00:00
// TODO implement something that doesn't reject all images if a single image fails validation
2020-12-15 15:28:05 +00:00
// Validate image URI to make sure it's serializable
_ , err = images . GetPayloadDataURI ( image . Payload )
if err != nil {
2022-08-02 12:56:26 +00:00
return clockUpdated , false , err
2020-12-15 15:28:05 +00:00
}
_ , err = stmt . Exec (
contactID ,
imageType ,
chatIdentity . Clock ,
image . Payload ,
)
if err != nil {
2022-08-02 12:56:26 +00:00
return false , false , err
}
imagesUpdated = true
}
if clockUpdated && chatIdentity . SocialLinks != nil {
stmt , err := tx . Prepare ( ` INSERT INTO chat_identity_social_links (chat_id, link_text, link_url) VALUES (?, ?, ?) ` )
if err != nil {
return clockUpdated , imagesUpdated , err
}
defer stmt . Close ( )
for _ , link := range chatIdentity . SocialLinks {
_ , err = stmt . Exec (
contactID ,
link . Text ,
link . Url ,
)
if err != nil {
return clockUpdated , imagesUpdated , err
}
2020-12-15 15:28:05 +00:00
}
}
return
}
2021-11-17 09:11:51 +00:00
func ( db sqlitePersistence ) ExpiredMessagesIDs ( maxSendCount int ) ( [ ] string , error ) {
2020-12-15 14:43:41 +00:00
ids := [ ] string { }
rows , err := db . db . Query ( `
SELECT
id
FROM
raw_messages
WHERE
2023-11-22 22:43:22 +00:00
( message_type IN ( ? , ? ) OR resend_automatically ) AND sent = ? AND send_count <= ? ` ,
2021-11-17 09:11:51 +00:00
protobuf . ApplicationMetadataMessage_CHAT_MESSAGE ,
protobuf . ApplicationMetadataMessage_EMOJI_REACTION ,
false ,
maxSendCount )
2020-12-15 14:43:41 +00:00
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
// Insert record
2021-03-24 08:04:03 +00:00
// NOTE: name, photo and tribute_to_talk are not used anymore, but it's not nullable
2021-01-11 10:32:51 +00:00
// 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 ,
alias ,
2022-02-17 15:13:10 +00:00
display_name ,
2019-11-15 08:52:28 +00:00
identicon ,
last_updated ,
2021-10-22 14:20:42 +00:00
last_updated_locally ,
2020-12-15 15:28:05 +00:00
local_nickname ,
2022-07-05 19:49:44 +00:00
contact_request_state ,
2023-01-20 14:28:30 +00:00
contact_request_local_clock ,
contact_request_remote_state ,
contact_request_remote_clock ,
2021-03-24 08:04:03 +00:00
blocked ,
2021-10-22 14:20:42 +00:00
removed ,
2022-07-05 19:49:44 +00:00
verification_status ,
2021-03-24 08:04:03 +00:00
name ,
2021-01-11 10:32:51 +00:00
photo ,
tribute_to_talk
2022-07-05 19:49:44 +00:00
) 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 ,
2019-09-26 07:01:17 +00:00
contact . Alias ,
2022-02-17 15:13:10 +00:00
contact . DisplayName ,
2019-09-26 07:01:17 +00:00
contact . Identicon ,
2019-07-30 18:39:16 +00:00
contact . LastUpdated ,
2021-10-22 14:20:42 +00:00
contact . LastUpdatedLocally ,
2020-08-20 14:06:38 +00:00
contact . LocalNickname ,
2023-01-20 14:28:30 +00:00
contact . ContactRequestLocalState ,
contact . ContactRequestLocalClock ,
contact . ContactRequestRemoteState ,
contact . ContactRequestRemoteClock ,
2021-03-24 08:04:03 +00:00
contact . Blocked ,
2021-10-22 14:20:42 +00:00
contact . Removed ,
2022-07-05 19:49:44 +00:00
contact . VerificationStatus ,
2021-03-24 08:04:03 +00:00
//TODO we need to drop these columns
"" ,
"" ,
2020-12-15 15:28:05 +00:00
"" ,
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 ,
2021-03-24 08:04:03 +00:00
message_id ,
2020-01-10 18:59:01 +00:00
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
}
2021-07-22 17:41:49 +00:00
2021-02-17 23:14:48 +00:00
func ( db sqlitePersistence ) ResetWhenChatIdentityLastPublished ( chatID string ) ( err 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 ( )
} ( )
stmt , err := tx . Prepare ( "INSERT INTO chat_identity_last_published (chat_id, clock_value, hash) VALUES (?, ?, ?)" )
if err != nil {
return err
}
defer stmt . Close ( )
_ , err = stmt . Exec ( chatID , 0 , [ ] byte ( "." ) )
if err != nil {
return err
}
return nil
}
2021-07-22 17:41:49 +00:00
func ( db sqlitePersistence ) InsertStatusUpdate ( userStatus UserStatus ) error {
_ , err := db . db . Exec ( ` INSERT INTO status_updates (
public_key ,
status_type ,
clock ,
custom_text )
VALUES ( ? , ? , ? , ? ) ` ,
userStatus . PublicKey ,
userStatus . StatusType ,
userStatus . Clock ,
userStatus . CustomText ,
)
return err
}
func ( db sqlitePersistence ) CleanOlderStatusUpdates ( ) error {
now := time . Now ( )
2021-10-28 15:21:28 +00:00
twoWeeksAgo := now . Add ( time . Duration ( - 1 * HoursInTwoWeeks ) * time . Hour )
2021-07-22 17:41:49 +00:00
_ , err := db . db . Exec ( ` DELETE FROM status_updates WHERE clock < ? ` ,
2021-10-28 15:21:28 +00:00
uint64 ( twoWeeksAgo . Unix ( ) ) ,
2021-07-22 17:41:49 +00:00
)
return err
}
func ( db sqlitePersistence ) StatusUpdates ( ) ( statusUpdates [ ] UserStatus , err error ) {
rows , err := db . db . Query ( `
SELECT
public_key ,
status_type ,
clock ,
custom_text
FROM status_updates
` )
if err != nil {
return
}
defer rows . Close ( )
for rows . Next ( ) {
var userStatus UserStatus
err = rows . Scan (
& userStatus . PublicKey ,
& userStatus . StatusType ,
& userStatus . Clock ,
& userStatus . CustomText ,
)
if err != nil {
return
}
statusUpdates = append ( statusUpdates , userStatus )
}
return
}
2022-06-08 18:39:53 +00:00
2023-01-05 16:11:57 +00:00
func ( db sqlitePersistence ) DeleteSwitcherCard ( cardID string ) error {
_ , err := db . db . Exec ( "DELETE from switcher_cards WHERE card_id = ?" , cardID )
return err
}
func ( db sqlitePersistence ) UpsertSwitcherCard ( switcherCard SwitcherCard ) error {
_ , err := db . db . Exec ( ` INSERT INTO switcher_cards (
card_id ,
type ,
clock ,
screen_id )
VALUES ( ? , ? , ? , ? ) ` ,
switcherCard . CardID ,
switcherCard . Type ,
switcherCard . Clock ,
switcherCard . ScreenID ,
)
return err
}
func ( db sqlitePersistence ) SwitcherCards ( ) ( switcherCards [ ] SwitcherCard , err error ) {
rows , err := db . db . Query ( `
SELECT
card_id ,
type ,
clock ,
screen_id
FROM switcher_cards
` )
if err != nil {
return
}
defer rows . Close ( )
for rows . Next ( ) {
var switcherCard SwitcherCard
err = rows . Scan (
& switcherCard . CardID ,
& switcherCard . Type ,
& switcherCard . Clock ,
& switcherCard . ScreenID ,
)
if err != nil {
return
}
switcherCards = append ( switcherCards , switcherCard )
}
return
}
2022-08-02 23:08:01 +00:00
func ( db sqlitePersistence ) NextHigherClockValueOfAutomaticStatusUpdates ( clock uint64 ) ( uint64 , error ) {
var nextClock uint64
err := db . db . QueryRow ( `
SELECT clock
FROM status_updates
WHERE clock > ? AND status_type = ?
LIMIT 1
` , clock , protobuf . StatusUpdate_AUTOMATIC ) . Scan ( & nextClock )
switch err {
case sql . ErrNoRows :
return 0 , common . ErrRecordNotFound
case nil :
return nextClock , nil
default :
return 0 , err
}
}
func ( db sqlitePersistence ) DeactivatedAutomaticStatusUpdates ( fromClock uint64 , tillClock uint64 ) ( statusUpdates [ ] UserStatus , err error ) {
rows , err := db . db . Query ( `
SELECT
public_key ,
? ,
clock + 1 ,
custom_text
FROM status_updates
WHERE clock > ? AND clock <= ? AND status_type = ?
` , protobuf . StatusUpdate_INACTIVE , fromClock , tillClock , protobuf . StatusUpdate_AUTOMATIC )
if err != nil {
return
}
defer rows . Close ( )
for rows . Next ( ) {
var userStatus UserStatus
err = rows . Scan (
& userStatus . PublicKey ,
& userStatus . StatusType ,
& userStatus . Clock ,
& userStatus . CustomText ,
)
if err != nil {
return
}
statusUpdates = append ( statusUpdates , userStatus )
}
return
}
2022-06-08 18:39:53 +00:00
func ( db * sqlitePersistence ) AddBookmark ( bookmark browsers . Bookmark ) ( browsers . Bookmark , error ) {
tx , err := db . db . Begin ( )
if err != nil {
return bookmark , err
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
_ = tx . Rollback ( )
} ( )
insert , err := tx . Prepare ( "INSERT OR REPLACE INTO bookmarks (url, name, image_url, removed, clock) VALUES (?, ?, ?, ?, ?)" )
if err != nil {
return bookmark , err
}
// Get the right icon
finder := besticon . IconFinder { }
icons , iconError := finder . FetchIcons ( bookmark . URL )
if iconError == nil && len ( icons ) > 0 {
2023-01-13 17:12:46 +00:00
icon := finder . IconInSizeRange ( besticon . SizeRange { Min : 48 , Perfect : 48 , Max : 100 } )
2022-06-08 18:39:53 +00:00
if icon != nil {
bookmark . ImageURL = icon . URL
} else {
bookmark . ImageURL = icons [ 0 ] . URL
}
} else {
log . Error ( "error getting the bookmark icon" , "iconError" , iconError )
}
_ , err = insert . Exec ( bookmark . URL , bookmark . Name , bookmark . ImageURL , bookmark . Removed , bookmark . Clock )
return bookmark , err
}
2022-08-24 14:05:35 +00:00
func ( db * sqlitePersistence ) AddBrowser ( browser browsers . Browser ) ( err error ) {
tx , err := db . db . Begin ( )
if err != nil {
return
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
_ = tx . Rollback ( )
} ( )
insert , err := tx . Prepare ( "INSERT OR REPLACE INTO browsers(id, name, timestamp, dapp, historyIndex) VALUES(?, ?, ?, ?, ?)" )
if err != nil {
return
}
_ , err = insert . Exec ( browser . ID , browser . Name , browser . Timestamp , browser . Dapp , browser . HistoryIndex )
insert . Close ( )
if err != nil {
return
}
if len ( browser . History ) == 0 {
return
}
bhInsert , err := tx . Prepare ( "INSERT INTO browsers_history(browser_id, history) VALUES(?, ?)" )
if err != nil {
return
}
defer bhInsert . Close ( )
for _ , history := range browser . History {
_ , err = bhInsert . Exec ( browser . ID , history )
if err != nil {
return
}
}
return
}
func ( db * sqlitePersistence ) InsertBrowser ( browser browsers . Browser ) ( err error ) {
tx , err := db . db . Begin ( )
if err != nil {
return
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
_ = tx . Rollback ( )
} ( )
bInsert , err := tx . Prepare ( "INSERT OR REPLACE INTO browsers(id, name, timestamp, dapp, historyIndex) VALUES(?, ?, ?, ?, ?)" )
if err != nil {
return
}
_ , err = bInsert . Exec ( browser . ID , browser . Name , browser . Timestamp , browser . Dapp , browser . HistoryIndex )
bInsert . Close ( )
if err != nil {
return
}
if len ( browser . History ) == 0 {
return
}
bhInsert , err := tx . Prepare ( "INSERT INTO browsers_history(browser_id, history) VALUES(?, ?)" )
if err != nil {
return
}
defer bhInsert . Close ( )
for _ , history := range browser . History {
_ , err = bhInsert . Exec ( browser . ID , history )
if err != nil {
return
}
}
return
}
2022-06-08 18:39:53 +00:00
func ( db * sqlitePersistence ) RemoveBookmark ( url string , deletedAt uint64 ) error {
tx , err := db . db . Begin ( )
if err != nil {
return err
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
_ = tx . Rollback ( )
} ( )
_ , err = tx . Exec ( ` UPDATE bookmarks SET removed = 1, deleted_at = ? WHERE url = ? ` , deletedAt , url )
return err
}
2022-08-24 14:05:35 +00:00
func ( db * sqlitePersistence ) GetBrowsers ( ) ( rst [ ] * browsers . Browser , err error ) {
tx , err := db . db . Begin ( )
if err != nil {
return
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
_ = tx . Rollback ( )
} ( )
// FULL and RIGHT joins are not supported
bRows , err := tx . Query ( "SELECT id, name, timestamp, dapp, historyIndex FROM browsers ORDER BY timestamp DESC" )
if err != nil {
return
}
defer bRows . Close ( )
browsersArr := map [ string ] * browsers . Browser { }
for bRows . Next ( ) {
browser := browsers . Browser { }
err = bRows . Scan ( & browser . ID , & browser . Name , & browser . Timestamp , & browser . Dapp , & browser . HistoryIndex )
if err != nil {
return nil , err
}
browsersArr [ browser . ID ] = & browser
rst = append ( rst , & browser )
}
bhRows , err := tx . Query ( "SELECT browser_id, history from browsers_history" )
if err != nil {
return
}
defer bhRows . Close ( )
var (
id string
history string
)
for bhRows . Next ( ) {
err = bhRows . Scan ( & id , & history )
if err != nil {
return
}
browsersArr [ id ] . History = append ( browsersArr [ id ] . History , history )
}
return rst , nil
}
func ( db * sqlitePersistence ) DeleteBrowser ( id string ) error {
_ , err := db . db . Exec ( "DELETE from browsers WHERE id = ?" , id )
return err
}
2022-06-08 18:39:53 +00:00
func ( db * sqlitePersistence ) GetBookmarkByURL ( url string ) ( * browsers . Bookmark , error ) {
bookmark := browsers . Bookmark { }
err := db . db . QueryRow ( ` SELECT url, name, image_url, removed, clock, deleted_at FROM bookmarks WHERE url = ? ` , url ) . Scan ( & bookmark . URL , & bookmark . Name , & bookmark . ImageURL , & bookmark . Removed , & bookmark . Clock , & bookmark . DeletedAt )
if err != nil {
return nil , err
}
return & bookmark , nil
}
func ( db * sqlitePersistence ) UpdateBookmark ( oldURL string , bookmark browsers . Bookmark ) error {
tx , err := db . db . Begin ( )
if err != nil {
return err
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
_ = tx . Rollback ( )
} ( )
insert , err := tx . Prepare ( "UPDATE bookmarks SET url = ?, name = ?, image_url = ?, removed = ?, clock = ?, deleted_at = ? WHERE url = ?" )
if err != nil {
return err
}
_ , err = insert . Exec ( bookmark . URL , bookmark . Name , bookmark . ImageURL , bookmark . Removed , bookmark . Clock , bookmark . DeletedAt , oldURL )
return err
}
2022-06-09 15:21:57 +00:00
func ( db * sqlitePersistence ) DeleteSoftRemovedBookmarks ( threshold uint64 ) error {
tx , err := db . db . Begin ( )
if err != nil {
return err
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
_ = tx . Rollback ( )
} ( )
_ , err = tx . Exec ( ` DELETE from bookmarks WHERE removed = 1 AND deleted_at < ? ` , threshold )
return err
}
2022-07-22 08:10:47 +00:00
func ( db * sqlitePersistence ) InsertWalletConnectSession ( session * WalletConnectSession ) error {
tx , err := db . db . Begin ( )
if err != nil {
return err
}
defer func ( ) {
if err == nil {
err = tx . Commit ( )
return
}
_ = tx . Rollback ( )
} ( )
sessionInsertPreparedStatement , err := tx . Prepare ( "INSERT OR REPLACE INTO wallet_connect_v1_sessions(peer_id, dapp_name, dapp_url, info) VALUES(?, ?, ?, ?)" )
if err != nil {
return err
}
defer sessionInsertPreparedStatement . Close ( )
_ , err = sessionInsertPreparedStatement . Exec ( session . PeerID , session . DAppName , session . DAppURL , session . Info )
return err
}
func ( db * sqlitePersistence ) GetWalletConnectSession ( ) ( [ ] WalletConnectSession , error ) {
var sessions [ ] WalletConnectSession
rows , err := db . db . Query ( "SELECT peer_id, dapp_name, dapp_url, info FROM wallet_connect_v1_sessions ORDER BY dapp_name" )
if err != nil {
return nil , err
}
defer rows . Close ( )
for rows . Next ( ) {
session := WalletConnectSession { }
err = rows . Scan ( & session . PeerID , & session . DAppName , & session . DAppURL , & session . Info )
if err != nil {
return nil , err
}
sessions = append ( sessions , session )
}
return sessions , nil
}
func ( db * sqlitePersistence ) DeleteWalletConnectSession ( peerID string ) error {
deleteStatement , err := db . db . Prepare ( "DELETE FROM wallet_connect_v1_sessions where peer_id=?" )
if err != nil {
return err
}
defer deleteStatement . Close ( )
_ , err = deleteStatement . Exec ( peerID )
return err
}