Avatar SDK  2.1.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;
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  {
61 #if UNITY_2018_3_OR_NEWER
62  var token = string.Format("{0}={1}", UnityWebRequest.EscapeURL(item.Key), UnityWebRequest.EscapeURL(item.Value));
63 #else
64  var token = string.Format ("{0}={1}", WWW.EscapeURL (item.Key), WWW.EscapeURL (item.Value));
65 #endif
66  paramTokens.Add(token);
67  }
68 
69  return string.Format("{0}?{1}", url, string.Join("&", paramTokens.ToArray()));
70  }
71 
75  public virtual string UrlWithParams(string url, string param, string value)
76  {
77  return UrlWithParams(url, new Dictionary<string, string> { { param, value } });
78  }
79 
81  public virtual Dictionary<string, string> GetAuthHeaders()
82  {
83  var headers = new Dictionary<string, string>() {
84  { "Authorization", string.Format ("{0} {1}", tokenType, accessToken) },
85 #if !UNITY_WEBGL
86  { "X-Unity-Plugin-Version", CoreTools.CloudSdkVersion.ToString () },
87 #endif
88  };
89  if (!string.IsNullOrEmpty(playerUID))
90  headers.Add("X-PlayerUID", playerUID);
91  return headers;
92  }
93 
97  protected void SetAuthHeaders(UnityWebRequest request)
98  {
99  var headers = GetAuthHeaders();
100  foreach (var h in headers)
101  request.SetRequestHeader(h.Key, h.Value);
102  }
103 
108  protected UnityWebRequest HttpGet(string url)
109  {
110  if (string.IsNullOrEmpty(url))
111  Debug.LogError("Provided empty url!");
112  var r = UnityWebRequest.Get(url);
113  SetAuthHeaders(r);
114  return r;
115  }
116  #endregion
117 
118  #region Generic request processing
119 
120  private static void PrintWebRequestInfo(UnityWebRequest webRequest, double requestDurationInSec)
121  {
122  StringBuilder sb = new StringBuilder();
123  sb.AppendFormat("{0}: {1}", webRequest.method, webRequest.url).AppendLine();
124  if (webRequest.uploadHandler != null)
125  {
126  sb.AppendFormat("Content type: {0}", webRequest.uploadHandler.contentType).AppendLine();
127  sb.AppendFormat("Uploaded body: {0}", Encoding.UTF8.GetString(webRequest.uploadHandler.data)).AppendLine();
128  }
129  sb.AppendFormat("Uploaded bytes: {0}", webRequest.uploadedBytes).AppendLine();
130  sb.AppendFormat("Response code: {0}", webRequest.responseCode).AppendLine();
131  if (webRequest.downloadHandler != null && webRequest.downloadHandler.text.Length < 5000)
132  {
133  sb.AppendFormat("Response body: {0}", webRequest.downloadHandler.text).AppendLine();
134  }
135  sb.AppendFormat("Downloaded bytes: {0}", webRequest.downloadedBytes).AppendLine();
136  sb.AppendFormat("Request duration: {0}", requestDurationInSec);
137  Debug.LogFormat("{0}", sb.ToString());
138  }
139 
143  private static IEnumerator AwaitAndTrackProgress<T>(UnityWebRequest webRequest, AsyncWebRequest<T> request)
144  {
145  DateTime sendRequestTime = DateTime.Now;
146  webRequest.SendWebRequest();
147  do
148  {
149  yield return null;
150 
151  switch (request.ProgressTracking)
152  {
153  case TrackProgress.DOWNLOAD:
154  request.Progress = webRequest.downloadProgress;
155  break;
156  case TrackProgress.UPLOAD:
157  request.Progress = webRequest.uploadProgress;
158  break;
159  }
160 
161  request.BytesDownloaded = webRequest.downloadedBytes;
162  request.BytesUploaded = webRequest.uploadedBytes;
163  } while (!webRequest.isDone);
164 
165  PrintWebRequestInfo(webRequest, (DateTime.Now - sendRequestTime).TotalSeconds);
166  }
167 
172  private static bool IsGoodResponse(UnityWebRequest webRequest, out StatusCode status, out string error)
173  {
174  error = string.Empty;
175 
176  try
177  {
178  status = new StatusCode(webRequest.responseCode);
179 
180  if (webRequest.isNetworkError)
181  { // apparently the API has changed in 2017
182  error = webRequest.error;
183  return false;
184  }
185 
186  if (status.IsBad)
187  {
188  error = string.Format("Bad response code. Msg: {0}", webRequest.downloadHandler.text);
189  return false;
190  }
191 
192  if (!webRequest.downloadHandler.isDone)
193  {
194  error = "Could not download response";
195  return false;
196  }
197  }
198  catch (Exception ex)
199  {
200  Debug.LogException(ex);
201  status = new StatusCode();
202  error = string.Format("Exception while checking response: {0}", ex.Message);
203  return false;
204  }
205 
206  return true;
207  }
208 
217  private IEnumerator AwaitWebRequestFunc<T>(
218  Func<UnityWebRequest> webRequestFactory,
219  AsyncWebRequest<T> request,
220  Func<UnityWebRequest, T> parseDataFunc
221  )
222  {
223  UnityWebRequest webRequest = null;
224 
225  StatusCode status = new StatusCode();
226  string error = string.Empty;
227 
228  int numAttempts = 2, lastAttempt = numAttempts - 1;
229  bool goodResponse = false;
230  for (int attempt = 0; attempt < numAttempts; ++attempt)
231  {
232  webRequest = webRequestFactory();
233  yield return AwaitAndTrackProgress(webRequest, request);
234 
235  if (goodResponse = IsGoodResponse(webRequest, out status, out error))
236  break;
237 
238  if(status.Value == (long)StatusCode.Code.TOO_MANY_REQUESTS_THROTTLING)
239  {
240  var responseHeaders = webRequest.GetResponseHeaders();
241  const string retryKey = "Retry-After";
242  if (responseHeaders.ContainsKey(retryKey))
243  {
244  var retryAfterStr = responseHeaders[retryKey];
245  int retryAfter;
246  int.TryParse(retryAfterStr, out retryAfter);
247  request.RetryPeriod = retryAfter;
248  }
249  }
250 
251  // all API requests have Authorization header, except for authorization requests
252  bool isAuthRequest = webRequest.GetRequestHeader("Authorization") == null;
253  Debug.LogWarningFormat("Server error: {0}, request: {1}", error, webRequest.url);
254  if (status.Value != (long)StatusCode.Code.UNAUTHORIZED || isAuthRequest)
255  {
256  // cannot recover, request has failed
257  break;
258  }
259 
260  if (attempt == lastAttempt)
261  {
262  Debug.LogError("No more retries left");
263  break;
264  }
265 
266  Debug.LogWarning("Auth issue, let's try one more time after refreshing access token");
267  yield return AuthorizeAsync();
268  }
269 
270  if (!goodResponse)
271  {
272  Debug.LogErrorFormat("Could not send the request, status: {0}, error: {1}", status, error);
273  request.Status = status;
274  request.SetError(error);
275  yield break;
276  }
277 
278  T data = default(T);
279  try
280  {
281  data = parseDataFunc(webRequest);
282  }
283  catch (Exception ex)
284  {
285  Debug.LogException(ex);
286  }
287 
288  if (data == null)
289  {
290  request.SetError("Could not parse request data");
291  yield break;
292  }
293  else
294  {
295  request.Result = data;
296  }
297 
298  request.IsDone = true;
299  }
300 
309  private IEnumerator CheckIfPipelineSupportedFunc(string url, AsyncRequest<bool> request)
310  {
311  DateTime sendRequestTime = DateTime.Now;
312  UnityWebRequest webRequest = HttpGet(url);
313  yield return webRequest.SendWebRequest();
314 
315  while (!webRequest.isDone)
316  yield return null;
317 
318  PrintWebRequestInfo(webRequest, (DateTime.Now - sendRequestTime).TotalSeconds);
319 
320  if (!webRequest.isDone)
321  Debug.LogErrorFormat("Request isn't completed");
322 
323  if (webRequest.isNetworkError)
324  {
325  Debug.LogErrorFormat("Could not send the request, status: {0}, error: {1}", webRequest.responseCode, webRequest.error);
326  request.SetError(webRequest.downloadHandler.text);
327  yield break;
328  }
329 
330  if (webRequest.isHttpError)
331  {
332  if (webRequest.responseCode == 400)
333  {
334  request.Result = false;
335  request.IsDone = true;
336  }
337  else
338  {
339  Debug.LogErrorFormat("Got error in response, status: {0}, error: {1}", webRequest.responseCode, webRequest.downloadHandler.text);
340  request.SetError(webRequest.downloadHandler.text);
341  yield break;
342  }
343  }
344  else
345  {
346  request.Result = true;
347  request.IsDone = true;
348 
349  }
350  }
351 
355  public virtual IEnumerator AwaitWebRequest(Func<UnityWebRequest> webRequestFactory, AsyncWebRequest request)
356  {
357  yield return AwaitWebRequestFunc(webRequestFactory, request, (r) => new object());
358  }
359 
363  public virtual IEnumerator AwaitJsonWebRequest<DataType>(
364  Func<UnityWebRequest> webRequestFactory,
366  {
367  yield return AwaitWebRequestFunc(webRequestFactory, request, (r) =>
368  {
369  return JsonUtility.FromJson<DataType>(r.downloadHandler.text);
370  });
371  }
372 
376  public virtual IEnumerator AwaitJsonPageWebRequest<T>(
377  Func<UnityWebRequest> webRequestFactory,
378  AsyncWebRequest<Page<T>> request
379  )
380  {
381  yield return AwaitWebRequestFunc(webRequestFactory, request, (r) =>
382  {
383  // Unity JsonUtility does not support Json array parsing, so we have to hack around it
384  // by wrapping it into object with a single array field.
385  var wrappedArrayJson = string.Format("{{ \"content\": {0} }}", r.downloadHandler.text);
386  var page = JsonUtility.FromJson<Page<T>>(wrappedArrayJson);
387  var paginationHeader = r.GetResponseHeader("Link");
388 
389  // parse "Link" header to get links to adjacent pages
390  if (!string.IsNullOrEmpty(paginationHeader))
391  {
392  var regex = new Regex(@".*<(?<link>.+)>.+rel=""(?<kind>.*)""");
393  var tokens = paginationHeader.Split(',');
394  foreach (var token in tokens)
395  {
396  var match = regex.Match(token);
397  if (!match.Success)
398  continue;
399 
400  string link = match.Groups["link"].Value, kind = match.Groups["kind"].Value;
401  if (string.IsNullOrEmpty(link) || string.IsNullOrEmpty(kind))
402  continue;
403 
404  if (kind == "first")
405  page.firstPageUrl = link;
406  else if (kind == "next")
407  page.nextPageUrl = link;
408  else if (kind == "prev")
409  page.prevPageUrl = link;
410  else if (kind == "last")
411  page.lastPageUrl = link;
412  }
413  }
414  return page;
415  });
416  }
417 
421  public virtual IEnumerator AwaitDataAsync(Func<UnityWebRequest> webRequestFactory, AsyncWebRequest<byte[]> request)
422  {
423  yield return AwaitWebRequestFunc(webRequestFactory, request, (r) => r.downloadHandler.data);
424  }
425 
429  public virtual IEnumerator AwaitStringDataAsync(Func<UnityWebRequest> webRequestFactory, AsyncWebRequest<string> request)
430  {
431  yield return AwaitWebRequestFunc(webRequestFactory, request, (r) => r.downloadHandler.text);
432  }
433 
437  public virtual IEnumerator AwaitFileDataAsync(Func<UnityWebRequest> webRequestFactory, AsyncWebRequest<DownloadedFileInfo> request)
438  {
439  yield return AwaitWebRequestFunc(webRequestFactory, request, (r) =>
440  {
441  DownloadedFileInfo fileInfo = new DownloadedFileInfo();
442  fileInfo.bytes = r.downloadHandler.data;
443  var headers = r.GetResponseHeaders();
444  string contentDispositionHeader = "Content-Disposition";
445  string filenameTag = "filename";
446  if (headers.ContainsKey(contentDispositionHeader))
447  {
448  string contentDispositionValue = headers[contentDispositionHeader];
449  int filenamePos = contentDispositionValue.IndexOf(filenameTag);
450  if (filenamePos >= 0)
451  {
452  fileInfo.fileName = contentDispositionValue.Substring(filenamePos + filenameTag.Length + 1);
453  }
454  else
455  Debug.LogErrorFormat("Filename isn't found in the Content-Disposition header: {0}", contentDispositionValue);
456  }
457  return fileInfo;
458  });
459  }
460 
465  public virtual AsyncWebRequest<DataType> AvatarJsonRequest<DataType>(string url)
466  {
467  var request = new AsyncWebRequest<DataType>();
468  AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(() => HttpGet(url), request));
469  return request;
470  }
471 
476  public virtual AsyncWebRequest<Page<T>> AvatarJsonPageRequest<T>(string url)
477  {
478  var request = new AsyncWebRequest<Page<T>>();
479  AvatarSdkMgr.SpawnCoroutine(AwaitJsonPageWebRequest(() => HttpGet(url), request));
480  return request;
481  }
482 
487  public virtual AsyncWebRequest<Page<T>> AvatarJsonPageRequest<T>(string baseUrl, int pageNumber)
488  {
489  return AvatarJsonPageRequest<T>(string.Format("{0}?page={1}", baseUrl, pageNumber));
490  }
491 
495  public virtual IEnumerator AwaitMultiplePages<T>(string url, AsyncRequest<T[]> request, int maxItems = int.MaxValue)
496  {
497  List<T> items = new List<T>();
498  do
499  {
500  var pageRequest = AvatarJsonPageRequest<T>(url);
501  yield return pageRequest;
502  if (pageRequest.IsError)
503  {
504  request.SetError(string.Format("Page request failed. Error: {0}", pageRequest.ErrorMessage));
505  yield break;
506  }
507 
508  // Debug.LogFormat ("Successfully loaded page {0}", url);
509  var page = pageRequest.Result;
510 
511  for (int i = 0; i < page.content.Length && items.Count < maxItems; ++i)
512  items.Add(page.content[i]);
513 
514  url = page.nextPageUrl;
515  } while (items.Count < maxItems && !string.IsNullOrEmpty(url));
516 
517  request.Result = items.ToArray();
518  request.IsDone = true;
519  }
520 
524  public virtual AsyncWebRequest<DataType[]> AvatarJsonArrayRequest<DataType>(string url, int maxItems = int.MaxValue)
525  {
526  var request = new AsyncWebRequest<DataType[]>();
527  AvatarSdkMgr.SpawnCoroutine(AwaitMultiplePages(url, request, maxItems));
528  return request;
529  }
530 
535  {
536  var request = new AsyncWebRequest<byte[]>();
537  AvatarSdkMgr.SpawnCoroutine(AwaitDataAsync(() => HttpGet(url), request));
538  return request;
539  }
540 
545  {
546  var request = new AsyncWebRequest<DownloadedFileInfo>();
547  AvatarSdkMgr.SpawnCoroutine(AwaitFileDataAsync(() => HttpGet(url), request));
548  return request;
549  }
550 
554  public virtual AsyncWebRequest<string> GetModelInfoAsync(string avatarCode)
555  {
556  string url = GetUrl("avatars", avatarCode, "model_info");
557  var request = new AsyncWebRequest<string>(Strings.RequestingModelInfo);
558  AvatarSdkMgr.SpawnCoroutine(AwaitStringDataAsync(() => HttpGet(url), request));
559  return request;
560  }
561 
562  #endregion
563 
564  #region Auth functions
565 
569  public virtual bool IsAuthorized { get { return !string.IsNullOrEmpty(accessToken); } }
570 
571  public virtual string TokenType { get { return tokenType; } }
572 
573  public virtual string AccessToken { get { return accessToken; } }
574 
578  public virtual string PlayerUID
579  {
580  get { return playerUID; }
581  set { playerUID = value; }
582  }
583 
587  private IEnumerator Authorize(AsyncRequest request)
588  {
589  var accessCredentials = AuthUtils.LoadCredentials();
590  if (accessCredentials == null || string.IsNullOrEmpty(accessCredentials.clientSecret))
591  {
592  request.SetError("Could not find API keys! Please provide valid credentials via Window->ItSeez3D Avatar SDK");
593  yield break;
594  }
595 
596  var authRequest = AuthorizeClientCredentialsGrantTypeAsync(accessCredentials);
597  yield return request.AwaitSubrequest(authRequest, 0.5f);
598  if (request.IsError)
599  yield break;
600 
601  tokenType = authRequest.Result.token_type;
602  accessToken = authRequest.Result.access_token;
603  Debug.LogFormat("Successful authentication!");
604 
605  // guarantees we re-register a Player if clientId changes
606  var playerIdentifier = string.Format("player_uid_{0}", accessCredentials.clientId.Substring(0, accessCredentials.clientId.Length / 3));
607 
608  if (string.IsNullOrEmpty(playerUID))
609  playerUID = AvatarSdkMgr.Storage().LoadPlayerUID(playerIdentifier);
610 
611  if (string.IsNullOrEmpty(playerUID))
612  {
613  Debug.Log("Registering new player UID");
614  var playerRequest = RegisterPlayerAsync();
615  yield return request.AwaitSubrequest(playerRequest, 1);
616  if (request.IsError)
617  yield break;
618 
619  playerUID = playerRequest.Result.code;
620  AvatarSdkMgr.Storage().StorePlayerUID(playerIdentifier, playerUID);
621  }
622 
623  request.IsDone = true;
624  }
625 
629  public virtual AsyncRequest AuthorizeAsync()
630  {
631  var request = new AsyncRequest(AvatarSdkMgr.Str(Strings.Authentication));
632  AvatarSdkMgr.SpawnCoroutine(Authorize(request));
633  return request;
634  }
635 
641  public virtual void AuthorizeWithCredentials(string tokenType, string accessToken, string playerUID)
642  {
643  this.tokenType = tokenType;
644  this.accessToken = accessToken;
645  this.playerUID = playerUID;
646  }
647 
651  private AsyncWebRequest<AccessData> AuthorizeClientCredentialsGrantTypeAsync(AccessCredentials credentials)
652  {
653  var request = new AsyncWebRequest<AccessData>(AvatarSdkMgr.Str(Strings.RequestingApiToken));
654  Func<UnityWebRequest> webRequestFactory = () => GenerateAuthRequest(credentials);
655  AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, request));
656  return request;
657  }
658 
662  private AsyncWebRequest<AccessData> AuthorizePasswordGrantTypeAsync(
663  string clientId,
664  string clientSecret,
665  string username,
666  string password
667  )
668  {
669  Debug.LogWarning("Don't use this auth method in production, use other grant types!");
670  var request = new AsyncWebRequest<AccessData>(AvatarSdkMgr.Str(Strings.RequestingApiToken));
671 
672  if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password) || string.IsNullOrEmpty(clientId))
673  {
674  request.SetError("itSeez3D credentials not provided");
675  Debug.LogError(request.ErrorMessage);
676  return request;
677  }
678 
679  var form = new Dictionary<string, string>() {
680  { "grant_type", "password" },
681  { "username", username },
682  { "password", password },
683  { "client_id", clientId },
684  { "client_secret", clientSecret },
685  };
686  Func<UnityWebRequest> webRequestFactory = () => HttpPost(GetUrl("o", "token"), form);
687  AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, request));
688  return request;
689  }
690 
695  public virtual AsyncWebRequest<Player> RegisterPlayerAsync(string comment = "")
696  {
697  var r = new AsyncWebRequest<Player>(AvatarSdkMgr.Str(Strings.RegisteringPlayerID));
698  var form = new Dictionary<string, string>() {
699  { "comment", comment },
700  };
701  Func<UnityWebRequest> webRequestFactory = () =>
702  {
703  var webRequest = HttpPost(GetUrl("players"), form);
704  SetAuthHeaders(webRequest);
705  return webRequest;
706  };
707  AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, r));
708  return r;
709  }
710 
711  #endregion
712 
713  #region Creating/awaiting/downloading an avatar
714 
719  string name, string description, byte[] photoBytes, bool forcePowerOfTwoTexture = false,
720  PipelineType pipeline = PipelineType.FACE, ComputationParameters computationParameters = null
721  )
722  {
723  var request = new AsyncWebRequest<AvatarData>(AvatarSdkMgr.Str(Strings.UploadingPhoto), TrackProgress.UPLOAD);
724 
725  var traits = pipeline.Traits();
726  var textFields = new Dictionary<string, string>
727  {
728  {"name", name },
729  {"pipeline", traits.PipelineTypeName},
730  {"pipeline_subtype", traits.PipelineSubtypeName}
731  };
732 
733  if (!string.IsNullOrEmpty(description))
734  textFields.Add("description", description);
735 
736  if (computationParameters != null)
737  {
738  string parametersJson = parametersController.GetCalculationParametersJson(computationParameters);
739  Debug.LogFormat("Computation parameters json: {0}", parametersJson);
740  textFields.Add("parameters", parametersJson);
741  }
742 
743  Func<UnityWebRequest> webRequestFactory = () =>
744  {
745  List<IMultipartFormSection> formData = new List<IMultipartFormSection>();
746  formData.Add(new MultipartFormFileSection("photo", photoBytes, "photo.jpg", "application/octet-stream"));
747  foreach (var item in textFields)
748  formData.Add(new MultipartFormDataSection(item.Key, item.Value));
749 
750  var webRequest = UnityWebRequest.Post(GetUrl("avatars"), formData);
751 #if UNITY_2017 || UNITY_2018
752  webRequest.chunkedTransfer = false;
753 #endif
754  SetAuthHeaders(webRequest);
755  return webRequest;
756  };
757 
758  Debug.LogFormat("Uploading photo...");
759  AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, request));
760  return request;
761  }
762 
766  public virtual AsyncWebRequest<AvatarData> GetAvatarAsync(string avatarCode)
767  {
768  var r = AvatarJsonRequest<AvatarData>(GetUrl("avatars", avatarCode));
769  r.State = AvatarSdkMgr.Str(Strings.GettingAvatarInfo);
770  return r;
771  }
772 
777  {
778  var r = AvatarJsonArrayRequest<AvatarHaircutData>(avatar.haircuts);
779  r.State = AvatarSdkMgr.Str(Strings.RequestingHaircutInfo);
780  return r;
781  }
782 
787  {
788  var r = AvatarJsonArrayRequest<TextureData>(GetUrl("avatars", avatar.code, "textures"));
789  r.State = AvatarSdkMgr.Str(Strings.RequestingTextureInfo);
790  return r;
791  }
792 
798  public virtual AsyncWebRequest<byte[]> DownloadAvatarThumbnailBytesAsync(AvatarData avatar, int maxW, int maxH)
799  {
800  var param = new Dictionary<string, string> {
801  { "max_w", maxW.ToString () },
802  { "max_h", maxH.ToString () },
803  };
804  var url = UrlWithParams(avatar.thumbnail, param);
805 
806  var r = AvatarDataRequestAsync(url);
807  r.State = AvatarSdkMgr.Str(Strings.DownloadingThumbnail);
808  return r;
809  }
810 
816  public virtual AsyncWebRequest<byte[]> DownloadMeshZipAsync(AvatarData avatar, int levelOfDetails = 0)
817  {
818  var paramsDictionary = new Dictionary<string, string>()
819  {
820  { "lod", levelOfDetails.ToString() }
821  };
822  var url = UrlWithParams(avatar.mesh, paramsDictionary);
823  var r = AvatarDataRequestAsync(url);
824  r.State = AvatarSdkMgr.Str(Strings.DownloadingHeadMesh);
825  return r;
826  }
827 
832  {
833  var r = AvatarDataRequestAsync(GetUrl("avatars", avatar.code, "pointcloud"));
834  r.State = AvatarSdkMgr.Str(Strings.DownloadingHeadMesh);
835  return r;
836  }
837 
842  {
843  var r = AvatarDataRequestAsync(avatar.texture);
844  r.State = AvatarSdkMgr.Str(Strings.DownloadingHeadTexture);
845  return r;
846  }
847 
848  public virtual AsyncWebRequest<DownloadedFileInfo> DownloadAdditionalTextureBytesAsync(AvatarData avatar, string textureName)
849  {
850  var r = FileDataRequestAsync(GetUrl("avatars", avatar.code, "textures", textureName, "file"));
851  r.State = AvatarSdkMgr.Str(Strings.DownloadingHeadTexture);
852  return r;
853  }
854 
859  {
860  var r = AvatarDataRequestAsync(haircut.mesh);
861  r.State = AvatarSdkMgr.Str(Strings.DownloadingHaircutMesh);
862  return r;
863  }
864 
869  {
870  var r = AvatarDataRequestAsync(haircut.texture);
871  r.State = AvatarSdkMgr.Str(Strings.DownloadingHaircutTexture);
872  return r;
873  }
874 
879  {
880  var r = AvatarDataRequestAsync(haircut.preview);
881  r.State = AvatarSdkMgr.Str(Strings.DownloadingHaircutPreview);
882  return r;
883  }
884 
889  {
890  var r = AvatarDataRequestAsync(haircut.pointcloud);
891  r.State = AvatarSdkMgr.Str(Strings.DownloadingHaircutPointCloud);
892  return r;
893  }
894 
900  {
901  string url = string.Format("{0}pointclouds/", avatar.haircuts);
902  var r = AvatarDataRequestAsync(url);
903  r.State = AvatarSdkMgr.Str(Strings.DownloadingAllHaircutPointClouds);
904  return r;
905  }
906 
912  public virtual AsyncWebRequest<byte[]> DownloadBlendshapesZipAsync(AvatarData avatar, BlendshapesFormat format = BlendshapesFormat.BIN, int levelOfDetails = 0)
913  {
914  string url = string.Format("{0}?fmt={1}&lod={2}", avatar.blendshapes, format.BlendshapesFormatToStr(), levelOfDetails);
915  var r = AvatarDataRequestAsync(url);
916  r.State = AvatarSdkMgr.Str(Strings.DownloadingBlendshapes);
917  return r;
918  }
919 
920  public virtual AsyncWebRequest<byte[]> DownloadUmaBonesDataAsync(AvatarData avatar)
921  {
922  var r = AvatarDataRequestAsync(GetUrl("avatars", avatar.code, "bones"));
923  r.State = AvatarSdkMgr.Str(Strings.DownloadingUmaBones);
924  return r;
925  }
926 
927  #endregion
928 
929  #region Actions with avatars on the server (list/update/delete/...)
930 
935  {
936  var r = AvatarJsonPageRequest<AvatarData>(GetUrl("avatars"), pageNumber);
937  r.State = AvatarSdkMgr.Str(Strings.GettingAvatarList);
938  return r;
939  }
940 
944  public virtual AsyncRequest<AvatarData[]> GetAvatarsAsync(int maxItems = int.MaxValue, Dictionary<string, string> filters = null)
945  {
946  var url = GetUrl("avatars");
947  url = UrlWithParams(url, filters);
948  var r = AvatarJsonArrayRequest<AvatarData>(url, maxItems);
949  r.State = AvatarSdkMgr.Str(Strings.GettingAvatarList);
950  return r;
951  }
952 
956  public virtual AsyncWebRequest EditAvatarAsync(AvatarData avatar, string name = null, string description = null)
957  {
958  var request = new AsyncWebRequest(AvatarSdkMgr.Str(Strings.EditingAvatar));
959 
960  byte[] requestBodyData = null;
961  using (var requestBody = new MultipartBody())
962  {
963  requestBody.WriteTextField("name", name);
964  requestBody.WriteTextField("description", description);
965  requestBody.WriteFooter();
966  requestBodyData = requestBody.GetRequestBodyData();
967 
968  Func<UnityWebRequest> webRequestFactory = () =>
969  {
970  var webRequest = UnityWebRequest.Post(avatar.url, " ");
971 #if UNITY_2017 || UNITY_2018
972  webRequest.chunkedTransfer = false;
973 #endif
974  webRequest.method = "PATCH";
975  webRequest.uploadHandler = new UploadHandlerRaw(requestBodyData);
976  webRequest.SetRequestHeader(
977  "Content-Type", string.Format("multipart/form-data; boundary=\"{0}\"", requestBody.Boundary)
978  );
979  SetAuthHeaders(webRequest);
980  return webRequest;
981  };
982 
983  Debug.LogFormat("Uploading photo...");
984  AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, request));
985  return request;
986  }
987  }
988 
993  {
994  var request = new AsyncWebRequest(AvatarSdkMgr.Str(Strings.DeletingAvatarOnServer));
995 
996  Func<UnityWebRequest> webRequestFactory = () =>
997  {
998  var webRequest = UnityWebRequest.Delete(avatar.url);
999  SetAuthHeaders(webRequest);
1000  webRequest.downloadHandler = new DownloadHandlerBuffer();
1001  return webRequest;
1002  };
1003  AvatarSdkMgr.SpawnCoroutine(AwaitWebRequest(webRequestFactory, request));
1004  return request;
1005  }
1006 
1007  #endregion
1008 
1009  #region Parameters
1010 
1015  {
1016  string subsetStr = "available";
1017  if (parametersSubset == ComputationParametersSubset.DEFAULT)
1018  subsetStr = "default";
1019 
1020  var tratis = pipelineType.Traits();
1021  var url = GetUrl("parameters", subsetStr, tratis.PipelineTypeName);
1022  url = UrlWithParams(url, "pipeline_subtype", tratis.PipelineSubtypeName);
1023  var request = new AsyncWebRequest<string>(Strings.GettingParametersList);
1024  AvatarSdkMgr.SpawnCoroutine(AwaitStringDataAsync(() => HttpGet(url), request));
1025  return request;
1026  }
1027 
1028  public virtual AsyncRequest<bool> CheckIfPipelineSupportedAsync(PipelineType pipelineType)
1029  {
1030  var tratis = pipelineType.Traits();
1031  string url = GetUrl("parameters", "available", tratis.PipelineTypeName);
1032  url = UrlWithParams(url, "pipeline_subtype", tratis.PipelineSubtypeName);
1033  AsyncRequest<bool> request = new AsyncRequest<bool>(Strings.PerformingRequest);
1034  AvatarSdkMgr.SpawnCoroutine(CheckIfPipelineSupportedFunc(url, request));
1035  return request;
1036  }
1037  #endregion
1038 
1039  #region Higher-level API, composite requests
1040 
1044  private IEnumerator AwaitAvatarCalculationsLoop(AvatarData avatar, AsyncRequest<AvatarData> request)
1045  {
1046  while (!Strings.FinalStates.Contains(avatar.status))
1047  {
1048  yield return new WaitForSecondsRealtime(4);
1049  var avatarStatusRequest = GetAvatarAsync(avatar.code);
1050  yield return avatarStatusRequest;
1051 
1052  if (avatarStatusRequest.Status.Value == (long)StatusCode.Code.NOT_FOUND)
1053  {
1054  Debug.LogWarning("404 error most likely means that avatar was deleted from the server");
1055  request.SetError(string.Format("Avatar status response: {0}", avatarStatusRequest.ErrorMessage));
1056  yield break;
1057  }
1058 
1059  if (avatarStatusRequest.Status.Value == (long)StatusCode.Code.TOO_MANY_REQUESTS_THROTTLING)
1060  {
1061  Debug.LogWarning("Too many requests!");
1062  yield return new WaitForSecondsRealtime(avatarStatusRequest.RetryPeriod.Value);
1063  }
1064 
1065  if (avatarStatusRequest.IsError)
1066  {
1067  Debug.LogWarningFormat("Status polling error: {0}", avatarStatusRequest.ErrorMessage);
1068  // Most likely this is a temporary issue. Keep polling.
1069  continue;
1070  }
1071 
1072  avatar = avatarStatusRequest.Result;
1073  Debug.LogFormat("Status: {0}, progress: {1}%", avatar.status, avatar.progress);
1074  request.State = AvatarSdkMgr.Str(avatar.status);
1075 
1076  if (avatar.status == Strings.Computing)
1077  request.Progress = (float)avatar.progress / 100;
1078  }
1079 
1080  if (Strings.GoodFinalStates.Contains(avatar.status))
1081  {
1082  request.Result = avatar;
1083  request.IsDone = true;
1084  }
1085  else
1086  {
1087  request.SetError(string.Format("Avatar calculations failed, status: {0}", avatar.status));
1088  }
1089  }
1090 
1097  {
1098  var request = new AsyncRequest<AvatarData>(AvatarSdkMgr.Str(Strings.StartingCalculations));
1099  AvatarSdkMgr.SpawnCoroutine(AwaitAvatarCalculationsLoop(avatar, request));
1100  return request;
1101  }
1102 
1103  #endregion
1104  }
1105 }
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:776
void SetAuthHeaders(UnityWebRequest request)
Adds auth header to UnityWebRequest.
Definition: Connection.cs:97
virtual Dictionary< string, string > GetAuthHeaders()
Dictionary with required auth HTTP headers.
Definition: Connection.cs:81
virtual AsyncWebRequest< AvatarData > GetAvatarAsync(string avatarCode)
Get avatar information by code.
Definition: Connection.cs:766
virtual string UrlWithParams(string url, string param, string value)
Simple overload for a single-parameter use case.
Definition: Connection.cs:75
virtual AsyncRequest AuthorizeAsync()
Authorize this session using the credentials loaded from encrypted binary resource.
Definition: Connection.cs:629
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:355
virtual IEnumerator AwaitFileDataAsync(Func< UnityWebRequest > webRequestFactory, AsyncWebRequest< DownloadedFileInfo > request)
Call AwaitWebRequestFunc for downloading file data and its name.
Definition: Connection.cs:437
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:544
virtual AsyncWebRequest< byte[]> DownloadMeshZipAsync(AvatarData avatar, int levelOfDetails=0)
Download mesh zip file into memory.
Definition: Connection.cs:816
virtual AsyncWebRequest< string > GetModelInfoAsync(string avatarCode)
Request to get model info json
Definition: Connection.cs:554
virtual AsyncWebRequest< byte[]> DownloadHaircutPreviewBytesAsync(AvatarHaircutData haircut)
Download haircut preview into memory.
Definition: Connection.cs:878
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:718
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:429
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:831
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:534
virtual AsyncWebRequest< byte[]> DownloadBlendshapesZipAsync(AvatarData avatar, BlendshapesFormat format=BlendshapesFormat.BIN, int levelOfDetails=0)
Downloads zip archive with all the blendshapes.
Definition: Connection.cs:912
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:798
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:956
virtual AsyncWebRequest< TextureData[]> GetTexturesAsync(AvatarData avatar)
Get list of all textures for avatar.
Definition: Connection.cs:786
UnityWebRequest HttpGet(string url)
Helper factory method.
Definition: Connection.cs:108
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:944
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:899
virtual AsyncWebRequest< byte[]> DownloadTextureBytesAsync(AvatarData avatar)
Download main texture into memory. Can be used right away to create Unity texture.
Definition: Connection.cs:841
Async request for web requests.
virtual AsyncWebRequest DeleteAvatarAsync(AvatarData avatar)
Delete avatar record on the server (does not delete local files).
Definition: Connection.cs:992
virtual AsyncWebRequest< Page< AvatarData > > GetAvatarsPageAsync(int pageNumber)
Get a particular page in the list of avatars.
Definition: Connection.cs:934
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:1014
virtual AsyncWebRequest< byte[]> DownloadHaircutMeshZipAsync(AvatarHaircutData haircut)
Downloads haircut zip file into memory.
Definition: Connection.cs:858
virtual AsyncWebRequest< byte[]> DownloadHaircutTextureBytesAsync(AvatarHaircutData haircut)
Download haircut texture into memory. Can be used right away to create Unity texture.
Definition: Connection.cs:868
virtual AsyncWebRequest< byte[]> DownloadHaircutPointCloudZipAsync(AvatarHaircutData haircut)
Downloads the haircut point cloud zip into memory.
Definition: Connection.cs:888
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:641
virtual IEnumerator AwaitDataAsync(Func< UnityWebRequest > webRequestFactory, AsyncWebRequest< byte[]> request)
Call AwaitWebRequestFunc for binary data.
Definition: Connection.cs:421
virtual AsyncWebRequest< Player > RegisterPlayerAsync(string comment="")
Register unique player UID that is used later to sign the requests.
Definition: Connection.cs:695
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:1096