LANCommander/LANCommander.SDK/Helpers/ApiRequestBuilder.cs
Aaron Powell d0f6782d3e Adding a file name when uploading
The file name is ignored by the server, but if you don't add one, you can't attach a file to the form
2025-12-04 13:35:27 +11:00

339 lines
No EOL
9.7 KiB
C#

using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using LANCommander.SDK.Abstractions;
using LANCommander.SDK.Extensions;
using LANCommander.SDK.Models;
using Action = System.Action;
namespace LANCommander.SDK.Helpers;
public class ApiRequestBuilder(
HttpClient httpClient,
ITokenProvider tokenProvider,
ISettingsProvider settingsProvider)
{
private AuthToken _token { get; set; } = tokenProvider.GetToken();
private bool _ignoreVersion { get; set; }
private object _body { get; set; }
private string _route { get; set; }
private HttpClient _httpClient { get; set; } = httpClient;
private HttpRequestMessage _request { get; set; } = new();
private CancellationToken _cancellationToken { get; set; } = CancellationToken.None;
private Action<DownloadProgressChangedEventArgs> _progressHandler { get; set; }
private Action _completeHandler { get; set; }
private Uri _baseAddress { get; set; } = settingsProvider.CurrentValue.Authentication.ServerAddress;
private async ValueTask<TResult> DeserializeResultAsync<TResult>(HttpResponseMessage response)
{
var body = await response.Content.ReadAsStringAsync(_cancellationToken);
try
{
return JsonSerializer.Deserialize<TResult>(body,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
}
catch
{
return default;
}
}
public ApiRequestBuilder UseAuthenticationToken()
{
_request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
return this;
}
public ApiRequestBuilder UseRoute(string route)
{
_request.RequestUri = _baseAddress.Join(route);
return this;
}
public ApiRequestBuilder UseCancellationToken(CancellationToken cancellationToken)
{
_cancellationToken = cancellationToken;
return this;
}
public ApiRequestBuilder UseVersioning()
{
_request.Headers.Add("X-API-Version", VersionHelper.GetCurrentVersion().ToString());
// _request.Interceptors = new List<Interceptor>() { new VersionInterceptor() };
return this;
}
public ApiRequestBuilder UseMethod(HttpMethod method)
{
_request.Method = method;
return this;
}
public ApiRequestBuilder UseBaseAddress(Uri baseAddress)
{
_baseAddress = baseAddress;
return this;
}
public ApiRequestBuilder SetTimeout(TimeSpan timeout)
{
_httpClient.Timeout = timeout;
return this;
}
public ApiRequestBuilder AddBody(object body)
{
_request.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, MediaTypeNames.Application.Json);
return this;
}
public ApiRequestBuilder AddHeader(string key, string value)
{
_request.Headers.Add(key, value);
return this;
}
public ApiRequestBuilder OnProgress(Action<DownloadProgressChangedEventArgs> progressHandler)
{
_progressHandler = progressHandler;
return this;
}
public ApiRequestBuilder OnComplete(Action completeHandler)
{
_completeHandler = completeHandler;
return this;
}
public async Task<ApiResponseMessage<TResult>> SendAsync<TResult>() where TResult : class
{
var response = await _httpClient.SendAsync(_request, _cancellationToken);
var result = new ApiResponseMessage<TResult>
{
Response = response,
Data = await DeserializeResultAsync<TResult>(response)
};
return result;
}
public async Task<TResult> GetAsync<TResult>()
{
_request.Method = HttpMethod.Get;
var response = await _httpClient.SendAsync(_request, _cancellationToken);
response
.EnsureSuccessStatusCode();
return await DeserializeResultAsync<TResult>(response);
}
public async Task<TResult> PostAsync<TResult>()
{
_request.Method = HttpMethod.Post;
var response = await _httpClient.SendAsync(_request, _cancellationToken);
response
.EnsureSuccessStatusCode();
return await DeserializeResultAsync<TResult>(response);
}
public async Task PostAsync()
{
_request.Method = HttpMethod.Post;
var response = await _httpClient.SendAsync(_request, _cancellationToken);
response
.EnsureSuccessStatusCode();
}
public async Task<TResult> PutAsync<TResult>()
{
_request.Method = HttpMethod.Put;
var response = await _httpClient.SendAsync(_request, _cancellationToken);
response
.EnsureSuccessStatusCode();
return await DeserializeResultAsync<TResult>(response);
}
public async Task<TResult> DeleteAsync<TResult>()
{
_request.Method = HttpMethod.Delete;
var response = await _httpClient.SendAsync(_request, _cancellationToken);
response
.EnsureSuccessStatusCode();
return await DeserializeResultAsync<TResult>(response);
}
public async Task HeadAsync()
{
_request.Method = HttpMethod.Head;
var response = await _httpClient.SendAsync(_request, _cancellationToken);
response
.EnsureSuccessStatusCode();
}
public async Task<TResult> HeadAsync<TResult>()
{
_request.Method = HttpMethod.Head;
var response = await _httpClient.SendAsync(_request, _cancellationToken);
response
.EnsureSuccessStatusCode();
return await DeserializeResultAsync<TResult>(response);
}
public async Task<FileInfo> DownloadAsync(string destination)
{
_request.Method = HttpMethod.Get;
var response = await _httpClient.SendAsync(_request, _cancellationToken);
var folder = Path.GetDirectoryName(destination);
if (!Directory.Exists(folder))
Directory.CreateDirectory(folder);
await using (var fs = new FileStream(destination, FileMode.Create))
{
var responseStream = await response.Content.ReadAsTrackableStreamAsync(_cancellationToken);
responseStream.OnProgress += (position, length) =>
{
if (_progressHandler != null)
_progressHandler.Invoke(new DownloadProgressChangedEventArgs
{
BytesReceived = position,
TotalBytes = length,
});
};
await responseStream.CopyToAsync(fs, _cancellationToken);
if (_completeHandler != null)
_completeHandler();
}
return new FileInfo(destination);
}
public async Task<TrackableStream> StreamAsync()
{
_request.Method = HttpMethod.Get;
var response = await _httpClient.SendAsync(_request, HttpCompletionOption.ResponseHeadersRead, _cancellationToken);
return await response.Content.ReadAsTrackableStreamAsync(_cancellationToken);
}
public async Task<TResult> UploadAsync<TResult>(string fileName, byte[] data)
{
ArgumentException.ThrowIfNullOrWhiteSpace(fileName);
using (var form = new MultipartFormDataContent())
{
var dataContent = new ByteArrayContent(data);
form.Add(dataContent, "file", fileName);
_request.Content = form;
_request.Method = HttpMethod.Post;
var response = await _httpClient.SendAsync(_request, _cancellationToken);
return await DeserializeResultAsync<TResult>(response);
}
}
public async Task<TResult> UploadAsync<TResult>(string fileName, Stream data)
{
var buffer = new byte[data.Length];
await data.ReadExactlyAsync(buffer, 0, buffer.Length, _cancellationToken);
return await UploadAsync<TResult>(fileName, buffer);
}
public async Task<Guid> UploadInChunksAsync(long chunkSize, Stream data)
{
try
{
var initResponse = await new ApiRequestBuilder(httpClient, tokenProvider, settingsProvider)
.UseRoute("/Upload/Init")
.UseVersioning()
.UseAuthenticationToken()
.UseCancellationToken(_cancellationToken)
.PostAsync<UploadInitResponse>();
var buffer = new byte[chunkSize];
while (data.Position < data.Length)
{
var start = data.Position;
if (data.Position + chunkSize > data.Length)
{
var remainingBytes = data.Length - data.Position;
buffer = new byte[remainingBytes];
}
await data.ReadExactlyAsync(buffer, 0, buffer.Length, _cancellationToken);
var chunkRequest = new UploadChunkRequest
{
Start = start,
End = data.Position,
File = buffer,
Key = initResponse.Key,
};
await new ApiRequestBuilder(httpClient, tokenProvider, settingsProvider)
.AddBody(chunkRequest)
.UseRoute("/Upload/Chunk")
.UseVersioning()
.UseAuthenticationToken()
.UseCancellationToken(_cancellationToken)
.PostAsync();
}
return initResponse.Key;
}
catch (Exception ex)
{
return default;
}
}
}