diff --git a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/CacheItem.cs b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/CacheItem.cs index b3be547f5..7d26d7dd9 100644 --- a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/CacheItem.cs +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/CacheItem.cs @@ -15,5 +15,7 @@ public class CacheItem public string ETag; /// Can be 'null' as not all APIs populated this value. Last-Modified value of API response in GMT: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Last-Modified public DateTime? LastModified; + + public DateTime ExpirationDate; } } diff --git a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/CachingWebFileSource.cs b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/CachingWebFileSource.cs index b881516ff..688b63802 100644 --- a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/CachingWebFileSource.cs +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/CachingWebFileSource.cs @@ -1,4 +1,7 @@ -namespace Mapbox.Platform.Cache +using Unity.UNetWeaver; +using UnityEngine; + +namespace Mapbox.Platform.Cache { using System; using Mapbox.Platform; @@ -11,8 +14,6 @@ public class CachingWebFileSource : IFileSource, IDisposable { - - #if MAPBOX_DEBUG_CACHE private string _className; #endif @@ -22,6 +23,9 @@ public class CachingWebFileSource : IFileSource, IDisposable private Func _getMapsSkuToken; private bool _autoRefreshCache; + private const string EtagHeaderName = "ETag"; + private const string LastModifiedHeaderName = "Last-Modified"; + private const string CacheControlHeaderName = "Cache-Control"; public CachingWebFileSource(string accessToken, Func getMapsSkuToken, bool autoRefreshCache) { @@ -34,8 +38,7 @@ public CachingWebFileSource(string accessToken, Func getMapsSkuToken, bo } -#region idisposable - + #region idisposable ~CachingWebFileSource() { @@ -64,12 +67,12 @@ protected virtual void Dispose(bool disposeManagedResources) } } } + _disposed = true; } } - -#endregion + #endregion /// @@ -102,7 +105,8 @@ public void Clear() } - public void ReInit() { + public void ReInit() + { foreach (var cache in _caches) { cache.ReInit(); @@ -118,7 +122,6 @@ string uri , string tilesetId = null ) { - if (string.IsNullOrEmpty(tilesetId)) { throw new Exception("Cannot cache without a tileset id"); @@ -150,6 +153,7 @@ string uri uriBuilder.Query = accessTokenQuery + "&" + mapsSkuToken; } } + string finalUrl = uriBuilder.ToString(); #if MAPBOX_DEBUG_CACHE @@ -166,58 +170,52 @@ string uri callback(Response.FromCache(cachedItem.Data)); // check for updated tiles online if this is enabled in the settings - if (_autoRefreshCache) + if (cachedItem.ExpirationDate < DateTime.Now) { // check if tile on the web is newer than the one we already have locally IAsyncRequestFactory.CreateRequest( finalUrl, - (Response headerOnly) => + timeout, + "If-None-Match", cachedItem.ETag, + (Response response) => { // on error getting information from API just return. tile we have locally has already been returned above - if (headerOnly.HasError) + if (response.HasError || response.StatusCode == null) { return; } - // TODO: remove Debug.Log before PR - //UnityEngine.Debug.LogFormat( - // "{1}{0}cached:{2}{0}header:{3}" - // , Environment.NewLine - // , finalUrl - // , cachedItem.ETag - // , headerOnly.Headers["ETag"] - //); - // data from cache is the same as on the web: // * tile has already been returned above // * make sure all all other caches have it too, but don't force insert via cache.add(false) // additional ETag empty check: for backwards compability with old caches - if (!string.IsNullOrEmpty(cachedItem.ETag) && cachedItem.ETag.Equals(headerOnly.Headers["ETag"])) + if (response.StatusCode == 304) // 304 NOT MODIFIED { - foreach (var cache in _caches) - { - cache.Add(tilesetId, tileId, cachedItem, false); - } + cachedItem.ExpirationDate = GetExpirationDate(response); + } + else if (response.StatusCode == 200) // 200 OK, it means etag&data has changed so need to update cache + { + string eTag = ETag(response); + + // not all APIs populate 'Last-Modified' header + // don't log error if it's missing + DateTime? lastModified = GetLastModified(response); + + DateTime expirationDate = GetExpirationDate(response); + + cachedItem.Data = response.Data; + cachedItem.ETag = eTag; + cachedItem.LastModified = lastModified; + cachedItem.ExpirationDate = expirationDate; } - else + + foreach (var cache in _caches) { - // TODO: remove Debug.Log before PR - UnityEngine.Debug.LogWarningFormat( - "updating cached tile {1} tilesetId:{2}{0}cached etag:{3}{0}remote etag:{4}{0}{5}" - , Environment.NewLine - , tileId - , tilesetId - , cachedItem.ETag - , headerOnly.Headers["ETag"] - , finalUrl - ); - - // request updated tile and pass callback to return new data to subscribers - requestTileAndCache(finalUrl, tilesetId, tileId, timeout, callback); + cache.Add(tilesetId, tileId, cachedItem, true); } + + callback(Response.FromCache(cachedItem.Data)); } - , timeout - , HttpRequestType.Head ); } @@ -233,34 +231,22 @@ string uri } } - private IAsyncRequest requestTileAndCache(string url, string tilesetId, CanonicalTileId tileId, int timeout, Action callback) { return IAsyncRequestFactory.CreateRequest( url, - (Response r) => + (Response response) => { // if the request was successful add tile to all caches - if (!r.HasError && null != r.Data) + if (!response.HasError && null != response.Data) { - string eTag = string.Empty; - DateTime? lastModified = null; - - if (!r.Headers.ContainsKey("ETag")) - { - UnityEngine.Debug.LogWarningFormat("no 'ETag' header present in response for {0}", url); - } - else - { - eTag = r.Headers["ETag"]; - } + string eTag = ETag(response); // not all APIs populate 'Last-Modified' header // don't log error if it's missing - if (r.Headers.ContainsKey("Last-Modified")) - { - lastModified = DateTime.ParseExact(r.Headers["Last-Modified"], "r", null); - } + DateTime? lastModified = GetLastModified(response); + + DateTime expirationDate = GetExpirationDate(response); // propagate to all caches forcing update foreach (var cache in _caches) @@ -270,27 +256,76 @@ private IAsyncRequest requestTileAndCache(string url, string tilesetId, Canonica , tileId , new CacheItem() { - Data = r.Data, + Data = response.Data, ETag = eTag, - LastModified = lastModified + LastModified = lastModified, + ExpirationDate = expirationDate } , true // force insert/update ); } } + if (null != callback) { - r.IsUpdate = true; - callback(r); + response.IsUpdate = true; + callback(response); } }, timeout); } + private string ETag(Response response) + { + string eTag = String.Empty; + if (!response.Headers.ContainsKey(EtagHeaderName)) + { + Debug.LogWarning("no 'ETag' header present in response"); + } + else + { + eTag = response.Headers[EtagHeaderName]; + } + + return eTag; + } + + private DateTime? GetLastModified(Response response) + { + DateTime? lastModified = null; + if (response.Headers.ContainsKey(LastModifiedHeaderName)) + { + lastModified = DateTime.ParseExact(response.Headers[LastModifiedHeaderName], "r", null); + } - class MemoryCacheAsyncRequest : IAsyncRequest + return lastModified; + } + + private DateTime GetExpirationDate(Response response) { + DateTime expirationDate = DateTime.Now; + if (response.Headers.ContainsKey(CacheControlHeaderName)) + { + var cacheEntries = response.Headers[CacheControlHeaderName].Split(','); + if (cacheEntries.Length > 0) + { + foreach (var entry in cacheEntries) + { + var value = entry.Split('='); + if (value[0] == "max-age") + { + expirationDate = expirationDate + TimeSpan.FromSeconds(int.Parse(value[1])); + return expirationDate; + } + } + } + } + return expirationDate; + } + + class MemoryCacheAsyncRequest : IAsyncRequest + { public string RequestUrl { get; private set; } @@ -302,14 +337,14 @@ public MemoryCacheAsyncRequest(string requestUrl) public bool IsCompleted { - get - { - return true; - } + get { return true; } } - public HttpRequestType RequestType { get { return HttpRequestType.Get; } } + public HttpRequestType RequestType + { + get { return HttpRequestType.Get; } + } public void Cancel() @@ -318,4 +353,4 @@ public void Cancel() } } } -} +} \ No newline at end of file diff --git a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/SQLiteCache/SQLiteCache.cs b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/SQLiteCache/SQLiteCache.cs index a221ce117..6d1dccfcf 100644 --- a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/SQLiteCache/SQLiteCache.cs +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/SQLiteCache/SQLiteCache.cs @@ -120,6 +120,7 @@ tile_set INTEGER REFERENCES tilesets (id) ON DELETE CASCADE ON UPDATE CASCAD tile_row BIGINT NOT NULL, tile_data BLOB NOT NULL, timestamp INTEGER NOT NULL, +expirationdate INTEGER NOT NULL, etag TEXT, lastmodified INTEGER, PRIMARY KEY( @@ -248,6 +249,7 @@ public void Add(string tilesetName, CanonicalTileId tileId, CacheItem item, bool tile_row = tileId.Y, tile_data = item.Data, timestamp = (int)UnixTimestampUtils.To(DateTime.Now), + expirationdate = (int)UnixTimestampUtils.To(item.ExpirationDate), etag = item.ETag }); if (1 != rowsAffected) diff --git a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/SQLiteCache/Tiles.cs b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/SQLiteCache/Tiles.cs index 18629526b..411e35fe6 100644 --- a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/SQLiteCache/Tiles.cs +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/SQLiteCache/Tiles.cs @@ -36,5 +36,8 @@ public class tiles /// Last-Modified header value of API response. Not all APIs populate it, will be -1 in that case. public int? lastmodified { get; set; } + + /// Last-Modified header value of API response. Not all APIs populate it, will be -1 in that case. + public int expirationdate { get; set; } } } diff --git a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/FileSource.cs b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/FileSource.cs index 3225832c1..5070aac02 100644 --- a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/FileSource.cs +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/FileSource.cs @@ -27,6 +27,7 @@ namespace Mapbox.Platform #endif #if UNITY_5_3_OR_NEWER using UnityEngine; + #endif /// @@ -40,7 +41,6 @@ namespace Mapbox.Platform /// public sealed class FileSource : IFileSource { - private Func _getMapsSkuToken; private readonly Dictionary _requests = new Dictionary(); private readonly string _accessToken; @@ -49,8 +49,10 @@ public sealed class FileSource : IFileSource /// 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 @@ -59,14 +61,7 @@ public sealed class FileSource : IFileSource public FileSource(Func getMapsSkuToken, string acessToken = null) { _getMapsSkuToken = getMapsSkuToken; - if (string.IsNullOrEmpty(acessToken)) - { - _accessToken = Environment.GetEnvironmentVariable("MAPBOX_ACCESS_TOKEN"); - } - else - { - _accessToken = acessToken; - } + _accessToken = acessToken; } /// Performs a request asynchronously. @@ -92,7 +87,8 @@ string url string skuToken = "sku=" + _getMapsSkuToken(); if (uriBuilder.Query != null && uriBuilder.Query.Length > 1) { - uriBuilder.Query = uriBuilder.Query.Substring(1) + "&" + accessTokenQuery + "&" + skuToken;; + uriBuilder.Query = uriBuilder.Query.Substring(1) + "&" + accessTokenQuery + "&" + skuToken; + ; } else { @@ -127,16 +123,27 @@ string url , 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; } + 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) { @@ -161,6 +168,7 @@ string url _requests.Add(request, 0); } } + //yield return request; return request; } @@ -193,13 +201,13 @@ public IEnumerator WaitForAllRequests() } } } + yield return new WaitForSeconds(0.2f); } } #endif - #if !UNITY_5_3_OR_NEWER /// /// Block until all the requests are processed. @@ -252,4 +260,4 @@ public void WaitForAllRequests() } #endif } -} +} \ No newline at end of file diff --git a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/IAsyncRequestFactory.cs b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/IAsyncRequestFactory.cs index 195d1e428..01e6e72e2 100644 --- a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/IAsyncRequestFactory.cs +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/IAsyncRequestFactory.cs @@ -34,6 +34,22 @@ string url #endif } - + public static IAsyncRequest CreateRequest( + string url + , int timeout + , string headerName + , string headerValue + , Action callback + ) { +#if !UNITY + if (Environment.ProcessorCount > 2) { + return new HTTPRequestThreaded(url, callback, timeout); + } else { + return new HTTPRequestNonThreaded(url, callback, timeout); + } +#else + return new Mapbox.Unity.Utilities.HTTPRequest(url, callback, timeout, headerName, headerValue); +#endif + } } } \ No newline at end of file diff --git a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Response.cs b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Response.cs index 5735aa7ca..2c5ded836 100644 --- a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Response.cs +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Response.cs @@ -315,7 +315,7 @@ public static Response FromWebResponse(IAsyncRequest request, UnityWebRequest ap int statusCode = (int)apiResponse.responseCode; response.StatusCode = statusCode; - if (statusCode != 200) + if (statusCode != 200 && statusCode != 304) { response.AddException(new Exception(string.Format("Status Code {0}", apiResponse.responseCode))); } diff --git a/sdkproject/Assets/Mapbox/Unity/Utilities/HTTPRequest.cs b/sdkproject/Assets/Mapbox/Unity/Utilities/HTTPRequest.cs index 12ad5936c..26282fd82 100644 --- a/sdkproject/Assets/Mapbox/Unity/Utilities/HTTPRequest.cs +++ b/sdkproject/Assets/Mapbox/Unity/Utilities/HTTPRequest.cs @@ -59,6 +59,25 @@ public HTTPRequest(string url, Action callback, int timeout, HttpReque _request.timeout = timeout; _callback = callback; +#if UNITY_EDITOR + if (!EditorApplication.isPlaying) + { + Runnable.EnableRunnableInEditor(); + } +#endif + Runnable.Run(DoRequest()); + } + + public HTTPRequest(string url, Action callback, int timeout, string headerName, string headerValue) + { + IsCompleted = false; + _requestType = HttpRequestType.Get; + _request = UnityWebRequest.Get(url); + _request.SetRequestHeader(headerName, headerValue); + + _request.timeout = timeout; + _callback = callback; + #if UNITY_EDITOR if (!EditorApplication.isPlaying) {