rework scheduled tasks in preparation of common project going portable

This commit is contained in:
Luke Pulverenti
2016-10-23 15:14:57 -04:00
parent 4829683402
commit 07791d46a5
78 changed files with 500 additions and 411 deletions

View File

@@ -0,0 +1,91 @@
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Tasks;
using System;
using System.Globalization;
using System.Threading;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.Common.ScheduledTasks
{
/// <summary>
/// Represents a task trigger that fires everyday
/// </summary>
public class DailyTrigger : ITaskTrigger
{
/// <summary>
/// Get the time of day to trigger the task to run
/// </summary>
/// <value>The time of day.</value>
public TimeSpan TimeOfDay { get; set; }
/// <summary>
/// Gets or sets the timer.
/// </summary>
/// <value>The timer.</value>
private Timer Timer { get; set; }
/// <summary>
/// Gets the execution properties of this task.
/// </summary>
/// <value>
/// The execution properties of this task.
/// </value>
public TaskExecutionOptions TaskOptions { get; set; }
/// <summary>
/// Stars waiting for the trigger action
/// </summary>
/// <param name="lastResult">The last result.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
{
DisposeTimer();
var now = DateTime.Now;
var triggerDate = now.TimeOfDay > TimeOfDay ? now.Date.AddDays(1) : now.Date;
triggerDate = triggerDate.Add(TimeOfDay);
var dueTime = triggerDate - now;
logger.Info("Daily trigger for {0} set to fire at {1}, which is {2} minutes from now.", taskName, triggerDate.ToString(), dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture));
Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
}
/// <summary>
/// Stops waiting for the trigger action
/// </summary>
public void Stop()
{
DisposeTimer();
}
/// <summary>
/// Disposes the timer.
/// </summary>
private void DisposeTimer()
{
if (Timer != null)
{
Timer.Dispose();
}
}
/// <summary>
/// Occurs when [triggered].
/// </summary>
public event EventHandler<GenericEventArgs<TaskExecutionOptions>> Triggered;
/// <summary>
/// Called when [triggered].
/// </summary>
private void OnTriggered()
{
if (Triggered != null)
{
Triggered(this, new GenericEventArgs<TaskExecutionOptions>(TaskOptions));
}
}
}
}

View File

@@ -0,0 +1,112 @@
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Tasks;
using System;
using System.Linq;
using System.Threading;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.Common.ScheduledTasks
{
/// <summary>
/// Represents a task trigger that runs repeatedly on an interval
/// </summary>
public class IntervalTrigger : ITaskTrigger
{
/// <summary>
/// Gets or sets the interval.
/// </summary>
/// <value>The interval.</value>
public TimeSpan Interval { get; set; }
/// <summary>
/// Gets or sets the timer.
/// </summary>
/// <value>The timer.</value>
private Timer Timer { get; set; }
/// <summary>
/// Gets the execution properties of this task.
/// </summary>
/// <value>
/// The execution properties of this task.
/// </value>
public TaskExecutionOptions TaskOptions { get; set; }
private DateTime _lastStartDate;
/// <summary>
/// Stars waiting for the trigger action
/// </summary>
/// <param name="lastResult">The last result.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
{
DisposeTimer();
DateTime triggerDate;
if (lastResult == null)
{
// Task has never been completed before
triggerDate = DateTime.UtcNow.AddHours(1);
}
else
{
triggerDate = new[] { lastResult.EndTimeUtc, _lastStartDate }.Max().Add(Interval);
}
if (DateTime.UtcNow > triggerDate)
{
triggerDate = DateTime.UtcNow.AddMinutes(1);
}
var dueTime = triggerDate - DateTime.UtcNow;
var maxDueTime = TimeSpan.FromDays(7);
if (dueTime > maxDueTime)
{
dueTime = maxDueTime;
}
Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
}
/// <summary>
/// Stops waiting for the trigger action
/// </summary>
public void Stop()
{
DisposeTimer();
}
/// <summary>
/// Disposes the timer.
/// </summary>
private void DisposeTimer()
{
if (Timer != null)
{
Timer.Dispose();
}
}
/// <summary>
/// Occurs when [triggered].
/// </summary>
public event EventHandler<GenericEventArgs<TaskExecutionOptions>> Triggered;
/// <summary>
/// Called when [triggered].
/// </summary>
private void OnTriggered()
{
DisposeTimer();
if (Triggered != null)
{
_lastStartDate = DateTime.UtcNow;
Triggered(this, new GenericEventArgs<TaskExecutionOptions>(TaskOptions));
}
}
}
}

View File

