using MySqlConnector; using SharpChat.Events; using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Text.Json; namespace SharpChat { public static partial class Database { private static string ConnectionString = null; public static bool HasDatabase => !string.IsNullOrWhiteSpace(ConnectionString); public static void ReadConfig() { if(!File.Exists(@"mariadb.txt")) return; string[] config = File.ReadAllLines(@"mariadb.txt"); if (config.Length < 4) return; Init(config[0], config[1], config[2], config[3]); } public static void Init(string host, string username, string password, string database) { ConnectionString = new MySqlConnectionStringBuilder { Server = host, UserID = username, Password = password, Database = database, OldGuids = false, TreatTinyAsBoolean = false, CharacterSet = @"utf8mb4", SslMode = MySqlSslMode.None, ForceSynchronous = true, ConnectionTimeout = 5, }.ToString(); RunMigrations(); } public static void Deinit() { ConnectionString = null; } private static MySqlConnection GetConnection() { if (!HasDatabase) return null; MySqlConnection conn = new MySqlConnection(ConnectionString); conn.Open(); return conn; } private static int RunCommand(string command, params MySqlParameter[] parameters) { if (!HasDatabase) return 0; try { using MySqlConnection conn = GetConnection(); using MySqlCommand cmd = conn.CreateCommand(); if (parameters?.Length > 0) cmd.Parameters.AddRange(parameters); cmd.CommandText = command; return cmd.ExecuteNonQuery(); } catch (MySqlException ex) { Logger.Write(ex); } return 0; } private static MySqlDataReader RunQuery(string command, params MySqlParameter[] parameters) { if (!HasDatabase) return null; try { MySqlConnection conn = GetConnection(); MySqlCommand cmd = conn.CreateCommand(); if (parameters?.Length > 0) cmd.Parameters.AddRange(parameters); cmd.CommandText = command; return cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection); } catch(MySqlException ex) { Logger.Write(ex); } return null; } private static object RunQueryValue(string command, params MySqlParameter[] parameters) { if (!HasDatabase) return null; try { using MySqlConnection conn = GetConnection(); using MySqlCommand cmd = conn.CreateCommand(); if (parameters?.Length > 0) cmd.Parameters.AddRange(parameters); cmd.CommandText = command; cmd.Prepare(); return cmd.ExecuteScalar(); } catch(MySqlException ex) { Logger.Write(ex); } return null; } private const long ID_EPOCH = 1588377600000; private static int IdCounter = 0; public static long GenerateId() { if (IdCounter > 200) IdCounter = 0; long id = 0; id |= (DateTimeOffset.Now.ToUnixTimeMilliseconds() - ID_EPOCH) << 8; id |= (ushort)(++IdCounter); return id; } public static void LogEvent(IChatEvent evt) { if(evt.SequenceId < 1) evt.SequenceId = GenerateId(); RunCommand( @"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`)" + @" VALUES (@id, FROM_UNIXTIME(@created), @type, @target, @flags, @data" + @", @sender, @sender_name, @sender_colour, @sender_rank, @sender_nick, @sender_perms)", new MySqlParameter(@"id", evt.SequenceId), new MySqlParameter(@"created", evt.DateTime.ToUnixTimeSeconds()), new MySqlParameter(@"type", evt.GetType().FullName), new MySqlParameter(@"target", evt.Target.TargetName), new MySqlParameter(@"flags", (byte)evt.Flags), new MySqlParameter(@"data", JsonSerializer.SerializeToUtf8Bytes(evt, evt.GetType())), new MySqlParameter(@"sender", evt.Sender?.UserId < 1 ? null : (long?)evt.Sender.UserId), new MySqlParameter(@"sender_name", evt.Sender?.Username), new MySqlParameter(@"sender_colour", evt.Sender?.Colour.Raw), new MySqlParameter(@"sender_rank", evt.Sender?.Rank), new MySqlParameter(@"sender_nick", evt.Sender?.Nickname), new MySqlParameter(@"sender_perms", evt.Sender?.Permissions) ); } public static void DeleteEvent(IChatEvent evt) { RunCommand( @"UPDATE IGNORE `sqc_events` SET `event_deleted` = NOW() WHERE `event_id` = @id AND `event_deleted` IS NULL", new MySqlParameter(@"id", evt.SequenceId) ); } private static IChatEvent ReadEvent(MySqlDataReader reader, IPacketTarget target = null) { Type evtType = Type.GetType(Encoding.ASCII.GetString((byte[])reader[@"event_type"])); IChatEvent evt = JsonSerializer.Deserialize(Encoding.ASCII.GetString((byte[])reader[@"event_data"]), evtType) as IChatEvent; evt.SequenceId = reader.GetInt64(@"event_id"); evt.Target = target; evt.TargetName = target?.TargetName ?? Encoding.ASCII.GetString((byte[])reader[@"event_target"]); evt.Flags = (ChatMessageFlags)reader.GetByte(@"event_flags"); evt.DateTime = DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32(@"event_created")); if (!reader.IsDBNull(reader.GetOrdinal(@"event_sender"))) { evt.Sender = new BasicUser { UserId = reader.GetInt64(@"event_sender"), Username = reader.GetString(@"event_sender_name"), Colour = new ChatColour(reader.GetInt32(@"event_sender_colour")), Rank = reader.GetInt32(@"event_sender_rank"), Nickname = reader.IsDBNull(reader.GetOrdinal(@"event_sender_nick")) ? null : reader.GetString(@"event_sender_nick"), Permissions = (ChatUserPermissions)reader.GetInt32(@"event_sender_perms") }; } return evt; } public static IEnumerable GetEvents(IPacketTarget target, int amount, int offset) { List events = new List(); try { using MySqlDataReader reader = RunQuery( @"SELECT `event_id`, `event_type`, `event_flags`, `event_data`" + @", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`" + @", UNIX_TIMESTAMP(`event_created`) AS `event_created`" + @" FROM `sqc_events`" + @" WHERE `event_deleted` IS NULL AND `event_target` = @target" + @" AND `event_id` > @offset" + @" ORDER BY `event_id` DESC" + @" LIMIT @amount", new MySqlParameter(@"target", target.TargetName), new MySqlParameter(@"amount", amount), new MySqlParameter(@"offset", offset) ); while (reader.Read()) { IChatEvent evt = ReadEvent(reader, target); if (evt != null) events.Add(evt); } } catch(MySqlException ex) { Logger.Write(ex); } return events; } public static IChatEvent GetEvent(long seqId) { try { using MySqlDataReader reader = RunQuery( @"SELECT `event_id`, `event_type`, `event_flags`, `event_data`, `event_target`" + @", `event_sender`, `event_sender_name`, `event_sender_colour`, `event_sender_rank`, `event_sender_nick`, `event_sender_perms`" + @", UNIX_TIMESTAMP(`event_created`) AS `event_created`" + @" FROM `sqc_events`" + @" WHERE `event_id` = @id", new MySqlParameter(@"id", seqId) ); while (reader.Read()) { IChatEvent evt = ReadEvent(reader); if (evt != null) return evt; } } catch(MySqlException ex) { Logger.Write(ex); } return null; } } }