Browse Source

Added cache for Upload objects and background processing of download increments

master
Teknikode 4 months ago
parent
commit
035c927326
  1. 4
      Teknik/Areas/Admin/Controllers/AdminController.cs
  2. 36
      Teknik/Areas/Upload/Controllers/UploadController.cs
  3. 66
      Teknik/Areas/Upload/UploadHelper.cs
  4. 4
      Teknik/Areas/User/Controllers/UserController.cs
  5. 57
      Utilities/ObjectCache.cs

4
Teknik/Areas/Admin/Controllers/AdminController.cs

@ -262,7 +262,7 @@ namespace Teknik.Areas.Admin.Controllers @@ -262,7 +262,7 @@ namespace Teknik.Areas.Admin.Controllers
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult DeleteData(string type, string id)
public IActionResult DeleteData(string type, string id, [FromServices] IBackgroundTaskQueue queue)
{
var context = new ControllerContext();
context.HttpContext = Request.HttpContext;
@ -272,7 +272,7 @@ namespace Teknik.Areas.Admin.Controllers @@ -272,7 +272,7 @@ namespace Teknik.Areas.Admin.Controllers
switch (type)
{
case "upload":
var uploadController = new Upload.Controllers.UploadController(_logger, _config, _dbContext);
var uploadController = new Upload.Controllers.UploadController(_logger, _config, _dbContext, queue);
uploadController.ControllerContext = context;
return uploadController.Delete(id);
case "paste":

36
Teknik/Areas/Upload/Controllers/UploadController.cs

@ -32,7 +32,15 @@ namespace Teknik.Areas.Upload.Controllers @@ -32,7 +32,15 @@ namespace Teknik.Areas.Upload.Controllers
[Area("Upload")]
public class UploadController : DefaultController
{
public UploadController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
private const int _cacheLength = 300;
private readonly ObjectCache _uploadCache;
private readonly IBackgroundTaskQueue _queue;
public UploadController(ILogger<Logger> logger, Config config, TeknikEntities dbContext, IBackgroundTaskQueue queue) : base(logger, config, dbContext)
{
_uploadCache = new ObjectCache(_cacheLength);
_queue = queue;
}
[HttpGet]
[AllowAnonymous]
@ -225,19 +233,18 @@ namespace Teknik.Areas.Upload.Controllers @@ -225,19 +233,18 @@ namespace Teknik.Areas.Upload.Controllers
long contentLength = 0;
DateTime dateUploaded = new DateTime();
Models.Upload upload = _dbContext.Uploads.Where(up => up.Url == file).FirstOrDefault();
var upload = UploadHelper.GetUpload(_dbContext, file);
if (upload != null)
{
// Check Expiration
if (UploadHelper.CheckExpiration(upload))
{
UploadHelper.DeleteFile(_dbContext, _config, _logger, upload);
UploadHelper.DeleteFile(_dbContext, _config, _logger, file);
return new StatusCodeResult(StatusCodes.Status404NotFound);
}
upload.Downloads += 1;
_dbContext.Entry(upload).State = EntityState.Modified;
_dbContext.SaveChanges();
// Increment the download count for this upload
UploadHelper.IncrementDownloadCount(_queue, _config, file);
fileName = upload.FileName;
url = upload.Url;
@ -431,13 +438,13 @@ namespace Teknik.Areas.Upload.Controllers @@ -431,13 +438,13 @@ namespace Teknik.Areas.Upload.Controllers
{
if (_config.UploadConfig.DownloadEnabled)
{
Models.Upload upload = _dbContext.Uploads.Where(up => up.Url == file).FirstOrDefault();
Models.Upload upload = UploadHelper.GetUpload(_dbContext, file);
if (upload != null)
{
// Check Expiration
if (UploadHelper.CheckExpiration(upload))
{
UploadHelper.DeleteFile(_dbContext, _config, _logger, upload);
UploadHelper.DeleteFile(_dbContext, _config, _logger, file);
return Json(new { error = new { message = "File Does Not Exist" } });
}
@ -485,14 +492,14 @@ namespace Teknik.Areas.Upload.Controllers @@ -485,14 +492,14 @@ namespace Teknik.Areas.Upload.Controllers
public IActionResult DeleteByKey(string file, string key)
{
ViewBag.Title = "File Delete | " + file ;
Models.Upload upload = _dbContext.Uploads.Where(up => up.Url == file).FirstOrDefault();
Models.Upload upload = UploadHelper.GetUpload(_dbContext, file);
if (upload != null)
{
DeleteViewModel model = new DeleteViewModel();
model.File = file;
if (!string.IsNullOrEmpty(upload.DeleteKey) && upload.DeleteKey == key)
{
UploadHelper.DeleteFile(_dbContext, _config, _logger, upload);
UploadHelper.DeleteFile(_dbContext, _config, _logger, file);
model.Deleted = true;
}
else
@ -507,16 +514,13 @@ namespace Teknik.Areas.Upload.Controllers @@ -507,16 +514,13 @@ namespace Teknik.Areas.Upload.Controllers
[HttpPost]
public IActionResult GenerateDeleteKey(string file)
{
Models.Upload upload = _dbContext.Uploads.Where(up => up.Url == file).FirstOrDefault();
Models.Upload upload = UploadHelper.GetUpload(_dbContext, file);
if (upload != null)
{
if (upload.User?.Username == User.Identity.Name ||
User.IsInRole("Admin"))
{
string delKey = StringHelper.RandomString(_config.UploadConfig.DeleteKeyLength);
upload.DeleteKey = delKey;
_dbContext.Entry(upload).State = EntityState.Modified;
_dbContext.SaveChanges();
var delKey = UploadHelper.GenerateDeleteKey(_dbContext, _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" } });
@ -534,7 +538,7 @@ namespace Teknik.Areas.Upload.Controllers @@ -534,7 +538,7 @@ namespace Teknik.Areas.Upload.Controllers
if (foundUpload.User?.Username == User.Identity.Name ||
User.IsInRole("Admin"))
{
UploadHelper.DeleteFile(_dbContext, _config, _logger, foundUpload);
UploadHelper.DeleteFile(_dbContext, _config, _logger, id);
return Json(new { result = true });
}
return Json(new { error = new { message = "You do not have permission to delete this Upload" } });

66
Teknik/Areas/Upload/UploadHelper.cs

@ -12,11 +12,15 @@ using Teknik.Data; @@ -12,11 +12,15 @@ using Teknik.Data;
using Teknik.StorageService;
using Teknik.Logging;
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
namespace Teknik.Areas.Upload
{
public static class UploadHelper
{
private static object _cacheLock = new object();
private readonly static ObjectCache _uploadCache = new ObjectCache(300);
public static Models.Upload SaveFile(TeknikEntities db, Config config, Stream file, string contentType, long contentLength, bool encrypt, ExpirationUnit expirationUnit, int expirationLength)
{
return SaveFile(db, config, file, contentType, contentLength, encrypt, expirationUnit, expirationLength, string.Empty, null, null, 256, 128);
@ -119,6 +123,19 @@ namespace Teknik.Areas.Upload @@ -119,6 +123,19 @@ namespace Teknik.Areas.Upload
return upload;
}
public static string GenerateDeleteKey(TeknikEntities db, 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);
return delKey;
}
return null;
}
public static bool CheckExpiration(Models.Upload upload)
{
if (upload.ExpireDate != null && DateTime.Now >= upload.ExpireDate)
@ -129,15 +146,37 @@ namespace Teknik.Areas.Upload @@ -129,15 +146,37 @@ namespace Teknik.Areas.Upload
return false;
}
public static Models.Upload GetUpload(TeknikEntities db, string url)
public static void IncrementDownloadCount(IBackgroundTaskQueue queue, Config config, string url)
{
Models.Upload upload = db.Uploads.Where(up => up.Url == url).FirstOrDefault();
// Fire and forget updating of the download count
queue.QueueBackgroundWorkItem(async token =>
{
var optionsBuilder = new DbContextOptionsBuilder<TeknikEntities>();
optionsBuilder.UseSqlServer(config.DbConnection);
return upload;
using (TeknikEntities db = new TeknikEntities(optionsBuilder.Options))
{
var upload = db.Uploads.FirstOrDefault(up => up.Url == url);
if (upload != null)
{
upload.Downloads++;
ModifyUpload(db, upload);
}
}
});
}
public static void DeleteFile(TeknikEntities db, Config config, ILogger<Logger> logger, Models.Upload upload)
public static Models.Upload GetUpload(TeknikEntities db, string url)
{
lock (_cacheLock)
{
return _uploadCache.GetObject(url, (key) => db.Uploads.FirstOrDefault(up => up.Url == key));
}
}
public static void DeleteFile(TeknikEntities db, Config config, ILogger<Logger> logger, string url)
{
var upload = db.Uploads.FirstOrDefault(up => up.Url == url);
try
{
var storageService = StorageServiceFactory.GetStorageService(config.UploadConfig.StorageConfig);
@ -148,9 +187,28 @@ namespace Teknik.Areas.Upload @@ -148,9 +187,28 @@ namespace Teknik.Areas.Upload
logger.LogError(ex, "Unable to delete file: {0}", upload.FileName);
}
// Remove from the cache
lock (_cacheLock)
{
_uploadCache.DeleteObject(upload.FileName);
}
// Delete from the DB
db.Uploads.Remove(upload);
db.SaveChanges();
}
public static void ModifyUpload(TeknikEntities db, Models.Upload upload)
{
// Update the cache's copy
lock (_cacheLock)
{
_uploadCache.UpdateObject(upload.Url, upload);
}
// Update the database
db.Entry(upload).State = EntityState.Modified;
db.SaveChanges();
}
}
}

4
Teknik/Areas/User/Controllers/UserController.cs

@ -1421,7 +1421,7 @@ namespace Teknik.Areas.Users.Controllers @@ -1421,7 +1421,7 @@ namespace Teknik.Areas.Users.Controllers
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult DeleteData(string type, string id)
public IActionResult DeleteData(string type, string id, [FromServices] IBackgroundTaskQueue queue)
{
var context = new ControllerContext();
context.HttpContext = Request.HttpContext;
@ -1431,7 +1431,7 @@ namespace Teknik.Areas.Users.Controllers @@ -1431,7 +1431,7 @@ namespace Teknik.Areas.Users.Controllers
switch (type)
{
case "upload":
var uploadController = new Upload.Controllers.UploadController(_logger, _config, _dbContext);
var uploadController = new Upload.Controllers.UploadController(_logger, _config, _dbContext, queue);
uploadController.ControllerContext = context;
return uploadController.Delete(id);
case "paste":

57
Utilities/ObjectCache.cs

@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Teknik.Utilities
{
public class ObjectCache
{
private readonly static Dictionary<string, Tuple<DateTime, object>> objectCache = new Dictionary<string, Tuple<DateTime, object>>();
private readonly int _cacheSeconds;
public ObjectCache(int cacheSeconds)
{
_cacheSeconds = cacheSeconds;
}
public T GetObject<T>(string key, Func<string, T> getObjectFunc)
{
T foundObject;
var cacheDate = DateTime.UtcNow;
if (objectCache.TryGetValue(key, out var result) &&
result.Item1 > cacheDate.Subtract(new TimeSpan(0, 0, _cacheSeconds)))
{
cacheDate = result.Item1;
foundObject = (T)result.Item2;
}
else
{
foundObject = getObjectFunc(key);
}
if (foundObject != null)
objectCache[key] = new Tuple<DateTime, object>(cacheDate, foundObject);
return foundObject;
}
public void UpdateObject<T>(string key, T update)
{
var cacheDate = DateTime.UtcNow;
if (objectCache.TryGetValue(key, out var result))
{
if (result.Item1 <= cacheDate.Subtract(new TimeSpan(0, 0, _cacheSeconds)))
DeleteObject(key);
else
objectCache[key] = new Tuple<DateTime, object>(result.Item1, update);
}
}
public void DeleteObject(string key)
{
objectCache.Remove(key);
}
}
}
Loading…
Cancel
Save