Use IDistributedCache on all platforms

This commit is contained in:
ClemensFischer 2024-02-03 20:53:32 +01:00
parent 16115413d8
commit c12e929fcc
39 changed files with 773 additions and 1686 deletions

View file

@ -0,0 +1,196 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// Copyright © 2024 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using FileDbNs;
using Microsoft.Extensions.Caching.Distributed;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MapControl.Caching
{
/// <summary>
/// IDistributedCache implementation based on FileDb, a free and simple No-SQL database by EzTools Software.
/// See http://www.eztools-software.com/tools/filedb/.
/// </summary>
public class FileDbCache : IDistributedCache, IDisposable
{
private const string keyField = "Key";
private const string valueField = "Value";
private const string expiresField = "Expires";
private readonly FileDb fileDb = new FileDb { AutoFlush = true };
public FileDbCache(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException("The path argument must not be null or empty.", nameof(path));
}
if (string.IsNullOrEmpty(Path.GetExtension(path)))
{
path = Path.Combine(path, "TileCache.fdb");
}
try
{
fileDb.Open(path);
Debug.WriteLine($"FileDbCache: Opened database {path}");
Clean();
}
catch
{
if (File.Exists(path))
{
File.Delete(path);
}
else
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
}
fileDb.Create(path, 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 {path}");
}
}
public void Dispose()
{
fileDb.Dispose();
}
public byte[] Get(string key)
{
byte[] value = null;
try
{
var record = fileDb.GetRecordByKey(key, new string[] { valueField, expiresField }, false);
if (record != null)
{
if ((DateTime)record[1] > DateTime.UtcNow)
{
value = (byte[])record[0];
}
else
{
fileDb.DeleteRecordByKey(key);
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"FileDbCache.Get({key}): {ex.Message}");
}
return value;
}
public Task<byte[]> GetAsync(string key, CancellationToken token = default)
{
return Task.FromResult(Get(key));
}
public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
{
DateTime expiration;
if (options.AbsoluteExpiration.HasValue)
{
expiration = options.AbsoluteExpiration.Value.DateTime;
}
else if (options.AbsoluteExpirationRelativeToNow.HasValue)
{
expiration = DateTime.UtcNow.Add(options.AbsoluteExpirationRelativeToNow.Value);
}
else if (options.SlidingExpiration.HasValue)
{
expiration = DateTime.UtcNow.Add(options.SlidingExpiration.Value);
}
else
{
expiration = DateTime.UtcNow.Add(TimeSpan.FromDays(1));
}
var fieldValues = new FieldValues(3)
{
{ valueField, value ?? new byte[0] },
{ expiresField, expiration }
};
try
{
if (fileDb.GetRecordByKey(key, new string[0], false) != null)
{
fileDb.UpdateRecordByKey(key, fieldValues);
}
else
{
fieldValues.Add(keyField, key);
fileDb.AddRecord(fieldValues);
}
}
catch (Exception ex)
{
Debug.WriteLine($"FileDbCache.Set({key}): {ex.Message}");
}
}
public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default)
{
Set(key, value, options);
return Task.CompletedTask;
}
public void Refresh(string key)
{
throw new NotSupportedException();
}
public Task RefreshAsync(string key, CancellationToken token = default)
{
throw new NotSupportedException();
}
public void Remove(string key)
{
try
{
fileDb.DeleteRecordByKey(key);
}
catch (Exception ex)
{
Debug.WriteLine($"FileDbCache.Remove({key}): {ex.Message}");
}
}
public Task RemoveAsync(string key, CancellationToken token = default)
{
Remove(key);
return Task.CompletedTask;
}
public void Clean()
{
var deleted = fileDb.DeleteRecords(new FilterExpression(expiresField, DateTime.UtcNow, ComparisonOperatorEnum.LessThanOrEqual));
if (deleted > 0)
{
Debug.WriteLine($"FileDbCache: Deleted {deleted} expired items");
fileDb.Clean();
}
}
}
}

View file

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="FileDb.Standard" Version="7.4.4" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,245 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// Copyright © 2024 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using Microsoft.Extensions.Caching.Distributed;
using System;
using System.Data.Common;
using System.Data.SQLite;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace MapControl.Caching
{
/// <summary>
/// IDistributedCache implementation based on SQLite.
/// </summary>
public class SQLiteCache : IDistributedCache, IDisposable
{
private readonly SQLiteConnection connection;
public SQLiteCache(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException("The path argument must not be null or empty.", nameof(path));
}
if (string.IsNullOrEmpty(Path.GetExtension(path)))
{
path = Path.Combine(path, "TileCache.sqlite");
}
var connection = new SQLiteConnection("Data Source=" + Path.GetFullPath(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}");
Clean();
}
public void Dispose()
{
connection.Dispose();
}
public byte[] Get(string key)
{
byte[] value = null;
try
{
using (var command = GetItemCommand(key))
{
var reader = command.ExecuteReader();
if (reader.Read() && !ReadValue(reader, ref value))
{
Remove(key);
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"SQLiteCache.Get({key}): {ex.Message}");
}
return value;
}
public async Task<byte[]> GetAsync(string key, CancellationToken token = default)
{
byte[] value = null;
try
{
using (var command = GetItemCommand(key))
{
var reader = await command.ExecuteReaderAsync(token);
if (await reader.ReadAsync() && !ReadValue(reader, ref value))
{
await RemoveAsync(key);
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"SQLiteCache.GetAsync({key}): {ex.Message}");
}
return value;
}
public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
{
try
{
using (var command = SetItemCommand(key, value, options))
{
command.ExecuteNonQuery();
}
}
catch (Exception ex)
{
Debug.WriteLine($"SQLiteCache.Set({key}): {ex.Message}");
}
}
public async Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default)
{
try
{
using (var command = SetItemCommand(key, value, options))
{
await command.ExecuteNonQueryAsync(token);
}
}
catch (Exception ex)
{
Debug.WriteLine($"SQLiteCache.SetAsync({key}): {ex.Message}");
}
}
public void Refresh(string key)
{
throw new NotSupportedException();
}
public Task RefreshAsync(string key, CancellationToken token = default)
{
throw new NotSupportedException();
}
public void Remove(string key)
{
try
{
using (var command = RemoveItemCommand(key))
{
command.ExecuteNonQuery();
}
}
catch (Exception ex)
{
Debug.WriteLine($"SQLiteCache.Remove({key}): {ex.Message}");
}
}
public async Task RemoveAsync(string key, CancellationToken token = default)
{
try
{
using (var command = RemoveItemCommand(key))
{
await command.ExecuteNonQueryAsync();
}
}
catch (Exception ex)
{
Debug.WriteLine($"SQLiteCache.RemoveAsync({key}): {ex.Message}");
}
}
public void Clean()
{
using (var command = new SQLiteCommand("delete from items where expiration < @exp", connection))
{
command.Parameters.AddWithValue("@exp", DateTimeOffset.UtcNow.Ticks);
command.ExecuteNonQuery();
}
#if DEBUG
using (var command = new SQLiteCommand("select changes()", connection))
{
var deleted = (long)command.ExecuteScalar();
if (deleted > 0)
{
Debug.WriteLine($"SQLiteCache: Deleted {deleted} expired items");
}
}
#endif
}
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 RemoveItemCommand(string key)
{
var command = new SQLiteCommand("delete from items where key = @key", connection);
command.Parameters.AddWithValue("@key", key);
return command;
}
private SQLiteCommand SetItemCommand(string key, byte[] buffer, DistributedCacheEntryOptions options)
{
DateTimeOffset expiration;
if (options.AbsoluteExpiration.HasValue)
{
expiration = options.AbsoluteExpiration.Value;
}
else if (options.AbsoluteExpirationRelativeToNow.HasValue)
{
expiration = DateTimeOffset.UtcNow.Add(options.AbsoluteExpirationRelativeToNow.Value);
}
else if (options.SlidingExpiration.HasValue)
{
expiration = DateTimeOffset.UtcNow.Add(options.SlidingExpiration.Value);
}
else
{
expiration = DateTimeOffset.UtcNow.Add(TimeSpan.FromDays(1));
}
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 ?? new byte[0]);
return command;
}
private bool ReadValue(DbDataReader reader, ref byte[] value)
{
var expiration = new DateTimeOffset((long)reader["expiration"], TimeSpan.Zero);
if (expiration <= DateTimeOffset.UtcNow)
{
return false;
}
value = (byte[])reader["buffer"];
return true;
}
}
}

View file

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.118" />
</ItemGroup>
</Project>

View file

