diff --git a/internal/payload/outgoing.go b/internal/payload/outgoing.go index b34e7fdca..2cd145e55 100644 --- a/internal/payload/outgoing.go +++ b/internal/payload/outgoing.go @@ -95,6 +95,20 @@ func ChannelView(v *sam.ChannelView) *outgoing.ChannelView { } } +func ChannelJoin(channelID, userID uint64) *outgoing.ChannelJoin { + return &outgoing.ChannelJoin{ + ID: Uint64toa(channelID), + UserID: Uint64toa(userID), + } +} + +func ChannelPart(channelID, userID uint64) *outgoing.ChannelPart { + return &outgoing.ChannelPart{ + ID: Uint64toa(channelID), + UserID: Uint64toa(userID), + } +} + func User(user *auth.User) *outgoing.User { if user == nil { return nil diff --git a/internal/payload/outgoing/channel.go b/internal/payload/outgoing/channel.go index 0b9de561e..94f9e182e 100644 --- a/internal/payload/outgoing/channel.go +++ b/internal/payload/outgoing/channel.go @@ -22,14 +22,6 @@ type ( UserID string `json:"uid"` } - ChannelDeleted struct { - // Channel that was deleted - ID string `json:"id"` - - // Who deleted it - UserID string `json:"uid"` - } - Channel struct { // Channel to part (nil) for ALL channels ID string `json:"ID"` diff --git a/internal/payload/outgoing/payload.go b/internal/payload/outgoing/payload.go index 17eef9cf5..422b95008 100644 --- a/internal/payload/outgoing/payload.go +++ b/internal/payload/outgoing/payload.go @@ -10,11 +10,10 @@ type ( *Message `json:"message,omitempty"` *MessageSet `json:"messages,omitempty"` - *ChannelJoin `json:"channelJoin,omitempty"` - *ChannelPart `json:"channelPart,omitempty"` - *ChannelDeleted `json:"channelDeleted,omitempty"` - *Channel `json:"channel,omitempty"` - *ChannelSet `json:"channels,omitempty"` + *ChannelJoin `json:"channelJoin,omitempty"` + *ChannelPart `json:"channelPart,omitempty"` + *Channel `json:"channel,omitempty"` + *ChannelSet `json:"channels,omitempty"` *ChannelMember `json:"channelMember,omitempty"` *ChannelMemberSet `json:"channelMembers,omitempty"` diff --git a/sam/service/channel.go b/sam/service/channel.go index 08ceab14d..a59ca913f 100644 --- a/sam/service/channel.go +++ b/sam/service/channel.go @@ -3,6 +3,7 @@ package service import ( "context" "fmt" + "time" "github.com/pkg/errors" "github.com/titpetric/factory" @@ -264,6 +265,12 @@ func (svc *channel) Create(in *types.Channel) (out *types.Channel, err error) { return } + // When broadcasting, make sure we let the subscribers know + // that creator is also a member... + out.Members = append(out.Members, chCreatorID) + + svc.evl.Join(chCreatorID, out.ID) + // Create the first message, doing this directly with repository to circumvent // message service constraints svc.scheduleSystemMessage( @@ -364,6 +371,9 @@ func (svc *channel) Delete(id uint64) error { if ch.DeletedAt != nil { return errors.New("Channel already deleted") + } else { + now := time.Now() + ch.DeletedAt = &now } svc.scheduleSystemMessage(ch, "@%d deleted this channel", userID) @@ -578,11 +588,14 @@ func (svc *channel) AddMember(channelID uint64, memberIDs ...uint64) (out types. member, err = svc.cmember.Create(member) } + svc.evl.Join(memberID, channelID) + if err != nil { return err } out = append(out, member) + } return svc.flushSystemMessages() @@ -623,6 +636,8 @@ func (svc *channel) DeleteMember(channelID uint64, memberIDs ...uint64) (err err if err = svc.cmember.Delete(channelID, memberID); err != nil { return err } + + svc.evl.Part(memberID, channelID) } return svc.flushSystemMessages() diff --git a/sam/service/events.go b/sam/service/events.go index a365700b9..8934e4065 100644 --- a/sam/service/events.go +++ b/sam/service/events.go @@ -20,6 +20,8 @@ type ( With(ctx context.Context) EventService Message(m *types.Message) error Channel(m *types.Channel) error + Join(userID, channelID uint64) error + Part(userID, channelID uint64) error } ) @@ -45,6 +47,7 @@ func (svc *event) Message(m *types.Message) error { // If this is a public channel we notify everyone func (svc *event) Channel(ch *types.Channel) error { sub := ch.ID + if ch.Type == types.ChannelTypePublic { sub = 0 } @@ -56,8 +59,40 @@ func (svc *event) Channel(ch *types.Channel) error { // // Subscription will match when session's user ID is the same as sub // We pack channel ID (the id to subscribe to) as payload -func (svc *event) Join(userID, channelID uint64) error { - return svc.push(payload.Channel(&types.Channel{ID: channelID}), types.EventQueueItemSubTypeUser, userID) +func (svc *event) Join(userID, channelID uint64) (err error) { + join := payload.ChannelJoin(channelID, userID) + + // Subscribe user to the channel + if err = svc.push(join, types.EventQueueItemSubTypeUser, 0); err != nil { + return + } + + // Let subscribers know that this user has joined the channel + if err = svc.push(join, types.EventQueueItemSubTypeChannel, channelID); err != nil { + return + } + + return +} + +// Part sends force-channel-part event to all matching subscriptions +// +// Subscription will match when session's user ID is the same as sub +// We pack channel ID (the id to subscribe to) as payload +func (svc *event) Part(userID, channelID uint64) (err error) { + part := payload.ChannelPart(channelID, userID) + + // Let subscribers know that this user has parted the channel + if err = svc.push(part, types.EventQueueItemSubTypeChannel, channelID); err != nil { + return + } + + // Subscribe user to the channel + if err = svc.push(part, types.EventQueueItemSubTypeUser, 0); err != nil { + return + } + + return } func (svc *event) push(m outgoing.MessageEncoder, subType types.EventQueueItemSubType, sub uint64) error { @@ -66,11 +101,10 @@ func (svc *event) push(m outgoing.MessageEncoder, subType types.EventQueueItemSu return err } - item := &types.EventQueueItem{Payload: enc} + item := &types.EventQueueItem{Payload: enc, SubType: subType} if sub > 0 { item.Subscriber = payload.Uint64toa(sub) - item.SubType = subType } return svc.events.Push(svc.ctx, item) diff --git a/sam/websocket/event_queue.go b/sam/websocket/event_queue.go index 5bf7c086a..99e5dc529 100644 --- a/sam/websocket/event_queue.go +++ b/sam/websocket/event_queue.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" + "github.com/davecgh/go-spew/spew" "github.com/titpetric/factory" "github.com/crusttech/crust/internal/payload" @@ -60,37 +61,53 @@ func (eq *eventQueue) feedSessions(ctx context.Context, config *repository.Flags return err } - if item.Subscriber == "" { + if item.SubType == types.EventQueueItemSubTypeUser { + p := &outgoing.Payload{} + + spew.Dump(string(item.Payload)) + if err := json.Unmarshal(item.Payload, p); err != nil { + return err + } + + if p.ChannelJoin != nil { + // Handle subscribing + + // This store.Walk handler does not send to subscribed sessions but + // subscribes all sessions that belong to the same user + store.Walk(func(s *Session) { + if payload.Uint64toa(s.user.Identity()) == p.ChannelJoin.UserID { + s.subs.Add(p.ChannelJoin.ID) + } + }) + } else if p.ChannelPart != nil { + // Handle un-subscribing + + // This store.Walk handler does not send to subscribed sessions but + // subscribes all sessions that belong to the same user + store.Walk(func(s *Session) { + if payload.Uint64toa(s.user.Identity()) == p.ChannelPart.UserID { + s.subs.Delete(p.ChannelPart.ID) + } + }) + } else { + // No other payload types we can handle at the moment. + return nil + } + + } else if item.Subscriber == "" { // Distribute payload to all connected sessions store.Walk(func(s *Session) { s.sendBytes(item.Payload) }) } else { - switch item.SubType { - case types.EventQueueItemSubTypeUser: - // Holds channel info (in fact, ID only) when payload is unmarshaled - var join *outgoing.Channel - if err := json.Unmarshal(item.Payload, join); err == nil { - return err + // Distribute payload to specific subscribers + store.Walk(func(s *Session) { + if s.subs.Get(item.Subscriber) != nil { + s.sendBytes(item.Payload) } - - // This store.Walk handler does send to subscribed sessions but - // subscribes all sessions that belong to the same user - store.Walk(func(s *Session) { - if payload.Uint64toa(s.user.Identity()) == item.Subscriber { - s.subs.Add(join.ID) - } - }) - case types.EventQueueItemSubTypeChannel: - // Distribute payload to specific subscribers - store.Walk(func(s *Session) { - if s.subs.Get(item.Subscriber) != nil { - s.sendBytes(item.Payload) - } - }) - } - + }) } + } }