Some cleanups (snapshot, don't run this).

This commit is contained in:
flash 2024-05-19 01:53:14 +00:00
parent 68a523f76a
commit bd23d3aa15
35 changed files with 102 additions and 209 deletions

View file

@ -12,14 +12,6 @@ namespace SharpChat {
public bool HasPassword public bool HasPassword
=> !string.IsNullOrWhiteSpace(Password); => !string.IsNullOrWhiteSpace(Password);
public ChatChannel(
ChatUser owner,
string name,
string? password = null,
bool isTemporary = false,
int rank = 0
) : this(name, password, isTemporary, rank, owner?.UserId ?? 0) {}
public ChatChannel( public ChatChannel(
string name, string name,
string? password = null, string? password = null,
@ -44,10 +36,6 @@ namespace SharpChat {
&& OwnerId == user.UserId; && OwnerId == user.UserId;
} }
public override int GetHashCode() {
return Name.GetHashCode();
}
public static bool CheckName(string name) { public static bool CheckName(string name) {
return !string.IsNullOrWhiteSpace(name) && name.All(CheckNameChar); return !string.IsNullOrWhiteSpace(name) && name.All(CheckNameChar);
} }

View file

@ -21,19 +21,11 @@
Inherits = false; Inherits = false;
} }
public override bool Equals(object? obj) {
return obj is ChatColour colour && Equals(colour);
}
public bool Equals(ChatColour other) { public bool Equals(ChatColour other) {
return Red == other.Red return Inherits == other.Inherits
&& Red == other.Red
&& Green == other.Green && Green == other.Green
&& Blue == other.Blue && Blue == other.Blue;
&& Inherits == other.Inherits;
}
public override int GetHashCode() {
return ToMisuzu();
} }
public override string ToString() { public override string ToString() {
@ -65,13 +57,5 @@
? None ? None
: FromRawRGB(raw); : FromRawRGB(raw);
} }
public static bool operator ==(ChatColour left, ChatColour right) {
return left.Equals(right);
}
public static bool operator !=(ChatColour left, ChatColour right) {
return !(left == right);
}
} }
} }

View file

@ -17,35 +17,16 @@ namespace SharpChat {
ChatConnection connection, ChatConnection connection,
ChatChannel channel ChatChannel channel
) { ) {
if(text == null) Chat = chat;
throw new ArgumentNullException(nameof(text)); User = user;
Connection = connection;
Chat = chat ?? throw new ArgumentNullException(nameof(chat)); Channel = channel;
User = user ?? throw new ArgumentNullException(nameof(user));
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
Channel = channel ?? throw new ArgumentNullException(nameof(channel));
string[] parts = text[1..].Split(' '); string[] parts = text[1..].Split(' ');
Name = parts.First().Replace(".", string.Empty); Name = parts.First().Replace(".", string.Empty);
Args = parts.Skip(1).ToArray(); Args = parts.Skip(1).ToArray();
} }
public ChatCommandContext(
string name,
string[] args,
ChatContext chat,
ChatUser user,
ChatConnection connection,
ChatChannel channel
) {
Name = name ?? throw new ArgumentNullException(nameof(name));
Args = args ?? throw new ArgumentNullException(nameof(args));
Chat = chat ?? throw new ArgumentNullException(nameof(chat));
User = user ?? throw new ArgumentNullException(nameof(user));
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
Channel = channel ?? throw new ArgumentNullException(nameof(channel));
}
public bool NameEquals(string name) { public bool NameEquals(string name) {
return Name.Equals(name, StringComparison.InvariantCultureIgnoreCase); return Name.Equals(name, StringComparison.InvariantCultureIgnoreCase);
} }

View file

@ -84,9 +84,5 @@ namespace SharpChat {
public override string ToString() { public override string ToString() {
return Id; return Id;
} }
public override int GetHashCode() {
return Id.GetHashCode();
}
} }
} }

View file

