mirror of
https://github.com/chylex/Discord-History-Tracker.git
synced 2024-11-25 14:42:44 +01:00
Compare commits
2 Commits
2875f5943b
...
031d521402
Author | SHA1 | Date | |
---|---|---|---|
031d521402 | |||
0131f8cb50 |
@ -21,6 +21,7 @@
|
|||||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.6" />
|
<PackageReference Include="Avalonia.Desktop" Version="11.0.6" />
|
||||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.6" Condition=" '$(Configuration)' == 'Debug' " />
|
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.6" Condition=" '$(Configuration)' == 'Debug' " />
|
||||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.6" />
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.0.6" />
|
||||||
|
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.6" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.6" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Threading;
|
using System.Reactive.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Avalonia.Threading;
|
using Avalonia.ReactiveUI;
|
||||||
using DHT.Desktop.Common;
|
using DHT.Desktop.Common;
|
||||||
using DHT.Desktop.Main.Controls;
|
using DHT.Desktop.Main.Controls;
|
||||||
using DHT.Server;
|
using DHT.Server;
|
||||||
@ -11,7 +11,6 @@ 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.Database;
|
using DHT.Server.Database;
|
||||||
using DHT.Server.Download;
|
|
||||||
using DHT.Utils.Models;
|
using DHT.Utils.Models;
|
||||||
using DHT.Utils.Tasks;
|
using DHT.Utils.Tasks;
|
||||||
|
|
||||||
@ -60,7 +59,8 @@ sealed class AttachmentsPageModel : BaseModel, IDisposable {
|
|||||||
|
|
||||||
private readonly State state;
|
private readonly State state;
|
||||||
private readonly AsyncValueComputer<DownloadStatusStatistics>.Single downloadStatisticsComputer;
|
private readonly AsyncValueComputer<DownloadStatusStatistics>.Single downloadStatisticsComputer;
|
||||||
|
|
||||||
|
private IDisposable? finishedItemsSubscription;
|
||||||
private int doneItemsCount;
|
private int doneItemsCount;
|
||||||
private int initialFinishedCount;
|
private int initialFinishedCount;
|
||||||
private int? totalItemsToDownloadCount;
|
private int? totalItemsToDownloadCount;
|
||||||
@ -69,19 +69,18 @@ sealed class AttachmentsPageModel : BaseModel, IDisposable {
|
|||||||
|
|
||||||
public AttachmentsPageModel(State state) {
|
public AttachmentsPageModel(State state) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
|
|
||||||
FilterModel = new AttachmentFilterPanelModel(state);
|
FilterModel = new AttachmentFilterPanelModel(state);
|
||||||
|
|
||||||
downloadStatisticsComputer = AsyncValueComputer<DownloadStatusStatistics>.WithResultProcessor(UpdateStatistics).WithOutdatedResults().BuildWithComputer(state.Db.GetDownloadStatusStatistics);
|
downloadStatisticsComputer = AsyncValueComputer<DownloadStatusStatistics>.WithResultProcessor(UpdateStatistics).WithOutdatedResults().BuildWithComputer(state.Db.GetDownloadStatusStatistics);
|
||||||
downloadStatisticsComputer.Recompute();
|
downloadStatisticsComputer.Recompute();
|
||||||
|
|
||||||
state.Db.Statistics.PropertyChanged += OnDbStatisticsChanged;
|
state.Db.Statistics.PropertyChanged += OnDbStatisticsChanged;
|
||||||
state.Downloader.OnItemFinished += DownloaderOnOnItemFinished;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
state.Db.Statistics.PropertyChanged -= OnDbStatisticsChanged;
|
state.Db.Statistics.PropertyChanged -= OnDbStatisticsChanged;
|
||||||
state.Downloader.OnItemFinished -= DownloaderOnOnItemFinished;
|
finishedItemsSubscription?.Dispose();
|
||||||
FilterModel.Dispose();
|
FilterModel.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,19 +138,22 @@ sealed class AttachmentsPageModel : BaseModel, IDisposable {
|
|||||||
OnPropertyChanged(nameof(DownloadProgress));
|
OnPropertyChanged(nameof(DownloadProgress));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DownloaderOnOnItemFinished(object? sender, DownloadItem e) {
|
private void OnItemsFinished(int finishedItemCount) {
|
||||||
Interlocked.Increment(ref doneItemsCount);
|
doneItemsCount += finishedItemCount;
|
||||||
|
UpdateDownloadMessage();
|
||||||
Dispatcher.UIThread.Invoke(UpdateDownloadMessage);
|
|
||||||
downloadStatisticsComputer.Recompute();
|
downloadStatisticsComputer.Recompute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnClickToggleDownload() {
|
public async Task OnClickToggleDownload() {
|
||||||
|
IsToggleDownloadButtonEnabled = false;
|
||||||
|
|
||||||
if (IsDownloading) {
|
if (IsDownloading) {
|
||||||
IsToggleDownloadButtonEnabled = false;
|
|
||||||
await state.Downloader.Stop();
|
await state.Downloader.Stop();
|
||||||
|
|
||||||
|
finishedItemsSubscription?.Dispose();
|
||||||
|
finishedItemsSubscription = null;
|
||||||
|
|
||||||
downloadStatisticsComputer.Recompute();
|
downloadStatisticsComputer.Recompute();
|
||||||
IsToggleDownloadButtonEnabled = true;
|
|
||||||
|
|
||||||
state.Db.RemoveDownloadItems(EnqueuedItemFilter, FilterRemovalMode.RemoveMatching);
|
state.Db.RemoveDownloadItems(EnqueuedItemFilter, FilterRemovalMode.RemoveMatching);
|
||||||
|
|
||||||
@ -161,13 +163,22 @@ sealed class AttachmentsPageModel : BaseModel, IDisposable {
|
|||||||
UpdateDownloadMessage();
|
UpdateDownloadMessage();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
var finishedItems = await state.Downloader.Start();
|
||||||
|
|
||||||
initialFinishedCount = statisticsDownloaded.Items + statisticsFailed.Items;
|
initialFinishedCount = statisticsDownloaded.Items + statisticsFailed.Items;
|
||||||
await state.Downloader.Start();
|
finishedItemsSubscription = finishedItems.Select(static _ => true)
|
||||||
|
.Buffer(TimeSpan.FromMilliseconds(100))
|
||||||
|
.Select(static items => items.Count)
|
||||||
|
.Where(static items => items > 0)
|
||||||
|
.ObserveOn(AvaloniaScheduler.Instance)
|
||||||
|
.Subscribe(OnItemsFinished);
|
||||||
|
|
||||||
EnqueueDownloadItems();
|
EnqueueDownloadItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
OnPropertyChanged(nameof(ToggleDownloadButtonText));
|
OnPropertyChanged(nameof(ToggleDownloadButtonText));
|
||||||
OnPropertyChanged(nameof(IsDownloading));
|
OnPropertyChanged(nameof(IsDownloading));
|
||||||
|
IsToggleDownloadButtonEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnClickRetryFailedDownloads() {
|
public void OnClickRetryFailedDownloads() {
|
||||||
|
@ -9,8 +9,6 @@ public sealed class Downloader {
|
|||||||
private DownloaderTask? current;
|
private DownloaderTask? current;
|
||||||
public bool IsDownloading => current != null;
|
public bool IsDownloading => current != null;
|
||||||
|
|
||||||
public event EventHandler<DownloadItem>? OnItemFinished;
|
|
||||||
|
|
||||||
private readonly IDatabaseFile db;
|
private readonly IDatabaseFile db;
|
||||||
private readonly SemaphoreSlim semaphore = new (1, 1);
|
private readonly SemaphoreSlim semaphore = new (1, 1);
|
||||||
|
|
||||||
@ -18,13 +16,11 @@ public sealed class Downloader {
|
|||||||
this.db = db;
|
this.db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Start() {
|
public async Task<IObservable<DownloadItem>> Start() {
|
||||||
await semaphore.WaitAsync();
|
await semaphore.WaitAsync();
|
||||||
try {
|
try {
|
||||||
if (current == null) {
|
current ??= new DownloaderTask(db);
|
||||||
current = new DownloaderTask(db);
|
return current.FinishedItems;
|
||||||
current.OnItemFinished += DelegateOnItemFinished;
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
semaphore.Release();
|
semaphore.Release();
|
||||||
}
|
}
|
||||||
@ -34,16 +30,11 @@ public sealed class Downloader {
|
|||||||
await semaphore.WaitAsync();
|
await semaphore.WaitAsync();
|
||||||
try {
|
try {
|
||||||
if (current != null) {
|
if (current != null) {
|
||||||
await current.Stop();
|
await current.DisposeAsync();
|
||||||
current.OnItemFinished -= DelegateOnItemFinished;
|
|
||||||
current = null;
|
current = null;
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
semaphore.Release();
|
semaphore.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DelegateOnItemFinished(object? sender, DownloadItem e) {
|
|
||||||
OnItemFinished?.Invoke(this, e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,23 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Reactive.Subjects;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DHT.Server.Database;
|
using DHT.Server.Database;
|
||||||
using DHT.Utils.Logging;
|
using DHT.Utils.Logging;
|
||||||
using DHT.Utils.Models;
|
|
||||||
using DHT.Utils.Tasks;
|
using DHT.Utils.Tasks;
|
||||||
|
|
||||||
namespace DHT.Server.Download;
|
namespace DHT.Server.Download;
|
||||||
|
|
||||||
sealed class DownloaderTask : BaseModel {
|
sealed class DownloaderTask : IAsyncDisposable {
|
||||||
private static readonly Log Log = Log.ForType<DownloaderTask>();
|
private static readonly Log Log = Log.ForType<DownloaderTask>();
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
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,
|
||||||
SingleWriter = true,
|
SingleWriter = true,
|
||||||
@ -31,12 +29,16 @@ sealed class DownloaderTask : BaseModel {
|
|||||||
private readonly CancellationToken cancellationToken;
|
private readonly CancellationToken cancellationToken;
|
||||||
|
|
||||||
private readonly IDatabaseFile db;
|
private readonly IDatabaseFile db;
|
||||||
|
private readonly Subject<DownloadItem> finishedItemPublisher = new ();
|
||||||
|
|
||||||
private readonly Task queueWriterTask;
|
private readonly Task queueWriterTask;
|
||||||
private readonly Task[] downloadTasks;
|
private readonly Task[] downloadTasks;
|
||||||
|
|
||||||
|
public IObservable<DownloadItem> FinishedItems => finishedItemPublisher;
|
||||||
|
|
||||||
internal DownloaderTask(IDatabaseFile db) {
|
internal DownloaderTask(IDatabaseFile db) {
|
||||||
this.cancellationToken = cancellationTokenSource.Token;
|
|
||||||
this.db = db;
|
this.db = db;
|
||||||
|
this.cancellationToken = cancellationTokenSource.Token;
|
||||||
this.queueWriterTask = Task.Run(RunQueueWriterTask);
|
this.queueWriterTask = Task.Run(RunQueueWriterTask);
|
||||||
this.downloadTasks = Enumerable.Range(1, DownloadTasks).Select(taskIndex => Task.Run(() => RunDownloadTask(taskIndex))).ToArray();
|
this.downloadTasks = Enumerable.Range(1, DownloadTasks).Select(taskIndex => Task.Run(() => RunDownloadTask(taskIndex))).ToArray();
|
||||||
}
|
}
|
||||||
@ -79,7 +81,7 @@ sealed class DownloaderTask : BaseModel {
|
|||||||
log.Error(e);
|
log.Error(e);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
OnItemFinished?.Invoke(this, item);
|
finishedItemPublisher.OnNext(item);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.Error("Caught exception in event handler: " + e);
|
log.Error("Caught exception in event handler: " + e);
|
||||||
}
|
}
|
||||||
@ -87,7 +89,7 @@ sealed class DownloaderTask : BaseModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task Stop() {
|
public async ValueTask DisposeAsync() {
|
||||||
try {
|
try {
|
||||||
await cancellationTokenSource.CancelAsync();
|
await cancellationTokenSource.CancelAsync();
|
||||||
} catch (Exception) {
|
} catch (Exception) {
|
||||||
@ -102,6 +104,7 @@ sealed class DownloaderTask : BaseModel {
|
|||||||
await Task.WhenAll(downloadTasks).WaitIgnoringCancellation();
|
await Task.WhenAll(downloadTasks).WaitIgnoringCancellation();
|
||||||
} finally {
|
} finally {
|
||||||
cancellationTokenSource.Dispose();
|
cancellationTokenSource.Dispose();
|
||||||
|
finishedItemPublisher.OnCompleted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.0" />
|
||||||
|
<PackageReference Include="System.Reactive" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -21,6 +21,7 @@ public sealed class State : IAsyncDisposable {
|
|||||||
|
|
||||||
public async ValueTask DisposeAsync() {
|
public async ValueTask DisposeAsync() {
|
||||||
await Downloader.Stop();
|
await Downloader.Stop();
|
||||||
|
await Server.Stop();
|
||||||
Db.Dispose();
|
Db.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user