@ -1,124 +0,0 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// Copyright © 2023 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using FileDbNs;
using System;
using System.Diagnostics;
using System.IO;
namespace MapControl.Caching
{
/// <summary>
/// Image cache implementation based on FileDb, a free and simple No-SQL database by EzTools Software.
/// See http://www.eztools-software.com/tools/filedb/.
/// </summary>
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 };
public FileDbCache(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException("The path argument must not be null or empty.", nameof(path));
}
if (string.IsNullOrEmpty(Path.GetExtension(path)))
{
path = Path.Combine(path, "TileCache.fdb");
}
Open(path);
}
public void Dispose()
{
fileDb.Dispose();
}
public void Clean()
{
var deleted = fileDb.DeleteRecords(new FilterExpression(expiresField, DateTime.UtcNow, ComparisonOperatorEnum.LessThan));
if (deleted > 0)
{
Debug.WriteLine($"FileDbCache: Deleted {deleted} expired items");
fileDb.Clean();
}
}
private void Open(string path)
{
try
{
fileDb.Open(path);
Debug.WriteLine($"FileDbCache: Opened database {path}");
Clean();
}
catch
{
if (File.Exists(path))
{
File.Delete(path);
}
else
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
}
fileDb.Create(path, 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 {path}");
}
}
private Record GetRecordByKey(string key)
{
try
{
return fileDb.GetRecordByKey(key, new string[] { valueField, expiresField }, false);
}
catch (Exception ex)
{
Debug.WriteLine($"FileDbCache.GetRecordByKey({key}): {ex.Message}");
}
return null;
}
private void AddOrUpdateRecord(string key, byte[] buffer, DateTime expiration)
{
var fieldValues = new FieldValues(3);
fieldValues.Add(valueField, buffer ?? new byte[0]);
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);
}
}
catch (Exception ex)
{
Debug.WriteLine($"FileDbCache.AddOrUpdateRecord({key}): {ex.Message}");
}
}
}
}

View file

@ -1,81 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MapControl.Caching</RootNamespace>
<AssemblyName>FileDbCache.UWP</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.22000.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;UWP</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>UWP</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Shared\FileDbCache.cs">
<Link>FileDbCache.cs</Link>
</Compile>
<Compile Include="..\WinUI\FileDbCache.WinUI.cs">
<Link>FileDbCache.WinUI.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Properties\FileDbCache.UWP.rd.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FileDb.Standard">
<Version>7.4.4</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.14</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<None Include="..\..\MapControl.snk">
<Link>MapControl.snk</Link>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\MapControl\UWP\MapControl.UWP.csproj">
<Project>{9545f73c-9c35-4cf6-baae-19a0baebd344}</Project>
<Name>MapControl.UWP</Name>
</ProjectReference>
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
</Project>

View file

@ -1,13 +0,0 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("XAML Map Control FileDbCache Library for UWP")]
[assembly: AssemblyProduct("XAML Map Control")]
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2023 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("8.3.0")]
[assembly: AssemblyFileVersion("8.3.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Library Name="FileDbCache.UWP">
</Library>
</Directives>

View file

@ -1,185 +0,0 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// Copyright © 2023 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Caching;
namespace MapControl.Caching
{
public partial class FileDbCache : ObjectCache
{
public override string Name => string.Empty;
public override DefaultCacheCapabilities DefaultCacheCapabilities =>
DefaultCacheCapabilities.AbsoluteExpirations | DefaultCacheCapabilities.SlidingExpirations;
public override object this[string key]
{
get => Get(key);
set => Set(key, value, null);
}
protected override IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
throw new NotSupportedException("FileDbCache does not support the ability to enumerate items.");
}
public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable<string> 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(nameof(key));
}
try
{
return fileDb.GetRecordByKey(key, Array.Empty<string>(), false) != null;
}
catch (Exception ex)
{
Debug.WriteLine($"FileDbCache.Contains({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(nameof(key));
}
var record = GetRecordByKey(key);
if (record == null)
{
return null;
}
return Tuple.Create((byte[])record[0], (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<string, object> GetValues(IEnumerable<string> 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(nameof(key));
}
if (!(value is Tuple<byte[], DateTime> cacheItem))
{
throw new ArgumentException("The value argument must be a Tuple<byte[], DateTime>.", nameof(value));
}
AddOrUpdateRecord(key, cacheItem.Item1, cacheItem.Item2);
}
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({key}): {ex.Message}");
}
}
return oldValue;
}
}
}

View file

@ -1,38 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-windows;net7.0-windows;net6.0-windows;net48;net462</TargetFrameworks>
<UseWPF>true</UseWPF>
<RootNamespace>MapControl.Caching</RootNamespace>
<AssemblyTitle>XAML Map Control FileDbCache Library for WPF</AssemblyTitle>
<Product>XAML Map Control</Product>
<Version>8.3.0</Version>
<Authors>Clemens Fischer</Authors>
<Copyright>Copyright © 2023 Clemens Fischer</Copyright>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageId>XAML.MapControl.FileDbCache</PackageId>
<DefineConstants></DefineConstants>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\MapControl.snk" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Shared\*.cs" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.EndsWith('windows'))">
<PackageReference Include="System.Runtime.Caching" Version="8.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework.TrimEnd(`2468`))'=='net'">
<Reference Include="System.Runtime.Caching" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FileDb.Standard" Version="7.4.4" />
</ItemGroup>
</Project>

View file

@ -1,32 +0,0 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// Copyright © 2023 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Threading.Tasks;
namespace MapControl.Caching
{
public partial class FileDbCache : IImageCache
{
public Task<Tuple<byte[], DateTime>> GetAsync(string key)
{
return Task.Run(() =>
{
var record = GetRecordByKey(key);
if (record == null)
{
return null;
}
return Tuple.Create((byte[])record[0], (DateTime)record[1]);
});
}
public Task SetAsync(string key, byte[] buffer, DateTime expiration)
{
return Task.Run(() => AddOrUpdateRecord(key, buffer, expiration));
}
}
}

View file

@ -1,38 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-windows10.0.17763.0;net7.0-windows10.0.17763.0;net6.0-windows10.0.17763.0</TargetFrameworks>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseRidGraph>true</UseRidGraph>
<UseWinUI>true</UseWinUI>
<RootNamespace>MapControl.Caching</RootNamespace>
<AssemblyTitle>XAML Map Control FileDbCache Library for WinUI</AssemblyTitle>
<Product>XAML Map Control</Product>
<Version>8.3.0</Version>
<Authors>Clemens Fischer</Authors>
<Copyright>Copyright © 2023 Clemens Fischer</Copyright>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageId>XAML.MapControl.FileDbCache</PackageId>
<DefineConstants>WINUI</DefineConstants>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\MapControl.snk" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Shared\*.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FileDb.Standard" Version="7.4.4" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\MapControl\WinUI\MapControl.WinUI.csproj" />
</ItemGroup>
</Project>

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0-windows;net6.0-windows;net48;net462</TargetFrameworks>
<TargetFrameworks>net6.0-windows;net462</TargetFrameworks>
<UseWPF>true</UseWPF>
<RootNamespace>MapControl.MBTiles</RootNamespace>
<AssemblyTitle>XAML Map Control MBTiles Library for WPF</AssemblyTitle>

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-windows10.0.17763.0;net7.0-windows10.0.17763.0;net6.0-windows10.0.17763.0</TargetFrameworks>
<TargetFramework>net6.0-windows10.0.17763.0</TargetFramework>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseRidGraph>true</UseRidGraph>
<UseWinUI>true</UseWinUI>

View file

