Files
Umbraco-CMS/src/Umbraco.Web/Scheduling/ScheduledTasks.cs
2018-09-10 11:32:49 +10:00

128 lines
5.0 KiB
C#

using System;
using System.Collections;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
using Umbraco.Core.Sync;
namespace Umbraco.Web.Scheduling
{
//TODO: No scheduled task (i.e. URL) would be secured, so if people are actually using these each task
// would need to be a publicly available task (URL) which isn't really very good :(
// We should really be using the AdminTokenAuthorizeAttribute for this stuff
internal class ScheduledTasks : RecurringTaskBase
{
private readonly IRuntimeState _runtime;
private readonly IUmbracoSettingsSection _settings;
private readonly ILogger _logger;
private readonly ProfilingLogger _proflog;
private static readonly Hashtable ScheduledTaskTimes = new Hashtable();
public ScheduledTasks(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayMilliseconds, int periodMilliseconds,
IRuntimeState runtime, IUmbracoSettingsSection settings, ILogger logger, ProfilingLogger proflog)
: base(runner, delayMilliseconds, periodMilliseconds)
{
_runtime = runtime;
_settings = settings;
_logger = logger;
_proflog = proflog;
}
private async Task ProcessTasksAsync(CancellationToken token)
{
var scheduledTasks = _settings.ScheduledTasks.Tasks;
foreach (var t in scheduledTasks)
{
var runTask = false;
if (ScheduledTaskTimes.ContainsKey(t.Alias) == false)
{
runTask = true;
ScheduledTaskTimes.Add(t.Alias, DateTime.Now);
}
// Add 1 second to timespan to compensate for differencies in timer
else if (
new TimeSpan(
DateTime.Now.Ticks - ((DateTime)ScheduledTaskTimes[t.Alias]).Ticks).TotalSeconds + 1 >= t.Interval)
{
runTask = true;
ScheduledTaskTimes[t.Alias] = DateTime.Now;
}
if (runTask)
{
var taskResult = await GetTaskByHttpAync(t.Url, token);
if (t.Log)
_logger.Info<ScheduledTasks>(string.Format("{0} has been called with response: {1}", t.Alias, taskResult));
}
}
}
private async Task<bool> GetTaskByHttpAync(string url, CancellationToken token)
{
using (var wc = new HttpClient())
{
// url could be relative, so better set a base url for the http client
wc.BaseAddress = _runtime.ApplicationUrl;
var request = new HttpRequestMessage(HttpMethod.Get, url);
//TODO: pass custom the authorization header, currently these aren't really secured!
//request.Headers.Authorization = AdminTokenAuthorizeAttribute.GetAuthenticationHeaderValue(_appContext);
try
{
var result = await wc.SendAsync(request, token).ConfigureAwait(false); // ConfigureAwait(false) is recommended? http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
return result.StatusCode == HttpStatusCode.OK;
}
catch (Exception ex)
{
_logger.Error<ScheduledTasks>(ex, "An error occurred calling web task for url: {Url}", url);
}
return false;
}
}
public override async Task<bool> PerformRunAsync(CancellationToken token)
{
switch (_runtime.ServerRole)
{
case ServerRole.Replica:
_logger.Debug<ScheduledTasks>("Does not run on slave servers.");
return true; // DO repeat, server role can change
case ServerRole.Unknown:
_logger.Debug<ScheduledTasks>("Does not run on servers with unknown role.");
return true; // DO repeat, server role can change
}
// ensure we do not run if not main domain, but do NOT lock it
if (_runtime.IsMainDom == false)
{
_logger.Debug<ScheduledTasks>("Does not run if not MainDom.");
return false; // do NOT repeat, going down
}
using (_proflog.DebugDuration<ScheduledTasks>("Scheduled tasks executing", "Scheduled tasks complete"))
{
try
{
await ProcessTasksAsync(token);
}
catch (Exception ex)
{
_logger.Error<ScheduledTasks>(ex, "Error executing scheduled task");
}
}
return true; // repeat
}
public override bool IsAsync => true;
}
}