/////////////////////////////////////////////////////////////////////////////// // Copyright © 2020 xx network SEZC // // // // Use of this source code is governed by a license that can be found in the // // LICENSE file // /////////////////////////////////////////////////////////////////////////////// package groupChat import ( "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/api" gs "gitlab.com/elixxir/client/groupChat/groupStore" "gitlab.com/elixxir/client/interfaces" "gitlab.com/elixxir/client/interfaces/message" "gitlab.com/elixxir/client/interfaces/preimage" "gitlab.com/elixxir/client/stoppable" "gitlab.com/elixxir/client/storage" "gitlab.com/elixxir/client/storage/edge" "gitlab.com/elixxir/client/storage/versioned" "gitlab.com/elixxir/crypto/cyclic" "gitlab.com/elixxir/crypto/fastRNG" "gitlab.com/elixxir/crypto/group" "gitlab.com/xx_network/primitives/id" ) const ( rawMessageBuffSize = 100 receiveStoppableName = "GroupChatReceive" receiveListenerName = "GroupChatReceiveListener" requestStoppableName = "GroupChatRequest" requestListenerName = "GroupChatRequestListener" groupStoppableName = "GroupChat" ) // Error messages. const ( newGroupStoreErr = "failed to create new group store: %+v" joinGroupErr = "failed to join new group %s: %+v" leaveGroupErr = "failed to leave group %s: %+v" ) // Manager handles the list of groups a user is a part of. type Manager struct { client *api.Client store *storage.Session swb interfaces.Switchboard net interfaces.NetworkManager rng *fastRNG.StreamGenerator gs *gs.Store requestFunc RequestCallback receiveFunc ReceiveCallback } // NewManager generates a new group chat manager. This functions satisfies the // GroupChat interface. func NewManager(client *api.Client, requestFunc RequestCallback, receiveFunc ReceiveCallback) (*Manager, error) { return newManager( client, client.GetUser().ReceptionID.DeepCopy(), client.GetStorage().E2e().GetDHPublicKey(), client.GetStorage(), client.GetSwitchboard(), client.GetNetworkInterface(), client.GetRng(), client.GetStorage().GetKV(), requestFunc, receiveFunc, ) } // newManager creates a new group chat manager from api.Client parts for easier // testing. func newManager(client *api.Client, userID *id.ID, userDhKey *cyclic.Int, store *storage.Session, swb interfaces.Switchboard, net interfaces.NetworkManager, rng *fastRNG.StreamGenerator, kv *versioned.KV, requestFunc RequestCallback, receiveFunc ReceiveCallback) (*Manager, error) { // Load the group chat storage or create one if one does not exist gStore, err := gs.NewOrLoadStore( kv, group.Member{ID: userID, DhKey: userDhKey}) if err != nil { return nil, errors.Errorf(newGroupStoreErr, err) } return &Manager{ client: client, store: store, swb: swb, net: net, rng: rng, gs: gStore, requestFunc: requestFunc, receiveFunc: receiveFunc, }, nil } // StartProcesses starts the reception worker. func (m *Manager) StartProcesses() (stoppable.Stoppable, error) { // Start group reception worker receiveStop := stoppable.NewSingle(receiveStoppableName) receiveChan := make(chan message.Receive, rawMessageBuffSize) m.swb.RegisterChannel(receiveListenerName, &id.ID{}, message.Raw, receiveChan) go m.receive(receiveChan, receiveStop) // Start group request worker requestStop := stoppable.NewSingle(requestStoppableName) requestChan := make(chan message.Receive, rawMessageBuffSize) m.swb.RegisterChannel(requestListenerName, &id.ID{}, message.GroupCreationRequest, requestChan) go m.receiveRequest(requestChan, requestStop) // Create a multi stoppable multiStoppable := stoppable.NewMulti(groupStoppableName) multiStoppable.Add(receiveStop) multiStoppable.Add(requestStop) return multiStoppable, nil } // JoinGroup adds the group to the list of group chats the user is a part of. // An error is returned if the user is already part of the group or if the // maximum number of groups have already been joined. func (m Manager) JoinGroup(g gs.Group) error { if err := m.gs.Add(g); err != nil { return errors.Errorf(joinGroupErr, g.ID, err) } edgeStore := m.store.GetEdge() edgeStore.Add(edge.Preimage{ Data: g.ID[:], Type: preimage.Group, Source: g.ID[:], }, m.store.GetUser().ReceptionID) jww.DEBUG.Printf("Joined group %q with ID %s.", g.Name, g.ID) return nil } // LeaveGroup removes a group from a list of groups the user is a part of. func (m Manager) LeaveGroup(groupID *id.ID) error { if err := m.gs.Remove(groupID); err != nil { return errors.Errorf(leaveGroupErr, groupID, err) } edgeStore := m.store.GetEdge() err := edgeStore.Remove(edge.Preimage{ Data: groupID[:], Type: preimage.Group, Source: groupID[:], }, m.store.GetUser().ReceptionID) jww.DEBUG.Printf("Left group with ID %s.", groupID) return err } // GetGroups returns a list of all registered groupChat IDs. func (m Manager) GetGroups() []*id.ID { jww.DEBUG.Print("Getting list of all groups.") return m.gs.GroupIDs() } // GetGroup returns the group with the matching ID or returns false if none // exist. func (m Manager) GetGroup(groupID *id.ID) (gs.Group, bool) { jww.DEBUG.Printf("Getting group with ID %s.", groupID) return m.gs.Get(groupID) } // NumGroups returns the number of groups the user is a part of. func (m Manager) NumGroups() int { return m.gs.Len() }