@ -1,21 +1,22 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// Copyright © 2023 Clemens Fischer
// Copyright © 2024 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using Microsoft.Extensions.Caching.Distributed;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace MapControl.Caching
{
/// <summary>
/// Image Cache implementation based on local image files.
/// The only valid data type for cached values is Tuple<byte[], DateTime>.
/// IDistributedCache implementation based on local image files.
/// </summary>
public partial class ImageFileCache
public class ImageFileCache : IDistributedCache
{
private const string expiresTag = "EXPIRES:";
@ -38,6 +39,155 @@ namespace MapControl.Caching
return Task.Factory.StartNew(CleanRootDirectory, TaskCreationOptions.LongRunning);
}
public byte[] Get(string key)
{
byte[] buffer = null;
var path = GetPath(key);
try
{
if (path != null && File.Exists(path))
{
buffer = File.ReadAllBytes(path);
CheckExpiration(path, ref buffer);
}
}
catch (Exception ex)
{
Debug.WriteLine($"ImageFileCache: Failed reading {path}: {ex.Message}");
}
return buffer;
}
public async Task<byte[]> GetAsync(string key, CancellationToken token = default)
{
byte[] buffer = null;
var path = GetPath(key);
try
{
if (path != null && File.Exists(path))
{
#if NETFRAMEWORK
using (var stream = File.OpenRead(path))
{
buffer = new byte[stream.Length];
var offset = 0;
while (offset < buffer.Length)
{
offset += await stream.ReadAsync(buffer, offset, buffer.Length - offset, token).ConfigureAwait(false);
}
}
#else
buffer = await File.ReadAllBytesAsync(path, token).ConfigureAwait(false);
#endif
CheckExpiration(path, ref buffer);
}
}
catch (Exception ex)
{
Debug.WriteLine($"ImageFileCache: Failed reading {path}: {ex.Message}");
}
return buffer;
}
public void Set(string key, byte[] buffer, DistributedCacheEntryOptions options)
{
var path = GetPath(key);
if (path != null && buffer != null && buffer.Length > 0)
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
using (var stream = File.Create(path))
{
stream.Write(buffer, 0, buffer.Length);
var expiration = GetExpiration(options);
if (expiration.HasValue)
{
stream.Write(Encoding.ASCII.GetBytes(expiresTag), 0, 8);
stream.Write(BitConverter.GetBytes(expiration.Value.Ticks), 0, 8);
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"ImageFileCache: Failed writing {path}: {ex.Message}");
}
}
}
public async Task SetAsync(string key, byte[] buffer, DistributedCacheEntryOptions options, CancellationToken token = default)
{
var path = GetPath(key);
if (path != null && buffer != null && buffer.Length > 0)
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
using (var stream = File.Create(path))
{
await stream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
var expiration = GetExpiration(options);
if (expiration.HasValue)
{
await stream.WriteAsync(Encoding.ASCII.GetBytes(expiresTag), 0, 8).ConfigureAwait(false);
await stream.WriteAsync(BitConverter.GetBytes(expiration.Value.Ticks), 0, 8).ConfigureAwait(false);
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"ImageFileCache: Failed writing {path}: {ex.Message}");
}
}
}
public void Refresh(string key)
{
throw new NotSupportedException();
}
public Task RefreshAsync(string key, CancellationToken token = default)
{
throw new NotSupportedException();
}
public void Remove(string key)
{
var path = GetPath(key);
try
{
if (path != null && File.Exists(path))
{
File.Delete(path);
}
}
catch (Exception ex)
{
Debug.WriteLine($"ImageFileCache: Failed deleting {path}: {ex.Message}");
}
}
public Task RemoveAsync(string key, CancellationToken token = default)
{
Remove(key);
return Task.CompletedTask;
}
private string GetPath(string key)
{
try
@ -101,7 +251,9 @@ namespace MapControl.Caching
try
{
if (ReadExpiration(file) < DateTime.UtcNow)
var expiration = ReadExpiration(file);
if (expiration.HasValue && expiration.Value <= DateTimeOffset.UtcNow)
{
file.Delete();
deletedFileCount = 1;
@ -115,9 +267,40 @@ namespace MapControl.Caching
return deletedFileCount;
}
private static DateTime ReadExpiration(FileInfo file)
private static DateTimeOffset? GetExpiration(DistributedCacheEntryOptions options)
{
DateTime? expiration = null;
DateTimeOffset? expiration = null;
if (options.AbsoluteExpiration.HasValue)
{
expiration = options.AbsoluteExpiration.Value;
}
else if (options.AbsoluteExpirationRelativeToNow.HasValue)
{
expiration = DateTimeOffset.UtcNow.Add(options.AbsoluteExpirationRelativeToNow.Value);
}
else if (options.SlidingExpiration.HasValue)
{
expiration = DateTimeOffset.UtcNow.Add(options.SlidingExpiration.Value);
}
return expiration;
}
private static void CheckExpiration(string path, ref byte[] buffer)
{
var expiration = ReadExpiration(ref buffer);
if (expiration.HasValue && expiration.Value <= DateTimeOffset.UtcNow)
{
File.Delete(path);
buffer = null;
}
}
private static DateTimeOffset? ReadExpiration(FileInfo file)
{
DateTimeOffset? expiration = null;
if (file.Length > 16)
{
@ -134,46 +317,32 @@ namespace MapControl.Caching
}
}
return expiration ?? DateTime.Today;
return expiration;
}
private static DateTime ReadExpiration(ref byte[] buffer)
private static DateTimeOffset? ReadExpiration(ref byte[] buffer)
{
DateTime? expiration = ReadExpiration(buffer);
var expiration = ReadExpiration(buffer);
if (expiration.HasValue)
{
Array.Resize(ref buffer, buffer.Length - 16);
return expiration.Value;
}
return DateTime.Today;
}
private static DateTime? ReadExpiration(byte[] buffer)
{
DateTime? expiration = null;
if (buffer.Length >= 16 &&
Encoding.ASCII.GetString(buffer, buffer.Length - 16, 8) == expiresTag)
{
expiration = new DateTime(BitConverter.ToInt64(buffer, buffer.Length - 8), DateTimeKind.Utc);
}
return expiration;
}
private static void WriteExpiration(Stream stream, DateTime expiration)
private static DateTimeOffset? ReadExpiration(byte[] buffer)
{
stream.Write(Encoding.ASCII.GetBytes(expiresTag), 0, 8);
stream.Write(BitConverter.GetBytes(expiration.Ticks), 0, 8);
DateTimeOffset? expiration = null;
if (buffer.Length >= 16 &&
Encoding.ASCII.GetString(buffer, buffer.Length - 16, 8) == expiresTag)
{
expiration = new DateTimeOffset(BitConverter.ToInt64(buffer, buffer.Length - 8), TimeSpan.Zero);
}
private static async Task WriteExpirationAsync(Stream stream, DateTime expiration)
{
await stream.WriteAsync(Encoding.ASCII.GetBytes(expiresTag), 0, 8);
await stream.WriteAsync(BitConverter.GetBytes(expiration.Ticks), 0, 8);
return expiration;
}
}
}

View file

@ -2,6 +2,7 @@
// Copyright © 2023 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using Microsoft.Extensions.Caching.Distributed;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -39,9 +40,9 @@ namespace MapControl
}
/// <summary>
/// Maximum number of parallel tile loading tasks. The default value is 4.
/// An IDistributedCache implementation used to cache tile images.
/// </summary>
public static int MaxLoadTasks { get; set; } = 4;
public static IDistributedCache Cache { get; set; }
/// <summary>
/// Default expiration time for cached tile images. Used when no expiration time
@ -55,6 +56,12 @@ namespace MapControl
/// </summary>
public static TimeSpan MaxCacheExpiration { get; set; } = TimeSpan.FromDays(10);
/// <summary>
/// Maximum number of parallel tile loading tasks. The default value is 4.
/// </summary>
public static int MaxLoadTasks { get; set; } = 4;
private TileQueue pendingTiles;
/// <summary>
@ -124,6 +131,14 @@ namespace MapControl
var uri = tileSource.GetUri(tile.Column, tile.Row, tile.ZoomLevel);
if (uri != null)
{
return LoadCachedTileAsync(tile, uri, cacheName);
}
return Task.CompletedTask;
}
private static async Task LoadCachedTileAsync(Tile tile, Uri uri, string cacheName)
{
var extension = Path.GetExtension(uri.LocalPath);
@ -135,24 +150,37 @@ namespace MapControl
var cacheKey = string.Format(CultureInfo.InvariantCulture,
"{0}/{1}/{2}/{3}{4}", cacheName, tile.ZoomLevel, tile.Column, tile.Row, extension);
return LoadCachedTileAsync(tile, uri, cacheKey);
}
var buffer = await Cache.GetAsync(cacheKey).ConfigureAwait(false);
return Task.CompletedTask;
}
if (buffer == null)
{
var response = await ImageLoader.GetHttpResponseAsync(uri).ConfigureAwait(false);
private static DateTime GetExpiration(TimeSpan? maxAge)
if (response != null) // download succeeded
{
if (!maxAge.HasValue)
{
maxAge = DefaultCacheExpiration;
}
else if (maxAge.Value > MaxCacheExpiration)
buffer = response.Buffer ?? Array.Empty<byte>(); // may be empty when no tile available, but still be cached
var maxAge = response.MaxAge ?? DefaultCacheExpiration;
if (maxAge > MaxCacheExpiration)
{
maxAge = MaxCacheExpiration;
}
return DateTime.UtcNow.Add(maxAge.Value);
var cacheOptions = new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.UtcNow.Add(maxAge)
};
await Cache.SetAsync(cacheKey, buffer, cacheOptions).ConfigureAwait(false);
}
}
//else System.Diagnostics.Debug.WriteLine($"Cached: {cacheKey}");
if (buffer != null && buffer.Length > 0)
{
await LoadTileAsync(tile, () => ImageLoader.LoadImageAsync(buffer)).ConfigureAwait(false);
}
}
}
}

