diff --git a/FileDbCache/Shared/FileDbCache.cs b/FileDbCache/Shared/FileDbCache.cs new file mode 100644 index 00000000..d278375d --- /dev/null +++ b/FileDbCache/Shared/FileDbCache.cs @@ -0,0 +1,141 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2019 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using FileDbNs; +using System; +using System.Diagnostics; +using System.IO; + +namespace MapControl.Caching +{ + /// + /// Image cache implementation based on FileDb, a free and simple No-SQL database by EzTools Software. + /// See http://www.eztools-software.com/tools/filedb/. + /// + public sealed partial class FileDbCache : IDisposable + { + private const string keyField = "Key"; + private const string valueField = "Value"; + private const string expiresField = "Expires"; + + private readonly FileDb fileDb = new FileDb() { AutoFlush = true }; + private readonly string dbPath; + + public void Dispose() + { + fileDb.Dispose(); + } + + public void Clean() + { + if (fileDb.IsOpen) + { + int deleted = fileDb.DeleteRecords(new FilterExpression(expiresField, DateTime.UtcNow, ComparisonOperatorEnum.LessThan)); + + if (deleted > 0) + { + Debug.WriteLine("FileDbCache: Deleted {0} expired items", deleted); + fileDb.Clean(); + } + } + } + + private void Open() + { + if (!fileDb.IsOpen) + { + try + { + fileDb.Open(dbPath); + Debug.WriteLine("FileDbCache: Opened database " + dbPath); + + Clean(); + } + catch + { + CreateDatabase(); + } + } + } + + private void Close() + { + if (fileDb.IsOpen) + { + fileDb.Close(); + } + } + + private void CreateDatabase() + { + Close(); + + if (File.Exists(dbPath)) + { + File.Delete(dbPath); + } + else + { + Directory.CreateDirectory(Path.GetDirectoryName(dbPath)); + } + + fileDb.Create(dbPath, new Field[] + { + new Field(keyField, DataTypeEnum.String) { IsPrimaryKey = true }, + new Field(valueField, DataTypeEnum.Byte) { IsArray = true }, + new Field(expiresField, DataTypeEnum.DateTime) + }); + + Debug.WriteLine("FileDbCache: Created database " + dbPath); + } + + private Record GetRecordByKey(string key) + { + Record record = null; + + if (fileDb.IsOpen) + { + try + { + record = fileDb.GetRecordByKey(key, new string[] { valueField, expiresField }, false); + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache.GetRecordByKey(\"{0}\"): {1}", key, ex.Message); + } + } + + return record; + } + + private void AddOrUpdateRecord(string key, byte[] value, DateTime expiration) + { + if (fileDb.IsOpen) + { + var fieldValues = new FieldValues(3); + fieldValues.Add(valueField, value); + fieldValues.Add(expiresField, expiration); + + try + { + if (fileDb.GetRecordByKey(key, new string[0], false) != null) + { + fileDb.UpdateRecordByKey(key, fieldValues); + } + else + { + fieldValues.Add(keyField, key); + fileDb.AddRecord(fieldValues); + } + + //Debug.WriteLine("FileDbCache: Writing \"{0}\", Expires {1}", key, imageCacheItem.Expiration.ToLocalTime()); + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache.AddOrUpdateRecord(\"{0}\"): {1}", key, ex.Message); return; + } + } + } + } +} diff --git a/FileDbCache/UWP/FileDbCache.UWP.cs b/FileDbCache/UWP/FileDbCache.UWP.cs new file mode 100644 index 00000000..eac150ee --- /dev/null +++ b/FileDbCache/UWP/FileDbCache.UWP.cs @@ -0,0 +1,76 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2019 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.IO; +using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; +using Windows.Storage; +using Windows.Storage.Streams; +using Windows.UI.Xaml; + +namespace MapControl.Caching +{ + public partial class FileDbCache : IImageCache + { + public FileDbCache(StorageFolder folder, string fileName = "TileCache.fdb") + { + if (folder == null) + { + throw new ArgumentNullException("The parameter folder must not be null."); + } + + if (string.IsNullOrEmpty(fileName)) + { + throw new ArgumentNullException("The parameter fileName must not be null."); + } + + dbPath = Path.Combine(folder.Path, "TileCache.fdb"); + + Open(); + + Application.Current.Resuming += (s, e) => Open(); + Application.Current.Suspending += (s, e) => Close(); + } + + public Task GetAsync(string key) + { + if (key == null) + { + throw new ArgumentNullException("The parameter key must not be null."); + } + + return Task.Run(() => + { + var record = GetRecordByKey(key); + + if (record == null) + { + return null; + } + + return new ImageCacheItem + { + Buffer = ((byte[])record[0]).AsBuffer(), + Expiration = (DateTime)record[1] + }; + }); + } + + public Task SetAsync(string key, IBuffer buffer, DateTime expiration) + { + if (key == null) + { + throw new ArgumentNullException("The parameter key must not be null."); + } + + if (buffer == null) + { + throw new ArgumentNullException("The parameter buffer must not be null."); + } + + return Task.Run(() => AddOrUpdateRecord(key, buffer.ToArray(), expiration)); + } + } +} diff --git a/FileDbCache/UWP/FileDbCache.UWP.csproj b/FileDbCache/UWP/FileDbCache.UWP.csproj index e80fd41a..19b99d0e 100644 --- a/FileDbCache/UWP/FileDbCache.UWP.csproj +++ b/FileDbCache/UWP/FileDbCache.UWP.csproj @@ -40,7 +40,10 @@ PackageReference - + + FileDbCache.cs + + diff --git a/FileDbCache/UWP/FileDbCache.cs b/FileDbCache/UWP/FileDbCache.cs deleted file mode 100644 index 34eb4be9..00000000 --- a/FileDbCache/UWP/FileDbCache.cs +++ /dev/null @@ -1,247 +0,0 @@ -// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control -// © 2019 Clemens Fischer -// Licensed under the Microsoft Public License (Ms-PL) - -using FileDbNs; -using System; -using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices.WindowsRuntime; -using System.Threading.Tasks; -using Windows.Storage; -using Windows.Storage.Streams; -using Windows.UI.Xaml; - -namespace MapControl.Caching -{ - /// - /// IImageCache implementation based on FileDb, a free and simple No-SQL database by EzTools Software. - /// See http://www.eztools-software.com/tools/filedb/. - /// - public sealed class FileDbCache : IImageCache, IDisposable - { - private const string keyField = "Key"; - private const string valueField = "Value"; - private const string expiresField = "Expires"; - - private readonly FileDb fileDb = new FileDb(); - private readonly string dbPath; - - public FileDbCache(StorageFolder folder, string fileName = "TileCache.fdb", bool autoFlush = true, int autoCleanThreshold = -1) - { - if (folder == null) - { - throw new ArgumentNullException("The parameter folder must not be null."); - } - - if (string.IsNullOrEmpty(fileName)) - { - throw new ArgumentNullException("The parameter fileName must not be null."); - } - - dbPath = Path.Combine(folder.Path, "TileCache.fdb"); - - fileDb.AutoFlush = autoFlush; - fileDb.AutoCleanThreshold = autoCleanThreshold; - - Application.Current.Resuming += (s, e) => Open(); - Application.Current.Suspending += (s, e) => Close(); - - Open(); - } - - public void Dispose() - { - Close(); - } - - public void Clean() - { - if (fileDb.IsOpen) - { - int deleted = fileDb.DeleteRecords(new FilterExpression(expiresField, DateTime.UtcNow, ComparisonOperatorEnum.LessThan)); - - if (deleted > 0) - { - Debug.WriteLine("FileDbCache: Deleted {0} expired items", deleted); - fileDb.Clean(); - } - } - } - - public Task GetAsync(string key) - { - if (key == null) - { - throw new ArgumentNullException("The parameter key must not be null."); - } - - return fileDb.IsOpen ? Task.Run(() => Get(key)) : null; - } - - public Task SetAsync(string key, IBuffer buffer, DateTime expiration) - { - if (key == null) - { - throw new ArgumentNullException("The parameter key must not be null."); - } - - if (buffer == null) - { - throw new ArgumentNullException("The parameter buffer must not be null."); - } - - return Task.Run(() => - { - if (fileDb.IsOpen) - { - var bytes = buffer.ToArray(); - var ok = AddOrUpdateRecord(key, bytes, expiration); - - if (!ok && RepairDatabase()) - { - AddOrUpdateRecord(key, bytes, expiration); - } - } - }); - } - - private void Open() - { - if (!fileDb.IsOpen) - { - try - { - fileDb.Open(dbPath); - Debug.WriteLine("FileDbCache: Opened database with {0} cached items in {1}", fileDb.NumRecords, dbPath); - - Clean(); - } - catch - { - CreateDatabase(); - } - } - } - - private void Close() - { - if (fileDb.IsOpen) - { - fileDb.Close(); - } - } - - private void CreateDatabase() - { - Close(); - - fileDb.Create(dbPath, new Field[] - { - new Field(keyField, DataTypeEnum.String) { IsPrimaryKey = true }, - new Field(valueField, DataTypeEnum.Byte) { IsArray = true }, - new Field(expiresField, DataTypeEnum.DateTime) - }); - - Debug.WriteLine("FileDbCache: Created database " + dbPath); - } - - private bool RepairDatabase() - { - try - { - fileDb.Reindex(); - return true; - } - catch (Exception ex) - { - Debug.WriteLine("FileDbCache: FileDb.Reindex(): " + ex.Message); - } - - try - { - CreateDatabase(); - return true; - } - catch (Exception ex) - { - Debug.WriteLine("FileDbCache: Creating database {0}: {1}", dbPath, ex.Message); - } - - return false; - } - - private ImageCacheItem Get(string key) - { - var fields = new string[] { valueField, expiresField }; - - try - { - var record = fileDb.GetRecordByKey(key, fields, false); - - if (record != null) - { - return new ImageCacheItem - { - Buffer = ((byte[])record[0]).AsBuffer(), - Expiration = (DateTime)record[1] - }; - } - } - catch (Exception ex) - { - Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\"): {1}", key, ex.Message); - } - - return null; - } - - private bool AddOrUpdateRecord(string key, byte[] value, DateTime expiration) - { - var fieldValues = new FieldValues(3); - fieldValues.Add(valueField, value); - fieldValues.Add(expiresField, expiration); - - bool recordExists; - - try - { - recordExists = fileDb.GetRecordByKey(key, new string[0], false) != null; - } - catch (Exception ex) - { - Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\"): {1}", key, ex.Message); - return false; - } - - if (recordExists) - { - try - { - fileDb.UpdateRecordByKey(key, fieldValues); - } - catch (Exception ex) - { - Debug.WriteLine("FileDbCache: FileDb.UpdateRecordByKey(\"{0}\"): {1}", key, ex.Message); - return false; - } - } - else - { - try - { - fieldValues.Add(keyField, key); - fileDb.AddRecord(fieldValues); - } - catch (Exception ex) - { - Debug.WriteLine("FileDbCache: FileDb.AddRecord(\"{0}\"): {1}", key, ex.Message); - return false; - } - } - - //Debug.WriteLine("FileDbCache: Writing \"{0}\", Expires {1}", key, expiration.ToLocalTime()); - return true; - } - } -} diff --git a/FileDbCache/WPF/FileDbCache.WPF.cs b/FileDbCache/WPF/FileDbCache.WPF.cs new file mode 100644 index 00000000..eddc854e --- /dev/null +++ b/FileDbCache/WPF/FileDbCache.WPF.cs @@ -0,0 +1,216 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2019 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.Caching; + +namespace MapControl.Caching +{ + public partial class FileDbCache : ObjectCache + { + public FileDbCache(string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException("The parameter path must not be null or empty."); + } + + if (string.IsNullOrEmpty(Path.GetExtension(path))) + { + path = Path.Combine(path, "TileCache.fdb"); + } + + dbPath = path; + + Open(); + + AppDomain.CurrentDomain.ProcessExit += (s, e) => Dispose(); + } + + public override string Name + { + get { return string.Empty; } + } + + public override DefaultCacheCapabilities DefaultCacheCapabilities + { + get { return DefaultCacheCapabilities.AbsoluteExpirations | DefaultCacheCapabilities.SlidingExpirations; } + } + + public override object this[string key] + { + get { return Get(key); } + set { Set(key, value, null); } + } + + protected override IEnumerator> GetEnumerator() + { + throw new NotSupportedException("FileDbCache does not support the ability to enumerate items."); + } + + public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable keys, string regionName = null) + { + throw new NotSupportedException("FileDbCache does not support the ability to create change monitors."); + } + + public override long GetCount(string regionName = null) + { + if (regionName != null) + { + throw new NotSupportedException("FileDbCache does not support named regions."); + } + + try + { + return fileDb.NumRecords; + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache.GetCount(): " + ex.Message); + } + + return 0; + } + + public override bool Contains(string key, string regionName = null) + { + if (regionName != null) + { + throw new NotSupportedException("FileDbCache does not support named regions."); + } + + if (key == null) + { + throw new ArgumentNullException("The parameter key must not be null."); + } + + try + { + return fileDb.GetRecordByKey(key, new string[0], false) != null; + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache.Contains(\"{0}\"): {1}", key, ex.Message); + } + + return false; + } + + public override object Get(string key, string regionName = null) + { + if (regionName != null) + { + throw new NotSupportedException("FileDbCache does not support named regions."); + } + + if (key == null) + { + throw new ArgumentNullException("The parameter key must not be null."); + } + + var record = GetRecordByKey(key); + + if (record == null) + { + return null; + } + + return new ImageCacheItem + { + Buffer = (byte[])record[0], + Expiration = (DateTime)record[1] + }; + } + + public override CacheItem GetCacheItem(string key, string regionName = null) + { + var value = Get(key, regionName); + + return value != null ? new CacheItem(key, value) : null; + } + + public override IDictionary GetValues(IEnumerable keys, string regionName = null) + { + return keys.ToDictionary(key => key, key => Get(key, regionName)); + } + + public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null) + { + if (regionName != null) + { + throw new NotSupportedException("FileDbCache does not support named regions."); + } + + if (key == null) + { + throw new ArgumentNullException("The parameter key must not be null."); + } + + var imageCacheItem = value as ImageCacheItem; + + if (imageCacheItem == null || imageCacheItem.Buffer == null || imageCacheItem.Buffer.Length == 0) + { + throw new ArgumentException("The parameter value must be an ImageCacheItem with a non-empty Buffer."); + } + + AddOrUpdateRecord(key, imageCacheItem.Buffer, imageCacheItem.Expiration); + } + + public override void Set(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null) + { + Set(key, value, new CacheItemPolicy { AbsoluteExpiration = absoluteExpiration }, regionName); + } + + public override void Set(CacheItem item, CacheItemPolicy policy) + { + Set(item.Key, item.Value, policy, item.RegionName); + } + + public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null) + { + var oldValue = Get(key, regionName); + + Set(key, value, policy); + + return oldValue; + } + + public override object AddOrGetExisting(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null) + { + return AddOrGetExisting(key, value, new CacheItemPolicy { AbsoluteExpiration = absoluteExpiration }, regionName); + } + + public override CacheItem AddOrGetExisting(CacheItem item, CacheItemPolicy policy) + { + var oldItem = GetCacheItem(item.Key, item.RegionName); + + Set(item, policy); + + return oldItem; + } + + public override object Remove(string key, string regionName = null) + { + var oldValue = Get(key, regionName); + + if (oldValue != null) + { + try + { + fileDb.DeleteRecordByKey(key); + } + catch (Exception ex) + { + Debug.WriteLine("FileDbCache.Remove(\"{0}\"): {1}", key, ex.Message); + } + } + + return oldValue; + } + } +} diff --git a/FileDbCache/WPF/FileDbCache.WPF.csproj b/FileDbCache/WPF/FileDbCache.WPF.csproj index 5aa9fb32..751fe391 100644 --- a/FileDbCache/WPF/FileDbCache.WPF.csproj +++ b/FileDbCache/WPF/FileDbCache.WPF.csproj @@ -44,7 +44,10 @@ - + + FileDbCache.cs + + diff --git a/FileDbCache/WPF/FileDbCache.cs b/FileDbCache/WPF/FileDbCache.cs deleted file mode 100644 index 117b44af..00000000 --- a/FileDbCache/WPF/FileDbCache.cs +++ /dev/null @@ -1,392 +0,0 @@ -// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control -// © 2019 Clemens Fischer -// Licensed under the Microsoft Public License (Ms-PL) - -using FileDbNs; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.Caching; - -namespace MapControl.Caching -{ - /// - /// ObjectCache implementation based on FileDb, a free and simple No-SQL database by EzTools Software. - /// See http://www.eztools-software.com/tools/filedb/. - /// - public sealed class FileDbCache : ObjectCache, IDisposable - { - private const string keyField = "Key"; - private const string valueField = "Value"; - private const string expiresField = "Expires"; - - private readonly FileDb fileDb = new FileDb(); - private readonly string dbPath; - - public FileDbCache(string path, bool autoFlush = true, int autoCleanThreshold = -1) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentException("The parameter path must not be null or empty."); - } - - if (string.IsNullOrEmpty(Path.GetExtension(path))) - { - path = Path.Combine(path, "TileCache.fdb"); - } - - dbPath = path; - - fileDb.AutoFlush = autoFlush; - fileDb.AutoCleanThreshold = autoCleanThreshold; - - try - { - fileDb.Open(dbPath, false); - - Debug.WriteLine("FileDbCache: Opened database with {0} cached items in {1}", fileDb.NumRecords, dbPath); - - Clean(); - } - catch - { - CreateDatabase(); - } - - AppDomain.CurrentDomain.ProcessExit += (s, e) => Close(); - } - - public override string Name - { - get { return string.Empty; } - } - - public override DefaultCacheCapabilities DefaultCacheCapabilities - { - get { return DefaultCacheCapabilities.AbsoluteExpirations | DefaultCacheCapabilities.SlidingExpirations; } - } - - public override object this[string key] - { - get { return Get(key); } - set { Set(key, value, null); } - } - - protected override IEnumerator> GetEnumerator() - { - throw new NotSupportedException("FileDbCache does not support the ability to enumerate items."); - } - - public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable keys, string regionName = null) - { - throw new NotSupportedException("FileDbCache does not support the ability to create change monitors."); - } - - public override long GetCount(string regionName = null) - { - if (regionName != null) - { - throw new NotSupportedException("The parameter regionName must be null."); - } - - if (fileDb.IsOpen) - { - try - { - return fileDb.NumRecords; - } - catch (Exception ex) - { - Debug.WriteLine("FileDbCache: FileDb.NumRecords: " + ex.Message); - } - } - - return 0; - } - - public override bool Contains(string key, string regionName = null) - { - if (key == null) - { - throw new ArgumentNullException("The parameter key must not be null."); - } - - if (regionName != null) - { - throw new NotSupportedException("The parameter regionName must be null."); - } - - if (fileDb.IsOpen) - { - try - { - return fileDb.GetRecordByKey(key, new string[0], false) != null; - } - catch (Exception ex) - { - Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\"): {1}", key, ex.Message); - } - } - - return false; - } - - public override object Get(string key, string regionName = null) - { - if (key == null) - { - throw new ArgumentNullException("The parameter key must not be null."); - } - - if (regionName != null) - { - throw new NotSupportedException("The parameter regionName must be null."); - } - - if (fileDb.IsOpen) - { - try - { - var record = fileDb.GetRecordByKey(key, new string[] { valueField, expiresField }, false); - - if (record != null) - { - return new ImageCacheItem - { - Buffer = (byte[])record[0], - Expiration = (DateTime)record[1] - }; - } - } - catch (Exception ex) - { - Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\"): {1}", key, ex.Message); - } - } - - return null; - } - - public override CacheItem GetCacheItem(string key, string regionName = null) - { - var value = Get(key, regionName); - - return value != null ? new CacheItem(key, value) : null; - } - - public override IDictionary GetValues(IEnumerable keys, string regionName = null) - { - return keys.ToDictionary(key => key, key => Get(key, regionName)); - } - - public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null) - { - if (key == null) - { - throw new ArgumentNullException("The parameter key must not be null."); - } - - if (regionName != null) - { - throw new NotSupportedException("The parameter regionName must be null."); - } - - var imageCacheItem = value as ImageCacheItem; - - if (imageCacheItem == null || imageCacheItem.Buffer == null || imageCacheItem.Buffer.Length == 0) - { - throw new NotSupportedException("The parameter value must be an ImageCacheItem with a non-empty Buffer."); - } - - if (fileDb.IsOpen && !AddOrUpdateRecord(key, imageCacheItem) && RepairDatabase()) - { - AddOrUpdateRecord(key, imageCacheItem); - } - } - - public override void Set(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null) - { - Set(key, value, new CacheItemPolicy { AbsoluteExpiration = absoluteExpiration }, regionName); - } - - public override void Set(CacheItem item, CacheItemPolicy policy) - { - Set(item.Key, item.Value, policy, item.RegionName); - } - - public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null) - { - var oldValue = Get(key, regionName); - - Set(key, value, policy); - - return oldValue; - } - - public override object AddOrGetExisting(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null) - { - return AddOrGetExisting(key, value, new CacheItemPolicy { AbsoluteExpiration = absoluteExpiration }, regionName); - } - - public override CacheItem AddOrGetExisting(CacheItem item, CacheItemPolicy policy) - { - var oldItem = GetCacheItem(item.Key, item.RegionName); - - Set(item, policy); - - return oldItem; - } - - public override object Remove(string key, string regionName = null) - { - var oldValue = Get(key, regionName); - - if (oldValue != null) - { - try - { - fileDb.DeleteRecordByKey(key); - } - catch (Exception ex) - { - Debug.WriteLine("FileDbCache: FileDb.DeleteRecordByKey(\"{0}\"): {1}", key, ex.Message); - } - } - - return oldValue; - } - - public void Dispose() - { - Close(); - } - - public void Flush() - { - if (fileDb.IsOpen) - { - fileDb.Flush(); - } - } - - public void Clean() - { - if (fileDb.IsOpen) - { - int deleted = fileDb.DeleteRecords(new FilterExpression(expiresField, DateTime.UtcNow, ComparisonOperatorEnum.LessThan)); - - if (deleted > 0) - { - Debug.WriteLine("FileDbCache: Deleted {0} expired items", deleted); - fileDb.Clean(); - } - } - } - - private void Close() - { - if (fileDb.IsOpen) - { - fileDb.Close(); - } - } - - private void CreateDatabase() - { - Close(); - - if (File.Exists(dbPath)) - { - File.Delete(dbPath); - } - else - { - Directory.CreateDirectory(Path.GetDirectoryName(dbPath)); - } - - fileDb.Create(dbPath, new Field[] - { - new Field(keyField, DataTypeEnum.String) { IsPrimaryKey = true }, - new Field(valueField, DataTypeEnum.Byte) { IsArray = true }, - new Field(expiresField, DataTypeEnum.DateTime) - }); - - Debug.WriteLine("FileDbCache: Created database " + dbPath); - } - - private bool RepairDatabase() - { - try - { - fileDb.Reindex(); - return true; - } - catch (Exception ex) - { - Debug.WriteLine("FileDbCache: FileDb.Reindex(): " + ex.Message); - } - - try - { - CreateDatabase(); - return true; - } - catch (Exception ex) - { - Debug.WriteLine("FileDbCache: Failed creating database {0}: {1}", dbPath, ex.Message); - } - - return false; - } - - private bool AddOrUpdateRecord(string key, ImageCacheItem imageCacheItem) - { - var fieldValues = new FieldValues(3); - fieldValues.Add(valueField, imageCacheItem.Buffer); - fieldValues.Add(expiresField, imageCacheItem.Expiration); - - bool recordExists; - - try - { - recordExists = fileDb.GetRecordByKey(key, new string[0], false) != null; - } - catch (Exception ex) - { - Debug.WriteLine("FileDbCache: FileDb.GetRecordByKey(\"{0}\"): {1}", key, ex.Message); - return false; - } - - if (recordExists) - { - try - { - fileDb.UpdateRecordByKey(key, fieldValues); - } - catch (Exception ex) - { - Debug.WriteLine("FileDbCache: FileDb.UpdateRecordByKey(\"{0}\"): {1}", key, ex.Message); - return false; - } - } - else - { - try - { - fieldValues.Add(keyField, key); - fileDb.AddRecord(fieldValues); - } - catch (Exception ex) - { - Debug.WriteLine("FileDbCache: FileDb.AddRecord(\"{0}\"): {1}", key, ex.Message); - return false; - } - } - - //Debug.WriteLine("FileDbCache: Writing \"{0}\", Expires {1}", key, imageCacheItem.Expiration.ToLocalTime()); - return true; - } - } -} diff --git a/MapControl/WPF/ImageFileCache.WPF.cs b/MapControl/WPF/ImageFileCache.WPF.cs index c860b7b2..2962e2db 100644 --- a/MapControl/WPF/ImageFileCache.WPF.cs +++ b/MapControl/WPF/ImageFileCache.WPF.cs @@ -72,31 +72,31 @@ namespace MapControl.Caching public override bool Contains(string key, string regionName = null) { + if (regionName != null) + { + throw new NotSupportedException("ImageFileCache does not support named regions."); + } + if (key == null) { throw new ArgumentNullException("The parameter key must not be null."); } - if (regionName != null) - { - throw new NotSupportedException("The parameter regionName must be null."); - } - return memoryCache.Contains(key) || FindFile(key) != null; } public override object Get(string key, string regionName = null) { + if (regionName != null) + { + throw new NotSupportedException("ImageFileCache does not support named regions."); + } + if (key == null) { throw new ArgumentNullException("The parameter key must not be null."); } - if (regionName != null) - { - throw new NotSupportedException("The parameter regionName must be null."); - } - var imageCacheItem = memoryCache.Get(key) as ImageCacheItem; if (imageCacheItem == null) @@ -150,21 +150,21 @@ namespace MapControl.Caching public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null) { + if (regionName != null) + { + throw new NotSupportedException("ImageFileCache does not support named regions."); + } + if (key == null) { throw new ArgumentNullException("The parameter key must not be null."); } - if (regionName != null) - { - throw new NotSupportedException("The parameter regionName must be null."); - } - var imageCacheItem = value as ImageCacheItem; if (imageCacheItem == null || imageCacheItem.Buffer == null || imageCacheItem.Buffer.Length == 0) { - throw new NotSupportedException("The parameter value must be an ImageCacheItem with a non-empty Buffer."); + throw new ArgumentException("The parameter value must be an ImageCacheItem with a non-empty Buffer."); } memoryCache.Set(key, imageCacheItem, policy); @@ -232,16 +232,16 @@ namespace MapControl.Caching public override object Remove(string key, string regionName = null) { + if (regionName != null) + { + throw new NotSupportedException("ImageFileCache does not support named regions."); + } + if (key == null) { throw new ArgumentNullException("The parameter key must not be null."); } - if (regionName != null) - { - throw new NotSupportedException("The parameter regionName must be null."); - } - memoryCache.Remove(key); var path = FindFile(key); diff --git a/SQLiteCache/Shared/SQLiteCache.cs b/SQLiteCache/Shared/SQLiteCache.cs new file mode 100644 index 00000000..cc3abea5 --- /dev/null +++ b/SQLiteCache/Shared/SQLiteCache.cs @@ -0,0 +1,75 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2019 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Diagnostics; +#if WINDOWS_UWP +using SQLiteCommand = Microsoft.Data.Sqlite.SqliteCommand; +using SQLiteConnection = Microsoft.Data.Sqlite.SqliteConnection; +#else +using System.Data.SQLite; +#endif + +namespace MapControl.Caching +{ + /// + /// Image cache implementation based on SqLite. + /// + public sealed partial class SQLiteCache : IDisposable + { + private readonly SQLiteConnection connection; + + private static SQLiteConnection Open(string path) + { + var connection = new SQLiteConnection("Data Source=" + path); + connection.Open(); + + using (var command = new SQLiteCommand("create table if not exists items (key text primary key, expiration integer, buffer blob)", connection)) + { + command.ExecuteNonQuery(); + } + + Debug.WriteLine("SQLiteCache: Opened database " + path); + + return connection; + } + + public void Dispose() + { + connection.Dispose(); + } + + public void Clean() + { + using (var command = new SQLiteCommand("delete from items where expiration < @exp", connection)) + { + command.Parameters.AddWithValue("@exp", DateTime.UtcNow.Ticks); + command.ExecuteNonQuery(); + } + } + + private SQLiteCommand GetItemCommand(string key) + { + var command = new SQLiteCommand("select expiration, buffer from items where key=@key", connection); + command.Parameters.AddWithValue("@key", key); + return command; + } + + private SQLiteCommand SetItemCommand(string key, DateTime expiration, byte[] buffer) + { + var command = new SQLiteCommand("insert or replace into items (key, expiration, buffer) values (@key, @exp, @buf)", connection); + command.Parameters.AddWithValue("@key", key); + command.Parameters.AddWithValue("@exp", expiration.Ticks); + command.Parameters.AddWithValue("@buf", buffer); + return command; + } + + private SQLiteCommand RemoveItemCommand(string key) + { + var command = new SQLiteCommand("delete from items where key = @key", connection); + command.Parameters.AddWithValue("@key", key); + return command; + } + } +} diff --git a/SQLiteCache/UWP/SQLiteCache.cs b/SQLiteCache/UWP/SQLiteCache.UWP.cs similarity index 56% rename from SQLiteCache/UWP/SQLiteCache.cs rename to SQLiteCache/UWP/SQLiteCache.UWP.cs index ab66f8ac..8811b41d 100644 --- a/SQLiteCache/UWP/SQLiteCache.cs +++ b/SQLiteCache/UWP/SQLiteCache.UWP.cs @@ -2,7 +2,6 @@ // © 2019 Clemens Fischer // Licensed under the Microsoft Public License (Ms-PL) -using Microsoft.Data.Sqlite; using System; using System.Diagnostics; using System.IO; @@ -16,10 +15,8 @@ namespace MapControl.Caching /// /// IImageCache implementation based on SqLite. /// - public sealed class SQLiteCache : IImageCache, IDisposable + public partial class SQLiteCache : IImageCache { - private readonly SqliteConnection connection; - public SQLiteCache(StorageFolder folder, string fileName = "TileCache.sqlite") { if (folder == null) @@ -32,33 +29,29 @@ namespace MapControl.Caching throw new ArgumentNullException("The parameter fileName must not be null."); } - connection = new SqliteConnection("Data Source=" + Path.Combine(folder.Path, fileName)); + connection = Open(Path.Combine(folder.Path, fileName)); - connection.Open(); - - using (var command = new SqliteCommand("create table if not exists items (key text, expiration integer, buffer blob)", connection)) - { - command.ExecuteNonQuery(); - } - } - - public void Dispose() - { - connection.Dispose(); + Clean(); } public async Task GetAsync(string key) { + if (key == null) + { + throw new ArgumentNullException("The parameter key must not be null."); + } + + ImageCacheItem imageCacheItem = null; + try { - using (var command = new SqliteCommand("select expiration, buffer from items where key=@key", connection)) + using (var command = GetItemCommand(key)) { - command.Parameters.AddWithValue("@key", key); var reader = await command.ExecuteReaderAsync(); - if (reader.Read()) + if (await reader.ReadAsync()) { - return new ImageCacheItem + imageCacheItem = new ImageCacheItem { Expiration = new DateTime((long)reader["expiration"]), Buffer = ((byte[])reader["buffer"]).AsBuffer() @@ -68,27 +61,36 @@ namespace MapControl.Caching } catch (Exception ex) { - Debug.WriteLine("SqLiteCache: GetAsync(\"{0}\"): {1}", key, ex.Message); + Debug.WriteLine("SQLiteCache.GetAsync(\"{0}\"): {1}", key, ex.Message); } - return null; + return imageCacheItem; } public async Task SetAsync(string key, IBuffer buffer, DateTime expiration) { + if (key == null) + { + throw new ArgumentNullException("The parameter key must not be null."); + } + + if (buffer == null) + { + throw new ArgumentNullException("The parameter buffer must not be null."); + } + try { - using (var command = new SqliteCommand("insert or replace into items (key, expiration, buffer) values (@key, @exp, @buf)", connection)) + using (var command = SetItemCommand(key, expiration, buffer.ToArray())) { - command.Parameters.AddWithValue("@key", key); - command.Parameters.AddWithValue("@exp", expiration.Ticks); - command.Parameters.AddWithValue("@buf", buffer.ToArray()); await command.ExecuteNonQueryAsync(); } + + //Debug.WriteLine("SQLiteCache.SetAsync(\"{0}\"): expires {1}", key, expiration.ToLocalTime()); } catch (Exception ex) { - Debug.WriteLine("SqLiteCache: SetAsync(\"{0}\"): {1}", key, ex.Message); + Debug.WriteLine("SQLiteCache.SetAsync(\"{0}\"): {1}", key, ex.Message); } } } diff --git a/SQLiteCache/UWP/SQLiteCache.UWP.csproj b/SQLiteCache/UWP/SQLiteCache.UWP.csproj index f9834a3c..08be08b8 100644 --- a/SQLiteCache/UWP/SQLiteCache.UWP.csproj +++ b/SQLiteCache/UWP/SQLiteCache.UWP.csproj @@ -40,8 +40,11 @@ PackageReference + + SQLiteCache.cs + - + diff --git a/SQLiteCache/WPF/SQLiteCache.cs b/SQLiteCache/WPF/SQLiteCache.WPF.cs similarity index 71% rename from SQLiteCache/WPF/SQLiteCache.cs rename to SQLiteCache/WPF/SQLiteCache.WPF.cs index 46e295a2..431b03cc 100644 --- a/SQLiteCache/WPF/SQLiteCache.cs +++ b/SQLiteCache/WPF/SQLiteCache.WPF.cs @@ -15,10 +15,8 @@ namespace MapControl.Caching /// /// ObjectCache implementation based on SqLite. /// - public sealed class SQLiteCache : ObjectCache, IDisposable + public partial class SQLiteCache : ObjectCache { - private readonly SQLiteConnection connection; - public SQLiteCache(string path) { if (string.IsNullOrEmpty(path)) @@ -31,14 +29,9 @@ namespace MapControl.Caching path = Path.Combine(path, "TileCache.sqlite"); } - connection = new SQLiteConnection("Data Source=" + Path.GetFullPath(path)); + connection = Open(Path.GetFullPath(path)); - connection.Open(); - - using (var command = new SQLiteCommand("create table if not exists items (key text, expiration integer, buffer blob)", connection)) - { - command.ExecuteNonQuery(); - } + Clean(); } public override string Name @@ -59,19 +52,19 @@ namespace MapControl.Caching protected override IEnumerator> GetEnumerator() { - throw new NotSupportedException("SqLiteCache does not support the ability to enumerate items."); + throw new NotSupportedException("SQLiteCache does not support the ability to enumerate items."); } public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable keys, string regionName = null) { - throw new NotSupportedException("SqLiteCache does not support the ability to create change monitors."); + throw new NotSupportedException("SQLiteCache does not support the ability to create change monitors."); } public override long GetCount(string regionName = null) { if (regionName != null) { - throw new NotSupportedException("The parameter regionName must be null."); + throw new NotSupportedException("SQLiteCache does not support named regions."); } try @@ -83,7 +76,7 @@ namespace MapControl.Caching } catch (Exception ex) { - Debug.WriteLine("SqLiteCache: GetCount(): {0}", ex.Message); + Debug.WriteLine("SQLiteCache.GetCount(): {0}", ex.Message); } return 0; @@ -91,28 +84,26 @@ namespace MapControl.Caching public override bool Contains(string key, string regionName = null) { + if (regionName != null) + { + throw new NotSupportedException("SQLiteCache does not support named regions."); + } + if (key == null) { throw new ArgumentNullException("The parameter key must not be null."); } - if (regionName != null) - { - throw new NotSupportedException("The parameter regionName must be null."); - } - try { - using (var command = new SQLiteCommand("select expiration, buffer from items where key=@key", connection)) + using (var command = GetItemCommand(key)) { - command.Parameters.AddWithValue("@key", key); - return command.ExecuteReader().Read(); } } catch (Exception ex) { - Debug.WriteLine("SqLiteCache: Get(\"{0}\"): {1}", key, ex.Message); + Debug.WriteLine("SQLiteCache.Contains(\"{0}\"): {1}", key, ex.Message); } return false; @@ -120,26 +111,27 @@ namespace MapControl.Caching public override object Get(string key, string regionName = null) { + if (regionName != null) + { + throw new NotSupportedException("SQLiteCache does not support named regions."); + } + if (key == null) { throw new ArgumentNullException("The parameter key must not be null."); } - if (regionName != null) - { - throw new NotSupportedException("The parameter regionName must be null."); - } + ImageCacheItem imageCacheItem = null; try { - using (var command = new SQLiteCommand("select expiration, buffer from items where key=@key", connection)) + using (var command = GetItemCommand(key)) { - command.Parameters.AddWithValue("@key", key); var reader = command.ExecuteReader(); if (reader.Read()) { - return new ImageCacheItem + imageCacheItem = new ImageCacheItem { Expiration = new DateTime((long)reader["expiration"]), Buffer = (byte[])reader["buffer"] @@ -149,10 +141,10 @@ namespace MapControl.Caching } catch (Exception ex) { - Debug.WriteLine("SqLiteCache: Get(\"{0}\"): {1}", key, ex.Message); + Debug.WriteLine("SQLiteCache.Get(\"{0}\"): {1}", key, ex.Message); } - return null; + return imageCacheItem; } public override CacheItem GetCacheItem(string key, string regionName = null) @@ -169,36 +161,35 @@ namespace MapControl.Caching public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null) { + if (regionName != null) + { + throw new NotSupportedException("SQLiteCache does not support named regions."); + } + if (key == null) { throw new ArgumentNullException("The parameter key must not be null."); } - if (regionName != null) - { - throw new NotSupportedException("The parameter regionName must be null."); - } - var imageCacheItem = value as ImageCacheItem; if (imageCacheItem == null || imageCacheItem.Buffer == null || imageCacheItem.Buffer.Length == 0) { - throw new NotSupportedException("The parameter value must be an ImageCacheItem with a non-empty Buffer."); + throw new ArgumentException("The parameter value must be an ImageCacheItem with a non-empty Buffer."); } try { - using (var command = new SQLiteCommand("insert or replace into items (key, expiration, buffer) values (@key, @exp, @buf)", connection)) + using (var command = SetItemCommand(key, imageCacheItem.Expiration, imageCacheItem.Buffer)) { - command.Parameters.AddWithValue("@key", key); - command.Parameters.AddWithValue("@exp", imageCacheItem.Expiration.Ticks); - command.Parameters.AddWithValue("@buf", imageCacheItem.Buffer); command.ExecuteNonQuery(); } + + //Debug.WriteLine("SQLiteCache.Set(\"{0}\"): expires {1}", key, expiration.ToLocalTime()); } catch (Exception ex) { - Debug.WriteLine("SqLiteCache: Set(\"{0}\"): {1}", key, ex.Message); + Debug.WriteLine("SQLiteCache.Set(\"{0}\"): {1}", key, ex.Message); } } @@ -237,12 +228,24 @@ namespace MapControl.Caching public override object Remove(string key, string regionName = null) { - throw new NotImplementedException(); - } + var oldValue = Get(key, regionName); - public void Dispose() - { - connection.Dispose(); + if (oldValue != null) + { + try + { + using (var command = RemoveItemCommand(key)) + { + command.ExecuteNonQuery(); + } + } + catch (Exception ex) + { + Debug.WriteLine("SQLiteCache.Remove(\"{0}\"): {1}", key, ex.Message); + } + } + + return oldValue; } } } diff --git a/SQLiteCache/WPF/SQLiteCache.WPF.csproj b/SQLiteCache/WPF/SQLiteCache.WPF.csproj index 96ba137c..ab87b643 100644 --- a/SQLiteCache/WPF/SQLiteCache.WPF.csproj +++ b/SQLiteCache/WPF/SQLiteCache.WPF.csproj @@ -49,11 +49,14 @@ + + SQLiteCache.cs + Code - +