Exemplo n.º 1
0
        public void SetAsTyping(UserId userId, RoomId roomId, Action onStartedTyping, Action onStoppedTyping)
        {
            // Update Activity Tracker for that user/room
            SetLastActiveInRoom(userId, roomId);

            // if the user is already typing in the room, just prolonge the typing duration
            var chatUser = GetUser(userId);

            if (chatUser.TypingTimer.Running && chatUser.RoomTypingIn == roomId)
            {
                chatUser.TypingTimer.ReTime(TypingTimeOut);
                return;
            }

            // if the user has switched to another room, complete is current existing activity
            if (chatUser.TypingTimer.Running && chatUser.RoomTypingIn != roomId)
            {
                UnsetAsTyping(chatUser.Id);
            }

            var onStoppedTypingEnhanced = new Action(() =>
            {
                onStoppedTyping?.Invoke();
                chatUser.RoomTypingIn = null;
            });

            // Start the tracker
            chatUser.TypingTimer.Start(onStartedTyping, onStoppedTypingEnhanced, TypingTimeOut);
            chatUser.RoomTypingIn = roomId;
        }
        public List <ITextChatMessage> GetHistory(RoomId roomId, List <Enumerables.MessageVisibility> withVisibilities, int messageCount)
        {
            using (var db = new HellolingoEntities())
            {
                // [Replaced by a SPROC call to try to improve perf issues]
                var entityResult = db.TextChats.AsNoTracking()
                                   .OrderByDescending(a => a.ID)
                                   .Where(a => a.RoomId == roomId && withVisibilities.Any(b => (byte)b == a.Visibility))
                                   // THIS NEXT LINE IS CRITICAL!!!! NOT USING MAKES THE QUERY WAY SLOWER, TO THE POINT THAT IT BRINGS
                                   // THE SERVER DOWN WHEN THE IIS APPLICATION POOL IS RECYCLED AND ALL USERS ARE RELOADING ALL THEIR ROOMS.
                                   // EF ACTUALLY LOADS THE FULL DEFINITION OF THE USER WHEN ToList IS CALLED... WHICH WILL CLEARLY TAKE A WHOLE LOT OF TIME.
                                   .Select(a => new { a.ID, a.When, a.UserId, a.FirstName, a.RoomId, a.Text, a.Visibility })
                                   .Take(messageCount).ToList().OrderBy(a => a.ID);

                //var entityResult = db.TextChat_GetHistory(messageCount, roomId, string.Join(",", withVisibilities.Cast<int>()));

                return(entityResult.Select(
                           msg => (ITextChatMessage) new TextChatMessage {
                    When = msg.When,
                    UserId = msg.UserId,
                    FirstName = msg.FirstName,
                    RoomId = msg.RoomId,
                    Text = msg.Text,
                    Visibility = (Enumerables.MessageVisibility)msg.Visibility
                }).OrderBy(msg => msg.When).ToList());
            }
        }
Exemplo n.º 3
0
        public void RemoveUserFromRoom(UserId userId, RoomId roomId)
        {
            var user = GetUser(userId);

            _rooms[roomId].RemoveUser(user.Id);
            _allUsers[userId].JoinedRooms.Remove(roomId);
        }
Exemplo n.º 4
0
        public void AddUserToRoom(UserId userId, RoomId roomId)
        {
            var user = _allUsers[userId];

            PrepareRoom(roomId);
            _rooms[roomId].AddUser(user.Id);
            user.JoinedRooms.Add(roomId);
        }
Exemplo n.º 5
0
        public virtual void LeaveRoom(UserId userId, RoomId roomId)
        {
            ChatModel.RemoveUserFromRoom(userId, roomId);
            if (roomId.IsPublic())
            {
                OnCountOfUsersUpdated?.Invoke(roomId, ChatModel.UsersCountOf(roomId));
            }

            OnUserLeftRoom?.Invoke(roomId, userId);
        }
