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