Added SQLite storage backend.
This commit is contained in:
parent
999ce86a27
commit
3f6007922c
21 changed files with 665 additions and 245 deletions
.gitignore
SharpChat.MariaDB
MariaDBConnection.csMariaDBMessageStorage.csMariaDBMessageStorage_Database.csMariaDBMigrations.csMariaDBStorage.csMariaDBUserPermissionsConverter.cs
SharpChat.SQLite
SQLiteConnection.csSQLiteMessageStorage.csSQLiteMigrations.csSQLiteStorage.csSQLiteUserPermissions.csSQLiteUserPermissionsConverter.csSharpChat.SQLite.csproj
SharpChat.slnSharpChat
SharpChatCommon
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -8,6 +8,9 @@ http-motd.txt
|
|||
_webdb.txt
|
||||
msz_url.txt
|
||||
sharpchat.cfg
|
||||
sharpchat.db
|
||||
sharpchat.db-wal
|
||||
sharpchat.db-shm
|
||||
SharpChat/version.txt
|
||||
|
||||
# User-specific files
|
||||
|
|
52
SharpChat.MariaDB/MariaDBConnection.cs
Normal file
52
SharpChat.MariaDB/MariaDBConnection.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
using MySqlConnector;
|
||||
using System.Data;
|
||||
|
||||
namespace SharpChat.MariaDB;
|
||||
|
||||
public class MariaDBConnection(MySqlConnection conn) : IDisposable {
|
||||
public async Task<int> RunCommand(string command, params MySqlParameter[] parameters) {
|
||||
using MySqlCommand cmd = conn.CreateCommand();
|
||||
if(parameters?.Length > 0)
|
||||
cmd.Parameters.AddRange(parameters);
|
||||
cmd.CommandText = command;
|
||||
return await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
public async Task<MySqlDataReader?> RunQuery(string command, params MySqlParameter[] parameters) {
|
||||
using MySqlCommand cmd = conn.CreateCommand();
|
||||
if(parameters?.Length > 0)
|
||||
cmd.Parameters.AddRange(parameters);
|
||||
cmd.CommandText = command;
|
||||
return await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection);
|
||||
}
|
||||
|
||||
public async Task<T> RunQueryValue<T>(string command, params MySqlParameter[] parameters)
|
||||
where T : struct {
|
||||
using MySqlCommand cmd = conn.CreateCommand();
|
||||
if(parameters?.Length > 0)
|
||||
cmd.Parameters.AddRange(parameters);
|
||||
cmd.CommandText = command;
|
||||
await cmd.PrepareAsync();
|
||||
|
||||
object? raw = await cmd.ExecuteScalarAsync();
|
||||
return raw is T value ? value : default;
|
||||
}
|
||||
|
||||
private bool disposed = false;
|
||||
|
||||
~MariaDBConnection() {
|
||||
DoDispose();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
DoDispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void DoDispose() {
|
||||
if(disposed)
|
||||
return;
|
||||
disposed = true;
|
||||
conn.Dispose();
|
||||
}
|
||||
}
|
|
@ -4,9 +4,7 @@ using System.Text.Json;
|
|||
|
||||
namespace SharpChat.MariaDB;
|
||||
|
||||
public partial class MariaDBMessageStorage(string connString) : MessageStorage {
|
||||
private string ConnectionString { get; } = connString ?? throw new ArgumentNullException(nameof(connString));
|
||||
|
||||
public class MariaDBMessageStorage(MariaDBStorage storage) : MessageStorage {
|
||||
public async Task LogMessage(
|
||||
long id,
|
||||
string type,
|
||||
|
@ -19,41 +17,77 @@ public partial class MariaDBMessageStorage(string connString) : MessageStorage {
|
|||
UserPermissions senderPerms,
|
||||
object? data = null
|
||||
) {
|
||||
await RunCommand(
|
||||
"INSERT INTO sqc_events (event_id, event_created, event_type, event_channel, event_data"
|
||||
+ ", event_sender, event_sender_name, event_sender_colour, event_sender_rank, event_sender_nick, event_sender_perms)"
|
||||
+ " VALUES (@id, NOW(), @type, @channel, @data"
|
||||
+ ", @sender, @sender_name, @sender_colour, @sender_rank, @sender_nick, @sender_perms)",
|
||||
new MySqlParameter("id", id),
|
||||
new MySqlParameter("type", type),
|
||||
new MySqlParameter("channel", string.IsNullOrWhiteSpace(channelName) ? null : channelName),
|
||||
new MySqlParameter("data", data == null ? "{}" : JsonSerializer.SerializeToUtf8Bytes(data)),
|
||||
new MySqlParameter("sender", long.TryParse(senderId, out long senderId64) && senderId64 > 0 ? senderId64 : null),
|
||||
new MySqlParameter("sender_name", senderName),
|
||||
new MySqlParameter("sender_colour", senderColour.ToMisuzu()),
|
||||
new MySqlParameter("sender_rank", senderRank),
|
||||
new MySqlParameter("sender_nick", string.IsNullOrWhiteSpace(senderNick) ? null : senderNick),
|
||||
new MySqlParameter("sender_perms", ToStoredPermissions(senderPerms))
|
||||
try {
|
||||
using MariaDBConnection conn = await storage.CreateConnection();
|
||||
await conn.RunCommand(
|
||||
"INSERT INTO sqc_events (event_id, event_created, event_type, event_channel, event_data"
|
||||
+ ", event_sender, event_sender_name, event_sender_colour, event_sender_rank, event_sender_nick, event_sender_perms)"
|
||||
+ " VALUES (@id, NOW(), @type, @channel, @data"
|
||||
+ ", @sender, @sender_name, @sender_colour, @sender_rank, @sender_nick, @sender_perms)",
|
||||
new MySqlParameter("id", id),
|
||||
new MySqlParameter("type", type),
|
||||
new MySqlParameter("channel", string.IsNullOrWhiteSpace(channelName) ? null : channelName),
|
||||
new MySqlParameter("data", data == null ? "{}" : JsonSerializer.SerializeToUtf8Bytes(data)),
|
||||
new MySqlParameter("sender", long.TryParse(senderId, out long senderId64) && senderId64 > 0 ? senderId64 : null),
|
||||
new MySqlParameter("sender_name", senderName),
|
||||
new MySqlParameter("sender_colour", senderColour.ToMisuzu()),
|
||||
new MySqlParameter("sender_rank", senderRank),
|
||||
new MySqlParameter("sender_nick", string.IsNullOrWhiteSpace(senderNick) ? null : senderNick),
|
||||
new MySqlParameter("sender_perms", MariaDBUserPermissionsConverter.To(senderPerms))
|
||||
);
|
||||
} catch(MySqlException ex) {
|
||||
Logger.Write(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteMessage(Message msg) {
|
||||
try {
|
||||
using MariaDBConnection conn = await storage.CreateConnection();
|
||||
await conn.RunCommand(
|
||||
"UPDATE IGNORE sqc_events SET event_deleted = NOW() WHERE event_id = @id AND event_deleted IS NULL",
|
||||
new MySqlParameter("id", msg.Id)
|
||||
);
|
||||
} catch(MySqlException ex) {
|
||||
Logger.Write(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static Message ReadMessage(MySqlDataReader reader) {
|
||||
using Stream data = reader.GetStream("event_data");
|
||||
return new Message(
|
||||
reader.GetInt64("event_id"),
|
||||
reader.GetString("event_type"),
|
||||
reader.IsDBNull(reader.GetOrdinal("event_sender")) ? null : reader.GetString("event_sender"),
|
||||
reader.IsDBNull(reader.GetOrdinal("event_sender_name")) ? string.Empty : reader.GetString("event_sender_name"),
|
||||
ColourInheritable.FromMisuzu(reader.GetInt32("event_sender_colour")),
|
||||
reader.GetInt32("event_sender_rank"),
|
||||
MariaDBUserPermissionsConverter.From((MariaDBUserPermissions)reader.GetInt32("event_sender_perms")),
|
||||
reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? string.Empty : reader.GetString("event_sender_nick"),
|
||||
DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")),
|
||||
reader.IsDBNull(reader.GetOrdinal("event_deleted")) ? null : DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_deleted")),
|
||||
reader.IsDBNull(reader.GetOrdinal("event_channel")) ? null : reader.GetString("event_channel"),
|
||||
JsonDocument.Parse(data)
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<Message?> GetMessage(long seqId) {
|
||||
public async Task<Message?> GetMessage(long id) {
|
||||
try {
|
||||
using MySqlDataReader? reader = await RunQuery(
|
||||
using MariaDBConnection conn = await storage.CreateConnection();
|
||||
using MySqlDataReader? reader = await conn.RunQuery(
|
||||
"SELECT event_id, event_type, event_data, event_channel"
|
||||
+ ", event_sender, event_sender_name, event_sender_colour, event_sender_rank, event_sender_nick, event_sender_perms"
|
||||
+ ", UNIX_TIMESTAMP(event_created) AS event_created"
|
||||
+ ", UNIX_TIMESTAMP(event_deleted) AS event_deleted"
|
||||
+ " FROM sqc_events"
|
||||
+ " WHERE event_id = @id",
|
||||
new MySqlParameter("id", seqId)
|
||||
new MySqlParameter("id", id)
|
||||
);
|
||||
|
||||
if(reader is null)
|
||||
return null;
|
||||
|
||||
while(reader.Read()) {
|
||||
Message evt = ReadEvent(reader);
|
||||
Message evt = ReadMessage(reader);
|
||||
if(evt != null)
|
||||
return evt;
|
||||
}
|
||||
|
@ -64,29 +98,12 @@ public partial class MariaDBMessageStorage(string connString) : MessageStorage {
|
|||
return null;
|
||||
}
|
||||
|
||||
private static Message ReadEvent(MySqlDataReader reader) {
|
||||
using Stream data = reader.GetStream("event_data");
|
||||
return new Message(
|
||||
reader.GetInt64("event_id"),
|
||||
reader.GetString("event_type"),
|
||||
reader.IsDBNull(reader.GetOrdinal("event_sender")) ? null : reader.GetString("event_sender"),
|
||||
reader.IsDBNull(reader.GetOrdinal("event_sender_name")) ? string.Empty : reader.GetString("event_sender_name"),
|
||||
ColourInheritable.FromMisuzu(reader.GetInt32("event_sender_colour")),
|
||||
reader.GetInt32("event_sender_rank"),
|
||||
FromMessagePermissions((MariaDBUserPermissions)reader.GetInt32("event_sender_perms")),
|
||||
reader.IsDBNull(reader.GetOrdinal("event_sender_nick")) ? string.Empty : reader.GetString("event_sender_nick"),
|
||||
DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_created")),
|
||||
reader.IsDBNull(reader.GetOrdinal("event_deleted")) ? null : DateTimeOffset.FromUnixTimeSeconds(reader.GetInt32("event_deleted")),
|
||||
reader.IsDBNull(reader.GetOrdinal("event_channel")) ? null : reader.GetString("event_channel"),
|
||||
JsonDocument.Parse(data)
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Message>> GetMessages(string channelName, int amount = 20, int offset = 0) {
|
||||
List<Message> events = [];
|
||||
List<Message> msgs = [];
|
||||
|
||||
try {
|
||||
using MySqlDataReader? reader = await RunQuery(
|
||||
using MariaDBConnection conn = await storage.CreateConnection();
|
||||
using MySqlDataReader? reader = await conn.RunQuery(
|
||||
"SELECT event_id, event_type, event_data, event_channel"
|
||||
+ ", event_sender, event_sender_name, event_sender_colour, event_sender_rank, event_sender_nick, event_sender_perms"
|
||||
+ ", UNIX_TIMESTAMP(event_created) AS event_created"
|
||||
|
@ -101,26 +118,19 @@ public partial class MariaDBMessageStorage(string connString) : MessageStorage {
|
|||
new MySqlParameter("offset", offset)
|
||||
);
|
||||
if(reader is null)
|
||||
return events;
|
||||
return msgs;
|
||||
|
||||
while(reader.Read()) {
|
||||
Message evt = ReadEvent(reader);
|
||||
Message evt = ReadMessage(reader);
|
||||
if(evt != null)
|
||||
events.Add(evt);
|
||||
msgs.Add(evt);
|
||||
}
|
||||
} catch(MySqlException ex) {
|
||||
Logger.Write(ex);
|
||||
}
|
||||
|
||||
events.Reverse();
|
||||
msgs.Reverse();
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
public async Task DeleteMessage(Message evt) {
|
||||
await RunCommand(
|
||||
"UPDATE IGNORE sqc_events SET event_deleted = NOW() WHERE event_id = @id AND event_deleted IS NULL",
|
||||
new MySqlParameter("id", evt.Id)
|
||||
);
|
||||
return msgs;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
using MySqlConnector;
|
||||
using SharpChat.Configuration;
|
||||
|
||||
namespace SharpChat.MariaDB;
|
||||
|
||||
public partial class MariaDBMessageStorage {
|
||||
public static string BuildConnString(Config config) {
|
||||
return BuildConnString(
|
||||
config.ReadValue("host", "localhost"),
|
||||
config.ReadValue("user", string.Empty),
|
||||
config.ReadValue("pass", string.Empty),
|
||||
config.ReadValue("db", "sharpchat")
|
||||
);
|
||||
}
|
||||
|
||||
public static string BuildConnString(string? host, string? username, string? password, string? database) {
|
||||
return new MySqlConnectionStringBuilder {
|
||||
Server = host,
|
||||
UserID = username,
|
||||
Password = password,
|
||||
Database = database,
|
||||
OldGuids = false,
|
||||
TreatTinyAsBoolean = false,
|
||||
CharacterSet = "utf8mb4",
|
||||
SslMode = MySqlSslMode.None,
|
||||
ForceSynchronous = true,
|
||||
ConnectionTimeout = 5,
|
||||
DefaultCommandTimeout = 900, // fuck it, 15 minutes
|
||||
}.ToString();
|
||||
}
|
||||
|
||||
private async Task<MySqlConnection> GetConnection() {
|
||||
MySqlConnection conn = new(ConnectionString);
|
||||
await conn.OpenAsync();
|
||||
return conn;
|
||||
}
|
||||
|
||||
private async Task<int> RunCommand(string command, params MySqlParameter[] parameters) {
|
||||
try {
|
||||
using MySqlConnection conn = await GetConnection();
|
||||
using MySqlCommand cmd = conn.CreateCommand();
|
||||
if(parameters?.Length > 0)
|
||||
cmd.Parameters.AddRange(parameters);
|
||||
cmd.CommandText = command;
|
||||
return await cmd.ExecuteNonQueryAsync();
|
||||
} catch(MySqlException ex) {
|
||||
Logger.Write(ex);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private async Task<MySqlDataReader?> RunQuery(string command, params MySqlParameter[] parameters) {
|
||||
try {
|
||||
MySqlConnection conn = await GetConnection();
|
||||
MySqlCommand cmd = conn.CreateCommand();
|
||||
if(parameters?.Length > 0)
|
||||
cmd.Parameters.AddRange(parameters);
|
||||
cmd.CommandText = command;
|
||||
return await cmd.ExecuteReaderAsync(System.Data.CommandBehavior.CloseConnection);
|
||||
} catch(MySqlException ex) {
|
||||
Logger.Write(ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<T> RunQueryValue<T>(string command, params MySqlParameter[] parameters)
|
||||
where T : struct {
|
||||
try {
|
||||
using MySqlConnection conn = await GetConnection();
|
||||
using MySqlCommand cmd = conn.CreateCommand();
|
||||
if(parameters?.Length > 0)
|
||||
cmd.Parameters.AddRange(parameters);
|
||||
cmd.CommandText = command;
|
||||
await cmd.PrepareAsync();
|
||||
|
||||
object? raw = await cmd.ExecuteScalarAsync();
|
||||
if(raw is T value)
|
||||
return value;
|
||||
} catch(MySqlException ex) {
|
||||
Logger.Write(ex);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
|
@ -2,16 +2,16 @@ using MySqlConnector;
|
|||
|
||||
namespace SharpChat.MariaDB;
|
||||
|
||||
public partial class MariaDBMessageStorage {
|
||||
public class MariaDBMigrations(MariaDBConnection conn) {
|
||||
private async Task DoMigration(string name, Func<Task> action) {
|
||||
bool done = await RunQueryValue<long>(
|
||||
bool done = await conn.RunQueryValue<long>(
|
||||
"SELECT COUNT(*) FROM sqc_migrations WHERE migration_name = @name",
|
||||
new MySqlParameter("name", name)
|
||||
) > 0;
|
||||
if(!done) {
|
||||
Logger.Write($"Running migration '{name}'...");
|
||||
await action();
|
||||
await RunCommand(
|
||||
await conn.RunCommand(
|
||||
"INSERT INTO sqc_migrations (migration_name) VALUES (@name)",
|
||||
new MySqlParameter("name", name)
|
||||
);
|
||||
|
@ -19,7 +19,7 @@ public partial class MariaDBMessageStorage {
|
|||
}
|
||||
|
||||
public async Task RunMigrations() {
|
||||
await RunCommand(
|
||||
await conn.RunCommand(
|
||||
"CREATE TABLE IF NOT EXISTS sqc_migrations ("
|
||||
+ "migration_name VARCHAR(255) NOT NULL,"
|
||||
+ "migration_completed TIMESTAMP NOT NULL DEFAULT current_timestamp(),"
|
||||
|
@ -38,9 +38,9 @@ public partial class MariaDBMessageStorage {
|
|||
}
|
||||
|
||||
private async Task UpdateCollationsAndUseJsonType() {
|
||||
await RunCommand("UPDATE sqc_events SET event_target = LOWER(CONVERT(event_target USING ascii))");
|
||||
await RunCommand("UPDATE sqc_events SET event_sender_nick = NULL WHERE event_sender_nick = ''");
|
||||
await RunCommand(
|
||||
await conn.RunCommand("UPDATE sqc_events SET event_target = LOWER(CONVERT(event_target USING ascii))");
|
||||
await conn.RunCommand("UPDATE sqc_events SET event_sender_nick = NULL WHERE event_sender_nick = ''");
|
||||
await conn.RunCommand(
|
||||
"ALTER TABLE sqc_events COLLATE='utf8mb4_unicode_520_ci',"
|
||||
+ " CHANGE COLUMN event_type event_type VARCHAR(255) NOT NULL COLLATE 'ascii_general_ci' AFTER event_id,"
|
||||
+ " CHANGE COLUMN event_created event_created TIMESTAMP NOT NULL DEFAULT current_timestamp() AFTER event_type,"
|
||||
|
@ -55,27 +55,27 @@ public partial class MariaDBMessageStorage {
|
|||
}
|
||||
|
||||
private async Task UpdateEventTypeNames() {
|
||||
await RunCommand(@"UPDATE sqc_events SET event_type = ""msg:add"" WHERE event_type = ""SharpChat.Events.ChatMessage""");
|
||||
await RunCommand(@"UPDATE sqc_events SET event_type = ""user:connect"" WHERE event_type = ""SharpChat.Events.UserConnectEvent""");
|
||||
await RunCommand(@"UPDATE sqc_events SET event_type = ""user:disconnect"" WHERE event_type = ""SharpChat.Events.UserDisconnectEvent""");
|
||||
await RunCommand(@"UPDATE sqc_events SET event_type = ""chan:join"" WHERE event_type = ""SharpChat.Events.UserChannelJoinEvent""");
|
||||
await RunCommand(@"UPDATE sqc_events SET event_type = ""chan:leave"" WHERE event_type = ""SharpChat.Events.UserChannelLeaveEvent""");
|
||||
await conn.RunCommand(@"UPDATE sqc_events SET event_type = ""msg:add"" WHERE event_type = ""SharpChat.Events.ChatMessage""");
|
||||
await conn.RunCommand(@"UPDATE sqc_events SET event_type = ""user:connect"" WHERE event_type = ""SharpChat.Events.UserConnectEvent""");
|
||||
await conn.RunCommand(@"UPDATE sqc_events SET event_type = ""user:disconnect"" WHERE event_type = ""SharpChat.Events.UserDisconnectEvent""");
|
||||
await conn.RunCommand(@"UPDATE sqc_events SET event_type = ""chan:join"" WHERE event_type = ""SharpChat.Events.UserChannelJoinEvent""");
|
||||
await conn.RunCommand(@"UPDATE sqc_events SET event_type = ""chan:leave"" WHERE event_type = ""SharpChat.Events.UserChannelLeaveEvent""");
|
||||
}
|
||||
|
||||
private async Task NoMoreFlagsField() {
|
||||
// MessageFlags.Action is just a field in the data object
|
||||
await RunCommand("UPDATE sqc_events SET event_data = JSON_MERGE_PATCH(event_data, JSON_OBJECT('act', true)) WHERE event_flags & 1");
|
||||
await conn.RunCommand("UPDATE sqc_events SET event_data = JSON_MERGE_PATCH(event_data, JSON_OBJECT('act', true)) WHERE event_flags & 1");
|
||||
|
||||
// MessageFlags.Broadcast can be implied by just having a NULL as the channel name
|
||||
await RunCommand("UPDATE sqc_events SET event_target = NULL WHERE event_flags & 2");
|
||||
await conn.RunCommand("UPDATE sqc_events SET event_target = NULL WHERE event_flags & 2");
|
||||
|
||||
// MessageFlags.Log was never meaningfully used by anything and basically just meant "not-msg:add"
|
||||
// MessageFlags.Private was also never meaningfully used, can be determined by checking if the channel name starts with @
|
||||
await RunCommand("ALTER TABLE sqc_events DROP COLUMN event_flags");
|
||||
await conn.RunCommand("ALTER TABLE sqc_events DROP COLUMN event_flags");
|
||||
}
|
||||
|
||||
private async Task EventUserAndNickNameTo1000() {
|
||||
await RunCommand(
|
||||
await conn.RunCommand(
|
||||
"ALTER TABLE sqc_events"
|
||||
+ " CHANGE COLUMN event_sender_name event_sender_name VARCHAR(1000) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_520_ci' AFTER event_sender,"
|
||||
+ " CHANGE COLUMN event_sender_nick event_sender_nick VARCHAR(1000) NULL DEFAULT NULL COLLATE 'utf8mb4_unicode_520_ci' AFTER event_sender_rank;"
|
||||
|
@ -83,21 +83,21 @@ public partial class MariaDBMessageStorage {
|
|||
}
|
||||
|
||||
private async Task EventDataAsMediumBlob() {
|
||||
await RunCommand(
|
||||
await conn.RunCommand(
|
||||
"ALTER TABLE sqc_events"
|
||||
+ " CHANGE COLUMN event_data event_data MEDIUMBLOB NULL DEFAULT NULL AFTER event_flags;"
|
||||
);
|
||||
}
|
||||
|
||||
private async Task AllowNullTarget() {
|
||||
await RunCommand(
|
||||
await conn.RunCommand(
|
||||
"ALTER TABLE sqc_events"
|
||||
+ " CHANGE COLUMN event_target event_target VARBINARY(255) NULL AFTER event_type;"
|
||||
);
|
||||
}
|
||||
|
||||
private async Task CreateEventsTable() {
|
||||
await RunCommand(
|
||||
await conn.RunCommand(
|
||||
"CREATE TABLE sqc_events ("
|
||||
+ "event_id BIGINT(20) NOT NULL,"
|
||||
+ "event_sender BIGINT(20) UNSIGNED NULL DEFAULT NULL,"
|
47
SharpChat.MariaDB/MariaDBStorage.cs
Normal file
47
SharpChat.MariaDB/MariaDBStorage.cs
Normal file
|
@ -0,0 +1,47 @@
|
|||
using MySqlConnector;
|
||||
using SharpChat.Configuration;
|
||||
using SharpChat.Messages;
|
||||
|
||||
namespace SharpChat.MariaDB;
|
||||
|
||||
public class MariaDBStorage(string connString) : Storage {
|
||||
public async Task<MariaDBConnection> CreateConnection() {
|
||||
MySqlConnection conn = new(connString);
|
||||
await conn.OpenAsync();
|
||||
return new MariaDBConnection(conn);
|
||||
}
|
||||
|
||||
public MessageStorage CreateMessageStorage() {
|
||||
return new MariaDBMessageStorage(this);
|
||||
}
|
||||
|
||||
public async Task UpgradeStorage() {
|
||||
using MariaDBConnection conn = await CreateConnection();
|
||||
await new MariaDBMigrations(conn).RunMigrations();
|
||||
}
|
||||
|
||||
public static string BuildConnectionString(Config config) {
|
||||
return BuildConnectionString(
|
||||
config.ReadValue("host", "localhost"),
|
||||
config.ReadValue("user", string.Empty),
|
||||
config.ReadValue("pass", string.Empty),
|
||||
config.ReadValue("db", "sharpchat")
|
||||
);
|
||||
}
|
||||
|
||||
public static string BuildConnectionString(string? host, string? username, string? password, string? database) {
|
||||
return new MySqlConnectionStringBuilder {
|
||||
Server = host,
|
||||
UserID = username,
|
||||
Password = password,
|
||||
Database = database,
|
||||
OldGuids = false,
|
||||
TreatTinyAsBoolean = false,
|
||||
CharacterSet = "utf8mb4",
|
||||
SslMode = MySqlSslMode.None,
|
||||
ForceSynchronous = false,
|
||||
ConnectionTimeout = 5,
|
||||
DefaultCommandTimeout = 900, // fuck it, 15 minutes
|
||||
}.ToString();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
namespace SharpChat.MariaDB;
|
||||
|
||||
public partial class MariaDBMessageStorage {
|
||||
public static UserPermissions FromMessagePermissions(MariaDBUserPermissions mup) {
|
||||
public static class MariaDBUserPermissionsConverter {
|
||||
public static UserPermissions From(MariaDBUserPermissions mup) {
|
||||
UserPermissions perms = 0;
|
||||
|
||||
if(mup.HasFlag(MariaDBUserPermissions.KickUser))
|
||||
|
@ -50,7 +50,7 @@ public partial class MariaDBMessageStorage {
|
|||
return perms;
|
||||
}
|
||||
|
||||
public static MariaDBUserPermissions ToStoredPermissions(UserPermissions up) {
|
||||
public static MariaDBUserPermissions To(UserPermissions up) {
|
||||
MariaDBUserPermissions perms = 0;
|
||||
|
||||
if(up.HasFlag(UserPermissions.KickUser))
|
56
SharpChat.SQLite/SQLiteConnection.cs
Normal file
56
SharpChat.SQLite/SQLiteConnection.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SQLite;
|
||||
using NativeSQLiteConnection = System.Data.SQLite.SQLiteConnection;
|
||||
|
||||
namespace SharpChat.SQLite;
|
||||
|
||||
public class SQLiteConnection(NativeSQLiteConnection conn) : IDisposable {
|
||||
public async Task<int> RunCommand(string command, params SQLiteParameter[] parameters) {
|
||||
using SQLiteCommand cmd = conn.CreateCommand();
|
||||
if(parameters?.Length > 0)
|
||||
cmd.Parameters.AddRange(parameters);
|
||||
cmd.CommandText = command;
|
||||
return await cmd.ExecuteNonQueryAsync();
|
||||
}
|
||||
|
||||
public async Task<DbDataReader?> RunQuery(string command, params SQLiteParameter[] parameters) {
|
||||
using SQLiteCommand cmd = conn.CreateCommand();
|
||||
if(parameters?.Length > 0)
|
||||
cmd.Parameters.AddRange(parameters);
|
||||
cmd.CommandText = command;
|
||||
return await cmd.ExecuteReaderAsync();
|
||||
}
|
||||
|
||||
public async Task<T> RunQueryValue<T>(string command, params SQLiteParameter[] parameters)
|
||||
where T : struct {
|
||||
using SQLiteCommand cmd = conn.CreateCommand();
|
||||
if(parameters?.Length > 0)
|
||||
cmd.Parameters.AddRange(parameters);
|
||||
cmd.CommandText = command;
|
||||
await cmd.PrepareAsync();
|
||||
|
||||
object? raw = await cmd.ExecuteScalarAsync();
|
||||
return raw is T value ? value : default;
|
||||
}
|
||||
|
||||
private bool disposed = false;
|
||||
|
||||
~SQLiteConnection() {
|
||||
DoDispose();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
DoDispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void DoDispose() {
|
||||
if(disposed)
|
||||
return;
|
||||
disposed = true;
|
||||
|
||||
RunCommand("VACUUM").Wait();
|
||||
conn.Dispose();
|
||||
}
|
||||
}
|
126
SharpChat.SQLite/SQLiteMessageStorage.cs
Normal file
126
SharpChat.SQLite/SQLiteMessageStorage.cs
Normal file
|
@ -0,0 +1,126 @@
|
|||
using SharpChat.Messages;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SQLite;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace SharpChat.SQLite;
|
||||
|
||||
public class SQLiteMessageStorage(SQLiteConnection conn) : MessageStorage {
|
||||
public async Task LogMessage(
|
||||
long id,
|
||||
string type,
|
||||
string channelName,
|
||||
string senderId,
|
||||
string senderName,
|
||||
ColourInheritable senderColour,
|
||||
int senderRank,
|
||||
string senderNick,
|
||||
UserPermissions senderPerms,
|
||||
object? data = null
|
||||
) {
|
||||
try {
|
||||
await conn.RunCommand(
|
||||
"INSERT INTO messages (msg_id, msg_type, msg_created, 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, @channel, @sender, @sender_name, @sender_colour, @sender_rank, @sender_nick, @sender_perms, @data)",
|
||||
new SQLiteParameter("id", id),
|
||||
new SQLiteParameter("type", type),
|
||||
new SQLiteParameter("created", $"{DateTimeOffset.UtcNow:s}Z"),
|
||||
new SQLiteParameter("channel", string.IsNullOrWhiteSpace(channelName) ? null : channelName),
|
||||
new SQLiteParameter("sender", long.TryParse(senderId, out long senderId64) && senderId64 > 0 ? senderId64 : null),
|
||||
new SQLiteParameter("sender_name", senderName),
|
||||
new SQLiteParameter("sender_colour", senderColour.Rgb.HasValue ? senderColour.Rgb.Value.Raw : null),
|
||||
new SQLiteParameter("sender_rank", senderRank),
|
||||
new SQLiteParameter("sender_nick", string.IsNullOrWhiteSpace(senderNick) ? null : senderNick),
|
||||
new SQLiteParameter("sender_perms", SQLiteUserPermissionsConverter.To(senderPerms)),
|
||||
new SQLiteParameter("data", data == null ? "{}" : JsonSerializer.SerializeToUtf8Bytes(data))
|
||||
);
|
||||
} catch(SQLiteException ex) {
|
||||
Logger.Write(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteMessage(Message msg) {
|
||||
try {
|
||||
await conn.RunCommand(
|
||||
"UPDATE IGNORE messages SET msg_deleted = NOW() WHERE msg_id = @id AND msg_deleted IS NULL",
|
||||
new SQLiteParameter("id", msg.Id)
|
||||
);
|
||||
} catch(SQLiteException ex) {
|
||||
Logger.Write(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static Message ReadMessage(DbDataReader reader) {
|
||||
return new Message(
|
||||
reader.GetInt64("msg_id"),
|
||||
reader.GetString("msg_type"),
|
||||
reader.IsDBNull(reader.GetOrdinal("msg_sender")) ? null : reader.GetString("msg_sender"),
|
||||
reader.IsDBNull(reader.GetOrdinal("msg_sender_name")) ? string.Empty : reader.GetString("msg_sender_name"),
|
||||
ColourInheritable.FromMisuzu(reader.GetInt32("msg_sender_colour")),
|
||||
reader.GetInt32("msg_sender_rank"),
|
||||
SQLiteUserPermissionsConverter.From((SQLiteUserPermissions)reader.GetInt32("msg_sender_perms")),
|
||||
reader.IsDBNull(reader.GetOrdinal("msg_sender_nick")) ? string.Empty : reader.GetString("msg_sender_nick"),
|
||||
DateTimeOffset.Parse(reader.GetString("msg_created")),
|
||||
reader.IsDBNull(reader.GetOrdinal("msg_deleted")) ? null : DateTimeOffset.Parse(reader.GetString("msg_deleted")),
|
||||
reader.IsDBNull(reader.GetOrdinal("msg_channel")) ? null : reader.GetString("msg_channel"),
|
||||
JsonDocument.Parse(reader.GetString("msg_data"))
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<Message?> GetMessage(long id) {
|
||||
try {
|
||||
using DbDataReader? reader = await conn.RunQuery(
|
||||
"SELECT 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"
|
||||
+ " FROM messages WHERE msg_id = @id",
|
||||
new SQLiteParameter("id", id)
|
||||
);
|
||||
|
||||
if(reader is null)
|
||||
return null;
|
||||
|
||||
while(reader.Read()) {
|
||||
Message evt = ReadMessage(reader);
|
||||
if(evt != null)
|
||||
return evt;
|
||||
}
|
||||
} catch(SQLiteException ex) {
|
||||
Logger.Write(ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Message>> GetMessages(string channelName, int amount = 20, int offset = 0) {
|
||||
List<Message> msgs = [];
|
||||
|
||||
try {
|
||||
using DbDataReader? reader = await conn.RunQuery(
|
||||
"SELECT 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"
|
||||
+ " FROM messages"
|
||||
+ " WHERE msg_deleted IS NULL AND (msg_channel = @channel OR msg_channel IS NULL)"
|
||||
+ " AND msg_id > @offset"
|
||||
+ " ORDER BY msg_id DESC"
|
||||
+ " LIMIT @amount",
|
||||
new SQLiteParameter("channel", channelName),
|
||||
new SQLiteParameter("amount", amount),
|
||||
new SQLiteParameter("offset", offset)
|
||||
);
|
||||
if(reader is null)
|
||||
return msgs;
|
||||
|
||||
while(reader.Read()) {
|
||||
Message evt = ReadMessage(reader);
|
||||
if(evt != null)
|
||||
msgs.Add(evt);
|
||||
}
|
||||
} catch(SQLiteException ex) {
|
||||
Logger.Write(ex);
|
||||
}
|
||||
|
||||
msgs.Reverse();
|
||||
|
||||
return msgs;
|
||||
}
|
||||
}
|
45
SharpChat.SQLite/SQLiteMigrations.cs
Normal file
45
SharpChat.SQLite/SQLiteMigrations.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
namespace SharpChat.SQLite;
|
||||
|
||||
public class SQLiteMigrations(SQLiteConnection conn) {
|
||||
public async Task RunMigrations() {
|
||||
long currentVersion = await conn.RunQueryValue<long>("PRAGMA user_version");
|
||||
long version = currentVersion;
|
||||
|
||||
async Task doMigration(int expect, Func<Task> action) {
|
||||
if(version < expect) {
|
||||
await action();
|
||||
++version;
|
||||
}
|
||||
};
|
||||
|
||||
await doMigration(1, CreateMessagesTable);
|
||||
|
||||
if(currentVersion != version)
|
||||
await conn.RunCommand($"PRAGMA user_version = {version}");
|
||||
}
|
||||
|
||||
private async Task CreateMessagesTable() {
|
||||
await conn.RunCommand(
|
||||
@"CREATE TABLE ""messages"" ("
|
||||
+ @"""msg_id"" INTEGER NOT NULL,"
|
||||
+ @"""msg_type"" TEXT NOT NULL COLLATE NOCASE,"
|
||||
+ @"""msg_created"" TEXT NOT NULL COLLATE NOCASE,"
|
||||
+ @"""msg_deleted"" TEXT DEFAULT NULL COLLATE NOCASE,"
|
||||
+ @"""msg_channel"" TEXT DEFAULT NULL COLLATE NOCASE,"
|
||||
+ @"""msg_sender"" TEXT DEFAULT NULL COLLATE BINARY,"
|
||||
+ @"""msg_sender_name"" TEXT DEFAULT NULL COLLATE NOCASE,"
|
||||
+ @"""msg_sender_colour"" INTEGER DEFAULT NULL,"
|
||||
+ @"""msg_sender_rank"" INTEGER DEFAULT NULL,"
|
||||
+ @"""msg_sender_nick"" TEXT DEFAULT NULL COLLATE NOCASE,"
|
||||
+ @"""msg_sender_perms"" INTEGER DEFAULT NULL,"
|
||||
+ @"""msg_data"" BLOB DEFAULT NULL CHECK(JSON_VALID(""msg_data"") AND JSON_TYPE(""msg_data"") = ""object"") COLLATE BINARY,"
|
||||
+ @"PRIMARY KEY(""msg_id"")"
|
||||
+ @");"
|
||||
);
|
||||
await conn.RunCommand(@"CREATE INDEX ""messages_channel_index"" ON ""messages"" (""msg_channel"");");
|
||||
await conn.RunCommand(@"CREATE INDEX ""messages_created_index"" ON ""messages"" (""msg_created"");");
|
||||
await conn.RunCommand(@"CREATE INDEX ""messages_deleted_index"" ON ""messages"" (""msg_deleted"");");
|
||||
await conn.RunCommand(@"CREATE INDEX ""messages_event_type"" ON ""messages"" (""msg_type"");");
|
||||
await conn.RunCommand(@"CREATE INDEX ""messages_sender_index"" ON ""messages"" (""msg_sender"");");
|
||||
}
|
||||
}
|
61
SharpChat.SQLite/SQLiteStorage.cs
Normal file
61
SharpChat.SQLite/SQLiteStorage.cs
Normal file
|
@ -0,0 +1,61 @@
|
|||
using SharpChat.Configuration;
|
||||
using SharpChat.Messages;
|
||||
using System.Data.SQLite;
|
||||
using NativeSQLiteConnection = System.Data.SQLite.SQLiteConnection;
|
||||
|
||||
namespace SharpChat.SQLite;
|
||||
|
||||
public class SQLiteStorage(string connString) : Storage, IDisposable {
|
||||
public const string MEMORY = "file::memory:?cache=shared";
|
||||
public const string DEFAULT = "sharpchat.db";
|
||||
|
||||
public SQLiteConnection Connection { get; } = new SQLiteConnection(new NativeSQLiteConnection(connString).OpenAndReturn());
|
||||
|
||||
public MessageStorage CreateMessageStorage() {
|
||||
return new SQLiteMessageStorage(Connection);
|
||||
}
|
||||
|
||||
public async Task UpgradeStorage() {
|
||||
await new SQLiteMigrations(Connection).RunMigrations();
|
||||
}
|
||||
|
||||
public static string BuildConnectionString(Config config) {
|
||||
return BuildConnectionString(
|
||||
config.ReadValue("path", DEFAULT)!,
|
||||
config.ReadValue("pass")
|
||||
);
|
||||
}
|
||||
|
||||
public static string BuildConnectionString(string path, string? password) {
|
||||
return new SQLiteConnectionStringBuilder {
|
||||
DataSource = string.IsNullOrWhiteSpace(path) ? MEMORY : path,
|
||||
DateTimeFormat = SQLiteDateFormats.ISO8601,
|
||||
DateTimeKind = DateTimeKind.Utc,
|
||||
FailIfMissing = false,
|
||||
ForeignKeys = true,
|
||||
JournalMode = SQLiteJournalModeEnum.Wal,
|
||||
LegacyFormat = false,
|
||||
Password = string.IsNullOrWhiteSpace(password) ? null : password,
|
||||
ReadOnly = false,
|
||||
UseUTF16Encoding = false,
|
||||
}.ToString();
|
||||
}
|
||||
|
||||
private bool disposed = false;
|
||||
|
||||
~SQLiteStorage() {
|
||||
DoDispose();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
DoDispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void DoDispose() {
|
||||
if(disposed)
|
||||
return;
|
||||
disposed = true;
|
||||
Connection.Dispose();
|
||||
}
|
||||
}
|
26
SharpChat.SQLite/SQLiteUserPermissions.cs
Normal file
26
SharpChat.SQLite/SQLiteUserPermissions.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
namespace SharpChat.SQLite;
|
||||
|
||||
[Flags]
|
||||
public enum SQLiteUserPermissions : long {
|
||||
SendMessage = 0x1L,
|
||||
DeleteOwnMessage = 0x2L,
|
||||
DeleteAnyMessage = 0x4L,
|
||||
EditOwnMessage = 0x8L,
|
||||
EditAnyMessage = 0x10L,
|
||||
SendBroadcast = 0x20L,
|
||||
ViewLogs = 0x40L,
|
||||
KickUser = 0x80L,
|
||||
BanUser = 0x100L,
|
||||
PardonUser = 0x200L,
|
||||
PardonIPAddress = 0x400L,
|
||||
ViewIPAddress = 0x800L,
|
||||
ViewBanList = 0x1000L,
|
||||
CreateChannel = 0x2000L,
|
||||
SetChannelPermanent = 0x4000L,
|
||||
SetChannelPassword = 0x8000L,
|
||||
SetChannelMinimumRank = 0x10000L,
|
||||
DeleteChannel = 0x20000L,
|
||||
JoinAnyChannel = 0x40000L,
|
||||
SetOwnNickname = 0x80000L,
|
||||
SetOthersNickname = 0x100000L,
|
||||
}
|
101
SharpChat.SQLite/SQLiteUserPermissionsConverter.cs
Normal file
101
SharpChat.SQLite/SQLiteUserPermissionsConverter.cs
Normal file
|
@ -0,0 +1,101 @@
|
|||
namespace SharpChat.SQLite;
|
||||
|
||||
public static class SQLiteUserPermissionsConverter {
|
||||
public static UserPermissions From(SQLiteUserPermissions sup) {
|
||||
UserPermissions up = 0;
|
||||
|
||||
if(sup.HasFlag(SQLiteUserPermissions.SendMessage))
|
||||
up |= UserPermissions.SendMessage;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.DeleteOwnMessage))
|
||||
up |= UserPermissions.DeleteOwnMessage;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.DeleteAnyMessage))
|
||||
up |= UserPermissions.DeleteAnyMessage;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.EditOwnMessage))
|
||||
up |= UserPermissions.EditOwnMessage;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.EditAnyMessage))
|
||||
up |= UserPermissions.EditAnyMessage;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.SendBroadcast))
|
||||
up |= UserPermissions.SendBroadcast;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.ViewLogs))
|
||||
up |= UserPermissions.ViewLogs;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.KickUser))
|
||||
up |= UserPermissions.KickUser;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.BanUser))
|
||||
up |= UserPermissions.BanUser;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.PardonUser))
|
||||
up |= UserPermissions.PardonUser;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.PardonIPAddress))
|
||||
up |= UserPermissions.PardonIPAddress;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.ViewIPAddress))
|
||||
up |= UserPermissions.ViewIPAddress;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.ViewBanList))
|
||||
up |= UserPermissions.ViewBanList;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.CreateChannel))
|
||||
up |= UserPermissions.CreateChannel;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.SetChannelPermanent))
|
||||
up |= UserPermissions.SetChannelPermanent;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.SetChannelPassword))
|
||||
up |= UserPermissions.SetChannelPassword;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.SetChannelMinimumRank))
|
||||
up |= UserPermissions.SetChannelMinimumRank;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.DeleteChannel))
|
||||
up |= UserPermissions.DeleteChannel;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.JoinAnyChannel))
|
||||
up |= UserPermissions.JoinAnyChannel;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.SetOwnNickname))
|
||||
up |= UserPermissions.SetOwnNickname;
|
||||
if(sup.HasFlag(SQLiteUserPermissions.SetOthersNickname))
|
||||
up |= UserPermissions.SetOthersNickname;
|
||||
|
||||
return up;
|
||||
}
|
||||
|
||||
public static SQLiteUserPermissions To(UserPermissions up) {
|
||||
SQLiteUserPermissions sup = 0;
|
||||
|
||||
if(up.HasFlag(UserPermissions.SendMessage))
|
||||
sup |= SQLiteUserPermissions.SendMessage;
|
||||
if(up.HasFlag(UserPermissions.DeleteOwnMessage))
|
||||
sup |= SQLiteUserPermissions.DeleteOwnMessage;
|
||||
if(up.HasFlag(UserPermissions.DeleteAnyMessage))
|
||||
sup |= SQLiteUserPermissions.DeleteAnyMessage;
|
||||
if(up.HasFlag(UserPermissions.EditOwnMessage))
|
||||
sup |= SQLiteUserPermissions.EditOwnMessage;
|
||||
if(up.HasFlag(UserPermissions.EditAnyMessage))
|
||||
sup |= SQLiteUserPermissions.EditAnyMessage;
|
||||
if(up.HasFlag(UserPermissions.SendBroadcast))
|
||||
sup |= SQLiteUserPermissions.SendBroadcast;
|
||||
if(up.HasFlag(UserPermissions.ViewLogs))
|
||||
sup |= SQLiteUserPermissions.ViewLogs;
|
||||
if(up.HasFlag(UserPermissions.KickUser))
|
||||
sup |= SQLiteUserPermissions.KickUser;
|
||||
if(up.HasFlag(UserPermissions.BanUser))
|
||||
sup |= SQLiteUserPermissions.BanUser;
|
||||
if(up.HasFlag(UserPermissions.PardonUser))
|
||||
sup |= SQLiteUserPermissions.PardonUser;
|
||||
if(up.HasFlag(UserPermissions.PardonIPAddress))
|
||||
sup |= SQLiteUserPermissions.PardonIPAddress;
|
||||
if(up.HasFlag(UserPermissions.ViewIPAddress))
|
||||
sup |= SQLiteUserPermissions.ViewIPAddress;
|
||||
if(up.HasFlag(UserPermissions.ViewBanList))
|
||||
sup |= SQLiteUserPermissions.ViewBanList;
|
||||
if(up.HasFlag(UserPermissions.CreateChannel))
|
||||
sup |= SQLiteUserPermissions.CreateChannel;
|
||||
if(up.HasFlag(UserPermissions.SetChannelPermanent))
|
||||
sup |= SQLiteUserPermissions.SetChannelPermanent;
|
||||
if(up.HasFlag(UserPermissions.SetChannelPassword))
|
||||
sup |= SQLiteUserPermissions.SetChannelPassword;
|
||||
if(up.HasFlag(UserPermissions.SetChannelMinimumRank))
|
||||
sup |= SQLiteUserPermissions.SetChannelMinimumRank;
|
||||
if(up.HasFlag(UserPermissions.DeleteChannel))
|
||||
sup |= SQLiteUserPermissions.DeleteChannel;
|
||||
if(up.HasFlag(UserPermissions.JoinAnyChannel))
|
||||
sup |= SQLiteUserPermissions.JoinAnyChannel;
|
||||
if(up.HasFlag(UserPermissions.SetOwnNickname))
|
||||
sup |= SQLiteUserPermissions.SetOwnNickname;
|
||||
if(up.HasFlag(UserPermissions.SetOthersNickname))
|
||||
sup |= SQLiteUserPermissions.SetOthersNickname;
|
||||
|
||||
return sup;
|
||||
}
|
||||
}
|
17
SharpChat.SQLite/SharpChat.SQLite.csproj
Normal file
17
SharpChat.SQLite/SharpChat.SQLite.csproj
Normal file
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SharpChatCommon\SharpChatCommon.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -29,6 +29,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Storage", "Storage", "{D5EB
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpChat.MariaDB", "SharpChat.MariaDB\SharpChat.MariaDB.csproj", "{5B760B2D-F0AD-46E5-B701-8C53D25E2355}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpChat.SQLite", "SharpChat.SQLite\SharpChat.SQLite.csproj", "{6D74CAE7-200D-44C8-B950-9F45B843E133}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -55,6 +57,10 @@ Global
|
|||
{5B760B2D-F0AD-46E5-B701-8C53D25E2355}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5B760B2D-F0AD-46E5-B701-8C53D25E2355}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5B760B2D-F0AD-46E5-B701-8C53D25E2355}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6D74CAE7-200D-44C8-B950-9F45B843E133}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6D74CAE7-200D-44C8-B950-9F45B843E133}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6D74CAE7-200D-44C8-B950-9F45B843E133}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6D74CAE7-200D-44C8-B950-9F45B843E133}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -63,6 +69,7 @@ Global
|
|||
{A9B0B652-C20F-4C62-A96A-EF7ACD2079E9} = {5BB7CDAA-06BB-4746-BA07-7EF9090774D8}
|
||||
{FEDDC565-B784-4D6F-BEF5-121C383D7AB2} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{5B760B2D-F0AD-46E5-B701-8C53D25E2355} = {D5EB4BD7-7C69-41F5-9D94-AA5E25B26BDA}
|
||||
{6D74CAE7-200D-44C8-B950-9F45B843E133} = {D5EB4BD7-7C69-41F5-9D94-AA5E25B26BDA}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {42279FE1-5980-440A-87F8-25338DFE54CF}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
using SharpChat;
|
||||
using SharpChat.Configuration;
|
||||
using SharpChat.Messages;
|
||||
using SharpChat.Flashii;
|
||||
using System.Text;
|
||||
using SharpChat.MariaDB;
|
||||
using SharpChat.Messages;
|
||||
using SharpChat.SQLite;
|
||||
using System.Text;
|
||||
|
||||
const string CONFIG = "sharpchat.cfg";
|
||||
|
||||
|
@ -124,18 +125,23 @@ FlashiiClient flashii = new(httpClient, config.ScopeTo("msz"));
|
|||
|
||||
if(hasCancelled) return;
|
||||
|
||||
MessageStorage msgStore;
|
||||
if(string.IsNullOrWhiteSpace(config.SafeReadValue("mariadb:host", string.Empty))) {
|
||||
msgStore = new VirtualMessageStorage();
|
||||
} else {
|
||||
MariaDBMessageStorage mdbes = new(MariaDBMessageStorage.BuildConnString(config.ScopeTo("mariadb")));
|
||||
msgStore = mdbes;
|
||||
await mdbes.RunMigrations();
|
||||
Storage storage = string.IsNullOrWhiteSpace(config.SafeReadValue("mariadb:host", string.Empty))
|
||||
? new SQLiteStorage(SQLiteStorage.BuildConnectionString(config.ScopeTo("sqlite")))
|
||||
: new MariaDBStorage(MariaDBStorage.BuildConnectionString(config.ScopeTo("mariadb")));
|
||||
|
||||
try {
|
||||
if(hasCancelled) return;
|
||||
|
||||
await storage.UpgradeStorage();
|
||||
|
||||
if(hasCancelled) return;
|
||||
|
||||
using SockChatServer scs = new(flashii, flashii, storage.CreateMessageStorage(), config.ScopeTo("chat"));
|
||||
scs.Listen(mre);
|
||||
|
||||
mre.WaitOne();
|
||||
} finally {
|
||||
if(storage is IDisposable disp)
|
||||
disp.Dispose();
|
||||
}
|
||||
|
||||
if(hasCancelled) return;
|
||||
|
||||
using SockChatServer scs = new(flashii, flashii, msgStore, config.ScopeTo("chat"));
|
||||
scs.Listen(mre);
|
||||
|
||||
mre.WaitOne();
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
<ProjectReference Include="..\SharpChat.Flashii\SharpChat.Flashii.csproj" />
|
||||
<ProjectReference Include="..\SharpChat.MariaDB\SharpChat.MariaDB.csproj" />
|
||||
<ProjectReference Include="..\SharpChat.SockChat\SharpChat.SockChat.csproj" />
|
||||
<ProjectReference Include="..\SharpChat.SQLite\SharpChat.SQLite.csproj" />
|
||||
<ProjectReference Include="..\SharpChatCommon\SharpChatCommon.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
namespace SharpChat;
|
||||
|
||||
public readonly record struct ColourInheritable(ColourRgb? rgb) {
|
||||
public readonly record struct ColourInheritable(ColourRgb? Rgb) {
|
||||
public static readonly ColourInheritable None = new(null);
|
||||
public override string ToString() => rgb.HasValue ? rgb.Value.ToString() : "inherit";
|
||||
public override string ToString() => Rgb.HasValue ? Rgb.Value.ToString() : "inherit";
|
||||
|
||||
public bool Inherits => !Rgb.HasValue;
|
||||
|
||||
public static ColourInheritable FromRaw(int? raw) => raw is null ? None : new(new ColourRgb(raw.Value));
|
||||
public static ColourInheritable FromRgb(byte red, byte green, byte blue) => new(ColourRgb.FromRgb(red, green, blue));
|
||||
|
||||
// these should go Away
|
||||
private const int MSZ_INHERIT = 0x40000000;
|
||||
public int ToMisuzu() => rgb.HasValue ? rgb.Value.Raw : MSZ_INHERIT;
|
||||
public int ToMisuzu() => Rgb.HasValue ? Rgb.Value.Raw : MSZ_INHERIT;
|
||||
public static ColourInheritable FromMisuzu(int msz) => (msz & MSZ_INHERIT) > 0 ? None : new(new ColourRgb(msz & 0xFFFFFF));
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ namespace SharpChat.Messages;
|
|||
|
||||
public interface MessageStorage {
|
||||
Task LogMessage(
|
||||
long msgId,
|
||||
long id,
|
||||
string type,
|
||||
string channelName,
|
||||
string senderId,
|
||||
|
@ -13,7 +13,7 @@ public interface MessageStorage {
|
|||
UserPermissions senderPerms,
|
||||
object? data = null
|
||||
);
|
||||
Task DeleteMessage(Message evt);
|
||||
Task<Message?> GetMessage(long msgId);
|
||||
Task DeleteMessage(Message msg);
|
||||
Task<Message?> GetMessage(long id);
|
||||
Task<IEnumerable<Message>> GetMessages(string channelName, int amount = 20, int offset = 0);
|
||||
}
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
using System.Text.Json;
|
||||
|
||||
namespace SharpChat.Messages;
|
||||
|
||||
public class VirtualMessageStorage : MessageStorage {
|
||||
private readonly Dictionary<long, Message> Messages = [];
|
||||
|
||||
public Task LogMessage(
|
||||
long id,
|
||||
string type,
|
||||
string channelName,
|
||||
string senderId,
|
||||
string senderName,
|
||||
ColourInheritable senderColour,
|
||||
int senderRank,
|
||||
string senderNick,
|
||||
UserPermissions senderPerms,
|
||||
object? data = null
|
||||
) {
|
||||
Messages.Add(
|
||||
id,
|
||||
new(
|
||||
id,
|
||||
type,
|
||||
long.TryParse(senderId, out long senderId64) && senderId64 > 0 ? senderId : null,
|
||||
senderName,
|
||||
senderColour,
|
||||
senderRank,
|
||||
senderPerms,
|
||||
senderNick,
|
||||
DateTimeOffset.Now,
|
||||
null,
|
||||
channelName,
|
||||
JsonDocument.Parse(data == null ? "{}" : JsonSerializer.Serialize(data))
|
||||
)
|
||||
);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<Message?> GetMessage(long seqId) {
|
||||
return Task.FromResult(Messages.TryGetValue(seqId, out Message? evt) ? evt : null);
|
||||
}
|
||||
|
||||
public Task DeleteMessage(Message evt) {
|
||||
Messages.Remove(evt.Id);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<Message>> GetMessages(string channelName, int amount = 20, int offset = 0) {
|
||||
IEnumerable<Message> subset = Messages.Values.Where(ev => ev.ChannelName == channelName);
|
||||
|
||||
int start = subset.Count() - offset - amount;
|
||||
if(start < 0) {
|
||||
amount += start;
|
||||
start = 0;
|
||||
}
|
||||
|
||||
return Task.FromResult(subset.Skip(start).Take(amount).ToArray() as IEnumerable<Message>);
|
||||
}
|
||||
}
|
8
SharpChatCommon/Storage.cs
Normal file
8
SharpChatCommon/Storage.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
using SharpChat.Messages;
|
||||
|
||||
namespace SharpChat;
|
||||
|
||||
public interface Storage {
|
||||
MessageStorage CreateMessageStorage();
|
||||
Task UpgradeStorage();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue