Address rmcrackan comments and refactor

This commit is contained in:
MBucari
2026-01-02 13:04:35 -07:00
parent f6b96fc210
commit 396d2c8a95
7 changed files with 87 additions and 68 deletions

View File

@@ -7,6 +7,7 @@ using Avalonia.Threading;
using DataLayer;
using LibationUiBase;
using LibationUiBase.Forms;
using LibationUiBase.ProcessQueue;
using System;
using System.Linq;
using System.Threading.Tasks;
@@ -31,9 +32,9 @@ public partial class FindBetterQualityBooksDialog : DialogWindow
};
VM.Books[0].AvailableCodec = "xHE-AAC";
VM.Books[0].AvailableBitrate = 256;
VM.Books[0].ScanStatus = BookScanStatus.Completed;
VM.Books[1].ScanStatus = BookScanStatus.Error;
VM.Books[2].ScanStatus = BookScanStatus.Cancelled;
VM.Books[0].ScanStatus = ProcessBookStatus.Completed;
VM.Books[1].ScanStatus = ProcessBookStatus.Failed;
VM.Books[2].ScanStatus = ProcessBookStatus.Cancelled;
VM.SignificantCount = 1;
}
else
@@ -49,7 +50,13 @@ public partial class FindBetterQualityBooksDialog : DialogWindow
private async void Opened_ShowInitialMessage(object? sender, System.EventArgs e)
{
await MessageBox.Show(this, FindBetterQualityBooksViewModel.InitialMessage, Title ?? "", MessageBoxButtons.OK, MessageBoxIcon.Information);
if (!VM.ShowFindBetterQualityBooksHelp)
return;
var result = await MessageBox.Show(this, FindBetterQualityBooksViewModel.InitialMessage, Title ?? "", MessageBoxButtons.YesNo, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);
if (result == DialogResult.No)
{
VM.ShowFindBetterQualityBooksHelp = false;
}
}
private async void Opened_LoadLibrary(object? sender, System.EventArgs e)
@@ -102,7 +109,7 @@ public partial class FindBetterQualityBooksDialog : DialogWindow
if (VM.IsScanning)
VM.StopScan();
else
await Task.Run(VM.ScanAsync);
await VM.ScanAsync();
}
catch (Exception ex)
{
@@ -138,15 +145,15 @@ public partial class FindBetterQualityBooksDialog : DialogWindow
}
}
public static FuncValueConverter<BookScanStatus, IBrush?> RowConverter { get; } = new(status =>
public static FuncValueConverter<ProcessBookStatus, IBrush?> RowConverter { get; } = new(status =>
{
var brush = status switch
{
BookScanStatus.Completed => "ProcessQueueBookCompletedBrush",
BookScanStatus.Cancelled => "ProcessQueueBookCancelledBrush",
BookScanStatus.Error => "ProcessQueueBookFailedBrush",
ProcessBookStatus.Completed => "ProcessQueueBookCompletedBrush",
ProcessBookStatus.Cancelled => "ProcessQueueBookCancelledBrush",
ProcessBookStatus.Failed => "ProcessQueueBookFailedBrush",
_ => null,
};
return brush is not null && App.Current.TryGetResource(brush, App.Current.ActualThemeVariant, out var res) ? res as Brush : null;
return brush is not null && App.Current.TryGetResource(brush, App.Current.ActualThemeVariant, out var res) ? res as Brush : null;
});
}

View File

@@ -1,15 +1,8 @@
using DataLayer;
using LibationUiBase.ProcessQueue;
namespace LibationUiBase;
public enum BookScanStatus
{
None,
Error,
Cancelled,
Completed,
}
public class BookDataViewModel : ReactiveObject
{
public LibraryBook LibraryBook { get; }
@@ -48,6 +41,6 @@ public class BookDataViewModel : ReactiveObject
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); }
public ProcessBookStatus ScanStatus { get => field; set => RaiseAndSetIfChanged(ref field, value); }
private static string? GetBitrateString(int bitrate) => bitrate > 0 ? $"{bitrate} kbps" : null;
}

