Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 47 additions & 24 deletions CloudConvert.API/CloudConvertAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,30 @@
using CloudConvert.API.Models.JobModels;
using CloudConvert.API.Models.TaskModels;
using CloudConvert.API.Models;
using System.Threading;

namespace CloudConvert.API
{
public interface ICloudConvertAPI
{
#region Jobs
Task<ListResponse<JobResponse>> GetAllJobsAsync(JobListFilter jobFilter);
Task<Response<JobResponse>> CreateJobAsync(JobCreateRequest request);
Task<Response<JobResponse>> GetJobAsync(string id);
Task<Response<JobResponse>> WaitJobAsync(string id);
Task DeleteJobAsync(string id);
Task<ListResponse<JobResponse>> GetAllJobsAsync(JobListFilter jobFilter, CancellationToken cancellationToken = default);
Task<Response<JobResponse>> CreateJobAsync(JobCreateRequest request, CancellationToken cancellationToken = default);
Task<Response<JobResponse>> GetJobAsync(string id, CancellationToken cancellationToken = default);
Task<Response<JobResponse>> WaitJobAsync(string id, CancellationToken cancellationToken = default);
Task DeleteJobAsync(string id, CancellationToken cancellationToken = default);
#endregion

#region Tasks
Task<ListResponse<TaskResponse>> GetAllTasksAsync(TaskListFilter jobFilter);
Task<Response<TaskResponse>> CreateTaskAsync<T>(string operation, T request);
Task<Response<TaskResponse>> GetTaskAsync(string id, string include = null);
Task<Response<TaskResponse>> WaitTaskAsync(string id);
Task DeleteTaskAsync(string id);
Task<ListResponse<TaskResponse>> GetAllTasksAsync(TaskListFilter jobFilter, CancellationToken cancellationToken = default);
Task<Response<TaskResponse>> CreateTaskAsync<T>(string operation, T request, CancellationToken cancellationToken = default);
Task<Response<TaskResponse>> GetTaskAsync(string id, string include = null, CancellationToken cancellationToken = default);
Task<Response<TaskResponse>> WaitTaskAsync(string id, CancellationToken cancellationToken = default);
Task DeleteTaskAsync(string id, CancellationToken cancellationToken = default);
#endregion

Task<string> UploadAsync(string url, byte[] file, string fileName, object parameters);
Task<string> UploadAsync(string url, Stream file, string fileName, object parameters);
Task<string> UploadAsync(string url, byte[] file, string fileName, object parameters, CancellationToken cancellationToken = default);
Task<string> UploadAsync(string url, Stream file, string fileName, object parameters, CancellationToken cancellationToken = default);
bool ValidateWebhookSignatures(string payloadString, string signature, string signingSecret);
string CreateSignedUrl(string baseUrl, string signingSecret, JobCreateRequest job, string cacheKey = null);
}
Expand Down Expand Up @@ -114,26 +115,32 @@ private HttpRequestMessage GetMultipartFormDataRequest(string endpoint, HttpMeth
/// List all jobs. Requires the task.read scope.
/// </summary>
/// <param name="jobFilter"></param>
/// <param name="cancellationToken"></param>
/// <returns>
/// The list of jobs. You can find details about the job model response in the documentation about the show jobs endpoint.
/// </returns>
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));
public Task<ListResponse<JobResponse>> GetAllJobsAsync(JobListFilter jobFilter, CancellationToken cancellationToken = default)
=> _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);

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

/// <summary>
/// Show a job. Requires the task.read scope.
/// </summary>
/// <param name="id"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task<Response<JobResponse>> GetJobAsync(string id) => _restHelper.RequestAsync<Response<JobResponse>>(GetRequest($"{_apiUrl}/jobs/{id}", HttpMethod.Get));
public Task<Response<JobResponse>> GetJobAsync(string id, CancellationToken cancellationToken = default)
=> _restHelper.RequestAsync<Response<JobResponse>>(GetRequest($"{_apiUrl}/jobs/{id}", HttpMethod.Get), cancellationToken);

