Skip to content

Commit

Permalink
boost(updater): auto retry when can't download the file
Browse files Browse the repository at this point in the history
  • Loading branch information
Belphemur committed Apr 9, 2024
1 parent 787c7f3 commit 51bac9c
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 51 deletions.
115 changes: 79 additions & 36 deletions SoundSwitch/Framework/Updater/FileDownloader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,76 +4,119 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Polly;
using Polly.Retry;
using Polly.Timeout;
using Serilog;

namespace SoundSwitch.Framework.Updater
{
public class FileDownloader
{
private static readonly ResiliencePipeline<HttpResponseMessage> RetryPipeline;
private const int MAX_RETRY_ATTEMPTS = 5;

static FileDownloader()
{
var retryOption = new RetryStrategyOptions<HttpResponseMessage>
{
BackoffType = DelayBackoffType.Exponential,
Delay = TimeSpan.FromSeconds(1),
MaxRetryAttempts = MAX_RETRY_ATTEMPTS,
UseJitter = true,
OnRetry = arguments =>
{
Log.Warning("Failed to download retrying {RetryCount}/{MaxRetries}", arguments.AttemptNumber, MAX_RETRY_ATTEMPTS);
return ValueTask.CompletedTask;
},
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.HandleResult(response => !response.IsSuccessStatusCode) // Handle results
.Handle<HttpRequestException>() // Or handle exception
.Handle<TimeoutRejectedException>() // Chaining is supported
.Handle<HttpIOException>()
};
RetryPipeline = new ResiliencePipelineBuilder<HttpResponseMessage>().AddRetry(retryOption).Build();
}

/// <summary>
/// Downloads a file from the specified Uri into the specified stream.
/// </summary>
/// <param name="cancellationToken">An optional CancellationToken that can be used to cancel the in-progress download.</param>
/// <param name="uri"></param>
/// <param name="toStream"></param>
/// <param name="progressCallback">If not null, will be called as the download progress. The first parameter will be the number of bytes downloaded so far, and the second the total size of the expected file after download.</param>
/// <param name="cancellationToken">An optional CancellationToken that can be used to cancel the in-progress download.</param>
/// <returns>A task that is completed once the download is complete.</returns>
public static async Task DownloadFileAsync(Uri uri, Stream toStream, CancellationToken cancellationToken = default, Action<long, long> progressCallback = null)
public static async Task DownloadFileAsync(Uri uri, Stream toStream, Action<long, long> progressCallback = null, CancellationToken cancellationToken = default)
{
if (uri == null)
throw new ArgumentNullException(nameof(uri));
if (toStream == null)
throw new ArgumentNullException(nameof(toStream));
ArgumentNullException.ThrowIfNull(uri);
ArgumentNullException.ThrowIfNull(toStream);

if (uri.IsFile)
{
await using Stream file = File.OpenRead(uri.LocalPath);
await ProcessFile(uri, toStream, progressCallback, cancellationToken);
return;
}

if (progressCallback != null)
{
var length = file.Length;
var buffer = new byte[4096];
int read;
var totalRead = 0;
while ((read = await file.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) > 0)
{
await toStream.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false);
totalRead += read;
progressCallback(totalRead, length);
}
await DownloadFileFromUrl(uri, toStream, progressCallback, cancellationToken);
}

Debug.Assert(totalRead == length || length == -1);
}
else
{
await file.CopyToAsync(toStream, cancellationToken).ConfigureAwait(false);
}
}
else
private static async Task DownloadFileFromUrl(Uri uri, Stream toStream, Action<long, long> progressCallback, CancellationToken cancellationToken)
{
using var client = new HttpClient();
client.DefaultRequestHeaders.UserAgent.Add(ApplicationInfo.ProductValue);
client.DefaultRequestHeaders.UserAgent.Add(ApplicationInfo.CommentValue);

using var response = await RetryPipeline.ExecuteAsync(async token =>
{
using var client = new HttpClient();
client.DefaultRequestHeaders.UserAgent.Add(ApplicationInfo.ProductValue);
client.DefaultRequestHeaders.UserAgent.Add(ApplicationInfo.CommentValue);
using var response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);

var response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false);
if (progressCallback != null)
{
var length = response.Content.Headers.ContentLength ?? -1;
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var buffer = new byte[4096];
int read;
var totalRead = 0;
while ((read = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) > 0)
while ((read = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) > 0)
{
await toStream.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false);
await toStream.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
totalRead += read;
progressCallback(totalRead, length);
}

Debug.Assert(totalRead == length || length == -1);
return response;
}
else

await response.Content.CopyToAsync(toStream, cancellationToken).ConfigureAwait(false);
return response;
}, cancellationToken);
}

private static async Task ProcessFile(Uri uri, Stream toStream, Action<long, long> progressCallback, CancellationToken cancellationToken)
{
await using var file = File.OpenRead(uri.LocalPath);

if (progressCallback != null)
{
var length = file.Length;
var buffer = new byte[4096];
int read;
var totalRead = 0;
while ((read = await file.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) > 0)
{
await response.Content.CopyToAsync(toStream, cancellationToken).ConfigureAwait(false);
await toStream.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
totalRead += read;
progressCallback(totalRead, length);
}

Debug.Assert(totalRead == length || length == -1);
return;
}

await file.CopyToAsync(toStream, cancellationToken).ConfigureAwait(false);


return;
}
}
}
28 changes: 13 additions & 15 deletions SoundSwitch/Framework/Updater/Installer/WebFile.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
/********************************************************************
* Copyright (C) 2015-2017 Antoine Aflalo
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Lesser GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
********************************************************************/
* Copyright (C) 2015-2017 Antoine Aflalo
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Lesser GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
********************************************************************/

using System;
using System.Diagnostics;
Expand Down Expand Up @@ -93,9 +93,7 @@ public void DownloadFile()
await FileDownloader.DownloadFileAsync(
FileUri,
stream,
_cancellationTokenSource.Token,
(downloaded, total) => { DownloadProgress?.Invoke(this, new DownloadProgress(downloaded, total)); }
);
(downloaded, total) => { DownloadProgress?.Invoke(this, new DownloadProgress(downloaded, total)); }, _cancellationTokenSource.Token);
}

if (Exists())
Expand Down
1 change: 1 addition & 0 deletions SoundSwitch/SoundSwitch.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
<PackageReference Include="Markdig" Version="0.36.2" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="NuGet.Versioning" Version="6.9.1" />
<PackageReference Include="Polly.Core" Version="8.3.1" />
<PackageReference Include="RailSharp" Version="1.0.0" />
<PackageReference Include="Sentry.Serilog" Version="4.2.1" />
<PackageReference Include="Serilog" Version="3.1.1" />
Expand Down

0 comments on commit 51bac9c

Please sign in to comment.