Exemplo n.º 6
0
        public async Task SetTypingActivity(UserId userId, RoomId roomId)
        {
            // Mark user as active
            SetLastActivity(userId);
            ChatModel.SetLastActiveInRoom(userId, roomId);

            // Mark user as Typing
            var onStartedTyping = new Action(() => OnUserStartedTyping?.Invoke(roomId, userId));
            var onStoppedTyping = new Action(() => OnUserStoppedTyping?.Invoke(roomId, userId));

            ChatModel.SetAsTyping(userId, roomId, onStartedTyping, onStoppedTyping);
        }
Exemplo n.º 7
0
        public List <ITextChatMessage> LatestMessagesIn(RoomId roomId, int messageCount, List <MessageVisibility> withVisibilities = null)
        {
            if (withVisibilities == null)
            {
                withVisibilities = new List <MessageVisibility> {
                    MessageVisibility.Everyone
                }
            }
            ;

            return(HasRoom(roomId) && _rooms[roomId].ValidHistory
                                ? _rooms[roomId].Messages.Where(msg => withVisibilities.Contains(msg.Visibility)).Reverse().Take(messageCount).Reverse().ToList()
                                : _storage.GetHistory(roomId, withVisibilities, messageCount));
        }
Exemplo n.º 8
0
        private void PrepareRoom(RoomId roomId)
        {
            lock (_locker) {
                if (!HasRoom(roomId))
                {
                    _rooms.Add(roomId, new RoomModel());
                }

                if (!_rooms[roomId].ValidHistory)
                {
                    var withVisibilities = new List <MessageVisibility> {
                        MessageVisibility.Everyone, MessageVisibility.Ephemeral, MessageVisibility.Sender, MessageVisibility.News
                    };
                    _rooms[roomId].Messages     = LatestMessagesIn(roomId, MinimumHistoryLength, withVisibilities);
                    _rooms[roomId].ValidHistory = true;
                }
            }
        }
        public static async Task <Result <bool> > JoinRoom(User user, RoomId roomId)
        {
            var result = Result <bool> .True;

            if (!roomId.IsPrivate())
            {
                return(result);
            }

            int userIdEf = user.Id; string roomIdEf = roomId;             // Casting named types into their base type because EF doesn't get it otherwise.
            int partnerId = ChatModel.PartnerIdFrom(roomId, user.Id);

            using (var db = new HellolingoEntities()) {
                var userTracker    = db.TextChatTrackers.Find(userIdEf, roomIdEf);
                var partnerTracker = db.TextChatTrackers.Find(partnerId, roomIdEf);

                // Handle people directly joining by the url from outside the chat
                if (userTracker == null || partnerTracker == null)
                {
                    await RequestPrivateChat(user, roomId, partnerId);

                    result.Reports.AddReport(LogTag.ChatRequestAddedFromJoinRoom, new { userId = user.Id, roomId });
                    return(result);
                }

                // Set new statuses according to current status
                if ((userTracker.Status == TrackerStatus.Invited || userTracker.Status == TrackerStatus.IgnoredInvite) && partnerTracker.Status == TrackerStatus.Inviting)
                {
                    userTracker.Status    = TrackerStatus.AcceptedInvite;
                    partnerTracker.Status = TrackerStatus.InviteAccepted;
                    userTracker.StatusAt  = partnerTracker.StatusAt = DateTime.Now;
                    result.Reports        = new LogReports(LogTag.ChatRequestAccepted);
                }
                else if (userTracker.Status == TrackerStatus.InviteAccepted)
                {
                    userTracker.Status   = TrackerStatus.SeenInviteResponse;
                    userTracker.StatusAt = DateTime.Now;
                }

                await db.SaveChangesAsync();
            }

            return(result);
        }
        public static async Task <Result <bool> > IgnorePrivateChat(UserId userId, RoomId roomId, UserId partnerId)
        {
            int userIdEf = userId, partnerIdEf = partnerId; string roomIdEf = roomId;             // Casting named types into their base type because EF doesn't get it otherwise.

            using (var db = new HellolingoEntities()) {
                var inviterRecord = db.TextChatTrackers.Find(partnerIdEf, roomIdEf);
                var inviteeRecord = db.TextChatTrackers.Find(userIdEf, roomIdEf);

                if (inviterRecord == null || inviteeRecord == null)
                {
                    return(new Result <bool>(false, LogTag.IgnorePrivateChatMissingTrackerRecord, new { userId, roomId, partnerId }, LogLevel.Error));
                }

                // Set record for invitee
                inviteeRecord.Status   = TrackerStatus.IgnoredInvite;
                inviteeRecord.StatusAt = DateTime.Now;
                await db.SaveChangesAsync();
            }

            return(Result <bool> .True);
        }
        public static async Task <Result <bool> > MarkAllCaughtUp(UserId userId, RoomId roomId)
        {
            int userIdEf = userId; string roomIdEf = roomId;             // Casting named types into their base type because EF doesn't get it otherwise.
            var result = Result <bool> .True;

            using (var db = new HellolingoEntities()) {
                var userRecord = db.TextChatTrackers.Find(userIdEf, roomIdEf);
                if (userRecord == null)
                {
                    return(new Result <bool>(false, LogTag.UnexpectedTrackerStatus, new { method = "MarkAllCaughtUp", userId, roomId }, LogLevel.Error));
                }
                if (userRecord.Status == TrackerStatus.AllCaughtUp || userRecord.Status == TrackerStatus.Inviting || userRecord.Status == TrackerStatus.Invited)
                {
                    return(result);
                }
                userRecord.Status   = TrackerStatus.AllCaughtUp;
                userRecord.StatusAt = DateTime.Now;
                await db.SaveChangesAsync();
            }
            return(result);
        }
        public static async Task <Result <bool> > PostTo(UserId userId, RoomId roomId)
        {
            var    result    = Result <bool> .True;
            string roomIdEf  = roomId;            // Casting named types into their base type because EF doesn't get it otherwise.
            int    partnerId = ChatModel.PartnerIdFrom(roomId, userId);

            using (var db = new HellolingoEntities()) {
                var partnerRecord = db.TextChatTrackers.Find(partnerId, roomIdEf);
                if (partnerRecord == null)
                {
                    return(new Result <bool>(false, LogTag.UnexpectedTrackerStatus, new { method = "PostTo", userId, roomId }, LogLevel.Error));
                }
                if (partnerRecord.Status == TrackerStatus.Invited || partnerRecord.Status == TrackerStatus.AutoBlocked || partnerRecord.Status == TrackerStatus.DeclinedInvite || partnerRecord.Status == TrackerStatus.IgnoredInvite || partnerRecord.Status == TrackerStatus.UnreadMessages)
                {
                    return(new Result <bool>(true));
                }
                partnerRecord.Status   = TrackerStatus.UnreadMessages;
                partnerRecord.StatusAt = DateTime.Now;
                await db.SaveChangesAsync();
            }
            return(result);
        }
