Initial resumable file upload.
Other changes.
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Google.Apis" Version="1.44.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="3.1.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.2" />
|
||||
@@ -12,6 +13,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="3.1.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.2" />
|
||||
<PackageReference Include="IdentityModel.OidcClient" Version="3.1.2" />
|
||||
<PackageReference Include="Winista.MimeDetect" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@@ -4,6 +4,7 @@
|
||||
{
|
||||
public string Authority { get; set; }
|
||||
public string Endpoint { get; set; }
|
||||
public string BasePath { get; set; }
|
||||
public string DiscoveryEndpointUrl { get; set; }
|
||||
public string RedirectUri { get; set; }
|
||||
public string ClientId { get; set; }
|
||||
|
45
FarmmapsApi/Models/FileRequest.cs
Normal file
45
FarmmapsApi/Models/FileRequest.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace FarmmapsApi.Models
|
||||
{
|
||||
public class FileRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Code of the parent
|
||||
/// </summary>
|
||||
/// <example>41971e7ea8a446069a817e66b608dcae</example>
|
||||
public string ParentCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Geometry meta data
|
||||
/// </summary>
|
||||
/// <example>{"type": "Point","coordinates": [5.27, 52.10]}</example>
|
||||
public JObject Geometry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the file to upload
|
||||
/// </summary>
|
||||
/// <example>MyFile.tiff</example>
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Size of the file to upload
|
||||
/// </summary>
|
||||
/// <example>67351</example>
|
||||
[Required]
|
||||
public long Size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If a chunked upload, the size of a single chunk
|
||||
/// </summary>
|
||||
/// <example>1048576</example>
|
||||
public long ChunkSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional data for the item coupled with the file
|
||||
/// </summary>
|
||||
public JObject Data { get; set; }
|
||||
}
|
||||
}
|
17
FarmmapsApi/Models/FileResponse.cs
Normal file
17
FarmmapsApi/Models/FileResponse.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace FarmmapsApi.Models
|
||||
{
|
||||
public class FileResponse : FileRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Code created for the registered file, use this in subsequent calls
|
||||
/// </summary>
|
||||
/// <example>a00fbd18320742c787f99f952aef0dbb</example>
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If a chunked upload, the number of chunks to upload
|
||||
/// </summary>
|
||||
/// <example>1</example>
|
||||
public long Chunks { get; set; }
|
||||
}
|
||||
}
|
@@ -10,30 +10,31 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using FarmmapsApi.Models;
|
||||
using Google.Apis.Http;
|
||||
using Google.Apis.Upload;
|
||||
using IdentityModel;
|
||||
using Microsoft.AspNetCore.Http.Connections;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Winista.Mime;
|
||||
|
||||
namespace FarmmapsApi.Services
|
||||
{
|
||||
public class FarmmapsApiService
|
||||
{
|
||||
private readonly ILogger<FarmmapsApiService> _logger;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly OpenIdConnectService _openIdConnectService;
|
||||
private readonly Configuration _configuration;
|
||||
|
||||
|
||||
private HubConnection _hubConnection;
|
||||
|
||||
public event Action<EventMessage> EventCallback;
|
||||
|
||||
public FarmmapsApiService(HttpClient httpClient, ILogger<FarmmapsApiService> logger,
|
||||
public FarmmapsApiService(HttpClient httpClient,
|
||||
OpenIdConnectService openIdConnectService, Configuration configuration)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpClient = httpClient;
|
||||
_openIdConnectService = openIdConnectService;
|
||||
_configuration = configuration;
|
||||
@@ -65,10 +66,9 @@ namespace FarmmapsApi.Services
|
||||
|
||||
private async Task StartEventHub(string accessToken)
|
||||
{
|
||||
var uri = new Uri(_configuration.Endpoint);
|
||||
var eventEndpoint = $"{uri.Scheme}://{uri.Host}:{uri.Port}/EventHub";
|
||||
var eventEndpoint = $"{_configuration.Endpoint}/EventHub";
|
||||
_hubConnection = new HubConnectionBuilder()
|
||||
.WithUrl(eventEndpoint, HttpTransportType.WebSockets,
|
||||
.WithUrl(eventEndpoint, HttpTransportType.WebSockets,
|
||||
options => options.SkipNegotiation = true)
|
||||
.ConfigureLogging(log => log.AddConsole())
|
||||
.WithAutomaticReconnect()
|
||||
@@ -76,9 +76,9 @@ namespace FarmmapsApi.Services
|
||||
|
||||
await _hubConnection.StartAsync();
|
||||
await AuthenticateEventHub(accessToken);
|
||||
|
||||
|
||||
_hubConnection.Reconnected += async s => await AuthenticateEventHub(accessToken);
|
||||
_hubConnection.On("event", (Object @event) => _logger.LogInformation(@event.ToString()));
|
||||
_hubConnection.On("event", (EventMessage eventMessage) => EventCallback?.Invoke(eventMessage));
|
||||
}
|
||||
|
||||
private async Task AuthenticateEventHub(string accessToken)
|
||||
@@ -88,7 +88,8 @@ namespace FarmmapsApi.Services
|
||||
|
||||
public async Task<string> GetCurrentUserCodeAsync()
|
||||
{
|
||||
var response = await _httpClient.GetAsync(ResourceEndpoints.CURRENTUSER_RESOURCE);
|
||||
var url = $"{_configuration.BasePath}/{ResourceEndpoints.CURRENTUSER_RESOURCE}";
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
throw new Exception(response.ReasonPhrase);
|
||||
@@ -100,7 +101,8 @@ namespace FarmmapsApi.Services
|
||||
|
||||
public async Task<List<UserRoot>> GetCurrentUserRootsAsync()
|
||||
{
|
||||
var response = await _httpClient.GetAsync(ResourceEndpoints.MYROOTS_RESOURCE);
|
||||
var url = $"{_configuration.BasePath}/{ResourceEndpoints.MYROOTS_RESOURCE}";
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
throw new Exception(response.ReasonPhrase);
|
||||
@@ -109,7 +111,7 @@ namespace FarmmapsApi.Services
|
||||
|
||||
return JsonConvert.DeserializeObject<List<UserRoot>>(jsonString);
|
||||
}
|
||||
|
||||
|
||||
public async Task<Item> GetItemAsync(string itemCode, string itemType = null, JObject dataFilter = null)
|
||||
{
|
||||
if (dataFilter == null)
|
||||
@@ -118,28 +120,29 @@ namespace FarmmapsApi.Services
|
||||
var items = await GetItemsAsync(itemCode, itemType, dataFilter);
|
||||
return items[0];
|
||||
}
|
||||
|
||||
|
||||
public async Task<List<Item>> GetItemsAsync(string itemCode, string itemType = null, JObject dataFilter = null)
|
||||
{
|
||||
var resourceUri = string.Format(ResourceEndpoints.ITEMS_RESOURCE, itemCode);
|
||||
var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMS_RESOURCE}";
|
||||
var resourceUri = string.Format(url, itemCode);
|
||||
|
||||
var queryString = HttpUtility.ParseQueryString(string.Empty);
|
||||
|
||||
if(itemType != null)
|
||||
|
||||
if (itemType != null)
|
||||
queryString["it"] = itemType;
|
||||
|
||||
if(dataFilter != null)
|
||||
queryString["df"] = dataFilter.ToString(Formatting.None);
|
||||
|
||||
if (dataFilter != null)
|
||||
queryString["df"] = dataFilter.ToString(Formatting.None);
|
||||
|
||||
resourceUri = (queryString.Count > 0 ? $"{resourceUri}?" : resourceUri) + queryString;
|
||||
|
||||
|
||||
var response = await _httpClient.GetAsync(resourceUri);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
|
||||
|
||||
throw new Exception(response.ReasonPhrase);
|
||||
}
|
||||
|
||||
@@ -149,36 +152,39 @@ namespace FarmmapsApi.Services
|
||||
|
||||
private async Task<Item> GetItemSingleAsync(string itemCode, string itemType = null)
|
||||
{
|
||||
var resourceUri = string.Format(ResourceEndpoints.ITEMS_RESOURCE, itemCode);
|
||||
var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMS_RESOURCE}";
|
||||
var resourceUri = string.Format(url, itemCode);
|
||||
|
||||
if (!string.IsNullOrEmpty(itemType))
|
||||
resourceUri = $"{resourceUri}/{itemType}";
|
||||
|
||||
|
||||
var response = await _httpClient.GetAsync(resourceUri);
|
||||
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
|
||||
|
||||
throw new Exception(response.ReasonPhrase);
|
||||
}
|
||||
|
||||
var jsonString = await response.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<Item>(jsonString);
|
||||
}
|
||||
|
||||
public async Task<List<Item>> GetItemChildrenAsync(string itemCode, string itemType = null, JObject dataFilter = null)
|
||||
|
||||
public async Task<List<Item>> GetItemChildrenAsync(string itemCode, string itemType = null,
|
||||
JObject dataFilter = null)
|
||||
{
|
||||
var resourceUri = string.Format(ResourceEndpoints.ITEMS_CHILDREN_RESOURCE, itemCode);
|
||||
|
||||
var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMS_CHILDREN_RESOURCE}";
|
||||
var resourceUri = string.Format(url, itemCode);
|
||||
|
||||
var queryString = HttpUtility.ParseQueryString(string.Empty);
|
||||
|
||||
if(itemType != null)
|
||||
|
||||
if (itemType != null)
|
||||
queryString["it"] = itemType;
|
||||
|
||||
if(dataFilter != null)
|
||||
queryString["df"] = dataFilter.ToString(Formatting.None);
|
||||
|
||||
if (dataFilter != null)
|
||||
queryString["df"] = dataFilter.ToString(Formatting.None);
|
||||
|
||||
resourceUri = (queryString.Count > 0 ? $"{resourceUri}?" : resourceUri) + queryString;
|
||||
|
||||
@@ -195,31 +201,34 @@ namespace FarmmapsApi.Services
|
||||
{
|
||||
var jsonString = JsonConvert.SerializeObject(itemRequest);
|
||||
|
||||
var content = new StringContent(jsonString, Encoding.UTF8,MediaTypeNames.Application.Json);
|
||||
var response = await _httpClient.PostAsync(ResourceEndpoints.ITEMS_CREATE_RESOURCE,
|
||||
var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMS_CREATE_RESOURCE}";
|
||||
var content = new StringContent(jsonString, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||
var response = await _httpClient.PostAsync(url,
|
||||
content);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
throw new Exception(response.ReasonPhrase);
|
||||
|
||||
|
||||
var jsonStringResponse = await response.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<Item>(jsonStringResponse);
|
||||
}
|
||||
|
||||
public async Task DeleteItemAsync(string itemCode)
|
||||
{
|
||||
var resourceUri = string.Format(ResourceEndpoints.ITEMS_RESOURCE, itemCode);
|
||||
var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMS_RESOURCE}";
|
||||
var resourceUri = string.Format(url, itemCode);
|
||||
var response = await _httpClient.DeleteAsync(resourceUri);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
throw new Exception(response.ReasonPhrase);
|
||||
}
|
||||
|
||||
|
||||
public async Task DeleteItemsAsync(IList<string> itemCodes)
|
||||
{
|
||||
var jsonString = JsonConvert.SerializeObject(itemCodes);
|
||||
var content = new StringContent(jsonString);
|
||||
var response = await _httpClient.PostAsync(ResourceEndpoints.ITEMS_DELETE_RESOURCE, content);
|
||||
var content = new StringContent(jsonString);
|
||||
var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMS_DELETE_RESOURCE}";
|
||||
var response = await _httpClient.PostAsync(url, content);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
throw new Exception(response.ReasonPhrase);
|
||||
@@ -227,10 +236,11 @@ namespace FarmmapsApi.Services
|
||||
|
||||
public async Task DownloadItemAsync(string itemCode, string filePath)
|
||||
{
|
||||
var resourceUri = string.Format(ResourceEndpoints.ITEMS_DOWNLOAD_RESOURCE, itemCode);
|
||||
var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMS_DOWNLOAD_RESOURCE}";
|
||||
var resourceUri = string.Format(url, itemCode);
|
||||
var response = await _httpClient.GetAsync(resourceUri, HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
if(response.IsSuccessStatusCode)
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
await using Stream streamToReadFrom = await response.Content.ReadAsStreamAsync();
|
||||
await using Stream streamToWriteTo = File.Open(filePath, FileMode.Create);
|
||||
@@ -238,30 +248,32 @@ namespace FarmmapsApi.Services
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new FileNotFoundException(response.ReasonPhrase);
|
||||
throw new FileNotFoundException(response.ReasonPhrase);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string> QueueTaskAsync(string itemCode, TaskRequest taskRequest)
|
||||
{
|
||||
var resourceUri = string.Format(ResourceEndpoints.ITEMTASK_REQUEST_RESOURCE, itemCode);
|
||||
var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMTASK_REQUEST_RESOURCE}";
|
||||
var resourceUri = string.Format(url, itemCode);
|
||||
|
||||
var jsonString = JsonConvert.SerializeObject(taskRequest);
|
||||
var content = new StringContent(jsonString, Encoding.UTF8,MediaTypeNames.Application.Json);
|
||||
var content = new StringContent(jsonString, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||
var response = await _httpClient.PostAsync(resourceUri, content);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
throw new Exception(response.ReasonPhrase);
|
||||
|
||||
|
||||
var jsonStringResponse = await response.Content.ReadAsStringAsync();
|
||||
var json = JsonConvert.DeserializeObject<JObject>(jsonStringResponse);
|
||||
|
||||
|
||||
return json["code"].Value<string>();
|
||||
}
|
||||
|
||||
public async Task<List<ItemTaskStatus>> GetTasksStatusAsync(string itemCode)
|
||||
{
|
||||
var resourceUri = string.Format(ResourceEndpoints.ITEMTASKS_RESOURCE, itemCode);
|
||||
var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMTASKS_RESOURCE}";
|
||||
var resourceUri = string.Format(url, itemCode);
|
||||
var response = await _httpClient.GetAsync(resourceUri);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
@@ -273,7 +285,8 @@ namespace FarmmapsApi.Services
|
||||
|
||||
public async Task<ItemTaskStatus> GetTaskStatusAsync(string itemCode, string itemTaskCode)
|
||||
{
|
||||
var resourceUri = string.Format(ResourceEndpoints.ITEMTASK_RESOURCE, itemCode, itemTaskCode);
|
||||
var url = $"{_configuration.BasePath}/{ResourceEndpoints.ITEMTASK_RESOURCE}";
|
||||
var resourceUri = string.Format(url, itemCode, itemTaskCode);
|
||||
var response = await _httpClient.GetAsync(resourceUri);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
@@ -282,5 +295,85 @@ namespace FarmmapsApi.Services
|
||||
var jsonString = await response.Content.ReadAsStringAsync();
|
||||
return JsonConvert.DeserializeObject<ItemTaskStatus>(jsonString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Experimental
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="parentItemCode"></param>
|
||||
/// <param name="uploadCallback"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="FileNotFoundException"></exception>
|
||||
public async Task<Uri> UploadFile(string filePath, string parentItemCode, Action<IUploadProgress> uploadCallback)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found {filePath}");
|
||||
|
||||
var mimeTypes = new MimeTypes();
|
||||
var mimeType = mimeTypes.GetMimeTypeFromFile(filePath);
|
||||
|
||||
await using var uploadStream = new FileStream(filePath, FileMode.OpenOrCreate);
|
||||
|
||||
var request = new FileRequest()
|
||||
{
|
||||
Name = Path.GetFileName(filePath),
|
||||
ParentCode = parentItemCode,
|
||||
Size = uploadStream.Length
|
||||
};
|
||||
|
||||
Uri uploadIdentifierUri = null;
|
||||
using var httpClient = CreateConfigurableHttpClient(_httpClient);
|
||||
var farmmapsUploader = new FarmmapsUploader(httpClient, uploadStream, request, mimeType.ToString());
|
||||
|
||||
farmmapsUploader.ProgressChanged += uploadCallback;
|
||||
farmmapsUploader.UploadSessionData += data => uploadIdentifierUri = data.UploadUri;
|
||||
|
||||
await farmmapsUploader.UploadAsync();
|
||||
|
||||
return uploadIdentifierUri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Experimental
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="location"></param>
|
||||
/// <param name="uploadCallback"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="FileNotFoundException"></exception>
|
||||
private async Task<Uri> ResumeUploadFile(string filePath, Uri location, Action<IUploadProgress> uploadCallback)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
throw new FileNotFoundException($"File not found {filePath}");
|
||||
|
||||
await using var uploadStream = new FileStream(filePath, FileMode.OpenOrCreate);
|
||||
|
||||
var request = new FileRequest()
|
||||
{
|
||||
Name = Path.GetFileName(filePath),
|
||||
ParentCode = string.Empty,
|
||||
Size = uploadStream.Length
|
||||
};
|
||||
|
||||
using var httpClient = CreateConfigurableHttpClient(_httpClient);
|
||||
var farmmapsUploader = new FarmmapsUploader(httpClient, uploadStream, request, string.Empty);
|
||||
farmmapsUploader.ProgressChanged += uploadCallback;
|
||||
|
||||
await farmmapsUploader.ResumeAsync(location);
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
private ConfigurableHttpClient CreateConfigurableHttpClient(HttpClient parent)
|
||||
{
|
||||
var googleHttpClient = new HttpClientFactory().CreateHttpClient(new CreateHttpClientArgs {GZipEnabled = true, ApplicationName = "FarmMaps"});
|
||||
googleHttpClient.BaseAddress = parent.BaseAddress;
|
||||
googleHttpClient.DefaultRequestHeaders.Add("Accept", MediaTypeNames.Application.Json);
|
||||
|
||||
var authHeader = parent.DefaultRequestHeaders.Authorization;
|
||||
googleHttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authHeader.Scheme, authHeader.Parameter);
|
||||
|
||||
return googleHttpClient;
|
||||
}
|
||||
}
|
||||
}
|
160
FarmmapsApi/Services/FarmmapsUploader.cs
Normal file
160
FarmmapsApi/Services/FarmmapsUploader.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FarmmapsApi.Models;
|
||||
using Google.Apis.Http;
|
||||
using Google.Apis.Json;
|
||||
using Google.Apis.Requests;
|
||||
using Google.Apis.Upload;
|
||||
using Google.Apis.Util;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace FarmmapsApi.Services
|
||||
{
|
||||
public class FarmmapsUploader : ResumableUpload
|
||||
{
|
||||
/// <summary>Payload description headers, describing the content itself.</summary>
|
||||
private const string PayloadContentTypeHeader = "X-Upload-Content-Type";
|
||||
|
||||
/// <summary>Payload description headers, describing the content itself.</summary>
|
||||
private const string PayloadContentLengthHeader = "X-Upload-Content-Length";
|
||||
|
||||
private const int UnknownSize = -1;
|
||||
|
||||
/// <summary>Gets or sets the service.</summary>
|
||||
private HttpClient HttpClient { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path of the method (combined with
|
||||
/// <see cref="Google.Apis.Services.IClientService.BaseUri"/>) to produce
|
||||
/// absolute Uri.
|
||||
/// </summary>
|
||||
private string Path { get; }
|
||||
|
||||
/// <summary>Gets or sets the HTTP method of this upload (used to initialize the upload).</summary>
|
||||
private string HttpMethod { get; }
|
||||
|
||||
/// <summary>Gets or sets the stream's Content-Type.</summary>
|
||||
private string ContentType { get; }
|
||||
|
||||
/// <summary>Gets or sets the body of this request.</summary>
|
||||
private FileRequest Body { get; }
|
||||
|
||||
private long _streamLength;
|
||||
|
||||
/// <summary>
|
||||
/// Create a resumable upload instance with the required parameters.
|
||||
/// </summary>
|
||||
/// <param name="contentStream">The stream containing the content to upload.</param>
|
||||
/// <remarks>
|
||||
/// Caller is responsible for maintaining the <paramref name="contentStream"/> open until the upload is
|
||||
/// completed.
|
||||
/// Caller is responsible for closing the <paramref name="contentStream"/>.
|
||||
/// </remarks>
|
||||
public FarmmapsUploader(ConfigurableHttpClient httpClient, Stream contentStream, FileRequest body, string contentType)
|
||||
: base(contentStream,
|
||||
new ResumableUploadOptions
|
||||
{
|
||||
HttpClient = httpClient,
|
||||
Serializer = new NewtonsoftJsonSerializer(),
|
||||
ServiceName = "FarmMaps"
|
||||
})
|
||||
{
|
||||
httpClient.ThrowIfNull(nameof(httpClient));
|
||||
contentStream.ThrowIfNull(nameof(contentStream));
|
||||
|
||||
_streamLength = ContentStream.CanSeek ? ContentStream.Length : UnknownSize;
|
||||
Body = body;
|
||||
Path = "api/v1/file";
|
||||
HttpClient = httpClient;
|
||||
HttpMethod = HttpConsts.Post;
|
||||
ContentType = contentType;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override async Task<Uri> InitiateSessionAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
HttpRequestMessage request = CreateInitializeRequest();
|
||||
Options?.ModifySessionInitiationRequest?.Invoke(request);
|
||||
var response = await HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
throw await ExceptionForResponseAsync(response).ConfigureAwait(false);
|
||||
}
|
||||
return response.Headers.Location;
|
||||
}
|
||||
|
||||
/// <summary>Creates a request to initialize a request.</summary>
|
||||
private HttpRequestMessage CreateInitializeRequest()
|
||||
{
|
||||
var baseAddres = HttpClient.BaseAddress;
|
||||
var uri = new Uri($"{baseAddres.Scheme}://{baseAddres.Host}:{baseAddres.Port}");
|
||||
var builder = new RequestBuilder()
|
||||
{
|
||||
BaseUri = uri,
|
||||
Path = Path,
|
||||
Method = HttpMethod,
|
||||
};
|
||||
|
||||
SetAllPropertyValues(builder);
|
||||
|
||||
HttpRequestMessage request = builder.CreateRequest();
|
||||
if (ContentType != null)
|
||||
{
|
||||
request.Headers.Add(PayloadContentTypeHeader, ContentType);
|
||||
}
|
||||
|
||||
// if the length is unknown at the time of this request, omit "X-Upload-Content-Length" header
|
||||
if (ContentStream.CanSeek)
|
||||
{
|
||||
request.Headers.Add(PayloadContentLengthHeader, _streamLength.ToString());
|
||||
}
|
||||
|
||||
var jsonText = JsonConvert.SerializeObject(Body);
|
||||
request.Content = new StringContent(jsonText, Encoding.UTF8, MediaTypeNames.Application.Json);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reflectively enumerate the properties of this object looking for all properties containing the
|
||||
/// RequestParameterAttribute and copy their values into the request builder.
|
||||
/// </summary>
|
||||
private void SetAllPropertyValues(RequestBuilder requestBuilder)
|
||||
{
|
||||
Type myType = this.GetType();
|
||||
var properties = myType.GetProperties();
|
||||
|
||||
foreach (var property in properties)
|
||||
{
|
||||
var attribute = property.GetCustomAttribute<RequestParameterAttribute>();
|
||||
if (attribute != null)
|
||||
{
|
||||
string name = attribute.Name ?? property.Name.ToLower();
|
||||
object value = property.GetValue(this, null);
|
||||
if (value != null)
|
||||
{
|
||||
if (!(value is string) && value is IEnumerable valueAsEnumerable)
|
||||
{
|
||||
foreach (var elem in valueAsEnumerable)
|
||||
{
|
||||
requestBuilder.AddParameter(attribute.Type, name, Utilities.ConvertToString(elem));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise just convert it to a string.
|
||||
requestBuilder.AddParameter(attribute.Type, name, Utilities.ConvertToString(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user