1
0
mirror of https://github.com/chylex/Discord-History-Tracker.git synced 2025-04-19 13:15:44 +02:00

Compare commits

..

No commits in common. "b2276600c74845ec699379bf4cb7ce7933c410b3" and "38f79dee7d9c12e0dbab9159a05c9f60f9098828" have entirely different histories.

8 changed files with 70 additions and 99 deletions

View File

@ -3,12 +3,12 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:main="clr-namespace:DHT.Desktop.Main" xmlns:main="clr-namespace:DHT.Desktop.Main"
mc:Ignorable="d" d:DesignWidth="510" d:DesignHeight="375" mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="360"
x:Class="DHT.Desktop.Main.AboutWindow" x:Class="DHT.Desktop.Main.AboutWindow"
x:DataType="main:AboutWindowModel" x:DataType="main:AboutWindowModel"
Title="About Discord History Tracker" Title="About Discord History Tracker"
Icon="avares://DiscordHistoryTracker/Resources/icon.ico" Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
Width="510" Height="375" CanResize="False" Width="480" Height="360" CanResize="False"
WindowStartupLocation="CenterOwner"> WindowStartupLocation="CenterOwner">
<Design.DataContext> <Design.DataContext>
@ -16,6 +16,10 @@
</Design.DataContext> </Design.DataContext>
<Window.Styles> <Window.Styles>
<Style Selector="StackPanel">
<Setter Property="Orientation" Value="Horizontal" />
<Setter Property="Spacing" Value="5" />
</Style>
<Style Selector="TextBlock"> <Style Selector="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" /> <Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" />
@ -29,45 +33,44 @@
<StackPanel Orientation="Vertical" Margin="20" Spacing="12"> <StackPanel Orientation="Vertical" Margin="20" Spacing="12">
<StackPanel Orientation="Vertical" Spacing="3"> <TextBlock VerticalAlignment="Center">
<TextBlock TextWrapping="Wrap">Discord History Tracker was created by chylex.</TextBlock> Discord History Tracker was created by chylex and released under the MIT license.
<TextBlock>It is available under the MIT license.</TextBlock> </TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="8"> <StackPanel>
<Button Command="{Binding ShowOfficialWebsite}">Official Website</Button> <Button Command="{Binding ShowOfficialWebsite}">Official Website</Button>
<Button Command="{Binding ShowIssueTracker}">Issue Tracker</Button> <Button Command="{Binding ShowIssueTracker}">Issue Tracker</Button>
<Button Command="{Binding ShowSourceCode}">Source Code</Button> <Button Command="{Binding ShowSourceCode}">Source Code</Button>
</StackPanel> </StackPanel>
<Grid RowDefinitions="Auto,5,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="*,115,95" Margin="0 10 0 0"> <Grid RowDefinitions="Auto,5,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="175,125,*" Margin="0 10 0 0">
<TextBlock Grid.Row="0" Grid.Column="0" FontWeight="Bold">Third-Party Software</TextBlock> <TextBlock Grid.Row="0" Grid.Column="0" FontWeight="Bold">Third-Party Software</TextBlock>
<TextBlock Grid.Row="0" Grid.Column="1" FontWeight="Bold">License</TextBlock> <TextBlock Grid.Row="0" Grid.Column="1" FontWeight="Bold">License</TextBlock>
<TextBlock Grid.Row="0" Grid.Column="2" FontWeight="Bold">Link</TextBlock> <TextBlock Grid.Row="0" Grid.Column="2" FontWeight="Bold">Link</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="0">.NET</TextBlock> <TextBlock Grid.Row="2" Grid.Column="0">.NET 8</TextBlock>
<TextBlock Grid.Row="2" Grid.Column="1">MIT</TextBlock> <TextBlock Grid.Row="2" Grid.Column="1">MIT</TextBlock>
<Button Grid.Row="2" Grid.Column="2" Command="{Binding ShowLibraryNetCore}">GitHub</Button> <Button Grid.Row="2" Grid.Column="2" Command="{Binding ShowLibraryNetCore}">GitHub</Button>
<TextBlock Grid.Row="3" Grid.Column="0">Avalonia</TextBlock> <TextBlock Grid.Row="3" Grid.Column="0">Avalonia</TextBlock>
<TextBlock Grid.Row="3" Grid.Column="1">MIT</TextBlock> <TextBlock Grid.Row="3" Grid.Column="1">MIT</TextBlock>
<Button Grid.Row="3" Grid.Column="2" Command="{Binding ShowLibraryAvalonia}">GitHub</Button> <Button Grid.Row="3" Grid.Column="2" Command="{Binding ShowLibraryAvalonia}">NuGet</Button>
<TextBlock Grid.Row="4" Grid.Column="0">Rx.NET</TextBlock> <TextBlock Grid.Row="4" Grid.Column="0">MVVM Toolkit</TextBlock>
<TextBlock Grid.Row="4" Grid.Column="1">MIT</TextBlock> <TextBlock Grid.Row="4" Grid.Column="1">MIT</TextBlock>
<Button Grid.Row="4" Grid.Column="2" Command="{Binding ShowLibraryRxNet}">GitHub</Button> <Button Grid.Row="4" Grid.Column="2" Command="{Binding ShowLibraryCommunityToolkit}">GitHub</Button>
<TextBlock Grid.Row="5" Grid.Column="0">SQLite</TextBlock> <TextBlock Grid.Row="5" Grid.Column="0">SQLite</TextBlock>
<TextBlock Grid.Row="5" Grid.Column="1">Public Domain</TextBlock> <TextBlock Grid.Row="5" Grid.Column="1">Public Domain</TextBlock>
<Button Grid.Row="5" Grid.Column="2" Command="{Binding ShowLibrarySqlite}">Website</Button> <Button Grid.Row="5" Grid.Column="2" Command="{Binding ShowLibrarySqlite}">Official Website</Button>
<TextBlock Grid.Row="6" Grid.Column="0">Microsoft.Data.Sqlite</TextBlock> <TextBlock Grid.Row="6" Grid.Column="0">Microsoft.Data.Sqlite</TextBlock>
<TextBlock Grid.Row="6" Grid.Column="1">Apache-2.0</TextBlock> <TextBlock Grid.Row="6" Grid.Column="1">Apache-2.0</TextBlock>
<Button Grid.Row="6" Grid.Column="2" Command="{Binding ShowLibrarySqliteAdoNet}">NuGet</Button> <Button Grid.Row="6" Grid.Column="2" Command="{Binding ShowLibrarySqliteAdoNet}">NuGet</Button>
<TextBlock Grid.Row="7" Grid.Column="0">PropertyChanged.SourceGenerator</TextBlock> <TextBlock Grid.Row="7" Grid.Column="0">Rx.NET</TextBlock>
<TextBlock Grid.Row="7" Grid.Column="1">MIT</TextBlock> <TextBlock Grid.Row="7" Grid.Column="1">MIT</TextBlock>
<Button Grid.Row="7" Grid.Column="2" Command="{Binding ShowLibraryPropertyChangedSourceGenerator}">GitHub</Button> <Button Grid.Row="7" Grid.Column="2" Command="{Binding ShowLibraryRxNet}">GitHub</Button>
</Grid> </Grid>
</StackPanel> </StackPanel>

