Avatar SDK  1.5.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 constants
28  private readonly string pipeline_subtype = "base/legacy";
29  #endregion
30 
31  #region Connection data
32 
33  // access data
34  private string tokenType = null, accessToken = null;
35 
41  private string playerUID = null;
42 
43  #endregion
44 
45  #region Helpers
46 
52  public virtual string GetUrl (params string[] urlTokens)
53  {
54  return string.Format ("{0}/{1}/", NetworkUtils.rootUrl, string.Join ("/", urlTokens));
55  }
56 
60  public virtual string UrlWithParams(string url, Dictionary<string, string> param)
61  {
62  if (param == null || param.Count == 0)
63  return url;
64 
65  var paramTokens = new List<string> ();
66  foreach (var item in param) {
67  var token = string.Format ("{0}={1}", WWW.EscapeURL (item.Key), WWW.EscapeURL (item.Value));
68  paramTokens.Add (token);
69  }
70 
71  return string.Format ("{0}?{1}", url, string.Join ("&", paramTokens.ToArray()));
72  }
73 
77  public virtual string UrlWithParams (string url, string param, string value)
78  {
79  return UrlWithParams (url, new Dictionary<string, string> { { param, value } });
80  }
81 
83  public virtual string GetAuthUrl ()
84  {
85  return GetUrl ("o", "token");
86  }
87 
89  public virtual Dictionary<string, string> GetAuthHeaders ()
90  {
91  var headers = new Dictionary<string, string> () {
92  { "Authorization", string.Format ("{0} {1}", tokenType, accessToken) },
93  #if !UNITY_WEBGL
94  { "X-Unity-Plugin-Version", CoreTools.SdkVersion.ToString () },
95  #endif
96  };
97  if (!string.IsNullOrEmpty (playerUID))
98  headers.Add ("X-PlayerUID", playerUID);
99  return headers;
100  }
101 
105  protected void SetAuthHeaders (UnityWebRequest request)
106  {
107  var headers = GetAuthHeaders ();
108  foreach (var h in headers)
109  request.SetRequestHeader (h.Key, h.Value);
110  }
111 
116  protected UnityWebRequest HttpGet (string url)
117  {
118  if (string.IsNullOrEmpty (url))
119  Debug.LogError ("Provided empty url!");
120  var r = UnityWebRequest.Get (url);
121  SetAuthHeaders (r);
122  return r;
123  }
124 
125  protected UnityWebRequest HttpPost(string url, Dictionary<string, string> form)
126  {
127  var r = UnityWebRequest.Post(url, form);
128  r.chunkedTransfer = false; // chunked transfer causes problems with UWSGI
129  return r;
130  }
131  #endregion
132 
133  #region Generic request processing
134 
138  private static IEnumerator AwaitAndTrackProgress<T> (UnityWebRequest webRequest, AsyncWebRequest<T> request)
139  {
140  #if UNITY_2017_2_OR_NEWER
141  webRequest.SendWebRequest();
142  #else
143  webRequest.Send ();
144  #endif
145  do {
146  yield return null;
147 
148  switch (request.ProgressTracking) {
149  case TrackProgress.DOWNLOAD:
150  request.Progress = webRequest.downloadProgress;
151  break;
152  case TrackProgress.UPLOAD:
153  request.Progress = webRequest.uploadProgress;
154  break;
155  }
156 
157  request.BytesDownloaded = webRequest.downloadedBytes;
158  request.BytesUploaded = webRequest.uploadedBytes;
159  } while(!webRequest.isDone);
160  }
161 
166  private static bool IsGoodResponse (UnityWebRequest webRequest, out StatusCode status, out string error)
167  {
168  error = string.Empty;
169 
170  try {
171  status = new StatusCode (webRequest.responseCode);
172 
173 #if UNITY_2017_1_OR_NEWER
174  if (webRequest.isNetworkError) { // apparently the API has changed in 2017
175 #else
176  if (webRequest.isError) {
177 #endif
178  error = webRequest.error;
179  return false;
180  }
181 
182  if (!webRequest.downloadHandler.isDone) {
183  error = "Could not download response";
184  return false;
185  }
186 
187  if (status.IsBad) {
188  error = string.Format ("Bad response code. Msg: {0}", webRequest.downloadHandler.text);
189  return false;
190  }
191  } catch (Exception ex) {
192  Debug.LogException (ex);
193  status = new StatusCode ();
194  error = string.Format ("Exception while checking response: {0}", ex.Message);
195  return false;
196  }
197 
198  return true;
199  }
200 
209  private IEnumerator AwaitWebRequestFunc<T> (
210  Func<UnityWebRequest> webRequestFactory,
211  AsyncWebRequest<T> request,
212  Func<UnityWebRequest, T> parseDataFunc
213  )
214  {
215  UnityWebRequest webRequest = null;
216 
217  StatusCode status = new StatusCode ();
218  string error = string.Empty;
219 
220  int numAttempts = 2, lastAttempt = numAttempts - 1;
221  bool goodResponse = false;
222  for (int attempt = 0; attempt < numAttempts; ++attempt) {
223  webRequest = webRequestFactory ();
224  yield return AwaitAndTrackProgress (webRequest, request);
225 
226  if (goodResponse = IsGoodResponse (webRequest, out status, out error))
227  break;
228 
229  // all API requests have Authorization header, except for authorization requests
230  bool isAuthRequest = webRequest.GetRequestHeader ("Authorization") == null;
231 
232  Debug.LogWarningFormat ("Server error: {0}, request: {1}", error, webRequest.url);
233  if (status.Value != (long)StatusCode.Code.UNAUTHORIZED || isAuthRequest) {
234  // cannot recover, request has failed
235  break;
236  }
237 
238  if (attempt == lastAttempt) {
239  Debug.LogError ("No more retries left");
240  break;
241  }
242 
243  Debug.LogWarning ("Auth issue, let's try one more time after refreshing access token");
244  yield return AuthorizeAsync ();
245  }
246 
247  if (!goodResponse) {
248  Debug.LogErrorFormat ("Could not send the request, status: {0}, error: {1}", status, error);
249  request.Status = status;
250  request.SetError (error);
251  yield break;
252  }
253 
254  T data = default(T);
255  try {
256  data = parseDataFunc (webRequest);
257  } catch (Exception ex) {
258  Debug.LogException (ex);
259  }
260 
261  if (data == null) {
262  request.SetError ("Could not parse request data");
263  yield break;
264  } else {
265  request.Result = data;
266  }
267 
268  request.IsDone = true;
269  }
270 
274  public virtual IEnumerator AwaitWebRequest (Func<UnityWebRequest> webRequestFactory, AsyncWebRequest request)
275  {
276  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => new object ());
277  }
278 
282  public virtual IEnumerator AwaitJsonWebRequest<DataType> (
283  Func<UnityWebRequest> webRequestFactory,
285  {
286  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => {
287  return JsonUtility.FromJson<DataType> (r.downloadHandler.text);
288  });
289  }
290 
294  public virtual IEnumerator AwaitJsonPageWebRequest<T> (
295  Func<UnityWebRequest> webRequestFactory,
296  AsyncWebRequest<Page<T>> request
297  )
298  {
299  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => {
300  // Unity JsonUtility does not support Json array parsing, so we have to hack around it
301  // by wrapping it into object with a single array field.
302  var wrappedArrayJson = string.Format ("{{ \"content\": {0} }}", r.downloadHandler.text);
303  var page = JsonUtility.FromJson<Page<T>> (wrappedArrayJson);
304  var paginationHeader = r.GetResponseHeader ("Link");
305 
306  // parse "Link" header to get links to adjacent pages
307  if (!string.IsNullOrEmpty (paginationHeader)) {
308  var regex = new Regex (@".*<(?<link>.+)>.+rel=""(?<kind>.*)""");
309  var tokens = paginationHeader.Split (',');
310  foreach (var token in tokens) {
311  var match = regex.Match (token);
312  if (!match.Success)
313  continue;
314 
315  string link = match.Groups ["link"].Value, kind = match.Groups ["kind"].Value;
316  if (string.IsNullOrEmpty (link) || string.IsNullOrEmpty (kind))
317  continue;
318 
319  if (kind == "first")
320  page.firstPageUrl = link;
321  else if (kind == "next")
322  page.nextPageUrl = link;
323  else if (kind == "prev")
324  page.prevPageUrl = link;
325  else if (kind == "last")
326  page.lastPageUrl = link;
327  }
328  }
329  return page;
330  });
331  }
332 
336  public virtual IEnumerator AwaitDataAsync (Func<UnityWebRequest> webRequestFactory, AsyncWebRequest<byte[]> request)
337  {
338  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => r.downloadHandler.data);
339  }
340 
344  public virtual IEnumerator AwaitStringDataAsync(Func<UnityWebRequest> webRequestFactory, AsyncWebRequest<string> request)
345  {
346  yield return AwaitWebRequestFunc(webRequestFactory, request, (r) => r.downloadHandler.text);
347  }
348 
353  public virtual AsyncWebRequest<DataType> AvatarJsonRequest<DataType> (string url)
354  {
355  var request = new AsyncWebRequest<DataType> ();
356  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (() => HttpGet (url), request));
357  return request;
358  }
359 
364  public virtual AsyncWebRequest<Page<T>> AvatarJsonPageRequest<T> (string url)
365  {
366  var request = new AsyncWebRequest<Page<T>> ();
367  AvatarSdkMgr.SpawnCoroutine (AwaitJsonPageWebRequest (() => HttpGet (url), request));
368  return request;
369  }
370 
375  public virtual AsyncWebRequest<Page<T>> AvatarJsonPageRequest<T> (string baseUrl, int pageNumber)
376  {
377  return AvatarJsonPageRequest<T> (string.Format ("{0}?page={1}", baseUrl, pageNumber));
378  }
379 
383  public virtual IEnumerator AwaitMultiplePages<T> (string url, AsyncRequest<T[]> request, int maxItems = int.MaxValue)
384  {
385  List<T> items = new List<T> ();
386  do {
387  var pageRequest = AvatarJsonPageRequest<T> (url);
388  yield return pageRequest;
389  if (pageRequest.IsError) {
390  request.SetError (string.Format ("Page request failed. Error: {0}", pageRequest.ErrorMessage));
391  yield break;
392  }
393 
394  // Debug.LogFormat ("Successfully loaded page {0}", url);
395  var page = pageRequest.Result;
396 
397  for (int i = 0; i < page.content.Length && items.Count < maxItems; ++i)
398  items.Add (page.content[i]);
399 
400  url = page.nextPageUrl;
401  } while (items.Count < maxItems && !string.IsNullOrEmpty (url));
402 
403  request.Result = items.ToArray ();
404  request.IsDone = true;
405  }
406 
410  public virtual AsyncWebRequest<DataType[]> AvatarJsonArrayRequest<DataType> (string url, int maxItems = int.MaxValue)
411  {
412  var request = new AsyncWebRequest<DataType[]> ();
413  AvatarSdkMgr.SpawnCoroutine (AwaitMultiplePages (url, request, maxItems));
414  return request;
415  }
416 
421  {
422  Debug.LogFormat ("Downloading from {0}...", url);
423  var request = new AsyncWebRequest<byte[]> ();
424  AvatarSdkMgr.SpawnCoroutine (AwaitDataAsync (() => HttpGet (url), request));
425  return request;
426  }
427 
428 #endregion
429 
430 #region Auth functions
431 
435  public virtual bool IsAuthorized { get { return !string.IsNullOrEmpty (accessToken); } }
436 
437  public virtual string TokenType { get { return tokenType; } }
438 
439  public virtual string AccessToken { get { return accessToken; } }
440 
444  public virtual string PlayerUID {
445  get { return playerUID; }
446  set { playerUID = value; }
447  }
448 
452  private IEnumerator Authorize (AsyncRequest request)
453  {
454  var accessCredentials = AuthUtils.LoadCredentials ();
455  if (accessCredentials == null || string.IsNullOrEmpty (accessCredentials.clientSecret)) {
456  request.SetError ("Could not find API keys! Please provide valid credentials via Window->ItSeez3D Avatar SDK");
457  yield break;
458  }
459 
460  var authRequest = AuthorizeClientCredentialsGrantTypeAsync (accessCredentials);
461  yield return request.AwaitSubrequest (authRequest, 0.5f);
462  if (request.IsError)
463  yield break;
464 
465  tokenType = authRequest.Result.token_type;
466  accessToken = authRequest.Result.access_token;
467  Debug.LogFormat ("Successful authentication!");
468 
469  // guarantees we re-register a Player if clientId changes
470  var playerIdentifier = string.Format ("player_uid_{0}", accessCredentials.clientId.Substring (0, accessCredentials.clientId.Length / 3));
471 
472  if (string.IsNullOrEmpty (playerUID))
473  playerUID = AvatarSdkMgr.Storage ().LoadPlayerUID (playerIdentifier);
474 
475  if (string.IsNullOrEmpty (playerUID)) {
476  Debug.Log ("Registering new player UID");
477  var playerRequest = RegisterPlayerAsync ();
478  yield return request.AwaitSubrequest (playerRequest, 1);
479  if (request.IsError)
480  yield break;
481 
482  playerUID = playerRequest.Result.code;
483  AvatarSdkMgr.Storage ().StorePlayerUID (playerIdentifier, playerUID);
484  }
485 
486  request.IsDone = true;
487  }
488 
492  public virtual AsyncRequest AuthorizeAsync ()
493  {
494  var request = new AsyncRequest (AvatarSdkMgr.Str (Strings.Authentication));
495  AvatarSdkMgr.SpawnCoroutine (Authorize (request));
496  return request;
497  }
498 
504  public virtual void AuthorizeWithCredentials (string tokenType, string accessToken, string playerUID)
505  {
506  this.tokenType = tokenType;
507  this.accessToken = accessToken;
508  this.playerUID = playerUID;
509  }
510 
516  public virtual UnityWebRequest GenerateAuthRequest(AccessCredentials credentials)
517  {
518  var form = new Dictionary<string, string>() {
519  { "grant_type", "client_credentials" },
520  { "client_id", credentials.clientId },
521  { "client_secret", credentials.clientSecret },
522  };
523  return HttpPost(GetAuthUrl(), form);
524  }
525 
529  private AsyncWebRequest<AccessData> AuthorizeClientCredentialsGrantTypeAsync (AccessCredentials credentials)
530  {
531  var request = new AsyncWebRequest<AccessData> (AvatarSdkMgr.Str (Strings.RequestingApiToken));
532  Func<UnityWebRequest> webRequestFactory = () => GenerateAuthRequest(credentials);
533  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
534  return request;
535  }
536 
540  private AsyncWebRequest<AccessData> AuthorizePasswordGrantTypeAsync (
541  string clientId,
542  string clientSecret,
543  string username,
544  string password
545  )
546  {
547  Debug.LogWarning ("Don't use this auth method in production, use other grant types!");
548  var request = new AsyncWebRequest<AccessData> (AvatarSdkMgr.Str (Strings.RequestingApiToken));
549 
550  if (string.IsNullOrEmpty (username) || string.IsNullOrEmpty (password) || string.IsNullOrEmpty (clientId)) {
551  request.SetError ("itSeez3D credentials not provided");
552  Debug.LogError (request.ErrorMessage);
553  return request;
554  }
555 
556  var form = new Dictionary<string,string> () {
557  { "grant_type", "password" },
558  { "username", username },
559  { "password", password },
560  { "client_id", clientId },
561  { "client_secret", clientSecret },
562  };
563  Func<UnityWebRequest> webRequestFactory = () => HttpPost (GetUrl ("o", "token"), form);
564  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
565  return request;
566  }
567 
572  public virtual AsyncWebRequest<Player> RegisterPlayerAsync (string comment = "")
573  {
574  var r = new AsyncWebRequest<Player> (AvatarSdkMgr.Str (Strings.RegisteringPlayerID));
575  var form = new Dictionary<string,string> () {
576  { "comment", comment },
577  };
578  Func<UnityWebRequest> webRequestFactory = () => {
579  var webRequest = HttpPost (GetUrl ("players"), form);
580  SetAuthHeaders (webRequest);
581  return webRequest;
582  };
583  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, r));
584  return r;
585  }
586 
587 #endregion
588 
589 #region Creating/awaiting/downloading an avatar
590 
595  string name, string description, byte[] photoBytes, bool forcePowerOfTwoTexture = false,
596  PipelineType pipeline = PipelineType.FACE, AvatarResources resources = null
597  )
598  {
599  var request = new AsyncWebRequest<AvatarData> (AvatarSdkMgr.Str (Strings.UploadingPhoto), TrackProgress.UPLOAD);
600 
601 #if UNITY_2017_1_OR_NEWER
602  Func<UnityWebRequest> webRequestFactory = () =>
603  {
604  List<IMultipartFormSection> formData = new List<IMultipartFormSection>();
605  formData.Add(new MultipartFormDataSection("name", name));
606  if (!string.IsNullOrEmpty(description))
607  formData.Add(new MultipartFormDataSection("description", description));
608  formData.Add(new MultipartFormFileSection("photo", photoBytes, "photo.jpg", "application/octet-stream"));
609  formData.Add(new MultipartFormDataSection("preserve_original_texture", (!forcePowerOfTwoTexture).ToString()));
610  formData.Add(new MultipartFormDataSection("pipeline", pipeline.GetPipelineTypeName()));
611 
612  if (resources != null)
613  {
614  formData.Add(new MultipartFormDataSection("pipeline_subtype", pipeline_subtype));
615  formData.Add(new MultipartFormDataSection("resources", CoreTools.GetAvatarCalculationParamsJson(resources)));
616  }
617 
618  var webRequest = UnityWebRequest.Post(GetUrl("avatars"), formData);
619  webRequest.chunkedTransfer = false;
620  SetAuthHeaders(webRequest);
621  return webRequest;
622  };
623 
624  Debug.LogFormat("Uploading photo...");
625  AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, request));
626  return request;
627 #else
628  // Unity 5.5.0 (and probably earlier versions) have a weird bug in default multipart form data
629  // implementation, which causes incorrect boundaries between data fields. To work around this bug the
630  // multipart request body is constructed manually, see below.
631  byte[] requestBodyData = null;
632  using (var requestBody = new MultipartBody ()) {
633  requestBody.WriteTextField ("name", name);
634  requestBody.WriteTextField ("description", description);
635  requestBody.WriteFileField ("photo", "photo.jpg", photoBytes);
636  requestBody.WriteTextField ("preserve_original_texture", (!forcePowerOfTwoTexture).ToString ());
637  requestBody.WriteTextField ("pipeline", pipeline.GetPipelineTypeName());
638 
639  if (resources != null)
640  {
641  requestBody.WriteTextField("pipeline_subtype", pipeline_subtype);
642  requestBody.WriteTextField("resources", CoreTools.GetAvatarCalculationParamsJson(resources));
643  }
644 
645  requestBody.WriteFooter ();
646  requestBodyData = requestBody.GetRequestBodyData ();
647 
648  Func<UnityWebRequest> webRequestFactory = () => {
649  var webRequest = UnityWebRequest.Post (GetUrl ("avatars"), " ");
650  webRequest.uploadHandler = new UploadHandlerRaw (requestBodyData);
651  webRequest.SetRequestHeader (
652  "Content-Type", string.Format ("multipart/form-data; boundary=\"{0}\"", requestBody.Boundary)
653  );
654  webRequest.chunkedTransfer = false;
655  SetAuthHeaders (webRequest);
656  return webRequest;
657  };
658 
659  Debug.LogFormat ("Uploading photo...");
660  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
661  return request;
662  }
663 #endif
664  }
665 
669  public virtual AsyncWebRequest<AvatarData> GetAvatarAsync (string avatarCode)
670  {
671  var r = AvatarJsonRequest<AvatarData> (GetUrl ("avatars", avatarCode));
672  r.State = AvatarSdkMgr.Str (Strings.GettingAvatarInfo);
673  return r;
674  }
675 
680  {
681  var r = AvatarJsonArrayRequest<AvatarHaircutData> (avatar.haircuts);
682  r.State = AvatarSdkMgr.Str (Strings.RequestingHaircutInfo);
683  return r;
684  }
685 
690  {
691  var r = AvatarJsonArrayRequest<TextureData> (GetUrl ("avatars", avatar.code, "textures"));
692  r.State = AvatarSdkMgr.Str (Strings.RequestingTextureInfo);
693  return r;
694  }
695 
701  public virtual AsyncWebRequest<byte[]> DownloadAvatarThumbnailBytesAsync (AvatarData avatar, int maxW, int maxH)
702  {
703  var param = new Dictionary<string, string> {
704  { "max_w", maxW.ToString () },
705  { "max_h", maxH.ToString () },
706  };
707  var url = UrlWithParams (avatar.thumbnail, param);
708 
709  var r = AvatarDataRequestAsync (url);
710  r.State = AvatarSdkMgr.Str (Strings.DownloadingThumbnail);
711  return r;
712  }
713 
719  public virtual AsyncWebRequest<byte[]> DownloadMeshZipAsync (AvatarData avatar, int levelOfDetails = 0)
720  {
721  var url = UrlWithParams (avatar.mesh, "lod", levelOfDetails.ToString ());
722  var r = AvatarDataRequestAsync (url);
723  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadMesh);
724  return r;
725  }
726 
731  {
732  var r = AvatarDataRequestAsync (GetUrl ("avatars", avatar.code, "pointcloud"));
733  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadMesh);
734  return r;
735  }
736 
741  {
742  var r = AvatarDataRequestAsync (avatar.texture);
743  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadTexture);
744  return r;
745  }
746 
751  {
752  var r = AvatarDataRequestAsync (haircut.mesh);
753  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutMesh);
754  return r;
755  }
756 
761  {
762  var r = AvatarDataRequestAsync (haircut.texture);
763  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutTexture);
764  return r;
765  }
766 
771  {
772  var r = AvatarDataRequestAsync(haircut.preview);
773  r.State = AvatarSdkMgr.Str(Strings.DownloadingHaircutPreview);
774  return r;
775  }
776 
781  {
782  var r = AvatarDataRequestAsync (haircut.pointcloud);
783  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutPointCloud);
784  return r;
785  }
786 
792  {
793  string url = string.Format ("{0}pointclouds/", avatar.haircuts);
794  var r = AvatarDataRequestAsync (url);
795  r.State = AvatarSdkMgr.Str (Strings.DownloadingAllHaircutPointClouds);
796  return r;
797  }
798 
804  public virtual AsyncWebRequest<byte[]> DownloadBlendshapesZipAsync (AvatarData avatar, BlendshapesFormat format = BlendshapesFormat.BIN, int levelOfDetails = 0)
805  {
806  var fmtToStr = new Dictionary<BlendshapesFormat, string> {
807  { BlendshapesFormat.BIN, "bin" },
808  { BlendshapesFormat.FBX, "fbx" },
809  { BlendshapesFormat.PLY, "ply" },
810  };
811 
812  string url = string.Format ("{0}?fmt={1}&lod={2}", avatar.blendshapes, fmtToStr [format], levelOfDetails);
813  var r = AvatarDataRequestAsync (url);
814  r.State = AvatarSdkMgr.Str (Strings.DownloadingBlendshapes);
815  return r;
816  }
817 
818 #endregion
819 
820 #region Actions with avatars on the server (list/update/delete/...)
821 
825  public virtual AsyncWebRequest<Page<AvatarData>> GetAvatarsPageAsync (int pageNumber)
826  {
827  var r = AvatarJsonPageRequest<AvatarData> (GetUrl ("avatars"), pageNumber);
828  r.State = AvatarSdkMgr.Str (Strings.GettingAvatarList);
829  return r;
830  }
831 
835  public virtual AsyncRequest<AvatarData[]> GetAvatarsAsync (int maxItems = int.MaxValue, Dictionary<string, string> filters = null)
836  {
837  var url = GetUrl ("avatars");
838  url = UrlWithParams (url, filters);
839  var r = AvatarJsonArrayRequest<AvatarData> (url, maxItems);
840  r.State = AvatarSdkMgr.Str (Strings.GettingAvatarList);
841  return r;
842  }
843 
847  public virtual AsyncWebRequest EditAvatarAsync (AvatarData avatar, string name = null, string description = null)
848  {
849  var request = new AsyncWebRequest (AvatarSdkMgr.Str (Strings.EditingAvatar));
850 
851  byte[] requestBodyData = null;
852  using (var requestBody = new MultipartBody ()) {
853  requestBody.WriteTextField ("name", name);
854  requestBody.WriteTextField ("description", description);
855  requestBody.WriteFooter ();
856  requestBodyData = requestBody.GetRequestBodyData ();
857 
858  Func<UnityWebRequest> webRequestFactory = () => {
859  var webRequest = UnityWebRequest.Post (avatar.url, " ");
860  webRequest.chunkedTransfer = false;
861  webRequest.method = "PATCH";
862  webRequest.uploadHandler = new UploadHandlerRaw (requestBodyData);
863  webRequest.SetRequestHeader (
864  "Content-Type", string.Format ("multipart/form-data; boundary=\"{0}\"", requestBody.Boundary)
865  );
866  SetAuthHeaders (webRequest);
867  return webRequest;
868  };
869 
870  Debug.LogFormat ("Uploading photo...");
871  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
872  return request;
873  }
874  }
875 
880  {
881  var request = new AsyncWebRequest (AvatarSdkMgr.Str (Strings.DeletingAvatarOnServer));
882 
883  Func<UnityWebRequest> webRequestFactory = () => {
884  var webRequest = UnityWebRequest.Delete (avatar.url);
885  SetAuthHeaders (webRequest);
886  webRequest.downloadHandler = new DownloadHandlerBuffer ();
887  return webRequest;
888  };
889  AvatarSdkMgr.SpawnCoroutine (AwaitWebRequest (webRequestFactory, request));
890  return request;
891  }
892 
893  #endregion
894 
895 #region Resources
896 
900  public virtual AsyncWebRequest<string> GetResourcesAsync(PipelineType pipelineType, AvatarResourcesSubset resourcesSubset)
901  {
902  string subsetStr = "available";
903  if (resourcesSubset == AvatarResourcesSubset.DEFAULT)
904  subsetStr = "default";
905 
906  var url = GetUrl("resources", subsetStr, pipelineType.GetPipelineTypeName());
907  url = UrlWithParams(url, "pipeline_subtype", pipeline_subtype);
908  var request = new AsyncWebRequest<string>(Strings.GettingResourcesList);
909  AvatarSdkMgr.SpawnCoroutine(AwaitStringDataAsync(() => HttpGet(url), request));
910  return request;
911  }
912 #endregion
913 
914 #region Higher-level API, composite requests
915 
919  private IEnumerator AwaitAvatarCalculationsLoop (AvatarData avatar, AsyncRequest<AvatarData> request)
920  {
921  while (!Strings.FinalStates.Contains (avatar.status)) {
922  yield return new WaitForSeconds (4);
923  var avatarStatusRequest = GetAvatarAsync (avatar.code);
924  yield return avatarStatusRequest;
925 
926  if (avatarStatusRequest.Status.Value == (long)StatusCode.Code.NOT_FOUND) {
927  Debug.LogWarning ("404 error most likely means that avatar was deleted from the server");
928  request.SetError (string.Format ("Avatar status response: {0}", avatarStatusRequest.ErrorMessage));
929  yield break;
930  }
931 
932  if (avatarStatusRequest.Status.Value == (long)StatusCode.Code.TOO_MANY_REQUESTS_THROTTLING) {
933  Debug.LogWarning ("Too many requests!");
934  yield return new WaitForSeconds (10);
935  }
936 
937  if (avatarStatusRequest.IsError) {
938  Debug.LogWarningFormat ("Status polling error: {0}", avatarStatusRequest.ErrorMessage);
939  // Most likely this is a temporary issue. Keep polling.
940  continue;
941  }
942 
943  avatar = avatarStatusRequest.Result;
944  Debug.LogFormat ("Status: {0}, progress: {1}%", avatar.status, avatar.progress);
945  request.State = AvatarSdkMgr.Str (avatar.status);
946 
947  if (avatar.status == Strings.Computing)
948  request.Progress = (float)avatar.progress / 100;
949  }
950 
951  if (Strings.GoodFinalStates.Contains (avatar.status)) {
952  request.Result = avatar;
953  request.IsDone = true;
954  } else {
955  request.SetError (string.Format ("Avatar calculations failed, status: {0}", avatar.status));
956  }
957  }
958 
965  {
966  var request = new AsyncRequest <AvatarData> (AvatarSdkMgr.Str (Strings.StartingCalculations));
967  AvatarSdkMgr.SpawnCoroutine (AwaitAvatarCalculationsLoop (avatar, request));
968  return request;
969  }
970 
971 #endregion
972  }
973 }
static void SpawnCoroutine(IEnumerator routine)
Spawn coroutine outside of MonoBehaviour.
Definition: AvatarSdkMgr.cs:90
virtual string UrlWithParams(string url, Dictionary< string, string > param)
Urlencoded param string.
Definition: Connection.cs:60
virtual AsyncWebRequest< AvatarHaircutData[]> GetHaircutsAsync(AvatarData avatar)
Get list of all haircuts for avatar.
Definition: Connection.cs:679
void SetAuthHeaders(UnityWebRequest request)
Adds auth header to UnityWebRequest.
Definition: Connection.cs:105
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:594
virtual Dictionary< string, string > GetAuthHeaders()
Dictionary with required auth HTTP headers.
Definition: Connection.cs:89
virtual AsyncWebRequest< AvatarData > GetAvatarAsync(string avatarCode)
Get avatar information by code.
Definition: Connection.cs:669
virtual string UrlWithParams(string url, string param, string value)
Simple overload for a single-parameter use case.
Definition: Connection.cs:77
virtual string GetUrl(params string[] urlTokens)
Helper function to construct absolute url from relative url tokens.
Definition: Connection.cs:52
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:492
static string Str(string s)
Return string modified (e.g. translated) by string manager.
virtual IEnumerator AwaitWebRequest(Func< UnityWebRequest > webRequestFactory, AsyncWebRequest request)
Variation of AwaitWebRequestFunc when we don&#39;t actually need the result.
Definition: Connection.cs:274
virtual AsyncWebRequest< string > GetResourcesAsync(PipelineType pipelineType, AvatarResourcesSubset resourcesSubset)
Request to get available resources for the pipeline
Definition: Connection.cs:900
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:719
virtual AsyncWebRequest< byte[]> DownloadHaircutPreviewBytesAsync(AvatarHaircutData haircut)
Download haircut preview into memory.
Definition: Connection.cs:770
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:344
virtual UnityWebRequest GenerateAuthRequest(AccessCredentials credentials)
Generate HTTP request object to obtain the access token.
Definition: Connection.cs:516
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:730
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:420
virtual AsyncWebRequest< byte[]> DownloadBlendshapesZipAsync(AvatarData avatar, BlendshapesFormat format=BlendshapesFormat.BIN, int levelOfDetails=0)
Downloads zip archive with all the blendshapes.
Definition: Connection.cs:804
virtual AsyncWebRequest< byte[]> DownloadAvatarThumbnailBytesAsync(AvatarData avatar, int maxW, int maxH)
Download thumbnail with the specified resolution.
Definition: Connection.cs:701
virtual string GetAuthUrl()
Url used to obtain access token.
Definition: Connection.cs:83
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:847
virtual AsyncWebRequest< TextureData[]> GetTexturesAsync(AvatarData avatar)
Get list of all textures for avatar.
Definition: Connection.cs:689
UnityWebRequest HttpGet(string url)
Helper factory method.
Definition: Connection.cs:116
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:835
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:791
virtual AsyncWebRequest< byte[]> DownloadTextureBytesAsync(AvatarData avatar)
Download main texture into memory. Can be used right away to create Unity texture.
Definition: Connection.cs:740
Async request for web requests.
virtual AsyncWebRequest DeleteAvatarAsync(AvatarData avatar)
Delete avatar record on the server (does not delete local files).
Definition: Connection.cs:879
virtual AsyncWebRequest< Page< AvatarData > > GetAvatarsPageAsync(int pageNumber)
Get a particular page in the list of avatars.
Definition: Connection.cs:825
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:750
virtual AsyncWebRequest< byte[]> DownloadHaircutTextureBytesAsync(AvatarHaircutData haircut)
Download haircut texture into memory. Can be used right away to create Unity texture.
Definition: Connection.cs:760
virtual AsyncWebRequest< byte[]> DownloadHaircutPointCloudZipAsync(AvatarHaircutData haircut)
Downloads the haircut point cloud zip into memory.
Definition: Connection.cs:780
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:504
virtual IEnumerator AwaitDataAsync(Func< UnityWebRequest > webRequestFactory, AsyncWebRequest< byte[]> request)
Call AwaitWebRequestFunc for binary data.
Definition: Connection.cs:336
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:572
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:964