sharp-chat/SharpChat/Program.cs

235 lines
9.8 KiB
C#

using Hamakaze;
using SharpChat.Configuration;
using SharpChat.Database;
using SharpChat.Database.Null;
using SharpChat.DataProvider;
using SharpChat.DataProvider.Null;
using SharpChat.Protocol;
using SharpChat.Protocol.IRC;
using SharpChat.Protocol.Null;
using SharpChat.Protocol.SockChat;
using SharpChat.Reflection;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
namespace SharpChat {
public class Program {
public const string CONFIG = @"sharpchat.cfg";
public const ushort DEFAULT_PORT = 6770;
private static string GetFlagArgument(string[] args, string flag) {
int offset = Array.IndexOf(args, flag) + 1;
return offset < 1 ? null : args.ElementAtOrDefault(offset);
}
public static void Main(string[] args) {
Console.WriteLine(@" _____ __ ________ __ ");
Console.WriteLine(@" / ___// /_ ____ __________ / ____/ /_ ____ _/ /_");
Console.WriteLine(@" \__ \/ __ \/ __ `/ ___/ __ \/ / / __ \/ __ `/ __/");
Console.WriteLine(@" ___/ / / / / /_/ / / / /_/ / /___/ / / / /_/ / /_ ");
Console.WriteLine(@"/____/_/ /_/\__,_/_/ / .___/\____/_/ /_/\__,_/\__/ ");
/**/Console.Write(@" / _/");
if(SharpInfo.IsDebugBuild) {
Console.WriteLine();
Console.Write(@"== ");
Console.Write(SharpInfo.VersionString);
Console.WriteLine(@" == DBG ==");
} else
Console.WriteLine(SharpInfo.VersionStringShort.PadLeft(28, ' '));
string configFile = GetFlagArgument(args, @"--cfg") ?? CONFIG;
// If the config file doesn't exist and we're using the default path, run the converter
if(!File.Exists(configFile) && configFile == CONFIG)
ConvertConfiguration();
using IConfig config = new StreamConfig(configFile);
// Load database and data provider libraries
ReflectionUtilities.LoadAssemblies(@"SharpChat.Database.*.dll");
ReflectionUtilities.LoadAssemblies(@"SharpChat.DataProvider.*.dll");
ReflectionUtilities.LoadAssemblies(@"SharpChat.Protocol.*.dll");
// Allow forcing a sqlite database through console flags
string sqliteDbPath = GetFlagArgument(args, @"--dbpath");
string databaseBackendName;
object databaseArgument;
if(!string.IsNullOrEmpty(sqliteDbPath)) {
Logger.Write($@"Forcing SQLite: {sqliteDbPath}");
databaseBackendName = @"sqlite";
databaseArgument = sqliteDbPath;
} else {
databaseBackendName = GetFlagArgument(args, @"--dbb") ?? config.ReadValue(@"db");
databaseArgument = config.ScopeTo($@"db:{databaseBackendName}");
}
IDatabaseBackend databaseBackend = new ObjectConstructor<IDatabaseBackend, DatabaseBackendAttribute, NullDatabaseBackend>()
.Construct(databaseBackendName, databaseArgument);
using HttpClient httpClient = new() {
DefaultUserAgent = @"SharpChat/1.0",
};
string dataProviderName = GetFlagArgument(args, @"--dpn") ?? config.ReadValue(@"dp");
IDataProvider dataProvider = new ObjectConstructor<IDataProvider, DataProviderAttribute, NullDataProvider>()
.Construct(dataProviderName, config.ScopeTo($@"dp:{dataProviderName}"), httpClient);
string portArg = GetFlagArgument(args, @"--port") ?? config.ReadValue(@"chat:port");
if(string.IsNullOrEmpty(portArg) || !ushort.TryParse(portArg, out ushort port))
port = DEFAULT_PORT;
Win32.IncreaseThreadPrecision();
ObjectConstructor<IServer, ServerAttribute, NullServer> serverConstructor = new();
Logger.Write(@"Creating context...");
using(Context ctx = new(config.ScopeTo(@"chat"), databaseBackend, dataProvider)) {
List<IServer> servers = new();
// Crusty temporary solution, just want to have the variable constructor arguments for servers in place already
string[] serverNames = new[] {
@"sockchat",
#if DEBUG
@"irc",
#endif
};
foreach(string serverName in serverNames) {
Logger.Write($@"Starting {serverName} server...");
IServer server = serverConstructor.Construct(serverName, ctx, config.ScopeTo(serverName));
servers.Add(server);
if(server is SockChatServer)
server.Listen(new IPEndPoint(IPAddress.Any, port));
else if(server is IRCServer)
server.Listen(new IPEndPoint(IPAddress.Any, 6667));
}
using ManualResetEvent mre = new(false);
Console.CancelKeyPress += (s, e) => { e.Cancel = true; mre.Set(); };
mre.WaitOne();
foreach(IServer server in servers)
server.Dispose();
if(dataProvider is IDisposable dpd)
dpd.Dispose();
if(databaseBackend is IDisposable dbd)
dbd.Dispose();
}
Win32.RestoreThreadPrecision();
}
private static void ConvertConfiguration() {
using Stream s = new FileStream(CONFIG, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
s.SetLength(0);
s.Flush();
using StreamWriter sw = new(s, new UTF8Encoding(false));
sw.WriteLine(@"# and ; can be used at the start of a line for comments.");
sw.WriteLine();
sw.WriteLine(@"# General Configuration");
sw.WriteLine($@"#chat:port {DEFAULT_PORT}");
sw.WriteLine($@"#chat:messages:maxLength {Messages.MessageManager.DEFAULT_LENGTH_MAX}");
sw.WriteLine($@"#chat:sessions:timeOut {Sessions.SessionManager.DEFAULT_TIMEOUT}");
sw.WriteLine($@"#chat:sessions:maxCount {Sessions.SessionManager.DEFAULT_MAX_COUNT}");
sw.WriteLine();
sw.WriteLine(@"# Rate Limiter Configuration");
sw.WriteLine($@"#chat:rateLimit:userSize {RateLimiting.RateLimitManager.DEFAULT_USER_SIZE}");
sw.WriteLine($@"#chat:rateLimit:userWarnSize {RateLimiting.RateLimitManager.DEFAULT_USER_WARN_SIZE}");
sw.WriteLine($@"#chat:rateLimit:connSize {RateLimiting.RateLimitManager.DEFAULT_CONN_SIZE}");
sw.WriteLine($@"#chat:rateLimit:minDelay {RateLimiting.RateLimitManager.DEFAULT_MINIMUM_DELAY}");
sw.WriteLine($@"#chat:rateLimit:kickLength {RateLimiting.RateLimitManager.DEFAULT_KICK_LENGTH}");
sw.WriteLine($@"#chat:rateLimit:kickMultiplier {RateLimiting.RateLimitManager.DEFAULT_KICK_MULTIPLIER}");
sw.WriteLine();
sw.WriteLine(@"# Channels");
sw.WriteLine(@"chat:channels lounge staff");
sw.WriteLine();
sw.WriteLine(@"# Lounge channel settings");
sw.WriteLine(@"chat:channels:lounge:autoJoin true");
sw.WriteLine();
sw.WriteLine(@"# Staff channel settings");
sw.WriteLine(@"chat:channels:staff:minRank 5");
sw.WriteLine();
const string msz_config = @"login_key.txt";
sw.WriteLine(@"# Selected DataProvider (misuzu, null)");
if(!File.Exists(msz_config))
sw.WriteLine(@"dp null");
else {
sw.WriteLine(@"dp misuzu");
sw.WriteLine();
sw.WriteLine(@"# Misuzu DataProvider settings");
sw.WriteLine(@"#db:misuzu:userId 61");
sw.Write(@"dp:misuzu:secret ");
sw.Write(File.ReadAllText(msz_config).Trim());
sw.WriteLine();
sw.Write(@"dp:misuzu:endpoint ");
#if DEBUG
sw.Write(@"https://misuzu.misaka.nl/_sockchat");
#else
sw.Write(@"https://flashii.net/_sockchat");
#endif
sw.WriteLine();
}
sw.WriteLine();
const string sql_config = @"sqlite.txt";
const string mdb_config = @"mariadb.txt";
bool hasMDB = File.Exists(mdb_config),
hasSQL = File.Exists(sql_config);
sw.WriteLine(@"# Selected Database Backend (mariadb, sqlite, null)");
if(hasMDB)
sw.WriteLine(@"db mariadb");
else if(hasSQL)
sw.WriteLine(@"db sqlite");
else
sw.WriteLine(@"db null");
sw.WriteLine();
if(hasMDB) {
string[] mdbCfg = File.ReadAllLines(mdb_config);
sw.WriteLine(@"# MariaDB configuration");
sw.WriteLine($@"db:mariadb:host {mdbCfg[0]}");
if(mdbCfg.Length > 1)
sw.WriteLine($@"db:mariadb:user {mdbCfg[1]}");
else
sw.WriteLine($@"#db:mariadb:user <username>");
if(mdbCfg.Length > 2)
sw.WriteLine($@"db:mariadb:pass {mdbCfg[2]}");
else
sw.WriteLine($@"#db:mariadb:pass <password>");
if(mdbCfg.Length > 3)
sw.WriteLine($@"db:mariadb:db {mdbCfg[3]}");
else
sw.WriteLine($@"#db:mariadb:db <database>");
sw.WriteLine();
}
if(hasSQL) {
string[] sqlCfg = File.ReadAllLines(sql_config);
sw.WriteLine(@"# SQLite configuration");
sw.WriteLine($@"db:sqlite:path {sqlCfg[0]}");
}
sw.Flush();
}
}
}