@@ -232,13 +232,12 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// <summary>
/// The _triggers
/// </summary>
private List<ITaskTrigger> _triggers;
private Tuple<TaskTriggerInfo,ITaskTrigger>[] _triggers;
/// <summary>
/// Gets the triggers that define when the task will run
/// </summary>
/// <value>The triggers.</value>
/// <exception cref="System.ArgumentNullException">value</exception>
public IEnumerable<ITaskTrigger> Triggers
private Tuple<TaskTriggerInfo, ITaskTrigger>[] InternalTriggers
{
get
{
@@ -257,11 +256,33 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
DisposeTriggers();
}
_triggers = value.ToList();
_triggers = value.ToArray();
ReloadTriggerEvents(false);
}
}
SaveTriggers(_triggers);
/// <summary>
/// Gets the triggers that define when the task will run
/// </summary>
/// <value>The triggers.</value>
/// <exception cref="System.ArgumentNullException">value</exception>
public TaskTriggerInfo[] Triggers
{
get
{
return InternalTriggers.Select(i => i.Item1).ToArray();
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
SaveTriggers(value);
InternalTriggers = value.Select(i => new Tuple<TaskTriggerInfo, ITaskTrigger>(i, GetTrigger(i))).ToArray();
}
}
@@ -304,8 +325,10 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
private void ReloadTriggerEvents(bool isApplicationStartup)
{
foreach (var trigger in Triggers)
foreach (var triggerInfo in InternalTriggers)
{
var trigger = triggerInfo.Item2;
trigger.Stop();
trigger.Triggered -= trigger_Triggered;
@@ -507,23 +530,29 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// Loads the triggers.
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
private List<ITaskTrigger> LoadTriggers()
private Tuple<TaskTriggerInfo, ITaskTrigger>[] LoadTriggers()
{
var settings = LoadTriggerSettings();
return settings.Select(i => new Tuple<TaskTriggerInfo, ITaskTrigger>(i, GetTrigger(i))).ToArray();
}
private TaskTriggerInfo[] LoadTriggerSettings()
{
try
{
return JsonSerializer.DeserializeFromFile<IEnumerable<TaskTriggerInfo>>(GetConfigurationFilePath())
.Select(ScheduledTaskHelpers.GetTrigger)
.ToList();
.ToArray();
}
catch (FileNotFoundException)
{
// File doesn't exist. No biggie. Return defaults.
return ScheduledTask.GetDefaultTriggers().ToList();
return ScheduledTask.GetDefaultTriggers().ToArray();
}
catch (DirectoryNotFoundException)
{
// File doesn't exist. No biggie. Return defaults.
return ScheduledTask.GetDefaultTriggers().ToList();
return ScheduledTask.GetDefaultTriggers().ToArray();
}
}
@@ -531,13 +560,13 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// Saves the triggers.
/// </summary>
/// <param name="triggers">The triggers.</param>
private void SaveTriggers(IEnumerable<ITaskTrigger> triggers)
private void SaveTriggers(TaskTriggerInfo[] triggers)
{
var path = GetConfigurationFilePath();
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
JsonSerializer.SerializeToFile(triggers.Select(ScheduledTaskHelpers.GetTriggerInfo), path);
JsonSerializer.SerializeToFile(triggers, path);
}
/// <summary>
@@ -561,11 +590,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
Id = Id
};
var hasKey = ScheduledTask as IHasKey;
if (hasKey != null)
{
result.Key = hasKey.Key;
}
result.Key = ScheduledTask.Key;
if (ex != null)
{
@@ -655,13 +680,98 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
}
}
/// <summary>
/// Converts a TaskTriggerInfo into a concrete BaseTaskTrigger
/// </summary>
/// <param name="info">The info.</param>
/// <returns>BaseTaskTrigger.</returns>
/// <exception cref="System.ArgumentNullException"></exception>
/// <exception cref="System.ArgumentException">Invalid trigger type: + info.Type</exception>
public static ITaskTrigger GetTrigger(TaskTriggerInfo info)
{
var options = new TaskExecutionOptions
{
MaxRuntimeMs = info.MaxRuntimeMs
};
if (info.Type.Equals(typeof(DailyTrigger).Name, StringComparison.OrdinalIgnoreCase))
{
if (!info.TimeOfDayTicks.HasValue)
{
throw new ArgumentNullException();
}
return new DailyTrigger
{
TimeOfDay = TimeSpan.FromTicks(info.TimeOfDayTicks.Value),
TaskOptions = options
};
}
if (info.Type.Equals(typeof(WeeklyTrigger).Name, StringComparison.OrdinalIgnoreCase))
{
if (!info.TimeOfDayTicks.HasValue)
{
throw new ArgumentNullException();
}
if (!info.DayOfWeek.HasValue)
{
throw new ArgumentNullException();
}
return new WeeklyTrigger
{
TimeOfDay = TimeSpan.FromTicks(info.TimeOfDayTicks.Value),
DayOfWeek = info.DayOfWeek.Value,
TaskOptions = options
};
}
if (info.Type.Equals(typeof(IntervalTrigger).Name, StringComparison.OrdinalIgnoreCase))
{
if (!info.IntervalTicks.HasValue)
{
throw new ArgumentNullException();
}
return new IntervalTrigger
{
Interval = TimeSpan.FromTicks(info.IntervalTicks.Value),
TaskOptions = options
};
}
if (info.Type.Equals(typeof(SystemEventTrigger).Name, StringComparison.OrdinalIgnoreCase))
{
if (!info.SystemEvent.HasValue)
{
throw new ArgumentNullException();
}
return new SystemEventTrigger
{
SystemEvent = info.SystemEvent.Value,
TaskOptions = options
};
}
if (info.Type.Equals(typeof(StartupTrigger).Name, StringComparison.OrdinalIgnoreCase))
{
return new StartupTrigger();
}
throw new ArgumentException("Unrecognized trigger type: " + info.Type);
}
/// <summary>
/// Disposes each trigger
/// </summary>
private void DisposeTriggers()
{
foreach (var trigger in Triggers)
foreach (var triggerInfo in InternalTriggers)
{
var trigger = triggerInfo.Item2;
trigger.Triggered -= trigger_Triggered;
trigger.Stop();
}

View File

@@ -0,0 +1,67 @@
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Tasks;
using System;
using System.Threading.Tasks;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.Common.ScheduledTasks
{
/// <summary>
/// Class StartupTaskTrigger
/// </summary>
public class StartupTrigger : ITaskTrigger
{
public int DelayMs { get; set; }
/// <summary>
/// Gets the execution properties of this task.
/// </summary>
/// <value>
/// The execution properties of this task.
/// </value>
public TaskExecutionOptions TaskOptions { get; set; }
public StartupTrigger()
{
DelayMs = 3000;
}
/// <summary>
/// Stars waiting for the trigger action
/// </summary>
/// <param name="lastResult">The last result.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
public async void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
{
if (isApplicationStartup)
{
await Task.Delay(DelayMs).ConfigureAwait(false);
OnTriggered();
}
}
/// <summary>
/// Stops waiting for the trigger action
/// </summary>
public void Stop()
{
}
/// <summary>
/// Occurs when [triggered].
/// </summary>
public event EventHandler<GenericEventArgs<TaskExecutionOptions>> Triggered;
/// <summary>
/// Called when [triggered].
/// </summary>
private void OnTriggered()
{
if (Triggered != null)
{
Triggered(this, new GenericEventArgs<TaskExecutionOptions>(TaskOptions));
}
}
}
}

View File

@@ -0,0 +1,84 @@
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Tasks;
using Microsoft.Win32;
using System;
using System.Threading.Tasks;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.Common.ScheduledTasks
{
/// <summary>
/// Class SystemEventTrigger
/// </summary>
public class SystemEventTrigger : ITaskTrigger
{
/// <summary>
/// Gets or sets the system event.
/// </summary>
/// <value>The system event.</value>
public SystemEvent SystemEvent { get; set; }
/// <summary>
/// Gets the execution properties of this task.
/// </summary>
/// <value>
/// The execution properties of this task.
/// </value>
public TaskExecutionOptions TaskOptions { get; set; }
/// <summary>
/// Stars waiting for the trigger action
/// </summary>
/// <param name="lastResult">The last result.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
{
switch (SystemEvent)
{
case SystemEvent.WakeFromSleep:
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
break;
}
}
/// <summary>
/// Stops waiting for the trigger action
/// </summary>
public void Stop()
{
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
}
/// <summary>
/// Handles the PowerModeChanged event of the SystemEvents control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="PowerModeChangedEventArgs" /> instance containing the event data.</param>
async void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
if (e.Mode == PowerModes.Resume && SystemEvent == SystemEvent.WakeFromSleep)
{
// This value is a bit arbitrary, but add a delay to help ensure network connections have been restored before running the task
await Task.Delay(10000).ConfigureAwait(false);
OnTriggered();
}
}
/// <summary>
/// Occurs when [triggered].
/// </summary>
public event EventHandler<GenericEventArgs<TaskExecutionOptions>> Triggered;
/// <summary>
/// Called when [triggered].
/// </summary>
private void OnTriggered()
{
if (Triggered != null)
{
Triggered(this, new GenericEventArgs<TaskExecutionOptions>(TaskOptions));
}
}
}
}

