Compare commits

..

No commits in common. "1ed26a16ea669677be69502106eb654ea3b26b28" and "6f1149ad5e45d7c49eafc9f260bdf6679563249e" have entirely different histories.

9 changed files with 40 additions and 159 deletions

View File

@ -106,10 +106,8 @@ namespace DHT.Desktop.Main {
} }
public void Dispose() { public void Dispose() {
WelcomeScreenModel.Dispose();
MainContentScreenModel?.Dispose(); MainContentScreenModel?.Dispose();
db?.Dispose(); db?.Dispose();
db = null;
} }
} }
} }

View File

@ -8,7 +8,7 @@ using DHT.Server.Database;
using DHT.Utils.Models; using DHT.Utils.Models;
namespace DHT.Desktop.Main { namespace DHT.Desktop.Main {
sealed class WelcomeScreenModel : BaseModel, IDisposable { sealed class WelcomeScreenModel : BaseModel {
public string Version => Program.Version; public string Version => Program.Version;
public IDatabaseFile? Db { get; private set; } public IDatabaseFile? Db { get; private set; }
@ -52,7 +52,8 @@ namespace DHT.Desktop.Main {
} }
public void CloseDatabase() { public void CloseDatabase() {
Dispose(); Db = null;
OnPropertyChanged(nameof(Db)); OnPropertyChanged(nameof(Db));
OnPropertyChanged(nameof(HasDatabase)); OnPropertyChanged(nameof(HasDatabase));
} }
@ -64,10 +65,5 @@ namespace DHT.Desktop.Main {
public void Exit() { public void Exit() {
window.Close(); window.Close();
} }
public void Dispose() {
Db?.Dispose();
Db = null;
}
} }
} }

View File

@ -31,21 +31,18 @@ namespace DHT.Server.Database.Export {
} }
} }
var opts = new JsonSerializerOptions();
opts.Converters.Add(new ViewerJsonSnowflakeSerializer());
var users = GenerateUserList(db, includedUserIds, out var userindex, out var userIndices); var users = GenerateUserList(db, includedUserIds, out var userindex, out var userIndices);
var servers = GenerateServerList(db, includedServerIds, out var serverindex); var servers = GenerateServerList(db, includedServerIds, out var serverindex);
var channels = GenerateChannelList(includedChannels, serverindex); var channels = GenerateChannelList(includedChannels, serverindex);
perf.Step("Collect database data");
var opts = new JsonSerializerOptions();
opts.Converters.Add(new ViewerJsonSnowflakeSerializer());
var json = JsonSerializer.Serialize(new { var json = JsonSerializer.Serialize(new {
meta = new { users, userindex, servers, channels }, meta = new { users, userindex, servers, channels },
data = GenerateMessageList(includedMessages, userIndices) data = GenerateMessageList(includedMessages, userIndices)
}, opts); }, opts);
perf.Step("Serialize to JSON");
perf.End(); perf.End();
return json; return json;
} }

View File