View file

@ -233,9 +233,6 @@
<Compile Include="..\WinUI\GeoImage.WinUI.cs">
<Link>GeoImage.WinUI.cs</Link>
</Compile>
<Compile Include="..\WinUI\ImageFileCache.WinUI.cs">
<Link>ImageFileCache.WinUI.cs</Link>
</Compile>
<Compile Include="..\WinUI\ImageLoader.WinUI.cs">
<Link>ImageLoader.WinUI.cs</Link>
</Compile>
@ -286,6 +283,9 @@
<EmbeddedResource Include="Properties\MapControl.UWP.rd.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions">
<Version>8.0.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.14</Version>
</PackageReference>

View file

@ -9,16 +9,6 @@ using Windows.UI.Xaml.Media;
namespace MapControl
{
namespace Caching
{
public interface IImageCache
{
Task<Tuple<byte[], DateTime>> GetAsync(string key);
Task SetAsync(string key, byte[] buffer, DateTime expiration);
}
}
public partial class TileImageLoader
{
/// <summary>
@ -27,35 +17,6 @@ namespace MapControl
/// </summary>
public static string DefaultCacheFolder => Windows.Storage.ApplicationData.Current.TemporaryFolder.Path;
/// <summary>
/// An IImageCache implementation used to cache tile images.
/// </summary>
public static Caching.IImageCache Cache { get; set; }
private static async Task LoadCachedTileAsync(Tile tile, Uri uri, string cacheKey)
{
var cacheItem = await Cache.GetAsync(cacheKey).ConfigureAwait(false);
var buffer = cacheItem?.Item1;
if (cacheItem == null || cacheItem.Item2 < DateTime.UtcNow)
{
var response = await ImageLoader.GetHttpResponseAsync(uri).ConfigureAwait(false);
if (response != null) // download succeeded
{
buffer = response.Buffer; // may be null or empty when no tile available, but still be cached
await Cache.SetAsync(cacheKey, buffer, GetExpiration(response.MaxAge)).ConfigureAwait(false);
}
}
//else System.Diagnostics.Debug.WriteLine($"Cached: {cacheKey}");
if (buffer != null && buffer.Length > 0)
{
await LoadTileAsync(tile, () => ImageLoader.LoadImageAsync(buffer)).ConfigureAwait(false);
}
}
private static async Task LoadTileAsync(Tile tile, Func<Task<ImageSource>> loadImageFunc)
{

View file

@ -1,241 +0,0 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// Copyright © 2023 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;
using System.Security.AccessControl;
using System.Security.Principal;
namespace MapControl.Caching
{
public partial class ImageFileCache : ObjectCache
{
private static readonly FileSystemAccessRule fullControlRule = new FileSystemAccessRule(
new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null),
FileSystemRights.FullControl, AccessControlType.Allow);
private readonly MemoryCache memoryCache = MemoryCache.Default;
public override string Name => string.Empty;
public override DefaultCacheCapabilities DefaultCacheCapabilities => DefaultCacheCapabilities.None;
public override object this[string key]
{
get => Get(key);
set => Set(key, value, null);
}
protected override IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
throw new NotSupportedException("ImageFileCache does not support the ability to enumerate items.");
}
public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable<string> keys, string regionName = null)
{
throw new NotSupportedException("ImageFileCache does not support the ability to create change monitors.");
}
public override long GetCount(string regionName = null)
{
throw new NotSupportedException("ImageFileCache does not support the ability to count items.");
}
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(nameof(key));
}
if (memoryCache.Contains(key))
{
return true;
}
var path = GetPath(key);
try
{
return path != null && File.Exists(path);
}
catch (Exception ex)
{
Debug.WriteLine($"ImageFileCache: Failed finding {path}: {ex.Message}");
}
return false;
}
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(nameof(key));
}
var cacheItem = memoryCache.Get(key) as Tuple<byte[], DateTime>;
if (cacheItem == null)
{
var path = GetPath(key);
try
{
if (path != null && File.Exists(path))
{
var buffer = File.ReadAllBytes(path);
var expiration = ReadExpiration(ref buffer);
cacheItem = new Tuple<byte[], DateTime>(buffer, expiration);
memoryCache.Set(key, cacheItem, new CacheItemPolicy { AbsoluteExpiration = expiration });
}
}
catch (Exception ex)
{
Debug.WriteLine($"ImageFileCache: Failed reading {path}: {ex.Message}");
}
}
return cacheItem;
}
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<string, object> GetValues(IEnumerable<string> 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("ImageFileCache does not support named regions.");
}
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (!(value is Tuple<byte[], DateTime> cacheItem))
{
throw new ArgumentException("The value argument must be a Tuple<byte[], DateTime>.", nameof(value));
}
memoryCache.Set(key, cacheItem, policy);
var buffer = cacheItem.Item1;
var path = GetPath(key);
if (buffer != null && buffer.Length > 0 && path != null)
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
using (var stream = File.Create(path))
{
stream.Write(buffer, 0, buffer.Length);
WriteExpiration(stream, cacheItem.Item2);
}
var fileInfo = new FileInfo(path);
var fileSecurity = fileInfo.GetAccessControl();
fileSecurity.AddAccessRule(fullControlRule);
fileInfo.SetAccessControl(fileSecurity);
}
catch (Exception ex)
{
Debug.WriteLine($"ImageFileCache: Failed writing {path}: {ex.Message}");
}
}
}
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)
{
if (regionName != null)
{
throw new NotSupportedException("ImageFileCache does not support named regions.");
}
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
memoryCache.Remove(key);
var path = GetPath(key);
try
{
if (path != null && File.Exists(path))
{
File.Delete(path);
}
}
catch (Exception ex)
{
Debug.WriteLine($"ImageFileCache: Failed removing {path}: {ex.Message}");
}
return null;
}
}
}

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-windows;net7.0-windows;net6.0-windows;net48;net462</TargetFrameworks>
<TargetFrameworks>net6.0-windows;net462</TargetFrameworks>
<UseWPF>true</UseWPF>
<RootNamespace>MapControl</RootNamespace>
<AssemblyTitle>XAML Map Control Library for WPF</AssemblyTitle>
@ -24,13 +24,12 @@
<Compile Include="..\Shared\*.cs" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.EndsWith('windows'))">
<PackageReference Include="System.Runtime.Caching" Version="8.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework.TrimEnd(`2468`))'=='net'">
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
<Reference Include="System.IO.Compression" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Runtime.Caching" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
</ItemGroup>
</Project>

View file

