From ac3c7e34658d10e59d8b064533ec12eadb3f9137 Mon Sep 17 00:00:00 2001 From: Quirijn Slings Date: Wed, 4 Sep 2024 14:46:44 +0200 Subject: [PATCH] Specify original filename, support custom parameters, include more asset properties during upload, upload file as stream --- Bynder/Sample/UploadSample.cs | 74 ++++++++++++++++++++++- Bynder/Sdk/Query/Upload/SaveMediaQuery.cs | 27 ++++++++- Bynder/Sdk/Query/Upload/UploadQuery.cs | 44 +++++++++++++- Bynder/Sdk/Service/Asset/AssetService.cs | 14 +++++ Bynder/Sdk/Service/Asset/IAssetService.cs | 15 ++++- Bynder/Sdk/Service/Upload/FileUploader.cs | 72 +++++++++++++++++----- 6 files changed, 227 insertions(+), 19 deletions(-) diff --git a/Bynder/Sample/UploadSample.cs b/Bynder/Sample/UploadSample.cs index 826279f..21a054a 100644 --- a/Bynder/Sample/UploadSample.cs +++ b/Bynder/Sample/UploadSample.cs @@ -8,6 +8,8 @@ using System.Threading.Tasks; using System.Linq; using Bynder.Sdk.Query.Upload; +using System.Collections.Generic; +using System.IO; namespace Bynder.Sample { public class UploadSample @@ -41,9 +43,77 @@ private async Task RunUploadSampleAsync() return; } - await assetService.UploadFileAsync(new UploadQuery { Filepath = uploadPath, BrandId = brands.First().Id }); + Console.WriteLine("Name of the media item after upload: "); + var name = Console.ReadLine(); + if (string.IsNullOrEmpty(name)) + { + name = null; + } + + Console.WriteLine("Override original filename (leave empty to use the actual filename): "); + var filename = Console.ReadLine(); + + Console.WriteLine("Description (leave empty to use default): "); + var description = Console.ReadLine(); + + var customParameters = GetCustomParameters(); + + Console.WriteLine("Do you want to pass a file stream to the SDK?: (y/n)"); + var passAsStream = Console.ReadLine().ToLower().StartsWith("y"); + + + var query = new UploadQuery + { + Filepath = uploadPath, + BrandId = brands.First().Id, + Name = name, + CustomParameters = customParameters + }; + if (!string.IsNullOrEmpty(filename)) + { + query.OriginalFileName = filename; + } + if (!string.IsNullOrEmpty(description)) + { + query.Description = description; + } + + FileStream fileStream = null; + if (passAsStream) + { + fileStream = File.Open(query.Filepath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); + } + var before = DateTime.Now; + var response = passAsStream ? await assetService.UploadFileAsync(fileStream, query) : await assetService.UploadFileAsync(query); + var ms = Math.Round((DateTime.Now - before).TotalMilliseconds); + Console.WriteLine($"Uploaded file as media with id {response.MediaId} (time elapsed: {ms})"); + } - + + private Dictionary GetCustomParameters() + { + Console.WriteLine("Do you want to add custom parameters during the upload? (y/n)"); + var input = Console.ReadLine(); + + if (!input.ToString().ToLower().StartsWith("y")) + { + return null; + } + + Dictionary parameters = new Dictionary(); + while (input.ToString().ToLower().StartsWith("y")) + { + Console.WriteLine("Parameter name: "); + var paramName = Console.ReadLine(); + Console.WriteLine("Parameter value: "); + var paramValue = Console.ReadLine(); + parameters.Add(paramName, paramValue); + Console.WriteLine("Do you want to add another custom parameter? (y/n)"); + input = Console.ReadLine(); + } + return parameters; + } + private async Task AuthenticateWithOAuth2Async(bool useClientCredentials) { if (useClientCredentials) diff --git a/Bynder/Sdk/Query/Upload/SaveMediaQuery.cs b/Bynder/Sdk/Query/Upload/SaveMediaQuery.cs index 0d8e01b..8e241fb 100644 --- a/Bynder/Sdk/Query/Upload/SaveMediaQuery.cs +++ b/Bynder/Sdk/Query/Upload/SaveMediaQuery.cs @@ -1,4 +1,4 @@ -// Copyright (c) Bynder. All rights reserved. +// Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System.Collections.Generic; @@ -43,6 +43,31 @@ internal class SaveMediaQuery [ApiField("tags", Converter = typeof(ListConverter))] public IList Tags { get; set; } + + /// + /// Description of the media + /// + [ApiField("description")] + public string Description { get; set; } + + /// + /// Published date of the media + /// + [ApiField("ISOPublicationDate")] + public string PublishedDate { get; set; } + + /// + /// Copyright information for the media + /// + [ApiField("copyright")] + public string Copyright { get; set; } + + /// + /// Indicates if the media is public + /// + [ApiField("isPublic")] + public bool IsPublic { get; set; } + /// /// Metaproperty options to set on the asset. /// diff --git a/Bynder/Sdk/Query/Upload/UploadQuery.cs b/Bynder/Sdk/Query/Upload/UploadQuery.cs index df69753..c3e2205 100644 --- a/Bynder/Sdk/Query/Upload/UploadQuery.cs +++ b/Bynder/Sdk/Query/Upload/UploadQuery.cs @@ -1,4 +1,4 @@ -// Copyright (c) Bynder. All rights reserved. +// Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System.Collections.Generic; @@ -15,6 +15,16 @@ public class UploadQuery /// public string Filepath { get; set; } + /// + /// Name the media will have. + /// + public string Name { get; set; } + + /// + /// Original file name the media will have. + /// + public string OriginalFileName { get; set; } + /// /// Brand id where we want to store the file /// @@ -31,5 +41,37 @@ public class UploadQuery /// Tags of the file that we want to update /// public IList Tags { get; set; } + + /// + /// Description of the media + /// + public string Description { get; set; } + + /// + /// Copyright information for the media + /// + public string Copyright { get; set; } + + /// + /// Indicates if the media is public + /// + public bool IsPublic { get; set; } + + /// + /// Metaproperties the media will have + /// + public IDictionary> MetapropertyOptions { get; set; } = new Dictionary>(); + + /// + /// Published date the media will have. + /// + public string PublishedDate { get; set; } + + /// + /// Custom parameters to add to the upload endpoint + /// + public IEnumerable> CustomParameters { get; set; } + + } } diff --git a/Bynder/Sdk/Service/Asset/AssetService.cs b/Bynder/Sdk/Service/Asset/AssetService.cs index f640327..ef16019 100644 --- a/Bynder/Sdk/Service/Asset/AssetService.cs +++ b/Bynder/Sdk/Service/Asset/AssetService.cs @@ -11,6 +11,7 @@ using Bynder.Sdk.Model; using Bynder.Sdk.Query.Asset; using Bynder.Sdk.Query.Upload; +using System.IO; namespace Bynder.Sdk.Service.Asset { @@ -143,6 +144,18 @@ public async Task UploadFileAsync(UploadQuery query) return await _uploader.UploadFileAsync(query).ConfigureAwait(false); } + /// + /// Check for more information + /// + /// Check for more information + /// Check for more information + /// Check for more information + public async Task UploadFileAsync(FileStream fileStream, UploadQuery query) + { + return await _uploader.UploadFileAsync(fileStream, query).ConfigureAwait(false); + } + + /// /// Check for more information /// @@ -266,5 +279,6 @@ private static MediaQueryFull CloneIntoFullMediaQuery(MediaQuery query) Total = true }; } + } } diff --git a/Bynder/Sdk/Service/Asset/IAssetService.cs b/Bynder/Sdk/Service/Asset/IAssetService.cs index afa8425..50ce12c 100644 --- a/Bynder/Sdk/Service/Asset/IAssetService.cs +++ b/Bynder/Sdk/Service/Asset/IAssetService.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using Bynder.Sdk.Model; using Bynder.Sdk.Query.Asset; @@ -74,7 +75,7 @@ public interface IAssetService Task> GetMediaListAsync(MediaQuery query); /// - /// Uploads a file async. + /// Uploads a file based on a filepath in the query /// /// Information to upload a file /// Task representing the upload @@ -82,6 +83,18 @@ public interface IAssetService /// Can be thrown when upload does not finish within expected time Task UploadFileAsync(UploadQuery query); + /// + /// Uploads a file as a stream + /// + /// Stream representing the file to be uploaded + /// Information to upload a file + /// Task representing the upload + /// Can be thrown when requests to server can't be completed or HTTP code returned by server is an error + /// Can be thrown when upload does not finish within expected time + + Task UploadFileAsync(FileStream fileStream, UploadQuery query); + + /// /// Modifies a media /// diff --git a/Bynder/Sdk/Service/Upload/FileUploader.cs b/Bynder/Sdk/Service/Upload/FileUploader.cs index cccc5fb..58b4aad 100644 --- a/Bynder/Sdk/Service/Upload/FileUploader.cs +++ b/Bynder/Sdk/Service/Upload/FileUploader.cs @@ -10,6 +10,8 @@ using Bynder.Sdk.Api.RequestSender; using Bynder.Sdk.Model; using Bynder.Sdk.Query.Upload; +using System.Linq; +using System.Web; namespace Bynder.Sdk.Service.Upload { @@ -69,6 +71,17 @@ public static FileUploader Create(IApiRequestSender requestSender) return new FileUploader(requestSender, new AmazonApi()); } + /// + /// Uploads a file with the data specified in query parameter + /// + /// Stream of the file to upload + /// Upload query information to upload a file + /// Task representing the upload + public async Task UploadFileAsync(Stream fileStream, UploadQuery query) + { + return await UploadFileAsync(fileStream, query, query.OriginalFileName ?? Path.GetFileName(query.Filepath)); + } + /// /// Uploads a file with the data specified in query parameter /// @@ -76,44 +89,66 @@ public static FileUploader Create(IApiRequestSender requestSender) /// Task representing the upload public async Task UploadFileAsync(UploadQuery query) { - var uploadRequest = await RequestUploadInformationAsync(new RequestUploadQuery { Filename = query.Filepath }).ConfigureAwait(false); + var filename = !string.IsNullOrEmpty(query.OriginalFileName) ? query.OriginalFileName : Path.GetFileName(query.Filepath); + var uploadRequest = await RequestUploadInformationAsync(new RequestUploadQuery { Filename = filename }).ConfigureAwait(false); uint chunkNumber = 0; - using (var file = File.Open(query.Filepath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) + var fileStream = File.Open(query.Filepath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); + return await UploadFileAsync(fileStream, query, filename); + + + } + + private async Task GetUploadRequest(string fileName) + { + return await RequestUploadInformationAsync(new RequestUploadQuery { Filename = fileName }).ConfigureAwait(false); + } + + + private async Task UploadFileAsync(Stream fileStream, UploadQuery query, string filename) + { + uint chunkNumber = 0; + var uploadRequest = await GetUploadRequest(filename); + using (fileStream) { int bytesRead = 0; var buffer = new byte[CHUNK_SIZE]; - long numberOfChunks = (file.Length + CHUNK_SIZE - 1) / CHUNK_SIZE; + long numberOfChunks = (fileStream.Length + CHUNK_SIZE - 1) / CHUNK_SIZE; - while ((bytesRead = file.Read(buffer, 0, buffer.Length)) > 0) + while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) > 0) { ++chunkNumber; await UploadPartAsync(Path.GetFileName(query.Filepath), buffer, bytesRead, chunkNumber, uploadRequest, (uint)numberOfChunks).ConfigureAwait(false); } } - var finalizeResponse = await FinalizeUploadAsync(uploadRequest, chunkNumber).ConfigureAwait(false); + var finalizeResponse = await FinalizeUploadAsync(uploadRequest, chunkNumber, query.CustomParameters).ConfigureAwait(false); if (await HasFinishedSuccessfullyAsync(finalizeResponse).ConfigureAwait(false)) { return await SaveMediaAsync(new SaveMediaQuery { - Filename = query.Filepath, + Filename = query.Name ?? query.Filepath, BrandId = query.BrandId, ImportId = finalizeResponse.ImportId, MediaId = query.MediaId, - Tags = query.Tags + Tags = query.Tags, + Description = query.Description, + Copyright = query.Copyright, + IsPublic = query.IsPublic, + MetapropertyOptions = query.MetapropertyOptions, + PublishedDate = query.PublishedDate }).ConfigureAwait(false); } else { - throw new BynderUploadException("Converter did not finished. Upload not completed"); + throw new BynderUploadException("Converter did not finish. Upload not completed"); } } /// - /// Gets the closes s3 endpoint. This is needed to know to which bucket Url it uploads chunks + /// Gets the closest s3 endpoint. This is needed to know to which bucket Url it uploads chunks /// /// Task containting string with the Url private async Task GetClosestS3EndpointAsync() @@ -243,7 +278,7 @@ private async Task HasFinishedSuccessfullyAsync(FinalizeResponse finalizeR } /// - /// Registers a chunk in Bynder using . + /// Registers a chunk in Bynder using . /// /// Upload request information /// Current chunk number @@ -274,7 +309,7 @@ private async Task RegisterChunkAsync(UploadRequest uploadRequest, uint chunkNum /// Requests information to start a new upload /// /// Contains the information needed to request upload information - /// Task containing information + /// Task containing information private async Task RequestUploadInformationAsync(RequestUploadQuery query) { var request = new ApiRequest @@ -288,12 +323,12 @@ private async Task RequestUploadInformationAsync(RequestUploadQue } /// - /// Finalizes an upload using . + /// Finalizes an upload using . /// /// Upload request information /// chunk number /// Task with information - private async Task FinalizeUploadAsync(UploadRequest uploadRequest, uint chunkNumber) + private async Task FinalizeUploadAsync(UploadRequest uploadRequest, uint chunkNumber, IEnumerable> customParameters) { var query = new FinalizeUploadQuery { @@ -302,9 +337,18 @@ private async Task FinalizeUploadAsync(UploadRequest uploadReq S3Filename = $"{uploadRequest.S3Filename}/p{chunkNumber}", Chunks = chunkNumber.ToString() }; + var requestParameters = ""; + if (customParameters != null) + { + requestParameters = string.Join('&', customParameters.Select(p => HttpUtility.UrlEncode(p.Key) + "=" + HttpUtility.UrlEncode(p.Value))); + if (!string.IsNullOrEmpty(requestParameters)) + { + requestParameters = "?" + requestParameters; + } + } var request = new ApiRequest { - Path = $"/api/v4/upload/{query.UploadId}/", + Path = $"/api/v4/upload/{query.UploadId}/{requestParameters}", HTTPMethod = HttpMethod.Post, Query = query };