Avatar SDK  2.0.1
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;
13 using System;
14 using System.Collections;
15 using System.Collections.Generic;
16 using System.Text;
17 using System.Text.RegularExpressions;
18 using UnityEngine;
19 using UnityEngine.Networking;
20 
21 
22 namespace ItSeez3D.AvatarSdk.Cloud
23 {
24  public class DownloadedFileInfo
25  {
26  public byte[] bytes;
27  public string fileName;
28  }
29 
30  public class Connection : ConnectionBase
31  {
33 
34  #region Connection data
35 
36  // access data
37  private string tokenType = null, accessToken = null;
38 
44  private string playerUID = null;
45 
46  #endregion
47 
48  #region Helpers
49 
53  public virtual string UrlWithParams(string url, Dictionary<string, string> param)
54  {
55  if (param == null || param.Count == 0)
56  return url;
57 
58  var paramTokens = new List<string> ();
59  foreach (var item in param) {
60 #if UNITY_2018_3_OR_NEWER
61  var token = string.Format("{0}={1}", UnityWebRequest.EscapeURL(item.Key), UnityWebRequest.EscapeURL(item.Value));
62 #else
63  var token = string.Format ("{0}={1}", WWW.EscapeURL (item.Key), WWW.EscapeURL (item.Value));
64 #endif
65  paramTokens.Add (token);
66  }
67 
68  return string.Format ("{0}?{1}", url, string.Join ("&", paramTokens.ToArray()));
69  }
70 
74  public virtual string UrlWithParams (string url, string param, string value)
75  {
76  return UrlWithParams (url, new Dictionary<string, string> { { param, value } });
77  }
78 
80  public virtual Dictionary<string, string> GetAuthHeaders ()
81  {
82  var headers = new Dictionary<string, string> () {
83  { "Authorization", string.Format ("{0} {1}", tokenType, accessToken) },
84 #if !UNITY_WEBGL
85  { "X-Unity-Plugin-Version", CoreTools.CloudSdkVersion.ToString () },
86 #endif
87  };
88  if (!string.IsNullOrEmpty (playerUID))
89  headers.Add ("X-PlayerUID", playerUID);
90  return headers;
91  }
92 
96  protected void SetAuthHeaders (UnityWebRequest request)
97  {
98  var headers = GetAuthHeaders ();
99  foreach (var h in headers)
100  request.SetRequestHeader (h.Key, h.Value);
101  }
102 
107  protected UnityWebRequest HttpGet (string url)
108  {
109  if (string.IsNullOrEmpty (url))
110  Debug.LogError ("Provided empty url!");
111  var r = UnityWebRequest.Get (url);
112  SetAuthHeaders (r);
113  return r;
114  }
115 #endregion
116 
117 #region Generic request processing
118 
119  private static void PrintWebRequestInfo(UnityWebRequest webRequest, double requestDurationInSec)
120  {
121  StringBuilder sb = new StringBuilder();
122  sb.AppendFormat("{0}: {1}", webRequest.method, webRequest.url).AppendLine();
123  if (webRequest.uploadHandler != null)
124  {
125  sb.AppendFormat("Content type: {0}", webRequest.uploadHandler.contentType).AppendLine();
126  sb.AppendFormat("Uploaded body: {0}", Encoding.UTF8.GetString(webRequest.uploadHandler.data)).AppendLine();
127  }
128  sb.AppendFormat("Uploaded bytes: {0}", webRequest.uploadedBytes).AppendLine();
129  sb.AppendFormat("Response code: {0}", webRequest.responseCode).AppendLine();
130  if (webRequest.downloadHandler != null && webRequest.downloadHandler.text.Length < 5000)
131  {
132  sb.AppendFormat("Response body: {0}", webRequest.downloadHandler.text).AppendLine();
133  }
134  sb.AppendFormat("Downloaded bytes: {0}", webRequest.downloadedBytes).AppendLine();
135  sb.AppendFormat("Request duration: {0}", requestDurationInSec);
136  Debug.LogFormat("{0}", sb.ToString());
137  }
138 
142  private static IEnumerator AwaitAndTrackProgress<T> (UnityWebRequest webRequest, AsyncWebRequest<T> request)
143  {
144  DateTime sendRequestTime = DateTime.Now;
145  webRequest.SendWebRequest();
146  do {
147  yield return null;
148 
149  switch (request.ProgressTracking) {
150  case TrackProgress.DOWNLOAD:
151  request.Progress = webRequest.downloadProgress;
152  break;
153  case TrackProgress.UPLOAD:
154  request.Progress = webRequest.uploadProgress;
155  break;
156  }
157 
158  request.BytesDownloaded = webRequest.downloadedBytes;
159  request.BytesUploaded = webRequest.uploadedBytes;
160  } while(!webRequest.isDone);
161 
162  PrintWebRequestInfo(webRequest, (DateTime.Now - sendRequestTime).TotalSeconds);
163  }
164 
169  private static bool IsGoodResponse (UnityWebRequest webRequest, out StatusCode status, out string error)
170  {
171  error = string.Empty;
172 
173  try {
174  status = new StatusCode (webRequest.responseCode);
175 
176  if (webRequest.isNetworkError) { // apparently the API has changed in 2017
177  error = webRequest.error;
178  return false;
179  }
180 
181  if (status.IsBad) {
182  error = string.Format ("Bad response code. Msg: {0}", webRequest.downloadHandler.text);
183  return false;
184  }
185 
186  if (!webRequest.downloadHandler.isDone)
187  {
188  error = "Could not download response";
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 
279  private IEnumerator CheckIfPipelineSupportedFunc(string url, AsyncRequest<bool> request)
280  {
281  DateTime sendRequestTime = DateTime.Now;
282  UnityWebRequest webRequest = HttpGet(url);
283  yield return webRequest.SendWebRequest();
284 
285  while (!webRequest.isDone)
286  yield return null;
287 
288  PrintWebRequestInfo(webRequest, (DateTime.Now - sendRequestTime).TotalSeconds);
289 
290  if (!webRequest.isDone)
291  Debug.LogErrorFormat("Request isn't completed");
292 
293  if (webRequest.isNetworkError)
294  {
295  Debug.LogErrorFormat("Could not send the request, status: {0}, error: {1}", webRequest.responseCode, webRequest.error);
296  request.SetError(webRequest.downloadHandler.text);
297  yield break;
298  }
299 
300  if (webRequest.isHttpError)
301  {
302  if (webRequest.responseCode == 400)
303  {
304  request.Result = false;
305  request.IsDone = true;
306  }
307  else
308  {
309  Debug.LogErrorFormat("Got error in response, status: {0}, error: {1}", webRequest.responseCode, webRequest.downloadHandler.text);
310  request.SetError(webRequest.downloadHandler.text);
311  yield break;
312  }
313  }
314  else
315  {
316  request.Result = true;
317  request.IsDone = true;
318 
319  }
320  }
321 
325  public virtual IEnumerator AwaitWebRequest (Func<UnityWebRequest> webRequestFactory, AsyncWebRequest request)
326  {
327  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => new object ());
328  }
329 
333  public virtual IEnumerator AwaitJsonWebRequest<DataType> (
334  Func<UnityWebRequest> webRequestFactory,
336  {
337  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => {
338  return JsonUtility.FromJson<DataType> (r.downloadHandler.text);
339  });
340  }
341 
345  public virtual IEnumerator AwaitJsonPageWebRequest<T> (
346  Func<UnityWebRequest> webRequestFactory,
347  AsyncWebRequest<Page<T>> request
348  )
349  {
350  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => {
351  // Unity JsonUtility does not support Json array parsing, so we have to hack around it
352  // by wrapping it into object with a single array field.
353  var wrappedArrayJson = string.Format ("{{ \"content\": {0} }}", r.downloadHandler.text);
354  var page = JsonUtility.FromJson<Page<T>> (wrappedArrayJson);
355  var paginationHeader = r.GetResponseHeader ("Link");
356 
357  // parse "Link" header to get links to adjacent pages
358  if (!string.IsNullOrEmpty (paginationHeader)) {
359  var regex = new Regex (@".*<(?<link>.+)>.+rel=""(?<kind>.*)""");
360  var tokens = paginationHeader.Split (',');
361  foreach (var token in tokens) {
362  var match = regex.Match (token);
363  if (!match.Success)
364  continue;
365 
366  string link = match.Groups ["link"].Value, kind = match.Groups ["kind"].Value;
367  if (string.IsNullOrEmpty (link) || string.IsNullOrEmpty (kind))
368  continue;
369 
370  if (kind == "first")
371  page.firstPageUrl = link;
372  else if (kind == "next")
373  page.nextPageUrl = link;
374  else if (kind == "prev")
375  page.prevPageUrl = link;
376  else if (kind == "last")
377  page.lastPageUrl = link;
378  }
379  }
380  return page;
381  });
382  }
383 
387  public virtual IEnumerator AwaitDataAsync (Func<UnityWebRequest> webRequestFactory, AsyncWebRequest<byte[]> request)
388  {
389  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => r.downloadHandler.data);
390  }
391 
395  public virtual IEnumerator AwaitStringDataAsync(Func<UnityWebRequest> webRequestFactory, AsyncWebRequest<string> request)
396  {
397  yield return AwaitWebRequestFunc(webRequestFactory, request, (r) => r.downloadHandler.text);
398  }
399 
403  public virtual IEnumerator AwaitFileDataAsync(Func<UnityWebRequest> webRequestFactory, AsyncWebRequest<DownloadedFileInfo> request)
404  {
405  yield return AwaitWebRequestFunc(webRequestFactory, request, (r) =>
406  {
407  DownloadedFileInfo fileInfo = new DownloadedFileInfo();
408  fileInfo.bytes = r.downloadHandler.data;
409  var headers = r.GetResponseHeaders();
410  string contentDispositionHeader = "Content-Disposition";
411  string filenameTag = "filename";
412  if (headers.ContainsKey(contentDispositionHeader))
413  {
414  string contentDispositionValue = headers[contentDispositionHeader];
415  int filenamePos = contentDispositionValue.IndexOf(filenameTag);
416  if (filenamePos >= 0)
417  {
418  fileInfo.fileName = contentDispositionValue.Substring(filenamePos + filenameTag.Length + 1);
419  }
420  else
421  Debug.LogErrorFormat("Filename isn't found in the Content-Disposition header: {0}", contentDispositionValue);
422  }
423  return fileInfo;
424  });
425  }
426 
431  public virtual AsyncWebRequest<DataType> AvatarJsonRequest<DataType> (string url)
432  {
433  var request = new AsyncWebRequest<DataType> ();
434  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (() => HttpGet (url), request));
435  return request;
436  }
437 
442  public virtual AsyncWebRequest<Page<T>> AvatarJsonPageRequest<T> (string url)
443  {
444  var request = new AsyncWebRequest<Page<T>> ();
445  AvatarSdkMgr.SpawnCoroutine (AwaitJsonPageWebRequest (() => HttpGet (url), request));
446  return request;
447  }
448 
453  public virtual AsyncWebRequest<Page<T>> AvatarJsonPageRequest<T> (string baseUrl, int pageNumber)
454  {
455  return AvatarJsonPageRequest<T> (string.Format ("{0}?page={1}", baseUrl, pageNumber));
456  }
457 
461  public virtual IEnumerator AwaitMultiplePages<T> (string url, AsyncRequest<T[]> request, int maxItems = int.MaxValue)
462  {
463  List<T> items = new List<T> ();
464  do {
465  var pageRequest = AvatarJsonPageRequest<T> (url);
466  yield return pageRequest;
467  if (pageRequest.IsError) {
468  request.SetError (string.Format ("Page request failed. Error: {0}", pageRequest.ErrorMessage));
469  yield break;
470  }
471 
472  // Debug.LogFormat ("Successfully loaded page {0}", url);
473  var page = pageRequest.Result;
474 
475  for (int i = 0; i < page.content.Length && items.Count < maxItems; ++i)
476  items.Add (page.content[i]);
477 
478  url = page.nextPageUrl;
479  } while (items.Count < maxItems && !string.IsNullOrEmpty (url));
480 
481  request.Result = items.ToArray ();
482  request.IsDone = true;
483  }
484 
488  public virtual AsyncWebRequest<DataType[]> AvatarJsonArrayRequest<DataType> (string url, int maxItems = int.MaxValue)
489  {
490  var request = new AsyncWebRequest<DataType[]> ();
491  AvatarSdkMgr.SpawnCoroutine (AwaitMultiplePages (url, request, maxItems));
492  return request;
493  }
494 
499  {
500  var request = new AsyncWebRequest<byte[]> ();
501  AvatarSdkMgr.SpawnCoroutine (AwaitDataAsync (() => HttpGet (url), request));
502  return request;
503  }
504 
509  {
510  var request = new AsyncWebRequest<DownloadedFileInfo>();
511  AvatarSdkMgr.SpawnCoroutine(AwaitFileDataAsync(() => HttpGet(url), request));
512  return request;
513  }
514 
518  public virtual AsyncWebRequest<string> GetModelInfoAsync(string avatarCode)
519  {
520  string url = GetUrl("avatars", avatarCode, "model_info");
521  var request = new AsyncWebRequest<string>(Strings.RequestingModelInfo);
522  AvatarSdkMgr.SpawnCoroutine(AwaitStringDataAsync(() => HttpGet(url), request));
523  return request;
524  }
525 
526 #endregion
527 
528 #region Auth functions
529 
533  public virtual bool IsAuthorized { get { return !string.IsNullOrEmpty (accessToken); } }
534 
535  public virtual string TokenType { get { return tokenType; } }
536 
537  public virtual string AccessToken { get { return accessToken; } }
538 
542  public virtual string PlayerUID {
543  get { return playerUID; }
544  set { playerUID = value; }
545  }
546 
550  private IEnumerator Authorize (AsyncRequest request)
551  {
552  var accessCredentials = AuthUtils.LoadCredentials ();
553  if (accessCredentials == null || string.IsNullOrEmpty (accessCredentials.clientSecret)) {
554  request.SetError ("Could not find API keys! Please provide valid credentials via Window->ItSeez3D Avatar SDK");
555  yield break;
556  }
557 
558  var authRequest = AuthorizeClientCredentialsGrantTypeAsync (accessCredentials);
559  yield return request.AwaitSubrequest (authRequest, 0.5f);
560  if (request.IsError)
561  yield break;
562 
563  tokenType = authRequest.Result.token_type;
564  accessToken = authRequest.Result.access_token;
565  Debug.LogFormat ("Successful authentication!");
566 
567  // guarantees we re-register a Player if clientId changes
568  var playerIdentifier = string.Format ("player_uid_{0}", accessCredentials.clientId.Substring (0, accessCredentials.clientId.Length / 3));
569 
570  if (string.IsNullOrEmpty (playerUID))
571  playerUID = AvatarSdkMgr.Storage ().LoadPlayerUID (playerIdentifier);
572 
573  if (string.IsNullOrEmpty (playerUID)) {
574  Debug.Log ("Registering new player UID");
575  var playerRequest = RegisterPlayerAsync ();
576  yield return request.AwaitSubrequest (playerRequest, 1);
577  if (request.IsError)
578  yield break;
579 
580  playerUID = playerRequest.Result.code;
581  AvatarSdkMgr.Storage ().StorePlayerUID (playerIdentifier, playerUID);
582  }
583 
584  request.IsDone = true;
585  }
586 
590  public virtual AsyncRequest AuthorizeAsync ()
591  {
592  var request = new AsyncRequest (AvatarSdkMgr.Str (Strings.Authentication));
593  AvatarSdkMgr.SpawnCoroutine (Authorize (request));
594  return request;
595  }
596 
602  public virtual void AuthorizeWithCredentials (string tokenType, string accessToken, string playerUID)
603  {
604  this.tokenType = tokenType;
605  this.accessToken = accessToken;
606  this.playerUID = playerUID;
607  }
608 
612  private AsyncWebRequest<AccessData> AuthorizeClientCredentialsGrantTypeAsync (AccessCredentials credentials)
613  {
614  var request = new AsyncWebRequest<AccessData> (AvatarSdkMgr.Str (Strings.RequestingApiToken));
615  Func<UnityWebRequest> webRequestFactory = () => GenerateAuthRequest(credentials);
616  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
617  return request;
618  }
619 
623  private AsyncWebRequest<AccessData> AuthorizePasswordGrantTypeAsync (
624  string clientId,
625  string clientSecret,
626  string username,
627  string password
628  )
629  {
630  Debug.LogWarning ("Don't use this auth method in production, use other grant types!");
631  var request = new AsyncWebRequest<AccessData> (AvatarSdkMgr.Str (Strings.RequestingApiToken));
632 
633  if (string.IsNullOrEmpty (username) || string.IsNullOrEmpty (password) || string.IsNullOrEmpty (clientId)) {
634  request.SetError ("itSeez3D credentials not provided");
635  Debug.LogError (request.ErrorMessage);
636  return request;
637  }
638 
639  var form = new Dictionary<string,string> () {
640  { "grant_type", "password" },
641  { "username", username },
642  { "password", password },
643  { "client_id", clientId },
644  { "client_secret", clientSecret },
645  };
646  Func<UnityWebRequest> webRequestFactory = () => HttpPost (GetUrl ("o", "token"), form);
647  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
648  return request;
649  }
650 
655  public virtual AsyncWebRequest<Player> RegisterPlayerAsync (string comment = "")
656  {
657  var r = new AsyncWebRequest<Player> (AvatarSdkMgr.Str (Strings.RegisteringPlayerID));
658  var form = new Dictionary<string,string> () {
659  { "comment", comment },
660  };
661  Func<UnityWebRequest> webRequestFactory = () => {
662  var webRequest = HttpPost (GetUrl ("players"), form);
663  SetAuthHeaders (webRequest);
664  return webRequest;
665  };
666  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, r));
667  return r;
668  }
669 
670 #endregion
671 
672 #region Creating/awaiting/downloading an avatar
673 
678  string name, string description, byte[] photoBytes, bool forcePowerOfTwoTexture = false,
679  PipelineType pipeline = PipelineType.FACE, ComputationParameters computationParameters = null
680  )
681  {
682  var request = new AsyncWebRequest<AvatarData> (AvatarSdkMgr.Str (Strings.UploadingPhoto), TrackProgress.UPLOAD);
683 
684  var traits = pipeline.Traits();
685  var textFields = new Dictionary<string, string>
686  {
687  {"name", name },
688  {"pipeline", traits.PipelineTypeName},
689  {"pipeline_subtype", traits.PipelineSubtypeName}
690  };
691 
692  if (!string.IsNullOrEmpty(description))
693  textFields.Add("description", description);
694 
695  if (computationParameters != null)
696  {
697  string parametersJson = parametersController.GetCalculationParametersJson(computationParameters);
698  Debug.LogFormat("Computation parameters json: {0}", parametersJson);
699  textFields.Add("parameters", parametersJson);
700  }
701 
702  Func<UnityWebRequest> webRequestFactory = () =>
703  {
704  List<IMultipartFormSection> formData = new List<IMultipartFormSection>();
705  formData.Add(new MultipartFormFileSection("photo", photoBytes, "photo.jpg", "application/octet-stream"));
706  foreach (var item in textFields)
707  formData.Add(new MultipartFormDataSection(item.Key, item.Value));
708 
709  var webRequest = UnityWebRequest.Post(GetUrl("avatars"), formData);
710 #if UNITY_2017 || UNITY_2018
711  webRequest.chunkedTransfer = false;
712 #endif
713  SetAuthHeaders(webRequest);
714  return webRequest;
715  };
716 
717  Debug.LogFormat("Uploading photo...");
718  AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, request));
719  return request;
720  }
721 
725  public virtual AsyncWebRequest<AvatarData> GetAvatarAsync (string avatarCode)
726  {
727  var r = AvatarJsonRequest<AvatarData> (GetUrl ("avatars", avatarCode));
728  r.State = AvatarSdkMgr.Str (Strings.GettingAvatarInfo);
729  return r;
730  }
731 
736  {
737  var r = AvatarJsonArrayRequest<AvatarHaircutData> (avatar.haircuts);
738  r.State = AvatarSdkMgr.Str (Strings.RequestingHaircutInfo);
739  return r;
740  }
741 
746  {
747  var r = AvatarJsonArrayRequest<TextureData> (GetUrl ("avatars", avatar.code, "textures"));
748  r.State = AvatarSdkMgr.Str (Strings.RequestingTextureInfo);
749  return r;
750  }
751 
757  public virtual AsyncWebRequest<byte[]> DownloadAvatarThumbnailBytesAsync (AvatarData avatar, int maxW, int maxH)
758  {
759  var param = new Dictionary<string, string> {
760  { "max_w", maxW.ToString () },
761  { "max_h", maxH.ToString () },
762  };
763  var url = UrlWithParams (avatar.thumbnail, param);
764 
765  var r = AvatarDataRequestAsync (url);
766  r.State = AvatarSdkMgr.Str (Strings.DownloadingThumbnail);
767  return r;
768  }
769 
775  public virtual AsyncWebRequest<byte[]> DownloadMeshZipAsync (AvatarData avatar, int levelOfDetails = 0)
776  {
777  var paramsDictionary = new Dictionary<string, string>()
778  {
779  { "lod", levelOfDetails.ToString() }
780  };
781  var url = UrlWithParams (avatar.mesh, paramsDictionary);
782  var r = AvatarDataRequestAsync (url);
783  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadMesh);
784  return r;
785  }
786 
791  {
792  var r = AvatarDataRequestAsync (GetUrl ("avatars", avatar.code, "pointcloud"));
793  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadMesh);
794  return r;
795  }
796 
801  {
802  var r = AvatarDataRequestAsync (avatar.texture);
803  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadTexture);
804  return r;
805  }
806 
807  public virtual AsyncWebRequest<DownloadedFileInfo> DownloadAdditionalTextureBytesAsync(AvatarData avatar, string textureName)
808  {
809  var r = FileDataRequestAsync(GetUrl("avatars", avatar.code, "textures", textureName, "file"));
810  r.State = AvatarSdkMgr.Str(Strings.DownloadingHeadTexture);
811  return r;
812  }
813 
818  {
819  var r = AvatarDataRequestAsync (haircut.mesh);
820  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutMesh);
821  return r;
822  }
823 
828  {
829  var r = AvatarDataRequestAsync (haircut.texture);
830  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutTexture);
831  return r;
832  }
833 
838  {
839  var r = AvatarDataRequestAsync(haircut.preview);
840  r.State = AvatarSdkMgr.Str(Strings.DownloadingHaircutPreview);
841  return r;
842  }
843 
848  {
849  var r = AvatarDataRequestAsync (haircut.pointcloud);
850  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutPointCloud);
851  return r;
852  }
853 
859  {
860  string url = string.Format ("{0}pointclouds/", avatar.haircuts);
861  var r = AvatarDataRequestAsync (url);
862  r.State = AvatarSdkMgr.Str (Strings.DownloadingAllHaircutPointClouds);
863  return r;
864  }
865 
871  public virtual AsyncWebRequest<byte[]> DownloadBlendshapesZipAsync (AvatarData avatar, BlendshapesFormat format = BlendshapesFormat.BIN, int levelOfDetails = 0)
872  {
873  string url = string.Format ("{0}?fmt={1}&lod={2}", avatar.blendshapes, format.BlendshapesFormatToStr(), levelOfDetails);
874  var r = AvatarDataRequestAsync (url);
875  r.State = AvatarSdkMgr.Str (Strings.DownloadingBlendshapes);
876  return r;
877  }
878 
879 #endregion
880 
881 #region Actions with avatars on the server (list/update/delete/...)
882 
886  public virtual AsyncWebRequest<Page<AvatarData>> GetAvatarsPageAsync (int pageNumber)
887  {
888  var r = AvatarJsonPageRequest<AvatarData> (GetUrl ("avatars"), pageNumber);
889  r.State = AvatarSdkMgr.Str (Strings.GettingAvatarList);
890  return r;
891  }
892 
896  public virtual AsyncRequest<AvatarData[]> GetAvatarsAsync (int maxItems = int.MaxValue, Dictionary<string, string> filters = null)
897  {
898  var url = GetUrl ("avatars");
899  url = UrlWithParams (url, filters);
900  var r = AvatarJsonArrayRequest<AvatarData> (url, maxItems);
901  r.State = AvatarSdkMgr.Str (Strings.GettingAvatarList);
902  return r;
903  }
904 
908  public virtual AsyncWebRequest EditAvatarAsync (AvatarData avatar, string name = null, string description = null)
909  {
910  var request = new AsyncWebRequest (AvatarSdkMgr.Str (Strings.EditingAvatar));
911 
912  byte[] requestBodyData = null;
913  using (var requestBody = new MultipartBody ()) {
914  requestBody.WriteTextField ("name", name);
915  requestBody.WriteTextField ("description", description);
916  requestBody.WriteFooter ();
917  requestBodyData = requestBody.GetRequestBodyData ();
918 
919  Func<UnityWebRequest> webRequestFactory = () => {
920  var webRequest = UnityWebRequest.Post (avatar.url, " ");
921 #if UNITY_2017 || UNITY_2018
922  webRequest.chunkedTransfer = false;
923 #endif
924  webRequest.method = "PATCH";
925  webRequest.uploadHandler = new UploadHandlerRaw (requestBodyData);
926  webRequest.SetRequestHeader (
927  "Content-Type", string.Format ("multipart/form-data; boundary=\"{0}\"", requestBody.Boundary)
928  );
929  SetAuthHeaders (webRequest);
930  return webRequest;
931  };
932 
933  Debug.LogFormat ("Uploading photo...");
934  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
935  return request;
936  }
937  }
938 
943  {
944  var request = new AsyncWebRequest (AvatarSdkMgr.Str (Strings.DeletingAvatarOnServer));
945 
946  Func<UnityWebRequest> webRequestFactory = () => {
947  var webRequest = UnityWebRequest.Delete (avatar.url);
948  SetAuthHeaders (webRequest);
949  webRequest.downloadHandler = new DownloadHandlerBuffer ();
950  return webRequest;
951  };
952  AvatarSdkMgr.SpawnCoroutine (AwaitWebRequest (webRequestFactory, request));
953  return request;
954  }
955 
956 #endregion
957 
958 #region Parameters
959 
964  {
965  string subsetStr = "available";
966  if (parametersSubset == ComputationParametersSubset.DEFAULT)
967  subsetStr = "default";
968 
969  var tratis = pipelineType.Traits();
970  var url = GetUrl("parameters", subsetStr, tratis.PipelineTypeName);
971  url = UrlWithParams(url, "pipeline_subtype", tratis.PipelineSubtypeName);
972  var request = new AsyncWebRequest<string>(Strings.GettingParametersList);
973  AvatarSdkMgr.SpawnCoroutine(AwaitStringDataAsync(() => HttpGet(url), request));
974  return request;
975  }
976 
977  public virtual AsyncRequest<bool> CheckIfPipelineSupportedAsync(PipelineType pipelineType)
978  {
979  var tratis = pipelineType.Traits();
980  string url = GetUrl("parameters", "available", tratis.PipelineTypeName);
981  url = UrlWithParams(url, "pipeline_subtype", tratis.PipelineSubtypeName);
982  AsyncRequest<bool> request = new AsyncRequest<bool>(Strings.PerformingRequest);
983  AvatarSdkMgr.SpawnCoroutine(CheckIfPipelineSupportedFunc(url, request));
984  return request;
985  }
986 #endregion
987 
988 #region Higher-level API, composite requests
989 
993  private IEnumerator AwaitAvatarCalculationsLoop (AvatarData avatar, AsyncRequest<AvatarData> request)
994  {
995  while (!Strings.FinalStates.Contains (avatar.status)) {
996  yield return new WaitForSecondsRealtime(4);
997  var avatarStatusRequest = GetAvatarAsync (avatar.code);
998  yield return avatarStatusRequest;
999 
1000  if (avatarStatusRequest.Status.Value == (long)StatusCode.Code.NOT_FOUND) {
1001  Debug.LogWarning ("404 error most likely means that avatar was deleted from the server");
1002  request.SetError (string.Format ("Avatar status response: {0}", avatarStatusRequest.ErrorMessage));
1003  yield break;
1004  }
1005 
1006  if (avatarStatusRequest.Status.Value == (long)StatusCode.Code.TOO_MANY_REQUESTS_THROTTLING) {
1007  Debug.LogWarning ("Too many requests!");
1008  yield return new WaitForSecondsRealtime (4);
1009  }
1010 
1011  if (avatarStatusRequest.IsError) {
1012  Debug.LogWarningFormat ("Status polling error: {0}", avatarStatusRequest.ErrorMessage);
1013  // Most likely this is a temporary issue. Keep polling.
1014  continue;
1015  }
1016 
1017  avatar = avatarStatusRequest.Result;
1018  Debug.LogFormat ("Status: {0}, progress: {1}%", avatar.status, avatar.progress);
1019  request.State = AvatarSdkMgr.Str (avatar.status);
1020 
1021  if (avatar.status == Strings.Computing)
1022  request.Progress = (float)avatar.progress / 100;
1023  }
1024 
1025  if (Strings.GoodFinalStates.Contains (avatar.status)) {
1026  request.Result = avatar;
1027  request.IsDone = true;
1028  } else {
1029  request.SetError (string.Format ("Avatar calculations failed, status: {0}", avatar.status));
1030  }
1031  }
1032 
1039  {
1040  var request = new AsyncRequest <AvatarData> (AvatarSdkMgr.Str (Strings.StartingCalculations));
1041  AvatarSdkMgr.SpawnCoroutine (AwaitAvatarCalculationsLoop (avatar, request));
1042  return request;
1043  }
1044 
1045 #endregion
1046  }
1047 }
static void SpawnCoroutine(IEnumerator routine)
Spawn coroutine outside of MonoBehaviour.
Definition: AvatarSdkMgr.cs:93
virtual string UrlWithParams(string url, Dictionary< string, string > param)
Urlencoded param string.
Definition: Connection.cs:53
virtual AsyncWebRequest< AvatarHaircutData[]> GetHaircutsAsync(AvatarData avatar)
Get list of all haircuts for avatar.
Definition: Connection.cs:735
void SetAuthHeaders(UnityWebRequest request)
Adds auth header to UnityWebRequest.
Definition: Connection.cs:96
virtual Dictionary< string, string > GetAuthHeaders()
Dictionary with required auth HTTP headers.
Definition: Connection.cs:80
virtual AsyncWebRequest< AvatarData > GetAvatarAsync(string avatarCode)
Get avatar information by code.
Definition: Connection.cs:725
virtual string UrlWithParams(string url, string param, string value)
Simple overload for a single-parameter use case.
Definition: Connection.cs:74
virtual AsyncRequest AuthorizeAsync()
Authorize this session using the credentials loaded from encrypted binary resource.
Definition: Connection.cs:590
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:325
virtual IEnumerator AwaitFileDataAsync(Func< UnityWebRequest > webRequestFactory, AsyncWebRequest< DownloadedFileInfo > request)
Call AwaitWebRequestFunc for downloading file data and its name.
Definition: Connection.cs:403
TrackProgress
For web requests we can track progress for either upload or download, not both.
virtual AsyncWebRequest< DownloadedFileInfo > FileDataRequestAsync(string url)
Download file and get its name.
Definition: Connection.cs:508
virtual AsyncWebRequest< byte[]> DownloadMeshZipAsync(AvatarData avatar, int levelOfDetails=0)
Download mesh zip file into memory.
Definition: Connection.cs:775
virtual AsyncWebRequest< string > GetModelInfoAsync(string avatarCode)
Request to get model info json
Definition: Connection.cs:518
virtual AsyncWebRequest< byte[]> DownloadHaircutPreviewBytesAsync(AvatarHaircutData haircut)
Download haircut preview into memory.
Definition: Connection.cs:837
virtual AsyncWebRequest< AvatarData > CreateAvatarWithPhotoAsync(string name, string description, byte[] photoBytes, bool forcePowerOfTwoTexture=false, PipelineType pipeline=PipelineType.FACE, ComputationParameters computationParameters=null)
Upload photo and create avatar instance on the server. Calculations will start right away after the p...
Definition: Connection.cs:677
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:395
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:790
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:498
virtual AsyncWebRequest< byte[]> DownloadBlendshapesZipAsync(AvatarData avatar, BlendshapesFormat format=BlendshapesFormat.BIN, int levelOfDetails=0)
Downloads zip archive with all the blendshapes.
Definition: Connection.cs:871
ComputationParametersSubset
Represents subset of avatar computation parameters
virtual AsyncWebRequest< byte[]> DownloadAvatarThumbnailBytesAsync(AvatarData avatar, int maxW, int maxH)
Download thumbnail with the specified resolution.
Definition: Connection.cs:757
Utility class (singleton) that takes care of a few things. 1) Singleton instance is a MonoBehaviour a...
Definition: AvatarSdkMgr.cs:24
virtual AsyncWebRequest EditAvatarAsync(AvatarData avatar, string name=null, string description=null)
Edit avatar name/description on the server.
Definition: Connection.cs:908
virtual AsyncWebRequest< TextureData[]> GetTexturesAsync(AvatarData avatar)
Get list of all textures for avatar.
Definition: Connection.cs:745
UnityWebRequest HttpGet(string url)
Helper factory method.
Definition: Connection.cs:107
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:896
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:858
virtual AsyncWebRequest< byte[]> DownloadTextureBytesAsync(AvatarData avatar)
Download main texture into memory. Can be used right away to create Unity texture.
Definition: Connection.cs:800
Async request for web requests.
virtual AsyncWebRequest DeleteAvatarAsync(AvatarData avatar)
Delete avatar record on the server (does not delete local files).
Definition: Connection.cs:942
virtual AsyncWebRequest< Page< AvatarData > > GetAvatarsPageAsync(int pageNumber)
Get a particular page in the list of avatars.
Definition: Connection.cs:886
override string GetCalculationParametersJson(ComputationParameters computationParams)
Converts ComputationParameters to the json format for cloud computations
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< string > GetParametersAsync(PipelineType pipelineType, ComputationParametersSubset parametersSubset)
Request to get available parameters for the pipeline
Definition: Connection.cs:963
virtual AsyncWebRequest< byte[]> DownloadHaircutMeshZipAsync(AvatarHaircutData haircut)
Downloads haircut zip file into memory.
Definition: Connection.cs:817
virtual AsyncWebRequest< byte[]> DownloadHaircutTextureBytesAsync(AvatarHaircutData haircut)
Download haircut texture into memory. Can be used right away to create Unity texture.
Definition: Connection.cs:827
virtual AsyncWebRequest< byte[]> DownloadHaircutPointCloudZipAsync(AvatarHaircutData haircut)
Downloads the haircut point cloud zip into memory.
Definition: Connection.cs:847
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:602
virtual IEnumerator AwaitDataAsync(Func< UnityWebRequest > webRequestFactory, AsyncWebRequest< byte[]> request)
Call AwaitWebRequestFunc for binary data.
Definition: Connection.cs:387
virtual AsyncWebRequest< Player > RegisterPlayerAsync(string comment="")
Register unique player UID that is used later to sign the requests.
Definition: Connection.cs:655
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:1038