@ -4,7 +4,6 @@
using System;
using System.IO;
using System.Runtime.Caching;
using System.Threading.Tasks;
using System.Windows.Media;
@ -13,42 +12,11 @@ namespace MapControl
public partial class TileImageLoader
{
/// <summary>
/// Default folder path where an ObjectCache instance may save cached data, i.e. C:\ProgramData\MapControl\TileCache
/// Default folder path where an IImageCache instance may save cached data, i.e. C:\ProgramData\MapControl\TileCache
/// </summary>
public static string DefaultCacheFolder =>
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapControl", "TileCache");
/// <summary>
/// An ObjectCache instance used to cache tile image data. The default value is MemoryCache.Default.
/// </summary>
public static ObjectCache Cache { get; set; } = MemoryCache.Default;
private static async Task LoadCachedTileAsync(Tile tile, Uri uri, string cacheKey)
{
var cacheItem = Cache.Get(cacheKey) as Tuple<byte[], DateTime>;
var buffer = cacheItem?.Item1;
if (cacheItem == null || cacheItem.Item2 < DateTime.UtcNow)
{
var response = await ImageLoader.GetHttpResponseAsync(uri).ConfigureAwait(false);
if (response != null) // download succeeded
{
buffer = response.Buffer; // may be null or empty when no tile available, but still be cached
cacheItem = Tuple.Create(buffer, GetExpiration(response.MaxAge));
Cache.Set(cacheKey, cacheItem, new CacheItemPolicy { AbsoluteExpiration = cacheItem.Item2 });
}
}
//else System.Diagnostics.Debug.WriteLine($"Cached: {cacheKey}");
if (buffer != null && buffer.Length > 0)
{
await LoadTileAsync(tile, () => ImageLoader.LoadImageAsync(buffer)).ConfigureAwait(false);
}
}
private static async Task LoadTileAsync(Tile tile, Func<Task<ImageSource>> loadImageFunc)
{

View file

@ -1,60 +0,0 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// Copyright © 2023 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
namespace MapControl.Caching
{
public partial class ImageFileCache : IImageCache
{
public async Task<Tuple<byte[], DateTime>> GetAsync(string key)
{
Tuple<byte[], DateTime> cacheItem = null;
var path = GetPath(key);
try
{
if (path != null && File.Exists(path))
{
var buffer = await File.ReadAllBytesAsync(path);
var expiration = ReadExpiration(ref buffer);
cacheItem = Tuple.Create(buffer, expiration);
}
}
catch (Exception ex)
{
Debug.WriteLine($"ImageFileCache: Failed reading {path}: {ex.Message}");
}
return cacheItem;
}
public async Task SetAsync(string key, byte[] buffer, DateTime expiration)
{
var path = GetPath(key);
if (buffer != null && buffer.Length > 0 && path != null)
{
try
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
using (var stream = File.Create(path))
{
await stream.WriteAsync(buffer, 0, buffer.Length);
await WriteExpirationAsync(stream, expiration);
}
}
catch (Exception ex)
{
Debug.WriteLine($"ImageFileCache: Failed writing {path}: {ex.Message}");
}
}
}
}
}

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-windows10.0.17763.0;net7.0-windows10.0.17763.0;net6.0-windows10.0.17763.0</TargetFrameworks>
<TargetFramework>net6.0-windows10.0.17763.0</TargetFramework>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseRidGraph>true</UseRidGraph>
<UseWinUI>true</UseWinUI>
@ -29,5 +29,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
</ItemGroup>
</Project>

View file

@ -10,16 +10,6 @@ using System.Threading.Tasks;
namespace MapControl
{
namespace Caching
{
public interface IImageCache
{
Task<Tuple<byte[], DateTime>> GetAsync(string key);
Task SetAsync(string key, byte[] buffer, DateTime expiration);
}
}
public partial class TileImageLoader
{
/// <summary>
@ -28,35 +18,6 @@ namespace MapControl
public static string DefaultCacheFolder =>
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapControl", "TileCache");
/// <summary>
/// An IImageCache implementation used to cache tile images.
/// </summary>
public static Caching.IImageCache Cache { get; set; }
private static async Task LoadCachedTileAsync(Tile tile, Uri uri, string cacheKey)
{
var cacheItem = await Cache.GetAsync(cacheKey).ConfigureAwait(false);
var buffer = cacheItem?.Item1;
if (cacheItem == null || cacheItem.Item2 < DateTime.UtcNow)
{
var response = await ImageLoader.GetHttpResponseAsync(uri).ConfigureAwait(false);
if (response != null) // download succeeded
{
buffer = response.Buffer; // may be null or empty when no tile available, but still be cached
await Cache.SetAsync(cacheKey, buffer, GetExpiration(response.MaxAge)).ConfigureAwait(false);
}
}
//else System.Diagnostics.Debug.WriteLine($"Cached: {cacheKey}");
if (buffer != null && buffer.Length > 0)
{
await LoadTileAsync(tile, () => ImageLoader.LoadImageAsync(buffer)).ConfigureAwait(false);
}
}
private static Task LoadTileAsync(Tile tile, Func<Task<ImageSource>> loadImageFunc)
{

View file

@ -15,12 +15,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MBTiles", "MBTiles", "{CEAD
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBTiles.WPF", "MBTiles\WPF\MBTiles.WPF.csproj", "{38B18AB6-6E70-4696-8FB4-E8C8E12BF50C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FileDbCache", "FileDbCache", "{261905DE-9653-4567-B498-1F46BEA2A4F3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileDbCache.WPF", "FileDbCache\WPF\FileDbCache.WPF.csproj", "{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileDbCache.UWP", "FileDbCache\UWP\FileDbCache.UWP.csproj", "{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapControl.UWP", "MapControl\UWP\MapControl.UWP.csproj", "{9545F73C-9C35-4CF6-BAAE-19A0BAEBD344}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBTiles.UWP", "MBTiles\UWP\MBTiles.UWP.csproj", "{DCC111E9-EC8B-492A-A09D-DF390D83AE8D}"
@ -33,18 +27,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfApplication", "SampleApp
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapProjections.UWP", "MapProjections\UWP\MapProjections.UWP.csproj", "{9EE69591-5EDC-45E3-893E-2F9A4B82D538}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SQLiteCache", "SQLiteCache", "{96FD1258-1597-48A2-8D64-1ADAE13E886A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLiteCache.UWP", "SQLiteCache\UWP\SQLiteCache.UWP.csproj", "{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SQLiteCache.WPF", "SQLiteCache\WPF\SQLiteCache.WPF.csproj", "{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MapControl.WinUI", "MapControl\WinUI\MapControl.WinUI.csproj", "{ACA8E56C-0F82-4010-A83E-2DBFF5D16919}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileDbCache.WinUI", "FileDbCache\WinUI\FileDbCache.WinUI.csproj", "{DFE09FD5-530D-48AB-8A46-4611F21BBBC3}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SQLiteCache.WinUI", "SQLiteCache\WinUI\SQLiteCache.WinUI.csproj", "{E33FC359-F713-462C-8A8E-7EEA15E36BE1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MapProjections.WinUI", "MapProjections\WinUI\MapProjections.WinUI.csproj", "{3572F71A-83FE-459D-8370-002CA28827FE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MBTiles.WinUI", "MBTiles\WinUI\MBTiles.WinUI.csproj", "{817D606F-A22D-485C-89CF-86062C8E97EF}"
@ -61,6 +45,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MapUiTools.WinUI", "MapUiTo
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MapUiTools.WPF", "MapUiTools\WPF\MapUiTools.WPF.csproj", "{12430DAE-DC53-4C37-95D5-B8923B5FD3D7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Caches", "Caches", "{69E6CD1A-5619-4549-95FF-2FD126F1A5D2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileDbCache", "Caches\FileDbCache\FileDbCache.csproj", "{E5A7A66A-36EC-4775-850A-A64253DF0383}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SQLiteCache", "Caches\SQLiteCache\SQLiteCache.csproj", "{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -129,38 +119,6 @@ Global
{38B18AB6-6E70-4696-8FB4-E8C8E12BF50C}.Release|x64.Build.0 = Release|Any CPU
{38B18AB6-6E70-4696-8FB4-E8C8E12BF50C}.Release|x86.ActiveCfg = Release|Any CPU
{38B18AB6-6E70-4696-8FB4-E8C8E12BF50C}.Release|x86.Build.0 = Release|Any CPU
{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133}.Debug|arm64.ActiveCfg = Debug|Any CPU
{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133}.Debug|arm64.Build.0 = Debug|Any CPU
{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133}.Debug|x64.ActiveCfg = Debug|Any CPU
{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133}.Debug|x64.Build.0 = Debug|Any CPU
{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133}.Debug|x86.ActiveCfg = Debug|Any CPU
{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133}.Debug|x86.Build.0 = Debug|Any CPU
{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133}.Release|Any CPU.Build.0 = Release|Any CPU
{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133}.Release|arm64.ActiveCfg = Release|Any CPU
{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133}.Release|arm64.Build.0 = Release|Any CPU
{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133}.Release|x64.ActiveCfg = Release|Any CPU
{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133}.Release|x64.Build.0 = Release|Any CPU
{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133}.Release|x86.ActiveCfg = Release|Any CPU
{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133}.Release|x86.Build.0 = Release|Any CPU
{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}.Debug|arm64.ActiveCfg = Debug|Any CPU
{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}.Debug|arm64.Build.0 = Debug|Any CPU
{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}.Debug|x64.ActiveCfg = Debug|Any CPU
{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}.Debug|x64.Build.0 = Debug|Any CPU
{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}.Debug|x86.ActiveCfg = Debug|Any CPU
{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}.Debug|x86.Build.0 = Debug|Any CPU
{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}.Release|Any CPU.Build.0 = Release|Any CPU
{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}.Release|arm64.ActiveCfg = Release|Any CPU
{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}.Release|arm64.Build.0 = Release|Any CPU
{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}.Release|x64.ActiveCfg = Release|Any CPU
{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}.Release|x64.Build.0 = Release|Any CPU
{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}.Release|x86.ActiveCfg = Release|Any CPU
{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D}.Release|x86.Build.0 = Release|Any CPU
{9545F73C-9C35-4CF6-BAAE-19A0BAEBD344}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9545F73C-9C35-4CF6-BAAE-19A0BAEBD344}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9545F73C-9C35-4CF6-BAAE-19A0BAEBD344}.Debug|arm64.ActiveCfg = Debug|Any CPU
@ -241,38 +199,6 @@ Global
{9EE69591-5EDC-45E3-893E-2F9A4B82D538}.Release|x64.Build.0 = Release|Any CPU
{9EE69591-5EDC-45E3-893E-2F9A4B82D538}.Release|x86.ActiveCfg = Release|Any CPU
{9EE69591-5EDC-45E3-893E-2F9A4B82D538}.Release|x86.Build.0 = Release|Any CPU
{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}.Debug|arm64.ActiveCfg = Debug|Any CPU
{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}.Debug|arm64.Build.0 = Debug|Any CPU
{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}.Debug|x64.ActiveCfg = Debug|Any CPU
{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}.Debug|x64.Build.0 = Debug|Any CPU
{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}.Debug|x86.ActiveCfg = Debug|Any CPU
{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}.Debug|x86.Build.0 = Debug|Any CPU
{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}.Release|Any CPU.Build.0 = Release|Any CPU
{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}.Release|arm64.ActiveCfg = Release|Any CPU
{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}.Release|arm64.Build.0 = Release|Any CPU
{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}.Release|x64.ActiveCfg = Release|Any CPU
{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}.Release|x64.Build.0 = Release|Any CPU
{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}.Release|x86.ActiveCfg = Release|Any CPU
{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}.Release|x86.Build.0 = Release|Any CPU
{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349}.Debug|arm64.ActiveCfg = Debug|Any CPU
{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349}.Debug|arm64.Build.0 = Debug|Any CPU
{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349}.Debug|x64.ActiveCfg = Debug|Any CPU
{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349}.Debug|x64.Build.0 = Debug|Any CPU
{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349}.Debug|x86.ActiveCfg = Debug|Any CPU
{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349}.Debug|x86.Build.0 = Debug|Any CPU
{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349}.Release|Any CPU.Build.0 = Release|Any CPU
{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349}.Release|arm64.ActiveCfg = Release|Any CPU
{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349}.Release|arm64.Build.0 = Release|Any CPU
{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349}.Release|x64.ActiveCfg = Release|Any CPU
{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349}.Release|x64.Build.0 = Release|Any CPU
{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349}.Release|x86.ActiveCfg = Release|Any CPU
{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349}.Release|x86.Build.0 = Release|Any CPU
{ACA8E56C-0F82-4010-A83E-2DBFF5D16919}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ACA8E56C-0F82-4010-A83E-2DBFF5D16919}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ACA8E56C-0F82-4010-A83E-2DBFF5D16919}.Debug|arm64.ActiveCfg = Debug|Any CPU
@ -289,38 +215,6 @@ Global
{ACA8E56C-0F82-4010-A83E-2DBFF5D16919}.Release|x64.Build.0 = Release|Any CPU
{ACA8E56C-0F82-4010-A83E-2DBFF5D16919}.Release|x86.ActiveCfg = Release|Any CPU
{ACA8E56C-0F82-4010-A83E-2DBFF5D16919}.Release|x86.Build.0 = Release|Any CPU
{DFE09FD5-530D-48AB-8A46-4611F21BBBC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DFE09FD5-530D-48AB-8A46-4611F21BBBC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DFE09FD5-530D-48AB-8A46-4611F21BBBC3}.Debug|arm64.ActiveCfg = Debug|Any CPU
{DFE09FD5-530D-48AB-8A46-4611F21BBBC3}.Debug|arm64.Build.0 = Debug|Any CPU
{DFE09FD5-530D-48AB-8A46-4611F21BBBC3}.Debug|x64.ActiveCfg = Debug|Any CPU
{DFE09FD5-530D-48AB-8A46-4611F21BBBC3}.Debug|x64.Build.0 = Debug|Any CPU
{DFE09FD5-530D-48AB-8A46-4611F21BBBC3}.Debug|x86.ActiveCfg = Debug|Any CPU
{DFE09FD5-530D-48AB-8A46-4611F21BBBC3}.Debug|x86.Build.0 = Debug|Any CPU
{DFE09FD5-530D-48AB-8A46-4611F21BBBC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DFE09FD5-530D-48AB-8A46-4611F21BBBC3}.Release|Any CPU.Build.0 = Release|Any CPU
{DFE09FD5-530D-48AB-8A46-4611F21BBBC3}.Release|arm64.ActiveCfg = Release|Any CPU
{DFE09FD5-530D-48AB-8A46-4611F21BBBC3}.Release|arm64.Build.0 = Release|Any CPU
{DFE09FD5-530D-48AB-8A46-4611F21BBBC3}.Release|x64.ActiveCfg = Release|Any CPU
{DFE09FD5-530D-48AB-8A46-4611F21BBBC3}.Release|x64.Build.0 = Release|Any CPU
{DFE09FD5-530D-48AB-8A46-4611F21BBBC3}.Release|x86.ActiveCfg = Release|Any CPU
{DFE09FD5-530D-48AB-8A46-4611F21BBBC3}.Release|x86.Build.0 = Release|Any CPU
{E33FC359-F713-462C-8A8E-7EEA15E36BE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E33FC359-F713-462C-8A8E-7EEA15E36BE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E33FC359-F713-462C-8A8E-7EEA15E36BE1}.Debug|arm64.ActiveCfg = Debug|Any CPU
{E33FC359-F713-462C-8A8E-7EEA15E36BE1}.Debug|arm64.Build.0 = Debug|Any CPU
{E33FC359-F713-462C-8A8E-7EEA15E36BE1}.Debug|x64.ActiveCfg = Debug|Any CPU
{E33FC359-F713-462C-8A8E-7EEA15E36BE1}.Debug|x64.Build.0 = Debug|Any CPU
{E33FC359-F713-462C-8A8E-7EEA15E36BE1}.Debug|x86.ActiveCfg = Debug|Any CPU
{E33FC359-F713-462C-8A8E-7EEA15E36BE1}.Debug|x86.Build.0 = Debug|Any CPU
{E33FC359-F713-462C-8A8E-7EEA15E36BE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E33FC359-F713-462C-8A8E-7EEA15E36BE1}.Release|Any CPU.Build.0 = Release|Any CPU
{E33FC359-F713-462C-8A8E-7EEA15E36BE1}.Release|arm64.ActiveCfg = Release|Any CPU
{E33FC359-F713-462C-8A8E-7EEA15E36BE1}.Release|arm64.Build.0 = Release|Any CPU
{E33FC359-F713-462C-8A8E-7EEA15E36BE1}.Release|x64.ActiveCfg = Release|Any CPU
{E33FC359-F713-462C-8A8E-7EEA15E36BE1}.Release|x64.Build.0 = Release|Any CPU
{E33FC359-F713-462C-8A8E-7EEA15E36BE1}.Release|x86.ActiveCfg = Release|Any CPU
{E33FC359-F713-462C-8A8E-7EEA15E36BE1}.Release|x86.Build.0 = Release|Any CPU
{3572F71A-83FE-459D-8370-002CA28827FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3572F71A-83FE-459D-8370-002CA28827FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3572F71A-83FE-459D-8370-002CA28827FE}.Debug|arm64.ActiveCfg = Debug|Any CPU
@ -441,6 +335,38 @@ Global
{12430DAE-DC53-4C37-95D5-B8923B5FD3D7}.Release|x64.Build.0 = Release|Any CPU
{12430DAE-DC53-4C37-95D5-B8923B5FD3D7}.Release|x86.ActiveCfg = Release|Any CPU
{12430DAE-DC53-4C37-95D5-B8923B5FD3D7}.Release|x86.Build.0 = Release|Any CPU
{E5A7A66A-36EC-4775-850A-A64253DF0383}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5A7A66A-36EC-4775-850A-A64253DF0383}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5A7A66A-36EC-4775-850A-A64253DF0383}.Debug|arm64.ActiveCfg = Debug|Any CPU
{E5A7A66A-36EC-4775-850A-A64253DF0383}.Debug|arm64.Build.0 = Debug|Any CPU
{E5A7A66A-36EC-4775-850A-A64253DF0383}.Debug|x64.ActiveCfg = Debug|Any CPU
{E5A7A66A-36EC-4775-850A-A64253DF0383}.Debug|x64.Build.0 = Debug|Any CPU
{E5A7A66A-36EC-4775-850A-A64253DF0383}.Debug|x86.ActiveCfg = Debug|Any CPU
{E5A7A66A-36EC-4775-850A-A64253DF0383}.Debug|x86.Build.0 = Debug|Any CPU
{E5A7A66A-36EC-4775-850A-A64253DF0383}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5A7A66A-36EC-4775-850A-A64253DF0383}.Release|Any CPU.Build.0 = Release|Any CPU
{E5A7A66A-36EC-4775-850A-A64253DF0383}.Release|arm64.ActiveCfg = Release|Any CPU
{E5A7A66A-36EC-4775-850A-A64253DF0383}.Release|arm64.Build.0 = Release|Any CPU
{E5A7A66A-36EC-4775-850A-A64253DF0383}.Release|x64.ActiveCfg = Release|Any CPU
{E5A7A66A-36EC-4775-850A-A64253DF0383}.Release|x64.Build.0 = Release|Any CPU
{E5A7A66A-36EC-4775-850A-A64253DF0383}.Release|x86.ActiveCfg = Release|Any CPU
{E5A7A66A-36EC-4775-850A-A64253DF0383}.Release|x86.Build.0 = Release|Any CPU
{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC}.Debug|arm64.ActiveCfg = Debug|Any CPU
{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC}.Debug|arm64.Build.0 = Debug|Any CPU
{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC}.Debug|x64.ActiveCfg = Debug|Any CPU
{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC}.Debug|x64.Build.0 = Debug|Any CPU
{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC}.Debug|x86.ActiveCfg = Debug|Any CPU
{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC}.Debug|x86.Build.0 = Debug|Any CPU
{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC}.Release|Any CPU.Build.0 = Release|Any CPU
{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC}.Release|arm64.ActiveCfg = Release|Any CPU
{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC}.Release|arm64.Build.0 = Release|Any CPU
{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC}.Release|x64.ActiveCfg = Release|Any CPU
{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC}.Release|x64.Build.0 = Release|Any CPU
{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC}.Release|x86.ActiveCfg = Release|Any CPU
{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -449,18 +375,12 @@ Global
{A204A102-C745-4D65-AEC8-7B96FAEDEF2D} = {52AECE49-F314-4F76-98F2-FA800F07824B}
{AA62B4AA-1CA3-4C20-BEB7-B824D0FC4BD1} = {8F2103C2-78AF-4810-8FB9-67572F50C8FC}
{38B18AB6-6E70-4696-8FB4-E8C8E12BF50C} = {CEAD0EA1-A971-4F5F-9EAE-C72F75D1F737}
{AD1CB53E-7AA4-4EC0-B901-B4E0E2665133} = {261905DE-9653-4567-B498-1F46BEA2A4F3}
{BEEB142A-5FA3-468D-810A-32A4A5BD6D5D} = {261905DE-9653-4567-B498-1F46BEA2A4F3}
{9545F73C-9C35-4CF6-BAAE-19A0BAEBD344} = {52AECE49-F314-4F76-98F2-FA800F07824B}
{DCC111E9-EC8B-492A-A09D-DF390D83AE8D} = {CEAD0EA1-A971-4F5F-9EAE-C72F75D1F737}
{426C21C0-5F14-491F-BCD1-6D2993510420} = {7BC11E28-8D3B-4C5B-AC08-AB249CC95F6D}
{F92DA93D-75DB-4308-A5F9-6B4C3908A675} = {8F2103C2-78AF-4810-8FB9-67572F50C8FC}
{9EE69591-5EDC-45E3-893E-2F9A4B82D538} = {7BC11E28-8D3B-4C5B-AC08-AB249CC95F6D}
{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1} = {96FD1258-1597-48A2-8D64-1ADAE13E886A}
{0109C2F0-BA2C-420F-B2CA-DB5B29B1A349} = {96FD1258-1597-48A2-8D64-1ADAE13E886A}
{ACA8E56C-0F82-4010-A83E-2DBFF5D16919} = {52AECE49-F314-4F76-98F2-FA800F07824B}
{DFE09FD5-530D-48AB-8A46-4611F21BBBC3} = {261905DE-9653-4567-B498-1F46BEA2A4F3}
{E33FC359-F713-462C-8A8E-7EEA15E36BE1} = {96FD1258-1597-48A2-8D64-1ADAE13E886A}
{3572F71A-83FE-459D-8370-002CA28827FE} = {7BC11E28-8D3B-4C5B-AC08-AB249CC95F6D}
{817D606F-A22D-485C-89CF-86062C8E97EF} = {CEAD0EA1-A971-4F5F-9EAE-C72F75D1F737}
{751EF297-7CF4-4879-BA8F-42661FA68668} = {8F2103C2-78AF-4810-8FB9-67572F50C8FC}
@ -468,6 +388,8 @@ Global
{DFFE8E49-AA07-457E-A459-99326B44F828} = {90C681E9-12AE-4B5F-932D-7EF5D35D8436}
{C412209E-D81D-4ACB-BECD-FEEF52B93468} = {90C681E9-12AE-4B5F-932D-7EF5D35D8436}
{12430DAE-DC53-4C37-95D5-B8923B5FD3D7} = {90C681E9-12AE-4B5F-932D-7EF5D35D8436}
{E5A7A66A-36EC-4775-850A-A64253DF0383} = {69E6CD1A-5619-4549-95FF-2FD126F1A5D2}
{FDD70FB5-3B6D-43DF-8C2E-04100315C8BC} = {69E6CD1A-5619-4549-95FF-2FD126F1A5D2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {458346DD-B23F-4FDC-8F9D-A10F1882A4DB}

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-windows;net7.0-windows;net6.0-windows;net48;net462</TargetFrameworks>
<TargetFrameworks>net6.0-windows;net462</TargetFrameworks>
<UseWPF>true</UseWPF>
<RootNamespace>MapControl.Projections</RootNamespace>
<AssemblyTitle>XAML Map Control Projections Library for WPF</AssemblyTitle>
@ -24,7 +24,7 @@
<Compile Include="..\Shared\*.cs" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework.TrimEnd(`2468`))'=='net'">
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
<Reference Include="System.Net.Http" />
</ItemGroup>

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-windows10.0.17763.0;net7.0-windows10.0.17763.0;net6.0-windows10.0.17763.0</TargetFrameworks>
<TargetFramework>net6.0-windows10.0.17763.0</TargetFramework>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseRidGraph>true</UseRidGraph>
<UseWinUI>true</UseWinUI>

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-windows;net7.0-windows;net6.0-windows;net48;net462</TargetFrameworks>
<TargetFrameworks>net6.0-windows;net462</TargetFrameworks>
<UseWPF>true</UseWPF>
<RootNamespace>MapControl.UiTools</RootNamespace>
<AssemblyTitle>XAML Map Control UI Tools Library for WPF</AssemblyTitle>

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-windows10.0.17763.0;net7.0-windows10.0.17763.0;net6.0-windows10.0.17763.0</TargetFrameworks>
<TargetFramework>net6.0-windows10.0.17763.0</TargetFramework>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseRidGraph>true</UseRidGraph>
<UseWinUI>true</UseWinUI>

View file

@ -1,98 +0,0 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// Copyright © 2023 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Data.SQLite;
using System.Diagnostics;
using System.IO;
namespace MapControl.Caching
{
/// <summary>
/// Image cache implementation based on SqLite.
/// </summary>
public sealed partial class SQLiteCache : IDisposable
{
private readonly SQLiteConnection connection;
public SQLiteCache(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException("The path argument must not be null or empty.", nameof(path));
}
if (string.IsNullOrEmpty(Path.GetExtension(path)))
{
path = Path.Combine(path, "TileCache.sqlite");
}
connection = Open(Path.GetFullPath(path));
Clean();
}
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();
}
#if DEBUG
using (var command = new SQLiteCommand("select changes()", connection))
{
var deleted = (long)command.ExecuteScalar();
if (deleted > 0)
{
Debug.WriteLine($"SQLiteCache: Deleted {deleted} expired items");
}
}
#endif
}
private SQLiteCommand RemoveItemCommand(string key)
{
var command = new SQLiteCommand("delete from items where key = @key", connection);
command.Parameters.AddWithValue("@key", key);
return command;
}
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, byte[] buffer, DateTime expiration)
{
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 ?? new byte[0]);
return command;
}
}
}

View file

@ -1,13 +0,0 @@
using System.Reflection;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("XAML Map Control SQLiteCache Library for UWP")]
[assembly: AssemblyProduct("XAML Map Control")]
[assembly: AssemblyCompany("Clemens Fischer")]
[assembly: AssemblyCopyright("Copyright © 2023 Clemens Fischer")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyVersion("8.3.0")]
[assembly: AssemblyFileVersion("8.3.0")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]

View file

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file contains Runtime Directives, specifications about types your application accesses
through reflection and other dynamic code patterns. Runtime Directives are used to control the
.NET Native optimizer and ensure that it does not remove code accessed by your library. If your
library does not do any reflection, then you generally do not need to edit this file. However,
if your library reflects over types, especially types passed to it or derived from its types,
then you should write Runtime Directives.
The most common use of reflection in libraries is to discover information about types passed
to the library. Runtime Directives have three ways to express requirements on types passed to
your library.
1. Parameter, GenericParameter, TypeParameter, TypeEnumerableParameter
Use these directives to reflect over types passed as a parameter.
2. SubTypes
Use a SubTypes directive to reflect over types derived from another type.
3. AttributeImplies
Use an AttributeImplies directive to indicate that your library needs to reflect over
types or methods decorated with an attribute.
For more information on writing Runtime Directives for libraries, please visit
https://go.microsoft.com/fwlink/?LinkID=391919
-->
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Library Name="SQLiteCache.UWP">
<!-- add directives for your library here -->
</Library>
</Directives>

View file

@ -1,81 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{56DFA7CF-F31D-45CE-9C36-DA8DBB8413B1}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MapControl.Caching</RootNamespace>
<AssemblyName>SQLiteCache.UWP</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion Condition=" '$(TargetPlatformVersion)' == '' ">10.0.22000.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;UWP</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>UWP</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Shared\SQLiteCache.cs">
<Link>SQLiteCache.cs</Link>
</Compile>
<Compile Include="..\WinUI\SQLiteCache.WinUI.cs">
<Link>SQLiteCache.WinUI.cs</Link>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Properties\SQLiteCache.UWP.rd.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NETCore.UniversalWindowsPlatform">
<Version>6.2.14</Version>
</PackageReference>
<PackageReference Include="System.Data.SQLite.Core">
<Version>1.0.118</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<None Include="..\..\MapControl.snk">
<Link>MapControl.snk</Link>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\MapControl\UWP\MapControl.UWP.csproj">
<Project>{9545f73c-9c35-4cf6-baae-19a0baebd344}</Project>
<Name>MapControl.UWP</Name>
</ProjectReference>
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>
<PropertyGroup>
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
</Project>

View file

@ -1,215 +0,0 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// Copyright © 2023 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Caching;
namespace MapControl.Caching
{
public partial class SQLiteCache : ObjectCache
{
public override string Name => string.Empty;
public override DefaultCacheCapabilities DefaultCacheCapabilities =>
DefaultCacheCapabilities.AbsoluteExpirations | DefaultCacheCapabilities.SlidingExpirations;
public override object this[string key]
{
get => Get(key);
set => Set(key, value, null);
}
protected override IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
throw new NotSupportedException("SQLiteCache does not support the ability to enumerate items.");
}
public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable<string> keys, string regionName = null)
{
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("SQLiteCache does not support named regions.");
}
try
{
using (var command = new SQLiteCommand("select count(*) from items", connection))
{
return (long)command.ExecuteScalar();
}
}
catch (Exception ex)
{
Debug.WriteLine($"SQLiteCache.GetCount(): {ex.Message}");
}
return 0;
}
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(nameof(key));
}
try
{
using (var command = GetItemCommand(key))
{
return command.ExecuteReader().Read();
}
}
catch (Exception ex)
{
Debug.WriteLine($"SQLiteCache.Contains({key}): {ex.Message}");
}
return false;
}
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(nameof(key));
}
try
{
using (var command = GetItemCommand(key))
{
var reader = command.ExecuteReader();
if (reader.Read())
{
return Tuple.Create((byte[])reader["buffer"], new DateTime((long)reader["expiration"]));
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"SQLiteCache.Get({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<string, object> GetValues(IEnumerable<string> 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("SQLiteCache does not support named regions.");
}
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (!(value is Tuple<byte[], DateTime> cacheItem))
{
throw new ArgumentException("The value argument must be a Tuple<byte[], DateTime>.", nameof(value));
}
try
{
using (var command = SetItemCommand(key, cacheItem.Item1, cacheItem.Item2))
{
command.ExecuteNonQuery();
}
}
catch (Exception ex)
{
Debug.WriteLine($"SQLiteCache.Set({key}): {ex.Message}");
}
}
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
{
using (var command = RemoveItemCommand(key))
{
command.ExecuteNonQuery();
}
}
catch (Exception ex)
{
Debug.WriteLine($"SQLiteCache.Remove({key}): {ex.Message}");
}
}
return oldValue;
}
}
}

