Refactor injecting multiaccounts into messenger and service

This commit is contained in:
Samuel Hawksby-Robinson 2020-11-24 13:13:46 +00:00 committed by Andrea Maria Piana
parent 67d44c6992
commit 193ab30ada
17 changed files with 337 additions and 325 deletions

View File

@ -5,7 +5,6 @@ package api
import (
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"
"math/big"
@ -26,7 +25,6 @@ import (
"github.com/status-im/status-go/appdatabase"
"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/logutils"
"github.com/status-im/status-go/mailserver/registry"
"github.com/status-im/status-go/multiaccounts"
@ -291,7 +289,7 @@ func (b *GethStatusBackend) startNodeWithKey(acc multiaccounts.Account, password
return err
}
b.accountManager.SetAccountAddresses(walletAddr, watchAddrs...)
err = b.injectAccountIntoServices()
err = b.injectAccountsIntoServices()
if err != nil {
return err
}
@ -595,7 +593,7 @@ func (b *GethStatusBackend) startNode(config *params.NodeConfig) (err error) {
// Handle a case when a node is stopped and resumed.
// If there is no account selected, an error is returned.
if _, err := b.accountManager.SelectedChatAccount(); err == nil {
if err := b.injectAccountIntoServices(); err != nil {
if err := b.injectAccountsIntoServices(); err != nil {
return err
}
} else if err != account.ErrNoAccountSelected {
@ -1099,14 +1097,14 @@ func (b *GethStatusBackend) SelectAccount(loginParams account.LoginParams) error
return err
}
if err := b.injectAccountIntoServices(); err != nil {
if err := b.injectAccountsIntoServices(); err != nil {
return err
}
return nil
}
func (b *GethStatusBackend) injectAccountIntoServices() error {
func (b *GethStatusBackend) injectAccountsIntoServices() error {
chatAccount, err := b.accountManager.SelectedChatAccount()
if err != nil {
return err
@ -1135,7 +1133,7 @@ func (b *GethStatusBackend) injectAccountIntoServices() error {
return err
}
if err := st.InitProtocol(identity, b.appDB, logutils.ZapLogger()); err != nil {
if err := st.InitProtocol(identity, b.appDB, b.multiaccountsDB, logutils.ZapLogger()); err != nil {
return err
}
return nil
@ -1163,7 +1161,7 @@ func (b *GethStatusBackend) injectAccountIntoServices() error {
return err
}
if err := st.InitProtocol(identity, b.appDB, logutils.ZapLogger()); err != nil {
if err := st.InitProtocol(identity, b.appDB, b.multiaccountsDB, logutils.ZapLogger()); err != nil {
return err
}
}
@ -1225,7 +1223,7 @@ func (b *GethStatusBackend) InjectChatAccount(chatKeyHex, _ string) error {
}
b.accountManager.SetChatAccount(chatKey)
return b.injectAccountIntoServices()
return b.injectAccountsIntoServices()
}
func appendIf(condition bool, services []gethnode.ServiceConstructor, service gethnode.ServiceConstructor) []gethnode.ServiceConstructor {
@ -1270,56 +1268,3 @@ func (b *GethStatusBackend) SignHash(hexEncodedHash string) (string, error) {
hexEncodedSignature := types.EncodeHex(signature)
return hexEncodedSignature, nil
}
// GetIdentityImages returns an array of json marshalled IdentityImages assigned to the user's identity
func (b *GethStatusBackend) GetIdentityImages() (string, error) {
idb := images.NewDatabase(b.appDB)
iis, err := idb.GetIdentityImages()
if err != nil {
return "", err
}
js, err := json.Marshal(iis)
return string(js), err
}
// GetIdentityImage returns a json object representing the image with the given name
func (b *GethStatusBackend) GetIdentityImage(name string) (string, error) {
idb := images.NewDatabase(b.appDB)
ii, err := idb.GetIdentityImage(name)
if err != nil {
return "", err
}
js, err := json.Marshal(ii)
return string(js), err
}
// StoreIdentityImage takes the filepath of an image, crops it as per the rect coords and finally resizes the image.
// The resulting image(s) will be stored in the DB along with other user account information.
// aX and aY represent the pixel coordinates of the upper left corner of the image's cropping area
// bX and bY represent the pixel coordinates of the lower right corner of the image's cropping area
func (b *GethStatusBackend) StoreIdentityImage(filepath string, aX, aY, bX, bY int) (string, error) {
iis, err := images.GenerateIdentityImages(filepath, aX, aY, bX, bY)
if err != nil {
return "", err
}
idb := images.NewDatabase(b.appDB)
err = idb.StoreIdentityImages(iis)
if err != nil {
return "", err
}
js, err := json.Marshal(iis)
return string(js), err
}
// DeleteIdentityImage deletes an IdentityImage from the db with the given name
func (b *GethStatusBackend) DeleteIdentityImage(name string) error {
idb := images.NewDatabase(b.appDB)
return idb.DeleteIdentityImage(name)
}

View File

@ -1,17 +1,12 @@
package images
import (
"context"
"database/sql"
"encoding/json"
"errors"
)
type Database struct {
db *sql.DB
}
type IdentityImage struct {
KeyUID string
Name string
Payload []byte
Width int
@ -20,89 +15,6 @@ type IdentityImage struct {
ResizeTarget int
}
func NewDatabase(db *sql.DB) Database {
return Database{db: db}
}
func (d *Database) GetIdentityImages() ([]*IdentityImage, error) {
rows, err := d.db.Query(`SELECT name, image_payload, width, height, file_size, resize_target FROM identity_images`)
if err != nil {
return nil, err
}
defer rows.Close()
var iis []*IdentityImage
for rows.Next() {
ii := &IdentityImage{}
err = rows.Scan(&ii.Name, &ii.Payload, &ii.Width, &ii.Height, &ii.FileSize, &ii.ResizeTarget)
if err != nil {
return nil, err
}
iis = append(iis, ii)
}
return iis, nil
}
func (d *Database) GetIdentityImage(it string) (*IdentityImage, error) {
query := "SELECT name, image_payload, width, height, file_size, resize_target FROM identity_images WHERE name = ?"
rows, err := d.db.Query(query, it)
if err != nil {
return nil, err
}
defer rows.Close()
var ii IdentityImage
for rows.Next() {
err = rows.Scan(&ii.Name, &ii.Payload, &ii.Width, &ii.Height, &ii.FileSize, &ii.ResizeTarget)
if err != nil {
return nil, err
}
}
return &ii, nil
}
func (d *Database) StoreIdentityImages(iis []*IdentityImage) (err error) {
tx, err := d.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()
}()
query := "INSERT INTO identity_images (name, image_payload, width, height, file_size, resize_target) VALUES (?, ?, ?, ?, ?, ?)"
stmt, err := tx.Prepare(query)
if err != nil {
return
}
defer stmt.Close()
for _, ii := range iis {
if ii == nil {
continue
}
_, err = stmt.Exec(ii.Name, ii.Payload, ii.Width, ii.Height, ii.FileSize, ii.ResizeTarget)
if err != nil {
return
}
}
return
}
func (d *Database) DeleteIdentityImage(it string) error {
_, err := d.db.Exec(`DELETE FROM identity_images WHERE name = ?`, it)
return err
}
func (i IdentityImage) GetType() (ImageType, error) {
it := GetType(i.Payload)
if it == UNKNOWN {
@ -123,6 +35,7 @@ func (i IdentityImage) MarshalJSON() ([]byte, error) {
}
temp := struct {
KeyUID string `json:"key_uid"`
Name string `json:"type"`
URI string `json:"uri"`
Width int `json:"width"`
@ -130,6 +43,7 @@ func (i IdentityImage) MarshalJSON() ([]byte, error) {
FileSize int `json:"file_size"`
ResizeTarget int `json:"resize_target"`
}{
KeyUID: i.KeyUID,
Name: i.Name,
URI: uri,
Width: i.Width,

View File

@ -3,12 +3,8 @@ package images
import (
"encoding/json"
"errors"
"io/ioutil"
"os"
"testing"
"github.com/status-im/status-go/appdatabase"
"github.com/stretchr/testify/require"
)
@ -73,93 +69,3 @@ func TestIdentityImage_MarshalJSON(t *testing.T) {
require.NoError(t, err)
require.Exactly(t, expected, string(js))
}
func setupTestDB(t *testing.T) (Database, func()) {
tmpfile, err := ioutil.TempFile("", "images-tests-")
require.NoError(t, err)
db, err := appdatabase.InitializeDB(tmpfile.Name(), "images-tests")
require.NoError(t, err)
return NewDatabase(db), func() {
require.NoError(t, db.Close())
require.NoError(t, os.Remove(tmpfile.Name()))
}
}
func seedTestDB(t *testing.T, db Database) {
iis := []*IdentityImage{
{
Name: SmallDimName,
Payload: testJpegBytes,
Width: 80,
Height: 80,
FileSize: 256,
ResizeTarget: 80,
},
{
Name: LargeDimName,
Payload: testPngBytes,
Width: 240,
Height: 300,
FileSize: 1024,
ResizeTarget: 240,
},
}
require.NoError(t, db.StoreIdentityImages(iis))
}
func TestDatabase_GetIdentityImages(t *testing.T) {
db, stop := setupTestDB(t)
defer stop()
seedTestDB(t, db)
expected := `[{"type":"large","uri":"data:image/png;base64,iVBORw0KGgoAAAANSUg=","width":240,"height":300,"file_size":1024,"resize_target":240},{"type":"thumbnail","uri":"data:image/jpeg;base64,/9j/2wCEAFA3PEY8MlA=","width":80,"height":80,"file_size":256,"resize_target":80}]`
oiis, err := db.GetIdentityImages()
require.NoError(t, err)
joiis, err := json.Marshal(oiis)
require.NoError(t, err)
require.Exactly(t, expected, string(joiis))
}
func TestDatabase_GetIdentityImage(t *testing.T) {
db, stop := setupTestDB(t)
defer stop()
seedTestDB(t, db)
cs := []struct {
Name string
Expected string
}{
{
SmallDimName,
`{"type":"thumbnail","uri":"data:image/jpeg;base64,/9j/2wCEAFA3PEY8MlA=","width":80,"height":80,"file_size":256,"resize_target":80}`,
},
{
LargeDimName,
`{"type":"large","uri":"data:image/png;base64,iVBORw0KGgoAAAANSUg=","width":240,"height":300,"file_size":1024,"resize_target":240}`,
},
}
for _, c := range cs {
oii, err := db.GetIdentityImage(c.Name)
require.NoError(t, err)
joii, err := json.Marshal(oii)
require.NoError(t, err)
require.Exactly(t, c.Expected, string(joii))
}
}
func TestDatabase_DeleteIdentityImage(t *testing.T) {
db, stop := setupTestDB(t)
defer stop()
seedTestDB(t, db)
require.NoError(t, db.DeleteIdentityImage(SmallDimName))
oii, err := db.GetIdentityImage(SmallDimName)
require.NoError(t, err)
require.Empty(t, oii)
}

View File

@ -12,14 +12,6 @@ const (
path = "../_assets/tests/"
)
var (
testJpegBytes = []byte{0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x50, 0x37, 0x3c, 0x46, 0x3c, 0x32, 0x50}
testPngBytes = []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48}
testGifBytes = []byte{0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x00, 0x01, 0x00, 0x01, 0x84, 0x1f, 0x00, 0xff}
testWebpBytes = []byte{0x52, 0x49, 0x46, 0x46, 0x90, 0x49, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50}
testAacBytes = []byte{0xff, 0xf1, 0x50, 0x80, 0x1c, 0x3f, 0xfc, 0xda, 0x00, 0x4c, 0x61, 0x76, 0x63, 0x35}
)
func TestDecode(t *testing.T) {
cs := []struct {

View File

@ -58,6 +58,10 @@ func EncodeToBestSize(bb *bytes.Buffer, img image.Image, size ResizeDimension) e
}
func GetPayloadDataURI(payload []byte) (string, error) {
if len(payload) == 0 {
return "", nil
}
mt, err := GetMimeType(payload)
if err != nil {
return "", err

31
images/test_data.go Normal file
View File

@ -0,0 +1,31 @@
package images
// Test data that would typically only exist in a test file, used for exporting sample data outside the package.
var (
testJpegBytes = []byte{0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x50, 0x37, 0x3c, 0x46, 0x3c, 0x32, 0x50}
testPngBytes = []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48}
testGifBytes = []byte{0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x00, 0x01, 0x00, 0x01, 0x84, 0x1f, 0x00, 0xff}
testWebpBytes = []byte{0x52, 0x49, 0x46, 0x46, 0x90, 0x49, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50}
testAacBytes = []byte{0xff, 0xf1, 0x50, 0x80, 0x1c, 0x3f, 0xfc, 0xda, 0x00, 0x4c, 0x61, 0x76, 0x63, 0x35}
)
func SampleIdentityImages() []*IdentityImage {
return []*IdentityImage{
{
Name: SmallDimName,
Payload: testJpegBytes,
Width: 80,
Height: 80,
FileSize: 256,
ResizeTarget: 80,
},
{
Name: LargeDimName,
Payload: testPngBytes,
Width: 240,
Height: 300,
FileSize: 1024,
ResizeTarget: 240,
},
}
}

View File

@ -628,46 +628,3 @@ func MultiformatDeserializePublicKey(key, outBase string) string {
return pk
}
// GetProfileImages returns an array of json marshalled IdentityImages assigned to the user's identity
func GetProfileImages() string {
pis, err := statusBackend.GetIdentityImages()
if err != nil {
return makeJSONResponse(err)
}
return pis
}
// GetProfileImage returns a json object representing the image with the given name
func GetProfileImage(name string) string {
ii, err := statusBackend.GetIdentityImage(name)
if err != nil {
return makeJSONResponse(err)
}
return ii
}
// SaveProfileImage takes the filepath of an image, crops it as per the rect coords and finally resizes the image.
// The resulting image(s) will be stored in the DB along with other user account information.
// aX and aY represent the pixel coordinates of the upper left corner of the image's cropping area
// bX and bY represent the pixel coordinates of the lower right corner of the image's cropping area
func SaveProfileImage(filepath string, aX, aY, bX, bY int) string {
imgs, err := statusBackend.StoreIdentityImage(filepath, aX, aY, bX, bY)
if err != nil {
return makeJSONResponse(err)
}
return imgs
}
// DeleteProfileImage deletes an IdentityImage from the db with the given name
func DeleteProfileImage(name string) string {
err := statusBackend.DeleteIdentityImage(name)
if err != nil {
return makeJSONResponse(err)
}
return "ok"
}

View File

@ -122,6 +122,7 @@ func (db Database) Close() error {
return db.db.Close()
}
// TODO remove photoPath from settings
func (db *Database) CreateSettings(s Settings, nodecfg params.NodeConfig) error {
_, err := db.db.Exec(`
INSERT INTO settings (

View File

@ -1,19 +1,28 @@
package multiaccounts
import (
"context"
"database/sql"
"github.com/status-im/status-go/images"
"github.com/status-im/status-go/multiaccounts/migrations"
"github.com/status-im/status-go/sqlite"
)
// TODO remove reference to PhotoPath
// TODO write migration to drop PhotoPath
// Account stores public information about account.
type Account struct {
Name string `json:"name"`
Timestamp int64 `json:"timestamp"`
PhotoPath string `json:"photo-path"`
KeycardPairing string `json:"keycard-pairing"`
KeyUID string `json:"key-uid"`
Name string `json:"name"`
Timestamp int64 `json:"timestamp"`
KeycardPairing string `json:"keycard-pairing"`
KeyUID string `json:"key-uid"`
ImageURIs []string `json:"image-uris"`
}
type Database struct {
db *sql.DB
}
// InitializeDB creates db file at a given path and applies migrations.
@ -29,25 +38,21 @@ func InitializeDB(path string) (*Database, error) {
return &Database{db: db}, nil
}
type Database struct {
db *sql.DB
}
func (db *Database) Close() error {
return db.db.Close()
}
func (db *Database) GetAccounts() ([]Account, error) {
rows, err := db.db.Query("SELECT name, loginTimestamp, photoPath, keycardPairing, keyUid from accounts ORDER BY loginTimestamp DESC")
rows, err := db.db.Query("SELECT name, loginTimestamp, keycardPairing, keyUid from accounts ORDER BY loginTimestamp DESC")
if err != nil {
return nil, err
}
defer rows.Close()
rst := []Account{}
var rst []Account
inthelper := sql.NullInt64{}
for rows.Next() {
acc := Account{}
err = rows.Scan(&acc.Name, &inthelper, &acc.PhotoPath, &acc.KeycardPairing, &acc.KeyUID)
err = rows.Scan(&acc.Name, &inthelper, &acc.KeycardPairing, &acc.KeyUID)
if err != nil {
return nil, err
}
@ -58,12 +63,12 @@ func (db *Database) GetAccounts() ([]Account, error) {
}
func (db *Database) SaveAccount(account Account) error {
_, err := db.db.Exec("INSERT OR REPLACE INTO accounts (name, photoPath, keycardPairing, keyUid) VALUES (?, ?, ?, ?)", account.Name, account.PhotoPath, account.KeycardPairing, account.KeyUID)
_, err := db.db.Exec("INSERT OR REPLACE INTO accounts (name, keycardPairing, keyUid) VALUES (?, ?, ?)", account.Name, account.KeycardPairing, account.KeyUID)
return err
}
func (db *Database) UpdateAccount(account Account) error {
_, err := db.db.Exec("UPDATE accounts SET name = ?, photoPath = ?, keycardPairing = ? WHERE keyUid = ?", account.Name, account.PhotoPath, account.KeycardPairing, account.KeyUID)
_, err := db.db.Exec("UPDATE accounts SET name = ?, keycardPairing = ? WHERE keyUid = ?", account.Name, account.KeycardPairing, account.KeyUID)
return err
}
@ -76,3 +81,87 @@ func (db *Database) DeleteAccount(keyUID string) error {
_, err := db.db.Exec("DELETE FROM accounts WHERE keyUid = ?", keyUID)
return err
}
// Account images
func (db *Database) GetIdentityImages(keyUid 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 = ?`, keyUid)
if err != nil {
return nil, err
}
defer rows.Close()
var iis []*images.IdentityImage
for rows.Next() {
ii := &images.IdentityImage{}
err = rows.Scan(&ii.KeyUID, &ii.Name, &ii.Payload, &ii.Width, &ii.Height, &ii.FileSize, &ii.ResizeTarget)
if err != nil {
return nil, err
}
iis = append(iis, ii)
}
return iis, nil
}
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 {
return nil, err
}
}
return &ii, nil
}
func (db *Database) StoreIdentityImages(keyUid string, iis []*images.IdentityImage) error {
// Because SQL INSERTs are triggered in a loop use a tx to ensure a single call to the DB.
tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
if err != nil {
return err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
// don't shadow original error
_ = tx.Rollback()
}()
for _, ii := range iis {
if ii == nil {
continue
}
_, err := tx.Exec(
"INSERT INTO identity_images (key_uid, name, image_payload, width, height, file_size, resize_target) VALUES (?, ?, ?, ?, ?, ?, ?)",
keyUid,
ii.Name,
ii.Payload,
ii.Width,
ii.Height,
ii.FileSize,
ii.ResizeTarget,
)
if err != nil {
return err
}
}
return nil
}
func (db *Database) DeleteIdentityImage(keyUid string) error {
_, err := db.db.Exec(`DELETE FROM identity_images WHERE key_uid = ?`, keyUid)
return err
}

View File

@ -1,10 +1,13 @@
package multiaccounts
import (
"encoding/json"
"io/ioutil"
"os"
"testing"
"github.com/status-im/status-go/images"
"github.com/stretchr/testify/require"
)
@ -35,7 +38,7 @@ func TestAccountsUpdate(t *testing.T) {
defer stop()
expected := Account{KeyUID: "string"}
require.NoError(t, db.SaveAccount(expected))
expected.PhotoPath = "chars"
expected.Name = "chars"
require.NoError(t, db.UpdateAccount(expected))
rst, err := db.GetAccounts()
require.NoError(t, err)
@ -59,3 +62,84 @@ func TestLoginUpdate(t *testing.T) {
require.NoError(t, err)
require.Equal(t, accounts, rst)
}
// Profile Image tests
var (
keyUid = "0xdeadbeef"
keyUid2 = "0x1337beef"
)
func seedTestDB(t *testing.T, db *Database) {
iis := images.SampleIdentityImages()
require.NoError(t, db.StoreIdentityImages(keyUid, iis))
}
func TestDatabase_GetIdentityImages(t *testing.T) {
db, stop := setupTestDB(t)
defer stop()
seedTestDB(t, db)
expected := `[{"key_uid":"0xdeadbeef","type":"large","uri":"data:image/png;base64,iVBORw0KGgoAAAANSUg=","width":240,"height":300,"file_size":1024,"resize_target":240},{"key_uid":"0xdeadbeef","type":"thumbnail","uri":"data:image/jpeg;base64,/9j/2wCEAFA3PEY8MlA=","width":80,"height":80,"file_size":256,"resize_target":80}]`
oiis, err := db.GetIdentityImages(keyUid)
require.NoError(t, err)
joiis, err := json.Marshal(oiis)
require.NoError(t, err)
require.Exactly(t, expected, string(joiis))
oiis, err = db.GetIdentityImages(keyUid2)
require.NoError(t, err)
require.Exactly(t, 0, len(oiis))
}
func TestDatabase_GetIdentityImage(t *testing.T) {
db, stop := setupTestDB(t)
defer stop()
seedTestDB(t, db)
cs := []struct {
KeyUid string
Name string
Expected string
}{
{
keyUid,
images.SmallDimName,
`{"key_uid":"0xdeadbeef","type":"thumbnail","uri":"data:image/jpeg;base64,/9j/2wCEAFA3PEY8MlA=","width":80,"height":80,"file_size":256,"resize_target":80}`,
},
{
keyUid,
images.LargeDimName,
`{"key_uid":"0xdeadbeef","type":"large","uri":"data:image/png;base64,iVBORw0KGgoAAAANSUg=","width":240,"height":300,"file_size":1024,"resize_target":240}`,
},
{
keyUid2,
images.LargeDimName,
`{"key_uid":"","type":"","uri":"","width":0,"height":0,"file_size":0,"resize_target":0}`,
},
}
for _, c := range cs {
oii, err := db.GetIdentityImage(c.KeyUid, c.Name)
require.NoError(t, err)
joii, err := json.Marshal(oii)
require.NoError(t, err)
require.Exactly(t, c.Expected, string(joii))
}
}
func TestDatabase_DeleteIdentityImage(t *testing.T) {
db, stop := setupTestDB(t)
defer stop()
seedTestDB(t, db)
require.NoError(t, db.DeleteIdentityImage(keyUid))
oii, err := db.GetIdentityImage(keyUid, images.SmallDimName)
require.NoError(t, err)
require.Empty(t, oii)
}

View File

@ -3,7 +3,7 @@
// 0001_accounts.down.sql (21B)
// 0001_accounts.up.sql (163B)
// 1605007189_identity_images.down.sql (29B)
// 1605007189_identity_images.up.sql (266B)
// 1605007189_identity_images.up.sql (268B)
// doc.go (74B)
package migrations
@ -133,7 +133,7 @@ func _1605007189_identity_imagesDownSql() (*asset, error) {
return a, nil
}
var __1605007189_identity_imagesUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\xce\xc1\x6a\xc3\x30\x10\x04\xd0\xbb\xc1\xff\x30\xc7\x04\xf2\x07\x3d\xc9\xaa\x42\x44\x55\x29\x28\x72\xd3\x9c\x84\x40\x5b\x7b\x69\xe2\x96\x44\x50\xdc\xaf\x2f\x75\x7c\x30\x39\xee\x63\x76\x18\xe9\x95\x08\x0a\x41\x34\x46\x41\x6f\x61\x5d\x80\x7a\xd7\x87\x70\x00\x67\x1a\x0a\x97\x31\xf2\x25\x75\x74\x5b\xd5\x15\x00\x7c\xd2\xd8\x72\xc6\x9b\xf0\x72\x27\xfc\xe6\x8e\x43\xba\xd0\x03\x4d\x4f\xf1\x3b\x8d\xe7\xaf\x94\xd1\x18\xd7\x4c\xdd\xb6\x35\x66\x4e\xfc\x70\x2e\x3d\x78\x28\xf3\xdd\x13\x77\x7d\x59\xc0\x07\x9f\x29\xde\xf8\x97\x16\x76\xa5\x7f\x88\x25\x5d\x3b\x5a\x66\xf7\x5e\xbf\x0a\x7f\xc2\x8b\x3a\x61\x75\xdf\xb8\x99\x66\xad\xe1\x2c\xa4\xb3\x5b\xa3\x65\x80\x57\x7b\x23\xa4\xaa\xab\x35\x8e\x3a\xec\x5c\x1b\xe0\xdd\x51\x3f\x3f\xd5\xd5\x5f\x00\x00\x00\xff\xff\xec\x31\x60\x93\x0a\x01\x00\x00")
var __1605007189_identity_imagesUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\xce\xc1\x6a\xc3\x30\x10\x04\xd0\xbb\xc1\xff\x30\xc7\x04\xf2\x07\x3d\xc9\xaa\x42\x44\x55\x29\x28\x4a\xd3\x9c\x84\x40\x5b\x7b\x69\xe2\x96\x58\xa5\xb8\x5f\x5f\xea\xfa\x60\x72\xdc\xc7\xec\x30\xd2\x2b\x11\x14\x82\x68\x8c\x82\xde\xc2\xba\x00\xf5\xaa\x0f\xe1\x00\xce\xd4\x17\x2e\x63\xe4\x6b\x6a\x69\x58\xd5\x15\x00\xbc\xd3\x18\xbf\x38\xe3\x45\x78\xb9\x13\x7e\xf3\xaf\x7d\xba\xd2\x1d\x4d\x5f\xf1\x33\x8d\x97\x8f\x94\xd1\x18\xd7\x4c\xe5\xf6\x68\xcc\x9c\xf8\xe6\x5c\x3a\x70\x5f\xe6\xbb\x23\x6e\xbb\xb2\x80\x37\xbe\x50\x1c\xf8\x87\x16\x76\xa3\x3f\x88\x25\xdd\x5a\x5a\x66\xf7\x5e\x3f\x0b\x7f\xc6\x93\x3a\x63\x35\x8f\xdc\x4c\xbb\xd6\x70\x16\xd2\xd9\xad\xd1\x32\xc0\xab\xbd\x11\x52\xd5\xd5\x1a\x27\x1d\x76\xee\x18\xe0\xdd\x49\x3f\x3e\xd4\xd5\x6f\x00\x00\x00\xff\xff\x8c\x6a\x0a\x57\x0c\x01\x00\x00")
func _1605007189_identity_imagesUpSqlBytes() ([]byte, error) {
return bindataRead(
@ -148,8 +148,8 @@ func _1605007189_identity_imagesUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1605007189_identity_images.up.sql", size: 266, mode: os.FileMode(0644), modTime: time.Unix(1608048507, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xe, 0x33, 0xd, 0xf1, 0x5c, 0xb0, 0x64, 0x4e, 0xec, 0x89, 0xa1, 0x6, 0x56, 0x70, 0x36, 0xca, 0xe5, 0x6c, 0xd8, 0xdd, 0x45, 0xce, 0xc3, 0xe9, 0x66, 0xbd, 0x1c, 0x23, 0xe8, 0x42, 0xb6, 0x17}}
info := bindataFileInfo{name: "1605007189_identity_images.up.sql", size: 268, mode: os.FileMode(0644), modTime: time.Unix(1608048533, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x50, 0xb6, 0xc1, 0x5c, 0x76, 0x72, 0x6b, 0x22, 0x34, 0xdc, 0x96, 0xdc, 0x2b, 0xfd, 0x2d, 0xbe, 0xcc, 0x1e, 0xd4, 0x5, 0x93, 0xd, 0xc2, 0x51, 0xf3, 0x1a, 0xef, 0x2b, 0x26, 0xa4, 0xeb, 0x65}}
return a, nil
}

View File

@ -1,10 +1,10 @@
CREATE TABLE IF NOT EXISTS identity_images(
keyUid VARCHAR,
key_uid VARCHAR,
name VARCHAR,
image_payload BLOB NOT NULL,
width int,
height int,
file_size int,
resize_target int,
PRIMARY KEY (keyUid, name) ON CONFLICT REPLACE
PRIMARY KEY (key_uid, name) ON CONFLICT REPLACE
) WITHOUT ROWID;

View File

@ -3,8 +3,11 @@ package protocol
import (
"context"
"crypto/ecdsa"
"crypto/sha256"
"database/sql"
"encoding/hex"
gethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/status-im/status-go/multiaccounts"
"io/ioutil"
"math"
"math/rand"
@ -78,6 +81,7 @@ type Messenger struct {
installationID string
mailserver []byte
database *sql.DB
multiAccounts *multiaccounts.Database
quit chan struct{}
mutex sync.Mutex
@ -277,6 +281,7 @@ func NewMessenger(
messagesPersistenceEnabled: c.messagesPersistenceEnabled,
verifyTransactionClient: c.verifyTransactionClient,
database: database,
multiAccounts: c.multiAccount,
quit: make(chan struct{}),
shutdownTasks: []func() error{
database.Close,
@ -565,6 +570,9 @@ func (m *Messenger) shouldPublishChatIdentity(chatId string) (bool, error) {
// context 'public-chat' will attach only the 'thumbnail' IdentityImage
// context 'private-chat' will attach all IdentityImage
func (m *Messenger) createChatIdentity(context string) (*protobuf.ChatIdentity, error) {
keyUIDBytes := sha256.Sum256(gethcrypto.FromECDSAPub(&m.identity.PublicKey))
keyUID := types.EncodeHex(keyUIDBytes[:])
ci := &protobuf.ChatIdentity{
Clock: m.transport.GetCurrentTime(),
EnsName: "", // TODO add ENS name handling to dedicate PR
@ -574,8 +582,7 @@ func (m *Messenger) createChatIdentity(context string) (*protobuf.ChatIdentity,
switch context {
case "public-chat":
idb := userimage.NewDatabase(m.database)
img, err := idb.GetIdentityImage(userimage.SmallDimName)
img, err := m.multiAccounts.GetIdentityImage(keyUID, userimage.SmallDimName)
if err != nil {
return nil, err
}
@ -584,8 +591,7 @@ func (m *Messenger) createChatIdentity(context string) (*protobuf.ChatIdentity,
ci.Images = ciis
case "private-chat":
idb := userimage.NewDatabase(m.database)
imgs, err := idb.GetIdentityImages()
imgs, err := m.multiAccounts.GetIdentityImages(keyUID)
if err != nil {
return nil, err
}

View File

@ -2,6 +2,7 @@ package protocol
import (
"database/sql"
"github.com/status-im/status-go/multiaccounts"
"go.uber.org/zap"
@ -30,6 +31,7 @@ type config struct {
// The database instance has a higher priority.
dbConfig dbConfig
db *sql.DB
multiAccount *multiaccounts.Database
verifyTransactionClient EthClient
@ -92,6 +94,13 @@ func WithDatabase(db *sql.DB) Option {
}
}
func WithMultiAccounts(ma *multiaccounts.Database) Option {
return func(c *config) error {
c.multiAccount = ma
return nil
}
}
func WithPushNotificationServerConfig(pushNotificationServerConfig *pushnotificationserver.Config) Option {
return func(c *config) error {
c.pushNotificationServerConfig = pushNotificationServerConfig

View File

@ -3,8 +3,10 @@ package ext
import (
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/status-im/status-go/images"
"math/big"
"time"
@ -583,6 +585,59 @@ func (api *PublicAPI) Echo(ctx context.Context, message string) (string, error)
return message, nil
}
//
// Profile Images
//
// GetIdentityImages returns an array of json marshalled IdentityImages assigned to the user's identity
func (api *PublicAPI) GetIdentityImages(keyUid string) (string, error) {
iis, err := api.service.multiAccountsDB.GetIdentityImages(keyUid)
if err != nil {
return "", err
}
js, err := json.Marshal(iis)
return string(js), err
}
// GetIdentityImage returns a json object representing the image with the given name
func (api *PublicAPI) GetIdentityImage(keyUid, name string) (string, error) {
ii, err := api.service.multiAccountsDB.GetIdentityImage(keyUid, name)
if err != nil {
return "", err
}
js, err := json.Marshal(ii)
return string(js), err
}
// StoreIdentityImage takes the filepath of an image, crops it as per the rect coords and finally resizes the image.
// The resulting image(s) will be stored in the DB along with other user account information.
// aX and aY represent the pixel coordinates of the upper left corner of the image's cropping area
// bX and bY represent the pixel coordinates of the lower right corner of the image's cropping area
func (api *PublicAPI) StoreIdentityImage(keyUid, filepath string, aX, aY, bX, bY int) (string, error) {
iis, err := images.GenerateIdentityImages(filepath, aX, aY, bX, bY)
if err != nil {
return "", err
}
err = api.service.multiAccountsDB.StoreIdentityImages(keyUid, iis)
if err != nil {
return "", err
}
js, err := json.Marshal(iis)
return string(js), err
}
// DeleteIdentityImage deletes an IdentityImage from the db with the given name
func (api *PublicAPI) DeleteIdentityImage(keyUid string) error {
return api.service.multiAccountsDB.DeleteIdentityImage(keyUid)
}
// -----
// HELPER
// -----

View File

@ -4,6 +4,7 @@ import (
"context"
"crypto/ecdsa"
"database/sql"
"github.com/status-im/status-go/multiaccounts"
"math/big"
"os"
"path/filepath"
@ -70,6 +71,7 @@ type Service struct {
connManager *mailservers.ConnectionManager
lastUsedMonitor *mailservers.LastUsedConnectionMonitor
accountsDB *accounts.Database
multiAccountsDB *multiaccounts.Database
}
// Make sure that Service implements node.Service interface.
@ -115,7 +117,7 @@ func (s *Service) GetPeer(rawURL string) (*enode.Node, error) {
return enode.ParseV4(rawURL)
}
func (s *Service) InitProtocol(identity *ecdsa.PrivateKey, db *sql.DB, logger *zap.Logger) error {
func (s *Service) InitProtocol(identity *ecdsa.PrivateKey, db *sql.DB, multiAccountDb *multiaccounts.Database, logger *zap.Logger) error {
if !s.config.PFSEnabled {
return nil
}
@ -147,8 +149,9 @@ func (s *Service) InitProtocol(identity *ecdsa.PrivateKey, db *sql.DB, logger *z
Logger: logger,
}
s.accountsDB = accounts.NewDB(db)
s.multiAccountsDB = multiAccountDb
options, err := buildMessengerOptions(s.config, identity, db, envelopesMonitorConfig, s.accountsDB, logger)
options, err := buildMessengerOptions(s.config, identity, db, s.multiAccountsDB, envelopesMonitorConfig, s.accountsDB, logger)
if err != nil {
return err
}
@ -454,6 +457,7 @@ func buildMessengerOptions(
config params.ShhextConfig,
identity *ecdsa.PrivateKey,
db *sql.DB,
multiAccounts *multiaccounts.Database,
envelopesMonitorConfig *transport.EnvelopesMonitorConfig,
accountsDB *accounts.Database,
logger *zap.Logger,
@ -462,6 +466,7 @@ func buildMessengerOptions(
protocol.WithCustomLogger(logger),
protocol.WithPushNotifications(),
protocol.WithDatabase(db),
protocol.WithMultiAccounts(multiAccounts),
protocol.WithEnvelopesMonitorConfig(envelopesMonitorConfig),
protocol.WithOnNegotiatedFilters(onNegotiatedFilters),
}

View File

@ -4,6 +4,7 @@ import (
"context"
"encoding/hex"
"fmt"
"github.com/status-im/status-go/multiaccounts"
"io/ioutil"
"math"
"net"
@ -129,7 +130,12 @@ func TestInitProtocol(t *testing.T) {
sqlDB, err := appdatabase.InitializeDB(fmt.Sprintf("%s/db.sql", tmpdir), "password")
require.NoError(t, err)
err = service.InitProtocol(privateKey, sqlDB, zap.NewNop())
tmpfile, err := ioutil.TempFile("", "multi-accounts-tests-")
require.NoError(t, err)
multiAccounts, err := multiaccounts.InitializeDB(tmpfile.Name())
require.NoError(t, err)
err = service.InitProtocol(privateKey, sqlDB, multiAccounts, zap.NewNop())
require.NoError(t, err)
}
@ -181,10 +187,18 @@ func (s *ShhExtSuite) createAndAddNode() {
service := New(config, nodeWrapper, nil, nil, db)
sqlDB, err := appdatabase.InitializeDB(fmt.Sprintf("%s/%d", s.dir, idx), "password")
s.Require().NoError(err)
tmpfile, err := ioutil.TempFile("", "multi-accounts-tests-")
s.Require().NoError(err)
multiAccounts, err := multiaccounts.InitializeDB(tmpfile.Name())
s.Require().NoError(err)
privateKey, err := crypto.GenerateKey()
s.NoError(err)
err = service.InitProtocol(privateKey, sqlDB, zap.NewNop())
err = service.InitProtocol(privateKey, sqlDB, multiAccounts, zap.NewNop())
s.NoError(err)
err = stack.Register(func(n *node.ServiceContext) (node.Service, error) {
return service, nil
})