using DataLayer; using Dinah.Core; using System; using System.Collections.Generic; using System.Linq; namespace ApplicationServices; /// One title Libation is waiting on before attempting it again, and why. public sealed record DeferredDownload( string Account, string AudibleProductId, DownloadFailureKind Kind, int ConsecutiveFailures, DateTimeOffset LastFailedAt, DateTimeOffset RetryAfter, string? Reason) { /// The label used in the log, the CLI summary and the GUI's skipped-titles breakdown. public string KindLabel => Kind switch { DownloadFailureKind.LicenseDenied => "Audible denied a download license", DownloadFailureKind.AssetUnavailable => "Audible has no downloadable audio yet", DownloadFailureKind.ServiceInterruption => "A possible Audible service interruption", _ => "A previous failure" }; } /// /// The titles a bulk or automatic download run should leave alone for now, looked up by library book. /// /// Read once per run: a run that takes hours must not have its own failures start suppressing the titles /// still ahead of it in the same pass. /// /// public sealed class DownloadDeferrals { /// No title is deferred. Used by targeted and forced runs, which must attempt what was asked. public static DownloadDeferrals None { get; } = new([]); private readonly Dictionary<(string Account, string AudibleProductId), DeferredDownload> byBook; private DownloadDeferrals(IEnumerable deferred) => byBook = deferred.ToDictionary(d => (d.Account, d.AudibleProductId)); public static DownloadDeferrals Create(IEnumerable deferred) => new(deferred); /// Reads the store. Never throws: a failure here must not stop a download run. public static DownloadDeferrals Load(DateTimeOffset now) => Create(DownloadAttemptFailureStore.GetDeferred(now)); public int Count => byBook.Count; public bool Any => byBook.Count > 0; public DeferredDownload? Find(LibraryBook libraryBook) => libraryBook.Account is { } account && byBook.TryGetValue((account, libraryBook.Book.AudibleProductId), out var deferred) ? deferred : null; public bool IsDeferred(LibraryBook libraryBook) => Find(libraryBook) is not null; }