Avatar SDK  1.8.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;
12 using System;
13 using System.Collections;
14 using System.Collections.Generic;
15 using System.Text;
16 using System.Text.RegularExpressions;
17 using UnityEngine;
18 using UnityEngine.Networking;
19 
20 namespace ItSeez3D.AvatarSdk.Cloud
21 {
22  public class DownloadedFileInfo
23  {
24  public byte[] bytes;
25  public string fileName;
26  }
27 
28  public class Connection
29  {
31 
32  #region Connection data
33 
37  private string rootServerUrl = NetworkUtils.rootUrl;
38  public string RootServerUrl {
39  get { return rootServerUrl; }
40  set { rootServerUrl = value; }
41  }
42 
43  // access data
44  private string tokenType = null, accessToken = null;
45 
51  private string playerUID = null;
52 
53  #endregion
54 
55  #region Helpers
56 
62  public virtual string GetUrl (params string[] urlTokens)
63  {
64  return string.Format ("{0}/{1}/", RootServerUrl, string.Join ("/", urlTokens));
65  }
66 
70  public virtual string UrlWithParams(string url, Dictionary<string, string> param)
71  {
72  if (param == null || param.Count == 0)
73  return url;
74 
75  var paramTokens = new List<string> ();
76  foreach (var item in param) {
77 #if UNITY_2018_3_OR_NEWER
78  var token = string.Format("{0}={1}", UnityWebRequest.EscapeURL(item.Key), UnityWebRequest.EscapeURL(item.Value));
79 #else
80  var token = string.Format ("{0}={1}", WWW.EscapeURL (item.Key), WWW.EscapeURL (item.Value));
81 #endif
82  paramTokens.Add (token);
83  }
84 
85  return string.Format ("{0}?{1}", url, string.Join ("&", paramTokens.ToArray()));
86  }
87 
91  public virtual string UrlWithParams (string url, string param, string value)
92  {
93  return UrlWithParams (url, new Dictionary<string, string> { { param, value } });
94  }
95 
97  public virtual string GetAuthUrl ()
98  {
99  return GetUrl ("o", "token");
100  }
101 
103  public virtual Dictionary<string, string> GetAuthHeaders ()
104  {
105  var headers = new Dictionary<string, string> () {
106  { "Authorization", string.Format ("{0} {1}", tokenType, accessToken) },
107 #if !UNITY_WEBGL
108  { "X-Unity-Plugin-Version", CoreTools.SdkVersion.ToString () },
109 #endif
110  };
111  if (!string.IsNullOrEmpty (playerUID))
112  headers.Add ("X-PlayerUID", playerUID);
113  return headers;
114  }
115 
119  protected void SetAuthHeaders (UnityWebRequest request)
120  {
121  var headers = GetAuthHeaders ();
122  foreach (var h in headers)
123  request.SetRequestHeader (h.Key, h.Value);
124  }
125 
130  protected UnityWebRequest HttpGet (string url)
131  {
132  if (string.IsNullOrEmpty (url))
133  Debug.LogError ("Provided empty url!");
134  var r = UnityWebRequest.Get (url);
135  SetAuthHeaders (r);
136  return r;
137  }
138 
139  protected UnityWebRequest HttpPost(string url, Dictionary<string, string> form)
140  {
141  var r = UnityWebRequest.Post(url, form);
142 #if UNITY_2017 || UNITY_2018
143  r.chunkedTransfer = false; // chunked transfer causes problems with UWSGI
144 #endif
145  return r;
146  }
147 #endregion
148 
149 #region Generic request processing
150 
151  private static void PrintWebRequestInfo(UnityWebRequest webRequest, double requestDurationInSec)
152  {
153  StringBuilder sb = new StringBuilder();
154  sb.AppendFormat("{0}: {1}", webRequest.method, webRequest.url).AppendLine();
155  if (webRequest.uploadHandler != null)
156  {
157  sb.AppendFormat("Content type: {0}", webRequest.uploadHandler.contentType).AppendLine();
158  sb.AppendFormat("Uploaded body: {0}", Encoding.UTF8.GetString(webRequest.uploadHandler.data)).AppendLine();
159  }
160  sb.AppendFormat("Uploaded bytes: {0}", webRequest.uploadedBytes).AppendLine();
161  sb.AppendFormat("Response code: {0}", webRequest.responseCode).AppendLine();
162  if (webRequest.downloadHandler != null && webRequest.downloadHandler.text.Length < 5000)
163  {
164  sb.AppendFormat("Response body: {0}", webRequest.downloadHandler.text).AppendLine();
165  }
166  sb.AppendFormat("Downloaded bytes: {0}", webRequest.downloadedBytes).AppendLine();
167  sb.AppendFormat("Request duration: {0}", requestDurationInSec);
168  Debug.LogFormat("{0}", sb.ToString());
169  }
170 
174  private static IEnumerator AwaitAndTrackProgress<T> (UnityWebRequest webRequest, AsyncWebRequest<T> request)
175  {
176  DateTime sendRequestTime = DateTime.Now;
177  webRequest.SendWebRequest();
178  do {
179  yield return null;
180 
181  switch (request.ProgressTracking) {
182  case TrackProgress.DOWNLOAD:
183  request.Progress = webRequest.downloadProgress;
184  break;
185  case TrackProgress.UPLOAD:
186  request.Progress = webRequest.uploadProgress;
187  break;
188  }
189 
190  request.BytesDownloaded = webRequest.downloadedBytes;
191  request.BytesUploaded = webRequest.uploadedBytes;
192  } while(!webRequest.isDone);
193 
194  PrintWebRequestInfo(webRequest, (DateTime.Now - sendRequestTime).TotalSeconds);
195  }
196 
201  private static bool IsGoodResponse (UnityWebRequest webRequest, out StatusCode status, out string error)
202  {
203  error = string.Empty;
204 
205  try {
206  status = new StatusCode (webRequest.responseCode);
207 
208  if (webRequest.isNetworkError) { // apparently the API has changed in 2017
209  error = webRequest.error;
210  return false;
211  }
212 
213  if (status.IsBad) {
214  error = string.Format ("Bad response code. Msg: {0}", webRequest.downloadHandler.text);
215  return false;
216  }
217 
218  if (!webRequest.downloadHandler.isDone)
219  {
220  error = "Could not download response";
221  return false;
222  }
223  } catch (Exception ex) {
224  Debug.LogException (ex);
225  status = new StatusCode ();
226  error = string.Format ("Exception while checking response: {0}", ex.Message);
227  return false;
228  }
229 
230  return true;
231  }
232 
241  private IEnumerator AwaitWebRequestFunc<T> (
242  Func<UnityWebRequest> webRequestFactory,
243  AsyncWebRequest<T> request,
244  Func<UnityWebRequest, T> parseDataFunc
245  )
246  {
247  UnityWebRequest webRequest = null;
248 
249  StatusCode status = new StatusCode ();
250  string error = string.Empty;
251 
252  int numAttempts = 2, lastAttempt = numAttempts - 1;
253  bool goodResponse = false;
254  for (int attempt = 0; attempt < numAttempts; ++attempt) {
255  webRequest = webRequestFactory ();
256  yield return AwaitAndTrackProgress (webRequest, request);
257 
258  if (goodResponse = IsGoodResponse (webRequest, out status, out error))
259  break;
260 
261  // all API requests have Authorization header, except for authorization requests
262  bool isAuthRequest = webRequest.GetRequestHeader ("Authorization") == null;
263 
264  Debug.LogWarningFormat ("Server error: {0}, request: {1}", error, webRequest.url);
265  if (status.Value != (long)StatusCode.Code.UNAUTHORIZED || isAuthRequest) {
266  // cannot recover, request has failed
267  break;
268  }
269 
270  if (attempt == lastAttempt) {
271  Debug.LogError ("No more retries left");
272  break;
273  }
274 
275  Debug.LogWarning ("Auth issue, let's try one more time after refreshing access token");
276  yield return AuthorizeAsync ();
277  }
278 
279  if (!goodResponse) {
280  Debug.LogErrorFormat ("Could not send the request, status: {0}, error: {1}", status, error);
281  request.Status = status;
282  request.SetError (error);
283  yield break;
284  }
285 
286  T data = default(T);
287  try {
288  data = parseDataFunc (webRequest);
289  } catch (Exception ex) {
290  Debug.LogException (ex);
291  }
292 
293  if (data == null) {
294  request.SetError ("Could not parse request data");
295  yield break;
296  } else {
297  request.Result = data;
298  }
299 
300  request.IsDone = true;
301  }
302 
311  private IEnumerator CheckIfPipelineSupportedFunc(string url, AsyncRequest<bool> request)
312  {
313  DateTime sendRequestTime = DateTime.Now;
314  UnityWebRequest webRequest = HttpGet(url);
315  yield return webRequest.SendWebRequest();
316 
317  while (!webRequest.isDone)
318  yield return null;
319 
320  PrintWebRequestInfo(webRequest, (DateTime.Now - sendRequestTime).TotalSeconds);
321 
322  if (!webRequest.isDone)
323  Debug.LogErrorFormat("Request isn't completed");
324 
325  if (webRequest.isNetworkError)
326  {
327  Debug.LogErrorFormat("Could not send the request, status: {0}, error: {1}", webRequest.responseCode, webRequest.error);
328  request.SetError(webRequest.downloadHandler.text);
329  yield break;
330  }
331 
332  if (webRequest.isHttpError)
333  {
334  if (webRequest.responseCode == 400)
335  {
336  request.Result = false;
337  request.IsDone = true;
338  }
339  else
340  {
341  Debug.LogErrorFormat("Got error in response, status: {0}, error: {1}", webRequest.responseCode, webRequest.downloadHandler.text);
342  request.SetError(webRequest.downloadHandler.text);
343  yield break;
344  }
345  }
346  else
347  {
348  request.Result = true;
349  request.IsDone = true;
350 
351  }
352  }
353 
357  public virtual IEnumerator AwaitWebRequest (Func<UnityWebRequest> webRequestFactory, AsyncWebRequest request)
358  {
359  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => new object ());
360  }
361 
365  public virtual IEnumerator AwaitJsonWebRequest<DataType> (
366  Func<UnityWebRequest> webRequestFactory,
368  {
369  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => {
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  // 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  var regex = new Regex (@".*<(?<link>.+)>.+rel=""(?<kind>.*)""");
392  var tokens = paginationHeader.Split (',');
393  foreach (var token in tokens) {
394  var match = regex.Match (token);
395  if (!match.Success)
396  continue;
397 
398  string link = match.Groups ["link"].Value, kind = match.Groups ["kind"].Value;
399  if (string.IsNullOrEmpty (link) || string.IsNullOrEmpty (kind))
400  continue;
401 
402  if (kind == "first")
403  page.firstPageUrl = link;
404  else if (kind == "next")
405  page.nextPageUrl = link;
406  else if (kind == "prev")
407  page.prevPageUrl = link;
408  else if (kind == "last")
409  page.lastPageUrl = link;
410  }
411  }
412  return page;
413  });
414  }
415 
419  public virtual IEnumerator AwaitDataAsync (Func<UnityWebRequest> webRequestFactory, AsyncWebRequest<byte[]> request)
420  {
421  yield return AwaitWebRequestFunc (webRequestFactory, request, (r) => r.downloadHandler.data);
422  }
423 
427  public virtual IEnumerator AwaitStringDataAsync(Func<UnityWebRequest> webRequestFactory, AsyncWebRequest<string> request)
428  {
429  yield return AwaitWebRequestFunc(webRequestFactory, request, (r) => r.downloadHandler.text);
430  }
431 
435  public virtual IEnumerator AwaitFileDataAsync(Func<UnityWebRequest> webRequestFactory, AsyncWebRequest<DownloadedFileInfo> request)
436  {
437  yield return AwaitWebRequestFunc(webRequestFactory, request, (r) =>
438  {
439  DownloadedFileInfo fileInfo = new DownloadedFileInfo();
440  fileInfo.bytes = r.downloadHandler.data;
441  var headers = r.GetResponseHeaders();
442  string contentDispositionHeader = "Content-Disposition";
443  string filenameTag = "filename";
444  if (headers.ContainsKey(contentDispositionHeader))
445  {
446  string contentDispositionValue = headers[contentDispositionHeader];
447  int filenamePos = contentDispositionValue.IndexOf(filenameTag);
448  if (filenamePos >= 0)
449  {
450  fileInfo.fileName = contentDispositionValue.Substring(filenamePos + filenameTag.Length + 1);
451  }
452  else
453  Debug.LogErrorFormat("Filename isn't found in the Content-Disposition header: {0}", contentDispositionValue);
454  }
455  return fileInfo;
456  });
457  }
458 
463  public virtual AsyncWebRequest<DataType> AvatarJsonRequest<DataType> (string url)
464  {
465  var request = new AsyncWebRequest<DataType> ();
466  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (() => HttpGet (url), request));
467  return request;
468  }
469 
474  public virtual AsyncWebRequest<Page<T>> AvatarJsonPageRequest<T> (string url)
475  {
476  var request = new AsyncWebRequest<Page<T>> ();
477  AvatarSdkMgr.SpawnCoroutine (AwaitJsonPageWebRequest (() => HttpGet (url), request));
478  return request;
479  }
480 
485  public virtual AsyncWebRequest<Page<T>> AvatarJsonPageRequest<T> (string baseUrl, int pageNumber)
486  {
487  return AvatarJsonPageRequest<T> (string.Format ("{0}?page={1}", baseUrl, pageNumber));
488  }
489 
493  public virtual IEnumerator AwaitMultiplePages<T> (string url, AsyncRequest<T[]> request, int maxItems = int.MaxValue)
494  {
495  List<T> items = new List<T> ();
496  do {
497  var pageRequest = AvatarJsonPageRequest<T> (url);
498  yield return pageRequest;
499  if (pageRequest.IsError) {
500  request.SetError (string.Format ("Page request failed. Error: {0}", pageRequest.ErrorMessage));
501  yield break;
502  }
503 
504  // Debug.LogFormat ("Successfully loaded page {0}", url);
505  var page = pageRequest.Result;
506 
507  for (int i = 0; i < page.content.Length && items.Count < maxItems; ++i)
508  items.Add (page.content[i]);
509 
510  url = page.nextPageUrl;
511  } while (items.Count < maxItems && !string.IsNullOrEmpty (url));
512 
513  request.Result = items.ToArray ();
514  request.IsDone = true;
515  }
516 
520  public virtual AsyncWebRequest<DataType[]> AvatarJsonArrayRequest<DataType> (string url, int maxItems = int.MaxValue)
521  {
522  var request = new AsyncWebRequest<DataType[]> ();
523  AvatarSdkMgr.SpawnCoroutine (AwaitMultiplePages (url, request, maxItems));
524  return request;
525  }
526 
531  {
532  var request = new AsyncWebRequest<byte[]> ();
533  AvatarSdkMgr.SpawnCoroutine (AwaitDataAsync (() => HttpGet (url), request));
534  return request;
535  }
536 
541  {
542  var request = new AsyncWebRequest<DownloadedFileInfo>();
543  AvatarSdkMgr.SpawnCoroutine(AwaitFileDataAsync(() => HttpGet(url), request));
544  return request;
545  }
546 
550  public virtual AsyncWebRequest<string> GetModelInfoAsync(string avatarCode)
551  {
552  string url = GetUrl("avatars", avatarCode, "model_info");
553  var request = new AsyncWebRequest<string>(Strings.RequestingModelInfo);
554  AvatarSdkMgr.SpawnCoroutine(AwaitStringDataAsync(() => HttpGet(url), request));
555  return request;
556  }
557 
558 #endregion
559 
560 #region Auth functions
561 
565  public virtual bool IsAuthorized { get { return !string.IsNullOrEmpty (accessToken); } }
566 
567  public virtual string TokenType { get { return tokenType; } }
568 
569  public virtual string AccessToken { get { return accessToken; } }
570 
574  public virtual string PlayerUID {
575  get { return playerUID; }
576  set { playerUID = value; }
577  }
578 
582  private IEnumerator Authorize (AsyncRequest request)
583  {
584  var accessCredentials = AuthUtils.LoadCredentials ();
585  if (accessCredentials == null || string.IsNullOrEmpty (accessCredentials.clientSecret)) {
586  request.SetError ("Could not find API keys! Please provide valid credentials via Window->ItSeez3D Avatar SDK");
587  yield break;
588  }
589 
590  var authRequest = AuthorizeClientCredentialsGrantTypeAsync (accessCredentials);
591  yield return request.AwaitSubrequest (authRequest, 0.5f);
592  if (request.IsError)
593  yield break;
594 
595  tokenType = authRequest.Result.token_type;
596  accessToken = authRequest.Result.access_token;
597  Debug.LogFormat ("Successful authentication!");
598 
599  // guarantees we re-register a Player if clientId changes
600  var playerIdentifier = string.Format ("player_uid_{0}", accessCredentials.clientId.Substring (0, accessCredentials.clientId.Length / 3));
601 
602  if (string.IsNullOrEmpty (playerUID))
603  playerUID = AvatarSdkMgr.Storage ().LoadPlayerUID (playerIdentifier);
604 
605  if (string.IsNullOrEmpty (playerUID)) {
606  Debug.Log ("Registering new player UID");
607  var playerRequest = RegisterPlayerAsync ();
608  yield return request.AwaitSubrequest (playerRequest, 1);
609  if (request.IsError)
610  yield break;
611 
612  playerUID = playerRequest.Result.code;
613  AvatarSdkMgr.Storage ().StorePlayerUID (playerIdentifier, playerUID);
614  }
615 
616  request.IsDone = true;
617  }
618 
622  public virtual AsyncRequest AuthorizeAsync ()
623  {
624  var request = new AsyncRequest (AvatarSdkMgr.Str (Strings.Authentication));
625  AvatarSdkMgr.SpawnCoroutine (Authorize (request));
626  return request;
627  }
628 
634  public virtual void AuthorizeWithCredentials (string tokenType, string accessToken, string playerUID)
635  {
636  this.tokenType = tokenType;
637  this.accessToken = accessToken;
638  this.playerUID = playerUID;
639  }
640 
646  public virtual UnityWebRequest GenerateAuthRequest(AccessCredentials credentials)
647  {
648  var form = new Dictionary<string, string>() {
649  { "grant_type", "client_credentials" },
650  { "client_id", credentials.clientId },
651  { "client_secret", credentials.clientSecret },
652  };
653  return HttpPost(GetAuthUrl(), form);
654  }
655 
659  private AsyncWebRequest<AccessData> AuthorizeClientCredentialsGrantTypeAsync (AccessCredentials credentials)
660  {
661  var request = new AsyncWebRequest<AccessData> (AvatarSdkMgr.Str (Strings.RequestingApiToken));
662  Func<UnityWebRequest> webRequestFactory = () => GenerateAuthRequest(credentials);
663  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
664  return request;
665  }
666 
670  private AsyncWebRequest<AccessData> AuthorizePasswordGrantTypeAsync (
671  string clientId,
672  string clientSecret,
673  string username,
674  string password
675  )
676  {
677  Debug.LogWarning ("Don't use this auth method in production, use other grant types!");
678  var request = new AsyncWebRequest<AccessData> (AvatarSdkMgr.Str (Strings.RequestingApiToken));
679 
680  if (string.IsNullOrEmpty (username) || string.IsNullOrEmpty (password) || string.IsNullOrEmpty (clientId)) {
681  request.SetError ("itSeez3D credentials not provided");
682  Debug.LogError (request.ErrorMessage);
683  return request;
684  }
685 
686  var form = new Dictionary<string,string> () {
687  { "grant_type", "password" },
688  { "username", username },
689  { "password", password },
690  { "client_id", clientId },
691  { "client_secret", clientSecret },
692  };
693  Func<UnityWebRequest> webRequestFactory = () => HttpPost (GetUrl ("o", "token"), form);
694  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
695  return request;
696  }
697 
702  public virtual AsyncWebRequest<Player> RegisterPlayerAsync (string comment = "")
703  {
704  var r = new AsyncWebRequest<Player> (AvatarSdkMgr.Str (Strings.RegisteringPlayerID));
705  var form = new Dictionary<string,string> () {
706  { "comment", comment },
707  };
708  Func<UnityWebRequest> webRequestFactory = () => {
709  var webRequest = HttpPost (GetUrl ("players"), form);
710  SetAuthHeaders (webRequest);
711  return webRequest;
712  };
713  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, r));
714  return r;
715  }
716 
717 #endregion
718 
719 #region Creating/awaiting/downloading an avatar
720 
725  string name, string description, byte[] photoBytes, bool forcePowerOfTwoTexture = false,
726  PipelineType pipeline = PipelineType.FACE, ComputationParameters computationParameters = null
727  )
728  {
729  var request = new AsyncWebRequest<AvatarData> (AvatarSdkMgr.Str (Strings.UploadingPhoto), TrackProgress.UPLOAD);
730 
731  var textFields = new Dictionary<string, string>
732  {
733  {"name", name },
734  {"preserve_original_texture", (!forcePowerOfTwoTexture).ToString() },
735  {"pipeline", pipeline.GetPipelineTypeName() },
736  {"pipeline_subtype", pipeline.GetPipelineSubtypeName()}
737  };
738 
739  if (!string.IsNullOrEmpty(description))
740  textFields.Add("description", description);
741 
742  if (computationParameters != null)
743  {
744  string parametersJson = parametersController.GetCalculationParametersJson(computationParameters);
745  Debug.LogFormat("Computation parameters json: {0}", parametersJson);
746  textFields.Add("parameters", parametersJson);
747  }
748 
749  Func<UnityWebRequest> webRequestFactory = () =>
750  {
751  List<IMultipartFormSection> formData = new List<IMultipartFormSection>();
752  formData.Add(new MultipartFormFileSection("photo", photoBytes, "photo.jpg", "application/octet-stream"));
753  foreach (var item in textFields)
754  formData.Add(new MultipartFormDataSection(item.Key, item.Value));
755 
756  var webRequest = UnityWebRequest.Post(GetUrl("avatars"), formData);
757 #if UNITY_2017 || UNITY_2018
758  webRequest.chunkedTransfer = false;
759 #endif
760  SetAuthHeaders(webRequest);
761  return webRequest;
762  };
763 
764  Debug.LogFormat("Uploading photo...");
765  AvatarSdkMgr.SpawnCoroutine(AwaitJsonWebRequest(webRequestFactory, request));
766  return request;
767  }
768 
772  public virtual AsyncWebRequest<AvatarData> GetAvatarAsync (string avatarCode)
773  {
774  var r = AvatarJsonRequest<AvatarData> (GetUrl ("avatars", avatarCode));
775  r.State = AvatarSdkMgr.Str (Strings.GettingAvatarInfo);
776  return r;
777  }
778 
783  {
784  var r = AvatarJsonArrayRequest<AvatarHaircutData> (avatar.haircuts);
785  r.State = AvatarSdkMgr.Str (Strings.RequestingHaircutInfo);
786  return r;
787  }
788 
793  {
794  var r = AvatarJsonArrayRequest<TextureData> (GetUrl ("avatars", avatar.code, "textures"));
795  r.State = AvatarSdkMgr.Str (Strings.RequestingTextureInfo);
796  return r;
797  }
798 
804  public virtual AsyncWebRequest<byte[]> DownloadAvatarThumbnailBytesAsync (AvatarData avatar, int maxW, int maxH)
805  {
806  var param = new Dictionary<string, string> {
807  { "max_w", maxW.ToString () },
808  { "max_h", maxH.ToString () },
809  };
810  var url = UrlWithParams (avatar.thumbnail, param);
811 
812  var r = AvatarDataRequestAsync (url);
813  r.State = AvatarSdkMgr.Str (Strings.DownloadingThumbnail);
814  return r;
815  }
816 
822  public virtual AsyncWebRequest<byte[]> DownloadMeshZipAsync (AvatarData avatar, int levelOfDetails = 0)
823  {
824  var url = UrlWithParams (avatar.mesh, "lod", levelOfDetails.ToString ());
825  var r = AvatarDataRequestAsync (url);
826  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadMesh);
827  return r;
828  }
829 
834  {
835  var r = AvatarDataRequestAsync (GetUrl ("avatars", avatar.code, "pointcloud"));
836  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadMesh);
837  return r;
838  }
839 
844  {
845  var r = AvatarDataRequestAsync (avatar.texture);
846  r.State = AvatarSdkMgr.Str (Strings.DownloadingHeadTexture);
847  return r;
848  }
849 
850  public virtual AsyncWebRequest<DownloadedFileInfo> DownloadAdditionalTextureBytesAsync(AvatarData avatar, string textureName)
851  {
852  var r = FileDataRequestAsync(GetUrl("avatars", avatar.code, "textures", textureName, "file"));
853  r.State = AvatarSdkMgr.Str(Strings.DownloadingHeadTexture);
854  return r;
855  }
856 
861  {
862  var r = AvatarDataRequestAsync (haircut.mesh);
863  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutMesh);
864  return r;
865  }
866 
871  {
872  var r = AvatarDataRequestAsync (haircut.texture);
873  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutTexture);
874  return r;
875  }
876 
881  {
882  var r = AvatarDataRequestAsync(haircut.preview);
883  r.State = AvatarSdkMgr.Str(Strings.DownloadingHaircutPreview);
884  return r;
885  }
886 
891  {
892  var r = AvatarDataRequestAsync (haircut.pointcloud);
893  r.State = AvatarSdkMgr.Str (Strings.DownloadingHaircutPointCloud);
894  return r;
895  }
896 
902  {
903  string url = string.Format ("{0}pointclouds/", avatar.haircuts);
904  var r = AvatarDataRequestAsync (url);
905  r.State = AvatarSdkMgr.Str (Strings.DownloadingAllHaircutPointClouds);
906  return r;
907  }
908 
914  public virtual AsyncWebRequest<byte[]> DownloadBlendshapesZipAsync (AvatarData avatar, BlendshapesFormat format = BlendshapesFormat.BIN, int levelOfDetails = 0)
915  {
916  var fmtToStr = new Dictionary<BlendshapesFormat, string> {
917  { BlendshapesFormat.BIN, "bin" },
918  { BlendshapesFormat.FBX, "fbx" },
919  { BlendshapesFormat.PLY, "ply" },
920  };
921 
922  string url = string.Format ("{0}?fmt={1}&lod={2}", avatar.blendshapes, fmtToStr [format], levelOfDetails);
923  var r = AvatarDataRequestAsync (url);
924  r.State = AvatarSdkMgr.Str (Strings.DownloadingBlendshapes);
925  return r;
926  }
927 
928 #endregion
929 
930 #region Actions with avatars on the server (list/update/delete/...)
931 
935  public virtual AsyncWebRequest<Page<AvatarData>> GetAvatarsPageAsync (int pageNumber)
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  requestBody.WriteTextField ("name", name);
964  requestBody.WriteTextField ("description", description);
965  requestBody.WriteFooter ();
966  requestBodyData = requestBody.GetRequestBodyData ();
967 
968  Func<UnityWebRequest> webRequestFactory = () => {
969  var webRequest = UnityWebRequest.Post (avatar.url, " ");
970 #if UNITY_2017 || UNITY_2018
971  webRequest.chunkedTransfer = false;
972 #endif
973  webRequest.method = "PATCH";
974  webRequest.uploadHandler = new UploadHandlerRaw (requestBodyData);
975  webRequest.SetRequestHeader (
976  "Content-Type", string.Format ("multipart/form-data; boundary=\"{0}\"", requestBody.Boundary)
977  );
978  SetAuthHeaders (webRequest);
979  return webRequest;
980  };
981 
982  Debug.LogFormat ("Uploading photo...");
983  AvatarSdkMgr.SpawnCoroutine (AwaitJsonWebRequest (webRequestFactory, request));
984  return request;
985  }
986  }
987 
992  {
993  var request = new AsyncWebRequest (AvatarSdkMgr.Str (Strings.DeletingAvatarOnServer));
994 
995  Func<UnityWebRequest> webRequestFactory = () => {
996  var webRequest = UnityWebRequest.Delete (avatar.url);
997  SetAuthHeaders (webRequest);
998  webRequest.downloadHandler = new DownloadHandlerBuffer ();
999  return webRequest;
1000  };
1001  AvatarSdkMgr.SpawnCoroutine (AwaitWebRequest (webRequestFactory, request));
1002  return request;
1003  }
1004 
1005 #endregion
1006 
1007 #region Parameters
1008 
1013  {
1014  string subsetStr = "available";
1015  if (parametersSubset == ComputationParametersSubset.DEFAULT)
1016  subsetStr = "default";
1017 
1018  var url = GetUrl("parameters", subsetStr, pipelineType.GetPipelineTypeName());
1019  url = UrlWithParams(url, "pipeline_subtype", pipelineType.GetPipelineSubtypeName());
1020  var request = new AsyncWebRequest<string>(Strings.GettingParametersList);
1021  AvatarSdkMgr.SpawnCoroutine(AwaitStringDataAsync(() => HttpGet(url), request));
1022  return request;
1023  }
1024 
1025  public virtual AsyncRequest<bool> CheckIfPipelineSupportedAsync(PipelineType pipelineType)
1026  {
1027  string url = GetUrl("parameters", "available", pipelineType.GetPipelineTypeName());
1028  url = UrlWithParams(url, "pipeline_subtype", pipelineType.GetPipelineSubtypeName());
1029  AsyncRequest<bool> request = new AsyncRequest<bool>(Strings.PerformingRequest);
1030  AvatarSdkMgr.SpawnCoroutine(CheckIfPipelineSupportedFunc(url, request));
1031  return request;
1032  }
1033 #endregion
1034 
1035 #region Higher-level API, composite requests
1036 
1040  private IEnumerator AwaitAvatarCalculationsLoop (AvatarData avatar, AsyncRequest<AvatarData> request)
1041  {
1042  while (!Strings.FinalStates.Contains (avatar.status)) {
1043  yield return new WaitForSecondsRealtime(4);
1044  var avatarStatusRequest = GetAvatarAsync (avatar.code);
1045  yield return avatarStatusRequest;
1046 
1047  if (avatarStatusRequest.Status.Value == (long)StatusCode.Code.NOT_FOUND) {
1048  Debug.LogWarning ("404 error most likely means that avatar was deleted from the server");
1049  request.SetError (string.Format ("Avatar status response: {0}", avatarStatusRequest.ErrorMessage));
1050  yield break;
1051  }
1052 
1053  if (avatarStatusRequest.Status.Value == (long)StatusCode.Code.TOO_MANY_REQUESTS_THROTTLING) {
1054  Debug.LogWarning ("Too many requests!");
1055  yield return new WaitForSecondsRealtime (4);
1056  }
1057 
1058  if (avatarStatusRequest.IsError) {
1059  Debug.LogWarningFormat ("Status polling error: {0}", avatarStatusRequest.ErrorMessage);
1060  // Most likely this is a temporary issue. Keep polling.
1061  continue;
1062  }
1063 
1064  avatar = avatarStatusRequest.Result;
1065  Debug.LogFormat ("Status: {0}, progress: {1}%", avatar.status, avatar.progress);
1066  request.State = AvatarSdkMgr.Str (avatar.status);
1067 
1068  if (avatar.status == Strings.Computing)
1069  request.Progress = (float)avatar.progress / 100;
1070  }
1071 
1072  if (Strings.GoodFinalStates.Contains (avatar.status)) {
1073  request.Result = avatar;
1074  request.IsDone = true;
1075  } else {
1076  request.SetError (string.Format ("Avatar calculations failed, status: {0}", avatar.status));
1077  }
1078  }
1079 
1086  {
1087  var request = new AsyncRequest <AvatarData> (AvatarSdkMgr.Str (Strings.StartingCalculations));
1088  AvatarSdkMgr.SpawnCoroutine (AwaitAvatarCalculationsLoop (avatar, request));
1089  return request;
1090  }
1091 
1092 #endregion
1093  }
1094 }
static void SpawnCoroutine(IEnumerator routine)
Spawn coroutine outside of MonoBehaviour.
Definition: AvatarSdkMgr.cs:92
virtual string UrlWithParams(string url, Dictionary< string, string > param)
Urlencoded param string.
Definition: Connection.cs:70
virtual AsyncWebRequest< AvatarHaircutData[]> GetHaircutsAsync(AvatarData avatar)
Get list of all haircuts for avatar.
Definition: Connection.cs:782
void SetAuthHeaders(UnityWebRequest request)
Adds auth header to UnityWebRequest.
Definition: Connection.cs:119
virtual Dictionary< string, string > GetAuthHeaders()
Dictionary with required auth HTTP headers.
Definition: Connection.cs:103
virtual AsyncWebRequest< AvatarData > GetAvatarAsync(string avatarCode)
Get avatar information by code.
Definition: Connection.cs:772
virtual string UrlWithParams(string url, string param, string value)
Simple overload for a single-parameter use case.
Definition: Connection.cs:91
virtual string GetUrl(params string[] urlTokens)
Helper function to construct absolute url from relative url tokens.
Definition: Connection.cs:62
virtual AsyncRequest AuthorizeAsync()
Authorize this session using the credentials loaded from encrypted binary resource.
Definition: Connection.cs:622
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:357
virtual IEnumerator AwaitFileDataAsync(Func< UnityWebRequest > webRequestFactory, AsyncWebRequest< DownloadedFileInfo > request)
Call AwaitWebRequestFunc for downloading file data and its name.
Definition: Connection.cs:435
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:540
virtual AsyncWebRequest< byte[]> DownloadMeshZipAsync(AvatarData avatar, int levelOfDetails=0)
Download mesh zip file into memory.
Definition: Connection.cs:822
virtual AsyncWebRequest< string > GetModelInfoAsync(string avatarCode)
Request to get model info json
Definition: Connection.cs:550
virtual AsyncWebRequest< byte[]> DownloadHaircutPreviewBytesAsync(AvatarHaircutData haircut)
Download haircut preview into memory.
Definition: Connection.cs:880
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:724
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:427
virtual UnityWebRequest GenerateAuthRequest(AccessCredentials credentials)
Generate HTTP request object to obtain the access token.
Definition: Connection.cs:646
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:833
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:530
virtual AsyncWebRequest< byte[]> DownloadBlendshapesZipAsync(AvatarData avatar, BlendshapesFormat format=BlendshapesFormat.BIN, int levelOfDetails=0)
Downloads zip archive with all the blendshapes.
Definition: Connection.cs:914
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:804
virtual string GetAuthUrl()
Url used to obtain access token.
Definition: Connection.cs:97
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:957
virtual AsyncWebRequest< TextureData[]> GetTexturesAsync(AvatarData avatar)
Get list of all textures for avatar.
Definition: Connection.cs:792
UnityWebRequest HttpGet(string url)
Helper factory method.
Definition: Connection.cs:130
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:901
virtual AsyncWebRequest< byte[]> DownloadTextureBytesAsync(AvatarData avatar)
Download main texture into memory. Can be used right away to create Unity texture.
Definition: Connection.cs:843
Async request for web requests.
virtual AsyncWebRequest DeleteAvatarAsync(AvatarData avatar)
Delete avatar record on the server (does not delete local files).
Definition: Connection.cs:991
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:1012
virtual AsyncWebRequest< byte[]> DownloadHaircutMeshZipAsync(AvatarHaircutData haircut)
Downloads haircut zip file into memory.
Definition: Connection.cs:860
virtual AsyncWebRequest< byte[]> DownloadHaircutTextureBytesAsync(AvatarHaircutData haircut)
Download haircut texture into memory. Can be used right away to create Unity texture.
Definition: Connection.cs:870
virtual AsyncWebRequest< byte[]> DownloadHaircutPointCloudZipAsync(AvatarHaircutData haircut)
Downloads the haircut point cloud zip into memory.
Definition: Connection.cs:890
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:634
virtual IEnumerator AwaitDataAsync(Func< UnityWebRequest > webRequestFactory, AsyncWebRequest< byte[]> request)
Call AwaitWebRequestFunc for binary data.
Definition: Connection.cs:419
virtual AsyncWebRequest< Player > RegisterPlayerAsync(string comment="")
Register unique player UID that is used later to sign the requests.
Definition: Connection.cs:702
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:1085