Avatar SDK  1.7.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 #if UNITY_2018_3_OR_NEWER
73  var token = string.Format("{0}={1}", UnityWebRequest.EscapeURL(item.Key), UnityWebRequest.EscapeURL(item.Value));
74 #else
75  var token = string.Format ("{0}={1}", WWW.EscapeURL (item.Key), WWW.EscapeURL (item.Value));
76 #endif
77  paramTokens.Add (token);
78  }
79 
80  return string.Format ("{0}?{1}", url, string.Join ("&", paramTokens.ToArray()));
81  }
82 
86  public virtual string UrlWithParams (string url, string param, string value)
87  {
88  return UrlWithParams (url, new Dictionary<string, string> { { param, value } });
89  }
90 
92  public virtual string GetAuthUrl ()
93  {
94  return GetUrl ("o", "token");
95  }
96 
98  public virtual Dictionary<string, string> GetAuthHeaders ()
99  {
100  var headers = new Dictionary<string, string> () {
101  { "Authorization", string.Format ("{0} {1}", tokenType, accessToken) },
102 #if !UNITY_WEBGL
103  { "X-Unity-Plugin-Version", CoreTools.SdkVersion.ToString () },
104 #endif
105  };
106  if (!string.IsNullOrEmpty (playerUID))
107  headers.Add ("X-PlayerUID", playerUID);
108  return headers;
109  }
110 
114  protected void SetAuthHeaders (UnityWebRequest request)
115  {
116  var headers = GetAuthHeaders ();
117  foreach (var h in headers)
118  request.SetRequestHeader (h.Key, h.Value);
119  }
120 
125  protected UnityWebRequest HttpGet (string url)
126  {
127  if (string.IsNullOrEmpty (url))
128  Debug.LogError ("Provided empty url!");
129  var r = UnityWebRequest.Get (url);
130  SetAuthHeaders (r);
131  return r;
132  }
133 
134  protected UnityWebRequest HttpPost(string url, Dictionary<string, string> form)
135  {
136  var r = UnityWebRequest.Post(url, form);
137  r.chunkedTransfer = false; // chunked transfer causes problems with UWSGI
138  return r;
139  }
140 #endregion
141 
142 #region Generic request processing
143 
147  private static IEnumerator AwaitAndTrackProgress<T> (UnityWebRequest webRequest, AsyncWebRequest<T> request)
148  {
149  webRequest.SendWebRequest();
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 (webRequest.isNetworkError) { // apparently the API has changed in 2017
179  error = webRequest.error;
180  return false;
181  }
182 
183  if (!webRequest.downloadHandler.isDone) {
184  error = "Could not download response";
185  return false;
186  }
187 
188  if (status.IsBad) {
189  error = string.Format ("Bad response code. Msg: {0}", webRequest.downloadHandler.text);
190  return false;
191  }
192  } catch (Exception ex) {
193  Debug.LogException (ex);
194  status = new StatusCode ();
195  error = string.Format ("Exception while checking response: {0}", ex.Message);
196  return false;
197  }
198 
199  return true;
200  }
201 
210  private IEnumerator AwaitWebRequestFunc<T> (
211  Func<UnityWebRequest> webRequestFactory,
212  AsyncWebRequest<T> request,
213  Func<UnityWebRequest, T> parseDataFunc
214  )
215  {
216  UnityWebRequest webRequest = null;
217 
218  StatusCode status = new StatusCode ();
219  string error = string.Empty;
220 
221  int numAttempts = 2, lastAttempt = numAttempts - 1;
222  bool goodResponse = false;
223  for (int attempt = 0; attempt < numAttempts; ++attempt) {
224  webRequest = webRequestFactory ();
225  yield return AwaitAndTrackProgress (webRequest, request);
226 
227  if (goodResponse = IsGoodResponse (webRequest, out status, out error))
228  break;
229 
230  // all API requests have Authorization header, except for authorization requests
231  bool isAuthRequest = webRequest.GetRequestHeader ("Authorization") == null;
232 
233  Debug.LogWarningFormat ("Server error: {0}, request: {1}", error, webRequest.url);
234  if (status.Value != (long)StatusCode.Code.UNAUTHORIZED || isAuthRequest) {
235  // cannot recover, request has failed
236  break;
237  }
238 
239  if (attempt == lastAttempt) {
240  Debug.LogError ("No more retries left");
241  break;
242  }
243 
244  Debug.LogWarning ("Auth issue, let's try one more time after refreshing access token");
245  yield return AuthorizeAsync ();
246  }
247 
248  if (!goodResponse) {
249  Debug.LogErrorFormat ("Could not send the request, status: {0}, error: {1}", status, error);
250  request.Status = status;
251  request.SetError (error);
252  yield break;
253  }
254 
255  T data = default(T);
256  try {
257  data = parseDataFunc (webRequest);
258  } catch (Exception ex) {
259  Debug.LogException (ex);
260  }
261 
262  if (data == null) {
263  request.SetError ("Could not parse request data");
264  yield break;
265  } else {
266  request.Result = data;
267  }
268 
269  request.IsDone = true;
270  }
271 
275  public virtual IEnumerator AwaitWebRequest (Func<UnityWebRequest> webRequestFactory, AsyncWebRequest request)
276  {
277  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => new object ());
278  }
279 
283  public virtual IEnumerator AwaitJsonWebRequest<DataType> (
284  Func<UnityWebRequest> webRequestFactory,
286  {
287  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => {
288  return JsonUtility.FromJson<DataType> (r.downloadHandler.text);
289  });
290  }
291 
295  public virtual IEnumerator AwaitJsonPageWebRequest<T> (
296  Func<UnityWebRequest> webRequestFactory,
297  AsyncWebRequest<Page<T>> request
298  )
299  {
300  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => {
301  // Unity JsonUtility does not support Json array parsing, so we have to hack around it
302  // by wrapping it into object with a single array field.
303  var wrappedArrayJson = string.Format ("{{ \"content\": {0} }}", r.downloadHandler.text);
304  var page = JsonUtility.FromJson<Page<T>> (wrappedArrayJson);
305  var paginationHeader = r.GetResponseHeader ("Link");
306 
307  // parse "Link" header to get links to adjacent pages
308  if (!string.IsNullOrEmpty (paginationHeader)) {
309  var regex = new Regex (@".*<(?<link>.+)>.+rel=""(?<kind>.*)""");
310  var tokens = paginationHeader.Split (',');
311  foreach (var token in tokens) {
312  var match = regex.Match (token);
313  if (!match.Success)
314  continue;
315 
316  string link = match.Groups ["link"].Value, kind = match.Groups ["kind"].Value;
317  if (string.IsNullOrEmpty (link) || string.IsNullOrEmpty (kind))
318  continue;
319 
320  if (kind == "first")
321  page.firstPageUrl = link;
322  else if (kind == "next")
323  page.nextPageUrl = link;
324  else if (kind == "prev")
325  page.prevPageUrl = link;
326  else if (kind == "last")
327  page.lastPageUrl = link;
328  }
329  }
330  return page;
331  });
332  }
333 
337  public virtual IEnumerator AwaitDataAsync (Func<UnityWebRequest> webRequestFactory, AsyncWebRequest<byte[]> request)
338  {
339  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => r.downloadHandler.data);
340  }
341 
345  public virtual IEnumerator AwaitStringDataAsync(Func<UnityWebRequest> webRequestFactory, AsyncWebRequest<string> request)
346  {
347  yield return AwaitWebRequestFunc(webRequestFactory, request, (r) => r.downloadHandler.text);
348  }
349 
354  public virtual AsyncWebRequest<DataType> AvatarJsonRequest<DataType> (string url)
355  {
356  var request = new AsyncWebRequest<DataType> ();
357  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (() => HttpGet (url), request));
358  return request;
359  }
360 
365  public virtual AsyncWebRequest<Page<T>> AvatarJsonPageRequest<T> (string url)
366  {
367  var request = new AsyncWebRequest<Page<T>> ();
368  AvatarSdkMgr.SpawnCoroutine (AwaitJsonPageWebRequest (() => HttpGet (url), request));
369  return request;
370  }
371 
376  public virtual AsyncWebRequest<Page<T>> AvatarJsonPageRequest<T> (string baseUrl, int pageNumber)
377  {
378  return AvatarJsonPageRequest<T> (string.Format ("{0}?page={1}", baseUrl, pageNumber));
379  }
380 
384  public virtual IEnumerator AwaitMultiplePages<T> (string url, AsyncRequest<T[]> request, int maxItems = int.MaxValue)
385  {
386  List<T> items = new List<T> ();
387  do {
388  var pageRequest = AvatarJsonPageRequest<T> (url);
389  yield return pageRequest;
390  if (pageRequest.IsError) {
391  request.SetError (string.Format ("Page request failed. Error: {0}", pageRequest.ErrorMessage));
392  yield break;
393  }
394 
395  // Debug.LogFormat ("Successfully loaded page {0}", url);
396  var page = pageRequest.Result;
397 
398  for (int i = 0; i < page.content.Length && items.Count < maxItems; ++i)
399  items.Add (page.content[i]);
400 
401  url = page.nextPageUrl;
402  } while (items.Count < maxItems && !string.IsNullOrEmpty (url));
403 
404  request.Result = items.ToArray ();
405  request.IsDone = true;
406  }
407 
411  public virtual AsyncWebRequest<DataType[]> AvatarJsonArrayRequest<DataType> (string url, int maxItems = int.MaxValue)
412  {
413  var request = new AsyncWebRequest<DataType[]> ();
414  AvatarSdkMgr.SpawnCoroutine (AwaitMultiplePages (url, request, maxItems));
415  return request;
416  }
417 
422  {
423  Debug.LogFormat ("Downloading from {0}...", url);
424  var request = new AsyncWebRequest<byte[]> ();
425  AvatarSdkMgr.SpawnCoroutine (AwaitDataAsync (() => HttpGet (url), request));
426  return request;
427  }
428 
432  public virtual AsyncWebRequest<ModelInfo> GetModelInfoAsync(string avatarCode)
433  {
434  var r = AvatarJsonRequest<ModelInfo>(GetUrl("avatars", avatarCode, "model_info"));
435  r.State = AvatarSdkMgr.Str(Strings.RequestingModelInfo);
436  return r;
437  }
438 
439 #endregion
440 
441 #region Auth functions
442 
446  public virtual bool IsAuthorized { get { return !string.IsNullOrEmpty (accessToken); } }
447 
448  public virtual string TokenType { get { return tokenType; } }
449 
450  public virtual string AccessToken { get { return accessToken; } }
451 
455  public virtual string PlayerUID {
456  get { return playerUID; }
457  set { playerUID = value; }
458  }
459 
463  private IEnumerator Authorize (AsyncRequest request)
464  {
465  var accessCredentials = AuthUtils.LoadCredentials ();
466  if (accessCredentials == null || string.IsNullOrEmpty (accessCredentials.clientSecret)) {
467  request.SetError ("Could not find API keys! Please provide valid credentials via Window->ItSeez3D Avatar SDK");
468  yield break;
469  }
470 
471  var authRequest = AuthorizeClientCredentialsGrantTypeAsync (accessCredentials);
472  yield return request.AwaitSubrequest (authRequest, 0.5f);
473  if (request.IsError)
474  yield break;
475 
476  tokenType = authRequest.Result.token_type;
477  accessToken = authRequest.Result.access_token;
478  Debug.LogFormat ("Successful authentication!");
479 
480  // guarantees we re-register a Player if clientId changes
481  var playerIdentifier = string.Format ("player_uid_{0}", accessCredentials.clientId.Substring (0, accessCredentials.clientId.Length / 3));
482 
483  if (string.IsNullOrEmpty (playerUID))
484  playerUID = AvatarSdkMgr.Storage ().LoadPlayerUID (playerIdentifier);
485 
486  if (string.IsNullOrEmpty (playerUID)) {
487  Debug.Log ("Registering new player UID");
488  var playerRequest = RegisterPlayerAsync ();
489  yield return request.AwaitSubrequest (playerRequest, 1);
490  if (request.IsError)
491  yield break;
492 
493  playerUID = playerRequest.Result.code;
494  AvatarSdkMgr.Storage ().StorePlayerUID (playerIdentifier, playerUID);
495  }
496 
497  request.IsDone = true;
498  }
499 
503  public virtual AsyncRequest AuthorizeAsync ()
504  {
505  var request = new AsyncRequest (AvatarSdkMgr.Str (Strings.Authentication));
506  AvatarSdkMgr.SpawnCoroutine (Authorize (request));
507  return request;
508  }
509 
515  public virtual void AuthorizeWithCredentials (string tokenType, string accessToken, string playerUID)
516  {
517  this.tokenType = tokenType;
518  this.accessToken = accessToken;
519  this.playerUID = playerUID;
520  }
521 
527  public virtual UnityWebRequest GenerateAuthRequest(AccessCredentials credentials)
528  {
529  var form = new Dictionary<string, string>() {
530  { "grant_type", "client_credentials" },
531  { "client_id", credentials.clientId },
532  { "client_secret", credentials.clientSecret },
533  };
534  return HttpPost(GetAuthUrl(), form);
535  }
536 
540  private AsyncWebRequest<AccessData> AuthorizeClientCredentialsGrantTypeAsync (AccessCredentials credentials)
541  {
542  var request = new AsyncWebRequest<AccessData> (AvatarSdkMgr.Str (Strings.RequestingApiToken));
543  Func<UnityWebRequest> webRequestFactory = () => GenerateAuthRequest(credentials);
544  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
545  return request;
546  }
547 
551  private AsyncWebRequest<AccessData> AuthorizePasswordGrantTypeAsync (
552  string clientId,
553  string clientSecret,
554  string username,
555  string password
556  )
557  {
558  Debug.LogWarning ("Don't use this auth method in production, use other grant types!");
559  var request = new AsyncWebRequest<AccessData> (AvatarSdkMgr.Str (Strings.RequestingApiToken));
560 
561  if (string.IsNullOrEmpty (username) || string.IsNullOrEmpty (password) || string.IsNullOrEmpty (clientId)) {
562  request.SetError ("itSeez3D credentials not provided");
563  Debug.LogError (request.ErrorMessage);
564  return request;
565  }
566 
567  var form = new Dictionary<string,string> () {
568  { "grant_type", "password" },
569  { "username", username },
570  { "password", password },
571  { "client_id", clientId },
572  { "client_secret", clientSecret },
573  };
574  Func<UnityWebRequest> webRequestFactory = () => HttpPost (GetUrl ("o", "token"), form);
575  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
576  return request;
577  }
578 
583  public virtual AsyncWebRequest<Player> RegisterPlayerAsync (string comment = "")
584  {
585  var r = new AsyncWebRequest<Player> (AvatarSdkMgr.Str (Strings.RegisteringPlayerID));
586  var form = new Dictionary<string,string> () {
587  { "comment", comment },
588  };
589  Func<UnityWebRequest> webRequestFactory = () => {
590  var webRequest = HttpPost (GetUrl ("players"), form);
591  SetAuthHeaders (webRequest);
592  return webRequest;
593  };
594  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, r));
595  return r;
596  }
597 
598 #endregion
599 
600 #region Creating/awaiting/downloading an avatar
601 
606  string name, string description, byte[] photoBytes, bool forcePowerOfTwoTexture = false,
607  PipelineType pipeline = PipelineType.FACE, AvatarResources resources = null
608  )
609  {
610  var request = new AsyncWebRequest<AvatarData> (AvatarSdkMgr.Str (Strings.UploadingPhoto), TrackProgress.UPLOAD);
611 
612  var textFields = new Dictionary<string, string>
613  {
614  {"name", name },
615  {"preserve_original_texture", (!forcePowerOfTwoTexture).ToString() },
616  {"pipeline", pipeline.GetPipelineTypeName() },
617  };
618 
619  if (!string.IsNullOrEmpty(description))
620  textFields.Add("description", description);
621 
622  if (resources != null)
623  {
624  textFields.Add("pipeline_subtype", pipeline_subtype);
625  textFields.Add("resources", AvatarSdkMgr.CalculationParametersGenerator().GetAvatarCalculationParamsJson(resources));
626  }
627 
628  Func<UnityWebRequest> webRequestFactory = () =>
629  {
630  List<IMultipartFormSection> formData = new List<IMultipartFormSection>();
631  formData.Add(new MultipartFormFileSection("photo", photoBytes, "photo.jpg", "application/octet-stream"));
632  foreach (var item in textFields)
633  formData.Add(new MultipartFormDataSection(item.Key, item.Value));
634 
635  var webRequest = UnityWebRequest.Post(GetUrl("avatars"), formData);
636  webRequest.chunkedTransfer = false;
637  SetAuthHeaders(webRequest);
638  return webRequest;
639  };
640 
641  Debug.LogFormat("Uploading photo...");
642  AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, request));
643  return request;
644  }
645 
649  public virtual AsyncWebRequest<AvatarData> GetAvatarAsync (string avatarCode)
650  {
651  var r = AvatarJsonRequest<AvatarData> (GetUrl ("avatars", avatarCode));
652  r.State = AvatarSdkMgr.Str (Strings.GettingAvatarInfo);
653  return r;
654  }
655 
660  {
661  var r = AvatarJsonArrayRequest<AvatarHaircutData> (avatar.haircuts);
662  r.State = AvatarSdkMgr.Str (Strings.RequestingHaircutInfo);
663  return r;
664  }
665 
670  {
671  var r = AvatarJsonArrayRequest<TextureData> (GetUrl ("avatars", avatar.code, "textures"));
672  r.State = AvatarSdkMgr.Str (Strings.RequestingTextureInfo);
673  return r;
674  }
675 
681  public virtual AsyncWebRequest<byte[]> DownloadAvatarThumbnailBytesAsync (AvatarData avatar, int maxW, int maxH)
682  {
683  var param = new Dictionary<string, string> {
684  { "max_w", maxW.ToString () },
685  { "max_h", maxH.ToString () },
686  };
687  var url = UrlWithParams (avatar.thumbnail, param);
688 
689  var r = AvatarDataRequestAsync (url);
690  r.State = AvatarSdkMgr.Str (Strings.DownloadingThumbnail);
691  return r;
692  }
693 
699  public virtual AsyncWebRequest<byte[]> DownloadMeshZipAsync (AvatarData avatar, int levelOfDetails = 0)
700  {
701  var url = UrlWithParams (avatar.mesh, "lod", levelOfDetails.ToString ());
702  var r = AvatarDataRequestAsync (url);
703  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadMesh);
704  return r;
705  }
706 
711  {
712  var r = AvatarDataRequestAsync (GetUrl ("avatars", avatar.code, "pointcloud"));
713  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadMesh);
714  return r;
715  }
716 
721  {
722  var r = AvatarDataRequestAsync (avatar.texture);
723  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadTexture);
724  return r;
725  }
726 
731  {
732  var r = AvatarDataRequestAsync (haircut.mesh);
733  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutMesh);
734  return r;
735  }
736 
741  {
742  var r = AvatarDataRequestAsync (haircut.texture);
743  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutTexture);
744  return r;
745  }
746 
751  {
752  var r = AvatarDataRequestAsync(haircut.preview);
753  r.State = AvatarSdkMgr.Str(Strings.DownloadingHaircutPreview);
754  return r;
755  }
756 
761  {
762  var r = AvatarDataRequestAsync (haircut.pointcloud);
763  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutPointCloud);
764  return r;
765  }
766 
772  {
773  string url = string.Format ("{0}pointclouds/", avatar.haircuts);
774  var r = AvatarDataRequestAsync (url);
775  r.State = AvatarSdkMgr.Str (Strings.DownloadingAllHaircutPointClouds);
776  return r;
777  }
778 
784  public virtual AsyncWebRequest<byte[]> DownloadBlendshapesZipAsync (AvatarData avatar, BlendshapesFormat format = BlendshapesFormat.BIN, int levelOfDetails = 0)
785  {
786  var fmtToStr = new Dictionary<BlendshapesFormat, string> {
787  { BlendshapesFormat.BIN, "bin" },
788  { BlendshapesFormat.FBX, "fbx" },
789  { BlendshapesFormat.PLY, "ply" },
790  };
791 
792  string url = string.Format ("{0}?fmt={1}&lod={2}", avatar.blendshapes, fmtToStr [format], levelOfDetails);
793  var r = AvatarDataRequestAsync (url);
794  r.State = AvatarSdkMgr.Str (Strings.DownloadingBlendshapes);
795  return r;
796  }
797 
798 #endregion
799 
800 #region Actions with avatars on the server (list/update/delete/...)
801 
805  public virtual AsyncWebRequest<Page<AvatarData>> GetAvatarsPageAsync (int pageNumber)
806  {
807  var r = AvatarJsonPageRequest<AvatarData> (GetUrl ("avatars"), pageNumber);
808  r.State = AvatarSdkMgr.Str (Strings.GettingAvatarList);
809  return r;
810  }
811 
815  public virtual AsyncRequest<AvatarData[]> GetAvatarsAsync (int maxItems = int.MaxValue, Dictionary<string, string> filters = null)
816  {
817  var url = GetUrl ("avatars");
818  url = UrlWithParams (url, filters);
819  var r = AvatarJsonArrayRequest<AvatarData> (url, maxItems);
820  r.State = AvatarSdkMgr.Str (Strings.GettingAvatarList);
821  return r;
822  }
823 
827  public virtual AsyncWebRequest EditAvatarAsync (AvatarData avatar, string name = null, string description = null)
828  {
829  var request = new AsyncWebRequest (AvatarSdkMgr.Str (Strings.EditingAvatar));
830 
831  byte[] requestBodyData = null;
832  using (var requestBody = new MultipartBody ()) {
833  requestBody.WriteTextField ("name", name);
834  requestBody.WriteTextField ("description", description);
835  requestBody.WriteFooter ();
836  requestBodyData = requestBody.GetRequestBodyData ();
837 
838  Func<UnityWebRequest> webRequestFactory = () => {
839  var webRequest = UnityWebRequest.Post (avatar.url, " ");
840  webRequest.chunkedTransfer = false;
841  webRequest.method = "PATCH";
842  webRequest.uploadHandler = new UploadHandlerRaw (requestBodyData);
843  webRequest.SetRequestHeader (
844  "Content-Type", string.Format ("multipart/form-data; boundary=\"{0}\"", requestBody.Boundary)
845  );
846  SetAuthHeaders (webRequest);
847  return webRequest;
848  };
849 
850  Debug.LogFormat ("Uploading photo...");
851  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
852  return request;
853  }
854  }
855 
860  {
861  var request = new AsyncWebRequest (AvatarSdkMgr.Str (Strings.DeletingAvatarOnServer));
862 
863  Func<UnityWebRequest> webRequestFactory = () => {
864  var webRequest = UnityWebRequest.Delete (avatar.url);
865  SetAuthHeaders (webRequest);
866  webRequest.downloadHandler = new DownloadHandlerBuffer ();
867  return webRequest;
868  };
869  AvatarSdkMgr.SpawnCoroutine (AwaitWebRequest (webRequestFactory, request));
870  return request;
871  }
872 
873 #endregion
874 
875 #region Resources
876 
880  public virtual AsyncWebRequest<string> GetResourcesAsync(PipelineType pipelineType, AvatarResourcesSubset resourcesSubset)
881  {
882  string subsetStr = "available";
883  if (resourcesSubset == AvatarResourcesSubset.DEFAULT)
884  subsetStr = "default";
885 
886  var url = GetUrl("resources", subsetStr, pipelineType.GetPipelineTypeName());
887  url = UrlWithParams(url, "pipeline_subtype", pipeline_subtype);
888  var request = new AsyncWebRequest<string>(Strings.GettingResourcesList);
889  AvatarSdkMgr.SpawnCoroutine(AwaitStringDataAsync(() => HttpGet(url), request));
890  return request;
891  }
892 #endregion
893 
894 #region Higher-level API, composite requests
895 
899  private IEnumerator AwaitAvatarCalculationsLoop (AvatarData avatar, AsyncRequest<AvatarData> request)
900  {
901  while (!Strings.FinalStates.Contains (avatar.status)) {
902  yield return new WaitForSecondsRealtime(4);
903  var avatarStatusRequest = GetAvatarAsync (avatar.code);
904  yield return avatarStatusRequest;
905 
906  if (avatarStatusRequest.Status.Value == (long)StatusCode.Code.NOT_FOUND) {
907  Debug.LogWarning ("404 error most likely means that avatar was deleted from the server");
908  request.SetError (string.Format ("Avatar status response: {0}", avatarStatusRequest.ErrorMessage));
909  yield break;
910  }
911 
912  if (avatarStatusRequest.Status.Value == (long)StatusCode.Code.TOO_MANY_REQUESTS_THROTTLING) {
913  Debug.LogWarning ("Too many requests!");
914  yield return new WaitForSecondsRealtime (4);
915  }
916 
917  if (avatarStatusRequest.IsError) {
918  Debug.LogWarningFormat ("Status polling error: {0}", avatarStatusRequest.ErrorMessage);
919  // Most likely this is a temporary issue. Keep polling.
920  continue;
921  }
922 
923  avatar = avatarStatusRequest.Result;
924  Debug.LogFormat ("Status: {0}, progress: {1}%", avatar.status, avatar.progress);
925  request.State = AvatarSdkMgr.Str (avatar.status);
926 
927  if (avatar.status == Strings.Computing)
928  request.Progress = (float)avatar.progress / 100;
929  }
930 
931  if (Strings.GoodFinalStates.Contains (avatar.status)) {
932  request.Result = avatar;
933  request.IsDone = true;
934  } else {
935  request.SetError (string.Format ("Avatar calculations failed, status: {0}", avatar.status));
936  }
937  }
938 
945  {
946  var request = new AsyncRequest <AvatarData> (AvatarSdkMgr.Str (Strings.StartingCalculations));
947  AvatarSdkMgr.SpawnCoroutine (AwaitAvatarCalculationsLoop (avatar, request));
948  return request;
949  }
950 
951 #endregion
952  }
953 }
static void SpawnCoroutine(IEnumerator routine)
Spawn coroutine outside of MonoBehaviour.
Definition: AvatarSdkMgr.cs:94
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:659
void SetAuthHeaders(UnityWebRequest request)
Adds auth header to UnityWebRequest.
Definition: Connection.cs:114
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:605
virtual Dictionary< string, string > GetAuthHeaders()
Dictionary with required auth HTTP headers.
Definition: Connection.cs:98
virtual AsyncWebRequest< AvatarData > GetAvatarAsync(string avatarCode)
Get avatar information by code.
Definition: Connection.cs:649
virtual string UrlWithParams(string url, string param, string value)
Simple overload for a single-parameter use case.
Definition: Connection.cs:86
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:503
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:275
virtual AsyncWebRequest< string > GetResourcesAsync(PipelineType pipelineType, AvatarResourcesSubset resourcesSubset)
Request to get available resources for the pipeline
Definition: Connection.cs:880
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:699
virtual AsyncWebRequest< byte[]> DownloadHaircutPreviewBytesAsync(AvatarHaircutData haircut)
Download haircut preview into memory.
Definition: Connection.cs:750
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:345
virtual UnityWebRequest GenerateAuthRequest(AccessCredentials credentials)
Generate HTTP request object to obtain the access token.
Definition: Connection.cs:527
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:710
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:421
virtual AsyncWebRequest< byte[]> DownloadBlendshapesZipAsync(AvatarData avatar, BlendshapesFormat format=BlendshapesFormat.BIN, int levelOfDetails=0)
Downloads zip archive with all the blendshapes.
Definition: Connection.cs:784
virtual AsyncWebRequest< byte[]> DownloadAvatarThumbnailBytesAsync(AvatarData avatar, int maxW, int maxH)
Download thumbnail with the specified resolution.
Definition: Connection.cs:681
virtual string GetAuthUrl()
Url used to obtain access token.
Definition: Connection.cs:92
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:827
virtual AsyncWebRequest< TextureData[]> GetTexturesAsync(AvatarData avatar)
Get list of all textures for avatar.
Definition: Connection.cs:669
UnityWebRequest HttpGet(string url)
Helper factory method.
Definition: Connection.cs:125
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:815
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:771
virtual AsyncWebRequest< byte[]> DownloadTextureBytesAsync(AvatarData avatar)
Download main texture into memory. Can be used right away to create Unity texture.
Definition: Connection.cs:720
Async request for web requests.
virtual AsyncWebRequest< ModelInfo > GetModelInfoAsync(string avatarCode)
Request to get skin and hair colors.
Definition: Connection.cs:432
virtual AsyncWebRequest DeleteAvatarAsync(AvatarData avatar)
Delete avatar record on the server (does not delete local files).
Definition: Connection.cs:859
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:805
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:730
virtual AsyncWebRequest< byte[]> DownloadHaircutTextureBytesAsync(AvatarHaircutData haircut)
Download haircut texture into memory. Can be used right away to create Unity texture.
Definition: Connection.cs:740
virtual AsyncWebRequest< byte[]> DownloadHaircutPointCloudZipAsync(AvatarHaircutData haircut)
Downloads the haircut point cloud zip into memory.
Definition: Connection.cs:760
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:515
virtual IEnumerator AwaitDataAsync(Func< UnityWebRequest > webRequestFactory, AsyncWebRequest< byte[]> request)
Call AwaitWebRequestFunc for binary data.
Definition: Connection.cs:337
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:583
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:944