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

Made tracking operations go to a background queue to be processed.

This commit is contained in:
Uncled1023 2019-02-01 00:31:09 -08:00
parent 7945ade961
commit 89ba60b593
31 changed files with 302 additions and 121 deletions

View File

@ -19,7 +19,7 @@ namespace Teknik.Areas.API.V1.Controllers
public AccountAPIv1Controller(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[HttpGet]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult GetClaims()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });

View File

@ -25,7 +25,7 @@ namespace Teknik.Areas.API.V1.Controllers
[HttpPost]
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Paste(PasteAPIv1Model model)
{
try

View File

@ -26,7 +26,7 @@ namespace Teknik.Areas.API.V1.Controllers
[HttpPost]
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Shorten(ShortenAPIv1Model model)
{
try

View File

@ -30,7 +30,7 @@ namespace Teknik.Areas.API.V1.Controllers
[HttpPost]
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public async Task<IActionResult> Upload(UploadAPIv1Model model)
{
try

View File

@ -22,7 +22,7 @@ namespace Teknik.Areas.About.Controllers
public AboutController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Index([FromServices] Config config)
{
ViewBag.Title = "About";

View File

@ -22,7 +22,7 @@ namespace Teknik.Areas.Abuse.Controllers
public AbuseController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Index()
{
ViewBag.Title = "Abuse Reporting";

View File

@ -29,7 +29,7 @@ namespace Teknik.Areas.Admin.Controllers
public AdminController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base (logger, config, dbContext) { }
[HttpGet]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Dashboard()
{
DashboardViewModel model = new DashboardViewModel();
@ -37,7 +37,7 @@ namespace Teknik.Areas.Admin.Controllers
}
[HttpGet]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult UserSearch()
{
UserSearchViewModel model = new UserSearchViewModel();
@ -45,7 +45,7 @@ namespace Teknik.Areas.Admin.Controllers
}
[HttpGet]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public async Task<IActionResult> UserInfo(string username)
{
if (UserHelper.UserExists(_dbContext, username))
@ -66,7 +66,7 @@ namespace Teknik.Areas.Admin.Controllers
}
[HttpGet]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult UploadSearch()
{
UploadSearchViewModel model = new UploadSearchViewModel();

View File

@ -30,7 +30,7 @@ namespace Teknik.Areas.Blog.Controllers
public BlogController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Blog(string username)
{
BlogViewModel model = new BlogViewModel();
@ -120,7 +120,7 @@ namespace Teknik.Areas.Blog.Controllers
#region Posts
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Post(string username, int id)
{
if (string.IsNullOrEmpty(username))
@ -163,7 +163,7 @@ namespace Teknik.Areas.Blog.Controllers
return View("~/Areas/Blog/Views/Blog/ViewPost.cshtml", model);
}
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult NewPost(string username, int blogID)
{
if (string.IsNullOrEmpty(username))
@ -203,7 +203,7 @@ namespace Teknik.Areas.Blog.Controllers
return View("~/Areas/Blog/Views/Blog/Blog.cshtml", model);
}
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult EditPost(string username, int id)
{
if (string.IsNullOrEmpty(username))

View File

@ -26,7 +26,7 @@ namespace Teknik.Areas.Contact.Controllers
public ContactController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Index()
{
ViewBag.Title = "Contact Us";

View File

@ -28,7 +28,7 @@ namespace Teknik.Areas.Error.Controllers
public ErrorController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult HttpError(int statusCode)
{
switch (statusCode)
@ -45,7 +45,7 @@ namespace Teknik.Areas.Error.Controllers
}
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult HttpGeneral(int statusCode)
{
ViewBag.Title = statusCode;
@ -59,7 +59,7 @@ namespace Teknik.Areas.Error.Controllers
}
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Http401()
{
Response.StatusCode = StatusCodes.Status401Unauthorized;
@ -76,7 +76,7 @@ namespace Teknik.Areas.Error.Controllers
}
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Http403()
{
Response.StatusCode = StatusCodes.Status403Forbidden;
@ -93,7 +93,7 @@ namespace Teknik.Areas.Error.Controllers
}
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Http404()
{
Response.StatusCode = StatusCodes.Status404NotFound;
@ -110,7 +110,7 @@ namespace Teknik.Areas.Error.Controllers
}
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Http500(Exception exception)
{
try

View File

@ -18,7 +18,7 @@ namespace Teknik.Areas.FAQ.Controllers
public FAQController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Index()
{
ViewBag.Title = "Frequently Asked Questions";

View File

@ -14,7 +14,7 @@ namespace Teknik.Areas.Help.Controllers
{
[Authorize]
[Area("Help")]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public class HelpController : DefaultController
{
public HelpController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }

View File

@ -23,7 +23,7 @@ namespace Teknik.Areas.Home.Controllers
public HomeController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Index()
{
HomeViewModel model = new HomeViewModel();

View File

@ -31,7 +31,7 @@ namespace Teknik.Areas.Paste.Controllers
public PasteController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Index()
{
ViewBag.Title = "Pastebin";
@ -41,7 +41,7 @@ namespace Teknik.Areas.Paste.Controllers
}
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public async Task<IActionResult> ViewPaste(string type, string url, string password)
{
Models.Paste paste = _dbContext.Pastes.Where(p => p.Url == url).FirstOrDefault();
@ -205,7 +205,7 @@ namespace Teknik.Areas.Paste.Controllers
return View("~/Areas/Paste/Views/Paste/Index.cshtml", model);
}
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public async Task<IActionResult> Edit(string url, string password)
{
Models.Paste paste = _dbContext.Pastes.Where(p => p.Url == url).FirstOrDefault();

View File

@ -29,7 +29,7 @@ namespace Teknik.Areas.Podcast.Controllers
public PodcastController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Index()
{
MainViewModel model = new MainViewModel();
@ -64,7 +64,7 @@ namespace Teknik.Areas.Podcast.Controllers
#region Podcasts
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult View(int episode)
{
PodcastViewModel model = new PodcastViewModel();
@ -86,7 +86,7 @@ namespace Teknik.Areas.Podcast.Controllers
[HttpGet]
[AllowAnonymous]
[ResponseCache(Duration = 31536000, Location = ResponseCacheLocation.Any)]
[ServiceFilter(typeof(TrackDownload))]
[TrackDownload]
public IActionResult Download(int episode, string fileName)
{
string path = string.Empty;

View File

@ -18,7 +18,7 @@ namespace Teknik.Areas.Privacy.Controllers
public PrivacyController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Index()
{
ViewBag.Title = "Privacy Policy";

View File

@ -32,7 +32,7 @@ namespace Teknik.Areas.RSS.Controllers
public RSSController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public async Task Index()
{
Response.ContentType = "application/rss+xml";
@ -50,7 +50,7 @@ namespace Teknik.Areas.RSS.Controllers
}
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public async Task Blog(string username)
{
Response.ContentType = "application/rss+xml";
@ -156,7 +156,7 @@ namespace Teknik.Areas.RSS.Controllers
}
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public async Task Podcast()
{
Response.ContentType = "application/rss+xml";

View File

@ -24,7 +24,7 @@ namespace Teknik.Areas.Shortener.Controllers
public ShortenerController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Index()
{
ViewBag.Title = "Url Shortener";
@ -33,7 +33,7 @@ namespace Teknik.Areas.Shortener.Controllers
}
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult RedirectToUrl(string url)
{
ShortenedUrl shortUrl = _dbContext.ShortenedUrls.Where(s => s.ShortUrl == url).FirstOrDefault();

View File

@ -24,7 +24,7 @@ namespace Teknik.Areas.Stats.Controllers
public StatsController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Index()
{
ViewBag.Title = "System Statistics";

View File

@ -18,7 +18,7 @@ namespace Teknik.Areas.TOS.Controllers
public TOSController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Index()
{
ViewBag.Title = "Terms of Service";

View File

@ -33,7 +33,7 @@ namespace Teknik.Areas.Upload.Controllers
[HttpGet]
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public async Task<IActionResult> Index()
{
ViewBag.Title = "Upload Files";
@ -181,8 +181,8 @@ namespace Teknik.Areas.Upload.Controllers
[HttpGet]
[AllowAnonymous]
[ServiceFilter(typeof(TrackDownload))]
[ServiceFilter(typeof(TrackPageView))]
[TrackDownload]
[TrackPageView]
[ResponseCache(Duration = 31536000, Location = ResponseCacheLocation.Any)]
public async Task<IActionResult> Download(string file)
{

View File

@ -62,7 +62,7 @@ namespace Teknik.Areas.Users.Controllers
}
[HttpGet]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Login(string returnUrl)
{
// Let's double check their email and git accounts to make sure they exist
@ -86,7 +86,7 @@ namespace Teknik.Areas.Users.Controllers
}
[HttpGet]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public async Task Logout()
{
// these are the sub & sid to signout
@ -100,7 +100,7 @@ namespace Teknik.Areas.Users.Controllers
}
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult GetPremium()
{
ViewBag.Title = "Get a Premium Account";
@ -112,7 +112,7 @@ namespace Teknik.Areas.Users.Controllers
[HttpGet]
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Register(string inviteCode, string ReturnUrl)
{
RegisterViewModel model = new RegisterViewModel();
@ -206,7 +206,7 @@ namespace Teknik.Areas.Users.Controllers
// GET: Profile/Profile
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public async Task<IActionResult> ViewProfile(string username)
{
if (string.IsNullOrEmpty(username))
@ -264,7 +264,7 @@ namespace Teknik.Areas.Users.Controllers
return View(model);
}
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult ViewServiceData()
{
string username = User.Identity.Name;
@ -303,13 +303,13 @@ namespace Teknik.Areas.Users.Controllers
return View(model);
}
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult Settings()
{
return Redirect(Url.SubRouteUrl("account", "User.ProfileSettings"));
}
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult ProfileSettings()
{
string username = User.Identity.Name;
@ -334,7 +334,7 @@ namespace Teknik.Areas.Users.Controllers
return new StatusCodeResult(StatusCodes.Status403Forbidden);
}
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult AccountSettings()
{
string username = User.Identity.Name;
@ -356,7 +356,7 @@ namespace Teknik.Areas.Users.Controllers
return new StatusCodeResult(StatusCodes.Status403Forbidden);
}
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public async Task<IActionResult> SecuritySettings()
{
string username = User.Identity.Name;
@ -399,7 +399,7 @@ namespace Teknik.Areas.Users.Controllers
return new StatusCodeResult(StatusCodes.Status403Forbidden);
}
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public async Task<IActionResult> DeveloperSettings()
{
string username = User.Identity.Name;
@ -447,7 +447,7 @@ namespace Teknik.Areas.Users.Controllers
return new StatusCodeResult(StatusCodes.Status403Forbidden);
}
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult InviteSettings()
{
string username = User.Identity.Name;
@ -493,7 +493,7 @@ namespace Teknik.Areas.Users.Controllers
return new StatusCodeResult(StatusCodes.Status403Forbidden);
}
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult BlogSettings()
{
string username = User.Identity.Name;
@ -517,7 +517,7 @@ namespace Teknik.Areas.Users.Controllers
return new StatusCodeResult(StatusCodes.Status403Forbidden);
}
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult UploadSettings()
{
string username = User.Identity.Name;
@ -544,7 +544,7 @@ namespace Teknik.Areas.Users.Controllers
[HttpGet]
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public async Task<IActionResult> ViewRawPGP(string username)
{
ViewBag.Title = username + "'s Public Key";
@ -839,7 +839,7 @@ namespace Teknik.Areas.Users.Controllers
[HttpGet]
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult ResetPassword(string username)
{
ResetPasswordViewModel model = new ResetPasswordViewModel();
@ -883,7 +883,7 @@ namespace Teknik.Areas.Users.Controllers
[HttpGet]
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public async Task<IActionResult> VerifyResetPassword(string username, string code)
{
bool verified = true;

View File

@ -34,7 +34,7 @@ namespace Teknik.Areas.Vault.Controllers
public VaultController(ILogger<Logger> logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { }
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public async Task<IActionResult> ViewVault(string id)
{
Models.Vault foundVault = _dbContext.Vaults.Where(v => v.Url == id).FirstOrDefault();
@ -136,7 +136,7 @@ namespace Teknik.Areas.Vault.Controllers
[HttpGet]
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult NewVault()
{
ViewBag.Title = "Create Vault";
@ -147,7 +147,7 @@ namespace Teknik.Areas.Vault.Controllers
[HttpGet]
[AllowAnonymous]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult NewVaultFromService(string type, string items)
{
ViewBag.Title = "Create Vault";
@ -184,7 +184,7 @@ namespace Teknik.Areas.Vault.Controllers
}
[HttpGet]
[ServiceFilter(typeof(TrackPageView))]
[TrackPageView]
public IActionResult EditVault(string url, string type, string items)
{
ViewBag.Title = "Edit Vault";

View File

@ -10,15 +10,24 @@ using Teknik.Tracking;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
namespace Teknik.Filters
{
public class TrackDownloadAttribute : TypeFilterAttribute
{
public TrackDownloadAttribute() : base(typeof(TrackDownload))
{
}
public class TrackDownload : ActionFilterAttribute
{
private readonly IBackgroundTaskQueue _queue;
private readonly Config _config;
public TrackDownload(Config config)
public TrackDownload(IBackgroundTaskQueue queue, Config config)
{
_queue = queue;
_config = config;
}
@ -42,7 +51,11 @@ namespace Teknik.Filters
string url = UriHelper.GetEncodedUrl(request);
// Fire and forget. Don't need to wait for it.
_queue.QueueBackgroundWorkItem(async token =>
{
Tracking.Tracking.TrackDownload(filterContext.HttpContext, _config, userAgent, clientIp, url, urlReferrer);
});
}
}
}
}

View File

@ -14,12 +14,19 @@ using Microsoft.AspNetCore.Mvc;
namespace Teknik.Filters
{
public class TrackPageViewAttribute : TypeFilterAttribute
{
public TrackPageViewAttribute() : base(typeof(TrackPageView))
{
}
public class TrackPageView : ActionFilterAttribute
{
private readonly IBackgroundTaskQueue _queue;
private readonly Config _config;
public TrackPageView(Config config)
public TrackPageView(IBackgroundTaskQueue queue, Config config)
{
_queue = queue;
_config = config;
}
@ -60,7 +67,11 @@ namespace Teknik.Filters
bool hasJava = false;
// Fire and forget. Don't need to wait for it.
_queue.QueueBackgroundWorkItem(async token =>
{
Tracking.Tracking.TrackPageView(filterContext.HttpContext, _config, title, sub, clientIp, url, urlReferrer, userAgent, pixelWidth, pixelHeight, hasCookies, acceptLang, hasJava);
});
}
}
}
}

View File

@ -39,6 +39,7 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Authorization;
using System.Text.Encodings.Web;
using Teknik.Tracking;
namespace Teknik
{
@ -96,10 +97,13 @@ namespace Teknik
#endif
});
services.AddHostedService<TrackingService>();
services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
// Add Tracking Filter scopes
services.AddScoped<TrackDownload>();
//services.AddScoped<TrackDownload>();
//services.AddScoped<TrackLink>();
services.AddScoped<TrackPageView>();
//services.AddScoped<TrackPageView>();
// Create the Database Context
services.AddDbContext<TeknikEntities>(options => options

View File

@ -13,6 +13,8 @@
<ItemGroup>
<ProjectReference Include="..\Configuration\Configuration.csproj" />
<ProjectReference Include="..\Logging\Logging.csproj" />
<ProjectReference Include="..\Utilities\Utilities.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,48 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Teknik.Logging;
using Teknik.Utilities;
namespace Teknik.Tracking
{
public class TrackingService : BackgroundService
{
private readonly ILogger<Logger> _logger;
public TrackingService(IBackgroundTaskQueue taskQueue,
ILogger<Logger> logger)
{
TaskQueue = taskQueue;
_logger = logger;
}
public IBackgroundTaskQueue TaskQueue { get; }
protected async override Task ExecuteAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Queued Hosted Service is starting.");
while (!cancellationToken.IsCancellationRequested)
{
var workItem = await TaskQueue.DequeueAsync(cancellationToken);
try
{
await workItem(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
$"Error occurred executing {nameof(workItem)}.");
}
}
_logger.LogInformation("Queued Hosted Service is stopping.");
}
}
}

View File

@ -0,0 +1,38 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Teknik.Utilities
{
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private ConcurrentQueue<Func<CancellationToken, Task>> _workItems =
new ConcurrentQueue<Func<CancellationToken, Task>>();
private SemaphoreSlim _signal = new SemaphoreSlim(0);
public void QueueBackgroundWorkItem(
Func<CancellationToken, Task> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
_workItems.Enqueue(workItem);
_signal.Release();
}
public async Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_workItems.TryDequeue(out var workItem);
return workItem;
}
}
}

View File

@ -0,0 +1,49 @@
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Teknik.Utilities
{
public abstract class HostedService : IHostedService
{
private Task _executingTask;
private CancellationTokenSource _cts;
public Task StartAsync(CancellationToken cancellationToken)
{
// Create a linked token so we can trigger cancellation outside of this token's cancellation
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
// Store the task we're executing
_executingTask = ExecuteAsync(_cts.Token);
// If the task is completed then return it, otherwise it's running
return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
// Signal cancellation to the executing method
_cts.Cancel();
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
// Throw if cancellation triggered
cancellationToken.ThrowIfCancellationRequested();
}
// Derived classes should override this and execute a long running method until
// cancellation is requested
protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Teknik.Utilities
{
public interface IBackgroundTaskQueue
{
void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
Task<Func<CancellationToken, Task>> DequeueAsync(
CancellationToken cancellationToken);
}
}