/// <summary>
/// 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.
Expand All @@ -145,20 +152,24 @@ private HttpRequestMessage GetMultipartFormDataRequest(string endpoint, HttpMeth
/// Using an asynchronous approach with webhooks is beneficial in such cases.
/// </summary>
/// <param name="id"></param>
/// <param name="cancellationToken"></param>
/// <returns>
/// The finished or failed job, including tasks. You can find details about the job model response in the documentation about the show job endpoint.
/// </returns>
public Task<Response<JobResponse>> WaitJobAsync(string id) => _restHelper.RequestAsync<Response<JobResponse>>(GetRequest($"{_apiSyncUrl}/jobs/{id}", HttpMethod.Get));
public Task<Response<JobResponse>> WaitJobAsync(string id, CancellationToken cancellationToken = default)
=> _restHelper.RequestAsync<Response<JobResponse>>(GetRequest($"{_apiSyncUrl}/jobs/{id}", HttpMethod.Get), cancellationToken);

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

#endregion

Expand All @@ -168,28 +179,34 @@ private HttpRequestMessage GetMultipartFormDataRequest(string endpoint, HttpMeth
/// List all tasks with their status, payload and result. Requires the task.read scope.
/// </summary>
/// <param name="taskFilter"></param>
/// <param name="cancellationToken"></param>
/// <returns>
/// The list of tasks. You can find details about the task model response in the documentation about the show tasks endpoint.
/// </returns>
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));
public Task<ListResponse<TaskResponse>> GetAllTasksAsync(TaskListFilter taskFilter, CancellationToken cancellationToken = default)
=> _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);

/// <summary>
/// Create task.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="model"></param>
/// <param name="cancellationToken"></param>
/// <returns>
/// The created task. You can find details about the task model response in the documentation about the show tasks endpoint.
/// </returns>
public Task<Response<TaskResponse>> CreateTaskAsync<T>(string operation, T model) => _restHelper.RequestAsync<Response<TaskResponse>>(GetRequest($"{_apiUrl}/{operation}", HttpMethod.Post, model));
public Task<Response<TaskResponse>> CreateTaskAsync<T>(string operation, T model, CancellationToken cancellationToken = default)
=> _restHelper.RequestAsync<Response<TaskResponse>>(GetRequest($"{_apiUrl}/{operation}", HttpMethod.Post, model), cancellationToken);

/// <summary>
/// Show a task. Requires the task.read scope.
/// </summary>
/// <param name="id"></param>
/// <param name="include"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task<Response<TaskResponse>> GetTaskAsync(string id, string include = null) => _restHelper.RequestAsync<Response<TaskResponse>>(GetRequest($"{_apiUrl}/tasks/{id}?include={include}", HttpMethod.Get));
public Task<Response<TaskResponse>> GetTaskAsync(string id, string include = null, CancellationToken cancellationToken = default)
=> _restHelper.RequestAsync<Response<TaskResponse>>(GetRequest($"{_apiUrl}/tasks/{id}?include={include}", HttpMethod.Get), cancellationToken);