@ -1,14 +1,11 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using DHT.Server.Database.Exceptions; using DHT.Server.Database.Exceptions;
using DHT.Utils.Logging;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
namespace DHT.Server.Database.Sqlite { namespace DHT.Server.Database.Sqlite {
sealed class Schema { sealed class Schema {
internal const int Version = 3; internal const int Version = 2;
private static readonly Log Log = Log.ForType<Schema>();
private readonly SqliteConnection conn; private readonly SqliteConnection conn;
@ -100,9 +97,6 @@ namespace DHT.Server.Database.Sqlite {
emoji_flags INTEGER NOT NULL, emoji_flags INTEGER NOT NULL,
count INTEGER NOT NULL)"); count INTEGER NOT NULL)");
CreateMessageEditTimestampTable();
CreateMessageRepliedToTable();
Execute("CREATE INDEX attachments_message_ix ON attachments(message_id)"); Execute("CREATE INDEX attachments_message_ix ON attachments(message_id)");
Execute("CREATE INDEX embeds_message_ix ON embeds(message_id)"); Execute("CREATE INDEX embeds_message_ix ON embeds(message_id)");
Execute("CREATE INDEX reactions_message_ix ON reactions(message_id)"); Execute("CREATE INDEX reactions_message_ix ON reactions(message_id)");
@ -110,50 +104,12 @@ namespace DHT.Server.Database.Sqlite {
Execute("INSERT INTO metadata (key, value) VALUES ('version', " + Version + ")"); Execute("INSERT INTO metadata (key, value) VALUES ('version', " + Version + ")");
} }
private void CreateMessageEditTimestampTable() {
Execute(@"CREATE TABLE edit_timestamps (
message_id INTEGER PRIMARY KEY NOT NULL,
edit_timestamp INTEGER NOT NULL)");
}
private void CreateMessageRepliedToTable() {
Execute(@"CREATE TABLE replied_to (
message_id INTEGER PRIMARY KEY NOT NULL,
replied_to_id INTEGER NOT NULL)");
}
private void UpgradeSchemas(int dbVersion) { private void UpgradeSchemas(int dbVersion) {
var perf = Log.Start("from version " + dbVersion);
Execute("UPDATE metadata SET value = " + Version + " WHERE key = 'version'"); Execute("UPDATE metadata SET value = " + Version + " WHERE key = 'version'");
if (dbVersion <= 1) { if (dbVersion <= 1) {
Execute("ALTER TABLE channels ADD parent_id INTEGER"); Execute("ALTER TABLE channels ADD parent_id INTEGER");
perf.Step("Upgrade to version 2");
} }
if (dbVersion <= 2) {
CreateMessageEditTimestampTable();
CreateMessageRepliedToTable();
Execute(@"INSERT INTO edit_timestamps (message_id, edit_timestamp)
SELECT message_id, edit_timestamp FROM messages
WHERE edit_timestamp IS NOT NULL");
Execute(@"INSERT INTO replied_to (message_id, replied_to_id)
SELECT message_id, replied_to_id FROM messages
WHERE replied_to_id IS NOT NULL");
Execute("ALTER TABLE messages DROP COLUMN replied_to_id");
Execute("ALTER TABLE messages DROP COLUMN edit_timestamp");
perf.Step("Upgrade to version 3");
Execute("VACUUM");
perf.Step("Vacuum");
}
perf.End();
} }
} }
} }

View File

