Compare commits

..

7 Commits

Author SHA1 Message Date
chylex
6f1149ad5e
Add utilities to simplify working with SQLite 2022-03-05 22:58:47 +01:00
chylex
b9899922e0
Optimize viewer export in the app 2022-03-05 21:35:56 +01:00
chylex
6a2933ea0a
Add utilities for performance logging 2022-03-05 21:05:43 +01:00
chylex
be5c76c3bd
Add debug log level and reset console colors after logging 2022-03-05 20:09:24 +01:00
chylex
217c1f9e10
Tell users to backup database file(s) before a schema upgrade 2022-03-05 18:43:48 +01:00
chylex
725ab7accf
Update SQLite version to 3.35.0 2022-03-05 17:18:33 +01:00
chylex
9a7a2cffc2
Allow database file path to be passed as the first command line argument to the app
This adds support for directly opening files with the DHT app, for ex. in Windows Explorer by using "Open With", or by associating the ".dht" extension with the app.
2022-03-05 16:43:58 +01:00
9 changed files with 288 additions and 184 deletions

View File

@ -15,12 +15,25 @@ namespace DHT.Desktop {
for (int i = 0; i < args.Length; i++) {
string key = args[i];
if (i >= args.Length - 1) {
switch (key) {
case "-debug":
Log.IsDebugEnabled = true;
continue;
}
string value;
if (i == 0 && !key.StartsWith('-')) {
value = key;
key = "-db";
}
else if (i >= args.Length - 1) {
Log.Warn("Missing value for command line argument: " + key);
continue;
}
string value = args[++i];
else {
value = args[++i];
}
switch (key) {
case "-db":

View File

@ -56,11 +56,11 @@ namespace DHT.Desktop.Common {
}
public static async Task<DialogResult.YesNo> ShowCanUpgradeDatabaseDialog(Window window) {
return await Dialog.ShowYesNo(window, "Database Upgrade", "This database was created with an older version of DHT. If you proceed, the database will be upgraded and will no longer open in previous versions of DHT. Do you want to proceed with the upgrade?");
return await Dialog.ShowYesNo(window, "Database Upgrade", "This database was created with an older version of DHT. If you proceed, the database will be upgraded and will no longer open in previous versions of DHT.\n\nPlease ensure you have a backup of the database. Do you want to proceed with the upgrade?");
}
public static async Task<DialogResult.YesNo> ShowCanUpgradeMultipleDatabaseDialog(Window window) {
return await Dialog.ShowYesNo(window, "Database Upgrade", "One or more databases were created with an older version of DHT. If you proceed, these databases will be upgraded and will no longer open in previous versions of DHT. Otherwise, these databases will be skipped. Do you want to proceed with the upgrade?");
return await Dialog.ShowYesNo(window, "Database Upgrade", "One or more databases were created with an older version of DHT. If you proceed, these databases will be upgraded and will no longer open in previous versions of DHT. Otherwise, these databases will be skipped.\n\nPlease ensure you have a backup of the databases. Do you want to proceed with the upgrade?");
}
}
}

View File

@ -1,13 +1,17 @@
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text.Json;
using DHT.Server.Data;
using DHT.Server.Data.Filters;
using DHT.Utils.Logging;
namespace DHT.Server.Database.Export {
public static class ViewerJsonExport {
private static readonly Log Log = Log.ForType(typeof(ViewerJsonExport));
public static string Generate(IDatabaseFile db, MessageFilter? filter = null) {
var perf = Log.Start();
var includedUserIds = new HashSet<ulong>();
var includedChannelIds = new HashSet<ulong>();
var includedServerIds = new HashSet<ulong>();
@ -34,16 +38,19 @@ namespace DHT.Server.Database.Export {
var servers = GenerateServerList(db, includedServerIds, out var serverindex);
var channels = GenerateChannelList(includedChannels, serverindex);
return JsonSerializer.Serialize(new {
var json = JsonSerializer.Serialize(new {
meta = new { users, userindex, servers, channels },
data = GenerateMessageList(includedMessages, userIndices)
}, opts);
perf.End();
return json;
}
private static dynamic GenerateUserList(IDatabaseFile db, HashSet<ulong> userIds, out List<string> userindex, out Dictionary<ulong, int> userIndices) {
var users = new Dictionary<string, dynamic>();
private static object GenerateUserList(IDatabaseFile db, HashSet<ulong> userIds, out List<string> userindex, out Dictionary<ulong, object> userIndices) {
var users = new Dictionary<string, object>();
userindex = new List<string>();
userIndices = new Dictionary<ulong, int>();
userIndices = new Dictionary<ulong, object>();
foreach (var user in db.GetAllUsers()) {
var id = user.Id;
@ -51,15 +58,16 @@ namespace DHT.Server.Database.Export {
continue;
}
dynamic obj = new ExpandoObject();
obj.name = user.Name;
var obj = new Dictionary<string, object> {
["name"] = user.Name
};
if (user.AvatarUrl != null) {
obj.avatar = user.AvatarUrl;
obj["avatar"] = user.AvatarUrl;
}
if (user.Discriminator != null) {
obj.tag = user.Discriminator;
obj["tag"] = user.Discriminator;
}
var idStr = id.ToString();
@ -71,9 +79,9 @@ namespace DHT.Server.Database.Export {
return users;
}
private static dynamic GenerateServerList(IDatabaseFile db, HashSet<ulong> serverIds, out Dictionary<ulong, int> serverIndices) {
var servers = new List<dynamic>();
serverIndices = new Dictionary<ulong, int>();
private static object GenerateServerList(IDatabaseFile db, HashSet<ulong> serverIds, out Dictionary<ulong, object> serverIndices) {
var servers = new List<object>();
serverIndices = new Dictionary<ulong, object>();
foreach (var server in db.GetAllServers()) {
var id = server.Id;
@ -82,37 +90,38 @@ namespace DHT.Server.Database.Export {
}
serverIndices[id] = servers.Count;
servers.Add(new {
name = server.Name,
type = ServerTypes.ToJsonViewerString(server.Type)
servers.Add(new Dictionary<string, object> {
["name"] = server.Name,
["type"] = ServerTypes.ToJsonViewerString(server.Type)
});
}
return servers;
}
private static dynamic GenerateChannelList(List<Channel> includedChannels, Dictionary<ulong, int> serverIndices) {
var channels = new Dictionary<string, dynamic>();
private static object GenerateChannelList(List<Channel> includedChannels, Dictionary<ulong, object> serverIndices) {
var channels = new Dictionary<string, object>();
foreach (var channel in includedChannels) {
dynamic obj = new ExpandoObject();
obj.server = serverIndices[channel.Server];
obj.name = channel.Name;
var obj = new Dictionary<string, object> {
["server"] = serverIndices[channel.Server],
["name"] = channel.Name
};
if (channel.ParentId != null) {
obj.parent = channel.ParentId;
obj["parent"] = channel.ParentId;
}
if (channel.Position != null) {
obj.position = channel.Position;
obj["position"] = channel.Position;
}
if (channel.Topic != null) {
obj.topic = channel.Topic;
obj["topic"] = channel.Topic;
}
if (channel.Nsfw != null) {
obj.nsfw = channel.Nsfw;
obj["nsfw"] = channel.Nsfw;
}
channels[channel.Id.ToString()] = obj;
@ -121,54 +130,55 @@ namespace DHT.Server.Database.Export {
return channels;
}
private static dynamic GenerateMessageList(List<Message> includedMessages, Dictionary<ulong, int> userIndices) {
var data = new Dictionary<string, Dictionary<string, dynamic>>();
private static object GenerateMessageList(List<Message> includedMessages, Dictionary<ulong, object> userIndices) {
var data = new Dictionary<string, Dictionary<string, object>>();
foreach (var grouping in includedMessages.GroupBy(static message => message.Channel)) {
var channel = grouping.Key.ToString();
var channelData = new Dictionary<string, dynamic>();
var channelData = new Dictionary<string, object>();
foreach (var message in grouping) {
dynamic obj = new ExpandoObject();
obj.u = userIndices[message.Sender];
obj.t = message.Timestamp;
var obj = new Dictionary<string, object> {
["u"] = userIndices[message.Sender],
["t"] = message.Timestamp
};
if (!string.IsNullOrEmpty(message.Text)) {
obj.m = message.Text;
obj["m"] = message.Text;
}
if (message.EditTimestamp != null) {
obj.te = message.EditTimestamp;
obj["te"] = message.EditTimestamp;
}
if (message.RepliedToId != null) {
obj.r = message.RepliedToId.Value;
obj["r"] = message.RepliedToId.Value;
}
if (!message.Attachments.IsEmpty) {
obj.a = message.Attachments.Select(static attachment => new {
obj["a"] = message.Attachments.Select(static attachment => new {
url = attachment.Url
}).ToArray();
}
if (!message.Embeds.IsEmpty) {
obj.e = message.Embeds.Select(static embed => embed.Json).ToArray();
obj["e"] = message.Embeds.Select(static embed => embed.Json).ToArray();
}
if (!message.Reactions.IsEmpty) {
obj.re = message.Reactions.Select(static reaction => {
dynamic r = new ExpandoObject();
obj["re"] = message.Reactions.Select(static reaction => {
var r = new Dictionary<string, object>();
if (reaction.EmojiId != null) {
r.id = reaction.EmojiId.Value;
r["id"] = reaction.EmojiId.Value;
}
if (reaction.EmojiName != null) {
r.n = reaction.EmojiName;
r["n"] = reaction.EmojiName;
}
r.a = reaction.EmojiFlags.HasFlag(EmojiFlags.Animated);
r.c = reaction.Count;
r["a"] = reaction.EmojiFlags.HasFlag(EmojiFlags.Animated);
r["c"] = reaction.Count;
return r;
});
}

View File

@ -26,7 +26,7 @@ namespace DHT.Server.Database.Sqlite {
public async Task<bool> Setup(Func<Task<bool>> checkCanUpgradeSchemas) {
Execute(@"CREATE TABLE IF NOT EXISTS metadata (key TEXT PRIMARY KEY, value TEXT)");
var dbVersionStr = Sql("SELECT value FROM metadata WHERE key = 'version'").ExecuteScalar();
var dbVersionStr = conn.SelectScalar("SELECT value FROM metadata WHERE key = 'version'");
if (dbVersionStr == null) {
InitializeSchemas();
}

View File

@ -43,13 +43,14 @@ namespace DHT.Server.Database.Sqlite {
public void AddServer(Data.Server server) {
using var cmd = conn.Upsert("servers", new[] {
"id", "name", "type"
("id", SqliteType.Integer),
("name", SqliteType.Text),
("type", SqliteType.Text)
});
var serverParams = cmd.Parameters;
serverParams.AddAndSet(":id", server.Id);
serverParams.AddAndSet(":name", server.Name);
serverParams.AddAndSet(":type", ServerTypes.ToString(server.Type));
cmd.Set(":id", server.Id);
cmd.Set(":name", server.Name);
cmd.Set(":type", ServerTypes.ToString(server.Type));
cmd.ExecuteNonQuery();
UpdateServerStatistics();
}
@ -62,7 +63,7 @@ namespace DHT.Server.Database.Sqlite {
while (reader.Read()) {
list.Add(new Data.Server {
Id = (ulong) reader.GetInt64(0),
Id = reader.GetUint64(0),
Name = reader.GetString(1),
Type = ServerTypes.FromString(reader.GetString(2))
});
@ -73,17 +74,22 @@ namespace DHT.Server.Database.Sqlite {
public void AddChannel(Channel channel) {
using var cmd = conn.Upsert("channels", new[] {
"id", "server", "name", "parent_id", "position", "topic", "nsfw"
("id", SqliteType.Integer),
("server", SqliteType.Integer),
("name", SqliteType.Text),
("parent_id", SqliteType.Integer),
("position", SqliteType.Integer),
("topic", SqliteType.Text),
("nsfw", SqliteType.Integer)
});
var channelParams = cmd.Parameters;
channelParams.AddAndSet(":id", channel.Id);
channelParams.AddAndSet(":server", channel.Server);
channelParams.AddAndSet(":name", channel.Name);
channelParams.AddAndSet(":parent_id", channel.ParentId);
channelParams.AddAndSet(":position", channel.Position);
channelParams.AddAndSet(":topic", channel.Topic);
channelParams.AddAndSet(":nsfw", channel.Nsfw);
cmd.Set(":id", channel.Id);
cmd.Set(":server", channel.Server);
cmd.Set(":name", channel.Name);
cmd.Set(":parent_id", channel.ParentId);
cmd.Set(":position", channel.Position);
cmd.Set(":topic", channel.Topic);
cmd.Set(":nsfw", channel.Nsfw);
cmd.ExecuteNonQuery();
UpdateChannelStatistics();
}
@ -96,10 +102,10 @@ namespace DHT.Server.Database.Sqlite {
while (reader.Read()) {
list.Add(new Channel {
Id = (ulong) reader.GetInt64(0),
Server = (ulong) reader.GetInt64(1),
Id = reader.GetUint64(0),
Server = reader.GetUint64(1),
Name = reader.GetString(2),
ParentId = reader.IsDBNull(3) ? null : (ulong) reader.GetInt64(3),
ParentId = reader.IsDBNull(3) ? null : reader.GetUint64(3),
Position = reader.IsDBNull(4) ? null : reader.GetInt32(4),
Topic = reader.IsDBNull(5) ? null : reader.GetString(5),
Nsfw = reader.IsDBNull(6) ? null : reader.GetBoolean(6)
@ -112,20 +118,17 @@ namespace DHT.Server.Database.Sqlite {
public void AddUsers(User[] users) {
using var tx = conn.BeginTransaction();
using var cmd = conn.Upsert("users", new[] {
"id", "name", "avatar_url", "discriminator"
("id", SqliteType.Integer),
("name", SqliteType.Text),
("avatar_url", SqliteType.Text),
("discriminator", SqliteType.Text)
});
var userParams = cmd.Parameters;
userParams.Add(":id", SqliteType.Integer);
userParams.Add(":name", SqliteType.Text);
userParams.Add(":avatar_url", SqliteType.Text);
userParams.Add(":discriminator", SqliteType.Text);
foreach (var user in users) {
userParams.Set(":id", user.Id);
userParams.Set(":name", user.Name);
userParams.Set(":avatar_url", user.AvatarUrl);
userParams.Set(":discriminator", user.Discriminator);
cmd.Set(":id", user.Id);
cmd.Set(":name", user.Name);
cmd.Set(":avatar_url", user.AvatarUrl);
cmd.Set(":discriminator", user.Discriminator);
cmd.ExecuteNonQuery();
}
@ -141,7 +144,7 @@ namespace DHT.Server.Database.Sqlite {
while (reader.Read()) {
list.Add(new User {
Id = (ulong) reader.GetInt64(0),
Id = reader.GetUint64(0),
Name = reader.GetString(1),
AvatarUrl = reader.IsDBNull(2) ? null : reader.GetString(2),
Discriminator = reader.IsDBNull(3) ? null : reader.GetString(3)
@ -153,110 +156,91 @@ namespace DHT.Server.Database.Sqlite {
public void AddMessages(Message[] messages) {
using var tx = conn.BeginTransaction();
using var messageCmd = conn.Upsert("messages", new[] {
"message_id", "sender_id", "channel_id", "text", "timestamp", "edit_timestamp", "replied_to_id"
("message_id", SqliteType.Integer),
("sender_id", SqliteType.Integer),
("channel_id", SqliteType.Integer),
("text", SqliteType.Text),
("timestamp", SqliteType.Integer),
("edit_timestamp", SqliteType.Integer),
("replied_to_id", SqliteType.Integer)
});
using var deleteAttachmentsCmd = conn.Command("DELETE FROM attachments WHERE message_id = :message_id");
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[] {
"message_id", "attachment_id", "name", "type", "url", "size"
("message_id", SqliteType.Integer),
("attachment_id", SqliteType.Integer),
("name", SqliteType.Text),
("type", SqliteType.Text),
("url", SqliteType.Text),
("size", SqliteType.Integer)
});
using var deleteEmbedsCmd = conn.Command("DELETE FROM embeds WHERE message_id = :message_id");
using var embedCmd = conn.Insert("embeds", new[] {
"message_id", "json"
("message_id", SqliteType.Integer),
("json", SqliteType.Text)
});
using var deleteReactionsCmd = conn.Command("DELETE FROM reactions WHERE message_id = :message_id");
using var reactionCmd = conn.Insert("reactions", new[] {
"message_id", "emoji_id", "emoji_name", "emoji_flags", "count"
("message_id", SqliteType.Integer),
("emoji_id", SqliteType.Integer),
("emoji_name", SqliteType.Text),
("emoji_flags", SqliteType.Integer),
("count", SqliteType.Integer)
});
var messageParams = messageCmd.Parameters;
messageParams.Add(":message_id", SqliteType.Integer);
messageParams.Add(":sender_id", SqliteType.Integer);
messageParams.Add(":channel_id", SqliteType.Integer);
messageParams.Add(":text", SqliteType.Text);
messageParams.Add(":timestamp", SqliteType.Integer);
messageParams.Add(":edit_timestamp", SqliteType.Integer);
messageParams.Add(":replied_to_id", SqliteType.Integer);
var deleteAttachmentsParams = deleteAttachmentsCmd.Parameters;
deleteAttachmentsParams.Add(":message_id", SqliteType.Integer);
var attachmentParams = attachmentCmd.Parameters;
attachmentParams.Add(":message_id", SqliteType.Integer);
attachmentParams.Add(":attachment_id", SqliteType.Integer);
attachmentParams.Add(":name", SqliteType.Text);
attachmentParams.Add(":type", SqliteType.Text);
attachmentParams.Add(":url", SqliteType.Text);
attachmentParams.Add(":size", SqliteType.Integer);
var deleteEmbedsParams = deleteEmbedsCmd.Parameters;
deleteEmbedsParams.Add(":message_id", SqliteType.Integer);
var embedParams = embedCmd.Parameters;
embedParams.Add(":message_id", SqliteType.Integer);
embedParams.Add(":json", SqliteType.Text);
var deleteReactionsParams = deleteReactionsCmd.Parameters;
deleteReactionsParams.Add(":message_id", SqliteType.Integer);
var reactionParams = reactionCmd.Parameters;
reactionParams.Add(":message_id", SqliteType.Integer);
reactionParams.Add(":emoji_id", SqliteType.Integer);
reactionParams.Add(":emoji_name", SqliteType.Text);
reactionParams.Add(":emoji_flags", SqliteType.Integer);
reactionParams.Add(":count", SqliteType.Integer);
foreach (var message in messages) {
object messageId = message.Id;
messageParams.Set(":message_id", messageId);
messageParams.Set(":sender_id", message.Sender);
messageParams.Set(":channel_id", message.Channel);
messageParams.Set(":text", message.Text);
messageParams.Set(":timestamp", message.Timestamp);
messageParams.Set(":edit_timestamp", message.EditTimestamp);
messageParams.Set(":replied_to_id", message.RepliedToId);
messageCmd.Set(":message_id", messageId);
messageCmd.Set(":sender_id", message.Sender);
messageCmd.Set(":channel_id", message.Channel);
messageCmd.Set(":text", message.Text);
messageCmd.Set(":timestamp", message.Timestamp);
messageCmd.Set(":edit_timestamp", message.EditTimestamp);
messageCmd.Set(":replied_to_id", message.RepliedToId);
messageCmd.ExecuteNonQuery();
deleteAttachmentsParams.Set(":message_id", messageId);
deleteAttachmentsCmd.Set(":message_id", messageId);
deleteAttachmentsCmd.ExecuteNonQuery();
deleteEmbedsParams.Set(":message_id", messageId);
deleteEmbedsCmd.Set(":message_id", messageId);
deleteEmbedsCmd.ExecuteNonQuery();
deleteReactionsParams.Set(":message_id", messageId);
deleteReactionsCmd.Set(":message_id", messageId);
deleteReactionsCmd.ExecuteNonQuery();
if (!message.Attachments.IsEmpty) {
foreach (var attachment in message.Attachments) {
attachmentParams.Set(":message_id", messageId);
attachmentParams.Set(":attachment_id", attachment.Id);
attachmentParams.Set(":name", attachment.Name);
attachmentParams.Set(":type", attachment.Type);
attachmentParams.Set(":url", attachment.Url);
attachmentParams.Set(":size", attachment.Size);
attachmentCmd.Set(":message_id", messageId);
attachmentCmd.Set(":attachment_id", attachment.Id);
attachmentCmd.Set(":name", attachment.Name);
attachmentCmd.Set(":type", attachment.Type);
attachmentCmd.Set(":url", attachment.Url);
attachmentCmd.Set(":size", attachment.Size);
attachmentCmd.ExecuteNonQuery();
}
}
if (!message.Embeds.IsEmpty) {
foreach (var embed in message.Embeds) {
embedParams.Set(":message_id", messageId);
embedParams.Set(":json", embed.Json);
embedCmd.Set(":message_id", messageId);
embedCmd.Set(":json", embed.Json);
embedCmd.ExecuteNonQuery();
}
}
if (!message.Reactions.IsEmpty) {
foreach (var reaction in message.Reactions) {
reactionParams.Set(":message_id", messageId);
reactionParams.Set(":emoji_id", reaction.EmojiId);
reactionParams.Set(":emoji_name", reaction.EmojiName);
reactionParams.Set(":emoji_flags", (int) reaction.EmojiFlags);
reactionParams.Set(":count", reaction.Count);
reactionCmd.Set(":message_id", messageId);
reactionCmd.Set(":emoji_id", reaction.EmojiId);
reactionCmd.Set(":emoji_name", reaction.EmojiName);
reactionCmd.Set(":emoji_flags", (int) reaction.EmojiFlags);
reactionCmd.Set(":count", reaction.Count);
reactionCmd.ExecuteNonQuery();
}
}
@ -284,16 +268,16 @@ namespace DHT.Server.Database.Sqlite {
using var reader = cmd.ExecuteReader();
while (reader.Read()) {
ulong id = (ulong) reader.GetInt64(0);
ulong id = reader.GetUint64(0);
list.Add(new Message {
Id = id,
Sender = (ulong) reader.GetInt64(1),
Channel = (ulong) reader.GetInt64(2),
Sender = reader.GetUint64(1),
Channel = reader.GetUint64(2),
Text = reader.GetString(3),
Timestamp = reader.GetInt64(4),
EditTimestamp = reader.IsDBNull(5) ? null : reader.GetInt64(5),
RepliedToId = reader.IsDBNull(6) ? null : (ulong) reader.GetInt64(6),
RepliedToId = reader.IsDBNull(6) ? null : reader.GetUint64(6),
Attachments = attachments.GetListOrNull(id)?.ToImmutableArray() ?? ImmutableArray<Attachment>.Empty,
Embeds = embeds.GetListOrNull(id)?.ToImmutableArray() ?? ImmutableArray<Embed>.Empty,
Reactions = reactions.GetListOrNull(id)?.ToImmutableArray() ?? ImmutableArray<Reaction>.Empty
@ -328,14 +312,14 @@ namespace DHT.Server.Database.Sqlite {
using var reader = cmd.ExecuteReader();
while (reader.Read()) {
ulong messageId = (ulong) reader.GetInt64(0);
ulong messageId = reader.GetUint64(0);
dict.Add(messageId, new Attachment {
Id = (ulong) reader.GetInt64(1),
Id = reader.GetUint64(1),
Name = reader.GetString(2),
Type = reader.IsDBNull(3) ? null : reader.GetString(3),
Url = reader.GetString(4),
Size = (ulong) reader.GetInt64(5)
Size = reader.GetUint64(5)
});
}
@ -349,7 +333,7 @@ namespace DHT.Server.Database.Sqlite {
using var reader = cmd.ExecuteReader();
while (reader.Read()) {
ulong messageId = (ulong) reader.GetInt64(0);
ulong messageId = reader.GetUint64(0);
dict.Add(messageId, new Embed {
Json = reader.GetString(1)
@ -366,10 +350,10 @@ namespace DHT.Server.Database.Sqlite {
using var reader = cmd.ExecuteReader();
while (reader.Read()) {
ulong messageId = (ulong) reader.GetInt64(0);
ulong messageId = reader.GetUint64(0);
dict.Add(messageId, new Reaction {
EmojiId = reader.IsDBNull(1) ? null : (ulong) reader.GetInt64(1),
EmojiId = reader.IsDBNull(1) ? null : reader.GetUint64(1),
EmojiName = reader.IsDBNull(2) ? null : reader.GetString(2),
EmojiFlags = (EmojiFlags) reader.GetInt16(3),
Count = reader.GetInt32(4)
@ -380,23 +364,19 @@ namespace DHT.Server.Database.Sqlite {
}
private void UpdateServerStatistics() {
using var cmd = conn.Command("SELECT COUNT(*) FROM servers");
Statistics.TotalServers = cmd.ExecuteScalar() as long? ?? 0;
Statistics.TotalServers = conn.SelectScalar("SELECT COUNT(*) FROM servers") as long? ?? 0;
}
private void UpdateChannelStatistics() {
using var cmd = conn.Command("SELECT COUNT(*) FROM channels");
Statistics.TotalChannels = cmd.ExecuteScalar() as long? ?? 0;
Statistics.TotalChannels = conn.SelectScalar("SELECT COUNT(*) FROM channels") as long? ?? 0;
}
private void UpdateUserStatistics() {
using var cmd = conn.Command("SELECT COUNT(*) FROM users");
Statistics.TotalUsers = cmd.ExecuteScalar() as long? ?? 0;
Statistics.TotalUsers = conn.SelectScalar("SELECT COUNT(*) FROM users") as long? ?? 0;
}
private void UpdateMessageStatistics() {
using var cmd = conn.Command("SELECT COUNT(*) FROM messages");
Statistics.TotalMessages = cmd.ExecuteScalar() as long? ?? 0L;
Statistics.TotalMessages = conn.SelectScalar("SELECT COUNT(*) FROM messages") as long? ?? 0L;
}
}
}

View File

@ -10,31 +10,54 @@ namespace DHT.Server.Database.Sqlite {
return cmd;
}
public static SqliteCommand Insert(this SqliteConnection conn, string tableName, string[] columns) {
string columnNames = string.Join(',', columns);
string columnParams = string.Join(',', columns.Select(static c => ':' + c));
return conn.Command("INSERT INTO " + tableName + " (" + columnNames + ")" +
"VALUES (" + columnParams + ")");
public static object? SelectScalar(this SqliteConnection conn, string sql) {
using var cmd = conn.Command(sql);
return cmd.ExecuteScalar();
}
public static SqliteCommand Upsert(this SqliteConnection conn, string tableName, string[] columns) {
string columnNames = string.Join(',', columns);
string columnParams = string.Join(',', columns.Select(static c => ':' + c));
string columnUpdates = string.Join(',', columns.Skip(1).Select(static c => c + " = excluded." + c));
public static SqliteCommand Insert(this SqliteConnection conn, string tableName, (string Name, SqliteType Type)[] columns) {
string columnNames = string.Join(',', columns.Select(static c => c.Name));
string columnParams = string.Join(',', columns.Select(static c => ':' + c.Name));
return conn.Command("INSERT INTO " + tableName + " (" + columnNames + ")" +
"VALUES (" + columnParams + ")" +
"ON CONFLICT (" + columns[0] + ")" +
"DO UPDATE SET " + columnUpdates);
var cmd = conn.Command("INSERT INTO " + tableName + " (" + columnNames + ")" +
"VALUES (" + columnParams + ")");
CreateParameters(cmd, columns);
return cmd;
}
public static void AddAndSet(this SqliteParameterCollection parameters, string key, object? value) {
parameters.AddWithValue(key, value ?? DBNull.Value);
public static SqliteCommand Upsert(this SqliteConnection conn, string tableName, (string Name, SqliteType Type)[] columns) {
string columnNames = string.Join(',', columns.Select(static c => c.Name));
string columnParams = string.Join(',', columns.Select(static c => ':' + c.Name));
string columnUpdates = string.Join(',', columns.Skip(1).Select(static c => c.Name + " = excluded." + c.Name));
var cmd = conn.Command("INSERT INTO " + tableName + " (" + columnNames + ")" +
"VALUES (" + columnParams + ")" +
"ON CONFLICT (" + columns[0].Name + ")" +
"DO UPDATE SET " + columnUpdates);
CreateParameters(cmd, columns);
return cmd;
}
public static void Set(this SqliteParameterCollection parameters, string key, object? value) {
parameters[key].Value = value ?? DBNull.Value;
public static SqliteCommand Delete(this SqliteConnection conn, string tableName, (string Name, SqliteType Type) column) {
var cmd = conn.Command("DELETE FROM " + tableName + " WHERE " + column.Name + " = :" + column.Name);
CreateParameters(cmd, new [] { column });
return cmd;
}
private static void CreateParameters(SqliteCommand cmd, (string Name, SqliteType Type)[] columns) {
foreach (var (name, type) in columns) {
cmd.Parameters.Add(":" + name, type);
}
}
public static void Set(this SqliteCommand cmd, string key, object? value) {
cmd.Parameters[key].Value = value ?? DBNull.Value;
}
public static ulong GetUint64(this SqliteDataReader reader, int ordinal) {
return (ulong) reader.GetInt64(ordinal);
}
}
}

View File

@ -19,7 +19,7 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="5.0.5" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="6.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Utils\Utils.csproj" />

View File

@ -1,8 +1,18 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
namespace DHT.Utils.Logging {
public sealed class Log {
public static bool IsDebugEnabled { get; set; }
static Log() {
#if DEBUG
IsDebugEnabled = true;
#endif
}
public static Log ForType<T>() {
return ForType(typeof(T));
}
@ -11,19 +21,54 @@ namespace DHT.Utils.Logging {
return new Log(type.Name);
}
private readonly string tag;
public static Log ForType<T>(string context) {
return ForType(typeof(T), context);
}
private Log(string tag) {
public static Log ForType(Type type, string context) {
return new Log(type.Name, context);
}
private readonly string tag;
private readonly string? context;
private Log(string tag, string? context = null) {
this.tag = tag;
this.context = context;
}
private void FormatTags(StringBuilder builder) {
builder.Append('[').Append(tag).Append("] ");
if (context != null) {
builder.Append('[').Append(context).Append("] ");
}
}
private void LogLevel(ConsoleColor color, string level, string text) {
ConsoleColor prevColor = Console.ForegroundColor;
Console.ForegroundColor = color;
StringBuilder builder = new StringBuilder();
foreach (string line in text.Replace("\r", "").Split('\n')) {
string formatted = $"[{level}] [{tag}] {line}";
builder.Clear();
builder.Append('[').Append(level).Append("] ");
FormatTags(builder);
builder.Append(line);
string formatted = builder.ToString();
Console.WriteLine(formatted);
Trace.WriteLine(formatted);
}
Console.ForegroundColor = prevColor;
}
public void Debug(string message) {
if (IsDebugEnabled) {
LogLevel(ConsoleColor.Gray, "DEBUG", message);
}
}
public void Info(string message) {
@ -41,5 +86,9 @@ namespace DHT.Utils.Logging {
public void Error(Exception e) {
LogLevel(ConsoleColor.Red, "ERROR", e.ToString());
}
public Perf Start([CallerMemberName] string callerMemberName = "") {
return Perf.Start(this, callerMemberName);
}
}
}

29
app/Utils/Logging/Perf.cs Normal file
View File

@ -0,0 +1,29 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace DHT.Utils.Logging {
public sealed class Perf {
internal static Perf Start(Log log, [CallerMemberName] string callerMemberName = "") {
return new Perf(log, callerMemberName);
}
private readonly Log log;
private readonly string method;
private readonly Stopwatch stopwatch;
private Perf(Log log, string method) {
this.log = log;
this.method = method;
this.stopwatch = new Stopwatch();
this.stopwatch.Start();
}
public void End() {
stopwatch.Stop();
if (Log.IsDebugEnabled) {
log.Debug($"Finished '{method}' in {stopwatch.ElapsedMilliseconds} ms.");
}
}
}
}