diff --git a/Source/LibationAvalonia/Dialogs/FindBetterQualityBooksDialog.axaml b/Source/LibationAvalonia/Dialogs/FindBetterQualityBooksDialog.axaml
index c9909a35..a8f2a0a5 100644
--- a/Source/LibationAvalonia/Dialogs/FindBetterQualityBooksDialog.axaml
+++ b/Source/LibationAvalonia/Dialogs/FindBetterQualityBooksDialog.axaml
@@ -8,7 +8,7 @@
Width="800" Height="450"
x:Class="LibationAvalonia.Dialogs.FindBetterQualityBooksDialog"
x:DataType="vm:FindBetterQualityBooksViewModel"
- Title="FindBetterQualityBooksDialog">
+ Title="Scan Audible for Better Quality Audiobooks">
-
@@ -82,14 +82,13 @@
-
+
-
-
+
+ IsVisible="{Binding SignificantCount}" Click="MarkBooks_Click" />
diff --git a/Source/LibationAvalonia/Dialogs/FindBetterQualityBooksDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/FindBetterQualityBooksDialog.axaml.cs
index a708bdc5..ab3349e1 100644
--- a/Source/LibationAvalonia/Dialogs/FindBetterQualityBooksDialog.axaml.cs
+++ b/Source/LibationAvalonia/Dialogs/FindBetterQualityBooksDialog.axaml.cs
@@ -3,15 +3,21 @@ using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Media;
+using Avalonia.Threading;
using DataLayer;
using LibationUiBase;
+using LibationUiBase.Forms;
+using System;
using System.Linq;
+using System.Threading.Tasks;
namespace LibationAvalonia.Dialogs;
public partial class FindBetterQualityBooksDialog : DialogWindow
{
private FindBetterQualityBooksViewModel VM { get; }
+
+ private Task? scanTask;
public FindBetterQualityBooksDialog()
{
InitializeComponent();
@@ -19,37 +25,126 @@ public partial class FindBetterQualityBooksDialog : DialogWindow
if (Design.IsDesignMode)
{
var library = Enumerable.Repeat(MockLibraryBook.CreateBook(), 3);
- AvaloniaList list = new(library.Select(lb => new FindBetterQualityBooksViewModel.BookData(lb)));
- DataContext = VM = new FindBetterQualityBooksViewModel(list);
+ DataContext = VM = new FindBetterQualityBooksViewModel()
+ {
+ Books = new AvaloniaList(library.Select(lb => new BookDataViewModel(lb)))
+ };
VM.Books[0].AvailableCodec = "xHE-AAC";
VM.Books[0].AvailableBitrate = 256;
- VM.Books[0].ScanStatus = FindBetterQualityBooksViewModel.ScanStatus.Completed;
- VM.Books[1].ScanStatus = FindBetterQualityBooksViewModel.ScanStatus.Error;
- VM.Books[2].ScanStatus = FindBetterQualityBooksViewModel.ScanStatus.Cancelled;
+ VM.Books[0].ScanStatus = BookScanStatus.Completed;
+ VM.Books[1].ScanStatus = BookScanStatus.Error;
+ VM.Books[2].ScanStatus = BookScanStatus.Cancelled;
VM.SignificantCount = 1;
}
else
{
- var library = DbContexts.GetLibrary_Flat_NoTracking();
- AvaloniaList list = new(library.Where(FindBetterQualityBooksViewModel.ShouldScan).Select(lb => new FindBetterQualityBooksViewModel.BookData(lb)));
- DataContext = VM = new FindBetterQualityBooksViewModel(list);
+ DataContext = VM = new FindBetterQualityBooksViewModel();
VM.BookScanned += VM_BookScanned;
+ VM.PropertyChanged += VM_PropertyChanged;
+ Opened += Opened_LoadLibrary;
+ Opened += Opened_ShowInitialMessage;
+ Closing += FindBetterQualityBooksDialog_Closing;
}
}
- private void VM_BookScanned(object? sender, FindBetterQualityBooksViewModel.BookData e)
+ private async void Opened_ShowInitialMessage(object? sender, System.EventArgs e)
{
- booksDataGrid.ScrollIntoView(e, booksDataGrid.Columns[0]);
+ await MessageBox.Show(this, FindBetterQualityBooksViewModel.InitialMessage, Title ?? "", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
+ private async void Opened_LoadLibrary(object? sender, System.EventArgs e)
+ {
+ var library = await Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking());
+ VM.Books = new AvaloniaList(library.Where(FindBetterQualityBooksViewModel.ShouldScan).Select(lb => new BookDataViewModel(lb)));
+ Dispatcher.UIThread.Invoke(() => scanBtn.IsEnabled = true);
+ }
- public static FuncValueConverter RowConverter { get; } = new(status =>
+ private void VM_BookScanned(object? sender, BookDataViewModel e)
+ {
+ Dispatcher.UIThread.Invoke(() => booksDataGrid.ScrollIntoView(e, booksDataGrid.Columns[0]));
+ }
+
+ private void VM_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(FindBetterQualityBooksViewModel.IsScanning))
+ {
+ Dispatcher.UIThread.Invoke(() => scanBtn.IsEnabled = true);
+ }
+ }
+
+ private async void FindBetterQualityBooksDialog_Closing(object? sender, WindowClosingEventArgs e)
+ {
+ if (scanTask is not null)
+ {
+ await scanTask;
+ scanTask = null;
+ Dispatcher.UIThread.Invoke(Close);
+ }
+ }
+ protected override void OnClosing(WindowClosingEventArgs e)
+ {
+ if (scanTask is not null)
+ {
+ this.SaveSizeAndLocation(LibationFileManager.Configuration.Instance);
+ e.Cancel = true;
+ VM.StopScan();
+ }
+ base.OnClosing(e);
+ }
+
+ public void Scan_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ (sender as Button)?.IsEnabled = false;
+ scanTask = Task.Run(async () =>
+ {
+ try
+ {
+ if (VM.IsScanning)
+ VM.StopScan();
+ else
+ await Task.Run(VM.ScanAsync);
+ }
+ catch (Exception ex)
+ {
+ Serilog.Log.Error(ex, "Failed to scan for better quality books");
+ await MessageBox.Show(this, "An error occurred while scanning for better quality books. Please see the logs for more information.", "Error Scanning Books", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ finally
+ {
+ Dispatcher.UIThread.Invoke(() =>
+ {
+ VM.IsScanning = false;
+ (sender as Button)?.IsEnabled = true;
+ });
+ }
+ });
+ }
+
+ public async void MarkBooks_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
+ {
+ (sender as Button)?.IsEnabled = false;
+ try
+ {
+ await VM.MarkBooksAsync();
+ }
+ catch (Exception ex)
+ {
+ Serilog.Log.Error(ex, "Failed to mark books as Not Liberated");
+ await MessageBox.Show(this, "An error occurred while marking books as Not Liberated. Please see the logs for more information.", "Error Marking Books", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ finally
+ {
+ Dispatcher.UIThread.Invoke(() => (sender as Button)?.IsEnabled = true);
+ }
+ }
+
+ public static FuncValueConverter RowConverter { get; } = new(status =>
{
var brush = status switch
{
- FindBetterQualityBooksViewModel.ScanStatus.Completed => "ProcessQueueBookCompletedBrush",
- FindBetterQualityBooksViewModel.ScanStatus.Cancelled => "ProcessQueueBookCancelledBrush",
- FindBetterQualityBooksViewModel.ScanStatus.Error => "ProcessQueueBookFailedBrush",
+ BookScanStatus.Completed => "ProcessQueueBookCompletedBrush",
+ BookScanStatus.Cancelled => "ProcessQueueBookCancelledBrush",
+ BookScanStatus.Error => "ProcessQueueBookFailedBrush",
_ => null,
};
return brush is not null && App.Current.TryGetResource(brush, App.Current.ActualThemeVariant, out var res) ? res as Brush : null;
diff --git a/Source/LibationUiBase/BookDataViewModel.cs b/Source/LibationUiBase/BookDataViewModel.cs
new file mode 100644
index 00000000..84195665
--- /dev/null
+++ b/Source/LibationUiBase/BookDataViewModel.cs
@@ -0,0 +1,53 @@
+using DataLayer;
+
+namespace LibationUiBase;
+
+public enum BookScanStatus
+{
+ None,
+ Error,
+ Cancelled,
+ Completed,
+}
+
+public class BookDataViewModel : ReactiveObject
+{
+ public LibraryBook LibraryBook { get; }
+ public BookDataViewModel(LibraryBook libraryBook)
+ {
+ LibraryBook = libraryBook;
+ Asin = libraryBook.Book.AudibleProductId;
+ Title = libraryBook.Book.Title;
+ }
+ public string Asin { get; }
+ public string Title { get; }
+ public string? FoundFile { get => field; set => RaiseAndSetIfChanged(ref field, value); }
+ public string? Codec { get => field; set => RaiseAndSetIfChanged(ref field, value); }
+ public string? AvailableCodec { get => field; set => RaiseAndSetIfChanged(ref field, value); }
+ public int Bitrate
+ {
+ get => field;
+ set
+ {
+ RaiseAndSetIfChanged(ref field, value);
+ BitrateString = GetBitrateString(value);
+ }
+ }
+ public int AvailableBitrate
+ {
+ get => field;
+ set
+ {
+ RaiseAndSetIfChanged(ref field, value);
+ AvailableBitrateString = GetBitrateString(value);
+ var diff = (double)AvailableBitrate / Bitrate;
+ IsSignificant = diff >= 1.15;
+ }
+ }
+
+ public string? BitrateString { get => field; private set => RaiseAndSetIfChanged(ref field, value); }
+ public string? AvailableBitrateString { get => field; private set => RaiseAndSetIfChanged(ref field, value); }
+ public bool IsSignificant { get => field; private set => RaiseAndSetIfChanged(ref field, value); }
+ public BookScanStatus ScanStatus { get => field; set => RaiseAndSetIfChanged(ref field, value); }
+ private static string? GetBitrateString(int bitrate) => bitrate > 0 ? $"{bitrate} kbps" : null;
+}
diff --git a/Source/LibationUiBase/FindBetterQualityBooksViewModel.cs b/Source/LibationUiBase/FindBetterQualityBooksViewModel.cs
index 7780265c..3017e098 100644
--- a/Source/LibationUiBase/FindBetterQualityBooksViewModel.cs
+++ b/Source/LibationUiBase/FindBetterQualityBooksViewModel.cs
@@ -16,16 +16,28 @@ namespace LibationUiBase;
public class FindBetterQualityBooksViewModel : ReactiveObject
{
- public enum ScanStatus
- {
- None,
- Error,
- Cancelled,
- Completed,
- }
+ public const string StartScanBtnText = "Scan Audible for Higher Quality Audio";
+ public const string StopScanBtnText = "Stop Scanning";
+ public const string UseWidevineSboxText = "Use Widevine?";
+ public const string InitialMessage = """
+ This tool will scan your liberated audiobooks to see if Audible
+ has a higher quality version available.
- public event EventHandler? BookScanned;
- public IList Books { get; }
+ For each liberated audiobook in your library, it will try to read the existing audio file to determine its codec and bitrate. If no local file is found, it will use the 'Last Downloaded' format information stored in the database.
+
+ It will then query Audible's API to get the highest quality format currently available for that audiobook.
+
+ If you check the 'Use Widevine' option, it will query for Widevine-protected formats, which may or may not be xHE-AAC. If unchecked, it will query for Audible DRM-protected formats, which are typically AAC-LC.
+
+ Click 'Scan Audible for Higher Quality Audio' to begin.
+
+ When done, click the 'Mark X books as Not Liberated' to allow Libation to re-download those books in the higher.
+
+ Note: make sure you adjust your download quality settings before re-liberating the books.
+ """;
+
+ public event EventHandler? BookScanned;
+ public IList? Books { get => field; set => RaiseAndSetIfChanged(ref field, value); }
public bool ScanWidevine { get; set; }
public int SignificantCount
@@ -33,21 +45,21 @@ public class FindBetterQualityBooksViewModel : ReactiveObject
get => field;
set
{
- this.RaiseAndSetIfChanged(ref field, value);
- MarkBooksButtonText = value == 0 ? string.Empty
+ RaiseAndSetIfChanged(ref field, value);
+ MarkBooksButtonText = value == 0 ? null
: value == 1 ? "Mark 1 book as 'Not Liberated'"
: $"Mark {value} books as 'Not Liberated'";
}
}
- public bool IsScanning { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
- public string? MarkBooksButtonText { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
- public string? ScanCount { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
+ public bool IsScanning { get => field; set { RaiseAndSetIfChanged(ref field, value); ScanButtonText = field ? StopScanBtnText : StartScanBtnText; } }
+ public string? MarkBooksButtonText { get => field; set => RaiseAndSetIfChanged(ref field, value); }
+ public string? ScanCount { get => field; set => RaiseAndSetIfChanged(ref field, value); }
+ public string ScanButtonText { get => field; set => RaiseAndSetIfChanged(ref field, value); } = StartScanBtnText;
private CancellationTokenSource? cts;
- public FindBetterQualityBooksViewModel(IList books)
+ public FindBetterQualityBooksViewModel()
{
- Books = books;
ScanWidevine = Configuration.Instance.UseWidevine;
}
@@ -63,17 +75,17 @@ public class FindBetterQualityBooksViewModel : ReactiveObject
public async Task MarkBooksAsync()
{
- var significant = Books.Where(b => b.IsSignificant).ToArray();
+ var significant = Books?.Where(b => b.IsSignificant).ToArray() ?? [];
await significant.Select(b => b.LibraryBook).UpdateBookStatusAsync(LiberatedStatus.NotLiberated);
- Array.ForEach(significant, b => Books.Remove(b));
+ Array.ForEach(significant, b => Books?.Remove(b));
- SignificantCount = Books.Count(b => b.IsSignificant);
+ SignificantCount = Books?.Count(b => b.IsSignificant) ?? 0;
}
public async Task ScanAsync()
{
- if (cts?.IsCancellationRequested is true)
+ if (cts?.IsCancellationRequested is true || Books is null)
return;
IsScanning = true;
@@ -81,7 +93,7 @@ public class FindBetterQualityBooksViewModel : ReactiveObject
{
b.AvailableBitrate = 0;
b.AvailableCodec = null;
- b.ScanStatus = ScanStatus.None;
+ b.ScanStatus = BookScanStatus.None;
}
ScanCount = $"0 of {Books.Count:N0} scanned";
@@ -115,7 +127,7 @@ public class FindBetterQualityBooksViewModel : ReactiveObject
else
{
b.FoundFile = "File not found and no 'Last Downloaded' format found.";
- b.ScanStatus = ScanStatus.Error;
+ b.ScanStatus = BookScanStatus.Error;
continue;
}
}
@@ -125,22 +137,23 @@ public class FindBetterQualityBooksViewModel : ReactiveObject
b.AvailableCodec = codecString;
b.AvailableBitrate = bitrate;
- b.ScanStatus = ScanStatus.Completed;
+ b.ScanStatus = BookScanStatus.Completed;
}
catch (OperationCanceledException)
{
- b.ScanStatus = ScanStatus.Cancelled;
+ b.ScanStatus = BookScanStatus.Cancelled;
break;
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "Error checking for better quality for {@Asin}", b.Asin);
- b.ScanStatus = ScanStatus.Error;
+ b.FoundFile = $"Error: {ex.Message}";
+ b.ScanStatus = BookScanStatus.Error;
}
finally
{
SignificantCount = Books.Count(b => b.IsSignificant);
- ScanCount = $"{i:N0} of {Books.Count:N0} scanned";
+ ScanCount = $"{i + 1:N0} of {Books.Count:N0} scanned";
BookScanned?.Invoke(this, b);
}
}
@@ -196,46 +209,5 @@ public class FindBetterQualityBooksViewModel : ReactiveObject
return string.Format(BaseUrl, locale.TopDomain, libraryBook.Book.AudibleProductId, drm_type);
}
- const string BaseUrl = "ht" + "tps://api.audible.{0}/1.0/content/{1}/metadata?response_groups=chapter_info,content_reference&quality=High&drm_type={2}";
- public class BookData : ReactiveObject
- {
- public LibraryBook LibraryBook { get; }
- public BookData(LibraryBook libraryBook)
- {
- LibraryBook = libraryBook;
- Asin = libraryBook.Book.AudibleProductId;
- Title = libraryBook.Book.Title;
- }
- public string Asin { get; }
- public string Title { get; }
- public string? FoundFile { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
- public string? Codec { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
- public string? AvailableCodec { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
- public int Bitrate
- {
- get => field;
- set
- {
- this.RaiseAndSetIfChanged(ref field, value);
- BitrateString = GetBitrateString(value);
- }
- }
- public int AvailableBitrate
- {
- get => field;
- set
- {
- this.RaiseAndSetIfChanged(ref field, value);
- AvailableBitrateString = GetBitrateString(value);
- var diff = (double)AvailableBitrate / Bitrate;
- IsSignificant = diff >= 1.15;
- }
- }
-
- public string? BitrateString { get => field; private set => this.RaiseAndSetIfChanged(ref field, value); }
- public string? AvailableBitrateString { get => field; private set => this.RaiseAndSetIfChanged(ref field, value); }
- public bool IsSignificant { get => field; private set => this.RaiseAndSetIfChanged(ref field, value); }
- public ScanStatus ScanStatus { get => field; set => this.RaiseAndSetIfChanged(ref field, value); }
- private static string? GetBitrateString(int bitrate) => bitrate > 0 ? $"{bitrate} kbps" : null;
- }
+ const string BaseUrl = "ht" + "tps://api.audible.{0}/1.0/content/{1}/metadata?response_groups=chapter_info,content_reference&quality=High&drm_type={2}";
}
diff --git a/Source/LibationWinForms/AccessibleDataGridViewTextBoxCell.cs b/Source/LibationWinForms/AccessibleDataGridViewTextBoxCell.cs
index 0801a725..7877c9c4 100644
--- a/Source/LibationWinForms/AccessibleDataGridViewTextBoxCell.cs
+++ b/Source/LibationWinForms/AccessibleDataGridViewTextBoxCell.cs
@@ -1,21 +1,65 @@
-using System.Windows.Forms;
+using System.ComponentModel;
+using System.Windows.Forms;
namespace LibationWinForms
{
- internal class AccessibleDataGridViewTextBoxCell : DataGridViewTextBoxCell
- {
- protected string AccessibilityName { get; }
+ public class AccessibleDataGridViewColumn : DataGridViewColumn
+ {
+ [DefaultValue(null)]
+ [Category("Accessibility")]
+ [Description("Accessibility Object Name")]
+ public string AccessibilityName { get => field; set { field = value; cellTemplate.AccessibilityName = value; } }
- ///
- /// Get or set description for accessibility. eg: screen readers. Also sets the ToolTipText
- ///
- protected string AccessibilityDescription
+ [DefaultValue(null)]
+ [Category("Accessibility")]
+ [Description("Accessibility Object Description")]
+ public string AccessibilityDescription { get => field; set { field = value; cellTemplate.AccessibilityDescription = value; } }
+ private readonly AccessibleDataGridViewTextBoxCell cellTemplate;
+
+ public AccessibleDataGridViewColumn()
+ {
+ CellTemplate = cellTemplate = new AccessibleDataGridViewTextBoxCell();
+ }
+ public AccessibleDataGridViewColumn(AccessibleDataGridViewTextBoxCell cellTemplate) : base(cellTemplate)
+ {
+ this.cellTemplate = cellTemplate;
+ }
+
+ public override object Clone()
+ {
+ //This is necessary for the designer to work properly
+ var col = (AccessibleDataGridViewColumn)base.Clone();
+ col.AccessibilityDescription = AccessibilityDescription;
+ col.AccessibilityName = AccessibilityName;
+ return col;
+ }
+ }
+
+ public class AccessibleDataGridViewTextBoxCell : DataGridViewTextBoxCell
+ {
+ private string _accessibilityName;
+
+ public string AccessibilityName
+ {
+ get => _accessibilityName;
+ set
+ {
+ _accessibilityName = value;
+ (AccessibilityObject as TextBoxCellAccessibilityObject).SetName(_accessibilityName);
+ }
+ }
+
+ ///
+ /// Get or set description for accessibility. eg: screen readers. Also sets the ToolTipText
+ ///
+ public string AccessibilityDescription
{
get => field;
set
{
field = value;
- ToolTipText = value;
+ (AccessibilityObject as TextBoxCellAccessibilityObject).SetDescription(field);
+ ToolTipText = value;
}
}
@@ -23,9 +67,11 @@ namespace LibationWinForms
public AccessibleDataGridViewTextBoxCell(string accessibilityName) : base()
{
- AccessibilityName = accessibilityName;
+ _accessibilityName = accessibilityName;
}
+ public AccessibleDataGridViewTextBoxCell() { }
+
protected class TextBoxCellAccessibilityObject : DataGridViewTextBoxCellAccessibleObject
{
private string _name;
@@ -34,6 +80,9 @@ namespace LibationWinForms
private string _description;
public override string Description => _description;
+ public void SetName(string name) => _name = name;
+ public void SetDescription(string description) => _description = description;
+
public TextBoxCellAccessibilityObject(DataGridViewCell owner, string name, string description) : base(owner)
{
_name = name;
diff --git a/Source/LibationWinForms/BitrateDataGridTextBoxColumn.cs b/Source/LibationWinForms/BitrateDataGridTextBoxColumn.cs
new file mode 100644
index 00000000..8095acee
--- /dev/null
+++ b/Source/LibationWinForms/BitrateDataGridTextBoxColumn.cs
@@ -0,0 +1,21 @@
+using System.Drawing;
+using System.Windows.Forms;
+
+namespace LibationWinForms
+{
+ public class BitrateDataGridTextBoxColumn : AccessibleDataGridViewColumn
+ {
+ public BitrateDataGridTextBoxColumn() : base(new BitrateDataGridViewTextBoxCell()) { }
+ private class BitrateDataGridViewTextBoxCell : AccessibleDataGridViewTextBoxCell
+ {
+ protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
+ {
+ if (value is int bitrate)
+ {
+ formattedValue = bitrate > 0 ? $"{bitrate} kbps" : "";
+ }
+ base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts);
+ }
+ }
+ }
+}
diff --git a/Source/LibationWinForms/Dialogs/BookRecordsDialog.cs b/Source/LibationWinForms/Dialogs/BookRecordsDialog.cs
index d9edb711..65e8e53c 100644
--- a/Source/LibationWinForms/Dialogs/BookRecordsDialog.cs
+++ b/Source/LibationWinForms/Dialogs/BookRecordsDialog.cs
@@ -2,9 +2,9 @@
using AudibleApi.Common;
using DataLayer;
using FileLiberator;
+using LibationWinForms;
using System;
using System.Collections.Generic;
-using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
@@ -16,7 +16,7 @@ namespace LibationWinForms.Dialogs
{
private readonly Func VScrollBar;
private readonly LibraryBook libraryBook;
- private BookRecordBindingList bookRecordEntries;
+ private SortBindingList bookRecordEntries;
public BookRecordsDialog()
{
@@ -55,7 +55,7 @@ namespace LibationWinForms.Dialogs
var api = await libraryBook.GetApiAsync();
var records = await api.GetRecordsAsync(libraryBook.Book.AudibleProductId);
- bookRecordEntries = new BookRecordBindingList(records.Select(r => new BookRecordEntry(r)));
+ bookRecordEntries = new SortBindingList(records.Select(r => new BookRecordEntry(r)));
}
catch(Exception ex)
{
@@ -158,7 +158,7 @@ namespace LibationWinForms.Dialogs
var api = await libraryBook.GetApiAsync();
var records = await api.GetRecordsAsync(libraryBook.Book.AudibleProductId);
- bookRecordEntries = new BookRecordBindingList(records.Select(r => new BookRecordEntry(r)));
+ bookRecordEntries = new SortBindingList(records.Select(r => new BookRecordEntry(r)));
syncBindingSource.DataSource = bookRecordEntries;
}
catch (Exception ex)
@@ -219,37 +219,6 @@ namespace LibationWinForms.Dialogs
#region DataGridView Bindings
- private class BookRecordBindingList : BindingList
- {
- private PropertyDescriptor _propertyDescriptor;
- private ListSortDirection _listSortDirection;
- private bool _isSortedCore;
-
- protected override PropertyDescriptor SortPropertyCore => _propertyDescriptor;
- protected override ListSortDirection SortDirectionCore => _listSortDirection;
- protected override bool IsSortedCore => _isSortedCore;
- protected override bool SupportsSortingCore => true;
- public BookRecordBindingList() : base(new List()) { }
- public BookRecordBindingList(IEnumerable records) : base(records.ToList()) { }
- protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
- {
- var itemsList = (List)Items;
-
- var sorted =
- direction is ListSortDirection.Ascending ? itemsList.OrderBy(prop.GetValue).ToList()
- : itemsList.OrderByDescending(prop.GetValue).ToList();
-
- itemsList.Clear();
- itemsList.AddRange(sorted);
-
- _propertyDescriptor = prop;
- _listSortDirection = direction;
- _isSortedCore = true;
-
- OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
- }
- }
-
private class BookRecordEntry : LibationUiBase.ReactiveObject
{
private const string DateFormat = "yyyy-MM-dd HH\\:mm";
diff --git a/Source/LibationWinForms/Dialogs/FindBetterQualityBooksDialog.Designer.cs b/Source/LibationWinForms/Dialogs/FindBetterQualityBooksDialog.Designer.cs
new file mode 100644
index 00000000..d5f42b5a
--- /dev/null
+++ b/Source/LibationWinForms/Dialogs/FindBetterQualityBooksDialog.Designer.cs
@@ -0,0 +1,242 @@
+using LibationWinForms.GridView;
+
+namespace LibationWinForms.Dialogs;
+
+partial class FindBetterQualityBooksDialog
+{
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ components = new System.ComponentModel.Container();
+ dataGridView1 = new System.Windows.Forms.DataGridView();
+ asinDataGridViewTextBoxColumn = new AccessibleDataGridViewColumn();
+ titleDataGridViewTextBoxColumn = new AccessibleDataGridViewColumn();
+ foundFileDataGridViewTextBoxColumn = new AccessibleDataGridViewColumn();
+ codecDataGridViewTextBoxColumn = new AccessibleDataGridViewColumn();
+ bitrateStringDataGridViewTextBoxColumn = new BitrateDataGridTextBoxColumn();
+ availableCodecDataGridViewTextBoxColumn = new AccessibleDataGridViewColumn();
+ availableBitrateStringDataGridViewTextBoxColumn = new BitrateDataGridTextBoxColumn();
+ isSignificantDataGridViewCheckBoxColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn();
+ bookDataViewModelBindingSource = new SyncBindingSource(components);
+ btnScan = new System.Windows.Forms.Button();
+ cboxUseWidevine = new System.Windows.Forms.CheckBox();
+ lblScanCount = new System.Windows.Forms.Label();
+ btnMarkBooks = new System.Windows.Forms.Button();
+ ((System.ComponentModel.ISupportInitialize)dataGridView1).BeginInit();
+ ((System.ComponentModel.ISupportInitialize)bookDataViewModelBindingSource).BeginInit();
+ SuspendLayout();
+ //
+ // dataGridView1
+ //
+ dataGridView1.AllowUserToAddRows = false;
+ dataGridView1.AllowUserToDeleteRows = false;
+ dataGridView1.AllowUserToResizeRows = false;
+ dataGridView1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
+ dataGridView1.AutoGenerateColumns = false;
+ dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
+ dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { asinDataGridViewTextBoxColumn, titleDataGridViewTextBoxColumn, foundFileDataGridViewTextBoxColumn, codecDataGridViewTextBoxColumn, bitrateStringDataGridViewTextBoxColumn, availableCodecDataGridViewTextBoxColumn, availableBitrateStringDataGridViewTextBoxColumn, isSignificantDataGridViewCheckBoxColumn });
+ dataGridView1.DataSource = bookDataViewModelBindingSource;
+ dataGridView1.Location = new System.Drawing.Point(12, 12);
+ dataGridView1.Name = "dataGridView1";
+ dataGridView1.RowHeadersVisible = false;
+ dataGridView1.Size = new System.Drawing.Size(897, 397);
+ dataGridView1.TabIndex = 0;
+ dataGridView1.CellFormatting += dataGridView1_CellFormatting;
+ //
+ // asinDataGridViewTextBoxColumn
+ //
+ asinDataGridViewTextBoxColumn.AccessibilityDescription = "Audible product identifier.";
+ asinDataGridViewTextBoxColumn.AccessibilityName = "ASIN";
+ asinDataGridViewTextBoxColumn.DataPropertyName = "Asin";
+ asinDataGridViewTextBoxColumn.HeaderText = "Asin";
+ asinDataGridViewTextBoxColumn.Name = "asinDataGridViewTextBoxColumn";
+ asinDataGridViewTextBoxColumn.ReadOnly = true;
+ asinDataGridViewTextBoxColumn.Resizable = System.Windows.Forms.DataGridViewTriState.True;
+ asinDataGridViewTextBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
+ asinDataGridViewTextBoxColumn.Width = 80;
+ //
+ // titleDataGridViewTextBoxColumn
+ //
+ titleDataGridViewTextBoxColumn.AccessibilityDescription = "Title of the Audiobook to scan for.";
+ titleDataGridViewTextBoxColumn.AccessibilityName = "Book Title";
+ titleDataGridViewTextBoxColumn.DataPropertyName = "Title";
+ titleDataGridViewTextBoxColumn.HeaderText = "Title";
+ titleDataGridViewTextBoxColumn.Name = "titleDataGridViewTextBoxColumn";
+ titleDataGridViewTextBoxColumn.ReadOnly = true;
+ titleDataGridViewTextBoxColumn.Resizable = System.Windows.Forms.DataGridViewTriState.True;
+ titleDataGridViewTextBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
+ titleDataGridViewTextBoxColumn.Width = 180;
+ //
+ // foundFileDataGridViewTextBoxColumn
+ //
+ foundFileDataGridViewTextBoxColumn.AccessibilityDescription = "Highest quality audio file that has been found in your 'Books' folder.";
+ foundFileDataGridViewTextBoxColumn.AccessibilityName = "Best Found File";
+ foundFileDataGridViewTextBoxColumn.DataPropertyName = "FoundFile";
+ foundFileDataGridViewTextBoxColumn.HeaderText = "Best Found File";
+ foundFileDataGridViewTextBoxColumn.Name = "foundFileDataGridViewTextBoxColumn";
+ foundFileDataGridViewTextBoxColumn.ReadOnly = true;
+ foundFileDataGridViewTextBoxColumn.Resizable = System.Windows.Forms.DataGridViewTriState.True;
+ foundFileDataGridViewTextBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
+ foundFileDataGridViewTextBoxColumn.Width = 180;
+ //
+ // codecDataGridViewTextBoxColumn
+ //
+ codecDataGridViewTextBoxColumn.AccessibilityDescription = "The audio format codec of the Best Found File";
+ codecDataGridViewTextBoxColumn.AccessibilityName = "Existing Codec";
+ codecDataGridViewTextBoxColumn.DataPropertyName = "Codec";
+ codecDataGridViewTextBoxColumn.HeaderText = "Existing Codec";
+ codecDataGridViewTextBoxColumn.Name = "codecDataGridViewTextBoxColumn";
+ codecDataGridViewTextBoxColumn.ReadOnly = true;
+ codecDataGridViewTextBoxColumn.Resizable = System.Windows.Forms.DataGridViewTriState.True;
+ codecDataGridViewTextBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
+ codecDataGridViewTextBoxColumn.Width = 80;
+ //
+ // bitrateStringDataGridViewTextBoxColumn
+ //
+ bitrateStringDataGridViewTextBoxColumn.AccessibilityDescription = "The audio bitrate of the Best Found File";
+ bitrateStringDataGridViewTextBoxColumn.AccessibilityName = "Existing Bitrate";
+ bitrateStringDataGridViewTextBoxColumn.DataPropertyName = "Bitrate";
+ bitrateStringDataGridViewTextBoxColumn.HeaderText = "Existing Bitrate";
+ bitrateStringDataGridViewTextBoxColumn.Name = "bitrateStringDataGridViewTextBoxColumn";
+ bitrateStringDataGridViewTextBoxColumn.ReadOnly = true;
+ bitrateStringDataGridViewTextBoxColumn.Resizable = System.Windows.Forms.DataGridViewTriState.True;
+ bitrateStringDataGridViewTextBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
+ bitrateStringDataGridViewTextBoxColumn.Width = 80;
+ //
+ // availableCodecDataGridViewTextBoxColumn
+ //
+ availableCodecDataGridViewTextBoxColumn.AccessibilityDescription = "The audio format codec available from Audible.";
+ availableCodecDataGridViewTextBoxColumn.AccessibilityName = "Available Codec";
+ availableCodecDataGridViewTextBoxColumn.DataPropertyName = "AvailableCodec";
+ availableCodecDataGridViewTextBoxColumn.HeaderText = "Available Codec";
+ availableCodecDataGridViewTextBoxColumn.Name = "availableCodecDataGridViewTextBoxColumn";
+ availableCodecDataGridViewTextBoxColumn.ReadOnly = true;
+ availableCodecDataGridViewTextBoxColumn.Resizable = System.Windows.Forms.DataGridViewTriState.True;
+ availableCodecDataGridViewTextBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
+ availableCodecDataGridViewTextBoxColumn.Width = 80;
+ //
+ // availableBitrateStringDataGridViewTextBoxColumn
+ //
+ availableBitrateStringDataGridViewTextBoxColumn.AccessibilityDescription = "The highest audio bitrate available from Audible.";
+ availableBitrateStringDataGridViewTextBoxColumn.AccessibilityName = "Available Bitrate";
+ availableBitrateStringDataGridViewTextBoxColumn.DataPropertyName = "AvailableBitrate";
+ availableBitrateStringDataGridViewTextBoxColumn.HeaderText = "Available Bitrate";
+ availableBitrateStringDataGridViewTextBoxColumn.Name = "availableBitrateStringDataGridViewTextBoxColumn";
+ availableBitrateStringDataGridViewTextBoxColumn.ReadOnly = true;
+ availableBitrateStringDataGridViewTextBoxColumn.Resizable = System.Windows.Forms.DataGridViewTriState.True;
+ availableBitrateStringDataGridViewTextBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
+ availableBitrateStringDataGridViewTextBoxColumn.Width = 80;
+ //
+ // isSignificantDataGridViewCheckBoxColumn
+ //
+ isSignificantDataGridViewCheckBoxColumn.DataPropertyName = "IsSignificant";
+ isSignificantDataGridViewCheckBoxColumn.HeaderText = "Significantly Greater?";
+ isSignificantDataGridViewCheckBoxColumn.Name = "isSignificantDataGridViewCheckBoxColumn";
+ isSignificantDataGridViewCheckBoxColumn.ReadOnly = true;
+ isSignificantDataGridViewCheckBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
+ //
+ // bookDataViewModelBindingSource
+ //
+ bookDataViewModelBindingSource.DataSource = typeof(LibationUiBase.BookDataViewModel);
+ //
+ // btnScan
+ //
+ btnScan.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
+ btnScan.Location = new System.Drawing.Point(120, 415);
+ btnScan.Name = "btnScan";
+ btnScan.Size = new System.Drawing.Size(221, 23);
+ btnScan.TabIndex = 1;
+ btnScan.Text = "Scan Audible for Higher Quality Audio";
+ btnScan.UseVisualStyleBackColor = true;
+ btnScan.Click += btnScan_Click;
+ //
+ // cboxUseWidevine
+ //
+ cboxUseWidevine.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
+ cboxUseWidevine.AutoSize = true;
+ cboxUseWidevine.Location = new System.Drawing.Point(12, 418);
+ cboxUseWidevine.Name = "cboxUseWidevine";
+ cboxUseWidevine.Size = new System.Drawing.Size(102, 19);
+ cboxUseWidevine.TabIndex = 2;
+ cboxUseWidevine.Text = "Use Widevine?";
+ cboxUseWidevine.UseVisualStyleBackColor = true;
+ //
+ // lblScanCount
+ //
+ lblScanCount.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
+ lblScanCount.AutoSize = true;
+ lblScanCount.Location = new System.Drawing.Point(369, 419);
+ lblScanCount.Name = "lblScanCount";
+ lblScanCount.Size = new System.Drawing.Size(52, 15);
+ lblScanCount.TabIndex = 3;
+ lblScanCount.Text = "## of ##";
+ //
+ // btnMarkBooks
+ //
+ btnMarkBooks.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
+ btnMarkBooks.Location = new System.Drawing.Point(699, 415);
+ btnMarkBooks.Name = "btnMarkBooks";
+ btnMarkBooks.Size = new System.Drawing.Size(210, 23);
+ btnMarkBooks.TabIndex = 4;
+ btnMarkBooks.Text = "Mark 1,000 books as 'Not Liberated'";
+ btnMarkBooks.UseVisualStyleBackColor = true;
+ btnMarkBooks.Click += btnMarkBooks_Click;
+ //
+ // FindBetterQualityBooksDialog
+ //
+ AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
+ AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ ClientSize = new System.Drawing.Size(921, 450);
+ Controls.Add(btnMarkBooks);
+ Controls.Add(lblScanCount);
+ Controls.Add(cboxUseWidevine);
+ Controls.Add(btnScan);
+ Controls.Add(dataGridView1);
+ Name = "FindBetterQualityBooksDialog";
+ Text = "Scan Audible for Better Quality Audiobooks";
+ ((System.ComponentModel.ISupportInitialize)dataGridView1).EndInit();
+ ((System.ComponentModel.ISupportInitialize)bookDataViewModelBindingSource).EndInit();
+ ResumeLayout(false);
+ PerformLayout();
+ }
+
+ #endregion
+
+ private System.Windows.Forms.DataGridView dataGridView1;
+ private LibationWinForms.GridView.SyncBindingSource bookDataViewModelBindingSource;
+ private System.Windows.Forms.Button btnScan;
+ private AccessibleDataGridViewColumn asinDataGridViewTextBoxColumn;
+ private AccessibleDataGridViewColumn titleDataGridViewTextBoxColumn;
+ private AccessibleDataGridViewColumn foundFileDataGridViewTextBoxColumn;
+ private AccessibleDataGridViewColumn codecDataGridViewTextBoxColumn;
+ private BitrateDataGridTextBoxColumn bitrateStringDataGridViewTextBoxColumn;
+ private AccessibleDataGridViewColumn availableCodecDataGridViewTextBoxColumn;
+ private BitrateDataGridTextBoxColumn availableBitrateStringDataGridViewTextBoxColumn;
+ private System.Windows.Forms.DataGridViewCheckBoxColumn isSignificantDataGridViewCheckBoxColumn;
+ private System.Windows.Forms.CheckBox cboxUseWidevine;
+ private System.Windows.Forms.Label lblScanCount;
+ private System.Windows.Forms.Button btnMarkBooks;
+}
\ No newline at end of file
diff --git a/Source/LibationWinForms/Dialogs/FindBetterQualityBooksDialog.cs b/Source/LibationWinForms/Dialogs/FindBetterQualityBooksDialog.cs
new file mode 100644
index 00000000..1961aac0
--- /dev/null
+++ b/Source/LibationWinForms/Dialogs/FindBetterQualityBooksDialog.cs
@@ -0,0 +1,173 @@
+using ApplicationServices;
+using LibationUiBase;
+using System;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+#nullable enable
+namespace LibationWinForms.Dialogs;
+
+public partial class FindBetterQualityBooksDialog : Form
+{
+ private FindBetterQualityBooksViewModel VM { get; }
+
+ private Task? scanTask;
+ public FindBetterQualityBooksDialog()
+ {
+ InitializeComponent();
+
+ dataGridView1.EnableHeadersVisualStyles = !Application.IsDarkModeEnabled;
+ lblScanCount.Visible = btnMarkBooks.Visible = false;
+ DataContext = VM = new FindBetterQualityBooksViewModel();
+ VM.PropertyChanged += VM_PropertyChanged;
+ VM.BookScanned += VM_BookScanned;
+
+ cboxUseWidevine.Text = FindBetterQualityBooksViewModel.UseWidevineSboxText;
+ cboxUseWidevine.DataBindings.Add(new Binding(nameof(CheckBox.Checked), VM, nameof(FindBetterQualityBooksViewModel.ScanWidevine)));
+ btnScan.DataBindings.Add(new Binding(nameof(Button.Text), VM, nameof(FindBetterQualityBooksViewModel.ScanButtonText)));
+ btnScan.Enabled = false;
+
+ this.RestoreSizeAndLocation(LibationFileManager.Configuration.Instance);
+ this.SetLibationIcon();
+ Shown += Shown_LoadLibrary;
+ Shown += Shown_ShowInitialMessage;
+ FormClosing += FindBetterQualityBooksDialog_FormClosing;
+ }
+
+
+ private void Shown_ShowInitialMessage(object? sender, EventArgs e)
+ {
+ MessageBox.Show(this, FindBetterQualityBooksViewModel.InitialMessage, Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
+ }
+
+ private async void Shown_LoadLibrary(object? sender, EventArgs e)
+ {
+ var library = await Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking());
+ var list = library.Where(FindBetterQualityBooksViewModel.ShouldScan).Select(lb => new BookDataViewModel(lb)).ToList();
+ VM.Books = new SortBindingList(list);
+
+ Invoke(() =>
+ {
+ bookDataViewModelBindingSource.DataSource = VM.Books;
+ btnScan.Enabled = true;
+ });
+ }
+
+ private void VM_BookScanned(object? sender, BookDataViewModel e)
+ {
+ Invoke(() => dataGridView1.CurrentCell = dataGridView1.Rows
+ .Cast()
+ .FirstOrDefault(r => r.DataBoundItem == e)?
+ .Cells[foundFileDataGridViewTextBoxColumn.Index]);
+ }
+
+ private void VM_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
+ {
+ switch (e.PropertyName)
+ {
+ case nameof(FindBetterQualityBooksViewModel.IsScanning):
+ btnScan.Enabled = true;
+ break;
+ case nameof(FindBetterQualityBooksViewModel.ScanCount):
+ lblScanCount.Visible = !string.IsNullOrEmpty(VM.ScanCount);
+ lblScanCount.Text = VM.ScanCount;
+ break;
+ case nameof(FindBetterQualityBooksViewModel.MarkBooksButtonText):
+ btnMarkBooks.Visible = !string.IsNullOrEmpty(VM.MarkBooksButtonText);
+ btnMarkBooks.Text = VM.MarkBooksButtonText;
+ break;
+ }
+ }
+
+ private async void FindBetterQualityBooksDialog_FormClosing(object? sender, FormClosingEventArgs e)
+ {
+ if (scanTask is not null)
+ {
+ await scanTask;
+ scanTask = null;
+ Invoke(Close);
+ }
+ }
+
+ protected override void OnFormClosing(FormClosingEventArgs e)
+ {
+ if (scanTask is not null)
+ {
+ this.SaveSizeAndLocation(LibationFileManager.Configuration.Instance);
+ e.Cancel = true;
+ VM.StopScan();
+ }
+ base.OnFormClosing(e);
+ }
+
+ private void btnScan_Click(object sender, EventArgs e)
+ {
+ btnScan.Enabled = false;
+ scanTask = Task.Run(async () =>
+ {
+ try
+ {
+ if (VM.IsScanning)
+ VM.StopScan();
+ else
+ await VM.ScanAsync();
+ }
+ catch (Exception ex)
+ {
+ Serilog.Log.Error(ex, "Failed to scan for better quality books");
+ MessageBox.Show(this, "An error occurred while scanning for better quality books. Please see the logs for more information.", "Error Scanning Books", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ finally
+ {
+ Invoke(() =>
+ {
+ VM.IsScanning = false;
+ btnScan.Enabled = true;
+ });
+ }
+ });
+ }
+
+ private async void btnMarkBooks_Click(object sender, EventArgs e)
+ {
+ btnMarkBooks.Enabled = false;
+ try
+ {
+ await VM.MarkBooksAsync();
+ }
+ catch (Exception ex)
+ {
+ Serilog.Log.Error(ex, "Failed to mark books as Not Liberated");
+ MessageBox.Show(this, "An error occurred while marking books as Not Liberated. Please see the logs for more information.", "Error Marking Books", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ finally
+ {
+ Invoke(() => btnMarkBooks.Enabled = true);
+ }
+ }
+
+ private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
+ {
+ if (e.RowIndex < 0 || e.RowIndex >= dataGridView1.Rows.Count)
+ return;
+
+ var row = dataGridView1.Rows[e.RowIndex];
+ if (row.DataBoundItem is BookDataViewModel bvm)
+ {
+ ///yes, this is tight coupling and bad practice.
+ ///If we ever need tese colors in a third place,
+ ///consider moving them to a shared location like
+ ///App.axaml in LibationAvalonia
+ var color = bvm.ScanStatus switch
+ {
+ BookScanStatus.Completed => ProcessQueue.ProcessBookControl.SuccessColor,
+ BookScanStatus.Cancelled => ProcessQueue.ProcessBookControl.CancelledColor,
+ BookScanStatus.Error => ProcessQueue.ProcessBookControl.FailedColor,
+ _ => ProcessQueue.ProcessBookControl.QueuedColor,
+ };
+ row.DefaultCellStyle.BackColor = color;
+ }
+ }
+}
diff --git a/Source/LibationWinForms/Dialogs/FindBetterQualityBooksDialog.resx b/Source/LibationWinForms/Dialogs/FindBetterQualityBooksDialog.resx
new file mode 100644
index 00000000..06c325ca
--- /dev/null
+++ b/Source/LibationWinForms/Dialogs/FindBetterQualityBooksDialog.resx
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ 17, 17
+
+
+ 52
+
+
\ No newline at end of file
diff --git a/Source/LibationWinForms/Form1.Designer.cs b/Source/LibationWinForms/Form1.Designer.cs
index ee067251..70ad0f60 100644
--- a/Source/LibationWinForms/Form1.Designer.cs
+++ b/Source/LibationWinForms/Form1.Designer.cs
@@ -63,6 +63,7 @@
this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator();
this.removeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator4 = new System.Windows.Forms.ToolStripSeparator();
+ this.toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator();
this.openTrashBinToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.launchHangoverToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.locateAudiobooksToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
@@ -71,6 +72,7 @@
this.basicSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
this.tourToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.scanForHigherQualityBooksStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
this.upgradePb = new System.Windows.Forms.ToolStripProgressBar();
@@ -387,6 +389,8 @@
this.toolStripSeparator4,
this.openTrashBinToolStripMenuItem,
this.launchHangoverToolStripMenuItem,
+ this.toolStripSeparator5,
+ this.scanForHigherQualityBooksStripMenuItem,
this.toolStripSeparator2,
this.tourToolStripMenuItem,
this.aboutToolStripMenuItem});
@@ -419,6 +423,13 @@
this.tourToolStripMenuItem.Size = new System.Drawing.Size(133, 22);
this.tourToolStripMenuItem.Text = "Take a Guided &Tour of Libation";
this.tourToolStripMenuItem.Click += new System.EventHandler(this.tourToolStripMenuItem_Click);
+ //
+ // this.
+ //
+ this.scanForHigherQualityBooksStripMenuItem.Name = "scanForHigherQualityBooksStripMenuItem";
+ this.scanForHigherQualityBooksStripMenuItem.Size = new System.Drawing.Size(133, 22);
+ this.scanForHigherQualityBooksStripMenuItem.Text = "Scan for Better Quality Audiobooks";
+ this.scanForHigherQualityBooksStripMenuItem.Click += new System.EventHandler(this.scanForHigherQualityBooksStripMenuItem_Click);
//
// aboutToolStripMenuItem
//
@@ -675,6 +686,7 @@
private System.Windows.Forms.ToolStripMenuItem removeSomeAccountsToolStripMenuItem;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator2;
private System.Windows.Forms.ToolStripMenuItem tourToolStripMenuItem;
+ private System.Windows.Forms.ToolStripMenuItem scanForHigherQualityBooksStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem scanningToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem autoScanLibraryToolStripMenuItem;
@@ -687,6 +699,7 @@
private System.Windows.Forms.ToolStripSeparator toolStripSeparator3;
private System.Windows.Forms.ToolStripMenuItem locateAudiobooksToolStripMenuItem;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator4;
+ private System.Windows.Forms.ToolStripSeparator toolStripSeparator5;
private System.Windows.Forms.ToolStripMenuItem openTrashBinToolStripMenuItem;
private System.Windows.Forms.ToolStripMenuItem launchHangoverToolStripMenuItem;
private LibationWinForms.FormattableToolStripMenuItem liberateVisibleToolStripMenuItem_LiberateMenu;
diff --git a/Source/LibationWinForms/Form1.Settings.cs b/Source/LibationWinForms/Form1.Settings.cs
index 735ff308..235c0897 100644
--- a/Source/LibationWinForms/Form1.Settings.cs
+++ b/Source/LibationWinForms/Form1.Settings.cs
@@ -35,6 +35,8 @@ namespace LibationWinForms
private void aboutToolStripMenuItem_Click(object sender, EventArgs e) => new AboutDialog().ShowDialog(this);
private async void tourToolStripMenuItem_Click(object sender, EventArgs e)
=> await new Walkthrough(this).RunAsync();
+ private void scanForHigherQualityBooksStripMenuItem_Click(object sender, EventArgs e)
+ => new FindBetterQualityBooksDialog().ShowDialog(this);
private void launchHangoverToolStripMenuItem_Click(object sender, EventArgs e)
{
diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs
index 83524efc..bb72fa47 100644
--- a/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs
+++ b/Source/LibationWinForms/ProcessQueue/ProcessBookControl.cs
@@ -12,10 +12,10 @@ namespace LibationWinForms.ProcessQueue
private readonly int ProgressBarDistanceFromEdge;
private object? m_OldContext;
- private static Color FailedColor => Application.IsDarkModeEnabled ? Color.FromArgb(0x50, 0x27, 0x27) : Color.LightCoral;
- private static Color CancelledColor => Application.IsDarkModeEnabled ? Color.FromArgb(0x4e, 0x4b, 0x15) : Color.Khaki;
- private static Color QueuedColor { get; } = SystemColors.Control;
- private static Color SuccessColor => Application.IsDarkModeEnabled ? Color.FromArgb(0x1c, 0x3e, 0x20) : Color.PaleGreen;
+ public static Color FailedColor => Application.IsDarkModeEnabled ? Color.FromArgb(0x50, 0x27, 0x27) : Color.LightCoral;
+ public static Color CancelledColor => Application.IsDarkModeEnabled ? Color.FromArgb(0x4e, 0x4b, 0x15) : Color.Khaki;
+ public static Color QueuedColor { get; } = SystemColors.Control;
+ public static Color SuccessColor => Application.IsDarkModeEnabled ? Color.FromArgb(0x1c, 0x3e, 0x20) : Color.PaleGreen;
public ProcessBookControl()
{
diff --git a/Source/LibationWinForms/Properties/DataSources/LibationUiBase.BookDataViewModel.datasource b/Source/LibationWinForms/Properties/DataSources/LibationUiBase.BookDataViewModel.datasource
new file mode 100644
index 00000000..731786b0
--- /dev/null
+++ b/Source/LibationWinForms/Properties/DataSources/LibationUiBase.BookDataViewModel.datasource
@@ -0,0 +1,10 @@
+
+
+
+ LibationUiBase.BookDataViewModel, LibationUiBase, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
+
\ No newline at end of file
diff --git a/Source/LibationWinForms/SeriesView/SeriesEntryBindingList.cs b/Source/LibationWinForms/SeriesView/SeriesEntryBindingList.cs
deleted file mode 100644
index 0c07e8e7..00000000
--- a/Source/LibationWinForms/SeriesView/SeriesEntryBindingList.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using LibationUiBase.SeriesView;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-
-namespace LibationWinForms.SeriesView
-{
- internal class SeriesEntryBindingList : BindingList
- {
- private PropertyDescriptor _propertyDescriptor;
-
- private ListSortDirection _listSortDirection;
-
- private bool _isSortedCore;
-
- protected override PropertyDescriptor SortPropertyCore => _propertyDescriptor;
-
- protected override ListSortDirection SortDirectionCore => _listSortDirection;
-
- protected override bool IsSortedCore => _isSortedCore;
-
- protected override bool SupportsSortingCore => true;
-
- public SeriesEntryBindingList() : base(new List()) { }
- public SeriesEntryBindingList(IEnumerable entries) : base(entries.ToList()) { }
-
- protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
- {
- var itemsList = (List)base.Items;
-
- var sorted
- = (direction == ListSortDirection.Ascending)
- ? itemsList.OrderBy(prop.GetValue).ToList()
- : itemsList.OrderByDescending(prop.GetValue).ToList();
-
- itemsList.Clear();
- itemsList.AddRange(sorted);
-
- _propertyDescriptor = prop;
- _listSortDirection = direction;
- _isSortedCore = true;
-
- OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
- }
- }
-}
diff --git a/Source/LibationWinForms/SeriesView/SeriesViewDialog.cs b/Source/LibationWinForms/SeriesView/SeriesViewDialog.cs
index 372420f4..5a03b0d9 100644
--- a/Source/LibationWinForms/SeriesView/SeriesViewDialog.cs
+++ b/Source/LibationWinForms/SeriesView/SeriesViewDialog.cs
@@ -42,7 +42,7 @@ namespace LibationWinForms.SeriesView
{
var dgv = createNewSeriesGrid();
dgv.CellContentClick += Dgv_CellContentClick;
- dgv.DataSource = new SeriesEntryBindingList(seriesEntries[series]);
+ dgv.DataSource = new SortBindingList(seriesEntries[series]);
dgv.BindingContextChanged += (_, _) => dgv.Sort(dgv.Columns["Order"], ListSortDirection.Ascending);
dgv.EnableHeadersVisualStyles = !Application.IsDarkModeEnabled;
diff --git a/Source/LibationWinForms/SortBindingList.cs b/Source/LibationWinForms/SortBindingList.cs
new file mode 100644
index 00000000..d4f69b3f
--- /dev/null
+++ b/Source/LibationWinForms/SortBindingList.cs
@@ -0,0 +1,41 @@
+using LibationWinForms;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+
+namespace LibationWinForms;
+
+///
+/// Basic implementation of a sortable binding list to allow automatic column sorting in DataGridViews
+///
+internal class SortBindingList : BindingList
+{
+ private PropertyDescriptor _propertyDescriptor;
+ private ListSortDirection _listSortDirection;
+ private bool _isSortedCore;
+
+ protected override PropertyDescriptor SortPropertyCore => _propertyDescriptor;
+ protected override ListSortDirection SortDirectionCore => _listSortDirection;
+ protected override bool IsSortedCore => _isSortedCore;
+ protected override bool SupportsSortingCore => true;
+ public SortBindingList() : base(new List()) { }
+ public SortBindingList(IEnumerable records) : base(records.ToList()) { }
+ public SortBindingList(List records) : base(records) { }
+ protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
+ {
+ var itemsList = (List)Items;
+
+ var sorted =
+ direction is ListSortDirection.Ascending ? itemsList.OrderBy(i => prop.GetValue(i)).ToList()
+ : itemsList.OrderByDescending(i => prop.GetValue(i)).ToList();
+
+ itemsList.Clear();
+ itemsList.AddRange(sorted);
+
+ _propertyDescriptor = prop;
+ _listSortDirection = direction;
+ _isSortedCore = true;
+
+ OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
+ }
+}
\ No newline at end of file