Initial resumable file upload.

Other changes.
This commit is contained in:
2020-03-25 17:31:42 +01:00
parent 2a8f4cbb8d
commit d546edaa0d
11 changed files with 462 additions and 68 deletions

View File

@@ -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;
}
}
}