1
0
mirror of https://git.teknik.io/Teknikode/Teknik.git synced 2023-08-02 14:16:22 +02:00

Fixed caching to be singleton w/ background cleaner

This commit is contained in:
Uncled1023 2022-05-28 20:32:31 -07:00
parent ad532b0106
commit 0007dc8690
12 changed files with 225 additions and 121 deletions

View File

@ -261,7 +261,7 @@ namespace Teknik.Areas.Admin.Controllers
[HttpPost] [HttpPost]
[ValidateAntiForgeryToken] [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(); var context = new ControllerContext();
context.HttpContext = Request.HttpContext; context.HttpContext = Request.HttpContext;
@ -271,11 +271,11 @@ namespace Teknik.Areas.Admin.Controllers
switch (type) switch (type)
{ {
case "upload": 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; uploadController.ControllerContext = context;
return uploadController.Delete(id); return uploadController.Delete(id);
case "paste": 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; pasteController.ControllerContext = context;
return pasteController.Delete(id); return pasteController.Delete(id);
case "shortenedUrl": case "shortenedUrl":

View File

@ -26,10 +26,12 @@ namespace Teknik.Areas.Paste.Controllers
public class PasteController : DefaultController public class PasteController : DefaultController
{ {
private readonly IBackgroundTaskQueue _queue; private readonly IBackgroundTaskQueue _queue;
private readonly ObjectCache _cache;
public PasteController(ILogger<Logger> logger, Config config, TeknikEntities dbContext, IBackgroundTaskQueue queue) : base(logger, config, dbContext) public PasteController(ILogger<Logger> logger, Config config, TeknikEntities dbContext, IBackgroundTaskQueue queue, ObjectCache cache) : base(logger, config, dbContext)
{ {
_queue = queue; _queue = queue;
_cache = cache;
} }
[AllowAnonymous] [AllowAnonymous]
@ -46,7 +48,7 @@ namespace Teknik.Areas.Paste.Controllers
[TrackPageView] [TrackPageView]
public async Task<IActionResult> ViewPaste(string type, string url, string password) public async Task<IActionResult> 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) if (paste != null)
{ {
ViewBag.Title = (string.IsNullOrEmpty(paste.Title)) ? "Untitled Paste" : paste.Title + " | Pastebin"; ViewBag.Title = (string.IsNullOrEmpty(paste.Title)) ? "Untitled Paste" : paste.Title + " | Pastebin";
@ -62,12 +64,12 @@ namespace Teknik.Areas.Paste.Controllers
// Check Expiration // Check Expiration
if (PasteHelper.CheckExpiration(paste)) if (PasteHelper.CheckExpiration(paste))
{ {
PasteHelper.DeleteFile(_dbContext, _config, _logger, url); PasteHelper.DeleteFile(_dbContext, _cache, _config, _logger, url);
return new StatusCodeResult(StatusCodes.Status404NotFound); return new StatusCodeResult(StatusCodes.Status404NotFound);
} }
// Increment View Count // Increment View Count
PasteHelper.IncrementViewCount(_queue, _config, url); PasteHelper.IncrementViewCount(_queue, _cache, _config, url);
PasteViewModel model = new PasteViewModel(); PasteViewModel model = new PasteViewModel();
model.Url = url; model.Url = url;
@ -234,7 +236,7 @@ namespace Teknik.Areas.Paste.Controllers
[TrackPageView] [TrackPageView]
public async Task<IActionResult> Edit(string url, string password) public async Task<IActionResult> 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 != null)
{ {
if (paste.User?.Username != User.Identity.Name) if (paste.User?.Username != User.Identity.Name)
@ -246,7 +248,7 @@ namespace Teknik.Areas.Paste.Controllers
// Check Expiration // Check Expiration
if (PasteHelper.CheckExpiration(paste)) if (PasteHelper.CheckExpiration(paste))
{ {
PasteHelper.DeleteFile(_dbContext, _config, _logger, url); PasteHelper.DeleteFile(_dbContext, _cache, _config, _logger, url);
return new StatusCodeResult(StatusCodes.Status404NotFound); return new StatusCodeResult(StatusCodes.Status404NotFound);
} }
@ -338,7 +340,7 @@ namespace Teknik.Areas.Paste.Controllers
{ {
try try
{ {
Models.Paste paste = PasteHelper.GetPaste(_dbContext, model.Url); Models.Paste paste = PasteHelper.GetPaste(_dbContext, _cache, model.Url);
if (paste != null) if (paste != null)
{ {
if (paste.User?.Username != User.Identity.Name) if (paste.User?.Username != User.Identity.Name)
@ -396,7 +398,7 @@ namespace Teknik.Areas.Paste.Controllers
paste.Syntax = model.Syntax; paste.Syntax = model.Syntax;
paste.DateEdited = DateTime.Now; paste.DateEdited = DateTime.Now;
PasteHelper.ModifyPaste(_dbContext, paste); PasteHelper.ModifyPaste(_dbContext, _cache, paste);
// Delete the old file // Delete the old file
storageService.DeleteFile(oldFile); storageService.DeleteFile(oldFile);
@ -416,13 +418,13 @@ namespace Teknik.Areas.Paste.Controllers
[HttpOptions] [HttpOptions]
public IActionResult Delete(string id) 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 != null)
{ {
if (foundPaste.User?.Username == User.Identity.Name || if (foundPaste.User?.Username == User.Identity.Name ||
User.IsInRole("Admin")) 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") }); return Json(new { result = true, redirect = Url.SubRouteUrl("p", "Paste.Index") });
} }

View File

@ -19,9 +19,6 @@ namespace Teknik.Areas.Paste
{ {
public static class PasteHelper public static class PasteHelper
{ {
private static object _cacheLock = new object();
private readonly static ObjectCache _pasteCache = new ObjectCache(300);
public static async Task<Models.Paste> CreatePaste(Config config, TeknikEntities db, string content, string title = "", string syntax = "text", ExpirationUnit expireUnit = ExpirationUnit.Never, int expireLength = 1, string password = "") public static async Task<Models.Paste> 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(); 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 // Fire and forget updating of the download count
queue.QueueBackgroundWorkItem(async token => queue.QueueBackgroundWorkItem(async token =>
@ -139,22 +136,20 @@ namespace Teknik.Areas.Paste
using (TeknikEntities db = new TeknikEntities(optionsBuilder.Options)) using (TeknikEntities db = new TeknikEntities(optionsBuilder.Options))
{ {
var paste = GetPaste(db, url); var paste = GetPaste(db, cache, url);
if (paste != null) if (paste != null)
{ {
paste.Views++; 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 = cache.AddOrGetObject(url, (key) => db.Pastes.FirstOrDefault(up => up.Url == key));
{
var paste = _pasteCache.GetObject(url, (key) => db.Pastes.FirstOrDefault(up => up.Url == key));
if (paste != null && if (paste != null &&
!db.Exists(paste)) !db.Exists(paste))
@ -162,11 +157,10 @@ namespace Teknik.Areas.Paste
return paste; return paste;
} }
}
public static void DeleteFile(TeknikEntities db, Config config, ILogger<Logger> logger, string url) public static void DeleteFile(TeknikEntities db, ObjectCache cache, Config config, ILogger<Logger> logger, string url)
{ {
var paste = GetPaste(db, url); var paste = GetPaste(db, cache, url);
if (paste != null) if (paste != null)
{ {
try try
@ -185,19 +179,13 @@ namespace Teknik.Areas.Paste
} }
// Remove from the cache // Remove from the cache
lock (_cacheLock) cache.DeleteObject<PasteConfig>(url);
{
_pasteCache.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 // Update the cache's copy
lock (_cacheLock) cache.UpdateObject(paste.Url, paste);
{
_pasteCache.UpdateObject(paste.Url, paste);
}
if (paste != null) if (paste != null)
{ {

View File

@ -29,10 +29,12 @@ namespace Teknik.Areas.Upload.Controllers
public class UploadController : DefaultController public class UploadController : DefaultController
{ {
private readonly IBackgroundTaskQueue _queue; private readonly IBackgroundTaskQueue _queue;
private readonly ObjectCache _cache;
public UploadController(ILogger<Logger> logger, Config config, TeknikEntities dbContext, IBackgroundTaskQueue queue) : base(logger, config, dbContext) public UploadController(ILogger<Logger> logger, Config config, TeknikEntities dbContext, IBackgroundTaskQueue queue, ObjectCache cache) : base(logger, config, dbContext)
{ {
_queue = queue; _queue = queue;
_cache = cache;
} }
[HttpGet] [HttpGet]
@ -226,18 +228,18 @@ namespace Teknik.Areas.Upload.Controllers
long contentLength = 0; long contentLength = 0;
DateTime dateUploaded = new DateTime(); DateTime dateUploaded = new DateTime();
var upload = UploadHelper.GetUpload(_dbContext, file); var upload = UploadHelper.GetUpload(_dbContext, _cache, file);
if (upload != null) if (upload != null)
{ {
// Check Expiration // Check Expiration
if (UploadHelper.CheckExpiration(upload)) if (UploadHelper.CheckExpiration(upload))
{ {
UploadHelper.DeleteFile(_dbContext, _config, _logger, file); UploadHelper.DeleteFile(_dbContext, _cache, _config, _logger, file);
return new StatusCodeResult(StatusCodes.Status404NotFound); return new StatusCodeResult(StatusCodes.Status404NotFound);
} }
// Increment the download count for this upload // Increment the download count for this upload
UploadHelper.IncrementDownloadCount(_queue, _config, file); UploadHelper.IncrementDownloadCount(_queue, _cache, _config, file);
fileName = upload.FileName; fileName = upload.FileName;
url = upload.Url; url = upload.Url;
@ -412,13 +414,13 @@ namespace Teknik.Areas.Upload.Controllers
{ {
if (_config.UploadConfig.DownloadEnabled) if (_config.UploadConfig.DownloadEnabled)
{ {
Models.Upload upload = UploadHelper.GetUpload(_dbContext, file); Models.Upload upload = UploadHelper.GetUpload(_dbContext, _cache, file);
if (upload != null) if (upload != null)
{ {
// Check Expiration // Check Expiration
if (UploadHelper.CheckExpiration(upload)) 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" } }); 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) public IActionResult DeleteByKey(string file, string key)
{ {
ViewBag.Title = "File Delete | " + file ; ViewBag.Title = "File Delete | " + file ;
Models.Upload upload = UploadHelper.GetUpload(_dbContext, file); Models.Upload upload = UploadHelper.GetUpload(_dbContext, _cache, file);
if (upload != null) if (upload != null)
{ {
DeleteViewModel model = new DeleteViewModel(); DeleteViewModel model = new DeleteViewModel();
model.File = file; model.File = file;
if (!string.IsNullOrEmpty(upload.DeleteKey) && upload.DeleteKey == key) if (!string.IsNullOrEmpty(upload.DeleteKey) && upload.DeleteKey == key)
{ {
UploadHelper.DeleteFile(_dbContext, _config, _logger, file); UploadHelper.DeleteFile(_dbContext, _cache, _config, _logger, file);
model.Deleted = true; model.Deleted = true;
} }
else else
@ -476,13 +478,13 @@ namespace Teknik.Areas.Upload.Controllers
[HttpPost] [HttpPost]
public IActionResult GenerateDeleteKey(string file) 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 != null)
{ {
if (upload.User?.Username == User.Identity.Name || if (upload.User?.Username == User.Identity.Name ||
User.IsInRole("Admin")) 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 { 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" } }); 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] [HttpOptions]
public IActionResult Delete(string id) 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 != null)
{ {
if (foundUpload.User?.Username == User.Identity.Name || if (foundUpload.User?.Username == User.Identity.Name ||
User.IsInRole("Admin")) User.IsInRole("Admin"))
{ {
UploadHelper.DeleteFile(_dbContext, _config, _logger, id); UploadHelper.DeleteFile(_dbContext, _cache, _config, _logger, id);
return Json(new { result = true }); return Json(new { result = true });
} }
return Json(new { error = new { message = "You do not have permission to delete this Upload" } }); return Json(new { error = new { message = "You do not have permission to delete this Upload" } });

View File

@ -19,9 +19,6 @@ namespace Teknik.Areas.Upload
{ {
public static class UploadHelper public static class UploadHelper
{ {
private static object _cacheLock = new object();
private readonly static ObjectCache _uploadCache = new ObjectCache(300);
public static async Task<Models.Upload> SaveFile(TeknikEntities db, Config config, Stream file, string contentType, long contentLength, bool encrypt, ExpirationUnit expirationUnit, int expirationLength) public static async Task<Models.Upload> 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); 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; 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); var upload = db.Uploads.FirstOrDefault(up => up.Url == url);
if (upload != null) if (upload != null)
{ {
string delKey = StringHelper.RandomString(config.UploadConfig.DeleteKeyLength); string delKey = StringHelper.RandomString(config.UploadConfig.DeleteKeyLength);
upload.DeleteKey = delKey; upload.DeleteKey = delKey;
ModifyUpload(db, upload); ModifyUpload(db, cache, upload);
return delKey; return delKey;
} }
return null; return null;
@ -151,7 +148,7 @@ namespace Teknik.Areas.Upload
return false; 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 // Fire and forget updating of the download count
queue.QueueBackgroundWorkItem(async token => queue.QueueBackgroundWorkItem(async token =>
@ -163,22 +160,20 @@ namespace Teknik.Areas.Upload
using (TeknikEntities db = new TeknikEntities(optionsBuilder.Options)) using (TeknikEntities db = new TeknikEntities(optionsBuilder.Options))
{ {
var upload = GetUpload(db, url); var upload = GetUpload(db, cache, url);
if (upload != null) if (upload != null)
{ {
upload.Downloads++; 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 = cache.AddOrGetObject(url, (key) => db.Uploads.FirstOrDefault(up => up.Url == key));
{
var upload = _uploadCache.GetObject(url, (key) => db.Uploads.FirstOrDefault(up => up.Url == key));
if (upload != null && if (upload != null &&
!db.Exists(upload)) !db.Exists(upload))
@ -186,11 +181,10 @@ namespace Teknik.Areas.Upload
return upload; return upload;
} }
}
public static void DeleteFile(TeknikEntities db, Config config, ILogger<Logger> logger, string url) public static void DeleteFile(TeknikEntities db, ObjectCache cache, Config config, ILogger<Logger> logger, string url)
{ {
var upload = GetUpload(db, url); var upload = GetUpload(db, cache, url);
if (upload != null) if (upload != null)
{ {
try try
@ -209,19 +203,13 @@ namespace Teknik.Areas.Upload
} }
// Remove from the cache // Remove from the cache
lock (_cacheLock) cache.DeleteObject<UploadConfig>(url);
{
_uploadCache.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 // Update the cache's copy
lock (_cacheLock) cache.UpdateObject(upload.Url, upload);
{
_uploadCache.UpdateObject(upload.Url, upload);
}
if (upload != null) if (upload != null)
{ {

View File

@ -1408,7 +1408,7 @@ namespace Teknik.Areas.Users.Controllers
[HttpPost] [HttpPost]
[ValidateAntiForgeryToken] [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(); var context = new ControllerContext();
context.HttpContext = Request.HttpContext; context.HttpContext = Request.HttpContext;
@ -1418,11 +1418,11 @@ namespace Teknik.Areas.Users.Controllers
switch (type) switch (type)
{ {
case "upload": 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; uploadController.ControllerContext = context;
return uploadController.Delete(id); return uploadController.Delete(id);
case "paste": 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; pasteController.ControllerContext = context;
return pasteController.Delete(id); return pasteController.Delete(id);
case "shortenedUrl": case "shortenedUrl":

View File

@ -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> _logger;
public readonly ObjectCache _cache;
public CacheCleanerService(ObjectCache cache, ILogger<Logger> 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.");
}
}
}

View File

@ -101,6 +101,8 @@ namespace Teknik
services.AddHostedService<TaskQueueService>(); services.AddHostedService<TaskQueueService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>(); services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
services.AddSingleton<ObjectCache, ObjectCache>(c => new ObjectCache(300));
services.AddHostedService<CacheCleanerService>();
services.AddScoped<IErrorController, ErrorController>(); services.AddScoped<IErrorController, ErrorController>();
// Add Tracking Filter scopes // Add Tracking Filter scopes

24
Utilities/CacheEntry.cs Normal file
View File

@ -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;
}
}
}

View File

@ -15,9 +15,9 @@ namespace Teknik.Utilities.Cryptography
public AesCounterMode(PooledArray initialCounter) public AesCounterMode(PooledArray initialCounter)
{ {
if (initialCounter == null) throw new ArgumentNullException("counter"); 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})", 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 // Generate a new instance of the Aes Algorithm in ECB mode with no padding
_Algo = Aes.Create(); _Algo = Aes.Create();
@ -82,7 +82,7 @@ namespace Teknik.Utilities.Cryptography
} }
set set
{ {
if (value >= 0 && value < _EncryptedCounter.Array.Length) if (value >= 0 && value < _EncryptedCounter.Length)
{ {
_CounterPosition = value; _CounterPosition = value;
} }
@ -97,14 +97,14 @@ namespace Teknik.Utilities.Cryptography
if (initialCounter == null) throw new ArgumentNullException("counter"); if (initialCounter == null) throw new ArgumentNullException("counter");
// Check lengths // 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})", 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; _BlockSize = symmetricAlgorithm.BlockSize;
// Initialize Counter // Initialize Counter
_Counter = new PooledArray(initialCounter.Array.Length); _Counter = new PooledArray(initialCounter.Length);
initialCounter.Array.CopyTo(_Counter.Array, 0); initialCounter.Array.CopyTo(_Counter.Array, 0);
// Initialize the encrypted counter // Initialize the encrypted counter
@ -144,7 +144,7 @@ namespace Teknik.Utilities.Cryptography
for (var i = 0; i < inputCount; i++) for (var i = 0; i < inputCount; i++)
{ {
// Encrypt the counter if we have reached the end, or // Encrypt the counter if we have reached the end, or
if (_CounterPosition >= _EncryptedCounter.Array.Length) if (_CounterPosition >= _EncryptedCounter.Length)
{ {
//Reset current counter position //Reset current counter position
_CounterPosition = 0; _CounterPosition = 0;
@ -178,7 +178,7 @@ namespace Teknik.Utilities.Cryptography
for (var i = 0; i < inputCount; i++) for (var i = 0; i < inputCount; i++)
{ {
// Encrypt the counter if we have reached the end, or // Encrypt the counter if we have reached the end, or
if (_CounterPosition >= _EncryptedCounter.Array.Length) if (_CounterPosition >= _EncryptedCounter.Length)
{ {
//Reset current counter position //Reset current counter position
_CounterPosition = 0; _CounterPosition = 0;
@ -203,7 +203,7 @@ namespace Teknik.Utilities.Cryptography
public void EncryptCounter() public void EncryptCounter()
{ {
// Encrypt the current counter to the encrypted counter // 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() public void ResetCounter()
@ -214,7 +214,7 @@ namespace Teknik.Utilities.Cryptography
public void IncrementCounter() public void IncrementCounter()
{ {
int j = _Counter.Array.Length; int j = _Counter.Length;
while (--j >= 0 && ++_Counter.Array[j] == 0) while (--j >= 0 && ++_Counter.Array[j] == 0)
{ {
} }

View File

@ -3,61 +3,112 @@ using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Teknik.Utilities namespace Teknik.Utilities
{ {
public class ObjectCache public class ObjectCache
{ {
private readonly static ConcurrentDictionary<string, Tuple<DateTime, object>> objectCache = new ConcurrentDictionary<string, Tuple<DateTime, object>>(); private readonly int _defaultCacheTime;
private readonly int _cacheSeconds; private static object _cacheLock = new object();
public ObjectCache(int cacheSeconds) private readonly static ConcurrentDictionary<string, CacheEntry> _objectCache = new ConcurrentDictionary<string, CacheEntry>();
public ObjectCache(int defaultCacheTime)
{ {
_cacheSeconds = cacheSeconds; _defaultCacheTime = defaultCacheTime;
} }
public T GetObject<T>(string key, Func<string, T> getObjectFunc) public T AddOrGetObject<T>(string key, Func<string, T> getObjectFunc)
{
return AddOrGetObject(key, new TimeSpan(0, 0, _defaultCacheTime), getObjectFunc);
}
public T AddOrGetObject<T>(string key, TimeSpan cacheTime, Func<string, T> getObjectFunc)
{ {
T foundObject; T foundObject;
var cacheDate = DateTime.UtcNow; lock (_cacheLock)
if (objectCache.TryGetValue(key, out var result) &&
result.Item1 > cacheDate.Subtract(new TimeSpan(0, 0, _cacheSeconds)))
{ {
return (T)result.Item2; if (_objectCache.TryGetValue(GenerateKey<T>(key), out var result) &&
CacheValid(result))
{
if (result.RollingExpiration)
{
result.LastUpdate = DateTime.UtcNow;
}
return (T)result.Data;
} }
else else
{ {
foundObject = getObjectFunc(key); foundObject = getObjectFunc(key);
// Update the cache for this key // Update the cache for this key
if (foundObject != null) if (foundObject != null)
UpdateObject(key, foundObject, cacheDate); UpdateObject(key, foundObject, cacheTime);
}
} }
return foundObject; return foundObject;
} }
public void UpdateObject<T>(string key, T update) public void UpdateObject<T>(string key, T data)
{ {
UpdateObject(key, update, DateTime.UtcNow); UpdateObject(key, data, null, DateTime.UtcNow);
} }
public void UpdateObject<T>(string key, T update, DateTime cacheTime) public void UpdateObject<T>(string key, T data, TimeSpan cacheTime)
{ {
objectCache[key] = new Tuple<DateTime, object>(cacheTime, update); UpdateObject(key, data, cacheTime, DateTime.UtcNow);
} }
public void DeleteObject(string key) public void UpdateObject<T>(string key, T data, TimeSpan? cacheTime, DateTime lastUpdate)
{ {
objectCache.TryRemove(key, out _); if (_objectCache.TryGetValue(GenerateKey<T>(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<T>(key)] = CreateCacheEntry(data, cacheTime.Value, lastUpdate);
}
} }
public bool CacheValid(string key) public void DeleteObject<T>(string key)
{ {
if (objectCache.TryGetValue(key, out var result) && _objectCache.TryRemove(GenerateKey<T>(key), out _);
result.Item1 > DateTime.UtcNow.Subtract(new TimeSpan(0, 0, _cacheSeconds))) }
return true;
return false; 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<T>(string key)
{
var typeKey = typeof(T).ToString();
return $"{typeKey}.{key}";
}
public CacheEntry CreateCacheEntry<T>(T data, TimeSpan cacheTime, DateTime lastUpdate)
{
return new CacheEntry(false, cacheTime, lastUpdate, data);
} }
} }
} }

View File

@ -13,15 +13,19 @@ namespace Teknik.Utilities
public byte[] Array { get; private set; } public byte[] Array { get; private set; }
public readonly int Length;
public PooledArray(int size) public PooledArray(int size)
{ {
Array = _arrayPool.Rent(size); Array = _arrayPool.Rent(size);
Length = size;
} }
public PooledArray(byte[] array) public PooledArray(byte[] array)
{ {
Array = _arrayPool.Rent(array.Length); Array = _arrayPool.Rent(array.Length);
array.CopyTo(Array, 0); array.CopyTo(Array, 0);
Length = array.Length;
} }
public void Dispose() public void Dispose()