mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-09-12 21:57:19 -04:00
The dialog names the account in full because it is shown to whoever owns it, but log files get attached to public issue reports, which is why the codebase has MaskedLogEntry. Naming the account in the log the same way the dialog does would have put real email addresses into every shared log. Co-authored-by: rmcrackan <rmcrackan@gmail.com>
104 lines
2.8 KiB
C#
104 lines
2.8 KiB
C#
using ApplicationServices;
|
|
using AudibleUtilities;
|
|
using Serilog;
|
|
using System;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace LibationUiBase;
|
|
|
|
/// <summary>
|
|
/// Runs background library auto-scan without opening login UI.
|
|
/// Pauses the timer after an authentication failure until the user logs in manually.
|
|
/// </summary>
|
|
public sealed class AutoScanRunner
|
|
{
|
|
private readonly Func<bool> isAutoScanEnabled;
|
|
private readonly Action pauseTimer;
|
|
private readonly Action resumeTimer;
|
|
private readonly Func<AuthenticationRequiredException, Task>? notifyAuthRequired;
|
|
|
|
private bool pausedForAuthentication;
|
|
|
|
public AutoScanRunner(
|
|
Func<bool> isAutoScanEnabled,
|
|
Action pauseTimer,
|
|
Action resumeTimer,
|
|
Func<AuthenticationRequiredException, Task>? notifyAuthRequired = null)
|
|
{
|
|
this.isAutoScanEnabled = isAutoScanEnabled;
|
|
this.pauseTimer = pauseTimer;
|
|
this.resumeTimer = resumeTimer;
|
|
this.notifyAuthRequired = notifyAuthRequired;
|
|
}
|
|
|
|
public void OnManualScanSucceeded()
|
|
{
|
|
if (!pausedForAuthentication)
|
|
return;
|
|
|
|
pausedForAuthentication = false;
|
|
if (isAutoScanEnabled())
|
|
resumeTimer();
|
|
}
|
|
|
|
public void OnAutoScanSettingChanged()
|
|
{
|
|
if (!isAutoScanEnabled())
|
|
pausedForAuthentication = false;
|
|
}
|
|
|
|
public async Task RunAsync()
|
|
{
|
|
if (!isAutoScanEnabled() || pausedForAuthentication)
|
|
return;
|
|
|
|
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
|
var accounts = persister.AccountsSettings
|
|
.GetAll()
|
|
.Where(a => a.LibraryScan)
|
|
.ToArray();
|
|
|
|
if (accounts.Length == 0)
|
|
return;
|
|
|
|
try
|
|
{
|
|
await Task.Run(() => LibraryCommands.ImportAccountAsync(accounts, allowInteractiveLogin: false));
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
Log.Information("Audible login attempt cancelled by user");
|
|
}
|
|
catch (Exception ex) when (AuthenticationExceptionHelper.IsAuthenticationFailure(ex))
|
|
{
|
|
// LoginFailedException and the "ADP token is null" case do not name an account, so fall back to one
|
|
// that at least carries the original failure
|
|
await pauseForAuthenticationAsync(
|
|
AuthenticationExceptionHelper.FindAuthenticationRequired(ex)
|
|
?? new AuthenticationRequiredException(account: null, message: ex.Message, innerException: ex));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, "Error invoking auto-scan");
|
|
}
|
|
}
|
|
|
|
private async Task pauseForAuthenticationAsync(AuthenticationRequiredException ex)
|
|
{
|
|
if (pausedForAuthentication)
|
|
return;
|
|
|
|
pausedForAuthentication = true;
|
|
pauseTimer();
|
|
|
|
// masked, not the label the dialog uses: log files get attached to public issue reports
|
|
Log.Warning(ex,
|
|
"Auto-scan paused: Audible login is required for {Account}. Log in with Import > Scan Library to resume background scans.",
|
|
ex.Account?.MaskedLogEntry ?? "[unknown account]");
|
|
|
|
if (notifyAuthRequired is not null)
|
|
await notifyAuthRequired(ex);
|
|
}
|
|
}
|