View File

@ -20,11 +20,11 @@ sealed class AboutWindowModel {
} }
public void ShowLibraryAvalonia() { public void ShowLibraryAvalonia() {
SystemUtils.OpenUrl("https://github.com/AvaloniaUI/Avalonia"); SystemUtils.OpenUrl("https://www.nuget.org/packages/Avalonia");
} }
public void ShowLibraryPropertyChangedSourceGenerator() { public void ShowLibraryCommunityToolkit() {
SystemUtils.OpenUrl("https://github.com/canton7/PropertyChanged.SourceGenerator"); SystemUtils.OpenUrl("https://github.com/CommunityToolkit/dotnet");
} }
public void ShowLibrarySqlite() { public void ShowLibrarySqlite() {

View File

@ -35,7 +35,7 @@
<Button Command="{Binding OnClickToggleDownload}" Content="{Binding ToggleDownloadButtonText}" IsEnabled="{Binding IsToggleDownloadButtonEnabled}" /> <Button Command="{Binding OnClickToggleDownload}" Content="{Binding ToggleDownloadButtonText}" IsEnabled="{Binding IsToggleDownloadButtonEnabled}" />
<Button Command="{Binding OnClickRetryFailed}" IsEnabled="{Binding IsRetryFailedOnDownloadsButtonEnabled}">Retry Failed</Button> <Button Command="{Binding OnClickRetryFailed}" IsEnabled="{Binding IsRetryFailedOnDownloadsButtonEnabled}">Retry Failed</Button>
<Button Command="{Binding OnClickDeleteOrphaned}">Delete Orphaned</Button> <Button Command="{Binding OnClickDeleteOrphaned}">Delete Orphaned</Button>
<Button Command="{Binding OnClickExportAll}" IsEnabled="{Binding HasSuccessfulDownloads}">Export All...</Button> <Button Command="{Binding OnClickExportAll}" IsEnabled="{Binding HasSuccessfulDownloads}">Export All</Button>
</WrapPanel> </WrapPanel>
<StackPanel Orientation="Vertical" Spacing="20" Margin="0 10 0 0"> <StackPanel Orientation="Vertical" Spacing="20" Margin="0 10 0 0">
<controls:DownloadItemFilterPanel DataContext="{Binding FilterModel}" IsEnabled="{Binding !$parent[UserControl].((pages:DownloadsPageModel)DataContext).IsDownloading}" /> <controls:DownloadItemFilterPanel DataContext="{Binding FilterModel}" IsEnabled="{Binding !$parent[UserControl].((pages:DownloadsPageModel)DataContext).IsDownloading}" />

View File

@ -21,13 +21,12 @@
<PropertyGroup> <PropertyGroup>
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings> <SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
<PublishTrimmed>true</PublishTrimmed> <PublishTrimmed>true</PublishTrimmed>
<TrimMode>full</TrimMode> <TrimMode>partial</TrimMode>
<EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization> <EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
<EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding> <EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>
<EventSourceSupport>false</EventSourceSupport> <EventSourceSupport>false</EventSourceSupport>
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport> <HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault> <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
<XmlResolverIsNetworkingEnabledByDefault>false</XmlResolverIsNetworkingEnabledByDefault>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View File

@ -1,20 +1,16 @@
using System; using System;
using System.Buffers.Text;
using System.Diagnostics;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace DHT.Server.Database.Export; namespace DHT.Server.Database.Export;
sealed class SnowflakeJsonSerializer : JsonConverter<Snowflake> { sealed class SnowflakeJsonSerializer : JsonConverter<Snowflake> {
private const int MaxUlongStringLength = 20;
public override Snowflake Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { public override Snowflake Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
return new Snowflake(ulong.Parse(reader.GetString()!)); return new Snowflake(ulong.Parse(reader.GetString()!));
} }
public override void Write(Utf8JsonWriter writer, Snowflake value, JsonSerializerOptions options) { public override void Write(Utf8JsonWriter writer, Snowflake value, JsonSerializerOptions options) {
writer.WriteStringValue(Format(value, stackalloc byte[MaxUlongStringLength])); writer.WriteStringValue(value.Id.ToString());
} }
public override Snowflake ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { public override Snowflake ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
@ -22,14 +18,6 @@ sealed class SnowflakeJsonSerializer : JsonConverter<Snowflake> {
} }
public override void WriteAsPropertyName(Utf8JsonWriter writer, Snowflake value, JsonSerializerOptions options) { public override void WriteAsPropertyName(Utf8JsonWriter writer, Snowflake value, JsonSerializerOptions options) {
writer.WritePropertyName(Format(value, stackalloc byte[MaxUlongStringLength])); writer.WritePropertyName(value.Id.ToString());
}
private static ReadOnlySpan<byte> Format(Snowflake value, Span<byte> destination) {
if (!Utf8Formatter.TryFormat(value.Id, destination, out int bytesWritten)) {
Debug.Fail("Failed to format Snowflake value.");
}
return destination[..bytesWritten];
} }
} }

View File

@ -30,7 +30,7 @@ static class ViewerJson {
public required string Name { get; init; } public required string Name { get; init; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Snowflake? Parent { get; init; } public string? Parent { get; init; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public int? Position { get; init; } public int? Position { get; init; }
@ -55,7 +55,7 @@ static class ViewerJson {
public long? Te { get; init; } public long? Te { get; init; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Snowflake? R { get; init; } public string? R { get; init; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public JsonMessageAttachment[]? A { get; init; } public JsonMessageAttachment[]? A { get; init; }
@ -80,7 +80,7 @@ static class ViewerJson {
public sealed class JsonMessageReaction { public sealed class JsonMessageReaction {
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Snowflake? Id { get; init; } public string? Id { get; init; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? N { get; init; } public string? N { get; init; }

View File

@ -2,15 +2,13 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks; 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.Logging; using DHT.Utils.Logging;
using Channel = System.Threading.Channels.Channel;
using DiscordChannel = DHT.Server.Data.Channel;
namespace DHT.Server.Database.Export; namespace DHT.Server.Database.Export;
@ -20,12 +18,12 @@ static class ViewerJsonExport {
public static async Task GetMetadata(Stream stream, IDatabaseFile db, MessageFilter? filter = null, CancellationToken cancellationToken = default) { public static async Task GetMetadata(Stream stream, IDatabaseFile db, MessageFilter? filter = null, CancellationToken cancellationToken = default) {
Perf perf = Log.Start(); Perf perf = Log.Start();
var includedChannels = new List<DiscordChannel>(); var includedChannels = new List<Channel>();
var includedServerIds = new HashSet<ulong>(); var includedServerIds = new HashSet<ulong>();
HashSet<ulong>? channelIdFilter = filter?.ChannelIds; HashSet<ulong>? channelIdFilter = filter?.ChannelIds;
await foreach (DiscordChannel channel in db.Channels.Get(cancellationToken)) { await foreach (Channel channel in db.Channels.Get(cancellationToken)) {
if (channelIdFilter == null || channelIdFilter.Contains(channel.Id)) { if (channelIdFilter == null || channelIdFilter.Contains(channel.Id)) {
includedChannels.Add(channel); includedChannels.Add(channel);
includedServerIds.Add(channel.Server); includedServerIds.Add(channel.Server);
@ -55,30 +53,11 @@ static class ViewerJsonExport {
ReadOnlyMemory<byte> newLine = "\n"u8.ToArray(); ReadOnlyMemory<byte> newLine = "\n"u8.ToArray();
Channel<Message> channel = Channel.CreateBounded<Message>(new BoundedChannelOptions(32) { await foreach (ViewerJson.JsonMessage message in GenerateMessageList(db, filter, cancellationToken)) {
SingleWriter = true, await JsonSerializer.SerializeAsync(stream, message, ViewerJsonMessageContext.Default.JsonMessage, cancellationToken);
SingleReader = true,
AllowSynchronousContinuations = true,
FullMode = BoundedChannelFullMode.Wait,
});
Task writerTask = Task.Run(async () => {
try {
await foreach (Message message in db.Messages.Get(filter, cancellationToken)) {
await channel.Writer.WriteAsync(message, cancellationToken);
}
} finally {
channel.Writer.Complete();
}
}, cancellationToken);
await foreach (Message message in channel.Reader.ReadAllAsync(cancellationToken)) {
await JsonSerializer.SerializeAsync(stream, ToJsonMessage(message), ViewerJsonMessageContext.Default.JsonMessage, cancellationToken);
await stream.WriteAsync(newLine, cancellationToken); await stream.WriteAsync(newLine, cancellationToken);
} }
await writerTask;
perf.Step("Generate and serialize messages to JSON"); perf.Step("Generate and serialize messages to JSON");
perf.End(); perf.End();
} }
@ -114,14 +93,14 @@ static class ViewerJsonExport {
return servers; return servers;
} }
private static Dictionary<Snowflake, ViewerJson.JsonChannel> GenerateChannelList(List<DiscordChannel> includedChannels) { private static Dictionary<Snowflake, ViewerJson.JsonChannel> GenerateChannelList(List<Channel> includedChannels) {
var channels = new Dictionary<Snowflake, ViewerJson.JsonChannel>(); var channels = new Dictionary<Snowflake, ViewerJson.JsonChannel>();
foreach (DiscordChannel channel in includedChannels) { foreach (Channel channel in includedChannels) {
channels[channel.Id] = new ViewerJson.JsonChannel { channels[channel.Id] = new ViewerJson.JsonChannel {
Server = channel.Server, Server = channel.Server,
Name = channel.Name, Name = channel.Name,
Parent = channel.ParentId, Parent = channel.ParentId?.ToString(),
Position = channel.Position, Position = channel.Position,
Topic = channel.Topic, Topic = channel.Topic,
Nsfw = channel.Nsfw, Nsfw = channel.Nsfw,
@ -131,15 +110,16 @@ static class ViewerJsonExport {
return channels; return channels;
} }
private static ViewerJson.JsonMessage ToJsonMessage(Message message) { private static async IAsyncEnumerable<ViewerJson.JsonMessage> GenerateMessageList(IDatabaseFile db, MessageFilter? filter, [EnumeratorCancellation] CancellationToken cancellationToken) {
return new ViewerJson.JsonMessage { await foreach (Message message in db.Messages.Get(filter, cancellationToken)) {
yield return new ViewerJson.JsonMessage {
Id = message.Id, Id = message.Id,
C = message.Channel, C = message.Channel,
U = message.Sender, U = message.Sender,
T = message.Timestamp, T = message.Timestamp,
M = string.IsNullOrEmpty(message.Text) ? null : message.Text, M = string.IsNullOrEmpty(message.Text) ? null : message.Text,
Te = message.EditTimestamp, Te = message.EditTimestamp,
R = message.RepliedToId, R = message.RepliedToId?.ToString(),
A = message.Attachments.IsEmpty ? null : message.Attachments.Select(static attachment => { A = message.Attachments.IsEmpty ? null : message.Attachments.Select(static attachment => {
var a = new ViewerJson.JsonMessageAttachment { var a = new ViewerJson.JsonMessageAttachment {
@ -158,11 +138,12 @@ static class ViewerJsonExport {
E = message.Embeds.IsEmpty ? null : message.Embeds.Select(static embed => embed.Json).ToArray(), E = message.Embeds.IsEmpty ? null : message.Embeds.Select(static embed => embed.Json).ToArray(),
Re = message.Reactions.IsEmpty ? null : message.Reactions.Select(static reaction => new ViewerJson.JsonMessageReaction { Re = message.Reactions.IsEmpty ? null : message.Reactions.Select(static reaction => new ViewerJson.JsonMessageReaction {
Id = reaction.EmojiId, Id = reaction.EmojiId?.ToString(),
N = reaction.EmojiName, N = reaction.EmojiName,
A = reaction.EmojiFlags.HasFlag(EmojiFlags.Animated), A = reaction.EmojiFlags.HasFlag(EmojiFlags.Animated),
C = reaction.Count, C = reaction.Count,
}).ToArray(), }).ToArray(),
}; };
} }
}
} }

View File

@ -8,5 +8,5 @@ using DHT.Utils;
namespace DHT.Utils; namespace DHT.Utils;
static class Version { static class Version {
public const string Tag = "46.0.0.0"; public const string Tag = "45.0.0.0";
} }