mirror of
https://github.com/chylex/Discord-History-Tracker.git
synced 2025-05-31 15:49:22 +03:00
Optimize viewer JSON export using source generators
This commit is contained in:
parent
8aeb590bb3
commit
6e64c86d7a
3
app/Server/Database/Export/Snowflake.cs
Normal file
3
app/Server/Database/Export/Snowflake.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace DHT.Server.Database.Export;
|
||||||
|
|
||||||
|
readonly record struct Snowflake(ulong Id);
|
23
app/Server/Database/Export/SnowflakeJsonSerializer.cs
Normal file
23
app/Server/Database/Export/SnowflakeJsonSerializer.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace DHT.Server.Database.Export;
|
||||||
|
|
||||||
|
sealed class SnowflakeJsonSerializer : JsonConverter<Snowflake> {
|
||||||
|
public override Snowflake Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||||
|
return new Snowflake(ulong.Parse(reader.GetString()!));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, Snowflake value, JsonSerializerOptions options) {
|
||||||
|
writer.WriteStringValue(value.Id.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Snowflake ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||||
|
return new Snowflake(ulong.Parse(reader.GetString()!));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteAsPropertyName(Utf8JsonWriter writer, Snowflake value, JsonSerializerOptions options) {
|
||||||
|
writer.WritePropertyName(value.Id.ToString());
|
||||||
|
}
|
||||||
|
}
|
93
app/Server/Database/Export/ViewerJson.cs
Normal file
93
app/Server/Database/Export/ViewerJson.cs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace DHT.Server.Database.Export;
|
||||||
|
|
||||||
|
sealed class ViewerJson {
|
||||||
|
public required JsonMeta Meta { get; init; }
|
||||||
|
public required Dictionary<Snowflake, Dictionary<Snowflake, JsonMessage>> Data { get; init; }
|
||||||
|
|
||||||
|
public sealed class JsonMeta {
|
||||||
|
public required Dictionary<Snowflake, JsonUser> Users { get; init; }
|
||||||
|
public required List<Snowflake> Userindex { get; init; }
|
||||||
|
public required List<JsonServer> Servers { get; init; }
|
||||||
|
public required Dictionary<Snowflake, JsonChannel> Channels { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class JsonUser {
|
||||||
|
public required string Name { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? Avatar { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? Tag { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class JsonServer {
|
||||||
|
public required string Name { get; init; }
|
||||||
|
public required string Type { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class JsonChannel {
|
||||||
|
public required int Server { get; init; }
|
||||||
|
public required string Name { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? Parent { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public int? Position { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? Topic { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public bool? Nsfw { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class JsonMessage {
|
||||||
|
public required int U { get; init; }
|
||||||
|
public required long T { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? M { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public long? Te { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? R { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public JsonMessageAttachment[]? A { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string[]? E { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public JsonMessageReaction[]? Re { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class JsonMessageAttachment {
|
||||||
|
public required string Url { get; init; }
|
||||||
|
public required string Name { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public int? Width { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public int? Height { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class JsonMessageReaction {
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? Id { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? N { get; init; }
|
||||||
|
|
||||||
|
public required bool A { get; init; }
|
||||||
|
public required int C { get; init; }
|
||||||
|
}
|
||||||
|
}
|
11
app/Server/Database/Export/ViewerJsonContext.cs
Normal file
11
app/Server/Database/Export/ViewerJsonContext.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace DHT.Server.Database.Export;
|
||||||
|
|
||||||
|
[JsonSourceGenerationOptions(
|
||||||
|
Converters = new [] { typeof(SnowflakeJsonSerializer) },
|
||||||
|
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
|
||||||
|
GenerationMode = JsonSourceGenerationMode.Default
|
||||||
|
)]
|
||||||
|
[JsonSerializable(typeof(ViewerJson))]
|
||||||
|
sealed partial class ViewerJsonContext : JsonSerializerContext {}
|
@ -42,26 +42,28 @@ public static class ViewerJsonExport {
|
|||||||
|
|
||||||
perf.Step("Collect database data");
|
perf.Step("Collect database data");
|
||||||
|
|
||||||
var value = new {
|
var value = new ViewerJson {
|
||||||
meta = new { users, userindex, servers, channels },
|
Meta = new ViewerJson.JsonMeta {
|
||||||
data = GenerateMessageList(includedMessages, userIndices, strategy),
|
Users = users,
|
||||||
|
Userindex = userindex,
|
||||||
|
Servers = servers,
|
||||||
|
Channels = channels
|
||||||
|
},
|
||||||
|
Data = GenerateMessageList(includedMessages, userIndices, strategy)
|
||||||
};
|
};
|
||||||
|
|
||||||
perf.Step("Generate value object");
|
perf.Step("Generate value object");
|
||||||
|
|
||||||
var opts = new JsonSerializerOptions();
|
await JsonSerializer.SerializeAsync(stream, value, ViewerJsonContext.Default.ViewerJson);
|
||||||
opts.Converters.Add(new ViewerJsonSnowflakeSerializer());
|
|
||||||
|
|
||||||
await JsonSerializer.SerializeAsync(stream, value, opts);
|
|
||||||
|
|
||||||
perf.Step("Serialize to JSON");
|
perf.Step("Serialize to JSON");
|
||||||
perf.End();
|
perf.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<string, object> GenerateUserList(IDatabaseFile db, HashSet<ulong> userIds, out List<string> userindex, out Dictionary<ulong, object> userIndices) {
|
private static Dictionary<Snowflake, ViewerJson.JsonUser> GenerateUserList(IDatabaseFile db, HashSet<ulong> userIds, out List<Snowflake> userindex, out Dictionary<ulong, int> userIndices) {
|
||||||
var users = new Dictionary<string, object>();
|
var users = new Dictionary<Snowflake, ViewerJson.JsonUser>();
|
||||||
userindex = new List<string>();
|
userindex = new List<Snowflake>();
|
||||||
userIndices = new Dictionary<ulong, object>();
|
userIndices = new Dictionary<ulong, int>();
|
||||||
|
|
||||||
foreach (var user in db.GetAllUsers()) {
|
foreach (var user in db.GetAllUsers()) {
|
||||||
var id = user.Id;
|
var id = user.Id;
|
||||||
@ -69,30 +71,23 @@ public static class ViewerJsonExport {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var obj = new Dictionary<string, object> {
|
var idSnowflake = new Snowflake(id);
|
||||||
["name"] = user.Name
|
|
||||||
};
|
|
||||||
|
|
||||||
if (user.AvatarUrl != null) {
|
|
||||||
obj["avatar"] = user.AvatarUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.Discriminator != null) {
|
|
||||||
obj["tag"] = user.Discriminator;
|
|
||||||
}
|
|
||||||
|
|
||||||
var idStr = id.ToString();
|
|
||||||
userIndices[id] = users.Count;
|
userIndices[id] = users.Count;
|
||||||
userindex.Add(idStr);
|
userindex.Add(idSnowflake);
|
||||||
users[idStr] = obj;
|
|
||||||
|
users[idSnowflake] = new ViewerJson.JsonUser {
|
||||||
|
Name = user.Name,
|
||||||
|
Avatar = user.AvatarUrl,
|
||||||
|
Tag = user.Discriminator
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<object> GenerateServerList(IDatabaseFile db, HashSet<ulong> serverIds, out Dictionary<ulong, object> serverIndices) {
|
private static List<ViewerJson.JsonServer> GenerateServerList(IDatabaseFile db, HashSet<ulong> serverIds, out Dictionary<ulong, int> serverIndices) {
|
||||||
var servers = new List<object>();
|
var servers = new List<ViewerJson.JsonServer>();
|
||||||
serverIndices = new Dictionary<ulong, object>();
|
serverIndices = new Dictionary<ulong, int>();
|
||||||
|
|
||||||
foreach (var server in db.GetAllServers()) {
|
foreach (var server in db.GetAllServers()) {
|
||||||
var id = server.Id;
|
var id = server.Id;
|
||||||
@ -101,113 +96,78 @@ public static class ViewerJsonExport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
serverIndices[id] = servers.Count;
|
serverIndices[id] = servers.Count;
|
||||||
servers.Add(new Dictionary<string, object> {
|
|
||||||
["name"] = server.Name,
|
servers.Add(new ViewerJson.JsonServer {
|
||||||
["type"] = ServerTypes.ToJsonViewerString(server.Type),
|
Name = server.Name,
|
||||||
|
Type = ServerTypes.ToJsonViewerString(server.Type)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return servers;
|
return servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<string, object> GenerateChannelList(List<Channel> includedChannels, Dictionary<ulong, object> serverIndices) {
|
private static Dictionary<Snowflake, ViewerJson.JsonChannel> GenerateChannelList(List<Channel> includedChannels, Dictionary<ulong, int> serverIndices) {
|
||||||
var channels = new Dictionary<string, object>();
|
var channels = new Dictionary<Snowflake, ViewerJson.JsonChannel>();
|
||||||
|
|
||||||
foreach (var channel in includedChannels) {
|
foreach (var channel in includedChannels) {
|
||||||
var obj = new Dictionary<string, object> {
|
var channelIdSnowflake = new Snowflake(channel.Id);
|
||||||
["server"] = serverIndices[channel.Server],
|
|
||||||
["name"] = channel.Name,
|
channels[channelIdSnowflake] = new ViewerJson.JsonChannel {
|
||||||
|
Server = serverIndices[channel.Server],
|
||||||
|
Name = channel.Name,
|
||||||
|
Parent = channel.ParentId?.ToString(),
|
||||||
|
Position = channel.Position,
|
||||||
|
Topic = channel.Topic,
|
||||||
|
Nsfw = channel.Nsfw
|
||||||
};
|
};
|
||||||
|
|
||||||
if (channel.ParentId != null) {
|
|
||||||
obj["parent"] = channel.ParentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (channel.Position != null) {
|
|
||||||
obj["position"] = channel.Position;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (channel.Topic != null) {
|
|
||||||
obj["topic"] = channel.Topic;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (channel.Nsfw != null) {
|
|
||||||
obj["nsfw"] = channel.Nsfw;
|
|
||||||
}
|
|
||||||
|
|
||||||
channels[channel.Id.ToString()] = obj;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return channels;
|
return channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<string, Dictionary<string, object>> GenerateMessageList( List<Message> includedMessages, Dictionary<ulong, object> userIndices, IViewerExportStrategy strategy) {
|
private static Dictionary<Snowflake, Dictionary<Snowflake, ViewerJson.JsonMessage>> GenerateMessageList(List<Message> includedMessages, Dictionary<ulong, int> userIndices, IViewerExportStrategy strategy) {
|
||||||
var data = new Dictionary<string, Dictionary<string, object>>();
|
var data = new Dictionary<Snowflake, Dictionary<Snowflake, ViewerJson.JsonMessage>>();
|
||||||
|
|
||||||
foreach (var grouping in includedMessages.GroupBy(static message => message.Channel)) {
|
foreach (var grouping in includedMessages.GroupBy(static message => message.Channel)) {
|
||||||
var channel = grouping.Key.ToString();
|
var channelIdSnowflake = new Snowflake(grouping.Key);
|
||||||
var channelData = new Dictionary<string, object>();
|
var channelData = new Dictionary<Snowflake, ViewerJson.JsonMessage>();
|
||||||
|
|
||||||
foreach (var message in grouping) {
|
foreach (var message in grouping) {
|
||||||
var obj = new Dictionary<string, object> {
|
var messageIdSnowflake = new Snowflake(message.Id);
|
||||||
["u"] = userIndices[message.Sender],
|
|
||||||
["t"] = message.Timestamp,
|
channelData[messageIdSnowflake] = new ViewerJson.JsonMessage {
|
||||||
};
|
U = userIndices[message.Sender],
|
||||||
|
T = message.Timestamp,
|
||||||
if (!string.IsNullOrEmpty(message.Text)) {
|
M = string.IsNullOrEmpty(message.Text) ? null : message.Text,
|
||||||
obj["m"] = message.Text;
|
Te = message.EditTimestamp,
|
||||||
}
|
R = message.RepliedToId?.ToString(),
|
||||||
|
|
||||||
if (message.EditTimestamp != null) {
|
A = message.Attachments.IsEmpty ? null : message.Attachments.Select(attachment => {
|
||||||
obj["te"] = message.EditTimestamp;
|
var a = new ViewerJson.JsonMessageAttachment {
|
||||||
}
|
Url = strategy.GetAttachmentUrl(attachment),
|
||||||
|
Name = Uri.TryCreate(attachment.NormalizedUrl, UriKind.Absolute, out var uri) ? Path.GetFileName(uri.LocalPath) : attachment.NormalizedUrl
|
||||||
if (message.RepliedToId != null) {
|
|
||||||
obj["r"] = message.RepliedToId.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!message.Attachments.IsEmpty) {
|
|
||||||
obj["a"] = message.Attachments.Select(attachment => {
|
|
||||||
var a = new Dictionary<string, object> {
|
|
||||||
{ "url", strategy.GetAttachmentUrl(attachment) },
|
|
||||||
{ "name", Uri.TryCreate(attachment.NormalizedUrl, UriKind.Absolute, out var uri) ? Path.GetFileName(uri.LocalPath) : attachment.NormalizedUrl },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (attachment is { Width: not null, Height: not null }) {
|
if (attachment is { Width: not null, Height: not null }) {
|
||||||
a["width"] = attachment.Width;
|
a.Width = attachment.Width;
|
||||||
a["height"] = attachment.Height;
|
a.Height = attachment.Height;
|
||||||
}
|
}
|
||||||
|
|
||||||
return a;
|
return a;
|
||||||
}).ToArray();
|
}).ToArray(),
|
||||||
}
|
|
||||||
|
E = message.Embeds.IsEmpty ? null : message.Embeds.Select(static embed => embed.Json).ToArray(),
|
||||||
if (!message.Embeds.IsEmpty) {
|
|
||||||
obj["e"] = message.Embeds.Select(static embed => embed.Json).ToArray();
|
Re = message.Reactions.IsEmpty ? null : message.Reactions.Select(static reaction => new ViewerJson.JsonMessageReaction {
|
||||||
}
|
Id = reaction.EmojiId?.ToString(),
|
||||||
|
N = reaction.EmojiName,
|
||||||
if (!message.Reactions.IsEmpty) {
|
A = reaction.EmojiFlags.HasFlag(EmojiFlags.Animated),
|
||||||
obj["re"] = message.Reactions.Select(static reaction => {
|
C = reaction.Count
|
||||||
var r = new Dictionary<string, object>();
|
}).ToArray()
|
||||||
|
};
|
||||||
if (reaction.EmojiId != null) {
|
|
||||||
r["id"] = reaction.EmojiId.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reaction.EmojiName != null) {
|
|
||||||
r["n"] = reaction.EmojiName;
|
|
||||||
}
|
|
||||||
|
|
||||||
r["a"] = reaction.EmojiFlags.HasFlag(EmojiFlags.Animated);
|
|
||||||
r["c"] = reaction.Count;
|
|
||||||
return r;
|
|
||||||
}).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
channelData[message.Id.ToString()] = obj;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data[channel] = channelData;
|
data[channelIdSnowflake] = channelData;
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace DHT.Server.Database.Export;
|
|
||||||
|
|
||||||
sealed class ViewerJsonSnowflakeSerializer : JsonConverter<ulong> {
|
|
||||||
public override ulong Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
|
||||||
return ulong.Parse(reader.GetString()!);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, ulong value, JsonSerializerOptions options) {
|
|
||||||
writer.WriteStringValue(value.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user