mirror of
https://github.com/velopack/velopack.git
synced 2025-10-25 15:19:22 +00:00
Add --timeout as a cli option
This commit is contained in:
@@ -55,7 +55,7 @@ namespace Velopack.Sources
|
||||
// this might be a browser url or an api url (depending on whether we have a AccessToken or not)
|
||||
// https://docs.github.com/en/rest/reference/releases#get-a-release-asset
|
||||
var assetUrl = GetAssetUrlFromName(githubEntry.Release, releaseEntry.FileName);
|
||||
return Downloader.DownloadFile(assetUrl, localFile, progress, Authorization, "application/octet-stream", cancelToken);
|
||||
return Downloader.DownloadFile(assetUrl, localFile, progress, Authorization, "application/octet-stream", cancelToken: cancelToken);
|
||||
}
|
||||
|
||||
throw new ArgumentException($"Expected releaseEntry to be {nameof(GitBaseAsset)} but got {releaseEntry.GetType().Name}.");
|
||||
|
||||
@@ -18,9 +18,10 @@ namespace Velopack.Sources
|
||||
public static ProductInfoHeaderValue UserAgent => new("Velopack", VelopackRuntimeInfo.VelopackNugetVersion.ToFullString());
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task DownloadFile(string url, string targetFile, Action<int> progress, string? authorization, string? accept, CancellationToken cancelToken = default)
|
||||
public virtual async Task DownloadFile(string url, string targetFile, Action<int> progress, string? authorization, string? accept, double timeout, CancellationToken cancelToken = default)
|
||||
{
|
||||
using var client = CreateHttpClient(authorization, accept);
|
||||
using var client = CreateHttpClient(authorization, accept, timeout);
|
||||
|
||||
try {
|
||||
using (var fs = File.Open(targetFile, FileMode.Create)) {
|
||||
await DownloadToStreamInternal(client, url, fs, progress, cancelToken).ConfigureAwait(false);
|
||||
@@ -35,9 +36,10 @@ namespace Velopack.Sources
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<byte[]> DownloadBytes(string url, string? authorization, string? accept)
|
||||
public virtual async Task<byte[]> DownloadBytes(string url, string? authorization, string? accept, double timeout)
|
||||
{
|
||||
using var client = CreateHttpClient(authorization, accept);
|
||||
using var client = CreateHttpClient(authorization, accept, timeout);
|
||||
|
||||
try {
|
||||
return await client.GetByteArrayAsync(url).ConfigureAwait(false);
|
||||
} catch {
|
||||
@@ -48,9 +50,10 @@ namespace Velopack.Sources
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<string> DownloadString(string url, string? authorization, string? accept)
|
||||
public virtual async Task<string> DownloadString(string url, string? authorization, string? accept, double timeout)
|
||||
{
|
||||
using var client = CreateHttpClient(authorization, accept);
|
||||
using var client = CreateHttpClient(authorization, accept, timeout);
|
||||
|
||||
try {
|
||||
return await client.GetStringAsync(url).ConfigureAwait(false);
|
||||
} catch {
|
||||
@@ -120,9 +123,9 @@ namespace Velopack.Sources
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HttpClient"/> for every request.
|
||||
/// </summary>
|
||||
protected virtual HttpClient CreateHttpClient(string? authorization, string? accept)
|
||||
protected virtual HttpClient CreateHttpClient(string? authorization, string? accept, double timeout = 30)
|
||||
{
|
||||
var client = new HttpClient(CreateHttpClientHandler(), true);
|
||||
var client = new HttpClient(CreateHttpClientHandler());
|
||||
client.DefaultRequestHeaders.UserAgent.Add(UserAgent);
|
||||
|
||||
if (authorization != null)
|
||||
@@ -131,6 +134,7 @@ namespace Velopack.Sources
|
||||
if (accept != null)
|
||||
client.DefaultRequestHeaders.Add("Accept", accept);
|
||||
|
||||
client.Timeout = TimeSpan.FromMinutes(timeout);
|
||||
return client;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,17 +25,20 @@ namespace Velopack.Sources
|
||||
/// <param name="accept">
|
||||
/// Text to be sent in the 'Accept' header of the request.
|
||||
/// </param>
|
||||
/// <param name="timeout">
|
||||
/// The maximum time in minutes to wait for the download to complete.
|
||||
/// </param>
|
||||
/// <param name="cancelToken">Optional token to cancel the request.</param>
|
||||
Task DownloadFile(string url, string targetFile, Action<int> progress, string? authorization = null, string? accept = null, CancellationToken cancelToken = default);
|
||||
Task DownloadFile(string url, string targetFile, Action<int> progress, string? authorization = null, string? accept = null, double timeout = 30, CancellationToken cancelToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a byte array containing the contents of the file at the specified url
|
||||
/// </summary>
|
||||
Task<byte[]> DownloadBytes(string url, string? authorization = null, string? accept = null);
|
||||
Task<byte[]> DownloadBytes(string url, string? authorization = null, string? accept = null, double timeout = 30);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string containing the contents of the specified url
|
||||
/// </summary>
|
||||
Task<string> DownloadString(string url, string? authorization = null, string? accept = null);
|
||||
Task<string> DownloadString(string url, string? authorization = null, string? accept = null, double timeout = 30);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ namespace Velopack.Sources
|
||||
/// </summary>
|
||||
public class SimpleWebSource : IUpdateSource
|
||||
{
|
||||
/// <summary> The timeout for http requests, in minutes. </summary>
|
||||
public double Timeout { get; set; }
|
||||
|
||||
/// <summary> The URL of the server hosting packages to update to. </summary>
|
||||
public virtual Uri BaseUri { get; }
|
||||
|
||||
@@ -21,15 +24,16 @@ namespace Velopack.Sources
|
||||
public virtual IFileDownloader Downloader { get; }
|
||||
|
||||
/// <inheritdoc cref="SimpleWebSource" />
|
||||
public SimpleWebSource(string baseUrl, IFileDownloader? downloader = null)
|
||||
: this(new Uri(baseUrl), downloader)
|
||||
public SimpleWebSource(string baseUrl, IFileDownloader? downloader = null, double timeout = 30)
|
||||
: this(new Uri(baseUrl), downloader, timeout)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc cref="SimpleWebSource" />
|
||||
public SimpleWebSource(Uri baseUri, IFileDownloader? downloader = null)
|
||||
public SimpleWebSource(Uri baseUri, IFileDownloader? downloader = null, double timeout = 30)
|
||||
{
|
||||
BaseUri = baseUri;
|
||||
Downloader = downloader ?? HttpUtil.CreateDefaultDownloader();
|
||||
Timeout = timeout;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -57,7 +61,7 @@ namespace Velopack.Sources
|
||||
|
||||
logger.Info($"Downloading release file '{releaseFilename}' from '{uriAndQuery}'.");
|
||||
|
||||
var json = await Downloader.DownloadString(uriAndQuery.ToString()).ConfigureAwait(false);
|
||||
var json = await Downloader.DownloadString(uriAndQuery.ToString(), timeout: Timeout).ConfigureAwait(false);
|
||||
return VelopackAssetFeed.FromJson(json);
|
||||
}
|
||||
|
||||
@@ -76,7 +80,7 @@ namespace Velopack.Sources
|
||||
: HttpUtil.AppendPathToUri(sourceBaseUri, releaseEntry.FileName).ToString();
|
||||
|
||||
logger.Info($"Downloading '{releaseEntry.FileName}' from '{source}'.");
|
||||
await Downloader.DownloadFile(source, localFile, progress, cancelToken: cancelToken).ConfigureAwait(false);
|
||||
await Downloader.DownloadFile(source, localFile, progress, timeout: Timeout, cancelToken: cancelToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ public class AzureDownloadOptions : RepositoryOptions, IObjectDownloadOptions
|
||||
public string Container { get; set; }
|
||||
|
||||
public string SasToken { get; set; }
|
||||
|
||||
public double Timeout { get; set; }
|
||||
}
|
||||
|
||||
public class AzureUploadOptions : AzureDownloadOptions, IObjectUploadOptions
|
||||
@@ -39,8 +41,10 @@ public class AzureRepository : ObjectRepository<AzureDownloadOptions, AzureUploa
|
||||
}
|
||||
|
||||
BlobServiceClient client;
|
||||
|
||||
// Override default timeout with user-specified value
|
||||
BlobClientOptions clientOptions = new BlobClientOptions();
|
||||
clientOptions.Retry.NetworkTimeout = TimeSpan.FromMinutes(30);
|
||||
clientOptions.Retry.NetworkTimeout = TimeSpan.FromMinutes(options.Timeout);
|
||||
|
||||
if (!String.IsNullOrEmpty(options.SasToken)) {
|
||||
client = new BlobServiceClient(new Uri(serviceUrl), new AzureSasCredential(options.SasToken), clientOptions);
|
||||
|
||||
@@ -16,6 +16,8 @@ public class GitHubDownloadOptions : RepositoryOptions
|
||||
public string RepoUrl { get; set; }
|
||||
|
||||
public string Token { get; set; }
|
||||
|
||||
public double Timeout { get; set; }
|
||||
}
|
||||
|
||||
public class GitHubUploadOptions : GitHubDownloadOptions
|
||||
@@ -67,7 +69,7 @@ public class GitHubRepository(ILogger logger) : SourceRepository<GitHubDownloadO
|
||||
Credentials = new Credentials(options.Token)
|
||||
};
|
||||
|
||||
client.SetRequestTimeout(TimeSpan.FromHours(1));
|
||||
client.SetRequestTimeout(TimeSpan.FromMinutes(options.Timeout));
|
||||
|
||||
var existingReleases = await client.Repository.Release.GetAll(repoOwner, repoName);
|
||||
if (!options.Merge) {
|
||||
|
||||
@@ -20,6 +20,8 @@ public class GiteaDownloadOptions : RepositoryOptions
|
||||
|
||||
public string Token { get; set; }
|
||||
|
||||
public double Timeout { get; set; }
|
||||
|
||||
///// <summary>
|
||||
///// Example https://gitea.com
|
||||
///// </summary>
|
||||
@@ -40,6 +42,7 @@ public class GiteaUploadOptions : GiteaDownloadOptions
|
||||
|
||||
public bool Merge { get; set; }
|
||||
}
|
||||
|
||||
public class GiteaRepository : SourceRepository<GiteaDownloadOptions, GiteaSource>, IRepositoryCanUpload<GiteaUploadOptions>
|
||||
{
|
||||
public GiteaRepository(ILogger logger) : base(logger)
|
||||
@@ -79,6 +82,7 @@ public class GiteaRepository : SourceRepository<GiteaDownloadOptions, GiteaSourc
|
||||
var uri = new Uri(options.RepoUrl);
|
||||
var baseUri = uri.GetLeftPart(System.UriPartial.Authority);
|
||||
config.BasePath = baseUri + "/api/v1";
|
||||
config.Timeout = (int)TimeSpan.FromMinutes(options.Timeout).TotalMilliseconds;
|
||||
|
||||
Log.Info($"Preparing to upload {build.Files.Count} asset(s) to Gitea");
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ namespace Velopack.Deployment;
|
||||
public class HttpDownloadOptions : RepositoryOptions
|
||||
{
|
||||
public string Url { get; set; }
|
||||
|
||||
public double Timeout { get; set; }
|
||||
}
|
||||
|
||||
public class HttpRepository : SourceRepository<HttpDownloadOptions, SimpleWebSource>
|
||||
@@ -16,6 +18,6 @@ public class HttpRepository : SourceRepository<HttpDownloadOptions, SimpleWebSou
|
||||
|
||||
public override SimpleWebSource CreateSource(HttpDownloadOptions options)
|
||||
{
|
||||
return new SimpleWebSource(options.Url);
|
||||
return new SimpleWebSource(options.Url, timeout: options.Timeout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ public class S3DownloadOptions : RepositoryOptions, IObjectDownloadOptions
|
||||
public string Bucket { get; set; }
|
||||
|
||||
public string Prefix { get; set; }
|
||||
|
||||
public double Timeout { get; set; }
|
||||
}
|
||||
|
||||
public class S3UploadOptions : S3DownloadOptions, IObjectUploadOptions
|
||||
@@ -97,6 +99,7 @@ public class S3Repository : ObjectRepository<S3DownloadOptions, S3UploadOptions,
|
||||
var config = new AmazonS3Config() {
|
||||
ServiceURL = options.Endpoint,
|
||||
ForcePathStyle = true, // support for MINIO
|
||||
Timeout = TimeSpan.FromMinutes(options.Timeout)
|
||||
};
|
||||
|
||||
if (options.Endpoint != null) {
|
||||
|
||||
@@ -12,6 +12,8 @@ public class AzureBaseCommand : OutputCommand
|
||||
|
||||
public string SasToken { get; private set; }
|
||||
|
||||
public double Timeout { get; private set; }
|
||||
|
||||
protected AzureBaseCommand(string name, string description)
|
||||
: base(name, description)
|
||||
{
|
||||
@@ -38,6 +40,11 @@ public class AzureBaseCommand : OutputCommand
|
||||
.SetArgumentHelpName("URL")
|
||||
.MustBeValidHttpUri();
|
||||
|
||||
AddOption<double>((v) => Timeout = v, "--timeout")
|
||||
.SetDescription("Network timeout in minutes.")
|
||||
.SetArgumentHelpName("MINUTES")
|
||||
.SetDefault(30);
|
||||
|
||||
this.AtLeastOneRequired(sas, key);
|
||||
this.AreMutuallyExclusive(sas, key);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ public abstract class GitHubBaseCommand : OutputCommand
|
||||
|
||||
public string Token { get; private set; }
|
||||
|
||||
public double Timeout { get; private set; }
|
||||
|
||||
protected GitHubBaseCommand(string name, string description)
|
||||
: base(name, description)
|
||||
{
|
||||
@@ -16,5 +18,10 @@ public abstract class GitHubBaseCommand : OutputCommand
|
||||
|
||||
AddOption<string>((v) => Token = v, "--token")
|
||||
.SetDescription("OAuth token to use as login credentials.");
|
||||
|
||||
AddOption<double>((v) => Timeout = v, "--timeout")
|
||||
.SetDescription("Network timeout in minutes.")
|
||||
.SetArgumentHelpName("MINUTES")
|
||||
.SetDefault(30);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ public abstract class GiteaBaseCommand : OutputCommand
|
||||
|
||||
public string Token { get; private set; }
|
||||
|
||||
public double Timeout { get; private set; }
|
||||
|
||||
protected GiteaBaseCommand(string name, string description)
|
||||
: base(name, description)
|
||||
{
|
||||
@@ -15,5 +17,10 @@ public abstract class GiteaBaseCommand : OutputCommand
|
||||
|
||||
AddOption<string>((v) => Token = v, "--token")
|
||||
.SetDescription("OAuth token to use as login credentials.");
|
||||
|
||||
AddOption<double>((v) => Timeout = v, "--timeout")
|
||||
.SetDescription("Network timeout in minutes.")
|
||||
.SetArgumentHelpName("MINUTES")
|
||||
.SetDefault(30);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
namespace Velopack.Vpk.Commands.Deployment;
|
||||
using System.Threading;
|
||||
|
||||
namespace Velopack.Vpk.Commands.Deployment;
|
||||
|
||||
public class HttpDownloadCommand : OutputCommand
|
||||
{
|
||||
public string Url { get; private set; }
|
||||
|
||||
public double Timeout { get; private set; }
|
||||
|
||||
public HttpDownloadCommand()
|
||||
: base("http", "Download latest release from a HTTP source.")
|
||||
{
|
||||
@@ -11,5 +15,10 @@ public class HttpDownloadCommand : OutputCommand
|
||||
.SetDescription("Url to download remote releases from.")
|
||||
.MustBeValidHttpUri()
|
||||
.SetRequired();
|
||||
|
||||
AddOption<double>((v) => Timeout = v, "--timeout")
|
||||
.SetDescription("Network timeout in minutes.")
|
||||
.SetArgumentHelpName("MINUTES")
|
||||
.SetDefault(30);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ public class S3BaseCommand : OutputCommand
|
||||
|
||||
public string Prefix { get; private set; }
|
||||
|
||||
public double Timeout { get; private set; }
|
||||
|
||||
protected S3BaseCommand(string name, string description)
|
||||
: base(name, description)
|
||||
{
|
||||
@@ -53,6 +55,11 @@ public class S3BaseCommand : OutputCommand
|
||||
AddOption<string>((v) => Prefix = v, "--prefix")
|
||||
.SetDescription("Prefix to the S3 url.")
|
||||
.SetArgumentHelpName("PREFIX");
|
||||
|
||||
AddOption<double>((v) => Timeout = v, "--timeout")
|
||||
.SetDescription("Network timeout in minutes.")
|
||||
.SetArgumentHelpName("MINUTES")
|
||||
.SetDefault(30);
|
||||
}
|
||||
|
||||
private static void MustBeValidAwsRegion(OptionResult result)
|
||||
|
||||
@@ -17,7 +17,7 @@ public class OptionMapperTests
|
||||
public void MapCommand()
|
||||
{
|
||||
AzureUploadCommand command = new();
|
||||
string cli = $"--account \"account-name\" --key \"shhhh\" --endpoint \"https://endpoint\" --container \"mycontainer\"";
|
||||
string cli = $"--account \"account-name\" --key \"shhhh\" --endpoint \"https://endpoint\" --container \"mycontainer\" --timeout 45";
|
||||
ParseResult parseResult = command.ParseAndApply(cli);
|
||||
var options = OptionMapper.Map<AzureUploadOptions>(command);
|
||||
|
||||
@@ -26,5 +26,6 @@ public class OptionMapperTests
|
||||
Assert.Equal("shhhh", options.Key);
|
||||
Assert.Equal("https://endpoint/", options.Endpoint);
|
||||
Assert.Equal("mycontainer", options.Container);
|
||||
Assert.Equal(45, options.Timeout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public class FakeDownloader : Sources.IFileDownloader
|
||||
public byte[] MockedResponseBytes { get; set; } = new byte[0];
|
||||
public bool WriteMockLocalFile { get; set; } = false;
|
||||
|
||||
public Task<byte[]> DownloadBytes(string url, string auth, string acc)
|
||||
public Task<byte[]> DownloadBytes(string url, string auth, string acc, double timeout = 30)
|
||||
{
|
||||
LastUrl = url;
|
||||
LastAuthHeader = auth;
|
||||
@@ -19,7 +19,7 @@ public class FakeDownloader : Sources.IFileDownloader
|
||||
return Task.FromResult(MockedResponseBytes);
|
||||
}
|
||||
|
||||
public async Task DownloadFile(string url, string targetFile, Action<int> progress, string auth, string acc, CancellationToken token)
|
||||
public async Task DownloadFile(string url, string targetFile, Action<int> progress, string auth, string acc, double timeout, CancellationToken token)
|
||||
{
|
||||
LastLocalFile = targetFile;
|
||||
var resp = await DownloadBytes(url, auth, acc);
|
||||
@@ -31,7 +31,7 @@ public class FakeDownloader : Sources.IFileDownloader
|
||||
File.WriteAllBytes(targetFile, resp);
|
||||
}
|
||||
|
||||
public async Task<string> DownloadString(string url, string auth, string acc)
|
||||
public async Task<string> DownloadString(string url, string auth, string acc, double timeout = 30)
|
||||
{
|
||||
return Encoding.UTF8.GetString(await DownloadBytes(url, auth, acc));
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ internal class FakeFixtureRepository : Sources.IFileDownloader
|
||||
_releases = releases;
|
||||
}
|
||||
|
||||
public Task<byte[]> DownloadBytes(string url, string authorization = null, string accept = null)
|
||||
public Task<byte[]> DownloadBytes(string url, string authorization = null, string accept = null, double timeout = 30)
|
||||
{
|
||||
if (url.Contains($"/{_releasesName}?")) {
|
||||
MemoryStream ms = new MemoryStream();
|
||||
@@ -80,7 +80,7 @@ internal class FakeFixtureRepository : Sources.IFileDownloader
|
||||
return Task.FromResult(File.ReadAllBytes(filePath));
|
||||
}
|
||||
|
||||
public Task DownloadFile(string url, string targetFile, Action<int> progress, string authorization = null, string accept = null, CancellationToken token = default)
|
||||
public Task DownloadFile(string url, string targetFile, Action<int> progress, string authorization = null, string accept = null, double timeout = 30, CancellationToken token = default)
|
||||
{
|
||||
var rel = _releases.FirstOrDefault(r => url.EndsWith(r.OriginalFilename));
|
||||
var filePath = PathHelper.GetFixture(rel.OriginalFilename);
|
||||
@@ -96,7 +96,7 @@ internal class FakeFixtureRepository : Sources.IFileDownloader
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<string> DownloadString(string url, string authorization = null, string accept = null)
|
||||
public Task<string> DownloadString(string url, string authorization = null, string accept = null, double timeout = 30)
|
||||
{
|
||||
if (url.Contains($"/{_releasesName}?")) {
|
||||
MemoryStream ms = new MemoryStream();
|
||||
|
||||
Reference in New Issue
Block a user