From 24cfffd7327ea5b6b3fd6b891a0cbfb80aeb61a9 Mon Sep 17 00:00:00 2001 From: "Simon (Darkside) Jackson" Date: Thu, 9 Mar 2023 21:54:52 +0000 Subject: [PATCH] Release 1.0.3 (#24) * Updated Rest features (#23) * Updating Rest utilities, added cancellation token to all calls. Plus clean up * Added Unit Tests to Rest functionality * Standardised overload options into a new RestArgs struct * Rationalised implementations for standards * Added overloaded Download function to return a byte[] or file string contents * Fix new script icon * Update workflow to multi-version for test * Updated PostForm following 2022 testing * Include uGUI dependency for the UI extensions in the package * Extract Canvas dependencies from the utility package * Final review and cleanup * Update Readme --------- Co-authored-by: Simon Jackson --- .../development-buildandtestupmrelease.yml | 3 +- Editor/AssemblyInfo.cs | 11 - README.md | 83 ++- Runtime/AssemblyInfo.cs | 11 - Runtime/AssemblyInfo.cs.meta | 11 - .../Attributes/Il2CppSetOptionAttribute.cs | 3 + .../Utilities/AutoStartBehavior.cs | 2 +- Runtime/Definitions/Utilities/AxisType.cs | 2 +- Runtime/Definitions/Utilities/CardinalAxis.cs | 2 +- Runtime/Definitions/Utilities/FlattenMode.cs | 2 +- .../Definitions/Utilities/RenderPipeline.cs | 2 +- Runtime/Definitions/Utilities/SystemType.cs | 2 +- Runtime/Extensions/CanvasExtensions.cs | 212 ------ Runtime/Extensions/ColliderExtensions.cs | 2 +- Runtime/Extensions/CollisionExtensions.cs | 2 +- Runtime/Extensions/ComponentExtensions.cs | 2 +- Runtime/Extensions/EnumExtensions.cs | 2 +- Runtime/Extensions/GameObjectExtensions.cs | 6 - Runtime/Extensions/StringExtensions.cs | 2 +- .../Extensions/SystemNumericsExtensions.cs | 2 +- Runtime/Extensions/TypeExtensions.cs | 2 +- Runtime/Extensions/VectorExtensions.cs | 22 - .../BackgroundThread.cs | 3 + Runtime/Utilities/TypeCache.cs | 2 +- Runtime/Utilities/WebRequestRest/Response.cs | 2 +- Runtime/Utilities/WebRequestRest/Rest.cs | 702 +++++++++--------- Runtime/Utilities/WebRequestRest/RestArgs.cs | 22 + .../WebRequestRest/RestArgs.cs.meta} | 2 +- Tests/Rest.meta | 8 + Tests/Rest/RestTests.cs | 248 +++++++ .../Rest/RestTests.cs.meta | 2 +- package.json | 2 +- 32 files changed, 716 insertions(+), 665 deletions(-) delete mode 100644 Editor/AssemblyInfo.cs delete mode 100644 Runtime/AssemblyInfo.cs delete mode 100644 Runtime/AssemblyInfo.cs.meta delete mode 100644 Runtime/Extensions/CanvasExtensions.cs create mode 100644 Runtime/Utilities/WebRequestRest/RestArgs.cs rename Runtime/{Extensions/CanvasExtensions.cs.meta => Utilities/WebRequestRest/RestArgs.cs.meta} (86%) create mode 100644 Tests/Rest.meta create mode 100644 Tests/Rest/RestTests.cs rename Editor/AssemblyInfo.cs.meta => Tests/Rest/RestTests.cs.meta (86%) diff --git a/.github/workflows/development-buildandtestupmrelease.yml b/.github/workflows/development-buildandtestupmrelease.yml index acca31f..ffc1ca3 100644 --- a/.github/workflows/development-buildandtestupmrelease.yml +++ b/.github/workflows/development-buildandtestupmrelease.yml @@ -34,8 +34,7 @@ jobs: Run-Unit-Tests: name: Run Unity Unit Tests needs: Validate-Unity - uses: realitycollective/reusableworkflows/.github/workflows/rununityUPMbuild.yml@v2 + uses: realitycollective/reusableworkflows/.github/workflows/rununityUPMbuildmultiversion.yml@v2 with: - unityversion: ${{ needs.Validate-Unity.outputs.unityeditorversion }} dependencies: '{"development": "github.com/realitycollective/com.realitycollective.buildtools.git"}' secrets: inherit \ No newline at end of file diff --git a/Editor/AssemblyInfo.cs b/Editor/AssemblyInfo.cs deleted file mode 100644 index 0d194bd..0000000 --- a/Editor/AssemblyInfo.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Reality Collective. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -#define REALITYCOLLECTIVE_UTILITIES_EDITOR - -using System.Reflection; - -[assembly: AssemblyVersion("1.0.0")] -[assembly: AssemblyTitle("com.realitycollective.utilities.editor")] -[assembly: AssemblyCompany("Reality Collective")] -[assembly: AssemblyCopyright("Copyright (c) Reality Collective. All rights reserved.")] diff --git a/README.md b/README.md index b81d588..b0d4eaa 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,82 @@ A collection of useful utilities for Unity Projects by the Reality Collective. Useful for any Unity Project to accelerate and code safely within Unity. ## What's included? - -tbc. +### Utilities + +* Rest - A collection of Rest based tools to make making and using Rest calls in Unity very easy. +* Async - A collection of Asynchronous and CoRoutine helpers for working with Sync -> Async code. + +### Extensions + +* AnimationCurveExtensions +* ArrayExtensions +* AssemblyExtensions +* BoundsExtensions +* CameraExtensions +* CollectionsExtensions +* ColliderExtensions +* CollisionExtensions +* Color32Extensions +* ComparerExtensions +* ComponentExtensions +* ConverterExtensions +* DoubleExtensions +* EnumerableExtensions +* EnumExtensions +* FloatExtensions +* GameObjectExtensions +* HandednessExtensions +* LayerExtensions +* MathfExtensions +* ProcessExtensions +* QuaternionExtensions +* RayExtensions +* SpriteExtensions +* StringExtensions +* SystemNumericsExtensions +* TextureExtensions +* TransformExtensions +* TypeExtensions +* UnityObjectExtensions +* VectorExtensions + +### Reusable definitions + +* AnimatorParameter +* AutoStartBehavior +* AxisType +* CardinalAxis +* CollationOrderType +* FlattenMode +* Handedness +* OrientationType +* PivotAxis +* ProcessResult +* QuaternionSmoothed +* RecognitionConfidenceLevel +* RenderPipeline +* RotationConstraintType +* ScaleStateType +* SystemType +* TypeGrouping +* Vector3Smoothed + +### Property Attributes + +* EnumFlagsAttribute +* ExtendsAttribute +* Il2CppSetOptionAttribute +* ImplementsAttribute +* PhysicsLayerAttribute +* PrefabAttribute +* SystemTypeAttribute +* Vector3RangeAttribute ## Requirements -- [Unity 2020.3 and above](https://unity.com/) +* [Unity 2020.3 and above](https://unity.com/) ### OpenUPM @@ -19,8 +87,8 @@ tbc. The simplest way to getting started using the utilities package in your project is via OpenUPM. Visit [OpenUPM](https://openupm.com/docs/) to learn more about it. Once you have the OpenUPM CLI set up use the following command to add the package to your project: -``` -`openupm add com.realitycollective.utilities` +```text + openupm add com.realitycollective.utilities ``` > For more details on using [OpenUPM CLI, check the docs here](https://github.com/openupm/openupm-cli#installation). @@ -30,6 +98,5 @@ The simplest way to getting started using the utilities package in your project | branch | build status | | --- | --- | -| main | [![main](https://github.com/realitycollective/com.realitycollective.utilities/actions/workflows/buildupmpackages.yml/badge.svg?branch=main)](https://github.com/realitycollective/com.realitycollective.utilities/actions/workflows/buildupmpackages.yml) | -| development | [![development](https://github.com/realitycollective/com.realitycollective.utilities/actions/workflows/buildupmpackages.yml/badge.svg?branch=development)](https://github.com/realitycollective/com.realitycollective.utilities/actions/workflows/buildupmpackages.yml) | - +| main | [![main](https://github.com/realitycollective/com.realitycollective.utilities/actions/workflows/main-publish.yml/badge.svg?branch=main)](https://github.com/realitycollective/com.realitycollective.utilities/actions/workflows/main-publish.yml) | +| development | [![development](https://github.com/realitycollective/com.realitycollective.utilities/actions/workflows/development-buildandtestupmrelease.yml/badge.svg?branch=development)](https://github.com/realitycollective/com.realitycollective.utilities/actions/workflows/development-buildandtestupmrelease.yml) | diff --git a/Runtime/AssemblyInfo.cs b/Runtime/AssemblyInfo.cs deleted file mode 100644 index ad55b4f..0000000 --- a/Runtime/AssemblyInfo.cs +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Reality Collective. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -#define REALITYCOLLECTIVE_UTILITIES - -using System.Reflection; - -[assembly: AssemblyVersion("1.0.0")] -[assembly: AssemblyTitle("com.realitycollective.utilities")] -[assembly: AssemblyCompany("Reality Collective")] -[assembly: AssemblyCopyright("Copyright (c) Reality Collective. All rights reserved.")] diff --git a/Runtime/AssemblyInfo.cs.meta b/Runtime/AssemblyInfo.cs.meta deleted file mode 100644 index c88029c..0000000 --- a/Runtime/AssemblyInfo.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: ed4efc21f337765448a1c29fec28b9ed -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {fileID: 2800000, guid: 8ac5213854cf4dbabd140decf8df1946, type: 3} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/Attributes/Il2CppSetOptionAttribute.cs b/Runtime/Attributes/Il2CppSetOptionAttribute.cs index b35f45f..68674db 100644 --- a/Runtime/Attributes/Il2CppSetOptionAttribute.cs +++ b/Runtime/Attributes/Il2CppSetOptionAttribute.cs @@ -1,3 +1,6 @@ +// Copyright (c) Reality Collective. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + using System; namespace Unity.IL2CPP.CompilerServices diff --git a/Runtime/Definitions/Utilities/AutoStartBehavior.cs b/Runtime/Definitions/Utilities/AutoStartBehavior.cs index 9fb5779..4db40d9 100644 --- a/Runtime/Definitions/Utilities/AutoStartBehavior.cs +++ b/Runtime/Definitions/Utilities/AutoStartBehavior.cs @@ -1,4 +1,4 @@ -// Copyright (c) XRTK. All rights reserved. +// Copyright (c) Reality Collective. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. namespace RealityCollective.Definitions.Utilities diff --git a/Runtime/Definitions/Utilities/AxisType.cs b/Runtime/Definitions/Utilities/AxisType.cs index 0542015..300d7a3 100644 --- a/Runtime/Definitions/Utilities/AxisType.cs +++ b/Runtime/Definitions/Utilities/AxisType.cs @@ -1,4 +1,4 @@ -// Copyright (c) XRTK. All rights reserved. +// Copyright (c) Reality Collective. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. namespace RealityCollective.Definitions.Utilities diff --git a/Runtime/Definitions/Utilities/CardinalAxis.cs b/Runtime/Definitions/Utilities/CardinalAxis.cs index 34ac248..afa97a6 100644 --- a/Runtime/Definitions/Utilities/CardinalAxis.cs +++ b/Runtime/Definitions/Utilities/CardinalAxis.cs @@ -1,4 +1,4 @@ -// Copyright (c) XRTK. All rights reserved. +// Copyright (c) Reality Collective. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using System; diff --git a/Runtime/Definitions/Utilities/FlattenMode.cs b/Runtime/Definitions/Utilities/FlattenMode.cs index 4078d6f..335409d 100644 --- a/Runtime/Definitions/Utilities/FlattenMode.cs +++ b/Runtime/Definitions/Utilities/FlattenMode.cs @@ -1,4 +1,4 @@ -// Copyright (c) XRTK. All rights reserved. +// Copyright (c) Reality Collective. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. namespace RealityCollective.Definitions.Utilities diff --git a/Runtime/Definitions/Utilities/RenderPipeline.cs b/Runtime/Definitions/Utilities/RenderPipeline.cs index 966d190..defca4c 100644 --- a/Runtime/Definitions/Utilities/RenderPipeline.cs +++ b/Runtime/Definitions/Utilities/RenderPipeline.cs @@ -1,4 +1,4 @@ -// Copyright (c) XRTK. All rights reserved. +// Copyright (c) Reality Collective. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. namespace RealityCollective.Definitions.Utilities diff --git a/Runtime/Definitions/Utilities/SystemType.cs b/Runtime/Definitions/Utilities/SystemType.cs index 5097f45..05300aa 100644 --- a/Runtime/Definitions/Utilities/SystemType.cs +++ b/Runtime/Definitions/Utilities/SystemType.cs @@ -1,4 +1,4 @@ -// Copyright (c) XRTK. All rights reserved. +// Copyright (c) Reality Collective. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using System; diff --git a/Runtime/Extensions/CanvasExtensions.cs b/Runtime/Extensions/CanvasExtensions.cs deleted file mode 100644 index 25a559a..0000000 --- a/Runtime/Extensions/CanvasExtensions.cs +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See LICENSE in the project root for license information. - -using RealityCollective.Utilities; -using UnityEngine; -using UnityEngine.UI; - -namespace RealityCollective.Extensions -{ - /// - /// Extensions for the Canvas class. - /// - public static class CanvasExtensions - { - /// - /// Convenience method for getting a plane for this canvas in world coordinates. - /// - /// The canvas to get the plane from. - /// A Plane for this canvas. - public static Plane GetPlane(this Canvas canvas) - { - Vector3[] corners = canvas.GetWorldCorners(); - - // Now set a plane from any of the 3 corners (clockwise) so that we can compute our gaze intersection - Plane plane = new Plane(corners[0], corners[1], corners[2]); - - return plane; - } - - /// - /// Convenience method for getting the corners of the canvas in world coordinates. Ordered clockwise from bottom-left. - /// - /// The canvas to get the world corners from. - /// An array of Vector3s that represent the corners of the canvas in world coordinates. - public static Vector3[] GetWorldCorners(this Canvas canvas) - { - Vector3[] worldCorners = new Vector3[4]; - RectTransform rect = canvas.GetComponent(); - rect.GetWorldCorners(worldCorners); - return worldCorners; - } - - /// - /// Convenience method for getting the corners of the canvas in local coordinates. Ordered clockwise from bottom-left. - /// - /// The canvas to get the local corners from. - /// An array of Vector3s that represent the corners of the canvas in local coordinates. - public static Vector3[] GetLocalCorners(this Canvas canvas) - { - Vector3[] localCorners = new Vector3[4]; - RectTransform rect = canvas.GetComponent(); - rect.GetLocalCorners(localCorners); - return localCorners; - } - - /// - /// Convenience method for getting the corners of the canvas in viewport coordinates. Note - /// that the points have the same ordering as the array returned in GetWorldCorners() - /// - /// The canvas to get the viewport corners from - /// An array of Vector3s that represent the corners of the canvas in viewport coordinates - public static Vector3[] GetViewportCorners(this Canvas canvas) - { - Vector3[] viewportCorners = new Vector3[4]; - - Vector3[] worldCorners = canvas.GetWorldCorners(); - - for (int i = 0; i < 4; i++) - { - viewportCorners[i] = Camera.main.WorldToViewportPoint(worldCorners[i]); - } - - return viewportCorners; - } - - /// - /// Gets the position of the corners for a canvas in screen space. - /// 1 -- 2 - /// | | - /// 0 -- 3 - /// - /// The canvas to get the screen corners for. - /// - public static Vector3[] GetScreenCorners(this Canvas canvas) - { - Vector3[] screenCorners = new Vector3[4]; - Vector3[] worldCorners = canvas.GetWorldCorners(); - - for (int i = 0; i < 4; i++) - { - screenCorners[i] = Camera.main.WorldToScreenPoint(worldCorners[i]); - } - - return screenCorners; - } - - /// - /// Returns a rectangle in screen coordinates that encompasses the bounds of the target canvas. - /// - /// The canvas the get the screen rect for - /// - public static Rect GetScreenRect(this Canvas canvas) - { - Vector3[] screenCorners = canvas.GetScreenCorners(); - float x = Mathf.Min(screenCorners[0].x, screenCorners[1].x); - float y = Mathf.Min(screenCorners[0].y, screenCorners[3].y); - float xMax = Mathf.Max(screenCorners[2].x, screenCorners[3].x); - float yMax = Mathf.Max(screenCorners[1].y, screenCorners[2].y); - return new Rect(x, y, xMax - x, yMax - y); - } - - /// - /// Raycast against a canvas using a ray. - /// - /// The canvas to raycast against - /// The origin of the ray - /// The direction of the ray - /// The distance of the ray - /// The hit point of the ray - /// The child object that was hit or the canvas itself if it has no active children that were within the hit range. - /// - public static bool Raycast(this Canvas canvas, Vector3 rayOrigin, Vector3 rayDirection, out float distance, out Vector3 hitPoint, out GameObject hitChildObject) - { - hitChildObject = null; - Plane plane = canvas.GetPlane(); - Ray ray = new Ray(rayOrigin, rayDirection); - - if (plane.Raycast(ray, out distance)) - { - // See if the point lies within the local canvas rect of the plane - Vector3[] corners = canvas.GetLocalCorners(); - hitPoint = rayOrigin + (rayDirection.normalized * distance); - Vector3 localHitPoint = canvas.transform.InverseTransformPoint(hitPoint); - if (localHitPoint.x >= corners[0].x - && localHitPoint.x <= corners[3].x - && localHitPoint.y <= corners[2].y - && localHitPoint.y >= corners[3].y) - { - hitChildObject = canvas.gameObject; - - // look for the child object that was hit - RectTransform rectTransform = GetChildRectTransformAtPoint(canvas.GetComponent(), hitPoint, true, true, true); - if (rectTransform != null) - { - hitChildObject = rectTransform.gameObject; - } - else - { - hitChildObject = canvas.gameObject; - } - - return true; - } - } - - hitPoint = Vector3.zero; - - return false; - } - - /// - /// Gets a child rect transform for the given point and parameters. - /// - /// The rect transform to look for children that may contain the projected (orthogonal to the child's normal) world point - /// The world point - /// Indicates if the check should be done recursively - /// If true, will only check children that are active, otherwise it will check all children. - /// If true, will only check children that if they have a graphic and have it's member raycastTarget set to true, otherwise will ignore the raycastTarget value. Will still allow children to be checked that do not have a graphic component. - /// - public static RectTransform GetChildRectTransformAtPoint(this RectTransform rectTransformParent, Vector3 worldPoint, bool recursive, bool shouldReturnActive, bool shouldReturnRaycastable) - { - Vector3[] localCorners = new Vector3[4]; - Vector3 childLocalPoint; - RectTransform rectTransform; - bool shouldRaycast = false; - - for (int i = rectTransformParent.childCount - 1; i >= 0; i--) - { - rectTransform = rectTransformParent.GetChild(i).GetComponent(); - Graphic graphic = rectTransform.GetComponent(); - shouldRaycast = ((shouldReturnRaycastable && !graphic.IsNull() && graphic.raycastTarget) || graphic.IsNull() || !shouldReturnRaycastable); - - if (((shouldReturnActive && rectTransform.gameObject.activeSelf) || !shouldReturnActive)) - { - rectTransform.GetLocalCorners(localCorners); - childLocalPoint = rectTransform.InverseTransformPoint(worldPoint); - - if (recursive) - { - RectTransform childRect = GetChildRectTransformAtPoint(rectTransform, worldPoint, true, shouldReturnActive, shouldReturnRaycastable); - - if (childRect != null) - { - return childRect; - } - } - - if (shouldRaycast - && childLocalPoint.x >= localCorners[0].x - && childLocalPoint.x <= localCorners[3].x - && childLocalPoint.y <= localCorners[2].y - && childLocalPoint.y >= localCorners[3].y) - { - return rectTransform; - } - } - } - - return null; - } - } -} \ No newline at end of file diff --git a/Runtime/Extensions/ColliderExtensions.cs b/Runtime/Extensions/ColliderExtensions.cs index 662a9d3..54316d6 100644 --- a/Runtime/Extensions/ColliderExtensions.cs +++ b/Runtime/Extensions/ColliderExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) XRTK. All rights reserved. +// Copyright (c) Reality Collective. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using UnityEngine; diff --git a/Runtime/Extensions/CollisionExtensions.cs b/Runtime/Extensions/CollisionExtensions.cs index ad10768..eef0562 100644 --- a/Runtime/Extensions/CollisionExtensions.cs +++ b/Runtime/Extensions/CollisionExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) XRTK. All rights reserved. +// Copyright (c) Reality Collective. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using UnityEngine; diff --git a/Runtime/Extensions/ComponentExtensions.cs b/Runtime/Extensions/ComponentExtensions.cs index 32db900..8f65184 100644 --- a/Runtime/Extensions/ComponentExtensions.cs +++ b/Runtime/Extensions/ComponentExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) XRTK. All rights reserved. +// Copyright (c) Reality Collective. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using System; diff --git a/Runtime/Extensions/EnumExtensions.cs b/Runtime/Extensions/EnumExtensions.cs index bed2ad2..7124905 100644 --- a/Runtime/Extensions/EnumExtensions.cs +++ b/Runtime/Extensions/EnumExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) XRTK. All rights reserved. +// Copyright (c) Reality Collective. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using System; diff --git a/Runtime/Extensions/GameObjectExtensions.cs b/Runtime/Extensions/GameObjectExtensions.cs index 6ebe2aa..8e63ca5 100644 --- a/Runtime/Extensions/GameObjectExtensions.cs +++ b/Runtime/Extensions/GameObjectExtensions.cs @@ -6,7 +6,6 @@ using System.IO; using System.Runtime.CompilerServices; using UnityEngine; -using UnityEngine.UI; namespace RealityCollective.Extensions { @@ -154,11 +153,6 @@ public static void SetRenderingActive(this GameObject gameObject, bool isActive, renderer.enabled = isActive; } - foreach (var graphic in gameObject.GetComponentsInChildren()) - { - graphic.enabled = isActive; - } - if (includeColliders) { foreach (var collider in gameObject.GetComponentsInChildren()) diff --git a/Runtime/Extensions/StringExtensions.cs b/Runtime/Extensions/StringExtensions.cs index 32ec0ec..622e04a 100644 --- a/Runtime/Extensions/StringExtensions.cs +++ b/Runtime/Extensions/StringExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) XRTK. All rights reserved. +// Copyright (c) Reality Collective. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using System; diff --git a/Runtime/Extensions/SystemNumericsExtensions.cs b/Runtime/Extensions/SystemNumericsExtensions.cs index d83bf8c..d50fcdb 100644 --- a/Runtime/Extensions/SystemNumericsExtensions.cs +++ b/Runtime/Extensions/SystemNumericsExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) XRTK. All rights reserved. +// Copyright (c) Reality Collective. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. namespace RealityCollective.Extensions diff --git a/Runtime/Extensions/TypeExtensions.cs b/Runtime/Extensions/TypeExtensions.cs index 865c6e1..0d24864 100644 --- a/Runtime/Extensions/TypeExtensions.cs +++ b/Runtime/Extensions/TypeExtensions.cs @@ -1,4 +1,4 @@ -// Copyright (c) XRTK. All rights reserved. +// Copyright (c) Reality Collective. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using RealityCollective.Utilities; diff --git a/Runtime/Extensions/VectorExtensions.cs b/Runtime/Extensions/VectorExtensions.cs index a5222e3..9a908ae 100644 --- a/Runtime/Extensions/VectorExtensions.cs +++ b/Runtime/Extensions/VectorExtensions.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using UnityEngine; -using UnityEngine.EventSystems; namespace RealityCollective.Extensions { @@ -396,27 +395,6 @@ public static Vector3 ScatterMapping(this Vector3 source, float radius) return source; } - /// - /// Determine the move direction based off of the direction provided - /// - /// - /// - /// - public static MoveDirection DetermineMoveDirection(this Vector2 direction, float deadZone = 0.6f) - { - if (direction.sqrMagnitude < deadZone * deadZone) - { - return MoveDirection.None; - } - - if (Mathf.Abs(direction.x) > Mathf.Abs(direction.y)) - { - return direction.x > 0 ? MoveDirection.Right : MoveDirection.Left; - } - - return direction.y > 0 ? MoveDirection.Up : MoveDirection.Down; - } - /// /// Checks if a normal is nearly vertical /// diff --git a/Runtime/Utilities/Async/AwaitYieldInstructions/BackgroundThread.cs b/Runtime/Utilities/Async/AwaitYieldInstructions/BackgroundThread.cs index b13d7bb..077e13a 100644 --- a/Runtime/Utilities/Async/AwaitYieldInstructions/BackgroundThread.cs +++ b/Runtime/Utilities/Async/AwaitYieldInstructions/BackgroundThread.cs @@ -1,3 +1,6 @@ +// Copyright (c) Reality Collective. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + using System.Runtime.CompilerServices; using System.Threading.Tasks; diff --git a/Runtime/Utilities/TypeCache.cs b/Runtime/Utilities/TypeCache.cs index 9662258..94ec620 100644 --- a/Runtime/Utilities/TypeCache.cs +++ b/Runtime/Utilities/TypeCache.cs @@ -1,4 +1,4 @@ -// Copyright (c) XRTK. All rights reserved. +// Copyright (c) Reality Collective. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using RealityCollective.Extensions; diff --git a/Runtime/Utilities/WebRequestRest/Response.cs b/Runtime/Utilities/WebRequestRest/Response.cs index 6ae92ca..2f14e3c 100644 --- a/Runtime/Utilities/WebRequestRest/Response.cs +++ b/Runtime/Utilities/WebRequestRest/Response.cs @@ -1,4 +1,4 @@ -// Copyright (c) XRTK. All rights reserved. +// Copyright (c) Reality Collective. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. namespace RealityCollective.Utilities.WebRequestRest diff --git a/Runtime/Utilities/WebRequestRest/Rest.cs b/Runtime/Utilities/WebRequestRest/Rest.cs index 7bd8146..09511ec 100644 --- a/Runtime/Utilities/WebRequestRest/Rest.cs +++ b/Runtime/Utilities/WebRequestRest/Rest.cs @@ -1,18 +1,17 @@ -// Copyright (c) XRTK. All rights reserved. +// Copyright (c) Reality Collective. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. +using RealityCollective.Utilities.Async; using System; -using System.Collections.Generic; using System.IO; using System.Linq; +using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; -using RealityCollective.Utilities.Async; using UnityEngine; using UnityEngine.Networking; using UnityEngine.ResourceManagement.ResourceProviders; -using RealityCollective.Extensions; namespace RealityCollective.Utilities.WebRequestRest { @@ -57,54 +56,12 @@ public static string GetBearerOAuthToken(string authToken) /// Rest GET. /// /// Finalized Endpoint Query with parameters. - /// Optional header information for the request. - /// Optional handler. - /// Optional time in seconds before request expires. - /// The response data. - public static async Task GetAsync(string query, Dictionary headers = null, IProgress progress = null, int timeout = -1) - { - using (var webRequest = UnityWebRequest.Get(query)) - { - return await ProcessRequestAsync(webRequest, headers, progress, timeout); - } - } - - /// - /// Rest GET. - /// - /// Finalized Endpoint Query with parameters. - /// Optional header information for the request. - /// Optional time in seconds before request expires. - /// Optional DownloadHandler for the request. - /// Optional bool. If its true, response data will be read from web request download handler. - /// Optional certificate handler for custom certificate verification - /// Optional bool. If true and is not null, will be disposed, when the underlying UnityWebRequest is disposed. + /// Optional additional argumens for the Rest request, . /// The response data. - public static async Task GetAsync( - string query, - DownloadHandler downloadHandler = null, - bool readResponseData = false, - CertificateHandler certificateHandler = null, - bool disposeCertificateHandlerOnDispose = true, - CancellationToken cancellationToken = default(CancellationToken), - Dictionary headers = null, - IProgress progress = null, - int timeout = -1 - ) + public static async Task GetAsync(string query, RestArgs getArgs = default) { - using (var webRequest = UnityWebRequest.Get(query)) - { - if (downloadHandler != null) - { - webRequest.downloadHandler = downloadHandler; - } - cancellationToken.Register(() => - { - webRequest.Abort(); - }); - - return await ProcessRequestAsync(webRequest, headers, progress, timeout, readResponseData, certificateHandler, disposeCertificateHandlerOnDispose); - } + using var webRequest = UnityWebRequest.Get(query); + return await ProcessRequestAsync(webRequest, getArgs); } #endregion GET @@ -115,16 +72,16 @@ public static async Task GetAsync( /// Rest POST. /// /// Finalized Endpoint Query with parameters. - /// Optional header information for the request. - /// Optional handler. - /// Optional time in seconds before request expires. + /// Optional additional argumens for the Rest request, . /// The response data. - public static async Task PostAsync(string query, Dictionary headers = null, IProgress progress = null, int timeout = -1) + public static async Task PostAsync(string query, RestArgs postArgs = default) { - using (var webRequest = UnityWebRequest.Post(query, null as string)) - { - return await ProcessRequestAsync(webRequest, headers, progress, timeout); - } +#if UNITY_2022_2_OR_NEWER + using var webRequest = UnityWebRequest.PostWwwForm(query, null); +#else + using var webRequest = UnityWebRequest.Post(query, null as string); +#endif + return await ProcessRequestAsync(webRequest, postArgs); } /// @@ -132,16 +89,13 @@ public static async Task PostAsync(string query, Dictionary /// Finalized Endpoint Query with parameters. /// Form Data. - /// Optional header information for the request. - /// Optional handler. - /// Optional time in seconds before request expires. + /// Optional additional argumens for the Rest request, . /// The response data. - public static async Task PostAsync(string query, WWWForm formData, Dictionary headers = null, IProgress progress = null, int timeout = -1) + public static async Task PostAsync(string query, WWWForm formData, RestArgs postArgs = default) { - using (var webRequest = UnityWebRequest.Post(query, formData)) - { - return await ProcessRequestAsync(webRequest, headers, progress, timeout); - } + + using var webRequest = UnityWebRequest.Post(query, formData); + return await ProcessRequestAsync(webRequest, postArgs); } /// @@ -149,21 +103,21 @@ public static async Task PostAsync(string query, WWWForm formData, Dic /// /// Finalized Endpoint Query with parameters. /// JSON data for the request. - /// Optional header information for the request. - /// Optional handler. - /// Optional time in seconds before request expires. + /// Optional additional argumens for the Rest request, . /// The response data. - public static async Task PostAsync(string query, string jsonData, Dictionary headers = null, IProgress progress = null, int timeout = -1) + public static async Task PostAsync(string query, string jsonData, RestArgs postArgs = default) { - using (var webRequest = UnityWebRequest.Post(query, "POST")) - { - var data = new UTF8Encoding().GetBytes(jsonData); - webRequest.uploadHandler = new UploadHandlerRaw(data); - webRequest.downloadHandler = new DownloadHandlerBuffer(); - webRequest.SetRequestHeader("Content-Type", "application/json"); - webRequest.SetRequestHeader("Accept", "application/json"); - return await ProcessRequestAsync(webRequest, headers, progress, timeout); - } +#if UNITY_2022_2_OR_NEWER + using var webRequest = UnityWebRequest.PostWwwForm(query, "POST"); +#else + using var webRequest = UnityWebRequest.Post(query, "POST"); +#endif + var data = new UTF8Encoding().GetBytes(jsonData); + webRequest.uploadHandler = new UploadHandlerRaw(data); + webRequest.downloadHandler = new DownloadHandlerBuffer(); + webRequest.SetRequestHeader("Content-Type", "application/json"); + webRequest.SetRequestHeader("Accept", "application/json"); + return await ProcessRequestAsync(webRequest, postArgs); } /// @@ -171,83 +125,19 @@ public static async Task PostAsync(string query, string jsonData, Dict /// /// Finalized Endpoint Query with parameters. /// The raw data to post. - /// Optional header information for the request. - /// Optional handler. - /// Optional time in seconds before request expires. - /// The response data. - public static async Task PostAsync(string query, byte[] bodyData, Dictionary headers = null, IProgress progress = null, int timeout = -1) - { - using (var webRequest = UnityWebRequest.Post(query, "POST")) - { - webRequest.uploadHandler = new UploadHandlerRaw(bodyData); - webRequest.downloadHandler = new DownloadHandlerBuffer(); - webRequest.SetRequestHeader("Content-Type", "application/octet-stream"); - return await ProcessRequestAsync(webRequest, headers, progress, timeout); - } - } - - /// - /// Rest POST. - /// - /// Finalized Endpoint Query with parameters. - /// Optional header information for the request. - /// Optional time in seconds before request expires. - /// Optional bool. If its true, response data will be read from web request download handler. - /// Optional certificate handler for custom certificate verification - /// Optional bool. If true and is not null, will be disposed, when the underlying UnityWebRequest is disposed. - /// The response data. - public static async Task PostAsync( - string query, - bool readResponseData = false, - CertificateHandler certificateHandler = null, - bool disposeCertificateHandlerOnDispose = true, - CancellationToken cancellationToken = default(CancellationToken), - Dictionary headers = null, - IProgress progress = null, - int timeout = -1 -) - { - using (var webRequest = UnityWebRequest.Post(query, null as string)) - { - cancellationToken.Register(() => - { - webRequest.Abort(); - }); - return await ProcessRequestAsync(webRequest, headers, progress, timeout, readResponseData, certificateHandler, disposeCertificateHandlerOnDispose); - } - } - - /// - /// Rest POST. - /// - /// Finalized Endpoint Query with parameters. - /// Form Data. - /// Optional header information for the request. - /// Optional time in seconds before request expires. - /// Optional bool. If its true, response data will be read from web request download handler. - /// Optional certificate handler for custom certificate verification - /// Optional bool. If true and is not null, will be disposed, when the underlying UnityWebRequest is disposed. + /// Optional additional argumens for the Rest request, . /// The response data. - public static async Task PostAsync( - string query, - WWWForm formData, - bool readResponseData = false, - CertificateHandler certificateHandler = null, - bool disposeCertificateHandlerOnDispose = true, - CancellationToken cancellationToken = default(CancellationToken), - Dictionary headers = null, - IProgress progress = null, - int timeout = -1 - ) + public static async Task PostAsync(string query, byte[] bodyData, RestArgs postArgs = default) { - using (var webRequest = UnityWebRequest.Post(query, formData)) - { - cancellationToken.Register(() => - { - webRequest.Abort(); - }); - return await ProcessRequestAsync(webRequest, headers, progress, timeout, readResponseData, certificateHandler, disposeCertificateHandlerOnDispose); - } +#if UNITY_2022_2_OR_NEWER + using var webRequest = UnityWebRequest.PostWwwForm(query, "POST"); +#else + using var webRequest = UnityWebRequest.Post(query, "POST"); +#endif + webRequest.uploadHandler = new UploadHandlerRaw(bodyData); + webRequest.downloadHandler = new DownloadHandlerBuffer(); + webRequest.SetRequestHeader("Content-Type", "application/octet-stream"); + return await ProcessRequestAsync(webRequest, postArgs); } #endregion POST @@ -259,17 +149,13 @@ public static async Task PostAsync( /// /// Finalized Endpoint Query with parameters. /// Data to be submitted. - /// Optional header information for the request. - /// Optional handler. - /// Optional time in seconds before request expires. + /// Optional additional argumens for the Rest request, . /// The response data. - public static async Task PutAsync(string query, string jsonData, Dictionary headers = null, IProgress progress = null, int timeout = -1) + public static async Task PutAsync(string query, string jsonData, RestArgs putArgs = default) { - using (var webRequest = UnityWebRequest.Put(query, jsonData)) - { - webRequest.SetRequestHeader("Content-Type", "application/json"); - return await ProcessRequestAsync(webRequest, headers, progress, timeout); - } + using var webRequest = UnityWebRequest.Put(query, jsonData); + webRequest.SetRequestHeader("Content-Type", "application/json"); + return await ProcessRequestAsync(webRequest, putArgs); } /// @@ -277,51 +163,13 @@ public static async Task PutAsync(string query, string jsonData, Dicti /// /// Finalized Endpoint Query with parameters. /// Data to be submitted. - /// Optional header information for the request. - /// Optional handler. - /// Optional time in seconds before request expires. + /// Optional additional argumens for the Rest request, . /// The response data. - public static async Task PutAsync(string query, byte[] bodyData, Dictionary headers = null, IProgress progress = null, int timeout = -1) + public static async Task PutAsync(string query, byte[] bodyData, RestArgs putArgs = default) { - using (var webRequest = UnityWebRequest.Put(query, bodyData)) - { - webRequest.SetRequestHeader("Content-Type", "application/octet-stream"); - return await ProcessRequestAsync(webRequest, headers, progress, timeout); - } - } - - /// - /// Rest PUT. - /// - /// Finalized Endpoint Query with parameters. - /// Data to be submitted. - /// Optional header information for the request. - /// Optional time in seconds before request expires. - /// Optional bool. If its true, response data will be read from web request download handler. - /// Optional certificate handler for custom certificate verification - /// Optional bool. If true and is not null, will be disposed, when the underlying UnityWebRequest is disposed. - /// The response data. - public static async Task PutAsync( - string query, - byte[] bodyData, - bool readResponseData = false, - CertificateHandler certificateHandler = null, - bool disposeCertificateHandlerOnDispose = true, - CancellationToken cancellationToken = default(CancellationToken), - Dictionary headers = null, - IProgress progress = null, - int timeout = -1 - ) - { - using (var webRequest = UnityWebRequest.Put(query, bodyData)) - { - cancellationToken.Register(() => - { - webRequest.Abort(); - }); - webRequest.SetRequestHeader("Content-Type", "application/octet-stream"); - return await ProcessRequestAsync(webRequest, headers, progress, timeout, readResponseData, certificateHandler, disposeCertificateHandlerOnDispose); - } + using var webRequest = UnityWebRequest.Put(query, bodyData); + webRequest.SetRequestHeader("Content-Type", "application/octet-stream"); + return await ProcessRequestAsync(webRequest, putArgs); } #endregion PUT @@ -332,55 +180,42 @@ public static async Task PutAsync( /// Rest DELETE. /// /// Finalized Endpoint Query with parameters. - /// Optional header information for the request. - /// Optional handler. - /// Optional time in seconds before request expires. - /// The response data. - public static async Task DeleteAsync(string query, Dictionary headers = null, IProgress progress = null, int timeout = -1) - { - using (var webRequest = UnityWebRequest.Delete(query)) - { - return await ProcessRequestAsync(webRequest, headers, progress, timeout); - } - } - - /// - /// Rest DELETE. - /// - /// Finalized Endpoint Query with parameters. - /// Optional header information for the request. - /// Optional time in seconds before request expires. - /// Optional bool. If its true, response data will be read from web request download handler. - /// Optional certificate handler for custom certificate verification - /// Optional bool. If true and is not null, will be disposed, when the underlying UnityWebRequest is disposed. + /// Optional additional argumens for the Rest request, . /// The response data. - public static async Task DeleteAsync( - string query, - bool readResponseData = false, - CertificateHandler certificateHandler = null, - bool disposeCertificateHandlerOnDispose = true, - CancellationToken cancellationToken = default(CancellationToken), - Dictionary headers = null, - IProgress progress = null, - int timeout = -1 - ) + public static async Task DeleteAsync(string query, RestArgs deleteArgs = default) { - using (var webRequest = UnityWebRequest.Delete(query)) - { - cancellationToken.Register(() => - { - webRequest.Abort(); - }); - return await ProcessRequestAsync(webRequest, headers, progress, timeout,readResponseData, certificateHandler, disposeCertificateHandlerOnDispose); - } + using var webRequest = UnityWebRequest.Delete(query); + return await ProcessRequestAsync(webRequest, deleteArgs); } #endregion DELETE #region Get Multimedia Content - public static string DownloadCacheDirectory => $"{Application.temporaryCachePath}{Path.DirectorySeparatorChar}download_cache"; + #region Download Cache + private const string DOWNLOAD_CACHE = "download_cache"; + + /// + /// Generates a based on the string. + /// + /// The string to generate the . + /// A new that represents the string. + private static Guid GenerateGuid(string @string) + { + using MD5 md5 = MD5.Create(); + return new Guid(md5.ComputeHash(Encoding.Default.GetBytes(@string))); + } + + /// + /// The download cache directory. + /// + public static string DownloadCacheDirectory + => Path.Combine(Application.temporaryCachePath, DOWNLOAD_CACHE); + + /// + /// Creates the if it does not exist. + /// public static void ValidateCacheDirectory() { if (!Directory.Exists(DownloadCacheDirectory)) @@ -398,8 +233,18 @@ public static void ValidateCacheDirectory() public static bool TryGetDownloadCacheItem(string uri, out string filePath) { ValidateCacheDirectory(); - filePath = $"{DownloadCacheDirectory}{Path.DirectorySeparatorChar}{uri.GenerateGuid()}"; - var exists = File.Exists(filePath); + bool exists; + + if (TryGetFileNameFromUrl(uri, out var fileName)) + { + filePath = Path.Combine(DownloadCacheDirectory, fileName); + exists = File.Exists(filePath); + } + else + { + filePath = Path.Combine(DownloadCacheDirectory, GenerateGuid(uri).ToString()); + exists = File.Exists(filePath); + } if (exists) { @@ -416,21 +261,21 @@ public static bool TryGetDownloadCacheItem(string uri, out string filePath) /// True, if the cached item was successfully deleted. public static bool TryDeleteCacheItem(string uri) { - if (TryGetDownloadCacheItem(uri, out var filePath)) + if (!TryGetDownloadCacheItem(uri, out var filePath)) { - try - { - File.Delete(filePath); - } - catch (Exception e) - { - Debug.LogError(e); - } + return false; + } - return !File.Exists(filePath); + try + { + File.Delete(filePath); + } + catch (Exception e) + { + Debug.LogError(e); } - return false; + return !File.Exists(filePath); } /// @@ -444,56 +289,77 @@ public static void DeleteDownloadCache() } } + /// + /// Find the filename based on the url. + /// + /// The url to parse to try to guess file name. + /// The filename if found. + /// True, if a valid filename is found. + private static bool TryGetFileNameFromUrl(string url, out string fileName) + { + var baseUrl = url.Split('?')[0]; + var index = baseUrl.LastIndexOf('/') + 1; + fileName = baseUrl.Substring(index, baseUrl.Length - index); + return Path.HasExtension(fileName); + } + + #endregion Download Cache + /// /// Download a from the provided . /// /// The url to download the from. - /// Optional header information for the request. - /// Optional handler. - /// Optional time in seconds before request expires. + /// Optional, file name to download (including extension). + /// Optional additional argumens for the Rest request, . /// A new instance. - public static async Task DownloadTextureAsync(string url, Dictionary headers = null, IProgress progress = null, int timeout = -1) + public static async Task DownloadTextureAsync(string url, string fileName = null, RestArgs downloadTextureArgs = default) { await Awaiters.UnityMainThread; - bool isCached = TryGetDownloadCacheItem(url, out var cachePath); + bool isCached; + string cachePath; - if (isCached) + if (string.IsNullOrWhiteSpace(fileName)) { - url = cachePath; + TryGetFileNameFromUrl(url, out fileName); } - using (var webRequest = UnityWebRequestTexture.GetTexture(url)) + if (url.Contains("file://")) { - var response = await ProcessRequestAsync(webRequest, headers, progress, timeout); + isCached = true; + cachePath = url; + } + else + { + isCached = TryGetDownloadCacheItem(fileName, out cachePath); + } - if (!response.Successful) - { - Debug.LogError($"Failed to download texture from \"{url}\"!"); + using var webRequest = UnityWebRequestTexture.GetTexture(url); + var response = await ProcessRequestAsync(webRequest, downloadTextureArgs); - return null; - } + if (!response.Successful) + { + Debug.LogError(GenerateErrorMessage("texture", url, response)); + return null; + } - var downloadHandler = (DownloadHandlerTexture)webRequest.downloadHandler; + var downloadHandler = (DownloadHandlerTexture)webRequest.downloadHandler; - if (!isCached && - !File.Exists(cachePath)) + if (downloadTextureArgs.ForceDownload || (!isCached && + !File.Exists(cachePath))) + { + try { - try - { - using (var fileStream = File.OpenWrite(cachePath)) - { - await fileStream.WriteAsync(downloadHandler.data, 0, downloadHandler.data.Length, CancellationToken.None); - } - } - catch (Exception e) - { - Debug.LogError($"Failed to write texture to disk!\n{e}"); - } + using var fileStream = File.OpenWrite(cachePath); + await fileStream.WriteAsync(downloadHandler.data, 0, downloadHandler.data.Length, CancellationToken.None); + } + catch (Exception e) + { + Debug.LogError($"Failed to write texture to disk!\n{e}"); } - - return downloadHandler.texture; } + + return downloadHandler.texture; } /// @@ -501,52 +367,62 @@ public static async Task DownloadTextureAsync(string url, Dictionary< /// /// The url to download the from. /// to download. - /// Optional header information for the request. - /// Optional handler. - /// Optional time in seconds before request expires. + /// Optional, file name to download (including extension). + /// Optional additional argumens for the Rest request, . /// A new instance. - public static async Task DownloadAudioClipAsync(string url, AudioType audioType, Dictionary headers = null, IProgress progress = null, int timeout = -1) + public static async Task DownloadAudioClipAsync(string url, AudioType audioType, string fileName = "", RestArgs downloadAudioClipArgs = default) { await Awaiters.UnityMainThread; - bool isCached = TryGetDownloadCacheItem(url, out var cachePath); + if (string.IsNullOrWhiteSpace(fileName)) + { + TryGetFileNameFromUrl(url, out fileName); + } + + bool isCached; + string cachePath; + + if (url.Contains("file://")) + { + isCached = true; + cachePath = url; + } + else + { + isCached = TryGetDownloadCacheItem(fileName, out cachePath); + } if (isCached) { url = cachePath; } - using (var webRequest = UnityWebRequestMultimedia.GetAudioClip(url, audioType)) + using var webRequest = UnityWebRequestMultimedia.GetAudioClip(url, audioType); + var response = await ProcessRequestAsync(webRequest, downloadAudioClipArgs); + + if (!response.Successful) { - var response = await ProcessRequestAsync(webRequest, headers, progress, timeout); + Debug.LogError(GenerateErrorMessage("audio clip", url, response)); + return null; + } - if (!response.Successful) - { - Debug.LogError($"Failed to download audio clip from \"{url}\"!"); + var downloadHandler = (DownloadHandlerAudioClip)webRequest.downloadHandler; - return null; + if (downloadAudioClipArgs.ForceDownload || (!isCached && + !File.Exists(cachePath))) + { + try + { + using var fileStream = File.OpenWrite(cachePath); + await fileStream.WriteAsync(downloadHandler.data, 0, downloadHandler.data.Length, CancellationToken.None); } - - var downloadHandler = (DownloadHandlerAudioClip)webRequest.downloadHandler; - - if (!isCached && - !File.Exists(cachePath)) + catch (Exception e) { - try - { - using (var fileStream = File.OpenWrite(cachePath)) - { - await fileStream.WriteAsync(downloadHandler.data, 0, downloadHandler.data.Length, CancellationToken.None); - } - } - catch (Exception e) - { - Debug.LogError($"Failed to write audio asset to disk! {e}"); - } + Debug.LogError($"Failed to write audio asset to disk! {e}"); } - - return downloadHandler.audioClip; } + + return downloadHandler.audioClip; } /// @@ -554,10 +430,9 @@ public static async Task DownloadAudioClipAsync(string url, AudioType /// /// The url to download the from. /// Asset bundle request options. - /// Optional header information for the request. - /// Optional handler. + /// Optional additional argumens for the Rest request, . /// A new instance. - public static async Task DownloadAssetBundleAsync(string url, AssetBundleRequestOptions options, Dictionary headers = null, IProgress progress = null) + public static async Task DownloadAssetBundleAsync(string url, AssetBundleRequestOptions options, RestArgs downloadAssetBundleArgs = default) { await Awaiters.UnityMainThread; @@ -611,7 +486,11 @@ public static async Task DownloadAssetBundleAsync(string url, Asset try { - response = await ProcessRequestAsync(webRequest, headers, progress, options?.Timeout ?? -1); + if (downloadAssetBundleArgs.Timeout == 0 && options?.Timeout > 0) + { + downloadAssetBundleArgs.Timeout = options.Timeout; + } + response = await ProcessRequestAsync(webRequest, downloadAssetBundleArgs); } catch (Exception e) { @@ -621,7 +500,7 @@ public static async Task DownloadAssetBundleAsync(string url, Asset if (!response.Successful) { - Debug.LogError($"Failed to download asset bundle from \"{url}\"!\n{response.ResponseCode}:{response.ResponseBody}"); + Debug.LogError(GenerateErrorMessage("asset bundle", url, response)); return null; } @@ -635,65 +514,133 @@ public static async Task DownloadAssetBundleAsync(string url, Asset /// /// The url to download the file from. /// Optional file name to download (including extension). - /// Optional header information for the request. - /// Optional handler. - /// Optional time in seconds before request expires. + /// Optional additional argumens for the Rest request, . /// The path to the downloaded file. - public static async Task DownloadFileAsync(string url, string fileName = null, Dictionary headers = null, IProgress progress = null, int timeout = -1) + public static async Task DownloadFileAsync(string url, string fileName = null, RestArgs downloadFileArgs = default) { await Awaiters.UnityMainThread; if (string.IsNullOrWhiteSpace(fileName)) { - // We will try go guess the name based on the url endpoint. - var index = url.LastIndexOf('/'); - fileName = url.Substring(index, url.Length - index); + TryGetFileNameFromUrl(url, out fileName); } - ValidateCacheDirectory(); - var filePath = $"{DownloadCacheDirectory}{Path.DirectorySeparatorChar}{fileName}"; - - if (File.Exists(filePath)) + if (TryGetDownloadCacheItem(fileName, out var filePath) && !downloadFileArgs.ForceDownload) { return filePath; } - using (var webRequest = UnityWebRequest.Get(url)) - using (var fileDownloadHandler = new DownloadHandlerFile(filePath) + using var webRequest = UnityWebRequest.Get(url); + using var fileDownloadHandler = new DownloadHandlerFile(filePath) { removeFileOnAbort = true - }) + }; + + webRequest.downloadHandler = fileDownloadHandler; + var response = await ProcessRequestAsync(webRequest, downloadFileArgs); + + if (!response.Successful) { - webRequest.downloadHandler = fileDownloadHandler; - var response = await ProcessRequestAsync(webRequest, headers, progress, timeout); - fileDownloadHandler.Dispose(); + Debug.LogError(GenerateErrorMessage("file", url, response)); + return null; + } - if (!response.Successful) - { - Debug.LogError($"Failed to download file from \"{url}\"!"); + return filePath; + } - return null; + /// + /// Download a file from the provided and return its contents as a . + /// + /// The url to download the file from. + /// Optional file name to download (including extension). + /// Optional additional argumens for the Rest request, . + /// The path to the downloaded file. + /// Intended for use where you need the raw contents of the target file + public static async Task DownloadFileBytesAsync(string url, string fileName = null, RestArgs downloadFileArgs = default) + { + byte[] bytes = null; + await Awaiters.UnityMainThread; + + if (string.IsNullOrWhiteSpace(fileName)) + { + TryGetFileNameFromUrl(url, out fileName); + } + + if (!TryGetDownloadCacheItem(fileName, out var filePath) || downloadFileArgs.ForceDownload) + { + filePath = await DownloadFileAsync(url, fileName, downloadFileArgs); + } + + if (File.Exists(filePath)) + { + try + { + bytes = File.ReadAllBytes(filePath); + } + catch (Exception ex) + { + Debug.LogError(GenerateErrorMessage("File as Bytes", url, ex)); } + } - return filePath; + return bytes; + } + + /// + /// Download a file from the provided and return its contents as a . + /// + /// The url to download the file from. + /// Optional file name to download (including extension). + /// Optional additional argumens for the Rest request, . + /// The path to the downloaded file. + /// Intended for use in configuration/json files. + public static async Task DownloadFileStringAsync(string url, string fileName = null, RestArgs downloadFileArgs = default) + { + string fileContents = null; + await Awaiters.UnityMainThread; + + if (string.IsNullOrWhiteSpace(fileName)) + { + TryGetFileNameFromUrl(url, out fileName); } + + if (!TryGetDownloadCacheItem(fileName, out string filePath) || downloadFileArgs.ForceDownload) + { + filePath = await DownloadFileAsync(url, fileName, downloadFileArgs); + } + + if (File.Exists(filePath)) + { + try + { + using var sr = new StreamReader(filePath); + fileContents = sr.ReadToEnd(); + } + catch (Exception ex) + { + Debug.LogError(GenerateErrorMessage("File as String", url, ex)); + } + } + + return fileContents; } #endregion Get Multimedia Content #region Private Functions - private static async Task ProcessRequestAsync(UnityWebRequest webRequest, Dictionary headers, IProgress progress, int timeout, bool readResponseData = false, CertificateHandler certificateHandler = null, bool disposeCertificateHandlerOnDispose = true) + + private static async Task ProcessRequestAsync(UnityWebRequest webRequest, RestArgs processArgs) { await Awaiters.UnityMainThread; - if (timeout > 0) + if (processArgs.Timeout > 0) { - webRequest.timeout = timeout; + webRequest.timeout = processArgs.Timeout; } - if (headers != null) + if (processArgs.Headers != null) { - foreach (var header in headers) + foreach (var header in processArgs.Headers) { webRequest.SetRequestHeader(header.Key, header.Value); } @@ -702,7 +649,6 @@ private static async Task ProcessRequestAsync(UnityWebRequest webReque var isUpload = webRequest.method == UnityWebRequest.kHttpVerbPOST || webRequest.method == UnityWebRequest.kHttpVerbPUT; - // HACK: Workaround for extra quotes around boundary. if (isUpload) { var contentType = webRequest.GetRequestHeader("Content-Type"); @@ -714,11 +660,15 @@ private static async Task ProcessRequestAsync(UnityWebRequest webReque } } + webRequest.disposeCertificateHandlerOnDispose = true; + webRequest.disposeDownloadHandlerOnDispose = true; + webRequest.disposeUploadHandlerOnDispose = true; + Thread backgroundThread = null; - if (progress != null) + if (processArgs.Progress != null) { - backgroundThread = new Thread(async () => + async void ProgressReportingThread() { try { @@ -726,7 +676,13 @@ private static async Task ProcessRequestAsync(UnityWebRequest webReque while (!webRequest.isDone) { - progress.Report(isUpload ? webRequest.uploadProgress : webRequest.downloadProgress * 100f); + processArgs.Progress.Report(isUpload ? webRequest.uploadProgress : webRequest.downloadProgress * 100f); + + if (processArgs.CancellationToken.IsCancellationRequested) + { + webRequest.Abort(); + } + await Awaiters.UnityMainThread; } } @@ -734,7 +690,9 @@ private static async Task ProcessRequestAsync(UnityWebRequest webReque { // Throw away } - }) + } + + backgroundThread = new Thread(ProgressReportingThread) { IsBackground = true }; @@ -744,8 +702,11 @@ private static async Task ProcessRequestAsync(UnityWebRequest webReque try { - webRequest.certificateHandler = certificateHandler; - webRequest.disposeCertificateHandlerOnDispose = disposeCertificateHandlerOnDispose; + if (processArgs.CertificateHandler != null) + { + webRequest.certificateHandler = processArgs.CertificateHandler; + webRequest.disposeCertificateHandlerOnDispose = processArgs.DisposeCertificateHandlerOnDispose; + } await webRequest.SendWebRequest(); } catch (Exception e) @@ -754,13 +715,10 @@ private static async Task ProcessRequestAsync(UnityWebRequest webReque } backgroundThread?.Join(); - progress?.Report(100f); + processArgs.Progress?.Report(100f); -#if UNITY_2020_1_OR_NEWER - if (webRequest.result == UnityWebRequest.Result.ConnectionError || webRequest.result == UnityWebRequest.Result.ProtocolError) -#else - if (webRequest.isNetworkError || webRequest.isHttpError) -#endif + if (webRequest.result == + UnityWebRequest.Result.ConnectionError || webRequest.result == UnityWebRequest.Result.ProtocolError) { if (webRequest.responseCode == 401) { @@ -777,10 +735,16 @@ private static async Task ProcessRequestAsync(UnityWebRequest webReque return new Response(false, $"{responseHeaders}\n{webRequest.downloadHandler?.text}", null, webRequest.responseCode); } - if (readResponseData) + if (!string.IsNullOrEmpty(webRequest.downloadHandler?.error)) + { + return new Response(false, webRequest.downloadHandler.error, webRequest.downloadHandler.data, webRequest.responseCode); + } + + if (processArgs.ReadResponseData) { return new Response(true, webRequest.downloadHandler?.text, webRequest.downloadHandler?.data, webRequest.responseCode); } + switch (webRequest.downloadHandler) { case DownloadHandlerFile _: @@ -795,6 +759,16 @@ private static async Task ProcessRequestAsync(UnityWebRequest webReque return new Response(true, webRequest.downloadHandler?.text, webRequest.downloadHandler?.data, webRequest.responseCode); } } + + private static string GenerateErrorMessage(string typeName, string url, Response response) + { + return $"Failed to download {typeName} from \"{url}\"!\n{response.ResponseCode}:{response.ResponseBody}"; + } + + private static string GenerateErrorMessage(string typeName, string url, Exception exception) + { + return $"Failed to download {typeName} from \"{url}\"!\n{exception.Message}\n{exception.StackTrace}"; + } #endregion Private Functions } } \ No newline at end of file diff --git a/Runtime/Utilities/WebRequestRest/RestArgs.cs b/Runtime/Utilities/WebRequestRest/RestArgs.cs new file mode 100644 index 0000000..1de0104 --- /dev/null +++ b/Runtime/Utilities/WebRequestRest/RestArgs.cs @@ -0,0 +1,22 @@ +// Copyright (c) Reality Collective. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading; +using UnityEngine.Networking; + +namespace RealityCollective.Utilities.WebRequestRest +{ + public struct RestArgs + { + public Dictionary Headers; + public IProgress Progress; + public int Timeout; + public CancellationToken CancellationToken; + public bool ReadResponseData; + public CertificateHandler CertificateHandler; + public bool DisposeCertificateHandlerOnDispose; + public bool ForceDownload; + } +} diff --git a/Runtime/Extensions/CanvasExtensions.cs.meta b/Runtime/Utilities/WebRequestRest/RestArgs.cs.meta similarity index 86% rename from Runtime/Extensions/CanvasExtensions.cs.meta rename to Runtime/Utilities/WebRequestRest/RestArgs.cs.meta index e9fe250..033f622 100644 --- a/Runtime/Extensions/CanvasExtensions.cs.meta +++ b/Runtime/Utilities/WebRequestRest/RestArgs.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 8cc79d6829414ef459ce5f6ae7cd3319 +guid: 615b391074810234e8123c22de6794fe MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Tests/Rest.meta b/Tests/Rest.meta new file mode 100644 index 0000000..959d1cd --- /dev/null +++ b/Tests/Rest.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f838482214b5e9141bad631a94a74374 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Rest/RestTests.cs b/Tests/Rest/RestTests.cs new file mode 100644 index 0000000..f36268d --- /dev/null +++ b/Tests/Rest/RestTests.cs @@ -0,0 +1,248 @@ +using NUnit.Framework; +using RealityCollective.Extensions; +using RealityCollective.Utilities.Async; +using RealityCollective.Utilities.WebRequestRest; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Security.AccessControl; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace RealityCollective.Utilities.Tests.Extensions +{ + public class RestTests + { + #region Get Request + + [Test] + public async void Test_01_01_GetRequest() + { + string getURL = "https://httpbin.org/get"; + + await Rest.GetAsync(getURL).ContinueWith((response) => + { + Assert.IsNotNull(response.Result, "No result returned from Get request"); + Assert.IsTrue(response.Result.ResponseCode == 200, $"Bad result from Get request, result code was [{response.Result.ResponseCode}]"); + Assert.IsNotEmpty(response.Result.ResponseBody, "Returned Get result was empty"); + var resultText = System.Text.Encoding.Default.GetString(response.Result.ResponseData); + Assert.IsTrue(response.Result.ResponseBody == resultText, "Returned Text does not match Get response data"); + }); + } + + [Test] + public async void Test_01_02_GetRequestWithHeaders() + { + string getURL = "https://httpbin.org/get"; + Dictionary Headers = new Dictionary(); + Headers.Add("Content-Type", "application/json"); + + await Rest.GetAsync(getURL, new RestArgs() { Headers = Headers }).ContinueWith((response) => + { + Assert.IsNotNull(response.Result, "No result returned from Get request"); + Assert.IsTrue(response.Result.ResponseCode == 200, $"Bad result from Get request, result code was [{response.Result.ResponseCode}]"); + Assert.IsNotEmpty(response.Result.ResponseBody, "Returned Get result was empty"); + Assert.IsTrue(response.Result.ResponseBody.Contains("application/json"), "Get header was missing"); + }); + } + #endregion Get Request + + #region Post Request + + [Test] + public async void Test_02_01_PostRequest() + { + string postURL = "https://httpbin.org/post?data='The Result that was intended'"; + + await Rest.PostAsync(postURL).ContinueWith((response) => + { + Assert.IsNotNull(response.Result, "No result returned from Post request"); + Assert.IsTrue(response.Result.ResponseCode == 200, $"Bad result from Post request, result code was [{response.Result.ResponseCode}]"); + Assert.IsTrue(response.Result.ResponseBody.Contains("The Result that was intended"), "Returned Post string was not as expected"); + }); + } + + [Test] + public async void Test_02_02_PostRequestWithHeaders() + { + string postURL = "https://httpbin.org/post?data='The Result that was intended with headers'"; + + Dictionary Headers = new Dictionary(); + Headers.Add("Content-Type", "application/json"); + + await Rest.PostAsync(postURL, new RestArgs() { Headers = Headers }).ContinueWith((response) => + { + Assert.IsNotNull(response, "No result returned from Post request"); + Assert.IsTrue(response.Result.ResponseCode == 200, $"Bad result from Post request, result code was [{response.Result.ResponseCode}]"); + Assert.IsTrue(response.Result.ResponseBody.Contains("The Result that was intended"), "Returned Post string was not as expected"); + Assert.IsTrue(response.Result.ResponseBody.Contains("application/json"), "Post header was missing"); + }); + } + + [Test] + public async void Test_02_03_PostFormRequest() + { + string postURL = "https://httpbin.org/post"; + WWWForm form = new WWWForm(); + form.AddField("MyForm", "MyData"); + + await Rest.PostAsync(postURL, form).ContinueWith((response) => + { + Assert.IsNotNull(response, "No result returned from Get request"); + Assert.IsTrue(response.Result.ResponseCode == 200, $"Bad result from request, result code was [{response.Result.ResponseCode}]"); + Assert.IsTrue(response.Result.ResponseBody.Contains("MyForm"), "Post MyForm Parameter was missing"); + Assert.IsTrue(response.Result.ResponseBody.Contains("MyData"), "Post MyData value was missing"); + }); + } + + [Serializable] + struct myJson { public string MyJsonProperty; } + + [Test] + public async void Test_02_04_PostJSONRequest() + { + string postURL = "https://httpbin.org/post"; + var input = new myJson() { MyJsonProperty = "MyJsonValue" }; + var json = JsonUtility.ToJson(input); + + await Rest.PostAsync(postURL, json).ContinueWith((response) => + { + Assert.IsNotNull(response, "No result returned from Get request"); + Assert.IsTrue(response.Result.ResponseCode == 200, $"Bad result from Post request, result code was [{response.Result.ResponseCode}]"); + Assert.IsTrue(response.Result.ResponseBody.Contains("MyJsonProperty"),$"JSON Property was not found in th Post response\n[{response.Result.ResponseBody}]"); + Assert.IsTrue(response.Result.ResponseBody.Contains("MyJsonValue"), $"JSON Value was not found in the Post response\n[{response.Result.ResponseBody}]"); + }); + } + + [Test] + public async void Test_02_05_PostBytesRequest() + { + string postURL = "https://httpbin.org/post"; + string postString = "My word in my bond"; + var postBytes = Encoding.UTF8.GetBytes(postString); + + await Rest.PostAsync(postURL, postBytes).ContinueWith((response) => + { + Assert.IsNotNull(response, "No result returned from Post request"); + Assert.IsTrue(response.Result.ResponseCode == 200, $"Bad result from Post request, result code was [{response.Result.ResponseCode}]"); + Assert.IsNotNull(response.Result.ResponseData, "No data returned from Post request"); + + var outputString = Encoding.UTF8.GetString(response.Result.ResponseData); + Assert.IsTrue(outputString.Contains(postString), "The decoded Post message does not match the input string"); + }); + } + #endregion Post Request + + #region Put Request + + [Test] + public async void Test_03_01_PutRequest() + { + string putURL = "https://httpbin.org/put?data='The Result that was intended'"; + var input = new myJson() { MyJsonProperty = "MyJsonValue" }; + var json = JsonUtility.ToJson(input); + + await Rest.PutAsync(putURL, json).ContinueWith((response) => + { + Assert.IsNotNull(response.Result, "No result returned from Put request"); + Assert.IsTrue(response.Result.ResponseCode == 200, $"Bad result from Put request, result code was [{response.Result.ResponseCode}]"); + Assert.IsTrue(response.Result.ResponseBody.Contains("The Result that was intended"), "Returned Put string was not as expected"); + }); + } + + [Test] + public async void Test_03_02_PutRequestWithHeaders() + { + string putURL = "https://httpbin.org/put?data='The Result that was intended with headers'"; + var input = new myJson() { MyJsonProperty = "MyJsonValue" }; + var json = JsonUtility.ToJson(input); + + Dictionary Headers = new Dictionary(); + Headers.Add("Content-Type", "application/json"); + + await Rest.PutAsync(putURL, json, new RestArgs() { Headers = Headers }).ContinueWith((response) => + { + Assert.IsNotNull(response, "No result returned from Put request"); + Assert.IsTrue(response.Result.ResponseCode == 200, $"Bad result from Put request, result code was [{response.Result.ResponseCode}]"); + Assert.IsTrue(response.Result.ResponseBody.Contains("The Result that was intended"), "Returned Put string was not as expected"); + Assert.IsTrue(response.Result.ResponseBody.Contains("application/json"), "Put header was missing"); + }); + } + + [Test] + public async void Test_03_03_PutBytesRequest() + { + string putURL = "https://httpbin.org/put"; + string putString = "My word in my bond"; + var postBytes = Encoding.UTF8.GetBytes(putString); + + await Rest.PutAsync(putURL, postBytes).ContinueWith((response) => + { + Assert.IsNotNull(response, "No result returned from Get request"); + Assert.IsTrue(response.Result.ResponseCode == 200, $"Bad result from Put request, result code was [{response.Result.ResponseCode}]"); + Assert.IsNotNull(response.Result.ResponseData, "No data returned from Put request"); + + var outputString = Encoding.UTF8.GetString(response.Result.ResponseData); + Assert.IsTrue(outputString.Contains(putString), "The decoded Put message does not match the input string"); + }); + } + #endregion Put Request + + #region Delete Request + + [Test] + public async void Test_04_01_DeleteRequest() + { + string putURL = "https://httpbin.org/delete?data='The Result that was intended'"; + + await Rest.DeleteAsync(putURL).ContinueWith((response) => + { + Assert.IsNotNull(response.Result, "No result returned from Delete request"); + Assert.IsTrue(response.Result.ResponseCode == 200, $"Bad result from Delete request, result code was [{response.Result.ResponseCode}]"); + }); + } + + [Test] + public async void Test_04_02_DeleteRequestWithHeaders() + { + string putURL = "https://httpbin.org/delete?data='The Result that was intended with headers'"; + + Dictionary Headers = new Dictionary(); + Headers.Add("Content-Type", "application/json"); + + await Rest.DeleteAsync(putURL, new RestArgs() { Headers = Headers }).ContinueWith((response) => + { + Assert.IsNotNull(response, "No result returned from Delete request"); + Assert.IsTrue(response.Result.ResponseCode == 200, $"Bad result from Delete request, result code was [{response.Result.ResponseCode}]"); + }); + } + #endregion Delete Request + + #region Download Handlers + + [Test] + public async void Test_05_01_DownloadTexture() + { + string textureURL = "https://httpbin.org/image/jpeg"; + + await Rest.DownloadTextureAsync(textureURL).ContinueWith((response) => + { + Assert.IsNotNull(response, "No result returned from Texture request"); + }); + } + + [Test] + public async void Test_05_02_DownloadFile() + { + string fileURL = "https://httpbin.org/image/jpeg"; + + await Rest.DownloadFileAsync(fileURL).ContinueWith((response) => + { + Assert.IsNotNull(response, "No result returned from File request"); + }); + } + #endregion Download Handlers + + } +} \ No newline at end of file diff --git a/Editor/AssemblyInfo.cs.meta b/Tests/Rest/RestTests.cs.meta similarity index 86% rename from Editor/AssemblyInfo.cs.meta rename to Tests/Rest/RestTests.cs.meta index 5823f8b..00a0aae 100644 --- a/Editor/AssemblyInfo.cs.meta +++ b/Tests/Rest/RestTests.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: cdcf984f376ad0c488696c94d7872c5c +guid: ea427a26a924a5d49801d89cbf5cbca6 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/package.json b/package.json index 3315ab3..347abc4 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "Unity", "Tools" ], - "version": "1.0.2", + "version": "1.0.3-pre.1", "unity": "2020.3", "homepage": "https://realitycollective.io", "bugs": {