diff --git a/MapControl/Shared/ImageFileCache.cs b/MapControl/Shared/ImageFileCache.cs new file mode 100644 index 00000000..a1a376d9 --- /dev/null +++ b/MapControl/Shared/ImageFileCache.cs @@ -0,0 +1,71 @@ +// XAML Map Control - https://github.com/ClemensFischer/XAML-Map-Control +// © 2021 Clemens Fischer +// Licensed under the Microsoft Public License (Ms-PL) + +using System; +using System.Diagnostics; +using System.IO; +using System.Text; + +namespace MapControl.Caching +{ + /// + /// Image Cache implementation based on local image files. + /// The only valid data type for cached values is MapControl.ImageCacheItem. + /// + public partial class ImageFileCache + { + private const string expiresTag = "EXPIRES:"; + + private readonly string rootDirectory; + + public ImageFileCache(string directory) + { + if (string.IsNullOrEmpty(directory)) + { + throw new ArgumentException("The directory argument must not be null or empty.", nameof(directory)); + } + + rootDirectory = directory; + Debug.WriteLine("Created ImageFileCache in " + rootDirectory); + } + + private string GetPath(string key) + { + try + { + return Path.Combine(rootDirectory, Path.Combine(key.Split('/', ':', ';', ','))); + } + catch (Exception ex) + { + Debug.WriteLine("ImageFileCache: Invalid key {0}/{1}: {2}", rootDirectory, key, ex.Message); + } + + return null; + } + + private static DateTime ReadExpiration(ref byte[] buffer) + { + DateTime? 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) + { + if (buffer.Length >= 16 && + Encoding.ASCII.GetString(buffer, buffer.Length - 16, 8) == expiresTag) + { + return new DateTime(BitConverter.ToInt64(buffer, buffer.Length - 8), DateTimeKind.Utc); + } + + return null; + } + } +} diff --git a/MapControl/UWP/ImageFileCache.UWP.cs b/MapControl/UWP/ImageFileCache.UWP.cs index 8ff93582..bc28b68f 100644 --- a/MapControl/UWP/ImageFileCache.UWP.cs +++ b/MapControl/UWP/ImageFileCache.UWP.cs @@ -12,23 +12,8 @@ using Windows.Storage.Streams; namespace MapControl.Caching { - public class ImageFileCache : IImageCache + public partial class ImageFileCache : IImageCache { - private const string expiresTag = "EXPIRES:"; - - private readonly string rootDirectory; - - public ImageFileCache(string directory) - { - if (string.IsNullOrEmpty(directory)) - { - throw new ArgumentException("The directory argument must not be null or empty.", nameof(directory)); - } - - rootDirectory = directory; - Debug.WriteLine("Created ImageFileCache in " + rootDirectory); - } - public async Task GetAsync(string key) { ImageCacheItem imageCacheItem = null; @@ -39,7 +24,7 @@ namespace MapControl.Caching if (path != null && File.Exists(path)) { var buffer = await File.ReadAllBytesAsync(path); - var expiration = GetExpiration(ref buffer); + var expiration = ReadExpiration(ref buffer); imageCacheItem = new ImageCacheItem { @@ -83,32 +68,5 @@ namespace MapControl.Caching } } } - - private string GetPath(string key) - { - try - { - return Path.Combine(rootDirectory, Path.Combine(key.Split('/', ':', ';', ','))); - } - catch (Exception ex) - { - Debug.WriteLine("ImageFileCache: Invalid key {0}/{1}: {2}", rootDirectory, key, ex.Message); - } - - return null; - } - - private static DateTime GetExpiration(ref byte[] buffer) - { - DateTime expiration = DateTime.Today; - - if (buffer.Length > 16 && Encoding.ASCII.GetString(buffer, buffer.Length - 16, 8) == expiresTag) - { - expiration = new DateTime(BitConverter.ToInt64(buffer, buffer.Length - 8), DateTimeKind.Utc); - Array.Resize(ref buffer, buffer.Length - 16); - } - - return expiration; - } } } diff --git a/MapControl/UWP/MapControl.UWP.csproj b/MapControl/UWP/MapControl.UWP.csproj index b73d5592..78727012 100644 --- a/MapControl/UWP/MapControl.UWP.csproj +++ b/MapControl/UWP/MapControl.UWP.csproj @@ -77,6 +77,9 @@ HyperlinkText.cs + + ImageFileCache.cs + ImageLoader.cs diff --git a/MapControl/WPF/ImageFileCache.WPF.cs b/MapControl/WPF/ImageFileCache.WPF.cs index e9ec997a..d84462b2 100644 --- a/MapControl/WPF/ImageFileCache.WPF.cs +++ b/MapControl/WPF/ImageFileCache.WPF.cs @@ -19,27 +19,13 @@ namespace MapControl.Caching /// ObjectCache implementation based on local image files. /// The only valid data type for cached values is MapControl.ImageCacheItem. /// - public class ImageFileCache : ObjectCache + public partial class ImageFileCache : ObjectCache { - private const string expiresTag = "EXPIRES:"; - private static readonly FileSystemAccessRule fullControlRule = new FileSystemAccessRule( new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null), FileSystemRights.FullControl, AccessControlType.Allow); private readonly MemoryCache memoryCache = MemoryCache.Default; - private readonly string rootDirectory; - - public ImageFileCache(string directory) - { - if (string.IsNullOrEmpty(directory)) - { - throw new ArgumentException("The directory argument must not be null or empty.", nameof(directory)); - } - - rootDirectory = directory; - Debug.WriteLine("Created ImageFileCache in " + rootDirectory); - } public Task Clean() { @@ -115,7 +101,7 @@ namespace MapControl.Caching try { var buffer = File.ReadAllBytes(path); - var expiration = GetExpiration(ref buffer); + var expiration = ReadExpiration(ref buffer); imageCacheItem = new ImageCacheItem { @@ -282,20 +268,6 @@ namespace MapControl.Caching return null; } - private string GetPath(string key) - { - try - { - return Path.Combine(rootDirectory, Path.Combine(key.Split('/', ':', ';', ','))); - } - catch (Exception ex) - { - Debug.WriteLine("ImageFileCache: Invalid key {0}/{1}: {2}", rootDirectory, key, ex.Message); - } - - return null; - } - private async Task CleanRootDirectory() { foreach (var dir in new DirectoryInfo(rootDirectory).EnumerateDirectories()) @@ -349,22 +321,9 @@ namespace MapControl.Caching return deletedFileCount; } - private static DateTime GetExpiration(ref byte[] buffer) - { - DateTime expiration = DateTime.Today; - - if (buffer.Length > 16 && Encoding.ASCII.GetString(buffer, buffer.Length - 16, 8) == expiresTag) - { - expiration = new DateTime(BitConverter.ToInt64(buffer, buffer.Length - 8), DateTimeKind.Utc); - Array.Resize(ref buffer, buffer.Length - 16); - } - - return expiration; - } - private static async Task ReadExpirationAsync(FileInfo file) { - DateTime expiration = DateTime.Today; + DateTime? expiration = null; if (file.Length > 16) { @@ -374,15 +333,14 @@ namespace MapControl.Caching { stream.Seek(-16, SeekOrigin.End); - if (await stream.ReadAsync(buffer, 0, 16).ConfigureAwait(false) == 16 && - Encoding.ASCII.GetString(buffer, 0, 8) == expiresTag) + if (await stream.ReadAsync(buffer, 0, 16).ConfigureAwait(false) == 16) { - expiration = new DateTime(BitConverter.ToInt64(buffer, 8), DateTimeKind.Utc); + expiration = ReadExpiration(buffer); } } } - return expiration; + return expiration ?? DateTime.Today; } } } diff --git a/MapControl/WPF/TileImageLoader.WPF.cs b/MapControl/WPF/TileImageLoader.WPF.cs index d6b121be..02cef0cf 100644 --- a/MapControl/WPF/TileImageLoader.WPF.cs +++ b/MapControl/WPF/TileImageLoader.WPF.cs @@ -2,12 +2,11 @@ // © 2021 Clemens Fischer // Licensed under the Microsoft Public License (Ms-PL) +using MapControl.Caching; using System; using System.IO; using System.Runtime.Caching; using System.Threading.Tasks; -using System.Windows.Media; -using MapControl.Caching; namespace MapControl { @@ -39,7 +38,7 @@ namespace MapControl private static async Task LoadCachedTileAsync(Tile tile, Uri uri, string cacheKey) { - var cacheItem = await GetCacheAsync(cacheKey).ConfigureAwait(false); + var cacheItem = Cache.Get(cacheKey) as ImageCacheItem; var buffer = cacheItem?.Buffer; if (cacheItem == null || cacheItem.Expiration < DateTime.UtcNow) @@ -48,9 +47,15 @@ namespace MapControl if (response != null) // download succeeded { - buffer = response.Buffer; // may be null or empty when no tile available, but still be cached + buffer = response.Buffer; - await SetCacheAsync(cacheKey, buffer, GetExpiration(response.MaxAge)).ConfigureAwait(false); + cacheItem = new ImageCacheItem + { + Buffer = buffer, // may be null or empty when no tile available, but still be cached + Expiration = GetExpiration(response.MaxAge) + }; + + Cache.Set(cacheKey, cacheItem, new CacheItemPolicy { AbsoluteExpiration = cacheItem.Expiration }); } } @@ -68,26 +73,5 @@ namespace MapControl await tile.Image.Dispatcher.InvokeAsync(() => tile.SetImage(image)); } - - private static Task GetCacheAsync(string cacheKey) - { - return Task.Run(() => Cache.Get(cacheKey) as ImageCacheItem); - } - - private static Task SetCacheAsync(string cacheKey, byte[] buffer, DateTime expiration) - { - var imageCacheItem = new ImageCacheItem - { - Buffer = buffer, - Expiration = expiration - }; - - var cacheItemPolicy = new CacheItemPolicy - { - AbsoluteExpiration = expiration - }; - - return Task.Run(() => Cache.Set(cacheKey, imageCacheItem, cacheItemPolicy)); - } } }