@ -6,7 +6,6 @@ using System.Threading.Tasks;
using DHT.Server.Data; using DHT.Server.Data;
using DHT.Server.Data.Filters; using DHT.Server.Data.Filters;
using DHT.Utils.Collections; using DHT.Utils.Collections;
using DHT.Utils.Logging;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
namespace DHT.Server.Database.Sqlite { namespace DHT.Server.Database.Sqlite {
@ -26,11 +25,9 @@ namespace DHT.Server.Database.Sqlite {
public string Path { get; } public string Path { get; }
public DatabaseStatistics Statistics { get; } public DatabaseStatistics Statistics { get; }
private readonly Log log;
private readonly SqliteConnection conn; private readonly SqliteConnection conn;
private SqliteDatabaseFile(string path, SqliteConnection conn) { private SqliteDatabaseFile(string path, SqliteConnection conn) {
this.log = Log.ForType(typeof(SqliteDatabaseFile), System.IO.Path.GetFileName(path));
this.conn = conn; this.conn = conn;
this.Path = path; this.Path = path;
this.Statistics = new DatabaseStatistics(); this.Statistics = new DatabaseStatistics();
@ -59,7 +56,6 @@ namespace DHT.Server.Database.Sqlite {
} }
public List<Data.Server> GetAllServers() { public List<Data.Server> GetAllServers() {
var perf = log.Start();
var list = new List<Data.Server>(); var list = new List<Data.Server>();
using var cmd = conn.Command("SELECT id, name, type FROM servers"); using var cmd = conn.Command("SELECT id, name, type FROM servers");
@ -73,7 +69,6 @@ namespace DHT.Server.Database.Sqlite {
}); });
} }
perf.End();
return list; return list;
} }
@ -142,7 +137,6 @@ namespace DHT.Server.Database.Sqlite {
} }
public List<User> GetAllUsers() { public List<User> GetAllUsers() {
var perf = log.Start();
var list = new List<User>(); var list = new List<User>();
using var cmd = conn.Command("SELECT id, name, avatar_url, discriminator FROM users"); using var cmd = conn.Command("SELECT id, name, avatar_url, discriminator FROM users");
@ -157,20 +151,10 @@ namespace DHT.Server.Database.Sqlite {
}); });
} }
perf.End();
return list; return list;
} }
public void AddMessages(Message[] messages) { public void AddMessages(Message[] messages) {
static SqliteCommand DeleteByMessageId(SqliteConnection conn, string tableName) {
return conn.Delete(tableName, ("message_id", SqliteType.Integer));
}
static void ExecuteDeleteByMessageId(SqliteCommand cmd, object id) {
cmd.Set(":message_id", id);
cmd.ExecuteNonQuery();
}
using var tx = conn.BeginTransaction(); using var tx = conn.BeginTransaction();
using var messageCmd = conn.Upsert("messages", new[] { using var messageCmd = conn.Upsert("messages", new[] {
@ -178,26 +162,15 @@ namespace DHT.Server.Database.Sqlite {
("sender_id", SqliteType.Integer), ("sender_id", SqliteType.Integer),
("channel_id", SqliteType.Integer), ("channel_id", SqliteType.Integer),
("text", SqliteType.Text), ("text", SqliteType.Text),
("timestamp", SqliteType.Integer) ("timestamp", SqliteType.Integer),
}); ("edit_timestamp", SqliteType.Integer),
using var deleteEditTimestampCmd = DeleteByMessageId(conn, "edit_timestamps");
using var deleteRepliedToCmd = DeleteByMessageId(conn, "replied_to");
using var deleteAttachmentsCmd = DeleteByMessageId(conn, "attachments");
using var deleteEmbedsCmd = DeleteByMessageId(conn, "embeds");
using var deleteReactionsCmd = DeleteByMessageId(conn, "reactions");
using var editTimestampCmd = conn.Insert("edit_timestamps", new [] {
("message_id", SqliteType.Integer),
("edit_timestamp", SqliteType.Integer)
});
using var repliedToCmd = conn.Insert("replied_to", new [] {
("message_id", SqliteType.Integer),
("replied_to_id", SqliteType.Integer) ("replied_to_id", SqliteType.Integer)
}); });
using var deleteAttachmentsCmd = conn.Delete("attachments", ("message_id", SqliteType.Integer));
using var deleteEmbedsCmd = conn.Delete("embeds", ("message_id", SqliteType.Integer));
using var deleteReactionsCmd = conn.Delete("reactions", ("message_id", SqliteType.Integer));
using var attachmentCmd = conn.Insert("attachments", new[] { using var attachmentCmd = conn.Insert("attachments", new[] {
("message_id", SqliteType.Integer), ("message_id", SqliteType.Integer),
("attachment_id", SqliteType.Integer), ("attachment_id", SqliteType.Integer),
@ -228,26 +201,18 @@ namespace DHT.Server.Database.Sqlite {
messageCmd.Set(":channel_id", message.Channel); messageCmd.Set(":channel_id", message.Channel);
messageCmd.Set(":text", message.Text); messageCmd.Set(":text", message.Text);
messageCmd.Set(":timestamp", message.Timestamp); messageCmd.Set(":timestamp", message.Timestamp);
messageCmd.Set(":edit_timestamp", message.EditTimestamp);
messageCmd.Set(":replied_to_id", message.RepliedToId);
messageCmd.ExecuteNonQuery(); messageCmd.ExecuteNonQuery();
ExecuteDeleteByMessageId(deleteEditTimestampCmd, messageId); deleteAttachmentsCmd.Set(":message_id", messageId);
ExecuteDeleteByMessageId(deleteRepliedToCmd, messageId); deleteAttachmentsCmd.ExecuteNonQuery();
ExecuteDeleteByMessageId(deleteAttachmentsCmd, messageId); deleteEmbedsCmd.Set(":message_id", messageId);
ExecuteDeleteByMessageId(deleteEmbedsCmd, messageId); deleteEmbedsCmd.ExecuteNonQuery();
ExecuteDeleteByMessageId(deleteReactionsCmd, messageId);
if (message.EditTimestamp is {} timestamp) { deleteReactionsCmd.Set(":message_id", messageId);
editTimestampCmd.Set(":message_id", messageId); deleteReactionsCmd.ExecuteNonQuery();
editTimestampCmd.Set(":edit_timestamp", timestamp);
editTimestampCmd.ExecuteNonQuery();
}
if (message.RepliedToId is {} repliedToId) {
repliedToCmd.Set(":message_id", messageId);
repliedToCmd.Set(":replied_to_id", repliedToId);
repliedToCmd.ExecuteNonQuery();
}
if (!message.Attachments.IsEmpty) { if (!message.Attachments.IsEmpty) {
foreach (var attachment in message.Attachments) { foreach (var attachment in message.Attachments) {
@ -293,18 +258,13 @@ namespace DHT.Server.Database.Sqlite {
} }
public List<Message> GetMessages(MessageFilter? filter = null) { public List<Message> GetMessages(MessageFilter? filter = null) {
var perf = log.Start();
var list = new List<Message>();
var attachments = GetAllAttachments(); var attachments = GetAllAttachments();
var embeds = GetAllEmbeds(); var embeds = GetAllEmbeds();
var reactions = GetAllReactions(); var reactions = GetAllReactions();
using var cmd = conn.Command(@" var list = new List<Message>();
SELECT m.message_id, m.sender_id, m.channel_id, m.text, m.timestamp, et.edit_timestamp, rt.replied_to_id
FROM messages m using var cmd = conn.Command("SELECT message_id, sender_id, channel_id, text, timestamp, edit_timestamp, replied_to_id FROM messages" + filter.GenerateWhereClause());
LEFT JOIN edit_timestamps et ON m.message_id = et.message_id
LEFT JOIN replied_to rt ON m.message_id = rt.message_id" + filter.GenerateWhereClause("m"));
using var reader = cmd.ExecuteReader(); using var reader = cmd.ExecuteReader();
while (reader.Read()) { while (reader.Read()) {
@ -324,7 +284,6 @@ LEFT JOIN replied_to rt ON m.message_id = rt.message_id" + filter.GenerateWhereC
}); });
} }
perf.End();
return list; return list;
} }
@ -334,8 +293,6 @@ LEFT JOIN replied_to rt ON m.message_id = rt.message_id" + filter.GenerateWhereC
return; return;
} }
var perf = log.Start();
// Rider is being stupid... // Rider is being stupid...
StringBuilder build = new StringBuilder() StringBuilder build = new StringBuilder()
.Append("DELETE ") .Append("DELETE ")
@ -346,7 +303,6 @@ LEFT JOIN replied_to rt ON m.message_id = rt.message_id" + filter.GenerateWhereC
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
UpdateMessageStatistics(); UpdateMessageStatistics();
perf.End();
} }
private MultiDictionary<ulong, Attachment> GetAllAttachments() { private MultiDictionary<ulong, Attachment> GetAllAttachments() {

View File

@ -4,35 +4,31 @@ using DHT.Server.Data.Filters;
namespace DHT.Server.Database.Sqlite { namespace DHT.Server.Database.Sqlite {
static class SqliteMessageFilter { static class SqliteMessageFilter {
public static string GenerateWhereClause(this MessageFilter? filter, string? tableAlias = null, bool invert = false) { public static string GenerateWhereClause(this MessageFilter? filter, bool invert = false) {
if (filter == null) { if (filter == null) {
return ""; return "";
} }
if (tableAlias != null) {
tableAlias += ".";
}
List<string> conditions = new(); List<string> conditions = new();
if (filter.StartDate != null) { if (filter.StartDate != null) {
conditions.Add(tableAlias + "timestamp >= " + new DateTimeOffset(filter.StartDate.Value).ToUnixTimeMilliseconds()); conditions.Add("timestamp >= " + new DateTimeOffset(filter.StartDate.Value).ToUnixTimeMilliseconds());
} }
if (filter.EndDate != null) { if (filter.EndDate != null) {
conditions.Add(tableAlias + "timestamp <= " + new DateTimeOffset(filter.EndDate.Value).ToUnixTimeMilliseconds()); conditions.Add("timestamp <= " + new DateTimeOffset(filter.EndDate.Value).ToUnixTimeMilliseconds());
} }
if (filter.ChannelIds != null) { if (filter.ChannelIds != null) {
conditions.Add(tableAlias + "channel_id IN (" + string.Join(",", filter.ChannelIds) + ")"); conditions.Add("channel_id IN (" + string.Join(",", filter.ChannelIds) + ")");
} }
if (filter.UserIds != null) { if (filter.UserIds != null) {
conditions.Add(tableAlias + "sender_id IN (" + string.Join(",", filter.UserIds) + ")"); conditions.Add("sender_id IN (" + string.Join(",", filter.UserIds) + ")");
} }
if (filter.MessageIds != null) { if (filter.MessageIds != null) {
conditions.Add(tableAlias + "message_id IN (" + string.Join(",", filter.MessageIds) + ")"); conditions.Add("message_id IN (" + string.Join(",", filter.MessageIds) + ")");
} }
if (conditions.Count == 0) { if (conditions.Count == 0) {

View File

@ -87,8 +87,8 @@ namespace DHT.Utils.Logging {
LogLevel(ConsoleColor.Red, "ERROR", e.ToString()); LogLevel(ConsoleColor.Red, "ERROR", e.ToString());
} }
public Perf Start(string? context = null, [CallerMemberName] string callerMemberName = "") { public Perf Start([CallerMemberName] string callerMemberName = "") {
return Perf.Start(this, context, callerMemberName); return Perf.Start(this, callerMemberName);
} }
} }
} }

View File

@ -3,44 +3,26 @@ using System.Runtime.CompilerServices;
namespace DHT.Utils.Logging { namespace DHT.Utils.Logging {
public sealed class Perf { public sealed class Perf {
internal static Perf Start(Log log, string? context = null, [CallerMemberName] string callerMemberName = "") { internal static Perf Start(Log log, [CallerMemberName] string callerMemberName = "") {
return new Perf(log, callerMemberName, context); return new Perf(log, callerMemberName);
} }
private readonly Log log; private readonly Log log;
private readonly string method; private readonly string method;
private readonly string? context; private readonly Stopwatch stopwatch;
private readonly Stopwatch totalStopwatch;
private readonly Stopwatch stepStopwatch;
private Perf(Log log, string method, string? context) { private Perf(Log log, string method) {
this.log = log; this.log = log;
this.method = method; this.method = method;
this.context = context; this.stopwatch = new Stopwatch();
this.totalStopwatch = new Stopwatch(); this.stopwatch.Start();
this.totalStopwatch.Start();
this.stepStopwatch = new Stopwatch();
this.stepStopwatch.Start();
}
public void Step(string name) {
stepStopwatch.Stop();
if (Log.IsDebugEnabled) {
string ctx = context == null ? string.Empty : " " + context;
log.Debug($"Finished step '{name}' of '{method}'{ctx} in {stepStopwatch.ElapsedMilliseconds} ms.");
}
stepStopwatch.Restart();
} }
public void End() { public void End() {
totalStopwatch.Stop(); stopwatch.Stop();
stepStopwatch.Stop();
if (Log.IsDebugEnabled) { if (Log.IsDebugEnabled) {
string ctx = context == null ? string.Empty : " " + context; log.Debug($"Finished '{method}' in {stopwatch.ElapsedMilliseconds} ms.");
log.Debug($"Finished '{method}'{ctx} in {totalStopwatch.ElapsedMilliseconds} ms.");
} }
} }
} }

Binary file not shown.