diff --git a/Octokit.Reactive/Clients/IObservableReleasesClient.cs b/Octokit.Reactive/Clients/IObservableReleasesClient.cs index b4a2b2a200..7320f5addd 100644 --- a/Octokit.Reactive/Clients/IObservableReleasesClient.cs +++ b/Octokit.Reactive/Clients/IObservableReleasesClient.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Reactive; +using System.Threading; namespace Octokit.Reactive { @@ -250,8 +251,9 @@ public interface IObservableReleasesClient /// /// The to attach the uploaded asset to /// Description of the asset with its data + /// An optional token to monitor for cancellation requests /// Thrown when a general API error occurs. - IObservable UploadAsset(Release release, ReleaseAssetUpload data); + IObservable UploadAsset(Release release, ReleaseAssetUpload data, CancellationToken cancellationToken = default); /// /// Gets the specified for the specified release of the specified repository. diff --git a/Octokit.Reactive/Clients/ObservableReleasesClient.cs b/Octokit.Reactive/Clients/ObservableReleasesClient.cs index b9568e56dc..644269643b 100644 --- a/Octokit.Reactive/Clients/ObservableReleasesClient.cs +++ b/Octokit.Reactive/Clients/ObservableReleasesClient.cs @@ -1,6 +1,7 @@ using System; using System.Reactive; using System.Reactive.Threading.Tasks; +using System.Threading; using Octokit.Reactive.Internal; namespace Octokit.Reactive @@ -400,13 +401,14 @@ public IObservable GetAsset(long repositoryId, int assetId) /// /// The to attach the uploaded asset to /// Description of the asset with its data + /// An optional token to monitor for cancellation requests /// Thrown when a general API error occurs. - public IObservable UploadAsset(Release release, ReleaseAssetUpload data) + public IObservable UploadAsset(Release release, ReleaseAssetUpload data, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(release, nameof(release)); Ensure.ArgumentNotNull(data, nameof(data)); - return _client.UploadAsset(release, data).ToObservable(); + return _client.UploadAsset(release, data, cancellationToken).ToObservable(); } /// diff --git a/Octokit.Tests/Clients/ReleasesClientTests.cs b/Octokit.Tests/Clients/ReleasesClientTests.cs index db25679e88..742c8e8912 100644 --- a/Octokit.Tests/Clients/ReleasesClientTests.cs +++ b/Octokit.Tests/Clients/ReleasesClientTests.cs @@ -1,7 +1,9 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; using NSubstitute; +using Octokit.Internal; using Xunit; namespace Octokit.Tests.Clients @@ -484,6 +486,42 @@ public async Task OverrideDefaultTimeout() apiConnection.Received().Post(Arg.Any(), uploadData.RawData, Arg.Any(), uploadData.ContentType, newTimeout); } + + [Fact] + public async Task CanBeCancelled() + { + var httpClient = new CancellationTestHttpClient(); + var connection = new Connection(new ProductHeaderValue("TEST"), httpClient); + var apiConnection = new ApiConnection(connection); + + var fixture = new ReleasesClient(apiConnection); + + var release = new Release("https://uploads.github.com/anything"); + var uploadData = new ReleaseAssetUpload("good", "good/good", Stream.Null, null); + + using (var cts = new CancellationTokenSource()) + { + var uploadTask = fixture.UploadAsset(release, uploadData, cts.Token); + + cts.Cancel(); + + await Assert.ThrowsAsync(() => uploadTask); + } + } + + private class CancellationTestHttpClient : IHttpClient + { + public async Task Send(IRequest request, CancellationToken cancellationToken) + { + await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken); + + throw new Exception("HTTP operation was not cancelled"); + } + + public void Dispose() { } + + public void SetRequestTimeout(TimeSpan timeout) { } + } } public class TheGetAssetMethod diff --git a/Octokit/Clients/IReleasesClient.cs b/Octokit/Clients/IReleasesClient.cs index 56a700a23c..d8ef30bb33 100644 --- a/Octokit/Clients/IReleasesClient.cs +++ b/Octokit/Clients/IReleasesClient.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; namespace Octokit @@ -250,8 +251,9 @@ public interface IReleasesClient /// /// The to attach the uploaded asset to /// Description of the asset with its data + /// An optional token to monitor for cancellation requests /// Thrown when a general API error occurs. - Task UploadAsset(Release release, ReleaseAssetUpload data); + Task UploadAsset(Release release, ReleaseAssetUpload data, CancellationToken cancellationToken = default); /// /// Gets the specified for the specified release of the specified repository. diff --git a/Octokit/Clients/ReleasesClient.cs b/Octokit/Clients/ReleasesClient.cs index d1e6e0d3c0..dd1b360eb6 100644 --- a/Octokit/Clients/ReleasesClient.cs +++ b/Octokit/Clients/ReleasesClient.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using System.Collections.Generic; +using System.Threading; namespace Octokit { @@ -398,9 +399,10 @@ public Task> GetAllAssets(long repositoryId, int id, /// /// The to attach the uploaded asset to /// Description of the asset with its data + /// An optional token to monitor for cancellation requests /// Thrown when a general API error occurs. [ManualRoute("POST", "{server}/repos/{owner}/{repo}/releases/{release_id}/assets")] - public Task UploadAsset(Release release, ReleaseAssetUpload data) + public Task UploadAsset(Release release, ReleaseAssetUpload data, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(release, nameof(release)); Ensure.ArgumentNotNull(data, nameof(data)); @@ -414,14 +416,16 @@ public Task UploadAsset(Release release, ReleaseAssetUpload data) data.RawData, AcceptHeaders.StableVersion, data.ContentType, - data.Timeout.GetValueOrDefault()); + data.Timeout.GetValueOrDefault(), + cancellationToken); } return ApiConnection.Post( endpoint, data.RawData, AcceptHeaders.StableVersion, - data.ContentType); + data.ContentType, + cancellationToken); } /// diff --git a/Octokit/Http/ApiConnection.cs b/Octokit/Http/ApiConnection.cs index 5a477db6e0..1a5694271c 100644 --- a/Octokit/Http/ApiConnection.cs +++ b/Octokit/Http/ApiConnection.cs @@ -216,13 +216,14 @@ public Task> GetAll(Uri uri, IDictionary par /// Creates a new API resource in the list at the specified URI. /// /// URI endpoint to send request to + /// An optional token to monitor for cancellation requests /// Representing the received HTTP response /// Thrown when an API error occurs. - public Task Post(Uri uri) + public Task Post(Uri uri, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(uri, nameof(uri)); - return Connection.Post(uri); + return Connection.Post(uri, cancellationToken); } /// @@ -230,13 +231,14 @@ public Task Post(Uri uri) /// /// The API resource's type. /// URI of the API resource to get + /// An optional token to monitor for cancellation requests /// The created API resource. /// Thrown when an API error occurs. - public async Task Post(Uri uri) + public async Task Post(Uri uri, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(uri, nameof(uri)); - var response = await Connection.Post(uri).ConfigureAwait(false); + var response = await Connection.Post(uri, cancellationToken).ConfigureAwait(false); return response.Body; } @@ -246,14 +248,15 @@ public async Task Post(Uri uri) /// The API resource's type. /// URI of the API resource to get /// Object that describes the new API resource; this will be serialized and used as the request's body + /// An optional token to monitor for cancellation requests /// The created API resource. /// Thrown when an API error occurs. - public Task Post(Uri uri, object data) + public Task Post(Uri uri, object data, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(uri, nameof(uri)); Ensure.ArgumentNotNull(data, nameof(data)); - return Post(uri, data, null, null); + return Post(uri, data, null, null, cancellationToken); } /// @@ -263,11 +266,12 @@ public Task Post(Uri uri, object data) /// URI of the API resource to get /// Object that describes the new API resource; this will be serialized and used as the request's body /// Accept header to use for the API request + /// An optional token to monitor for cancellation requests /// The created API resource. /// Thrown when an API error occurs. - public Task Post(Uri uri, object data, string accepts) + public Task Post(Uri uri, object data, string accepts, CancellationToken cancellationToken = default) { - return Post(uri, data, accepts, null); + return Post(uri, data, accepts, null, cancellationToken); } /// @@ -278,14 +282,15 @@ public Task Post(Uri uri, object data, string accepts) /// Object that describes the new API resource; this will be serialized and used as the request's body /// Accept header to use for the API request /// Content type of the API request + /// An optional token to monitor for cancellation requests /// The created API resource. /// Thrown when an API error occurs. - public async Task Post(Uri uri, object data, string accepts, string contentType) + public async Task Post(Uri uri, object data, string accepts, string contentType, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(uri, nameof(uri)); Ensure.ArgumentNotNull(data, nameof(data)); - var response = await Connection.Post(uri, data, accepts, contentType).ConfigureAwait(false); + var response = await Connection.Post(uri, data, accepts, contentType, cancellationToken: cancellationToken).ConfigureAwait(false); return response.Body; } @@ -298,25 +303,26 @@ public async Task Post(Uri uri, object data, string accepts, string conten /// Accept header to use for the API request /// Content type of the API request /// Two Factor Authentication Code + /// An optional token to monitor for cancellation requests /// The created API resource. /// Thrown when an API error occurs. - public async Task Post(Uri uri, object data, string accepts, string contentType, string twoFactorAuthenticationCode) + public async Task Post(Uri uri, object data, string accepts, string contentType, string twoFactorAuthenticationCode, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(uri, nameof(uri)); Ensure.ArgumentNotNull(data, nameof(data)); Ensure.ArgumentNotNull(twoFactorAuthenticationCode, nameof(twoFactorAuthenticationCode)); - var response = await Connection.Post(uri, data, accepts, contentType, twoFactorAuthenticationCode).ConfigureAwait(false); + var response = await Connection.Post(uri, data, accepts, contentType, twoFactorAuthenticationCode, cancellationToken).ConfigureAwait(false); return response.Body; } - public async Task Post(Uri uri, object data, string accepts, string contentType, TimeSpan timeout) + public async Task Post(Uri uri, object data, string accepts, string contentType, TimeSpan timeout, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(uri, nameof(uri)); Ensure.ArgumentNotNull(data, nameof(data)); - var response = await Connection.Post(uri, data, accepts, contentType, timeout).ConfigureAwait(false); + var response = await Connection.Post(uri, data, accepts, contentType, timeout, cancellationToken).ConfigureAwait(false); return response.Body; } diff --git a/Octokit/Http/Connection.cs b/Octokit/Http/Connection.cs index 6c2c7560cd..81759fdc2a 100644 --- a/Octokit/Http/Connection.cs +++ b/Octokit/Http/Connection.cs @@ -251,44 +251,51 @@ public Task> Patch(Uri uri, object body, string accepts) /// Performs an asynchronous HTTP POST request. /// /// URI endpoint to send request to + /// An optional token to monitor for cancellation requests /// representing the received HTTP response - public async Task Post(Uri uri) + public async Task Post(Uri uri, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(uri, nameof(uri)); - var response = await SendData(uri, HttpMethod.Post, null, null, null, CancellationToken.None).ConfigureAwait(false); + var response = await SendData(uri, HttpMethod.Post, null, null, null, cancellationToken).ConfigureAwait(false); return response.HttpResponse.StatusCode; } - public async Task Post(Uri uri, object body, string accepts) + public async Task Post(Uri uri, object body, string accepts, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(uri, nameof(uri)); - var response = await SendData(uri, HttpMethod.Post, body, accepts, null, CancellationToken.None).ConfigureAwait(false); + var response = await SendData(uri, HttpMethod.Post, body, accepts, null, cancellationToken).ConfigureAwait(false); return response.HttpResponse.StatusCode; } - public Task> Post(Uri uri) + public Task> Post(Uri uri, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(uri, nameof(uri)); - return SendData(uri, HttpMethod.Post, null, null, null, CancellationToken.None); + return SendData(uri, HttpMethod.Post, null, null, null, cancellationToken); } - public Task> Post(Uri uri, object body, string accepts, string contentType) + public Task> Post(Uri uri, object body, string accepts, string contentType, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(uri, nameof(uri)); Ensure.ArgumentNotNull(body, nameof(body)); - return SendData(uri, HttpMethod.Post, body, accepts, contentType, CancellationToken.None); + return SendData(uri, HttpMethod.Post, body, accepts, contentType, cancellationToken); } - public Task> Post(Uri uri, object body, string accepts, string contentType, IDictionary parameters) + public Task> Post( + Uri uri, + object body, + string accepts, + string contentType, + IDictionary parameters, + CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(uri, nameof(uri)); Ensure.ArgumentNotNull(body, nameof(body)); - return SendData(uri.ApplyParameters(parameters), HttpMethod.Post, body, accepts, contentType, CancellationToken.None); + return SendData(uri.ApplyParameters(parameters), HttpMethod.Post, body, accepts, contentType, cancellationToken); } /// @@ -301,30 +308,37 @@ public Task> Post(Uri uri, object body, string accepts, strin /// Specifies accepted response media types. /// Specifies the media type of the request body /// Two Factor Authentication Code + /// An optional token to monitor for cancellation requests /// representing the received HTTP response - public Task> Post(Uri uri, object body, string accepts, string contentType, string twoFactorAuthenticationCode) + public Task> Post( + Uri uri, + object body, + string accepts, + string contentType, + string twoFactorAuthenticationCode, + CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(uri, nameof(uri)); Ensure.ArgumentNotNull(body, nameof(body)); Ensure.ArgumentNotNullOrEmptyString(twoFactorAuthenticationCode, nameof(twoFactorAuthenticationCode)); - return SendData(uri, HttpMethod.Post, body, accepts, contentType, CancellationToken.None, twoFactorAuthenticationCode); + return SendData(uri, HttpMethod.Post, body, accepts, contentType, cancellationToken, twoFactorAuthenticationCode); } - public Task> Post(Uri uri, object body, string accepts, string contentType, TimeSpan timeout) + public Task> Post(Uri uri, object body, string accepts, string contentType, TimeSpan timeout, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(uri, nameof(uri)); Ensure.ArgumentNotNull(body, nameof(body)); - return SendData(uri, HttpMethod.Post, body, accepts, contentType, timeout, CancellationToken.None); + return SendData(uri, HttpMethod.Post, body, accepts, contentType, timeout, cancellationToken); } - public Task> Post(Uri uri, object body, string accepts, string contentType, Uri baseAddress) + public Task> Post(Uri uri, object body, string accepts, string contentType, Uri baseAddress, CancellationToken cancellationToken = default) { Ensure.ArgumentNotNull(uri, nameof(uri)); Ensure.ArgumentNotNull(body, nameof(body)); - return SendData(uri, HttpMethod.Post, body, accepts, contentType, CancellationToken.None, baseAddress: baseAddress); + return SendData(uri, HttpMethod.Post, body, accepts, contentType, cancellationToken, baseAddress: baseAddress); } public Task> Put(Uri uri, object body) diff --git a/Octokit/Http/IApiConnection.cs b/Octokit/Http/IApiConnection.cs index 976d6c9b61..730cd2f5d8 100644 --- a/Octokit/Http/IApiConnection.cs +++ b/Octokit/Http/IApiConnection.cs @@ -148,18 +148,20 @@ public interface IApiConnection /// Creates a new API resource in the list at the specified URI. /// /// URI endpoint to send request to + /// An optional token to monitor for cancellation requests /// Representing the received HTTP response /// Thrown when an API error occurs. - Task Post(Uri uri); + Task Post(Uri uri, CancellationToken cancellationToken = default); /// /// Creates a new API resource in the list at the specified URI. /// /// The API resource's type. /// URI endpoint to send request to + /// An optional token to monitor for cancellation requests /// The created API resource. /// Thrown when an API error occurs. - Task Post(Uri uri); + Task Post(Uri uri, CancellationToken cancellationToken = default); /// /// Creates a new API resource in the list at the specified URI. @@ -167,9 +169,10 @@ public interface IApiConnection /// The API resource's type. /// URI of the API resource to get /// Object that describes the new API resource; this will be serialized and used as the request's body + /// An optional token to monitor for cancellation requests /// The created API resource. /// Thrown when an API error occurs. - Task Post(Uri uri, object data); + Task Post(Uri uri, object data, CancellationToken cancellationToken = default); /// /// Creates a new API resource in the list at the specified URI. @@ -178,9 +181,10 @@ public interface IApiConnection /// URI of the API resource to get /// Object that describes the new API resource; this will be serialized and used as the request's body /// Accept header to use for the API request + /// An optional token to monitor for cancellation requests /// The created API resource. /// Thrown when an API error occurs. - Task Post(Uri uri, object data, string accepts); + Task Post(Uri uri, object data, string accepts, CancellationToken cancellationToken = default); /// /// Creates a new API resource in the list at the specified URI. @@ -190,9 +194,10 @@ public interface IApiConnection /// Object that describes the new API resource; this will be serialized and used as the request's body /// Accept header to use for the API request /// Content type of the API request + /// An optional token to monitor for cancellation requests /// The created API resource. /// Thrown when an API error occurs. - Task Post(Uri uri, object data, string accepts, string contentType); + Task Post(Uri uri, object data, string accepts, string contentType, CancellationToken cancellationToken = default); /// /// Creates a new API resource in the list at the specified URI. @@ -203,9 +208,10 @@ public interface IApiConnection /// Accept header to use for the API request /// Content type of the API request /// Two Factor Authentication Code + /// An optional token to monitor for cancellation requests /// The created API resource. /// Thrown when an API error occurs. - Task Post(Uri uri, object data, string accepts, string contentType, string twoFactorAuthenticationCode); + Task Post(Uri uri, object data, string accepts, string contentType, string twoFactorAuthenticationCode, CancellationToken cancellationToken = default); /// /// Creates a new API resource in the list at the specified URI. @@ -216,9 +222,10 @@ public interface IApiConnection /// Accept header to use for the API request /// Content type of the API request /// Timeout for the request + /// An optional token to monitor for cancellation requests /// The created API resource. /// Thrown when an API error occurs. - Task Post(Uri uri, object data, string accepts, string contentType, TimeSpan timeout); + Task Post(Uri uri, object data, string accepts, string contentType, TimeSpan timeout, CancellationToken cancellationToken = default); /// /// Creates or replaces the API resource at the specified URI diff --git a/Octokit/Http/IConnection.cs b/Octokit/Http/IConnection.cs index 920b871d9b..73594c3441 100644 --- a/Octokit/Http/IConnection.cs +++ b/Octokit/Http/IConnection.cs @@ -105,8 +105,9 @@ public interface IConnection : IApiInfoProvider /// Performs an asynchronous HTTP POST request. /// /// URI endpoint to send request to + /// An optional token to monitor for cancellation requests /// The returned - Task Post(Uri uri); + Task Post(Uri uri, CancellationToken cancellationToken = default); /// /// Performs an asynchronous HTTP POST request. @@ -114,8 +115,9 @@ public interface IConnection : IApiInfoProvider /// URI endpoint to send request to /// The object to serialize as the body of the request /// Specifies accepted response media types. + /// An optional token to monitor for cancellation requests /// The returned - Task Post(Uri uri, object body, string accepts); + Task Post(Uri uri, object body, string accepts, CancellationToken cancellationToken = default); /// /// Performs an asynchronous HTTP POST request. @@ -123,8 +125,9 @@ public interface IConnection : IApiInfoProvider /// /// The type to map the response to /// URI endpoint to send request to + /// An optional token to monitor for cancellation requests /// representing the received HTTP response - Task> Post(Uri uri); + Task> Post(Uri uri, CancellationToken cancellationToken = default); /// /// Performs an asynchronous HTTP POST request. @@ -136,8 +139,15 @@ public interface IConnection : IApiInfoProvider /// Specifies accepted response media types. /// Specifies the media type of the request body /// Extra parameters for authentication. + /// An optional token to monitor for cancellation requests /// representing the received HTTP response - Task> Post(Uri uri, object body, string accepts, string contentType, IDictionary parameters = null); + Task> Post( + Uri uri, + object body, + string accepts, + string contentType, + IDictionary parameters = null, + CancellationToken cancellationToken = default); /// /// Performs an asynchronous HTTP POST request. @@ -149,8 +159,9 @@ public interface IConnection : IApiInfoProvider /// Specifies accepted response media types. /// Specifies the media type of the request body /// Two Factor Authentication Code + /// An optional token to monitor for cancellation requests /// representing the received HTTP response - Task> Post(Uri uri, object body, string accepts, string contentType, string twoFactorAuthenticationCode); + Task> Post(Uri uri, object body, string accepts, string contentType, string twoFactorAuthenticationCode, CancellationToken cancellationToken = default); /// /// Performs an asynchronous HTTP POST request. @@ -162,8 +173,9 @@ public interface IConnection : IApiInfoProvider /// Specifies accepted response media types. /// Specifies the media type of the request body /// + /// An optional token to monitor for cancellation requests /// representing the received HTTP response - Task> Post(Uri uri, object body, string accepts, string contentType, TimeSpan timeout); + Task> Post(Uri uri, object body, string accepts, string contentType, TimeSpan timeout, CancellationToken cancellationToken = default); /// @@ -180,8 +192,9 @@ public interface IConnection : IApiInfoProvider /// Specifies accepted response media types. /// Specifies the media type of the request body /// Allows overriding the base address for a post. + /// An optional token to monitor for cancellation requests /// representing the received HTTP response - Task> Post(Uri uri, object body, string accepts, string contentType, Uri baseAddress); + Task> Post(Uri uri, object body, string accepts, string contentType, Uri baseAddress, CancellationToken cancellationToken = default); /// /// Performs an asynchronous HTTP PUT request.