From e10ef1a35762db4383de9ed2de8ce1526cf488e2 Mon Sep 17 00:00:00 2001 From: Poker Date: Wed, 17 Jul 2024 00:28:34 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=B8=8B=E8=BD=BD=E9=80=BB?= =?UTF-8?q?=E8=BE=91=EF=BC=8C=E6=8F=90=E7=A4=BA=E4=B8=8B=E8=BD=BD=E8=BF=9B?= =?UTF-8?q?=E5=BA=A6=20(#513)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修复颜色不更新bug * 优化下载组逻辑,允许排队时暂停 * 更新信号灯控件 * 仅重试失败项 --- src/Pixeval.Controls/AppButtonItem.xaml | 4 +- src/Pixeval.Controls/C.cs | 2 + src/Pixeval.Controls/DigitalSignalItem.xaml | 19 +++ .../DigitalSignalItem.xaml.cs | 11 ++ .../PersonView/PersonView.xaml | 2 +- src/Pixeval.Controls/Pixeval.Controls.csproj | 16 ++- src/Pixeval.CoreApi/Pixeval.CoreApi.csproj | 2 +- src/Pixeval/AppManagement/AppSettings.cs | 2 +- src/Pixeval/Controls/Comment/CommentItem.xaml | 2 +- .../Controls/Download/DownloadItem.xaml | 19 ++- .../Controls/Download/DownloadItem.xaml.cs | 18 +-- .../Download/DownloadItemViewModel.cs | 27 +++-- .../Controls/Illustrator/IllustratorItem.xaml | 4 +- .../Controls/Settings/ColorSettingsCard.xaml | 2 +- .../Settings/ColorSettingsCard.xaml.cs | 3 + src/Pixeval/Download/DownloadManager.cs | 110 ++++++++---------- .../Download/Models/DownloadTaskGroup.cs | 78 ++++++++----- .../Download/Models/IDownloadTaskGroup.cs | 14 +++ .../Download/Models/ImageDownloadTask.cs | 19 +-- .../Download/Models/MangaDownloadTaskGroup.cs | 10 +- .../Models/SingleImageDownloadTaskGroup.cs | 26 ++++- src/Pixeval/Pages/MainPage.xaml | 2 +- src/Pixeval/Pixeval.csproj | 10 +- src/Pixeval/Themes/Colors.xaml | 24 ---- 24 files changed, 268 insertions(+), 158 deletions(-) create mode 100644 src/Pixeval.Controls/DigitalSignalItem.xaml create mode 100644 src/Pixeval.Controls/DigitalSignalItem.xaml.cs diff --git a/src/Pixeval.Controls/AppButtonItem.xaml b/src/Pixeval.Controls/AppButtonItem.xaml index 0a0f627b..22e47ee1 100644 --- a/src/Pixeval.Controls/AppButtonItem.xaml +++ b/src/Pixeval.Controls/AppButtonItem.xaml @@ -16,14 +16,14 @@ HorizontalAlignment="Left" controls:DockPanel.Dock="Top" FontWeight="SemiBold" - Foreground="{StaticResource TextSecondaryAccentColor}" + Foreground="{StaticResource TextFillColorSecondaryBrush}" Style="{StaticResource BaseTextBlockStyle}" Text="{x:Bind Title, Mode=OneWay}" /> value is not 0; + public static Visibility IsNotZeroToVisibility(int value) => value is not 0 ? Visibility.Visible : Visibility.Collapsed; + public static Visibility IsNotZeroDToVisibility(double value) => value is not 0 ? Visibility.Visible : Visibility.Collapsed; public static unsafe Color ToAlphaColor(uint color) diff --git a/src/Pixeval.Controls/DigitalSignalItem.xaml b/src/Pixeval.Controls/DigitalSignalItem.xaml new file mode 100644 index 00000000..4d234da8 --- /dev/null +++ b/src/Pixeval.Controls/DigitalSignalItem.xaml @@ -0,0 +1,19 @@ + + + + diff --git a/src/Pixeval.Controls/DigitalSignalItem.xaml.cs b/src/Pixeval.Controls/DigitalSignalItem.xaml.cs new file mode 100644 index 00000000..09f04662 --- /dev/null +++ b/src/Pixeval.Controls/DigitalSignalItem.xaml.cs @@ -0,0 +1,11 @@ +using Microsoft.UI.Xaml.Media; +using WinUI3Utilities.Attributes; + +namespace Pixeval.Controls; + +[DependencyProperty("Text")] +[DependencyProperty("Fill")] +public sealed partial class DigitalSignalItem +{ + public DigitalSignalItem() => InitializeComponent(); +} diff --git a/src/Pixeval.Controls/PersonView/PersonView.xaml b/src/Pixeval.Controls/PersonView/PersonView.xaml index 5a12505d..58ab0189 100644 --- a/src/Pixeval.Controls/PersonView/PersonView.xaml +++ b/src/Pixeval.Controls/PersonView/PersonView.xaml @@ -30,7 +30,7 @@ Text="{x:Bind PersonNickname, Mode=OneWay}" /> diff --git a/src/Pixeval.Controls/Pixeval.Controls.csproj b/src/Pixeval.Controls/Pixeval.Controls.csproj index 14439de6..cdda6971 100644 --- a/src/Pixeval.Controls/Pixeval.Controls.csproj +++ b/src/Pixeval.Controls/Pixeval.Controls.csproj @@ -16,14 +16,14 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + @@ -49,4 +49,16 @@ + + + + + + + + + + + + diff --git a/src/Pixeval.CoreApi/Pixeval.CoreApi.csproj b/src/Pixeval.CoreApi/Pixeval.CoreApi.csproj index 7d5459ea..0e840309 100644 --- a/src/Pixeval.CoreApi/Pixeval.CoreApi.csproj +++ b/src/Pixeval.CoreApi/Pixeval.CoreApi.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/Pixeval/AppManagement/AppSettings.cs b/src/Pixeval/AppManagement/AppSettings.cs index d3240e43..7691512a 100644 --- a/src/Pixeval/AppManagement/AppSettings.cs +++ b/src/Pixeval/AppManagement/AppSettings.cs @@ -88,7 +88,7 @@ public partial record AppSettings() : IWindowSettings /// The max download tasks that are allowed to run concurrently /// [SettingsEntry(Symbol.DeveloperBoardLightning, nameof(MaxDownloadConcurrencyLevelEntryHeader), nameof(MaxDownloadConcurrencyLevelEntryDescription))] - public int MaxDownloadTaskConcurrencyLevel { get; set; } = Environment.ProcessorCount / 2; + public int MaxDownloadTaskConcurrencyLevel { get; set; } = Environment.ProcessorCount / 4; [SettingsEntry(Symbol.SaveEdit, nameof(DownloadWhenBookmarkedEntryHeader), nameof(DownloadWhenBookmarkedEntryDescription))] public bool DownloadWhenBookmarked { get; set; } diff --git a/src/Pixeval/Controls/Comment/CommentItem.xaml b/src/Pixeval/Controls/Comment/CommentItem.xaml index 57c38b6c..1f994616 100644 --- a/src/Pixeval/Controls/Comment/CommentItem.xaml +++ b/src/Pixeval/Controls/Comment/CommentItem.xaml @@ -69,7 +69,7 @@ diff --git a/src/Pixeval/Controls/Download/DownloadItem.xaml b/src/Pixeval/Controls/Download/DownloadItem.xaml index 7547ba24..94f6069e 100644 --- a/src/Pixeval/Controls/Download/DownloadItem.xaml +++ b/src/Pixeval/Controls/Download/DownloadItem.xaml @@ -50,8 +50,25 @@ ShowError="{x:Bind ViewModel.IsError(ViewModel.DownloadTask.CurrentState), Mode=OneWay}" ShowPaused="{x:Bind ViewModel.IsPaused(ViewModel.DownloadTask.CurrentState), Mode=OneWay}" Value="{x:Bind ViewModel.DownloadTask.ProgressPercentage, Mode=OneWay}" /> + + + + + ThrowHelper.ArgumentOutOfRange(state) }; - public string ActionButtonContent(DownloadState state) => state switch + public string ActionButtonContent(DownloadState state) => ActionButtonSymbol(state) switch { - DownloadState.Queued or DownloadState.Pending => DownloadItemResources.ActionDownloadCancelled, - DownloadState.Running => DownloadItemResources.ActionButtonContentPause, - DownloadState.Cancelled or DownloadState.Error => DownloadItemResources.ActionButtonContentRetry, - DownloadState.Completed => DownloadItemResources.ActionButtonContentOpen, - DownloadState.Paused => DownloadItemResources.ActionButtonContentResume, + Symbol.Dismiss => DownloadItemResources.ActionDownloadCancelled, + Symbol.Pause => DownloadItemResources.ActionButtonContentPause, + Symbol.ArrowRepeatAll => DownloadItemResources.ActionButtonContentRetry, + Symbol.Open => DownloadItemResources.ActionButtonContentOpen, + Symbol.Play => DownloadItemResources.ActionButtonContentResume, _ => ThrowHelper.ArgumentOutOfRange(state) }; public Symbol ActionButtonSymbol(DownloadState state) => state switch { - DownloadState.Queued or DownloadState.Pending => Symbol.Dismiss, - DownloadState.Running => Symbol.Pause, + DownloadState.Pending => Symbol.Dismiss, + DownloadState.Queued or DownloadState.Running => Symbol.Pause, DownloadState.Cancelled or DownloadState.Error => Symbol.ArrowRepeatAll, DownloadState.Completed => Symbol.Open, DownloadState.Paused => Symbol.Play, @@ -119,6 +121,15 @@ public sealed partial class DownloadItemViewModel(IDownloadTaskGroup downloadTas public bool IsPaused(DownloadState state) => state is DownloadState.Paused; + public Visibility IsGroup(IDownloadTaskGroup group) => C.ToVisibility(group is DownloadTaskGroup); + + public Brush CurrentStateBrush(DownloadState state) => Application.Current.GetResource(state switch + { + DownloadState.Paused => "SystemFillColorCautionBrush", + DownloadState.Cancelled => "SystemFillColorNeutralBrush", + _ => "SystemFillColorAttentionBrush" + }); + #pragma warning restore CA1822 #region Not supported diff --git a/src/Pixeval/Controls/Illustrator/IllustratorItem.xaml b/src/Pixeval/Controls/Illustrator/IllustratorItem.xaml index dd655e3f..956fdcaa 100644 --- a/src/Pixeval/Controls/Illustrator/IllustratorItem.xaml +++ b/src/Pixeval/Controls/Illustrator/IllustratorItem.xaml @@ -45,10 +45,10 @@ VerticalAlignment="Center" Content="{fluent:SymbolIcon Symbol=Guest, FontSize=12}" - Foreground="{StaticResource TextSecondaryAccentColor}" /> + Foreground="{StaticResource TextFillColorSecondaryBrush}" /> + SelectedColor="{x:Bind controls1:C.ToAlphaColor(Entry.Value), BindBack=ColorBindBack, Mode=TwoWay}" /> diff --git a/src/Pixeval/Controls/Settings/ColorSettingsCard.xaml.cs b/src/Pixeval/Controls/Settings/ColorSettingsCard.xaml.cs index 4f2dee76..fc7ffe59 100644 --- a/src/Pixeval/Controls/Settings/ColorSettingsCard.xaml.cs +++ b/src/Pixeval/Controls/Settings/ColorSettingsCard.xaml.cs @@ -1,3 +1,4 @@ +using Windows.UI; using CommunityToolkit.WinUI.Controls; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -22,4 +23,6 @@ public sealed partial class ColorSettingsCard { Entry.ValueChanged?.Invoke(Entry.Value); } + + private void ColorBindBack(Color color) => Entry.Value = C.ToAlphaUInt(color); } diff --git a/src/Pixeval/Download/DownloadManager.cs b/src/Pixeval/Download/DownloadManager.cs index 1140ba68..7a4055f7 100644 --- a/src/Pixeval/Download/DownloadManager.cs +++ b/src/Pixeval/Download/DownloadManager.cs @@ -28,16 +28,15 @@ using System.Threading.Tasks; using Pixeval.CoreApi.Net; using Pixeval.Download.Models; using Pixeval.Utilities.Threading; -using WinUI3Utilities; namespace Pixeval.Download; public partial class DownloadManager : IDisposable { /// - /// 正在下载的任务队列,数量不超过 + /// 正在排队的任务队列 /// - private readonly Channel _downloadTaskChannel = Channel.CreateUnbounded(); + private readonly Channel _downloadTaskChannel = Channel.CreateUnbounded(); /// /// 使用 @@ -53,7 +52,7 @@ public partial class DownloadManager : IDisposable private readonly ReenterableAwaiter _throttle = new(true, true); /// - /// 中的数量 + /// 正在下载中的数量 /// private int _workingTasks; @@ -93,16 +92,17 @@ public partial class DownloadManager : IDisposable /// /// intrinsic download task are not counted /// - public void QueueTask(IDownloadTaskGroup task) + public void QueueTask(IDownloadTaskGroup taskGroup) { - if (_taskQuerySet.TryGetValue(task.Destination, out var v) && v == task) + if (_taskQuerySet.TryGetValue(taskGroup.Destination, out var v) && v == taskGroup) return; - _taskQuerySet[task.Destination] = task; + _taskQuerySet[taskGroup.Destination] = taskGroup; if (v is not null) _ = QueuedTasks.Remove(v); - QueuedTasks.Insert(0, task); - _ = _downloadTaskChannel.Writer.TryWrite(task); + QueuedTasks.Insert(0, taskGroup); + taskGroup.SubscribeProgress(_downloadTaskChannel.Writer); + _ = _downloadTaskChannel.Writer.TryWrite(taskGroup.GetToken()); } /// @@ -111,40 +111,30 @@ public partial class DownloadManager : IDisposable private async void PollTask() { while (await _downloadTaskChannel.Reader.WaitToReadAsync()) - while (await _throttle && _downloadTaskChannel.Reader.TryRead(out var queuedTask)) - switch (queuedTask) - { - case ImageDownloadTask imageTask: - imageTask.DownloadTryResume += t => _downloadTaskChannel.Writer.TryWrite(t); - imageTask.DownloadTryReset += t => _downloadTaskChannel.Writer.TryWrite(t); - // 子任务入列时一定是处于Queued状态,需要直接运行的 - _ = DownloadAsync(imageTask); - break; - case IDownloadTaskGroup taskGroup: - await taskGroup.InitializeTaskGroupAsync(); - foreach (var subTask in taskGroup) - { - subTask.DownloadTryResume += t => _downloadTaskChannel.Writer.TryWrite(t); - subTask.DownloadTryReset += t => _downloadTaskChannel.Writer.TryWrite(t); - // 任务组中的任务需要判断是否处于Queued状态才能运行 - if (subTask.CurrentState is DownloadState.Queued) - { - _ = DownloadAsync(subTask); - _ = await _throttle; - } - } - break; - } + while (await _throttle + && _downloadTaskChannel.Reader.TryRead(out var taskToken) + && taskToken is { Token.IsCancellationRequested: false, Task: var taskGroup }) + { + await taskGroup.InitializeTaskGroupAsync(); + foreach (var subTask in taskGroup) + // 需要判断是否处于Queued状态才能运行 + if (subTask.CurrentState is DownloadState.Queued) + { + await DownloadAsync(subTask); + _ = await _throttle; + } + } } /// /// 清除指定任务 /// - /// - public void RemoveTask(IDownloadTaskGroup task) + /// + public void RemoveTask(IDownloadTaskGroup taskGroup) { - _ = _taskQuerySet.Remove(task.Destination); - _ = QueuedTasks.Remove(task); + taskGroup.Cancel(); + _ = _taskQuerySet.Remove(taskGroup.Destination); + _ = QueuedTasks.Remove(taskGroup); } /// @@ -152,9 +142,8 @@ public partial class DownloadManager : IDisposable /// public void ClearTasks() { - // foreach (var task in QueuedTasks) - // await task.CancelAsync(); - + foreach (var task in QueuedTasks) + task.Cancel(); QueuedTasks.Clear(); _taskQuerySet.Clear(); } @@ -165,11 +154,12 @@ public partial class DownloadManager : IDisposable /// /// Execute the task only if it's already queued /// - public bool TryExecuteTaskGroupInline(IDownloadTaskGroup task) + public bool TryExecuteTaskGroupInline(IDownloadTaskGroup taskGroup) { - if (QueuedTasks.Contains(task) && task.CurrentState is DownloadState.Queued) + if (QueuedTasks.Contains(taskGroup) && taskGroup.CurrentState is DownloadState.Queued) { - _ = _downloadTaskChannel.Writer.TryWrite(task); + taskGroup.SubscribeProgress(_downloadTaskChannel.Writer); + _ = _downloadTaskChannel.Writer.TryWrite(taskGroup.GetToken()); return true; } @@ -179,8 +169,21 @@ public partial class DownloadManager : IDisposable private async Task DownloadAsync(ImageDownloadTask task) { await IncrementCounterAsync(); - await DownloadInternalAsync(task); - await DecrementCounterAsync(); + _ = Download(); + + return; + async Task Download() + { + try + { + await task.StartAsync(_httpClient); + } + catch + { + // ignored + } + await DecrementCounterAsync(); + } } private async Task IncrementCounterAsync() @@ -194,21 +197,4 @@ public partial class DownloadManager : IDisposable _ = Interlocked.Decrement(ref _workingTasks); await _throttle.SetResultAsync(true); } - - private async Task DownloadInternalAsync(ImageDownloadTask task) - { - try - { - await (task.CurrentState switch - { - DownloadState.Queued => task.StartAsync(_httpClient), - DownloadState.Paused => task.ResumeAsync(_httpClient), - _ => ThrowHelper.ArgumentOutOfRange(task.CurrentState) - }); - } - catch - { - // ignored - } - } } diff --git a/src/Pixeval/Download/Models/DownloadTaskGroup.cs b/src/Pixeval/Download/Models/DownloadTaskGroup.cs index 3fc91f4d..5b10c0ce 100644 --- a/src/Pixeval/Download/Models/DownloadTaskGroup.cs +++ b/src/Pixeval/Download/Models/DownloadTaskGroup.cs @@ -25,6 +25,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.Extensions.DependencyInjection; @@ -32,7 +33,6 @@ using Pixeval.Controls.Windowing; using Pixeval.CoreApi.Model; using Pixeval.Database; using Pixeval.Database.Managers; -using Pixeval.Util.UI; using Pixeval.Utilities; using WinUI3Utilities; @@ -58,6 +58,10 @@ public abstract partial class DownloadTaskGroup(DownloadHistoryEntry entry) : Ob var g = sender.To(); if (e.PropertyName is not nameof(CurrentState)) return; + // 子任务状态变化时,不一定需要任务组状态变化,也会进入此分支 + OnPropertyChanged(nameof(ActiveCount)); + OnPropertyChanged(nameof(CompletedCount)); + OnPropertyChanged(nameof(ErrorCount)); if (g.CurrentState is DownloadState.Running or DownloadState.Paused or DownloadState.Pending) return; g.DatabaseEntry.State = g.CurrentState; @@ -108,18 +112,23 @@ public abstract partial class DownloadTaskGroup(DownloadHistoryEntry entry) : Ob if (CurrentState is not (DownloadState.Completed or DownloadState.Error or DownloadState.Cancelled)) return; IsProcessing = true; - TasksSet.ForEach(t => t.TryReset()); + (CurrentState is DownloadState.Error + ? TasksSet.Where(t => t.CurrentState is DownloadState.Error) + : TasksSet) + .ForEach(t => t.TryReset()); if (CancellationTokenSource.IsCancellationRequested) { CancellationTokenSource.Dispose(); CancellationTokenSource = new(); } + DownloadTryReset?.Invoke(this); IsProcessing = false; } public void Pause() { IsProcessing = true; + CancellationTokenSource.Cancel(); TasksSet.ForEach(t => t.Pause()); IsProcessing = false; } @@ -127,7 +136,13 @@ public abstract partial class DownloadTaskGroup(DownloadHistoryEntry entry) : Ob public void TryResume() { IsProcessing = true; + if (CancellationTokenSource.IsCancellationRequested) + { + CancellationTokenSource.Dispose(); + CancellationTokenSource = new(); + } TasksSet.ForEach(t => t.TryResume()); + DownloadTryResume?.Invoke(this); IsProcessing = false; } @@ -146,9 +161,8 @@ public abstract partial class DownloadTaskGroup(DownloadHistoryEntry entry) : Ob public abstract void Delete(); - /// - /// 用于的取消令牌 - /// + public DownloadToken GetToken() => new(this, CancellationTokenSource.Token); + private CancellationTokenSource CancellationTokenSource { get; set; } = new(); /// @@ -219,6 +233,10 @@ public abstract partial class DownloadTaskGroup(DownloadHistoryEntry entry) : Ob public event Func? AfterAllDownloadAsync; + private event Action? DownloadTryResume; + + private event Action? DownloadTryReset; + protected void AddToTasksSet(ImageDownloadTask task) { _tasksSet.Add(task); @@ -228,31 +246,24 @@ public abstract partial class DownloadTaskGroup(DownloadHistoryEntry entry) : Ob task.DownloadErrorAsync += x => ItemDownloadErrorAsync?.Invoke(x) ?? Task.CompletedTask; task.PropertyChanged += (_, e) => { - switch (e.PropertyName) + OnPropertyChanged(e.PropertyName switch { - case nameof(ImageDownloadTask.ProgressPercentage): - { - var time = DateTime.Now; - if (time - _lastReportedTime < TimeSpan.FromMilliseconds(500)) - return; - _lastReportedTime = time; - OnPropertyChanged(nameof(ProgressPercentage)); - break; - } - case nameof(ImageDownloadTask.CurrentState): - OnPropertyChanged(nameof(CurrentState)); - break; - case nameof(ImageDownloadTask.ErrorCause): - OnPropertyChanged(nameof(ErrorCause)); - break; - } + nameof(ImageDownloadTask.ProgressPercentage) => nameof(ProgressPercentage), + nameof(ImageDownloadTask.CurrentState) => nameof(CurrentState), + nameof(ImageDownloadTask.ErrorCause) => nameof(ErrorCause), + _ => "" + }); }; } - /// - /// 降低汇报频率 - /// - private DateTime _lastReportedTime = DateTime.MinValue; + public void SubscribeProgress(ChannelWriter writer) + { + DownloadTryResume += OnDownloadWrite; + DownloadTryReset += OnDownloadWrite; + + return; + void OnDownloadWrite(DownloadTaskGroup o) => writer.TryWrite(o.GetToken()); + } public Exception? ErrorCause => TasksSet.FirstOrDefault(t => t.ErrorCause is not null)?.ErrorCause; @@ -260,7 +271,20 @@ public abstract partial class DownloadTaskGroup(DownloadHistoryEntry entry) : Ob public bool IsAnyError => TasksSet.Any(t => t.CurrentState is DownloadState.Error); - public double ProgressPercentage => TasksSet.Count is 0 ? 100 : TasksSet.Average(t => t.ProgressPercentage); + public int ActiveCount => TasksSet.Count(t => t.CurrentState is DownloadState.Queued or DownloadState.Running or DownloadState.Pending or DownloadState.Paused or DownloadState.Cancelled); + + public int CompletedCount => TasksSet.Count(t => t.CurrentState is DownloadState.Completed); + + public int ErrorCount => TasksSet.Count(t => t.CurrentState is DownloadState.Error); + + public double ProgressPercentage => + IsCreateFromEntry + ? DatabaseEntry.State is DownloadState.Queued + ? 0 + : 100 + : TasksSet.Count is 0 + ? 100 + : TasksSet.Average(t => t.ProgressPercentage); public IEnumerator GetEnumerator() => TasksSet.GetEnumerator(); diff --git a/src/Pixeval/Download/Models/IDownloadTaskGroup.cs b/src/Pixeval/Download/Models/IDownloadTaskGroup.cs index 0ee5ddbf..e7b53989 100644 --- a/src/Pixeval/Download/Models/IDownloadTaskGroup.cs +++ b/src/Pixeval/Download/Models/IDownloadTaskGroup.cs @@ -23,6 +23,8 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using Pixeval.CoreApi.Model; using Pixeval.Database; @@ -34,4 +36,16 @@ public interface IDownloadTaskGroup : IDownloadTaskBase, IIdEntry, INotifyProper DownloadHistoryEntry DatabaseEntry { get; } ValueTask InitializeTaskGroupAsync(); + + void SubscribeProgress(ChannelWriter writer); + + DownloadToken GetToken(); + + int ActiveCount { get; } + + int CompletedCount { get; } + + int ErrorCount { get; } } + +public readonly record struct DownloadToken(IDownloadTaskGroup Task, CancellationToken Token); diff --git a/src/Pixeval/Download/Models/ImageDownloadTask.cs b/src/Pixeval/Download/Models/ImageDownloadTask.cs index 23a19c2e..0657e382 100644 --- a/src/Pixeval/Download/Models/ImageDownloadTask.cs +++ b/src/Pixeval/Download/Models/ImageDownloadTask.cs @@ -39,6 +39,8 @@ public partial class ImageDownloadTask : ObservableObject, IDownloadTaskBase, IP Uri = uri; Destination = destination; CurrentState = initState; + if (initState is DownloadState.Completed or DownloadState.Cancelled or DownloadState.Error) + ProgressPercentage = 100; DownloadStartedAsync += DownloadStartedAsyncOverride; DownloadStoppedAsync += DownloadStoppedAsyncOverride; DownloadErrorAsync += DownloadErrorAsyncOverride; @@ -66,13 +68,13 @@ public partial class ImageDownloadTask : ObservableObject, IDownloadTaskBase, IP [ObservableProperty] private DownloadState _currentState; - [ObservableProperty] private double _progressPercentage = 100; + [ObservableProperty] private double _progressPercentage; [ObservableProperty] private Exception? _errorCause; [ObservableProperty] private bool _isProcessing; - private CancellationTokenSource CancellationTokenSource { get; set; } = new(); + protected CancellationTokenSource CancellationTokenSource { get; private set; } = new(); private bool _isRunning; @@ -199,11 +201,12 @@ public partial class ImageDownloadTask : ObservableObject, IDownloadTaskBase, IP public void Pause() { - if (CurrentState is not DownloadState.Running) + if (CurrentState is not (DownloadState.Queued or DownloadState.Running)) return; IsProcessing = true; CancellationTokenSource.Cancel(); CurrentState = DownloadState.Paused; + DownloadPaused?.Invoke(this); IsProcessing = false; } @@ -222,11 +225,6 @@ public partial class ImageDownloadTask : ObservableObject, IDownloadTaskBase, IP IsProcessing = false; } - public async Task ResumeAsync(HttpClient httpClient) - { - await StartAsync(httpClient, true); - } - public void Cancel() { if (CurrentState is not (DownloadState.Paused or DownloadState.Pending or DownloadState.Running or DownloadState.Queued)) @@ -234,6 +232,7 @@ public partial class ImageDownloadTask : ObservableObject, IDownloadTaskBase, IP IsProcessing = true; CancellationTokenSource.Cancel(); CurrentState = DownloadState.Cancelled; + DownloadCancelled?.Invoke(this); IsProcessing = false; } @@ -255,6 +254,10 @@ public partial class ImageDownloadTask : ObservableObject, IDownloadTaskBase, IP public event Action? DownloadTryReset; + public event Action? DownloadPaused; + + public event Action? DownloadCancelled; + public event Func AfterDownloadAsync; void IProgress.Report(double value) => ProgressPercentage = value; diff --git a/src/Pixeval/Download/Models/MangaDownloadTaskGroup.cs b/src/Pixeval/Download/Models/MangaDownloadTaskGroup.cs index 4f7ace52..b0081e13 100644 --- a/src/Pixeval/Download/Models/MangaDownloadTaskGroup.cs +++ b/src/Pixeval/Download/Models/MangaDownloadTaskGroup.cs @@ -85,7 +85,15 @@ public class MangaDownloadTaskGroup : DownloadTaskGroup, IImageDownloadTaskGroup private IllustrationDownloadFormat IllustrationDownloadFormat { get; } - public override string OpenLocalDestination => Path.GetDirectoryName(TasksSet[0].Destination)!; + public override string OpenLocalDestination + { + get + { + if (TasksSet.Count is 0) + SetTasksSet(); + return Path.GetDirectoryName(TasksSet[0].Destination)!; + } + } public override void Delete() { diff --git a/src/Pixeval/Download/Models/SingleImageDownloadTaskGroup.cs b/src/Pixeval/Download/Models/SingleImageDownloadTaskGroup.cs index 067b995a..e313ff06 100644 --- a/src/Pixeval/Download/Models/SingleImageDownloadTaskGroup.cs +++ b/src/Pixeval/Download/Models/SingleImageDownloadTaskGroup.cs @@ -24,6 +24,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; using System.Threading; +using System.Threading.Channels; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Pixeval.CoreApi.Model; @@ -40,7 +41,11 @@ public class SingleImageDownloadTaskGroup : ImageDownloadTask, IImageDownloadTas { public DownloadHistoryEntry DatabaseEntry { get; } - public ValueTask InitializeTaskGroupAsync() => ValueTask.CompletedTask; + public ValueTask InitializeTaskGroupAsync() + { + SetNotCreateFromEntry(); + return ValueTask.CompletedTask; + } public Illustration Entry => DatabaseEntry.Entry.To(); @@ -60,6 +65,8 @@ public class SingleImageDownloadTaskGroup : ImageDownloadTask, IImageDownloadTas { DatabaseEntry = entry; CurrentState = entry.State; + if (entry.State is DownloadState.Completed or DownloadState.Cancelled or DownloadState.Error) + ProgressPercentage = 100; IllustrationDownloadFormat = IoHelper.GetIllustrationFormat(Path.GetExtension(Destination)); } @@ -92,6 +99,23 @@ public class SingleImageDownloadTaskGroup : ImageDownloadTask, IImageDownloadTas await TagsManager.SetTagsAsync(Destination, Entry, token); } + public DownloadToken GetToken() => new(this, CancellationTokenSource.Token); + + public int ActiveCount => CurrentState is DownloadState.Queued or DownloadState.Running or DownloadState.Pending or DownloadState.Paused or DownloadState.Cancelled ? 1 : 0; + + public int CompletedCount => CurrentState is DownloadState.Completed ? 1 : 0; + + public int ErrorCount => CurrentState is DownloadState.Error ? 1 : 0; + + public void SubscribeProgress(ChannelWriter writer) + { + DownloadTryResume += OnDownloadWrite; + DownloadTryReset += OnDownloadWrite; + + return; + void OnDownloadWrite(ImageDownloadTask o) => writer.TryWrite(o.To().GetToken()); + } + public int Count => 1; public IEnumerator GetEnumerator() => ((IReadOnlyList)[this]).GetEnumerator(); diff --git a/src/Pixeval/Pages/MainPage.xaml b/src/Pixeval/Pages/MainPage.xaml index 2353a717..af1d0fab 100644 --- a/src/Pixeval/Pages/MainPage.xaml +++ b/src/Pixeval/Pages/MainPage.xaml @@ -30,7 +30,7 @@ TextWrapping="WrapWholeWords" /> - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + - - + + diff --git a/src/Pixeval/Themes/Colors.xaml b/src/Pixeval/Themes/Colors.xaml index 5b2deb34..3ccf5f1a 100644 --- a/src/Pixeval/Themes/Colors.xaml +++ b/src/Pixeval/Themes/Colors.xaml @@ -12,12 +12,6 @@ TintOpacity="0.3" /> - - - - - - #FAFAFA #F6F6F6 @@ -36,12 +30,6 @@ TintOpacity="0.3" /> - - - - - - #FAFAFA #F6F6F6 @@ -60,13 +48,7 @@ TintOpacity="0.3" /> - - - - - - #302C2D #323232 #323232 @@ -78,12 +60,6 @@ - - - - - - Cyan #323232