View File

@@ -4,6 +4,7 @@ using DataLayer;
using Dinah.Core;
using Dinah.Core.Net.Http;
using LibationFileManager;
using LibationUiBase.ProcessQueue;
using System;
using System.Collections.Generic;
using System.IO;
@@ -34,11 +35,13 @@ public class FindBetterQualityBooksViewModel : ReactiveObject
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.
Display this help message again in the future?
""";
public event EventHandler<BookDataViewModel>? BookScanned;
public IList<BookDataViewModel>? Books { get => field; set => RaiseAndSetIfChanged(ref field, value); }
public bool ShowFindBetterQualityBooksHelp { get => Configuration.Instance.GetNonString(defaultValue: true); set => Configuration.Instance.SetNonString(value); }
public bool ScanWidevine { get; set; }
public int SignificantCount
{
@@ -93,7 +96,7 @@ public class FindBetterQualityBooksViewModel : ReactiveObject
{
b.AvailableBitrate = 0;
b.AvailableCodec = null;
b.ScanStatus = BookScanStatus.None;
b.ScanStatus = ProcessBookStatus.Queued;
}
ScanCount = $"0 of {Books.Count:N0} scanned";
@@ -104,19 +107,20 @@ public class FindBetterQualityBooksViewModel : ReactiveObject
for (int i = 0; i < Books.Count; i++)
{
var b = Books[i];
var url = GetUrl(b.LibraryBook);
try
{
cts.Token.ThrowIfCancellationRequested();
var url = GetUrl(b.LibraryBook);
//Don't re-scan a file if we have already loaded existing audio codec and bitrate.
if (b.Bitrate == 0 && b.Codec == null)
{
var (file, bestformat) = FindHighestExistingFormat(b.LibraryBook);
var (file, bestFormat) = FindHighestExistingFormat(b.LibraryBook);
if (file is not null)
{
b.FoundFile = Configuration.Instance.Books?.Path is string booksDir ? Path.GetRelativePath(booksDir, file) : file;
b.Bitrate = bestformat.BitRate;
b.Codec = bestformat.CodecString;
b.Bitrate = bestFormat.BitRate;
b.Codec = bestFormat.CodecString;
}
else if (b.LibraryBook.Book.UserDefinedItem.LastDownloadedFormat is not null)
{
@@ -127,7 +131,7 @@ public class FindBetterQualityBooksViewModel : ReactiveObject
else
{
b.FoundFile = "File not found and no 'Last Downloaded' format found.";
b.ScanStatus = BookScanStatus.Error;
b.ScanStatus = ProcessBookStatus.Failed;
continue;
}
}
@@ -137,18 +141,18 @@ public class FindBetterQualityBooksViewModel : ReactiveObject
b.AvailableCodec = codecString;
b.AvailableBitrate = bitrate;
b.ScanStatus = BookScanStatus.Completed;
b.ScanStatus = ProcessBookStatus.Completed;
}
catch (OperationCanceledException)
{
b.ScanStatus = BookScanStatus.Cancelled;
b.ScanStatus = ProcessBookStatus.Cancelled;
break;
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "Error checking for better quality for {@Asin}", b.Asin);
b.FoundFile = $"Error: {ex.Message}";
b.ScanStatus = BookScanStatus.Error;
b.ScanStatus = ProcessBookStatus.Failed;
}
finally
{
@@ -168,20 +172,20 @@ public class FindBetterQualityBooksViewModel : ReactiveObject
private static (string? file, AudioFormat format) FindHighestExistingFormat(LibraryBook libraryBook)
{
var largestfile
var largestFile
= AudibleFileStorage.Audio
.GetPaths(libraryBook.Book.AudibleProductId)
.Select(p => new FileInfo(p))
.Where(f => f.Exists && f.Extension.EqualsInsensitive(".m4b"))
.OrderByDescending(f => f.Length)
.FirstOrDefault();
.FirstOrDefault()
?.FullName;
if (largestfile is null)
return (null, AudioFormat.Default);
return (largestfile.FullName, AudioFormatDecoder.FromMpeg4(largestfile.FullName));
return largestFile is null ? (null, AudioFormat.Default)
: (largestFile, AudioFormatDecoder.FromMpeg4(largestFile));
}
static async Task<(string codec, int bitrate)> ReadAudioInfoAsync(HttpResponseMessage response)
private static async Task<(string codec, int bitrate)> ReadAudioInfoAsync(HttpResponseMessage response)
{
var data = await response.Content.ReadAsJObjectAsync();
var totalLengthMs = data["content_metadata"]?["chapter_info"]?.Value<long>("runtime_length_ms") ?? throw new InvalidDataException("Missing runtime length");
@@ -202,12 +206,12 @@ public class FindBetterQualityBooksViewModel : ReactiveObject
return (codecString, bitrate);
}
string GetUrl(LibraryBook libraryBook)
private string GetUrl(LibraryBook libraryBook)
{
var drm_type = ScanWidevine ? "Widevine" : "Adrm";
var locale = AudibleApi.Localization.Get(libraryBook.Book.Locale);
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}";
private const string BaseUrl = "ht" + "tps://api.audible.{0}/1.0/content/{1}/metadata?response_groups=chapter_info,content_reference&quality=High&drm_type={2}";
}

View File

@@ -64,7 +64,6 @@ partial class FindBetterQualityBooksDialog
dataGridView1.RowHeadersVisible = false;
dataGridView1.Size = new System.Drawing.Size(897, 397);
dataGridView1.TabIndex = 0;
dataGridView1.CellFormatting += dataGridView1_CellFormatting;
//
// asinDataGridViewTextBoxColumn
//

View File

@@ -1,8 +1,10 @@
using ApplicationServices;
using LibationUiBase;
using LibationUiBase.ProcessQueue;
using System;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows.Forms;
@@ -34,12 +36,23 @@ public partial class FindBetterQualityBooksDialog : Form
Shown += Shown_LoadLibrary;
Shown += Shown_ShowInitialMessage;
FormClosing += FindBetterQualityBooksDialog_FormClosing;
SetDoubleBuffer(dataGridView1, true);
}
static void SetDoubleBuffer(Control control, bool DoubleBuffered)
{
typeof(Control).InvokeMember("DoubleBuffered", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty, null, control, [DoubleBuffered]);
}
private void Shown_ShowInitialMessage(object? sender, EventArgs e)
{
MessageBox.Show(this, FindBetterQualityBooksViewModel.InitialMessage, Text, MessageBoxButtons.OK, MessageBoxIcon.Information);
if (!VM.ShowFindBetterQualityBooksHelp)
return;
var result = MessageBox.Show(this, FindBetterQualityBooksViewModel.InitialMessage, Text, MessageBoxButtons.YesNo, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);
if (result == DialogResult.No)
{
VM.ShowFindBetterQualityBooksHelp = false;
}
}
private async void Shown_LoadLibrary(object? sender, EventArgs e)
@@ -51,7 +64,13 @@ public partial class FindBetterQualityBooksDialog : Form
Invoke(() =>
{
bookDataViewModelBindingSource.DataSource = VM.Books;
foreach (DataGridViewRow r in dataGridView1.Rows)
{
//Force creation of DefaultCellStyle to speed up later coloring
//_ = r.DefaultCellStyle;
}
btnScan.Enabled = true;
dataGridView1.CellFormatting += dataGridView1_CellFormatting;
});
}
@@ -87,6 +106,8 @@ public partial class FindBetterQualityBooksDialog : Form
{
await scanTask;
scanTask = null;
//give the UI a moment to update after cancelling the first close
await Task.Delay(100);
Invoke(Close);
}
}
@@ -148,7 +169,7 @@ public partial class FindBetterQualityBooksDialog : Form
}
}
private void dataGridView1_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
private void dataGridView1_CellFormatting(object? sender, DataGridViewCellFormattingEventArgs e)
{
if (e.RowIndex < 0 || e.RowIndex >= dataGridView1.Rows.Count)
return;
@@ -156,18 +177,7 @@ public partial class FindBetterQualityBooksDialog : Form
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;
row.DefaultCellStyle.BackColor = bvm.ScanStatus.GetColor();
}
}
}

View File

@@ -12,11 +12,6 @@ namespace LibationWinForms.ProcessQueue
private readonly int ProgressBarDistanceFromEdge;
private object? m_OldContext;
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()
{
InitializeComponent();
@@ -82,15 +77,6 @@ namespace LibationWinForms.ProcessQueue
private void SetStatus(ProcessBookStatus status, string statusText)
{
Color backColor = status switch
{
ProcessBookStatus.Completed => SuccessColor,
ProcessBookStatus.Cancelled => CancelledColor,
ProcessBookStatus.Queued => QueuedColor,
ProcessBookStatus.Working => QueuedColor,
_ => FailedColor
};
cancelBtn.Visible = status is ProcessBookStatus.Queued or ProcessBookStatus.Working;
moveLastBtn.Visible = status == ProcessBookStatus.Queued;
moveDownBtn.Visible = status == ProcessBookStatus.Queued;
@@ -101,7 +87,7 @@ namespace LibationWinForms.ProcessQueue
etaLbl.Visible = status == ProcessBookStatus.Working;
statusLbl.Visible = status != ProcessBookStatus.Working;
statusLbl.Text = statusText;
BackColor = backColor;
BackColor = status.GetColor();
int deltaX = Width - cancelBtn.Location.X - CancelBtnDistanceFromEdge;

View File

@@ -1,4 +1,5 @@
using System.Drawing;
using LibationUiBase.ProcessQueue;
using System.Drawing;
using System.Windows.Forms;
namespace LibationWinForms;
@@ -9,6 +10,13 @@ internal static class ThemeExtensions
private static readonly Color LinkLabelVisited = Color.FromKnownColor(KnownColor.Purple);
private static readonly Color LinkLabelNew_Dark = Color.FromKnownColor(KnownColor.CornflowerBlue);
private static readonly Color LinkLabelVisited_Dark = Color.FromKnownColor(KnownColor.Orchid);
private static readonly Color FailedColor = Color.LightCoral;
private static readonly Color FailedColor_Dark = Color.FromArgb(0x50, 0x27, 0x27);
private static readonly Color CancelledColor = Color.Khaki;
private static readonly Color CancelledColor_Dark = Color.FromArgb(0x4e, 0x4b, 0x15);
private static readonly Color SuccessColor = Color.PaleGreen;
private static readonly Color SuccessColor_Dark = Color.FromArgb(0x1c, 0x3e, 0x20);
public static Color LinkColor => Application.IsDarkModeEnabled ? LinkLabelNew_Dark : LinkLabelNew;
public static Color VisitedLinkColor => Application.IsDarkModeEnabled ? LinkLabelVisited_Dark : LinkLabelVisited;
extension(LinkLabel ll)
@@ -19,4 +27,16 @@ internal static class ThemeExtensions
ll.LinkColor = LinkColor;
}
}
extension(ProcessBookStatus status)
{
public Color GetColor() => status switch
{
ProcessBookStatus.Completed => Application.IsDarkModeEnabled ? SuccessColor_Dark : SuccessColor,
ProcessBookStatus.Cancelled => Application.IsDarkModeEnabled ? CancelledColor_Dark : CancelledColor,
ProcessBookStatus.Queued => SystemColors.Control,
ProcessBookStatus.Working => SystemColors.Control,
_ => Application.IsDarkModeEnabled ? FailedColor_Dark : FailedColor
};
}
}