mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-09-12 21:57:19 -04:00
Records every successful audiobook download in the library database (a new DownloadHistory table) and, when the user opts in, stops downloading once the rolling 24 hour window is full. The history lives in the database rather than a file under LibationFiles because in Docker only the database is on a volume; a file there is discarded on every container restart. The limit is checked immediately before each book downloads, never at queueing time, so a full queue stays full and the user can raise or disable the limit mid-run. When nothing in the queue can proceed the queue pauses and re-checks every 15 seconds, recomputing settings, history and clock from scratch, so a queue left running for days drip-feeds itself as downloads age out. The CLI never waits: it skips covered titles and reports a count. Co-authored-by: rmcrackan <rmcrackan@gmail.com>
93 lines
5.3 KiB
C#
93 lines
5.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace LibationFileManager;
|
|
|
|
/// <summary>
|
|
/// User-facing copy for the opt-in daily download limit, plus the suggestion to turn it on when Audible
|
|
/// looks like it is throttling. Lives here rather than in LibationUiBase because LibationCli needs the same
|
|
/// wording and does not reference LibationUiBase.
|
|
/// </summary>
|
|
public static class DailyDownloadLimitUserMessage
|
|
{
|
|
public const string DialogCaption = "Daily download limit reached";
|
|
|
|
private const string SettingsLocation = "Settings > Download/Decrypt > Daily download limit";
|
|
|
|
/// <summary>Shown once when the queue pauses. Informational only: the queue resumes on its own.</summary>
|
|
public static string BuildQueuePausedBody(DailyDownloadLimit.Allowance allowance, string bookTitleWithSubtitle)
|
|
=> $"""
|
|
Libation has paused before downloading {bookTitleWithSubtitle} because you have reached your daily download limit.
|
|
|
|
{DescribeUsage(allowance)}
|
|
|
|
Nothing is lost and nothing was cancelled. Your books are still queued, and Libation will continue on its own {DescribeResumption(allowance)}.
|
|
|
|
To download more now, change or turn off the limit in {SettingsLocation}. Libation picks up the new setting within a few seconds, so there is no need to requeue anything. To stop instead, use Cancel All in the process queue.
|
|
""";
|
|
|
|
/// <summary>Per-book status shown in the queue while paused. Recomputed on each re-check.</summary>
|
|
public static string BuildWaitingStatus(DailyDownloadLimit.Allowance allowance)
|
|
=> allowance.NextCapacityAt is DateTimeOffset next
|
|
? $"Daily limit reached, resumes about {next.ToLocalTime():t}"
|
|
: "Daily limit reached, waiting";
|
|
|
|
public static string BuildQueueLogEntry(DailyDownloadLimit.Allowance allowance, string bookTitleWithSubtitle)
|
|
=> $"Daily download limit reached ({DescribeUsage(allowance)}). Waiting before downloading {bookTitleWithSubtitle}. "
|
|
+ $"Libation will continue on its own {DescribeResumption(allowance)}, or change the limit in {SettingsLocation}.";
|
|
|
|
public static string BuildDeferredLogEntry(DailyDownloadLimit.Allowance allowance, string bookTitleWithSubtitle)
|
|
=> $"Daily download limit reached for Audible Plus titles ({DescribeUsage(allowance)}). "
|
|
+ $"Moved {bookTitleWithSubtitle} to the end of the queue and continued with titles the limit does not cover.";
|
|
|
|
/// <summary>stderr lines for the CLI, which skips blocked titles instead of waiting.</summary>
|
|
public static IEnumerable<string> BuildCliSkippedLines(DailyDownloadLimit.Allowance allowance)
|
|
{
|
|
yield return $"Daily download limit reached: {DescribeUsage(allowance)}.";
|
|
yield return $"Skipping the titles it covers. Capacity returns {DescribeResumption(allowance)}.";
|
|
yield return $"To change or turn off the limit, set \"{nameof(Configuration.DailyDownloadLimit)}\" in Settings.json (or use {SettingsLocation} in the Libation app).";
|
|
}
|
|
|
|
public static string BuildCliSkippedSummary(int skippedCount)
|
|
=> $"Skipped {skippedCount} title(s) because of your daily download limit. They remain un-liberated and will be tried on the next run.";
|
|
|
|
/// <summary>
|
|
/// Suggests turning the limit on after a license denial that looks like Audible throttling. Returns null when
|
|
/// the suggestion would be unhelpful: a limit is already configured, or too little was downloaded recently for
|
|
/// throttling to be a plausible explanation.
|
|
/// </summary>
|
|
public static string? BuildSuggestionParagraph(Configuration config, IReadOnlyList<DownloadHistoryEntry> history, DateTimeOffset now)
|
|
{
|
|
if (config.DailyDownloadLimit is not Configuration.DailyLimitScope.NoLimit)
|
|
return null;
|
|
|
|
var recent = DailyDownloadLimit.SummarizeRecent(history, now);
|
|
if (recent.TotalDownloads < DailyDownloadLimit.SuggestionMinimumRecentDownloads)
|
|
return null;
|
|
|
|
var plus = recent.PlusDownloads == recent.TotalDownloads
|
|
? "all of them from the Plus catalog"
|
|
: $"{recent.PlusDownloads} of them from the Plus catalog";
|
|
|
|
return $"""
|
|
Libation successfully downloaded {recent.TotalDownloads} titles in the last 24 hours, {plus}. That is the kind of volume that leads Audible to deny licenses for a day or two.
|
|
|
|
To have Libation pace itself from now on, turn on a daily download limit in {SettingsLocation}. "Plus titles only" with a limit of 50 books is a reasonable starting point; titles you own are not affected. Libation counts only the downloads it performs, over a rolling 24 hours.
|
|
""";
|
|
}
|
|
|
|
private static string DescribeUsage(DailyDownloadLimit.Allowance allowance)
|
|
{
|
|
var scope = allowance.Scope is Configuration.DailyLimitScope.PlusOnly ? "Audible Plus titles" : "books";
|
|
|
|
return allowance.LimitBytes is long limitBytes
|
|
? $"your limit is {allowance.Quantity} {allowance.Unit} of {scope} per 24 hours, and Libation has downloaded about {DiskSpaceHelper.FormatBytes(allowance.UsedBytes)} of {DiskSpaceHelper.FormatBytes(limitBytes)} in the last 24 hours ({allowance.UsedBooks} title(s))"
|
|
: $"your limit is {allowance.Quantity} {scope} per 24 hours, and Libation has downloaded {allowance.UsedBooks} in the last 24 hours";
|
|
}
|
|
|
|
private static string DescribeResumption(DailyDownloadLimit.Allowance allowance)
|
|
=> allowance.NextCapacityAt is DateTimeOffset next
|
|
? $"at about {next.ToLocalTime():t} ({next.ToLocalTime():d}), when the oldest of those downloads is more than 24 hours old"
|
|
: "once the oldest of those downloads is more than 24 hours old";
|
|
}
|