diff --git a/bindings/client.go b/bindings/client.go index e543b80428c9b6ac9a5439c0534142bd8a1d17f1..e0d9a8782cc14c3e96b616a669c156ce60c48297 100644 --- a/bindings/client.go +++ b/bindings/client.go @@ -13,6 +13,11 @@ import ( "encoding/json" "errors" "fmt" + "runtime/pprof" + "strings" + "sync" + "time" + jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/api" "gitlab.com/elixxir/client/interfaces/message" @@ -24,10 +29,6 @@ import ( "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/netTime" "google.golang.org/grpc/grpclog" - "runtime/pprof" - "strings" - "sync" - "time" ) var extantClient bool @@ -572,6 +573,12 @@ func (c *Client) getSingle() (*single.Manager, error) { return c.single, nil } +// GetInternalClient returns a reference to the client api. This is for internal +// use only and should not be called by bindings clients. +func (c *Client) GetInternalClient() api.Client { + return c.api +} + // DumpStack returns a string with the stack trace of every running thread. func DumpStack() (string, error) { buf := new(bytes.Buffer) diff --git a/xxmutils/README.md b/xxmutils/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a817417b4cd7297e090ff556388333b155fc761f --- /dev/null +++ b/xxmutils/README.md @@ -0,0 +1,7 @@ += XX Messenger Utility Functions + +xxDK users should not use these functions. These functions are used by +the mobile phone apps and are not intended to be part of the xxDK. They +should be treated as internal functions specific to the phone apps. + +These functions are subject to change without notice. diff --git a/xxmutils/restoreContacts.go b/xxmutils/restoreContacts.go new file mode 100644 index 0000000000000000000000000000000000000000..95c8eefb101d0156113af595f0476a35a30e42c1 --- /dev/null +++ b/xxmutils/restoreContacts.go @@ -0,0 +1,118 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright © 2020 xx network SEZC // +// // +// Use of this source code is governed by a license that can be found in the // +// LICENSE file // +/////////////////////////////////////////////////////////////////////////////// + +package xxmutils + +import ( + "encoding/json" + "fmt" + "time" + + "gitlab.com/elixxir/client/bindings" + "gitlab.com/elixxir/crypto/contact" + "gitlab.com/xx_network/primitives/id" +) + +// RestoreContactsUpdater interface provides a callback funciton +// for receiving update information from RestoreContactsFromBackup. +type RestoreContactsUpdater interface { + // RestoreContactsCallback is called to report the current # of contacts + // that are done processing against the total number that need to be + // processed. If an error occurs it it set on the err variable as a + // plain string. + RestoreContactsCallback(current, total int, err string) +} + +// RestoreContactsFromBackup takes as input the jason output of the +// `NewClientFromBackup` function, unmarshals it into IDs, looks up +// each ID in user discovery, and initiates a session reset request. +// This function will not return until every id in the list has been sent a +// request. It should be called again and again until it completes. +func RestoreContactsFromBackup(backupPartnerIDs []byte, client *bindings.Client, + udManager *bindings.UserDiscovery, + updatesCb RestoreContactsUpdater) error { + api := client.GetInternalClient() + + // Unmarshal IDs + var idList []*id.ID + if err := json.Unmarshal(backupPartnerIDs, &idList); err != nil { + return err + } + + var idsToLookup []*id.ID + for id := range idList { + // TODO: Check storage for ID, if present, skip + // if !IDRestored(id) { + idsToLookup := append(idsToLookup, id) + //} + } + idList = idsToLookup + + // Start looking up contacts with user discovery and feed this + // contacts channel. + contactsCh := LookupContacts(idList, client, udManager) + + // Send a reset for each contact we looked up + cnt := 0 + total := len(idList) + msg := "Restored from backup" + me := api.GetUser() + for contact := range contactsCh { + cnt += 1 + // Report lookup failures + if contact.DhPubKey == nil { + updatesCb.RestoreContactsCallback(cnt, total, + fmt.Sprintf("ID %s could not be found in "+ + " User Discovery.", contact.ID)) + continue + } + _, err := api.ResetSession(contact, me, msg) + if err != nil { + jww.WARN.Printf("Could not reset: %v", err) + // TODO: Add contact object back into channel? + // other retry logic? + continue + } + + // TODO: Mark ID done in storage + } + + return nil +} + +type lookupcb struct { + contactsCh chan *contact.Contact +} + +func (l lookupcb) Callback(contact *bindings.Contact, err string) { + if err != nil && err != "" { + jww.WARN.Printf("Restoring contact: %s", err) + } + l.contactsCh <- contact +} + +// LookupContacts starts a thread that looks up the contacts for each user +// in the idList and returns their contact object. It returns a buffered channel +// of contact objects which can be used to reset sessions. If a user cannot be +// found in user discovery, it returns a contact with an empty DhPubKey. +func LookupContacts(idList []*id.ID, udManager *bindings.UserDiscovery) chan *contact.Contact { + contactsCh := make(chan *contact.Contact, len(idList)) + timeout := int(time.Duration(90 * time.Second)) + + lookup := lookupcb{contactsCh: contactsCh} + + // TODO: + // numConcurrent := 8 + // then only run 8 at a time... + for _, uid := range idList { + go func() { + udManager.Lookup(uid[:], lookup, timeout) + }() + } + + return contactsCh +}