1
0
mirror of https://github.com/chylex/Discord-History-Tracker.git synced 2024-11-25 14:42:44 +01:00

Compare commits

..

No commits in common. "3bf5acfa65a7f1cb92aaf7fb3bb6fdef186df5ad" and "d2934f4d6ae4a2d242e90a028219c5fb67fc97bd" have entirely different histories.

28 changed files with 150 additions and 238 deletions

View File

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using DHT.Desktop.Common; using DHT.Desktop.Common;
using DHT.Server;
using DHT.Server.Data.Filters; using DHT.Server.Data.Filters;
using DHT.Server.Database; using DHT.Server.Database;
using DHT.Utils.Models; using DHT.Utils.Models;
@ -48,7 +47,7 @@ sealed class AttachmentFilterPanelModel : BaseModel, IDisposable {
public IEnumerable<Unit> Units => AllUnits; public IEnumerable<Unit> Units => AllUnits;
private readonly State state; private readonly IDatabaseFile db;
private readonly string verb; private readonly string verb;
private readonly AsyncValueComputer<long> matchingAttachmentCountComputer; private readonly AsyncValueComputer<long> matchingAttachmentCountComputer;
@ -56,10 +55,10 @@ sealed class AttachmentFilterPanelModel : BaseModel, IDisposable {
private long? totalAttachmentCount; private long? totalAttachmentCount;
[Obsolete("Designer")] [Obsolete("Designer")]
public AttachmentFilterPanelModel() : this(State.Dummy) {} public AttachmentFilterPanelModel() : this(DummyDatabaseFile.Instance) {}
public AttachmentFilterPanelModel(State state, string verb = "Matches") { public AttachmentFilterPanelModel(IDatabaseFile db, string verb = "Matches") {
this.state = state; this.db = db;
this.verb = verb; this.verb = verb;
this.matchingAttachmentCountComputer = AsyncValueComputer<long>.WithResultProcessor(SetAttachmentCounts).Build(); this.matchingAttachmentCountComputer = AsyncValueComputer<long>.WithResultProcessor(SetAttachmentCounts).Build();
@ -67,11 +66,11 @@ sealed class AttachmentFilterPanelModel : BaseModel, IDisposable {
UpdateFilterStatistics(); UpdateFilterStatistics();
PropertyChanged += OnPropertyChanged; PropertyChanged += OnPropertyChanged;
state.Db.Statistics.PropertyChanged += OnDbStatisticsChanged; db.Statistics.PropertyChanged += OnDbStatisticsChanged;
} }
public void Dispose() { public void Dispose() {
state.Db.Statistics.PropertyChanged -= OnDbStatisticsChanged; db.Statistics.PropertyChanged -= OnDbStatisticsChanged;
} }
private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) { private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) {
@ -82,7 +81,7 @@ sealed class AttachmentFilterPanelModel : BaseModel, IDisposable {
private void OnDbStatisticsChanged(object? sender, PropertyChangedEventArgs e) { private void OnDbStatisticsChanged(object? sender, PropertyChangedEventArgs e) {
if (e.PropertyName == nameof(DatabaseStatistics.TotalAttachments)) { if (e.PropertyName == nameof(DatabaseStatistics.TotalAttachments)) {
totalAttachmentCount = state.Db.Statistics.TotalAttachments; totalAttachmentCount = db.Statistics.TotalAttachments;
UpdateFilterStatistics(); UpdateFilterStatistics();
} }
} }
@ -97,7 +96,7 @@ sealed class AttachmentFilterPanelModel : BaseModel, IDisposable {
else { else {
matchingAttachmentCount = null; matchingAttachmentCount = null;
UpdateFilterStatisticsText(); UpdateFilterStatisticsText();
matchingAttachmentCountComputer.Compute(() => state.Db.CountAttachments(filter)); matchingAttachmentCountComputer.Compute(() => db.CountAttachments(filter));
} }
} }

View File

@ -8,7 +8,6 @@ using Avalonia.Controls;
using DHT.Desktop.Common; using DHT.Desktop.Common;
using DHT.Desktop.Dialogs.CheckBox; using DHT.Desktop.Dialogs.CheckBox;
using DHT.Desktop.Dialogs.Message; using DHT.Desktop.Dialogs.Message;
using DHT.Server;
using DHT.Server.Data; using DHT.Server.Data;
using DHT.Server.Data.Filters; using DHT.Server.Data.Filters;
using DHT.Server.Database; using DHT.Server.Database;
@ -63,7 +62,7 @@ sealed class MessageFilterPanelModel : BaseModel, IDisposable {
} }
public HashSet<ulong> IncludedChannels { public HashSet<ulong> IncludedChannels {
get => includedChannels ?? state.Db.GetAllChannels().Select(static channel => channel.Id).ToHashSet(); get => includedChannels ?? db.GetAllChannels().Select(static channel => channel.Id).ToHashSet();
set => Change(ref includedChannels, value); set => Change(ref includedChannels, value);
} }
@ -73,7 +72,7 @@ sealed class MessageFilterPanelModel : BaseModel, IDisposable {
} }
public HashSet<ulong> IncludedUsers { public HashSet<ulong> IncludedUsers {
get => includedUsers ?? state.Db.GetAllUsers().Select(static user => user.Id).ToHashSet(); get => includedUsers ?? db.GetAllUsers().Select(static user => user.Id).ToHashSet();
set => Change(ref includedUsers, value); set => Change(ref includedUsers, value);
} }
@ -92,7 +91,7 @@ sealed class MessageFilterPanelModel : BaseModel, IDisposable {
} }
private readonly Window window; private readonly Window window;
private readonly State state; private readonly IDatabaseFile db;
private readonly string verb; private readonly string verb;
private readonly AsyncValueComputer<long> exportedMessageCountComputer; private readonly AsyncValueComputer<long> exportedMessageCountComputer;
@ -100,11 +99,11 @@ sealed class MessageFilterPanelModel : BaseModel, IDisposable {
private long? totalMessageCount; private long? totalMessageCount;
[Obsolete("Designer")] [Obsolete("Designer")]
public MessageFilterPanelModel() : this(null!, State.Dummy) {} public MessageFilterPanelModel() : this(null!, DummyDatabaseFile.Instance) {}
public MessageFilterPanelModel(Window window, State state, string verb = "Matches") { public MessageFilterPanelModel(Window window, IDatabaseFile db, string verb = "Matches") {
this.window = window; this.window = window;
this.state = state; this.db = db;
this.verb = verb; this.verb = verb;
this.exportedMessageCountComputer = AsyncValueComputer<long>.WithResultProcessor(SetExportedMessageCount).Build(); this.exportedMessageCountComputer = AsyncValueComputer<long>.WithResultProcessor(SetExportedMessageCount).Build();
@ -114,11 +113,11 @@ sealed class MessageFilterPanelModel : BaseModel, IDisposable {
UpdateUserFilterLabel(); UpdateUserFilterLabel();
PropertyChanged += OnPropertyChanged; PropertyChanged += OnPropertyChanged;
state.Db.Statistics.PropertyChanged += OnDbStatisticsChanged; db.Statistics.PropertyChanged += OnDbStatisticsChanged;
} }
public void Dispose() { public void Dispose() {
state.Db.Statistics.PropertyChanged -= OnDbStatisticsChanged; db.Statistics.PropertyChanged -= OnDbStatisticsChanged;
} }
private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) { private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) {
@ -137,7 +136,7 @@ sealed class MessageFilterPanelModel : BaseModel, IDisposable {
private void OnDbStatisticsChanged(object? sender, PropertyChangedEventArgs e) { private void OnDbStatisticsChanged(object? sender, PropertyChangedEventArgs e) {
if (e.PropertyName == nameof(DatabaseStatistics.TotalMessages)) { if (e.PropertyName == nameof(DatabaseStatistics.TotalMessages)) {
totalMessageCount = state.Db.Statistics.TotalMessages; totalMessageCount = db.Statistics.TotalMessages;
UpdateFilterStatistics(); UpdateFilterStatistics();
} }
else if (e.PropertyName == nameof(DatabaseStatistics.TotalChannels)) { else if (e.PropertyName == nameof(DatabaseStatistics.TotalChannels)) {
@ -158,7 +157,7 @@ sealed class MessageFilterPanelModel : BaseModel, IDisposable {
else { else {
exportedMessageCount = null; exportedMessageCount = null;
UpdateFilterStatisticsText(); UpdateFilterStatisticsText();
exportedMessageCountComputer.Compute(() => state.Db.CountMessages(filter)); exportedMessageCountComputer.Compute(() => db.CountMessages(filter));
} }
} }
@ -176,11 +175,11 @@ sealed class MessageFilterPanelModel : BaseModel, IDisposable {
} }
public async void OpenChannelFilterDialog() { public async void OpenChannelFilterDialog() {
var servers = state.Db.GetAllServers().ToDictionary(static server => server.Id); var servers = db.GetAllServers().ToDictionary(static server => server.Id);
var items = new List<CheckBoxItem<ulong>>(); var items = new List<CheckBoxItem<ulong>>();
var included = IncludedChannels; var included = IncludedChannels;
foreach (var channel in state.Db.GetAllChannels()) { foreach (var channel in db.GetAllChannels()) {
var channelId = channel.Id; var channelId = channel.Id;
var channelName = channel.Name; var channelName = channel.Name;
@ -224,7 +223,7 @@ sealed class MessageFilterPanelModel : BaseModel, IDisposable {
var items = new List<CheckBoxItem<ulong>>(); var items = new List<CheckBoxItem<ulong>>();
var included = IncludedUsers; var included = IncludedUsers;
foreach (var user in state.Db.GetAllUsers()) { foreach (var user in db.GetAllUsers()) {
var name = user.Name; var name = user.Name;
var discriminator = user.Discriminator; var discriminator = user.Discriminator;
@ -241,13 +240,13 @@ sealed class MessageFilterPanelModel : BaseModel, IDisposable {
} }
private void UpdateChannelFilterLabel() { private void UpdateChannelFilterLabel() {
long total = state.Db.Statistics.TotalChannels; long total = db.Statistics.TotalChannels;
long included = FilterByChannel ? IncludedChannels.Count : total; long included = FilterByChannel ? IncludedChannels.Count : total;
ChannelFilterLabel = "Selected " + included.Format() + " / " + total.Pluralize("channel") + "."; ChannelFilterLabel = "Selected " + included.Format() + " / " + total.Pluralize("channel") + ".";
} }
private void UpdateUserFilterLabel() { private void UpdateUserFilterLabel() {
long total = state.Db.Statistics.TotalUsers; long total = db.Statistics.TotalUsers;
long included = FilterByUser ? IncludedUsers.Count : total; long included = FilterByUser ? IncludedUsers.Count : total;
UserFilterLabel = "Selected " + included.Format() + " / " + total.Pluralize("user") + "."; UserFilterLabel = "Selected " + included.Format() + " / " + total.Pluralize("user") + ".";
} }

View File

@ -2,7 +2,7 @@ using System;
using Avalonia.Controls; using Avalonia.Controls;
using DHT.Desktop.Dialogs.Message; using DHT.Desktop.Dialogs.Message;
using DHT.Desktop.Server; using DHT.Desktop.Server;
using DHT.Server; using DHT.Server.Database;
using DHT.Server.Service; using DHT.Server.Service;
using DHT.Utils.Models; using DHT.Utils.Models;
@ -46,7 +46,7 @@ sealed class ServerConfigurationPanelModel : BaseModel, IDisposable {
private readonly ServerManager serverManager; private readonly ServerManager serverManager;
[Obsolete("Designer")] [Obsolete("Designer")]
public ServerConfigurationPanelModel() : this(null!, new ServerManager(State.Dummy)) {} public ServerConfigurationPanelModel() : this(null!, new ServerManager(DummyDatabaseFile.Instance)) {}
public ServerConfigurationPanelModel(Window window, ServerManager serverManager) { public ServerConfigurationPanelModel(Window window, ServerManager serverManager) {
this.window = window; this.window = window;

View File

@ -11,7 +11,7 @@
Width="800" Height="500" Width="800" Height="500"
MinWidth="520" MinHeight="300" MinWidth="520" MinHeight="300"
WindowStartupLocation="CenterScreen" WindowStartupLocation="CenterScreen"
Closing="OnClosing"> Closed="OnClosed">
<Design.DataContext> <Design.DataContext>
<main:MainWindowModel /> <main:MainWindowModel />

View File

@ -1,18 +1,14 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using DHT.Desktop.Main.Pages; using DHT.Desktop.Main.Pages;
using DHT.Utils.Logging;
using JetBrains.Annotations; using JetBrains.Annotations;
namespace DHT.Desktop.Main; namespace DHT.Desktop.Main;
[SuppressMessage("ReSharper", "MemberCanBeInternal")] [SuppressMessage("ReSharper", "MemberCanBeInternal")]
public sealed partial class MainWindow : Window { public sealed partial class MainWindow : Window {
private static readonly Log Log = Log.ForType<MainWindow>();
[UsedImplicitly] [UsedImplicitly]
public MainWindow() { public MainWindow() {
InitializeComponent(); InitializeComponent();
@ -24,24 +20,9 @@ public sealed partial class MainWindow : Window {
DataContext = new MainWindowModel(this, args); DataContext = new MainWindowModel(this, args);
} }
public async void OnClosing(object? sender, WindowClosingEventArgs e) { public async void OnClosed(object? sender, EventArgs e) {
e.Cancel = true;
Closing -= OnClosing;
try {
await Dispose();
} finally {
Close();
}
}
private async Task Dispose() {
if (DataContext is MainWindowModel model) { if (DataContext is MainWindowModel model) {
try { await model.DisposeAsync();
await model.DisposeAsync();
} catch (Exception ex) {
Log.Error("Caught exception while disposing window: " + ex);
}
} }
foreach (var temporaryFile in ViewerPageModel.TemporaryFiles) { foreach (var temporaryFile in ViewerPageModel.TemporaryFiles) {

View File

@ -7,7 +7,7 @@ using Avalonia.Controls;
using DHT.Desktop.Dialogs.Message; using DHT.Desktop.Dialogs.Message;
using DHT.Desktop.Main.Screens; using DHT.Desktop.Main.Screens;
using DHT.Desktop.Server; using DHT.Desktop.Server;
using DHT.Server; using DHT.Server.Database;
using DHT.Utils.Models; using DHT.Utils.Models;
namespace DHT.Desktop.Main; namespace DHT.Desktop.Main;
@ -27,7 +27,7 @@ sealed class MainWindowModel : BaseModel, IAsyncDisposable {
private readonly Window window; private readonly Window window;
private State? state; private IDatabaseFile? db;
[Obsolete("Designer")] [Obsolete("Designer")]
public MainWindowModel() : this(null!, Arguments.Empty) {} public MainWindowModel() : this(null!, Arguments.Empty) {}
@ -75,24 +75,21 @@ sealed class MainWindowModel : BaseModel, IAsyncDisposable {
if (e.PropertyName == nameof(welcomeScreenModel.Db)) { if (e.PropertyName == nameof(welcomeScreenModel.Db)) {
if (mainContentScreenModel != null) { if (mainContentScreenModel != null) {
mainContentScreenModel.DatabaseClosed -= MainContentScreenModelOnDatabaseClosed; mainContentScreenModel.DatabaseClosed -= MainContentScreenModelOnDatabaseClosed;
mainContentScreenModel.Dispose(); await mainContentScreenModel.DisposeAsync();
} }
if (state != null) { db?.Dispose();
await state.DisposeAsync(); db = welcomeScreenModel.Db;
}
state = welcomeScreenModel.Db == null ? null : new State(welcomeScreenModel.Db); if (db == null) {
if (state == null) {
Title = DefaultTitle; Title = DefaultTitle;
mainContentScreenModel = null; mainContentScreenModel = null;
mainContentScreen = null; mainContentScreen = null;
CurrentScreen = welcomeScreen; CurrentScreen = welcomeScreen;
} }
else { else {
Title = Path.GetFileName(state.Db.Path) + " - " + DefaultTitle; Title = Path.GetFileName(db.Path) + " - " + DefaultTitle;
mainContentScreenModel = new MainContentScreenModel(window, state); mainContentScreenModel = new MainContentScreenModel(window, db);
await mainContentScreenModel.Initialize(); await mainContentScreenModel.Initialize();
mainContentScreenModel.DatabaseClosed += MainContentScreenModelOnDatabaseClosed; mainContentScreenModel.DatabaseClosed += MainContentScreenModelOnDatabaseClosed;
mainContentScreen = new MainContentScreen { DataContext = mainContentScreenModel }; mainContentScreen = new MainContentScreen { DataContext = mainContentScreenModel };
@ -111,13 +108,13 @@ sealed class MainWindowModel : BaseModel, IAsyncDisposable {
} }
public async ValueTask DisposeAsync() { public async ValueTask DisposeAsync() {
mainContentScreenModel?.Dispose(); welcomeScreenModel.Dispose();
if (state != null) { if (mainContentScreenModel != null) {
await state.DisposeAsync(); await mainContentScreenModel.DisposeAsync();
state = null;
} }
welcomeScreenModel.Dispose(); db?.Dispose();
db = null;
} }
} }

View File

@ -3,7 +3,7 @@ using Avalonia.Controls;
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.Desktop.Server;
using DHT.Server; using DHT.Server.Database;
using DHT.Utils.Models; using DHT.Utils.Models;
namespace DHT.Desktop.Main.Pages; namespace DHT.Desktop.Main.Pages;
@ -12,14 +12,14 @@ sealed class AdvancedPageModel : BaseModel, IDisposable {
public ServerConfigurationPanelModel ServerConfigurationModel { get; } public ServerConfigurationPanelModel ServerConfigurationModel { get; }
private readonly Window window; private readonly Window window;
private readonly State state; private readonly IDatabaseFile db;
[Obsolete("Designer")] [Obsolete("Designer")]
public AdvancedPageModel() : this(null!, State.Dummy, new ServerManager(State.Dummy)) {} public AdvancedPageModel() : this(null!, DummyDatabaseFile.Instance, new ServerManager(DummyDatabaseFile.Instance)) {}
public AdvancedPageModel(Window window, State state, ServerManager serverManager) { public AdvancedPageModel(Window window, IDatabaseFile db, ServerManager serverManager) {
this.window = window; this.window = window;
this.state = state; this.db = db;
ServerConfigurationModel = new ServerConfigurationPanelModel(window, serverManager); ServerConfigurationModel = new ServerConfigurationPanelModel(window, serverManager);
} }
@ -33,7 +33,7 @@ sealed class AdvancedPageModel : BaseModel, IDisposable {
} }
public async void VacuumDatabase() { public async void VacuumDatabase() {
state.Db.Vacuum(); db.Vacuum();
await Dialog.ShowOk(window, "Vacuum Database", "Done."); await Dialog.ShowOk(window, "Vacuum Database", "Done.");
} }
} }

View File

@ -6,7 +6,6 @@ using System.Threading.Tasks;
using Avalonia.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;
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;
@ -17,8 +16,8 @@ using DHT.Utils.Tasks;
namespace DHT.Desktop.Main.Pages; namespace DHT.Desktop.Main.Pages;
sealed class AttachmentsPageModel : BaseModel, IDisposable { sealed class AttachmentsPageModel : BaseModel, IAsyncDisposable {
private static readonly DownloadItemFilter EnqueuedItemFilter = new () { private static readonly DownloadItemFilter EnqueuedItemFilter = new() {
IncludeStatuses = new HashSet<DownloadStatus> { IncludeStatuses = new HashSet<DownloadStatus> {
DownloadStatus.Enqueued, DownloadStatus.Enqueued,
DownloadStatus.Downloading DownloadStatus.Downloading
@ -27,7 +26,7 @@ sealed class AttachmentsPageModel : BaseModel, IDisposable {
private bool isThreadDownloadButtonEnabled = true; private bool isThreadDownloadButtonEnabled = true;
public string ToggleDownloadButtonText => IsDownloading ? "Stop Downloading" : "Start Downloading"; public string ToggleDownloadButtonText => downloader == null ? "Start Downloading" : "Stop Downloading";
public bool IsToggleDownloadButtonEnabled { public bool IsToggleDownloadButtonEnabled {
get => isThreadDownloadButtonEnabled; get => isThreadDownloadButtonEnabled;
@ -55,34 +54,34 @@ sealed class AttachmentsPageModel : BaseModel, IDisposable {
} }
} }
public bool IsDownloading => state.Downloader.IsDownloading; public bool IsDownloading => downloader != null;
public bool HasFailedDownloads => statisticsFailed.Items > 0; public bool HasFailedDownloads => statisticsFailed.Items > 0;
private readonly State state; private readonly IDatabaseFile db;
private readonly AsyncValueComputer<DownloadStatusStatistics>.Single downloadStatisticsComputer; private readonly AsyncValueComputer<DownloadStatusStatistics>.Single downloadStatisticsComputer;
private BackgroundDownloader? downloader;
private int doneItemsCount; private int doneItemsCount;
private int initialFinishedCount; private int initialFinishedCount;
private int? totalItemsToDownloadCount; private int? totalItemsToDownloadCount;
public AttachmentsPageModel() : this(State.Dummy) {} public AttachmentsPageModel() : this(DummyDatabaseFile.Instance) {}
public AttachmentsPageModel(State state) { public AttachmentsPageModel(IDatabaseFile db) {
this.state = state; this.db = db;
this.FilterModel = new AttachmentFilterPanelModel(db);
FilterModel = new AttachmentFilterPanelModel(state); this.downloadStatisticsComputer = AsyncValueComputer<DownloadStatusStatistics>.WithResultProcessor(UpdateStatistics).WithOutdatedResults().BuildWithComputer(db.GetDownloadStatusStatistics);
this.downloadStatisticsComputer.Recompute();
downloadStatisticsComputer = AsyncValueComputer<DownloadStatusStatistics>.WithResultProcessor(UpdateStatistics).WithOutdatedResults().BuildWithComputer(state.Db.GetDownloadStatusStatistics); db.Statistics.PropertyChanged += OnDbStatisticsChanged;
downloadStatisticsComputer.Recompute();
state.Db.Statistics.PropertyChanged += OnDbStatisticsChanged;
state.Downloader.OnItemFinished += DownloaderOnOnItemFinished;
} }
public void Dispose() { public async ValueTask DisposeAsync() {
state.Db.Statistics.PropertyChanged -= OnDbStatisticsChanged; db.Statistics.PropertyChanged -= OnDbStatisticsChanged;
state.Downloader.OnItemFinished -= DownloaderOnOnItemFinished;
FilterModel.Dispose(); FilterModel.Dispose();
await DisposeDownloader();
} }
private void OnDbStatisticsChanged(object? sender, PropertyChangedEventArgs e) { private void OnDbStatisticsChanged(object? sender, PropertyChangedEventArgs e) {
@ -102,7 +101,7 @@ sealed class AttachmentsPageModel : BaseModel, IDisposable {
private void EnqueueDownloadItems() { private void EnqueueDownloadItems() {
var filter = FilterModel.CreateFilter(); var filter = FilterModel.CreateFilter();
filter.DownloadItemRule = AttachmentFilter.DownloadItemRules.OnlyNotPresent; filter.DownloadItemRule = AttachmentFilter.DownloadItemRules.OnlyNotPresent;
state.Db.EnqueueDownloadItems(filter); db.EnqueueDownloadItems(filter);
downloadStatisticsComputer.Recompute(); downloadStatisticsComputer.Recompute();
} }
@ -147,24 +146,25 @@ sealed class AttachmentsPageModel : BaseModel, IDisposable {
} }
public async Task OnClickToggleDownload() { public async Task OnClickToggleDownload() {
if (IsDownloading) { if (downloader == null) {
initialFinishedCount = statisticsDownloaded.Items + statisticsFailed.Items;
EnqueueDownloadItems();
downloader = new BackgroundDownloader(db);
downloader.OnItemFinished += DownloaderOnOnItemFinished;
}
else {
IsToggleDownloadButtonEnabled = false; IsToggleDownloadButtonEnabled = false;
await state.Downloader.Stop(); await DisposeDownloader();
downloadStatisticsComputer.Recompute(); downloadStatisticsComputer.Recompute();
IsToggleDownloadButtonEnabled = true; IsToggleDownloadButtonEnabled = true;
state.Db.RemoveDownloadItems(EnqueuedItemFilter, FilterRemovalMode.RemoveMatching); db.RemoveDownloadItems(EnqueuedItemFilter, FilterRemovalMode.RemoveMatching);
doneItemsCount = 0; doneItemsCount = 0;
initialFinishedCount = 0; initialFinishedCount = 0;
totalItemsToDownloadCount = null; totalItemsToDownloadCount = null;
UpdateDownloadMessage(); UpdateDownloadMessage();
} }
else {
initialFinishedCount = statisticsDownloaded.Items + statisticsFailed.Items;
await state.Downloader.Start();
EnqueueDownloadItems();
}
OnPropertyChanged(nameof(ToggleDownloadButtonText)); OnPropertyChanged(nameof(ToggleDownloadButtonText));
OnPropertyChanged(nameof(IsDownloading)); OnPropertyChanged(nameof(IsDownloading));
@ -179,13 +179,22 @@ sealed class AttachmentsPageModel : BaseModel, IDisposable {
} }
}; };
state.Db.RemoveDownloadItems(allExceptFailedFilter, FilterRemovalMode.KeepMatching); db.RemoveDownloadItems(allExceptFailedFilter, FilterRemovalMode.KeepMatching);
if (IsDownloading) { if (IsDownloading) {
EnqueueDownloadItems(); EnqueueDownloadItems();
} }
} }
private async Task DisposeDownloader() {
if (downloader != null) {
downloader.OnItemFinished -= DownloaderOnOnItemFinished;
await downloader.Stop();
}
downloader = null;
}
public sealed class StatisticsRow { public sealed class StatisticsRow {
public string State { get; } public string State { get; }
public int Items { get; set; } public int Items { get; set; }

View File

@ -14,7 +14,6 @@ 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;
using DHT.Server;
using DHT.Server.Data; using DHT.Server.Data;
using DHT.Server.Database; using DHT.Server.Database;
using DHT.Server.Database.Import; using DHT.Server.Database.Import;
@ -34,11 +33,11 @@ sealed class DatabasePageModel : BaseModel {
private readonly Window window; private readonly Window window;
[Obsolete("Designer")] [Obsolete("Designer")]
public DatabasePageModel() : this(null!, State.Dummy) {} public DatabasePageModel() : this(null!, DummyDatabaseFile.Instance) {}
public DatabasePageModel(Window window, State state) { public DatabasePageModel(Window window, IDatabaseFile db) {
this.window = window; this.window = window;
this.Db = state.Db; this.Db = db;
} }
public async void OpenDatabaseFolder() { public async void OpenDatabaseFolder() {

View File

@ -6,8 +6,8 @@ using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using DHT.Desktop.Dialogs.Message; using DHT.Desktop.Dialogs.Message;
using DHT.Desktop.Dialogs.Progress; using DHT.Desktop.Dialogs.Progress;
using DHT.Server;
using DHT.Server.Data; using DHT.Server.Data;
using DHT.Server.Database;
using DHT.Server.Service; using DHT.Server.Service;
using DHT.Utils.Models; using DHT.Utils.Models;
@ -18,14 +18,14 @@ namespace DHT.Desktop.Main.Pages {
public string GenerateMessages { get; set; } = "0"; public string GenerateMessages { get; set; } = "0";
private readonly Window window; private readonly Window window;
private readonly State state; private readonly IDatabaseFile db;
[Obsolete("Designer")] [Obsolete("Designer")]
public DebugPageModel() : this(null!, State.Dummy) {} public DebugPageModel() : this(null!, DummyDatabaseFile.Instance) {}
public DebugPageModel(Window window, State state) { public DebugPageModel(Window window, IDatabaseFile db) {
this.window = window; this.window = window;
this.state = state; this.db = db;
} }
public async void OnClickAddRandomDataToDatabase() { public async void OnClickAddRandomDataToDatabase() {
@ -83,11 +83,11 @@ namespace DHT.Desktop.Main.Pages {
Discriminator = rand.Next(0, 9999).ToString(), Discriminator = rand.Next(0, 9999).ToString(),
}).ToArray(); }).ToArray();
state.Db.AddServer(server); db.AddServer(server);
state.Db.AddUsers(users); db.AddUsers(users);
foreach (var channel in channels) { foreach (var channel in channels) {
state.Db.AddChannel(channel); db.AddChannel(channel);
} }
var now = DateTimeOffset.Now; var now = DateTimeOffset.Now;
@ -117,7 +117,7 @@ namespace DHT.Desktop.Main.Pages {
}; };
}).ToArray(); }).ToArray();
state.Db.AddMessages(messages); db.AddMessages(messages);
messageCount -= BatchSize; messageCount -= BatchSize;
await callback.Update("Adding messages in batches of " + BatchSize, ++batchIndex, batchCount); await callback.Update("Adding messages in batches of " + BatchSize, ++batchIndex, batchCount);

View File

@ -13,8 +13,8 @@ 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.Desktop.Server;
using DHT.Server;
using DHT.Server.Data.Filters; using DHT.Server.Data.Filters;
using DHT.Server.Database;
using DHT.Server.Database.Export; using DHT.Server.Database.Export;
using DHT.Server.Database.Export.Strategy; using DHT.Server.Database.Export.Strategy;
using DHT.Utils.Models; using DHT.Utils.Models;
@ -38,16 +38,16 @@ sealed class ViewerPageModel : BaseModel, IDisposable {
public MessageFilterPanelModel FilterModel { get; } public MessageFilterPanelModel FilterModel { get; }
private readonly Window window; private readonly Window window;
private readonly State state; private readonly IDatabaseFile db;
[Obsolete("Designer")] [Obsolete("Designer")]
public ViewerPageModel() : this(null!, State.Dummy) {} public ViewerPageModel() : this(null!, DummyDatabaseFile.Instance) {}
public ViewerPageModel(Window window, State state) { public ViewerPageModel(Window window, IDatabaseFile db) {
this.window = window; this.window = window;
this.state = state; this.db = db;
FilterModel = new MessageFilterPanelModel(window, state, "Will export"); FilterModel = new MessageFilterPanelModel(window, db, "Will export");
FilterModel.FilterPropertyChanged += OnFilterPropertyChanged; FilterModel.FilterPropertyChanged += OnFilterPropertyChanged;
} }
@ -72,7 +72,7 @@ sealed class ViewerPageModel : BaseModel, IDisposable {
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, strategy, state.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;
@ -98,7 +98,7 @@ sealed class ViewerPageModel : BaseModel, IDisposable {
public async void OnClickOpenViewer() { public async void OnClickOpenViewer() {
string rootPath = Path.Combine(Path.GetTempPath(), "DiscordHistoryTracker"); string rootPath = Path.Combine(Path.GetTempPath(), "DiscordHistoryTracker");
string filenameBase = Path.GetFileNameWithoutExtension(state.Db.Path) + "-" + DateTime.Now.ToString("yyyy-MM-dd"); string filenameBase = Path.GetFileNameWithoutExtension(db.Path) + "-" + DateTime.Now.ToString("yyyy-MM-dd");
string fullPath = Path.Combine(rootPath, filenameBase + ".html"); string fullPath = Path.Combine(rootPath, filenameBase + ".html");
int counter = 0; int counter = 0;
@ -123,8 +123,8 @@ sealed class ViewerPageModel : BaseModel, IDisposable {
string? path = await window.StorageProvider.SaveFile(new FilePickerSaveOptions { string? path = await window.StorageProvider.SaveFile(new FilePickerSaveOptions {
Title = "Save Viewer", Title = "Save Viewer",
FileTypeChoices = ViewerFileTypes, FileTypeChoices = ViewerFileTypes,
SuggestedFileName = Path.GetFileNameWithoutExtension(state.Db.Path) + ".html", SuggestedFileName = Path.GetFileNameWithoutExtension(db.Path) + ".html",
SuggestedStartLocation = await FileDialogs.GetSuggestedStartLocation(window, Path.GetDirectoryName(state.Db.Path)), SuggestedStartLocation = await FileDialogs.GetSuggestedStartLocation(window, Path.GetDirectoryName(db.Path)),
}); });
if (path != null) { if (path != null) {
@ -136,13 +136,13 @@ sealed class ViewerPageModel : BaseModel, IDisposable {
var filter = FilterModel.CreateFilter(); var filter = FilterModel.CreateFilter();
if (DatabaseToolFilterModeKeep) { if (DatabaseToolFilterModeKeep) {
if (DialogResult.YesNo.Yes == await Dialog.ShowYesNo(window, "Keep Matching Messages in This Database", state.Db.CountMessages(filter).Pluralize("message") + " will be kept, and the rest will be removed from this database. This action cannot be undone. Proceed?")) { if (DialogResult.YesNo.Yes == await Dialog.ShowYesNo(window, "Keep Matching Messages in This Database", db.CountMessages(filter).Pluralize("message") + " will be kept, and the rest will be removed from this database. This action cannot be undone. Proceed?")) {
state.Db.RemoveMessages(filter, FilterRemovalMode.KeepMatching); db.RemoveMessages(filter, FilterRemovalMode.KeepMatching);
} }
} }
else if (DatabaseToolFilterModeRemove) { else if (DatabaseToolFilterModeRemove) {
if (DialogResult.YesNo.Yes == await Dialog.ShowYesNo(window, "Remove Matching Messages in This Database", state.Db.CountMessages(filter).Pluralize("message") + " will be removed from this database. This action cannot be undone. Proceed?")) { if (DialogResult.YesNo.Yes == await Dialog.ShowYesNo(window, "Remove Matching Messages in This Database", db.CountMessages(filter).Pluralize("message") + " will be removed from this database. This action cannot be undone. Proceed?")) {
state.Db.RemoveMessages(filter, FilterRemovalMode.RemoveMatching); db.RemoveMessages(filter, FilterRemovalMode.RemoveMatching);
} }
} }
} }

View File

@ -5,13 +5,13 @@ using DHT.Desktop.Dialogs.Message;
using DHT.Desktop.Main.Controls; using DHT.Desktop.Main.Controls;
using DHT.Desktop.Main.Pages; using DHT.Desktop.Main.Pages;
using DHT.Desktop.Server; using DHT.Desktop.Server;
using DHT.Server; 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 : IAsyncDisposable {
private static readonly Log Log = Log.ForType<MainContentScreenModel>(); private static readonly Log Log = Log.ForType<MainContentScreenModel>();
public DatabasePage DatabasePage { get; } public DatabasePage DatabasePage { get; }
@ -53,37 +53,37 @@ sealed class MainContentScreenModel : IDisposable {
private readonly ServerManager serverManager; private readonly ServerManager serverManager;
[Obsolete("Designer")] [Obsolete("Designer")]
public MainContentScreenModel() : this(null!, State.Dummy) {} public MainContentScreenModel() : this(null!, DummyDatabaseFile.Instance) {}
public MainContentScreenModel(Window window, State state) { public MainContentScreenModel(Window window, IDatabaseFile db) {
this.window = window; this.window = window;
this.serverManager = new ServerManager(state); this.serverManager = new ServerManager(db);
ServerLauncher.ServerManagementExceptionCaught += ServerLauncherOnServerManagementExceptionCaught; ServerLauncher.ServerManagementExceptionCaught += ServerLauncherOnServerManagementExceptionCaught;
DatabasePageModel = new DatabasePageModel(window, state); DatabasePageModel = new DatabasePageModel(window, db);
DatabasePage = new DatabasePage { DataContext = DatabasePageModel }; DatabasePage = new DatabasePage { DataContext = DatabasePageModel };
TrackingPageModel = new TrackingPageModel(window); TrackingPageModel = new TrackingPageModel(window);
TrackingPage = new TrackingPage { DataContext = TrackingPageModel }; TrackingPage = new TrackingPage { DataContext = TrackingPageModel };
AttachmentsPageModel = new AttachmentsPageModel(state); AttachmentsPageModel = new AttachmentsPageModel(db);
AttachmentsPage = new AttachmentsPage { DataContext = AttachmentsPageModel }; AttachmentsPage = new AttachmentsPage { DataContext = AttachmentsPageModel };
ViewerPageModel = new ViewerPageModel(window, state); ViewerPageModel = new ViewerPageModel(window, db);
ViewerPage = new ViewerPage { DataContext = ViewerPageModel }; ViewerPage = new ViewerPage { DataContext = ViewerPageModel };
AdvancedPageModel = new AdvancedPageModel(window, state, serverManager); AdvancedPageModel = new AdvancedPageModel(window, db, serverManager);
AdvancedPage = new AdvancedPage { DataContext = AdvancedPageModel }; AdvancedPage = new AdvancedPage { DataContext = AdvancedPageModel };
#if DEBUG #if DEBUG
DebugPageModel = new DebugPageModel(window, state); DebugPageModel = new DebugPageModel(window, db);
DebugPage = new DebugPage { DataContext = DebugPageModel }; DebugPage = new DebugPage { DataContext = DebugPageModel };
#else #else
DebugPage = null; DebugPage = null;
#endif #endif
StatusBarModel = new StatusBarModel(state.Db.Statistics); StatusBarModel = new StatusBarModel(db.Statistics);
AdvancedPageModel.ServerConfigurationModel.ServerStatusChanged += OnServerStatusChanged; AdvancedPageModel.ServerConfigurationModel.ServerStatusChanged += OnServerStatusChanged;
DatabaseClosed += OnDatabaseClosed; DatabaseClosed += OnDatabaseClosed;
@ -97,9 +97,9 @@ sealed class MainContentScreenModel : IDisposable {
serverManager.Launch(); serverManager.Launch();
} }
public void Dispose() { public async ValueTask DisposeAsync() {
ServerLauncher.ServerManagementExceptionCaught -= ServerLauncherOnServerManagementExceptionCaught; ServerLauncher.ServerManagementExceptionCaught -= ServerLauncherOnServerManagementExceptionCaught;
AttachmentsPageModel.Dispose(); await AttachmentsPageModel.DisposeAsync();
ViewerPageModel.Dispose(); ViewerPageModel.Dispose();
serverManager.Dispose(); serverManager.Dispose();
} }

View File

@ -1,5 +1,5 @@
using System; using System;
using DHT.Server; using DHT.Server.Database;
using DHT.Server.Service; using DHT.Server.Service;
namespace DHT.Desktop.Server; namespace DHT.Desktop.Server;
@ -12,10 +12,10 @@ sealed class ServerManager : IDisposable {
public bool IsRunning => ServerLauncher.IsRunning; public bool IsRunning => ServerLauncher.IsRunning;
private readonly State state; private readonly IDatabaseFile db;
public ServerManager(State state) { public ServerManager(IDatabaseFile db) {
if (state != State.Dummy) { if (db != DummyDatabaseFile.Instance) {
if (instance != null) { if (instance != null) {
throw new InvalidOperationException("Only one instance of ServerManager can exist at the same time!"); throw new InvalidOperationException("Only one instance of ServerManager can exist at the same time!");
} }
@ -23,11 +23,11 @@ sealed class ServerManager : IDisposable {
instance = this; instance = this;
} }
this.state = state; this.db = db;
} }
public void Launch() { public void Launch() {
ServerLauncher.Relaunch(Port, Token, state.Db); ServerLauncher.Relaunch(Port, Token, db);
} }
public void Relaunch(ushort port, string token) { public void Relaunch(ushort port, string token) {

View File

@ -11,14 +11,14 @@ using DHT.Utils.Tasks;
namespace DHT.Server.Download; namespace DHT.Server.Download;
sealed class DownloaderTask : BaseModel { public sealed class BackgroundDownloader : BaseModel {
private static readonly Log Log = Log.ForType<DownloaderTask>(); private static readonly Log Log = Log.ForType<BackgroundDownloader>();
private const int DownloadTasks = 4; private const int DownloadTasks = 4;
private const int QueueSize = 25; private const int QueueSize = 25;
private const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"; private const string UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
internal event EventHandler<DownloadItem>? OnItemFinished; public event EventHandler<DownloadItem>? OnItemFinished;
private readonly Channel<DownloadItem> downloadQueue = Channel.CreateBounded<DownloadItem>(new BoundedChannelOptions(QueueSize) { private readonly Channel<DownloadItem> downloadQueue = Channel.CreateBounded<DownloadItem>(new BoundedChannelOptions(QueueSize) {
SingleReader = false, SingleReader = false,
@ -34,7 +34,7 @@ sealed class DownloaderTask : BaseModel {
private readonly Task queueWriterTask; private readonly Task queueWriterTask;
private readonly Task[] downloadTasks; private readonly Task[] downloadTasks;
internal DownloaderTask(IDatabaseFile db) { public BackgroundDownloader(IDatabaseFile db) {
this.cancellationToken = cancellationTokenSource.Token; this.cancellationToken = cancellationTokenSource.Token;
this.db = db; this.db = db;
this.queueWriterTask = Task.Run(RunQueueWriterTask); this.queueWriterTask = Task.Run(RunQueueWriterTask);
@ -56,7 +56,7 @@ sealed class DownloaderTask : BaseModel {
} }
private async Task RunDownloadTask(int taskIndex) { private async Task RunDownloadTask(int taskIndex) {
var log = Log.ForType<DownloaderTask>("Task " + taskIndex); var log = Log.ForType<BackgroundDownloader>("Task " + taskIndex);
var client = new HttpClient(); var client = new HttpClient();
client.DefaultRequestHeaders.UserAgent.ParseAdd(UserAgent); client.DefaultRequestHeaders.UserAgent.ParseAdd(UserAgent);
@ -87,7 +87,7 @@ sealed class DownloaderTask : BaseModel {
} }
} }
internal async Task Stop() { public async Task Stop() {
try { try {
await cancellationTokenSource.CancelAsync(); await cancellationTokenSource.CancelAsync();
} catch (Exception) { } catch (Exception) {

View File

@ -1,49 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using DHT.Server.Database;
namespace DHT.Server.Download;
public sealed class Downloader {
private DownloaderTask? current;
public bool IsDownloading => current != null;
public event EventHandler<DownloadItem>? OnItemFinished;
private readonly IDatabaseFile db;
private readonly SemaphoreSlim semaphore = new (1, 1);
internal Downloader(IDatabaseFile db) {
this.db = db;
}
public async Task Start() {
await semaphore.WaitAsync();
try {
if (current == null) {
current = new DownloaderTask(db);
current.OnItemFinished += DelegateOnItemFinished;
}
} finally {
semaphore.Release();
}
}
public async Task Stop() {
await semaphore.WaitAsync();
try {
if (current != null) {
await current.Stop();
current.OnItemFinished -= DelegateOnItemFinished;
current = null;
}
} finally {
semaphore.Release();
}
}
private void DelegateOnItemFinished(object? sender, DownloadItem e) {
OnItemFinished?.Invoke(this, e);
}
}

View File

@ -1,23 +0,0 @@
using System;
using System.Threading.Tasks;
using DHT.Server.Database;
using DHT.Server.Download;
namespace DHT.Server;
public sealed class State : IAsyncDisposable {
public static State Dummy { get; } = new (DummyDatabaseFile.Instance);
public IDatabaseFile Db { get; }
public Downloader Downloader { get; }
public State(IDatabaseFile db) {
Db = db;
Downloader = new Downloader(db);
}
public async ValueTask DisposeAsync() {
await Downloader.Stop();
Db.Dispose();
}
}