using OF_DL.Models.Downloads; using OF_DL.Services; namespace OF_DL.Gui.Services; internal sealed class AvaloniaDownloadEventHandler( Action activitySink, Action progressStatusUpdate, Action progressStart, Action progressIncrement, Action progressStop, Func isCancellationRequested, CancellationToken cancellationToken) : IDownloadEventHandler { private string _lastProgressDescription = string.Empty; private string _activeUsername = string.Empty; private DateTime _activeUserStartedAtUtc; private readonly Dictionary _contentObjectCounts = new(StringComparer.Ordinal); private bool _hasPerUserCompletionLogged; public CancellationToken CancellationToken { get; } = cancellationToken; public async Task WithStatusAsync(string statusMessage, Func> work) { ThrowIfCancellationRequested(); progressStart(statusMessage, 0, false); try { AvaloniaStatusReporter statusReporter = new(progressStatusUpdate, isCancellationRequested); return await work(statusReporter); } finally { progressStop(); } } public async Task WithProgressAsync(string description, long maxValue, bool showSize, Func> work) { ThrowIfCancellationRequested(); _lastProgressDescription = description; progressStart(description, maxValue, showSize); try { AvaloniaProgressReporter reporter = new(progressIncrement, isCancellationRequested, CancellationToken); return await work(reporter); } finally { progressStop(); } } public void OnContentFound(string contentType, int mediaCount, int objectCount) { ThrowIfCancellationRequested(); _contentObjectCounts[contentType] = objectCount; progressStatusUpdate($"Found {mediaCount} media from {objectCount} {contentType}."); } public void OnNoContentFound(string contentType) { ThrowIfCancellationRequested(); progressStatusUpdate($"Found 0 {contentType}."); } public void OnDownloadComplete(string contentType, DownloadResult result) { ThrowIfCancellationRequested(); if (!string.IsNullOrWhiteSpace(_activeUsername) && result.NewDownloads > 0) { if (_contentObjectCounts.TryGetValue(contentType, out int objectCount) && objectCount > 0) { activitySink( $"Downloaded {result.NewDownloads} media from {objectCount} {contentType.ToLowerInvariant()}."); } else { activitySink($"Downloaded {result.NewDownloads} media from {contentType.ToLowerInvariant()}."); } } progressStatusUpdate( $"{contentType} complete. Existing: {result.ExistingDownloads}, New: {result.NewDownloads}, Total: {result.TotalCount}."); } public void OnUserStarting(string username) { ThrowIfCancellationRequested(); _activeUsername = username; _activeUserStartedAtUtc = DateTime.UtcNow; _hasPerUserCompletionLogged = false; activitySink($"Starting scrape for {username}."); progressStatusUpdate($"Scraping data for {username}..."); } public void OnUserComplete(string username, CreatorDownloadResult result) { ThrowIfCancellationRequested(); TimeSpan elapsed = DateTime.UtcNow - _activeUserStartedAtUtc; activitySink($"Completed {username} in {elapsed.TotalMinutes:0.0} minutes."); _activeUsername = string.Empty; _hasPerUserCompletionLogged = true; _contentObjectCounts.Clear(); } public void OnPurchasedTabUserComplete(string username, int paidPostCount, int paidMessagesCount) { ThrowIfCancellationRequested(); TimeSpan elapsed = DateTime.UtcNow - _activeUserStartedAtUtc; activitySink($"Completed {username} in {elapsed.TotalMinutes:0.0} minutes."); _activeUsername = string.Empty; _hasPerUserCompletionLogged = true; _contentObjectCounts.Clear(); } public void OnScrapeComplete(TimeSpan elapsed) { ThrowIfCancellationRequested(); if (_hasPerUserCompletionLogged) { return; } string summary = BuildCompletionSummary(elapsed); activitySink(summary); } public void OnMessage(string message) { ThrowIfCancellationRequested(); progressStatusUpdate(message); } private void ThrowIfCancellationRequested() { if (isCancellationRequested()) { throw new OperationCanceledException("Operation canceled by user."); } } private string BuildCompletionSummary(TimeSpan elapsed) { if (string.IsNullOrWhiteSpace(_lastProgressDescription)) { return $"Download completed in {elapsed.TotalMinutes:0.0} minutes."; } string normalized = _lastProgressDescription.Trim().TrimEnd('.'); if (normalized.StartsWith("Downloading ", StringComparison.OrdinalIgnoreCase)) { string remainder = normalized["Downloading ".Length..].ToLowerInvariant(); return $"Downloaded {remainder} in {elapsed.TotalMinutes:0.0} minutes."; } return $"{normalized} in {elapsed.TotalMinutes:0.0} minutes."; } }