mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-06-27 00:36:20 -04:00
Merge pull request #1868 from rmcrackan/rmcrackan/1865-bad-book-setting
#1865 - Add in-dialog "Apply to all" and "Remember in Settings"
This commit is contained in:
30
Source/FileLiberator/SimulateBadBookFailure.cs
Normal file
30
Source/FileLiberator/SimulateBadBookFailure.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using DataLayer;
|
||||
using Dinah.Core.ErrorHandling;
|
||||
using LibationFileManager;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileLiberator;
|
||||
|
||||
/// <summary>
|
||||
/// Instantly fails processing so the bad-book error dialog can be tested without a real download failure.
|
||||
/// </summary>
|
||||
public class SimulateBadBookFailure : Processable, IProcessable<SimulateBadBookFailure>
|
||||
{
|
||||
public override string Name => "Simulate Bad Book Failure";
|
||||
|
||||
public override bool Validate(LibraryBook libraryBook) => true;
|
||||
|
||||
public override Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
|
||||
{
|
||||
OnBegin(libraryBook);
|
||||
|
||||
var result = new StatusHandler();
|
||||
result.AddError("Simulated processing failure for testing the bad-book error dialog.");
|
||||
|
||||
OnCompleted(libraryBook);
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
|
||||
public static SimulateBadBookFailure Create(Configuration config)
|
||||
=> new() { Configuration = config };
|
||||
}
|
||||
@@ -47,6 +47,9 @@ public class App : Application
|
||||
MessageBoxBase.ShowAsyncImpl = (owner, message, caption, buttons, icon, defaultButton, saveAndRestorePosition) =>
|
||||
MessageBox.Show(owner as Window, message, caption, buttons, icon, defaultButton, saveAndRestorePosition);
|
||||
|
||||
BadBookActionDialogBase.ShowAsyncImpl = (owner, message, caption) =>
|
||||
Dialogs.BadBookActionDialog.ShowAsync(owner as Window, message, caption);
|
||||
|
||||
if (LibraryTask is null)
|
||||
{
|
||||
RunSetupIfNeededAsync(desktop, Configuration.Instance);
|
||||
|
||||
40
Source/LibationAvalonia/Dialogs/BadBookActionDialog.axaml
Normal file
40
Source/LibationAvalonia/Dialogs/BadBookActionDialog.axaml
Normal file
@@ -0,0 +1,40 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="320"
|
||||
MinWidth="400" MaxWidth="520"
|
||||
SizeToContent="Height"
|
||||
CanMaximize="False"
|
||||
CanMinimize="False"
|
||||
x:Class="LibationAvalonia.Dialogs.BadBookActionDialog"
|
||||
ShowInTaskbar="True"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
|
||||
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto" Margin="5,10,10,10">
|
||||
<Image Grid.Row="0" Grid.Column="0" Height="32" Width="32" Margin="5,0,5,0"
|
||||
VerticalAlignment="Top" Stretch="Uniform" Source="/Assets/MBIcons/Question_64.png"/>
|
||||
<TextBlock Grid.Row="0" Grid.Column="1" Name="messageTextBlock" Margin="5,0,0,0" MinWidth="300"
|
||||
TextWrapping="Wrap" HorizontalAlignment="Stretch" VerticalAlignment="Top"
|
||||
FontSize="12"/>
|
||||
|
||||
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Margin="10,10,10,10" Spacing="6">
|
||||
<CheckBox Name="applyToAllCheckBox" Content="Apply to all remaining books in this queue"/>
|
||||
<CheckBox Name="rememberInSettingsCheckBox" Content="Remember this choice in Settings"/>
|
||||
</StackPanel>
|
||||
|
||||
<DockPanel Height="45" Grid.Row="2" Grid.ColumnSpan="2" Background="{DynamicResource SystemChromeMediumLowColor}">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="5" DockPanel.Dock="Bottom">
|
||||
<Button MinWidth="75" MinHeight="28" Name="abortButton" Click="AbortButton_Click" Margin="5">
|
||||
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="Abort"/>
|
||||
</Button>
|
||||
<Button MinWidth="75" MinHeight="28" Name="retryButton" Click="RetryButton_Click" Margin="5">
|
||||
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="Retry"/>
|
||||
</Button>
|
||||
<Button MinWidth="75" MinHeight="28" Name="ignoreButton" Click="IgnoreButton_Click" Margin="5">
|
||||
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="Ignore"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</DockPanel>
|
||||
</Grid>
|
||||
</Window>
|
||||
94
Source/LibationAvalonia/Dialogs/BadBookActionDialog.axaml.cs
Normal file
94
Source/LibationAvalonia/Dialogs/BadBookActionDialog.axaml.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Threading;
|
||||
using LibationUiBase;
|
||||
using LibationUiBase.Forms;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.Dialogs;
|
||||
|
||||
public partial class BadBookActionDialog : DialogWindow
|
||||
{
|
||||
public BadBookDialogResult Result { get; private set; } = new(DialogResult.Retry, false, false);
|
||||
|
||||
public BadBookActionDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
SaveOnEnter = false;
|
||||
CancelOnEscape = false;
|
||||
}
|
||||
|
||||
public BadBookActionDialog(string message, string caption) : this()
|
||||
{
|
||||
Title = caption;
|
||||
messageTextBlock.Text = message;
|
||||
ControlToFocusOnShow = retryButton;
|
||||
}
|
||||
|
||||
private void CloseWith(DialogResult action)
|
||||
{
|
||||
Result = new BadBookDialogResult(
|
||||
action,
|
||||
applyToAllCheckBox.IsChecked == true,
|
||||
rememberInSettingsCheckBox.IsChecked == true);
|
||||
Close(DialogResult.None);
|
||||
}
|
||||
|
||||
public void AbortButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
=> CloseWith(DialogResult.Abort);
|
||||
|
||||
public void RetryButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
=> CloseWith(DialogResult.Retry);
|
||||
|
||||
public void IgnoreButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs args)
|
||||
=> CloseWith(DialogResult.Ignore);
|
||||
|
||||
public static Task<BadBookDialogResult> ShowAsync(Window? owner, string message, string caption)
|
||||
=> Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
owner = owner?.IsLoaded is true ? owner : null;
|
||||
var dialog = new BadBookActionDialog(message, caption);
|
||||
await DisplayDialogAsync(dialog, owner);
|
||||
return dialog.Result;
|
||||
});
|
||||
|
||||
private static async Task DisplayDialogAsync(BadBookActionDialog dialog, Window? owner)
|
||||
{
|
||||
if (owner is null)
|
||||
{
|
||||
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
if (desktop.MainWindow?.IsLoaded is true)
|
||||
await dialog.ShowDialog(desktop.MainWindow);
|
||||
else
|
||||
{
|
||||
var tcs = new TaskCompletionSource();
|
||||
desktop.MainWindow = dialog;
|
||||
dialog.Closed += (_, _) => tcs.SetResult();
|
||||
dialog.Show();
|
||||
await tcs.Task;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var window = new Window
|
||||
{
|
||||
IsVisible = false,
|
||||
Height = 1,
|
||||
Width = 1,
|
||||
WindowDecorations = WindowDecorations.None,
|
||||
ShowInTaskbar = false
|
||||
};
|
||||
|
||||
window.Show();
|
||||
await dialog.ShowDialog(window);
|
||||
window.Close();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await dialog.ShowDialog(owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Source/LibationAvalonia/ViewModels/MainVM.Debug.cs
Normal file
39
Source/LibationAvalonia/ViewModels/MainVM.Debug.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
#if DEBUG
|
||||
using LibationUiBase.Forms;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationAvalonia.ViewModels;
|
||||
|
||||
partial class MainVM
|
||||
{
|
||||
public async Task SimulateBadBookFailuresAsync()
|
||||
{
|
||||
var books = ProductsDisplay.GetVisibleBookEntries().Take(5).ToArray();
|
||||
if (books.Length == 0)
|
||||
{
|
||||
await MessageBox.Show(
|
||||
"No books are visible in the grid.\n\nClear your filter or widen it, then try again.",
|
||||
"Test bad book dialog",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
var confirm = await MessageBox.Show(
|
||||
$"Queue {books.Length} visible book(s) with simulated failures?\n\n"
|
||||
+ "No files will be downloaded. Each book will immediately show the bad-book error dialog.\n\n"
|
||||
+ "Set error handling to \"Ask each time\" in Settings > Download/Decrypt before testing.",
|
||||
"Test bad book dialog",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question,
|
||||
MessageBoxDefaultButton.Button1);
|
||||
|
||||
if (confirm != DialogResult.Yes)
|
||||
return;
|
||||
|
||||
ProcessQueue.QueueSimulatedBadBookFailures(books);
|
||||
setQueueCollapseState(false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -46,8 +46,41 @@ public partial class MainWindow : ReactiveWindow<MainVM>
|
||||
Configuration.Instance.PropertyChanged += Settings_PropertyChanged;
|
||||
Settings_PropertyChanged(this, null);
|
||||
DataContext = new MainVM(this);
|
||||
#if DEBUG
|
||||
Configure_DebugMenu();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private void Configure_DebugMenu()
|
||||
{
|
||||
var simulateItem = new MenuItem { Header = "Simulate bad book failures (test dialog)..." };
|
||||
simulateItem.Click += async (_, _) =>
|
||||
{
|
||||
if (ViewModel is MainVM vm)
|
||||
await vm.SimulateBadBookFailuresAsync();
|
||||
};
|
||||
|
||||
// Insert before Tour; the Separator above Tour in axaml already provides the divider.
|
||||
var items = settingsToolStripMenuItem.Items;
|
||||
var insertIndex = -1;
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
if (items[i] is MenuItem menuItem
|
||||
&& menuItem.Header?.ToString()?.Contains("Tour", StringComparison.OrdinalIgnoreCase) == true)
|
||||
{
|
||||
insertIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (insertIndex < 0)
|
||||
insertIndex = items.Count;
|
||||
|
||||
items.Insert(insertIndex, simulateItem);
|
||||
}
|
||||
#endif
|
||||
|
||||
[Dinah.Core.PropertyChangeFilter(nameof(Configuration.Books))]
|
||||
private void Settings_PropertyChanged(object? sender, Dinah.Core.PropertyChangedEventArgsEx? e)
|
||||
{
|
||||
|
||||
35
Source/LibationUiBase/BadBookActionDialogBase.cs
Normal file
35
Source/LibationUiBase/BadBookActionDialogBase.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using LibationUiBase.Forms;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace LibationUiBase;
|
||||
|
||||
public record BadBookDialogResult(
|
||||
DialogResult Action,
|
||||
bool ApplyToAll,
|
||||
bool RememberInSettings);
|
||||
|
||||
public delegate Task<BadBookDialogResult> ShowBadBookDialogAsyncDelegate(
|
||||
object? owner, string message, string caption);
|
||||
|
||||
public static class BadBookActionDialogBase
|
||||
{
|
||||
private static ShowBadBookDialogAsyncDelegate? s_ShowAsyncImpl;
|
||||
|
||||
public static ShowBadBookDialogAsyncDelegate ShowAsyncImpl
|
||||
{
|
||||
get => s_ShowAsyncImpl ?? DefaultShowAsyncImpl;
|
||||
set => s_ShowAsyncImpl = value;
|
||||
}
|
||||
|
||||
private static Task<BadBookDialogResult> DefaultShowAsyncImpl(object? owner, string message, string caption)
|
||||
{
|
||||
Serilog.Log.Logger.Error("BadBookActionDialogBase implementation not set. {@DebugInfo}", new { owner, message, caption });
|
||||
return Task.FromResult(new BadBookDialogResult(DialogResult.Retry, false, false));
|
||||
}
|
||||
|
||||
public static Task<BadBookDialogResult> Show(string message, string caption)
|
||||
=> ShowAsyncImpl(null, message, caption);
|
||||
|
||||
public static Task<BadBookDialogResult> Show(object? owner, string message, string caption)
|
||||
=> ShowAsyncImpl(owner, message, caption);
|
||||
}
|
||||
10
Source/LibationUiBase/ProcessQueue/BadBookSessionContext.cs
Normal file
10
Source/LibationUiBase/ProcessQueue/BadBookSessionContext.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using LibationFileManager;
|
||||
|
||||
namespace LibationUiBase.ProcessQueue;
|
||||
|
||||
public class BadBookSessionContext
|
||||
{
|
||||
public Configuration.BadBookAction? Override { get; set; }
|
||||
|
||||
public void Reset() => Override = null;
|
||||
}
|
||||
@@ -45,6 +45,7 @@ public class ProcessBookViewModel : ReactiveObject
|
||||
{
|
||||
public LibraryBook LibraryBook { get; protected set; }
|
||||
public Configuration Configuration { get; }
|
||||
private readonly BadBookSessionContext? _badBookSession;
|
||||
|
||||
#region Properties exposed to the view
|
||||
public ProcessBookResult Result { get => field; set { RaiseAndSetIfChanged(ref field, value); RaisePropertyChanged(nameof(StatusText)); } }
|
||||
@@ -99,10 +100,11 @@ public class ProcessBookViewModel : ReactiveObject
|
||||
/// <summary> A series of Processable actions to perform on this book </summary>
|
||||
protected Queue<Func<Processable>> Processes { get; } = new();
|
||||
|
||||
public ProcessBookViewModel(LibraryBook libraryBook, Configuration configuration)
|
||||
public ProcessBookViewModel(LibraryBook libraryBook, Configuration configuration, BadBookSessionContext? badBookSession = null)
|
||||
{
|
||||
LibraryBook = libraryBook;
|
||||
Configuration = configuration;
|
||||
_badBookSession = badBookSession;
|
||||
|
||||
Title = LibraryBook.Book.TitleWithSubtitle;
|
||||
Author = LibraryBook.Book.AuthorNames;
|
||||
@@ -258,6 +260,7 @@ public class ProcessBookViewModel : ReactiveObject
|
||||
public ProcessBookViewModel AddDownloadPdf() => AddProcessable<DownloadPdf>();
|
||||
public ProcessBookViewModel AddDownloadDecryptBook() => AddProcessable<DownloadDecryptBook>();
|
||||
public ProcessBookViewModel AddConvertToMp3() => AddProcessable<ConvertToMp3>();
|
||||
public ProcessBookViewModel AddSimulateBadBookFailure() => AddProcessable<SimulateBadBookFailure>();
|
||||
|
||||
private ProcessBookViewModel AddProcessable<T>() where T : Processable, IProcessable<T>
|
||||
{
|
||||
@@ -374,12 +377,14 @@ public class ProcessBookViewModel : ReactiveObject
|
||||
const DialogResult SkipResult = DialogResult.Ignore;
|
||||
LogError($"ERROR. All books have not been processed. Book failed: {libraryBook.Book}");
|
||||
|
||||
DialogResult? dialogResult = Configuration.BadBook switch
|
||||
DialogResult dialogResult = Configuration.BadBook switch
|
||||
{
|
||||
Configuration.BadBookAction.Abort => DialogResult.Abort,
|
||||
Configuration.BadBookAction.Retry => DialogResult.Retry,
|
||||
Configuration.BadBookAction.Ignore => DialogResult.Ignore,
|
||||
Configuration.BadBookAction.Ask or _ => await ShowRetryDialogAsync(libraryBook)
|
||||
Configuration.BadBookAction.Ask or _ => _badBookSession?.Override is Configuration.BadBookAction sessionOverride
|
||||
? ToDialogResult(sessionOverride)
|
||||
: await ShowRetryDialogAsync(libraryBook)
|
||||
};
|
||||
|
||||
if (dialogResult == SkipResult)
|
||||
@@ -425,15 +430,21 @@ public class ProcessBookViewModel : ReactiveObject
|
||||
|
||||
- IGNORE: Permanently ignore this book. Continue processing the queued books. (Will not try this book again later.)
|
||||
|
||||
See Settings in the Download/Decrypt tab to avoid this box in the future.
|
||||
Check "Apply to all remaining books" to use your choice for the rest of this queue.
|
||||
Check "Remember in Settings" to save your choice in Download/Decrypt settings.
|
||||
""";
|
||||
|
||||
const MessageBoxButtons SkipDialogButtons = MessageBoxButtons.AbortRetryIgnore;
|
||||
const MessageBoxDefaultButton SkipDialogDefaultButton = MessageBoxDefaultButton.Button1;
|
||||
|
||||
try
|
||||
{
|
||||
return await MessageBoxBase.Show(skipDialogText, "Skip this book?", SkipDialogButtons, MessageBoxIcon.Question, SkipDialogDefaultButton);
|
||||
var result = await BadBookActionDialogBase.Show(skipDialogText, "Skip this book?");
|
||||
|
||||
if (result.ApplyToAll)
|
||||
_badBookSession?.Override = ToBadBookAction(result.Action);
|
||||
|
||||
if (result.RememberInSettings)
|
||||
Configuration.BadBook = ToBadBookAction(result.Action);
|
||||
|
||||
return result.Action;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -442,5 +453,23 @@ public class ProcessBookViewModel : ReactiveObject
|
||||
}
|
||||
}
|
||||
|
||||
private static DialogResult ToDialogResult(Configuration.BadBookAction action)
|
||||
=> action switch
|
||||
{
|
||||
Configuration.BadBookAction.Abort => DialogResult.Abort,
|
||||
Configuration.BadBookAction.Retry => DialogResult.Retry,
|
||||
Configuration.BadBookAction.Ignore => DialogResult.Ignore,
|
||||
_ => DialogResult.Retry
|
||||
};
|
||||
|
||||
private static Configuration.BadBookAction ToBadBookAction(DialogResult action)
|
||||
=> action switch
|
||||
{
|
||||
DialogResult.Abort => Configuration.BadBookAction.Abort,
|
||||
DialogResult.Retry => Configuration.BadBookAction.Retry,
|
||||
DialogResult.Ignore => Configuration.BadBookAction.Ignore,
|
||||
_ => Configuration.BadBookAction.Retry
|
||||
};
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -21,6 +21,7 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
{
|
||||
public ObservableCollection<LogEntry> LogEntries { get; } = new();
|
||||
public TrackedQueue<ProcessBookViewModel> Queue { get; } = new();
|
||||
private readonly BadBookSessionContext _badBookSession = new();
|
||||
public Task? QueueRunner { get; private set; }
|
||||
public bool Running => !QueueRunner?.IsCompleted ?? false;
|
||||
|
||||
@@ -132,6 +133,34 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queues visible books with an instant simulated failure for testing the bad-book error dialog.
|
||||
/// Does not download or modify files.
|
||||
/// </summary>
|
||||
public void QueueSimulatedBadBookFailures(IList<LibraryBook> libraryBooks, Configuration? config = null, int maxBooks = 5)
|
||||
{
|
||||
config ??= Configuration.Instance;
|
||||
if (libraryBooks.Count == 0)
|
||||
return;
|
||||
|
||||
RunOnQueueUiThread(() => addSimulatedBadBookFailuresCore(libraryBooks, config, maxBooks));
|
||||
}
|
||||
|
||||
private void addSimulatedBadBookFailuresCore(IList<LibraryBook> libraryBooks, Configuration config, int maxBooks)
|
||||
{
|
||||
var procs = libraryBooks
|
||||
.Where(e => !IsBookInQueue(e))
|
||||
.Take(maxBooks)
|
||||
.Select(entry => new ProcessBookViewModel(entry, config, _badBookSession).AddSimulateBadBookFailure())
|
||||
.ToArray();
|
||||
|
||||
if (procs.Length == 0)
|
||||
return;
|
||||
|
||||
Serilog.Log.Logger.Information("Queueing {count} books for simulated bad-book failure testing", procs.Length);
|
||||
AddToQueue(procs);
|
||||
}
|
||||
|
||||
public async Task<bool> QueueDownloadDecryptAsync(IList<LibraryBook> libraryBooks, Configuration? config = null)
|
||||
{
|
||||
config ??= Configuration.Instance;
|
||||
@@ -271,7 +300,7 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
AddToQueue(procs);
|
||||
|
||||
ProcessBookViewModel Create(LibraryBook entry)
|
||||
=> new ProcessBookViewModel(entry, config).AddDownloadPdf();
|
||||
=> new ProcessBookViewModel(entry, config, _badBookSession).AddDownloadPdf();
|
||||
}
|
||||
|
||||
private void AddDownloadDecrypt(IList<LibraryBook> entries, Configuration config)
|
||||
@@ -284,7 +313,7 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
AddToQueue(procs);
|
||||
|
||||
ProcessBookViewModel Create(LibraryBook entry)
|
||||
=> new ProcessBookViewModel(entry, config).AddDownloadDecryptBook().AddDownloadPdf();
|
||||
=> new ProcessBookViewModel(entry, config, _badBookSession).AddDownloadDecryptBook().AddDownloadPdf();
|
||||
}
|
||||
|
||||
private void AddConvertMp3(IList<LibraryBook> entries, Configuration config)
|
||||
@@ -297,7 +326,7 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
AddToQueue(procs);
|
||||
|
||||
ProcessBookViewModel Create(LibraryBook entry)
|
||||
=> new ProcessBookViewModel(entry, config).AddConvertToMp3();
|
||||
=> new ProcessBookViewModel(entry, config, _badBookSession).AddConvertToMp3();
|
||||
}
|
||||
|
||||
private void AddToQueue(IList<ProcessBookViewModel> pbook)
|
||||
@@ -319,6 +348,7 @@ public class ProcessQueueViewModel : ReactiveObject
|
||||
{
|
||||
Serilog.Log.Logger.Information("Begin processing queue");
|
||||
|
||||
_badBookSession.Reset();
|
||||
RunningTime = string.Empty;
|
||||
ProgressBarVisible = true;
|
||||
var startingTime = DateTime.Now;
|
||||
|
||||
154
Source/LibationWinForms/Dialogs/BadBookActionDialog.Designer.cs
generated
Normal file
154
Source/LibationWinForms/Dialogs/BadBookActionDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,154 @@
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
partial class BadBookActionDialog
|
||||
{
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
components.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.pictureBox = new System.Windows.Forms.PictureBox();
|
||||
this.messageLbl = new System.Windows.Forms.Label();
|
||||
this.applyToAllCb = new System.Windows.Forms.CheckBox();
|
||||
this.rememberInSettingsCb = new System.Windows.Forms.CheckBox();
|
||||
this.abortBtn = new System.Windows.Forms.Button();
|
||||
this.retryBtn = new System.Windows.Forms.Button();
|
||||
this.ignoreBtn = new System.Windows.Forms.Button();
|
||||
this.buttonPanel = new System.Windows.Forms.Panel();
|
||||
((System.ComponentModel.ISupportInitialize)(this.pictureBox)).BeginInit();
|
||||
this.buttonPanel.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// pictureBox
|
||||
//
|
||||
this.pictureBox.Location = new System.Drawing.Point(12, 12);
|
||||
this.pictureBox.Name = "pictureBox";
|
||||
this.pictureBox.Size = new System.Drawing.Size(32, 32);
|
||||
this.pictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
|
||||
this.pictureBox.TabIndex = 0;
|
||||
this.pictureBox.TabStop = false;
|
||||
this.pictureBox.Image = System.Drawing.SystemIcons.Question.ToBitmap();
|
||||
//
|
||||
// messageLbl
|
||||
//
|
||||
this.messageLbl.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.messageLbl.AutoSize = true;
|
||||
this.messageLbl.Location = new System.Drawing.Point(50, 12);
|
||||
this.messageLbl.Name = "messageLbl";
|
||||
this.messageLbl.Size = new System.Drawing.Size(422, 15);
|
||||
this.messageLbl.TabIndex = 1;
|
||||
//
|
||||
// applyToAllCb
|
||||
//
|
||||
this.applyToAllCb.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.applyToAllCb.AutoSize = true;
|
||||
this.applyToAllCb.Location = new System.Drawing.Point(12, 185);
|
||||
this.applyToAllCb.Name = "applyToAllCb";
|
||||
this.applyToAllCb.Size = new System.Drawing.Size(220, 19);
|
||||
this.applyToAllCb.TabIndex = 2;
|
||||
this.applyToAllCb.Text = "Apply to all remaining books in this queue";
|
||||
this.applyToAllCb.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// rememberInSettingsCb
|
||||
//
|
||||
this.rememberInSettingsCb.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.rememberInSettingsCb.AutoSize = true;
|
||||
this.rememberInSettingsCb.Location = new System.Drawing.Point(12, 210);
|
||||
this.rememberInSettingsCb.Name = "rememberInSettingsCb";
|
||||
this.rememberInSettingsCb.Size = new System.Drawing.Size(195, 19);
|
||||
this.rememberInSettingsCb.TabIndex = 3;
|
||||
this.rememberInSettingsCb.Text = "Remember this choice in Settings";
|
||||
this.rememberInSettingsCb.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// abortBtn
|
||||
//
|
||||
this.abortBtn.Anchor = System.Windows.Forms.AnchorStyles.Right;
|
||||
this.abortBtn.Location = new System.Drawing.Point(197, 8);
|
||||
this.abortBtn.MinimumSize = new System.Drawing.Size(75, 28);
|
||||
this.abortBtn.Name = "abortBtn";
|
||||
this.abortBtn.Size = new System.Drawing.Size(75, 28);
|
||||
this.abortBtn.TabIndex = 4;
|
||||
this.abortBtn.Text = "Abort";
|
||||
this.abortBtn.UseVisualStyleBackColor = true;
|
||||
this.abortBtn.Click += new System.EventHandler(this.AbortBtn_Click);
|
||||
//
|
||||
// retryBtn
|
||||
//
|
||||
this.retryBtn.Anchor = System.Windows.Forms.AnchorStyles.Right;
|
||||
this.retryBtn.Location = new System.Drawing.Point(278, 8);
|
||||
this.retryBtn.MinimumSize = new System.Drawing.Size(75, 28);
|
||||
this.retryBtn.Name = "retryBtn";
|
||||
this.retryBtn.Size = new System.Drawing.Size(75, 28);
|
||||
this.retryBtn.TabIndex = 5;
|
||||
this.retryBtn.Text = "Retry";
|
||||
this.retryBtn.UseVisualStyleBackColor = true;
|
||||
this.retryBtn.Click += new System.EventHandler(this.RetryBtn_Click);
|
||||
//
|
||||
// ignoreBtn
|
||||
//
|
||||
this.ignoreBtn.Anchor = System.Windows.Forms.AnchorStyles.Right;
|
||||
this.ignoreBtn.Location = new System.Drawing.Point(359, 8);
|
||||
this.ignoreBtn.MinimumSize = new System.Drawing.Size(75, 28);
|
||||
this.ignoreBtn.Name = "ignoreBtn";
|
||||
this.ignoreBtn.Size = new System.Drawing.Size(75, 28);
|
||||
this.ignoreBtn.TabIndex = 6;
|
||||
this.ignoreBtn.Text = "Ignore";
|
||||
this.ignoreBtn.UseVisualStyleBackColor = true;
|
||||
this.ignoreBtn.Click += new System.EventHandler(this.IgnoreBtn_Click);
|
||||
//
|
||||
// buttonPanel
|
||||
//
|
||||
this.buttonPanel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.buttonPanel.Controls.Add(this.abortBtn);
|
||||
this.buttonPanel.Controls.Add(this.retryBtn);
|
||||
this.buttonPanel.Controls.Add(this.ignoreBtn);
|
||||
this.buttonPanel.Location = new System.Drawing.Point(0, 240);
|
||||
this.buttonPanel.Name = "buttonPanel";
|
||||
this.buttonPanel.Size = new System.Drawing.Size(484, 45);
|
||||
this.buttonPanel.TabIndex = 7;
|
||||
//
|
||||
// BadBookActionDialog
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||
this.ClientSize = new System.Drawing.Size(484, 285);
|
||||
this.Controls.Add(this.buttonPanel);
|
||||
this.Controls.Add(this.rememberInSettingsCb);
|
||||
this.Controls.Add(this.applyToAllCb);
|
||||
this.Controls.Add(this.messageLbl);
|
||||
this.Controls.Add(this.pictureBox);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||
this.MaximizeBox = false;
|
||||
this.MinimizeBox = false;
|
||||
this.Name = "BadBookActionDialog";
|
||||
this.ShowInTaskbar = true;
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Skip this book?";
|
||||
((System.ComponentModel.ISupportInitialize)(this.pictureBox)).EndInit();
|
||||
this.buttonPanel.ResumeLayout(false);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.PictureBox pictureBox;
|
||||
private System.Windows.Forms.Label messageLbl;
|
||||
private System.Windows.Forms.CheckBox applyToAllCb;
|
||||
private System.Windows.Forms.CheckBox rememberInSettingsCb;
|
||||
private System.Windows.Forms.Button abortBtn;
|
||||
private System.Windows.Forms.Button retryBtn;
|
||||
private System.Windows.Forms.Button ignoreBtn;
|
||||
private System.Windows.Forms.Panel buttonPanel;
|
||||
}
|
||||
}
|
||||
68
Source/LibationWinForms/Dialogs/BadBookActionDialog.cs
Normal file
68
Source/LibationWinForms/Dialogs/BadBookActionDialog.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using LibationUiBase;
|
||||
using LibationUiBase.Forms;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.Dialogs;
|
||||
|
||||
public partial class BadBookActionDialog : Form
|
||||
{
|
||||
public BadBookDialogResult Result { get; private set; } = new(LibationUiBase.Forms.DialogResult.Retry, false, false);
|
||||
|
||||
public BadBookActionDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.SetLibationIcon();
|
||||
}
|
||||
|
||||
public BadBookActionDialog(string message, string caption) : this()
|
||||
{
|
||||
Text = caption;
|
||||
messageLbl.Text = message;
|
||||
|
||||
SizeChanged += (_, _) => AdjustLayout();
|
||||
Shown += (_, _) => AdjustLayout();
|
||||
messageLbl.SizeChanged += (_, _) => AdjustLayout();
|
||||
AdjustLayout();
|
||||
}
|
||||
|
||||
private void AdjustLayout()
|
||||
{
|
||||
const int margin = 12;
|
||||
const int gap = 10;
|
||||
const int messageLeft = 50;
|
||||
|
||||
var messageWidth = ClientSize.Width - messageLeft - margin;
|
||||
messageLbl.Left = messageLeft;
|
||||
messageLbl.Top = margin;
|
||||
messageLbl.Width = messageWidth;
|
||||
messageLbl.MaximumSize = new Size(messageWidth, 0);
|
||||
|
||||
applyToAllCb.Top = messageLbl.Bottom + gap;
|
||||
rememberInSettingsCb.Top = applyToAllCb.Bottom + gap;
|
||||
buttonPanel.Top = rememberInSettingsCb.Bottom + gap;
|
||||
buttonPanel.Width = ClientSize.Width;
|
||||
|
||||
var desiredHeight = buttonPanel.Bottom + margin;
|
||||
if (ClientSize.Height != desiredHeight)
|
||||
ClientSize = new Size(ClientSize.Width, desiredHeight);
|
||||
}
|
||||
|
||||
private void CloseWith(LibationUiBase.Forms.DialogResult action)
|
||||
{
|
||||
Result = new BadBookDialogResult(action, applyToAllCb.Checked, rememberInSettingsCb.Checked);
|
||||
base.DialogResult = System.Windows.Forms.DialogResult.OK;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void AbortBtn_Click(object sender, EventArgs e)
|
||||
=> CloseWith(LibationUiBase.Forms.DialogResult.Abort);
|
||||
|
||||
private void RetryBtn_Click(object sender, EventArgs e)
|
||||
=> CloseWith(LibationUiBase.Forms.DialogResult.Retry);
|
||||
|
||||
private void IgnoreBtn_Click(object sender, EventArgs e)
|
||||
=> CloseWith(LibationUiBase.Forms.DialogResult.Ignore);
|
||||
}
|
||||
55
Source/LibationWinForms/Form1.Debug.cs
Normal file
55
Source/LibationWinForms/Form1.Debug.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
#if DEBUG
|
||||
using DataLayer;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms;
|
||||
|
||||
public partial class Form1
|
||||
{
|
||||
private void Configure_DebugMenu()
|
||||
{
|
||||
var simulateItem = new ToolStripMenuItem("Simulate bad book failures (test dialog)...");
|
||||
simulateItem.Click += async (_, _) => await SimulateBadBookFailuresAsync();
|
||||
|
||||
// Insert before Tour; toolStripSeparator2 above Tour already provides the divider.
|
||||
var insertIndex = settingsToolStripMenuItem.DropDownItems.IndexOf(tourToolStripMenuItem);
|
||||
if (insertIndex < 0)
|
||||
insertIndex = settingsToolStripMenuItem.DropDownItems.Count;
|
||||
|
||||
settingsToolStripMenuItem.DropDownItems.Insert(insertIndex, simulateItem);
|
||||
}
|
||||
|
||||
private async Task SimulateBadBookFailuresAsync()
|
||||
{
|
||||
var books = productsDisplay.GetVisible().Take(5).ToArray();
|
||||
if (books.Length == 0)
|
||||
{
|
||||
MessageBox.Show(
|
||||
this,
|
||||
"No books are visible in the grid.\n\nClear your filter or widen it, then try again.",
|
||||
"Test bad book dialog",
|
||||
MessageBoxButtons.OK,
|
||||
MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
var confirm = MessageBox.Show(
|
||||
this,
|
||||
$"Queue {books.Length} visible book(s) with simulated failures?\n\n"
|
||||
+ "No files will be downloaded. Each book will immediately show the bad-book error dialog.\n\n"
|
||||
+ "Set error handling to \"Ask each time\" in Settings > Download/Decrypt before testing.",
|
||||
"Test bad book dialog",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question,
|
||||
MessageBoxDefaultButton.Button1);
|
||||
|
||||
if (confirm != System.Windows.Forms.DialogResult.Yes)
|
||||
return;
|
||||
|
||||
processBookQueue1.ViewModel.QueueSimulatedBadBookFailures(books);
|
||||
SetQueueCollapseState(false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -50,6 +50,9 @@ public partial class Form1 : Form
|
||||
Configure_Upgrade();
|
||||
// misc which belongs in winforms app but doesn't have a UI element
|
||||
Configure_NonUI();
|
||||
#if DEBUG
|
||||
Configure_DebugMenu();
|
||||
#endif
|
||||
|
||||
// Configure_Grid(); // since it's just this, can keep here. If it needs more, then give grid it's own 'partial class Form1'
|
||||
{
|
||||
|
||||
@@ -48,6 +48,7 @@ static class Program
|
||||
// Migrations which must occur before configuration is loaded for the first time. Usually ones which alter the Configuration
|
||||
var config = AppScaffolding.LibationScaffolding.RunPreConfigMigrations();
|
||||
LibationUiBase.Forms.MessageBoxBase.ShowAsyncImpl = ShowMessageBox;
|
||||
BadBookActionDialogBase.ShowAsyncImpl = ShowBadBookActionDialog;
|
||||
|
||||
// do this as soon as possible (post-config)
|
||||
RunSetupIfNeededAsync(config);
|
||||
@@ -127,6 +128,22 @@ static class Program
|
||||
}
|
||||
#endregion;
|
||||
|
||||
#region Bad Book Action Dialog Handler for LibationUiBase
|
||||
static Task<BadBookDialogResult> ShowBadBookActionDialog(object? owner, string message, string caption)
|
||||
{
|
||||
Func<BadBookDialogResult> showDialog = () =>
|
||||
{
|
||||
BadBookDialogResult result = new(LibationUiBase.Forms.DialogResult.Retry, false, false);
|
||||
using var dialog = new BadBookActionDialog(message, caption);
|
||||
dialog.ShowDialog(owner as IWin32Window ?? form1);
|
||||
return dialog.Result;
|
||||
};
|
||||
|
||||
var result = form1 is null ? showDialog() : form1.Invoke(showDialog);
|
||||
return Task.FromResult(result);
|
||||
}
|
||||
#endregion;
|
||||
|
||||
private static void SetThemeColor(Configuration config)
|
||||
{
|
||||
var theme = config.ThemeVariant switch
|
||||
|
||||
Reference in New Issue
Block a user