Move ProcessQueue biz logic into viewmodel

This commit is contained in:
Michael Bucari-Tovo 2022-07-13 16:22:45 -06:00
parent 3a61c32881
commit c727286d22
10 changed files with 180 additions and 215 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

View File

@ -1,14 +0,0 @@
using Dinah.Core.Threading;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace LibationWinForms.AvaloniaUI
{
public abstract class AsyncNotifyPropertyChanged2 : INotifyPropertyChanged
{
// see also notes in Libation/Source/_ARCHITECTURE NOTES.txt :: MVVM
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
=> Avalonia.Threading.Dispatcher.UIThread.Post(() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)));
}
}

View File

@ -1,15 +1,10 @@
using Avalonia.Media;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LibationWinForms.AvaloniaUI
{
internal static class AvaloniaUtils
{
public static IBrush GetBrushFromResources(string name)
=> GetBrushFromResources(name, Brushes.Transparent);
public static IBrush GetBrushFromResources(string name, IBrush defaultBrush)

View File

@ -1,7 +1,11 @@
using Avalonia.Threading;
using ApplicationServices;
using Avalonia.Threading;
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace LibationWinForms.AvaloniaUI.ViewModels
{
@ -15,7 +19,52 @@ namespace LibationWinForms.AvaloniaUI.ViewModels
set => this.RaiseAndSetIfChanged(ref _items, value);
}
private TrackedQueue2<ProcessBook2> Queue => Items;
public ProcessBook2 SelectedItem { get; set; }
public Task QueueRunner { get; private set; }
public bool Running => !QueueRunner?.IsCompleted ?? false;
public ProcessQueueViewModel()
{
Queue.QueuededCountChanged += Queue_QueuededCountChanged;
Queue.CompletedCountChanged += Queue_CompletedCountChanged;
}
private int _completedCount;
private int _errorCount;
private int _queuedCount;
private string _runningTime;
private bool _progressBarVisible;
public int CompletedCount { get => _completedCount; private set { this.RaiseAndSetIfChanged(ref _completedCount, value); this.RaisePropertyChanged(nameof(AnyCompleted)); } }
public int QueuedCount { get => _queuedCount; private set { this.RaiseAndSetIfChanged(ref _queuedCount, value); this.RaisePropertyChanged(nameof(AnyQueued)); } }
public int ErrorCount { get => _errorCount; private set { this.RaiseAndSetIfChanged(ref _errorCount, value); this.RaisePropertyChanged(nameof(AnyErrors)); } }
public string RunningTime { get => _runningTime; set { this.RaiseAndSetIfChanged(ref _runningTime, value); } }
public bool ProgressBarVisible { get => _progressBarVisible; set { this.RaiseAndSetIfChanged(ref _progressBarVisible, value); } }
public bool AnyCompleted => CompletedCount > 0;
public bool AnyQueued => QueuedCount > 0;
public bool AnyErrors => ErrorCount > 0;
public double Progress => 100d * Queue.Completed.Count / Queue.Count;
private void Queue_CompletedCountChanged(object sender, int e)
{
int errCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.FailedAbort or ProcessBookResult.FailedSkip or ProcessBookResult.FailedRetry or ProcessBookResult.ValidationFail);
int completeCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.Success);
ErrorCount = errCount;
CompletedCount = completeCount;
this.RaisePropertyChanged(nameof(Progress));
}
private void Queue_QueuededCountChanged(object sender, int cueCount)
{
QueuedCount = cueCount;
this.RaisePropertyChanged(nameof(Progress));
}
public void WriteLine(string text)
{
@ -26,6 +75,69 @@ namespace LibationWinForms.AvaloniaUI.ViewModels
LogMessage = text.Trim()
}));
}
public void AddToQueue(IEnumerable<ProcessBook2> pbook)
{
Dispatcher.UIThread.Post(() =>
{
Queue.Enqueue(pbook);
if (!Running)
QueueRunner = QueueLoop();
});
}
DateTime StartingTime;
private async Task QueueLoop()
{
try
{
Serilog.Log.Logger.Information("Begin processing queue");
RunningTime = string.Empty;
ProgressBarVisible = true;
StartingTime = DateTime.Now;
using var counterTimer = new System.Threading.Timer(CounterTimer_Tick, null, 0, 500);
while (Queue.MoveNext())
{
var nextBook = Queue.Current;
Serilog.Log.Logger.Information("Begin processing queued item. {item_LibraryBook}", nextBook?.LibraryBook);
var result = await nextBook.ProcessOneAsync();
Serilog.Log.Logger.Information("Completed processing queued item: {item_LibraryBook}\r\nResult: {result}", nextBook?.LibraryBook, result);
if (result == ProcessBookResult.ValidationFail)
Queue.ClearCurrent();
else if (result == ProcessBookResult.FailedAbort)
Queue.ClearQueue();
else if (result == ProcessBookResult.FailedSkip)
nextBook.LibraryBook.Book.UpdateBookStatus(DataLayer.LiberatedStatus.Error);
}
Serilog.Log.Logger.Information("Completed processing queue");
Queue_CompletedCountChanged(this, 0);
ProgressBarVisible = false;
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "An error was encountered while processing queued items");
}
}
private void CounterTimer_Tick(object? state)
{
string timeToStr(TimeSpan time)
{
string minsSecs = $"{time:mm\\:ss}";
if (time.TotalHours >= 1)
return $"{time.TotalHours:F0}:{minsSecs}";
return minsSecs;
}
RunningTime = timeToStr(DateTime.Now - StartingTime);
}
}
public class LogEntry

