255 lines
11 KiB
C#
255 lines
11 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using MySqlConnector;
|
|
using SharpChat;
|
|
using SharpChat.Configuration;
|
|
using SharpChat.Flashii;
|
|
using SharpChat.MariaDB;
|
|
using SharpChat.SQLite;
|
|
using System.Data.SQLite;
|
|
using System.Reflection.PortableExecutable;
|
|
using System.Text;
|
|
using ZLogger;
|
|
using ZLogger.Providers;
|
|
|
|
const string CONFIG = "sharpchat.cfg";
|
|
|
|
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, ' '));
|
|
|
|
using ILoggerFactory logFactory = LoggerFactory.Create(logging => {
|
|
logging.ClearProviders();
|
|
|
|
#if DEBUG
|
|
logging.SetMinimumLevel(LogLevel.Trace);
|
|
#else
|
|
logging.SetMinimumLevel(LogLevel.Information);
|
|
#endif
|
|
|
|
logging.AddZLoggerConsole(opts => {
|
|
opts.OutputEncodingToUtf8 = true;
|
|
opts.UsePlainTextFormatter(formatter => {
|
|
formatter.SetPrefixFormatter($"{0} [{1} {2}] ", (in MessageTemplate template, in LogInfo info) => template.Format(info.Timestamp, info.Category, info.LogLevel));
|
|
});
|
|
});
|
|
logging.AddZLoggerRollingFile(opts => {
|
|
opts.FilePathSelector = (ts, seqNo) => $"logs/{ts.ToLocalTime():yyyy-MM-dd_HH-mm-ss}_{seqNo:000}.json";
|
|
opts.RollingInterval = RollingInterval.Day;
|
|
opts.RollingSizeKB = 1024;
|
|
opts.FileShared = true;
|
|
opts.UseJsonFormatter(formatter => {
|
|
formatter.UseUtcTimestamp = true;
|
|
});
|
|
});
|
|
});
|
|
|
|
ILogger logger = logFactory.CreateLogger("main");
|
|
|
|
using CancellationTokenSource cts = new();
|
|
|
|
void exitHandler() {
|
|
if(cts.IsCancellationRequested)
|
|
return;
|
|
|
|
try {
|
|
cts.Cancel();
|
|
logger.ZLogInformation($"Shutdown requested through console...");
|
|
} catch(ObjectDisposedException) { }
|
|
}
|
|
|
|
AppDomain.CurrentDomain.ProcessExit += (sender, ev) => { exitHandler(); };
|
|
Console.CancelKeyPress += (sender, ev) => { ev.Cancel = true; exitHandler(); };
|
|
|
|
if(cts.IsCancellationRequested) return;
|
|
|
|
string configFile = CONFIG;
|
|
|
|
// If the config file doesn't exist and we're using the default path, run the converter
|
|
if(!File.Exists(configFile) && configFile == CONFIG) {
|
|
logger.ZLogInformation($"Creating example configuration file...");
|
|
|
|
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: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");
|
|
sw.WriteLine("chat:channels lounge staff");
|
|
sw.WriteLine();
|
|
|
|
sw.WriteLine("# Lounge channel settings");
|
|
sw.WriteLine("chat:channels:lounge:name Lounge");
|
|
sw.WriteLine("chat:channels:lounge:autoJoin true");
|
|
sw.WriteLine();
|
|
|
|
sw.WriteLine("# Staff channel settings");
|
|
sw.WriteLine("chat:channels:staff:name Staff");
|
|
sw.WriteLine("chat:channels:staff:minRank 5");
|
|
|
|
|
|
const string msz_secret = "login_key.txt";
|
|
const string msz_url = "msz_url.txt";
|
|
|
|
sw.WriteLine();
|
|
sw.WriteLine("# Misuzu integration settings");
|
|
if(File.Exists(msz_secret))
|
|
sw.WriteLine(string.Format("msz:secret {0}", File.ReadAllText(msz_secret).Trim()));
|
|
else
|
|
sw.WriteLine("#msz:secret woomy");
|
|
if(File.Exists(msz_url))
|
|
sw.WriteLine(string.Format("msz:url {0}/_sockchat", File.ReadAllText(msz_url).Trim()));
|
|
else
|
|
sw.WriteLine("#msz:url https://flashii.net/_sockchat");
|
|
|
|
|
|
const string mdb_config = @"mariadb.txt";
|
|
string[] mdbCfg = File.Exists(mdb_config) ? File.ReadAllLines(mdb_config) : [];
|
|
|
|
sw.WriteLine();
|
|
sw.WriteLine("# MariaDB configuration");
|
|
if(mdbCfg.Length > 0)
|
|
sw.WriteLine($"mariadb:host {mdbCfg[0]}");
|
|
else
|
|
sw.WriteLine($"#mariadb:host <username>");
|
|
if(mdbCfg.Length > 1)
|
|
sw.WriteLine($"mariadb:user {mdbCfg[1]}");
|
|
else
|
|
sw.WriteLine($"#mariadb:user <username>");
|
|
if(mdbCfg.Length > 2)
|
|
sw.WriteLine($"mariadb:pass {mdbCfg[2]}");
|
|
else
|
|
sw.WriteLine($"#mariadb:pass <password>");
|
|
if(mdbCfg.Length > 3)
|
|
sw.WriteLine($"mariadb:db {mdbCfg[3]}");
|
|
else
|
|
sw.WriteLine($"#mariadb:db <database>");
|
|
|
|
sw.Flush();
|
|
}
|
|
|
|
logger.ZLogInformation($"Initialising configuration...");
|
|
using StreamConfig config = StreamConfig.FromPath(configFile);
|
|
|
|
if(cts.IsCancellationRequested) return;
|
|
|
|
if(args.Contains("--convert-db")) {
|
|
logger.ZLogInformation($"Converting MariaDB storage to SQLite");
|
|
|
|
MariaDBStorage mariadbStorage = new(logFactory.CreateLogger("mariadb"), MariaDBStorage.BuildConnectionString(config.ScopeTo("mariadb")));
|
|
await mariadbStorage.UpgradeStorage();
|
|
|
|
using SQLiteStorage sqlite = new(logFactory.CreateLogger("sqlite"), SQLiteStorage.BuildConnectionString(config.ScopeTo("sqlite"), false));
|
|
await sqlite.UpgradeStorage();
|
|
|
|
using MariaDBConnection mariadb = await mariadbStorage.CreateConnection();
|
|
long rows = await mariadb.RunQueryValue<long>("SELECT COUNT(*) FROM sqc_events");
|
|
|
|
using MySqlCommand export = mariadb.Connection.CreateCommand();
|
|
export.CommandText = "SELECT event_id, event_type, UNIX_TIMESTAMP(event_created) AS event_created, UNIX_TIMESTAMP(event_deleted) AS event_deleted, event_channel, event_sender, event_sender_name, event_sender_colour, event_sender_rank, event_sender_nick, event_sender_perms, event_data FROM sqc_events";
|
|
export.CommandTimeout = int.MaxValue;
|
|
using MySqlDataReader reader = await export.ExecuteReaderAsync();
|
|
|
|
using SQLiteCommand import = sqlite.Connection.Connection.CreateCommand();
|
|
import.CommandText = "INSERT OR IGNORE INTO messages (msg_id, msg_type, msg_created, msg_deleted, msg_channel, msg_sender, msg_sender_name, msg_sender_colour, msg_sender_rank, msg_sender_nick, msg_sender_perms, msg_data) VALUES (@id, @type, @created, @deleted, @channel, @sender, @sender_name, @sender_colour, @sender_rank, @sender_nick, @sender_perms, @data)";
|
|
|
|
long completed = 0;
|
|
DateTimeOffset lastReport = DateTimeOffset.UtcNow;
|
|
logger.ZLogInformation($"Beginning conversion of {rows} rows...");
|
|
while(reader.Read()) {
|
|
if(cts.IsCancellationRequested) return;
|
|
|
|
import.Parameters.Clear();
|
|
import.Parameters.Add(new SQLiteParameter("id", reader.GetInt64("event_id").ToString()));
|
|
import.Parameters.Add(new SQLiteParameter("type", reader.GetString("event_type")));
|
|
import.Parameters.Add(new SQLiteParameter("created", DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")).ToString("s") + "Z"));
|
|
import.Parameters.Add(new SQLiteParameter("deleted", reader.IsDBNull(reader.GetOrdinal("event_deleted")) ? null : DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_deleted")).ToString("s") + "Z"));
|
|
import.Parameters.Add(new SQLiteParameter("channel", reader.IsDBNull(reader.GetOrdinal("event_channel")) ? null : reader.GetString("event_channel")));
|
|
import.Parameters.Add(new SQLiteParameter("sender", reader.IsDBNull(reader.GetOrdinal("event_sender")) ? null : reader.GetString("event_sender")));
|
|
import.Parameters.Add(new SQLiteParameter("sender_name", reader.IsDBNull(reader.GetOrdinal("event_sender_name")) ? string.Empty : reader.GetString("event_sender_name")));
|
|
ColourInheritable colour = ColourInheritable.FromMisuzu(reader.GetInt32("event_sender_colour"));
|
|
import.Parameters.Add(new SQLiteParameter("sender_colour", colour.Rgb.HasValue ? colour.Rgb.Value : null));
|
|
import.Parameters.Add(new SQLiteParameter("sender_rank", reader.IsDBNull(reader.GetOrdinal("event_sender_rank")) ? null : reader.GetInt32("event_sender_rank")));
|
|
import.Parameters.Add(new SQLiteParameter("sender_nick", reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? string.Empty : reader.GetString("event_sender_nick")));
|
|
import.Parameters.Add(new SQLiteParameter("sender_perms", SQLiteUserPermissionsConverter.To(MariaDBUserPermissionsConverter.From((MariaDBUserPermissions)reader.GetInt32("event_sender_perms")))));
|
|
import.Parameters.Add(new SQLiteParameter("data", reader.GetString("event_data")));
|
|
await import.PrepareAsync();
|
|
await import.ExecuteNonQueryAsync();
|
|
|
|
++completed;
|
|
if(DateTimeOffset.UtcNow - lastReport > TimeSpan.FromMinutes(1)) {
|
|
lastReport = DateTimeOffset.UtcNow;
|
|
double completion = (double)completed / rows;
|
|
logger.ZLogInformation($"{completed} of {rows} converted ({completion:P2})...");
|
|
}
|
|
}
|
|
logger.ZLogInformation($"Converted all {completed} rows!");
|
|
return;
|
|
}
|
|
|
|
logger.ZLogInformation($"Initialising HTTP client...");
|
|
using HttpClient httpClient = new(new HttpClientHandler() {
|
|
UseProxy = false,
|
|
});
|
|
httpClient.DefaultRequestHeaders.Add("User-Agent", SharpInfo.ProgramName);
|
|
|
|
if(cts.IsCancellationRequested) return;
|
|
|
|
logger.ZLogInformation($"Initialising Flashii client...");
|
|
FlashiiClient flashii = new(logFactory.CreateLogger("flashii"), httpClient, config.ScopeTo("msz"));
|
|
|
|
if(cts.IsCancellationRequested) return;
|
|
|
|
logger.ZLogInformation($"Initialising storage...");
|
|
Storage storage = string.IsNullOrWhiteSpace(config.SafeReadValue("mariadb:host", string.Empty))
|
|
? new SQLiteStorage(logFactory.CreateLogger("sqlite"), SQLiteStorage.BuildConnectionString(config.ScopeTo("sqlite")))
|
|
: new MariaDBStorage(logFactory.CreateLogger("mariadb"), MariaDBStorage.BuildConnectionString(config.ScopeTo("mariadb")));
|
|
|
|
try {
|
|
if(cts.IsCancellationRequested) return;
|
|
|
|
await storage.UpgradeStorage();
|
|
|
|
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(
|
|
cts,
|
|
ctx,
|
|
config.ScopeTo("sockchat")
|
|
).Listen(cts.Token);
|
|
} finally {
|
|
if(storage is IDisposable disp) {
|
|
logger.ZLogInformation($"Cleaning storage...");
|
|
disp.Dispose();
|
|
}
|
|
}
|
|
|
|
logger.ZLogInformation($"Exiting...");
|