From 15d86e35d813b90085fc2ebe1206586ddb2a6ab9 Mon Sep 17 00:00:00 2001 From: Paul Hebble Date: Wed, 6 Mar 2024 17:16:08 -0600 Subject: [PATCH] Trigger progress updates for frozen downloads --- .gitignore | 3 +++ Core/Extensions/IOExtensions.cs | 33 +++++++++++++++++++++++++++++---- Core/Net/ResumingWebClient.cs | 1 + 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index fe1924a804..3e7b8a28d7 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,9 @@ bld/ # Don't ignore the root bin folder !/bin/ +.config/ +.mypy_cache/ + # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore diff --git a/Core/Extensions/IOExtensions.cs b/Core/Extensions/IOExtensions.cs index 0e6e04f167..5f2ec9f6ef 100644 --- a/Core/Extensions/IOExtensions.cs +++ b/Core/Extensions/IOExtensions.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Collections.Generic; using System.Threading; +using Timer = System.Timers.Timer; namespace CKAN.Extensions { @@ -64,15 +65,37 @@ public static DriveInfo GetDrive(this DirectoryInfo dir) /// Stream from which to copy /// Stream to which to copy /// Callback to notify as we traverse the input, called with count of bytes received - public static void CopyTo(this Stream src, Stream dest, IProgress progress, CancellationToken cancelToken = default) + /// Maximum timespand to elapse between progress updates, will synthesize extra updates as needed + public static void CopyTo(this Stream src, + Stream dest, + IProgress progress, + TimeSpan? idleInterval = null, + CancellationToken cancelToken = default) { // CopyTo says its default buffer is 81920, but we want more than 1 update for a 100 KiB file const int bufSize = 16384; var buffer = new byte[bufSize]; long total = 0; + var lastProgressTime = DateTime.Now; + // Sometimes a server just freezes and times out, send extra updates if requested + Timer timer = null; + if (idleInterval.HasValue) + { + timer = new Timer(idleInterval.Value > minProgressInterval + ? idleInterval.Value.TotalMilliseconds + : minProgressInterval.TotalMilliseconds) + { + AutoReset = true, + }; + timer.Elapsed += (sender, args) => + { + progress.Report(total); + lastProgressTime = DateTime.Now; + }; + timer.Start(); + } // Make sure we get an initial progress notification at the start progress.Report(total); - var lastProgressTime = DateTime.Now; while (true) { var bytesRead = src.Read(buffer, 0, bufSize); @@ -84,8 +107,10 @@ public static void CopyTo(this Stream src, Stream dest, IProgress progress total += bytesRead; cancelToken.ThrowIfCancellationRequested(); var now = DateTime.Now; - if (now - lastProgressTime >= progressInterval) + if (now - lastProgressTime >= minProgressInterval) { + timer?.Stop(); + timer?.Start(); progress.Report(total); lastProgressTime = now; } @@ -103,6 +128,6 @@ public static IEnumerable BytesFromStream(this Stream s) } } - private static readonly TimeSpan progressInterval = TimeSpan.FromMilliseconds(200); + private static readonly TimeSpan minProgressInterval = TimeSpan.FromMilliseconds(200); } } diff --git a/Core/Net/ResumingWebClient.cs b/Core/Net/ResumingWebClient.cs index f851e86d04..10ddec921f 100644 --- a/Core/Net/ResumingWebClient.cs +++ b/Core/Net/ResumingWebClient.cs @@ -139,6 +139,7 @@ protected override void OnOpenReadCompleted(OpenReadCompletedEventArgs e) DownloadProgress?.Invoke((int)(100 * bytesDownloaded / contentLength), bytesDownloaded, contentLength); }), + TimeSpan.FromSeconds(5), cancelTokenSrc.Token); // Make sure caller knows we've finished DownloadProgress?.Invoke(100, contentLength, contentLength);