| | 1 | | using Cysharp.Threading.Tasks; |
| | 2 | | using NFTShape_Internal; |
| | 3 | | using System; |
| | 4 | | using System.Collections; |
| | 5 | | using System.Collections.Generic; |
| | 6 | | using System.Threading; |
| | 7 | | using UnityEngine; |
| | 8 | | using UnityEngine.Networking; |
| | 9 | |
|
| | 10 | | namespace DCL |
| | 11 | | { |
| | 12 | | public interface INFTAssetRetriever : IDisposable |
| | 13 | | { |
| | 14 | | IEnumerator LoadNFTAsset(string url, Action<INFTAsset> OnSuccess, Action<Exception> OnFail); |
| | 15 | | UniTask<INFTAsset> LoadNFTAsset(string url); |
| | 16 | | } |
| | 17 | |
|
| | 18 | | public class NFTAssetRetriever : INFTAssetRetriever |
| | 19 | | { |
| | 20 | | private const string CONTENT_TYPE = "Content-Type"; |
| | 21 | | private const string CONTENT_LENGTH = "Content-Length"; |
| | 22 | | private const string CONTENT_TYPE_GIF = "image/gif"; |
| | 23 | | private const string CONTENT_TYPE_WEBP = "image/webp"; |
| | 24 | | private const long PREVIEW_IMAGE_SIZE_LIMIT = 6000000; |
| | 25 | |
|
| | 26 | | protected AssetPromise_Texture imagePromise = null; |
| | 27 | | protected AssetPromise_Gif gifPromise = null; |
| | 28 | | private CancellationTokenSource tokenSource; |
| | 29 | |
|
| | 30 | | public async UniTask<INFTAsset> LoadNFTAsset(string url) |
| | 31 | | { |
| 0 | 32 | | tokenSource = new CancellationTokenSource(); |
| 0 | 33 | | tokenSource.Token.ThrowIfCancellationRequested(); |
| 0 | 34 | | INFTAsset result = null; |
| 0 | 35 | | await LoadNFTAsset(url, (nftAsset) => |
| | 36 | | { |
| 0 | 37 | | result = nftAsset; |
| 0 | 38 | | }, (exception) => |
| | 39 | | { |
| 0 | 40 | | Debug.Log("Fail to load nft " + url + " due to " + exception.Message); |
| 0 | 41 | | }).WithCancellation(tokenSource.Token); |
| | 42 | |
|
| 0 | 43 | | return result; |
| 0 | 44 | | } |
| | 45 | |
|
| | 46 | | public IEnumerator LoadNFTAsset(string url, Action<INFTAsset> OnSuccess, Action<Exception> OnFail) |
| | 47 | | { |
| 6 | 48 | | if (string.IsNullOrEmpty(url)) |
| | 49 | | { |
| 0 | 50 | | OnFail?.Invoke(new Exception($"Image url is null!")); |
| 0 | 51 | | yield break; |
| | 52 | | } |
| | 53 | |
|
| 6 | 54 | | HashSet<string> headers = new HashSet<string>() {CONTENT_TYPE, CONTENT_LENGTH}; |
| 6 | 55 | | Dictionary<string, string> responseHeaders = new Dictionary<string, string>(); |
| 6 | 56 | | string headerRequestError = string.Empty; |
| | 57 | |
|
| 12 | 58 | | yield return GetHeaders(url, headers, result => responseHeaders = result, (x) => headerRequestError = x); |
| | 59 | |
|
| 6 | 60 | | if (!string.IsNullOrEmpty(headerRequestError)) |
| | 61 | | { |
| 0 | 62 | | OnFail?.Invoke(new Exception($"Error fetching headers! ({headerRequestError})")); |
| 0 | 63 | | yield break; |
| | 64 | | } |
| | 65 | |
|
| 6 | 66 | | string contentType = responseHeaders[CONTENT_TYPE]; |
| 6 | 67 | | long.TryParse(responseHeaders[CONTENT_LENGTH], out long contentLength); |
| 6 | 68 | | bool isGif = string.Equals(contentType, CONTENT_TYPE_GIF, StringComparison.InvariantCultureIgnoreCase); |
| 6 | 69 | | bool isWebp = string.Equals(contentType, CONTENT_TYPE_WEBP, StringComparison.InvariantCultureIgnoreCase); |
| | 70 | |
|
| 6 | 71 | | if (isWebp) |
| | 72 | | { |
| | 73 | | // We are going to fallback into gifs until we have proper support |
| 0 | 74 | | yield return FetchGif(url + "&fm=gif", |
| | 75 | | OnSuccess: (promise) => |
| | 76 | | { |
| 0 | 77 | | UnloadPromises(); |
| 0 | 78 | | this.gifPromise = promise; |
| 0 | 79 | | OnSuccess?.Invoke(new NFTAsset_Gif(promise.asset)); |
| 0 | 80 | | }, |
| 0 | 81 | | OnFail: (exception) => { OnFail?.Invoke(exception); } |
| | 82 | | ); |
| | 83 | |
|
| 0 | 84 | | yield break; |
| | 85 | | } |
| 6 | 86 | | if (isGif) |
| | 87 | | { |
| 2 | 88 | | yield return FetchGif(url, |
| | 89 | | OnSuccess: (promise) => |
| | 90 | | { |
| 2 | 91 | | UnloadPromises(); |
| 2 | 92 | | this.gifPromise = promise; |
| 2 | 93 | | OnSuccess?.Invoke(new NFTAsset_Gif(promise.asset)); |
| 2 | 94 | | }, |
| 0 | 95 | | OnFail: (exception) => { OnFail?.Invoke(exception); } |
| | 96 | | ); |
| | 97 | |
|
| 2 | 98 | | yield break; |
| | 99 | | } |
| | 100 | |
|
| 4 | 101 | | if (contentLength > PREVIEW_IMAGE_SIZE_LIMIT) |
| | 102 | | { |
| 1 | 103 | | OnFail?.Invoke(new Exception($"Image is too big! {contentLength} > {PREVIEW_IMAGE_SIZE_LIMIT}")); |
| 1 | 104 | | yield break; |
| | 105 | | } |
| | 106 | |
|
| 3 | 107 | | yield return FetchImage(url, |
| | 108 | | OnSuccess: (promise) => |
| | 109 | | { |
| 3 | 110 | | UnloadPromises(); |
| 3 | 111 | | this.imagePromise = promise; |
| 3 | 112 | | OnSuccess?.Invoke(new NFTAsset_Image(promise.asset)); |
| 3 | 113 | | }, |
| 0 | 114 | | OnFail: (exc) => { OnFail?.Invoke(exc); }); |
| 3 | 115 | | } |
| | 116 | |
|
| | 117 | | public void Dispose() |
| | 118 | | { |
| 5 | 119 | | UnloadPromises(); |
| 5 | 120 | | tokenSource?.Cancel(); |
| 5 | 121 | | tokenSource?.Dispose(); |
| 0 | 122 | | } |
| | 123 | |
|
| | 124 | | protected virtual IEnumerator GetHeaders(string url, HashSet<string> headerField, |
| | 125 | | Action<Dictionary<string, string>> OnSuccess, Action<string> OnFail) |
| | 126 | | { |
| 0 | 127 | | using (var request = UnityWebRequest.Head(url)) |
| | 128 | | { |
| 0 | 129 | | yield return request.SendWebRequest(); |
| | 130 | |
|
| 0 | 131 | | if (request.WebRequestSucceded()) |
| | 132 | | { |
| 0 | 133 | | var result = new Dictionary<string, string>(); |
| | 134 | |
|
| 0 | 135 | | foreach (var key in headerField) |
| | 136 | | { |
| 0 | 137 | | result.Add(key, request.GetResponseHeader(key)); |
| | 138 | | } |
| | 139 | |
|
| 0 | 140 | | OnSuccess?.Invoke(result); |
| | 141 | | } |
| | 142 | | else |
| | 143 | | { |
| 0 | 144 | | OnFail?.Invoke(request.error); |
| | 145 | | } |
| 0 | 146 | | } |
| 0 | 147 | | } |
| | 148 | |
|
| | 149 | | protected virtual IEnumerator FetchGif(string url, Action<AssetPromise_Gif> OnSuccess, |
| | 150 | | Action<Exception> OnFail = null) |
| | 151 | | { |
| 0 | 152 | | AssetPromise_Gif gifPromise = new AssetPromise_Gif(url); |
| 0 | 153 | | gifPromise.OnSuccessEvent += texture => { OnSuccess?.Invoke(gifPromise); }; |
| 0 | 154 | | gifPromise.OnFailEvent += (x, error) => OnFail?.Invoke(error); |
| | 155 | |
|
| 0 | 156 | | AssetPromiseKeeper_Gif.i.Keep(gifPromise); |
| | 157 | |
|
| 0 | 158 | | yield return gifPromise; |
| 0 | 159 | | } |
| | 160 | |
|
| | 161 | | protected virtual IEnumerator FetchImage(string url, Action<AssetPromise_Texture> OnSuccess, |
| | 162 | | Action<Exception> OnFail = null) |
| | 163 | | { |
| 0 | 164 | | AssetPromise_Texture texturePromise = new AssetPromise_Texture(url); |
| 0 | 165 | | texturePromise.OnSuccessEvent += texture => { OnSuccess?.Invoke(texturePromise); }; |
| 0 | 166 | | texturePromise.OnFailEvent += (x, error) => OnFail?.Invoke(error); |
| | 167 | |
|
| 0 | 168 | | AssetPromiseKeeper_Texture.i.Keep(texturePromise); |
| | 169 | |
|
| 0 | 170 | | yield return texturePromise; |
| 0 | 171 | | } |
| | 172 | |
|
| | 173 | | protected virtual void UnloadPromises() |
| | 174 | | { |
| 10 | 175 | | if (gifPromise != null) |
| 2 | 176 | | AssetPromiseKeeper_Gif.i.Forget(gifPromise); |
| | 177 | |
|
| 10 | 178 | | if (imagePromise != null) |
| 3 | 179 | | AssetPromiseKeeper_Texture.i.Forget(imagePromise); |
| | 180 | |
|
| 10 | 181 | | gifPromise = null; |
| 10 | 182 | | imagePromise = null; |
| 10 | 183 | | } |
| | 184 | | } |
| | 185 | | } |