View File

@@ -8,6 +8,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Model.Tasks;
namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
{
@@ -40,13 +41,12 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// Creates the triggers that define when the task will run
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<ITaskTrigger> GetDefaultTriggers()
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
// Until we can vary these default triggers per server and MBT, we need something that makes sense for both
return new ITaskTrigger[] {
return new[] {
// Every so often
new IntervalTrigger { Interval = TimeSpan.FromHours(24)}
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
};
}
@@ -95,7 +95,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// <param name="progress">The progress.</param>
private void DeleteCacheFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress<double> progress)
{
var filesToDelete = _fileSystem.GetFiles(directory, true)
var filesToDelete = _fileSystem.GetFiles(directory, true)
.Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
.ToList();
@@ -168,6 +168,11 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
get { return "Cache file cleanup"; }
}
public string Key
{
get { return "DeleteCacheFiles"; }
}
/// <summary>
/// Gets the description.
/// </summary>
@@ -202,5 +207,10 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
{
get { return true; }
}
public bool IsLogged
{
get { return true; }
}
}
}

View File

@@ -6,6 +6,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Model.Tasks;
namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
{
@@ -36,13 +37,12 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// Creates the triggers that define when the task will run
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<ITaskTrigger> GetDefaultTriggers()
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
// Until we can vary these default triggers per server and MBT, we need something that makes sense for both
return new ITaskTrigger[] {
return new[] {
// Every so often
new IntervalTrigger { Interval = TimeSpan.FromHours(24)}
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
};
}
@@ -82,6 +82,11 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
return Task.FromResult(true);
}
public string Key
{
get { return "CleanLogFiles"; }
}
/// <summary>
/// Gets the name of the task
/// </summary>
@@ -125,5 +130,10 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
{
get { return true; }
}
public bool IsLogged
{
get { return true; }
}
}
}

