diff --git a/cli/cli.go b/cli/cli.go index 8aa3de3..e5fcef1 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -6,7 +6,6 @@ import ( "golang.org/x/net/context" "google.golang.org/grpc" "gopkg.in/readline.v1" - "io" "log" "strings" ) @@ -15,35 +14,6 @@ const ( defaultAddress = "127.0.0.1:58281" ) -type Client struct { - Backend rpc.RicochetCoreClient - Input *readline.Instance -} - -func (c *Client) Initialize() error { - stream, err := c.Backend.MonitorNetwork(context.Background(), &rpc.MonitorNetworkRequest{}) - if err != nil { - return err - } - - go func() { - for { - resp, err := stream.Recv() - if err == io.EOF { - log.Printf("stream eof") - break - } - if err != nil { - log.Printf("stream error: %v", err) - break - } - log.Printf("stream says: %v", resp) - } - }() - - return nil -} - func main() { conn, err := grpc.Dial(defaultAddress, grpc.WithInsecure()) if err != nil { @@ -80,20 +50,10 @@ func main() { switch words[0] { case "clear": readline.ClearScreen(readline.Stdout) - case "status": - r, err := c.Backend.GetServerStatus(context.Background(), &rpc.ServerStatusRequest{ - RpcVersion: 1, - }) - if err != nil { - log.Fatalf("could not get status: %v", err) - } - log.Printf("get status result: %v", r) - identity, err := c.Backend.GetIdentity(context.Background(), &rpc.IdentityRequest{}) - if err != nil { - log.Fatalf("could not get identity: %v", err) - } - log.Printf("identity: %v", identity) + case "status": + log.Printf("server: %v", c.ServerStatus) + log.Printf("identity: %v", c.Identity) case "connect": status, err := c.Backend.StartNetwork(context.Background(), &rpc.StartNetworkRequest{}) @@ -102,6 +62,7 @@ func main() { } else { log.Printf("network started: %v", status) } + case "disconnect": status, err := c.Backend.StopNetwork(context.Background(), &rpc.StopNetworkRequest{}) if err != nil { @@ -109,27 +70,17 @@ func main() { } else { log.Printf("network stopped: %v", status) } + case "contacts": - stream, err := c.Backend.MonitorContacts(context.Background(), &rpc.MonitorContactsRequest{}) - if err != nil { - log.Printf("contacts error: %v", err) - } else { - for { - event, err := stream.Recv() - if err == io.EOF { - break - } - if err != nil { - log.Printf("contacts error: %v", err) - break - } - log.Printf("contact event: %v", event) - } + for _, contact := range c.Contacts { + log.Printf(" %s (%s)", contact.Nickname, contact.Status.String()) } + case "help": fallthrough + default: - fmt.Println("Commands: clear, status, connect, disconnect, help") + fmt.Println("Commands: clear, status, connect, disconnect, contacts, help") } } } diff --git a/cli/client.go b/cli/client.go new file mode 100644 index 0000000..736fcab --- /dev/null +++ b/cli/client.go @@ -0,0 +1,179 @@ +package main + +import ( + "fmt" + "github.com/special/notricochet/rpc" + "golang.org/x/net/context" + "gopkg.in/readline.v1" + "log" +) + +type Client struct { + Backend ricochet.RicochetCoreClient + Input *readline.Instance + + ServerStatus ricochet.ServerStatusReply + Identity ricochet.Identity + + // XXX threadsafety + NetworkStatus ricochet.NetworkStatus + Contacts []*ricochet.Contact +} + +// XXX need to handle backend connection loss/reconnection.. +func (c *Client) Initialize() error { + // Query server status and version + status, err := c.Backend.GetServerStatus(context.Background(), &ricochet.ServerStatusRequest{ + RpcVersion: 1, + }) + if err != nil { + return err + } + c.ServerStatus = *status + if status.RpcVersion != 1 { + return fmt.Errorf("Unsupported backend RPC version %d", status.RpcVersion) + } + + // Query identity + identity, err := c.Backend.GetIdentity(context.Background(), &ricochet.IdentityRequest{}) + if err != nil { + return err + } + c.Identity = *identity + + // Spawn routines to query and monitor state changes + go c.monitorNetwork() + go c.monitorContacts() + + // XXX block until populated/initialized? + return nil +} + +func (c *Client) monitorNetwork() { + stream, err := c.Backend.MonitorNetwork(context.Background(), &ricochet.MonitorNetworkRequest{}) + if err != nil { + log.Printf("Initializing network status monitor failed: %v", err) + // XXX handle + return + } + + for { + status, err := stream.Recv() + if err != nil { + log.Printf("Network status monitor error: %v", err) + // XXX handle + break + } + + log.Printf("Network status changed: %v", status) + c.NetworkStatus = *status + } +} + +func (c *Client) monitorContacts() { + stream, err := c.Backend.MonitorContacts(context.Background(), &ricochet.MonitorContactsRequest{}) + if err != nil { + log.Printf("Initializing contact status monitor failed: %v", err) + // XXX handle + return + } + + // Populate initial contacts list + for { + event, err := stream.Recv() + if err != nil { + log.Printf("Contact populate error: %v", err) + // XXX handle + break + } + + if event.Type != ricochet.ContactEvent_POPULATE { + log.Printf("Ignoring unexpected contact event during populate: %v", event) + continue + } + + // Populate is terminated by a nil subject + if event.Subject == nil { + break + } + + if contact := event.GetContact(); contact != nil { + c.Contacts = append(c.Contacts, contact) + } else if request := event.GetRequest(); request != nil { + // XXX handle requests + log.Printf("XXX contact requests not supported") + } else { + log.Printf("XXX invalid event") + } + } + + log.Printf("Loaded %d contacts", len(c.Contacts)) + + for { + event, err := stream.Recv() + if err != nil { + log.Printf("Contact status monitor error: %v", err) + // XXX handle + break + } + + contact := event.GetContact() + + switch event.Type { + case ricochet.ContactEvent_ADD: + if contact == nil { + log.Printf("Ignoring contact add event with null contact") + continue + } + c.Contacts = append(c.Contacts, contact) + log.Printf("new contact: %v", contact) + + case ricochet.ContactEvent_UPDATE: + if contact == nil { + log.Printf("Ignoring contact update event with null contact") + 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) + } else { + log.Printf("updated contact: %v", contact) + } + + case ricochet.ContactEvent_DELETE: + if contact == nil { + log.Printf("Ignoring contact delete event with null contact") + 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 + } + } + + if !found { + log.Printf("Ignoring contact delete event for unknown contact: %v", contact) + } else { + log.Printf("deleted contact: %v", contact) + } + + default: + log.Printf("Ignoring unknown contact event: %v", event) + } + } +}