using SharpChat.Users; namespace SharpChat.Channels; public class ChannelsUsersContext(ChannelsContext channelsCtx, UsersContext usersCtx) { private readonly Dictionary<string, Dictionary<long, DateTimeOffset>> UserChannels = []; private readonly Dictionary<long, HashSet<string>> ChannelUsers = []; private readonly Dictionary<string, long> UserLastChannel = []; private readonly Lock @lock = new(); private void UpdateUserLastChannel(string userId, long? channelId = null) { if(channelId.HasValue && UserLastChannel.TryGetValue(userId, out long lastChannelId) && channelId.Value != lastChannelId) return; if(UserChannels.TryGetValue(userId, out var userChannels) && userChannels.Count > 0) UserLastChannel[userId] = userChannels.OrderByDescending(kvp => kvp.Value).First().Key; else UserLastChannel.Remove(userId); } public void RemoveChannel(Channel channel) => RemoveChannel(channel.Id); public void RemoveChannel(long channelId) { lock(@lock) { if(!ChannelUsers.TryGetValue(channelId, out var channelUsers)) return; ChannelUsers.Remove(channelId); foreach(string userId in channelUsers) if(UserChannels.TryGetValue(userId, out var userChannels)) { userChannels.Remove(channelId); UpdateUserLastChannel(userId, channelId); } } } public void RemoveUser(User user) => RemoveUser(user.UserId); public void RemoveUser(string userId) { lock(@lock) { if(!UserChannels.TryGetValue(userId, out var userChannels)) return; UserChannels.Remove(userId); UserLastChannel.Remove(userId); foreach(long channelId in userChannels.Keys) if(ChannelUsers.TryGetValue(channelId, out var channelUsers)) channelUsers.Remove(userId); } } public void RecordChannelUserActivity(Channel channel, User user) => RecordChannelUserActivity(channel.Id, user.UserId); public void RecordChannelUserActivity(long channelId, User user) => RecordChannelUserActivity(channelId, user.UserId); public void RecordChannelUserActivity(Channel channel, string userId) => RecordChannelUserActivity(channel.Id, userId); public void RecordChannelUserActivity(long channelId, string userId) { lock(@lock) { if(!UserChannels.TryGetValue(userId, out var userChannels)) throw new ArgumentException("Attempted to register activity for non-existent user.", nameof(userId)); userChannels[channelId] = DateTimeOffset.UtcNow; UserLastChannel[userId] = channelId; } } public void AddChannelUser(Channel channel, User user) => AddChannelUser(channel.Id, user.UserId); public void AddChannelUser(long channelId, string userId) { lock(@lock) { if(!ChannelUsers.TryGetValue(channelId, out var channelUsers)) ChannelUsers.Add(channelId, channelUsers = []); if(!UserChannels.ContainsKey(userId)) UserChannels.Add(userId, []); channelUsers.Add(userId); RecordChannelUserActivity(channelId, userId); } } public void RemoveChannelUser(Channel channel, User user) => RemoveChannelUser(channel.Id, user.UserId); public void RemoveChannelUser(long channelId, User user) => RemoveChannelUser(channelId, user.UserId); public void RemoveChannelUser(Channel channel, string userId) => RemoveChannelUser(channel.Id, userId); public void RemoveChannelUser(long channelId, string userId) { lock(@lock) { ChannelUsers.Remove(channelId); UserChannels.Remove(userId); UpdateUserLastChannel(userId, channelId); } } public bool HasChannelUser(Channel channel, User user) => HasChannelUser(channel.Id, user.UserId); public bool HasChannelUser(long channelId, User user) => HasChannelUser(channelId, user.UserId); public bool HasChannelUser(Channel channel, string userId) => HasChannelUser(channel.Id, userId); public bool HasChannelUser(long channelId, string userId) { lock(@lock) return ChannelUsers.TryGetValue(channelId, out var channelUsers) && channelUsers.Contains(userId); } public long? GetUserLastChannelId(User user) => GetUserLastChannelId(user.UserId); public long? GetUserLastChannelId(string userId) { lock(@lock) return UserLastChannel.TryGetValue(userId, out long channelId) ? channelId : null; } public Channel? GetUserLastChannel(User user) => GetUserLastChannel(user.UserId); public Channel? GetUserLastChannel(string userId) { lock(@lock) { long? channelId = GetUserLastChannelId(userId); return channelId.HasValue ? channelsCtx.GetChannel(channelId.Value) : null; } } public IEnumerable<string> GetChannelUserIds(Channel channel) => GetChannelUserIds(channel.Id); public IEnumerable<string> GetChannelUserIds(long channelId) { lock(@lock) return [.. GetChannelUserIdsInternal(channelId)]; } private HashSet<string> GetChannelUserIdsInternal(long channelId) { return ChannelUsers.TryGetValue(channelId, out var channelUsers) ? channelUsers : []; } public IEnumerable<User> GetChannelUsers(Channel channel) { lock(@lock) return usersCtx.GetUsers(GetChannelUserIdsInternal(channel.Id)); } public IEnumerable<User> GetChannelUsers(long channelId) { lock(@lock) return usersCtx.GetUsers(GetChannelUserIdsInternal(channelId)); } public IEnumerable<long> GetUserChannelIds(User user) => GetUserChannelIds(user.UserId); public IEnumerable<long> GetUserChannelIds(string userId) { lock(@lock) return [.. GetUserChannelIdsInternal(userId)]; } private IEnumerable<long> GetUserChannelIdsInternal(string userId) { return UserChannels.TryGetValue(userId, out var userChannels) ? userChannels.Keys : []; } public IEnumerable<Channel> GetUserChannels(User user) { lock(@lock) return channelsCtx.GetChannels(GetUserChannelIdsInternal(user.UserId)); } public IEnumerable<Channel> GetUserChannels(string userId) { lock(@lock) return channelsCtx.GetChannels(GetUserChannelIdsInternal(userId)); } }