using DataLayer;
using LibationFileManager;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
namespace ApplicationServices;
///
/// Reads and writes the record of successful audiobook downloads backing the daily download limit.
///
/// Caches nothing: every read is a fresh query, so a queue paused for hours sees entries age out of the
/// rolling window, and downloads performed by a concurrently running CLI or second container are counted too.
///
///
public static class DownloadHistoryStore
{
/// Kept a little beyond the 24 hour window so the table stays small without losing anything in use.
private static readonly TimeSpan RetentionPeriod = TimeSpan.FromHours(48);
///
/// Records a finished download. Never throws: bookkeeping must not fail a download that already succeeded.
///
public static void Record(string? audibleProductId, bool isAudiblePlus, long bytes, DateTimeOffset? completedAt = null)
{
try
{
var when = completedAt ?? DateTimeOffset.Now;
using var context = DbContexts.GetContext();
context.DownloadHistory.Add(new DownloadHistory(when, audibleProductId, isAudiblePlus, bytes));
var cutoff = (when - RetentionPeriod).UtcTicks;
var expired = context.DownloadHistory.Where(dh => dh.CompletedAtUtcTicks < cutoff);
context.DownloadHistory.RemoveRange(expired);
context.SaveChanges();
Serilog.Log.Logger.Debug(
"Recorded download for the daily download limit. {@DebugInfo}",
new { audibleProductId, isAudiblePlus, bytes, completedAt = when });
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(
ex,
"Failed to record a completed download for the daily download limit. The download itself succeeded. {@DebugInfo}",
new { audibleProductId, isAudiblePlus, bytes });
}
}
/// Downloads completed at or after . Empty when the query fails.
public static IReadOnlyList GetSince(DateTimeOffset since)
{
try
{
var ticks = since.UtcTicks;
using var context = DbContexts.GetContext();
return context.DownloadHistory
.AsNoTracking()
.Where(dh => dh.CompletedAtUtcTicks >= ticks)
.OrderBy(dh => dh.CompletedAtUtcTicks)
.Select(dh => new { dh.CompletedAtUtcTicks, dh.AudibleProductId, dh.IsAudiblePlus, dh.Bytes })
.ToList()
.Select(dh => new DownloadHistoryEntry(
new DateTimeOffset(dh.CompletedAtUtcTicks, TimeSpan.Zero),
dh.AudibleProductId,
dh.IsAudiblePlus,
dh.Bytes))
.ToList();
}
catch (Exception ex)
{
// Failing open is the safer default: a broken query must not block downloading.
Serilog.Log.Logger.Error(ex, "Failed to read download history. Treating the last 24 hours as empty.");
return [];
}
}
/// The rolling window the daily download limit uses.
public static IReadOnlyList GetCurrentWindow(DateTimeOffset now)
=> GetSince(now - DailyDownloadLimit.Window);
}