Skip to content

Commit 98791d7

Browse files
authored
Merge pull request #25 from Combeenation/cancellation-token
Add CancellationToken support
2 parents a976c31 + d4e4fa3 commit 98791d7

File tree

7 files changed

+92
-47
lines changed

7 files changed

+92
-47
lines changed

CloudConvert.API/CloudConvertAPI.cs

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,30 @@
1010
using CloudConvert.API.Models.JobModels;
1111
using CloudConvert.API.Models.TaskModels;
1212
using CloudConvert.API.Models;
13+
using System.Threading;
1314

1415
namespace CloudConvert.API
1516
{
1617
public interface ICloudConvertAPI
1718
{
1819
#region Jobs
19-
Task<ListResponse<JobResponse>> GetAllJobsAsync(JobListFilter jobFilter);
20-
Task<Response<JobResponse>> CreateJobAsync(JobCreateRequest request);
21-
Task<Response<JobResponse>> GetJobAsync(string id);
22-
Task<Response<JobResponse>> WaitJobAsync(string id);
23-
Task DeleteJobAsync(string id);
20+
Task<ListResponse<JobResponse>> GetAllJobsAsync(JobListFilter jobFilter, CancellationToken cancellationToken = default);
21+
Task<Response<JobResponse>> CreateJobAsync(JobCreateRequest request, CancellationToken cancellationToken = default);
22+
Task<Response<JobResponse>> GetJobAsync(string id, CancellationToken cancellationToken = default);
23+
Task<Response<JobResponse>> WaitJobAsync(string id, CancellationToken cancellationToken = default);
24+
Task DeleteJobAsync(string id, CancellationToken cancellationToken = default);
2425
#endregion
2526

2627
#region Tasks
27-
Task<ListResponse<TaskResponse>> GetAllTasksAsync(TaskListFilter jobFilter);
28-
Task<Response<TaskResponse>> CreateTaskAsync<T>(string operation, T request);
29-
Task<Response<TaskResponse>> GetTaskAsync(string id, string include = null);
30-
Task<Response<TaskResponse>> WaitTaskAsync(string id);
31-
Task DeleteTaskAsync(string id);
28+
Task<ListResponse<TaskResponse>> GetAllTasksAsync(TaskListFilter jobFilter, CancellationToken cancellationToken = default);
29+
Task<Response<TaskResponse>> CreateTaskAsync<T>(string operation, T request, CancellationToken cancellationToken = default);
30+
Task<Response<TaskResponse>> GetTaskAsync(string id, string include = null, CancellationToken cancellationToken = default);
31+
Task<Response<TaskResponse>> WaitTaskAsync(string id, CancellationToken cancellationToken = default);
32+
Task DeleteTaskAsync(string id, CancellationToken cancellationToken = default);
3233
#endregion
3334

34-
Task<string> UploadAsync(string url, byte[] file, string fileName, object parameters);
35-
Task<string> UploadAsync(string url, Stream file, string fileName, object parameters);
35+
Task<string> UploadAsync(string url, byte[] file, string fileName, object parameters, CancellationToken cancellationToken = default);
36+
Task<string> UploadAsync(string url, Stream file, string fileName, object parameters, CancellationToken cancellationToken = default);
3637
bool ValidateWebhookSignatures(string payloadString, string signature, string signingSecret);
3738
string CreateSignedUrl(string baseUrl, string signingSecret, JobCreateRequest job, string cacheKey = null);
3839
}
@@ -114,26 +115,32 @@ private HttpRequestMessage GetMultipartFormDataRequest(string endpoint, HttpMeth
114115
/// List all jobs. Requires the task.read scope.
115116
/// </summary>
116117
/// <param name="jobFilter"></param>
118+
/// <param name="cancellationToken"></param>
117119
/// <returns>
118120
/// The list of jobs. You can find details about the job model response in the documentation about the show jobs endpoint.
119121
/// </returns>
120-
public Task<ListResponse<JobResponse>> GetAllJobsAsync(JobListFilter jobFilter) => _restHelper.RequestAsync<ListResponse<JobResponse>>(GetRequest($"{_apiUrl}/jobs?filter[status]={jobFilter.Status}&filter[tag]={jobFilter.Tag}&include={jobFilter.Include}&per_page={jobFilter.PerPage}&page={jobFilter.Page}", HttpMethod.Get));
122+
public Task<ListResponse<JobResponse>> GetAllJobsAsync(JobListFilter jobFilter, CancellationToken cancellationToken = default)
123+
=> _restHelper.RequestAsync<ListResponse<JobResponse>>(GetRequest($"{_apiUrl}/jobs?filter[status]={jobFilter.Status}&filter[tag]={jobFilter.Tag}&include={jobFilter.Include}&per_page={jobFilter.PerPage}&page={jobFilter.Page}", HttpMethod.Get), cancellationToken);
121124

122125
/// <summary>
123126
/// Create a job with one ore more tasks. Requires the task.write scope.
124127
/// </summary>
125128
/// <param name="model"></param>
129+
/// <param name="cancellationToken"></param>
126130
/// <returns>
127131
/// The created job. You can find details about the job model response in the documentation about the show jobs endpoint.
128132
/// </returns>
129-
public Task<Response<JobResponse>> CreateJobAsync(JobCreateRequest model) => _restHelper.RequestAsync<Response<JobResponse>>(GetRequest($"{_apiUrl}/jobs", HttpMethod.Post, model));
133+
public Task<Response<JobResponse>> CreateJobAsync(JobCreateRequest model, CancellationToken cancellationToken = default)
134+
=> _restHelper.RequestAsync<Response<JobResponse>>(GetRequest($"{_apiUrl}/jobs", HttpMethod.Post, model), cancellationToken);
130135

131136
/// <summary>
132137
/// Show a job. Requires the task.read scope.
133138
/// </summary>
134139
/// <param name="id"></param>
140+
/// <param name="cancellationToken"></param>
135141
/// <returns></returns>
136-
public Task<Response<JobResponse>> GetJobAsync(string id) => _restHelper.RequestAsync<Response<JobResponse>>(GetRequest($"{_apiUrl}/jobs/{id}", HttpMethod.Get));
142+
public Task<Response<JobResponse>> GetJobAsync(string id, CancellationToken cancellationToken = default)
143+
=> _restHelper.RequestAsync<Response<JobResponse>>(GetRequest($"{_apiUrl}/jobs/{id}", HttpMethod.Get), cancellationToken);
137144

138145
/// <summary>
139146
/// Wait until the job status is finished or error. This makes the request block until the job has been completed. Requires the task.read scope.
@@ -145,20 +152,24 @@ private HttpRequestMessage GetMultipartFormDataRequest(string endpoint, HttpMeth
145152
/// Using an asynchronous approach with webhooks is beneficial in such cases.
146153
/// </summary>
147154
/// <param name="id"></param>
155+
/// <param name="cancellationToken"></param>
148156
/// <returns>
149157
/// The finished or failed job, including tasks. You can find details about the job model response in the documentation about the show job endpoint.
150158
/// </returns>
151-
public Task<Response<JobResponse>> WaitJobAsync(string id) => _restHelper.RequestAsync<Response<JobResponse>>(GetRequest($"{_apiSyncUrl}/jobs/{id}", HttpMethod.Get));
159+
public Task<Response<JobResponse>> WaitJobAsync(string id, CancellationToken cancellationToken = default)
160+
=> _restHelper.RequestAsync<Response<JobResponse>>(GetRequest($"{_apiSyncUrl}/jobs/{id}", HttpMethod.Get), cancellationToken);
152161

153162
/// <summary>
154163
/// Delete a job, including all tasks and data. Requires the task.write scope.
155164
/// Jobs are deleted automatically 24 hours after they have ended.
156165
/// </summary>
157166
/// <param name="id"></param>
167+
/// <param name="cancellationToken"></param>
158168
/// <returns>
159169
/// An empty response with HTTP Code 204.
160170
/// </returns>
161-
public Task DeleteJobAsync(string id) => _restHelper.RequestAsync<object>(GetRequest($"{_apiUrl}/jobs/{id}", HttpMethod.Delete));
171+
public Task DeleteJobAsync(string id, CancellationToken cancellationToken = default)
172+
=> _restHelper.RequestAsync<object>(GetRequest($"{_apiUrl}/jobs/{id}", HttpMethod.Delete), cancellationToken);
162173

163174
#endregion
164175

@@ -168,28 +179,34 @@ private HttpRequestMessage GetMultipartFormDataRequest(string endpoint, HttpMeth
168179
/// List all tasks with their status, payload and result. Requires the task.read scope.
169180
/// </summary>
170181
/// <param name="taskFilter"></param>
182+
/// <param name="cancellationToken"></param>
171183
/// <returns>
172184
/// The list of tasks. You can find details about the task model response in the documentation about the show tasks endpoint.
173185
/// </returns>
174-
public Task<ListResponse<TaskResponse>> GetAllTasksAsync(TaskListFilter taskFilter) => _restHelper.RequestAsync<ListResponse<TaskResponse>>(GetRequest($"{_apiUrl}/tasks?filter[job_id]={taskFilter.JobId}&filter[status]={taskFilter.Status}&filter[operation]={taskFilter.Operation}&include={taskFilter.Include}&per_page={taskFilter.PerPage}&page={taskFilter.Page}", HttpMethod.Get));
186+
public Task<ListResponse<TaskResponse>> GetAllTasksAsync(TaskListFilter taskFilter, CancellationToken cancellationToken = default)
187+
=> _restHelper.RequestAsync<ListResponse<TaskResponse>>(GetRequest($"{_apiUrl}/tasks?filter[job_id]={taskFilter.JobId}&filter[status]={taskFilter.Status}&filter[operation]={taskFilter.Operation}&include={taskFilter.Include}&per_page={taskFilter.PerPage}&page={taskFilter.Page}", HttpMethod.Get), cancellationToken);
175188

176189
/// <summary>
177190
/// Create task.
178191
/// </summary>
179192
/// <typeparam name="T"></typeparam>
180193
/// <param name="model"></param>
194+
/// <param name="cancellationToken"></param>
181195
/// <returns>
182196
/// The created task. You can find details about the task model response in the documentation about the show tasks endpoint.
183197
/// </returns>
184-
public Task<Response<TaskResponse>> CreateTaskAsync<T>(string operation, T model) => _restHelper.RequestAsync<Response<TaskResponse>>(GetRequest($"{_apiUrl}/{operation}", HttpMethod.Post, model));
198+
public Task<Response<TaskResponse>> CreateTaskAsync<T>(string operation, T model, CancellationToken cancellationToken = default)
199+
=> _restHelper.RequestAsync<Response<TaskResponse>>(GetRequest($"{_apiUrl}/{operation}", HttpMethod.Post, model), cancellationToken);
185200

186201
/// <summary>
187202
/// Show a task. Requires the task.read scope.
188203
/// </summary>
189204
/// <param name="id"></param>
190205
/// <param name="include"></param>
206+
/// <param name="cancellationToken"></param>
191207
/// <returns></returns>
192-
public Task<Response<TaskResponse>> GetTaskAsync(string id, string include = null) => _restHelper.RequestAsync<Response<TaskResponse>>(GetRequest($"{_apiUrl}/tasks/{id}?include={include}", HttpMethod.Get));
208+
public Task<Response<TaskResponse>> GetTaskAsync(string id, string include = null, CancellationToken cancellationToken = default)
209+
=> _restHelper.RequestAsync<Response<TaskResponse>>(GetRequest($"{_apiUrl}/tasks/{id}?include={include}", HttpMethod.Get), cancellationToken);
193210

194211
/// <summary>
195212
/// Wait until the task status is finished or error. This makes the request block until the task has been completed. Requires the task.read scope.
@@ -201,26 +218,32 @@ private HttpRequestMessage GetMultipartFormDataRequest(string endpoint, HttpMeth
201218
/// Using an asynchronous approach with webhooks is beneficial in such cases.
202219
/// </summary>
203220
/// <param name="id"></param>
221+
/// <param name="cancellationToken"></param>
204222
/// <returns>
205223
/// The finished or failed task. You can find details about the task model response in the documentation about the show tasks endpoint.
206224
/// </returns>
207-
public Task<Response<TaskResponse>> WaitTaskAsync(string id) => _restHelper.RequestAsync<Response<TaskResponse>>(GetRequest($"{_apiSyncUrl}/tasks/{id}", HttpMethod.Get));
225+
public Task<Response<TaskResponse>> WaitTaskAsync(string id, CancellationToken cancellationToken = default)
226+
=> _restHelper.RequestAsync<Response<TaskResponse>>(GetRequest($"{_apiSyncUrl}/tasks/{id}", HttpMethod.Get), cancellationToken);
208227

209228
/// <summary>
210229
/// Delete a task, including all data. Requires the task.write scope.
211230
/// Tasks are deleted automatically 24 hours after they have ended.
212231
/// </summary>
213232
/// <param name="id"></param>
233+
/// <param name="cancellationToken"></param>
214234
/// <returns>
215235
/// An empty response with HTTP Code 204.
216236
/// </returns>
217-
public Task DeleteTaskAsync(string id) => _restHelper.RequestAsync<object>(GetRequest($"{_apiUrl}/tasks/{id}", HttpMethod.Delete));
237+
public Task DeleteTaskAsync(string id, CancellationToken cancellationToken = default)
238+
=> _restHelper.RequestAsync<object>(GetRequest($"{_apiUrl}/tasks/{id}", HttpMethod.Delete), cancellationToken);
218239

219240
#endregion
220241

221-
public Task<string> UploadAsync(string url, byte[] file, string fileName, object parameters) => _restHelper.RequestAsync(GetMultipartFormDataRequest(url, HttpMethod.Post, new ByteArrayContent(file), fileName, GetParameters(parameters)));
242+
public Task<string> UploadAsync(string url, byte[] file, string fileName, object parameters, CancellationToken cancellationToken)
243+
=> _restHelper.RequestAsync(GetMultipartFormDataRequest(url, HttpMethod.Post, new ByteArrayContent(file), fileName, GetParameters(parameters)), cancellationToken);
222244

223-
public Task<string> UploadAsync(string url, Stream stream, string fileName, object parameters) => _restHelper.RequestAsync(GetMultipartFormDataRequest(url, HttpMethod.Post, new StreamContent(stream), fileName, GetParameters(parameters)));
245+
public Task<string> UploadAsync(string url, Stream stream, string fileName, object parameters, CancellationToken cancellationToken = default)
246+
=> _restHelper.RequestAsync(GetMultipartFormDataRequest(url, HttpMethod.Post, new StreamContent(stream), fileName, GetParameters(parameters)), cancellationToken);
224247

225248
public string CreateSignedUrl(string baseUrl, string signingSecret, JobCreateRequest job, string cacheKey = null)
226249
{

CloudConvert.API/RestHelper.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Net.Http;
22
using System.Text.Json;
3+
using System.Threading;
34
using System.Threading.Tasks;
45

56
namespace CloudConvert.API
@@ -19,18 +20,18 @@ internal RestHelper(HttpClient httpClient)
1920
_httpClient = httpClient;
2021
}
2122

22-
public async Task<T> RequestAsync<T>(HttpRequestMessage request)
23+
public async Task<T> RequestAsync<T>(HttpRequestMessage request, CancellationToken cancellationToken)
2324
{
24-
var response = await _httpClient.SendAsync(request);
25-
var responseRaw = await response.Content.ReadAsStringAsync();
25+
var response = await _httpClient.SendAsync(request, cancellationToken);
26+
var responseRaw = await response.Content.ReadAsStringAsync(cancellationToken);
2627

2728
return JsonSerializer.Deserialize<T>(responseRaw, DefaultJsonSerializerOptions.SerializerOptions);
2829
}
2930

30-
public async Task<string> RequestAsync(HttpRequestMessage request)
31+
public async Task<string> RequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
3132
{
32-
var response = await _httpClient.SendAsync(request);
33-
return await response.Content.ReadAsStringAsync();
33+
var response = await _httpClient.SendAsync(request, cancellationToken);
34+
return await response.Content.ReadAsStringAsync(cancellationToken);
3435
}
3536
}
3637
}

CloudConvert.API/WebApiHandler.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,19 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
2525
{
2626
if (writeLog)
2727
{
28-
var requestString = request.Content != null ? await request.Content.ReadAsStringAsync() : string.Empty;
28+
var requestString = request.Content != null ? await request.Content.ReadAsStringAsync(cancellationToken) : string.Empty;
2929
}
3030

3131
var response = await base.SendAsync(request, cancellationToken);
3232

3333
if (writeLog)
3434
{
35-
string responseString = (await response.Content.ReadAsStringAsync()).TrimLengthWithEllipsis(20000);
35+
string responseString = (await response.Content.ReadAsStringAsync(cancellationToken)).TrimLengthWithEllipsis(20000);
3636
}
3737

3838
if ((int)response.StatusCode >= 400)
3939
{
40-
throw new WebApiException((await response.Content.ReadAsStringAsync()).TrimLengthWithEllipsis(20000));
40+
throw new WebApiException((await response.Content.ReadAsStringAsync(cancellationToken)).TrimLengthWithEllipsis(20000));
4141
}
4242

4343
return response;

CloudConvert.Test/IntegrationTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Collections.Generic;
1313
using System.Dynamic;
1414
using System.Text.Json;
15+
using System.Threading;
1516

1617
namespace CloudConvert.Test
1718
{
@@ -27,6 +28,28 @@ public void Setup()
2728
_cloudConvertAPI = new CloudConvertAPI(apiKey, true);
2829
}
2930

31+
[TestCase("cancellationtoken")]
32+
public async Task CancellationToken(string streamingMethod)
33+
{
34+
using var cts = new CancellationTokenSource();
35+
var token = cts.Token;
36+
37+
cts.Cancel();
38+
39+
try
40+
{
41+
await _cloudConvertAPI.CreateJobAsync(new JobCreateRequest
42+
{
43+
Tasks = new()
44+
}, token).ConfigureAwait(false);
45+
46+
Assert.Fail("Expected TaskCanceledException");
47+
}
48+
catch (WebApiException ex) when (ex.InnerException is TaskCanceledException)
49+
{
50+
}
51+
}
52+
3053
[TestCase("stream")]
3154
[TestCase("bytes")]
3255
public async Task CreateJob(string streamingMethod)

CloudConvert.Test/TestJobs.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.IO;
33
using System.Text.Json;
4-
using System.Text.Json.Serialization;
54
using System.Threading.Tasks;
65
using CloudConvert.API;
76
using CloudConvert.API.Models;
@@ -24,7 +23,7 @@ public async Task GetAllJobs()
2423

2524
var path = @"Responses/jobs.json";
2625
string json = File.ReadAllText(path);
27-
_cloudConvertAPI.Setup(cc => cc.GetAllJobsAsync(filter))
26+
_cloudConvertAPI.Setup(cc => cc.GetAllJobsAsync(filter, default))
2827
.ReturnsAsync(JsonSerializer.Deserialize<ListResponse<JobResponse>>(json, DefaultJsonSerializerOptions.SerializerOptions));
2928

3029
var jobs = await _cloudConvertAPI.Object.GetAllJobsAsync(filter);
@@ -56,7 +55,7 @@ public async Task CreateJob()
5655

5756
var path = AppDomain.CurrentDomain.BaseDirectory + @"Responses/job_created.json";
5857
string json = File.ReadAllText(path);
59-
_cloudConvertAPI.Setup(cc => cc.CreateJobAsync(req))
58+
_cloudConvertAPI.Setup(cc => cc.CreateJobAsync(req, default))
6059
.ReturnsAsync(JsonSerializer.Deserialize<Response<JobResponse>>(json, DefaultJsonSerializerOptions.SerializerOptions));
6160

6261
var job = await _cloudConvertAPI.Object.CreateJobAsync(req);
@@ -73,7 +72,7 @@ public async Task GetJob()
7372

7473
var path = AppDomain.CurrentDomain.BaseDirectory + @"Responses/job.json";
7574
string json = File.ReadAllText(path);
76-
_cloudConvertAPI.Setup(cc => cc.GetJobAsync(id))
75+
_cloudConvertAPI.Setup(cc => cc.GetJobAsync(id, default))
7776
.ReturnsAsync(JsonSerializer.Deserialize<Response<JobResponse>>(json, DefaultJsonSerializerOptions.SerializerOptions));
7877

7978
var job = await _cloudConvertAPI.Object.GetJobAsync(id);
@@ -90,7 +89,7 @@ public async Task WaitJob()
9089

9190
var path = AppDomain.CurrentDomain.BaseDirectory + @"Responses/job_finished.json";
9291
string json = File.ReadAllText(path);
93-
_cloudConvertAPI.Setup(cc => cc.WaitJobAsync(id))
92+
_cloudConvertAPI.Setup(cc => cc.WaitJobAsync(id, default))
9493
.ReturnsAsync(JsonSerializer.Deserialize<Response<JobResponse>>(json, DefaultJsonSerializerOptions.SerializerOptions));
9594

9695
var job = await _cloudConvertAPI.Object.WaitJobAsync(id);
@@ -105,7 +104,7 @@ public async Task DeleteJob()
105104
{
106105
string id = "cd82535b-0614-4b23-bbba-b24ab0e892f7";
107106

108-
_cloudConvertAPI.Setup(cc => cc.DeleteJobAsync(id));
107+
_cloudConvertAPI.Setup(cc => cc.DeleteJobAsync(id, default));
109108

110109
await _cloudConvertAPI.Object.DeleteJobAsync(id);
111110
}

CloudConvert.Test/TestSignedUrl.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
using CloudConvert.API;
32
using CloudConvert.API.Models.JobModels;
43
using CloudConvert.API.Models.TaskOperations;

0 commit comments

Comments
 (0)