<commit_before>using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using Xpressive.Home.Contracts.Messaging;

namespace Xpressive.Home.WebApi.Controllers
{
    [HubName("notificationHub")]
    public class UserNotificationHub : Hub, IMessageQueueListener<NotifyUserMessage>
    {
        private static readonly ConcurrentDictionary<string, string> _connectionsByUser = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
        private static readonly ConcurrentDictionary<string, HashSet<string>> _sentNotifications = new ConcurrentDictionary<string, HashSet<string>>();
        private static readonly List<UserNotificationDto> _notifications = new List<UserNotificationDto>();
        private static readonly object _lock = new object();

        public void Notify(NotifyUserMessage message)
        {
            lock (_lock)
            {
                var dto = new UserNotificationDto
                {
                    Timestamp = DateTime.UtcNow,
                    Message = message.Notification
                };

                _notifications.Add(dto);

                var connectionIds = _connectionsByUser.Keys.ToList();

                if (connectionIds.Any())
                {
                    Clients?.Clients(connectionIds)?.onNotification(dto);
                }
            }
        }

        public void Register(string userId)
        {
            lock (_lock)
            {
                _connectionsByUser.TryAdd(Context.ConnectionId, userId);
                var alreadySent = _sentNotifications.GetOrAdd(userId, new HashSet<string>(StringComparer.OrdinalIgnoreCase));
                var toSend = _notifications.Where(n => !alreadySent.Contains(n.Id));

                foreach (var notification in toSend)
                {
                    Clients.Client(Context.ConnectionId).onNotification(notification);
                }
            }
        }

        public override Task OnDisconnected(bool stopCalled)
        {
            lock (_lock)
            {
                string userId;
                if (_connectionsByUser.TryRemove(Context.ConnectionId, out userId))
                {
                    HashSet<string> notificationIds;
                    _sentNotifications.TryRemove(userId, out notificationIds);
                }
                return base.OnDisconnected(stopCalled);
            }
        }

        public class UserNotificationDto
        {
            public string Id { get; set; }
            public string Message { get; set; }
            public DateTime Timestamp { get; set; }
        }
    }
}<commit_msg>Send the same message on SignalR connect only once<commit_after>using System;
using System.Collections.Generic;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using Xpressive.Home.Contracts.Messaging;

namespace Xpressive.Home.WebApi.Controllers
{
    [HubName("notificationHub")]
    public class UserNotificationHub : Hub, IMessageQueueListener<NotifyUserMessage>
    {
        private static readonly List<UserNotificationDto> _notifications = new List<UserNotificationDto>();
        private static readonly object _lock = new object();

        public void Notify(NotifyUserMessage message)
        {
            lock (_lock)
            {
                var dto = new UserNotificationDto
                {
                    Timestamp = DateTime.UtcNow,
                    Message = message.Notification
                };

                _notifications.RemoveAll(n => n.Message.Equals(message.Notification, StringComparison.Ordinal));
                _notifications.Add(dto);

                Clients?.All?.onNotification(dto);
            }
        }

        public void Register(string userId)
        {
            lock (_lock)
            {
                foreach (var notification in _notifications)
                {
                    Clients.Client(Context.ConnectionId).onNotification(notification);
                }
            }
        }

        public class UserNotificationDto
        {
            public string Message { get; set; }
            public DateTime Timestamp { get; set; }
        }
    }
}
{'fraction_non_alphanumeric': 0.07729692200879426, 'fraction_numerical': 0.0, 'mean_word_length': 1.8967828418230563, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3223721', 'n_tokens_mistral': 1054, 'n_tokens_neox': 1017, 'n_words': 222}