View file

@ -1,38 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-windows;net7.0-windows;net6.0-windows;net48;net462</TargetFrameworks>
<UseWPF>true</UseWPF>
<RootNamespace>MapControl.Caching</RootNamespace>
<AssemblyTitle>XAML Map Control SQLiteCache Library for WPF</AssemblyTitle>
<Product>XAML Map Control</Product>
<Version>8.3.0</Version>
<Authors>Clemens Fischer</Authors>
<Copyright>Copyright © 2023 Clemens Fischer</Copyright>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageId>XAML.MapControl.SQLiteCache</PackageId>
<DefineConstants></DefineConstants>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\MapControl.snk" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Shared\*.cs" />
</ItemGroup>
<ItemGroup Condition="$(TargetFramework.EndsWith('windows'))">
<PackageReference Include="System.Runtime.Caching" Version="8.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework.TrimEnd(`2468`))'=='net'">
<Reference Include="System.Runtime.Caching" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.118" />
</ItemGroup>
</Project>

View file

@ -1,50 +0,0 @@
// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control
// Copyright © 2023 Clemens Fischer
// Licensed under the Microsoft Public License (Ms-PL)
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace MapControl.Caching
{
public partial class SQLiteCache : IImageCache
{
public async Task<Tuple<byte[], DateTime>> GetAsync(string key)
{
try
{
using (var command = GetItemCommand(key))
{
var reader = await command.ExecuteReaderAsync();
if (await reader.ReadAsync())
{
return Tuple.Create((byte[])reader["buffer"], new DateTime((long)reader["expiration"]));
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"SQLiteCache.GetAsync({key}): {ex.Message}");
}
return null;
}
public async Task SetAsync(string key, byte[] buffer, DateTime expiration)
{
try
{
using (var command = SetItemCommand(key, buffer, expiration))
{
await command.ExecuteNonQueryAsync();
}
}
catch (Exception ex)
{
Debug.WriteLine($"SQLiteCache.SetAsync({key}): {ex.Message}");
}
}
}
}

View file

@ -1,38 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0-windows10.0.17763.0;net7.0-windows10.0.17763.0;net6.0-windows10.0.17763.0</TargetFrameworks>
<RuntimeIdentifiers>win10-x86;win10-x64;win10-arm64</RuntimeIdentifiers>
<UseRidGraph>true</UseRidGraph>
<UseWinUI>true</UseWinUI>
<RootNamespace>MapControl.Caching</RootNamespace>
<AssemblyTitle>XAML Map Control SQLiteCache Library for WinUI</AssemblyTitle>
<Product>XAML Map Control</Product>
<Version>8.3.0</Version>
<Authors>Clemens Fischer</Authors>
<Copyright>Copyright © 2023 Clemens Fischer</Copyright>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\..\MapControl.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageId>XAML.MapControl.SQLiteCache</PackageId>
<DefineConstants>WINUI</DefineConstants>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\MapControl.snk" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Shared\*.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.4.231219000" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.2428" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.118" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\MapControl\WinUI\MapControl.WinUI.csproj" />
</ItemGroup>
</Project>

View file

@ -19,7 +19,6 @@ namespace SampleApplication
TileImageLoader.Cache = new ImageFileCache(TileImageLoader.DefaultCacheFolder);
//TileImageLoader.Cache = new FileDbCache(TileImageLoader.DefaultCacheFolder);
//TileImageLoader.Cache = new SQLiteCache(TileImageLoader.DefaultCacheFolder);
//TileImageLoader.Cache = null;
var bingMapsApiKeyPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MapControl", "BingMapsApiKey.txt");

View file

@ -21,6 +21,8 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Caches\FileDbCache\FileDbCache.csproj" />
<ProjectReference Include="..\..\Caches\SQLiteCache\SQLiteCache.csproj" />
<ProjectReference Include="..\..\MapControl\WPF\MapControl.WPF.csproj" />
<ProjectReference Include="..\..\MapUiTools\WPF\MapUiTools.WPF.csproj" />
</ItemGroup>
@ -36,6 +38,5 @@
<ItemGroup Condition="'$(TargetFramework)'=='net48'">
<Reference Include="System.Net.Http" />
<Reference Include="System.Runtime.Caching" />
</ItemGroup>
</Project>