From 0007dc86904187e24224b71f985b7d2e42c4a62b Mon Sep 17 00:00:00 2001 From: Uncled1023 Date: Sat, 28 May 2022 20:32:31 -0700 Subject: [PATCH] Fixed caching to be singleton w/ background cleaner --- .../Admin/Controllers/AdminController.cs | 6 +- .../Paste/Controllers/PasteController.cs | 24 ++-- Teknik/Areas/Paste/PasteHelper.cs | 40 +++---- .../Upload/Controllers/UploadController.cs | 28 ++--- Teknik/Areas/Upload/UploadHelper.cs | 44 +++----- .../Areas/User/Controllers/UserController.cs | 6 +- Teknik/Services/CacheCleanerService.cs | 43 +++++++ Teknik/Startup.cs | 2 + Utilities/CacheEntry.cs | 24 ++++ Utilities/Cryptography/AesCounterMode.cs | 20 ++-- Utilities/ObjectCache.cs | 105 +++++++++++++----- Utilities/PooledArray.cs | 4 + 12 files changed, 225 insertions(+), 121 deletions(-) create mode 100644 Teknik/Services/CacheCleanerService.cs create mode 100644 Utilities/CacheEntry.cs diff --git a/Teknik/Areas/Admin/Controllers/AdminController.cs b/Teknik/Areas/Admin/Controllers/AdminController.cs index 74968cb..b87835d 100644 --- a/Teknik/Areas/Admin/Controllers/AdminController.cs +++ b/Teknik/Areas/Admin/Controllers/AdminController.cs @@ -261,7 +261,7 @@ namespace Teknik.Areas.Admin.Controllers [HttpPost] [ValidateAntiForgeryToken] - public IActionResult DeleteData(string type, string id, [FromServices] IBackgroundTaskQueue queue) + public IActionResult DeleteData(string type, string id, [FromServices] IBackgroundTaskQueue queue, [FromServices] ObjectCache cache) { var context = new ControllerContext(); context.HttpContext = Request.HttpContext; @@ -271,11 +271,11 @@ namespace Teknik.Areas.Admin.Controllers switch (type) { case "upload": - var uploadController = new Upload.Controllers.UploadController(_logger, _config, _dbContext, queue); + var uploadController = new Upload.Controllers.UploadController(_logger, _config, _dbContext, queue, cache); uploadController.ControllerContext = context; return uploadController.Delete(id); case "paste": - var pasteController = new Paste.Controllers.PasteController(_logger, _config, _dbContext, queue); + var pasteController = new Paste.Controllers.PasteController(_logger, _config, _dbContext, queue, cache); pasteController.ControllerContext = context; return pasteController.Delete(id); case "shortenedUrl": diff --git a/Teknik/Areas/Paste/Controllers/PasteController.cs b/Teknik/Areas/Paste/Controllers/PasteController.cs index 4a2ab31..30837f2 100644 --- a/Teknik/Areas/Paste/Controllers/PasteController.cs +++ b/Teknik/Areas/Paste/Controllers/PasteController.cs @@ -26,12 +26,14 @@ namespace Teknik.Areas.Paste.Controllers public class PasteController : DefaultController { private readonly IBackgroundTaskQueue _queue; + private readonly ObjectCache _cache; - public PasteController(ILogger logger, Config config, TeknikEntities dbContext, IBackgroundTaskQueue queue) : base(logger, config, dbContext) + public PasteController(ILogger logger, Config config, TeknikEntities dbContext, IBackgroundTaskQueue queue, ObjectCache cache) : base(logger, config, dbContext) { _queue = queue; + _cache = cache; } - + [AllowAnonymous] [TrackPageView] public IActionResult Index() @@ -46,7 +48,7 @@ namespace Teknik.Areas.Paste.Controllers [TrackPageView] public async Task ViewPaste(string type, string url, string password) { - Models.Paste paste = PasteHelper.GetPaste(_dbContext, url); + Models.Paste paste = PasteHelper.GetPaste(_dbContext, _cache, url); if (paste != null) { ViewBag.Title = (string.IsNullOrEmpty(paste.Title)) ? "Untitled Paste" : paste.Title + " | Pastebin"; @@ -62,12 +64,12 @@ namespace Teknik.Areas.Paste.Controllers // Check Expiration if (PasteHelper.CheckExpiration(paste)) { - PasteHelper.DeleteFile(_dbContext, _config, _logger, url); + PasteHelper.DeleteFile(_dbContext, _cache, _config, _logger, url); return new StatusCodeResult(StatusCodes.Status404NotFound); } // Increment View Count - PasteHelper.IncrementViewCount(_queue, _config, url); + PasteHelper.IncrementViewCount(_queue, _cache, _config, url); PasteViewModel model = new PasteViewModel(); model.Url = url; @@ -234,7 +236,7 @@ namespace Teknik.Areas.Paste.Controllers [TrackPageView] public async Task Edit(string url, string password) { - Models.Paste paste = PasteHelper.GetPaste(_dbContext, url); + Models.Paste paste = PasteHelper.GetPaste(_dbContext, _cache, url); if (paste != null) { if (paste.User?.Username != User.Identity.Name) @@ -246,7 +248,7 @@ namespace Teknik.Areas.Paste.Controllers // Check Expiration if (PasteHelper.CheckExpiration(paste)) { - PasteHelper.DeleteFile(_dbContext, _config, _logger, url); + PasteHelper.DeleteFile(_dbContext, _cache, _config, _logger, url); return new StatusCodeResult(StatusCodes.Status404NotFound); } @@ -338,7 +340,7 @@ namespace Teknik.Areas.Paste.Controllers { try { - Models.Paste paste = PasteHelper.GetPaste(_dbContext, model.Url); + Models.Paste paste = PasteHelper.GetPaste(_dbContext, _cache, model.Url); if (paste != null) { if (paste.User?.Username != User.Identity.Name) @@ -396,7 +398,7 @@ namespace Teknik.Areas.Paste.Controllers paste.Syntax = model.Syntax; paste.DateEdited = DateTime.Now; - PasteHelper.ModifyPaste(_dbContext, paste); + PasteHelper.ModifyPaste(_dbContext, _cache, paste); // Delete the old file storageService.DeleteFile(oldFile); @@ -416,13 +418,13 @@ namespace Teknik.Areas.Paste.Controllers [HttpOptions] public IActionResult Delete(string id) { - Models.Paste foundPaste = PasteHelper.GetPaste(_dbContext, id); + Models.Paste foundPaste = PasteHelper.GetPaste(_dbContext, _cache, id); if (foundPaste != null) { if (foundPaste.User?.Username == User.Identity.Name || User.IsInRole("Admin")) { - PasteHelper.DeleteFile(_dbContext, _config, _logger, id); + PasteHelper.DeleteFile(_dbContext, _cache, _config, _logger, id); return Json(new { result = true, redirect = Url.SubRouteUrl("p", "Paste.Index") }); } diff --git a/Teknik/Areas/Paste/PasteHelper.cs b/Teknik/Areas/Paste/PasteHelper.cs index a85f02a..367ffeb 100644 --- a/Teknik/Areas/Paste/PasteHelper.cs +++ b/Teknik/Areas/Paste/PasteHelper.cs @@ -19,9 +19,6 @@ namespace Teknik.Areas.Paste { public static class PasteHelper { - private static object _cacheLock = new object(); - private readonly static ObjectCache _pasteCache = new ObjectCache(300); - public static async Task CreatePaste(Config config, TeknikEntities db, string content, string title = "", string syntax = "text", ExpirationUnit expireUnit = ExpirationUnit.Never, int expireLength = 1, string password = "") { Models.Paste paste = new Models.Paste(); @@ -127,7 +124,7 @@ namespace Teknik.Areas.Paste } } - public static void IncrementViewCount(IBackgroundTaskQueue queue, Config config, string url) + public static void IncrementViewCount(IBackgroundTaskQueue queue, ObjectCache cache, Config config, string url) { // Fire and forget updating of the download count queue.QueueBackgroundWorkItem(async token => @@ -139,34 +136,31 @@ namespace Teknik.Areas.Paste using (TeknikEntities db = new TeknikEntities(optionsBuilder.Options)) { - var paste = GetPaste(db, url); + var paste = GetPaste(db, cache, url); if (paste != null) { paste.Views++; - ModifyPaste(db, paste); + ModifyPaste(db, cache, paste); } } }); }); } - public static Models.Paste GetPaste(TeknikEntities db, string url) + public static Models.Paste GetPaste(TeknikEntities db, ObjectCache cache, string url) { - lock (_cacheLock) - { - var paste = _pasteCache.GetObject(url, (key) => db.Pastes.FirstOrDefault(up => up.Url == key)); + var paste = cache.AddOrGetObject(url, (key) => db.Pastes.FirstOrDefault(up => up.Url == key)); - if (paste != null && - !db.Exists(paste)) - db.Attach(paste); + if (paste != null && + !db.Exists(paste)) + db.Attach(paste); - return paste; - } + return paste; } - public static void DeleteFile(TeknikEntities db, Config config, ILogger logger, string url) + public static void DeleteFile(TeknikEntities db, ObjectCache cache, Config config, ILogger logger, string url) { - var paste = GetPaste(db, url); + var paste = GetPaste(db, cache, url); if (paste != null) { try @@ -185,19 +179,13 @@ namespace Teknik.Areas.Paste } // Remove from the cache - lock (_cacheLock) - { - _pasteCache.DeleteObject(url); - } + cache.DeleteObject(url); } - public static void ModifyPaste(TeknikEntities db, Models.Paste paste) + public static void ModifyPaste(TeknikEntities db, ObjectCache cache, Models.Paste paste) { // Update the cache's copy - lock (_cacheLock) - { - _pasteCache.UpdateObject(paste.Url, paste); - } + cache.UpdateObject(paste.Url, paste); if (paste != null) { diff --git a/Teknik/Areas/Upload/Controllers/UploadController.cs b/Teknik/Areas/Upload/Controllers/UploadController.cs index 54a5dab..321ead0 100644 --- a/Teknik/Areas/Upload/Controllers/UploadController.cs +++ b/Teknik/Areas/Upload/Controllers/UploadController.cs @@ -29,12 +29,14 @@ namespace Teknik.Areas.Upload.Controllers public class UploadController : DefaultController { private readonly IBackgroundTaskQueue _queue; + private readonly ObjectCache _cache; - public UploadController(ILogger logger, Config config, TeknikEntities dbContext, IBackgroundTaskQueue queue) : base(logger, config, dbContext) + public UploadController(ILogger logger, Config config, TeknikEntities dbContext, IBackgroundTaskQueue queue, ObjectCache cache) : base(logger, config, dbContext) { _queue = queue; + _cache = cache; } - + [HttpGet] [AllowAnonymous] [TrackPageView] @@ -226,18 +228,18 @@ namespace Teknik.Areas.Upload.Controllers long contentLength = 0; DateTime dateUploaded = new DateTime(); - var upload = UploadHelper.GetUpload(_dbContext, file); + var upload = UploadHelper.GetUpload(_dbContext, _cache, file); if (upload != null) { // Check Expiration if (UploadHelper.CheckExpiration(upload)) { - UploadHelper.DeleteFile(_dbContext, _config, _logger, file); + UploadHelper.DeleteFile(_dbContext, _cache, _config, _logger, file); return new StatusCodeResult(StatusCodes.Status404NotFound); } // Increment the download count for this upload - UploadHelper.IncrementDownloadCount(_queue, _config, file); + UploadHelper.IncrementDownloadCount(_queue, _cache, _config, file); fileName = upload.FileName; url = upload.Url; @@ -412,13 +414,13 @@ namespace Teknik.Areas.Upload.Controllers { if (_config.UploadConfig.DownloadEnabled) { - Models.Upload upload = UploadHelper.GetUpload(_dbContext, file); + Models.Upload upload = UploadHelper.GetUpload(_dbContext, _cache, file); if (upload != null) { // Check Expiration if (UploadHelper.CheckExpiration(upload)) { - UploadHelper.DeleteFile(_dbContext, _config, _logger, file); + UploadHelper.DeleteFile(_dbContext, _cache, _config, _logger, file); return Json(new { error = new { message = "File Does Not Exist" } }); } @@ -454,14 +456,14 @@ namespace Teknik.Areas.Upload.Controllers public IActionResult DeleteByKey(string file, string key) { ViewBag.Title = "File Delete | " + file ; - Models.Upload upload = UploadHelper.GetUpload(_dbContext, file); + Models.Upload upload = UploadHelper.GetUpload(_dbContext, _cache, file); if (upload != null) { DeleteViewModel model = new DeleteViewModel(); model.File = file; if (!string.IsNullOrEmpty(upload.DeleteKey) && upload.DeleteKey == key) { - UploadHelper.DeleteFile(_dbContext, _config, _logger, file); + UploadHelper.DeleteFile(_dbContext, _cache, _config, _logger, file); model.Deleted = true; } else @@ -476,13 +478,13 @@ namespace Teknik.Areas.Upload.Controllers [HttpPost] public IActionResult GenerateDeleteKey(string file) { - Models.Upload upload = UploadHelper.GetUpload(_dbContext, file); + Models.Upload upload = UploadHelper.GetUpload(_dbContext, _cache, file); if (upload != null) { if (upload.User?.Username == User.Identity.Name || User.IsInRole("Admin")) { - var delKey = UploadHelper.GenerateDeleteKey(_dbContext, _config, file); + var delKey = UploadHelper.GenerateDeleteKey(_dbContext, _cache, _config, file); return Json(new { result = new { url = Url.SubRouteUrl("u", "Upload.DeleteByKey", new { file = file, key = delKey }) } }); } return Json(new { error = new { message = "You do not have permission to delete this Upload" } }); @@ -494,13 +496,13 @@ namespace Teknik.Areas.Upload.Controllers [HttpOptions] public IActionResult Delete(string id) { - Models.Upload foundUpload = UploadHelper.GetUpload(_dbContext, id); + Models.Upload foundUpload = UploadHelper.GetUpload(_dbContext, _cache, id); if (foundUpload != null) { if (foundUpload.User?.Username == User.Identity.Name || User.IsInRole("Admin")) { - UploadHelper.DeleteFile(_dbContext, _config, _logger, id); + UploadHelper.DeleteFile(_dbContext, _cache, _config, _logger, id); return Json(new { result = true }); } return Json(new { error = new { message = "You do not have permission to delete this Upload" } }); diff --git a/Teknik/Areas/Upload/UploadHelper.cs b/Teknik/Areas/Upload/UploadHelper.cs index 5612de9..8ddd6c3 100644 --- a/Teknik/Areas/Upload/UploadHelper.cs +++ b/Teknik/Areas/Upload/UploadHelper.cs @@ -19,9 +19,6 @@ namespace Teknik.Areas.Upload { public static class UploadHelper { - private static object _cacheLock = new object(); - private readonly static ObjectCache _uploadCache = new ObjectCache(300); - public static async Task SaveFile(TeknikEntities db, Config config, Stream file, string contentType, long contentLength, bool encrypt, ExpirationUnit expirationUnit, int expirationLength) { return await SaveFile(db, config, file, contentType, contentLength, encrypt, expirationUnit, expirationLength, string.Empty, null, null, 256, 128); @@ -128,14 +125,14 @@ namespace Teknik.Areas.Upload return upload; } - public static string GenerateDeleteKey(TeknikEntities db, Config config, string url) + public static string GenerateDeleteKey(TeknikEntities db, ObjectCache cache, Config config, string url) { var upload = db.Uploads.FirstOrDefault(up => up.Url == url); if (upload != null) { string delKey = StringHelper.RandomString(config.UploadConfig.DeleteKeyLength); upload.DeleteKey = delKey; - ModifyUpload(db, upload); + ModifyUpload(db, cache, upload); return delKey; } return null; @@ -151,7 +148,7 @@ namespace Teknik.Areas.Upload return false; } - public static void IncrementDownloadCount(IBackgroundTaskQueue queue, Config config, string url) + public static void IncrementDownloadCount(IBackgroundTaskQueue queue, ObjectCache cache, Config config, string url) { // Fire and forget updating of the download count queue.QueueBackgroundWorkItem(async token => @@ -163,34 +160,31 @@ namespace Teknik.Areas.Upload using (TeknikEntities db = new TeknikEntities(optionsBuilder.Options)) { - var upload = GetUpload(db, url); + var upload = GetUpload(db, cache, url); if (upload != null) { upload.Downloads++; - ModifyUpload(db, upload); + ModifyUpload(db, cache, upload); } } }); }); } - public static Models.Upload GetUpload(TeknikEntities db, string url) + public static Models.Upload GetUpload(TeknikEntities db, ObjectCache cache, string url) { - lock (_cacheLock) - { - var upload = _uploadCache.GetObject(url, (key) => db.Uploads.FirstOrDefault(up => up.Url == key)); + var upload = cache.AddOrGetObject(url, (key) => db.Uploads.FirstOrDefault(up => up.Url == key)); - if (upload != null && - !db.Exists(upload)) - db.Attach(upload); + if (upload != null && + !db.Exists(upload)) + db.Attach(upload); - return upload; - } + return upload; } - public static void DeleteFile(TeknikEntities db, Config config, ILogger logger, string url) + public static void DeleteFile(TeknikEntities db, ObjectCache cache, Config config, ILogger logger, string url) { - var upload = GetUpload(db, url); + var upload = GetUpload(db, cache, url); if (upload != null) { try @@ -209,19 +203,13 @@ namespace Teknik.Areas.Upload } // Remove from the cache - lock (_cacheLock) - { - _uploadCache.DeleteObject(url); - } + cache.DeleteObject(url); } - public static void ModifyUpload(TeknikEntities db, Models.Upload upload) + public static void ModifyUpload(TeknikEntities db, ObjectCache cache, Models.Upload upload) { // Update the cache's copy - lock (_cacheLock) - { - _uploadCache.UpdateObject(upload.Url, upload); - } + cache.UpdateObject(upload.Url, upload); if (upload != null) { diff --git a/Teknik/Areas/User/Controllers/UserController.cs b/Teknik/Areas/User/Controllers/UserController.cs index a0f2baa..486b7f0 100644 --- a/Teknik/Areas/User/Controllers/UserController.cs +++ b/Teknik/Areas/User/Controllers/UserController.cs @@ -1408,7 +1408,7 @@ namespace Teknik.Areas.Users.Controllers [HttpPost] [ValidateAntiForgeryToken] - public IActionResult DeleteData(string type, string id, [FromServices] IBackgroundTaskQueue queue) + public IActionResult DeleteData(string type, string id, [FromServices] IBackgroundTaskQueue queue, [FromServices] ObjectCache cache) { var context = new ControllerContext(); context.HttpContext = Request.HttpContext; @@ -1418,11 +1418,11 @@ namespace Teknik.Areas.Users.Controllers switch (type) { case "upload": - var uploadController = new Upload.Controllers.UploadController(_logger, _config, _dbContext, queue); + var uploadController = new Upload.Controllers.UploadController(_logger, _config, _dbContext, queue, cache); uploadController.ControllerContext = context; return uploadController.Delete(id); case "paste": - var pasteController = new Paste.Controllers.PasteController(_logger, _config, _dbContext, queue); + var pasteController = new Paste.Controllers.PasteController(_logger, _config, _dbContext, queue, cache); pasteController.ControllerContext = context; return pasteController.Delete(id); case "shortenedUrl": diff --git a/Teknik/Services/CacheCleanerService.cs b/Teknik/Services/CacheCleanerService.cs new file mode 100644 index 0000000..2ca2a94 --- /dev/null +++ b/Teknik/Services/CacheCleanerService.cs @@ -0,0 +1,43 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; +using Teknik.Logging; +using Teknik.Utilities; + +namespace Teknik.Services +{ + public class CacheCleanerService : BackgroundService + { + private readonly ILogger _logger; + public readonly ObjectCache _cache; + + public CacheCleanerService(ObjectCache cache, ILogger logger) + { + _cache = cache; + _logger = logger; + } + + protected async override Task ExecuteAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("Cache Cleaning Service is starting."); + + while (!cancellationToken.IsCancellationRequested) + { + try + { + _cache.CleanCache(); + + await Task.Delay(new TimeSpan(0, 5, 0), cancellationToken); + } + catch (Exception ex) + { + _logger.LogError(ex, $"Error occurred cleaning cache."); + } + } + + _logger.LogInformation("Cache Cleaning Service is stopping."); + } + } +} diff --git a/Teknik/Startup.cs b/Teknik/Startup.cs index 64ca114..c7eb0fc 100644 --- a/Teknik/Startup.cs +++ b/Teknik/Startup.cs @@ -101,6 +101,8 @@ namespace Teknik services.AddHostedService(); services.AddSingleton(); + services.AddSingleton(c => new ObjectCache(300)); + services.AddHostedService(); services.AddScoped(); // Add Tracking Filter scopes diff --git a/Utilities/CacheEntry.cs b/Utilities/CacheEntry.cs new file mode 100644 index 0000000..3bdf6cf --- /dev/null +++ b/Utilities/CacheEntry.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Teknik.Utilities +{ + public class CacheEntry + { + public bool RollingExpiration { get; set; } + public TimeSpan CacheTime { get; set; } + public DateTime LastUpdate { get; set; } + public object Data { get; set; } + + public CacheEntry(bool rollingExpiration, TimeSpan cacheTime, DateTime lastUpdate, object data) + { + RollingExpiration = rollingExpiration; + CacheTime = cacheTime; + LastUpdate = lastUpdate; + Data = data; + } + } +} diff --git a/Utilities/Cryptography/AesCounterMode.cs b/Utilities/Cryptography/AesCounterMode.cs index a8f5691..82b347e 100644 --- a/Utilities/Cryptography/AesCounterMode.cs +++ b/Utilities/Cryptography/AesCounterMode.cs @@ -15,9 +15,9 @@ namespace Teknik.Utilities.Cryptography public AesCounterMode(PooledArray initialCounter) { if (initialCounter == null) throw new ArgumentNullException("counter"); - if (initialCounter.Array.Length != _BlockSize) + if (initialCounter.Length != _BlockSize) throw new ArgumentException(String.Format("Counter size must be same as block size (actual: {0}, expected: {1})", - initialCounter.Array.Length, _BlockSize)); + initialCounter.Length, _BlockSize)); // Generate a new instance of the Aes Algorithm in ECB mode with no padding _Algo = Aes.Create(); @@ -82,7 +82,7 @@ namespace Teknik.Utilities.Cryptography } set { - if (value >= 0 && value < _EncryptedCounter.Array.Length) + if (value >= 0 && value < _EncryptedCounter.Length) { _CounterPosition = value; } @@ -97,14 +97,14 @@ namespace Teknik.Utilities.Cryptography if (initialCounter == null) throw new ArgumentNullException("counter"); // Check lengths - if (initialCounter.Array.Length != symmetricAlgorithm.BlockSize / 8) + if (initialCounter.Length != symmetricAlgorithm.BlockSize / 8) throw new ArgumentException(String.Format("Counter size must be same as block size (actual: {0}, expected: {1})", - initialCounter.Array.Length, symmetricAlgorithm.BlockSize / 8)); + initialCounter.Length, symmetricAlgorithm.BlockSize / 8)); _BlockSize = symmetricAlgorithm.BlockSize; // Initialize Counter - _Counter = new PooledArray(initialCounter.Array.Length); + _Counter = new PooledArray(initialCounter.Length); initialCounter.Array.CopyTo(_Counter.Array, 0); // Initialize the encrypted counter @@ -144,7 +144,7 @@ namespace Teknik.Utilities.Cryptography for (var i = 0; i < inputCount; i++) { // Encrypt the counter if we have reached the end, or - if (_CounterPosition >= _EncryptedCounter.Array.Length) + if (_CounterPosition >= _EncryptedCounter.Length) { //Reset current counter position _CounterPosition = 0; @@ -178,7 +178,7 @@ namespace Teknik.Utilities.Cryptography for (var i = 0; i < inputCount; i++) { // Encrypt the counter if we have reached the end, or - if (_CounterPosition >= _EncryptedCounter.Array.Length) + if (_CounterPosition >= _EncryptedCounter.Length) { //Reset current counter position _CounterPosition = 0; @@ -203,7 +203,7 @@ namespace Teknik.Utilities.Cryptography public void EncryptCounter() { // Encrypt the current counter to the encrypted counter - _CounterEncryptor.TransformBlock(_Counter.Array, 0, _Counter.Array.Length, _EncryptedCounter.Array, 0); + _CounterEncryptor.TransformBlock(_Counter.Array, 0, _Counter.Length, _EncryptedCounter.Array, 0); } public void ResetCounter() @@ -214,7 +214,7 @@ namespace Teknik.Utilities.Cryptography public void IncrementCounter() { - int j = _Counter.Array.Length; + int j = _Counter.Length; while (--j >= 0 && ++_Counter.Array[j] == 0) { } diff --git a/Utilities/ObjectCache.cs b/Utilities/ObjectCache.cs index 57a4ca7..1a9b914 100644 --- a/Utilities/ObjectCache.cs +++ b/Utilities/ObjectCache.cs @@ -3,61 +3,112 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace Teknik.Utilities { public class ObjectCache { - private readonly static ConcurrentDictionary> objectCache = new ConcurrentDictionary>(); - private readonly int _cacheSeconds; + private readonly int _defaultCacheTime; + private static object _cacheLock = new object(); - public ObjectCache(int cacheSeconds) + private readonly static ConcurrentDictionary _objectCache = new ConcurrentDictionary(); + + public ObjectCache(int defaultCacheTime) { - _cacheSeconds = cacheSeconds; + _defaultCacheTime = defaultCacheTime; } - public T GetObject(string key, Func getObjectFunc) + public T AddOrGetObject(string key, Func getObjectFunc) + { + return AddOrGetObject(key, new TimeSpan(0, 0, _defaultCacheTime), getObjectFunc); + } + + public T AddOrGetObject(string key, TimeSpan cacheTime, Func getObjectFunc) { T foundObject; - var cacheDate = DateTime.UtcNow; - if (objectCache.TryGetValue(key, out var result) && - result.Item1 > cacheDate.Subtract(new TimeSpan(0, 0, _cacheSeconds))) + lock (_cacheLock) { - return (T)result.Item2; - } - else - { - foundObject = getObjectFunc(key); - // Update the cache for this key - if (foundObject != null) - UpdateObject(key, foundObject, cacheDate); + if (_objectCache.TryGetValue(GenerateKey(key), out var result) && + CacheValid(result)) + { + if (result.RollingExpiration) + { + result.LastUpdate = DateTime.UtcNow; + } + return (T)result.Data; + } + else + { + foundObject = getObjectFunc(key); + // Update the cache for this key + if (foundObject != null) + UpdateObject(key, foundObject, cacheTime); + } } return foundObject; } - public void UpdateObject(string key, T update) + public void UpdateObject(string key, T data) { - UpdateObject(key, update, DateTime.UtcNow); + UpdateObject(key, data, null, DateTime.UtcNow); } - public void UpdateObject(string key, T update, DateTime cacheTime) + public void UpdateObject(string key, T data, TimeSpan cacheTime) { - objectCache[key] = new Tuple(cacheTime, update); + UpdateObject(key, data, cacheTime, DateTime.UtcNow); } - public void DeleteObject(string key) + public void UpdateObject(string key, T data, TimeSpan? cacheTime, DateTime lastUpdate) { - objectCache.TryRemove(key, out _); + if (_objectCache.TryGetValue(GenerateKey(key), out var result)) + { + result.Data = data; + if (cacheTime.HasValue) + result.CacheTime = cacheTime.Value; + result.LastUpdate = lastUpdate; + } + else + { + if (!cacheTime.HasValue) + cacheTime = new TimeSpan(0, 0, _defaultCacheTime); + _objectCache[GenerateKey(key)] = CreateCacheEntry(data, cacheTime.Value, lastUpdate); + } } - public bool CacheValid(string key) + public void DeleteObject(string key) { - if (objectCache.TryGetValue(key, out var result) && - result.Item1 > DateTime.UtcNow.Subtract(new TimeSpan(0, 0, _cacheSeconds))) - return true; - return false; + _objectCache.TryRemove(GenerateKey(key), out _); + } + + public bool CacheValid(CacheEntry entry) + { + return entry.LastUpdate > DateTime.UtcNow.Subtract(entry.CacheTime); + } + + public void CleanCache() + { + lock (_cacheLock) + { + foreach (var obj in _objectCache) + { + if (!CacheValid(obj.Value)) + _objectCache.TryRemove(obj.Key, out _); + } + } + } + + public string GenerateKey(string key) + { + var typeKey = typeof(T).ToString(); + return $"{typeKey}.{key}"; + } + + public CacheEntry CreateCacheEntry(T data, TimeSpan cacheTime, DateTime lastUpdate) + { + return new CacheEntry(false, cacheTime, lastUpdate, data); } } } diff --git a/Utilities/PooledArray.cs b/Utilities/PooledArray.cs index c0f5d58..34779c8 100644 --- a/Utilities/PooledArray.cs +++ b/Utilities/PooledArray.cs @@ -13,15 +13,19 @@ namespace Teknik.Utilities public byte[] Array { get; private set; } + public readonly int Length; + public PooledArray(int size) { Array = _arrayPool.Rent(size); + Length = size; } public PooledArray(byte[] array) { Array = _arrayPool.Rent(array.Length); array.CopyTo(Array, 0); + Length = array.Length; } public void Dispose()