From 09be09bd1b1af3fd36c802413b9273b28eb3d2c6 Mon Sep 17 00:00:00 2001 From: Robert McRackan Date: Sun, 30 Aug 2026 14:16:27 -0400 Subject: [PATCH] Cull dead code. Simplify --- Source/ApplicationServices/LibraryCommands.cs | 50 ++++------------ Source/AudibleUtilities/ApiExtended.cs | 5 -- .../AuthenticationExceptionHelper.cs | 4 +- Source/AudibleUtilities/HtmlText.cs | 57 ++++++++++--------- .../HangoverAvalonia/HangoverAvalonia.csproj | 3 + Source/HangoverAvalonia/ViewModels/MainVM.cs | 2 +- .../ViewModels/TrashBinViewModel.cs | 2 +- .../Dialogs/Login/AvaloniaLoginCallback.cs | 18 ------ .../Dialogs/Login/AvaloniaLoginChoiceEager.cs | 18 +++--- .../Options/LoginExternalOptions.cs | 5 +- .../Dialogs/Login/WinformLoginCallback.cs | 18 ------ .../Dialogs/Login/WinformLoginChoiceEager.cs | 15 +++-- ...ticationRequiredExceptionLogSafetyTests.cs | 1 - .../TokenStorageSettingsUiTests.cs | 3 +- 14 files changed, 66 insertions(+), 135 deletions(-) delete mode 100644 Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginCallback.cs delete mode 100644 Source/LibationWinForms/Dialogs/Login/WinformLoginCallback.cs diff --git a/Source/ApplicationServices/LibraryCommands.cs b/Source/ApplicationServices/LibraryCommands.cs index e567224a..3a85d63a 100644 --- a/Source/ApplicationServices/LibraryCommands.cs +++ b/Source/ApplicationServices/LibraryCommands.cs @@ -82,25 +82,12 @@ public static class LibraryCommands return inactive; } - catch (AudibleApi.Authentication.LoginFailedException lfEx) - { - lfEx.SaveFiles(Configuration.Instance.LibationFiles.Location); - - // nuget Serilog.Exceptions would automatically log custom properties - // However, it comes with a scary warning when used with EntityFrameworkCore which I'm not yet ready to implement: - // https://github.com/RehanSaeed/Serilog.Exceptions - // work-around: use 3rd param. don't just put exception object in 3rd param -- info overload: stack trace, etc - Log.Logger.Error(lfEx, "Error scanning library. Login failed. {@DebugInfo}", new - { - lfEx.RequestUrl, - ResponseStatusCodeNumber = (int)lfEx.ResponseStatusCode, - ResponseStatusCodeDesc = lfEx.ResponseStatusCode, - lfEx.ResponseInputFields, - lfEx.ResponseBodyFilePaths - }); - throw; - } - catch (Exception ex) + catch (Exception lfEx) when (AuthenticationExceptionHelper.IsAuthenticationFailure(lfEx)) + { + Log.Logger.Error(lfEx, "Error scanning library. Authentication failed."); + throw; + } + catch (Exception ex) { Log.Logger.Error(ex, "Error scanning library"); throw; @@ -188,25 +175,12 @@ public static class LibraryCommands return (totalCount, newCount); } - catch (AudibleApi.Authentication.LoginFailedException lfEx) - { - lfEx.SaveFiles(Configuration.Instance.LibationFiles.Location); - - // nuget Serilog.Exceptions would automatically log custom properties - // However, it comes with a scary warning when used with EntityFrameworkCore which I'm not yet ready to implement: - // https://github.com/RehanSaeed/Serilog.Exceptions - // work-around: use 3rd param. don't just put exception object in 3rd param -- info overload: stack trace, etc - Log.Logger.Error(lfEx, "Error importing library. Login failed. {@DebugInfo}", new - { - lfEx.RequestUrl, - ResponseStatusCodeNumber = (int)lfEx.ResponseStatusCode, - ResponseStatusCodeDesc = lfEx.ResponseStatusCode, - lfEx.ResponseInputFields, - lfEx.ResponseBodyFilePaths - }); - throw; - } - catch (Exception ex) + catch (Exception lfEx) when (AuthenticationExceptionHelper.IsAuthenticationFailure(lfEx)) + { + Log.Logger.Error(lfEx, "Error scanning library. Authentication failed."); + throw; + } + catch (Exception ex) { Log.Logger.Error(ex, "Error importing library"); throw; diff --git a/Source/AudibleUtilities/ApiExtended.cs b/Source/AudibleUtilities/ApiExtended.cs index 90a89d0f..398f7ebe 100644 --- a/Source/AudibleUtilities/ApiExtended.cs +++ b/Source/AudibleUtilities/ApiExtended.cs @@ -5,13 +5,8 @@ using LibationFileManager; using Newtonsoft.Json.Linq; using Polly; using Polly.Retry; -using System; -using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Threading; using System.Threading.Channels; -using System.Threading.Tasks; namespace AudibleUtilities; diff --git a/Source/AudibleUtilities/AuthenticationExceptionHelper.cs b/Source/AudibleUtilities/AuthenticationExceptionHelper.cs index a86dcdfe..af9d66ea 100644 --- a/Source/AudibleUtilities/AuthenticationExceptionHelper.cs +++ b/Source/AudibleUtilities/AuthenticationExceptionHelper.cs @@ -1,5 +1,3 @@ -using AudibleApi.Authentication; - namespace AudibleUtilities; public static class AuthenticationExceptionHelper @@ -14,7 +12,7 @@ public static class AuthenticationExceptionHelper for (var current = ex; current is not null; current = current.InnerException) { - if (current is AuthenticationRequiredException or LoginFailedException) + if (current is AuthenticationRequiredException) return true; if (current is InvalidOperationException { Message: var message } diff --git a/Source/AudibleUtilities/HtmlText.cs b/Source/AudibleUtilities/HtmlText.cs index 007b65c0..aa21d5e6 100644 --- a/Source/AudibleUtilities/HtmlText.cs +++ b/Source/AudibleUtilities/HtmlText.cs @@ -1,5 +1,4 @@ -using HtmlAgilityPack; -using System.Linq; +using System.Net; using System.Text.RegularExpressions; namespace AudibleUtilities; @@ -11,35 +10,39 @@ namespace AudibleUtilities; /// public static partial class HtmlText { - /// Joins the block-level runs. A single newline matches what - /// Audible itself embeds in the description tags of its .aaxc files. - public static string ToPlainText(string? html, string paragraphSeparator = "\n") - { - if (string.IsNullOrWhiteSpace(html)) - return ""; + /// Joins the block-level runs. A single newline matches what + /// Audible itself embeds in the description tags of its .aaxc files. + public static string ToPlainText(string? html, string paragraphSeparator = "\n") + { + if (string.IsNullOrWhiteSpace(html)) + return ""; - // Not every summary is marked up. Running the unmarked ones through the parser risks a stray - // '<' swallowing the rest of the sentence, and buys nothing. - if (!html.Contains('<')) - return (HtmlEntity.DeEntitize(html) ?? html).Trim(); + // Not every summary is marked up. Running the unmarked ones through the parser risks a stray + // '<' swallowing the rest of the sentence, and buys nothing. + if (!html.Contains('<')) + return (WebUtility.HtmlDecode(html) ?? html).Trim(); - var doc = new HtmlDocument(); - doc.LoadHtml(BlockBoundary().Replace(html, "\n")); + // Replace block-level boundaries with newlines, then strip all remaining tags. + var stripped = StripTags().Replace(BlockBoundary().Replace(html, "\n"), ""); - var text = HtmlEntity.DeEntitize(doc.DocumentNode.InnerText) ?? doc.DocumentNode.InnerText; + var text = WebUtility.HtmlDecode(stripped) ?? stripped; - var paragraphs = text - .Replace("\r\n", "\n") - .Replace('\r', '\n') - .Split('\n') - .Select(line => line.Trim()) - .Where(line => line.Length > 0); + var paragraphs = text + .Replace("\r\n", "\n") + .Replace('\r', '\n') + .Split('\n') + .Select(line => line.Trim()) + .Where(line => line.Length > 0); - return string.Join(paragraphSeparator, paragraphs); - } + return string.Join(paragraphSeparator, paragraphs); + } - /// Every tag that ends a run of text. A line break counts as a paragraph break because - /// Audible's summaries mark up paragraphs and nothing finer. - [GeneratedRegex(@"<\s*br\s*/?\s*>|<\s*/\s*(?:p|div|li|tr|blockquote|h[1-6])\s*>", RegexOptions.IgnoreCase)] - private static partial Regex BlockBoundary(); + /// Every tag that ends a run of text. A line break counts as a paragraph break because + /// Audible's summaries mark up paragraphs and nothing finer. + [GeneratedRegex(@"<\s*br\s*/?\s*>|<\s*/\s*(?:p|div|li|tr|blockquote|h[1-6])\s*>", RegexOptions.IgnoreCase)] + private static partial Regex BlockBoundary(); + + /// Matches any HTML/XML tag. + [GeneratedRegex(@"<[^>]+>")] + private static partial Regex StripTags(); } diff --git a/Source/HangoverAvalonia/HangoverAvalonia.csproj b/Source/HangoverAvalonia/HangoverAvalonia.csproj index 075ee45a..dd9e794a 100644 --- a/Source/HangoverAvalonia/HangoverAvalonia.csproj +++ b/Source/HangoverAvalonia/HangoverAvalonia.csproj @@ -79,6 +79,9 @@ + + + diff --git a/Source/HangoverAvalonia/ViewModels/MainVM.cs b/Source/HangoverAvalonia/ViewModels/MainVM.cs index 4df87d05..bc83863c 100644 --- a/Source/HangoverAvalonia/ViewModels/MainVM.cs +++ b/Source/HangoverAvalonia/ViewModels/MainVM.cs @@ -5,7 +5,7 @@ namespace HangoverAvalonia.ViewModels; public partial class MainVM : ViewModelBase { - public Func>? ConfirmDbMutationAsync { get; set; } + public Func> ConfirmDbMutationAsync { get; set; } public MainVM() { diff --git a/Source/HangoverAvalonia/ViewModels/TrashBinViewModel.cs b/Source/HangoverAvalonia/ViewModels/TrashBinViewModel.cs index 9cc5adcc..75dba7e2 100644 --- a/Source/HangoverAvalonia/ViewModels/TrashBinViewModel.cs +++ b/Source/HangoverAvalonia/ViewModels/TrashBinViewModel.cs @@ -77,7 +77,7 @@ public class TrashBinViewModel : ViewModelBase, IDisposable item.IsChecked = false; } - public Func>? ConfirmDbMutationAsync { get; set; } + public Func> ConfirmDbMutationAsync { get; set; } public async Task RestoreCheckedAsync() { diff --git a/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginCallback.cs b/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginCallback.cs deleted file mode 100644 index 8772f1e5..00000000 --- a/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginCallback.cs +++ /dev/null @@ -1,18 +0,0 @@ -using AudibleApi; -using System.Threading.Tasks; - -namespace LibationAvalonia.Dialogs.Login; - -public class AvaloniaLoginCallback : ILoginCallback -{ - public string DeviceName { get; } = "Libation"; - - public Task Get2faCodeAsync(string prompt) => throw new System.NotSupportedException(); - public Task<(string password, string guess)> GetCaptchaAnswerAsync(string password, byte[] captchaImage) - => throw new System.NotSupportedException(); - public Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig) - => throw new System.NotSupportedException(); - public Task<(string email, string password)> GetLoginAsync() - => throw new System.NotSupportedException(); - public Task ShowApprovalNeededAsync() => throw new System.NotSupportedException(); -} \ No newline at end of file diff --git a/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginChoiceEager.cs b/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginChoiceEager.cs index 8e9289a4..d436621c 100644 --- a/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginChoiceEager.cs +++ b/Source/LibationAvalonia/Dialogs/Login/AvaloniaLoginChoiceEager.cs @@ -14,19 +14,17 @@ namespace LibationAvalonia.Dialogs.Login; public class AvaloniaLoginChoiceEager : ILoginChoiceEager { - public ILoginCallback LoginCallback { get; } = new AvaloniaLoginCallback(); - private readonly Account _account; public AvaloniaLoginChoiceEager(Account account) { - _account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account)); + _account = ArgumentValidator.EnsureNotNull(account, nameof(account)); } - public async Task StartAsync(ChoiceIn choiceIn) + public async Task StartAsync(ChoiceIn choiceIn) => await Dispatcher.UIThread.InvokeAsync(() => StartAsyncInternal(choiceIn)); - private async Task StartAsyncInternal(ChoiceIn choiceIn) + private async Task StartAsyncInternal(ChoiceIn choiceIn) { try { @@ -35,7 +33,7 @@ public class AvaloniaLoginChoiceEager : ILoginChoiceEager { try { - if (await BrowserLoginAsync(choiceIn) is ChoiceOut external) + if (await BrowserLoginAsync(choiceIn) is string external) return external; } catch (Exception ex) when (WebView2LoginErrorMessage.IsWebView2SignInInfrastructureFailure(ex)) @@ -65,17 +63,17 @@ public class AvaloniaLoginChoiceEager : ILoginChoiceEager var externalDialog = new LoginExternalDialog(_account, choiceIn.LoginUrl); return await externalDialog.ShowDialogAsync() is DialogResult.OK - ? ChoiceOut.External(externalDialog.ResponseUrl) + ? externalDialog.ResponseUrl : null; } - private async Task BrowserLoginAsync(ChoiceIn shoiceIn) + private async Task BrowserLoginAsync(ChoiceIn shoiceIn) { // Time-of-use: setting can change before Show (e.g. settings saved) — never open NativeWebView when disabled. if (!Configuration.Instance.UseWebView) return null; - TaskCompletionSource tcs = new(); + TaskCompletionSource tcs = new(); NativeWebDialog dialog = new() { @@ -89,7 +87,7 @@ public class AvaloniaLoginChoiceEager : ILoginChoiceEager { if (e.Request?.AbsolutePath.StartsWith("/ap/maplanding") is true) { - tcs.TrySetResult(ChoiceOut.External(e.Request.ToString())); + tcs.TrySetResult(e.Request.ToString()); dialog.Close(); } }; diff --git a/Source/LibationCli/Options/LoginExternalOptions.cs b/Source/LibationCli/Options/LoginExternalOptions.cs index b865b581..42d7ee30 100644 --- a/Source/LibationCli/Options/LoginExternalOptions.cs +++ b/Source/LibationCli/Options/LoginExternalOptions.cs @@ -103,8 +103,7 @@ internal class LoginExternalOptions : OptionsBase ?? AudibleApi.Locale.Empty; } - internal static bool IsEmptyLocale(AudibleApi.Locale locale) - => string.IsNullOrEmpty(locale.CountryCode); + internal static bool IsEmptyLocale(Locale locale) => string.IsNullOrEmpty(locale.CountryCode); private sealed class CliLoginExternal : ILoginExternal { @@ -112,8 +111,6 @@ internal class LoginExternalOptions : OptionsBase public CliLoginExternal(string? presetResponseUrl) => _presetResponseUrl = presetResponseUrl; - public string DeviceName => "Libation"; - public string GetResponseUrl(string loginUrl, CookieCollection signInCookies) { if (!string.IsNullOrEmpty(_presetResponseUrl)) diff --git a/Source/LibationWinForms/Dialogs/Login/WinformLoginCallback.cs b/Source/LibationWinForms/Dialogs/Login/WinformLoginCallback.cs deleted file mode 100644 index 23d588a1..00000000 --- a/Source/LibationWinForms/Dialogs/Login/WinformLoginCallback.cs +++ /dev/null @@ -1,18 +0,0 @@ -using AudibleApi; -using System.Threading.Tasks; - -namespace LibationWinForms.Login; - -public class WinformLoginCallback : ILoginCallback -{ - public string DeviceName { get; } = "Libation"; - - public Task Get2faCodeAsync(string prompt) => throw new System.NotSupportedException(); - public Task<(string password, string guess)> GetCaptchaAnswerAsync(string password, byte[] captchaImage) - => throw new System.NotSupportedException(); - public Task<(string name, string value)> GetMfaChoiceAsync(MfaConfig mfaConfig) - => throw new System.NotSupportedException(); - public Task<(string email, string password)> GetLoginAsync() - => throw new System.NotSupportedException(); - public Task ShowApprovalNeededAsync() => throw new System.NotSupportedException(); -} \ No newline at end of file diff --git a/Source/LibationWinForms/Dialogs/Login/WinformLoginChoiceEager.cs b/Source/LibationWinForms/Dialogs/Login/WinformLoginChoiceEager.cs index 58321754..686e0503 100644 --- a/Source/LibationWinForms/Dialogs/Login/WinformLoginChoiceEager.cs +++ b/Source/LibationWinForms/Dialogs/Login/WinformLoginChoiceEager.cs @@ -1,5 +1,6 @@ using AudibleApi; using AudibleUtilities; +using Dinah.Core; using LibationFileManager; using LibationUiBase; using LibationWinForms.Dialogs.Login; @@ -11,20 +12,18 @@ namespace LibationWinForms.Login; public class WinformLoginChoiceEager : ILoginChoiceEager { - public ILoginCallback LoginCallback { get; } = new WinformLoginCallback(); - private Account _account { get; } private Control Owner { get; } public WinformLoginChoiceEager(Account account, Control owner) { - _account = Dinah.Core.ArgumentValidator.EnsureNotNull(account, nameof(account)); - Owner = Dinah.Core.ArgumentValidator.EnsureNotNull(owner, nameof(owner)); + _account = ArgumentValidator.EnsureNotNull(account, nameof(account)); + Owner = ArgumentValidator.EnsureNotNull(owner, nameof(owner)); } - public Task StartAsync(ChoiceIn choiceIn) + public Task StartAsync(ChoiceIn choiceIn) => Owner.Invoke(() => StartAsyncInternal(choiceIn)); - private Task StartAsyncInternal(ChoiceIn choiceIn) + private Task StartAsyncInternal(ChoiceIn choiceIn) { if (Configuration.Instance.UseWebView && Environment.OSVersion.Version.Major >= 10) { @@ -35,7 +34,7 @@ public class WinformLoginChoiceEager : ILoginChoiceEager { using var weblogin = new WebLoginDialog(_account.AccountId, choiceIn); if (ShowDialog(weblogin)) - return Task.FromResult(ChoiceOut.External(weblogin.ResponseUrl)); + return Task.FromResult(weblogin.ResponseUrl); } } catch (Exception ex) @@ -49,7 +48,7 @@ public class WinformLoginChoiceEager : ILoginChoiceEager using var externalDialog = new LoginExternalDialog(_account, choiceIn.LoginUrl); return Task.FromResult( ShowDialog(externalDialog) - ? ChoiceOut.External(externalDialog.ResponseUrl) + ? externalDialog.ResponseUrl : null); } diff --git a/Source/_Tests/AudibleUtilities.Tests/AuthenticationRequiredExceptionLogSafetyTests.cs b/Source/_Tests/AudibleUtilities.Tests/AuthenticationRequiredExceptionLogSafetyTests.cs index 3fc921b9..7771ad49 100644 --- a/Source/_Tests/AudibleUtilities.Tests/AuthenticationRequiredExceptionLogSafetyTests.cs +++ b/Source/_Tests/AudibleUtilities.Tests/AuthenticationRequiredExceptionLogSafetyTests.cs @@ -1,7 +1,6 @@ using AudibleApi; using AudibleApi.Authorization; using AudibleApi.Cryptography; -using AudibleUtilities; using Dinah.Core.Security; using LibationFileManager; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/Source/_Tests/LibationUiBase.Tests/TokenStorageSettingsUiTests.cs b/Source/_Tests/LibationUiBase.Tests/TokenStorageSettingsUiTests.cs index 2753c86e..d9a18cfd 100644 --- a/Source/_Tests/LibationUiBase.Tests/TokenStorageSettingsUiTests.cs +++ b/Source/_Tests/LibationUiBase.Tests/TokenStorageSettingsUiTests.cs @@ -59,6 +59,7 @@ public class TokenStorageSettingsUiTests { StringAssert.Contains(TokenStorageSettingsUi.ExportConfirmBody, "password"); StringAssert.Contains(TokenStorageSettingsUi.ExportButtonToolTip, "Docker"); - Assert.AreEqual("Export encryption key...", TokenStorageSettingsUi.ExportButtonText); + var temp = TokenStorageSettingsUi.ExportButtonText; + Assert.AreEqual("Export encryption key...", temp); } }