Avatar SDK  1.6.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@avatarsdk.com>, April 2017
9 */
10 
11 using ItSeez3D.AvatarSdk.Core;
12 using System;
13 using System.Collections;
14 using System.Collections.Generic;
15 using System.Text.RegularExpressions;
16 using UnityEngine;
17 using UnityEngine.Networking;
18 
19 namespace ItSeez3D.AvatarSdk.Cloud
20 {
21  public class Connection
22  {
23  #region constants
24  private readonly string pipeline_subtype = "base/legacy";
25  #endregion
26 
27  #region Connection data
28 
32  private string rootServerUrl = NetworkUtils.rootUrl;
33  public string RootServerUrl {
34  get { return rootServerUrl; }
35  set { rootServerUrl = value; }
36  }
37 
38  // access data
39  private string tokenType = null, accessToken = null;
40 
46  private string playerUID = null;
47 
48  #endregion
49 
50  #region Helpers
51 
57  public virtual string GetUrl (params string[] urlTokens)
58  {
59  return string.Format ("{0}/{1}/", RootServerUrl, string.Join ("/", urlTokens));
60  }
61 
65  public virtual string UrlWithParams(string url, Dictionary<string, string> param)
66  {
67  if (param == null || param.Count == 0)
68  return url;
69 
70  var paramTokens = new List<string> ();
71  foreach (var item in param) {
72  var token = string.Format ("{0}={1}", WWW.EscapeURL (item.Key), WWW.EscapeURL (item.Value));
73  paramTokens.Add (token);
74  }
75 
76  return string.Format ("{0}?{1}", url, string.Join ("&", paramTokens.ToArray()));
77  }
78 
82  public virtual string UrlWithParams (string url, string param, string value)
83  {
84  return UrlWithParams (url, new Dictionary<string, string> { { param, value } });
85  }
86 
88  public virtual string GetAuthUrl ()
89  {
90  return GetUrl ("o", "token");
91  }
92 
94  public virtual Dictionary<string, string> GetAuthHeaders ()
95  {
96  var headers = new Dictionary<string, string> () {
97  { "Authorization", string.Format ("{0} {1}", tokenType, accessToken) },
98  #if !UNITY_WEBGL
99  { "X-Unity-Plugin-Version", CoreTools.SdkVersion.ToString () },
100  #endif
101  };
102  if (!string.IsNullOrEmpty (playerUID))
103  headers.Add ("X-PlayerUID", playerUID);
104  return headers;
105  }
106 
110  protected void SetAuthHeaders (UnityWebRequest request)
111  {
112  var headers = GetAuthHeaders ();
113  foreach (var h in headers)
114  request.SetRequestHeader (h.Key, h.Value);
115  }
116 
121  protected UnityWebRequest HttpGet (string url)
122  {
123  if (string.IsNullOrEmpty (url))
124  Debug.LogError ("Provided empty url!");
125  var r = UnityWebRequest.Get (url);
126  SetAuthHeaders (r);
127  return r;
128  }
129 
130  protected UnityWebRequest HttpPost(string url, Dictionary<string, string> form)
131  {
132  var r = UnityWebRequest.Post(url, form);
133  r.chunkedTransfer = false; // chunked transfer causes problems with UWSGI
134  return r;
135  }
136  #endregion
137 
138  #region Generic request processing
139 
143  private static IEnumerator AwaitAndTrackProgress<T> (UnityWebRequest webRequest, AsyncWebRequest<T> request)
144  {
145  #if UNITY_2017_2_OR_NEWER
146  webRequest.SendWebRequest();
147  #else
148  webRequest.Send ();
149  #endif
150  do {
151  yield return null;
152 
153  switch (request.ProgressTracking) {
154  case TrackProgress.DOWNLOAD:
155  request.Progress = webRequest.downloadProgress;
156  break;
157  case TrackProgress.UPLOAD:
158  request.Progress = webRequest.uploadProgress;
159  break;
160  }
161 
162  request.BytesDownloaded = webRequest.downloadedBytes;
163  request.BytesUploaded = webRequest.uploadedBytes;
164  } while(!webRequest.isDone);
165  }
166 
171  private static bool IsGoodResponse (UnityWebRequest webRequest, out StatusCode status, out string error)
172  {
173  error = string.Empty;
174 
175  try {
176  status = new StatusCode (webRequest.responseCode);
177 
178 #if UNITY_2017_1_OR_NEWER
179  if (webRequest.isNetworkError) { // apparently the API has changed in 2017
180 #else
181  if (webRequest.isError) {
182 #endif
183  error = webRequest.error;
184  return false;
185  }
186 
187  if (!webRequest.downloadHandler.isDone) {
188  error = "Could not download response";
189  return false;
190  }
191 
192  if (status.IsBad) {
193  error = string.Format ("Bad response code. Msg: {0}", webRequest.downloadHandler.text);
194  return false;
195  }
196  } catch (Exception ex) {
197  Debug.LogException (ex);
198  status = new StatusCode ();
199  error = string.Format ("Exception while checking response: {0}", ex.Message);
200  return false;
201  }
202 
203  return true;
204  }
205 
214  private IEnumerator AwaitWebRequestFunc<T> (
215  Func<UnityWebRequest> webRequestFactory,
216  AsyncWebRequest<T> request,
217  Func<UnityWebRequest, T> parseDataFunc
218  )
219  {
220  UnityWebRequest webRequest = null;
221 
222  StatusCode status = new StatusCode ();
223  string error = string.Empty;
224 
225  int numAttempts = 2, lastAttempt = numAttempts - 1;
226  bool goodResponse = false;
227  for (int attempt = 0; attempt < numAttempts; ++attempt) {
228  webRequest = webRequestFactory ();
229  yield return AwaitAndTrackProgress (webRequest, request);
230 
231  if (goodResponse = IsGoodResponse (webRequest, out status, out error))
232  break;
233 
234  // all API requests have Authorization header, except for authorization requests
235  bool isAuthRequest = webRequest.GetRequestHeader ("Authorization") == null;
236 
237  Debug.LogWarningFormat ("Server error: {0}, request: {1}", error, webRequest.url);
238  if (status.Value != (long)StatusCode.Code.UNAUTHORIZED || isAuthRequest) {
239  // cannot recover, request has failed
240  break;
241  }
242 
243  if (attempt == lastAttempt) {
244  Debug.LogError ("No more retries left");
245  break;
246  }
247 
248  Debug.LogWarning ("Auth issue, let's try one more time after refreshing access token");
249  yield return AuthorizeAsync ();
250  }
251 
252  if (!goodResponse) {
253  Debug.LogErrorFormat ("Could not send the request, status: {0}, error: {1}", status, error);
254  request.Status = status;
255  request.SetError (error);
256  yield break;
257  }
258 
259  T data = default(T);
260  try {
261  data = parseDataFunc (webRequest);
262  } catch (Exception ex) {
263  Debug.LogException (ex);
264  }
265 
266  if (data == null) {
267  request.SetError ("Could not parse request data");
268  yield break;
269  } else {
270  request.Result = data;
271  }
272 
273  request.IsDone = true;
274  }
275 
279  public virtual IEnumerator AwaitWebRequest (Func<UnityWebRequest> webRequestFactory, AsyncWebRequest request)
280  {
281  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => new object ());
282  }
283 
287  public virtual IEnumerator AwaitJsonWebRequest<DataType> (
288  Func<UnityWebRequest> webRequestFactory,
290  {
291  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => {
292  return JsonUtility.FromJson<DataType> (r.downloadHandler.text);
293  });
294  }
295 
299  public virtual IEnumerator AwaitJsonPageWebRequest<T> (
300  Func<UnityWebRequest> webRequestFactory,
301  AsyncWebRequest<Page<T>> request
302  )
303  {
304  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => {
305  // Unity JsonUtility does not support Json array parsing, so we have to hack around it
306  // by wrapping it into object with a single array field.
307  var wrappedArrayJson = string.Format ("{{ \"content\": {0} }}", r.downloadHandler.text);
308  var page = JsonUtility.FromJson<Page<T>> (wrappedArrayJson);
309  var paginationHeader = r.GetResponseHeader ("Link");
310 
311  // parse "Link" header to get links to adjacent pages
312  if (!string.IsNullOrEmpty (paginationHeader)) {
313  var regex = new Regex (@".*<(?<link>.+)>.+rel=""(?<kind>.*)""");
314  var tokens = paginationHeader.Split (',');
315  foreach (var token in tokens) {
316  var match = regex.Match (token);
317  if (!match.Success)
318  continue;
319 
320  string link = match.Groups ["link"].Value, kind = match.Groups ["kind"].Value;
321  if (string.IsNullOrEmpty (link) || string.IsNullOrEmpty (kind))
322  continue;
323 
324  if (kind == "first")
325  page.firstPageUrl = link;
326  else if (kind == "next")
327  page.nextPageUrl = link;
328  else if (kind == "prev")
329  page.prevPageUrl = link;
330  else if (kind == "last")
331  page.lastPageUrl = link;
332  }
333  }
334  return page;
335  });
336  }
337 
341  public virtual IEnumerator AwaitDataAsync (Func<UnityWebRequest> webRequestFactory, AsyncWebRequest<byte[]> request)
342  {
343  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => r.downloadHandler.data);
344  }
345 
349  public virtual IEnumerator AwaitStringDataAsync(Func<UnityWebRequest> webRequestFactory, AsyncWebRequest<string> request)
350  {
351  yield return AwaitWebRequestFunc(webRequestFactory, request, (r) => r.downloadHandler.text);
352  }
353 
358  public virtual AsyncWebRequest<DataType> AvatarJsonRequest<DataType> (string url)
359  {
360  var request = new AsyncWebRequest<DataType> ();
361  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (() => HttpGet (url), request));
362  return request;
363  }
364 
369  public virtual AsyncWebRequest<Page<T>> AvatarJsonPageRequest<T> (string url)
370  {
371  var request = new AsyncWebRequest<Page<T>> ();
372  AvatarSdkMgr.SpawnCoroutine (AwaitJsonPageWebRequest (() => HttpGet (url), request));
373  return request;
374  }
375 
380  public virtual AsyncWebRequest<Page<T>> AvatarJsonPageRequest<T> (string baseUrl, int pageNumber)
381  {
382  return AvatarJsonPageRequest<T> (string.Format ("{0}?page={1}", baseUrl, pageNumber));
383  }
384 
388  public virtual IEnumerator AwaitMultiplePages<T> (string url, AsyncRequest<T[]> request, int maxItems = int.MaxValue)
389  {
390  List<T> items = new List<T> ();
391  do {
392  var pageRequest = AvatarJsonPageRequest<T> (url);
393  yield return pageRequest;
394  if (pageRequest.IsError) {
395  request.SetError (string.Format ("Page request failed. Error: {0}", pageRequest.ErrorMessage));
396  yield break;
397  }
398 
399  // Debug.LogFormat ("Successfully loaded page {0}", url);
400  var page = pageRequest.Result;
401 
402  for (int i = 0; i < page.content.Length && items.Count < maxItems; ++i)
403  items.Add (page.content[i]);
404 
405  url = page.nextPageUrl;
406  } while (items.Count < maxItems && !string.IsNullOrEmpty (url));
407 
408  request.Result = items.ToArray ();
409  request.IsDone = true;
410  }
411 
415  public virtual AsyncWebRequest<DataType[]> AvatarJsonArrayRequest<DataType> (string url, int maxItems = int.MaxValue)
416  {
417  var request = new AsyncWebRequest<DataType[]> ();
418  AvatarSdkMgr.SpawnCoroutine (AwaitMultiplePages (url, request, maxItems));
419  return request;
420  }
421 
426  {
427  Debug.LogFormat ("Downloading from {0}...", url);
428  var request = new AsyncWebRequest<byte[]> ();
429  AvatarSdkMgr.SpawnCoroutine (AwaitDataAsync (() => HttpGet (url), request));
430  return request;
431  }
432 
436  public virtual AsyncWebRequest<ModelInfo> GetModelInfoAsync(string avatarCode)
437  {
438  var r = AvatarJsonRequest<ModelInfo>(GetUrl("avatars", avatarCode, "model_info"));
439  r.State = AvatarSdkMgr.Str(Strings.RequestingModelInfo);
440  return r;
441  }
442 
443 #endregion
444 
445 #region Auth functions
446 
450  public virtual bool IsAuthorized { get { return !string.IsNullOrEmpty (accessToken); } }
451 
452  public virtual string TokenType { get { return tokenType; } }
453 
454  public virtual string AccessToken { get { return accessToken; } }
455 
459  public virtual string PlayerUID {
460  get { return playerUID; }
461  set { playerUID = value; }
462  }
463 
467  private IEnumerator Authorize (AsyncRequest request)
468  {
469  var accessCredentials = AuthUtils.LoadCredentials ();
470  if (accessCredentials == null || string.IsNullOrEmpty (accessCredentials.clientSecret)) {
471  request.SetError ("Could not find API keys! Please provide valid credentials via Window->ItSeez3D Avatar SDK");
472  yield break;
473  }
474 
475  var authRequest = AuthorizeClientCredentialsGrantTypeAsync (accessCredentials);
476  yield return request.AwaitSubrequest (authRequest, 0.5f);
477  if (request.IsError)
478  yield break;
479 
480  tokenType = authRequest.Result.token_type;
481  accessToken = authRequest.Result.access_token;
482  Debug.LogFormat ("Successful authentication!");
483 
484  // guarantees we re-register a Player if clientId changes
485  var playerIdentifier = string.Format ("player_uid_{0}", accessCredentials.clientId.Substring (0, accessCredentials.clientId.Length / 3));
486 
487  if (string.IsNullOrEmpty (playerUID))
488  playerUID = AvatarSdkMgr.Storage ().LoadPlayerUID (playerIdentifier);
489 
490  if (string.IsNullOrEmpty (playerUID)) {
491  Debug.Log ("Registering new player UID");
492  var playerRequest = RegisterPlayerAsync ();
493  yield return request.AwaitSubrequest (playerRequest, 1);
494  if (request.IsError)
495  yield break;
496 
497  playerUID = playerRequest.Result.code;
498  AvatarSdkMgr.Storage ().StorePlayerUID (playerIdentifier, playerUID);
499  }
500 
501  request.IsDone = true;
502  }
503 
507  public virtual AsyncRequest AuthorizeAsync ()
508  {
509  var request = new AsyncRequest (AvatarSdkMgr.Str (Strings.Authentication));
510  AvatarSdkMgr.SpawnCoroutine (Authorize (request));
511  return request;
512  }
513 
519  public virtual void AuthorizeWithCredentials (string tokenType, string accessToken, string playerUID)
520  {
521  this.tokenType = tokenType;
522  this.accessToken = accessToken;
523  this.playerUID = playerUID;
524  }
525 
531  public virtual UnityWebRequest GenerateAuthRequest(AccessCredentials credentials)
532  {
533  var form = new Dictionary<string, string>() {
534  { "grant_type", "client_credentials" },
535  { "client_id", credentials.clientId },
536  { "client_secret", credentials.clientSecret },
537  };
538  return HttpPost(GetAuthUrl(), form);
539  }
540 
544  private AsyncWebRequest<AccessData> AuthorizeClientCredentialsGrantTypeAsync (AccessCredentials credentials)
545  {
546  var request = new AsyncWebRequest<AccessData> (AvatarSdkMgr.Str (Strings.RequestingApiToken));
547  Func<UnityWebRequest> webRequestFactory = () => GenerateAuthRequest(credentials);
548  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
549  return request;
550  }
551 
555  private AsyncWebRequest<AccessData> AuthorizePasswordGrantTypeAsync (
556  string clientId,
557  string clientSecret,
558  string username,
559  string password
560  )
561  {
562  Debug.LogWarning ("Don't use this auth method in production, use other grant types!");
563  var request = new AsyncWebRequest<AccessData> (AvatarSdkMgr.Str (Strings.RequestingApiToken));
564 
565  if (string.IsNullOrEmpty (username) || string.IsNullOrEmpty (password) || string.IsNullOrEmpty (clientId)) {
566  request.SetError ("itSeez3D credentials not provided");
567  Debug.LogError (request.ErrorMessage);
568  return request;
569  }
570 
571  var form = new Dictionary<string,string> () {
572  { "grant_type", "password" },
573  { "username", username },
574  { "password", password },
575  { "client_id", clientId },
576  { "client_secret", clientSecret },
577  };
578  Func<UnityWebRequest> webRequestFactory = () => HttpPost (GetUrl ("o", "token"), form);
579  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
580  return request;
581  }
582 
587  public virtual AsyncWebRequest<Player> RegisterPlayerAsync (string comment = "")
588  {
589  var r = new AsyncWebRequest<Player> (AvatarSdkMgr.Str (Strings.RegisteringPlayerID));
590  var form = new Dictionary<string,string> () {
591  { "comment", comment },
592  };
593  Func<UnityWebRequest> webRequestFactory = () => {
594  var webRequest = HttpPost (GetUrl ("players"), form);
595  SetAuthHeaders (webRequest);
596  return webRequest;
597  };
598  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, r));
599  return r;
600  }
601 
602 #endregion
603 
604 #region Creating/awaiting/downloading an avatar
605 
610  string name, string description, byte[] photoBytes, bool forcePowerOfTwoTexture = false,
611  PipelineType pipeline = PipelineType.FACE, AvatarResources resources = null
612  )
613  {
614  var request = new AsyncWebRequest<AvatarData> (AvatarSdkMgr.Str (Strings.UploadingPhoto), TrackProgress.UPLOAD);
615 
616  var textFields = new Dictionary<string, string>
617  {
618  {"name", name },
619  {"description", description },
620  {"preserve_original_texture", (!forcePowerOfTwoTexture).ToString() },
621  {"pipeline", pipeline.GetPipelineTypeName() },
622  };
623 
624  if (resources != null)
625  {
626  textFields.Add("pipeline_subtype", pipeline_subtype);
627  textFields.Add("resources", AvatarSdkMgr.CalculationParametersGenerator().GetAvatarCalculationParamsJson(resources));
628  }
629 
630 #if UNITY_2017_1_OR_NEWER
631  Func<UnityWebRequest> webRequestFactory = () =>
632  {
633  List<IMultipartFormSection> formData = new List<IMultipartFormSection>();
634  formData.Add(new MultipartFormFileSection("photo", photoBytes, "photo.jpg", "application/octet-stream"));
635  foreach (var item in textFields)
636  formData.Add(new MultipartFormDataSection(item.Key, item.Value));
637 
638  var webRequest = UnityWebRequest.Post(GetUrl("avatars"), formData);
639  webRequest.chunkedTransfer = false;
640  SetAuthHeaders(webRequest);
641  return webRequest;
642  };
643 
644  Debug.LogFormat("Uploading photo...");
645  AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, request));
646  return request;
647 #else
648  // Unity 5.5.0 (and probably earlier versions) have a weird bug in default multipart form data
649  // implementation, which causes incorrect boundaries between data fields. To work around this bug the
650  // multipart request body is constructed manually, see below.
651  byte[] requestBodyData = null;
652  using (var requestBody = new MultipartBody ()) {
653  requestBody.WriteFileField ("photo", "photo.jpg", photoBytes);
654  foreach (var item in textFields)
655  requestBody.WriteTextField(item.Key, item.Value);
656 
657  requestBody.WriteFooter ();
658  requestBodyData = requestBody.GetRequestBodyData ();
659 
660  Func<UnityWebRequest> webRequestFactory = () => {
661  var webRequest = UnityWebRequest.Post (GetUrl ("avatars"), " ");
662  webRequest.uploadHandler = new UploadHandlerRaw (requestBodyData);
663  webRequest.SetRequestHeader (
664  "Content-Type", string.Format ("multipart/form-data; boundary=\"{0}\"", requestBody.Boundary)
665  );
666  webRequest.chunkedTransfer = false;
667  SetAuthHeaders (webRequest);
668  return webRequest;
669  };
670 
671  Debug.LogFormat ("Uploading photo...");
672  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
673  return request;
674  }
675 #endif
676  }
677 
681  public virtual AsyncWebRequest<AvatarData> GetAvatarAsync (string avatarCode)
682  {
683  var r = AvatarJsonRequest<AvatarData> (GetUrl ("avatars", avatarCode));
684  r.State = AvatarSdkMgr.Str (Strings.GettingAvatarInfo);
685  return r;
686  }
687 
692  {
693  var r = AvatarJsonArrayRequest<AvatarHaircutData> (avatar.haircuts);
694  r.State = AvatarSdkMgr.Str (Strings.RequestingHaircutInfo);
695  return r;
696  }
697 
702  {
703  var r = AvatarJsonArrayRequest<TextureData> (GetUrl ("avatars", avatar.code, "textures"));
704  r.State = AvatarSdkMgr.Str (Strings.RequestingTextureInfo);
705  return r;
706  }
707 
713  public virtual AsyncWebRequest<byte[]> DownloadAvatarThumbnailBytesAsync (AvatarData avatar, int maxW, int maxH)
714  {
715  var param = new Dictionary<string, string> {
716  { "max_w", maxW.ToString () },
717  { "max_h", maxH.ToString () },
718  };
719  var url = UrlWithParams (avatar.thumbnail, param);
720 
721  var r = AvatarDataRequestAsync (url);
722  r.State = AvatarSdkMgr.Str (Strings.DownloadingThumbnail);
723  return r;
724  }
725 
731  public virtual AsyncWebRequest<byte[]> DownloadMeshZipAsync (AvatarData avatar, int levelOfDetails = 0)
732  {
733  var url = UrlWithParams (avatar.mesh, "lod", levelOfDetails.ToString ());
734  var r = AvatarDataRequestAsync (url);
735  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadMesh);
736  return r;
737  }
738 
743  {
744  var r = AvatarDataRequestAsync (GetUrl ("avatars", avatar.code, "pointcloud"));
745  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadMesh);
746  return r;
747  }
748 
753  {
754  var r = AvatarDataRequestAsync (avatar.texture);
755  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadTexture);
756  return r;
757  }
758 
763  {
764  var r = AvatarDataRequestAsync (haircut.mesh);
765  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutMesh);
766  return r;
767  }
768 
773  {
774  var r = AvatarDataRequestAsync (haircut.texture);
775  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutTexture);
776  return r;
777  }
778 
783  {
784  var r = AvatarDataRequestAsync(haircut.preview);
785  r.State = AvatarSdkMgr.Str(Strings.DownloadingHaircutPreview);
786  return r;
787  }
788 
793  {
794  var r = AvatarDataRequestAsync (haircut.pointcloud);
795  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutPointCloud);
796  return r;
797  }
798 
804  {
805  string url = string.Format ("{0}pointclouds/", avatar.haircuts);
806  var r = AvatarDataRequestAsync (url);
807  r.State = AvatarSdkMgr.Str (Strings.DownloadingAllHaircutPointClouds);
808  return r;
809  }
810 
816  public virtual AsyncWebRequest<byte[]> DownloadBlendshapesZipAsync (AvatarData avatar, BlendshapesFormat format = BlendshapesFormat.BIN, int levelOfDetails = 0)
817  {
818  var fmtToStr = new Dictionary<BlendshapesFormat, string> {
819  { BlendshapesFormat.BIN, "bin" },
820  { BlendshapesFormat.FBX, "fbx" },
821  { BlendshapesFormat.PLY, "ply" },
822  };
823 
824  string url = string.Format ("{0}?fmt={1}&lod={2}", avatar.blendshapes, fmtToStr [format], levelOfDetails);
825  var r = AvatarDataRequestAsync (url);
826  r.State = AvatarSdkMgr.Str (Strings.DownloadingBlendshapes);
827  return r;
828  }
829 
830 #endregion
831 
832 #region Actions with avatars on the server (list/update/delete/...)
833 
837  public virtual AsyncWebRequest<Page<AvatarData>> GetAvatarsPageAsync (int pageNumber)
838  {
839  var r = AvatarJsonPageRequest<AvatarData> (GetUrl ("avatars"), pageNumber);
840  r.State = AvatarSdkMgr.Str (Strings.GettingAvatarList);
841  return r;
842  }
843 
847  public virtual AsyncRequest<AvatarData[]> GetAvatarsAsync (int maxItems = int.MaxValue, Dictionary<string, string> filters = null)
848  {
849  var url = GetUrl ("avatars");
850  url = UrlWithParams (url, filters);
851  var r = AvatarJsonArrayRequest<AvatarData> (url, maxItems);
852  r.State = AvatarSdkMgr.Str (Strings.GettingAvatarList);
853  return r;
854  }
855 
859  public virtual AsyncWebRequest EditAvatarAsync (AvatarData avatar, string name = null, string description = null)
860  {
861  var request = new AsyncWebRequest (AvatarSdkMgr.Str (Strings.EditingAvatar));
862 
863  byte[] requestBodyData = null;
864  using (var requestBody = new MultipartBody ()) {
865  requestBody.WriteTextField ("name", name);
866  requestBody.WriteTextField ("description", description);
867  requestBody.WriteFooter ();
868  requestBodyData = requestBody.GetRequestBodyData ();
869 
870  Func<UnityWebRequest> webRequestFactory = () => {
871  var webRequest = UnityWebRequest.Post (avatar.url, " ");
872  webRequest.chunkedTransfer = false;
873  webRequest.method = "PATCH";
874  webRequest.uploadHandler = new UploadHandlerRaw (requestBodyData);
875  webRequest.SetRequestHeader (
876  "Content-Type", string.Format ("multipart/form-data; boundary=\"{0}\"", requestBody.Boundary)
877  );
878  SetAuthHeaders (webRequest);
879  return webRequest;
880  };
881 
882  Debug.LogFormat ("Uploading photo...");
883  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
884  return request;
885  }
886  }
887 
892  {
893  var request = new AsyncWebRequest (AvatarSdkMgr.Str (Strings.DeletingAvatarOnServer));
894 
895  Func<UnityWebRequest> webRequestFactory = () => {
896  var webRequest = UnityWebRequest.Delete (avatar.url);
897  SetAuthHeaders (webRequest);
898  webRequest.downloadHandler = new DownloadHandlerBuffer ();
899  return webRequest;
900  };
901  AvatarSdkMgr.SpawnCoroutine (AwaitWebRequest (webRequestFactory, request));
902  return request;
903  }
904 
905 #endregion
906 
907 #region Resources
908 
912  public virtual AsyncWebRequest<string> GetResourcesAsync(PipelineType pipelineType, AvatarResourcesSubset resourcesSubset)
913  {
914  string subsetStr = "available";
915  if (resourcesSubset == AvatarResourcesSubset.DEFAULT)
916  subsetStr = "default";
917 
918  var url = GetUrl("resources", subsetStr, pipelineType.GetPipelineTypeName());
919  url = UrlWithParams(url, "pipeline_subtype", pipeline_subtype);
920  var request = new AsyncWebRequest<string>(Strings.GettingResourcesList);
921  AvatarSdkMgr.SpawnCoroutine(AwaitStringDataAsync(() => HttpGet(url), request));
922  return request;
923  }
924 #endregion
925 
926 #region Higher-level API, composite requests
927 
931  private IEnumerator AwaitAvatarCalculationsLoop (AvatarData avatar, AsyncRequest<AvatarData> request)
932  {
933  while (!Strings.FinalStates.Contains (avatar.status)) {
934  yield return new WaitForSeconds (4);
935  var avatarStatusRequest = GetAvatarAsync (avatar.code);
936  yield return avatarStatusRequest;
937 
938  if (avatarStatusRequest.Status.Value == (long)StatusCode.Code.NOT_FOUND) {
939  Debug.LogWarning ("404 error most likely means that avatar was deleted from the server");
940  request.SetError (string.Format ("Avatar status response: {0}", avatarStatusRequest.ErrorMessage));
941  yield break;
942  }
943 
944  if (avatarStatusRequest.Status.Value == (long)StatusCode.Code.TOO_MANY_REQUESTS_THROTTLING) {
945  Debug.LogWarning ("Too many requests!");
946  yield return new WaitForSeconds (10);
947  }
948 
949  if (avatarStatusRequest.IsError) {
950  Debug.LogWarningFormat ("Status polling error: {0}", avatarStatusRequest.ErrorMessage);
951  // Most likely this is a temporary issue. Keep polling.
952  continue;
953  }
954 
955  avatar = avatarStatusRequest.Result;
956  Debug.LogFormat ("Status: {0}, progress: {1}%", avatar.status, avatar.progress);
957  request.State = AvatarSdkMgr.Str (avatar.status);
958 
959  if (avatar.status == Strings.Computing)
960  request.Progress = (float)avatar.progress / 100;
961  }
962 
963  if (Strings.GoodFinalStates.Contains (avatar.status)) {
964  request.Result = avatar;
965  request.IsDone = true;
966  } else {
967  request.SetError (string.Format ("Avatar calculations failed, status: {0}", avatar.status));
968  }
969  }
970 
977  {
978  var request = new AsyncRequest <AvatarData> (AvatarSdkMgr.Str (Strings.StartingCalculations));
979  AvatarSdkMgr.SpawnCoroutine (AwaitAvatarCalculationsLoop (avatar, request));
980  return request;
981  }
982 
983 #endregion
984  }
985 }
static void SpawnCoroutine(IEnumerator routine)
Spawn coroutine outside of MonoBehaviour.
Definition: AvatarSdkMgr.cs:97
virtual string UrlWithParams(string url, Dictionary< string, string > param)
Urlencoded param string.
Definition: Connection.cs:65
virtual AsyncWebRequest< AvatarHaircutData[]> GetHaircutsAsync(AvatarData avatar)
Get list of all haircuts for avatar.
Definition: Connection.cs:691
void SetAuthHeaders(UnityWebRequest request)
Adds auth header to UnityWebRequest.
Definition: Connection.cs:110
virtual AsyncWebRequest< AvatarData > CreateAvatarWithPhotoAsync(string name, string description, byte[] photoBytes, bool forcePowerOfTwoTexture=false, PipelineType pipeline=PipelineType.FACE, AvatarResources resources=null)
Upload photo and create avatar instance on the server. Calculations will start right away after the p...
Definition: Connection.cs:609
virtual Dictionary< string, string > GetAuthHeaders()
Dictionary with required auth HTTP headers.
Definition: Connection.cs:94
virtual AsyncWebRequest< AvatarData > GetAvatarAsync(string avatarCode)
Get avatar information by code.
Definition: Connection.cs:681
virtual string UrlWithParams(string url, string param, string value)
Simple overload for a single-parameter use case.
Definition: Connection.cs:82
virtual string GetUrl(params string[] urlTokens)
Helper function to construct absolute url from relative url tokens.
Definition: Connection.cs:57
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:507
static string Str(string s)
Return string modified (e.g. translated) by string manager.
static ParametersGenerator CalculationParametersGenerator()
Return object that generates calculation parameters
virtual IEnumerator AwaitWebRequest(Func< UnityWebRequest > webRequestFactory, AsyncWebRequest request)
Variation of AwaitWebRequestFunc when we don&#39;t actually need the result.
Definition: Connection.cs:279
virtual AsyncWebRequest< string > GetResourcesAsync(PipelineType pipelineType, AvatarResourcesSubset resourcesSubset)
Request to get available resources for the pipeline
Definition: Connection.cs:912
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:731
virtual AsyncWebRequest< byte[]> DownloadHaircutPreviewBytesAsync(AvatarHaircutData haircut)
Download haircut preview into memory.
Definition: Connection.cs:782
AvatarResourcesSubset
Represents subset of avatar resources
Unity 5.5.0 (and probably earlier versions) have a weird bug in default multipart form data implement...
virtual IEnumerator AwaitStringDataAsync(Func< UnityWebRequest > webRequestFactory, AsyncWebRequest< string > request)
Call AwaitWebRequestFunc for string data.
Definition: Connection.cs:349
virtual UnityWebRequest GenerateAuthRequest(AccessCredentials credentials)
Generate HTTP request object to obtain the access token.
Definition: Connection.cs:531
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:742
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:425
virtual AsyncWebRequest< byte[]> DownloadBlendshapesZipAsync(AvatarData avatar, BlendshapesFormat format=BlendshapesFormat.BIN, int levelOfDetails=0)
Downloads zip archive with all the blendshapes.
Definition: Connection.cs:816
virtual AsyncWebRequest< byte[]> DownloadAvatarThumbnailBytesAsync(AvatarData avatar, int maxW, int maxH)
Download thumbnail with the specified resolution.
Definition: Connection.cs:713
virtual string GetAuthUrl()
Url used to obtain access token.
Definition: Connection.cs:88
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:859
virtual AsyncWebRequest< TextureData[]> GetTexturesAsync(AvatarData avatar)
Get list of all textures for avatar.
Definition: Connection.cs:701
UnityWebRequest HttpGet(string url)
Helper factory method.
Definition: Connection.cs:121
virtual AsyncRequest< AvatarData[]> GetAvatarsAsync(int maxItems=int.MaxValue, Dictionary< string, string > filters=null)
Get "maxItems" latest avatars (will loop through the pages).
Definition: Connection.cs:847
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:803
virtual AsyncWebRequest< byte[]> DownloadTextureBytesAsync(AvatarData avatar)
Download main texture into memory. Can be used right away to create Unity texture.
Definition: Connection.cs:752
Async request for web requests.
virtual AsyncWebRequest< ModelInfo > GetModelInfoAsync(string avatarCode)
Request to get skin and hair colors.
Definition: Connection.cs:436
virtual AsyncWebRequest DeleteAvatarAsync(AvatarData avatar)
Delete avatar record on the server (does not delete local files).
Definition: Connection.cs:891
virtual string GetAvatarCalculationParamsJson(AvatarResources avatarResources)
Prepares JSON with parameters required for avatar calculation.
virtual AsyncWebRequest< Page< AvatarData > > GetAvatarsPageAsync(int pageNumber)
Get a particular page in the list of avatars.
Definition: Connection.cs:837
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:762
virtual AsyncWebRequest< byte[]> DownloadHaircutTextureBytesAsync(AvatarHaircutData haircut)
Download haircut texture into memory. Can be used right away to create Unity texture.
Definition: Connection.cs:772
virtual AsyncWebRequest< byte[]> DownloadHaircutPointCloudZipAsync(AvatarHaircutData haircut)
Downloads the haircut point cloud zip into memory.
Definition: Connection.cs:792
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:519
virtual IEnumerator AwaitDataAsync(Func< UnityWebRequest > webRequestFactory, AsyncWebRequest< byte[]> request)
Call AwaitWebRequestFunc for binary data.
Definition: Connection.cs:341
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:587
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:976