Exemplo n.º 13
0
 public void CloseRoom(RoomId roomId) => _rooms.Remove(roomId);
Exemplo n.º 14
0
 public int CountOfMessagesInRoom(RoomId roomId) => LatestMessagesIn(roomId, MinimumHistoryLength).Count;
Exemplo n.º 15
0
 public bool HasRoom(RoomId roomId) => _rooms.ContainsKey(roomId);
Exemplo n.º 16
0
        public static UserId PartnerIdFrom(RoomId roomId, UserId userId)
        {
            var userIds = roomId.ToString().Split('-').Select(int.Parse).ToArray();

            return(userId == userIds[0] ? userIds[1] : userIds[0]);
        }
Exemplo n.º 17
0
 public static UserId PartnerInPrivateRoom(RoomId roomId, UserId thisUserId) => UserIdsInPrivateRoom(roomId).First(id => id != thisUserId);
Exemplo n.º 18
0
 public void AudioCallConnected(UserId userId, RoomId roomId) => OnAudioCallConnected?.Invoke(roomId, userId);
Exemplo n.º 19
0
 public void DeclineAudioCall(RoomId roomId, string reason, ConnectionId connId) => OnUserDeclinedAudioCall?.Invoke(roomId, reason, connId);
Exemplo n.º 20
0
 public void RequestAudioCall(RoomId roomId, UserId userId)
 {
     SetLastActivity(userId);
     OnUserRequestedAudioCall?.Invoke(roomId, userId);
 }
