From ed87d87f753198f4492075efcd940790168e947c Mon Sep 17 00:00:00 2001 From: ClemensFischer Date: Thu, 20 Feb 2025 20:29:49 +0100 Subject: [PATCH] Updated cache implementations --- .editorconfig | 9 +++ Caches/FileDbCache/FileDbCache.cs | 45 ++++---------- Caches/SQLiteCache/SQLiteCache.cs | 41 ++++--------- MapControl/Shared/ImageFileCache.cs | 94 +++++++++++++++++------------ 4 files changed, 90 insertions(+), 99 deletions(-) diff --git a/.editorconfig b/.editorconfig index 8b41cbce..6a1a8578 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,13 @@ [*.cs] +# CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' +dotnet_diagnostic.CA1835.severity = silent + # CsWinRT1028: Class is not marked partial dotnet_diagnostic.CsWinRT1028.severity = silent + +# CsWinRT1030: Project does not enable unsafe blocks +dotnet_diagnostic.CsWinRT1030.severity = silent + +# IDE0063: Use simple 'using' statement +dotnet_diagnostic.IDE0063.severity = silent diff --git a/Caches/FileDbCache/FileDbCache.cs b/Caches/FileDbCache/FileDbCache.cs index b41da10a..fc128fee 100644 --- a/Caches/FileDbCache/FileDbCache.cs +++ b/Caches/FileDbCache/FileDbCache.cs @@ -23,14 +23,14 @@ namespace MapControl.Caching private const string expiresField = "Expires"; private readonly FileDb fileDb = new FileDb { AutoFlush = true }; - private readonly Timer cleanTimer; + private readonly Timer expirationScanTimer; public FileDbCache(string path) : this(path, TimeSpan.FromHours(1)) { } - public FileDbCache(string path, TimeSpan autoCleanInterval) + public FileDbCache(string path, TimeSpan expirationScanFrequency) { if (string.IsNullOrEmpty(path)) { @@ -45,6 +45,7 @@ namespace MapControl.Caching try { fileDb.Open(path); + Debug.WriteLine($"{nameof(FileDbCache)}: Opened database {path}"); } catch @@ -68,15 +69,15 @@ namespace MapControl.Caching Debug.WriteLine($"{nameof(FileDbCache)}: Created database {path}"); } - if (autoCleanInterval > TimeSpan.Zero) + if (expirationScanFrequency > TimeSpan.Zero) { - cleanTimer = new Timer(_ => Clean(), null, TimeSpan.Zero, autoCleanInterval); + expirationScanTimer = new Timer(_ => DeleteExpiredItems(), null, TimeSpan.Zero, expirationScanFrequency); } } public void Dispose() { - cleanTimer?.Dispose(); + expirationScanTimer?.Dispose(); fileDb.Dispose(); } @@ -92,7 +93,7 @@ namespace MapControl.Caching if (record != null) { - if ((DateTime)record[1] > DateTime.UtcNow) + if ((DateTime)record[1] > DateTime.Now) { value = (byte[])record[0]; } @@ -114,24 +115,9 @@ namespace MapControl.Caching { CheckArguments(key, value, 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 expiration = options.AbsoluteExpiration.HasValue + ? options.AbsoluteExpiration.Value.LocalDateTime + : DateTime.Now.Add(options.AbsoluteExpirationRelativeToNow ?? (options.SlidingExpiration ?? TimeSpan.FromDays(1))); var fieldValues = new FieldValues(3) { @@ -175,9 +161,9 @@ namespace MapControl.Caching } } - public void Clean() + public void DeleteExpiredItems() { - var deleted = fileDb.DeleteRecords(new FilterExpression(expiresField, DateTime.UtcNow, ComparisonOperatorEnum.LessThanOrEqual)); + var deleted = fileDb.DeleteRecords(new FilterExpression(expiresField, DateTime.Now, ComparisonOperatorEnum.LessThanOrEqual)); if (deleted > 0) { @@ -212,13 +198,6 @@ namespace MapControl.Caching return Task.CompletedTask; } - public Task CleanAsync() - { - Clean(); - - return Task.CompletedTask; - } - private static void CheckArgument(string key) { if (string.IsNullOrEmpty(key)) diff --git a/Caches/SQLiteCache/SQLiteCache.cs b/Caches/SQLiteCache/SQLiteCache.cs index b5d47d59..996ecd48 100644 --- a/Caches/SQLiteCache/SQLiteCache.cs +++ b/Caches/SQLiteCache/SQLiteCache.cs @@ -19,14 +19,14 @@ namespace MapControl.Caching public sealed class SQLiteCache : IDistributedCache, IDisposable { private readonly SQLiteConnection connection; - private readonly Timer cleanTimer; + private readonly Timer expirationScanTimer; public SQLiteCache(string path) : this(path, TimeSpan.FromHours(1)) { } - public SQLiteCache(string path, TimeSpan autoCleanInterval) + public SQLiteCache(string path, TimeSpan expirationScanFrequency) { if (string.IsNullOrEmpty(path)) { @@ -48,15 +48,15 @@ namespace MapControl.Caching Debug.WriteLine($"{nameof(SQLiteCache)}: Opened database {path}"); - if (autoCleanInterval > TimeSpan.Zero) + if (expirationScanFrequency > TimeSpan.Zero) { - cleanTimer = new Timer(_ => Clean(), null, TimeSpan.Zero, autoCleanInterval); + expirationScanTimer = new Timer(_ => DeleteExpiredItems(), null, TimeSpan.Zero, expirationScanFrequency); } } public void Dispose() { - cleanTimer?.Dispose(); + expirationScanTimer?.Dispose(); connection.Dispose(); } @@ -189,11 +189,11 @@ namespace MapControl.Caching } } - public void Clean() + public void DeleteExpiredItems() { using (var command = new SQLiteCommand("delete from items where expiration < @exp", connection)) { - command.Parameters.AddWithValue("@exp", DateTimeOffset.UtcNow.Ticks); + command.Parameters.AddWithValue("@exp", DateTimeOffset.Now.Ticks); command.ExecuteNonQuery(); } #if DEBUG @@ -208,11 +208,11 @@ namespace MapControl.Caching #endif } - public async Task CleanAsync() + public async Task DeleteExpiredItemsAsync() { using (var command = new SQLiteCommand("delete from items where expiration < @exp", connection)) { - command.Parameters.AddWithValue("@exp", DateTimeOffset.UtcNow.Ticks); + command.Parameters.AddWithValue("@exp", DateTimeOffset.Now.Ticks); await command.ExecuteNonQueryAsync(); } #if DEBUG @@ -243,24 +243,9 @@ namespace MapControl.Caching 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 expiration = options.AbsoluteExpiration.HasValue + ? options.AbsoluteExpiration.Value + : DateTimeOffset.Now.Add(options.AbsoluteExpirationRelativeToNow ?? (options.SlidingExpiration ?? 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); @@ -273,7 +258,7 @@ namespace MapControl.Caching { var expiration = new DateTimeOffset((long)reader["expiration"], TimeSpan.Zero); - if (expiration <= DateTimeOffset.UtcNow) + if (expiration <= DateTimeOffset.Now) { return false; } diff --git a/MapControl/Shared/ImageFileCache.cs b/MapControl/Shared/ImageFileCache.cs index 735af29e..d5020e6b 100644 --- a/MapControl/Shared/ImageFileCache.cs +++ b/MapControl/Shared/ImageFileCache.cs @@ -19,17 +19,17 @@ namespace MapControl.Caching /// public sealed class ImageFileCache : IDistributedCache, IDisposable { - private readonly MemoryDistributedCache memoryCache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions())); + private readonly MemoryDistributedCache memoryCache; private readonly DirectoryInfo rootDirectory; - private readonly Timer cleanTimer; - private bool cleaning; + private readonly Timer expirationScanTimer; + private bool scanningExpiration; public ImageFileCache(string path) : this(path, TimeSpan.FromHours(1)) { } - public ImageFileCache(string path, TimeSpan autoCleanInterval) + public ImageFileCache(string path, TimeSpan expirationScanFrequency) { if (string.IsNullOrEmpty(path)) { @@ -41,15 +41,21 @@ namespace MapControl.Caching Debug.WriteLine($"{nameof(ImageFileCache)}: {rootDirectory.FullName}"); - if (autoCleanInterval > TimeSpan.Zero) + var options = new MemoryDistributedCacheOptions(); + + if (expirationScanFrequency > TimeSpan.Zero) { - cleanTimer = new Timer(_ => Clean(), null, TimeSpan.Zero, autoCleanInterval); + options.ExpirationScanFrequency = expirationScanFrequency; + + expirationScanTimer = new Timer(_ => DeleteExpiredItems(), null, TimeSpan.Zero, expirationScanFrequency); } + + memoryCache = new MemoryDistributedCache(Options.Create(options)); } public void Dispose() { - cleanTimer?.Dispose(); + expirationScanTimer?.Dispose(); } public byte[] Get(string key) @@ -64,15 +70,7 @@ namespace MapControl.Caching { if (file != null && file.Exists && file.CreationTime > DateTime.Now) { - using (var stream = file.OpenRead()) - { - buffer = new byte[stream.Length]; - var offset = 0; - while (offset < buffer.Length) - { - offset += stream.Read(buffer, offset, buffer.Length - offset); - } - } + buffer = ReadAllBytes(file); var options = new DistributedCacheEntryOptions { AbsoluteExpiration = file.CreationTime }; @@ -100,15 +98,7 @@ namespace MapControl.Caching { if (file != null && file.Exists && file.CreationTime > DateTime.Now && !token.IsCancellationRequested) { - using (var stream = file.OpenRead()) - { - buffer = new byte[stream.Length]; - var offset = 0; - while (offset < buffer.Length) - { - offset += await stream.ReadAsync(buffer, offset, buffer.Length - offset, token).ConfigureAwait(false); - } - } + buffer = await ReadAllBytes(file, token).ConfigureAwait(false); var options = new DistributedCacheEntryOptions { AbsoluteExpiration = file.CreationTime }; @@ -117,6 +107,7 @@ namespace MapControl.Caching } catch (Exception ex) { + buffer = null; Debug.WriteLine($"{nameof(ImageFileCache)}: Failed reading {file.FullName}: {ex.Message}"); } } @@ -224,31 +215,26 @@ namespace MapControl.Caching } } - public void Clean() + public void DeleteExpiredItems() { - if (!cleaning) + if (!scanningExpiration) { - cleaning = true; + scanningExpiration = true; foreach (var directory in rootDirectory.EnumerateDirectories()) { - var deletedFileCount = CleanDirectory(directory); + var deletedFileCount = ScanDirectory(directory); if (deletedFileCount > 0) { - Debug.WriteLine($"{nameof(ImageFileCache)}: Deleted {deletedFileCount} expired files in {directory.Name}."); + Debug.WriteLine($"{nameof(ImageFileCache)}: Deleted {deletedFileCount} expired items in {directory.Name}."); } } - cleaning = false; + scanningExpiration = false; } } - public Task CleanAsync() - { - return Task.Run(Clean); - } - private FileInfo GetFile(string key) { try @@ -263,6 +249,38 @@ namespace MapControl.Caching return null; } + private static byte[] ReadAllBytes(FileInfo file) + { + using (var stream = file.OpenRead()) + { + var buffer = new byte[stream.Length]; + var offset = 0; + + while (offset < buffer.Length) + { + offset += stream.Read(buffer, offset, buffer.Length - offset); + } + + return buffer; + } + } + + private static async Task ReadAllBytes(FileInfo file, CancellationToken token) + { + using (var stream = file.OpenRead()) + { + var buffer = new byte[stream.Length]; + var offset = 0; + + while (offset < buffer.Length) + { + offset += await stream.ReadAsync(buffer, offset, buffer.Length - offset, token).ConfigureAwait(false); + } + + return buffer; + } + } + private static void SetExpiration(FileInfo file, DistributedCacheEntryOptions options) { file.CreationTime = options.AbsoluteExpiration.HasValue @@ -270,13 +288,13 @@ namespace MapControl.Caching : DateTime.Now.Add(options.AbsoluteExpirationRelativeToNow ?? (options.SlidingExpiration ?? TimeSpan.FromDays(1))); } - private static int CleanDirectory(DirectoryInfo directory) + private static int ScanDirectory(DirectoryInfo directory) { var deletedFileCount = 0; try { - deletedFileCount = directory.EnumerateDirectories().Sum(CleanDirectory); + deletedFileCount = directory.EnumerateDirectories().Sum(ScanDirectory); foreach (var file in directory.EnumerateFiles().Where(f => f.CreationTime <= DateTime.Now)) {