/// <summary> /// Processes the result of fetching an individual file. /// </summary> /// <param name="state">Indicates the state of the ongoing fetch operation (as set up in FetchObj).</param> /// <param name="index">If ROOT_FILE_INDEX, then this is a result for the main file; else this is a result for /// the resource file with that index.</param> /// <param name="status">The status indicating if the download succeed</param> /// <param name="data">The data that was downloaded.</param> private void ProcessFileFetchResult(FetchOperationState state, int index, PolyStatus status, byte[] data) { if (state.pendingFiles == 0) { // Another request for this format failed, so we ignore any further responses. return; } if (!status.ok) { // This request failed, so we set pendingFiles to 0 so we ignore any further responses, and callback with // an error message. state.pendingFiles = 0; state.completionCallback(state.asset, PolyStatus.Error(status, "Failed to fetch file #{0}", index)); return; } PolyFormat package = state.packageBeingFetched; PolyFile file = index == ROOT_FILE_INDEX ? package.root : package.resources[index]; file.contents = data; --state.pendingFiles; if (state.progressCallback != null) { state.progressCallback(state.asset, 1.0f - ((float)state.pendingFiles / state.totalFiles)); } if (state.pendingFiles <= 0) { // All files done, call callback indicating success. state.completionCallback(state.asset, PolyStatus.Success()); } }
/// <summary> /// Imports the given format of the given asset, asynchronously in a background thread. /// Calls the supplied callback when done. /// </summary> public void ImportAsync(PolyAsset asset, PolyFormat format, PolyImportOptions options, AsyncImportCallback callback = null) { ImportOperation operation = new ImportOperation(); operation.instance = this; operation.asset = asset; operation.format = format; operation.options = options; operation.callback = callback; operation.status = PolyStatus.Success(); operation.loader = new FormatLoader(format); if (Application.isPlaying) { Task.Run(() => BackgroundThreadProc(operation)); //ThreadPool.QueueUserWorkItem(new WaitCallback(BackgroundThreadProc), operation); } else { // If we are in the editor, don't do this in a background thread. Do it directly // here on the main thread. BackgroundThreadProc(operation); Update(); } }
/// <summary> /// Attempts to authenticate using the provided tokens. /// This will NOT launch a sign-in flow. It will use the given tokens directly. /// </summary> /// <param name="accessToken">The access token to use.</param> /// <param name="refreshToken">The refresh token to use.</param> /// <param name="callback">The callback to call when authentication completes.</param> public void Authenticate(string accessToken, string refreshToken, Action <PolyStatus> callback) { if (!instance.IsAuthenticationSupported) { callback(PolyStatus.Error("Authentication is not supported on this platform.")); } oauth2Identity.LoginWithTokens( () => { callback(PolyStatus.Success()); }, () => { callback(PolyStatus.Error("Authentication failed (with tokens).")); }, accessToken, refreshToken); }
/// <summary> /// Attempts to authenticate. /// </summary> /// <param name="interactive">If true, launch the sign in flow (browser) if necessary. If false, /// attempt to authenticate silently.</param> /// <param name="callback">Callback to call when authentication completes.</param> public void Authenticate(bool interactive, Action <PolyStatus> callback) { if (!instance.IsAuthenticationSupported) { callback(PolyStatus.Error("Authentication is not supported on this platform.")); } oauth2Identity.Login( () => { callback(PolyStatus.Success()); }, () => { callback(PolyStatus.Error("Authentication failed.")); }, interactive); }
/// <summary> /// Refreshes an access token, if a given refresh token is valid, and then calls one of the given callbacks. /// </summary> public void Reauthorize(Action <PolyStatus> callback) { if (!instance.IsAuthenticationSupported) { callback(PolyStatus.Error("Authentication is not supported on this platform.")); } CoroutineRunner.StartCoroutine(this, oauth2Identity.Reauthorize( successCallback: () => { callback(PolyStatus.Success()); }, failureCallback: (string error) => { callback(PolyStatus.Error(error)); } )); }
/// <summary> /// Takes a string, representing either a ListAssetsResponse or ListUserAssetsResponse proto, and /// fills polyListResult with relevant fields from the response and returns a success status /// if the response is of the expected format, or a failure status if it's not. /// </summary> public static PolyStatus ParseReturnedAssets(string response, out PolyListAssetsResult polyListAssetsResult) { // Try and actually parse the string. JObject results = JObject.Parse(response); IJEnumerable <JToken> assets = results["assets"].AsJEnumerable(); // If assets is null, check for a userAssets object, which would be present if the response was // a ListUserAssets response. if (assets == null) { assets = results["userAssets"].AsJEnumerable(); } if (assets == null) { // Empty response means there were no assets that matched the request parameters. polyListAssetsResult = new PolyListAssetsResult(PolyStatus.Success(), /*totalSize*/ 0); return(PolyStatus.Success()); } List <PolyAsset> polyAssets = new List <PolyAsset>(); foreach (JToken asset in assets) { PolyAsset polyAsset; if (!(asset is JObject)) { Debug.LogWarningFormat("Ignoring asset since it's not a JSON object: " + asset); continue; } JObject jObjectAsset = (JObject)asset; if (asset["asset"] != null) { // If this isn't null, means we are parsing a ListUserAssets response, which has an added // layer of nesting. jObjectAsset = (JObject)asset["asset"]; } PolyStatus parseStatus = ParseAsset(jObjectAsset, out polyAsset); if (parseStatus.ok) { polyAssets.Add(polyAsset); } else { Debug.LogWarningFormat("Failed to parse a returned asset: {0}", parseStatus); } } var totalSize = results["totalSize"] != null?int.Parse(results["totalSize"].ToString()) : 0; var nextPageToken = results["nextPageToken"] != null ? results["nextPageToken"].ToString() : null; polyListAssetsResult = new PolyListAssetsResult(PolyStatus.Success(), totalSize, polyAssets, nextPageToken); return(PolyStatus.Success()); }
/// <summary> /// Parses a single asset. /// </summary> public static PolyStatus ParseAsset(JObject asset, out PolyAsset polyAsset) { polyAsset = new PolyAsset(); if (asset["visibility"] == null) { return(PolyStatus.Error("Asset has no visibility set.")); } polyAsset.name = asset["name"].ToString(); polyAsset.authorName = asset["authorName"].ToString(); if (asset["thumbnail"] != null) { var thumbnailElements = asset["thumbnail"].ToObject <JObject>(); //IJEnumerable<JToken> thumbnailElements = asset["thumbnail"].AsJEnumerable(); polyAsset.thumbnail = new PolyFile(thumbnailElements["relativePath"].ToString(), thumbnailElements["url"].ToString(), thumbnailElements["contentType"].ToString()); } if (asset["formats"] == null) { Debug.LogError("No formats found"); } else { foreach (var format in asset["formats"].ToObject <List <JObject> >()) //foreach (JToken format in asset["formats"]) { PolyFormat newFormat = ParseAssetsPackage(format); newFormat.formatType = ParsePolyFormatType(format["formatType"]); if (newFormat.formatType == PolyFormatType.UNKNOWN) { PtDebug.Log("Did not recognize format type: " + format["formatType"].ToString()); } polyAsset.formats.Add(newFormat); } } polyAsset.displayName = asset["displayName"].ToString(); polyAsset.createTime = DateTime.Parse(asset["createTime"].ToString()); polyAsset.updateTime = DateTime.Parse(asset["updateTime"].ToString()); polyAsset.visibility = ParsePolyVisibility(asset["visibility"]); polyAsset.license = ParsePolyAssetLicense(asset["license"]); if (asset["isCurated"] != null) { polyAsset.isCurated = bool.Parse(asset["isCurated"].ToString()); } return(PolyStatus.Success()); }
/// <summary> /// Gets raw file data from Poly given a data URL. /// </summary> /// <param name="dataUrl">Data URL to retrieve from.</param> /// <param name="accessToken">The access token to use for authentication.</param> /// <param name="callback">The callback to call when the download is complete.</param> /// <param name="maxCacheAgeMillis">Maximum age of the cached copy, in millis. See /// WebRequestManager for useful constants.</param> public static void GetRawFileBytes(string dataUrl, string accessToken, long maxCacheAgeMillis, GetRawFileDataBytesCallback callback) { PolyMainInternal.Instance.webRequestManager.EnqueueRequest( () => { return(MakeRawFileGetRequest(dataUrl, accessToken)); }, (PolyStatus status, int responseCode, byte[] response) => { if (!status.ok) { callback(PolyStatus.Error(status, "Failed to get raw file bytes for {0}", dataUrl), null); } else { callback(PolyStatus.Success(), response); } }, maxCacheAgeMillis); }
public void Update() { // We process at most one import result per frame, to avoid doing too much work // in the main thread. ImportOperation operation; lock (finishedOperationsLock) { if (finishedOperations.Count == 0) { return; } operation = finishedOperations.Dequeue(); } if (!operation.status.ok) { // Import failed. operation.callback(operation.status, root: null, meshCreator: null); return; } try { IEnumerable meshCreator; ImportGltf.GltfImportResult result = ImportGltf.EndImport(operation.importState, operation.loader, out meshCreator); if (!operation.options.clientThrottledMainThread) { // If we're not in throttled mode, create all the meshes immediately by exhausting // the meshCreator enumeration. Otherwise, it's the caller's responsibility to // do this. foreach (var unused in meshCreator) /* empty */ } { meshCreator = null; } // Success. operation.callback(PolyStatus.Success(), result.root, meshCreator); } catch (Exception ex) { // Import failed. Debug.LogException(ex); operation.callback(PolyStatus.Error("Failed to convert import to Unity objects.", ex), root: null, meshCreator: null); } }
/// <summary> /// Verify if the response can be parsed as json and, if so, that it contains no error token. /// If either conditions are false return a PolyStatusError with relevant information. /// </summary> private PolyStatus CheckResponseForError(string response) { JObject results = new JObject(); try { results = JObject.Parse(response); } catch (Exception ex) { return(PolyStatus.Error("Failed to parse Poly API response, encountered exception: {0}", ex.Message)); } IJEnumerable <JToken> error = results["error"].AsJEnumerable(); if (error == null) { return(PolyStatus.Success()); } return(PolyStatus.Error("{0}: {1}", error["code"] != null ? error["code"].ToString() : "(no error code)", error["message"] != null ? error["message"].ToString() : "(no error message)")); }
public static PolyStatus ParseResponse(byte[] response, out JObject result) { try { result = JObject.Parse(Encoding.UTF8.GetString(response)); JToken errorToken = result["error"]; if (errorToken != null) { IJEnumerable <JToken> error = errorToken.AsJEnumerable(); return(PolyStatus.Error("{0}: {1}", error["code"] != null ? error["code"].ToString() : "(no error code)", error["message"] != null ? error["message"].ToString() : "(no error message)")); } else { return(PolyStatus.Success()); } } catch (Exception ex) { result = null; return(PolyStatus.Error("Failed to parse Poly API response, encountered exception: {0}", ex.Message)); } }
// Searches directly by Poly URL. private void SearchByPolyUrl(string polyUrl, OnActorableSearchResult resultCallback, System.Action <bool> onComplete) { string[] parts = polyUrl.Split('/'); string assetId = parts[parts.Length - 1]; PolyApi.GetAsset(assetId, result => { PolyListAssetsResult assetsResult; List <PolyAsset> assetList = new List <PolyAsset>(); if (result.Ok) { // Successfully got the asset. This is good. // Is it acceptably licensed? if (result.Value.license == PolyAssetLicense.CREATIVE_COMMONS_BY) { // Good license. We can use it. assetList.Add(result.Value); assetsResult = new PolyListAssetsResult(PolyStatus.Success(), 1, assetList); } else { // Not CC-By. Can't use it. Debug.LogError("This asset (" + assetId + ") is not licensed by CC-By. Try another asset."); assetsResult = new PolyListAssetsResult(PolyStatus.Error("Asset " + assetId + " is not licensed as CC-By."), 0, assetList); } } else { // Failed to get the asset. This is bad. assetsResult = new PolyListAssetsResult(PolyStatus.Error("Failed to get asset " + assetId), 0, assetList); } PolySearchCallback( new PolyStatusOr <PolyListAssetsResult>(assetsResult), resultCallback, onComplete); }); return; }
/// <summary> /// Parses a single asset. /// </summary> // public static PolyStatus ParseAsset(JObject asset, out PolyAsset polyAsset) // { // polyAsset = new PolyAsset(); // if (asset["visibility"] == null) // { // return PolyStatus.Error("Asset has no visibility set."); // } // polyAsset.name = asset["name"].ToString(); // polyAsset.authorName = asset["authorName"].ToString(); // if (asset["thumbnail"] != null) // { // IJEnumerable<JToken> thumbnailElements = asset["thumbnail"].AsJEnumerable(); // polyAsset.thumbnail = new PolyFile(thumbnailElements["relativePath"].ToString(), // thumbnailElements["url"].ToString(), thumbnailElements["contentType"].ToString()); // } // if (asset["formats"] == null) // { // Debug.LogError("No formats found"); // } // else // { // foreach (JToken format in asset["formats"]) // { // PolyFormat newFormat = ParseAssetsPackage(format); // newFormat.formatType = ParsePolyFormatType(format["formatType"]); // if (newFormat.formatType == PolyFormatType.UNKNOWN) // { // PtDebug.Log("Did not recognize format type: " + format["formatType"].ToString()); // } // polyAsset.formats.Add(newFormat); // } // } // polyAsset.displayName = asset["displayName"].ToString(); // polyAsset.createTime = DateTime.Parse(asset["createTime"].ToString()); // polyAsset.updateTime = DateTime.Parse(asset["updateTime"].ToString()); // polyAsset.visibility = ParsePolyVisibility(asset["visibility"]); // polyAsset.license = ParsePolyAssetLicense(asset["license"]); // if (asset["isCurated"] != null) // { // polyAsset.isCurated = bool.Parse(asset["isCurated"].ToString()); // } // return PolyStatus.Success(); // } // private static PolyFormatType ParsePolyFormatType(JToken token) // { // if (token == null) return PolyFormatType.UNKNOWN; // string tokenValue = token.ToString(); // return tokenValue == "OBJ" ? PolyFormatType.OBJ : // tokenValue == "GLTF2" ? PolyFormatType.GLTF_2 : // tokenValue == "GLTF" ? PolyFormatType.GLTF : // tokenValue == "TILT" ? PolyFormatType.TILT : // PolyFormatType.UNKNOWN; // } // private static PolyVisibility ParsePolyVisibility(JToken token) // { // if (token == null) return PolyVisibility.UNSPECIFIED; // string tokenValue = token.ToString(); // return tokenValue == "PRIVATE" ? PolyVisibility.PRIVATE : // tokenValue == "UNLISTED" ? PolyVisibility.UNLISTED : // tokenValue == "PUBLIC" ? PolyVisibility.PUBLISHED : // PolyVisibility.UNSPECIFIED; // } // private static PolyAssetLicense ParsePolyAssetLicense(JToken token) // { // if (token == null) return PolyAssetLicense.UNKNOWN; // string tokenValue = token.ToString(); // return tokenValue == "CREATIVE_COMMONS_BY" ? PolyAssetLicense.CREATIVE_COMMONS_BY : // tokenValue == "ALL_RIGHTS_RESERVED" ? PolyAssetLicense.ALL_RIGHTS_RESERVED : // PolyAssetLicense.UNKNOWN; // } // As above, accepting a string response (such that we can parse on a background thread). public static PolyStatus ParseAsset(string response, out PolyAsset objectStoreEntry, bool hackUrls) { objectStoreEntry = null; return(PolyStatus.Success()); }
/// <summary> /// Co-routine that services one PendingRequest. This method must be called with StartCoroutine. /// </summary> /// <param name="request">The request to service.</param> private IEnumerator HandleWebRequest(PendingRequest request, BufferHolder bufferHolder) { // NOTE: This method runs on the main thread, but never blocks -- the blocking part of the work is // done by yielding the UnityWebRequest, which releases the main thread for other tasks while we // are waiting for the web request to complete (by the miracle of coroutines). // Let the caller create the UnityWebRequest, configuring it as they want. The caller can set the URL, // method, headers, anything they want. The only thing they can't do is call Send(), as we're in charge // of doing that. UnityWebRequest webRequest = request.creationCallback(); PtDebug.LogVerboseFormat("Web request: {0} {1}", webRequest.method, webRequest.url); bool cacheAllowed = cache != null && webRequest.method == "GET" && request.maxAgeMillis != CACHE_NONE; // Check the cache (if it's a GET request and cache is enabled). if (cacheAllowed) { bool cacheHit = false; byte[] cacheData = null; bool cacheReadDone = false; cache.RequestRead(webRequest.url, request.maxAgeMillis, (bool success, byte[] data) => { cacheHit = success; cacheData = data; cacheReadDone = true; }); while (!cacheReadDone) { yield return(null); } if (cacheHit) { PtDebug.LogVerboseFormat("Web request CACHE HIT: {0}, response: {1} bytes", webRequest.url, cacheData.Length); request.completionCallback(PolyStatus.Success(), /* responseCode */ 200, cacheData); // Return the buffer to the pool for reuse. CleanUpAfterWebRequest(bufferHolder); yield break; } else { PtDebug.LogVerboseFormat("Web request CACHE MISS: {0}.", webRequest.url); } } DownloadHandlerBuffer handler = new DownloadHandlerBuffer(); webRequest.downloadHandler = handler; // We need to asset that we actually succeeded in setting the download handler, because this can fail // if, for example, the creation callback mistakenly called Send(). PolyUtils.AssertTrue(webRequest.downloadHandler == handler, "Couldn't set download handler. It's either disposed of, or the creation callback mistakenly called Send()."); // Start the web request. This will suspend this coroutine until the request is done. PtDebug.LogVerboseFormat("Sending web request: {0}", webRequest.url); yield return(UnityCompat.SendWebRequest(webRequest)); // Request is finished. Call user-supplied callback. PtDebug.LogVerboseFormat("Web request finished: {0}, HTTP response code {1}, response: {2}", webRequest.url, webRequest.responseCode, webRequest.downloadHandler.text); PolyStatus status = UnityCompat.IsNetworkError(webRequest) ? PolyStatus.Error(webRequest.error) : PolyStatus.Success(); request.completionCallback(status, (int)webRequest.responseCode, webRequest.downloadHandler.data); // Cache the result, if applicable. if (!UnityCompat.IsNetworkError(webRequest) && cacheAllowed) { byte[] data = webRequest.downloadHandler.data; if (data != null && data.Length > 0) { byte[] copy = new byte[data.Length]; Buffer.BlockCopy(data, 0, copy, 0, data.Length); cache.RequestWrite(webRequest.url, copy); } } // Clean up. webRequest.Dispose(); CleanUpAfterWebRequest(bufferHolder); }