From 5d009c185c87febc726a23a271f453b345d26853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bia=C5=82as?= Date: Mon, 26 Feb 2024 14:44:41 +0100 Subject: [PATCH] Encoded data upload support These changes cover: - uploading mod archives created in memory (MemoryStream) - uploading encoded images (byte arrays) - EncodedImage class - for passing target image extension --- Runtime/Classes/EncodedImage.cs | 19 +++++++ Runtime/Classes/EncodedImage.cs.meta | 11 ++++ Runtime/Classes/ModProfileDetails.cs | 48 ++++++++++++---- Runtime/Classes/ModfileDetails.cs | 12 +++- .../Classes/CompressOperationMultiple.cs | 9 +-- .../Classes/ModIOUnityImplementation.cs | 56 +++++++++++-------- .../Implementation.API.Requests/AddMod.cs | 7 ++- .../AddModMedia.cs | 14 +++-- .../Implementation.API.Requests/EditMod.cs | 7 ++- 9 files changed, 134 insertions(+), 49 deletions(-) create mode 100644 Runtime/Classes/EncodedImage.cs create mode 100644 Runtime/Classes/EncodedImage.cs.meta diff --git a/Runtime/Classes/EncodedImage.cs b/Runtime/Classes/EncodedImage.cs new file mode 100644 index 0000000..ffe830e --- /dev/null +++ b/Runtime/Classes/EncodedImage.cs @@ -0,0 +1,19 @@ +using UnityEngine; + +namespace ModIO +{ + public class EncodedImage + { + public string extension; + public byte[] data; + + public static EncodedImage PNGFromTexture2D(Texture2D texture2D) + { + return new EncodedImage + { + extension = "png", + data = texture2D.EncodeToPNG() + }; + } + } +} diff --git a/Runtime/Classes/EncodedImage.cs.meta b/Runtime/Classes/EncodedImage.cs.meta new file mode 100644 index 0000000..57fb5a5 --- /dev/null +++ b/Runtime/Classes/EncodedImage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 629bf80cff7b9a64a94abd75a351d9df +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Classes/ModProfileDetails.cs b/Runtime/Classes/ModProfileDetails.cs index 1fde29b..42cbcf1 100644 --- a/Runtime/Classes/ModProfileDetails.cs +++ b/Runtime/Classes/ModProfileDetails.cs @@ -35,6 +35,11 @@ public class ModProfileDetails /// #if UNITY_2019_4_OR_NEWER public Texture2D logo; + /// + /// Logo encoded in supported format. + /// + /// + public EncodedImage encodedLogo; #else public byte[] logo; #endif @@ -45,6 +50,11 @@ public class ModProfileDetails /// Can be null #if UNITY_2019_4_OR_NEWER public Texture2D[] images; + /// + /// Images encoded in supported format. + /// + /// + public IReadOnlyList encodedImages; #else public List images; #endif @@ -125,28 +135,44 @@ public class ModProfileDetails /// Can be null public CommunityOptions? communityOptions = CommunityOptions.AllowCommenting; - internal byte[] GetLogo() + internal bool HasLogo() + { + return logo != null || encodedLogo != null; + } + + internal EncodedImage GetLogo() { #if UNITY_2019_4_OR_NEWER - // If a Texture2D type is not set to 'Sprite (2D or UI)' it will get flagged - // by cloudflare as suspicious and be rejected. This will return a 403 - return logo.EncodeToPNG(); + // If a Texture2D type is not set to 'Sprite (2D or UI)' it will get flagged + // by cloudflare as suspicious and be rejected. This will return a 403 + if (encodedLogo == null) + encodedLogo = EncodedImage.PNGFromTexture2D(logo); + return encodedLogo; #else - return logo; + return logo; #endif } - + + internal bool HasGalleryImages() + { + return encodedImages != null || images != null; + } + internal List GetGalleryImages() { #if UNITY_2019_4_OR_NEWER - List gallery = new List(); - foreach(var texture in images) + if (encodedImages == null) { - gallery.Add(texture.EncodeToPNG()); + List gallery = new List(); + foreach(var texture in images) + { + gallery.Add(EncodedImage.PNGFromTexture2D(texture)); + } + encodedImages = gallery; } - return gallery; + return encodedImages; #else - return images; + return images; #endif } } diff --git a/Runtime/Classes/ModfileDetails.cs b/Runtime/Classes/ModfileDetails.cs index f17970c..2327cbf 100644 --- a/Runtime/Classes/ModfileDetails.cs +++ b/Runtime/Classes/ModfileDetails.cs @@ -1,4 +1,6 @@ -namespace ModIO +using System.IO; + +namespace ModIO { public class ModfileDetails { @@ -12,7 +14,13 @@ public class ModfileDetails /// its contents will be compressed and uploaded when submitted via /// ModIOUnity.UploadModfile. /// - public string directory; + public string? directory; + + /// + /// Compressed data to send. + /// Alternative to directory for creating mods in memory. + /// + public MemoryStream compressedDirectory /// /// the changelog for this file version of the mod. diff --git a/Runtime/ModIO.Implementation/Classes/CompressOperationMultiple.cs b/Runtime/ModIO.Implementation/Classes/CompressOperationMultiple.cs index f01eb94..0b9656a 100644 --- a/Runtime/ModIO.Implementation/Classes/CompressOperationMultiple.cs +++ b/Runtime/ModIO.Implementation/Classes/CompressOperationMultiple.cs @@ -7,9 +7,9 @@ namespace ModIO.Implementation { internal class CompressOperationMultiple : CompressOperationBase { - public IEnumerable data; + public IEnumerable data; - public CompressOperationMultiple(IEnumerable compressed, ProgressHandle progressHandle) + public CompressOperationMultiple(IEnumerable compressed, ProgressHandle progressHandle) : base(progressHandle) { this.data = compressed; @@ -31,11 +31,12 @@ public override async Task> Compress() { zipStream.SetLevel(3); - foreach(var bytes in data) + foreach(var encodedImage in data) { - string entryName = $"image_{count}.png"; + string entryName = $"image_{count}.{encodedImage.extension}"; count++; + byte[] bytes = encodedImage.data; using(MemoryStream memoryStream = new MemoryStream()) { memoryStream.Write(bytes, 0, bytes.Length); diff --git a/Runtime/ModIO.Implementation/Classes/ModIOUnityImplementation.cs b/Runtime/ModIO.Implementation/Classes/ModIOUnityImplementation.cs index 20898eb..b79ddf3 100644 --- a/Runtime/ModIO.Implementation/Classes/ModIOUnityImplementation.cs +++ b/Runtime/ModIO.Implementation/Classes/ModIOUnityImplementation.cs @@ -2005,7 +2005,7 @@ public static async Task EditModProfile(ModProfileDetails modDetails) + " The 'tags' array in the ModProfileDetails will be ignored."); } - var config = modDetails.logo != null + var config = modDetails.HasLogo() ? API.Requests.EditMod.RequestPOST(modDetails) : API.Requests.EditMod.RequestPUT(modDetails); @@ -2353,12 +2353,7 @@ public static async Task UploadModfile(ModfileDetails modfile) if(IsInitialized(out result) && IsAuthenticatedSessionValid(out result) && IsModfileDetailsValid(modfile, out result)) { - CompressOperationDirectory compressOperation = new CompressOperationDirectory(modfile.directory); - - Task> compressTask = compressOperation.Compress(); - - - var compressionTaskResult = await openCallbacks.Run(callbackConfirmation, compressTask); + var compressionTaskResult = await GetCompressedData(modfile, callbackConfirmation); result = compressionTaskResult.result; if(!result.Succeeded()) @@ -2370,9 +2365,6 @@ public static async Task UploadModfile(ModfileDetails modfile) } else { - Logger.Log(LogLevel.Verbose, $"Compressed file ({modfile.directory})" - + $"\nstream length: {compressionTaskResult.value.Length}"); - callbackConfirmation = openCallbacks.New(); var requestConfig = await API.Requests.AddModFile.Request(modfile, compressionTaskResult.value); Task> task = WebRequestManager.Request(requestConfig, currentUploadHandle); @@ -2399,6 +2391,23 @@ public static async Task UploadModfile(ModfileDetails modfile) openCallbacks.Complete(callbackConfirmation); return result; + + static async Task> GetCompressedData(ModfileDetails modfile, TaskCompletionSource callbackConfirmation) + { + if(modfile.compressedDirectory != null) { + Logger.Log(LogLevel.Verbose, $"Using supplied MemoryStream of length: {modfile.compressedDirectory.Length}"); + return ResultAnd.Create(ResultBuilder.Success, modfile.compressedDirectory); + } + + + CompressOperationDirectory compressOperation = new CompressOperationDirectory(modfile.directory); + Task> compressTask = compressOperation.Compress(); + + var compressionTaskResult = await openCallbacks.Run(callbackConfirmation, compressTask); + Logger.Log(LogLevel.Verbose, $"Compressed file ({modfile.directory})" + + $"\nstream length: {compressionTaskResult.value.Length}"); + return compressionTaskResult; + } } public static async void UploadModMedia(ModProfileDetails modProfileDetails, Action callback) @@ -2467,15 +2476,18 @@ public static async void ArchiveModProfile(ModId modId, Action callback) static bool IsModfileDetailsValid(ModfileDetails modfile, out Result result) { - // Check directory exists - if(!DataStorage.TryGetModfileDetailsDirectory(modfile.directory, - out string notbeingusedhere)) + if(modfile.compressedDirectory == null) { - Logger.Log(LogLevel.Error, - "The provided directory in ModfileDetails could not be found or" - + $" does not exist ({modfile.directory})."); - result = ResultBuilder.Create(ResultCode.IO_DirectoryDoesNotExist); - return false; + // Check directory exists + if(!DataStorage.TryGetModfileDetailsDirectory(modfile.directory, + out string _)) + { + Logger.Log(LogLevel.Error, + "The provided directory in ModfileDetails could not be found or" + + $" does not exist ({modfile.directory})."); + result = ResultBuilder.Create(ResultCode.IO_DirectoryDoesNotExist); + return false; + } } // check metadata isn't too large @@ -2504,8 +2516,8 @@ static bool IsModfileDetailsValid(ModfileDetails modfile, out Result result) static bool IsModProfileDetailsValid(ModProfileDetails modDetails, out Result result) { - if(modDetails.logo == null || string.IsNullOrWhiteSpace(modDetails.summary) - || string.IsNullOrWhiteSpace(modDetails.name)) + if(!modDetails.HasLogo() || string.IsNullOrWhiteSpace(modDetails.summary) + || string.IsNullOrWhiteSpace(modDetails.name)) { Logger.Log( LogLevel.Error, @@ -2530,9 +2542,9 @@ static bool IsModProfileDetailsValidForEdit(ModProfileDetails modDetails, out Re return false; } - if(modDetails.logo != null) + if(modDetails.HasLogo()) { - if(modDetails.logo.EncodeToPNG().Length > 8388608) + if(modDetails.GetLogo().data.Length > 8388608) { Logger.Log(LogLevel.Error, "The provided logo in ModProfileDetails exceeds 8 megabytes"); diff --git a/Runtime/ModIO.Implementation/Implementation.API/Implementation.API.Requests/AddMod.cs b/Runtime/ModIO.Implementation/Implementation.API/Implementation.API.Requests/AddMod.cs index 88a4702..a3832fd 100644 --- a/Runtime/ModIO.Implementation/Implementation.API/Implementation.API.Requests/AddMod.cs +++ b/Runtime/ModIO.Implementation/Implementation.API/Implementation.API.Requests/AddMod.cs @@ -37,8 +37,11 @@ public static WebRequestConfig Request(ModProfileDetails details) } } - if(details.logo != null) - request.AddField("logo","logo.png", details.GetLogo()); + if(details.HasLogo()) + { + var logo = details.GetLogo(); + request.AddField("logo", $"logo.{logo.extension}", logo.data); + } return request; } diff --git a/Runtime/ModIO.Implementation/Implementation.API/Implementation.API.Requests/AddModMedia.cs b/Runtime/ModIO.Implementation/Implementation.API/Implementation.API.Requests/AddModMedia.cs index 1cdec78..e535d78 100644 --- a/Runtime/ModIO.Implementation/Implementation.API/Implementation.API.Requests/AddModMedia.cs +++ b/Runtime/ModIO.Implementation/Implementation.API/Implementation.API.Requests/AddModMedia.cs @@ -1,6 +1,5 @@ using System.IO; using System.Threading.Tasks; -using UnityEngine; namespace ModIO.Implementation.API.Requests { @@ -15,13 +14,16 @@ public static async Task> Request(ModProfileDetails ShouldRequestTimeout = false, }; - if(details.logo != null) - request.AddField("logo", "logo.png", details.logo.EncodeToPNG()); + if(details.HasLogo()) + { + var logo = details.GetLogo(); + request.AddField("logo", $"logo.{logo.extension}", logo.data); + } - if(details.images != null) + if(details.HasGalleryImages()) { - var imageBytes = details.GetGalleryImages(); - CompressOperationMultiple zipOperation = new CompressOperationMultiple(imageBytes, null); + var encodedImages = details.GetGalleryImages(); + CompressOperationMultiple zipOperation = new CompressOperationMultiple(encodedImages, null); ResultAnd resultAnd = await zipOperation.Compress(); diff --git a/Runtime/ModIO.Implementation/Implementation.API/Implementation.API.Requests/EditMod.cs b/Runtime/ModIO.Implementation/Implementation.API/Implementation.API.Requests/EditMod.cs index abf65ab..ebdaad8 100644 --- a/Runtime/ModIO.Implementation/Implementation.API/Implementation.API.Requests/EditMod.cs +++ b/Runtime/ModIO.Implementation/Implementation.API/Implementation.API.Requests/EditMod.cs @@ -44,8 +44,11 @@ public static WebRequestConfig InternalRequest(ModProfileDetails details, string request.AddField("metadata_blob", details.metadata); - if(details.logo != null) - request.AddField("logo", "logo.png", details.GetLogo()); + if(details.HasLogo()) + { + var logo = details.GetLogo(); + request.AddField("logo", $"logo.{logo.extension}", logo.data); + } return request; }