sharp-chat/SharpChatCommon/Channels/ChannelsUsersContext.cs
flashwave 5a7756894b
First bits of the Context overhaul.
Reintroduces separate contexts for users, channels, connections (now split into sessions and connections) and user-channel associations.
It builds which is as much assurance as I can give about the stability of this commit, but its also the bare minimum of what i like to commit sooooo
A lot of things still need to be broadcast through events throughout the application in order to keep states consistent but we'll cross that bridge when we get to it.
I really need to stop using that phrase thingy, I'm overusing it.
2025-05-03 02:49:51 +00:00

176 lines
6.5 KiB
C#

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));
}
}