using ApplicationServices; using AudibleUtilities; using Serilog; using System; using System.Linq; using System.Threading.Tasks; namespace LibationUiBase; /// /// Runs background library auto-scan without opening login UI. /// Pauses the timer after an authentication failure until the user logs in manually. /// public sealed class AutoScanRunner { private readonly Func isAutoScanEnabled; private readonly Action pauseTimer; private readonly Action resumeTimer; private readonly Func? notifyAuthRequired; private bool pausedForAuthentication; public AutoScanRunner( Func isAutoScanEnabled, Action pauseTimer, Action resumeTimer, Func? 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.AccountInfo?.MaskedLogEntry ?? "[unknown account]"); if (notifyAuthRequired is not null) await notifyAuthRequired(ex); } }