From c537df792e95815d4220f7ae0407358bf27c4199 Mon Sep 17 00:00:00 2001
From: flashwave <me@flash.moe>
Date: Mon, 28 Apr 2025 13:03:38 +0000
Subject: [PATCH] Create Context before creating a server.

---
 SharpChat.Flashii/FlashiiClient.cs            |  3 +-
 SharpChat/Context.cs                          | 55 +++++++++++++-
 SharpChat/Program.cs                          | 22 +++---
 SharpChat/SockChatServer.cs                   | 73 ++++---------------
 SharpChatCommon/Configuration/Config.cs       |  2 +-
 SharpChatCommon/Configuration/ScopedConfig.cs |  4 -
 SharpChatCommon/Configuration/StreamConfig.cs |  2 +-
 7 files changed, 85 insertions(+), 76 deletions(-)

diff --git a/SharpChat.Flashii/FlashiiClient.cs b/SharpChat.Flashii/FlashiiClient.cs
index ffac01b..7799431 100644
--- a/SharpChat.Flashii/FlashiiClient.cs
+++ b/SharpChat.Flashii/FlashiiClient.cs
@@ -61,10 +61,11 @@ public class FlashiiClient(ILogger logger, HttpClient httpClient, Config config)
     private const string AUTH_BUMP_USERS_ONLINE_URL = "{0}/bump";
 
     public async Task AuthBumpUsersOnline(IEnumerable<(IPAddress remoteAddr, string userId)> entries) {
-        logger.ZLogInformation($"Bumping online users list...");
         if(!entries.Any())
             return;
 
+        logger.ZLogInformation($"Bumping online users list...");
+
         string now = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
         StringBuilder sb = new();
         sb.AppendFormat("bump#{0}", now);
diff --git a/SharpChat/Context.cs b/SharpChat/Context.cs
index 4e066f1..9328994 100644
--- a/SharpChat/Context.cs
+++ b/SharpChat/Context.cs
@@ -1,5 +1,8 @@
 using Microsoft.Extensions.Logging;
+using SharpChat.Auth;
+using SharpChat.Bans;
 using SharpChat.Channels;
+using SharpChat.Configuration;
 using SharpChat.Events;
 using SharpChat.Messages;
 using SharpChat.Snowflake;
@@ -12,11 +15,26 @@ using ZLogger;
 namespace SharpChat;
 
 public class Context {
+    public const int DEFAULT_MSG_LENGTH_MAX = 5000;
+    public const int DEFAULT_MAX_CONNECTIONS = 5;
+    public const int DEFAULT_FLOOD_KICK_LENGTH = 30;
+    public const int DEFAULT_FLOOD_KICK_EXEMPT_RANK = 9;
+
     public record ChannelUserAssoc(string UserId, string ChannelName);
 
     public readonly SemaphoreSlim ContextAccess = new(1, 1);
 
     public ILoggerFactory LoggerFactory { get; }
+    public Config Config { get; }
+    public MessageStorage Messages { get; }
+    public AuthClient Auth { get; }
+    public BansClient Bans { get; }
+
+    public CachedValue<int> MaxMessageLength { get; }
+    public CachedValue<int> MaxConnections { get; }
+    public CachedValue<int> FloodKickLength { get; }
+    public CachedValue<int> FloodKickExemptRank { get; }
+
     private readonly ILogger Logger;
 
     public SnowflakeGenerator SnowflakeGenerator { get; } = new();
@@ -25,16 +43,47 @@ public class Context {
     public ChannelsContext Channels { get; } = new();
     public HashSet<Connection> Connections { get; } = [];
     public HashSet<User> Users { get; } = [];
-    public MessageStorage Messages { get; }
     public HashSet<ChannelUserAssoc> ChannelUsers { get; } = [];
     public Dictionary<string, RateLimiter> UserRateLimiters { get; } = [];
     public Dictionary<string, Channel> UserLastChannel { get; } = [];
 
-    public Context(ILoggerFactory logFactory, MessageStorage msgs) {
+    public Context(
+        ILoggerFactory logFactory,
+        Config config,
+        Storage storage,
+        AuthClient authClient,
+        BansClient bansClient
+    ) {
         LoggerFactory = logFactory;
         Logger = logFactory.CreateLogger("ctx");
-        Messages = msgs;
+        Config = config;
+        Messages = storage.CreateMessageStorage();
+        Auth = authClient;
+        Bans = bansClient;
         RandomSnowflake = new(SnowflakeGenerator);
+
+        Logger.ZLogDebug($"Reading cached config values...");
+        MaxMessageLength = config.ReadCached("msgMaxLength", DEFAULT_MSG_LENGTH_MAX);
+        MaxConnections = config.ReadCached("connMaxCount", DEFAULT_MAX_CONNECTIONS);
+        FloodKickLength = config.ReadCached("floodKickLength", DEFAULT_FLOOD_KICK_LENGTH);
+        FloodKickExemptRank = config.ReadCached("floodKickExemptRank", DEFAULT_FLOOD_KICK_EXEMPT_RANK);
+
+        Logger.ZLogDebug($"Creating channel list...");
+        string[] channelNames = config.ReadValue<string[]>("channels") ?? ["lounge"];
+        if(channelNames is not null)
+            foreach(string channelName in channelNames) {
+                Config channelCfg = config.ScopeTo($"channels:{channelName}");
+
+                string name = channelCfg.SafeReadValue("name", string.Empty)!;
+                if(string.IsNullOrWhiteSpace(name))
+                    name = channelName;
+
+                Channels.CreateChannel(
+                    name,
+                    channelCfg.SafeReadValue("password", string.Empty)!,
+                    rank: channelCfg.SafeReadValue("minRank", 0)
+                );
+            }
     }
 
     public async Task DispatchEvent(ChatEvent eventInfo) {
diff --git a/SharpChat/Program.cs b/SharpChat/Program.cs
index 39d0e07..523c660 100644
--- a/SharpChat/Program.cs
+++ b/SharpChat/Program.cs
@@ -82,11 +82,12 @@ if(!File.Exists(configFile) && configFile == CONFIG) {
     sw.WriteLine();
 
     sw.WriteLine("# General Configuration");
-    sw.WriteLine($"#chat:port            {SockChatServer.DEFAULT_PORT}");
-    sw.WriteLine($"#chat:msgMaxLength    {SockChatServer.DEFAULT_MSG_LENGTH_MAX}");
-    sw.WriteLine($"#chat:connMaxCount    {SockChatServer.DEFAULT_MAX_CONNECTIONS}");
-    sw.WriteLine($"#chat:floodKickLength {SockChatServer.DEFAULT_FLOOD_KICK_LENGTH}");
+    sw.WriteLine($"#chat:msgMaxLength    {Context.DEFAULT_MSG_LENGTH_MAX}");
+    sw.WriteLine($"#chat:connMaxCount    {Context.DEFAULT_MAX_CONNECTIONS}");
+    sw.WriteLine($"#chat:floodKickLength {Context.DEFAULT_FLOOD_KICK_LENGTH}");
 
+    sw.WriteLine("# Sock Chat Configuration");
+    sw.WriteLine($"#sockchat:port {SockChatServer.DEFAULT_PORT}");
 
     sw.WriteLine();
     sw.WriteLine("# Channels");
@@ -173,14 +174,17 @@ try {
 
     if(cts.IsCancellationRequested) return;
 
+    logger.ZLogDebug($"Creating context...");
+    Config chatConfig = config.ScopeTo("chat");
+    Context ctx = new(logFactory, chatConfig, storage, flashii, flashii);
+
+    if(cts.IsCancellationRequested) return;
+
     logger.ZLogInformation($"Preparing server...");
     await new SockChatServer(
-        logFactory,
         cts,
-        flashii,
-        flashii,
-        storage.CreateMessageStorage(),
-        config.ScopeTo("chat")
+        ctx,
+        config.ScopeTo("sockchat")
     ).Listen(cts.Token);
 } finally {
     if(storage is IDisposable disp) {
diff --git a/SharpChat/SockChatServer.cs b/SharpChat/SockChatServer.cs
index c1a3ae0..5af25d5 100644
--- a/SharpChat/SockChatServer.cs
+++ b/SharpChat/SockChatServer.cs
@@ -1,10 +1,8 @@
 using Microsoft.Extensions.Logging;
-using SharpChat.Auth;
 using SharpChat.Bans;
 using SharpChat.C2SPacketHandlers;
 using SharpChat.ClientCommands;
 using SharpChat.Configuration;
-using SharpChat.Messages;
 using SharpChat.SockChat.S2CPackets;
 using System.Net;
 using ZLogger;
@@ -13,83 +11,44 @@ namespace SharpChat;
 
 public class SockChatServer {
     public const ushort DEFAULT_PORT = 6770;
-    public const int DEFAULT_MSG_LENGTH_MAX = 5000;
-    public const int DEFAULT_MAX_CONNECTIONS = 5;
-    public const int DEFAULT_FLOOD_KICK_LENGTH = 30;
-    public const int DEFAULT_FLOOD_KICK_EXEMPT_RANK = 9;
 
     public Context Context { get; }
 
     private readonly ILogger Logger;
 
-    private readonly BansClient BansClient;
-
     private readonly CachedValue<ushort> Port;
-    private readonly CachedValue<int> MaxMessageLength;
-    private readonly CachedValue<int> MaxConnections;
-    private readonly CachedValue<int> FloodKickLength;
-    private readonly CachedValue<int> FloodKickExemptRank;
 
     private readonly List<C2SPacketHandler> GuestHandlers = [];
     private readonly List<C2SPacketHandler> AuthedHandlers = [];
     private readonly SendMessageC2SPacketHandler SendMessageHandler;
 
-    private static readonly string[] DEFAULT_CHANNELS = ["lounge"];
-
     public SockChatServer(
-        ILoggerFactory logFactory,
         CancellationTokenSource cancellationTokenSource,
-        AuthClient authClient,
-        BansClient bansClient,
-        MessageStorage msgStorage,
+        Context ctx,
         Config config
     ) {
-        Logger = logFactory.CreateLogger("sockchat");
+        Logger = ctx.LoggerFactory.CreateLogger("sockchat");
         Logger.ZLogInformation($"Initialising Sock Chat server...");
 
-        BansClient = bansClient;
+        Context = ctx;
 
         Logger.ZLogDebug($"Fetching configuration values...");
         Port = config.ReadCached("port", DEFAULT_PORT);
-        MaxMessageLength = config.ReadCached("msgMaxLength", DEFAULT_MSG_LENGTH_MAX);
-        MaxConnections = config.ReadCached("connMaxCount", DEFAULT_MAX_CONNECTIONS);
-        FloodKickLength = config.ReadCached("floodKickLength", DEFAULT_FLOOD_KICK_LENGTH);
-        FloodKickExemptRank = config.ReadCached("floodKickExemptRank", DEFAULT_FLOOD_KICK_EXEMPT_RANK);
-
-        Logger.ZLogDebug($"Creating context...");
-        Context = new Context(logFactory, msgStorage ?? throw new ArgumentNullException(nameof(msgStorage)));
-
-        Logger.ZLogDebug($"Creating channel list...");
-        string[]? channelNames = config.ReadValue("channels", DEFAULT_CHANNELS);
-        if(channelNames is not null)
-            foreach(string channelName in channelNames) {
-                Config channelCfg = config.ScopeTo($"channels:{channelName}");
-
-                string name = channelCfg.SafeReadValue("name", string.Empty)!;
-                if(string.IsNullOrWhiteSpace(name))
-                    name = channelName;
-
-                Context.Channels.CreateChannel(
-                    name,
-                    channelCfg.SafeReadValue("password", string.Empty)!,
-                    rank: channelCfg.SafeReadValue("minRank", 0)
-                );
-            }
 
         Logger.ZLogDebug($"Registering unauthenticated handlers...");
         GuestHandlers.Add(new AuthC2SPacketHandler(
-            authClient,
-            bansClient,
+            Context.Auth,
+            Context.Bans,
             Context.Channels,
             Context.RandomSnowflake,
-            MaxMessageLength,
-            MaxConnections
+            Context.MaxMessageLength,
+            Context.MaxConnections
         ));
 
         Logger.ZLogDebug($"Registering authenticated handlers...");
         AuthedHandlers.AddRange([
-            new PingC2SPacketHandler(authClient),
-            SendMessageHandler = new SendMessageC2SPacketHandler(Context.RandomSnowflake, MaxMessageLength),
+            new PingC2SPacketHandler(Context.Auth),
+            SendMessageHandler = new SendMessageC2SPacketHandler(Context.RandomSnowflake, Context.MaxMessageLength),
         ]);
 
         Logger.ZLogDebug($"Registering client commands...");
@@ -106,10 +65,10 @@ public class SockChatServer {
             new RankChannelClientCommand(),
             new BroadcastClientCommand(),
             new DeleteMessageClientCommand(),
-            new KickBanClientCommand(bansClient),
-            new PardonUserClientCommand(bansClient),
-            new PardonAddressClientCommand(bansClient),
-            new BanListClientCommand(bansClient),
+            new KickBanClientCommand(Context.Bans),
+            new PardonUserClientCommand(Context.Bans),
+            new PardonAddressClientCommand(Context.Bans),
+            new BanListClientCommand(Context.Bans),
             new RemoteAddressClientCommand(),
             new ShutdownRestartClientCommand(cancellationTokenSource)
         ]);
@@ -190,7 +149,7 @@ public class SockChatServer {
         await Context.SafeUpdate();
 
         // this doesn't affect non-authed connections?????
-        if(conn.User is not null && conn.User.Rank < FloodKickExemptRank) {
+        if(conn.User is not null && conn.User.Rank < Context.FloodKickExemptRank) {
             User? banUser = null;
             string banAddr = string.Empty;
             TimeSpan banDuration = TimeSpan.MinValue;
@@ -207,7 +166,7 @@ public class SockChatServer {
                 rateLimiter.Update();
 
                 if(rateLimiter.IsExceeded) {
-                    banDuration = TimeSpan.FromSeconds(FloodKickLength);
+                    banDuration = TimeSpan.FromSeconds(Context.FloodKickLength);
                     banUser = conn.User;
                     banAddr = conn.RemoteEndPoint.Address.ToString();
                     conn.Logger.ZLogWarning($"Exceeded flood limit! Issuing ban with duration {banDuration} on {banAddr}/{banUser.UserId}...");
@@ -224,7 +183,7 @@ public class SockChatServer {
                         await Context.BanUser(conn.User, banDuration, UserDisconnectS2CPacket.Reason.Flood);
 
                         if(banDuration > TimeSpan.Zero)
-                            await BansClient.BanCreate(
+                            await Context.Bans.BanCreate(
                                 BanKind.User,
                                 banDuration,
                                 conn.RemoteEndPoint.Address,
diff --git a/SharpChatCommon/Configuration/Config.cs b/SharpChatCommon/Configuration/Config.cs
index 068061a..26f49db 100644
--- a/SharpChatCommon/Configuration/Config.cs
+++ b/SharpChatCommon/Configuration/Config.cs
@@ -1,6 +1,6 @@
 namespace SharpChat.Configuration;
 
-public interface Config : IDisposable {
+public interface Config {
     /// <summary>
     /// Creates a proxy object that forces all names to start with the given prefix.
     /// </summary>
diff --git a/SharpChatCommon/Configuration/ScopedConfig.cs b/SharpChatCommon/Configuration/ScopedConfig.cs
index 86b4770..41f2446 100644
--- a/SharpChatCommon/Configuration/ScopedConfig.cs
+++ b/SharpChatCommon/Configuration/ScopedConfig.cs
@@ -27,8 +27,4 @@ public class ScopedConfig(Config config, string prefix) : Config {
     public CachedValue<T> ReadCached<T>(string name, T? fallback = default, TimeSpan? lifetime = null) {
         return Config.ReadCached(GetName(name), fallback, lifetime);
     }
-
-    public void Dispose() {
-        GC.SuppressFinalize(this);
-    }
 }
diff --git a/SharpChatCommon/Configuration/StreamConfig.cs b/SharpChatCommon/Configuration/StreamConfig.cs
index 78b389f..ffe8f60 100644
--- a/SharpChatCommon/Configuration/StreamConfig.cs
+++ b/SharpChatCommon/Configuration/StreamConfig.cs
@@ -2,7 +2,7 @@ using System.Text;
 
 namespace SharpChat.Configuration;
 
-public class StreamConfig : Config {
+public class StreamConfig : Config, IDisposable {
     private Stream Stream { get; }
     private StreamReader StreamReader { get; }
     private Mutex Lock { get; }