View File

@ -8,7 +8,9 @@
xmlns:prgid="clr-namespace:LibationWinForms.AvaloniaUI.Views.ProductsGrid"
xmlns:controls="clr-namespace:LibationWinForms.AvaloniaUI.Controls"
mc:Ignorable="d" d:DesignWidth="2000" d:DesignHeight="700"
x:Class="LibationWinForms.AvaloniaUI.Views.MainWindow" Title="MainWindow">
x:Class="LibationWinForms.AvaloniaUI.Views.MainWindow"
Title="MainWindow"
Icon="/AvaloniaUI/Assets/glass-with-glow_16.png">
<Border BorderBrush="{DynamicResource DataGridGridLinesBrush}" BorderThickness="2" Padding="15">
<Grid RowDefinitions="Auto,Auto,*,Auto">

View File

@ -91,24 +91,24 @@
<Setter Property="MinWidth" Value="100" />
</Style>
</Panel.Styles>
<ProgressBar Name="toolStripProgressBar1" ShowProgressText="True" />
<ProgressBar IsVisible="{Binding ProgressBarVisible}" Value="{Binding Progress}" ShowProgressText="True" />
</Panel>
<StackPanel Orientation="Horizontal" Grid.Column="1">
<StackPanel Margin="5,0,0,0" Orientation="Horizontal">
<Image Name="queueNumberLbl_Icon" Width="20" Height="20" Source="/AvaloniaUI/Assets/queued.png" />
<TextBlock Name="queueNumberLbl_Text" VerticalAlignment="Center" Text="[Q#]" />
<Image IsVisible="{Binding AnyQueued}" Width="20" Height="20" Source="/AvaloniaUI/Assets/queued.png" />
<TextBlock IsVisible="{Binding AnyQueued}" VerticalAlignment="Center" Text="{Binding QueuedCount}" />
</StackPanel>
<StackPanel Margin="5,0,0,0" Orientation="Horizontal">
<Image Name="completedNumberLbl_Icon" Width="20" Height="20" Source="/AvaloniaUI/Assets/completed.png" />
<TextBlock Name="completedNumberLbl_Text" VerticalAlignment="Center" Text="[DL#]" />
<Image IsVisible="{Binding AnyCompleted}" Width="20" Height="20" Source="/AvaloniaUI/Assets/completed.png" />
<TextBlock IsVisible="{Binding AnyCompleted}" VerticalAlignment="Center" Text="{Binding CompletedCount}" />
</StackPanel>
<StackPanel Margin="5,0,0,0" Orientation="Horizontal">
<Image Name="errorNumberLbl_Icon" Width="20" Height="20" Source="/AvaloniaUI/Assets/errored.png" />
<TextBlock Name="errorNumberLbl_Text" VerticalAlignment="Center" Text="[ERR#]" />
<Image IsVisible="{Binding AnyErrors}" Width="20" Height="20" Source="/AvaloniaUI/Assets/errored.png" />
<TextBlock IsVisible="{Binding AnyErrors}" VerticalAlignment="Center" Text="{Binding ErrorCount}" />
</StackPanel>
</StackPanel>
<Panel Grid.Column="2" Margin="0,0,5,0" HorizontalAlignment="Right" VerticalAlignment="Center">
<TextBlock Name="runningTimeLbl">00:00:25</TextBlock>
<TextBlock Text="{Binding RunningTime}" />
</Panel>
</Grid>
</Grid>

