This commit is contained in:
runette committed 2024-04-22 15:52:51 +01:00
commit e0afef912e
1285 files changed
+83219

No files matched your search

@@ -0,0 +1,19 @@
namespace Mapbox.Platform.Cache
{
using System;
public class CacheItem
{
/// <summary> Raw response data- </summary>
public byte[] Data;
/// <summary> UTC ticks when item was added to the cache. </summary>
public long AddedToCacheTicksUtc;
/// <summary> ETag value of API response. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag </summary>
public string ETag;
/// <summary> 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 </summary>
public DateTime? LastModified;
}
}
@@ -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:
@@ -0,0 +1,321 @@
namespace Mapbox.Platform.Cache
{
using System;
using Mapbox.Platform;
using System.Collections.Generic;
using Mapbox.Unity.Utilities;
using Mapbox.Map;
using System.Collections;
using System.Linq;
public class CachingWebFileSource : IFileSource, IDisposable
{
#if MAPBOX_DEBUG_CACHE
private string _className;
#endif
private bool _disposed;
private List<ICache> _caches = new List<ICache>();
private string _accessToken;
private Func<string> _getMapsSkuToken;
private bool _autoRefreshCache;
public CachingWebFileSource(string accessToken, Func<string> getMapsSkuToken, bool autoRefreshCache)
{
#if MAPBOX_DEBUG_CACHE
_className = this.GetType().Name;
#endif
_accessToken = accessToken;
_getMapsSkuToken = getMapsSkuToken;
_autoRefreshCache = autoRefreshCache;
}
#region idisposable
~CachingWebFileSource()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposeManagedResources)
{
if (!_disposed)
{
if (disposeManagedResources)
{
for (int i = 0; i < _caches.Count; i++)
{
IDisposable cache = _caches[i] as IDisposable;
if (null != cache)
{
cache.Dispose();
cache = null;
}
}
}
_disposed = true;
}
}
#endregion
/// <summary>
/// Add an ICache instance
/// </summary>
/// <param name="cache">Implementation of ICache</param>
/// <returns></returns>
public CachingWebFileSource AddCache(ICache cache)
{
// don't add cache when cache size is 0
if (0 == cache.MaxCacheSize)
{
return this;
}
_caches.Add(cache);
return this;
}
/// <summary>
/// Clear all caches
/// </summary>
public void Clear()
{
foreach (var cache in _caches)
{
cache.Clear();
}
}
public void ReInit() {
foreach (var cache in _caches)
{
cache.ReInit();
}
}
public IAsyncRequest Request(
string uri
, Action<Response> callback
, int timeout = 10
, CanonicalTileId tileId = new CanonicalTileId()
, string tilesetId = null
)
{
if (string.IsNullOrEmpty(tilesetId))
{
throw new Exception("Cannot cache without a tileset id");
}
CacheItem cachedItem = null;
// go through existing caches and check if we already have the requested tile available
foreach (var cache in _caches)
{
cachedItem = cache.Get(tilesetId, tileId);
if (null != cachedItem)
{
break;
}
}
var uriBuilder = new UriBuilder(uri);
if (!string.IsNullOrEmpty(_accessToken))
{
string accessTokenQuery = "access_token=" + _accessToken;
string mapsSkuToken = "sku=" + _getMapsSkuToken();
if (uriBuilder.Query != null && uriBuilder.Query.Length > 1)
{
uriBuilder.Query = uriBuilder.Query.Substring(1) + "&" + accessTokenQuery + "&" + mapsSkuToken;
}
else
{
uriBuilder.Query = accessTokenQuery + "&" + mapsSkuToken;
}
}
string finalUrl = uriBuilder.ToString();
#if MAPBOX_DEBUG_CACHE
string methodName = _className + "." + new System.Diagnostics.StackFrame().GetMethod().Name;
#endif
// if tile was available call callback with it, propagate to all other caches and check if a newer one is available
if (null != cachedItem)
{
#if MAPBOX_DEBUG_CACHE
UnityEngine.Debug.LogFormat("{0} {1} {2} {3}", methodName, tilesetId, tileId, null != cachedItem.Data ? cachedItem.Data.Length.ToString() : "cachedItem.Data is NULL");
#endif
// 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(tilesetId, tileId, cachedItem, false);
}
}
else
{
// 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);
}
}
, timeout
, HttpRequestType.Head
);
}
return new MemoryCacheAsyncRequest(uri);
}
else
{
// requested tile is not in any of the caches yet, get it
#if MAPBOX_DEBUG_CACHE
UnityEngine.Debug.LogFormat("{0} {1} {2} not cached", methodName, tilesetId, tileId);
#endif
return requestTileAndCache(finalUrl, tilesetId, tileId, timeout, callback);
}
}
private IAsyncRequest requestTileAndCache(string url, string tilesetId, CanonicalTileId tileId, int timeout, Action<Response> callback)
{
return IAsyncRequestFactory.CreateRequest(
url,
(Response r) =>
{
// if the request was successful add tile to all caches
if (!r.HasError && null != r.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"];
}
// 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(
tilesetId
, tileId
, new CacheItem()
{
Data = r.Data,
ETag = eTag,
LastModified = lastModified
}
, true // force insert/update
);
}
}
if (null != callback)
{
r.IsUpdate = true;
callback(r);
}
}, timeout);
}
class MemoryCacheAsyncRequest : IAsyncRequest
{
public string RequestUrl { get; private set; }
public MemoryCacheAsyncRequest(string requestUrl)
{
RequestUrl = requestUrl;
}
public bool IsCompleted
{
get
{
return true;
}
}
public HttpRequestType RequestType { get { return HttpRequestType.Get; } }
public void Cancel()
{
// Empty. We can't cancel an instantaneous response.
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 149bb7da83149324dab3982be7bc8cca
timeCreated: 1494581732
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,53 @@
namespace Mapbox.Platform.Cache
{
using Mapbox.Map;
using System;
public interface ICache
{
/// <summary>
/// Maximum number of tiles to store
/// </summary>
uint MaxCacheSize { get; }
/// <summary>
/// Add tile data to the cache
/// </summary>
/// <param name="tilesetId">Tile set name</param>
/// <param name="tileId</param>
/// <param name="item">Item to cache</param>
/// <param name="replaceIfExists">Force insert even if item already exists.</param>
void Add(string tilesetId, CanonicalTileId tileId, CacheItem item, bool replaceIfExists);
/// <summary>
/// Get tile
/// </summary>
/// <param name="tilesetId"></param>
/// <param name="tileId"></param>
/// <returns>byte[] with tile data. Null if requested tile is not in cache</returns>
CacheItem Get(string tilesetId, CanonicalTileId tileId);
/// <summary>Clear cache for all tile sets</summary>
void Clear();
/// <summary>
/// Clear cache for one tile set
/// </summary>
/// <param name="tilesetId"></param>
void Clear(string tilesetId);
/// <summary>
/// Reinitialize cache. Might be needed after 'Clear', eg for SQLiteCache
/// </summary>
void ReInit();
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4ebb2d197f248a7469620904e2adb43e
timeCreated: 1494580896
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,112 @@
using Mapbox.Map;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Mapbox.Platform.Cache
{
public class MemoryCache : ICache
{
// TODO: add support for disposal strategy (timestamp, distance, etc.)
public MemoryCache(uint maxCacheSize)
{
#if MAPBOX_DEBUG_CACHE
_className = this.GetType().Name;
#endif
_maxCacheSize = maxCacheSize;
_cachedResponses = new Dictionary<string, CacheItem>();
}
#if MAPBOX_DEBUG_CACHE
private string _className;
#endif
private uint _maxCacheSize;
private object _lock = new object();
private Dictionary<string, CacheItem> _cachedResponses;
public uint MaxCacheSize
{
get { return _maxCacheSize; }
}
public void ReInit()
{
_cachedResponses = new Dictionary<string, CacheItem>();
}
public void Add(string mapdId, CanonicalTileId tilesetId, CacheItem item, bool forceInsert)
{
string key = mapdId + "||" + tilesetId;
lock (_lock)
{
if (_cachedResponses.Count >= _maxCacheSize)
{
_cachedResponses.Remove(_cachedResponses.OrderBy(c => c.Value.AddedToCacheTicksUtc).First().Key);
}
// TODO: forceInsert
if (!_cachedResponses.ContainsKey(key))
{
item.AddedToCacheTicksUtc = DateTime.UtcNow.Ticks;
_cachedResponses.Add(key, item);
}
}
}
public CacheItem Get(string tilesetId, CanonicalTileId tileId)
{
string key = tilesetId + "||" + tileId;
#if MAPBOX_DEBUG_CACHE
string methodName = _className + "." + new System.Diagnostics.StackFrame().GetMethod().Name;
UnityEngine.Debug.LogFormat("{0} {1}", methodName, key);
#endif
lock (_lock)
{
if (!_cachedResponses.ContainsKey(key))
{
return null;
}
return _cachedResponses[key];
}
}
public void Clear()
{
lock (_lock)
{
_cachedResponses.Clear();
}
}
public void Clear(string tilesetId)
{
lock (_lock)
{
tilesetId += "||";
List<string> toDelete = _cachedResponses.Keys.Where(k => k.Contains(tilesetId)).ToList();
foreach (string key in toDelete)
{
_cachedResponses.Remove(key);
}
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2bd70b309ec2fd645af3e189d1d7ea5f
timeCreated: 1494580911
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: c6f1c408c0acc43538b678d17f6ea53d
folderAsset: yes
timeCreated: 1497883478
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,485 @@
namespace Mapbox.Platform.Cache
{
using Mapbox.Map;
using Mapbox.Utils;
using SQLite4Unity3d;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
public class SQLiteCache : ICache, IDisposable
{
/// <summary>
/// maximum number tiles that get cached
/// </summary>
public uint MaxCacheSize { get { return _maxTileCount; } }
/// <summary>
/// Check cache size every n inserts
/// </summary>
public uint PruneCacheDelta { get { return _pruneCacheDelta; } }
#if MAPBOX_DEBUG_CACHE
private string _className;
#endif
private bool _disposed;
private string _dbName;
private string _dbPath;
private SQLiteConnection _sqlite;
private readonly uint _maxTileCount;
/// <summary>check cache size only every '_pruneCacheDelta' calls to 'Add()' to avoid being too chatty with the database</summary>
private const int _pruneCacheDelta = 20;
/// <summary>counter to keep track of calls to `Add()`</summary>
private int _pruneCacheCounter = 0;
private object _lock = new object();
public SQLiteCache(uint? maxTileCount = null, string dbName = "cache.db")
{
_maxTileCount = maxTileCount ?? 3000;
_dbName = dbName;
init();
}
#region idisposable
~SQLiteCache()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposeManagedResources)
{
if (!_disposed)
{
if (disposeManagedResources)
{
if (null != _sqlite)
{
_sqlite.Execute("VACUUM;"); // compact db to keep file size small
_sqlite.Close();
_sqlite.Dispose();
_sqlite = null;
}
}
_disposed = true;
}
}
#endregion
private void init()
{
#if MAPBOX_DEBUG_CACHE
_className = this.GetType().Name;
#endif
openOrCreateDb(_dbName);
//hrmpf: multiple PKs not supported by sqlite.net
//https://github.com/praeclarum/sqlite-net/issues/282
//do it via plain SQL
List<SQLiteConnection.ColumnInfo> colInfoTileset = _sqlite.GetTableInfo(typeof(tilesets).Name);
if (0 == colInfoTileset.Count)
{
string cmdCreateTableTilesets = @"CREATE TABLE tilesets(
id INTEGER PRIMARY KEY ASC AUTOINCREMENT NOT NULL UNIQUE,
name STRING NOT NULL
);";
_sqlite.Execute(cmdCreateTableTilesets);
string cmdCreateIdxNames = @"CREATE UNIQUE INDEX idx_names ON tilesets (name ASC);";
_sqlite.Execute(cmdCreateIdxNames);
}
List<SQLiteConnection.ColumnInfo> colInfoTiles = _sqlite.GetTableInfo(typeof(tiles).Name);
if (0 == colInfoTiles.Count)
{
string cmdCreateTableTiles = @"CREATE TABLE tiles(
tile_set INTEGER REFERENCES tilesets (id) ON DELETE CASCADE ON UPDATE CASCADE,
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(
tile_set ASC,
zoom_level ASC,
tile_column ASC,
tile_row ASC
)
);";
_sqlite.Execute(cmdCreateTableTiles);
string cmdIdxTileset = "CREATE INDEX idx_tileset ON tiles (tile_set ASC);";
_sqlite.Execute(cmdIdxTileset);
string cmdIdxTimestamp = "CREATE INDEX idx_timestamp ON tiles (timestamp ASC);";
_sqlite.Execute(cmdIdxTimestamp);
}
// some pragmas to speed things up a bit :-)
// inserting 1,000 tiles takes 1-2 sec as opposed to ~20 sec
string[] cmds = new string[]
{
"PRAGMA synchronous=OFF",
"PRAGMA count_changes=OFF",
"PRAGMA journal_mode=MEMORY",
"PRAGMA temp_store=MEMORY"
};
foreach (var cmd in cmds)
{
try
{
_sqlite.Execute(cmd);
}
catch (SQLiteException ex)
{
// workaround for sqlite.net's exeception:
// https://stackoverflow.com/a/23839503
if (ex.Result != SQLite3.Result.Row)
{
UnityEngine.Debug.LogErrorFormat("{0}: {1}", cmd, ex);
// TODO: when mapbox-sdk-cs gets backported to its own repo -> throw
//throw; // to throw or not to throw???
}
}
}
}
private void openOrCreateDb(string dbName)
{
_dbPath = GetFullDbPath(dbName);
_sqlite = new SQLiteConnection(_dbPath, SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create);
}
/// <summary>
/// <para>Reinitialize cache.</para>
/// <para>This is needed after 'Clear()' to recreate the cache database.</para>
/// <para>And has been implemented on purpose to not hold on to any references to the cache directory after 'Clear()'</para>
/// </summary>
public void ReInit()
{
if (null != _sqlite)
{
_sqlite.Dispose();
_sqlite = null;
}
init();
}
public static string GetFullDbPath(string dbName)
{
string dbPath = Path.Combine(Application.persistentDataPath, "cache");
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN || UNITY_WSA
dbPath = Path.GetFullPath(dbPath);
#endif
if (!Directory.Exists(dbPath)) { Directory.CreateDirectory(dbPath); }
dbPath = Path.Combine(dbPath, dbName);
return dbPath;
}
public void Add(string tilesetName, CanonicalTileId tileId, CacheItem item, bool forceInsert = false)
{
#if MAPBOX_DEBUG_CACHE
string methodName = _className + "." + new System.Diagnostics.StackFrame().GetMethod().Name;
UnityEngine.Debug.LogFormat("{0} {1} {2} forceInsert:{3}", methodName, tileset, tileId, forceInsert);
#endif
try
{
// tile exists and we don't want to overwrite -> exit early
if (
TileExists(tilesetName, tileId)
&& !forceInsert
)
{
return;
}
int? tilesetId = null;
lock (_lock)
{
tilesetId = getTilesetId(tilesetName);
if (!tilesetId.HasValue)
{
tilesetId = insertTileset(tilesetName);
}
}
if (tilesetId < 0)
{
Debug.LogErrorFormat("could not get tilesetID for [{0}] tile: {1}", tilesetName, tileId);
return;
}
int rowsAffected = _sqlite.InsertOrReplace(new tiles
{
tile_set = tilesetId.Value,
zoom_level = tileId.Z,
tile_column = tileId.X,
tile_row = tileId.Y,
tile_data = item.Data,
timestamp = (int)UnixTimestampUtils.To(DateTime.Now),
etag = item.ETag
});
if (1 != rowsAffected)
{
throw new Exception(string.Format("tile [{0} / {1}] was not inserted, rows affected:{2}", tilesetName, tileId, rowsAffected));
}
}
catch (Exception ex)
{
Debug.LogErrorFormat("Error inserting {0} {1} {2} ", tilesetName, tileId, ex);
}
// update counter only when new tile gets inserted
if (!forceInsert)
{
_pruneCacheCounter++;
}
if (0 == _pruneCacheCounter % _pruneCacheDelta)
{
_pruneCacheCounter = 0;
prune();
}
}
private void prune()
{
long tileCnt = _sqlite.ExecuteScalar<long>("SELECT COUNT(zoom_level) FROM tiles");
if (tileCnt < _maxTileCount) { return; }
long toDelete = tileCnt - _maxTileCount;
#if MAPBOX_DEBUG_CACHE
string methodName = _className + "." + new System.Diagnostics.StackFrame().GetMethod().Name;
Debug.LogFormat("{0} {1} about to prune()", methodName, _tileset);
#endif
try
{
// no 'ORDER BY' or 'LIMIT' possible if sqlite hasn't been compiled with 'SQLITE_ENABLE_UPDATE_DELETE_LIMIT'
// https://sqlite.org/compile.html#enable_update_delete_limit
_sqlite.Execute("DELETE FROM tiles WHERE rowid IN ( SELECT rowid FROM tiles ORDER BY timestamp ASC LIMIT ? );", toDelete);
}
catch (Exception ex)
{
Debug.LogErrorFormat("error pruning: {0}", ex);
}
}
/// <summary>
/// Returns the tile data, otherwise null
/// </summary>
/// <param name="tileId">Canonical tile id to identify the tile</param>
/// <returns>tile data as byte[], if tile is not cached returns null</returns>
public CacheItem Get(string tilesetName, CanonicalTileId tileId)
{
#if MAPBOX_DEBUG_CACHE
string methodName = _className + "." + new System.Diagnostics.StackFrame().GetMethod().Name;
Debug.LogFormat("{0} {1} {2}", methodName, _tileset, tileId);
#endif
tiles tile = null;
try
{
int? tilesetId = getTilesetId(tilesetName);
if (!tilesetId.HasValue)
{
return null;
}
tile = _sqlite
.Table<tiles>()
.Where(t =>
t.tile_set == tilesetId.Value
&& t.zoom_level == tileId.Z
&& t.tile_column == tileId.X
&& t.tile_row == tileId.Y
)
.FirstOrDefault();
}
catch (Exception ex)
{
Debug.LogErrorFormat("error getting tile {1} {2} from cache{0}{3}", Environment.NewLine, tilesetName, tileId, ex);
return null;
}
if (null == tile)
{
return null;
}
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
};
}
/// <summary>
/// Check if tile exists
/// </summary>
/// <param name="tileId">Canonical tile id</param>
/// <returns>True if tile exists</returns>
public bool TileExists(string tilesetName, CanonicalTileId tileId)
{
int? tilesetId = getTilesetId(tilesetName);
if (!tilesetId.HasValue)
{
return false;
}
return null != _sqlite
.Table<tiles>()
.Where(t =>
t.tile_set == tilesetId.Value
&& t.zoom_level == tileId.Z
&& t.tile_column == tileId.X
&& t.tile_row == tileId.Y
)
.FirstOrDefault();
}
private int insertTileset(string tilesetName)
{
try
{
_sqlite.BeginTransaction(true);
tilesets newTileset = new tilesets { name = tilesetName };
int rowsAffected = _sqlite.Insert(newTileset);
if (1 != rowsAffected)
{
throw new Exception(string.Format("tileset [{0}] was not inserted, rows affected:{1}", tilesetName, rowsAffected));
}
return newTileset.id;
}
catch (Exception ex)
{
Debug.LogErrorFormat("could not insert tileset [{0}]: {1}", tilesetName, ex);
return -1;
}
finally
{
_sqlite.Commit();
}
}
private int? getTilesetId(string tilesetName)
{
tilesets tileset = _sqlite
.Table<tilesets>()
.Where(ts => ts.name.Equals(tilesetName))
.FirstOrDefault();
return null == tileset ? (int?)null : tileset.id;
}
/// <summary>
/// FOR INTERNAL DEBUGGING ONLY - DON'T RELY ON IN PRODUCTION
/// </summary>
/// <param name="tilesetName"></param>
/// <returns></returns>
public long TileCount(string tilesetName)
{
int? tilesetId = getTilesetId(tilesetName);
if (!tilesetId.HasValue) { return 0; }
return _sqlite
.Table<tiles>()
.Where(t => t.tile_set == tilesetId.Value)
.LongCount();
}
/// <summary>
/// Clear cache for one tile set
/// </summary>
/// <param name="tilesetName"></param>
public void Clear(string tilesetName)
{
int? tilesetId = getTilesetId(tilesetName);
if (!tilesetId.HasValue) { return; }
//just delete on table 'tilesets', we've setup cascading which should take care of tabls 'tiles'
_sqlite.Delete<tilesets>(tilesetId.Value);
}
/// <summary>
/// <para>Delete the database file.</para>
/// <para>Call 'ReInit()' if you intend to continue using the cache after 'Clear()!</para>
/// </summary>
public void Clear()
{
//already disposed
if (null == _sqlite) { return; }
_sqlite.Close();
_sqlite.Dispose();
_sqlite = null;
Debug.LogFormat("deleting {0}", _dbPath);
// try several times in case SQLite needs a bit more time to dispose
for (int i = 0; i < 5; i++)
{
try
{
File.Delete(_dbPath);
return;
}
catch
{
#if !WINDOWS_UWP
System.Threading.Thread.Sleep(100);
#else
System.Threading.Tasks.Task.Delay(100).Wait();
#endif
}
}
// if we got till here, throw on last try
File.Delete(_dbPath);
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 8aeee08edfa6c46648cf1dc623b72a1e
timeCreated: 1497883479
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,40 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using SQLite4Unity3d;
namespace Mapbox.Platform.Cache
{
/// <summary>
/// Don't change the class name: sqlite-net uses it for table creation
/// </summary>
public class tiles
{
public int tile_set { get; set; }
//hrmpf: multiple PKs not supported by sqlite.net
//https://github.com/praeclarum/sqlite-net/issues/282
//TODO: do it via plain SQL
//[PrimaryKey]
public int zoom_level { get; set; }
//[PrimaryKey]
public long tile_column { get; set; }
//[PrimaryKey]
public long tile_row { get; set; }
public byte[] tile_data { get; set; }
/// <summary>Unix epoch for simple FIFO pruning </summary>
public int timestamp { get; set; }
/// <summary> ETag Header value of the reponse for auto updating cache</summary>
public string etag { get; set; }
/// <summary>Last-Modified header value of API response. Not all APIs populate it, will be -1 in that case. </summary>
public int? lastmodified { get; set; }
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 7d8f9ca4f2240481ba4023eff7781487
timeCreated: 1497883479
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,23 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using SQLite4Unity3d;
namespace Mapbox.Platform.Cache
{
/// <summary>
/// Don't change the class name: sqlite-net uses it for table creation
/// </summary>
public class tilesets
{
//hrmpf: multiple PKs not supported by sqlite.net
//https://github.com/praeclarum/sqlite-net/issues/282
//TODO: do it via plain SQL
[PrimaryKey, AutoIncrement]
public int id { get; set; }
public string name { get; set; }
}
}
@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: bb0cc61c516c1984f8ab3ac0ce3c3e64
timeCreated: 1527515199
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: