mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-02-18 00:17:43 +01:00
Address rmcrackan comments and refactor
This commit is contained in:
@@ -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;
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}";
|
||||
}
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user