mirror of
https://github.com/chylex/Discord-History-Tracker.git
synced 2025-08-18 04:31:41 +02:00
Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
d35280a6a6
|
|||
ae8a34f938
|
|||
18f5823f2a
|
|||
37640c97b0
|
|||
3cc5c75c48
|
|||
2424a8ac8d
|
|||
e7705587c2
|
|||
bd7904b88e
|
|||
3db599e901
|
|||
69a68b463c
|
|||
b2389dd521
|
|||
610516de1f
|
|||
3a6b83e0ba
|
|||
22958536e7
|
|||
4db8c302d8
|
|||
7e7d140957
|
|||
8c68438fbb
|
|||
f625a39b4d
|
|||
7fd644449c
|
|||
e4a09515b0
|
|||
9ac9f2246f
|
|||
bbc734ba9b
|
|||
6837b05b0d
|
|||
c94808a15f
|
|||
739e87c5ab
|
|||
d463b407f4
|
|||
cd418f4871
|
|||
176a81e055 | |||
![]() |
1cf3e76644 |
@@ -19,7 +19,7 @@ Folder organization:
|
|||||||
* `lib/` contains utilities required to build the project
|
* `lib/` contains utilities required to build the project
|
||||||
* `web/` contains source code of the [official website](https://dht.chylex.com), which can be used as a template when making your own website
|
* `web/` contains source code of the [official website](https://dht.chylex.com), which can be used as a template when making your own website
|
||||||
|
|
||||||
To start editing source code for the desktop app, install the [.NET 5 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/5.0), and then open `app/DiscordHistoryTracker.sln` in [Visual Studio](https://visualstudio.microsoft.com/downloads/) or [Rider](https://www.jetbrains.com/rider/).
|
To start editing source code for the desktop app, install the [.NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0), and then open `app/DiscordHistoryTracker.sln` in [Visual Studio](https://visualstudio.microsoft.com/downloads/) or [Rider](https://www.jetbrains.com/rider/).
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
@@ -50,6 +50,6 @@ Run the `app/build.sh` script, and read the [Distribution](#distribution) sectio
|
|||||||
|
|
||||||
#### Distribution
|
#### Distribution
|
||||||
|
|
||||||
The mentioned build scripts will prepare `Release` builds ready for distribution. Once the script finishes, the `app/bin` folder will contain self-contained executables for each major operating system, and a portable version that works on all other systems but requires .NET 5 to be installed.
|
The mentioned build scripts will prepare `Release` builds ready for distribution. Once the script finishes, the `app/bin` folder will contain self-contained executables for each major operating system, and a portable version that works on all other systems but requires .NET 8 to be installed.
|
||||||
|
|
||||||
Note that when building on Windows, the generated `.zip` files for Linux and Mac will not have correct file permissions, so it will not be possible to run them by double-clicking `DiscordHistoryTracker`. I tried using Python to re-create the archives with correct file permissions, but found that Linux `zip` tools could not see them. The only working solution is building the Windows + portable version on Windows, and Linux + Mac version on Linux.
|
Note that when building on Windows, the generated `.zip` files for Linux and Mac will not have correct file permissions, so it will not be possible to run them by double-clicking `DiscordHistoryTracker`. I tried using Python to re-create the archives with correct file permissions, but found that Linux `zip` tools could not see them. The only working solution is building the Windows + portable version on Windows, and Linux + Mac version on Linux.
|
||||||
|
@@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
<Application.Styles>
|
<Application.Styles>
|
||||||
|
|
||||||
<FluentTheme Mode="Light" />
|
<FluentTheme />
|
||||||
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Default.xaml" />
|
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Simple.xaml" />
|
||||||
|
|
||||||
<Style Selector="Button, CheckBox, RadioButton, Expander /template/ ToggleButton#ExpanderHeader">
|
<Style Selector="Button, CheckBox, RadioButton, Expander /template/ ToggleButton#ExpanderHeader">
|
||||||
<Setter Property="Cursor" Value="Hand" />
|
<Setter Property="Cursor" Value="Hand" />
|
||||||
@@ -33,9 +33,6 @@
|
|||||||
<Style Selector="TextBox:focus /template/ Border#PART_BorderElement">
|
<Style Selector="TextBox:focus /template/ Border#PART_BorderElement">
|
||||||
<Setter Property="BorderThickness" Value="1" />
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="TextBox:disabled"><!-- TODO bug in Avalonia (https://github.com/AvaloniaUI/Avalonia/pull/7792) -->
|
|
||||||
<Setter Property="Foreground" Value="{DynamicResource TextControlForegroundDisabled}" />
|
|
||||||
</Style>
|
|
||||||
<Style Selector="TextBox:error DataValidationErrors">
|
<Style Selector="TextBox:error DataValidationErrors">
|
||||||
<Style.Resources>
|
<Style.Resources>
|
||||||
<ControlTemplate x:Key="InlineDataValidationContentTemplate" TargetType="DataValidationErrors">
|
<ControlTemplate x:Key="InlineDataValidationContentTemplate" TargetType="DataValidationErrors">
|
||||||
@@ -51,68 +48,9 @@
|
|||||||
<Setter Property="Template" Value="{StaticResource InlineDataValidationContentTemplate}" />
|
<Setter Property="Template" Value="{StaticResource InlineDataValidationContentTemplate}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style Selector="Expander /template/ ToggleButton#ExpanderHeader">
|
<Style Selector="Expander">
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
<Setter Property="MinHeight" Value="40" />
|
||||||
<Setter Property="Template">
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
<ControlTemplate>
|
|
||||||
<Border x:Name="ToggleButtonBackground">
|
|
||||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="35">
|
|
||||||
<Border x:Name="ExpandCollapseChevronBorder"
|
|
||||||
Grid.Column="0"
|
|
||||||
Width="35"
|
|
||||||
Height="35"
|
|
||||||
Margin="2,0"
|
|
||||||
RenderTransformOrigin="50%,50%">
|
|
||||||
<Path x:Name="ExpandCollapseChevron"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
RenderTransformOrigin="50%,50%"
|
|
||||||
Stretch="None"
|
|
||||||
Stroke="{DynamicResource ExpanderChevronForeground}"
|
|
||||||
StrokeThickness="1" />
|
|
||||||
<Border.RenderTransform>
|
|
||||||
<RotateTransform />
|
|
||||||
</Border.RenderTransform>
|
|
||||||
</Border>
|
|
||||||
<ContentPresenter x:Name="PART_ContentPresenter"
|
|
||||||
Grid.Column="1"
|
|
||||||
Margin="0"
|
|
||||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
|
||||||
VerticalContentAlignment="Center"
|
|
||||||
Background="Transparent"
|
|
||||||
BorderBrush="Transparent"
|
|
||||||
BorderThickness="0"
|
|
||||||
Content="{TemplateBinding Content}"
|
|
||||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
|
||||||
TextBlock.Foreground="{DynamicResource ExpanderForeground}" />
|
|
||||||
</Grid>
|
|
||||||
</Border>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Expander:expanded /template/ ToggleButton#ExpanderHeader /template/ Border#ExpandCollapseChevronBorder">
|
|
||||||
<Style.Animations>
|
|
||||||
<Animation FillMode="Both" Duration="0:0:0.0625">
|
|
||||||
<KeyFrame Cue="0%">
|
|
||||||
<Setter Property="RotateTransform.Angle" Value="180" />
|
|
||||||
</KeyFrame>
|
|
||||||
<KeyFrame Cue="100%">
|
|
||||||
<Setter Property="RotateTransform.Angle" Value="180" />
|
|
||||||
</KeyFrame>
|
|
||||||
</Animation>
|
|
||||||
</Style.Animations>
|
|
||||||
</Style>
|
|
||||||
<Style Selector="Expander:not(:expanded) /template/ ToggleButton#ExpanderHeader /template/ Border#ExpandCollapseChevronBorder">
|
|
||||||
<Style.Animations>
|
|
||||||
<Animation FillMode="Both" Duration="0:0:0.0625">
|
|
||||||
<KeyFrame Cue="0%">
|
|
||||||
<Setter Property="RotateTransform.Angle" Value="0" />
|
|
||||||
</KeyFrame>
|
|
||||||
<KeyFrame Cue="100%">
|
|
||||||
<Setter Property="RotateTransform.Angle" Value="0" />
|
|
||||||
</KeyFrame>
|
|
||||||
</Animation>
|
|
||||||
</Style.Animations>
|
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
@@ -171,12 +109,26 @@
|
|||||||
|
|
||||||
<Thickness x:Key="ExpanderHeaderPadding">15,0</Thickness>
|
<Thickness x:Key="ExpanderHeaderPadding">15,0</Thickness>
|
||||||
<Thickness x:Key="ExpanderContentPadding">12</Thickness>
|
<Thickness x:Key="ExpanderContentPadding">12</Thickness>
|
||||||
<SolidColorBrush x:Key="ExpanderBorderBrush" Color="#697DAB" />
|
<SolidColorBrush x:Key="ExpanderHeaderBorderBrush" Color="#697DAB" />
|
||||||
<SolidColorBrush x:Key="ExpanderBackground" Color="#697DAB" />
|
<SolidColorBrush x:Key="ExpanderHeaderBorderBrushPointerOver" Color="#697DAB" />
|
||||||
<SolidColorBrush x:Key="ExpanderForeground" Color="#FFFFFF" />
|
<SolidColorBrush x:Key="ExpanderHeaderBorderBrushPressed" Color="#697DAB" />
|
||||||
|
<SolidColorBrush x:Key="ExpanderHeaderBorderBrushDisabled" Color="#697DAB" />
|
||||||
|
<SolidColorBrush x:Key="ExpanderHeaderBackground" Color="#697DAB" />
|
||||||
|
<SolidColorBrush x:Key="ExpanderHeaderBackgroundPointerOver" Color="#536794" />
|
||||||
|
<SolidColorBrush x:Key="ExpanderHeaderBackgroundPressed" Color="#47587F" />
|
||||||
|
<SolidColorBrush x:Key="ExpanderHeaderBackgroundDisabled" Color="#697DAB" />
|
||||||
|
<SolidColorBrush x:Key="ExpanderHeaderForeground" Color="#FFFFFF" />
|
||||||
|
<SolidColorBrush x:Key="ExpanderHeaderForegroundPointerOver" Color="#FFFFFF" />
|
||||||
|
<SolidColorBrush x:Key="ExpanderHeaderForegroundPressed" Color="#FFFFFF" />
|
||||||
|
<SolidColorBrush x:Key="ExpanderHeaderForegroundDisabled" Color="#FFFFFF" />
|
||||||
|
<SolidColorBrush x:Key="ExpanderChevronBackground" Color="Transparent" />
|
||||||
|
<SolidColorBrush x:Key="ExpanderChevronBackgroundPointerOver" Color="#536794" />
|
||||||
|
<SolidColorBrush x:Key="ExpanderChevronBackgroundPressed" Color="#47587F" />
|
||||||
|
<SolidColorBrush x:Key="ExpanderChevronBackgroundDisabled" Color="Transparent" />
|
||||||
<SolidColorBrush x:Key="ExpanderChevronForeground" Color="#FFFFFF" />
|
<SolidColorBrush x:Key="ExpanderChevronForeground" Color="#FFFFFF" />
|
||||||
<SolidColorBrush x:Key="ExpanderDropDownBorderBrush" Color="#697DAB" />
|
<SolidColorBrush x:Key="ExpanderChevronForegroundPointerOver" Color="#FFFFFF" />
|
||||||
<SolidColorBrush x:Key="ExpanderDropDownBackground" Color="#FFFFFF" />
|
<SolidColorBrush x:Key="ExpanderChevronForegroundPressed" Color="#FFFFFF" />
|
||||||
|
<SolidColorBrush x:Key="ExpanderChevronForegroundDisabled" Color="#FFFFFF" />
|
||||||
|
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
|
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
|
using System;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using DHT.Desktop.Main;
|
using DHT.Desktop.Main;
|
||||||
|
|
||||||
namespace DHT.Desktop {
|
namespace DHT.Desktop;
|
||||||
|
|
||||||
sealed class App : Application {
|
sealed class App : Application {
|
||||||
public override void Initialize() {
|
public override void Initialize() {
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
@@ -11,10 +13,9 @@ namespace DHT.Desktop {
|
|||||||
|
|
||||||
public override void OnFrameworkInitializationCompleted() {
|
public override void OnFrameworkInitializationCompleted() {
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
|
||||||
desktop.MainWindow = new MainWindow(new Arguments(desktop.Args));
|
desktop.MainWindow = new MainWindow(new Arguments(desktop.Args ?? Array.Empty<string>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnFrameworkInitializationCompleted();
|
base.OnFrameworkInitializationCompleted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using DHT.Utils.Logging;
|
using DHT.Utils.Logging;
|
||||||
|
|
||||||
namespace DHT.Desktop {
|
namespace DHT.Desktop;
|
||||||
|
|
||||||
sealed class Arguments {
|
sealed class Arguments {
|
||||||
private static readonly Log Log = Log.ForType<Arguments>();
|
private static readonly Log Log = Log.ForType<Arguments>();
|
||||||
|
|
||||||
@@ -62,4 +63,3 @@ namespace DHT.Desktop {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -2,14 +2,29 @@ using System;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Avalonia.Data.Converters;
|
using Avalonia.Data.Converters;
|
||||||
|
|
||||||
namespace DHT.Desktop.Common {
|
namespace DHT.Desktop.Common;
|
||||||
|
|
||||||
sealed class BytesValueConverter : IValueConverter {
|
sealed class BytesValueConverter : IValueConverter {
|
||||||
private static readonly string[] Units = {
|
private sealed class Unit {
|
||||||
"B",
|
private readonly string label;
|
||||||
"kB",
|
private readonly string numberFormat;
|
||||||
"MB",
|
|
||||||
"GB",
|
public Unit(string label, int decimalPlaces) {
|
||||||
"TB"
|
this.label = label;
|
||||||
|
this.numberFormat = "{0:n" + decimalPlaces + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Format(double size) {
|
||||||
|
return string.Format(Program.Culture, numberFormat, size) + " " + label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly Unit[] Units = {
|
||||||
|
new ("B", decimalPlaces: 0),
|
||||||
|
new ("kB", decimalPlaces: 0),
|
||||||
|
new ("MB", decimalPlaces: 1),
|
||||||
|
new ("GB", decimalPlaces: 1),
|
||||||
|
new ("TB", decimalPlaces: 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
private const int Scale = 1000;
|
private const int Scale = 1000;
|
||||||
@@ -17,13 +32,7 @@ namespace DHT.Desktop.Common {
|
|||||||
private static string Convert(ulong size) {
|
private static string Convert(ulong size) {
|
||||||
int power = size == 0L ? 0 : (int) Math.Log(size, Scale);
|
int power = size == 0L ? 0 : (int) Math.Log(size, Scale);
|
||||||
int unit = power >= Units.Length ? Units.Length - 1 : power;
|
int unit = power >= Units.Length ? Units.Length - 1 : power;
|
||||||
if (unit == 0) {
|
return Units[unit].Format(unit == 0 ? size : size / Math.Pow(Scale, unit));
|
||||||
return string.Format(Program.Culture, "{0:n0}", size) + " " + Units[unit];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
double humanReadableSize = size / Math.Pow(Scale, unit);
|
|
||||||
return string.Format(Program.Culture, "{0:n0}", humanReadableSize) + " " + Units[unit];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
||||||
@@ -42,4 +51,3 @@ namespace DHT.Desktop.Common {
|
|||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -3,39 +3,42 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Platform.Storage;
|
||||||
|
using DHT.Desktop.Dialogs.File;
|
||||||
using DHT.Desktop.Dialogs.Message;
|
using DHT.Desktop.Dialogs.Message;
|
||||||
using DHT.Server.Database;
|
using DHT.Server.Database;
|
||||||
using DHT.Server.Database.Exceptions;
|
using DHT.Server.Database.Exceptions;
|
||||||
using DHT.Server.Database.Sqlite;
|
using DHT.Server.Database.Sqlite;
|
||||||
using DHT.Utils.Logging;
|
using DHT.Utils.Logging;
|
||||||
|
|
||||||
namespace DHT.Desktop.Common {
|
namespace DHT.Desktop.Common;
|
||||||
|
|
||||||
static class DatabaseGui {
|
static class DatabaseGui {
|
||||||
private static readonly Log Log = Log.ForType(typeof(DatabaseGui));
|
private static readonly Log Log = Log.ForType(typeof(DatabaseGui));
|
||||||
|
|
||||||
private const string DatabaseFileInitialName = "archive.dht";
|
private const string DatabaseFileInitialName = "archive.dht";
|
||||||
|
|
||||||
private static readonly List<FileDialogFilter> DatabaseFileDialogFilter = new() {
|
private static readonly IReadOnlyList<FilePickerFileType> DatabaseFileDialogFilter = new List<FilePickerFileType> {
|
||||||
new FileDialogFilter {
|
FileDialogs.CreateFilter("Discord History Tracker Database", new [] { "dht" })
|
||||||
Name = "Discord History Tracker Database",
|
|
||||||
Extensions = { "dht" }
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static OpenFileDialog NewOpenDatabaseFileDialog() {
|
public static async Task<string[]> NewOpenDatabaseFilesDialog(Window window, string? suggestedDirectory) {
|
||||||
return new OpenFileDialog {
|
return await window.StorageProvider.OpenFiles(new FilePickerOpenOptions {
|
||||||
Title = "Open Database File",
|
Title = "Open Database File",
|
||||||
InitialFileName = DatabaseFileInitialName,
|
FileTypeFilter = DatabaseFileDialogFilter,
|
||||||
Filters = DatabaseFileDialogFilter
|
SuggestedStartLocation = await FileDialogs.GetSuggestedStartLocation(window, suggestedDirectory),
|
||||||
};
|
AllowMultiple = true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SaveFileDialog NewOpenOrCreateDatabaseFileDialog() {
|
public static async Task<string?> NewOpenOrCreateDatabaseFileDialog(Window window, string? suggestedDirectory) {
|
||||||
return new SaveFileDialog {
|
return await window.StorageProvider.SaveFile(new FilePickerSaveOptions {
|
||||||
Title = "Open or Create Database File",
|
Title = "Open or Create Database File",
|
||||||
InitialFileName = DatabaseFileInitialName,
|
FileTypeChoices = DatabaseFileDialogFilter,
|
||||||
Filters = DatabaseFileDialogFilter
|
SuggestedFileName = DatabaseFileInitialName,
|
||||||
};
|
SuggestedStartLocation = await FileDialogs.GetSuggestedStartLocation(window, suggestedDirectory),
|
||||||
|
ShowOverwritePrompt = false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<IDatabaseFile?> TryOpenOrCreateDatabaseFromPath(string path, Window window, Func<Task<bool>> checkCanUpgradeDatabase) {
|
public static async Task<IDatabaseFile?> TryOpenOrCreateDatabaseFromPath(string path, Window window, Func<Task<bool>> checkCanUpgradeDatabase) {
|
||||||
@@ -63,4 +66,3 @@ namespace DHT.Desktop.Common {
|
|||||||
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?");
|
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?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -2,7 +2,8 @@ using System;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Avalonia.Data.Converters;
|
using Avalonia.Data.Converters;
|
||||||
|
|
||||||
namespace DHT.Desktop.Common {
|
namespace DHT.Desktop.Common;
|
||||||
|
|
||||||
sealed class NumberValueConverter : IValueConverter {
|
sealed class NumberValueConverter : IValueConverter {
|
||||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) {
|
||||||
return value == null ? "-" : string.Format(Program.Culture, "{0:n0}", value);
|
return value == null ? "-" : string.Format(Program.Culture, "{0:n0}", value);
|
||||||
@@ -12,4 +13,3 @@ namespace DHT.Desktop.Common {
|
|||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
namespace DHT.Desktop.Common {
|
namespace DHT.Desktop.Common;
|
||||||
|
|
||||||
static class TextFormat {
|
static class TextFormat {
|
||||||
public static string Format(this int number) {
|
public static string Format(this int number) {
|
||||||
return number.ToString("N0", Program.Culture);
|
return number.ToString("N0", Program.Culture);
|
||||||
@@ -16,4 +17,3 @@ namespace DHT.Desktop.Common {
|
|||||||
return number.Format() + "\u00A0" + (number == 1 ? singular : singular + "s");
|
return number.Format() + "\u00A0" + (number == 1 ? singular : singular + "s");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AssemblyName>DiscordHistoryTracker</AssemblyName>
|
<AssemblyName>DiscordHistoryTracker</AssemblyName>
|
||||||
<RootNamespace>DHT.Desktop</RootNamespace>
|
<RootNamespace>DHT.Desktop</RootNamespace>
|
||||||
@@ -21,10 +20,13 @@
|
|||||||
<DebugType>none</DebugType>
|
<DebugType>none</DebugType>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="0.10.14" />
|
<PackageReference Include="Avalonia" Version="11.0.5" />
|
||||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.14" />
|
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.5" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.14" />
|
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.5" />
|
||||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.14" Condition=" '$(Configuration)' == 'Debug' " />
|
<PackageReference Include="Avalonia.Desktop" Version="11.0.5" />
|
||||||
|
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.5" Condition=" '$(Configuration)' == 'Debug' " />
|
||||||
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.5" />
|
||||||
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.5" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Server\Server.csproj" />
|
<ProjectReference Include="..\Server\Server.csproj" />
|
||||||
|
@@ -34,7 +34,7 @@
|
|||||||
|
|
||||||
<StackPanel Margin="20">
|
<StackPanel Margin="20">
|
||||||
<ScrollViewer MaxHeight="400">
|
<ScrollViewer MaxHeight="400">
|
||||||
<ItemsRepeater Items="{Binding Items}">
|
<ItemsRepeater ItemsSource="{Binding Items}">
|
||||||
<ItemsRepeater.ItemTemplate>
|
<ItemsRepeater.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<CheckBox IsChecked="{Binding Checked}">
|
<CheckBox IsChecked="{Binding Checked}">
|
||||||
|
@@ -1,22 +1,14 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
using DHT.Desktop.Dialogs.Message;
|
using DHT.Desktop.Dialogs.Message;
|
||||||
|
|
||||||
namespace DHT.Desktop.Dialogs.CheckBox {
|
namespace DHT.Desktop.Dialogs.CheckBox;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class CheckBoxDialog : Window {
|
public sealed partial class CheckBoxDialog : Window {
|
||||||
public CheckBoxDialog() {
|
public CheckBoxDialog() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
#if DEBUG
|
|
||||||
this.AttachDevTools();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent() {
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClickOk(object? sender, RoutedEventArgs e) {
|
public void ClickOk(object? sender, RoutedEventArgs e) {
|
||||||
@@ -27,5 +19,3 @@ namespace DHT.Desktop.Dialogs.CheckBox {
|
|||||||
Close(DialogResult.OkCancel.Cancel);
|
Close(DialogResult.OkCancel.Cancel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
@@ -4,7 +4,8 @@ using System.ComponentModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
|
|
||||||
namespace DHT.Desktop.Dialogs.CheckBox {
|
namespace DHT.Desktop.Dialogs.CheckBox;
|
||||||
|
|
||||||
class CheckBoxDialogModel : BaseModel {
|
class CheckBoxDialogModel : BaseModel {
|
||||||
public string Title { get; init; } = "";
|
public string Title { get; init; } = "";
|
||||||
|
|
||||||
@@ -67,4 +68,3 @@ namespace DHT.Desktop.Dialogs.CheckBox {
|
|||||||
base.Items = this.Items;
|
base.Items = this.Items;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
|
|
||||||
namespace DHT.Desktop.Dialogs.CheckBox {
|
namespace DHT.Desktop.Dialogs.CheckBox;
|
||||||
|
|
||||||
class CheckBoxItem : BaseModel {
|
class CheckBoxItem : BaseModel {
|
||||||
public string Title { get; init; } = "";
|
public string Title { get; init; } = "";
|
||||||
public object? Item { get; init; } = null;
|
public object? Item { get; init; } = null;
|
||||||
@@ -21,4 +22,3 @@ namespace DHT.Desktop.Dialogs.CheckBox {
|
|||||||
base.Item = item;
|
base.Item = item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
36
app/Desktop/Dialogs/File/FileDialogs.cs
Normal file
36
app/Desktop/Dialogs/File/FileDialogs.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Platform.Storage;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Dialogs.File;
|
||||||
|
|
||||||
|
static class FileDialogs {
|
||||||
|
public static async Task<string[]> OpenFiles(this IStorageProvider storageProvider, FilePickerOpenOptions options) {
|
||||||
|
return (await storageProvider.OpenFilePickerAsync(options)).ToLocalPaths();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<string?> SaveFile(this IStorageProvider storageProvider, FilePickerSaveOptions options) {
|
||||||
|
return (await storageProvider.SaveFilePickerAsync(options))?.ToLocalPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FilePickerFileType CreateFilter(string name, string[] extensions) {
|
||||||
|
return new FilePickerFileType(name) {
|
||||||
|
Patterns = extensions.Select(static ext => "*." + ext).ToArray()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task<IStorageFolder?> GetSuggestedStartLocation(Window window, string? suggestedDirectory) {
|
||||||
|
return suggestedDirectory == null ? Task.FromResult<IStorageFolder?>(null) : window.StorageProvider.TryGetFolderFromPathAsync(suggestedDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ToLocalPath(this IStorageFile file) {
|
||||||
|
return file.TryGetLocalPath() ?? throw new NotSupportedException("Local filesystem is not supported.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] ToLocalPaths(this IReadOnlyList<IStorageFile> files) {
|
||||||
|
return files.Select(ToLocalPath).ToArray();
|
||||||
|
}
|
||||||
|
}
|
@@ -2,7 +2,8 @@ using System.Threading.Tasks;
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
|
|
||||||
namespace DHT.Desktop.Dialogs.Message {
|
namespace DHT.Desktop.Dialogs.Message;
|
||||||
|
|
||||||
static class Dialog {
|
static class Dialog {
|
||||||
public static async Task ShowOk(Window owner, string title, string message) {
|
public static async Task ShowOk(Window owner, string title, string message) {
|
||||||
if (!Dispatcher.UIThread.CheckAccess()) {
|
if (!Dispatcher.UIThread.CheckAccess()) {
|
||||||
@@ -71,4 +72,3 @@ namespace DHT.Desktop.Dialogs.Message {
|
|||||||
return result.ToYesNoCancel();
|
return result.ToYesNoCancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace DHT.Desktop.Dialogs.Message {
|
namespace DHT.Desktop.Dialogs.Message;
|
||||||
|
|
||||||
static class DialogResult {
|
static class DialogResult {
|
||||||
public enum All {
|
public enum All {
|
||||||
Ok,
|
Ok,
|
||||||
@@ -56,4 +57,3 @@ namespace DHT.Desktop.Dialogs.Message {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,21 +1,13 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace DHT.Desktop.Dialogs.Message {
|
namespace DHT.Desktop.Dialogs.Message;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class MessageDialog : Window {
|
public sealed partial class MessageDialog : Window {
|
||||||
public MessageDialog() {
|
public MessageDialog() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
#if DEBUG
|
|
||||||
this.AttachDevTools();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent() {
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClickOk(object? sender, RoutedEventArgs e) {
|
public void ClickOk(object? sender, RoutedEventArgs e) {
|
||||||
@@ -34,4 +26,3 @@ namespace DHT.Desktop.Dialogs.Message {
|
|||||||
Close(DialogResult.All.Cancel);
|
Close(DialogResult.All.Cancel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
namespace DHT.Desktop.Dialogs.Message {
|
namespace DHT.Desktop.Dialogs.Message;
|
||||||
|
|
||||||
sealed class MessageDialogModel {
|
sealed class MessageDialogModel {
|
||||||
public string Title { get; init; } = "";
|
public string Title { get; init; } = "";
|
||||||
public string Message { get; init; } = "";
|
public string Message { get; init; } = "";
|
||||||
@@ -8,4 +9,3 @@ namespace DHT.Desktop.Dialogs.Message {
|
|||||||
public bool IsNoVisible { get; init; } = false;
|
public bool IsNoVisible { get; init; } = false;
|
||||||
public bool IsCancelVisible { get; init; } = false;
|
public bool IsCancelVisible { get; init; } = false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace DHT.Desktop.Dialogs.Progress {
|
namespace DHT.Desktop.Dialogs.Progress;
|
||||||
|
|
||||||
interface IProgressCallback {
|
interface IProgressCallback {
|
||||||
Task Update(string message, int finishedItems, int totalItems);
|
Task Update(string message, int finishedItems, int totalItems);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
x:Class="DHT.Desktop.Dialogs.Progress.ProgressDialog"
|
x:Class="DHT.Desktop.Dialogs.Progress.ProgressDialog"
|
||||||
Title="{Binding Title}"
|
Title="{Binding Title}"
|
||||||
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
||||||
Opened="Loaded"
|
Opened="OnOpened"
|
||||||
Closing="OnClosing"
|
Closing="OnClosing"
|
||||||
Width="500" SizeToContent="Height" CanResize="False"
|
Width="500" SizeToContent="Height" CanResize="False"
|
||||||
WindowStartupLocation="CenterOwner">
|
WindowStartupLocation="CenterOwner">
|
||||||
|
@@ -1,40 +1,30 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace DHT.Desktop.Dialogs.Progress {
|
namespace DHT.Desktop.Dialogs.Progress;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class ProgressDialog : Window {
|
public sealed partial class ProgressDialog : Window {
|
||||||
private bool isFinished = false;
|
private bool isFinished = false;
|
||||||
|
|
||||||
public ProgressDialog() {
|
public ProgressDialog() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
#if DEBUG
|
|
||||||
this.AttachDevTools();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent() {
|
public void OnOpened(object? sender, EventArgs e) {
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnClosing(object? sender, CancelEventArgs e) {
|
|
||||||
e.Cancel = !isFinished;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Loaded(object? sender, EventArgs e) {
|
|
||||||
if (DataContext is ProgressDialogModel model) {
|
if (DataContext is ProgressDialogModel model) {
|
||||||
Task.Run(model.StartTask).ContinueWith(OnFinished, TaskScheduler.FromCurrentSynchronizationContext());
|
Task.Run(model.StartTask).ContinueWith(OnFinished, TaskScheduler.FromCurrentSynchronizationContext());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnClosing(object? sender, WindowClosingEventArgs e) {
|
||||||
|
e.Cancel = !isFinished;
|
||||||
|
}
|
||||||
|
|
||||||
private void OnFinished(Task task) {
|
private void OnFinished(Task task) {
|
||||||
isFinished = true;
|
isFinished = true;
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -4,7 +4,8 @@ using Avalonia.Threading;
|
|||||||
using DHT.Desktop.Common;
|
using DHT.Desktop.Common;
|
||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
|
|
||||||
namespace DHT.Desktop.Dialogs.Progress {
|
namespace DHT.Desktop.Dialogs.Progress;
|
||||||
|
|
||||||
sealed class ProgressDialogModel : BaseModel {
|
sealed class ProgressDialogModel : BaseModel {
|
||||||
public string Title { get; init; } = "";
|
public string Title { get; init; } = "";
|
||||||
|
|
||||||
@@ -62,4 +63,3 @@ namespace DHT.Desktop.Dialogs.Progress {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -31,7 +31,7 @@
|
|||||||
<ScrollViewer MaxHeight="400">
|
<ScrollViewer MaxHeight="400">
|
||||||
<StackPanel Spacing="10">
|
<StackPanel Spacing="10">
|
||||||
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" />
|
<TextBlock Text="{Binding Description}" TextWrapping="Wrap" />
|
||||||
<ItemsRepeater Items="{Binding Items}">
|
<ItemsRepeater ItemsSource="{Binding Items}">
|
||||||
<ItemsRepeater.ItemTemplate>
|
<ItemsRepeater.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<DockPanel Margin="0 5 25 0">
|
<DockPanel Margin="0 5 25 0">
|
||||||
|
@@ -1,22 +1,14 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
using DHT.Desktop.Dialogs.Message;
|
using DHT.Desktop.Dialogs.Message;
|
||||||
|
|
||||||
namespace DHT.Desktop.Dialogs.TextBox {
|
namespace DHT.Desktop.Dialogs.TextBox;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class TextBoxDialog : Window {
|
public sealed partial class TextBoxDialog : Window {
|
||||||
public TextBoxDialog() {
|
public TextBoxDialog() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
#if DEBUG
|
|
||||||
this.AttachDevTools();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent() {
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClickOk(object? sender, RoutedEventArgs e) {
|
public void ClickOk(object? sender, RoutedEventArgs e) {
|
||||||
@@ -27,5 +19,3 @@ namespace DHT.Desktop.Dialogs.TextBox {
|
|||||||
Close(DialogResult.OkCancel.Cancel);
|
Close(DialogResult.OkCancel.Cancel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
@@ -4,7 +4,8 @@ using System.ComponentModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
|
|
||||||
namespace DHT.Desktop.Dialogs.TextBox {
|
namespace DHT.Desktop.Dialogs.TextBox;
|
||||||
|
|
||||||
class TextBoxDialogModel : BaseModel {
|
class TextBoxDialogModel : BaseModel {
|
||||||
public string Title { get; init; } = "";
|
public string Title { get; init; } = "";
|
||||||
public string Description { get; init; } = "";
|
public string Description { get; init; } = "";
|
||||||
@@ -44,4 +45,3 @@ namespace DHT.Desktop.Dialogs.TextBox {
|
|||||||
base.Items = this.Items;
|
base.Items = this.Items;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -3,7 +3,8 @@ using System.Collections;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
|
|
||||||
namespace DHT.Desktop.Dialogs.TextBox {
|
namespace DHT.Desktop.Dialogs.TextBox;
|
||||||
|
|
||||||
class TextBoxItem : BaseModel, INotifyDataErrorInfo {
|
class TextBoxItem : BaseModel, INotifyDataErrorInfo {
|
||||||
public string Title { get; init; } = "";
|
public string Title { get; init; } = "";
|
||||||
public object? Item { get; init; } = null;
|
public object? Item { get; init; } = null;
|
||||||
@@ -39,4 +40,3 @@ namespace DHT.Desktop.Dialogs.TextBox {
|
|||||||
base.Item = item;
|
base.Item = item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -9,7 +9,8 @@ using DHT.Utils.Logging;
|
|||||||
using static System.Environment.SpecialFolder;
|
using static System.Environment.SpecialFolder;
|
||||||
using static System.Environment.SpecialFolderOption;
|
using static System.Environment.SpecialFolderOption;
|
||||||
|
|
||||||
namespace DHT.Desktop.Discord {
|
namespace DHT.Desktop.Discord;
|
||||||
|
|
||||||
static class DiscordAppSettings {
|
static class DiscordAppSettings {
|
||||||
private static readonly Log Log = Log.ForType(typeof(DiscordAppSettings));
|
private static readonly Log Log = Log.ForType(typeof(DiscordAppSettings));
|
||||||
|
|
||||||
@@ -39,7 +40,9 @@ namespace DHT.Desktop.Discord {
|
|||||||
public static async Task<bool?> AreDevToolsEnabled() {
|
public static async Task<bool?> AreDevToolsEnabled() {
|
||||||
try {
|
try {
|
||||||
return AreDevToolsEnabled(await ReadSettingsJson());
|
return AreDevToolsEnabled(await ReadSettingsJson());
|
||||||
} catch (Exception) {
|
} catch (Exception e) {
|
||||||
|
Log.Error("Cannot read settings file.");
|
||||||
|
Log.Error(e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,4 +119,3 @@ namespace DHT.Desktop.Discord {
|
|||||||
await JsonSerializer.SerializeAsync(stream, json, new JsonSerializerOptions { WriteIndented = true });
|
await JsonSerializer.SerializeAsync(stream, json, new JsonSerializerOptions { WriteIndented = true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
namespace DHT.Desktop.Discord {
|
namespace DHT.Desktop.Discord;
|
||||||
|
|
||||||
enum SettingsJsonResult {
|
enum SettingsJsonResult {
|
||||||
Success,
|
Success,
|
||||||
AlreadySet,
|
AlreadySet,
|
||||||
@@ -7,4 +8,3 @@ namespace DHT.Desktop.Discord {
|
|||||||
InvalidJson,
|
InvalidJson,
|
||||||
WriteError
|
WriteError
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -46,7 +46,7 @@
|
|||||||
<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 5</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>
|
||||||
|
|
||||||
|
@@ -1,20 +1,11 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace DHT.Desktop.Main {
|
namespace DHT.Desktop.Main;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class AboutWindow : Window {
|
public sealed partial class AboutWindow : Window {
|
||||||
public AboutWindow() {
|
public AboutWindow() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
#if DEBUG
|
|
||||||
this.AttachDevTools();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent() {
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace DHT.Desktop.Main {
|
namespace DHT.Desktop.Main;
|
||||||
|
|
||||||
sealed class AboutWindowModel {
|
sealed class AboutWindowModel {
|
||||||
public void ShowOfficialWebsite() {
|
public void ShowOfficialWebsite() {
|
||||||
OpenUrl("https://dht.chylex.com");
|
OpenUrl("https://dht.chylex.com");
|
||||||
@@ -30,4 +31,3 @@ namespace DHT.Desktop.Main {
|
|||||||
Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true });
|
Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -36,7 +36,7 @@
|
|||||||
<CheckBox IsChecked="{Binding LimitSize}">Limit Size</CheckBox>
|
<CheckBox IsChecked="{Binding LimitSize}">Limit Size</CheckBox>
|
||||||
<StackPanel Orientation="Horizontal">
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBox Text="{Binding MaximumSize}" IsEnabled="{Binding LimitSize}" HorizontalContentAlignment="Right" />
|
<TextBox Text="{Binding MaximumSize}" IsEnabled="{Binding LimitSize}" HorizontalContentAlignment="Right" />
|
||||||
<ComboBox IsEnabled="{Binding LimitSize}" Items="{Binding Units}" SelectedItem="{Binding MaximumSizeUnit}">
|
<ComboBox IsEnabled="{Binding LimitSize}" ItemsSource="{Binding Units}" SelectedItem="{Binding MaximumSizeUnit}">
|
||||||
<ComboBox.ItemTemplate>
|
<ComboBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Text="{Binding Name}" />
|
<TextBlock Text="{Binding Name}" />
|
||||||
|
@@ -1,17 +1,11 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Controls {
|
namespace DHT.Desktop.Main.Controls;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class AttachmentFilterPanel : UserControl {
|
public sealed partial class AttachmentFilterPanel : UserControl {
|
||||||
public AttachmentFilterPanel() {
|
public AttachmentFilterPanel() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent() {
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@@ -7,7 +7,8 @@ using DHT.Server.Database;
|
|||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
using DHT.Utils.Tasks;
|
using DHT.Utils.Tasks;
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Controls {
|
namespace DHT.Desktop.Main.Controls;
|
||||||
|
|
||||||
sealed class AttachmentFilterPanelModel : BaseModel, IDisposable {
|
sealed class AttachmentFilterPanelModel : BaseModel, IDisposable {
|
||||||
public sealed record Unit(string Name, uint Scale);
|
public sealed record Unit(string Name, uint Scale);
|
||||||
|
|
||||||
@@ -126,4 +127,3 @@ namespace DHT.Desktop.Main.Controls {
|
|||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,19 +1,12 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Controls {
|
namespace DHT.Desktop.Main.Controls;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class MessageFilterPanel : UserControl {
|
public sealed partial class MessageFilterPanel : UserControl {
|
||||||
private CalendarDatePicker StartDatePicker => this.FindControl<CalendarDatePicker>("StartDatePicker");
|
|
||||||
private CalendarDatePicker EndDatePicker => this.FindControl<CalendarDatePicker>("EndDatePicker");
|
|
||||||
|
|
||||||
public MessageFilterPanel() {
|
public MessageFilterPanel() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent() {
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
|
|
||||||
var culture = Program.Culture;
|
var culture = Program.Culture;
|
||||||
foreach (var picker in new CalendarDatePicker[] { StartDatePicker, EndDatePicker }) {
|
foreach (var picker in new CalendarDatePicker[] { StartDatePicker, EndDatePicker }) {
|
||||||
@@ -31,4 +24,3 @@ namespace DHT.Desktop.Main.Controls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -14,7 +14,8 @@ using DHT.Server.Database;
|
|||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
using DHT.Utils.Tasks;
|
using DHT.Utils.Tasks;
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Controls {
|
namespace DHT.Desktop.Main.Controls;
|
||||||
|
|
||||||
sealed class MessageFilterPanelModel : BaseModel, IDisposable {
|
sealed class MessageFilterPanelModel : BaseModel, IDisposable {
|
||||||
private static readonly HashSet<string> FilterProperties = new () {
|
private static readonly HashSet<string> FilterProperties = new () {
|
||||||
nameof(FilterByDate),
|
nameof(FilterByDate),
|
||||||
@@ -282,4 +283,3 @@ namespace DHT.Desktop.Main.Controls {
|
|||||||
return result == DialogResult.OkCancel.Ok ? model.SelectedItems.Select(static item => item.Item).ToHashSet() : null;
|
return result == DialogResult.OkCancel.Ok ? model.SelectedItems.Select(static item => item.Item).ToHashSet() : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,16 +1,11 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Controls {
|
namespace DHT.Desktop.Main.Controls;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class ServerConfigurationPanel : UserControl {
|
public sealed partial class ServerConfigurationPanel : UserControl {
|
||||||
public ServerConfigurationPanel() {
|
public ServerConfigurationPanel() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent() {
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,8 @@ using DHT.Server.Database;
|
|||||||
using DHT.Server.Service;
|
using DHT.Server.Service;
|
||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Controls {
|
namespace DHT.Desktop.Main.Controls;
|
||||||
|
|
||||||
sealed class ServerConfigurationPanelModel : BaseModel, IDisposable {
|
sealed class ServerConfigurationPanelModel : BaseModel, IDisposable {
|
||||||
private string inputPort;
|
private string inputPort;
|
||||||
|
|
||||||
@@ -113,4 +114,3 @@ namespace DHT.Desktop.Main.Controls {
|
|||||||
InputToken = ServerManager.Token;
|
InputToken = ServerManager.Token;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,16 +1,11 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Controls {
|
namespace DHT.Desktop.Main.Controls;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class StatusBar : UserControl {
|
public sealed partial class StatusBar : UserControl {
|
||||||
public StatusBar() {
|
public StatusBar() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent() {
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,8 @@ using System;
|
|||||||
using DHT.Server.Database;
|
using DHT.Server.Database;
|
||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Controls {
|
namespace DHT.Desktop.Main.Controls;
|
||||||
|
|
||||||
sealed class StatusBarModel : BaseModel {
|
sealed class StatusBarModel : BaseModel {
|
||||||
public DatabaseStatistics DatabaseStatistics { get; }
|
public DatabaseStatistics DatabaseStatistics { get; }
|
||||||
|
|
||||||
@@ -42,4 +43,3 @@ namespace DHT.Desktop.Main.Controls {
|
|||||||
Stopped
|
Stopped
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,31 +1,23 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
using DHT.Desktop.Main.Pages;
|
using DHT.Desktop.Main.Pages;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
namespace DHT.Desktop.Main {
|
namespace DHT.Desktop.Main;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class MainWindow : Window {
|
public sealed partial class MainWindow : Window {
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public MainWindow() {
|
public MainWindow() {
|
||||||
InitializeComponent(Arguments.Empty);
|
InitializeComponent();
|
||||||
|
DataContext = new MainWindowModel(this, Arguments.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal MainWindow(Arguments args) {
|
internal MainWindow(Arguments args) {
|
||||||
InitializeComponent(args);
|
InitializeComponent();
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeComponent(Arguments args) {
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
DataContext = new MainWindowModel(this, args);
|
DataContext = new MainWindowModel(this, args);
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
this.AttachDevTools();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnClosed(object? sender, EventArgs e) {
|
public void OnClosed(object? sender, EventArgs e) {
|
||||||
@@ -42,4 +34,3 @@ namespace DHT.Desktop.Main {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -10,7 +10,8 @@ using DHT.Desktop.Server;
|
|||||||
using DHT.Server.Database;
|
using DHT.Server.Database;
|
||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
|
|
||||||
namespace DHT.Desktop.Main {
|
namespace DHT.Desktop.Main;
|
||||||
|
|
||||||
sealed class MainWindowModel : BaseModel, IDisposable {
|
sealed class MainWindowModel : BaseModel, IDisposable {
|
||||||
private const string DefaultTitle = "Discord History Tracker";
|
private const string DefaultTitle = "Discord History Tracker";
|
||||||
|
|
||||||
@@ -113,4 +114,3 @@ namespace DHT.Desktop.Main {
|
|||||||
db = null;
|
db = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,16 +1,11 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Pages {
|
namespace DHT.Desktop.Main.Pages;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class AdvancedPage : UserControl {
|
public sealed partial class AdvancedPage : UserControl {
|
||||||
public AdvancedPage() {
|
public AdvancedPage() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent() {
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,8 @@ using DHT.Desktop.Server;
|
|||||||
using DHT.Server.Database;
|
using DHT.Server.Database;
|
||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Pages {
|
namespace DHT.Desktop.Main.Pages;
|
||||||
|
|
||||||
sealed class AdvancedPageModel : BaseModel, IDisposable {
|
sealed class AdvancedPageModel : BaseModel, IDisposable {
|
||||||
public ServerConfigurationPanelModel ServerConfigurationModel { get; }
|
public ServerConfigurationPanelModel ServerConfigurationModel { get; }
|
||||||
|
|
||||||
@@ -36,4 +37,3 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
await Dialog.ShowOk(window, "Vacuum Database", "Done.");
|
await Dialog.ShowOk(window, "Vacuum Database", "Done.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -38,7 +38,7 @@
|
|||||||
<controls:AttachmentFilterPanel DataContext="{Binding FilterModel}" IsEnabled="{Binding !DataContext.IsDownloading, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
<controls:AttachmentFilterPanel DataContext="{Binding FilterModel}" IsEnabled="{Binding !DataContext.IsDownloading, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
||||||
<StackPanel Orientation="Vertical" Spacing="12">
|
<StackPanel Orientation="Vertical" Spacing="12">
|
||||||
<Expander Header="Download Status" IsExpanded="True">
|
<Expander Header="Download Status" IsExpanded="True">
|
||||||
<DataGrid Items="{Binding StatisticsRows}" AutoGenerateColumns="False" CanUserReorderColumns="False" CanUserResizeColumns="False" CanUserSortColumns="False" IsReadOnly="True">
|
<DataGrid ItemsSource="{Binding StatisticsRows}" AutoGenerateColumns="False" CanUserReorderColumns="False" CanUserResizeColumns="False" CanUserSortColumns="False" IsReadOnly="True">
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
<DataGridTextColumn Header="State" Binding="{Binding State}" Width="*" />
|
<DataGridTextColumn Header="State" Binding="{Binding State}" Width="*" />
|
||||||
<DataGridTextColumn Header="Attachments" Binding="{Binding Items, Converter={StaticResource NumberValueConverter}}" Width="*" CellStyleClasses="right" />
|
<DataGridTextColumn Header="Attachments" Binding="{Binding Items, Converter={StaticResource NumberValueConverter}}" Width="*" CellStyleClasses="right" />
|
||||||
|
@@ -1,16 +1,11 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Pages {
|
namespace DHT.Desktop.Main.Pages;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class AttachmentsPage : UserControl {
|
public sealed partial class AttachmentsPage : UserControl {
|
||||||
public AttachmentsPage() {
|
public AttachmentsPage() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent() {
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Threading;
|
||||||
|
using Avalonia.Threading;
|
||||||
using DHT.Desktop.Common;
|
using DHT.Desktop.Common;
|
||||||
using DHT.Desktop.Main.Controls;
|
using DHT.Desktop.Main.Controls;
|
||||||
using DHT.Server.Data;
|
using DHT.Server.Data;
|
||||||
@@ -11,7 +13,8 @@ using DHT.Server.Download;
|
|||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
using DHT.Utils.Tasks;
|
using DHT.Utils.Tasks;
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Pages {
|
namespace DHT.Desktop.Main.Pages;
|
||||||
|
|
||||||
sealed class AttachmentsPageModel : BaseModel, IDisposable {
|
sealed class AttachmentsPageModel : BaseModel, IDisposable {
|
||||||
private static readonly DownloadItemFilter EnqueuedItemFilter = new() {
|
private static readonly DownloadItemFilter EnqueuedItemFilter = new() {
|
||||||
IncludeStatuses = new HashSet<DownloadStatus> {
|
IncludeStatuses = new HashSet<DownloadStatus> {
|
||||||
@@ -133,9 +136,9 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void DownloadThreadOnOnItemFinished(object? sender, DownloadItem e) {
|
private void DownloadThreadOnOnItemFinished(object? sender, DownloadItem e) {
|
||||||
++doneItemsCount;
|
Interlocked.Increment(ref doneItemsCount);
|
||||||
|
|
||||||
UpdateDownloadMessage();
|
Dispatcher.UIThread.Invoke(UpdateDownloadMessage);
|
||||||
downloadStatisticsComputer.Recompute();
|
downloadStatisticsComputer.Recompute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,4 +203,3 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,16 +1,11 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Pages {
|
namespace DHT.Desktop.Main.Pages;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class DatabasePage : UserControl {
|
public sealed partial class DatabasePage : UserControl {
|
||||||
public DatabasePage() {
|
public DatabasePage() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent() {
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -7,8 +7,10 @@ using System.Text;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Platform.Storage;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
using DHT.Desktop.Common;
|
using DHT.Desktop.Common;
|
||||||
|
using DHT.Desktop.Dialogs.File;
|
||||||
using DHT.Desktop.Dialogs.Message;
|
using DHT.Desktop.Dialogs.Message;
|
||||||
using DHT.Desktop.Dialogs.Progress;
|
using DHT.Desktop.Dialogs.Progress;
|
||||||
using DHT.Desktop.Dialogs.TextBox;
|
using DHT.Desktop.Dialogs.TextBox;
|
||||||
@@ -18,7 +20,8 @@ using DHT.Server.Database.Import;
|
|||||||
using DHT.Utils.Logging;
|
using DHT.Utils.Logging;
|
||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Pages {
|
namespace DHT.Desktop.Main.Pages;
|
||||||
|
|
||||||
sealed class DatabasePageModel : BaseModel {
|
sealed class DatabasePageModel : BaseModel {
|
||||||
private static readonly Log Log = Log.ForType<DatabasePageModel>();
|
private static readonly Log Log = Log.ForType<DatabasePageModel>();
|
||||||
|
|
||||||
@@ -68,12 +71,8 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async void MergeWithDatabase() {
|
public async void MergeWithDatabase() {
|
||||||
var fileDialog = DatabaseGui.NewOpenDatabaseFileDialog();
|
var paths = await DatabaseGui.NewOpenDatabaseFilesDialog(window, Path.GetDirectoryName(Db.Path));
|
||||||
fileDialog.Directory = Path.GetDirectoryName(Db.Path);
|
if (paths.Length == 0) {
|
||||||
fileDialog.AllowMultiple = true;
|
|
||||||
|
|
||||||
string[]? paths = await fileDialog.ShowAsync(window);
|
|
||||||
if (paths == null || paths.Length == 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,14 +117,13 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async void ImportLegacyArchive() {
|
public async void ImportLegacyArchive() {
|
||||||
var fileDialog = new OpenFileDialog {
|
var paths = await window.StorageProvider.OpenFiles(new FilePickerOpenOptions {
|
||||||
Title = "Open Legacy DHT Archive",
|
Title = "Open Legacy DHT Archive",
|
||||||
Directory = Path.GetDirectoryName(Db.Path),
|
SuggestedStartLocation = await FileDialogs.GetSuggestedStartLocation(window, Path.GetDirectoryName(Db.Path)),
|
||||||
AllowMultiple = true
|
AllowMultiple = true
|
||||||
};
|
});
|
||||||
|
|
||||||
string[]? paths = await fileDialog.ShowAsync(window);
|
if (paths.Length == 0) {
|
||||||
if (paths == null || paths.Length == 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,4 +243,3 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
return message.ToString();
|
return message.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,16 +1,11 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Pages {
|
namespace DHT.Desktop.Main.Pages;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class DebugPage : UserControl {
|
public sealed partial class DebugPage : UserControl {
|
||||||
public DebugPage() {
|
public DebugPage() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent() {
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -63,7 +63,7 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
var server = new DHT.Server.Data.Server {
|
var server = new DHT.Server.Data.Server {
|
||||||
Id = RandomId(rand),
|
Id = RandomId(rand),
|
||||||
Name = RandomName("s"),
|
Name = RandomName("s"),
|
||||||
Type = ServerType.Server
|
Type = ServerType.Server,
|
||||||
};
|
};
|
||||||
|
|
||||||
var channels = Enumerable.Range(0, channelCount).Select(i => new Channel {
|
var channels = Enumerable.Range(0, channelCount).Select(i => new Channel {
|
||||||
@@ -73,14 +73,14 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
ParentId = null,
|
ParentId = null,
|
||||||
Position = i,
|
Position = i,
|
||||||
Topic = RandomText(rand, 10),
|
Topic = RandomText(rand, 10),
|
||||||
Nsfw = rand.Next(4) == 0
|
Nsfw = rand.Next(4) == 0,
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
var users = Enumerable.Range(0, userCount).Select(_ => new User {
|
var users = Enumerable.Range(0, userCount).Select(_ => new User {
|
||||||
Id = RandomId(rand),
|
Id = RandomId(rand),
|
||||||
Name = RandomName("u"),
|
Name = RandomName("u"),
|
||||||
AvatarUrl = null,
|
AvatarUrl = null,
|
||||||
Discriminator = rand.Next(0, 9999).ToString()
|
Discriminator = rand.Next(0, 9999).ToString(),
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
db.AddServer(server);
|
db.AddServer(server);
|
||||||
@@ -97,7 +97,7 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
int hourOffset = batchIndex;
|
int hourOffset = batchIndex;
|
||||||
|
|
||||||
var messages = Enumerable.Range(0, Math.Min(messageCount, BatchSize)).Select(i => {
|
var messages = Enumerable.Range(0, Math.Min(messageCount, BatchSize)).Select(i => {
|
||||||
DateTimeOffset time = now.AddHours(hourOffset).AddMinutes((i * 60.0) / BatchSize);
|
DateTimeOffset time = now.AddHours(hourOffset).AddMinutes(i * 60.0 / BatchSize);
|
||||||
DateTimeOffset? edit = rand.Next(100) == 0 ? time.AddSeconds(rand.Next(1, 60)) : null;
|
DateTimeOffset? edit = rand.Next(100) == 0 ? time.AddSeconds(rand.Next(1, 60)) : null;
|
||||||
|
|
||||||
var timeMillis = time.ToUnixTimeMilliseconds();
|
var timeMillis = time.ToUnixTimeMilliseconds();
|
||||||
@@ -113,7 +113,7 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
RepliedToId = null,
|
RepliedToId = null,
|
||||||
Attachments = ImmutableArray<Attachment>.Empty,
|
Attachments = ImmutableArray<Attachment>.Empty,
|
||||||
Embeds = ImmutableArray<Embed>.Empty,
|
Embeds = ImmutableArray<Embed>.Empty,
|
||||||
Reactions = ImmutableArray<Reaction>.Empty
|
Reactions = ImmutableArray<Reaction>.Empty,
|
||||||
};
|
};
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
"vanilla",
|
"vanilla",
|
||||||
"watercress", "watermelon",
|
"watercress", "watermelon",
|
||||||
"yam",
|
"yam",
|
||||||
"zucchini"
|
"zucchini",
|
||||||
};
|
};
|
||||||
|
|
||||||
private static string RandomText(Random rand, int maxWords) {
|
private static string RandomText(Random rand, int maxWords) {
|
||||||
|
@@ -3,36 +3,30 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Pages {
|
namespace DHT.Desktop.Main.Pages;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class TrackingPage : UserControl {
|
public sealed partial class TrackingPage : UserControl {
|
||||||
private bool isCopyingScript;
|
private bool isCopyingScript;
|
||||||
|
|
||||||
public TrackingPage() {
|
public TrackingPage() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent() {
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void CopyTrackingScriptButton_OnClick(object? sender, RoutedEventArgs e) {
|
public async void CopyTrackingScriptButton_OnClick(object? sender, RoutedEventArgs e) {
|
||||||
if (DataContext is TrackingPageModel model) {
|
if (DataContext is TrackingPageModel model) {
|
||||||
var button = this.FindControl<Button>("CopyTrackingScript");
|
var originalText = CopyTrackingScript.Content;
|
||||||
var originalText = button.Content;
|
CopyTrackingScript.MinWidth = CopyTrackingScript.Bounds.Width;
|
||||||
button.MinWidth = button.Bounds.Width;
|
|
||||||
|
|
||||||
if (await model.OnClickCopyTrackingScript() && !isCopyingScript) {
|
if (await model.OnClickCopyTrackingScript() && !isCopyingScript) {
|
||||||
isCopyingScript = true;
|
isCopyingScript = true;
|
||||||
button.Content = "Script Copied!";
|
CopyTrackingScript.Content = "Script Copied!";
|
||||||
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(2));
|
await Task.Delay(TimeSpan.FromSeconds(2));
|
||||||
button.Content = originalText;
|
CopyTrackingScript.Content = originalText;
|
||||||
isCopyingScript = false;
|
isCopyingScript = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using DHT.Desktop.Dialogs.Message;
|
using DHT.Desktop.Dialogs.Message;
|
||||||
using DHT.Desktop.Discord;
|
using DHT.Desktop.Discord;
|
||||||
@@ -9,7 +8,8 @@ using DHT.Desktop.Server;
|
|||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
using static DHT.Desktop.Program;
|
using static DHT.Desktop.Program;
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Pages {
|
namespace DHT.Desktop.Main.Pages;
|
||||||
|
|
||||||
sealed class TrackingPageModel : BaseModel {
|
sealed class TrackingPageModel : BaseModel {
|
||||||
private bool areDevToolsEnabled;
|
private bool areDevToolsEnabled;
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
.Replace("/*[CSS-CONTROLLER]*/", await Resources.ReadTextAsync("Tracker/styles/controller.css"))
|
.Replace("/*[CSS-CONTROLLER]*/", await Resources.ReadTextAsync("Tracker/styles/controller.css"))
|
||||||
.Replace("/*[CSS-SETTINGS]*/", await Resources.ReadTextAsync("Tracker/styles/settings.css"));
|
.Replace("/*[CSS-SETTINGS]*/", await Resources.ReadTextAsync("Tracker/styles/settings.css"));
|
||||||
|
|
||||||
var clipboard = Application.Current?.Clipboard;
|
var clipboard = window.Clipboard;
|
||||||
if (clipboard == null) {
|
if (clipboard == null) {
|
||||||
await Dialog.ShowOk(window, "Copy Tracking Script", "Clipboard is not available on this system.");
|
await Dialog.ShowOk(window, "Copy Tracking Script", "Clipboard is not available on this system.");
|
||||||
return false;
|
return false;
|
||||||
@@ -114,4 +114,3 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,16 +1,11 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Pages {
|
namespace DHT.Desktop.Main.Pages;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class ViewerPage : UserControl {
|
public sealed partial class ViewerPage : UserControl {
|
||||||
public ViewerPage() {
|
public ViewerPage() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent() {
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -8,16 +7,21 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Platform.Storage;
|
||||||
using DHT.Desktop.Common;
|
using DHT.Desktop.Common;
|
||||||
|
using DHT.Desktop.Dialogs.File;
|
||||||
using DHT.Desktop.Dialogs.Message;
|
using DHT.Desktop.Dialogs.Message;
|
||||||
using DHT.Desktop.Main.Controls;
|
using DHT.Desktop.Main.Controls;
|
||||||
|
using DHT.Desktop.Server;
|
||||||
using DHT.Server.Data.Filters;
|
using DHT.Server.Data.Filters;
|
||||||
using DHT.Server.Database;
|
using DHT.Server.Database;
|
||||||
using DHT.Server.Database.Export;
|
using DHT.Server.Database.Export;
|
||||||
|
using DHT.Server.Database.Export.Strategy;
|
||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
using static DHT.Desktop.Program;
|
using static DHT.Desktop.Program;
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Pages {
|
namespace DHT.Desktop.Main.Pages;
|
||||||
|
|
||||||
sealed class ViewerPageModel : BaseModel, IDisposable {
|
sealed class ViewerPageModel : BaseModel, IDisposable {
|
||||||
public static readonly ConcurrentBag<string> TemporaryFiles = new ();
|
public static readonly ConcurrentBag<string> TemporaryFiles = new ();
|
||||||
|
|
||||||
@@ -55,7 +59,7 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
HasFilters = FilterModel.HasAnyFilters;
|
HasFilters = FilterModel.HasAnyFilters;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task WriteViewerFile(string path) {
|
private async Task WriteViewerFile(string path, IViewerExportStrategy strategy) {
|
||||||
const string ArchiveTag = "/*[ARCHIVE]*/";
|
const string ArchiveTag = "/*[ARCHIVE]*/";
|
||||||
|
|
||||||
string indexFile = await Resources.ReadTextAsync("Viewer/index.html");
|
string indexFile = await Resources.ReadTextAsync("Viewer/index.html");
|
||||||
@@ -68,7 +72,7 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
string jsonTempFile = path + ".tmp";
|
string jsonTempFile = path + ".tmp";
|
||||||
|
|
||||||
await using (var jsonStream = new FileStream(jsonTempFile, FileMode.Create, FileAccess.ReadWrite, FileShare.Read)) {
|
await using (var jsonStream = new FileStream(jsonTempFile, FileMode.Create, FileAccess.ReadWrite, FileShare.Read)) {
|
||||||
await ViewerJsonExport.Generate(jsonStream, db, FilterModel.CreateFilter());
|
await ViewerJsonExport.Generate(jsonStream, strategy, db, FilterModel.CreateFilter());
|
||||||
|
|
||||||
char[] jsonBuffer = new char[Math.Min(32768, jsonStream.Position)];
|
char[] jsonBuffer = new char[Math.Min(32768, jsonStream.Position)];
|
||||||
jsonStream.Position = 0;
|
jsonStream.Position = 0;
|
||||||
@@ -106,27 +110,25 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
TemporaryFiles.Add(fullPath);
|
TemporaryFiles.Add(fullPath);
|
||||||
|
|
||||||
Directory.CreateDirectory(rootPath);
|
Directory.CreateDirectory(rootPath);
|
||||||
await WriteViewerFile(fullPath);
|
await WriteViewerFile(fullPath, new LiveViewerExportStrategy(ServerManager.Port, ServerManager.Token));
|
||||||
|
|
||||||
Process.Start(new ProcessStartInfo(fullPath) { UseShellExecute = true });
|
Process.Start(new ProcessStartInfo(fullPath) { UseShellExecute = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void OnClickSaveViewer() {
|
private static readonly FilePickerFileType[] ViewerFileTypes = {
|
||||||
var dialog = new SaveFileDialog {
|
FileDialogs.CreateFilter("Discord History Viewer", new string[] { "html" }),
|
||||||
Title = "Save Viewer",
|
};
|
||||||
InitialFileName = Path.GetFileNameWithoutExtension(db.Path) + ".html",
|
|
||||||
Directory = Path.GetDirectoryName(db.Path),
|
|
||||||
Filters = new List<FileDialogFilter> {
|
|
||||||
new() {
|
|
||||||
Name = "Discord History Viewer",
|
|
||||||
Extensions = { "html" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.ShowAsync(window);
|
|
||||||
|
|
||||||
string? path = await dialog;
|
public async void OnClickSaveViewer() {
|
||||||
if (!string.IsNullOrEmpty(path)) {
|
string? path = await window.StorageProvider.SaveFile(new FilePickerSaveOptions {
|
||||||
await WriteViewerFile(path);
|
Title = "Save Viewer",
|
||||||
|
FileTypeChoices = ViewerFileTypes,
|
||||||
|
SuggestedFileName = Path.GetFileNameWithoutExtension(db.Path) + ".html",
|
||||||
|
SuggestedStartLocation = await FileDialogs.GetSuggestedStartLocation(window, Path.GetDirectoryName(db.Path)),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (path != null) {
|
||||||
|
await WriteViewerFile(path, StandaloneViewerExportStrategy.Instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,4 +147,3 @@ namespace DHT.Desktop.Main.Pages {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,16 +1,11 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Screens {
|
namespace DHT.Desktop.Main.Screens;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class MainContentScreen : UserControl {
|
public sealed partial class MainContentScreen : UserControl {
|
||||||
public MainContentScreen() {
|
public MainContentScreen() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent() {
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -9,7 +9,8 @@ using DHT.Server.Database;
|
|||||||
using DHT.Server.Service;
|
using DHT.Server.Service;
|
||||||
using DHT.Utils.Logging;
|
using DHT.Utils.Logging;
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Screens {
|
namespace DHT.Desktop.Main.Screens;
|
||||||
|
|
||||||
sealed class MainContentScreenModel : IDisposable {
|
sealed class MainContentScreenModel : IDisposable {
|
||||||
private static readonly Log Log = Log.ForType<MainContentScreenModel>();
|
private static readonly Log Log = Log.ForType<MainContentScreenModel>();
|
||||||
|
|
||||||
@@ -116,4 +117,3 @@ namespace DHT.Desktop.Main.Screens {
|
|||||||
await Dialog.ShowOk(window, "Internal Server Error", ex.Message);
|
await Dialog.ShowOk(window, "Internal Server Error", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,16 +1,11 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Screens {
|
namespace DHT.Desktop.Main.Screens;
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
|
||||||
public sealed class WelcomeScreen : UserControl {
|
public sealed partial class WelcomeScreen : UserControl {
|
||||||
public WelcomeScreen() {
|
public WelcomeScreen() {
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeComponent() {
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,8 @@ using DHT.Desktop.Dialogs.Message;
|
|||||||
using DHT.Server.Database;
|
using DHT.Server.Database;
|
||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
|
|
||||||
namespace DHT.Desktop.Main.Screens {
|
namespace DHT.Desktop.Main.Screens;
|
||||||
|
|
||||||
sealed class WelcomeScreenModel : BaseModel, IDisposable {
|
sealed class WelcomeScreenModel : BaseModel, IDisposable {
|
||||||
public string Version => Program.Version;
|
public string Version => Program.Version;
|
||||||
|
|
||||||
@@ -26,11 +27,8 @@ namespace DHT.Desktop.Main.Screens {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async void OpenOrCreateDatabase() {
|
public async void OpenOrCreateDatabase() {
|
||||||
var dialog = DatabaseGui.NewOpenOrCreateDatabaseFileDialog();
|
var path = await DatabaseGui.NewOpenOrCreateDatabaseFileDialog(window, Path.GetDirectoryName(dbFilePath));
|
||||||
dialog.Directory = Path.GetDirectoryName(dbFilePath);
|
if (path != null) {
|
||||||
|
|
||||||
string? path = await dialog.ShowAsync(window);
|
|
||||||
if (!string.IsNullOrWhiteSpace(path)) {
|
|
||||||
await OpenOrCreateDatabaseFromPath(path);
|
await OpenOrCreateDatabaseFromPath(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,4 +68,3 @@ namespace DHT.Desktop.Main.Screens {
|
|||||||
Db = null;
|
Db = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -3,7 +3,8 @@ using System.Reflection;
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using DHT.Utils.Resources;
|
using DHT.Utils.Resources;
|
||||||
|
|
||||||
namespace DHT.Desktop {
|
namespace DHT.Desktop;
|
||||||
|
|
||||||
static class Program {
|
static class Program {
|
||||||
public static string Version { get; }
|
public static string Version { get; }
|
||||||
public static CultureInfo Culture { get; }
|
public static CultureInfo Culture { get; }
|
||||||
@@ -33,7 +34,7 @@ namespace DHT.Desktop {
|
|||||||
private static AppBuilder BuildAvaloniaApp() {
|
private static AppBuilder BuildAvaloniaApp() {
|
||||||
return AppBuilder.Configure<App>()
|
return AppBuilder.Configure<App>()
|
||||||
.UsePlatformDetect()
|
.UsePlatformDetect()
|
||||||
|
.WithInterFont()
|
||||||
.LogToTrace();
|
.LogToTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -2,7 +2,8 @@ using System;
|
|||||||
using DHT.Server.Database;
|
using DHT.Server.Database;
|
||||||
using DHT.Server.Service;
|
using DHT.Server.Service;
|
||||||
|
|
||||||
namespace DHT.Desktop.Server {
|
namespace DHT.Desktop.Server;
|
||||||
|
|
||||||
sealed class ServerManager : IDisposable {
|
sealed class ServerManager : IDisposable {
|
||||||
public static ushort Port { get; set; } = ServerUtils.FindAvailablePort(50000, 60000);
|
public static ushort Port { get; set; } = ServerUtils.FindAvailablePort(50000, 60000);
|
||||||
public static string Token { get; set; } = ServerUtils.GenerateRandomToken(20);
|
public static string Token { get; set; } = ServerUtils.GenerateRandomToken(20);
|
||||||
@@ -47,4 +48,3 @@ namespace DHT.Desktop.Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
8
app/Directory.build.props
Normal file
8
app/Directory.build.props
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<Project>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<LangVersion>11</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
@@ -9,7 +9,7 @@ class DISCORD {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static getMessageElements() {
|
static getMessageElements() {
|
||||||
return this.getMessageOuterElement().querySelectorAll("[class*='message-']");
|
return this.getMessageOuterElement().querySelectorAll("[class*='message_']");
|
||||||
}
|
}
|
||||||
|
|
||||||
static hasMoreMessages() {
|
static hasMoreMessages() {
|
||||||
@@ -168,7 +168,7 @@ class DISCORD {
|
|||||||
if (dms) {
|
if (dms) {
|
||||||
let name;
|
let name;
|
||||||
|
|
||||||
for (const ele of dms.querySelectorAll("[class*='channel-'] [class*='selected-'] [class^='name-'] *, [class*='channel-'][class*='selected-'] [class^='name-'] *")) {
|
for (const ele of dms.querySelectorAll("[class*='channel_'] [class*='selected_'] [class^='name_'] *")) {
|
||||||
const node = Array.prototype.find.call(ele.childNodes, node => node.nodeType === Node.TEXT_NODE);
|
const node = Array.prototype.find.call(ele.childNodes, node => node.nodeType === Node.TEXT_NODE);
|
||||||
|
|
||||||
if (node) {
|
if (node) {
|
||||||
@@ -197,9 +197,22 @@ class DISCORD {
|
|||||||
return { server, channel };
|
return { server, channel };
|
||||||
}
|
}
|
||||||
else if (obj.guild_id) {
|
else if (obj.guild_id) {
|
||||||
|
let guild;
|
||||||
|
|
||||||
|
for (const child of DOM.getReactProps(document.querySelector("nav header [class*='headerContent_']")).children) {
|
||||||
|
if (child && child.props && child.props.guild) {
|
||||||
|
guild = child.props.guild;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!guild || typeof guild.name !== "string" || obj.guild_id !== guild.id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const server = {
|
const server = {
|
||||||
"id": obj.guild_id,
|
"id": guild.id,
|
||||||
"name": document.querySelector("nav header h1[class*='name-']").innerText,
|
"name": guild.name,
|
||||||
"type": "SERVER"
|
"type": "SERVER"
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -238,10 +251,10 @@ class DISCORD {
|
|||||||
|
|
||||||
if (dms) {
|
if (dms) {
|
||||||
const currentChannel = DOM.queryReactClass("selected", dms);
|
const currentChannel = DOM.queryReactClass("selected", dms);
|
||||||
const currentChannelContainer = currentChannel && currentChannel.closest("[class*='channel-']");
|
const currentChannelContainer = currentChannel && currentChannel.closest("[class*='channel_']");
|
||||||
const nextChannel = currentChannelContainer && currentChannelContainer.nextElementSibling;
|
const nextChannel = currentChannelContainer && currentChannelContainer.nextElementSibling;
|
||||||
|
|
||||||
if (!nextChannel || !nextChannel.getAttribute("class").includes("channel-")) {
|
if (!nextChannel || !nextChannel.getAttribute("class").includes("channel_")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,7 +281,7 @@ class DISCORD {
|
|||||||
let nextChannel = null;
|
let nextChannel = null;
|
||||||
|
|
||||||
for (let index = 0; index < allTextChannels.length - 1; index++) {
|
for (let index = 0; index < allTextChannels.length - 1; index++) {
|
||||||
if (allTextChannels[index].className.includes("selected-")) {
|
if (allTextChannels[index].className.includes("selected_")) {
|
||||||
nextChannel = allTextChannels[index + 1];
|
nextChannel = allTextChannels[index + 1];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@@ -11,7 +11,7 @@ class DOM {
|
|||||||
* Returns the first child element containing the specified obfuscated class. Parent defaults to the entire document.
|
* Returns the first child element containing the specified obfuscated class. Parent defaults to the entire document.
|
||||||
*/
|
*/
|
||||||
static queryReactClass(cls, parent) {
|
static queryReactClass(cls, parent) {
|
||||||
return (parent || document).querySelector(`[class*="${cls}-"]`);
|
return (parent || document).querySelector(`[class*="${cls}_"]`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -251,6 +251,11 @@ const STATE = (function() {
|
|||||||
mapped.type = attachment.content_type;
|
mapped.type = attachment.content_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (attachment.width && attachment.height) {
|
||||||
|
mapped.width = attachment.width;
|
||||||
|
mapped.height = attachment.height;
|
||||||
|
}
|
||||||
|
|
||||||
return mapped;
|
return mapped;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,5 @@
|
|||||||
#app-mount div[class*="app-"] {
|
#app-mount {
|
||||||
margin-bottom: 48px !important;
|
height: calc(100% - 48px) !important;
|
||||||
}
|
|
||||||
|
|
||||||
#app-mount div[class*="app-"] > div[class*="app-"] {
|
|
||||||
margin-bottom: 0 !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#dht-ctrl {
|
#dht-ctrl {
|
||||||
|
@@ -78,6 +78,12 @@ const DISCORD = (function() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isImageUrl = function(url) {
|
||||||
|
const dot = url.pathname.lastIndexOf(".");
|
||||||
|
const ext = dot === -1 ? "" : url.pathname.substring(dot).toLowerCase();
|
||||||
|
return ext === ".png" || ext === ".gif" || ext === ".jpg" || ext === ".jpeg";
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setup() {
|
setup() {
|
||||||
templateChannelServer = new TEMPLATE([
|
templateChannelServer = new TEMPLATE([
|
||||||
@@ -123,7 +129,7 @@ const DISCORD = (function() {
|
|||||||
|
|
||||||
// noinspection HtmlUnknownTarget
|
// noinspection HtmlUnknownTarget
|
||||||
templateAttachmentDownload = new TEMPLATE([
|
templateAttachmentDownload = new TEMPLATE([
|
||||||
"<a href='{url}' class='embed download'>Download {filename}</a>"
|
"<a href='{url}' class='embed download'>Download {name}</a>"
|
||||||
].join(""));
|
].join(""));
|
||||||
|
|
||||||
// noinspection HtmlUnknownTarget
|
// noinspection HtmlUnknownTarget
|
||||||
@@ -176,9 +182,8 @@ const DISCORD = (function() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
isImageAttachment(attachment) {
|
isImageAttachment(attachment) {
|
||||||
const dot = attachment.url.lastIndexOf(".");
|
const url = DOM.tryParseUrl(attachment.url);
|
||||||
const ext = dot === -1 ? "" : attachment.url.substring(dot).toLowerCase();
|
return url != null && isImageUrl(url);
|
||||||
return ext === ".png" || ext === ".gif" || ext === ".jpg" || ext === ".jpeg";
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getChannelHTML(channel) { // noinspection FunctionWithInconsistentReturnsJS
|
getChannelHTML(channel) { // noinspection FunctionWithInconsistentReturnsJS
|
||||||
@@ -235,16 +240,14 @@ const DISCORD = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return value.map(attachment => {
|
return value.map(attachment => {
|
||||||
if (this.isImageAttachment(attachment) && SETTINGS.enableImagePreviews) {
|
if (!DISCORD.isImageAttachment(attachment) || !SETTINGS.enableImagePreviews) {
|
||||||
return templateEmbedImage.apply({ url: attachment.url, src: attachment.url });
|
return templateAttachmentDownload.apply(attachment);
|
||||||
|
}
|
||||||
|
else if ("width" in attachment && "height" in attachment) {
|
||||||
|
return templateEmbedImageWithSize.apply({ url: attachment.url, src: attachment.url, width: attachment.width, height: attachment.height });
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const sliced = attachment.url.split("/");
|
return templateEmbedImage.apply({ url: attachment.url, src: attachment.url });
|
||||||
|
|
||||||
return templateAttachmentDownload.apply({
|
|
||||||
"url": attachment.url,
|
|
||||||
"filename": sliced[sliced.length - 1]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}).join("");
|
}).join("");
|
||||||
}
|
}
|
||||||
|
@@ -51,4 +51,15 @@ class DOM {
|
|||||||
const date = new Date(timestamp);
|
const date = new Date(timestamp);
|
||||||
return date.toLocaleDateString() + ", " + date.toLocaleTimeString();
|
return date.toLocaleDateString() + ", " + date.toLocaleTimeString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a url string into a URL object and returns it. If the parsing fails, returns null.
|
||||||
|
*/
|
||||||
|
static tryParseUrl(url) {
|
||||||
|
try {
|
||||||
|
return new URL(url);
|
||||||
|
} catch (ignore) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
namespace DHT.Server.Data.Aggregations {
|
namespace DHT.Server.Data.Aggregations;
|
||||||
|
|
||||||
public sealed class DownloadStatusStatistics {
|
public sealed class DownloadStatusStatistics {
|
||||||
public int EnqueuedCount { get; internal set; }
|
public int EnqueuedCount { get; internal set; }
|
||||||
public ulong EnqueuedSize { get; internal set; }
|
public ulong EnqueuedSize { get; internal set; }
|
||||||
@@ -12,4 +13,3 @@ namespace DHT.Server.Data.Aggregations {
|
|||||||
public int SkippedCount { get; internal set; }
|
public int SkippedCount { get; internal set; }
|
||||||
public ulong SkippedSize { get; internal set; }
|
public ulong SkippedSize { get; internal set; }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
namespace DHT.Server.Data {
|
namespace DHT.Server.Data;
|
||||||
|
|
||||||
public readonly struct Attachment {
|
public readonly struct Attachment {
|
||||||
public ulong Id { get; internal init; }
|
public ulong Id { get; internal init; }
|
||||||
public string Name { get; internal init; }
|
public string Name { get; internal init; }
|
||||||
public string? Type { get; internal init; }
|
public string? Type { get; internal init; }
|
||||||
public string Url { get; internal init; }
|
public string Url { get; internal init; }
|
||||||
public ulong Size { get; internal init; }
|
public ulong Size { get; internal init; }
|
||||||
}
|
public int? Width { get; internal init; }
|
||||||
|
public int? Height { get; internal init; }
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
namespace DHT.Server.Data {
|
namespace DHT.Server.Data;
|
||||||
|
|
||||||
public readonly struct Channel {
|
public readonly struct Channel {
|
||||||
public ulong Id { get; init; }
|
public ulong Id { get; init; }
|
||||||
public ulong Server { get; init; }
|
public ulong Server { get; init; }
|
||||||
@@ -8,4 +9,3 @@ namespace DHT.Server.Data {
|
|||||||
public string? Topic { get; init; }
|
public string? Topic { get; init; }
|
||||||
public bool? Nsfw { get; init; }
|
public bool? Nsfw { get; init; }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
namespace DHT.Server.Data {
|
namespace DHT.Server.Data;
|
||||||
|
|
||||||
public readonly struct Download {
|
public readonly struct Download {
|
||||||
internal static Download NewSuccess(string url, byte[] data) {
|
internal static Download NewSuccess(string url, byte[] data) {
|
||||||
return new Download(url, DownloadStatus.Success, (ulong) Math.Max(data.LongLength, 0), data);
|
return new Download(url, DownloadStatus.Success, (ulong) Math.Max(data.LongLength, 0), data);
|
||||||
@@ -27,4 +28,3 @@ namespace DHT.Server.Data {
|
|||||||
return new Download(Url, Status, Size, data);
|
return new Download(Url, Status, Size, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
namespace DHT.Server.Data {
|
namespace DHT.Server.Data;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extends <see cref="HttpStatusCode"/> with custom status codes in the range 0-99.
|
/// Extends <see cref="HttpStatusCode"/> with custom status codes in the range 0-99.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -9,4 +10,3 @@ namespace DHT.Server.Data {
|
|||||||
GenericError = 1,
|
GenericError = 1,
|
||||||
Success = HttpStatusCode.OK
|
Success = HttpStatusCode.OK
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
6
app/Server/Data/DownloadedAttachment.cs
Normal file
6
app/Server/Data/DownloadedAttachment.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace DHT.Server.Data;
|
||||||
|
|
||||||
|
public readonly struct DownloadedAttachment {
|
||||||
|
public string? Type { get; internal init; }
|
||||||
|
public byte[] Data { get; internal init; }
|
||||||
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
namespace DHT.Server.Data {
|
namespace DHT.Server.Data;
|
||||||
|
|
||||||
public readonly struct Embed {
|
public readonly struct Embed {
|
||||||
public string Json { get; internal init; }
|
public string Json { get; internal init; }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace DHT.Server.Data {
|
namespace DHT.Server.Data;
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
public enum EmojiFlags : ushort {
|
public enum EmojiFlags : ushort {
|
||||||
None = 0,
|
None = 0,
|
||||||
Animated = 0b1
|
Animated = 0b1
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
namespace DHT.Server.Data.Filters {
|
namespace DHT.Server.Data.Filters;
|
||||||
|
|
||||||
public sealed class AttachmentFilter {
|
public sealed class AttachmentFilter {
|
||||||
public ulong? MaxBytes { get; set; } = null;
|
public ulong? MaxBytes { get; set; } = null;
|
||||||
|
|
||||||
@@ -12,4 +13,3 @@ namespace DHT.Server.Data.Filters {
|
|||||||
OnlyPresent
|
OnlyPresent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace DHT.Server.Data.Filters {
|
namespace DHT.Server.Data.Filters;
|
||||||
|
|
||||||
public sealed class DownloadItemFilter {
|
public sealed class DownloadItemFilter {
|
||||||
public HashSet<DownloadStatus>? IncludeStatuses { get; set; } = null;
|
public HashSet<DownloadStatus>? IncludeStatuses { get; init; } = null;
|
||||||
public HashSet<DownloadStatus>? ExcludeStatuses { get; set; } = null;
|
public HashSet<DownloadStatus>? ExcludeStatuses { get; init; } = null;
|
||||||
|
|
||||||
public bool IsEmpty => IncludeStatuses == null && ExcludeStatuses == null;
|
public bool IsEmpty => IncludeStatuses == null && ExcludeStatuses == null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
namespace DHT.Server.Data.Filters {
|
namespace DHT.Server.Data.Filters;
|
||||||
|
|
||||||
public enum FilterRemovalMode {
|
public enum FilterRemovalMode {
|
||||||
KeepMatching,
|
KeepMatching,
|
||||||
RemoveMatching
|
RemoveMatching
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace DHT.Server.Data.Filters {
|
namespace DHT.Server.Data.Filters;
|
||||||
|
|
||||||
public sealed class MessageFilter {
|
public sealed class MessageFilter {
|
||||||
public DateTime? StartDate { get; set; } = null;
|
public DateTime? StartDate { get; set; } = null;
|
||||||
public DateTime? EndDate { get; set; } = null;
|
public DateTime? EndDate { get; set; } = null;
|
||||||
@@ -16,4 +17,3 @@ namespace DHT.Server.Data.Filters {
|
|||||||
UserIds == null &&
|
UserIds == null &&
|
||||||
MessageIds == null;
|
MessageIds == null;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
|
||||||
namespace DHT.Server.Data {
|
namespace DHT.Server.Data;
|
||||||
|
|
||||||
public readonly struct Message {
|
public readonly struct Message {
|
||||||
public ulong Id { get; init; }
|
public ulong Id { get; init; }
|
||||||
public ulong Sender { get; init; }
|
public ulong Sender { get; init; }
|
||||||
@@ -13,4 +14,3 @@ namespace DHT.Server.Data {
|
|||||||
public ImmutableArray<Embed> Embeds { get; init; }
|
public ImmutableArray<Embed> Embeds { get; init; }
|
||||||
public ImmutableArray<Reaction> Reactions { get; init; }
|
public ImmutableArray<Reaction> Reactions { get; init; }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
namespace DHT.Server.Data {
|
namespace DHT.Server.Data;
|
||||||
|
|
||||||
public readonly struct Reaction {
|
public readonly struct Reaction {
|
||||||
public ulong? EmojiId { get; internal init; }
|
public ulong? EmojiId { get; internal init; }
|
||||||
public string? EmojiName { get; internal init; }
|
public string? EmojiName { get; internal init; }
|
||||||
public EmojiFlags EmojiFlags { get; internal init; }
|
public EmojiFlags EmojiFlags { get; internal init; }
|
||||||
public int Count { get; internal init; }
|
public int Count { get; internal init; }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
namespace DHT.Server.Data {
|
namespace DHT.Server.Data;
|
||||||
|
|
||||||
public readonly struct Server {
|
public readonly struct Server {
|
||||||
public ulong Id { get; init; }
|
public ulong Id { get; init; }
|
||||||
public string Name { get; init; }
|
public string Name { get; init; }
|
||||||
public ServerType? Type { get; init; }
|
public ServerType? Type { get; init; }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
namespace DHT.Server.Data {
|
namespace DHT.Server.Data;
|
||||||
|
|
||||||
public enum ServerType {
|
public enum ServerType {
|
||||||
Server,
|
Server,
|
||||||
Group,
|
Group,
|
||||||
@@ -42,4 +43,3 @@ namespace DHT.Server.Data {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
namespace DHT.Server.Data {
|
namespace DHT.Server.Data;
|
||||||
|
|
||||||
public readonly struct User {
|
public readonly struct User {
|
||||||
public ulong Id { get; init; }
|
public ulong Id { get; init; }
|
||||||
public string Name { get; init; }
|
public string Name { get; init; }
|
||||||
public string? AvatarUrl { get; init; }
|
public string? AvatarUrl { get; init; }
|
||||||
public string? Discriminator { get; init; }
|
public string? Discriminator { get; init; }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using DHT.Server.Data;
|
using DHT.Server.Data;
|
||||||
|
|
||||||
namespace DHT.Server.Database {
|
namespace DHT.Server.Database;
|
||||||
|
|
||||||
public static class DatabaseExtensions {
|
public static class DatabaseExtensions {
|
||||||
public static void AddFrom(this IDatabaseFile target, IDatabaseFile source) {
|
public static void AddFrom(this IDatabaseFile target, IDatabaseFile source) {
|
||||||
target.AddServers(source.GetAllServers());
|
target.AddServers(source.GetAllServers());
|
||||||
@@ -26,4 +27,3 @@ namespace DHT.Server.Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
|
|
||||||
namespace DHT.Server.Database {
|
namespace DHT.Server.Database;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A live view of database statistics.
|
/// A live view of database statistics.
|
||||||
/// Some of the totals are computed asynchronously and may not reflect the most recent version of the database, or may not be available at all until computed for the first time.
|
/// Some of the totals are computed asynchronously and may not reflect the most recent version of the database, or may not be available at all until computed for the first time.
|
||||||
@@ -43,4 +44,3 @@ namespace DHT.Server.Database {
|
|||||||
internal set => Change(ref totalDownloads, value);
|
internal set => Change(ref totalDownloads, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
namespace DHT.Server.Database {
|
namespace DHT.Server.Database;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A complete snapshot of database statistics at a particular point in time.
|
/// A complete snapshot of database statistics at a particular point in time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -8,4 +9,3 @@ namespace DHT.Server.Database {
|
|||||||
public long TotalUsers { get; internal init; }
|
public long TotalUsers { get; internal init; }
|
||||||
public long TotalMessages { get; internal init; }
|
public long TotalMessages { get; internal init; }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,10 +1,13 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using DHT.Server.Data;
|
using DHT.Server.Data;
|
||||||
using DHT.Server.Data.Aggregations;
|
using DHT.Server.Data.Aggregations;
|
||||||
using DHT.Server.Data.Filters;
|
using DHT.Server.Data.Filters;
|
||||||
using DHT.Server.Download;
|
using DHT.Server.Download;
|
||||||
|
|
||||||
namespace DHT.Server.Database {
|
namespace DHT.Server.Database;
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "ArrangeObjectCreationWhenTypeNotEvident")]
|
||||||
public sealed class DummyDatabaseFile : IDatabaseFile {
|
public sealed class DummyDatabaseFile : IDatabaseFile {
|
||||||
public static DummyDatabaseFile Instance { get; } = new();
|
public static DummyDatabaseFile Instance { get; } = new();
|
||||||
|
|
||||||
@@ -63,6 +66,10 @@ namespace DHT.Server.Database {
|
|||||||
return download;
|
return download;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DownloadedAttachment? GetDownloadedAttachment(string url) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public void AddDownload(Data.Download download) {}
|
public void AddDownload(Data.Download download) {}
|
||||||
|
|
||||||
public void EnqueueDownloadItems(AttachmentFilter? filter = null) {}
|
public void EnqueueDownloadItems(AttachmentFilter? filter = null) {}
|
||||||
@@ -81,4 +88,3 @@ namespace DHT.Server.Database {
|
|||||||
|
|
||||||
public void Dispose() {}
|
public void Dispose() {}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using DHT.Server.Database.Sqlite;
|
using DHT.Server.Database.Sqlite;
|
||||||
|
|
||||||
namespace DHT.Server.Database.Exceptions {
|
namespace DHT.Server.Database.Exceptions;
|
||||||
|
|
||||||
public sealed class DatabaseTooNewException : Exception {
|
public sealed class DatabaseTooNewException : Exception {
|
||||||
public int DatabaseVersion { get; }
|
public int DatabaseVersion { get; }
|
||||||
public int CurrentVersion => Schema.Version;
|
public int CurrentVersion => Schema.Version;
|
||||||
@@ -10,4 +11,3 @@ namespace DHT.Server.Database.Exceptions {
|
|||||||
this.DatabaseVersion = databaseVersion;
|
this.DatabaseVersion = databaseVersion;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace DHT.Server.Database.Exceptions {
|
namespace DHT.Server.Database.Exceptions;
|
||||||
|
|
||||||
public sealed class InvalidDatabaseVersionException : Exception {
|
public sealed class InvalidDatabaseVersionException : Exception {
|
||||||
public string Version { get; }
|
public string Version { get; }
|
||||||
|
|
||||||
@@ -8,4 +9,3 @@ namespace DHT.Server.Database.Exceptions {
|
|||||||
this.Version = version;
|
this.Version = version;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -0,0 +1,7 @@
|
|||||||
|
using DHT.Server.Data;
|
||||||
|
|
||||||
|
namespace DHT.Server.Database.Export.Strategy;
|
||||||
|
|
||||||
|
public interface IViewerExportStrategy {
|
||||||
|
string GetAttachmentUrl(Attachment attachment);
|
||||||
|
}
|
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Net;
|
||||||
|
using DHT.Server.Data;
|
||||||
|
|
||||||
|
namespace DHT.Server.Database.Export.Strategy;
|
||||||
|
|
||||||
|
public sealed class LiveViewerExportStrategy : IViewerExportStrategy {
|
||||||
|
private readonly string safePort;
|
||||||
|
private readonly string safeToken;
|
||||||
|
|
||||||
|
public LiveViewerExportStrategy(ushort port, string token) {
|
||||||
|
this.safePort = port.ToString();
|
||||||
|
this.safeToken = WebUtility.UrlEncode(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetAttachmentUrl(Attachment attachment) {
|
||||||
|
return "http://127.0.0.1:" + safePort + "/get-attachment/" + WebUtility.UrlEncode(attachment.Url) + "?token=" + safeToken;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,13 @@
|
|||||||
|
using DHT.Server.Data;
|
||||||
|
|
||||||
|
namespace DHT.Server.Database.Export.Strategy;
|
||||||
|
|
||||||
|
public sealed class StandaloneViewerExportStrategy : IViewerExportStrategy {
|
||||||
|
public static StandaloneViewerExportStrategy Instance { get; } = new ();
|
||||||
|
|
||||||
|
private StandaloneViewerExportStrategy() {}
|
||||||
|
|
||||||
|
public string GetAttachmentUrl(Attachment attachment) {
|
||||||
|
return attachment.Url;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -5,13 +6,15 @@ using System.Text.Json;
|
|||||||
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.Server.Database.Export.Strategy;
|
||||||
using DHT.Utils.Logging;
|
using DHT.Utils.Logging;
|
||||||
|
|
||||||
namespace DHT.Server.Database.Export {
|
namespace DHT.Server.Database.Export;
|
||||||
|
|
||||||
public static class ViewerJsonExport {
|
public static class ViewerJsonExport {
|
||||||
private static readonly Log Log = Log.ForType(typeof(ViewerJsonExport));
|
private static readonly Log Log = Log.ForType(typeof(ViewerJsonExport));
|
||||||
|
|
||||||
public static async Task Generate(Stream stream, IDatabaseFile db, MessageFilter? filter = null) {
|
public static async Task Generate(Stream stream, IViewerExportStrategy strategy, IDatabaseFile db, MessageFilter? filter = null) {
|
||||||
var perf = Log.Start();
|
var perf = Log.Start();
|
||||||
|
|
||||||
var includedUserIds = new HashSet<ulong>();
|
var includedUserIds = new HashSet<ulong>();
|
||||||
@@ -41,7 +44,7 @@ namespace DHT.Server.Database.Export {
|
|||||||
|
|
||||||
var value = new {
|
var value = new {
|
||||||
meta = new { users, userindex, servers, channels },
|
meta = new { users, userindex, servers, channels },
|
||||||
data = GenerateMessageList(includedMessages, userIndices)
|
data = GenerateMessageList(includedMessages, userIndices, strategy),
|
||||||
};
|
};
|
||||||
|
|
||||||
perf.Step("Generate value object");
|
perf.Step("Generate value object");
|
||||||
@@ -55,7 +58,7 @@ namespace DHT.Server.Database.Export {
|
|||||||
perf.End();
|
perf.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object GenerateUserList(IDatabaseFile db, HashSet<ulong> userIds, out List<string> userindex, out Dictionary<ulong, object> userIndices) {
|
private static Dictionary<string, object> GenerateUserList(IDatabaseFile db, HashSet<ulong> userIds, out List<string> userindex, out Dictionary<ulong, object> userIndices) {
|
||||||
var users = new Dictionary<string, object>();
|
var users = new Dictionary<string, object>();
|
||||||
userindex = new List<string>();
|
userindex = new List<string>();
|
||||||
userIndices = new Dictionary<ulong, object>();
|
userIndices = new Dictionary<ulong, object>();
|
||||||
@@ -87,7 +90,7 @@ namespace DHT.Server.Database.Export {
|
|||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object GenerateServerList(IDatabaseFile db, HashSet<ulong> serverIds, out Dictionary<ulong, object> serverIndices) {
|
private static List<object> GenerateServerList(IDatabaseFile db, HashSet<ulong> serverIds, out Dictionary<ulong, object> serverIndices) {
|
||||||
var servers = new List<object>();
|
var servers = new List<object>();
|
||||||
serverIndices = new Dictionary<ulong, object>();
|
serverIndices = new Dictionary<ulong, object>();
|
||||||
|
|
||||||
@@ -100,20 +103,20 @@ namespace DHT.Server.Database.Export {
|
|||||||
serverIndices[id] = servers.Count;
|
serverIndices[id] = servers.Count;
|
||||||
servers.Add(new Dictionary<string, object> {
|
servers.Add(new Dictionary<string, object> {
|
||||||
["name"] = server.Name,
|
["name"] = server.Name,
|
||||||
["type"] = ServerTypes.ToJsonViewerString(server.Type)
|
["type"] = ServerTypes.ToJsonViewerString(server.Type),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return servers;
|
return servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object GenerateChannelList(List<Channel> includedChannels, Dictionary<ulong, object> serverIndices) {
|
private static Dictionary<string, object> GenerateChannelList(List<Channel> includedChannels, Dictionary<ulong, object> serverIndices) {
|
||||||
var channels = new Dictionary<string, object>();
|
var channels = new Dictionary<string, object>();
|
||||||
|
|
||||||
foreach (var channel in includedChannels) {
|
foreach (var channel in includedChannels) {
|
||||||
var obj = new Dictionary<string, object> {
|
var obj = new Dictionary<string, object> {
|
||||||
["server"] = serverIndices[channel.Server],
|
["server"] = serverIndices[channel.Server],
|
||||||
["name"] = channel.Name
|
["name"] = channel.Name,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (channel.ParentId != null) {
|
if (channel.ParentId != null) {
|
||||||
@@ -138,7 +141,7 @@ namespace DHT.Server.Database.Export {
|
|||||||
return channels;
|
return channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object GenerateMessageList(List<Message> includedMessages, Dictionary<ulong, object> userIndices) {
|
private static Dictionary<string, Dictionary<string, object>> GenerateMessageList( List<Message> includedMessages, Dictionary<ulong, object> userIndices, IViewerExportStrategy strategy) {
|
||||||
var data = new Dictionary<string, Dictionary<string, object>>();
|
var data = new Dictionary<string, Dictionary<string, object>>();
|
||||||
|
|
||||||
foreach (var grouping in includedMessages.GroupBy(static message => message.Channel)) {
|
foreach (var grouping in includedMessages.GroupBy(static message => message.Channel)) {
|
||||||
@@ -148,7 +151,7 @@ namespace DHT.Server.Database.Export {
|
|||||||
foreach (var message in grouping) {
|
foreach (var message in grouping) {
|
||||||
var obj = new Dictionary<string, object> {
|
var obj = new Dictionary<string, object> {
|
||||||
["u"] = userIndices[message.Sender],
|
["u"] = userIndices[message.Sender],
|
||||||
["t"] = message.Timestamp
|
["t"] = message.Timestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(message.Text)) {
|
if (!string.IsNullOrEmpty(message.Text)) {
|
||||||
@@ -164,8 +167,18 @@ namespace DHT.Server.Database.Export {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!message.Attachments.IsEmpty) {
|
if (!message.Attachments.IsEmpty) {
|
||||||
obj["a"] = message.Attachments.Select(static attachment => new Dictionary<string, object> {
|
obj["a"] = message.Attachments.Select(attachment => {
|
||||||
{ "url", attachment.Url }
|
var a = new Dictionary<string, object> {
|
||||||
|
{ "url", strategy.GetAttachmentUrl(attachment) },
|
||||||
|
{ "name", Uri.TryCreate(attachment.Url, UriKind.Absolute, out var uri) ? Path.GetFileName(uri.LocalPath) : attachment.Url },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (attachment is { Width: not null, Height: not null }) {
|
||||||
|
a["width"] = attachment.Width;
|
||||||
|
a["height"] = attachment.Height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a;
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +201,7 @@ namespace DHT.Server.Database.Export {
|
|||||||
r["a"] = reaction.EmojiFlags.HasFlag(EmojiFlags.Animated);
|
r["a"] = reaction.EmojiFlags.HasFlag(EmojiFlags.Animated);
|
||||||
r["c"] = reaction.Count;
|
r["c"] = reaction.Count;
|
||||||
return r;
|
return r;
|
||||||
});
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
channelData[message.Id.ToString()] = obj;
|
channelData[message.Id.ToString()] = obj;
|
||||||
@@ -200,4 +213,3 @@ namespace DHT.Server.Database.Export {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -2,7 +2,8 @@ using System;
|
|||||||
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 ViewerJsonSnowflakeSerializer : JsonConverter<ulong> {
|
sealed class ViewerJsonSnowflakeSerializer : JsonConverter<ulong> {
|
||||||
public override ulong Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
public override ulong Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||||
return ulong.Parse(reader.GetString()!);
|
return ulong.Parse(reader.GetString()!);
|
||||||
@@ -12,4 +13,3 @@ namespace DHT.Server.Database.Export {
|
|||||||
writer.WriteStringValue(value.ToString());
|
writer.WriteStringValue(value.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -5,7 +5,8 @@ using DHT.Server.Data.Aggregations;
|
|||||||
using DHT.Server.Data.Filters;
|
using DHT.Server.Data.Filters;
|
||||||
using DHT.Server.Download;
|
using DHT.Server.Download;
|
||||||
|
|
||||||
namespace DHT.Server.Database {
|
namespace DHT.Server.Database;
|
||||||
|
|
||||||
public interface IDatabaseFile : IDisposable {
|
public interface IDatabaseFile : IDisposable {
|
||||||
string Path { get; }
|
string Path { get; }
|
||||||
DatabaseStatistics Statistics { get; }
|
DatabaseStatistics Statistics { get; }
|
||||||
@@ -31,6 +32,7 @@ namespace DHT.Server.Database {
|
|||||||
void AddDownload(Data.Download download);
|
void AddDownload(Data.Download download);
|
||||||
List<Data.Download> GetDownloadsWithoutData();
|
List<Data.Download> GetDownloadsWithoutData();
|
||||||
Data.Download GetDownloadWithData(Data.Download download);
|
Data.Download GetDownloadWithData(Data.Download download);
|
||||||
|
DownloadedAttachment? GetDownloadedAttachment(string url);
|
||||||
|
|
||||||
void EnqueueDownloadItems(AttachmentFilter? filter = null);
|
void EnqueueDownloadItems(AttachmentFilter? filter = null);
|
||||||
List<DownloadItem> GetEnqueuedDownloadItems(int count);
|
List<DownloadItem> GetEnqueuedDownloadItems(int count);
|
||||||
@@ -39,4 +41,3 @@ namespace DHT.Server.Database {
|
|||||||
|
|
||||||
void Vacuum();
|
void Vacuum();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace DHT.Server.Database.Import {
|
namespace DHT.Server.Database.Import;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// https://discord.com/developers/docs/reference#snowflakes
|
/// https://discord.com/developers/docs/reference#snowflakes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -18,4 +19,3 @@ namespace DHT.Server.Database.Import {
|
|||||||
return id++;
|
return id++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -12,7 +12,8 @@ using DHT.Utils.Http;
|
|||||||
using DHT.Utils.Logging;
|
using DHT.Utils.Logging;
|
||||||
using Microsoft.AspNetCore.StaticFiles;
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
|
|
||||||
namespace DHT.Server.Database.Import {
|
namespace DHT.Server.Database.Import;
|
||||||
|
|
||||||
public static class LegacyArchiveImport {
|
public static class LegacyArchiveImport {
|
||||||
private static readonly Log Log = Log.ForType(typeof(LegacyArchiveImport));
|
private static readonly Log Log = Log.ForType(typeof(LegacyArchiveImport));
|
||||||
|
|
||||||
@@ -56,11 +57,7 @@ namespace DHT.Server.Database.Import {
|
|||||||
for (var i = 0; i < servers.Length; i++) {
|
for (var i = 0; i < servers.Length; i++) {
|
||||||
var server = servers[i];
|
var server = servers[i];
|
||||||
if (askedServerIds.TryGetValue(server, out var serverId)) {
|
if (askedServerIds.TryGetValue(server, out var serverId)) {
|
||||||
servers[i] = new Data.Server {
|
servers[i] = server with { Id = serverId };
|
||||||
Id = serverId,
|
|
||||||
Name = server.Name,
|
|
||||||
Type = server.Type
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +109,7 @@ namespace DHT.Server.Database.Import {
|
|||||||
Id = userId,
|
Id = userId,
|
||||||
Name = userObj.RequireString("name", path),
|
Name = userObj.RequireString("name", path),
|
||||||
AvatarUrl = userObj.HasKey("avatar") ? userObj.RequireString("avatar", path) : null,
|
AvatarUrl = userObj.HasKey("avatar") ? userObj.RequireString("avatar", path) : null,
|
||||||
Discriminator = userObj.HasKey("tag") ? userObj.RequireString("tag", path) : null
|
Discriminator = userObj.HasKey("tag") ? userObj.RequireString("tag", path) : null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +122,7 @@ namespace DHT.Server.Database.Import {
|
|||||||
return meta.RequireArray("servers", "meta").Select(serverObj => new Data.Server {
|
return meta.RequireArray("servers", "meta").Select(serverObj => new Data.Server {
|
||||||
Id = fakeSnowflake.Next(),
|
Id = fakeSnowflake.Next(),
|
||||||
Name = serverObj.RequireString("name", ServersPath),
|
Name = serverObj.RequireString("name", ServersPath),
|
||||||
Type = ServerTypes.FromString(serverObj.RequireString("type", ServersPath))
|
Type = ServerTypes.FromString(serverObj.RequireString("type", ServersPath)),
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +150,7 @@ namespace DHT.Server.Database.Import {
|
|||||||
Name = channelObj.RequireString("name", path),
|
Name = channelObj.RequireString("name", path),
|
||||||
Position = channelObj.HasKey("position") ? channelObj.RequireInt("position", path, min: 0) : null,
|
Position = channelObj.HasKey("position") ? channelObj.RequireInt("position", path, min: 0) : null,
|
||||||
Topic = channelObj.HasKey("topic") ? channelObj.RequireString("topic", path) : null,
|
Topic = channelObj.HasKey("topic") ? channelObj.RequireString("topic", path) : null,
|
||||||
Nsfw = channelObj.HasKey("nsfw") ? channelObj.RequireBool("nsfw", path) : null
|
Nsfw = channelObj.HasKey("nsfw") ? channelObj.RequireBool("nsfw", path) : null,
|
||||||
};
|
};
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
@@ -184,7 +181,7 @@ namespace DHT.Server.Database.Import {
|
|||||||
RepliedToId = messageObj.HasKey("r") ? messageObj.RequireSnowflake("r", path) : null,
|
RepliedToId = messageObj.HasKey("r") ? messageObj.RequireSnowflake("r", path) : null,
|
||||||
Attachments = messageObj.HasKey("a") ? ReadMessageAttachments(messageObj.RequireArray("a", path), fakeSnowflake, path + ".a[]").ToImmutableArray() : ImmutableArray<Attachment>.Empty,
|
Attachments = messageObj.HasKey("a") ? ReadMessageAttachments(messageObj.RequireArray("a", path), fakeSnowflake, path + ".a[]").ToImmutableArray() : ImmutableArray<Attachment>.Empty,
|
||||||
Embeds = messageObj.HasKey("e") ? ReadMessageEmbeds(messageObj.RequireArray("e", path), path + ".e[]").ToImmutableArray() : ImmutableArray<Embed>.Empty,
|
Embeds = messageObj.HasKey("e") ? ReadMessageEmbeds(messageObj.RequireArray("e", path), path + ".e[]").ToImmutableArray() : ImmutableArray<Embed>.Empty,
|
||||||
Reactions = messageObj.HasKey("re") ? ReadMessageReactions(messageObj.RequireArray("re", path), path + ".re[]").ToImmutableArray() : ImmutableArray<Reaction>.Empty
|
Reactions = messageObj.HasKey("re") ? ReadMessageReactions(messageObj.RequireArray("re", path), path + ".re[]").ToImmutableArray() : ImmutableArray<Reaction>.Empty,
|
||||||
};
|
};
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
}
|
}
|
||||||
@@ -201,7 +198,7 @@ namespace DHT.Server.Database.Import {
|
|||||||
Name = name,
|
Name = name,
|
||||||
Type = type,
|
Type = type,
|
||||||
Url = url,
|
Url = url,
|
||||||
Size = 0 // unknown size
|
Size = 0, // unknown size
|
||||||
};
|
};
|
||||||
}).DistinctByKeyStable(static attachment => {
|
}).DistinctByKeyStable(static attachment => {
|
||||||
// Some Discord messages have duplicate attachments with the same id for unknown reasons.
|
// Some Discord messages have duplicate attachments with the same id for unknown reasons.
|
||||||
@@ -218,7 +215,7 @@ namespace DHT.Server.Database.Import {
|
|||||||
var embedJson = new Dictionary<string, object> {
|
var embedJson = new Dictionary<string, object> {
|
||||||
{ "url", url },
|
{ "url", url },
|
||||||
{ "type", type },
|
{ "type", type },
|
||||||
{ "dht_legacy", true }
|
{ "dht_legacy", true },
|
||||||
};
|
};
|
||||||
|
|
||||||
if (type == "image") {
|
if (type == "image") {
|
||||||
@@ -255,9 +252,8 @@ namespace DHT.Server.Database.Import {
|
|||||||
EmojiId = id,
|
EmojiId = id,
|
||||||
EmojiName = name,
|
EmojiName = name,
|
||||||
EmojiFlags = reactionObj.HasKey("an") && reactionObj.RequireBool("an", path) ? EmojiFlags.Animated : EmojiFlags.None,
|
EmojiFlags = reactionObj.HasKey("an") && reactionObj.RequireBool("an", path) ? EmojiFlags.Animated : EmojiFlags.None,
|
||||||
Count = reactionObj.RequireInt("c", path, min: 0)
|
Count = reactionObj.RequireInt("c", path, min: 0),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
@@ -4,9 +4,10 @@ using DHT.Server.Database.Exceptions;
|
|||||||
using DHT.Server.Database.Sqlite.Utils;
|
using DHT.Server.Database.Sqlite.Utils;
|
||||||
using DHT.Utils.Logging;
|
using DHT.Utils.Logging;
|
||||||
|
|
||||||
namespace DHT.Server.Database.Sqlite {
|
namespace DHT.Server.Database.Sqlite;
|
||||||
|
|
||||||
sealed class Schema {
|
sealed class Schema {
|
||||||
internal const int Version = 4;
|
internal const int Version = 5;
|
||||||
|
|
||||||
private static readonly Log Log = Log.ForType<Schema>();
|
private static readonly Log Log = Log.ForType<Schema>();
|
||||||
|
|
||||||
@@ -79,7 +80,9 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
type TEXT,
|
type TEXT,
|
||||||
url TEXT NOT NULL,
|
url TEXT NOT NULL,
|
||||||
size INTEGER NOT NULL)");
|
size INTEGER NOT NULL,
|
||||||
|
width INTEGER,
|
||||||
|
height INTEGER)");
|
||||||
|
|
||||||
Execute(@"CREATE TABLE embeds (
|
Execute(@"CREATE TABLE embeds (
|
||||||
message_id INTEGER NOT NULL,
|
message_id INTEGER NOT NULL,
|
||||||
@@ -159,7 +162,12 @@ namespace DHT.Server.Database.Sqlite {
|
|||||||
perf.Step("Upgrade to version 4");
|
perf.Step("Upgrade to version 4");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dbVersion <= 4) {
|
||||||
|
Execute("ALTER TABLE attachments ADD width INTEGER");
|
||||||
|
Execute("ALTER TABLE attachments ADD height INTEGER");
|
||||||
|
perf.Step("Upgrade to version 5");
|
||||||
|
}
|
||||||
|
|
||||||
perf.End();
|
perf.End();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user