From c089cf3e3451a75b8b3983d7e5d6e95c43d4746d Mon Sep 17 00:00:00 2001 From: John Brooks Date: Sat, 15 Oct 2016 21:13:02 -0600 Subject: [PATCH] cli: Better tracking of contact list state --- cli/cli.go | 26 ++++++-------- cli/client.go | 92 ++++++++++++++++---------------------------------- cli/contact.go | 89 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 79 deletions(-) create mode 100644 cli/contact.go diff --git a/cli/cli.go b/cli/cli.go index 8262e92..2776f6d 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -54,16 +54,10 @@ func main() { words := strings.SplitN(line.Line, " ", 1) if id, err := strconv.Atoi(words[0]); err == nil { - found := false - for _, contact := range c.Contacts { - if int(contact.Id) == id { - c.SetCurrentContact(contact) - found = true - break - } - } - - if !found { + contact := c.Contacts.ById(int32(id)) + if contact != nil { + c.SetCurrentContact(contact) + } else { fmt.Printf("no contact %d\n", id) } continue @@ -76,8 +70,8 @@ func main() { _, err := c.Backend.SendMessage(context.Background(), &rpc.Message{ Sender: &rpc.Entity{IsSelf: true}, Recipient: &rpc.Entity{ - ContactId: c.CurrentContact.Id, - Address: c.CurrentContact.Address, + ContactId: c.CurrentContact.Data.Id, + Address: c.CurrentContact.Data.Address, }, Text: line.Line, }) @@ -116,9 +110,9 @@ func main() { } case "contacts": - byStatus := make(map[rpc.Contact_Status][]*rpc.Contact) - for _, contact := range c.Contacts { - byStatus[contact.Status] = append(byStatus[contact.Status], contact) + byStatus := make(map[rpc.Contact_Status][]*Contact) + for _, contact := range c.Contacts.Contacts { + byStatus[contact.Data.Status] = append(byStatus[contact.Data.Status], contact) } order := []rpc.Contact_Status{rpc.Contact_ONLINE, rpc.Contact_UNKNOWN, rpc.Contact_OFFLINE, rpc.Contact_REQUEST, rpc.Contact_REJECTED} @@ -129,7 +123,7 @@ func main() { } fmt.Printf(". %s\n", strings.ToLower(status.String())) for _, contact := range contacts { - fmt.Printf("... [%d] %s\n", contact.Id, contact.Nickname) + fmt.Printf("... [%d] %s\n", contact.Data.Id, contact.Data.Nickname) } } diff --git a/cli/client.go b/cli/client.go index 8447f3c..345a71e 100644 --- a/cli/client.go +++ b/cli/client.go @@ -17,14 +17,15 @@ type Client struct { Identity ricochet.Identity // XXX threadsafety - NetworkStatus ricochet.NetworkStatus - Contacts []*ricochet.Contact - - CurrentContact *ricochet.Contact + NetworkStatus ricochet.NetworkStatus + Contacts *ContactList + CurrentContact *Contact } // XXX need to handle backend connection loss/reconnection.. func (c *Client) Initialize() error { + c.Contacts = NewContactList() + // Query server status and version status, err := c.Backend.GetServerStatus(context.Background(), &ricochet.ServerStatusRequest{ RpcVersion: 1, @@ -53,14 +54,14 @@ func (c *Client) Initialize() error { return nil } -func (c *Client) SetCurrentContact(contact *ricochet.Contact) { +func (c *Client) SetCurrentContact(contact *Contact) { c.CurrentContact = contact if c.CurrentContact != nil { config := *c.Input.Config - config.Prompt = fmt.Sprintf("%s > ", c.CurrentContact.Nickname) + config.Prompt = fmt.Sprintf("%s > ", c.CurrentContact.Data.Nickname) config.UniqueEditLine = true c.Input.SetConfig(&config) - fmt.Printf("--- %s (%s) ---\n", c.CurrentContact.Nickname, strings.ToLower(c.CurrentContact.Status.String())) + fmt.Printf("--- %s (%s) ---\n", c.CurrentContact.Data.Nickname, strings.ToLower(c.CurrentContact.Data.Status.String())) } else { config := *c.Input.Config config.Prompt = "> " @@ -118,7 +119,7 @@ func (c *Client) monitorContacts() { } if contact := event.GetContact(); contact != nil { - c.Contacts = append(c.Contacts, contact) + c.Contacts.Populate(contact) } else if request := event.GetRequest(); request != nil { // XXX handle requests log.Printf("XXX contact requests not supported") @@ -127,7 +128,7 @@ func (c *Client) monitorContacts() { } } - log.Printf("Loaded %d contacts", len(c.Contacts)) + log.Printf("Loaded %d contacts", len(c.Contacts.Contacts)) for { event, err := stream.Recv() @@ -137,66 +138,39 @@ func (c *Client) monitorContacts() { break } - contact := event.GetContact() + data := event.GetContact() switch event.Type { case ricochet.ContactEvent_ADD: - if contact == nil { - log.Printf("Ignoring contact add event with null contact") + if data == nil { + log.Printf("Ignoring contact add event with null data") continue } - c.Contacts = append(c.Contacts, contact) - log.Printf("new contact: %v", contact) + + c.Contacts.Added(data) case ricochet.ContactEvent_UPDATE: - if contact == nil { - log.Printf("Ignoring contact update event with null contact") + if data == nil { + log.Printf("Ignoring contact update event with null data") continue } - var found bool - for i, match := range c.Contacts { - if match.Id == contact.Id && match.Address == contact.Address { - contacts := append(c.Contacts[0:i], contact) - contacts = append(contacts, c.Contacts[i+1:]...) - c.Contacts = contacts - found = true - break - } - } - - if !found { - log.Printf("Ignoring contact update event for unknown contact: %v", contact) + contact := c.Contacts.ByIdAndAddress(data.Id, data.Address) + if contact == nil { + log.Printf("Ignoring contact update event for unknown contact: %v", data) } else { - log.Printf("updated contact: %v", contact) - } - - if c.CurrentContact != nil && c.CurrentContact.Id == contact.Id { - c.SetCurrentContact(contact) + contact.Updated(data) } case ricochet.ContactEvent_DELETE: - if contact == nil { - log.Printf("Ignoring contact delete event with null contact") + if data == nil { + log.Printf("Ignoring contact delete event with null data") continue } - var found bool - for i, match := range c.Contacts { - if match.Id == contact.Id && match.Address == contact.Address { - c.Contacts = append(c.Contacts[0:i], c.Contacts[i+1:]...) - found = true - break - } - } + contact, _ := c.Contacts.Deleted(data) - if !found { - log.Printf("Ignoring contact delete event for unknown contact: %v", contact) - } else { - log.Printf("deleted contact: %v", contact) - } - - if c.CurrentContact != nil && c.CurrentContact.Id == contact.Id { + if c.CurrentContact == contact { c.SetCurrentContact(nil) } @@ -236,22 +210,14 @@ func (c *Client) monitorConversations() { continue } - var remoteContact *ricochet.Contact var remoteEntity *ricochet.Entity - if !message.Sender.IsSelf { remoteEntity = message.Sender } else { remoteEntity = message.Recipient } - for _, contact := range c.Contacts { - if remoteEntity.ContactId == contact.Id && remoteEntity.Address == contact.Address { - remoteContact = contact - break - } - } - + remoteContact := c.Contacts.ByIdAndAddress(remoteEntity.ContactId, remoteEntity.Address) if remoteContact == nil { log.Printf("Ignoring conversation event with unknown contact: %v", event) continue @@ -260,12 +226,12 @@ func (c *Client) monitorConversations() { if remoteContact == c.CurrentContact { // XXX so unsafe if message.Sender.IsSelf { - fmt.Fprintf(c.Input.Stdout(), "\r%s > %s\n", remoteContact.Nickname, message.Text) + fmt.Fprintf(c.Input.Stdout(), "\r%s > %s\n", remoteContact.Data.Nickname, message.Text) } else { - fmt.Fprintf(c.Input.Stdout(), "\r%s < %s\n", remoteContact.Nickname, message.Text) + fmt.Fprintf(c.Input.Stdout(), "\r%s < %s\n", remoteContact.Data.Nickname, message.Text) } } else if !message.Sender.IsSelf { - fmt.Fprintf(c.Input.Stdout(), "\r---- %s < %s\n", remoteContact.Nickname, message.Text) + fmt.Fprintf(c.Input.Stdout(), "\r---- %s < %s\n", remoteContact.Data.Nickname, message.Text) } if !message.Sender.IsSelf { diff --git a/cli/contact.go b/cli/contact.go new file mode 100644 index 0000000..5e65e63 --- /dev/null +++ b/cli/contact.go @@ -0,0 +1,89 @@ +package main + +import ( + "errors" + "fmt" + "github.com/special/notricochet/rpc" +) + +type ContactList struct { + Contacts map[int32]*Contact +} + +func NewContactList() *ContactList { + return &ContactList{ + Contacts: make(map[int32]*Contact), + } +} + +func (cl *ContactList) Populate(data *ricochet.Contact) error { + if cl.Contacts[data.Id] != nil { + return fmt.Errorf("Duplicate contact ID %d in populate", data.Id) + } + + cl.Contacts[data.Id] = initContact(data) + return nil +} + +func (cl *ContactList) Added(data *ricochet.Contact) (*Contact, error) { + if cl.Contacts[data.Id] != nil { + return nil, fmt.Errorf("Duplicate contact ID %d in add", data.Id) + } + + contact := initContact(data) + cl.Contacts[data.Id] = contact + return contact, nil +} + +func (cl *ContactList) Deleted(data *ricochet.Contact) (*Contact, error) { + contact := cl.Contacts[data.Id] + if contact == nil { + return nil, fmt.Errorf("Contact ID %d does not exist in delete", data.Id) + } + + if contact.Data.Address != data.Address { + return nil, fmt.Errorf("Contact ID %d does not match address in delete (expected %s, received %s)", data.Id, contact.Data.Address, data.Address) + } + + contact.Deleted() + delete(cl.Contacts, data.Id) + return contact, nil +} + +func (cl *ContactList) ById(id int32) *Contact { + return cl.Contacts[id] +} + +func (cl *ContactList) ByIdAndAddress(id int32, address string) *Contact { + contact := cl.Contacts[id] + if contact != nil && contact.Data.Address == address { + return contact + } + return nil +} + +type Contact struct { + Data *ricochet.Contact +} + +func initContact(data *ricochet.Contact) *Contact { + return &Contact{ + Data: data, + } +} + +func (c *Contact) Updated(newData *ricochet.Contact) error { + if newData.Id != c.Data.Id || newData.Address != c.Data.Address { + return errors.New("Contact ID and address are immutable") + } + + c.Data = newData + return nil +} + +func (c *Contact) Deleted() { + c.Data = &ricochet.Contact{ + Id: c.Data.Id, + Address: c.Data.Address, + } +}