remove photo path in favor of images in contact

This commit is contained in:
Andrea Maria Piana 2020-12-15 16:28:05 +01:00
parent 1adfa38611
commit 31a885ced8
10 changed files with 188 additions and 109 deletions

View File

@ -157,20 +157,13 @@ func (db *Database) GetIdentityImages(keyUID string) ([]*images.IdentityImage, e
}
func (db *Database) GetIdentityImage(keyUID, it string) (*images.IdentityImage, error) {
rows, err := db.db.Query("SELECT key_uid, name, image_payload, width, height, file_size, resize_target FROM identity_images WHERE key_uid = ? AND name = ?", keyUID, it)
if err != nil {
return nil, err
}
defer rows.Close()
var ii images.IdentityImage
for rows.Next() {
err = rows.Scan(&ii.KeyUID, &ii.Name, &ii.Payload, &ii.Width, &ii.Height, &ii.FileSize, &ii.ResizeTarget)
if err != nil {
err := db.db.QueryRow("SELECT key_uid, name, image_payload, width, height, file_size, resize_target FROM identity_images WHERE key_uid = ? AND name = ?", keyUID, it).Scan(&ii.KeyUID, &ii.Name, &ii.Payload, &ii.Width, &ii.Height, &ii.FileSize, &ii.ResizeTarget)
if err == sql.ErrNoRows {
return nil, nil
} else if err != nil {
return nil, err
}
}
return &ii, nil
}

View File

@ -6,6 +6,7 @@ import (
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/images"
"github.com/status-im/status-go/protocol/identity/alias"
"github.com/status-im/status-go/protocol/identity/identicon"
)
@ -28,7 +29,6 @@ type ContactDeviceInfo struct {
// Contact has information about a "Contact". A contact is not necessarily one
// that we added or added us, that's based on SystemTags.
// TODO remove use of photoPath in Contact{}
type Contact struct {
// ID of the contact. It's a hex-encoded public key (prefixed with 0x).
ID string `json:"id"`
@ -49,8 +49,6 @@ type Contact struct {
Alias string `json:"alias,omitempty"`
// Identicon generated from public key
Identicon string `json:"identicon"`
// Photo is the base64 encoded photo
Photo string `json:"photoPath,omitempty"`
// LastUpdated is the last time we received an update from the contact
// updates should be discarded if last updated is less than the one stored
LastUpdated uint64 `json:"lastUpdated"`
@ -61,6 +59,8 @@ type Contact struct {
DeviceInfo []ContactDeviceInfo `json:"deviceInfo"`
TributeToTalk string `json:"tributeToTalk,omitempty"`
LocalNickname string `json:"localNickname,omitempty"`
Images map[string]images.IdentityImage `json:"images"`
}
func (c Contact) PublicKey() (*ecdsa.PublicKey, error) {

View File

@ -237,7 +237,6 @@ func (m *MessageHandler) HandleSyncInstallationContact(state *ReceivedMessageSta
contact.Name = message.EnsName
contact.ENSVerified = false
}
contact.Photo = message.ProfileImage
contact.LastUpdated = message.Clock
contact.LocalNickname = message.LocalNickname
@ -286,7 +285,6 @@ func (m *MessageHandler) HandleContactUpdate(state *ReceivedMessageState, messag
contact.Name = message.EnsName
contact.ENSVerified = false
}
contact.Photo = message.ProfileImage
contact.LastUpdated = message.Clock
state.ModifiedContacts[contact.ID] = true
state.AllContacts[contact.ID] = contact
@ -807,72 +805,23 @@ func (m *MessageHandler) HandleGroupChatInvitation(state *ReceivedMessageState,
func (m *MessageHandler) HandleChatIdentity(state *ReceivedMessageState, ci protobuf.ChatIdentity) error {
logger := m.logger.With(zap.String("site", "HandleChatIdentity"))
contact := state.CurrentMessageState.Contact
chat, ok := state.AllChats[contact.ID]
if !ok {
chat = OneToOneFromPublicKey(state.CurrentMessageState.PublicKey, state.Timesource)
// We don't want to show the chat to the user
chat.Active = false
}
logger.Info("Handling contact update")
// TODO investigate potential race condition where user updates this contact's details and the contact update's
// clock value is just less than the ChatIdentity clock and the ChatIdentity is processed first.
// In this case the contact update would not be processed
//
// TODO Potential fix: create an ChatIdentity last updated field on the contact table.
logger.Info(fmt.Sprintf("Update times, contact.LastUpdated '%d', ChatIdentity.Clock '%d'", contact.LastUpdated, ci.Clock))
if contact.LastUpdated < ci.Clock {
logger.Info("Updating contact")
if !contact.HasBeenAdded() && contact.ID != contactIDFromPublicKey(&m.identity.PublicKey) {
contact.SystemTags = append(contact.SystemTags, contactRequestReceived)
}
// TODO handle ENS things
/* if contact.Name != message.EnsName {
contact.Name = message.EnsName
contact.ENSVerified = false
} */
logger.Info(fmt.Sprintf("ChatIdentity has %d images attached", len(ci.Images)))
if len(ci.Images) > 0 {
// Get the largest
var name string
var iiSize int
for n, ii := range ci.Images {
if iiSize < len(ii.Payload) {
iiSize = len(ii.Payload)
name = n
}
}
logger.Info(fmt.Sprintf("largest image : name '%s', size '%d'", name, iiSize))
if ci.Images[name] == nil {
logger.Info("image empty")
return errors.New("image empty")
}
dataURI, err := images.GetPayloadDataURI(ci.Images[name].Payload)
newImages, err := m.persistence.SaveContactChatIdentity(contact.ID, &ci)
if err != nil {
return err
}
contact.Photo = dataURI
logger.Info(fmt.Sprintf("image payload '%s'", dataURI))
if newImages {
for imageType, image := range ci.Images {
if contact.Images == nil {
contact.Images = make(map[string]images.IdentityImage)
}
contact.Images[imageType] = images.IdentityImage{Name: imageType, Payload: image.Payload}
contact.LastUpdated = ci.Clock
}
state.ModifiedContacts[contact.ID] = true
state.AllContacts[contact.ID] = contact
}
if chat.LastClockValue < ci.Clock {
chat.LastClockValue = ci.Clock
}
state.ModifiedChats[chat.ID] = true
state.AllChats[chat.ID] = chat
return nil
}

View File

@ -2413,7 +2413,6 @@ func (m *Messenger) syncContact(ctx context.Context, contact *Contact) error {
Clock: clock,
Id: contact.ID,
EnsName: contact.Name,
ProfileImage: contact.Photo,
LocalNickname: contact.LocalNickname,
}
encodedMessage, err := proto.Marshal(syncMessage)

View File

@ -112,7 +112,6 @@ func (s *MessengerContactUpdateSuite) TestReceiveContactUpdate() {
receivedContact := response.Contacts[0]
s.Require().Equal(theirName, receivedContact.Name)
s.Require().Equal(theirPicture, receivedContact.Photo)
s.Require().False(receivedContact.ENSVerified)
s.Require().True(receivedContact.HasBeenAdded())
s.Require().NotEmpty(receivedContact.LastUpdated)
@ -135,7 +134,6 @@ func (s *MessengerContactUpdateSuite) TestReceiveContactUpdate() {
receivedContact = response.Contacts[0]
s.Require().Equal(theirContactID, receivedContact.ID)
s.Require().Equal(newName, receivedContact.Name)
s.Require().Equal(newPicture, receivedContact.Photo)
s.Require().False(receivedContact.ENSVerified)
s.Require().True(receivedContact.HasBeenAdded())
s.Require().NotEmpty(receivedContact.LastUpdated)

View File

@ -631,7 +631,6 @@ func (s *MessengerSuite) TestRetrieveBlockedContact() {
blockedContact := Contact{
ID: publicKeyHex,
Name: "contact-name",
Photo: "contact-photo",
LastUpdated: 20,
SystemTags: []string{contactBlocked},
TributeToTalk: "talk",
@ -1192,7 +1191,6 @@ func (s *MessengerSuite) TestBlockContact() {
contact := Contact{
ID: testPK,
Name: "contact-name",
Photo: "contact-photo",
LastUpdated: 20,
SystemTags: []string{contactAdded, contactRequestReceived},
DeviceInfo: []ContactDeviceInfo{
@ -1375,7 +1373,6 @@ func (s *MessengerSuite) TestContactPersistence() {
ID: testPK,
Name: "contact-name",
Photo: "contact-photo",
LastUpdated: 20,
SystemTags: []string{contactAdded, contactRequestReceived},
DeviceInfo: []ContactDeviceInfo{
@ -1410,7 +1407,6 @@ func (s *MessengerSuite) TestContactPersistenceUpdate() {
contact := Contact{
ID: contactID,
Name: "contact-name",
Photo: "contact-photo",
LastUpdated: 20,
SystemTags: []string{contactAdded, contactRequestReceived},
DeviceInfo: []ContactDeviceInfo{

View File

@ -32,7 +32,7 @@
// 1603816533_add_links.down.sql (0)
// 1603816533_add_links.up.sql (48B)
// 1603888149_create_chat_identity_last_published_table.down.sql (40B)
// 1603888149_create_chat_identity_last_published_table.up.sql (175B)
// 1603888149_create_chat_identity_last_published_table.up.sql (407B)
// doc.go (850B)
package migrations
@ -742,7 +742,7 @@ func _1603888149_create_chat_identity_last_published_tableDownSql() (*asset, err
return a, nil
}
var __1603888149_create_chat_identity_last_published_tableUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x3c\xcb\x41\xaa\xc2\x30\x14\x85\xe1\x79\xa1\x7b\x38\xc3\xf7\xc0\x1d\x38\x4a\xc3\x2d\x06\x63\x52\xd2\xab\xd8\x51\x88\x6d\x21\xc5\xa0\x42\x53\xc1\xdd\x0b\x05\x3b\x3d\xe7\xff\xa4\x23\xc1\x04\x16\x95\x26\xa8\x1a\xc6\x32\xe8\xaa\x5a\x6e\xd1\xc7\x90\xfd\x34\x8c\x8f\x3c\xe5\x8f\x4f\x61\xce\xfe\xb5\xdc\xd2\x34\xc7\x71\xc0\x5f\x59\xe0\x57\xe0\x22\x9c\x3c\x08\xb7\x62\x73\xd6\x1a\x8d\x53\x27\xe1\x3a\x1c\xa9\x83\x35\x90\xd6\xd4\x5a\x49\x86\xa3\x46\x0b\x49\xbb\x15\xa7\x67\x7f\xf7\xef\x90\x96\x11\xca\xf0\x86\xd7\x33\x86\x39\xa2\xd2\xb6\xda\xe6\xb2\xf8\xdf\x97\xc5\x37\x00\x00\xff\xff\x0a\xfa\x7a\x2a\xaf\x00\x00\x00")
var __1603888149_create_chat_identity_last_published_tableUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x90\xc1\x4a\xc4\x30\x10\x86\xef\x85\xbe\xc3\x1c\x77\x61\xdf\xc0\x53\x1a\x66\x31\x18\xd3\x35\x9b\x8a\x7b\x0a\x63\x1b\x4c\x30\x6e\x0b\xc9\x0a\x7d\x7b\x21\xab\x55\xa1\x88\xc7\xe4\x9f\x9f\xef\x9b\xe1\x1a\x99\x41\x30\xac\x91\x08\x62\x0f\xaa\x35\x80\x4f\xe2\x68\x8e\xd0\x7b\xca\x36\x0c\xee\x9c\x43\x9e\x6d\xa4\x94\xed\x74\x79\x8e\x21\x79\x37\xc0\xa6\xae\xe0\x6b\x02\x1e\x99\xe6\xb7\x4c\x97\xb2\xea\xa4\x84\x83\x16\xf7\x4c\x9f\xe0\x0e\x4f\xd0\x2a\xe0\xad\xda\x4b\xc1\x0d\x68\x3c\x48\xc6\x71\x57\xca\x71\xec\x5f\xed\x3b\xc5\x8b\x03\xa1\xcc\x52\x2e\xa1\xa7\xe4\xa1\x91\x6d\xb3\x7c\xd7\xd5\xf6\xa6\xae\xea\xea\xdf\xc2\xfd\x78\xce\xd4\xe7\xf4\xa9\x7a\x7d\xad\xd9\x16\x60\x78\xa3\x17\x67\xf3\x3c\xb9\xf5\xfc\x4f\xdb\x89\xe6\x38\xd2\xf0\x5b\xb8\x24\x9d\x12\x0f\x1d\x6e\xbe\xe9\xbb\x1f\xa4\xed\xda\x6d\xae\x7b\x7e\x04\x00\x00\xff\xff\xff\xdb\x89\x48\x97\x01\x00\x00")
func _1603888149_create_chat_identity_last_published_tableUpSqlBytes() ([]byte, error) {
return bindataRead(
@ -757,8 +757,8 @@ func _1603888149_create_chat_identity_last_published_tableUpSql() (*asset, error
return nil, err
}
info := bindataFileInfo{name: "1603888149_create_chat_identity_last_published_table.up.sql", size: 175, mode: os.FileMode(0644), modTime: time.Unix(1608048601, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xb0, 0x9b, 0x9a, 0xb6, 0x33, 0x2e, 0xb1, 0x8b, 0xe4, 0x18, 0x90, 0xe, 0x40, 0xa4, 0xdd, 0x13, 0x3f, 0x50, 0x55, 0x7, 0x31, 0x50, 0xe0, 0xb8, 0x1e, 0x51, 0xcc, 0xfd, 0xa5, 0x7e, 0x3, 0x7b}}
info := bindataFileInfo{name: "1603888149_create_chat_identity_last_published_table.up.sql", size: 407, mode: os.FileMode(0644), modTime: time.Unix(1608048655, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x7f, 0x9, 0xf, 0xfb, 0xdb, 0x3c, 0x86, 0x70, 0x82, 0xda, 0x10, 0x25, 0xe2, 0x4e, 0x40, 0x45, 0xab, 0x8b, 0x1c, 0x91, 0x7c, 0xf1, 0x70, 0x2e, 0x81, 0xf3, 0x71, 0x45, 0xda, 0xe2, 0xa4, 0x57}}
return a, nil
}

View File

@ -3,3 +3,11 @@ CREATE TABLE IF NOT EXISTS chat_identity_last_published (
clock_value INT NOT NULL,
hash BLOB NOT NULL
);
CREATE TABLE IF NOT EXISTS chat_identity_contacts (
contact_id VARCHAR NOT NULL,
image_type VARCHAR NOT NULL,
clock_value INT NOT NULL,
payload BLOB NOT NULL,
UNIQUE(contact_id, image_type) ON CONFLICT REPLACE
);

View File

@ -11,6 +11,7 @@ import (
"github.com/pkg/errors"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/images"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/protobuf"
)
@ -378,44 +379,50 @@ func (db sqlitePersistence) Chat(chatID string) (*Chat, error) {
}
func (db sqlitePersistence) Contacts() ([]*Contact, error) {
allContacts := make(map[string]*Contact)
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,
local_nickname
FROM contacts
c.id,
c.address,
c.name,
c.alias,
c.identicon,
c.last_updated,
c.system_tags,
c.device_info,
c.ens_verified,
c.ens_verified_at,
c.tribute_to_talk,
c.local_nickname,
i.image_type,
i.payload
FROM contacts c LEFT JOIN chat_identity_contacts i ON c.id = i.contact_id
`)
if err != nil {
return nil, err
}
defer rows.Close()
var response []*Contact
for rows.Next() {
var (
contact Contact
encodedDeviceInfo []byte
encodedSystemTags []byte
nickname sql.NullString
imageType sql.NullString
imagePayload []byte
)
contact.Images = make(map[string]images.IdentityImage)
err := rows.Scan(
&contact.ID,
&contact.Address,
&contact.Name,
&contact.Alias,
&contact.Identicon,
&contact.Photo,
&contact.LastUpdated,
&encodedSystemTags,
&encodedDeviceInfo,
@ -423,6 +430,8 @@ func (db sqlitePersistence) Contacts() ([]*Contact, error) {
&contact.ENSVerifiedAt,
&contact.TributeToTalk,
&nickname,
&imageType,
&imagePayload,
)
if err != nil {
return nil, err
@ -448,12 +457,89 @@ func (db sqlitePersistence) Contacts() ([]*Contact, error) {
}
}
response = append(response, &contact)
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
}
}
var response []*Contact
for key := range allContacts {
response = append(response, allContacts[key])
}
return response, nil
}
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
}
func (db sqlitePersistence) SaveRawMessage(message *common.RawMessage) error {
var pubKeys [][]byte
for _, pk := range message.Recipients {
@ -649,14 +735,14 @@ func (db sqlitePersistence) SaveContact(contact *Contact, tx *sql.Tx) (err error
name,
alias,
identicon,
photo,
last_updated,
system_tags,
device_info,
ens_verified,
ens_verified_at,
tribute_to_talk,
local_nickname
local_nickname,
photo
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?, ?)
`)
if err != nil {
@ -670,7 +756,6 @@ func (db sqlitePersistence) SaveContact(contact *Contact, tx *sql.Tx) (err error
contact.Name,
contact.Alias,
contact.Identicon,
contact.Photo,
contact.LastUpdated,
encodedSystemTags.Bytes(),
encodedDeviceInfo.Bytes(),
@ -678,6 +763,9 @@ func (db sqlitePersistence) SaveContact(contact *Contact, tx *sql.Tx) (err error
contact.ENSVerifiedAt,
contact.TributeToTalk,
contact.LocalNickname,
// 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
"",
)
return
}

View File

@ -671,6 +671,54 @@ func TestSqlitePersistence_GetWhenChatIdentityLastPublished(t *testing.T) {
require.Nil(t, actualHash2)
}
func TestSaveContactIdentityImage(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := sqlitePersistence{db: db}
key, err := crypto.GenerateKey()
require.NoError(t, err)
contactID := types.EncodeHex(crypto.FromECDSAPub(&key.PublicKey))
err = p.SaveContact(&Contact{ID: contactID}, nil)
require.NoError(t, err)
jpegType := []byte{0xff, 0xd8, 0xff, 0x1}
identityImages := make(map[string]*protobuf.IdentityImage)
identityImages["large"] = &protobuf.IdentityImage{
Payload: jpegType,
SourceType: protobuf.IdentityImage_RAW_PAYLOAD,
ImageType: protobuf.ImageType_PNG,
}
identityImages["small"] = &protobuf.IdentityImage{
Payload: jpegType,
SourceType: protobuf.IdentityImage_RAW_PAYLOAD,
ImageType: protobuf.ImageType_PNG,
}
images := &protobuf.ChatIdentity{
Clock: 1,
Images: identityImages,
}
result, err := p.SaveContactChatIdentity(contactID, images)
require.NoError(t, err)
require.True(t, result)
// Save again same clock, it should return false
result, err = p.SaveContactChatIdentity(contactID, images)
require.NoError(t, err)
require.False(t, result)
contacts, err := p.Contacts()
require.NoError(t, err)
require.Len(t, contacts, 1)
require.Len(t, contacts[0].Images, 2)
}
func TestSaveLinks(t *testing.T) {
chatID := testPublicChatID
db, err := openTestDB()