Cleaned up channel/user association logic.
This commit is contained in:
parent
1466562c54
commit
d268a419dc
7 changed files with 85 additions and 93 deletions
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
@ -11,8 +10,6 @@ namespace SharpChat {
|
||||||
public int Rank { get; set; } = 0;
|
public int Rank { get; set; } = 0;
|
||||||
public ChatUser Owner { get; set; } = null;
|
public ChatUser Owner { get; set; } = null;
|
||||||
|
|
||||||
private List<ChatUser> Users { get; } = new();
|
|
||||||
|
|
||||||
public bool HasPassword
|
public bool HasPassword
|
||||||
=> !string.IsNullOrWhiteSpace(Password);
|
=> !string.IsNullOrWhiteSpace(Password);
|
||||||
|
|
||||||
|
@ -22,37 +19,6 @@ namespace SharpChat {
|
||||||
Name = name;
|
Name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasUser(ChatUser user) {
|
|
||||||
return Users.Contains(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UserJoin(ChatUser user) {
|
|
||||||
if(!user.InChannel(this)) {
|
|
||||||
// Remove this, a different means for this should be established for V1 compat.
|
|
||||||
user.Channel?.UserLeave(user);
|
|
||||||
user.JoinChannel(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!HasUser(user))
|
|
||||||
Users.Add(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UserLeave(ChatUser user) {
|
|
||||||
Users.Remove(user);
|
|
||||||
|
|
||||||
if(user.InChannel(this))
|
|
||||||
user.LeaveChannel(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<ChatUser> GetUsers(IEnumerable<ChatUser> exclude = null) {
|
|
||||||
IEnumerable<ChatUser> users = Users.OrderByDescending(x => x.Rank);
|
|
||||||
|
|
||||||
if(exclude != null)
|
|
||||||
users = users.Except(exclude);
|
|
||||||
|
|
||||||
return users.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Pack() {
|
public string Pack() {
|
||||||
StringBuilder sb = new();
|
StringBuilder sb = new();
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ using System.Net;
|
||||||
|
|
||||||
namespace SharpChat {
|
namespace SharpChat {
|
||||||
public class ChatContext {
|
public class ChatContext {
|
||||||
|
public record ChannelUserAssoc(long UserId, string ChannelName);
|
||||||
|
|
||||||
public HashSet<ChatChannel> Channels { get; } = new();
|
public HashSet<ChatChannel> Channels { get; } = new();
|
||||||
public readonly object ChannelsAccess = new();
|
public readonly object ChannelsAccess = new();
|
||||||
|
|
||||||
|
@ -21,6 +23,9 @@ namespace SharpChat {
|
||||||
public IEventStorage Events { get; }
|
public IEventStorage Events { get; }
|
||||||
public readonly object EventsAccess = new();
|
public readonly object EventsAccess = new();
|
||||||
|
|
||||||
|
public HashSet<ChannelUserAssoc> ChannelUsers { get; } = new();
|
||||||
|
public readonly object ChannelUsersAccess = new();
|
||||||
|
|
||||||
public ChatContext(IEventStorage evtStore) {
|
public ChatContext(IEventStorage evtStore) {
|
||||||
Events = evtStore ?? throw new ArgumentNullException(nameof(evtStore));
|
Events = evtStore ?? throw new ArgumentNullException(nameof(evtStore));
|
||||||
}
|
}
|
||||||
|
@ -38,7 +43,7 @@ namespace SharpChat {
|
||||||
lock(UsersAccess)
|
lock(UsersAccess)
|
||||||
foreach(ChatUser user in Users)
|
foreach(ChatUser user in Users)
|
||||||
if(!Connections.Any(conn => conn.User == user)) {
|
if(!Connections.Any(conn => conn.User == user)) {
|
||||||
UserLeave(null, user, UserDisconnectReason.TimeOut);
|
HandleDisconnect(user, UserDisconnectReason.TimeOut);
|
||||||
Logger.Write($"Timed out {user} (no more connections).");
|
Logger.Write($"Timed out {user} (no more connections).");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +53,42 @@ namespace SharpChat {
|
||||||
return Connections.FirstOrDefault(s => s.Socket == sock);
|
return Connections.FirstOrDefault(s => s.Socket == sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsInChannel(ChatUser user, ChatChannel channel) {
|
||||||
|
lock(ChannelUsersAccess)
|
||||||
|
return ChannelUsers.Contains(new ChannelUserAssoc(user.UserId, channel.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string[] GetUserChannelNames(ChatUser user) {
|
||||||
|
lock(ChannelUsersAccess)
|
||||||
|
return ChannelUsers.Where(cu => cu.UserId == user.UserId).Select(cu => cu.ChannelName).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatChannel[] GetUserChannels(ChatUser user) {
|
||||||
|
string[] names = GetUserChannelNames(user);
|
||||||
|
lock(ChannelsAccess)
|
||||||
|
return Channels.Where(c => names.Any(n => c.NameEquals(n))).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long[] GetChannelUserIds(ChatChannel channel) {
|
||||||
|
lock(ChannelUsersAccess)
|
||||||
|
return ChannelUsers.Where(cu => channel.NameEquals(cu.ChannelName)).Select(cu => cu.UserId).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChatUser[] GetChannelUsers(ChatChannel channel) {
|
||||||
|
long[] ids = GetChannelUserIds(channel);
|
||||||
|
lock(UsersAccess)
|
||||||
|
return Users.Where(u => ids.Contains(u.UserId)).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DebugPrintChannelUsers() {
|
||||||
|
lock(ChannelUsersAccess) {
|
||||||
|
Logger.Write("DebugPrintChannelUsers()");
|
||||||
|
foreach(ChannelUserAssoc cua in ChannelUsers)
|
||||||
|
Logger.Write(cua);
|
||||||
|
Logger.Write(string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void BanUser(ChatUser user, TimeSpan duration, UserDisconnectReason reason = UserDisconnectReason.Kicked) {
|
public void BanUser(ChatUser user, TimeSpan duration, UserDisconnectReason reason = UserDisconnectReason.Kicked) {
|
||||||
if(duration > TimeSpan.Zero)
|
if(duration > TimeSpan.Zero)
|
||||||
SendTo(user, new ForceDisconnectPacket(ForceDisconnectReason.Banned, DateTimeOffset.Now + duration));
|
SendTo(user, new ForceDisconnectPacket(ForceDisconnectReason.Banned, DateTimeOffset.Now + duration));
|
||||||
|
@ -61,18 +102,18 @@ namespace SharpChat {
|
||||||
Connections.RemoveWhere(conn => conn.IsDisposed);
|
Connections.RemoveWhere(conn => conn.IsDisposed);
|
||||||
}
|
}
|
||||||
|
|
||||||
UserLeave(user.Channel, user, reason);
|
HandleDisconnect(user, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleJoin(ChatUser user, ChatChannel chan, ChatConnection conn, int maxMsgLength) {
|
public void HandleJoin(ChatUser user, ChatChannel chan, ChatConnection conn, int maxMsgLength) {
|
||||||
lock(EventsAccess) {
|
lock(EventsAccess) {
|
||||||
if(!chan.HasUser(user)) {
|
if(!IsInChannel(user, chan)) {
|
||||||
SendTo(chan, new UserConnectPacket(DateTimeOffset.Now, user));
|
SendTo(chan, new UserConnectPacket(DateTimeOffset.Now, user));
|
||||||
Events.AddEvent(new UserConnectEvent(DateTimeOffset.Now, user, chan));
|
Events.AddEvent(new UserConnectEvent(DateTimeOffset.Now, user, chan));
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.Send(new AuthSuccessPacket(user, chan, conn, maxMsgLength));
|
conn.Send(new AuthSuccessPacket(user, chan, conn, maxMsgLength));
|
||||||
conn.Send(new ContextUsersPacket(chan.GetUsers(new[] { user })));
|
conn.Send(new ContextUsersPacket(GetChannelUsers(chan).Except(new[] { user }).OrderByDescending(u => u.Rank)));
|
||||||
|
|
||||||
foreach(IChatEvent msg in Events.GetChannelEventLog(chan.Name))
|
foreach(IChatEvent msg in Events.GetChannelEventLog(chan.Name))
|
||||||
conn.Send(new ContextMessagePacket(msg));
|
conn.Send(new ContextMessagePacket(msg));
|
||||||
|
@ -80,32 +121,38 @@ namespace SharpChat {
|
||||||
lock(ChannelsAccess)
|
lock(ChannelsAccess)
|
||||||
conn.Send(new ContextChannelsPacket(Channels.Where(c => c.Rank <= user.Rank)));
|
conn.Send(new ContextChannelsPacket(Channels.Where(c => c.Rank <= user.Rank)));
|
||||||
|
|
||||||
if(!chan.HasUser(user))
|
|
||||||
chan.UserJoin(user);
|
|
||||||
|
|
||||||
lock(UsersAccess)
|
lock(UsersAccess)
|
||||||
Users.Add(user);
|
Users.Add(user);
|
||||||
|
|
||||||
|
lock(ChannelUsersAccess) {
|
||||||
|
ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.Name));
|
||||||
|
user.CurrentChannel = chan;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UserLeave(ChatChannel chan, ChatUser user, UserDisconnectReason reason = UserDisconnectReason.Leave) {
|
public void HandleDisconnect(ChatUser user, UserDisconnectReason reason = UserDisconnectReason.Leave) {
|
||||||
user.Status = ChatUserStatus.Offline;
|
user.Status = ChatUserStatus.Offline;
|
||||||
|
|
||||||
if(chan == null) {
|
|
||||||
foreach(ChatChannel channel in user.GetChannels()) {
|
|
||||||
UserLeave(channel, user, reason);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(chan.IsTemporary && chan.Owner == user)
|
|
||||||
lock(ChannelsAccess)
|
|
||||||
RemoveChannel(chan);
|
|
||||||
|
|
||||||
lock(EventsAccess) {
|
lock(EventsAccess) {
|
||||||
chan.UserLeave(user);
|
lock(UsersAccess)
|
||||||
SendTo(chan, new UserDisconnectPacket(DateTimeOffset.Now, user, reason));
|
Users.Remove(user);
|
||||||
Events.AddEvent(new UserDisconnectEvent(DateTimeOffset.Now, user, chan, reason));
|
|
||||||
|
lock(ChannelUsersAccess) {
|
||||||
|
ChatChannel[] channels = GetUserChannels(user);
|
||||||
|
|
||||||
|
foreach(ChatChannel chan in channels) {
|
||||||
|
ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, chan.Name));
|
||||||
|
DebugPrintChannelUsers();
|
||||||
|
|
||||||
|
SendTo(chan, new UserDisconnectPacket(DateTimeOffset.Now, user, reason));
|
||||||
|
Events.AddEvent(new UserDisconnectEvent(DateTimeOffset.Now, user, chan, reason));
|
||||||
|
|
||||||
|
if(chan.IsTemporary && chan.Owner == user)
|
||||||
|
lock(ChannelsAccess)
|
||||||
|
RemoveChannel(chan);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,14 +193,18 @@ namespace SharpChat {
|
||||||
Events.AddEvent(new UserChannelJoinEvent(DateTimeOffset.Now, user, chan));
|
Events.AddEvent(new UserChannelJoinEvent(DateTimeOffset.Now, user, chan));
|
||||||
|
|
||||||
SendTo(user, new ContextClearPacket(chan, ContextClearMode.MessagesUsers));
|
SendTo(user, new ContextClearPacket(chan, ContextClearMode.MessagesUsers));
|
||||||
SendTo(user, new ContextUsersPacket(chan.GetUsers(new[] { user })));
|
SendTo(user, new ContextUsersPacket(GetChannelUsers(chan).Except(new[] { user }).OrderByDescending(u => u.Rank)));
|
||||||
|
|
||||||
foreach(IChatEvent msg in Events.GetChannelEventLog(chan.Name))
|
foreach(IChatEvent msg in Events.GetChannelEventLog(chan.Name))
|
||||||
SendTo(user, new ContextMessagePacket(msg));
|
SendTo(user, new ContextMessagePacket(msg));
|
||||||
|
|
||||||
ForceChannel(user, chan);
|
ForceChannel(user, chan);
|
||||||
oldChan.UserLeave(user);
|
|
||||||
chan.UserJoin(user);
|
lock(ChannelUsersAccess) {
|
||||||
|
ChannelUsers.Remove(new ChannelUserAssoc(user.UserId, oldChan.Name));
|
||||||
|
ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.Name));
|
||||||
|
user.CurrentChannel = chan;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(oldChan.IsTemporary && oldChan.Owner == user)
|
if(oldChan.IsTemporary && oldChan.Owner == user)
|
||||||
|
@ -189,8 +240,9 @@ namespace SharpChat {
|
||||||
if(packet == null)
|
if(packet == null)
|
||||||
throw new ArgumentNullException(nameof(packet));
|
throw new ArgumentNullException(nameof(packet));
|
||||||
|
|
||||||
|
// might be faster to grab the users first and then cascade into that SendTo
|
||||||
lock(ConnectionsAccess) {
|
lock(ConnectionsAccess) {
|
||||||
IEnumerable<ChatConnection> conns = Connections.Where(c => c.IsAuthed && channel.HasUser(c.User));
|
IEnumerable<ChatConnection> conns = Connections.Where(c => c.IsAuthed && IsInChannel(c.User, channel));
|
||||||
foreach(ChatConnection conn in conns)
|
foreach(ChatConnection conn in conns)
|
||||||
conn.Send(packet);
|
conn.Send(packet);
|
||||||
}
|
}
|
||||||
|
@ -257,7 +309,7 @@ namespace SharpChat {
|
||||||
|
|
||||||
// Move all users back to the main channel
|
// Move all users back to the main channel
|
||||||
// TODO: Replace this with a kick. SCv2 supports being in 0 channels, SCv1 should force the user back to DefaultChannel.
|
// TODO: Replace this with a kick. SCv2 supports being in 0 channels, SCv1 should force the user back to DefaultChannel.
|
||||||
foreach(ChatUser user in channel.GetUsers())
|
foreach(ChatUser user in GetChannelUsers(channel))
|
||||||
SwitchChannel(user, defaultChannel, string.Empty);
|
SwitchChannel(user, defaultChannel, string.Empty);
|
||||||
|
|
||||||
// Broadcast deletion of channel
|
// Broadcast deletion of channel
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
using SharpChat.Misuzu;
|
using SharpChat.Misuzu;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace SharpChat {
|
namespace SharpChat {
|
||||||
|
@ -81,14 +79,10 @@ namespace SharpChat {
|
||||||
public class ChatUser : BasicUser {
|
public class ChatUser : BasicUser {
|
||||||
public DateTimeOffset SilencedUntil { get; set; }
|
public DateTimeOffset SilencedUntil { get; set; }
|
||||||
|
|
||||||
private readonly List<ChatChannel> Channels = new();
|
|
||||||
|
|
||||||
public readonly ChatRateLimiter RateLimiter = new();
|
public readonly ChatRateLimiter RateLimiter = new();
|
||||||
|
|
||||||
public ChatChannel Channel => Channels.FirstOrDefault();
|
|
||||||
|
|
||||||
// This needs to be a session thing
|
// This needs to be a session thing
|
||||||
public ChatChannel CurrentChannel { get; private set; }
|
public ChatChannel CurrentChannel { get; set; }
|
||||||
|
|
||||||
public bool IsSilenced
|
public bool IsSilenced
|
||||||
=> DateTimeOffset.UtcNow - SilencedUntil <= TimeSpan.Zero;
|
=> DateTimeOffset.UtcNow - SilencedUntil <= TimeSpan.Zero;
|
||||||
|
@ -114,26 +108,6 @@ namespace SharpChat {
|
||||||
SilencedUntil = auth.SilencedUntil;
|
SilencedUntil = auth.SilencedUntil;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool InChannel(ChatChannel chan) {
|
|
||||||
return Channels.Contains(chan);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void JoinChannel(ChatChannel chan) {
|
|
||||||
if(!InChannel(chan)) {
|
|
||||||
Channels.Add(chan);
|
|
||||||
CurrentChannel = chan;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void LeaveChannel(ChatChannel chan) {
|
|
||||||
Channels.Remove(chan);
|
|
||||||
CurrentChannel = Channels.FirstOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<ChatChannel> GetChannels() {
|
|
||||||
return Channels.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool NameEquals(string name) {
|
public bool NameEquals(string name) {
|
||||||
return string.Equals(name, Username, StringComparison.InvariantCultureIgnoreCase)
|
return string.Equals(name, Username, StringComparison.InvariantCultureIgnoreCase)
|
||||||
|| string.Equals(name, Nickname, StringComparison.InvariantCultureIgnoreCase)
|
|| string.Equals(name, Nickname, StringComparison.InvariantCultureIgnoreCase)
|
||||||
|
|
|
@ -44,7 +44,7 @@ namespace SharpChat.Commands {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach(ChatUser whoUser in whoChan.GetUsers()) {
|
foreach(ChatUser whoUser in ctx.Chat.GetChannelUsers(whoChan)) {
|
||||||
whoChanSB.Append(@"<a href=""javascript:void(0);"" onclick=""UI.InsertChatText(this.innerHTML);""");
|
whoChanSB.Append(@"<a href=""javascript:void(0);"" onclick=""UI.InsertChatText(this.innerHTML);""");
|
||||||
|
|
||||||
if(whoUser == ctx.User)
|
if(whoUser == ctx.User)
|
||||||
|
|
|
@ -105,8 +105,8 @@ namespace SharpChat.PacketHandlers {
|
||||||
user = new ChatUser(fai);
|
user = new ChatUser(fai);
|
||||||
else {
|
else {
|
||||||
user.ApplyAuth(fai);
|
user.ApplyAuth(fai);
|
||||||
if(user.Channel != null)
|
if(user.CurrentChannel != null)
|
||||||
ctx.Chat.SendTo(user.Channel, new UserUpdatePacket(user));
|
ctx.Chat.SendTo(user.CurrentChannel, new UserUpdatePacket(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enforce a maximum amount of connections per user
|
// Enforce a maximum amount of connections per user
|
||||||
|
|
|
@ -45,7 +45,7 @@ namespace SharpChat.PacketHandlers {
|
||||||
ChatChannel channel = user.CurrentChannel;
|
ChatChannel channel = user.CurrentChannel;
|
||||||
|
|
||||||
if(channel == null
|
if(channel == null
|
||||||
|| !user.InChannel(channel)
|
|| !ctx.Chat.IsInChannel(user, channel)
|
||||||
|| (user.IsSilenced && !user.Can(ChatUserPermissions.SilenceUser)))
|
|| (user.IsSilenced && !user.Can(ChatUserPermissions.SilenceUser)))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -151,7 +151,7 @@ namespace SharpChat {
|
||||||
Context.Connections.Remove(conn);
|
Context.Connections.Remove(conn);
|
||||||
|
|
||||||
if(conn.User != null && !Context.Connections.Any(c => c.User == conn.User))
|
if(conn.User != null && !Context.Connections.Any(c => c.User == conn.User))
|
||||||
Context.UserLeave(null, conn.User);
|
Context.HandleDisconnect(conn.User);
|
||||||
}
|
}
|
||||||
|
|
||||||
Context.Update();
|
Context.Update();
|
||||||
|
|
Loading…
Reference in a new issue