Cull dead code. Simplify

This commit is contained in:
Robert McRackan committed 2026-08-30 14:16:27 -04:00
1 parent 19a867488b
commit 09be09bd1b
14 files changed
+66 -135

No files matched your search

+12 -38
View File
@@ -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;
-5
View File
@@ -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;
@@ -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 }
+30 -27
View File
@@ -1,5 +1,4 @@
using HtmlAgilityPack;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
namespace AudibleUtilities;
@@ -11,35 +10,39 @@ namespace AudibleUtilities;
/// </summary>
public static partial class HtmlText
{
/// <param name="paragraphSeparator">Joins the block-level runs. A single newline matches what
/// Audible itself embeds in the description tags of its .aaxc files.</param>
public static string ToPlainText(string? html, string paragraphSeparator = "\n")
{
if (string.IsNullOrWhiteSpace(html))
return "";
/// <param name="paragraphSeparator">Joins the block-level runs. A single newline matches what
/// Audible itself embeds in the description tags of its .aaxc files.</param>
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);
}
/// <summary>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.</summary>
[GeneratedRegex(@"<\s*br\s*/?\s*>|<\s*/\s*(?:p|div|li|tr|blockquote|h[1-6])\s*>", RegexOptions.IgnoreCase)]
private static partial Regex BlockBoundary();
/// <summary>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.</summary>
[GeneratedRegex(@"<\s*br\s*/?\s*>|<\s*/\s*(?:p|div|li|tr|blockquote|h[1-6])\s*>", RegexOptions.IgnoreCase)]
private static partial Regex BlockBoundary();
/// <summary>Matches any HTML/XML tag.</summary>
[GeneratedRegex(@"<[^>]+>")]
private static partial Regex StripTags();
}
@@ -79,6 +79,9 @@
<ItemGroup>
<ProjectReference Include="..\HangoverBase\HangoverBase.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Update="Microsoft.SourceLink.GitHub" Version="10.0.400" />
</ItemGroup>
<Target Name="SpicNSpan" AfterTargets="Clean">
+1 -1
View File
@@ -5,7 +5,7 @@ namespace HangoverAvalonia.ViewModels;
public partial class MainVM : ViewModelBase
{
public Func<string, Task<bool>>? ConfirmDbMutationAsync { get; set; }
public Func<string, Task<bool>> ConfirmDbMutationAsync { get; set; }
public MainVM()
{
@@ -77,7 +77,7 @@ public class TrashBinViewModel : ViewModelBase, IDisposable
item.IsChecked = false;
}
public Func<string, Task<bool>>? ConfirmDbMutationAsync { get; set; }
public Func<string, Task<bool>> ConfirmDbMutationAsync { get; set; }
public async Task RestoreCheckedAsync()
{
@@ -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<string> 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();
}
@@ -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<ChoiceOut?> StartAsync(ChoiceIn choiceIn)
public async Task<string?> StartAsync(ChoiceIn choiceIn)
=> await Dispatcher.UIThread.InvokeAsync(() => StartAsyncInternal(choiceIn));
private async Task<ChoiceOut?> StartAsyncInternal(ChoiceIn choiceIn)
private async Task<string?> 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<ChoiceOut?> BrowserLoginAsync(ChoiceIn shoiceIn)
private async Task<string?> 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<ChoiceOut?> tcs = new();
TaskCompletionSource<string?> 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();
}
};
@@ -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))
@@ -1,18 +0,0 @@
using AudibleApi;
using System.Threading.Tasks;
namespace LibationWinForms.Login;
public class WinformLoginCallback : ILoginCallback
{
public string DeviceName { get; } = "Libation";
public Task<string> 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();
}
@@ -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<ChoiceOut?> StartAsync(ChoiceIn choiceIn)
public Task<string?> StartAsync(ChoiceIn choiceIn)
=> Owner.Invoke(() => StartAsyncInternal(choiceIn));
private Task<ChoiceOut?> StartAsyncInternal(ChoiceIn choiceIn)
private Task<string?> 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);
}
@@ -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;
@@ -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);
}
}