/// <summary> /// Obtain token using itSeez3D username and password. Not for production use! /// </summary> private AsyncWebRequest <AccessData> AuthorizePasswordGrantTypeAsync( string clientId, string clientSecret, string username, string password ) { Debug.LogWarning("Don't use this auth method in production, use other grant types!"); var request = new AsyncWebRequest <AccessData> (AvatarSdkMgr.Str(Strings.RequestingApiToken)); if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(clientId)) { request.SetError("itSeez3D credentials not provided"); Debug.LogError(request.ErrorMessage); return(request); } var form = new Dictionary <string, string> () { { "grant_type", "password" }, { "username", username }, { "password", password }, { "client_id", clientId }, { "client_secret", clientSecret }, }; Func <UnityWebRequest> webRequestFactory = () => HttpPost(GetUrl("o", "token"), form); AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, request)); return(request); }
/// <summary> /// Download pages until the desired number of items is requested. /// </summary> public virtual AsyncWebRequest <DataType[]> AvatarJsonArrayRequest <DataType> (string url, int maxItems = int.MaxValue) { var request = new AsyncWebRequest <DataType[]> (); AvatarSdkMgr.SpawnCoroutine(AwaitMultiplePages(url, request, maxItems)); return(request); }
/// <summary> /// Send HTTP GET request, deserialize response as DataType. /// </summary> /// <returns>Async request object that will contain an instance of DataType on success.</returns> public virtual AsyncWebRequest <DataType> AvatarJsonRequest <DataType> (string url) { var request = new AsyncWebRequest <DataType> (); AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(() => HttpGet(url), request)); return(request); }
/// <summary> /// Edit avatar name/description on the server. /// </summary> public virtual AsyncWebRequest EditAvatarAsync(AvatarData avatar, string name = null, string description = null) { var request = new AsyncWebRequest(AvatarSdkMgr.Str(Strings.EditingAvatar)); byte[] requestBodyData = null; using (var requestBody = new MultipartBody()) { requestBody.WriteTextField("name", name); requestBody.WriteTextField("description", description); requestBody.WriteFooter(); requestBodyData = requestBody.GetRequestBodyData(); Func <UnityWebRequest> webRequestFactory = () => { var webRequest = UnityWebRequest.Post(avatar.url, " "); webRequest.chunkedTransfer = false; webRequest.method = "PATCH"; webRequest.uploadHandler = new UploadHandlerRaw(requestBodyData); webRequest.SetRequestHeader( "Content-Type", string.Format("multipart/form-data; boundary=\"{0}\"", requestBody.Boundary) ); SetAuthHeaders(webRequest); return(webRequest); }; Debug.LogFormat("Uploading photo..."); AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, request)); return(request); } }
/// <summary> /// Send HTTP GET request, deserialize response as Page<T>. /// </summary> /// <returns>Async request object that will contain an instance of Page<T> on success.</returns> public virtual AsyncWebRequest <Page <T> > AvatarJsonPageRequest <T> (string url) { var request = new AsyncWebRequest <Page <T> > (); AvatarSdkMgr.SpawnCoroutine(AwaitJsonPageWebRequest(() => HttpGet(url), request)); return(request); }
/// <summary> /// Obtain token using client credentials. /// </summary> private AsyncWebRequest <AccessData> AuthorizeClientCredentialsGrantTypeAsync(AccessCredentials credentials) { var request = new AsyncWebRequest <AccessData> (AvatarSdkMgr.Str(Strings.RequestingApiToken)); Func <UnityWebRequest> webRequestFactory = () => GenerateAuthRequest(credentials); AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, request)); return(request); }
/// <summary> /// Call AwaitWebRequestFunc, interpret response as JSON. /// </summary> public virtual IEnumerator AwaitJsonWebRequest <DataType> ( Func <UnityWebRequest> webRequestFactory, AsyncWebRequest <DataType> request) { yield return(AwaitWebRequestFunc(webRequestFactory, request, (r) => { return JsonUtility.FromJson <DataType> (r.downloadHandler.text); })); }
/// <summary> /// Download file. /// </summary> public virtual AsyncWebRequest <byte[]> AvatarDataRequestAsync(string url) { Debug.LogFormat("Downloading from {0}...", url); var request = new AsyncWebRequest <byte[]> (); AvatarSdkMgr.SpawnCoroutine(AwaitDataAsync(() => HttpGet(url), request)); return(request); }
/// <summary> /// Call AwaitWebRequestFunc for paginated requests. /// </summary> public virtual IEnumerator AwaitJsonPageWebRequest <T> ( Func <UnityWebRequest> webRequestFactory, AsyncWebRequest <Page <T> > request ) { yield return(AwaitWebRequestFunc(webRequestFactory, request, (r) => { // Unity JsonUtility does not support Json array parsing, so we have to hack around it // by wrapping it into object with a single array field. var wrappedArrayJson = string.Format("{{ \"content\": {0} }}", r.downloadHandler.text); var page = JsonUtility.FromJson <Page <T> > (wrappedArrayJson); var paginationHeader = r.GetResponseHeader("Link"); // parse "Link" header to get links to adjacent pages if (!string.IsNullOrEmpty(paginationHeader)) { var regex = new Regex(@".*\<(?<link>.+)\>.+rel=""(?<kind>.*)"""); var tokens = paginationHeader.Split(','); foreach (var token in tokens) { var match = regex.Match(token); if (!match.Success) { continue; } string link = match.Groups ["link"].Value, kind = match.Groups ["kind"].Value; if (string.IsNullOrEmpty(link) || string.IsNullOrEmpty(kind)) { continue; } if (kind == "first") { page.firstPageUrl = link; } else if (kind == "next") { page.nextPageUrl = link; } else if (kind == "prev") { page.prevPageUrl = link; } else if (kind == "last") { page.lastPageUrl = link; } } } return page; })); }
/// <summary> /// Delete avatar record on the server (does not delete local files). /// </summary> public virtual AsyncWebRequest DeleteAvatarAsync(AvatarData avatar) { var request = new AsyncWebRequest(AvatarSdkMgr.Str(Strings.DeletingAvatarOnServer)); Func <UnityWebRequest> webRequestFactory = () => { var webRequest = UnityWebRequest.Delete(avatar.url); SetAuthHeaders(webRequest); webRequest.downloadHandler = new DownloadHandlerBuffer(); return(webRequest); }; AvatarSdkMgr.SpawnCoroutine(AwaitWebRequest(webRequestFactory, request)); return(request); }
/// <summary> /// Register unique player UID that is used later to sign the requests. /// </summary> /// <param name="comment">Arbitrary data associated with player UID.</param> public virtual AsyncWebRequest <Player> RegisterPlayerAsync(string comment = "") { var r = new AsyncWebRequest <Player> (AvatarSdkMgr.Str(Strings.RegisteringPlayerID)); var form = new Dictionary <string, string> () { { "comment", comment }, }; Func <UnityWebRequest> webRequestFactory = () => { var webRequest = HttpPost(GetUrl("players"), form); SetAuthHeaders(webRequest); return(webRequest); }; AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, r)); return(r); }
/// <summary> /// Request to get available resources for the pipeline /// </summary> public virtual AsyncWebRequest <string> GetResourcesAsync(PipelineType pipelineType, AvatarResourcesSubset resourcesSubset) { string subsetStr = "available"; if (resourcesSubset == AvatarResourcesSubset.DEFAULT) { subsetStr = "default"; } var url = GetUrl("resources", subsetStr, pipelineType.GetPipelineTypeName()); url = UrlWithParams(url, "pipeline_subtype", pipeline_subtype); var request = new AsyncWebRequest <string>(Strings.GettingResourcesList); AvatarSdkMgr.SpawnCoroutine(AwaitStringDataAsync(() => HttpGet(url), request)); return(request); }
/// <summary> /// Send the request, wait till it finishes and process the result. /// </summary> /// <returns>The web request func.</returns> /// <param name="webRequestFactory">Function that returns the new copy of UnityWebRequest /// (needed for retry).</param> /// <param name="request">"Parent" async request object to report progress to.</param> /// <param name="parseDataFunc">Function that takes care of parsing data (usually JSON parsing).</param> private IEnumerator AwaitWebRequestFunc <T> ( Func <UnityWebRequest> webRequestFactory, AsyncWebRequest <T> request, Func <UnityWebRequest, T> parseDataFunc ) { UnityWebRequest webRequest = null; StatusCode status = new StatusCode(); string error = string.Empty; int numAttempts = 2, lastAttempt = numAttempts - 1; bool goodResponse = false; for (int attempt = 0; attempt < numAttempts; ++attempt) { webRequest = webRequestFactory(); yield return(AwaitAndTrackProgress(webRequest, request)); if (goodResponse = IsGoodResponse(webRequest, out status, out error)) { break; } // all API requests have Authorization header, except for authorization requests bool isAuthRequest = webRequest.GetRequestHeader("Authorization") == null; Debug.LogWarningFormat("Server error: {0}, request: {1}", error, webRequest.url); if (status.Value != (long)StatusCode.Code.UNAUTHORIZED || isAuthRequest) { // cannot recover, request has failed break; } if (attempt == lastAttempt) { Debug.LogError("No more retries left"); break; } Debug.LogWarning("Auth issue, let's try one more time after refreshing access token"); yield return(AuthorizeAsync()); } if (!goodResponse) { Debug.LogErrorFormat("Could not send the request, status: {0}, error: {1}", status, error); request.Status = status; request.SetError(error); yield break; } T data = default(T); try { data = parseDataFunc(webRequest); } catch (Exception ex) { Debug.LogException(ex); } if (data == null) { request.SetError("Could not parse request data"); yield break; } else { request.Result = data; } request.IsDone = true; }
/// <summary> /// DownloadAndSaveAvatarModelAsync implementation. /// </summary> private static IEnumerator DownloadAndSaveAvatarModel( Connection connection, AvatarData avatar, bool withHaircutPointClouds, bool withBlendshapes, AsyncRequest <AvatarData> request ) { // By initializing multiple requests at the same time (without yield between them) we're // starting them in parallel. In this particular case we're downloading multiple files at the same time, // which is usually a bit faster than sequential download. var meshZip = connection.DownloadMeshZipAsync(avatar); var textureRequest = connection.DownloadTextureBytesAsync(avatar); var download = new List <AsyncRequest> { meshZip, textureRequest }; AsyncWebRequest <byte[]> allHaircutPointCloudsZip = null, blendshapesZip = null; #if BLENDSHAPES_IN_PLY_OR_FBX // just a sample of how to get blendshapes in a different format AsyncWebRequest <byte[]> blendshapesZipFbx = null, blendshapesZipPly = null; #endif if (withHaircutPointClouds) { allHaircutPointCloudsZip = connection.DownloadAllHaircutPointCloudsZipAsync(avatar); download.Add(allHaircutPointCloudsZip); } if (withBlendshapes) { blendshapesZip = connection.DownloadBlendshapesZipAsync(avatar); download.Add(blendshapesZip); #if BLENDSHAPES_IN_PLY_OR_FBX // just a sample of how to get blendshapes in a different format blendshapesZipFbx = connection.DownloadBlendshapesZipAsync(avatar, BlendshapesFormat.FBX); download.Add(blendshapesZipFbx); blendshapesZipPly = connection.DownloadBlendshapesZipAsync(avatar, BlendshapesFormat.PLY); download.Add(blendshapesZipPly); #endif } // continue execution when all requests finish yield return(request.AwaitSubrequests(0.9f, download.ToArray())); // return if any of the requests failed if (request.IsError) { yield break; } // save all the results to disk, also in parallel var saveMeshZip = CoreTools.SaveAvatarFileAsync(meshZip.Result, avatar.code, AvatarFile.MESH_ZIP); var saveTexture = CoreTools.SaveAvatarFileAsync(textureRequest.Result, avatar.code, AvatarFile.TEXTURE); var save = new List <AsyncRequest> () { saveMeshZip, saveTexture }; AsyncRequest <string> saveHaircutPointsZip = null, saveBlendshapesZip = null; if (allHaircutPointCloudsZip != null) { saveHaircutPointsZip = CoreTools.SaveAvatarFileAsync(allHaircutPointCloudsZip.Result, avatar.code, AvatarFile.ALL_HAIRCUT_POINTS_ZIP); save.Add(saveHaircutPointsZip); } if (blendshapesZip != null) { saveBlendshapesZip = CoreTools.SaveAvatarFileAsync(blendshapesZip.Result, avatar.code, AvatarFile.BLENDSHAPES_ZIP); save.Add(saveBlendshapesZip); } #if BLENDSHAPES_IN_PLY_OR_FBX // just a sample of how to get blendshapes in a different format if (blendshapesZipFbx != null) { var saveBlendshapesZipFbx = CoreTools.SaveAvatarFileAsync(blendshapesZipFbx.Result, avatar.code, AvatarFile.BLENDSHAPES_FBX_ZIP); save.Add(saveBlendshapesZipFbx); } if (blendshapesZipPly != null) { var saveBlendshapesZipPly = CoreTools.SaveAvatarFileAsync(blendshapesZipPly.Result, avatar.code, AvatarFile.BLENDSHAPES_PLY_ZIP); save.Add(saveBlendshapesZipPly); } #endif yield return(request.AwaitSubrequests(0.95f, save.ToArray())); if (request.IsError) { yield break; } var unzipMesh = CoreTools.UnzipFileAsync(saveMeshZip.Result); var unzip = new List <AsyncRequest> () { unzipMesh }; AsyncRequest <string> unzipHaircutPoints = null, unzipBlendshapes = null; if (saveHaircutPointsZip != null) { unzipHaircutPoints = CoreTools.UnzipFileAsync(saveHaircutPointsZip.Result); unzip.Add(unzipHaircutPoints); } if (saveBlendshapesZip != null) { unzipBlendshapes = UnzipBlendshapes(saveBlendshapesZip.Result, avatar.code); unzip.Add(unzipBlendshapes); } yield return(request.AwaitSubrequests(0.99f, unzip.ToArray())); if (request.IsError) { yield break; } // delete all .zip files we don't need anymore try { foreach (var fileToDelete in new AvatarFile[] { AvatarFile.MESH_ZIP, AvatarFile.ALL_HAIRCUT_POINTS_ZIP, AvatarFile.BLENDSHAPES_ZIP }) { CoreTools.DeleteAvatarFile(avatar.code, fileToDelete); } } catch (Exception ex) { // error here is not critical, we can just ignore it Debug.LogException(ex); } request.Result = avatar; request.IsDone = true; }
/// <summary> /// Variation of AwaitWebRequestFunc when we don't actually need the result. /// </summary> public virtual IEnumerator AwaitWebRequest(Func <UnityWebRequest> webRequestFactory, AsyncWebRequest request) { yield return(AwaitWebRequestFunc(webRequestFactory, request, (r) => new object())); }
/// <summary> /// Upload photo and create avatar instance on the server. Calculations will start right away after the photo is uploaded. /// </summary> public virtual AsyncWebRequest <AvatarData> CreateAvatarWithPhotoAsync( string name, string description, byte[] photoBytes, bool forcePowerOfTwoTexture = false, PipelineType pipeline = PipelineType.FACE, AvatarResources resources = null ) { var request = new AsyncWebRequest <AvatarData> (AvatarSdkMgr.Str(Strings.UploadingPhoto), TrackProgress.UPLOAD); #if UNITY_2017_1_OR_NEWER Func <UnityWebRequest> webRequestFactory = () => { List <IMultipartFormSection> formData = new List <IMultipartFormSection>(); formData.Add(new MultipartFormDataSection("name", name)); if (!string.IsNullOrEmpty(description)) { formData.Add(new MultipartFormDataSection("description", description)); } formData.Add(new MultipartFormFileSection("photo", photoBytes, "photo.jpg", "application/octet-stream")); formData.Add(new MultipartFormDataSection("preserve_original_texture", (!forcePowerOfTwoTexture).ToString())); formData.Add(new MultipartFormDataSection("pipeline", pipeline.GetPipelineTypeName())); if (resources != null) { formData.Add(new MultipartFormDataSection("pipeline_subtype", pipeline_subtype)); formData.Add(new MultipartFormDataSection("resources", CoreTools.GetAvatarCalculationParamsJson(resources))); } var webRequest = UnityWebRequest.Post(GetUrl("avatars"), formData); webRequest.chunkedTransfer = false; SetAuthHeaders(webRequest); return(webRequest); }; Debug.LogFormat("Uploading photo..."); AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, request)); return(request); #else // Unity 5.5.0 (and probably earlier versions) have a weird bug in default multipart form data // implementation, which causes incorrect boundaries between data fields. To work around this bug the // multipart request body is constructed manually, see below. byte[] requestBodyData = null; using (var requestBody = new MultipartBody()) { requestBody.WriteTextField("name", name); requestBody.WriteTextField("description", description); requestBody.WriteFileField("photo", "photo.jpg", photoBytes); requestBody.WriteTextField("preserve_original_texture", (!forcePowerOfTwoTexture).ToString()); requestBody.WriteTextField("pipeline", pipeline.GetPipelineTypeName()); if (resources != null) { requestBody.WriteTextField("pipeline_subtype", pipeline_subtype); requestBody.WriteTextField("resources", CoreTools.GetAvatarCalculationParamsJson(resources)); } requestBody.WriteFooter(); requestBodyData = requestBody.GetRequestBodyData(); Func <UnityWebRequest> webRequestFactory = () => { var webRequest = UnityWebRequest.Post(GetUrl("avatars"), " "); webRequest.uploadHandler = new UploadHandlerRaw(requestBodyData); webRequest.SetRequestHeader( "Content-Type", string.Format("multipart/form-data; boundary=\"{0}\"", requestBody.Boundary) ); webRequest.chunkedTransfer = false; SetAuthHeaders(webRequest); return(webRequest); }; Debug.LogFormat("Uploading photo..."); AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, request)); return(request); } #endif }
/// <summary> /// Call AwaitWebRequestFunc for binary data. /// </summary> public virtual IEnumerator AwaitDataAsync(Func <UnityWebRequest> webRequestFactory, AsyncWebRequest <byte[]> request) { yield return(AwaitWebRequestFunc(webRequestFactory, request, (r) => r.downloadHandler.data)); }
/// <summary> /// Call AwaitWebRequestFunc for string data. /// </summary> public virtual IEnumerator AwaitStringDataAsync(Func <UnityWebRequest> webRequestFactory, AsyncWebRequest <string> request) { yield return(AwaitWebRequestFunc(webRequestFactory, request, (r) => r.downloadHandler.text)); }
/// <summary> /// Helper routine that waits until the request finishes and updates progress for the request object. /// </summary> private static IEnumerator AwaitAndTrackProgress <T> (UnityWebRequest webRequest, AsyncWebRequest <T> request) { #if UNITY_2017_2_OR_NEWER webRequest.SendWebRequest(); #else webRequest.Send(); #endif do { yield return(null); switch (request.ProgressTracking) { case TrackProgress.DOWNLOAD: request.Progress = webRequest.downloadProgress; break; case TrackProgress.UPLOAD: request.Progress = webRequest.uploadProgress; break; } request.BytesDownloaded = webRequest.downloadedBytes; request.BytesUploaded = webRequest.uploadedBytes; } while(!webRequest.isDone); }