View File

@ -26,37 +26,7 @@ namespace LibationWinForms.AvaloniaUI.Views
private TrackedQueue2<ProcessBook2> Queue => _viewModel.Items;
private readonly ProcessQueue.LogMe Logger;
private int QueuedCount
{
set
{
queueNumberLbl_Text.Text = value.ToString();
queueNumberLbl_Text.IsVisible = value > 0;
queueNumberLbl_Icon.IsVisible = value > 0;
}
}
private int ErrorCount
{
set
{
errorNumberLbl_Text.Text = value.ToString();
errorNumberLbl_Text.IsVisible = value > 0;
errorNumberLbl_Icon.IsVisible = value > 0;
}
}
private int CompletedCount
{
set
{
completedNumberLbl_Text.Text = value.ToString();
completedNumberLbl_Text.IsVisible = value > 0;
completedNumberLbl_Icon.IsVisible = value > 0;
}
}
public Task QueueRunner { get; private set; }
public bool Running => !QueueRunner?.IsCompleted ?? false;
public ProcessQueueControl2()
{
@ -71,22 +41,6 @@ namespace LibationWinForms.AvaloniaUI.Views
ProcessBookControl2.PositionButtonClicked += ProcessBookControl2_ButtonClicked;
ProcessBookControl2.CancelButtonClicked += ProcessBookControl2_CancelButtonClicked;
queueNumberLbl_Icon = this.FindControl<Image>(nameof(queueNumberLbl_Icon));
errorNumberLbl_Icon = this.FindControl<Image>(nameof(errorNumberLbl_Icon));
completedNumberLbl_Icon = this.FindControl<Image>(nameof(completedNumberLbl_Icon));
queueNumberLbl_Text = this.FindControl<TextBlock>(nameof(queueNumberLbl_Text));
errorNumberLbl_Text = this.FindControl<TextBlock>(nameof(errorNumberLbl_Text));
completedNumberLbl_Text = this.FindControl<TextBlock>(nameof(completedNumberLbl_Text));
runningTimeLbl = this.FindControl<TextBlock>(nameof(runningTimeLbl));
toolStripProgressBar1 = this.FindControl<ProgressBar>(nameof(toolStripProgressBar1));
Queue.QueuededCountChanged += Queue_QueuededCountChanged;
Queue.CompletedCountChanged += Queue_CompletedCountChanged;
#region Design Mode Testing
if (Design.IsDesignMode)
{
@ -140,11 +94,6 @@ namespace LibationWinForms.AvaloniaUI.Views
return;
}
#endregion
runningTimeLbl.Text = string.Empty;
QueuedCount = 0;
ErrorCount = 0;
CompletedCount = 0;
}
private void InitializeComponent()
@ -152,61 +101,6 @@ namespace LibationWinForms.AvaloniaUI.Views
AvaloniaXamlLoader.Load(this);
}
private async void ProcessBookControl2_CancelButtonClicked(ProcessBook2 item)
{
if (item is not null)
await item.CancelAsync();
Queue.RemoveQueued(item);
}
private void ProcessBookControl2_ButtonClicked(ProcessBook2 item, QueuePosition queueButton)
{
Queue.MoveQueuePosition(item, queueButton);
}
private void RepeaterClick(object sender, PointerPressedEventArgs e)
{
if ((e.Source as TextBlock)?.DataContext is ProcessBook2 item)
{
_viewModel.SelectedItem = item;
_selectedIndex = _viewModel.Items.IndexOf(item);
}
}
private void RepeaterOnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.F5)
{
//_viewModel.ResetItems();
}
}
public async void CancelAllBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Queue.ClearQueue();
if (Queue.Current is not null)
await Queue.Current.CancelAsync();
}
public void ClearFinishedBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Queue.ClearCompleted();
if (!Running)
runningTimeLbl.Text = string.Empty;
}
public void ClearLogBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
_viewModel.LogEntries.Clear();
}
private void LogCopyBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
string logText = string.Join("\r\n", _viewModel.LogEntries.Select(r => $"{r.LogDate.ToShortDateString()} {r.LogDate.ToShortTimeString()}\t{r.LogMessage}"));
System.Windows.Forms.Clipboard.SetDataObject(logText, false, 5, 150);
}
private bool isBookInQueue(LibraryBook libraryBook)
=> Queue.Any(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId);
@ -233,7 +127,7 @@ namespace LibationWinForms.AvaloniaUI.Views
}
Serilog.Log.Logger.Information("Queueing {count} books", procs.Count);
AddToQueue(procs);
_viewModel.AddToQueue(procs);
}
public void AddDownloadDecrypt(IEnumerable<LibraryBook> entries)
@ -251,7 +145,7 @@ namespace LibationWinForms.AvaloniaUI.Views
}
Serilog.Log.Logger.Information("Queueing {count} books", procs.Count);
AddToQueue(procs);
_viewModel.AddToQueue(procs);
}
public void AddConvertMp3(IEnumerable<LibraryBook> entries)
@ -268,75 +162,63 @@ namespace LibationWinForms.AvaloniaUI.Views
}
Serilog.Log.Logger.Information("Queueing {count} books", procs.Count);
AddToQueue(procs);
}
private void AddToQueue(IEnumerable<ProcessBook2> pbook)
{
Dispatcher.UIThread.Post(() =>
{
Queue.Enqueue(pbook);
if (!Running)
QueueRunner = QueueLoop();
});
_viewModel.AddToQueue(procs);
}
DateTime StartingTime;
private async Task QueueLoop()
{
try
{
Serilog.Log.Logger.Information("Begin processing queue");
StartingTime = DateTime.Now;
using var counterTimer = new System.Threading.Timer(CounterTimer_Tick, null, 0, 500);
while (Queue.MoveNext())
{
var nextBook = Queue.Current;
Serilog.Log.Logger.Information("Begin processing queued item. {item_LibraryBook}", nextBook?.LibraryBook);
var result = await nextBook.ProcessOneAsync();
Serilog.Log.Logger.Information("Completed processing queued item: {item_LibraryBook}\r\nResult: {result}", nextBook?.LibraryBook, result);
if (result == ProcessBookResult.ValidationFail)
Queue.ClearCurrent();
else if (result == ProcessBookResult.FailedAbort)
Queue.ClearQueue();
else if (result == ProcessBookResult.FailedSkip)
nextBook.LibraryBook.Book.UpdateBookStatus(DataLayer.LiberatedStatus.Error);
}
Serilog.Log.Logger.Information("Completed processing queue");
Queue_CompletedCountChanged(this, 0);
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "An error was encountered while processing queued items");
}
}
#region Control event handlers
private void Queue_CompletedCountChanged(object sender, int e)
private async void ProcessBookControl2_CancelButtonClicked(ProcessBook2 item)
{
int errCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.FailedAbort or ProcessBookResult.FailedSkip or ProcessBookResult.FailedRetry or ProcessBookResult.ValidationFail);
int completeCount = Queue.Completed.Count(p => p.Result is ProcessBookResult.Success);
if (item is not null)
await item.CancelAsync();
Queue.RemoveQueued(item);
}
ErrorCount = errCount;
CompletedCount = completeCount;
UpdateProgressBar();
}
private void Queue_QueuededCountChanged(object sender, int cueCount)
private void ProcessBookControl2_ButtonClicked(ProcessBook2 item, QueuePosition queueButton)
{
QueuedCount = cueCount;
UpdateProgressBar();
Queue.MoveQueuePosition(item, queueButton);
}
private void UpdateProgressBar()
private void RepeaterClick(object sender, PointerPressedEventArgs e)
{
double percent = 100d * Queue.Completed.Count / Queue.Count;
toolStripProgressBar1.Value = percent;
if ((e.Source as TextBlock)?.DataContext is ProcessBook2 item)
{
_viewModel.SelectedItem = item;
_selectedIndex = _viewModel.Items.IndexOf(item);
}
}
private void RepeaterOnKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.F5)
{
//_viewModel.ResetItems();
}
}
public async void CancelAllBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Queue.ClearQueue();
if (Queue.Current is not null)
await Queue.Current.CancelAsync();
}
public void ClearFinishedBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Queue.ClearCompleted();
if (!_viewModel.Running)
_viewModel.RunningTime = string.Empty;
}
public void ClearLogBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
_viewModel.LogEntries.Clear();
}
private void LogCopyBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
string logText = string.Join("\r\n", _viewModel.LogEntries.Select(r => $"{r.LogDate.ToShortDateString()} {r.LogDate.ToShortTimeString()}\t{r.LogMessage}"));
System.Windows.Forms.Clipboard.SetDataObject(logText, false, 5, 150);
}
private async void cancelAllBtn_Click(object sender, EventArgs e)
@ -350,22 +232,8 @@ namespace LibationWinForms.AvaloniaUI.Views
{
Queue.ClearCompleted();
if (!Running)
runningTimeLbl.Text = string.Empty;
}
private void CounterTimer_Tick(object? state)
{
string timeToStr(TimeSpan time)
{
string minsSecs = $"{time:mm\\:ss}";
if (time.TotalHours >= 1)
return $"{time.TotalHours:F0}:{minsSecs}";
return minsSecs;
}
if (Running)
Dispatcher.UIThread.Post(() => runningTimeLbl.Text = timeToStr(DateTime.Now - StartingTime));
if (!_viewModel.Running)
_viewModel.RunningTime = string.Empty;
}
#endregion

View File

@ -46,6 +46,7 @@ namespace LibationWinForms.AvaloniaUI.Views.ProductsGrid
}
else
{
//List is already displayed. Replace all items with new ones, refilter, and re-sort
string existingFilter = _viewModel?.GridEntries?.Filter;
bindingList.ReplaceList(ProductsDisplayViewModel.CreateGridEntries(dbBooks));
bindingList.Filter = existingFilter;

View File

@ -46,6 +46,7 @@
<None Remove="AvaloniaUI\Assets\edit_25x25.png" />
<None Remove="AvaloniaUI\Assets\errored.png" />
<None Remove="AvaloniaUI\Assets\first.png" />
<None Remove="AvaloniaUI\Assets\glass-with-glow_16.png" />
<None Remove="AvaloniaUI\Assets\import_16x16.png" />
<None Remove="AvaloniaUI\Assets\last.png" />
<None Remove="AvaloniaUI\Assets\LibationStyles.xaml" />

View File

@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>x64</Platform>
<Platform>Any CPU</Platform>
<PublishDir>..\bin\publish\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<TargetFramework>net6.0-windows</TargetFramework>