Exemplo n.º 21
0
 public bool IsEmpty(RoomId roomId) => !_rooms[roomId].Users.Any();
Exemplo n.º 22
0
 public bool IsInRoom(RoomId roomId, UserId userId) => HasRoom(roomId) && _rooms[roomId].HasUser(userId);
Exemplo n.º 23
0
 public void CancelAudioCall(UserId userId, RoomId roomId) => OnUserCancelledAudioCall?.Invoke(roomId, userId);
Exemplo n.º 24
0
 public int UsersCountOf(RoomId roomId) => _rooms[roomId].Users.Count;
Exemplo n.º 25
0
 public void HangoutAudioCall(UserId userId, RoomId roomId) => OnUserHangoutedAudioCall?.Invoke(roomId, userId);
Exemplo n.º 26
0
 public ITextChatMessage LastMessageIn(RoomId roomId) => _rooms.ContainsKey(roomId) ? _rooms[roomId].LastMessage : null;
Exemplo n.º 27
0
 // Simple Helpers
 public static List <UserId> UserIdsInPrivateRoom(RoomId roomId) => roomId.ToString().Split('-').Select(id => (UserId)int.Parse(id)).ToList();
Exemplo n.º 28
0
 public List <UserId> UsersInRoom(RoomId roomId, UserId except = null) => _rooms[roomId].Users.Keys.Where(r => r != except).ToList();
Exemplo n.º 29
0
        public virtual Tuple <List <UserId>, List <ITextChatMessage> > JoinRoom(UserId userId, RoomId roomId)
        {
            var user = ChatModel.GetUser(userId);

            // If the room is private and the user has nothing to do here, EXPLODE!
            if (roomId.IsPrivate() && !UserIdsInPrivateRoom(roomId).Contains(userId))
            {
                throw new LogReadyException(LogTag.PrivateRoomIntrusionAttempt, new { userId, roomId });
            }

            if (!ChatModel.IsInRoom(roomId, user.Id))
            {
                ChatModel.AddUserToRoom(userId, roomId);
                OnUserJoinedRoom?.Invoke(roomId, user.Id);
                if (roomId.IsPublic())
                {
                    OnCountOfUsersUpdated?.Invoke(roomId, ChatModel.UsersCountOf(roomId));
                }
            }

            var withVisibilities = new List <MessageVisibility> {
                MessageVisibility.Everyone, MessageVisibility.Sender, MessageVisibility.Ephemeral, MessageVisibility.News
            };

            var customMessageHistory = ChatModel.LatestMessagesIn(roomId, HistoryLength * 2, withVisibilities)
                                       .Where(a => (a.Visibility != MessageVisibility.Sender && a.Visibility != MessageVisibility.Ephemeral) || a.UserId == userId)
                                       .Reverse().Take(HistoryLength).Reverse()
                                       .ToList();

            // If in a private room and the partner doesn't want private chat, Signal it with a service message
            if (roomId.IsGroup())
            {
                goto Skip;
            }
            var partnerId = PartnerInPrivateRoom(roomId, userId);

            if (!ChatModel.IsInChat(partnerId))
            {
                goto Skip;
            }
            var partner = ChatModel.GetUser(partnerId);

            if (partner.IsNoPrivateChat)
            {
                customMessageHistory.Add(new TextChatMessage {
                    RoomId = roomId,
                    Text   = JsonConvert.SerializeObject(new { noPrivateChat = ChatModel.GetPublicRoomsFor(partner.Id) }, Formatting.None, new JsonSerializerSettings {
                        ContractResolver = new CamelCasePropertyNamesContractResolver()
                    }),
                    Visibility = MessageVisibility.System
                });
            }
Skip:

            return(new Tuple <List <UserId>, List <ITextChatMessage> >(
                       ChatModel.UsersInRoom(roomId, user.Id),
                       customMessageHistory
                       ));
        }
Exemplo n.º 30
0
 public bool IsTypingInRoom(ITextChatUser user, RoomId roomId) => user.TypingTimer.Running && user.RoomTypingIn == roomId;