mirror of
https://github.com/status-im/status-console-client.git
synced 2025-02-23 16:18:23 +00:00
Rename Contact -> chats and remove histories (#116)
This commit renames contacts -> chats and remove histories. I have also collapsed the migrations for now so it's not backward compatible, but while in heavy development is probably best just to keep a single migration.
This commit is contained in:
parent
af2f7d96f0
commit
695bdfb61c
20
chat.go
20
chat.go
@ -21,7 +21,7 @@ import (
|
||||
type ChatViewController struct {
|
||||
*ViewController
|
||||
|
||||
contact client.Contact
|
||||
contact client.Chat
|
||||
|
||||
identity *ecdsa.PrivateKey
|
||||
messenger *client.Messenger
|
||||
@ -31,7 +31,7 @@ type ChatViewController struct {
|
||||
cancel chan struct{} // cancel the current chat loop
|
||||
done chan struct{} // wait for the current chat loop to finish
|
||||
|
||||
changeContact chan client.Contact
|
||||
changeChat chan client.Chat
|
||||
}
|
||||
|
||||
// NewChatViewController returns a new chat view controller.
|
||||
@ -45,11 +45,11 @@ func NewChatViewController(vc *ViewController, id Identity, m *client.Messenger,
|
||||
identity: id,
|
||||
messenger: m,
|
||||
onError: onError,
|
||||
changeContact: make(chan client.Contact, 1),
|
||||
changeChat: make(chan client.Chat, 1),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ChatViewController) readEventsLoop(contact client.Contact) {
|
||||
func (c *ChatViewController) readEventsLoop(contact client.Chat) {
|
||||
c.done = make(chan struct{})
|
||||
defer close(c.done)
|
||||
|
||||
@ -100,9 +100,9 @@ func (c *ChatViewController) readEventsLoop(contact client.Contact) {
|
||||
switch ev := event.Interface.(type) {
|
||||
case client.EventWithError:
|
||||
c.onError(ev.GetError())
|
||||
case client.EventWithContact:
|
||||
if !ev.GetContact().Equal(contact) {
|
||||
log.Printf("[ChatViewController::readEventsLoop] selected and received message contact are not equal: %s, %s", contact, ev.GetContact())
|
||||
case client.EventWithChat:
|
||||
if !ev.GetChat().Equal(contact) {
|
||||
log.Printf("[ChatViewController::readEventsLoop] selected and received message contact are not equal: %s, %s", contact, ev.GetChat())
|
||||
continue
|
||||
}
|
||||
msgev, ok := ev.(client.EventWithMessage)
|
||||
@ -126,7 +126,7 @@ func (c *ChatViewController) readEventsLoop(contact client.Contact) {
|
||||
|
||||
messages = append(messages, msg)
|
||||
}
|
||||
case contact = <-c.changeContact:
|
||||
case contact = <-c.changeChat:
|
||||
inorder = false
|
||||
clock = 0
|
||||
messages = []*protocol.Message{}
|
||||
@ -138,14 +138,14 @@ func (c *ChatViewController) readEventsLoop(contact client.Contact) {
|
||||
|
||||
// Select informs the chat view controller about a selected contact.
|
||||
// The chat view controller setup subscribers and request recent messages.
|
||||
func (c *ChatViewController) Select(contact client.Contact) error {
|
||||
func (c *ChatViewController) Select(contact client.Chat) error {
|
||||
log.Printf("[ChatViewController::Select] contact %s", contact.Name)
|
||||
|
||||
if c.cancel == nil {
|
||||
c.cancel = make(chan struct{})
|
||||
go c.readEventsLoop(contact)
|
||||
}
|
||||
c.changeContact <- contact
|
||||
c.changeChat <- contact
|
||||
c.contact = contact
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
|
24
chat_test.go
24
chat_test.go
@ -39,18 +39,9 @@ func TestSendMessage(t *testing.T) {
|
||||
Return(nil).
|
||||
Times(1)
|
||||
|
||||
protoMock.EXPECT().
|
||||
Request(
|
||||
gomock.Any(),
|
||||
gomock.Any(),
|
||||
).
|
||||
Return(nil).
|
||||
Times(1)
|
||||
|
||||
err = vc.Select(client.Contact{
|
||||
Name: chatName,
|
||||
Type: client.ContactPublicRoom,
|
||||
Topic: chatName,
|
||||
err = vc.Select(client.Chat{
|
||||
Name: chatName,
|
||||
Type: client.PublicChat,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// close reading loops
|
||||
@ -72,13 +63,4 @@ func TestSendMessage(t *testing.T) {
|
||||
err = vc.Send(payload)
|
||||
require.NoError(t, err)
|
||||
|
||||
// TODO: move to another layer
|
||||
// statusMessage, err := protocol.DecodeMessage(message.data)
|
||||
// require.NoError(t, err)
|
||||
// require.EqualValues(t, payload, statusMessage.Text)
|
||||
// require.Equal(t, protocol.ContentTypeTextPlain, statusMessage.ContentT)
|
||||
// require.Equal(t, protocol.MessageTypePublicGroup, statusMessage.MessageT)
|
||||
// require.Equal(t,
|
||||
// protocol.Content{ChatID: chatName, Text: string(payload)},
|
||||
// statusMessage.Content)
|
||||
}
|
||||
|
144
chats.go
Normal file
144
chats.go
Normal file
@ -0,0 +1,144 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
"github.com/status-im/status-console-client/protocol/client"
|
||||
)
|
||||
|
||||
const (
|
||||
refreshInterval = time.Second
|
||||
)
|
||||
|
||||
// chatToString returns a string representation.
|
||||
func chatToString(c client.Chat) string {
|
||||
switch c.Type {
|
||||
case client.PublicChat:
|
||||
return fmt.Sprintf("#%s", c.Name)
|
||||
case client.OneToOneChat:
|
||||
return fmt.Sprintf("@%s", c.Name)
|
||||
default:
|
||||
return c.Name
|
||||
}
|
||||
}
|
||||
|
||||
// ChatsViewController manages chats view.
|
||||
type ChatsViewController struct {
|
||||
*ViewController
|
||||
messenger *client.Messenger
|
||||
chats []client.Chat
|
||||
|
||||
quit chan struct{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewChatsViewController returns a new chat view controller.
|
||||
func NewChatsViewController(vm *ViewController, m *client.Messenger) *ChatsViewController {
|
||||
return &ChatsViewController{ViewController: vm, messenger: m, quit: make(chan struct{})}
|
||||
}
|
||||
|
||||
// refresh repaints the current list of chats.
|
||||
func (c *ChatsViewController) refresh() {
|
||||
c.g.Update(func(*gocui.Gui) error {
|
||||
if err := c.Clear(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, chat := range c.chats {
|
||||
if _, err := fmt.Fprintln(c.ViewController, chatToString(chat)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// load loads chats from the storage.
|
||||
func (c *ChatsViewController) load() error {
|
||||
chats, err := c.messenger.Chats()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.chats = chats
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadAndRefresh loads chats from the storage and refreshes the view.
|
||||
func (c *ChatsViewController) LoadAndRefresh() error {
|
||||
c.once.Do(func() {
|
||||
go func() {
|
||||
ticker := time.Tick(refreshInterval)
|
||||
for {
|
||||
select {
|
||||
case <-ticker:
|
||||
_ = c.refreshOnChanges()
|
||||
case <-c.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
})
|
||||
if err := c.load(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.refresh()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ChatsViewController) refreshOnChanges() error {
|
||||
chats, err := c.messenger.Chats()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.containsChanges(chats) {
|
||||
log.Printf("[chatS] new chats %v", chats)
|
||||
c.chats = chats
|
||||
c.refresh()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ChatsViewController) containsChanges(chats []client.Chat) bool {
|
||||
if len(chats) != len(c.chats) {
|
||||
return true
|
||||
}
|
||||
// every time chats are sorted in a same way.
|
||||
for i := range chats {
|
||||
if !chats[i].Equal(c.chats[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ChatByIdx allows to retrieve a chat for a given index.
|
||||
func (c *ChatsViewController) ChatByIdx(idx int) (client.Chat, bool) {
|
||||
if idx > -1 && idx < len(c.chats) {
|
||||
return c.chats[idx], true
|
||||
}
|
||||
return client.Chat{}, false
|
||||
}
|
||||
|
||||
// Add adds a new chat to the list.
|
||||
func (c *ChatsViewController) Add(chat client.Chat) error {
|
||||
if err := c.messenger.Join(context.TODO(), chat); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.LoadAndRefresh()
|
||||
}
|
||||
|
||||
// Remove removes a chat from the list.
|
||||
func (c *ChatsViewController) Remove(chat client.Chat) error {
|
||||
if err := c.messenger.RemoveChat(chat); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.LoadAndRefresh()
|
||||
}
|
143
contacts.go
143
contacts.go
@ -1,143 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
"github.com/status-im/status-console-client/protocol/client"
|
||||
)
|
||||
|
||||
const (
|
||||
refreshInterval = time.Second
|
||||
)
|
||||
|
||||
// contactToString returns a string representation.
|
||||
func contactToString(c client.Contact) string {
|
||||
switch c.Type {
|
||||
case client.ContactPublicRoom:
|
||||
return fmt.Sprintf("#%s", c.Name)
|
||||
case client.ContactPrivate:
|
||||
return fmt.Sprintf("@%s", c.Name)
|
||||
default:
|
||||
return c.Name
|
||||
}
|
||||
}
|
||||
|
||||
// ContactsViewController manages contacts view.
|
||||
type ContactsViewController struct {
|
||||
*ViewController
|
||||
messenger *client.Messenger
|
||||
contacts []client.Contact
|
||||
|
||||
quit chan struct{}
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewContactsViewController returns a new contact view controller.
|
||||
func NewContactsViewController(vm *ViewController, m *client.Messenger) *ContactsViewController {
|
||||
return &ContactsViewController{ViewController: vm, messenger: m, quit: make(chan struct{})}
|
||||
}
|
||||
|
||||
// refresh repaints the current list of contacts.
|
||||
func (c *ContactsViewController) refresh() {
|
||||
c.g.Update(func(*gocui.Gui) error {
|
||||
if err := c.Clear(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, contact := range c.contacts {
|
||||
if _, err := fmt.Fprintln(c.ViewController, contactToString(contact)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// load loads contacts from the storage.
|
||||
func (c *ContactsViewController) load() error {
|
||||
contacts, err := c.messenger.Contacts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.contacts = contacts
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadAndRefresh loads contacts from the storage and refreshes the view.
|
||||
func (c *ContactsViewController) LoadAndRefresh() error {
|
||||
c.once.Do(func() {
|
||||
go func() {
|
||||
ticker := time.Tick(refreshInterval)
|
||||
for {
|
||||
select {
|
||||
case <-ticker:
|
||||
_ = c.refreshOnChanges()
|
||||
case <-c.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}()
|
||||
})
|
||||
if err := c.load(); err != nil {
|
||||
return err
|
||||
}
|
||||
c.refresh()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ContactsViewController) refreshOnChanges() error {
|
||||
contacts, err := c.messenger.Contacts()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.containsChanges(contacts) {
|
||||
log.Printf("[CONTACTS] new contacts %v", contacts)
|
||||
c.contacts = contacts
|
||||
c.refresh()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ContactsViewController) containsChanges(contacts []client.Contact) bool {
|
||||
if len(contacts) != len(c.contacts) {
|
||||
return true
|
||||
}
|
||||
// every time contacts are sorted in a same way.
|
||||
for i := range contacts {
|
||||
if !contacts[i].Equal(c.contacts[i]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ContactByIdx allows to retrieve a contact for a given index.
|
||||
func (c *ContactsViewController) ContactByIdx(idx int) (client.Contact, bool) {
|
||||
if idx > -1 && idx < len(c.contacts) {
|
||||
return c.contacts[idx], true
|
||||
}
|
||||
return client.Contact{}, false
|
||||
}
|
||||
|
||||
// Add adds a new contact to the list.
|
||||
func (c *ContactsViewController) Add(contact client.Contact) error {
|
||||
if err := c.messenger.AddContact(contact); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.LoadAndRefresh()
|
||||
}
|
||||
|
||||
// Remove removes a contact from the list.
|
||||
func (c *ContactsViewController) Remove(contact client.Contact) error {
|
||||
if err := c.messenger.RemoveContact(contact); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.LoadAndRefresh()
|
||||
}
|
28
input.go
28
input.go
@ -67,40 +67,38 @@ func bytesToArgs(b []byte) []string {
|
||||
return argsStr
|
||||
}
|
||||
|
||||
func contactAddCmdHandler(args []string) (c client.Contact, err error) {
|
||||
func chatAddCmdHandler(args []string) (c client.Chat, err error) {
|
||||
if len(args) == 1 {
|
||||
name := args[0]
|
||||
c = client.Contact{
|
||||
Name: name,
|
||||
Type: client.ContactPublicRoom,
|
||||
Topic: name,
|
||||
c = client.Chat{
|
||||
Name: name,
|
||||
Type: client.PublicChat,
|
||||
}
|
||||
} else if len(args) == 2 {
|
||||
c, err = client.CreateContactPrivate(args[1], args[0], client.ContactAdded)
|
||||
c, err = client.CreateOneToOneChat(args[1], args[0])
|
||||
} else {
|
||||
err = errors.New("/contact: incorect arguments to add subcommand")
|
||||
err = errors.New("/chat: incorect arguments to add subcommand")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ContactCmdFactory(c *ContactsViewController) CmdHandler {
|
||||
func ChatCmdFactory(c *ChatsViewController) CmdHandler {
|
||||
return func(b []byte) error {
|
||||
args := bytesToArgs(b)[1:] // remove first item, i.e. "/contact"
|
||||
args := bytesToArgs(b)[1:] // remove first item, i.e. "/chat"
|
||||
|
||||
log.Printf("handle /contact command: %s", b)
|
||||
log.Printf("handle /chat command: %s", b)
|
||||
|
||||
switch args[0] {
|
||||
case "add":
|
||||
contact, err := contactAddCmdHandler(args[1:])
|
||||
chat, err := chatAddCmdHandler(args[1:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("adding contact with topic %s\n", contact.Topic)
|
||||
if err := c.Add(contact); err != nil {
|
||||
if err := c.Add(chat); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: fix removing contacts
|
||||
// TODO: fix removing chats
|
||||
// case "remove":
|
||||
// if len(args) == 2 {
|
||||
// if err := c.Remove(args[1]); err != nil {
|
||||
@ -109,7 +107,7 @@ func ContactCmdFactory(c *ContactsViewController) CmdHandler {
|
||||
// c.Refresh()
|
||||
// return nil
|
||||
// }
|
||||
// return errors.New("/contact: incorect arguments to remove subcommand")
|
||||
// return errors.New("/chat: incorect arguments to remove subcommand")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
71
main.go
71
main.go
@ -55,7 +55,7 @@ var (
|
||||
|
||||
// flags acting like commands
|
||||
createKeyPair = fs.Bool("create-key-pair", false, "creates and prints a key pair instead of running")
|
||||
addContact = fs.String("add-contact", "", "add contact using format: type,name[,public-key] where type can be 'private' or 'public' and 'public-key' is required for 'private' type")
|
||||
addChat = fs.String("add-chat", "", "add chat using format: type,name[,public-key] where type can be 'private' or 'public' and 'public-key' is required for 'private' type")
|
||||
|
||||
// flags for in-proc node
|
||||
dataDir = fs.String("data-dir", filepath.Join(os.TempDir(), "status-term-client"), "data directory for Ethereum node")
|
||||
@ -147,60 +147,59 @@ func main() {
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Log the current contact info in two places for easy retrieval.
|
||||
fmt.Printf("Contact address: %#x\n", crypto.FromECDSAPub(&privateKey.PublicKey))
|
||||
log.Printf("contact address: %#x", crypto.FromECDSAPub(&privateKey.PublicKey))
|
||||
// Log the current chat info in two places for easy retrieval.
|
||||
fmt.Printf("Chat address: %#x\n", crypto.FromECDSAPub(&privateKey.PublicKey))
|
||||
log.Printf("chat address: %#x", crypto.FromECDSAPub(&privateKey.PublicKey))
|
||||
|
||||
// Manage initial contacts.
|
||||
if contacts, err := db.Contacts(); len(contacts) == 0 || err != nil {
|
||||
debugContacts := []client.Contact{
|
||||
{Name: "status", Type: client.ContactPublicRoom, Topic: "status"},
|
||||
{Name: "status-core", Type: client.ContactPublicRoom, Topic: "status-core"},
|
||||
// Manage initial chats.
|
||||
if chats, err := db.Chats(); len(chats) == 0 || err != nil {
|
||||
debugChats := []client.Chat{
|
||||
{Name: "status", Type: client.PublicChat},
|
||||
{Name: "status-core", Type: client.PublicChat},
|
||||
}
|
||||
uniqueContacts := []client.Contact{}
|
||||
for _, c := range debugContacts {
|
||||
exist, err := db.ContactExist(c)
|
||||
uniqueChats := []client.Chat{}
|
||||
for _, c := range debugChats {
|
||||
exist, err := db.ChatExist(c)
|
||||
if err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
if !exist {
|
||||
uniqueContacts = append(uniqueContacts, c)
|
||||
uniqueChats = append(uniqueChats, c)
|
||||
}
|
||||
}
|
||||
if len(uniqueContacts) != 0 {
|
||||
if err := db.SaveContacts(uniqueContacts); err != nil {
|
||||
if len(uniqueChats) != 0 {
|
||||
if err := db.SaveChats(uniqueChats); err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle add contact.
|
||||
if *addContact != "" {
|
||||
options := strings.Split(*addContact, ",")
|
||||
// Handle add chat.
|
||||
if *addChat != "" {
|
||||
options := strings.Split(*addChat, ",")
|
||||
|
||||
var c client.Contact
|
||||
var c client.Chat
|
||||
|
||||
if len(options) == 2 && options[0] == "public" {
|
||||
c = client.Contact{
|
||||
Name: options[1],
|
||||
Type: client.ContactPublicRoom,
|
||||
Topic: options[1],
|
||||
c = client.Chat{
|
||||
Name: options[1],
|
||||
Type: client.PublicChat,
|
||||
}
|
||||
} else if len(options) == 3 && options[0] == "private" {
|
||||
c, err = client.CreateContactPrivate(options[1], options[2], client.ContactAdded)
|
||||
c, err = client.CreateOneToOneChat(options[1], options[2])
|
||||
if err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
} else {
|
||||
exitErr(errors.Errorf("invalid -add-contact value"))
|
||||
exitErr(errors.Errorf("invalid -add-chat value"))
|
||||
}
|
||||
|
||||
exists, err := db.ContactExist(c)
|
||||
exists, err := db.ChatExist(c)
|
||||
if err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
if !exists {
|
||||
if err := db.SaveContacts([]client.Contact{c}); err != nil {
|
||||
if err := db.SaveChats([]client.Chat{c}); err != nil {
|
||||
exitErr(err)
|
||||
}
|
||||
}
|
||||
@ -399,13 +398,13 @@ func createMessengerInProc(pk *ecdsa.PrivateKey, db client.Database) (*client.Me
|
||||
}
|
||||
|
||||
// Init and start Publisher
|
||||
broadcastContactCode := true
|
||||
broadcastChatCode := true
|
||||
online := func() bool {
|
||||
return statusNode.Server().PeerCount() > 0
|
||||
}
|
||||
|
||||
publisherService.Init(persistence.DB, protocol, protocolAdapter.OnNewMessages)
|
||||
if err := publisherService.Start(online, broadcastContactCode); err != nil {
|
||||
if err := publisherService.Start(online, broadcastChatCode); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to start Publisher")
|
||||
}
|
||||
|
||||
@ -439,8 +438,8 @@ func setupGUI(privateKey *ecdsa.PrivateKey, messenger *client.Messenger) error {
|
||||
},
|
||||
)
|
||||
|
||||
contacts := NewContactsViewController(&ViewController{vm, g, ViewContacts}, messenger)
|
||||
if err := contacts.LoadAndRefresh(); err != nil {
|
||||
chats := NewChatsViewController(&ViewController{vm, g, ViewChats}, messenger)
|
||||
if err := chats.LoadAndRefresh(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -449,12 +448,12 @@ func setupGUI(privateKey *ecdsa.PrivateKey, messenger *client.Messenger) error {
|
||||
log.Printf("default multiplexer handler")
|
||||
return chat.Send(b)
|
||||
})
|
||||
inputMultiplexer.AddHandler("/contact", ContactCmdFactory(contacts))
|
||||
inputMultiplexer.AddHandler("/chat", ChatCmdFactory(chats))
|
||||
inputMultiplexer.AddHandler("/request", RequestCmdFactory(chat))
|
||||
|
||||
views := []*View{
|
||||
&View{
|
||||
Name: ViewContacts,
|
||||
Name: ViewChats,
|
||||
Enabled: true,
|
||||
Cursor: true,
|
||||
Highlight: true,
|
||||
@ -479,16 +478,16 @@ func setupGUI(privateKey *ecdsa.PrivateKey, messenger *client.Messenger) error {
|
||||
Key: gocui.KeyEnter,
|
||||
Mod: gocui.ModNone,
|
||||
Handler: GetLineHandler(func(idx int, _ string) error {
|
||||
contact, ok := contacts.ContactByIdx(idx)
|
||||
selectedChat, ok := chats.ChatByIdx(idx)
|
||||
if !ok {
|
||||
return errors.New("contact could not be found")
|
||||
return errors.New("chat could not be found")
|
||||
}
|
||||
|
||||
// We need to call Select asynchronously,
|
||||
// otherwise the main thread is blocked
|
||||
// and nothing is rendered.
|
||||
go func() {
|
||||
if err := chat.Select(contact); err != nil {
|
||||
if err := chat.Select(selectedChat); err != nil {
|
||||
log.Printf("[GetLineHandler] error selecting a chat: %v", err)
|
||||
}
|
||||
}()
|
||||
|
@ -158,13 +158,6 @@ func (w *ProtocolWhisperAdapter) Send(ctx context.Context, data []byte, options
|
||||
if options.Recipient != nil {
|
||||
newMessage, err = w.publisher.CreateDirectMessage(privateKey, options.Recipient, false, data)
|
||||
} else {
|
||||
_, filterErr := w.publisher.LoadFilter(&msgfilter.Chat{
|
||||
ChatID: options.ChatName,
|
||||
})
|
||||
if filterErr != nil {
|
||||
return nil, errors.Wrap(filterErr, "failed to load filter")
|
||||
}
|
||||
|
||||
// Public messages are not wrapped (i.e have not bundle),
|
||||
// when sending in public chats as it would be a breaking change.
|
||||
// When we send a contact code, we send a public message but wrapped,
|
||||
|
154
protocol/client/chat.go
Normal file
154
protocol/client/chat.go
Normal file
@ -0,0 +1,154 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=ChatType
|
||||
|
||||
// ChatType defines a type of a chat.
|
||||
type ChatType int
|
||||
|
||||
func (c ChatType) MarshalJSON() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf(`"%s"`, c)), nil
|
||||
}
|
||||
|
||||
func (c *ChatType) UnmarshalJSON(data []byte) error {
|
||||
switch string(data) {
|
||||
case fmt.Sprintf(`"%s"`, PublicChat):
|
||||
*c = PublicChat
|
||||
case fmt.Sprintf(`"%s"`, OneToOneChat):
|
||||
*c = OneToOneChat
|
||||
default:
|
||||
return fmt.Errorf("invalid ChatType: %s", data)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Types of chats.
|
||||
const (
|
||||
PublicChat ChatType = iota + 1
|
||||
OneToOneChat
|
||||
PrivateGroupChat
|
||||
)
|
||||
|
||||
// ChatMember is a member of a group chat
|
||||
type ChatMember struct {
|
||||
Admin bool
|
||||
Joined bool
|
||||
PublicKey *ecdsa.PublicKey `json:"-"`
|
||||
}
|
||||
|
||||
// Chat is a single chat
|
||||
type Chat struct {
|
||||
// The id of the chat
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type ChatType `json:"type"`
|
||||
// Creation time, is that been used?
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
// Soft delete flag
|
||||
Active bool `json:"active"`
|
||||
// The color of the chat, makes no sense outside a UI context
|
||||
Color string `json:"color"`
|
||||
// Clock value of the last message before chat has been deleted
|
||||
DeletedAtClockValue int64 `json:"deletedAtClockValue"`
|
||||
PublicKey *ecdsa.PublicKey `json:"-"`
|
||||
// Denormalized fields
|
||||
|
||||
UnviewedMessageCount int `json:"unviewedMessageCount"`
|
||||
LastClockValue int64 `json:"lastClockValue"`
|
||||
LastMessageContentType string `json:"lastMessageContentType"`
|
||||
LastMessageContent string `json:"lastMessageContent"`
|
||||
}
|
||||
|
||||
// CreateOneToOneChat creates a new private chat.
|
||||
func CreateOneToOneChat(name, pubKeyHex string) (c Chat, err error) {
|
||||
pubKeyBytes, err := hexutil.Decode(pubKeyHex)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.Name = name
|
||||
c.Type = OneToOneChat
|
||||
c.PublicKey, err = crypto.UnmarshalPubkey(pubKeyBytes)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CreatePublicChat creates a public room chat.
|
||||
func CreatePublicChat(name string) Chat {
|
||||
return Chat{
|
||||
Name: name,
|
||||
Type: PublicChat,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of Chat.
|
||||
func (c Chat) String() string {
|
||||
return c.Name
|
||||
}
|
||||
|
||||
// Equal returns true if chats have same name and same type.
|
||||
func (c Chat) Equal(other Chat) bool {
|
||||
return c.Name == other.Name && c.Type == other.Type
|
||||
}
|
||||
|
||||
func (c Chat) MarshalJSON() ([]byte, error) {
|
||||
type ChatAlias Chat
|
||||
|
||||
item := struct {
|
||||
ChatAlias
|
||||
PublicKey string `json:"public_key,omitempty"`
|
||||
}{
|
||||
ChatAlias: ChatAlias(c),
|
||||
}
|
||||
|
||||
if c.PublicKey != nil {
|
||||
item.PublicKey = EncodePublicKeyAsString(c.PublicKey)
|
||||
}
|
||||
|
||||
return json.Marshal(&item)
|
||||
}
|
||||
|
||||
func (c *Chat) UnmarshalJSON(data []byte) error {
|
||||
type ChatAlias Chat
|
||||
|
||||
var item struct {
|
||||
*ChatAlias
|
||||
PublicKey string `json:"public_key,omitempty"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(item.PublicKey) > 2 {
|
||||
pubKey, err := hexutil.Decode(item.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
item.ChatAlias.PublicKey, err = crypto.UnmarshalPubkey(pubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*c = *(*Chat)(item.ChatAlias)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodePublicKeyAsString encodes a public key as a string.
|
||||
// It starts with 0x to indicate it's hex encoding.
|
||||
func EncodePublicKeyAsString(pubKey *ecdsa.PublicKey) string {
|
||||
return hexutil.Encode(crypto.FromECDSAPub(pubKey))
|
||||
}
|
63
protocol/client/chat_test.go
Normal file
63
protocol/client/chat_test.go
Normal file
@ -0,0 +1,63 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestChatTypeMarshal(t *testing.T) {
|
||||
ct := PublicChat
|
||||
data, err := json.Marshal(ct)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, `"PublicChat"`, string(data))
|
||||
}
|
||||
|
||||
func TestChatMarshalUnmarshal(t *testing.T) {
|
||||
privateKey, _ := crypto.GenerateKey()
|
||||
publicKeyStr := fmt.Sprintf("%#x", crypto.FromECDSAPub(&privateKey.PublicKey))
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
c Chat
|
||||
result string
|
||||
}{
|
||||
{
|
||||
name: "PublicChat",
|
||||
c: Chat{
|
||||
ID: "status",
|
||||
Name: "status",
|
||||
Type: PublicChat,
|
||||
Timestamp: 20,
|
||||
UpdatedAt: 21,
|
||||
},
|
||||
result: `{"id":"status","name":"status","type":"PublicChat","timestamp":20,"updatedAt":21,"active":false,"color":"","deletedAtClockValue":0,"unviewedMessageCount":0,"lastClockValue":0,"lastMessageContentType":"","lastMessageContent":""}`,
|
||||
},
|
||||
{
|
||||
name: "ChatPublicKey",
|
||||
c: Chat{
|
||||
Name: "user1",
|
||||
Type: OneToOneChat,
|
||||
PublicKey: &privateKey.PublicKey,
|
||||
},
|
||||
result: fmt.Sprintf(`{"id":"","name":"user1","type":"OneToOneChat","timestamp":0,"updatedAt":0,"active":false,"color":"","deletedAtClockValue":0,"unviewedMessageCount":0,"lastClockValue":0,"lastMessageContentType":"","lastMessageContent":"","public_key":"%s"}`, publicKeyStr),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
data, err := json.Marshal(tc.c)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.result, string(data))
|
||||
|
||||
var c Chat
|
||||
|
||||
err = json.Unmarshal(data, &c)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.c, c)
|
||||
})
|
||||
}
|
||||
}
|
17
protocol/client/chattype_string.go
Normal file
17
protocol/client/chattype_string.go
Normal file
@ -0,0 +1,17 @@
|
||||
// Code generated by "stringer -type=ChatType"; DO NOT EDIT.
|
||||
|
||||
package client
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _ChatType_name = "PublicChatOneToOneChatPrivateGroupChat"
|
||||
|
||||
var _ChatType_index = [...]uint8{0, 10, 22, 38}
|
||||
|
||||
func (i ChatType) String() string {
|
||||
i -= 1
|
||||
if i < 0 || i >= ChatType(len(_ChatType_index)-1) {
|
||||
return "ChatType(" + strconv.FormatInt(int64(i+1), 10) + ")"
|
||||
}
|
||||
return _ChatType_name[_ChatType_index[i]:_ChatType_index[i+1]]
|
||||
}
|
@ -1,147 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
//go:generate stringer -type=ContactType
|
||||
|
||||
// ContactType defines a type of a contact.
|
||||
type ContactType int
|
||||
|
||||
func (c ContactType) MarshalJSON() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf(`"%s"`, c)), nil
|
||||
}
|
||||
|
||||
func (c *ContactType) UnmarshalJSON(data []byte) error {
|
||||
switch string(data) {
|
||||
case fmt.Sprintf(`"%s"`, ContactPublicRoom):
|
||||
*c = ContactPublicRoom
|
||||
case fmt.Sprintf(`"%s"`, ContactPrivate):
|
||||
*c = ContactPrivate
|
||||
default:
|
||||
return fmt.Errorf("invalid ContactType: %s", data)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Types of contacts.
|
||||
const (
|
||||
ContactPublicRoom ContactType = iota + 1
|
||||
ContactPrivate
|
||||
)
|
||||
|
||||
// ContactState defines state of the contact.
|
||||
type ContactState int
|
||||
|
||||
const (
|
||||
// ContactAdded default level. Added or confirmed by user.
|
||||
ContactAdded ContactState = iota + 1
|
||||
// ContactNew contact got connected to us and waits for being added or blocked.
|
||||
ContactNew
|
||||
// ContactBlocked means that all incoming messages from it will be discarded.
|
||||
ContactBlocked
|
||||
)
|
||||
|
||||
// Contact is a single contact which has a type and name.
|
||||
type Contact struct {
|
||||
Name string `json:"name"`
|
||||
Type ContactType `json:"type"`
|
||||
State ContactState `json:"state"`
|
||||
Topic string `json:"topic"`
|
||||
PublicKey *ecdsa.PublicKey `json:"-"`
|
||||
}
|
||||
|
||||
// CreateContactPrivate creates a new private contact.
|
||||
func CreateContactPrivate(name, pubKeyHex string, state ContactState) (c Contact, err error) {
|
||||
pubKeyBytes, err := hexutil.Decode(pubKeyHex)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.Name = name
|
||||
c.Type = ContactPrivate
|
||||
c.State = state
|
||||
c.Topic = DefaultPrivateTopic()
|
||||
c.PublicKey, err = crypto.UnmarshalPubkey(pubKeyBytes)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CreateContactPublicRoom creates a public room contact.
|
||||
func CreateContactPublicRoom(name string, state ContactState) Contact {
|
||||
return Contact{
|
||||
Name: name,
|
||||
Type: ContactPublicRoom,
|
||||
State: state,
|
||||
Topic: name,
|
||||
}
|
||||
}
|
||||
|
||||
// String returns a string representation of Contact.
|
||||
func (c Contact) String() string {
|
||||
return c.Name
|
||||
}
|
||||
|
||||
// Equal returns true if contacts have same name and same type.
|
||||
func (c Contact) Equal(other Contact) bool {
|
||||
return c.Name == other.Name && c.Type == other.Type
|
||||
}
|
||||
|
||||
func (c Contact) MarshalJSON() ([]byte, error) {
|
||||
type ContactAlias Contact
|
||||
|
||||
item := struct {
|
||||
ContactAlias
|
||||
PublicKey string `json:"public_key,omitempty"`
|
||||
}{
|
||||
ContactAlias: ContactAlias(c),
|
||||
}
|
||||
|
||||
if c.PublicKey != nil {
|
||||
item.PublicKey = EncodePublicKeyAsString(c.PublicKey)
|
||||
}
|
||||
|
||||
return json.Marshal(&item)
|
||||
}
|
||||
|
||||
func (c *Contact) UnmarshalJSON(data []byte) error {
|
||||
type ContactAlias Contact
|
||||
|
||||
var item struct {
|
||||
*ContactAlias
|
||||
PublicKey string `json:"public_key,omitempty"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &item); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(item.PublicKey) > 2 {
|
||||
pubKey, err := hexutil.Decode(item.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
item.ContactAlias.PublicKey, err = crypto.UnmarshalPubkey(pubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
*c = *(*Contact)(item.ContactAlias)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodePublicKeyAsString encodes a public key as a string.
|
||||
// It starts with 0x to indicate it's hex encoding.
|
||||
func EncodePublicKeyAsString(pubKey *ecdsa.PublicKey) string {
|
||||
return hexutil.Encode(crypto.FromECDSAPub(pubKey))
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestContactTypeMarshal(t *testing.T) {
|
||||
ct := ContactPublicRoom
|
||||
data, err := json.Marshal(ct)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, `"ContactPublicRoom"`, string(data))
|
||||
}
|
||||
|
||||
func TestContactMarshalUnmarshal(t *testing.T) {
|
||||
privateKey, _ := crypto.GenerateKey()
|
||||
publicKeyStr := fmt.Sprintf("%#x", crypto.FromECDSAPub(&privateKey.PublicKey))
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
c Contact
|
||||
result string
|
||||
}{
|
||||
{
|
||||
name: "ContactPublicRoom",
|
||||
c: Contact{
|
||||
Name: "status",
|
||||
Type: ContactPublicRoom,
|
||||
},
|
||||
result: `{"name":"status","type":"ContactPublicRoom","state":0,"topic":""}`,
|
||||
},
|
||||
{
|
||||
name: "ContactPublicKey",
|
||||
c: Contact{
|
||||
Name: "user1",
|
||||
Type: ContactPrivate,
|
||||
PublicKey: &privateKey.PublicKey,
|
||||
},
|
||||
result: fmt.Sprintf(`{"name":"user1","type":"ContactPrivate","state":0,"topic":"","public_key":"%s"}`, publicKeyStr),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
data, err := json.Marshal(tc.c)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.result, string(data))
|
||||
|
||||
var c Contact
|
||||
|
||||
err = json.Unmarshal(data, &c)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.c, c)
|
||||
})
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
// Code generated by "stringer -type=ContactType"; DO NOT EDIT.
|
||||
|
||||
package client
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[ContactPublicRoom-1]
|
||||
_ = x[ContactPrivate-2]
|
||||
}
|
||||
|
||||
const _ContactType_name = "ContactPublicRoomContactPrivate"
|
||||
|
||||
var _ContactType_index = [...]uint8{0, 17, 31}
|
||||
|
||||
func (i ContactType) String() string {
|
||||
i -= 1
|
||||
if i < 0 || i >= ContactType(len(_ContactType_index)-1) {
|
||||
return "ContactType(" + strconv.FormatInt(int64(i+1), 10) + ")"
|
||||
}
|
||||
return _ContactType_name[_ContactType_index[i]:_ContactType_index[i+1]]
|
||||
}
|
@ -65,19 +65,17 @@ var (
|
||||
// Database is an interface for all db operations.
|
||||
type Database interface {
|
||||
Close() error
|
||||
Messages(c Contact, from, to time.Time) ([]*protocol.Message, error)
|
||||
NewMessages(c Contact, rowid int64) ([]*protocol.Message, error)
|
||||
UnreadMessages(c Contact) ([]*protocol.Message, error)
|
||||
SaveMessages(c Contact, messages []*protocol.Message) (int64, error)
|
||||
LastMessageClock(Contact) (int64, error)
|
||||
Contacts() ([]Contact, error)
|
||||
SaveContacts(contacts []Contact) error
|
||||
DeleteContact(Contact) error
|
||||
ContactExist(Contact) (bool, error)
|
||||
Histories() ([]History, error)
|
||||
UpdateHistories([]History) error
|
||||
GetPublicChat(name string) (*Contact, error)
|
||||
GetOneToOneChat(*ecdsa.PublicKey) (*Contact, error)
|
||||
Messages(c Chat, from, to time.Time) ([]*protocol.Message, error)
|
||||
NewMessages(c Chat, rowid int64) ([]*protocol.Message, error)
|
||||
UnreadMessages(c Chat) ([]*protocol.Message, error)
|
||||
SaveMessages(c Chat, messages []*protocol.Message) (int64, error)
|
||||
LastMessageClock(Chat) (int64, error)
|
||||
Chats() ([]Chat, error)
|
||||
SaveChats(chats []Chat) error
|
||||
DeleteChat(Chat) error
|
||||
ChatExist(Chat) (bool, error)
|
||||
GetPublicChat(name string) (*Chat, error)
|
||||
GetOneToOneChat(*ecdsa.PublicKey) (*Chat, error)
|
||||
}
|
||||
|
||||
// Migrate applies migrations.
|
||||
@ -173,9 +171,9 @@ func (db SQLLiteDatabase) Close() error {
|
||||
return db.db.Close()
|
||||
}
|
||||
|
||||
// SaveContacts inserts or replaces provided contacts.
|
||||
// TODO should it delete all previous contacts?
|
||||
func (db SQLLiteDatabase) SaveContacts(contacts []Contact) (err error) {
|
||||
// SaveChats inserts or replaces provided chats.
|
||||
// TODO should it delete all previous chats?
|
||||
func (db SQLLiteDatabase) SaveChats(chats []Chat) (err error) {
|
||||
var (
|
||||
tx *sql.Tx
|
||||
stmt *sql.Stmt
|
||||
@ -184,36 +182,57 @@ func (db SQLLiteDatabase) SaveContacts(contacts []Contact) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stmt, err = tx.Prepare("INSERT INTO user_contacts(id, name, type, state, topic, public_key) VALUES (?, ?, ?, ?, ?, ?)")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
history, err := tx.Prepare("INSERT INTO history_user_contact_topic(contact_id) VALUES (?)")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = tx.Commit()
|
||||
return
|
||||
} else {
|
||||
// don't shadow original error
|
||||
_ = tx.Rollback()
|
||||
return
|
||||
}
|
||||
}()
|
||||
for i := range contacts {
|
||||
stmt, err = tx.Prepare(`INSERT INTO chats(
|
||||
id,
|
||||
name,
|
||||
color,
|
||||
type,
|
||||
active,
|
||||
updated_at,
|
||||
deleted_at_clock_value,
|
||||
public_key,
|
||||
unviewed_message_count,
|
||||
last_clock_value,
|
||||
last_message_content_type,
|
||||
last_message_content
|
||||
)
|
||||
VALUES
|
||||
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range chats {
|
||||
pkey := []byte{}
|
||||
if contacts[i].PublicKey != nil {
|
||||
pkey, err = marshalEcdsaPub(contacts[i].PublicKey)
|
||||
if chats[i].PublicKey != nil {
|
||||
pkey, err = marshalEcdsaPub(chats[i].PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
id := contactID(contacts[i])
|
||||
_, err = stmt.Exec(id, contacts[i].Name, contacts[i].Type, contacts[i].State, contacts[i].Topic, pkey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// to avoid unmarshalling null into sql.NullInt64
|
||||
_, err = history.Exec(id)
|
||||
id := chatID(chats[i])
|
||||
_, err = stmt.Exec(id,
|
||||
chats[i].Name,
|
||||
chats[i].Color,
|
||||
chats[i].Type,
|
||||
chats[i].Active,
|
||||
chats[i].UpdatedAt,
|
||||
chats[i].DeletedAtClockValue,
|
||||
pkey,
|
||||
chats[i].UnviewedMessageCount,
|
||||
chats[i].LastClockValue,
|
||||
chats[i].LastMessageContentType,
|
||||
chats[i].LastMessageContent,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -221,105 +240,50 @@ func (db SQLLiteDatabase) SaveContacts(contacts []Contact) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Contacts returns all available contacts.
|
||||
func (db SQLLiteDatabase) Contacts() ([]Contact, error) {
|
||||
rows, err := db.db.Query("SELECT name, type, state, topic, public_key FROM user_contacts")
|
||||
// Chats returns all available chats.
|
||||
func (db SQLLiteDatabase) Chats() ([]Chat, error) {
|
||||
rows, err := db.db.Query("SELECT name, type, public_key FROM chats")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
rst = []Contact{}
|
||||
rst = []Chat{}
|
||||
)
|
||||
for rows.Next() {
|
||||
// do not reuse same gob instance. same instance marshalls two same objects differently
|
||||
// if used repetitively.
|
||||
contact := Contact{}
|
||||
chat := Chat{}
|
||||
pkey := []byte{}
|
||||
err = rows.Scan(&contact.Name, &contact.Type, &contact.State, &contact.Topic, &pkey)
|
||||
err = rows.Scan(&chat.Name, &chat.Type, &pkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(pkey) != 0 {
|
||||
contact.PublicKey, err = unmarshalEcdsaPub(pkey)
|
||||
chat.PublicKey, err = unmarshalEcdsaPub(pkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
rst = append(rst, contact)
|
||||
rst = append(rst, chat)
|
||||
}
|
||||
return rst, nil
|
||||
}
|
||||
|
||||
func (db SQLLiteDatabase) Histories() ([]History, error) {
|
||||
rows, err := db.db.Query("SELECT synced, u.name, u.type, u.state, u.topic, u.public_key FROM history_user_contact_topic JOIN user_contacts u ON contact_id = u.id")
|
||||
func (db SQLLiteDatabase) DeleteChat(c Chat) error {
|
||||
_, err := db.db.Exec("DELETE FROM chats WHERE id = ?", fmt.Sprintf("%s:%d", c.Name, c.Type))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rst := []History{}
|
||||
for rows.Next() {
|
||||
h := History{
|
||||
Contact: Contact{},
|
||||
}
|
||||
pkey := []byte{}
|
||||
err = rows.Scan(&h.Synced, &h.Contact.Name, &h.Contact.Type, &h.Contact.State, &h.Contact.Topic, &pkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(pkey) != 0 {
|
||||
h.Contact.PublicKey, err = unmarshalEcdsaPub(pkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
rst = append(rst, h)
|
||||
}
|
||||
return rst, nil
|
||||
}
|
||||
|
||||
func (db SQLLiteDatabase) UpdateHistories(histories []History) (err error) {
|
||||
var (
|
||||
tx *sql.Tx
|
||||
stmt *sql.Stmt
|
||||
)
|
||||
tx, err = db.db.BeginTx(context.Background(), &sql.TxOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stmt, err = tx.Prepare("UPDATE history_user_contact_topic SET synced = ? WHERE contact_Id = ?")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = tx.Commit()
|
||||
} else {
|
||||
_ = tx.Rollback()
|
||||
}
|
||||
}()
|
||||
for i := range histories {
|
||||
_, err = stmt.Exec(histories[i].Synced, contactID(histories[i].Contact))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return errors.Wrap(err, "error deleting chat from db")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db SQLLiteDatabase) DeleteContact(c Contact) error {
|
||||
_, err := db.db.Exec("DELETE FROM user_contacts WHERE id = ?", fmt.Sprintf("%s:%d", c.Name, c.Type))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error deleting contact from db")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db SQLLiteDatabase) ContactExist(c Contact) (exists bool, err error) {
|
||||
err = db.db.QueryRow("SELECT EXISTS(SELECT id FROM user_contacts WHERE id = ?)", contactID(c)).Scan(&exists)
|
||||
func (db SQLLiteDatabase) ChatExist(c Chat) (exists bool, err error) {
|
||||
err = db.db.QueryRow("SELECT EXISTS(SELECT id FROM chats WHERE id = ?)", chatID(c)).Scan(&exists)
|
||||
return
|
||||
}
|
||||
|
||||
func (db SQLLiteDatabase) GetOneToOneChat(publicKey *ecdsa.PublicKey) (*Contact, error) {
|
||||
func (db SQLLiteDatabase) GetOneToOneChat(publicKey *ecdsa.PublicKey) (*Chat, error) {
|
||||
if publicKey == nil {
|
||||
return nil, errors.New("No public key provided")
|
||||
}
|
||||
@ -329,9 +293,9 @@ func (db SQLLiteDatabase) GetOneToOneChat(publicKey *ecdsa.PublicKey) (*Contact,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := &Contact{}
|
||||
err = db.db.QueryRow("SELECT name, state, topic FROM user_contacts WHERE public_key = ?", pkey).Scan(&c.Name, &c.State, &c.Topic)
|
||||
c.Type = ContactPrivate
|
||||
c := &Chat{}
|
||||
err = db.db.QueryRow("SELECT name FROM chats WHERE public_key = ?", pkey).Scan(&c.Name)
|
||||
c.Type = OneToOneChat
|
||||
c.PublicKey = publicKey
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
@ -342,10 +306,10 @@ func (db SQLLiteDatabase) GetOneToOneChat(publicKey *ecdsa.PublicKey) (*Contact,
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (db SQLLiteDatabase) GetPublicChat(name string) (*Contact, error) {
|
||||
c := &Contact{}
|
||||
err := db.db.QueryRow("SELECT name, state, topic FROM user_contacts WHERE id = ?", formatID(name, ContactPublicRoom)).Scan(&c.Name, &c.State, &c.Topic)
|
||||
c.Type = ContactPublicRoom
|
||||
func (db SQLLiteDatabase) GetPublicChat(name string) (*Chat, error) {
|
||||
c := &Chat{}
|
||||
err := db.db.QueryRow("SELECT name FROM chats WHERE id = ?", formatID(name, PublicChat)).Scan(&c.Name)
|
||||
c.Type = PublicChat
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
@ -355,16 +319,16 @@ func (db SQLLiteDatabase) GetPublicChat(name string) (*Contact, error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (db SQLLiteDatabase) LastMessageClock(c Contact) (int64, error) {
|
||||
func (db SQLLiteDatabase) LastMessageClock(c Chat) (int64, error) {
|
||||
var last sql.NullInt64
|
||||
err := db.db.QueryRow("SELECT max(clock) FROM user_messages WHERE contact_id = ?", contactID(c)).Scan(&last)
|
||||
err := db.db.QueryRow("SELECT max(clock) FROM user_messages WHERE chat_id = ?", chatID(c)).Scan(&last)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return last.Int64, nil
|
||||
}
|
||||
|
||||
func (db SQLLiteDatabase) SaveMessages(c Contact, messages []*protocol.Message) (last int64, err error) {
|
||||
func (db SQLLiteDatabase) SaveMessages(c Chat, messages []*protocol.Message) (last int64, err error) {
|
||||
var (
|
||||
tx *sql.Tx
|
||||
stmt *sql.Stmt
|
||||
@ -374,7 +338,7 @@ func (db SQLLiteDatabase) SaveMessages(c Contact, messages []*protocol.Message)
|
||||
return
|
||||
}
|
||||
stmt, err = tx.Prepare(`INSERT INTO user_messages(
|
||||
id, contact_id, content_type, message_type, text, clock, timestamp, content_chat_id, content_text, public_key, flags)
|
||||
id, chat_id, content_type, message_type, text, clock, timestamp, content_chat_id, content_text, public_key, flags)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
||||
if err != nil {
|
||||
return
|
||||
@ -391,8 +355,8 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
||||
}()
|
||||
|
||||
var (
|
||||
contactID = fmt.Sprintf("%s:%d", c.Name, c.Type)
|
||||
rst sql.Result
|
||||
chatID = fmt.Sprintf("%s:%d", c.Name, c.Type)
|
||||
rst sql.Result
|
||||
)
|
||||
for _, msg := range messages {
|
||||
pkey := []byte{}
|
||||
@ -400,7 +364,7 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
||||
pkey, err = marshalEcdsaPub(msg.SigPubKey)
|
||||
}
|
||||
rst, err = stmt.Exec(
|
||||
msg.ID, contactID, msg.ContentT, msg.MessageT, msg.Text,
|
||||
msg.ID, chatID, msg.ContentT, msg.MessageT, msg.Text,
|
||||
msg.Clock, msg.Timestamp, msg.Content.ChatID, msg.Content.Text,
|
||||
pkey, msg.Flags)
|
||||
if err != nil {
|
||||
@ -417,13 +381,13 @@ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
||||
return
|
||||
}
|
||||
|
||||
// Messages returns messages for a given contact, in a given period. Ordered by a timestamp.
|
||||
func (db SQLLiteDatabase) Messages(c Contact, from, to time.Time) (result []*protocol.Message, err error) {
|
||||
contactID := fmt.Sprintf("%s:%d", c.Name, c.Type)
|
||||
// Messages returns messages for a given chat, in a given period. Ordered by a timestamp.
|
||||
func (db SQLLiteDatabase) Messages(c Chat, from, to time.Time) (result []*protocol.Message, err error) {
|
||||
chatID := fmt.Sprintf("%s:%d", c.Name, c.Type)
|
||||
rows, err := db.db.Query(`SELECT
|
||||
id, content_type, message_type, text, clock, timestamp, content_chat_id, content_text, public_key, flags
|
||||
FROM user_messages WHERE contact_id = ? AND timestamp >= ? AND timestamp <= ? ORDER BY timestamp`,
|
||||
contactID, protocol.TimestampInMsFromTime(from), protocol.TimestampInMsFromTime(to))
|
||||
FROM user_messages WHERE chat_id = ? AND timestamp >= ? AND timestamp <= ? ORDER BY timestamp`,
|
||||
chatID, protocol.TimestampInMsFromTime(from), protocol.TimestampInMsFromTime(to))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -452,12 +416,12 @@ FROM user_messages WHERE contact_id = ? AND timestamp >= ? AND timestamp <= ? OR
|
||||
return rst, nil
|
||||
}
|
||||
|
||||
func (db SQLLiteDatabase) NewMessages(c Contact, rowid int64) ([]*protocol.Message, error) {
|
||||
contactID := contactID(c)
|
||||
func (db SQLLiteDatabase) NewMessages(c Chat, rowid int64) ([]*protocol.Message, error) {
|
||||
chatID := chatID(c)
|
||||
rows, err := db.db.Query(`SELECT
|
||||
id, content_type, message_type, text, clock, timestamp, content_chat_id, content_text, public_key, flags
|
||||
FROM user_messages WHERE contact_id = ? AND rowid >= ? ORDER BY clock`,
|
||||
contactID, rowid)
|
||||
FROM user_messages WHERE chat_id = ? AND rowid >= ? ORDER BY clock`,
|
||||
chatID, rowid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -488,8 +452,8 @@ FROM user_messages WHERE contact_id = ? AND rowid >= ? ORDER BY clock`,
|
||||
|
||||
// TODO(adam): refactor all message getters in order not to
|
||||
// repeat the select fields over and over.
|
||||
func (db SQLLiteDatabase) UnreadMessages(c Contact) ([]*protocol.Message, error) {
|
||||
contactID := contactID(c)
|
||||
func (db SQLLiteDatabase) UnreadMessages(c Chat) ([]*protocol.Message, error) {
|
||||
chatID := chatID(c)
|
||||
rows, err := db.db.Query(`
|
||||
SELECT
|
||||
id,
|
||||
@ -505,10 +469,10 @@ func (db SQLLiteDatabase) UnreadMessages(c Contact) ([]*protocol.Message, error)
|
||||
FROM
|
||||
user_messages
|
||||
WHERE
|
||||
contact_id = ? AND
|
||||
chat_id = ? AND
|
||||
flags & ? == 0
|
||||
ORDER BY clock`,
|
||||
contactID, protocol.MessageRead,
|
||||
chatID, protocol.MessageRead,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -539,10 +503,10 @@ func (db SQLLiteDatabase) UnreadMessages(c Contact) ([]*protocol.Message, error)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func contactID(c Contact) string {
|
||||
func chatID(c Chat) string {
|
||||
return formatID(c.Name, c.Type)
|
||||
}
|
||||
|
||||
func formatID(name string, t ContactType) string {
|
||||
func formatID(name string, t ChatType) string {
|
||||
return fmt.Sprintf("%s:%d", name, t)
|
||||
}
|
||||
|
@ -10,27 +10,26 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestContactUniqueConstraint(t *testing.T) {
|
||||
func TestChatUniqueConstraint(t *testing.T) {
|
||||
db, err := InitializeTmpDB()
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
pk, err := crypto.GenerateKey()
|
||||
require.NoError(t, err)
|
||||
contact := Contact{
|
||||
chat := Chat{
|
||||
Name: "first",
|
||||
Type: ContactPublicRoom,
|
||||
Type: PublicChat,
|
||||
PublicKey: &pk.PublicKey,
|
||||
Topic: "first",
|
||||
}
|
||||
require.NoError(t, db.SaveContacts([]Contact{contact}))
|
||||
require.EqualError(t, db.SaveContacts([]Contact{contact}), "UNIQUE constraint failed: user_contacts.id")
|
||||
rst, err := db.Contacts()
|
||||
require.NoError(t, db.SaveChats([]Chat{chat}))
|
||||
require.EqualError(t, db.SaveChats([]Chat{chat}), "UNIQUE constraint failed: chats.id")
|
||||
rst, err := db.Chats()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rst, 1)
|
||||
require.Equal(t, contact.Name, rst[0].Name)
|
||||
require.Equal(t, contact.Type, rst[0].Type)
|
||||
require.Equal(t, contact.PublicKey.X, rst[0].PublicKey.X)
|
||||
require.Equal(t, contact.PublicKey.Y, rst[0].PublicKey.Y)
|
||||
require.Equal(t, chat.Name, rst[0].Name)
|
||||
require.Equal(t, chat.Type, rst[0].Type)
|
||||
require.Equal(t, chat.PublicKey.X, rst[0].PublicKey.X)
|
||||
require.Equal(t, chat.PublicKey.Y, rst[0].PublicKey.Y)
|
||||
}
|
||||
|
||||
func TestMessagesFilteredAndOrderedByTimestamp(t *testing.T) {
|
||||
@ -39,16 +38,15 @@ func TestMessagesFilteredAndOrderedByTimestamp(t *testing.T) {
|
||||
defer db.Close()
|
||||
pk, err := crypto.GenerateKey()
|
||||
require.NoError(t, err)
|
||||
contact := Contact{
|
||||
chat := Chat{
|
||||
Name: "test",
|
||||
Type: ContactPublicRoom,
|
||||
Type: PublicChat,
|
||||
PublicKey: &pk.PublicKey,
|
||||
Topic: "first",
|
||||
}
|
||||
require.NoError(t, db.SaveContacts([]Contact{contact}))
|
||||
contacts, err := db.Contacts()
|
||||
require.NoError(t, db.SaveChats([]Chat{chat}))
|
||||
chats, err := db.Chats()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, contacts, 1)
|
||||
require.Len(t, chats, 1)
|
||||
msg1 := protocol.Message{
|
||||
ID: []byte("hello1"),
|
||||
SigPubKey: &pk.PublicKey,
|
||||
@ -65,10 +63,10 @@ func TestMessagesFilteredAndOrderedByTimestamp(t *testing.T) {
|
||||
Timestamp: 2000,
|
||||
}
|
||||
|
||||
last, err := db.SaveMessages(contact, []*protocol.Message{&msg3, &msg1, &msg2})
|
||||
last, err := db.SaveMessages(chat, []*protocol.Message{&msg3, &msg1, &msg2})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(3), last)
|
||||
msgs, err := db.Messages(contact, time.Unix(3, 0), time.Unix(11, 0))
|
||||
msgs, err := db.Messages(chat, time.Unix(3, 0), time.Unix(11, 0))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, msgs, 2)
|
||||
require.Equal(t, msg2.Timestamp, msgs[0].Timestamp)
|
||||
@ -79,10 +77,9 @@ func TestUnreadMessages(t *testing.T) {
|
||||
db, err := InitializeTmpDB()
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
contact := Contact{
|
||||
Name: "test",
|
||||
Type: ContactPublicRoom,
|
||||
Topic: "first",
|
||||
chat := Chat{
|
||||
Name: "test",
|
||||
Type: PublicChat,
|
||||
}
|
||||
// insert some messages
|
||||
var messages []*protocol.Message
|
||||
@ -100,11 +97,11 @@ func TestUnreadMessages(t *testing.T) {
|
||||
}
|
||||
messages = append(messages, &m)
|
||||
}
|
||||
_, err = db.SaveMessages(contact, messages)
|
||||
_, err = db.SaveMessages(chat, messages)
|
||||
require.NoError(t, err)
|
||||
|
||||
// verify that we get only unread messages
|
||||
unread, err := db.UnreadMessages(contact)
|
||||
unread, err := db.UnreadMessages(chat)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, unread, 2)
|
||||
for _, m := range unread {
|
||||
@ -113,10 +110,9 @@ func TestUnreadMessages(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSaveMessagesUniqueConstraint(t *testing.T) {
|
||||
contact := Contact{
|
||||
Name: "test",
|
||||
Type: ContactPublicRoom,
|
||||
Topic: "first",
|
||||
chat := Chat{
|
||||
Name: "test",
|
||||
Type: PublicChat,
|
||||
}
|
||||
sameid := []byte("1")
|
||||
msg1 := protocol.Message{
|
||||
@ -129,7 +125,7 @@ func TestSaveMessagesUniqueConstraint(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
_, err = db.SaveMessages(contact, []*protocol.Message{&msg1, &msg2})
|
||||
_, err = db.SaveMessages(chat, []*protocol.Message{&msg1, &msg2})
|
||||
require.EqualError(t, err, ErrMsgAlreadyExist.Error())
|
||||
}
|
||||
|
||||
@ -146,14 +142,13 @@ func TestGetLastMessageClock(t *testing.T) {
|
||||
Clock: int64(count - i),
|
||||
}
|
||||
}
|
||||
contact := Contact{
|
||||
Name: "test",
|
||||
Type: ContactPublicRoom,
|
||||
Topic: "first",
|
||||
chat := Chat{
|
||||
Name: "test",
|
||||
Type: PublicChat,
|
||||
}
|
||||
_, err = db.SaveMessages(contact, messages)
|
||||
_, err = db.SaveMessages(chat, messages)
|
||||
require.NoError(t, err)
|
||||
last, err := db.LastMessageClock(contact)
|
||||
last, err := db.LastMessageClock(chat)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(count), last)
|
||||
}
|
||||
@ -164,54 +159,15 @@ func TestGetOneToOneChat(t *testing.T) {
|
||||
defer db.Close()
|
||||
pk, err := crypto.GenerateKey()
|
||||
require.NoError(t, err)
|
||||
expectedContact := Contact{
|
||||
expectedChat := Chat{
|
||||
Name: "first",
|
||||
Type: ContactPrivate,
|
||||
Type: OneToOneChat,
|
||||
PublicKey: &pk.PublicKey,
|
||||
Topic: "first",
|
||||
}
|
||||
require.NoError(t, db.SaveContacts([]Contact{expectedContact}))
|
||||
contact, err := db.GetOneToOneChat(&pk.PublicKey)
|
||||
require.NoError(t, db.SaveChats([]Chat{expectedChat}))
|
||||
chat, err := db.GetOneToOneChat(&pk.PublicKey)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, &expectedContact, contact, "contact expected to exist in database")
|
||||
}
|
||||
|
||||
func TestLoadHistories(t *testing.T) {
|
||||
db, err := InitializeTmpDB()
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
c1 := Contact{
|
||||
Name: "first",
|
||||
Type: ContactPublicRoom,
|
||||
}
|
||||
c2 := Contact{
|
||||
Name: "second",
|
||||
Type: ContactPublicRoom,
|
||||
}
|
||||
require.NoError(t, db.SaveContacts([]Contact{c1, c2}))
|
||||
histories, err := db.Histories()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, histories, 2)
|
||||
}
|
||||
|
||||
func TestUpdateHistories(t *testing.T) {
|
||||
db, err := InitializeTmpDB()
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
c1 := Contact{
|
||||
Name: "first",
|
||||
Type: ContactPublicRoom,
|
||||
}
|
||||
require.NoError(t, db.SaveContacts([]Contact{c1}))
|
||||
h := History{
|
||||
Synced: 100,
|
||||
Contact: c1,
|
||||
}
|
||||
require.NoError(t, db.UpdateHistories([]History{h}))
|
||||
histories, err := db.Histories()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, histories, 1)
|
||||
require.Equal(t, h.Synced, histories[0].Synced)
|
||||
require.Equal(t, &expectedChat, chat, "chat expected to exist in database")
|
||||
}
|
||||
|
||||
func BenchmarkLoadMessages(b *testing.B) {
|
||||
@ -220,29 +176,26 @@ func BenchmarkLoadMessages(b *testing.B) {
|
||||
defer db.Close()
|
||||
pk, err := crypto.GenerateKey()
|
||||
require.NoError(b, err)
|
||||
contacts := []Contact{
|
||||
chats := []Chat{
|
||||
{
|
||||
Name: "first",
|
||||
Type: ContactPrivate,
|
||||
Type: OneToOneChat,
|
||||
PublicKey: &pk.PublicKey,
|
||||
Topic: "test",
|
||||
},
|
||||
{
|
||||
Name: "second",
|
||||
Type: ContactPrivate,
|
||||
Type: OneToOneChat,
|
||||
PublicKey: &pk.PublicKey,
|
||||
Topic: "test",
|
||||
},
|
||||
{
|
||||
Name: "third",
|
||||
Type: ContactPrivate,
|
||||
Type: OneToOneChat,
|
||||
PublicKey: &pk.PublicKey,
|
||||
Topic: "test",
|
||||
},
|
||||
}
|
||||
count := 10000
|
||||
require.NoError(b, db.SaveContacts(contacts))
|
||||
for j, c := range contacts {
|
||||
require.NoError(b, db.SaveChats(chats))
|
||||
for j, c := range chats {
|
||||
messages := make([]*protocol.Message, count)
|
||||
for i := range messages {
|
||||
id := [8]byte{}
|
||||
@ -259,7 +212,7 @@ func BenchmarkLoadMessages(b *testing.B) {
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
rst, err := db.NewMessages(contacts[0], 0)
|
||||
rst, err := db.NewMessages(chats[0], 0)
|
||||
require.NoError(b, err)
|
||||
require.Len(b, rst, count)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package client
|
||||
|
||||
// Client is a higher level protocol interface.
|
||||
// It allows to interact with the messages and contacts
|
||||
// It allows to interact with the messages and chats
|
||||
// in a very abstract way where a lot of low-level operations
|
||||
// are hidden.
|
||||
|
@ -26,8 +26,8 @@ type Event struct {
|
||||
Interface interface{}
|
||||
}
|
||||
|
||||
type EventWithContact interface {
|
||||
GetContact() Contact
|
||||
type EventWithChat interface {
|
||||
GetChat() Chat
|
||||
}
|
||||
|
||||
type EventWithType interface {
|
||||
@ -43,12 +43,12 @@ type EventWithMessage interface {
|
||||
}
|
||||
|
||||
type baseEvent struct {
|
||||
Contact Contact `json:"contact"`
|
||||
Type EventType `json:"type"`
|
||||
Chat Chat `json:"chat"`
|
||||
Type EventType `json:"type"`
|
||||
}
|
||||
|
||||
func (e baseEvent) GetContact() Contact { return e.Contact }
|
||||
func (e baseEvent) GetType() EventType { return e.Type }
|
||||
func (e baseEvent) GetChat() Chat { return e.Chat }
|
||||
func (e baseEvent) GetType() EventType { return e.Type }
|
||||
|
||||
type messageEvent struct {
|
||||
baseEvent
|
||||
|
@ -14,7 +14,7 @@ func NewDatabaseWithEvents(db Database, feed *event.Feed) DatabaseWithEvents {
|
||||
return DatabaseWithEvents{Database: db, feed: feed}
|
||||
}
|
||||
|
||||
func (db DatabaseWithEvents) SaveMessages(c Contact, msgs []*protocol.Message) (int64, error) {
|
||||
func (db DatabaseWithEvents) SaveMessages(c Chat, msgs []*protocol.Message) (int64, error) {
|
||||
rowid, err := db.Database.SaveMessages(c, msgs)
|
||||
if err != nil {
|
||||
return rowid, err
|
||||
@ -22,8 +22,8 @@ func (db DatabaseWithEvents) SaveMessages(c Contact, msgs []*protocol.Message) (
|
||||
for _, m := range msgs {
|
||||
ev := messageEvent{
|
||||
baseEvent: baseEvent{
|
||||
Contact: c,
|
||||
Type: EventTypeMessage,
|
||||
Chat: c,
|
||||
Type: EventTypeMessage,
|
||||
},
|
||||
Message: m,
|
||||
}
|
||||
|
@ -1,53 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/status-im/status-console-client/protocol/v1"
|
||||
)
|
||||
|
||||
// History is used to track when contact was synced last time.
|
||||
// Contact extension. Deleted on cascade when parent contact is deleted.
|
||||
type History struct {
|
||||
// Synced is a timestamp in seconds.
|
||||
Synced int64
|
||||
Contact Contact
|
||||
}
|
||||
|
||||
func splitIntoSyncedNotSynced(histories []History) (sync []History, nosync []History) {
|
||||
for i := range histories {
|
||||
if histories[i].Synced != 0 {
|
||||
sync = append(sync, histories[i])
|
||||
} else {
|
||||
nosync = append(nosync, histories[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func syncedToOpts(histories []History, now time.Time) protocol.RequestOptions {
|
||||
opts := protocol.RequestOptions{
|
||||
To: now.Unix(),
|
||||
Limit: 1000,
|
||||
}
|
||||
for i := range histories {
|
||||
if opts.From == 0 || opts.From > histories[i].Synced {
|
||||
opts.From = histories[i].Synced
|
||||
}
|
||||
// TODO(dshulyak) remove contact type validation in that function
|
||||
// simply always add topic and (if set) public key
|
||||
_ = enhanceRequestOptions(histories[i].Contact, &opts)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func notsyncedToOpts(histories []History, now time.Time) protocol.RequestOptions {
|
||||
opts := protocol.DefaultRequestOptions()
|
||||
opts.To = now.Unix()
|
||||
for i := range histories {
|
||||
// TODO(dshulyak) remove contact type validation in that function
|
||||
// simply always add topic and (if set) public key
|
||||
_ = enhanceRequestOptions(histories[i].Contact, &opts)
|
||||
}
|
||||
return opts
|
||||
}
|
@ -37,7 +37,7 @@ func NewMessenger(identity *ecdsa.PrivateKey, proto protocol.Protocol, db Databa
|
||||
}
|
||||
}
|
||||
|
||||
func contactToChatOptions(c Contact) protocol.ChatOptions {
|
||||
func chatToChatOptions(c Chat) protocol.ChatOptions {
|
||||
return protocol.ChatOptions{
|
||||
ChatName: c.Name,
|
||||
Recipient: c.PublicKey,
|
||||
@ -51,13 +51,13 @@ func (m *Messenger) Start() error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
contacts, err := m.db.Contacts()
|
||||
chats, err := m.db.Chats()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to read contacts from database")
|
||||
return errors.Wrap(err, "unable to read chats from database")
|
||||
}
|
||||
|
||||
for i := range contacts {
|
||||
chatOptions = append(chatOptions, contactToChatOptions(contacts[i]))
|
||||
for i := range chats {
|
||||
chatOptions = append(chatOptions, chatToChatOptions(chats[i]))
|
||||
}
|
||||
|
||||
if err := m.proto.LoadChats(context.Background(), chatOptions); err != nil {
|
||||
@ -78,26 +78,24 @@ func (m *Messenger) Stop() {
|
||||
}
|
||||
|
||||
func (m *Messenger) handleDirectMessage(chatType protocol.ChatOptions, message protocol.Message) error {
|
||||
contact, err := m.db.GetOneToOneChat(message.SigPubKey)
|
||||
chat, err := m.db.GetOneToOneChat(message.SigPubKey)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not fetch chat from database")
|
||||
}
|
||||
if contact == nil {
|
||||
contact = &Contact{
|
||||
Type: ContactPrivate,
|
||||
State: ContactNew,
|
||||
if chat == nil {
|
||||
chat = &Chat{
|
||||
Type: OneToOneChat,
|
||||
Name: pubkeyToHex(message.SigPubKey), // TODO(dshulyak) replace with 3-word funny name
|
||||
PublicKey: message.SigPubKey,
|
||||
Topic: DefaultPrivateTopic(),
|
||||
}
|
||||
|
||||
err := m.db.SaveContacts([]Contact{*contact})
|
||||
err := m.db.SaveChats([]Chat{*chat})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "can't save a new contact")
|
||||
return errors.Wrap(err, "can't save a new chat")
|
||||
}
|
||||
}
|
||||
|
||||
_, err = m.db.SaveMessages(*contact, []*protocol.Message{&message})
|
||||
_, err = m.db.SaveMessages(*chat, []*protocol.Message{&message})
|
||||
if err == ErrMsgAlreadyExist {
|
||||
log.Printf("Message already exists")
|
||||
return nil
|
||||
@ -109,13 +107,13 @@ func (m *Messenger) handleDirectMessage(chatType protocol.ChatOptions, message p
|
||||
}
|
||||
|
||||
func (m *Messenger) handlePublicMessage(chatType protocol.ChatOptions, message protocol.Message) error {
|
||||
contact, err := m.db.GetPublicChat(chatType.ChatName)
|
||||
chat, err := m.db.GetPublicChat(chatType.ChatName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error getting public chat")
|
||||
} else if contact == nil {
|
||||
} else if chat == nil {
|
||||
return errors.Wrap(err, "no chat for this message, is that a deleted chat?")
|
||||
}
|
||||
_, err = m.db.SaveMessages(*contact, []*protocol.Message{&message})
|
||||
_, err = m.db.SaveMessages(*chat, []*protocol.Message{&message})
|
||||
if err == ErrMsgAlreadyExist {
|
||||
log.Printf("Message already exists")
|
||||
return nil
|
||||
@ -186,30 +184,24 @@ func (m *Messenger) ProcessMessages() {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Messenger) Join(ctx context.Context, c Contact) error {
|
||||
func (m *Messenger) Join(ctx context.Context, c Chat) error {
|
||||
log.Printf("[Messenger::Join] Joining a chat with contact %#v", c)
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if err := m.proto.LoadChats(context.Background(), []protocol.ChatOptions{contactToChatOptions(c)}); err != nil {
|
||||
if err := m.AddChat(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts := protocol.DefaultRequestOptions()
|
||||
// NOTE(dshulyak) join ctx shouldn't have an impact on history timeout.
|
||||
if err := m.Request(context.Background(), c, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
return m.db.UpdateHistories([]History{{Contact: c, Synced: opts.To}})
|
||||
return m.proto.LoadChats(context.Background(), []protocol.ChatOptions{chatToChatOptions(c)})
|
||||
}
|
||||
|
||||
// Messages reads all messages from database.
|
||||
func (m *Messenger) Messages(c Contact, offset int64) ([]*protocol.Message, error) {
|
||||
func (m *Messenger) Messages(c Chat, offset int64) ([]*protocol.Message, error) {
|
||||
return m.db.NewMessages(c, offset)
|
||||
}
|
||||
|
||||
func (m *Messenger) Request(ctx context.Context, c Contact, options protocol.RequestOptions) error {
|
||||
func (m *Messenger) Request(ctx context.Context, c Chat, options protocol.RequestOptions) error {
|
||||
err := enhanceRequestOptions(c, &options)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -217,75 +209,22 @@ func (m *Messenger) Request(ctx context.Context, c Contact, options protocol.Req
|
||||
return m.proto.Request(ctx, options)
|
||||
}
|
||||
|
||||
func (m *Messenger) requestHistories(ctx context.Context, histories []History, opts protocol.RequestOptions) error {
|
||||
log.Printf("[Messenger::requestHistories] requesting messages for chats %+v: from %d to %d\n", opts.Chats, opts.From, opts.To)
|
||||
start := time.Now()
|
||||
|
||||
err := m.proto.Request(ctx, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("[Messenger::requestHistories] requesting message for chats %+v finished in %s\n", opts.Chats, time.Since(start))
|
||||
|
||||
for i := range histories {
|
||||
histories[i].Synced = opts.To
|
||||
}
|
||||
return m.db.UpdateHistories(histories)
|
||||
}
|
||||
|
||||
func (m *Messenger) RequestAll(ctx context.Context, newest bool) error {
|
||||
// FIXME(dshulyak) if newest is false request 24 hour of messages older then the
|
||||
// earliest envelope for each contact.
|
||||
histories, err := m.db.Histories()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error fetching contacts")
|
||||
}
|
||||
var (
|
||||
now = time.Now()
|
||||
synced, notsynced = splitIntoSyncedNotSynced(histories)
|
||||
errors = make(chan error, 2)
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
if len(synced) != 0 {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
errors <- m.requestHistories(ctx, synced, syncedToOpts(synced, now))
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
if len(notsynced) != 0 {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
errors <- m.requestHistories(ctx, notsynced, notsyncedToOpts(notsynced, now))
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
log.Printf("[Messenger::RequestAll] finished requesting histories")
|
||||
|
||||
close(errors)
|
||||
for err := range errors {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Messenger) Send(c Contact, data []byte) ([]byte, error) {
|
||||
// FIXME(dshulyak) sending must be locked by contact to prevent sending second msg with same clock
|
||||
func (m *Messenger) Send(c Chat, data []byte) ([]byte, error) {
|
||||
// FIXME(dshulyak) sending must be locked by chat to prevent sending second msg with same clock
|
||||
clock, err := m.db.LastMessageClock(c)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to read last message clock for contact")
|
||||
return nil, errors.Wrap(err, "failed to read last message clock for chat")
|
||||
}
|
||||
var message protocol.Message
|
||||
|
||||
switch c.Type {
|
||||
case ContactPublicRoom:
|
||||
case PublicChat:
|
||||
message = protocol.CreatePublicTextMessage(data, clock, c.Name)
|
||||
case ContactPrivate:
|
||||
case OneToOneChat:
|
||||
message = protocol.CreatePrivateTextMessage(data, clock, c.Name)
|
||||
default:
|
||||
return nil, fmt.Errorf("failed to send message: unsupported contact type")
|
||||
@ -326,24 +265,24 @@ func (m *Messenger) Send(c Contact, data []byte) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Messenger) RemoveContact(c Contact) error {
|
||||
return m.db.DeleteContact(c)
|
||||
func (m *Messenger) RemoveChat(c Chat) error {
|
||||
return m.db.DeleteChat(c)
|
||||
}
|
||||
|
||||
func (m *Messenger) AddContact(c Contact) error {
|
||||
return m.db.SaveContacts([]Contact{c})
|
||||
func (m *Messenger) AddChat(c Chat) error {
|
||||
return m.db.SaveChats([]Chat{c})
|
||||
}
|
||||
|
||||
func (m *Messenger) Contacts() ([]Contact, error) {
|
||||
return m.db.Contacts()
|
||||
func (m *Messenger) Chats() ([]Chat, error) {
|
||||
return m.db.Chats()
|
||||
}
|
||||
|
||||
func (m *Messenger) Leave(c Contact) error {
|
||||
func (m *Messenger) Leave(c Chat) error {
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if err := m.proto.RemoveChats(context.Background(), []protocol.ChatOptions{contactToChatOptions(c)}); err != nil {
|
||||
if err := m.proto.RemoveChats(context.Background(), []protocol.ChatOptions{chatToChatOptions(c)}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -1,98 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/status-im/status-console-client/protocol/subscription"
|
||||
"github.com/status-im/status-console-client/protocol/v1"
|
||||
"github.com/status-im/status-go/messaging/filter"
|
||||
"github.com/status-im/status-go/messaging/multidevice"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type requestsMock struct {
|
||||
requests []protocol.RequestOptions
|
||||
}
|
||||
|
||||
func (proto *requestsMock) Send(ctx context.Context, data []byte, options protocol.SendOptions) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (proto *requestsMock) Subscribe(ctx context.Context, messages chan *protocol.StatusMessage, options protocol.SubscribeOptions) (*subscription.Subscription, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (proto *requestsMock) Request(ctx context.Context, params protocol.RequestOptions) error {
|
||||
proto.requests = append(proto.requests, params)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (proto *requestsMock) GetMessagesChan() chan *protocol.ReceivedMessages {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (proto *requestsMock) LoadChats(ctx context.Context, chats []protocol.ChatOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (proto *requestsMock) RemoveChats(ctx context.Context, chats []protocol.ChatOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (proto *requestsMock) SetInstallationMetadata(ctx context.Context, installationID string, data *multidevice.InstallationMetadata) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (proto *requestsMock) OnNewMessages(messages []*filter.Messages) {
|
||||
}
|
||||
|
||||
func TestRequestHistoryOneRequest(t *testing.T) {
|
||||
db, err := InitializeTmpDB()
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
proto := &requestsMock{}
|
||||
m := NewMessenger(nil, proto, db)
|
||||
|
||||
require.NoError(t, db.SaveContacts([]Contact{
|
||||
{Name: "first", Type: ContactPublicRoom},
|
||||
{Name: "second", Type: ContactPublicRoom}}))
|
||||
require.NoError(t, m.RequestAll(context.TODO(), true))
|
||||
|
||||
require.Len(t, proto.requests, 1)
|
||||
histories, err := db.Histories()
|
||||
require.NoError(t, err)
|
||||
require.Len(t, histories, 2)
|
||||
require.Equal(t, histories[0].Synced, proto.requests[0].To)
|
||||
require.Equal(t, histories[1].Synced, proto.requests[0].To)
|
||||
}
|
||||
|
||||
func TestRequestHistoryTwoRequest(t *testing.T) {
|
||||
db, err := InitializeTmpDB()
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
proto := &requestsMock{}
|
||||
m := NewMessenger(nil, proto, db)
|
||||
|
||||
contacts := []Contact{
|
||||
{Name: "first", Type: ContactPublicRoom, Topic: "first"},
|
||||
{Name: "second", Type: ContactPublicRoom, Topic: "second"},
|
||||
{Name: "third", Type: ContactPublicRoom, Topic: "third"},
|
||||
}
|
||||
require.NoError(t, db.SaveContacts(contacts))
|
||||
histories := []History{{Synced: time.Now().Add(-time.Hour).Unix(), Contact: contacts[0]}}
|
||||
require.NoError(t, db.UpdateHistories(histories))
|
||||
require.NoError(t, m.RequestAll(context.TODO(), true))
|
||||
|
||||
require.Len(t, proto.requests, 2)
|
||||
sort.Slice(proto.requests, func(i, j int) bool {
|
||||
return proto.requests[i].From < proto.requests[j].From
|
||||
})
|
||||
require.Len(t, proto.requests[0].Chats, 2)
|
||||
require.Len(t, proto.requests[1].Chats, 1)
|
||||
require.Equal(t, histories[0].Contact.Name, proto.requests[1].Chats[0].ChatName)
|
||||
require.Equal(t, histories[0].Synced, proto.requests[1].From)
|
||||
|
||||
}
|
@ -1,3 +1,10 @@
|
||||
// Code generated by go-bindata.
|
||||
// sources:
|
||||
// 000001_add_messages_contacts.down.db.sql
|
||||
// 000001_add_messages_contacts.up.db.sql
|
||||
// doc.go
|
||||
// DO NOT EDIT!
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
@ -5,10 +12,14 @@ import (
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func bindata_read(data []byte, name string) ([]byte, error) {
|
||||
func bindataRead(data []byte, name string) ([]byte, error) {
|
||||
gz, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
@ -16,71 +27,150 @@ func bindata_read(data []byte, name string) ([]byte, error) {
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err = io.Copy(&buf, gz)
|
||||
gz.Close()
|
||||
clErr := gz.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
if clErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
var __000001_add_messages_contacts_down_db_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x28\x2d\x4e\x2d\x8a\xcf\x4d\x2d\x2e\x4e\x4c\x4f\x2d\xb6\xe6\x42\x97\x49\xce\xcf\x2b\x49\x4c\x2e\x41\x95\xc9\xc8\x2c\x2e\xc9\x2f\xaa\x8c\x47\x56\x11\x5f\x92\x5f\x90\x99\x6c\xcd\x05\x08\x00\x00\xff\xff\x1b\x57\x14\x62\x5b\x00\x00\x00")
|
||||
type asset struct {
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
}
|
||||
|
||||
func _000001_add_messages_contacts_down_db_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000001_add_messages_contacts_down_db_sql,
|
||||
type bindataFileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
mode os.FileMode
|
||||
modTime time.Time
|
||||
}
|
||||
|
||||
func (fi bindataFileInfo) Name() string {
|
||||
return fi.name
|
||||
}
|
||||
func (fi bindataFileInfo) Size() int64 {
|
||||
return fi.size
|
||||
}
|
||||
func (fi bindataFileInfo) Mode() os.FileMode {
|
||||
return fi.mode
|
||||
}
|
||||
func (fi bindataFileInfo) ModTime() time.Time {
|
||||
return fi.modTime
|
||||
}
|
||||
func (fi bindataFileInfo) IsDir() bool {
|
||||
return false
|
||||
}
|
||||
func (fi bindataFileInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
var __000001_add_messages_contactsDownDbSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\x48\xce\x48\x2c\x29\xb6\xe6\x42\x12\x29\x2d\x4e\x2d\x8a\xcf\x4d\x2d\x2e\x4e\x4c\x4f\x45\x95\xc9\x4d\xcd\x4d\x4a\x2d\x2a\xce\xc8\x2c\x88\x2f\x2d\x48\x49\x2c\x41\x93\x06\x19\x15\x0f\x55\x63\xcd\xc5\x05\x08\x00\x00\xff\xff\x49\xbe\x78\x25\x65\x00\x00\x00")
|
||||
|
||||
func _000001_add_messages_contactsDownDbSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000001_add_messages_contactsDownDbSql,
|
||||
"000001_add_messages_contacts.down.db.sql",
|
||||
)
|
||||
}
|
||||
|
||||
var __000001_add_messages_contacts_up_db_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\x92\xcf\x6e\xb3\x30\x10\xc4\xef\x7e\x8a\x3d\xf2\x49\x1c\xbe\x7b\x4f\x06\x96\xc4\x2a\xb5\x5b\xc7\x34\xc9\x09\x51\xc7\x6a\x50\xc2\x1f\xc5\x8e\x54\xde\xbe\x82\x42\xe3\xa6\x69\xae\x33\xf6\x7a\xe6\xb7\x8e\x25\x52\x85\xa0\x68\x94\x21\xb0\x14\xb8\x50\x80\x1b\xb6\x52\x2b\x38\x5b\x73\x2a\x6a\x63\x6d\xf9\x6e\x2c\x04\xa4\xda\x41\x94\x89\x08\x72\xce\x5e\x72\x1c\x4f\xf2\x3c\xcb\x42\xa2\xdb\xc6\x95\xda\x15\xd5\x0e\x5e\xa9\x8c\x97\x54\x5e\x99\xa6\x71\x85\xeb\x3b\x33\xdb\x21\x99\xc6\x5e\xa9\xce\x7c\x38\x50\xb8\x51\x21\xd1\xc7\x56\x1f\x20\x62\x0b\xc6\x55\x48\x5c\x55\x1b\xeb\xca\xba\xfb\x56\xe6\xb1\x7a\x5f\x8e\x0f\x4f\xb7\xe6\xc7\x2e\x83\xba\xf3\xdb\xb1\xd2\xc5\xc1\xf4\x63\x7a\xf2\xef\x81\x90\xa9\x34\xe3\x09\x6e\xe0\x92\xde\x82\xe0\x3f\x5b\x07\x17\xd3\xbb\xf7\x27\xac\xe9\xf4\x04\x6b\x66\xf1\x2c\xd9\x13\x95\x5b\x78\xc4\xad\xc7\xa5\x29\x6b\x73\x03\x97\x6b\xbb\x4a\x8f\xd1\x7d\x71\xa0\xc4\xb8\x2f\x59\x57\xba\x51\xbb\xd1\x10\xd6\x4c\x2d\x45\xae\x40\x8a\x35\x4b\xee\xe7\xde\x57\xd6\xb5\xa7\xbe\xf0\xf3\x17\x5f\x21\x02\x62\xfb\x46\x9b\xdd\xc4\x1c\x12\x4c\x69\x9e\x29\xf8\x7f\x7f\xf5\xbf\xbe\x47\x2a\x24\xb2\x05\x1f\xfa\xfb\x3c\x41\x62\x8a\x12\x79\x8c\x57\xf4\x82\xc1\x14\x1c\x12\xcc\x50\x21\xc4\x74\x15\xd3\x04\x87\xc5\x7d\x06\x00\x00\xff\xff\x28\x6d\x42\x61\xad\x02\x00\x00")
|
||||
func _000001_add_messages_contactsDownDbSql() (*asset, error) {
|
||||
bytes, err := _000001_add_messages_contactsDownDbSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func _000001_add_messages_contacts_up_db_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000001_add_messages_contacts_up_db_sql,
|
||||
info := bindataFileInfo{name: "000001_add_messages_contacts.down.db.sql", size: 101, mode: os.FileMode(420), modTime: time.Unix(1562751645, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000001_add_messages_contactsUpDbSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x94\x54\xcb\x6e\xdb\x30\x10\xbc\xf3\x2b\x16\xe8\x21\x11\xa0\x43\x7b\x28\x5a\x20\x27\xda\x5e\x27\x44\x65\x2a\xa5\xa9\xc6\x39\x11\x8c\xc8\xc6\x6a\xf4\x82\x45\xb9\xcd\xdf\x17\xd6\xc3\x96\xab\xd8\x71\xaf\xcb\x99\xe5\xec\x70\x96\x53\x81\x54\x22\x48\x3a\x09\x10\xd8\x1c\x78\x28\x01\x57\x6c\x29\x97\x10\xaf\xb5\xab\xe0\x9a\x24\x06\x7e\x50\x31\xbd\xa3\x02\xee\x05\x5b\x50\xf1\x08\xdf\xf0\xb1\x41\xf2\x28\x08\x7c\x92\xeb\xcc\xee\x21\x87\x72\x5c\xa4\xc5\x66\x54\x87\x19\xce\x69\x14\x48\xb8\xfa\xa0\x3f\x7d\xfd\x62\x3e\x5f\xf9\xc4\xbd\x96\x16\x18\x97\x03\xb2\x8e\x5d\xb2\xb5\x30\x09\xc3\x00\x29\x1f\xb3\xa5\x88\xd0\x27\x2e\xc9\x6c\xe5\x74\x56\xc2\x8c\x4a\x94\x6c\x81\x63\xe4\x34\x12\x02\xb9\x54\xbb\xd3\xa5\xa4\x8b\x7b\x9f\xd4\xa5\xd1\xce\x1a\xa5\xdd\xff\xf1\x8c\x4d\x6d\xcb\x53\x71\x5a\xc4\x2f\x6a\xab\xd3\xfa\x58\xf9\x9e\xfe\xd1\x27\x65\xfd\x94\x26\xb1\x7a\xb1\xaf\x30\x09\xc2\x89\x4f\xea\x7c\x9b\xd8\xdf\xd6\xa8\xcc\x56\x95\x7e\xb6\x2a\x2e\xea\xdc\x9d\xe4\xa7\xba\xba\xec\xa2\x06\x78\xe8\x99\x3b\x9b\x3b\xd5\x98\xda\xb9\xff\x36\xa4\x3f\x25\x1e\x3c\x30\x79\x17\x46\x12\x44\xf8\xc0\x66\x37\x84\x9c\x89\x45\x5d\xd9\x4d\xdf\xa9\x8b\xc7\x6e\x3a\x88\x38\xfb\x1e\xe1\xf0\xfd\xd7\xda\xa9\x41\x78\x86\xc9\x78\x4b\x62\xaf\xee\xb8\xea\xec\x1f\x07\x12\x57\xd2\x27\x8d\x15\x30\x61\xb7\x8c\xcb\xe1\xdb\xf7\x95\xbe\x6d\x7f\x71\xc7\xea\x2f\x3b\x34\x1a\x3d\xcc\xcf\x54\x3f\x57\x27\xec\x25\xde\xc1\x0f\xc6\x67\xb8\x82\xae\x7f\x05\x21\x3f\x76\xe3\xba\x3b\xf1\xce\x3b\x98\xd9\xec\xc9\x6e\xaa\x75\x52\xaa\x36\x89\x3b\x1b\x01\xde\xdd\x33\x00\xa3\x9d\x6e\xdd\x1e\x56\x4f\x1b\x0d\x30\x0f\x05\xb2\x5b\xde\x74\xda\xcb\x03\x81\x73\x14\xc8\xa7\xd8\x6d\xf9\x75\x62\x3c\x02\x30\xce\x01\xc0\x3b\x1f\x84\xea\x86\x69\x26\x00\xf8\xc7\xd9\x23\x29\xe7\x85\x02\x68\x93\x25\xf9\xe9\x8d\x9f\xd3\x60\x89\x2d\xf2\x57\x91\xe4\xd6\x5c\x04\xbd\x7c\xfe\x16\xdf\xa6\xb8\x47\xfa\x83\x81\x3c\xef\x86\xfc\x0d\x00\x00\xff\xff\xb2\xa0\x14\x27\x2e\x05\x00\x00")
|
||||
|
||||
func _000001_add_messages_contactsUpDbSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000001_add_messages_contactsUpDbSql,
|
||||
"000001_add_messages_contacts.up.db.sql",
|
||||
)
|
||||
}
|
||||
|
||||
var __000002_add_unread_to_user_messages_down_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x44\x8e\x31\x4e\xc5\x30\x10\x44\x7b\x4e\x31\x25\x14\xfe\x07\xa0\xa6\xa1\x4a\x03\x07\xd8\xd8\x43\x62\xc9\xde\xb5\xec\xb5\x42\x6e\x8f\x2c\x21\x68\xdf\xee\xbc\x99\x10\xf0\xb1\xbd\x6d\xcf\x92\xa4\xbe\xbc\x22\x8a\xaa\x39\x52\xb7\x86\x68\x65\x56\xc5\x95\xfd\xb4\xe9\x88\xd6\xee\xac\x07\x04\x2e\x7b\xe1\xe3\x29\x04\xbc\xd7\x56\x58\xa9\xff\xd7\x3c\x40\x19\x37\xf6\xe9\xb8\x08\x25\x13\xdc\x90\x66\x2b\x39\x8a\x13\x23\x9e\xac\xb2\xc2\x6e\xe8\x8c\x9d\x8b\x66\x7f\xe0\x53\x0b\xc7\x80\x9f\xec\x5c\x1e\xc1\xa8\xd2\x9d\x1d\x97\xdc\xeb\xfb\xa0\x2f\xf8\x27\xb0\x2f\x88\x82\xdf\x79\xf8\xaa\xfe\x9d\xf5\x13\x00\x00\xff\xff\x1c\xd9\x39\x28\xd2\x00\x00\x00")
|
||||
func _000001_add_messages_contactsUpDbSql() (*asset, error) {
|
||||
bytes, err := _000001_add_messages_contactsUpDbSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func _000002_add_unread_to_user_messages_down_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000002_add_unread_to_user_messages_down_sql,
|
||||
"000002_add_unread_to_user_messages.down.sql",
|
||||
)
|
||||
info := bindataFileInfo{name: "000001_add_messages_contacts.up.db.sql", size: 1326, mode: os.FileMode(420), modTime: time.Unix(1562751940, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000002_add_unread_to_user_messages_up_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\xcc\x41\xca\xc2\x30\x10\x40\xe1\x7d\x4f\xf1\x2e\x50\xfe\x7f\xef\x2a\x9a\x08\xc2\x98\x82\x4c\xd6\x12\x70\x2c\xc5\xaa\x90\x21\x9e\x5f\x84\xae\xde\xea\x7d\x41\x34\x5d\xd0\xb0\x97\x44\x77\x6b\xd7\xa7\xb9\xd7\xd9\x7c\x00\x08\x31\x72\x98\xa4\x9c\x33\xf7\xb5\xce\xce\x29\x2b\x79\x52\x72\x11\x21\xa6\x63\x28\xa2\xfc\xef\x18\x47\x3e\xb5\x2d\xef\xee\x6c\xc0\x36\xac\xcb\xc3\x68\x56\x6f\x7f\xfd\xf5\xcb\xf0\x0d\x00\x00\xff\xff\xbc\x55\xda\x47\x71\x00\x00\x00")
|
||||
var _docGo = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\xc9\xb1\x0d\xc4\x20\x0c\x05\xd0\x9e\x29\xfe\x02\xd8\xfd\x6d\xe3\x4b\xac\x2f\x44\x82\x09\x78\x7f\xa5\x49\xfd\xa6\x1d\xdd\xe8\xd8\xcf\x55\x8a\x2a\xe3\x47\x1f\xbe\x2c\x1d\x8c\xfa\x6f\xe3\xb4\x34\xd4\xd9\x89\xbb\x71\x59\xb6\x18\x1b\x35\x20\xa2\x9f\x0a\x03\xa2\xe5\x0d\x00\x00\xff\xff\x60\xcd\x06\xbe\x4a\x00\x00\x00")
|
||||
|
||||
func _000002_add_unread_to_user_messages_up_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000002_add_unread_to_user_messages_up_sql,
|
||||
"000002_add_unread_to_user_messages.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
var _doc_go = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\xc9\xb1\x0d\xc4\x20\x0c\x05\xd0\x9e\x29\xfe\x02\xd8\xfd\x6d\xe3\x4b\xac\x2f\x44\x82\x09\x78\x7f\xa5\x49\xfd\xa6\x1d\xdd\xe8\xd8\xcf\x55\x8a\x2a\xe3\x47\x1f\xbe\x2c\x1d\x8c\xfa\x6f\xe3\xb4\x34\xd4\xd9\x89\xbb\x71\x59\xb6\x18\x1b\x35\x20\xa2\x9f\x0a\x03\xa2\xe5\x0d\x00\x00\xff\xff\x60\xcd\x06\xbe\x4a\x00\x00\x00")
|
||||
|
||||
func doc_go() ([]byte, error) {
|
||||
return bindata_read(
|
||||
_doc_go,
|
||||
func docGoBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
_docGo,
|
||||
"doc.go",
|
||||
)
|
||||
}
|
||||
|
||||
func docGo() (*asset, error) {
|
||||
bytes, err := docGoBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "doc.go", size: 74, mode: os.FileMode(420), modTime: time.Unix(1562307822, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// Asset loads and returns the asset for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func Asset(name string) ([]byte, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
return f()
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.bytes, nil
|
||||
}
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
|
||||
// MustAsset is like Asset but panics when Asset would return an error.
|
||||
// It simplifies safe initialization of global variables.
|
||||
func MustAsset(name string) []byte {
|
||||
a, err := Asset(name)
|
||||
if err != nil {
|
||||
panic("asset: Asset(" + name + "): " + err.Error())
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// AssetInfo loads and returns the asset info for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func AssetInfo(name string) (os.FileInfo, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.info, nil
|
||||
}
|
||||
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
||||
}
|
||||
|
||||
// AssetNames returns the names of the assets.
|
||||
func AssetNames() []string {
|
||||
names := make([]string, 0, len(_bindata))
|
||||
@ -91,13 +181,12 @@ func AssetNames() []string {
|
||||
}
|
||||
|
||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||
var _bindata = map[string]func() ([]byte, error){
|
||||
"000001_add_messages_contacts.down.db.sql": _000001_add_messages_contacts_down_db_sql,
|
||||
"000001_add_messages_contacts.up.db.sql": _000001_add_messages_contacts_up_db_sql,
|
||||
"000002_add_unread_to_user_messages.down.sql": _000002_add_unread_to_user_messages_down_sql,
|
||||
"000002_add_unread_to_user_messages.up.sql": _000002_add_unread_to_user_messages_up_sql,
|
||||
"doc.go": doc_go,
|
||||
var _bindata = map[string]func() (*asset, error){
|
||||
"000001_add_messages_contacts.down.db.sql": _000001_add_messages_contactsDownDbSql,
|
||||
"000001_add_messages_contacts.up.db.sql": _000001_add_messages_contactsUpDbSql,
|
||||
"doc.go": docGo,
|
||||
}
|
||||
|
||||
// AssetDir returns the file names below a certain
|
||||
// directory embedded in the file by go-bindata.
|
||||
// For example if you run go-bindata on data/... and data contains the
|
||||
@ -127,25 +216,66 @@ func AssetDir(name string) ([]string, error) {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
rv := make([]string, 0, len(node.Children))
|
||||
for name := range node.Children {
|
||||
rv = append(rv, name)
|
||||
for childName := range node.Children {
|
||||
rv = append(rv, childName)
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
type _bintree_t struct {
|
||||
Func func() ([]byte, error)
|
||||
Children map[string]*_bintree_t
|
||||
type bintree struct {
|
||||
Func func() (*asset, error)
|
||||
Children map[string]*bintree
|
||||
}
|
||||
var _bintree = &_bintree_t{nil, map[string]*_bintree_t{
|
||||
"000001_add_messages_contacts.down.db.sql": &_bintree_t{_000001_add_messages_contacts_down_db_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000001_add_messages_contacts.up.db.sql": &_bintree_t{_000001_add_messages_contacts_up_db_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000002_add_unread_to_user_messages.down.sql": &_bintree_t{_000002_add_unread_to_user_messages_down_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000002_add_unread_to_user_messages.up.sql": &_bintree_t{_000002_add_unread_to_user_messages_up_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"doc.go": &_bintree_t{doc_go, map[string]*_bintree_t{
|
||||
}},
|
||||
var _bintree = &bintree{nil, map[string]*bintree{
|
||||
"000001_add_messages_contacts.down.db.sql": &bintree{_000001_add_messages_contactsDownDbSql, map[string]*bintree{}},
|
||||
"000001_add_messages_contacts.up.db.sql": &bintree{_000001_add_messages_contactsUpDbSql, map[string]*bintree{}},
|
||||
"doc.go": &bintree{docGo, map[string]*bintree{}},
|
||||
}}
|
||||
|
||||
// RestoreAsset restores an asset under the given directory
|
||||
func RestoreAsset(dir, name string) error {
|
||||
data, err := Asset(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := AssetInfo(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreAssets restores an asset under the given directory recursively
|
||||
func RestoreAssets(dir, name string) error {
|
||||
children, err := AssetDir(name)
|
||||
// File
|
||||
if err != nil {
|
||||
return RestoreAsset(dir, name)
|
||||
}
|
||||
// Dir
|
||||
for _, child := range children {
|
||||
err = RestoreAssets(dir, filepath.Join(name, child))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func _filePath(dir, name string) string {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
DROP TABLE chats;
|
||||
DROP TABLE user_messages;
|
||||
DROP TABLE user_contacts;
|
||||
DROP TABLE history_user_contact_topic;
|
||||
DROP TABLE membership_updates;
|
||||
DROP TABLE chat_members;
|
||||
|
||||
|
@ -1,6 +1,22 @@
|
||||
CREATE TABLE IF NOT EXISTS chats (
|
||||
id VARCHAR PRIMARY KEY NOT NULL,
|
||||
name VARCHAR NOT NULL,
|
||||
color VARCHAR NOT NULL DEFAULT '#a187d5',
|
||||
type INT NOT NULL,
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
timestamp DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
deleted_at_clock_value INT NOT NULL DEFAULT 0,
|
||||
public_key BLOB,
|
||||
unviewed_message_count INT NOT NULL DEFAULT 0,
|
||||
last_clock_value INT NOT NULL DEFAULT 0,
|
||||
last_message_content_type VARCHAR,
|
||||
last_message_content VARCHAR
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_messages (
|
||||
id BLOB UNIQUE NOT NULL,
|
||||
contact_id VARCHAR NOT NULL,
|
||||
chat_id VARCHAR NOT NULL,
|
||||
content_type VARCHAR,
|
||||
message_type VARCHAR,
|
||||
text TEXT,
|
||||
@ -8,22 +24,23 @@ clock BIGINT,
|
||||
timestamp BIGINT,
|
||||
content_chat_id TEXT,
|
||||
content_text TEXT,
|
||||
public_key BLOB
|
||||
public_key BLOB,
|
||||
flags INT NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX contact_ids ON user_messages(contact_id);
|
||||
CREATE INDEX chat_ids ON user_messages(chat_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_contacts (
|
||||
id VARCHAR PRIMARY KEY NOT NULL,
|
||||
name VARCHAR NOT NULL,
|
||||
topic TEXT NOT NULL,
|
||||
type INT NOT NULL,
|
||||
state INT,
|
||||
public_key BLOB
|
||||
) WITHOUT ROWID;
|
||||
CREATE TABLE IF NOT EXISTS membership_updates (
|
||||
id VARCHAR PRIMARY KEY NOT NULL,
|
||||
data BLOB NOT NULL,
|
||||
chat_id VARCHAR NOT NULL,
|
||||
FOREIGN KEY (chat_id) REFERENCES chats(id)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS history_user_contact_topic (
|
||||
synced BIGINT DEFAULT 0 NOT NULL,
|
||||
contact_id VARCHAR UNIQUE NOT NULL,
|
||||
FOREIGN KEY(contact_id) REFERENCES user_contacts(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS chat_members (
|
||||
public_key BLOB NOT NULL,
|
||||
chat_id VARCHAR NOT NULL,
|
||||
admin BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
joined BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
FOREIGN KEY (chat_id) REFERENCES chats(id),
|
||||
UNIQUE(chat_id, public_key));
|
||||
|
@ -1,4 +0,0 @@
|
||||
-- TODO(adam): cannot drop column without copying a table.
|
||||
-- Implement copying is easy but we need to duplicate schema
|
||||
-- to recreate it. Unless there is a smarter way to get a schema
|
||||
-- of an existing table.
|
@ -1,2 +0,0 @@
|
||||
ALTER TABLE user_messages
|
||||
ADD COLUMN flags INT NOT NULL DEFAULT 0; -- various message flags like read/unread
|
@ -50,55 +50,55 @@ func (mr *MockDatabaseMockRecorder) Close() *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockDatabase)(nil).Close))
|
||||
}
|
||||
|
||||
// ContactExist mocks base method
|
||||
func (m *MockDatabase) ContactExist(arg0 client.Contact) (bool, error) {
|
||||
// ChatExist mocks base method
|
||||
func (m *MockDatabase) ChatExist(arg0 client.Chat) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "ContactExist", arg0)
|
||||
ret := m.ctrl.Call(m, "ChatExist", arg0)
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ContactExist indicates an expected call of ContactExist
|
||||
func (mr *MockDatabaseMockRecorder) ContactExist(arg0 interface{}) *gomock.Call {
|
||||
// ChatExist indicates an expected call of ChatExist
|
||||
func (mr *MockDatabaseMockRecorder) ChatExist(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContactExist", reflect.TypeOf((*MockDatabase)(nil).ContactExist), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChatExist", reflect.TypeOf((*MockDatabase)(nil).ChatExist), arg0)
|
||||
}
|
||||
|
||||
// Contacts mocks base method
|
||||
func (m *MockDatabase) Contacts() ([]client.Contact, error) {
|
||||
// Chats mocks base method
|
||||
func (m *MockDatabase) Chats() ([]client.Chat, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Contacts")
|
||||
ret0, _ := ret[0].([]client.Contact)
|
||||
ret := m.ctrl.Call(m, "Chats")
|
||||
ret0, _ := ret[0].([]client.Chat)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Contacts indicates an expected call of Contacts
|
||||
func (mr *MockDatabaseMockRecorder) Contacts() *gomock.Call {
|
||||
// Chats indicates an expected call of Chats
|
||||
func (mr *MockDatabaseMockRecorder) Chats() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Contacts", reflect.TypeOf((*MockDatabase)(nil).Contacts))
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Chats", reflect.TypeOf((*MockDatabase)(nil).Chats))
|
||||
}
|
||||
|
||||
// DeleteContact mocks base method
|
||||
func (m *MockDatabase) DeleteContact(arg0 client.Contact) error {
|
||||
// DeleteChat mocks base method
|
||||
func (m *MockDatabase) DeleteChat(arg0 client.Chat) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteContact", arg0)
|
||||
ret := m.ctrl.Call(m, "DeleteChat", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteContact indicates an expected call of DeleteContact
|
||||
func (mr *MockDatabaseMockRecorder) DeleteContact(arg0 interface{}) *gomock.Call {
|
||||
// DeleteChat indicates an expected call of DeleteChat
|
||||
func (mr *MockDatabaseMockRecorder) DeleteChat(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteContact", reflect.TypeOf((*MockDatabase)(nil).DeleteContact), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteChat", reflect.TypeOf((*MockDatabase)(nil).DeleteChat), arg0)
|
||||
}
|
||||
|
||||
// GetOneToOneChat mocks base method
|
||||
func (m *MockDatabase) GetOneToOneChat(arg0 *ecdsa.PublicKey) (*client.Contact, error) {
|
||||
func (m *MockDatabase) GetOneToOneChat(arg0 *ecdsa.PublicKey) (*client.Chat, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetOneToOneChat", arg0)
|
||||
ret0, _ := ret[0].(*client.Contact)
|
||||
ret0, _ := ret[0].(*client.Chat)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
@ -110,10 +110,10 @@ func (mr *MockDatabaseMockRecorder) GetOneToOneChat(arg0 interface{}) *gomock.Ca
|
||||
}
|
||||
|
||||
// GetPublicChat mocks base method
|
||||
func (m *MockDatabase) GetPublicChat(arg0 string) (*client.Contact, error) {
|
||||
func (m *MockDatabase) GetPublicChat(arg0 string) (*client.Chat, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetPublicChat", arg0)
|
||||
ret0, _ := ret[0].(*client.Contact)
|
||||
ret0, _ := ret[0].(*client.Chat)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
@ -124,23 +124,8 @@ func (mr *MockDatabaseMockRecorder) GetPublicChat(arg0 interface{}) *gomock.Call
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPublicChat", reflect.TypeOf((*MockDatabase)(nil).GetPublicChat), arg0)
|
||||
}
|
||||
|
||||
// Histories mocks base method
|
||||
func (m *MockDatabase) Histories() ([]client.History, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Histories")
|
||||
ret0, _ := ret[0].([]client.History)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// Histories indicates an expected call of Histories
|
||||
func (mr *MockDatabaseMockRecorder) Histories() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Histories", reflect.TypeOf((*MockDatabase)(nil).Histories))
|
||||
}
|
||||
|
||||
// LastMessageClock mocks base method
|
||||
func (m *MockDatabase) LastMessageClock(arg0 client.Contact) (int64, error) {
|
||||
func (m *MockDatabase) LastMessageClock(arg0 client.Chat) (int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "LastMessageClock", arg0)
|
||||
ret0, _ := ret[0].(int64)
|
||||
@ -155,7 +140,7 @@ func (mr *MockDatabaseMockRecorder) LastMessageClock(arg0 interface{}) *gomock.C
|
||||
}
|
||||
|
||||
// Messages mocks base method
|
||||
func (m *MockDatabase) Messages(arg0 client.Contact, arg1, arg2 time.Time) ([]*v1.Message, error) {
|
||||
func (m *MockDatabase) Messages(arg0 client.Chat, arg1, arg2 time.Time) ([]*v1.Message, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Messages", arg0, arg1, arg2)
|
||||
ret0, _ := ret[0].([]*v1.Message)
|
||||
@ -170,7 +155,7 @@ func (mr *MockDatabaseMockRecorder) Messages(arg0, arg1, arg2 interface{}) *gomo
|
||||
}
|
||||
|
||||
// NewMessages mocks base method
|
||||
func (m *MockDatabase) NewMessages(arg0 client.Contact, arg1 int64) ([]*v1.Message, error) {
|
||||
func (m *MockDatabase) NewMessages(arg0 client.Chat, arg1 int64) ([]*v1.Message, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "NewMessages", arg0, arg1)
|
||||
ret0, _ := ret[0].([]*v1.Message)
|
||||
@ -184,22 +169,22 @@ func (mr *MockDatabaseMockRecorder) NewMessages(arg0, arg1 interface{}) *gomock.
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewMessages", reflect.TypeOf((*MockDatabase)(nil).NewMessages), arg0, arg1)
|
||||
}
|
||||
|
||||
// SaveContacts mocks base method
|
||||
func (m *MockDatabase) SaveContacts(arg0 []client.Contact) error {
|
||||
// SaveChats mocks base method
|
||||
func (m *MockDatabase) SaveChats(arg0 []client.Chat) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SaveContacts", arg0)
|
||||
ret := m.ctrl.Call(m, "SaveChats", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SaveContacts indicates an expected call of SaveContacts
|
||||
func (mr *MockDatabaseMockRecorder) SaveContacts(arg0 interface{}) *gomock.Call {
|
||||
// SaveChats indicates an expected call of SaveChats
|
||||
func (mr *MockDatabaseMockRecorder) SaveChats(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveContacts", reflect.TypeOf((*MockDatabase)(nil).SaveContacts), arg0)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveChats", reflect.TypeOf((*MockDatabase)(nil).SaveChats), arg0)
|
||||
}
|
||||
|
||||
// SaveMessages mocks base method
|
||||
func (m *MockDatabase) SaveMessages(arg0 client.Contact, arg1 []*v1.Message) (int64, error) {
|
||||
func (m *MockDatabase) SaveMessages(arg0 client.Chat, arg1 []*v1.Message) (int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SaveMessages", arg0, arg1)
|
||||
ret0, _ := ret[0].(int64)
|
||||
@ -214,7 +199,7 @@ func (mr *MockDatabaseMockRecorder) SaveMessages(arg0, arg1 interface{}) *gomock
|
||||
}
|
||||
|
||||
// UnreadMessages mocks base method
|
||||
func (m *MockDatabase) UnreadMessages(arg0 client.Contact) ([]*v1.Message, error) {
|
||||
func (m *MockDatabase) UnreadMessages(arg0 client.Chat) ([]*v1.Message, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UnreadMessages", arg0)
|
||||
ret0, _ := ret[0].([]*v1.Message)
|
||||
@ -227,17 +212,3 @@ func (mr *MockDatabaseMockRecorder) UnreadMessages(arg0 interface{}) *gomock.Cal
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnreadMessages", reflect.TypeOf((*MockDatabase)(nil).UnreadMessages), arg0)
|
||||
}
|
||||
|
||||
// UpdateHistories mocks base method
|
||||
func (m *MockDatabase) UpdateHistories(arg0 []client.History) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateHistories", arg0)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpdateHistories indicates an expected call of UpdateHistories
|
||||
func (mr *MockDatabaseMockRecorder) UpdateHistories(arg0 interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateHistories", reflect.TypeOf((*MockDatabase)(nil).UpdateHistories), arg0)
|
||||
}
|
||||
|
@ -7,30 +7,30 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
errUnsupportedContactType = fmt.Errorf("unsupported contact type")
|
||||
errUnsupportedChatType = fmt.Errorf("unsupported chat type")
|
||||
)
|
||||
|
||||
func createSendOptions(c Contact) (opts protocol.SendOptions, err error) {
|
||||
opts.ChatName = c.Topic
|
||||
func createSendOptions(c Chat) (opts protocol.SendOptions, err error) {
|
||||
opts.ChatName = c.Name
|
||||
switch c.Type {
|
||||
case ContactPublicRoom:
|
||||
case ContactPrivate:
|
||||
case PublicChat:
|
||||
case OneToOneChat:
|
||||
opts.Recipient = c.PublicKey
|
||||
default:
|
||||
err = errUnsupportedContactType
|
||||
err = errUnsupportedChatType
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func enhanceRequestOptions(c Contact, opts *protocol.RequestOptions) error {
|
||||
func enhanceRequestOptions(c Chat, opts *protocol.RequestOptions) error {
|
||||
var chatOptions protocol.ChatOptions
|
||||
chatOptions.ChatName = c.Topic
|
||||
chatOptions.ChatName = c.Name
|
||||
switch c.Type {
|
||||
case ContactPublicRoom:
|
||||
case ContactPrivate:
|
||||
case PublicChat:
|
||||
case OneToOneChat:
|
||||
chatOptions.Recipient = c.PublicKey
|
||||
default:
|
||||
return errUnsupportedContactType
|
||||
return errUnsupportedChatType
|
||||
}
|
||||
|
||||
opts.Chats = append(opts.Chats, chatOptions)
|
||||
|
@ -21,36 +21,36 @@ var (
|
||||
// MessagesParams is an object with JSON-serializable parameters
|
||||
// for Messages method.
|
||||
type MessagesParams struct {
|
||||
Contact
|
||||
Chat
|
||||
}
|
||||
|
||||
// SendParams is an object with JSON-serializable parameters for Send method.
|
||||
type SendParams struct {
|
||||
Contact
|
||||
Chat
|
||||
}
|
||||
|
||||
// RequestParams is an object with JSON-serializable parameters for Request method.
|
||||
type RequestParams struct {
|
||||
Contact
|
||||
Chat
|
||||
Limit int `json:"limit"`
|
||||
From int64 `json:"from"`
|
||||
To int64 `json:"to"`
|
||||
}
|
||||
|
||||
// Contact
|
||||
type Contact struct {
|
||||
// Chat
|
||||
type Chat struct {
|
||||
Name string `json:"name"`
|
||||
PublicKey hexutil.Bytes `json:"key"`
|
||||
}
|
||||
|
||||
func parseContact(c Contact) (client.Contact, error) {
|
||||
func parseChat(c Chat) (client.Chat, error) {
|
||||
if len(c.PublicKey) != 0 {
|
||||
c, err := client.CreateContactPrivate(c.Name, c.PublicKey.String(), client.ContactAdded)
|
||||
c, err := client.CreateOneToOneChat(c.Name, c.PublicKey.String())
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
}
|
||||
return client.CreateContactPublicRoom(c.Name, client.ContactAdded), nil
|
||||
return client.CreatePublicChat(c.Name), nil
|
||||
}
|
||||
|
||||
// PublicAPI provides an JSON-RPC API to interact with
|
||||
@ -66,15 +66,15 @@ func NewPublicAPI(s *Service) *PublicAPI {
|
||||
}
|
||||
}
|
||||
|
||||
// Send sends payload to specified contact.
|
||||
// Contact should be added before sending message,
|
||||
// Send sends payload to specified chat.
|
||||
// Chat should be added before sending message,
|
||||
// otherwise error will be received.
|
||||
func (api *PublicAPI) Send(ctx context.Context, contact Contact, payload string) (hexutil.Bytes, error) {
|
||||
func (api *PublicAPI) Send(ctx context.Context, chat Chat, payload string) (hexutil.Bytes, error) {
|
||||
if api.service.messenger == nil {
|
||||
return nil, ErrMessengerNotSet
|
||||
}
|
||||
|
||||
c, err := parseContact(contact)
|
||||
c, err := parseChat(chat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -86,8 +86,7 @@ func (api *PublicAPI) Request(ctx context.Context, params RequestParams) (err er
|
||||
if api.service.messenger == nil {
|
||||
return ErrMessengerNotSet
|
||||
}
|
||||
|
||||
c, err := parseContact(params.Contact)
|
||||
c, err := parseChat(params.Chat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -100,9 +99,9 @@ func (api *PublicAPI) Request(ctx context.Context, params RequestParams) (err er
|
||||
}
|
||||
|
||||
// Messages is a high-level subscription-based RPC method.
|
||||
// It joins a chat for selected contact and streams
|
||||
// It joins a chat for selected chat and streams
|
||||
// events for that chat.
|
||||
func (api *PublicAPI) Messages(ctx context.Context, contact client.Contact) (*rpc.Subscription, error) {
|
||||
func (api *PublicAPI) Messages(ctx context.Context, chat client.Chat) (*rpc.Subscription, error) {
|
||||
notifier, supported := rpc.NotifierFromContext(ctx)
|
||||
if !supported {
|
||||
return nil, rpc.ErrNotificationsUnsupported
|
||||
@ -120,9 +119,9 @@ func (api *PublicAPI) Messages(ctx context.Context, contact client.Contact) (*rp
|
||||
|
||||
// Subscription needs to be created
|
||||
// before any events are delivered.
|
||||
sub := api.broadcaster.Subscribe(contact)
|
||||
sub := api.broadcaster.Subscribe(chat)
|
||||
|
||||
err := api.service.messenger.Join(ctx, contact)
|
||||
err := api.service.messenger.Join(ctx, chat)
|
||||
if err != nil {
|
||||
api.broadcaster.Unsubscribe(sub)
|
||||
return nil, err
|
||||
@ -132,9 +131,9 @@ func (api *PublicAPI) Messages(ctx context.Context, contact client.Contact) (*rp
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
err := api.service.messenger.Leave(contact)
|
||||
err := api.service.messenger.Leave(chat)
|
||||
if err != nil {
|
||||
log.Printf("failed to leave chat for '%s' contact", contact)
|
||||
log.Printf("failed to leave chat for '%s' chat", chat)
|
||||
}
|
||||
}()
|
||||
defer api.broadcaster.Unsubscribe(sub)
|
||||
@ -170,16 +169,16 @@ func (api *PublicAPI) RequestAll(ctx context.Context, newest bool) error {
|
||||
return api.service.messenger.RequestAll(ctx, newest)
|
||||
}
|
||||
|
||||
// AddContact will ensure that contact is added to messenger database and new stream spawned for a contact if needed.
|
||||
func (api *PublicAPI) AddContact(ctx context.Context, contact Contact) (err error) {
|
||||
// AddChat will ensure that chat is added to messenger database and new stream spawned for a chat if needed.
|
||||
func (api *PublicAPI) AddChat(ctx context.Context, chat Chat) (err error) {
|
||||
if api.service.messenger == nil {
|
||||
return ErrMessengerNotSet
|
||||
}
|
||||
c, err := parseContact(contact)
|
||||
c, err := parseChat(chat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = api.service.messenger.AddContact(c)
|
||||
err = api.service.messenger.AddChat(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -188,11 +187,11 @@ func (api *PublicAPI) AddContact(ctx context.Context, contact Contact) (err erro
|
||||
|
||||
// ReadContactMessages read contact messages starting from offset.
|
||||
// To read all offset should be zero. To read only new set offset to total number of previously read messages.
|
||||
func (api *PublicAPI) ReadContactMessages(ctx context.Context, contact Contact, offset int64) (rst []*protocol.Message, err error) {
|
||||
func (api *PublicAPI) ReadChatMessages(ctx context.Context, chat Chat, offset int64) (rst []*protocol.Message, err error) {
|
||||
if api.service.messenger == nil {
|
||||
return nil, ErrMessengerNotSet
|
||||
}
|
||||
c, err := parseContact(contact)
|
||||
c, err := parseChat(chat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ func TestPublicAPISend(t *testing.T) {
|
||||
defer func() { go discardStop(aNode) }() // Stop() is slow so do it in a goroutine
|
||||
|
||||
data := []byte("some payload")
|
||||
contact := Contact{
|
||||
contact := Chat{
|
||||
Name: "test-chat",
|
||||
}
|
||||
result := hexutil.Bytes("abc")
|
||||
@ -84,7 +84,7 @@ func TestPublicAPIRequest(t *testing.T) {
|
||||
|
||||
now := time.Now().Unix()
|
||||
params := RequestParams{
|
||||
Contact: Contact{
|
||||
Chat: Chat{
|
||||
Name: "test-chat",
|
||||
},
|
||||
Limit: 100,
|
||||
@ -126,6 +126,8 @@ func TestPublicAPIMessages(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer func() { go discardStop(aNode) }() // Stop() is slow so do it in a goroutine
|
||||
|
||||
database.EXPECT().SaveChats(gomock.Any()).Return(nil)
|
||||
|
||||
proto.EXPECT().
|
||||
LoadChats(
|
||||
gomock.Any(),
|
||||
@ -135,17 +137,9 @@ func TestPublicAPIMessages(t *testing.T) {
|
||||
).
|
||||
Return(nil)
|
||||
|
||||
proto.EXPECT().
|
||||
Request(gomock.Any(), gomock.Any()).
|
||||
Return(nil)
|
||||
|
||||
database.EXPECT().
|
||||
UpdateHistories(gomock.Any()).
|
||||
Return(nil)
|
||||
|
||||
messages := make(chan protocol.Message)
|
||||
// The first argument is a name of the method to use for subscription.
|
||||
_, err = rpcClient.Subscribe(context.Background(), StatusSecureMessagingProtocolAPIName, messages, "messages", client.Contact{Type: 1, Name: "test-chat"})
|
||||
_, err = rpcClient.Subscribe(context.Background(), StatusSecureMessagingProtocolAPIName, messages, "messages", client.Chat{Type: 1, Name: "test-chat"})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
|
@ -11,14 +11,14 @@ type publisher interface {
|
||||
|
||||
type broadcaster struct {
|
||||
source publisher
|
||||
subs map[client.Contact][]chan interface{}
|
||||
subs map[client.Chat][]chan interface{}
|
||||
cancel chan struct{}
|
||||
}
|
||||
|
||||
func newBroadcaster(source publisher) *broadcaster {
|
||||
b := broadcaster{
|
||||
source: source,
|
||||
subs: make(map[client.Contact][]chan interface{}),
|
||||
subs: make(map[client.Chat][]chan interface{}),
|
||||
cancel: make(chan struct{}),
|
||||
}
|
||||
|
||||
@ -36,8 +36,8 @@ func (b *broadcaster) start(cancel chan struct{}) {
|
||||
var subs []chan interface{}
|
||||
|
||||
switch v := item.Interface.(type) {
|
||||
case client.EventWithContact:
|
||||
subs = b.subs[v.GetContact()]
|
||||
case client.EventWithChat:
|
||||
subs = b.subs[v.GetChat()]
|
||||
}
|
||||
|
||||
// TODO: figure out if we need anything here
|
||||
@ -54,7 +54,7 @@ func (b *broadcaster) start(cancel chan struct{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func (b *broadcaster) Subscribe(c client.Contact) <-chan interface{} {
|
||||
func (b *broadcaster) Subscribe(c client.Chat) <-chan interface{} {
|
||||
// TODO: think about whether this can be a buffered channel.
|
||||
sub := make(chan interface{})
|
||||
b.subs[c] = append(b.subs[c], sub)
|
||||
|
Loading…
x
Reference in New Issue
Block a user