//----------------------------------------------------------------------- // // Copyright (c) 2016 Mapbox. All rights reserved. // //----------------------------------------------------------------------- using Mapbox.Unity; namespace Mapbox.Platform { using Mapbox.Map; using Mapbox.Unity.Utilities; using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Security; #if !NETFX_CORE using System.Security.Cryptography.X509Certificates; #endif #if !UNITY_5_3_OR_NEWER using System.Threading; #endif #if UNITY_EDITOR using UnityEditor; #endif #if UNITY_5_3_OR_NEWER using UnityEngine; #endif /// /// Mono implementation of the FileSource class. It will use Mono's /// runtime to /// asynchronously fetch data from the network via HTTP or HTTPS requests. /// /// /// This implementation requires .NET 4.5 and later. The access token is expected to /// be exported to the environment as MAPBOX_ACCESS_TOKEN. /// public sealed class FileSource : IFileSource { private Func _getMapsSkuToken; private readonly Dictionary _requests = new Dictionary(); private readonly string _accessToken; private readonly object _lock = new object(); /// Length of rate-limiting interval in seconds. https://www.mapbox.com/api-documentation/#rate-limit-headers #pragma warning disable 0414 private int? XRateLimitInterval; /// Maximum number of requests you may make in the current interval before reaching the limit. https://www.mapbox.com/api-documentation/#rate-limit-headers private long? XRateLimitLimit; /// Timestamp of when the current interval will end and the ratelimit counter is reset. https://www.mapbox.com/api-documentation/#rate-limit-headers private DateTime? XRateLimitReset; #pragma warning restore 0414 public FileSource(Func getMapsSkuToken, string acessToken = null) { _getMapsSkuToken = getMapsSkuToken; if (string.IsNullOrEmpty(acessToken)) { _accessToken = Environment.GetEnvironmentVariable("MAPBOX_ACCESS_TOKEN"); } else { _accessToken = acessToken; } } /// Performs a request asynchronously. /// The HTTP/HTTPS url. /// Callback to be called after the request is completed. /// /// Returns a that can be used for canceling a pending /// request. This handle can be completely ignored if there is no intention of ever /// canceling the request. /// public IAsyncRequest Request( string url , Action callback , int timeout = 10 , CanonicalTileId tileId = new CanonicalTileId() , string tilesetId = null ) { if (!string.IsNullOrEmpty(_accessToken)) { var uriBuilder = new UriBuilder(url); string accessTokenQuery = "access_token=" + _accessToken; string skuToken = "sku=" + _getMapsSkuToken(); if (uriBuilder.Query != null && uriBuilder.Query.Length > 1) { uriBuilder.Query = uriBuilder.Query.Substring(1) + "&" + accessTokenQuery + "&" + skuToken;; } else { uriBuilder.Query = accessTokenQuery + "&" + skuToken; } url = uriBuilder.ToString(); } // TODO: // * add queue for requests // * evaluate rate limits (headers and status code) // * throttle requests accordingly //var request = new HTTPRequest(url, callback); //IEnumerator proxy = proxyResponse(url, callback); //proxy.MoveNext(); //IAsyncRequest request = proxy.Current; //return request; return proxyResponse(url, callback, timeout, tileId, tilesetId); } // TODO: look at requests and implement throttling if needed //private IEnumerator proxyResponse(string url, Action callback) { private IAsyncRequest proxyResponse( string url , Action callback , int timeout , CanonicalTileId tileId , string tilesetId ) { // TODO: plugin caching somewhere around here var request = IAsyncRequestFactory.CreateRequest( url , (Response response) => { if (response.XRateLimitInterval.HasValue) { XRateLimitInterval = response.XRateLimitInterval; } if (response.XRateLimitLimit.HasValue) { XRateLimitLimit = response.XRateLimitLimit; } if (response.XRateLimitReset.HasValue) { XRateLimitReset = response.XRateLimitReset; } callback(response); lock (_lock) { //another place to catch if request has been cancelled try { _requests.Remove(response.Request); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } } } , timeout ); lock (_lock) { //sometimes we get here after the request has already finished if (!request.IsCompleted) { _requests.Add(request, 0); } } //yield return request; return request; } #if UNITY_5_3_OR_NEWER /// /// Block until all the requests are processed. /// public IEnumerator WaitForAllRequests() { while (_requests.Count > 0) { lock (_lock) { List reqs = _requests.Keys.ToList(); for (int i = reqs.Count - 1; i > -1; i--) { if (reqs[i].IsCompleted) { // another place to watch out if request has been cancelled try { _requests.Remove(reqs[i]); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } } } } yield return new WaitForSeconds(0.2f); } } #endif #if !UNITY_5_3_OR_NEWER /// /// Block until all the requests are processed. /// public void WaitForAllRequests() { int waitTimeMs = 200; while (_requests.Count > 0) { lock (_lock) { List reqs = _requests.Keys.ToList(); for (int i = reqs.Count - 1; i > -1; i--) { if (reqs[i].IsCompleted) { // another place to watch out if request has been cancelled try { _requests.Remove(reqs[i]); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } } } } #if WINDOWS_UWP System.Threading.Tasks.Task.Delay(waitTimeMs).Wait(); #else //Thread.Sleep(50); // TODO: get rid of DoEvents!!! and find non-blocking wait that works for Net3.5 //System.Windows.Forms.Application.DoEvents(); var resetEvent = new ManualResetEvent(false); ThreadPool.QueueUserWorkItem(new WaitCallback(delegate { Thread.Sleep(waitTimeMs); resetEvent.Set(); }), null); UnityEngine.Debug.Log("before waitOne " + DateTime.Now.Ticks); resetEvent.WaitOne(); UnityEngine.Debug.Log("after waitOne " + DateTime.Now.Ticks); resetEvent.Close(); resetEvent = null; #endif } } #endif } }