/// <summary>
/// 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.
Expand All @@ -201,26 +218,32 @@ private HttpRequestMessage GetMultipartFormDataRequest(string endpoint, HttpMeth
/// Using an asynchronous approach with webhooks is beneficial in such cases.
/// </summary>
/// <param name="id"></param>
/// <param name="cancellationToken"></param>
/// <returns>
/// The finished or failed task. You can find details about the task model response in the documentation about the show tasks endpoint.
/// </returns>
public Task<Response<TaskResponse>> WaitTaskAsync(string id) => _restHelper.RequestAsync<Response<TaskResponse>>(GetRequest($"{_apiSyncUrl}/tasks/{id}", HttpMethod.Get));
public Task<Response<TaskResponse>> WaitTaskAsync(string id, CancellationToken cancellationToken = default)
=> _restHelper.RequestAsync<Response<TaskResponse>>(GetRequest($"{_apiSyncUrl}/tasks/{id}", HttpMethod.Get), cancellationToken);

/// <summary>
/// Delete a task, including all data. Requires the task.write scope.
/// Tasks are deleted automatically 24 hours after they have ended.
/// </summary>
/// <param name="id"></param>
/// <param name="cancellationToken"></param>
/// <returns>
/// An empty response with HTTP Code 204.
/// </returns>
public Task DeleteTaskAsync(string id) => _restHelper.RequestAsync<object>(GetRequest($"{_apiUrl}/tasks/{id}", HttpMethod.Delete));
public Task DeleteTaskAsync(string id, CancellationToken cancellationToken = default)
=> _restHelper.RequestAsync<object>(GetRequest($"{_apiUrl}/tasks/{id}", HttpMethod.Delete), cancellationToken);

#endregion

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

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

public string CreateSignedUrl(string baseUrl, string signingSecret, JobCreateRequest job, string cacheKey = null)
{
Expand Down
13 changes: 7 additions & 6 deletions CloudConvert.API/RestHelper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace CloudConvert.API
Expand All @@ -19,18 +20,18 @@ internal RestHelper(HttpClient httpClient)
_httpClient = httpClient;
}

public async Task<T> RequestAsync<T>(HttpRequestMessage request)
public async Task<T> RequestAsync<T>(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await _httpClient.SendAsync(request);
var responseRaw = await response.Content.ReadAsStringAsync();
var response = await _httpClient.SendAsync(request, cancellationToken);
var responseRaw = await response.Content.ReadAsStringAsync(cancellationToken);

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

public async Task<string> RequestAsync(HttpRequestMessage request)
public async Task<string> RequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await _httpClient.SendAsync(request);
return await response.Content.ReadAsStringAsync();
var response = await _httpClient.SendAsync(request, cancellationToken);
return await response.Content.ReadAsStringAsync(cancellationToken);
}
}
}
6 changes: 3 additions & 3 deletions CloudConvert.API/WebApiHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,19 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
{
if (writeLog)
{
var requestString = request.Content != null ? await request.Content.ReadAsStringAsync() : string.Empty;
var requestString = request.Content != null ? await request.Content.ReadAsStringAsync(cancellationToken) : string.Empty;
}

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

if (writeLog)
{
string responseString = (await response.Content.ReadAsStringAsync()).TrimLengthWithEllipsis(20000);
string responseString = (await response.Content.ReadAsStringAsync(cancellationToken)).TrimLengthWithEllipsis(20000);
}

if ((int)response.StatusCode >= 400)
{
throw new WebApiException((await response.Content.ReadAsStringAsync()).TrimLengthWithEllipsis(20000));
throw new WebApiException((await response.Content.ReadAsStringAsync(cancellationToken)).TrimLengthWithEllipsis(20000));
}

return response;
Expand Down
23 changes: 23 additions & 0 deletions CloudConvert.Test/IntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Collections.Generic;
using System.Dynamic;
using System.Text.Json;
using System.Threading;

namespace CloudConvert.Test
{
Expand All @@ -27,6 +28,28 @@ public void Setup()
_cloudConvertAPI = new CloudConvertAPI(apiKey, true);
}

[TestCase("cancellationtoken")]
public async Task CancellationToken(string streamingMethod)
{
using var cts = new CancellationTokenSource();
var token = cts.Token;

cts.Cancel();

try
{
await _cloudConvertAPI.CreateJobAsync(new JobCreateRequest
{
Tasks = new()
}, token).ConfigureAwait(false);

Assert.Fail("Expected TaskCanceledException");
}
catch (WebApiException ex) when (ex.InnerException is TaskCanceledException)
{
}
}

[TestCase("stream")]
[TestCase("bytes")]
public async Task CreateJob(string streamingMethod)
Expand Down
11 changes: 5 additions & 6 deletions CloudConvert.Test/TestJobs.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using CloudConvert.API;
using CloudConvert.API.Models;
Expand All @@ -24,7 +23,7 @@ public async Task GetAllJobs()

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

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

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

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

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

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

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

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

_cloudConvertAPI.Setup(cc => cc.DeleteJobAsync(id));
_cloudConvertAPI.Setup(cc => cc.DeleteJobAsync(id, default));

await _cloudConvertAPI.Object.DeleteJobAsync(id);
}
Expand Down
1 change: 0 additions & 1 deletion CloudConvert.Test/TestSignedUrl.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

using CloudConvert.API;
using CloudConvert.API.Models.JobModels;
using CloudConvert.API.Models.TaskOperations;
Expand Down
Loading