View File

@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Tasks;
namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
{
@@ -39,9 +40,9 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// Gets the default triggers.
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<ITaskTrigger> GetDefaultTriggers()
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
var trigger = new DailyTrigger { TimeOfDay = TimeSpan.FromHours(0) }; //12am
var trigger = new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerDaily, TimeOfDayTicks = TimeSpan.FromHours(0).Ticks }; //12am
return new[] { trigger };
}
@@ -74,6 +75,8 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
get { return "Start new log file"; }
}
public string Key { get; }
/// <summary>
/// Gets the description.
/// </summary>
@@ -101,5 +104,10 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
{
get { return true; }
}
public bool IsLogged
{
get { return true; }
}
}
}

View File

@@ -0,0 +1,116 @@
using System;
using System.Threading;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Tasks;
namespace MediaBrowser.Common.ScheduledTasks
{
/// <summary>
/// Represents a task trigger that fires on a weekly basis
/// </summary>
public class WeeklyTrigger : ITaskTrigger
{
/// <summary>
/// Get the time of day to trigger the task to run
/// </summary>
/// <value>The time of day.</value>
public TimeSpan TimeOfDay { get; set; }
/// <summary>
/// Gets or sets the day of week.
/// </summary>
/// <value>The day of week.</value>
public DayOfWeek DayOfWeek { get; set; }
/// <summary>
/// Gets the execution properties of this task.
/// </summary>
/// <value>
/// The execution properties of this task.
/// </value>
public TaskExecutionOptions TaskOptions { get; set; }
/// <summary>
/// Gets or sets the timer.
/// </summary>
/// <value>The timer.</value>
private Timer Timer { get; set; }
/// <summary>
/// Stars waiting for the trigger action
/// </summary>
/// <param name="lastResult">The last result.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
{
DisposeTimer();
var triggerDate = GetNextTriggerDateTime();
Timer = new Timer(state => OnTriggered(), null, triggerDate - DateTime.Now, TimeSpan.FromMilliseconds(-1));
}
/// <summary>
/// Gets the next trigger date time.
/// </summary>
/// <returns>DateTime.</returns>
private DateTime GetNextTriggerDateTime()
{
var now = DateTime.Now;
// If it's on the same day
if (now.DayOfWeek == DayOfWeek)
{
// It's either later today, or a week from now
return now.TimeOfDay < TimeOfDay ? now.Date.Add(TimeOfDay) : now.Date.AddDays(7).Add(TimeOfDay);
}
var triggerDate = now.Date;
// Walk the date forward until we get to the trigger day
while (triggerDate.DayOfWeek != DayOfWeek)
{
triggerDate = triggerDate.AddDays(1);
}
// Return the trigger date plus the time offset
return triggerDate.Add(TimeOfDay);
}
/// <summary>
/// Stops waiting for the trigger action
/// </summary>
public void Stop()
{
DisposeTimer();
}
/// <summary>
/// Disposes the timer.
/// </summary>
private void DisposeTimer()
{
if (Timer != null)
{
Timer.Dispose();
}
}
/// <summary>
/// Occurs when [triggered].
/// </summary>
public event EventHandler<GenericEventArgs<TaskExecutionOptions>> Triggered;
/// <summary>
/// Called when [triggered].
/// </summary>
private void OnTriggered()
{
if (Triggered != null)
{
Triggered(this, new GenericEventArgs<TaskExecutionOptions>(TaskOptions));
}
}
}
}