diff --git a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Map/Tile.cs b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Map/Tile.cs index af5fd383d..25fa73db6 100644 --- a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Map/Tile.cs +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Map/Tile.cs @@ -11,6 +11,7 @@ namespace Mapbox.Map using System.Linq; using System.Collections.Generic; using System.Collections.ObjectModel; + using Mapbox.Unity.Utilities; /// @@ -38,7 +39,9 @@ public enum State /// Data loaded and parsed. Loaded, /// Data loading cancelled. - Canceled + Canceled, + /// Data has been loaded before and got updated. + Updated } /// Gets the identifier. @@ -103,6 +106,10 @@ public State CurrentState } } + + public HttpRequestType RequestType { get { return _request.RequestType; } } + + public bool IsCompleted { get @@ -209,7 +216,7 @@ private void HandleTileResponse(Response response) ids.Add(_id.ToString()); else return; - + response.Exceptions.ToList().ForEach(e => AddException(e)); } else @@ -225,7 +232,14 @@ private void HandleTileResponse(Response response) // Cancelled is not the same as loaded! if (_state != State.Canceled) { - _state = State.Loaded; + if (response.IsUpdate) + { + _state = State.Updated; + } + else + { + _state = State.Loaded; + } } _callback(); } 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 new file mode 100644 index 000000000..b3be547f5 --- /dev/null +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/CacheItem.cs @@ -0,0 +1,19 @@ + +namespace Mapbox.Platform.Cache +{ + + using System; + + + public class CacheItem + { + /// Raw response data- + public byte[] Data; + /// UTC ticks when item was added to the cache. + public long AddedToCacheTicksUtc; + /// ETag value of API response. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag + 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; + } +} diff --git a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/CacheItem.cs.meta b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/CacheItem.cs.meta new file mode 100644 index 000000000..452583767 --- /dev/null +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/CacheItem.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 3bad456c58de85d47b4ff050dbd99915 +timeCreated: 1510665832 +licenseType: Pro +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: 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 b11dba12f..e563e8a55 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 @@ -6,6 +6,8 @@ using Mapbox.Unity.Utilities; using Mapbox.Map; using System.Collections; + using System.Linq; + public class CachingWebFileSource : IFileSource, IDisposable { @@ -14,11 +16,13 @@ public class CachingWebFileSource : IFileSource, IDisposable private bool _disposed; private List _caches = new List(); private string _accessToken; + private bool _autoRefreshCache; - public CachingWebFileSource(string accessToken) + public CachingWebFileSource(string accessToken, bool autoRefreshCache) { _accessToken = accessToken; + _autoRefreshCache = autoRefreshCache; } @@ -104,64 +108,160 @@ string uri throw new Exception("Cannot cache without a map id"); } - byte[] data = null; + CacheItem cachedItem = null; // go through existing caches and check if we already have the requested tile available foreach (var cache in _caches) { - data = cache.Get(mapId, tileId); - if (null != data) + cachedItem = cache.Get(mapId, tileId); + if (null != cachedItem) { break; } } - // if tile was available propagate to all other caches and return - if (null != data) + var uriBuilder = new UriBuilder(uri); + if (!string.IsNullOrEmpty(_accessToken)) { - foreach (var cache in _caches) + string accessTokenQuery = "access_token=" + _accessToken; + if (uriBuilder.Query != null && uriBuilder.Query.Length > 1) + { + uriBuilder.Query = uriBuilder.Query.Substring(1) + "&" + accessTokenQuery; + } + else { - cache.Add(mapId, tileId, data); + uriBuilder.Query = accessTokenQuery; + } + } + string finalUrl = uriBuilder.ToString(); + + + // if tile was available call callback with it, propagate to all other caches and check if a newer one is available + if (null != cachedItem) + { + // immediately return cached tile + callback(Response.FromCache(cachedItem.Data)); + + // check for updated tiles online if this is enabled in the settings + if (_autoRefreshCache) + { + // check if tile on the web is newer than the one we already have locally + IAsyncRequestFactory.CreateRequest( + finalUrl, + (Response headerOnly) => + { + // on error getting information from API just return. tile we have locally has already been returned above + if (headerOnly.HasError) + { + 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"])) + { + foreach (var cache in _caches) + { + cache.Add(mapId, tileId, cachedItem, false); + } + } + else + { + // TODO: remove Debug.Log before PR + UnityEngine.Debug.LogWarningFormat( + "updating cached tile {1} mapid:{2}{0}cached etag:{3}{0}remote etag:{4}{0}{5}" + , Environment.NewLine + , tileId + , mapId + , cachedItem.ETag + , headerOnly.Headers["ETag"] + , finalUrl + ); + + // request updated tile and pass callback to return new data to subscribers + requestTileAndCache(finalUrl, mapId, tileId, timeout, callback); + } + } + , timeout + , HttpRequestType.Head + ); } - callback(Response.FromCache(data)); return new MemoryCacheAsyncRequest(uri); } else { // requested tile is not in any of the caches yet, get it - var uriBuilder = new UriBuilder(uri); + return requestTileAndCache(finalUrl, mapId, tileId, timeout, callback); + } + } - if (!string.IsNullOrEmpty(_accessToken)) + + private IAsyncRequest requestTileAndCache(string url, string mapId, CanonicalTileId tileId, int timeout, Action callback) + { + return IAsyncRequestFactory.CreateRequest( + url, + (Response r) => { - string accessTokenQuery = "access_token=" + _accessToken; - if (uriBuilder.Query != null && uriBuilder.Query.Length > 1) + // if the request was successful add tile to all caches + if (!r.HasError && null != r.Data) { - uriBuilder.Query = uriBuilder.Query.Substring(1) + "&" + accessTokenQuery; - } - else - { - uriBuilder.Query = accessTokenQuery; - } - } + //UnityEngine.Debug.Log(uri); + string eTag = string.Empty; + DateTime? lastModified = null; - return IAsyncRequestFactory.CreateRequest( - uriBuilder.ToString(), - (Response r) => - { - // if the request was successful add tile to all caches - if (!r.HasError && null != r.Data) + if (!r.Headers.ContainsKey("ETag")) { - foreach (var cache in _caches) - { - cache.Add(mapId, tileId, r.Data); - } + UnityEngine.Debug.LogWarningFormat("no 'ETag' header present in response for {0}", url); + } + else + { + eTag = r.Headers["ETag"]; + } + + // 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); + } + + // propagate to all caches forcing update + foreach (var cache in _caches) + { + cache.Add( + mapId + , tileId + , new CacheItem() + { + Data = r.Data, + ETag = eTag, + LastModified = lastModified + } + , true // force insert/update + ); } + } + if (null != callback) + { + r.IsUpdate = true; callback(r); - }, timeout); - } + } + }, timeout); } + class MemoryCacheAsyncRequest : IAsyncRequest { @@ -184,6 +284,9 @@ public bool IsCompleted } + public HttpRequestType RequestType { get { return HttpRequestType.Get; } } + + public void Cancel() { // Empty. We can't cancel an instantaneous response. diff --git a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/ICache.cs b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/ICache.cs index 0579ea90e..7a69903ba 100644 --- a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/ICache.cs +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/ICache.cs @@ -1,11 +1,10 @@ -using Mapbox.Map; -using System.Collections; -using System.Collections.Generic; - namespace Mapbox.Platform.Cache { + using Mapbox.Map; + using System; + public interface ICache { @@ -20,8 +19,9 @@ public interface ICache /// /// Tile set name /// Tile ID - /// Tile data - void Add(string mapId, CanonicalTileId tileId, byte[] data); + /// Item to cache + /// Force insert even if item already exists. + void Add(string mapId, CanonicalTileId tileId, CacheItem item, bool replaceIfExists); /// @@ -30,7 +30,7 @@ public interface ICache /// /// /// byte[] with tile data. Null if requested tile is not in cache - byte[] Get(string mapId, CanonicalTileId tileId); + CacheItem Get(string mapId, CanonicalTileId tileId); /// Clear cache for all tile sets diff --git a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/MbTilesCache.cs b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/MbTilesCache.cs index 7bf09b722..d83ff7341 100644 --- a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/MbTilesCache.cs +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/MbTilesCache.cs @@ -53,7 +53,7 @@ protected virtual void Dispose(bool disposeManagedResources) #endregion - + private bool _disposed; private uint _maxCacheSize; @@ -67,7 +67,7 @@ public uint MaxCacheSize } - public void Add(string mapId, CanonicalTileId tileId, byte[] data) + public void Add(string mapId, CanonicalTileId tileId, CacheItem item, bool forceInsert) { mapId = cleanMapId(mapId); @@ -82,9 +82,9 @@ public void Add(string mapId, CanonicalTileId tileId, byte[] data) MbTilesDb currentMbTiles = _mbTiles[mapId]; - if (!currentMbTiles.TileExists(tileId)) + if (!currentMbTiles.TileExists(tileId) || forceInsert) { - _mbTiles[mapId].AddTile(tileId, data); + _mbTiles[mapId].AddTile(tileId, item, forceInsert); } } @@ -114,7 +114,7 @@ private void initializeMbTiles(string mapId) - public byte[] Get(string mapId, CanonicalTileId tileId) + public CacheItem Get(string mapId, CanonicalTileId tileId) { mapId = cleanMapId(mapId); lock (_lock) @@ -125,7 +125,13 @@ public byte[] Get(string mapId, CanonicalTileId tileId) } } - return _mbTiles[mapId].GetTile(tileId); + CacheItem item = _mbTiles[mapId].GetTile(tileId); + if (null == item) + { + return null; + } + + return item; } diff --git a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/MemoryCache.cs b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/MemoryCache.cs index b039f28a0..a1d9d7a2d 100644 --- a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/MemoryCache.cs +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Cache/MemoryCache.cs @@ -12,13 +12,6 @@ public class MemoryCache : ICache { - private struct CacheItem - { - public long Timestamp; - public byte[] Data; - } - - // TODO: add support for disposal strategy (timestamp, distance, etc.) public MemoryCache(uint maxCacheSize) { @@ -38,7 +31,7 @@ public uint MaxCacheSize } - public void Add(string mapdId, CanonicalTileId tileId, byte[] data) + public void Add(string mapdId, CanonicalTileId tileId, CacheItem item, bool forceInsert) { string key = mapdId + "||" + tileId; @@ -46,18 +39,20 @@ public void Add(string mapdId, CanonicalTileId tileId, byte[] data) { if (_cachedResponses.Count >= _maxCacheSize) { - _cachedResponses.Remove(_cachedResponses.OrderBy(c => c.Value.Timestamp).First().Key); + _cachedResponses.Remove(_cachedResponses.OrderBy(c => c.Value.AddedToCacheTicksUtc).First().Key); } + // TODO: forceInsert if (!_cachedResponses.ContainsKey(key)) { - _cachedResponses.Add(key, new CacheItem() { Timestamp = DateTime.Now.Ticks, Data = data }); + item.AddedToCacheTicksUtc = DateTime.UtcNow.Ticks; + _cachedResponses.Add(key, item); } } } - public byte[] Get(string mapId, CanonicalTileId tileId) + public CacheItem Get(string mapId, CanonicalTileId tileId) { string key = mapId + "||" + tileId; @@ -68,7 +63,7 @@ public byte[] Get(string mapId, CanonicalTileId tileId) return null; } - return _cachedResponses[key].Data; + return _cachedResponses[key]; } } diff --git a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/IAsyncRequest.cs b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/IAsyncRequest.cs index ae08c4a73..f26596af1 100644 --- a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/IAsyncRequest.cs +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/IAsyncRequest.cs @@ -4,8 +4,11 @@ // //----------------------------------------------------------------------- + namespace Mapbox.Platform { + using Mapbox.Unity.Utilities; + /// A handle to an asynchronous request. public interface IAsyncRequest { @@ -15,5 +18,8 @@ public interface IAsyncRequest { /// Cancel the ongoing request, preventing it from firing a callback. void Cancel(); + + /// Type of request: GET, HEAD, ... + HttpRequestType RequestType { get; } } } \ 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 cf95c0783..195d1e428 100644 --- a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/IAsyncRequestFactory.cs +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/IAsyncRequestFactory.cs @@ -11,7 +11,8 @@ namespace Mapbox.Platform { using Mapbox.Map; - using System; + using Mapbox.Unity.Utilities; + using System; /// A handle to an asynchronous request. public static class IAsyncRequestFactory { @@ -20,6 +21,7 @@ public static IAsyncRequest CreateRequest( string url , Action callback , int timeout + , HttpRequestType requestType= HttpRequestType.Get ) { #if !UNITY if (Environment.ProcessorCount > 2) { @@ -28,7 +30,7 @@ string url return new HTTPRequestNonThreaded(url, callback, timeout); } #else - return new Mapbox.Unity.Utilities.HTTPRequest(url, callback, timeout); + return new Mapbox.Unity.Utilities.HTTPRequest(url, callback, timeout, requestType); #endif } diff --git a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/MbTiles/MbTiles.cs b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/MbTiles/MbTiles.cs index 28360f31c..550a960ff 100644 --- a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/MbTiles/MbTiles.cs +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/MbTiles/MbTiles.cs @@ -1,16 +1,15 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using SQLite4Unity3d; -using Mapbox.Utils; -using UnityEngine; -using Mapbox.Map; - - -namespace Mapbox.Platform.MbTiles +namespace Mapbox.Platform.MbTiles { + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using SQLite4Unity3d; + using Mapbox.Utils; + using UnityEngine; + using Mapbox.Map; + using Mapbox.Platform.Cache; public class MbTilesDb : IDisposable { @@ -43,11 +42,13 @@ public MbTilesDb(string tileset, uint? maxTileCount = null) //_sqlite.CreateTable(); string cmdCreateTTbliles = @"CREATE TABLE tiles( -zoom_level INTEGER NOT NULL, -tile_column BIGINT NOT NULL, -tile_row BIGINT NOT NULL, -tile_data BLOB NOT NULL, -timestamp INTEGER NOT NULL, +zoom_level INTEGER NOT NULL, +tile_column BIGINT NOT NULL, +tile_row BIGINT NOT NULL, +tile_data BLOB NOT NULL, +timestamp INTEGER NOT NULL, +etag TEXT, +lastmodified INTEGER, PRIMARY KEY( zoom_level ASC, tile_column ASC, @@ -60,6 +61,18 @@ tile_row ASC _sqlite.Execute(cmdIdxTimestamp); } + // auto migrate old caches + if ( + 0 != colInfo.Count + && null == colInfo.FirstOrDefault(ci => ci.Name.Equals("etag")) + ) + { + string sql = "ALTER TABLE tiles ADD COLUMN etag text;"; + _sqlite.Execute(sql); + sql = "ALTER TABLE tiles ADD COLUMN lastmodified INTEGER;"; + _sqlite.Execute(sql); + } + //some pragmas to speed things up a bit :-) string[] cmds = new string[] { @@ -162,18 +175,23 @@ public void CreateMetaData(MetaDataRequired md) } - public void AddTile(CanonicalTileId tileId, byte[] data) + public void AddTile(CanonicalTileId tileId, CacheItem item, bool update = false) { - _sqlite.Insert(new tiles + _sqlite.InsertOrReplace(new tiles { zoom_level = tileId.Z, tile_column = tileId.X, tile_row = tileId.Y, - tile_data = data, - timestamp = (int)UnixTimestampUtils.To(DateTime.Now) + tile_data = item.Data, + timestamp = (int)UnixTimestampUtils.To(DateTime.Now), + etag = item.ETag }); - _pruneCacheCounter++; + // update counter only when new tile gets inserted + if (!update) + { + _pruneCacheCounter++; + } if (0 == _pruneCacheCounter % _pruneCacheDelta) { _pruneCacheCounter = 0; @@ -204,7 +222,7 @@ private void prune() /// /// Canonical tile id to identify the tile /// tile data as byte[], if tile is not cached returns null - public byte[] GetTile(CanonicalTileId tileId) + public CacheItem GetTile(CanonicalTileId tileId) { tiles tile = _sqlite .Table() @@ -216,7 +234,16 @@ public byte[] GetTile(CanonicalTileId tileId) return null; } - return tile.tile_data; + DateTime? lastModified = null; + if (tile.lastmodified.HasValue) { lastModified = UnixTimestampUtils.From((double)tile.lastmodified.Value); } + + return new CacheItem() + { + Data = tile.tile_data, + AddedToCacheTicksUtc = tile.timestamp, + ETag = tile.etag, + LastModified = lastModified + }; } diff --git a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/MbTiles/Tiles.cs b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/MbTiles/Tiles.cs index 6a5b3b732..2b7edb878 100644 --- a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/MbTiles/Tiles.cs +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/MbTiles/Tiles.cs @@ -29,5 +29,11 @@ public class tiles /// Unix epoch for simple FIFO pruning public int timestamp { get; set; } + + /// ETag Header value of the reponse for auto updating cache + public string etag { get; set; } + + /// Last-Modified header value of API response. Not all APIs populate it, will be -1 in that case. + public int? lastmodified { get; set; } } } \ 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 f9c52dd46..e8fe97e81 100644 --- a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Response.cs +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Platform/Response.cs @@ -25,6 +25,7 @@ namespace Mapbox.Platform #endif #if UNITY using UnityEngine.Networking; + using Mapbox.Unity.Utilities; #endif /// A response from a request. @@ -50,8 +51,12 @@ public bool HasError get { return _exceptions == null ? false : _exceptions.Count > 0; } } + /// Flag to indicate if the request was fullfilled from a local cache public bool LoadedFromCache; + /// Flag to indicate if the request was issued before but was issued again and updated + public bool IsUpdate = false; + public string RequestUrl; @@ -257,9 +262,12 @@ public static Response FromWebResponse(IAsyncRequest request, UnityWebRequest ap response.AddException(new Exception(apiResponse.error)); } - if (null == apiResponse.downloadHandler.data) + if (request.RequestType != HttpRequestType.Head) { - response.AddException(new Exception("Response has no data.")); + if (null == apiResponse.downloadHandler.data) + { + response.AddException(new Exception("Response has no data.")); + } } #if NETFX_CORE @@ -316,7 +324,10 @@ public static Response FromWebResponse(IAsyncRequest request, UnityWebRequest ap response.AddException(new Exception("Rate limit hit")); } - response.Data = apiResponse.downloadHandler.data; + if (request.RequestType != HttpRequestType.Head) + { + response.Data = apiResponse.downloadHandler.data; + } return response; } diff --git a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Utils/UnixTimestampUtils.cs b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Utils/UnixTimestampUtils.cs index 4a110aa66..502a440f5 100644 --- a/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Utils/UnixTimestampUtils.cs +++ b/sdkproject/Assets/Mapbox/Core/mapbox-sdk-cs/Utils/UnixTimestampUtils.cs @@ -33,7 +33,8 @@ public static double To(DateTime date) { /// /// /// - public static DateTime From(double timestamp) { + public static DateTime From(double timestamp) + { //return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Add(TimeSpan.FromSeconds(timestamp)).ToLocalTime(); return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Add(TimeSpan.FromSeconds(timestamp)); } @@ -49,5 +50,6 @@ public static DateTime From(long timestamp) return new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).Add(TimeSpan.FromTicks(timestamp)); } + } } diff --git a/sdkproject/Assets/Mapbox/Unity/Editor/ClearMbTilesCache.cs b/sdkproject/Assets/Mapbox/Unity/Editor/ClearMbTilesCache.cs index 12f0652b5..ddb5f36fc 100644 --- a/sdkproject/Assets/Mapbox/Unity/Editor/ClearMbTilesCache.cs +++ b/sdkproject/Assets/Mapbox/Unity/Editor/ClearMbTilesCache.cs @@ -9,13 +9,19 @@ public class ClearMbTilesCache : MonoBehaviour { - [MenuItem("Mapbox/Clear Caches")] + [MenuItem("Mapbox/Clear Caches/Current Scene")] public static void ClearCachingFileSource() { - Mapbox.Unity.MapboxAccess.Instance.ClearCache(); + Mapbox.Unity.MapboxAccess.Instance.ClearSceneCache(); } + [MenuItem("Mapbox/Clear Caches/All")] + public static void ClearAllCachFiles() + { + Mapbox.Unity.MapboxAccess.Instance.ClearAllCacheFiles(); + } + } } \ No newline at end of file diff --git a/sdkproject/Assets/Mapbox/Unity/Editor/MapboxConfigurationWindow.cs b/sdkproject/Assets/Mapbox/Unity/Editor/MapboxConfigurationWindow.cs index 490f3a9f3..6e90f78ff 100644 --- a/sdkproject/Assets/Mapbox/Unity/Editor/MapboxConfigurationWindow.cs +++ b/sdkproject/Assets/Mapbox/Unity/Editor/MapboxConfigurationWindow.cs @@ -30,6 +30,7 @@ public class MapboxConfigurationWindow : EditorWindow [Range(0, 3000)] static int _mbtilesCacheSize = 2000; static int _webRequestTimeout = 30; + static bool _autoRefreshCache = false; //mapbox access callbacks static bool _listeningForTokenValidation = false; @@ -91,7 +92,7 @@ public static void ShowWindowOnImport() [MenuItem("Mapbox/Setup")] static void InitWhenLoaded() { - if(EditorApplication.isCompiling && !_waitingToLoad) + if (EditorApplication.isCompiling && !_waitingToLoad) { //subscribe to updates _waitingToLoad = true; @@ -99,10 +100,10 @@ static void InitWhenLoaded() return; } - if(!EditorApplication.isCompiling) + if (!EditorApplication.isCompiling) { //unsubscribe from updates if waiting - if(_waitingToLoad) + if (_waitingToLoad) { EditorApplication.update -= InitWhenLoaded; _waitingToLoad = false; @@ -130,6 +131,7 @@ static void Init() AccessToken = _accessToken, MemoryCacheSize = (uint)_memoryCacheSize, MbTilesCacheSize = (uint)_mbtilesCacheSize, + AutoRefreshCache = _autoRefreshCache, DefaultTimeout = _webRequestTimeout }; var json = JsonUtility.ToJson(_mapboxConfig); @@ -160,6 +162,7 @@ static void OpenWindow() _accessToken = _mapboxConfig.AccessToken; _memoryCacheSize = (int)_mapboxConfig.MemoryCacheSize; _mbtilesCacheSize = (int)_mapboxConfig.MbTilesCacheSize; + _autoRefreshCache = _mapboxConfig.AutoRefreshCache; _webRequestTimeout = (int)_mapboxConfig.DefaultTimeout; } @@ -232,6 +235,7 @@ private static void SubmitConfiguration() AccessToken = _accessToken, MemoryCacheSize = (uint)_memoryCacheSize, MbTilesCacheSize = (uint)_mbtilesCacheSize, + AutoRefreshCache = _autoRefreshCache, DefaultTimeout = _webRequestTimeout }; _mapboxAccess.SetConfiguration(mapboxConfiguration, false); @@ -531,6 +535,7 @@ void DrawConfigurationSettings() EditorGUI.indentLevel = 2; _memoryCacheSize = EditorGUILayout.IntSlider("Mem Cache Size (# of tiles)", _memoryCacheSize, 0, 1000); _mbtilesCacheSize = EditorGUILayout.IntSlider("MBTiles Cache Size (# of tiles)", _mbtilesCacheSize, 0, 3000); + _autoRefreshCache = EditorGUILayout.Toggle(new GUIContent("Auto refresh cache", "Automatically update tiles in the local ambient cache if there is a newer version available online. ATTENTION: for every tile displayed (even a cached one) a webrequest needs to be made to check for updates."), _autoRefreshCache); _webRequestTimeout = EditorGUILayout.IntField("Default Web Request Timeout (s)", _webRequestTimeout); EditorGUILayout.BeginHorizontal(_horizontalGroup); @@ -549,7 +554,7 @@ void DrawConfigurationSettings() void DrawExampleLinks() { EditorGUI.BeginDisabledGroup(_currentTokenStatus != MapboxTokenStatus.TokenValid - || _validating); + || _validating); if (_currentTokenStatus == MapboxTokenStatus.TokenValid) { @@ -581,7 +586,7 @@ void DrawExampleLinks() private void OpenAndPlayScene() { - if( EditorApplication.isPlaying) + if (EditorApplication.isPlaying) { return; } diff --git a/sdkproject/Assets/Mapbox/Unity/MapboxAccess.cs b/sdkproject/Assets/Mapbox/Unity/MapboxAccess.cs index 9bf08ac1a..443d3cec6 100644 --- a/sdkproject/Assets/Mapbox/Unity/MapboxAccess.cs +++ b/sdkproject/Assets/Mapbox/Unity/MapboxAccess.cs @@ -91,10 +91,21 @@ public void SetConfiguration(MapboxConfiguration configuration, bool throwExecpt ConfigureTelemetry(); } + /// - /// Clear all existing tile caches. Deletes MBTiles database files. + /// Deprecated. Use 'ClearSceneCache' or 'ClearAllCacheFiles' instead. /// + [Obsolete("Deprecated. Use 'ClearSceneCache' or 'ClearAllCacheFiles' instead.")] public void ClearCache() + { + ClearSceneCache(); + } + + + /// + /// Clear all existing tile caches. Deletes MBTiles database files. + /// + public void ClearSceneCache() { CachingWebFileSource cwfs = _fileSource as CachingWebFileSource; if (null != cwfs) @@ -104,6 +115,27 @@ public void ClearCache() } + public void ClearAllCacheFiles() + { + // call ClearSceneCache to close any connections that might be open + ClearSceneCache(); + + string cacheDirectory = Path.Combine(Application.persistentDataPath, "cache"); + if (!Directory.Exists(cacheDirectory)) { return; } + + foreach (var file in Directory.GetFiles(cacheDirectory)) + { + try + { + File.Delete(file); + } + catch (Exception deleteEx) + { + Debug.LogErrorFormat("Could not delete [{0}]: {1}", file, deleteEx); + } + } + } + /// /// Loads the access token from Resources folder. /// @@ -122,7 +154,7 @@ private void LoadAccessToken() void ConfigureFileSource() { - _fileSource = new CachingWebFileSource(_configuration.AccessToken) + _fileSource = new CachingWebFileSource(_configuration.AccessToken, _configuration.AutoRefreshCache) .AddCache(new MemoryCache(_configuration.MemoryCacheSize)) #if !UNITY_WEBGL .AddCache(new MbTilesCache(_configuration.MbTilesCacheSize)) @@ -271,5 +303,6 @@ public class MapboxConfiguration public uint MemoryCacheSize = 500; public uint MbTilesCacheSize = 2000; public int DefaultTimeout = 30; + public bool AutoRefreshCache = false; } } diff --git a/sdkproject/Assets/Mapbox/Unity/MeshGeneration/Factories/VectorTileFactory.cs b/sdkproject/Assets/Mapbox/Unity/MeshGeneration/Factories/VectorTileFactory.cs index f7b0afb66..4f286ddc3 100644 --- a/sdkproject/Assets/Mapbox/Unity/MeshGeneration/Factories/VectorTileFactory.cs +++ b/sdkproject/Assets/Mapbox/Unity/MeshGeneration/Factories/VectorTileFactory.cs @@ -85,8 +85,7 @@ internal override void OnRegistered(UnityTile tile) { var vectorTile = new VectorTile(); tile.AddTile(vectorTile); - - + vectorTile.Initialize(_fileSource, tile.CanonicalTileId, _mapId, () => { if (tile == null) @@ -119,6 +118,19 @@ internal override void OnRegistered(UnityTile tile) } else { + if (vectorTile.CurrentState == Tile.State.Updated) + { + foreach (var vis in Visualizers) + { + vis.UnregisterTile(tile); + } + + //foreach (Transform t in tile.transform) + //{ + // Destroy(t.gameObject); + //} + } + CreateMeshes(tile); } }); diff --git a/sdkproject/Assets/Mapbox/Unity/MeshGeneration/Modifiers/MergedModifierStack.cs b/sdkproject/Assets/Mapbox/Unity/MeshGeneration/Modifiers/MergedModifierStack.cs index bef14fac5..9d30f5737 100644 --- a/sdkproject/Assets/Mapbox/Unity/MeshGeneration/Modifiers/MergedModifierStack.cs +++ b/sdkproject/Assets/Mapbox/Unity/MeshGeneration/Modifiers/MergedModifierStack.cs @@ -67,7 +67,10 @@ public override void OnUnregisterTile(UnityTile tile) _counter = _activeObjects[tile].Count; for (int i = 0; i < _counter; i++) { - _activeObjects[tile][i].GameObject.SetActive(false); + if (null != _activeObjects[tile][i].GameObject) + { + _activeObjects[tile][i].GameObject.SetActive(false); + } _pool.Put(_activeObjects[tile][i]); } _activeObjects[tile].Clear(); diff --git a/sdkproject/Assets/Mapbox/Unity/MeshGeneration/Modifiers/ModifierStack.cs b/sdkproject/Assets/Mapbox/Unity/MeshGeneration/Modifiers/ModifierStack.cs index 68cc974ec..ce8aeaedc 100644 --- a/sdkproject/Assets/Mapbox/Unity/MeshGeneration/Modifiers/ModifierStack.cs +++ b/sdkproject/Assets/Mapbox/Unity/MeshGeneration/Modifiers/ModifierStack.cs @@ -84,7 +84,10 @@ public override void OnUnregisterTile(UnityTile tile) { item.OnPoolItem(_activeObjects[tile][i]); } - _activeObjects[tile][i].GameObject.SetActive(false); + if (null != _activeObjects[tile][i].GameObject) + { + _activeObjects[tile][i].GameObject.SetActive(false); + } _pool.Put(_activeObjects[tile][i]); } _activeObjects[tile].Clear(); diff --git a/sdkproject/Assets/Mapbox/Unity/Utilities/HTTPRequest.cs b/sdkproject/Assets/Mapbox/Unity/Utilities/HTTPRequest.cs index df67bc3b8..b24287b97 100644 --- a/sdkproject/Assets/Mapbox/Unity/Utilities/HTTPRequest.cs +++ b/sdkproject/Assets/Mapbox/Unity/Utilities/HTTPRequest.cs @@ -17,18 +17,45 @@ namespace Mapbox.Unity.Utilities using UnityEditor; #endif + public enum HttpRequestType + { + Get, + Head + } + + internal sealed class HTTPRequest : IAsyncRequest { + private UnityWebRequest _request; + private HttpRequestType _requestType; + private int _timeout; private readonly Action _callback; public bool IsCompleted { get; private set; } - public HTTPRequest(string url, Action callback, int timeout) + public HttpRequestType RequestType { get { return _requestType; } } + + // TODO: simplify timeout for Unity 5.6+ + // https://docs.unity3d.com/ScriptReference/Networking.UnityWebRequest-timeout.html + public HTTPRequest(string url, Action callback, int timeout, HttpRequestType requestType = HttpRequestType.Get) { - //UnityEngine.Debug.Log("HTTPRequest: " + url); IsCompleted = false; - _request = UnityWebRequest.Get(url); + _requestType = requestType; + + switch (_requestType) + { + case HttpRequestType.Get: + _request = UnityWebRequest.Get(url); + break; + case HttpRequestType.Head: + _request = UnityWebRequest.Head(url); + break; + default: + _request = UnityWebRequest.Get(url); + break; + } + _request.timeout = timeout; _callback = callback;