Avatar SDK  1.4.0
Realistic avatar generation toolset for Unity3D
Connection.cs
1 /* Copyright (C) Itseez3D, Inc. - All Rights Reserved
2 * You may not use this file except in compliance with an authorized license
3 * Unauthorized copying of this file, via any medium is strictly prohibited
4 * Proprietary and confidential
5 * UNLESS REQUIRED BY APPLICABLE LAW OR AGREED BY ITSEEZ3D, INC. IN WRITING, SOFTWARE DISTRIBUTED UNDER THE LICENSE IS DISTRIBUTED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
6 * CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED
7 * See the License for the specific language governing permissions and limitations under the License.
8 * Written by Itseez3D, Inc. <support@itseez3D.com>, April 2017
9 */
10 
11 using System;
12 using System.Collections;
13 using System.Collections.Generic;
14 using System.IO;
15 using System.Net;
16 using System.Security.Policy;
17 using System.Text;
18 using System.Text.RegularExpressions;
19 using ItSeez3D.AvatarSdk.Core;
20 using UnityEngine;
21 using UnityEngine.Networking;
22 
23 namespace ItSeez3D.AvatarSdk.Cloud
24 {
25  public class Connection
26  {
27  #region Connection data
28 
29  // access data
30  private string tokenType = null, accessToken = null;
31 
37  private string playerUID = null;
38 
39  #endregion
40 
41  #region Helpers
42 
48  public virtual string GetUrl (params string[] urlTokens)
49  {
50  return string.Format ("{0}/{1}/", NetworkUtils.rootUrl, string.Join ("/", urlTokens));
51  }
52 
54  public virtual string GetAuthUrl ()
55  {
56  return GetUrl ("o", "token");
57  }
58 
62  public virtual string AddParamsToUrl(string url, string paramName, string paramValue)
63  {
64  return string.Format("{0}?{1}={2}", url, paramName, paramValue);
65  }
66 
68  public virtual Dictionary<string, string> GetAuthHeaders ()
69  {
70  var headers = new Dictionary<string, string> () {
71  { "Authorization", string.Format ("{0} {1}", tokenType, accessToken) },
72  #if !UNITY_WEBGL
73  { "X-Unity-Plugin-Version", CoreTools.SdkVersion.ToString () },
74  #endif
75  };
76  if (!string.IsNullOrEmpty (playerUID))
77  headers.Add ("X-PlayerUID", playerUID);
78  return headers;
79  }
80 
84  private void SetAuthHeaders (UnityWebRequest request)
85  {
86  var headers = GetAuthHeaders ();
87  foreach (var h in headers)
88  request.SetRequestHeader (h.Key, h.Value);
89  }
90 
95  private UnityWebRequest HttpGet (string url)
96  {
97  if (string.IsNullOrEmpty (url))
98  Debug.LogError ("Provided empty url!");
99  var r = UnityWebRequest.Get (url);
100  SetAuthHeaders (r);
101  return r;
102  }
103 
104  #endregion
105 
106  #region Generic request processing
107 
111  private static IEnumerator AwaitAndTrackProgress<T> (UnityWebRequest webRequest, AsyncWebRequest<T> request)
112  {
113  #if UNITY_2017_1_OR_NEWER
114  webRequest.SendWebRequest();
115  #else
116  webRequest.Send ();
117  #endif
118  do {
119  yield return null;
120 
121  switch (request.ProgressTracking) {
122  case TrackProgress.DOWNLOAD:
123  request.Progress = webRequest.downloadProgress;
124  break;
125  case TrackProgress.UPLOAD:
126  request.Progress = webRequest.uploadProgress;
127  break;
128  }
129 
130  request.BytesDownloaded = webRequest.downloadedBytes;
131  request.BytesUploaded = webRequest.uploadedBytes;
132  } while(!webRequest.isDone);
133  }
134 
139  private static bool IsGoodResponse (UnityWebRequest webRequest, out StatusCode status, out string error)
140  {
141  error = string.Empty;
142 
143  try {
144  status = new StatusCode (webRequest.responseCode);
145 
146 #if UNITY_2017_1_OR_NEWER
147  if (webRequest.isNetworkError) { // apparently the API has changed in 2017
148 #else
149  if (webRequest.isError) {
150 #endif
151  error = webRequest.error;
152  return false;
153  }
154 
155  if (!webRequest.downloadHandler.isDone) {
156  error = "Could not download response";
157  return false;
158  }
159 
160  if (status.IsBad) {
161  error = string.Format ("Bad response code. Msg: {0}", webRequest.downloadHandler.text);
162  return false;
163  }
164  } catch (Exception ex) {
165  Debug.LogException (ex);
166  status = new StatusCode ();
167  error = string.Format ("Exception while checking response: {0}", ex.Message);
168  return false;
169  }
170 
171  return true;
172  }
173 
182  private IEnumerator AwaitWebRequestFunc<T> (
183  Func<UnityWebRequest> webRequestFactory,
184  AsyncWebRequest<T> request,
185  Func<UnityWebRequest, T> parseDataFunc
186  )
187  {
188  UnityWebRequest webRequest = null;
189 
190  StatusCode status = new StatusCode ();
191  string error = string.Empty;
192 
193  int numAttempts = 2, lastAttempt = numAttempts - 1;
194  bool goodResponse = false;
195  for (int attempt = 0; attempt < numAttempts; ++attempt) {
196  webRequest = webRequestFactory ();
197  yield return AwaitAndTrackProgress (webRequest, request);
198 
199  if (goodResponse = IsGoodResponse (webRequest, out status, out error))
200  break;
201 
202  // all API requests have Authorization header, except for authorization requests
203  bool isAuthRequest = webRequest.GetRequestHeader ("Authorization") == null;
204 
205  Debug.LogWarningFormat ("Server error: {0}, request: {1}", error, webRequest.url);
206  if (status.Value != (long)StatusCode.Code.UNAUTHORIZED || isAuthRequest) {
207  // cannot recover, request has failed
208  break;
209  }
210 
211  if (attempt == lastAttempt) {
212  Debug.LogError ("No more retries left");
213  break;
214  }
215 
216  Debug.LogWarning ("Auth issue, let's try one more time after refreshing access token");
217  yield return AuthorizeAsync ();
218  }
219 
220  if (!goodResponse) {
221  Debug.LogError ("Could not send the request");
222  request.Status = status;
223  request.SetError (error);
224  yield break;
225  }
226 
227  T data = default(T);
228  try {
229  data = parseDataFunc (webRequest);
230  } catch (Exception ex) {
231  Debug.LogException (ex);
232  }
233 
234  if (data == null) {
235  request.SetError ("Could not parse request data");
236  yield break;
237  } else {
238  request.Result = data;
239  }
240 
241  request.IsDone = true;
242  }
243 
247  public virtual IEnumerator AwaitWebRequest (Func<UnityWebRequest> webRequestFactory, AsyncWebRequest request)
248  {
249  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => new object ());
250  }
251 
255  public virtual IEnumerator AwaitJsonWebRequest<DataType> (
256  Func<UnityWebRequest> webRequestFactory,
258  {
259  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => {
260  return JsonUtility.FromJson<DataType> (r.downloadHandler.text);
261  });
262  }
263 
267  public virtual IEnumerator AwaitJsonPageWebRequest<T> (
268  Func<UnityWebRequest> webRequestFactory,
269  AsyncWebRequest<Page<T>> request
270  )
271  {
272  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => {
273  // Unity JsonUtility does not support Json array parsing, so we have to hack around it
274  // by wrapping it into object with a single array field.
275  var wrappedArrayJson = string.Format ("{{ \"content\": {0} }}", r.downloadHandler.text);
276  var page = JsonUtility.FromJson<Page<T>> (wrappedArrayJson);
277  var paginationHeader = r.GetResponseHeader ("Link");
278 
279  // parse "Link" header to get links to adjacent pages
280  if (!string.IsNullOrEmpty (paginationHeader)) {
281  var regex = new Regex (@".*<(?<link>.+)>.+rel=""(?<kind>.*)""");
282  var tokens = paginationHeader.Split (',');
283  foreach (var token in tokens) {
284  var match = regex.Match (token);
285  if (!match.Success)
286  continue;
287 
288  string link = match.Groups ["link"].Value, kind = match.Groups ["kind"].Value;
289  if (string.IsNullOrEmpty (link) || string.IsNullOrEmpty (kind))
290  continue;
291 
292  if (kind == "first")
293  page.firstPageUrl = link;
294  else if (kind == "next")
295  page.nextPageUrl = link;
296  else if (kind == "prev")
297  page.prevPageUrl = link;
298  else if (kind == "last")
299  page.lastPageUrl = link;
300  }
301  }
302  return page;
303  });
304  }
305 
309  public virtual IEnumerator AwaitDataAsync (Func<UnityWebRequest> webRequestFactory, AsyncWebRequest<byte[]> request)
310  {
311  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => r.downloadHandler.data);
312  }
313 
318  public virtual AsyncWebRequest<DataType> AvatarJsonRequest<DataType> (string url)
319  {
320  var request = new AsyncWebRequest<DataType> ();
321  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (() => HttpGet (url), request));
322  return request;
323  }
324 
329  public virtual AsyncWebRequest<Page<T>> AvatarJsonPageRequest<T> (string url)
330  {
331  var request = new AsyncWebRequest<Page<T>> ();
332  AvatarSdkMgr.SpawnCoroutine (AwaitJsonPageWebRequest (() => HttpGet (url), request));
333  return request;
334  }
335 
340  public virtual AsyncWebRequest<Page<T>> AvatarJsonPageRequest<T> (string baseUrl, int pageNumber)
341  {
342  return AvatarJsonPageRequest<T> (string.Format ("{0}?page={1}", baseUrl, pageNumber));
343  }
344 
348  public virtual IEnumerator AwaitMultiplePages<T> (string url, AsyncRequest<T[]> request, int maxItems = int.MaxValue)
349  {
350  List<T> items = new List<T> ();
351  do {
352  var pageRequest = AvatarJsonPageRequest<T> (url);
353  yield return pageRequest;
354  if (pageRequest.IsError) {
355  request.SetError (string.Format ("Page request failed. Error: {0}", pageRequest.ErrorMessage));
356  yield break;
357  }
358 
359  Debug.LogFormat ("Successfully loaded page {0}", url);
360  var page = pageRequest.Result;
361  items.AddRange (page.content);
362  url = page.nextPageUrl;
363  } while (items.Count < maxItems && !string.IsNullOrEmpty (url));
364 
365  request.Result = items.ToArray ();
366  request.IsDone = true;
367  }
368 
372  public virtual AsyncWebRequest<DataType[]> AvatarJsonArrayRequest<DataType> (string url, int maxItems = int.MaxValue)
373  {
374  var request = new AsyncWebRequest<DataType[]> ();
375  AvatarSdkMgr.SpawnCoroutine (AwaitMultiplePages (url, request, maxItems));
376  return request;
377  }
378 
383  {
384  Debug.LogFormat ("Downloading from {0}...", url);
385  var request = new AsyncWebRequest<byte[]> ();
386  AvatarSdkMgr.SpawnCoroutine (AwaitDataAsync (() => HttpGet (url), request));
387  return request;
388  }
389 
390 #endregion
391 
392 #region Auth functions
393 
397  public virtual bool IsAuthorized { get { return !string.IsNullOrEmpty (accessToken); } }
398 
399  public virtual string TokenType { get { return tokenType; } }
400 
401  public virtual string AccessToken { get { return accessToken; } }
402 
406  public virtual string PlayerUID {
407  get { return playerUID; }
408  set { playerUID = value; }
409  }
410 
414  private IEnumerator Authorize (AsyncRequest request)
415  {
416  var accessCredentials = AuthUtils.LoadCredentials ();
417  if (accessCredentials == null || string.IsNullOrEmpty (accessCredentials.clientSecret)) {
418  request.SetError ("Could not find API keys! Please provide valid credentials via Window->ItSeez3D Avatar SDK");
419  yield break;
420  }
421 
422  var authRequest = AuthorizeClientCredentialsGrantTypeAsync (accessCredentials);
423  yield return request.AwaitSubrequest (authRequest, 0.5f);
424  if (request.IsError)
425  yield break;
426 
427  tokenType = authRequest.Result.token_type;
428  accessToken = authRequest.Result.access_token;
429  Debug.LogFormat ("Successful authentication!");
430 
431  // guarantees we re-register a Player if clientId changes
432  var playerIdentifier = string.Format ("player_uid_{0}", accessCredentials.clientId.Substring (0, accessCredentials.clientId.Length / 3));
433 
434  if (string.IsNullOrEmpty (playerUID))
435  playerUID = AvatarSdkMgr.Storage ().LoadPlayerUID (playerIdentifier);
436 
437  if (string.IsNullOrEmpty (playerUID)) {
438  Debug.Log ("Registering new player UID");
439  var playerRequest = RegisterPlayerAsync ();
440  yield return request.AwaitSubrequest (playerRequest, 1);
441  if (request.IsError)
442  yield break;
443 
444  playerUID = playerRequest.Result.code;
445  AvatarSdkMgr.Storage ().StorePlayerUID (playerIdentifier, playerUID);
446  }
447 
448  request.IsDone = true;
449  }
450 
454  public virtual AsyncRequest AuthorizeAsync ()
455  {
456  var request = new AsyncRequest (AvatarSdkMgr.Str (Strings.Authentication));
457  AvatarSdkMgr.SpawnCoroutine (Authorize (request));
458  return request;
459  }
460 
466  public virtual void AuthorizeWithCredentials (string tokenType, string accessToken, string playerUID)
467  {
468  this.tokenType = tokenType;
469  this.accessToken = accessToken;
470  this.playerUID = playerUID;
471  }
472 
476  private AsyncWebRequest<AccessData> AuthorizeClientCredentialsGrantTypeAsync (
477  AccessCredentials credentials
478  )
479  {
480  var request = new AsyncWebRequest<AccessData> (AvatarSdkMgr.Str (Strings.RequestingApiToken));
481  var form = new Dictionary<string,string> () {
482  { "grant_type", "client_credentials" },
483  { "client_id", credentials.clientId },
484  { "client_secret", credentials.clientSecret },
485  };
486  Func<UnityWebRequest> webRequestFactory = () => UnityWebRequest.Post (GetAuthUrl (), form);
487  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
488  return request;
489  }
490 
494  private AsyncWebRequest<AccessData> AuthorizePasswordGrantTypeAsync (
495  string clientId,
496  string clientSecret,
497  string username,
498  string password
499  )
500  {
501  Debug.LogWarning ("Don't use this auth method in production, use other grant types!");
502  var request = new AsyncWebRequest<AccessData> (AvatarSdkMgr.Str (Strings.RequestingApiToken));
503 
504  if (string.IsNullOrEmpty (username) || string.IsNullOrEmpty (password) || string.IsNullOrEmpty (clientId)) {
505  request.SetError ("itSeez3D credentials not provided");
506  Debug.LogError (request.ErrorMessage);
507  return request;
508  }
509 
510  var form = new Dictionary<string,string> () {
511  { "grant_type", "password" },
512  { "username", username },
513  { "password", password },
514  { "client_id", clientId },
515  { "client_secret", clientSecret },
516  };
517  Func<UnityWebRequest> webRequestFactory = () => UnityWebRequest.Post (GetUrl ("o", "token"), form);
518  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
519  return request;
520  }
521 
526  public virtual AsyncWebRequest<Player> RegisterPlayerAsync (string comment = "")
527  {
528  var r = new AsyncWebRequest<Player> (AvatarSdkMgr.Str (Strings.RegisteringPlayerID));
529  var form = new Dictionary<string,string> () {
530  { "comment", comment },
531  };
532  Func<UnityWebRequest> webRequestFactory = () => {
533  var webRequest = UnityWebRequest.Post (GetUrl ("players"), form);
534  SetAuthHeaders (webRequest);
535  return webRequest;
536  };
537  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, r));
538  return r;
539  }
540 
541 #endregion
542 
543 #region Creating/awaiting/downloading an avatar
544 
549  public virtual AsyncWebRequest<AvatarData> CreateAvatarWithPhotoAsync (string name, string description, byte[] photoBytes, bool forcePowerOfTwoTexture = false)
550  {
551  var request = new AsyncWebRequest<AvatarData> (AvatarSdkMgr.Str (Strings.UploadingPhoto), TrackProgress.UPLOAD);
552 
553 #if UNITY_2017_1_OR_NEWER
554  Func<UnityWebRequest> webRequestFactory = () =>
555  {
556  List<IMultipartFormSection> formData = new List<IMultipartFormSection>();
557  formData.Add(new MultipartFormDataSection("name", name));
558  formData.Add(new MultipartFormDataSection("description", description));
559  formData.Add(new MultipartFormFileSection("photo", photoBytes, "photo.jpg", "application/octet-stream"));
560  formData.Add(new MultipartFormDataSection("preserve_original_texture", (!forcePowerOfTwoTexture).ToString()));
561  formData.Add(new MultipartFormDataSection("pipeline", "animated_face"));
562 
563  var webRequest = UnityWebRequest.Post(GetUrl("avatars"), formData);
564  SetAuthHeaders(webRequest);
565  return webRequest;
566  };
567 
568  Debug.LogFormat("Uploading photo...");
569  AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, request));
570  return request;
571 #else
572  // Unity 5.5.0 (and probably earlier versions) have a weird bug in default multipart form data
573  // implementation, which causes incorrect boundaries between data fields. To work around this bug the
574  // multipart request body is constructed manually, see below.
575  byte[] requestBodyData = null;
576  using (var requestBody = new MultipartBody ()) {
577  requestBody.WriteTextField ("name", name);
578  requestBody.WriteTextField ("description", description);
579  requestBody.WriteFileField ("photo", "photo.jpg", photoBytes);
580  requestBody.WriteTextField ("preserve_original_texture", (!forcePowerOfTwoTexture).ToString ());
581  requestBody.WriteTextField ("pipeline", "animated_face");
582  requestBody.WriteFooter ();
583  requestBodyData = requestBody.GetRequestBodyData ();
584 
585  Func<UnityWebRequest> webRequestFactory = () => {
586  var webRequest = UnityWebRequest.Post (GetUrl ("avatars"), " ");
587  webRequest.uploadHandler = new UploadHandlerRaw (requestBodyData);
588  webRequest.SetRequestHeader (
589  "Content-Type", string.Format ("multipart/form-data; boundary=\"{0}\"", requestBody.Boundary)
590  );
591  SetAuthHeaders (webRequest);
592  return webRequest;
593  };
594 
595  Debug.LogFormat ("Uploading photo...");
596  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
597  return request;
598  }
599 #endif
600  }
601 
605  public virtual AsyncWebRequest<AvatarData> GetAvatarAsync (string avatarCode)
606  {
607  var r = AvatarJsonRequest<AvatarData> (GetUrl ("avatars", avatarCode));
608  r.State = AvatarSdkMgr.Str (Strings.GettingAvatarInfo);
609  return r;
610  }
611 
616  {
617  var r = AvatarJsonArrayRequest<AvatarHaircutData> (avatar.haircuts);
618  r.State = AvatarSdkMgr.Str (Strings.RequestingHaircutInfo);
619  return r;
620  }
621 
626  {
627  var r = AvatarJsonArrayRequest<TextureData> (GetUrl ("avatars", avatar.code, "textures"));
628  r.State = AvatarSdkMgr.Str (Strings.RequestingTextureInfo);
629  return r;
630  }
631 
637  public virtual AsyncWebRequest<byte[]> DownloadMeshZipAsync (AvatarData avatar, int levelOfDetails = 0)
638  {
639  var r = AvatarDataRequestAsync (AddParamsToUrl(avatar.mesh, "lod", levelOfDetails.ToString()));
640  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadMesh);
641  return r;
642  }
643 
648  {
649  var r = AvatarDataRequestAsync (GetUrl ("avatars", avatar.code, "pointcloud"));
650  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadMesh);
651  return r;
652  }
653 
658  {
659  var r = AvatarDataRequestAsync (avatar.texture);
660  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadTexture);
661  return r;
662  }
663 
668  {
669  var r = AvatarDataRequestAsync (haircut.mesh);
670  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutMesh);
671  return r;
672  }
673 
678  {
679  var r = AvatarDataRequestAsync (haircut.texture);
680  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutTexture);
681  return r;
682  }
683 
688  {
689  var r = AvatarDataRequestAsync (haircut.pointcloud);
690  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutPointCloud);
691  return r;
692  }
693 
699  {
700  string url = string.Format ("{0}pointclouds/", avatar.haircuts);
701  var r = AvatarDataRequestAsync (url);
702  r.State = AvatarSdkMgr.Str (Strings.DownloadingAllHaircutPointClouds);
703  return r;
704  }
705 
711  public virtual AsyncWebRequest<byte[]> DownloadBlendshapesZipAsync (AvatarData avatar, BlendshapesFormat format = BlendshapesFormat.BIN, int levelOfDetails = 0)
712  {
713  var fmtToStr = new Dictionary<BlendshapesFormat, string> {
714  { BlendshapesFormat.BIN, "bin" },
715  { BlendshapesFormat.FBX, "fbx" },
716  { BlendshapesFormat.PLY, "ply" },
717  };
718 
719  string url = string.Format ("{0}?fmt={1}&lod={2}", avatar.blendshapes, fmtToStr [format], levelOfDetails);
720  var r = AvatarDataRequestAsync (url);
721  r.State = AvatarSdkMgr.Str (Strings.DownloadingBlendshapes);
722  return r;
723  }
724 
725 #endregion
726 
727 #region Actions with avatars on the server (list/update/delete/...)
728 
732  public virtual AsyncWebRequest<Page<AvatarData>> GetAvatarsPageAsync (int pageNumber)
733  {
734  var r = AvatarJsonPageRequest<AvatarData> (GetUrl ("avatars"), pageNumber);
735  r.State = AvatarSdkMgr.Str (Strings.GettingAvatarList);
736  return r;
737  }
738 
742  public virtual AsyncRequest<AvatarData[]> GetAvatarsAsync (int maxItems = int.MaxValue)
743  {
744  var r = AvatarJsonArrayRequest<AvatarData> (GetUrl ("avatars"), maxItems);
745  r.State = AvatarSdkMgr.Str (Strings.GettingAvatarList);
746  return r;
747  }
748 
752  public virtual AsyncWebRequest EditAvatarAsync (AvatarData avatar, string name = null, string description = null)
753  {
754  var request = new AsyncWebRequest (AvatarSdkMgr.Str (Strings.EditingAvatar));
755 
756  byte[] requestBodyData = null;
757  using (var requestBody = new MultipartBody ()) {
758  requestBody.WriteTextField ("name", name);
759  requestBody.WriteTextField ("description", description);
760  requestBody.WriteFooter ();
761  requestBodyData = requestBody.GetRequestBodyData ();
762 
763  Func<UnityWebRequest> webRequestFactory = () => {
764  var webRequest = UnityWebRequest.Post (avatar.url, " ");
765  webRequest.method = "PATCH";
766  webRequest.uploadHandler = new UploadHandlerRaw (requestBodyData);
767  webRequest.SetRequestHeader (
768  "Content-Type", string.Format ("multipart/form-data; boundary=\"{0}\"", requestBody.Boundary)
769  );
770  SetAuthHeaders (webRequest);
771  return webRequest;
772  };
773 
774  Debug.LogFormat ("Uploading photo...");
775  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
776  return request;
777  }
778  }
779 
784  {
785  var request = new AsyncWebRequest (AvatarSdkMgr.Str (Strings.DeletingAvatarOnServer));
786 
787  Func<UnityWebRequest> webRequestFactory = () => {
788  var webRequest = UnityWebRequest.Delete (avatar.url);
789  SetAuthHeaders (webRequest);
790  webRequest.downloadHandler = new DownloadHandlerBuffer ();
791  return webRequest;
792  };
793  AvatarSdkMgr.SpawnCoroutine (AwaitWebRequest (webRequestFactory, request));
794  return request;
795  }
796 
797 #endregion
798 
799 #region Higher-level API, composite requests
800 
804  private IEnumerator AwaitAvatarCalculationsLoop (AvatarData avatar, AsyncRequest<AvatarData> request)
805  {
806  while (!Strings.FinalStates.Contains (avatar.status)) {
807  yield return new WaitForSeconds (4);
808  var avatarStatusRequest = GetAvatarAsync (avatar.code);
809  yield return avatarStatusRequest;
810 
811  if (avatarStatusRequest.Status.Value == (long)StatusCode.Code.NOT_FOUND) {
812  Debug.LogWarning ("404 error most likely means that avatar was deleted from the server");
813  request.SetError (string.Format ("Avatar status response: {0}", avatarStatusRequest.ErrorMessage));
814  yield break;
815  }
816 
817  if (avatarStatusRequest.Status.Value == (long)StatusCode.Code.TOO_MANY_REQUESTS_THROTTLING) {
818  Debug.LogWarning ("Too many requests!");
819  yield return new WaitForSeconds (10);
820  }
821 
822  if (avatarStatusRequest.IsError) {
823  Debug.LogWarningFormat ("Status polling error: {0}", avatarStatusRequest.ErrorMessage);
824  // Most likely this is a temporary issue. Keep polling.
825  continue;
826  }
827 
828  avatar = avatarStatusRequest.Result;
829  Debug.LogFormat ("Status: {0}, progress: {1}%", avatar.status, avatar.progress);
830  request.State = AvatarSdkMgr.Str (avatar.status);
831 
832  if (avatar.status == Strings.Computing)
833  request.Progress = (float)avatar.progress / 100;
834  }
835 
836  if (Strings.GoodFinalStates.Contains (avatar.status)) {
837  request.Result = avatar;
838  request.IsDone = true;
839  } else {
840  request.SetError (string.Format ("Avatar calculations failed, status: {0}", avatar.status));
841  }
842  }
843 
850  {
851  var request = new AsyncRequest <AvatarData> (AvatarSdkMgr.Str (Strings.StartingCalculations));
852  AvatarSdkMgr.SpawnCoroutine (AwaitAvatarCalculationsLoop (avatar, request));
853  return request;
854  }
855 
856 #endregion
857  }
858 }
static void SpawnCoroutine(IEnumerator routine)
Spawn coroutine outside of MonoBehaviour.
Definition: AvatarSdkMgr.cs:85
virtual AsyncWebRequest< AvatarHaircutData[]> GetHaircutsAsync(AvatarData avatar)
Get list of all haircuts for avatar.
Definition: Connection.cs:615
virtual Dictionary< string, string > GetAuthHeaders()
Dictionary with required auth HTTP headers.
Definition: Connection.cs:68
virtual AsyncWebRequest< AvatarData > GetAvatarAsync(string avatarCode)
Get avatar information by code.
Definition: Connection.cs:605
virtual string AddParamsToUrl(string url, string paramName, string paramValue)
Helper function that adds parameter to url
Definition: Connection.cs:62
virtual string GetUrl(params string[] urlTokens)
Helper function to construct absolute url from relative url tokens.
Definition: Connection.cs:48
Code
Codes that can be returned by the Avatar Web API.
Definition: StatusCode.cs:24
virtual AsyncRequest AuthorizeAsync()
Authorize this session using the credentials loaded from encrypted binary resource.
Definition: Connection.cs:454
static string Str(string s)
Return string modified (e.g. translated) by string manager.
Definition: AvatarSdkMgr.cs:94
virtual IEnumerator AwaitWebRequest(Func< UnityWebRequest > webRequestFactory, AsyncWebRequest request)
Variation of AwaitWebRequestFunc when we don&#39;t actually need the result.
Definition: Connection.cs:247
TrackProgress
For web requests we can track progress for either upload or download, not both.
virtual AsyncWebRequest< byte[]> DownloadMeshZipAsync(AvatarData avatar, int levelOfDetails=0)
Download mesh zip file into memory.
Definition: Connection.cs:637
virtual AsyncRequest< AvatarData[]> GetAvatarsAsync(int maxItems=int.MaxValue)
Get "maxItems" latest avatars (will loop through the pages).
Definition: Connection.cs:742
Unity 5.5.0 (and probably earlier versions) have a weird bug in default multipart form data implement...
virtual AsyncWebRequest< byte[]> DownloadPointCloudZipAsync(AvatarData avatar)
Downloading coordinates of the vertices of the head model. This can be used to save download time...
Definition: Connection.cs:647
The main return type for most SDK calls. Analogue of Unity&#39;s WWW and UnityWebRequest classes...
Definition: AsyncRequest.cs:25
virtual AsyncWebRequest< byte[]> AvatarDataRequestAsync(string url)
Download file.
Definition: Connection.cs:382
virtual AsyncWebRequest< byte[]> DownloadBlendshapesZipAsync(AvatarData avatar, BlendshapesFormat format=BlendshapesFormat.BIN, int levelOfDetails=0)
Downloads zip archive with all the blendshapes.
Definition: Connection.cs:711
virtual AsyncWebRequest< AvatarData > CreateAvatarWithPhotoAsync(string name, string description, byte[] photoBytes, bool forcePowerOfTwoTexture=false)
Upload photo and create avatar instance on the server. Calculations will start right away after the p...
Definition: Connection.cs:549
virtual string GetAuthUrl()
Url used to obtain access token.
Definition: Connection.cs:54
Utility class (singleton) that takes care of a few things. 1) Singleton instance is a MonoBehaviour a...
Definition: AvatarSdkMgr.cs:27
virtual AsyncWebRequest EditAvatarAsync(AvatarData avatar, string name=null, string description=null)
Edit avatar name/description on the server.
Definition: Connection.cs:752
virtual AsyncWebRequest< TextureData[]> GetTexturesAsync(AvatarData avatar)
Get list of all textures for avatar.
Definition: Connection.cs:625
virtual AsyncWebRequest< byte[]> DownloadAllHaircutPointCloudsZipAsync(AvatarData avatar)
Downloads zip archive with point clouds for all haircuts. It is recommended to use this request for l...
Definition: Connection.cs:698
virtual AsyncWebRequest< byte[]> DownloadTextureBytesAsync(AvatarData avatar)
Download main texture into memory. Can be used right away to create Unity texture.
Definition: Connection.cs:657
Async request for web requests.
virtual AsyncWebRequest DeleteAvatarAsync(AvatarData avatar)
Delete avatar record on the server (does not delete local files).
Definition: Connection.cs:783
virtual AsyncWebRequest< Page< AvatarData > > GetAvatarsPageAsync(int pageNumber)
Get a particular page in the list of avatars.
Definition: Connection.cs:732
static IPersistentStorage Storage()
Return IPeristentStorage implementation.
Strings that occur in the plugin and can be potentially displayed on the screen (for example when sho...
virtual AsyncWebRequest< byte[]> DownloadHaircutMeshZipAsync(AvatarHaircutData haircut)
Downloads haircut zip file into memory.
Definition: Connection.cs:667
virtual AsyncWebRequest< byte[]> DownloadHaircutTextureBytesAsync(AvatarHaircutData haircut)
Download haircut texture into memory. Can be used right away to create Unity texture.
Definition: Connection.cs:677
virtual AsyncWebRequest< byte[]> DownloadHaircutPointCloudZipAsync(AvatarHaircutData haircut)
Downloads the haircut point cloud zip into memory.
Definition: Connection.cs:687
virtual void AuthorizeWithCredentials(string tokenType, string accessToken, string playerUID)
Provide your own credentials to authorize the session. This method is useful if you don&#39;t want to sto...
Definition: Connection.cs:466
virtual IEnumerator AwaitDataAsync(Func< UnityWebRequest > webRequestFactory, AsyncWebRequest< byte[]> request)
Call AwaitWebRequestFunc for binary data.
Definition: Connection.cs:309
Helper class for HTTP status codes.
Definition: StatusCode.cs:19
virtual AsyncWebRequest< Player > RegisterPlayerAsync(string comment="")
Register unique player UID that is used later to sign the requests.
Definition: Connection.cs:526
virtual AsyncRequest< AvatarData > AwaitAvatarCalculationsAsync(AvatarData avatar)
Wait until avatar is calculated. Report progress through the async request object. This function will return error (request.IsError == true) only if calculations failed on server or avatar has been deleted from the server. In all other cases it will continue to poll status.
Definition: Connection.cs:849