@ -13,16 +13,16 @@ namespace SharpChat {
public readonly SemaphoreSlim ContextAccess = new(1, 1); public readonly SemaphoreSlim ContextAccess = new(1, 1);
public HashSet<ChatChannel> Channels { get; } = new(); public Dictionary<string, ChatChannel> Channels { get; } = new();
public HashSet<ChatConnection> Connections { get; } = new(); public List<ChatConnection> Connections { get; } = new();
public HashSet<ChatUser> Users { get; } = new(); public Dictionary<long, ChatUser> Users { get; } = new();
public IEventStorage Events { get; } public IEventStorage Events { get; }
public HashSet<ChannelUserAssoc> ChannelUsers { get; } = new(); public HashSet<ChannelUserAssoc> ChannelUsers { get; } = new();
public Dictionary<long, RateLimiter> UserRateLimiters { get; } = new(); public Dictionary<long, RateLimiter> UserRateLimiters { get; } = new();
public Dictionary<long, ChatChannel> UserLastChannel { get; } = new(); public Dictionary<long, ChatChannel> UserLastChannel { get; } = new();
public ChatContext(IEventStorage evtStore) { public ChatContext(IEventStorage evtStore) {
Events = evtStore ?? throw new ArgumentNullException(nameof(evtStore)); Events = evtStore;
} }
public void DispatchEvent(IChatEvent eventInfo) { public void DispatchEvent(IChatEvent eventInfo) {
@ -37,11 +37,11 @@ namespace SharpChat {
if(mce.ChannelName?.StartsWith("@") != true) if(mce.ChannelName?.StartsWith("@") != true)
return; return;
IEnumerable<long> uids = mce.ChannelName[1..].Split('-', 3).Select(u => long.TryParse(u, out long up) ? up : -1); long[] targetIds = mce.ChannelName[1..].Split('-', 3).Select(u => long.TryParse(u, out long up) ? up : -1).ToArray();
if(uids.Count() != 2) if(targetIds.Length != 2)
return; return;
IEnumerable<ChatUser> users = Users.Where(u => uids.Any(uid => uid == u.UserId)); ChatUser[] users = Users.Where(kvp => targetIds.Contains(kvp.Key)).Select(kvp => kvp.Value).ToArray();
ChatUser? target = users.FirstOrDefault(u => u.UserId != mce.SenderId); ChatUser? target = users.FirstOrDefault(u => u.UserId != mce.SenderId);
if(target == null) if(target == null)
return; return;
@ -56,7 +56,7 @@ namespace SharpChat {
true true
)); ));
} else { } else {
ChatChannel? channel = Channels.FirstOrDefault(c => c.NameEquals(mce.ChannelName)); ChatChannel? channel = Channels.Values.FirstOrDefault(c => c.NameEquals(mce.ChannelName));
if(channel != null) if(channel != null)
SendTo(channel, new MessageAddPacket( SendTo(channel, new MessageAddPacket(
mce.MessageId, mce.MessageId,
@ -88,9 +88,11 @@ namespace SharpChat {
Logger.Write($"Nuked connection {conn.Id} associated with {conn.User}."); Logger.Write($"Nuked connection {conn.Id} associated with {conn.User}.");
} }
Connections.RemoveWhere(conn => conn.IsDisposed); int removed = Connections.RemoveAll(conn => conn.IsDisposed);
if(removed > 0)
Logger.Write($"Removed {removed} nuked connections from the list.");
foreach(ChatUser user in Users) foreach(ChatUser user in Users.Values)
if(!Connections.Any(conn => conn.User == user)) { if(!Connections.Any(conn => conn.User == user)) {
HandleDisconnect(user, ChatUserDisconnectReason.TimeOut); HandleDisconnect(user, ChatUserDisconnectReason.TimeOut);
Logger.Write($"Timed out {user} (no more connections)."); Logger.Write($"Timed out {user} (no more connections).");
@ -118,7 +120,7 @@ namespace SharpChat {
public ChatChannel[] GetUserChannels(ChatUser user) { public ChatChannel[] GetUserChannels(ChatUser user) {
string[] names = GetUserChannelNames(user); string[] names = GetUserChannelNames(user);
return Channels.Where(c => names.Any(n => c.NameEquals(n))).ToArray(); return Channels.Values.Where(c => names.Any(n => c.NameEquals(n))).ToArray();
} }
public long[] GetChannelUserIds(ChatChannel channel) { public long[] GetChannelUserIds(ChatChannel channel) {
@ -126,8 +128,8 @@ namespace SharpChat {
} }
public ChatUser[] GetChannelUsers(ChatChannel channel) { public ChatUser[] GetChannelUsers(ChatChannel channel) {
long[] ids = GetChannelUserIds(channel); long[] targetIds = GetChannelUserIds(channel);
return Users.Where(u => ids.Contains(u.UserId)).ToArray(); return Users.Values.Where(u => targetIds.Contains(u.UserId)).ToArray();
} }
public void UpdateUser( public void UpdateUser(
@ -142,9 +144,6 @@ namespace SharpChat {
bool? isSuper = null, bool? isSuper = null,
bool silent = false bool silent = false
) { ) {
if(user == null)
throw new ArgumentNullException(nameof(user));
bool hasChanged = false; bool hasChanged = false;
string? previousName = null; string? previousName = null;
@ -161,7 +160,7 @@ namespace SharpChat {
hasChanged = true; hasChanged = true;
} }
if(colour.HasValue && user.Colour != colour.Value) { if(colour.HasValue && user.Colour.Equals(colour.Value)) {
user.Colour = colour.Value; user.Colour = colour.Value;
hasChanged = true; hasChanged = true;
} }
@ -215,7 +214,7 @@ namespace SharpChat {
foreach(ChatConnection conn in Connections) foreach(ChatConnection conn in Connections)
if(conn.User == user) if(conn.User == user)
conn.Dispose(); conn.Dispose();
Connections.RemoveWhere(conn => conn.IsDisposed); Connections.RemoveAll(conn => conn.IsDisposed);
HandleDisconnect(user, reason); HandleDisconnect(user, reason);
} }
@ -261,11 +260,11 @@ namespace SharpChat {
HandleChannelEventLog(chan.Name, p => conn.Send(p)); HandleChannelEventLog(chan.Name, p => conn.Send(p));
conn.Send(new ChannelsPopulatePacket(Channels.Where(c => c.Rank <= user.Rank).Select( conn.Send(new ChannelsPopulatePacket(Channels.Values.Where(c => c.Rank <= user.Rank).Select(
channel => new ChannelsPopulatePacket.ListEntry(channel.Name, channel.HasPassword, channel.IsTemporary) channel => new ChannelsPopulatePacket.ListEntry(channel.Name, channel.HasPassword, channel.IsTemporary)
).ToArray())); ).ToArray()));
Users.Add(user); Users.Add(user.UserId, user);
ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.Name)); ChannelUsers.Add(new ChannelUserAssoc(user.UserId, chan.Name));
UserLastChannel[user.UserId] = chan; UserLastChannel[user.UserId] = chan;
@ -273,7 +272,7 @@ namespace SharpChat {
public void HandleDisconnect(ChatUser user, ChatUserDisconnectReason reason = ChatUserDisconnectReason.Leave) { public void HandleDisconnect(ChatUser user, ChatUserDisconnectReason reason = ChatUserDisconnectReason.Leave) {
UpdateUser(user, status: ChatUserStatus.Offline); UpdateUser(user, status: ChatUserStatus.Offline);
Users.Remove(user); Users.Remove(user.UserId);
UserLastChannel.Remove(user.UserId); UserLastChannel.Remove(user.UserId);
ChatChannel[] channels = GetUserChannels(user); ChatChannel[] channels = GetUserChannels(user);
@ -313,7 +312,7 @@ namespace SharpChat {
} }
public void ForceChannelSwitch(ChatUser user, ChatChannel chan) { public void ForceChannelSwitch(ChatUser user, ChatChannel chan) {
if(!Channels.Contains(chan)) if(!Channels.ContainsValue(chan))
return; return;
ChatChannel oldChan = UserLastChannel[user.UserId]; ChatChannel oldChan = UserLastChannel[user.UserId];
@ -340,31 +339,18 @@ namespace SharpChat {
} }
public void Send(IServerPacket packet) { public void Send(IServerPacket packet) {
if(packet == null)
throw new ArgumentNullException(nameof(packet));
foreach(ChatConnection conn in Connections) foreach(ChatConnection conn in Connections)
if(conn.IsAuthed) if(conn.IsAuthed)
conn.Send(packet); conn.Send(packet);
} }
public void SendTo(ChatUser user, IServerPacket packet) { public void SendTo(ChatUser user, IServerPacket packet) {
if(user == null)
throw new ArgumentNullException(nameof(user));
if(packet == null)
throw new ArgumentNullException(nameof(packet));
foreach(ChatConnection conn in Connections) foreach(ChatConnection conn in Connections)
if(conn.IsAlive && conn.User == user) if(conn.IsAlive && conn.User == user)
conn.Send(packet); conn.Send(packet);
} }
public void SendTo(ChatChannel channel, IServerPacket packet) { public void SendTo(ChatChannel channel, IServerPacket packet) {
if(channel == null)
throw new ArgumentNullException(nameof(channel));
if(packet == null)
throw new ArgumentNullException(nameof(packet));
// might be faster to grab the users first and then cascade into that SendTo // might be faster to grab the users first and then cascade into that SendTo
IEnumerable<ChatConnection> conns = Connections.Where(c => c.IsAuthed && IsInChannel(c.User, channel)); IEnumerable<ChatConnection> conns = Connections.Where(c => c.IsAuthed && IsInChannel(c.User, channel));
foreach(ChatConnection conn in conns) foreach(ChatConnection conn in conns)
@ -372,12 +358,7 @@ namespace SharpChat {
} }
public void SendToUserChannels(ChatUser user, IServerPacket packet) { public void SendToUserChannels(ChatUser user, IServerPacket packet) {
if(user == null) IEnumerable<ChatChannel> chans = Channels.Values.Where(c => IsInChannel(user, c));
throw new ArgumentNullException(nameof(user));
if(packet == null)
throw new ArgumentNullException(nameof(packet));
IEnumerable<ChatChannel> chans = Channels.Where(c => IsInChannel(user, c));
IEnumerable<ChatConnection> conns = Connections.Where(conn => conn.IsAuthed && ChannelUsers.Any(cu => cu.UserId == conn.User?.UserId && chans.Any(chan => chan.NameEquals(cu.ChannelName)))); IEnumerable<ChatConnection> conns = Connections.Where(conn => conn.IsAuthed && ChannelUsers.Any(cu => cu.UserId == conn.User?.UserId && chans.Any(chan => chan.NameEquals(cu.ChannelName))));
foreach(ChatConnection conn in conns) foreach(ChatConnection conn in conns)
conn.Send(packet); conn.Send(packet);
@ -388,9 +369,6 @@ namespace SharpChat {
} }
public void ForceChannel(ChatUser user, ChatChannel? chan = null) { public void ForceChannel(ChatUser user, ChatChannel? chan = null) {
if(user == null)
throw new ArgumentNullException(nameof(user));
if(chan == null && !UserLastChannel.TryGetValue(user.UserId, out chan)) if(chan == null && !UserLastChannel.TryGetValue(user.UserId, out chan))
throw new ArgumentException("no channel???"); throw new ArgumentException("no channel???");
@ -403,9 +381,7 @@ namespace SharpChat {
int? minRank = null, int? minRank = null,
string? password = null string? password = null
) { ) {
if(channel == null) if(!Channels.ContainsValue(channel))
throw new ArgumentNullException(nameof(channel));
if(!Channels.Contains(channel))
throw new ArgumentException("Provided channel is not registered with this manager.", nameof(channel)); throw new ArgumentException("Provided channel is not registered with this manager.", nameof(channel));
string prevName = channel.Name; string prevName = channel.Name;
@ -420,7 +396,7 @@ namespace SharpChat {
channel.Password = password; channel.Password = password;
// TODO: Users that no longer have access to the channel/gained access to the channel by the rank change should receive delete and create packets respectively // TODO: Users that no longer have access to the channel/gained access to the channel by the rank change should receive delete and create packets respectively
foreach(ChatUser user in Users.Where(u => u.Rank >= channel.Rank)) { foreach(ChatUser user in Users.Values.Where(u => u.Rank >= channel.Rank)) {
SendTo(user, new ChannelUpdatePacket(prevName, channel.Name, channel.HasPassword, channel.IsTemporary)); SendTo(user, new ChannelUpdatePacket(prevName, channel.Name, channel.HasPassword, channel.IsTemporary));
} }
} }
@ -429,12 +405,12 @@ namespace SharpChat {
if(channel == null || !Channels.Any()) if(channel == null || !Channels.Any())
return; return;
ChatChannel? defaultChannel = Channels.FirstOrDefault(); ChatChannel? defaultChannel = Channels.Values.FirstOrDefault();
if(defaultChannel == null) if(defaultChannel == null)
return; return;
// Remove channel from the listing // Remove channel from the listing
Channels.Remove(channel); Channels.Remove(channel.Name);
// 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.
@ -442,7 +418,7 @@ namespace SharpChat {
SwitchChannel(user, defaultChannel, string.Empty); SwitchChannel(user, defaultChannel, string.Empty);
// Broadcast deletion of channel // Broadcast deletion of channel
foreach(ChatUser user in Users.Where(u => u.Rank >= channel.Rank)) foreach(ChatUser user in Users.Values.Where(u => u.Rank >= channel.Rank))
SendTo(user, new ChannelDeletePacket(channel.Name)); SendTo(user, new ChannelDeletePacket(channel.Name));
} }
} }

View file

@ -11,9 +11,9 @@ namespace SharpChat {
ChatContext chat, ChatContext chat,
ChatConnection connection ChatConnection connection
) { ) {
Text = text ?? throw new ArgumentNullException(nameof(text)); Text = text;
Chat = chat ?? throw new ArgumentNullException(nameof(chat)); Chat = chat;
Connection = connection ?? throw new ArgumentNullException(nameof(connection)); Connection = connection;
} }
public bool CheckPacketId(string packetId) { public bool CheckPacketId(string packetId) {

View file

@ -2,7 +2,7 @@
using System.Text; using System.Text;
namespace SharpChat { namespace SharpChat {
public class ChatUser : IEquatable<ChatUser> { public class ChatUser {
public const int DEFAULT_SIZE = 30; public const int DEFAULT_SIZE = 30;
public const int DEFAULT_MINIMUM_DELAY = 10000; public const int DEFAULT_MINIMUM_DELAY = 10000;
public const int DEFAULT_RISKY_OFFSET = 5; public const int DEFAULT_RISKY_OFFSET = 5;
@ -34,7 +34,7 @@ namespace SharpChat {
public ChatUser( public ChatUser(
long userId, long userId,
string? userName, string userName,
ChatColour colour, ChatColour colour,
int rank, int rank,
ChatUserPermissions perms, ChatUserPermissions perms,
@ -44,7 +44,7 @@ namespace SharpChat {
bool isSuper = false bool isSuper = false
) { ) {
UserId = userId; UserId = userId;
UserName = userName ?? throw new ArgumentNullException(nameof(userName)); UserName = userName;
Colour = colour; Colour = colour;
Rank = rank; Rank = rank;
Permissions = perms; Permissions = perms;
@ -61,18 +61,6 @@ namespace SharpChat {
|| string.Equals(name, LegacyNameWithStatus, StringComparison.InvariantCultureIgnoreCase); || string.Equals(name, LegacyNameWithStatus, StringComparison.InvariantCultureIgnoreCase);
} }
public override int GetHashCode() {
return UserId.GetHashCode();
}
public override bool Equals(object? obj) {
return Equals(obj as ChatUser);
}
public bool Equals(ChatUser? other) {
return UserId == other?.UserId;
}
public static string GetDMChannelName(ChatUser user1, ChatUser user2) { public static string GetDMChannelName(ChatUser user1, ChatUser user2) {
return user1.UserId < user2.UserId return user1.UserId < user2.UserId
? $"@{user1.UserId}-{user2.UserId}" ? $"@{user1.UserId}-{user2.UserId}"

View file

@ -9,7 +9,7 @@ namespace SharpChat.Commands {
private readonly MisuzuClient Misuzu; private readonly MisuzuClient Misuzu;
public BanListCommand(MisuzuClient msz) { public BanListCommand(MisuzuClient msz) {
Misuzu = msz ?? throw new ArgumentNullException(nameof(msz)); Misuzu = msz;
} }
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ChatCommandContext ctx) {

View file

@ -38,19 +38,20 @@ namespace SharpChat.Commands {
return; return;
} }
if(ctx.Chat.Channels.Any(c => c.NameEquals(createChanName))) { if(ctx.Chat.Channels.Values.Any(c => c.NameEquals(createChanName))) {
ctx.Chat.SendTo(ctx.User, new ChannelNameInUseErrorPacket(createChanName)); ctx.Chat.SendTo(ctx.User, new ChannelNameInUseErrorPacket(createChanName));
return; return;
} }
ChatChannel createChan = new( ChatChannel createChan = new(
ctx.User, createChanName, createChanName,
isTemporary: !ctx.User.Permissions.HasFlag(ChatUserPermissions.SetChannelPermanent), isTemporary: !ctx.User.Permissions.HasFlag(ChatUserPermissions.SetChannelPermanent),
rank: createChanHierarchy rank: createChanHierarchy,
ownerId: ctx.User.UserId
); );
ctx.Chat.Channels.Add(createChan); ctx.Chat.Channels.Add(createChan.Name, createChan);
foreach(ChatUser ccu in ctx.Chat.Users.Where(u => u.Rank >= ctx.Channel.Rank)) foreach(ChatUser ccu in ctx.Chat.Users.Values.Where(u => u.Rank >= ctx.Channel.Rank))
ctx.Chat.SendTo(ccu, new ChannelCreatePacket( ctx.Chat.SendTo(ccu, new ChannelCreatePacket(
ctx.Channel.Name, ctx.Channel.Name,
ctx.Channel.HasPassword, ctx.Channel.HasPassword,

View file

@ -17,7 +17,7 @@ namespace SharpChat.Commands {
} }
string delChanName = string.Join('_', ctx.Args); string delChanName = string.Join('_', ctx.Args);
ChatChannel? delChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(delChanName)); ChatChannel? delChan = ctx.Chat.Channels.Values.FirstOrDefault(c => c.NameEquals(delChanName));
if(delChan == null) { if(delChan == null) {
ctx.Chat.SendTo(ctx.User, new ChannelNotFoundErrorPacket(delChanName)); ctx.Chat.SendTo(ctx.User, new ChannelNotFoundErrorPacket(delChanName));

View file

@ -9,7 +9,7 @@ namespace SharpChat.Commands {
public void Dispatch(ChatCommandContext ctx) { public void Dispatch(ChatCommandContext ctx) {
string joinChanStr = ctx.Args.FirstOrDefault() ?? string.Empty; string joinChanStr = ctx.Args.FirstOrDefault() ?? string.Empty;
ChatChannel? joinChan = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(joinChanStr)); ChatChannel? joinChan = ctx.Chat.Channels.Values.FirstOrDefault(c => c.NameEquals(joinChanStr));
if(joinChan == null) { if(joinChan == null) {
ctx.Chat.SendTo(ctx.User, new ChannelNotFoundErrorPacket(joinChanStr)); ctx.Chat.SendTo(ctx.User, new ChannelNotFoundErrorPacket(joinChanStr));

View file

@ -9,7 +9,7 @@ namespace SharpChat.Commands {
private readonly MisuzuClient Misuzu; private readonly MisuzuClient Misuzu;
public KickBanCommand(MisuzuClient msz) { public KickBanCommand(MisuzuClient msz) {
Misuzu = msz ?? throw new ArgumentNullException(nameof(msz)); Misuzu = msz;
} }
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ChatCommandContext ctx) {
@ -30,7 +30,7 @@ namespace SharpChat.Commands {
int banReasonIndex = 1; int banReasonIndex = 1;
ChatUser? banUser = null; ChatUser? banUser = null;
if(string.IsNullOrEmpty(banUserTarget) || (banUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(banUserTarget))) == null) { if(string.IsNullOrEmpty(banUserTarget) || (banUser = ctx.Chat.Users.Values.FirstOrDefault(u => u.NameEquals(banUserTarget))) == null) {
ctx.Chat.SendTo(ctx.User, new UserNotFoundErrorPacket(banUserTarget)); ctx.Chat.SendTo(ctx.User, new UserNotFoundErrorPacket(banUserTarget));
return; return;
} }

View file

@ -17,7 +17,7 @@ namespace SharpChat.Commands {
} }
string whisperUserStr = ctx.Args.FirstOrDefault() ?? string.Empty; string whisperUserStr = ctx.Args.FirstOrDefault() ?? string.Empty;
ChatUser? whisperUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(whisperUserStr)); ChatUser? whisperUser = ctx.Chat.Users.Values.FirstOrDefault(u => u.NameEquals(whisperUserStr));
if(whisperUser == null) { if(whisperUser == null) {
ctx.Chat.SendTo(ctx.User, new UserNotFoundErrorPacket(whisperUserStr)); ctx.Chat.SendTo(ctx.User, new UserNotFoundErrorPacket(whisperUserStr));

View file

@ -10,7 +10,7 @@ namespace SharpChat.Commands {
private readonly MisuzuClient Misuzu; private readonly MisuzuClient Misuzu;
public PardonAddressCommand(MisuzuClient msz) { public PardonAddressCommand(MisuzuClient msz) {
Misuzu = msz ?? throw new ArgumentNullException(nameof(msz)); Misuzu = msz;
} }
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ChatCommandContext ctx) {

View file

@ -9,7 +9,7 @@ namespace SharpChat.Commands {
private readonly MisuzuClient Misuzu; private readonly MisuzuClient Misuzu;
public PardonUserCommand(MisuzuClient msz) { public PardonUserCommand(MisuzuClient msz) {
Misuzu = msz ?? throw new ArgumentNullException(nameof(msz)); Misuzu = msz;
} }
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ChatCommandContext ctx) {
@ -31,10 +31,10 @@ namespace SharpChat.Commands {
return; return;
} }
ChatUser? unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(unbanUserTarget)); ChatUser? unbanUser = ctx.Chat.Users.Values.FirstOrDefault(u => u.NameEquals(unbanUserTarget));
if(unbanUser == null && long.TryParse(unbanUserTarget, out long unbanUserId)) { if(unbanUser == null && long.TryParse(unbanUserTarget, out long unbanUserId)) {
unbanUserTargetIsName = false; unbanUserTargetIsName = false;
unbanUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == unbanUserId); unbanUser = ctx.Chat.Users.Values.FirstOrDefault(u => u.UserId == unbanUserId);
} }
if(unbanUser != null) if(unbanUser != null)

View file

@ -8,8 +8,8 @@ namespace SharpChat.Commands {
private readonly Func<bool> ShutdownCheck; private readonly Func<bool> ShutdownCheck;
public ShutdownRestartCommand(ManualResetEvent waitHandle, Func<bool> shutdownCheck) { public ShutdownRestartCommand(ManualResetEvent waitHandle, Func<bool> shutdownCheck) {
WaitHandle = waitHandle ?? throw new ArgumentNullException(nameof(waitHandle)); WaitHandle = waitHandle;
ShutdownCheck = shutdownCheck ?? throw new ArgumentNullException(nameof(shutdownCheck)); ShutdownCheck = shutdownCheck;
} }
public bool IsMatch(ChatCommandContext ctx) { public bool IsMatch(ChatCommandContext ctx) {

View file

@ -19,7 +19,7 @@ namespace SharpChat.Commands {
int offset = 0; int offset = 0;
if(setOthersNick && long.TryParse(ctx.Args.FirstOrDefault(), out long targetUserId) && targetUserId > 0) { if(setOthersNick && long.TryParse(ctx.Args.FirstOrDefault(), out long targetUserId) && targetUserId > 0) {
targetUser = ctx.Chat.Users.FirstOrDefault(u => u.UserId == targetUserId); targetUser = ctx.Chat.Users.Values.FirstOrDefault(u => u.UserId == targetUserId);
++offset; ++offset;
} }
@ -42,7 +42,7 @@ namespace SharpChat.Commands {
else if(string.IsNullOrEmpty(nickStr)) else if(string.IsNullOrEmpty(nickStr))
nickStr = string.Empty; nickStr = string.Empty;
if(!string.IsNullOrWhiteSpace(nickStr) && ctx.Chat.Users.Any(u => u.NameEquals(nickStr))) { if(!string.IsNullOrWhiteSpace(nickStr) && ctx.Chat.Users.Values.Any(u => u.NameEquals(nickStr))) {
ctx.Chat.SendTo(ctx.User, new UserNameInUseErrorPacket(nickStr)); ctx.Chat.SendTo(ctx.User, new UserNameInUseErrorPacket(nickStr));
return; return;
} }

View file

@ -12,13 +12,13 @@ namespace SharpChat.Commands {
if(string.IsNullOrEmpty(channelName)) { if(string.IsNullOrEmpty(channelName)) {
ctx.Chat.SendTo(ctx.User, new WhoServerResponsePacket( ctx.Chat.SendTo(ctx.User, new WhoServerResponsePacket(
ctx.Chat.Users.Select(u => u.LegacyName).ToArray(), ctx.Chat.Users.Values.Select(u => u.LegacyName).ToArray(),
ctx.User.LegacyName ctx.User.LegacyName
)); ));
return; return;
} }
ChatChannel? channel = ctx.Chat.Channels.FirstOrDefault(c => c.NameEquals(channelName)); ChatChannel? channel = ctx.Chat.Channels.Values.FirstOrDefault(c => c.NameEquals(channelName));
if(channel == null) { if(channel == null) {
ctx.Chat.SendTo(ctx.User, new ChannelNotFoundErrorPacket(channelName)); ctx.Chat.SendTo(ctx.User, new ChannelNotFoundErrorPacket(channelName));

View file

@ -18,7 +18,7 @@ namespace SharpChat.Commands {
string ipUserStr = ctx.Args.FirstOrDefault() ?? string.Empty; string ipUserStr = ctx.Args.FirstOrDefault() ?? string.Empty;
ChatUser? ipUser; ChatUser? ipUser;
if(string.IsNullOrWhiteSpace(ipUserStr) || (ipUser = ctx.Chat.Users.FirstOrDefault(u => u.NameEquals(ipUserStr))) == null) { if(string.IsNullOrWhiteSpace(ipUserStr) || (ipUser = ctx.Chat.Users.Values.FirstOrDefault(u => u.NameEquals(ipUserStr))) == null) {
ctx.Chat.SendTo(ctx.User, new UserNotFoundErrorPacket(ipUserStr)); ctx.Chat.SendTo(ctx.User, new UserNotFoundErrorPacket(ipUserStr));
return; return;
} }

View file

@ -28,12 +28,13 @@ namespace SharpChat.Config {
public static implicit operator T?(CachedValue<T> val) => val.Value; public static implicit operator T?(CachedValue<T> val) => val.Value;
public CachedValue(IConfig config, string name, TimeSpan lifetime, T? fallback) { public CachedValue(IConfig config, string name, TimeSpan lifetime, T? fallback) {
Config = config ?? throw new ArgumentNullException(nameof(config));
Name = name ?? throw new ArgumentNullException(nameof(name));
Lifetime = lifetime;
Fallback = fallback;
if(string.IsNullOrWhiteSpace(name)) if(string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Name cannot be empty.", nameof(name)); throw new ArgumentException("Name cannot be empty.", nameof(name));
Config = config;
Name = name;
Lifetime = lifetime;
Fallback = fallback;
} }
public void Refresh() { public void Refresh() {

View file

@ -6,10 +6,12 @@ namespace SharpChat.Config {
private string Prefix { get; } private string Prefix { get; }
public ScopedConfig(IConfig config, string prefix) { public ScopedConfig(IConfig config, string prefix) {
Config = config ?? throw new ArgumentNullException(nameof(config));
Prefix = prefix ?? throw new ArgumentNullException(nameof(prefix));
if(string.IsNullOrWhiteSpace(prefix)) if(string.IsNullOrWhiteSpace(prefix))
throw new ArgumentException("Prefix must exist.", nameof(prefix)); throw new ArgumentException("Prefix must exist.", nameof(prefix));
Config = config;
Prefix = prefix;
if(Prefix[^1] != ':') if(Prefix[^1] != ':')
Prefix += ':'; Prefix += ':';
} }

View file

@ -17,7 +17,7 @@ namespace SharpChat.Config {
: this(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite)) { } : this(new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Read, FileShare.ReadWrite)) { }
public StreamConfig(Stream stream) { public StreamConfig(Stream stream) {
Stream = stream ?? throw new ArgumentNullException(nameof(stream)); Stream = stream;
if(!Stream.CanRead) if(!Stream.CanRead)
throw new ArgumentException("Provided stream must be readable.", nameof(stream)); throw new ArgumentException("Provided stream must be readable.", nameof(stream));
if(!Stream.CanSeek) if(!Stream.CanSeek)

View file

@ -1,18 +1,15 @@
using MySqlConnector; using MySqlConnector;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Dynamic;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading.Channels;
namespace SharpChat.EventStorage namespace SharpChat.EventStorage {
{
public partial class MariaDBEventStorage : IEventStorage { public partial class MariaDBEventStorage : IEventStorage {
private string ConnectionString { get; } private string ConnectionString { get; }
public MariaDBEventStorage(string connString) { public MariaDBEventStorage(string connString) {
ConnectionString = connString ?? throw new ArgumentNullException(nameof(connString)); ConnectionString = connString;
} }
public void AddEvent( public void AddEvent(
@ -48,9 +45,6 @@ namespace SharpChat.EventStorage
object? data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
) { ) {
if(type == null)
throw new ArgumentNullException(nameof(type));
RunCommand( RunCommand(
"INSERT INTO `sqc_events` (`event_id`, `event_created`, `event_type`, `event_target`, `event_flags`, `event_data`" "INSERT INTO `sqc_events` (`event_id`, `event_created`, `event_type`, `event_target`, `event_flags`, `event_data`"
+ ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`)" + ", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`)"
@ -71,9 +65,6 @@ namespace SharpChat.EventStorage
} }
public long AddEvent(string type, ChatUser user, ChatChannel channel, object? data = null, StoredEventFlags flags = StoredEventFlags.None) { public long AddEvent(string type, ChatUser user, ChatChannel channel, object? data = null, StoredEventFlags flags = StoredEventFlags.None) {
if(type == null)
throw new ArgumentNullException(nameof(type));
long id = SharpId.Next(); long id = SharpId.Next();
AddEvent( AddEvent(
@ -172,8 +163,6 @@ namespace SharpChat.EventStorage
} }
public void RemoveEvent(StoredEventInfo evt) { public void RemoveEvent(StoredEventInfo evt) {
if(evt == null)
throw new ArgumentNullException(nameof(evt));
RunCommand( RunCommand(
"UPDATE IGNORE `sqc_events` SET `event_deleted` = NOW() WHERE `event_id` = @id AND `event_deleted` IS NULL", "UPDATE IGNORE `sqc_events` SET `event_deleted` = NOW() WHERE `event_id` = @id AND `event_deleted` IS NULL",
new MySqlParameter("id", evt.Id) new MySqlParameter("id", evt.Id)

View file

@ -40,14 +40,11 @@ namespace SharpChat.EventStorage {
object? data = null, object? data = null,
StoredEventFlags flags = StoredEventFlags.None StoredEventFlags flags = StoredEventFlags.None
) { ) {
if(type == null)
throw new ArgumentNullException(nameof(type));
// VES is meant as an emergency fallback but this is something else // VES is meant as an emergency fallback but this is something else
JsonDocument hack = JsonDocument.Parse(data == null ? "{}" : JsonSerializer.Serialize(data)); JsonDocument hack = JsonDocument.Parse(data == null ? "{}" : JsonSerializer.Serialize(data));
Events.Add(id, new(id, type, senderId < 1 ? null : new ChatUser( Events.Add(id, new(id, type, senderId < 1 ? null : new ChatUser(
senderId, senderId,
senderName, senderName ?? string.Empty,
senderColour, senderColour,
senderRank, senderRank,
senderPerms, senderPerms,
@ -56,9 +53,6 @@ namespace SharpChat.EventStorage {
} }
public long AddEvent(string type, ChatUser user, ChatChannel channel, object? data = null, StoredEventFlags flags = StoredEventFlags.None) { public long AddEvent(string type, ChatUser user, ChatChannel channel, object? data = null, StoredEventFlags flags = StoredEventFlags.None) {
if(type == null)
throw new ArgumentNullException(nameof(type));
long id = SharpId.Next(); long id = SharpId.Next();
AddEvent( AddEvent(

View file

@ -34,9 +34,7 @@ namespace SharpChat.Misuzu {
private CachedValue<string> SecretKey { get; } private CachedValue<string> SecretKey { get; }
public MisuzuClient(HttpClient httpClient, IConfig config) { public MisuzuClient(HttpClient httpClient, IConfig config) {
if(config == null) HttpClient = httpClient;
throw new ArgumentNullException(nameof(config));
HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
BaseURL = config.ReadCached("url", DEFAULT_BASE_URL); BaseURL = config.ReadCached("url", DEFAULT_BASE_URL);
SecretKey = config.ReadCached("secret", DEFAULT_SECRET_KEY); SecretKey = config.ReadCached("secret", DEFAULT_SECRET_KEY);
@ -77,8 +75,6 @@ namespace SharpChat.Misuzu {
} }
public async Task BumpUsersOnlineAsync(IEnumerable<(string userId, string ipAddr)> list) { public async Task BumpUsersOnlineAsync(IEnumerable<(string userId, string ipAddr)> list) {
if(list == null)
throw new ArgumentNullException(nameof(list));
if(!list.Any()) if(!list.Any())
return; return;
@ -164,9 +160,6 @@ namespace SharpChat.Misuzu {
} }
public async Task<bool> RevokeBanAsync(MisuzuBanInfo banInfo, BanRevokeKind kind) { public async Task<bool> RevokeBanAsync(MisuzuBanInfo banInfo, BanRevokeKind kind) {
if(banInfo == null)
throw new ArgumentNullException(nameof(banInfo));
string type = kind switch { string type = kind switch {
BanRevokeKind.UserId => "user", BanRevokeKind.UserId => "user",
BanRevokeKind.RemoteAddress => "addr", BanRevokeKind.RemoteAddress => "addr",
@ -208,9 +201,9 @@ namespace SharpChat.Misuzu {
string reason string reason
) { ) {
if(string.IsNullOrWhiteSpace(targetAddr)) if(string.IsNullOrWhiteSpace(targetAddr))
throw new ArgumentNullException(nameof(targetAddr)); throw new ArgumentException("targetAddr may not be empty", nameof(targetAddr));
if(string.IsNullOrWhiteSpace(modAddr)) if(string.IsNullOrWhiteSpace(modAddr))
throw new ArgumentNullException(nameof(modAddr)); throw new ArgumentException("modAddr may not be empty", nameof(modAddr));
if(duration <= TimeSpan.Zero) if(duration <= TimeSpan.Zero)
return; return;

View file

@ -9,7 +9,7 @@ namespace SharpChat.Packet {
private readonly bool Notify; private readonly bool Notify;
public MessagePopulatePacket(StoredEventInfo evt, bool notify = false) { public MessagePopulatePacket(StoredEventInfo evt, bool notify = false) {
Event = evt ?? throw new ArgumentNullException(nameof(evt)); Event = evt;
Notify = notify; Notify = notify;
} }

View file

@ -5,7 +5,7 @@ namespace SharpChat.Packet {
private readonly string ChannelName; private readonly string ChannelName;
public UserChannelForceJoinPacket(string channelName) { public UserChannelForceJoinPacket(string channelName) {
ChannelName = channelName ?? throw new ArgumentNullException(nameof(channelName)); ChannelName = channelName;
} }
public override string Pack() { public override string Pack() {

View file

@ -16,7 +16,7 @@ namespace SharpChat.Packet {
ChatUserPermissions userPerms ChatUserPermissions userPerms
) { ) {
UserId = userId; UserId = userId;
UserName = userName ?? throw new ArgumentNullException(nameof(userName)); UserName = userName;
UserColour = userColour; UserColour = userColour;
UserRank = userRank; UserRank = userRank;
UserPerms = userPerms; UserPerms = userPerms;

View file

@ -7,8 +7,8 @@ namespace SharpChat.Packet {
private readonly long Timestamp; private readonly long Timestamp;
public UserUpdateNotificationPacket(string previousName, string newName) { public UserUpdateNotificationPacket(string previousName, string newName) {
PreviousName = previousName ?? throw new ArgumentNullException(nameof(previousName)); PreviousName = previousName;
NewName = newName ?? throw new ArgumentNullException(nameof(newName)); NewName = newName;
Timestamp = DateTimeOffset.Now.ToUnixTimeSeconds(); Timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
} }

View file

@ -16,7 +16,7 @@ namespace SharpChat.Packet {
ChatUserPermissions userPerms ChatUserPermissions userPerms
) { ) {
UserId = userId; UserId = userId;
UserName = userName ?? throw new ArgumentNullException(nameof(userName)); UserName = userName;
UserColour = userColour; UserColour = userColour;
UserRank = userRank; UserRank = userRank;
UserPerms = userPerms; UserPerms = userPerms;

View file

@ -8,7 +8,7 @@ namespace SharpChat.Packet {
private readonly ListEntry[] Entries; private readonly ListEntry[] Entries;
public UsersPopulatePacket(ListEntry[] entries) { public UsersPopulatePacket(ListEntry[] entries) {
Entries = entries ?? throw new ArgumentNullException(nameof(entries)); Entries = entries;
} }
public override string Pack() { public override string Pack() {

View file

@ -20,10 +20,10 @@ namespace SharpChat.PacketHandlers {
CachedValue<int> maxMsgLength, CachedValue<int> maxMsgLength,
CachedValue<int> maxConns CachedValue<int> maxConns
) { ) {
Misuzu = msz ?? throw new ArgumentNullException(nameof(msz)); Misuzu = msz;
DefaultChannel = defaultChannel ?? throw new ArgumentNullException(nameof(defaultChannel)); DefaultChannel = defaultChannel;
MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength)); MaxMessageLength = maxMsgLength;
MaxConnections = maxConns ?? throw new ArgumentNullException(nameof(maxConns)); MaxConnections = maxConns;
} }
public bool IsMatch(ChatPacketHandlerContext ctx) { public bool IsMatch(ChatPacketHandlerContext ctx) {
@ -114,12 +114,12 @@ namespace SharpChat.PacketHandlers {
await ctx.Chat.ContextAccess.WaitAsync(); await ctx.Chat.ContextAccess.WaitAsync();
try { try {
ChatUser? user = ctx.Chat.Users.FirstOrDefault(u => u.UserId == fai.UserId); ChatUser? user = ctx.Chat.Users.Values.FirstOrDefault(u => u.UserId == fai.UserId);
if(user == null) if(user == null)
user = new ChatUser( user = new ChatUser(
fai.UserId, fai.UserId,
fai.UserName, fai.UserName ?? string.Empty,
fai.Colour, fai.Colour,
fai.Rank, fai.Rank,
fai.Permissions, fai.Permissions,

View file

@ -12,7 +12,7 @@ namespace SharpChat.PacketHandlers {
private DateTimeOffset LastBump = DateTimeOffset.MinValue; private DateTimeOffset LastBump = DateTimeOffset.MinValue;
public PingHandler(MisuzuClient msz) { public PingHandler(MisuzuClient msz) {
Misuzu = msz ?? throw new ArgumentNullException(nameof(msz)); Misuzu = msz;
} }
public bool IsMatch(ChatPacketHandlerContext ctx) { public bool IsMatch(ChatPacketHandlerContext ctx) {
@ -31,7 +31,7 @@ namespace SharpChat.PacketHandlers {
ctx.Chat.ContextAccess.Wait(); ctx.Chat.ContextAccess.Wait();
try { try {
if(LastBump < DateTimeOffset.UtcNow - BumpInterval) { if(LastBump < DateTimeOffset.UtcNow - BumpInterval) {
(string, string)[] bumpList = ctx.Chat.Users (string, string)[] bumpList = ctx.Chat.Users.Values
.Where(u => u.Status == ChatUserStatus.Online && ctx.Chat.Connections.Any(c => c.User == u)) .Where(u => u.Status == ChatUserStatus.Online && ctx.Chat.Connections.Any(c => c.User == u))
.Select(u => (u.UserId.ToString(), ctx.Chat.GetRemoteAddresses(u).FirstOrDefault()?.ToString() ?? string.Empty)) .Select(u => (u.UserId.ToString(), ctx.Chat.GetRemoteAddresses(u).FirstOrDefault()?.ToString() ?? string.Empty))
.ToArray(); .ToArray();

View file

@ -15,15 +15,15 @@ namespace SharpChat.PacketHandlers
private List<IChatCommand> Commands { get; } = new(); private List<IChatCommand> Commands { get; } = new();
public SendMessageHandler(CachedValue<int> maxMsgLength) { public SendMessageHandler(CachedValue<int> maxMsgLength) {
MaxMessageLength = maxMsgLength ?? throw new ArgumentNullException(nameof(maxMsgLength)); MaxMessageLength = maxMsgLength;
} }
public void AddCommand(IChatCommand command) { public void AddCommand(IChatCommand command) {
Commands.Add(command ?? throw new ArgumentNullException(nameof(command))); Commands.Add(command);
} }
public void AddCommands(IEnumerable<IChatCommand> commands) { public void AddCommands(IEnumerable<IChatCommand> commands) {
Commands.AddRange(commands ?? throw new ArgumentNullException(nameof(commands))); Commands.AddRange(commands);
} }
public bool IsMatch(ChatPacketHandlerContext ctx) { public bool IsMatch(ChatPacketHandlerContext ctx) {

View file

@ -41,8 +41,8 @@ namespace SharpChat {
public SockChatServer(HttpClient httpClient, MisuzuClient msz, IEventStorage evtStore, IConfig config) { public SockChatServer(HttpClient httpClient, MisuzuClient msz, IEventStorage evtStore, IConfig config) {
Logger.Write("Initialising Sock Chat server..."); Logger.Write("Initialising Sock Chat server...");
HttpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient)); HttpClient = httpClient;
Misuzu = msz ?? throw new ArgumentNullException(nameof(msz)); Misuzu = msz;
MaxMessageLength = config.ReadCached("msgMaxLength", DEFAULT_MSG_LENGTH_MAX); MaxMessageLength = config.ReadCached("msgMaxLength", DEFAULT_MSG_LENGTH_MAX);
MaxConnections = config.ReadCached("connMaxCount", DEFAULT_MAX_CONNECTIONS); MaxConnections = config.ReadCached("connMaxCount", DEFAULT_MAX_CONNECTIONS);
@ -67,13 +67,13 @@ namespace SharpChat {
rank: channelCfg.SafeReadValue("minRank", 0) rank: channelCfg.SafeReadValue("minRank", 0)
); );
Context.Channels.Add(channelInfo); Context.Channels.Add(channelInfo.Name, channelInfo);
DefaultChannel ??= channelInfo; DefaultChannel ??= channelInfo;
} }
DefaultChannel ??= new ChatChannel("Default"); DefaultChannel ??= new ChatChannel("Default");
if(!Context.Channels.Any()) if(Context.Channels.Count < 1)
Context.Channels.Add(DefaultChannel); Context.Channels.Add(DefaultChannel.Name, DefaultChannel);
GuestHandlers.Add(new AuthHandler(Misuzu, DefaultChannel, MaxMessageLength, MaxConnections)); GuestHandlers.Add(new AuthHandler(Misuzu, DefaultChannel, MaxMessageLength, MaxConnections));