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