diff options
Diffstat (limited to 'Emby.Server.Implementations/ScheduledTasks')
15 files changed, 859 insertions, 500 deletions
diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index 08bb39578..f2cdfeb16 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -1,57 +1,75 @@ +#nullable disable + +#pragma warning disable CS1591 + using System; -using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Events; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Progress; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// <summary> - /// Class ScheduledTaskWorker + /// Class ScheduledTaskWorker. /// </summary> public class ScheduledTaskWorker : IScheduledTaskWorker { - public event EventHandler<GenericEventArgs<double>> TaskProgress; - - /// <summary> - /// Gets or sets the scheduled task. - /// </summary> - /// <value>The scheduled task.</value> - public IScheduledTask ScheduledTask { get; private set; } - /// <summary> - /// Gets or sets the json serializer. + /// The options for the json Serializer. /// </summary> - /// <value>The json serializer.</value> - private IJsonSerializer JsonSerializer { get; set; } + private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; /// <summary> /// Gets or sets the application paths. /// </summary> /// <value>The application paths.</value> - private IApplicationPaths ApplicationPaths { get; set; } + private readonly IApplicationPaths _applicationPaths; /// <summary> - /// Gets the logger. + /// Gets or sets the logger. /// </summary> /// <value>The logger.</value> - private ILogger Logger { get; set; } + private readonly ILogger _logger; /// <summary> - /// Gets the task manager. + /// Gets or sets the task manager. /// </summary> /// <value>The task manager.</value> - private ITaskManager TaskManager { get; set; } - private readonly IFileSystem _fileSystem; + private readonly ITaskManager _taskManager; + + /// <summary> + /// The _last execution result sync lock. + /// </summary> + private readonly object _lastExecutionResultSyncLock = new object(); + + private bool _readFromFile = false; + + /// <summary> + /// The _last execution result. + /// </summary> + private TaskResult _lastExecutionResult; + + private Task _currentTask; + + /// <summary> + /// The _triggers. + /// </summary> + private Tuple<TaskTriggerInfo, ITaskTrigger>[] _triggers; + + /// <summary> + /// The _id. + /// </summary> + private string _id; /// <summary> /// Initializes a new instance of the <see cref="ScheduledTaskWorker" /> class. @@ -59,7 +77,6 @@ namespace Emby.Server.Implementations.ScheduledTasks /// <param name="scheduledTask">The scheduled task.</param> /// <param name="applicationPaths">The application paths.</param> /// <param name="taskManager">The task manager.</param> - /// <param name="jsonSerializer">The json serializer.</param> /// <param name="logger">The logger.</param> /// <exception cref="ArgumentNullException"> /// scheduledTask @@ -70,50 +87,46 @@ namespace Emby.Server.Implementations.ScheduledTasks /// or /// jsonSerializer /// or - /// logger + /// logger. /// </exception> - public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, IJsonSerializer jsonSerializer, ILogger logger, IFileSystem fileSystem) + public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, ILogger logger) { if (scheduledTask == null) { throw new ArgumentNullException(nameof(scheduledTask)); } + if (applicationPaths == null) { throw new ArgumentNullException(nameof(applicationPaths)); } + if (taskManager == null) { throw new ArgumentNullException(nameof(taskManager)); } - if (jsonSerializer == null) - { - throw new ArgumentNullException(nameof(jsonSerializer)); - } + if (logger == null) { throw new ArgumentNullException(nameof(logger)); } ScheduledTask = scheduledTask; - ApplicationPaths = applicationPaths; - TaskManager = taskManager; - JsonSerializer = jsonSerializer; - Logger = logger; - _fileSystem = fileSystem; + _applicationPaths = applicationPaths; + _taskManager = taskManager; + _logger = logger; InitTriggerEvents(); } - private bool _readFromFile = false; - /// <summary> - /// The _last execution result - /// </summary> - private TaskResult _lastExecutionResult; + public event EventHandler<GenericEventArgs<double>> TaskProgress; + /// <summary> - /// The _last execution result sync lock + /// Gets the scheduled task. /// </summary> - private readonly object _lastExecutionResultSyncLock = new object(); + /// <value>The scheduled task.</value> + public IScheduledTask ScheduledTask { get; private set; } + /// <summary> /// Gets the last execution result. /// </summary> @@ -130,21 +143,31 @@ namespace Emby.Server.Implementations.ScheduledTasks { if (File.Exists(path)) { - try + var bytes = File.ReadAllBytes(path); + if (bytes.Length > 0) { - _lastExecutionResult = JsonSerializer.DeserializeFromFile<TaskResult>(path); + try + { + _lastExecutionResult = JsonSerializer.Deserialize<TaskResult>(bytes, _jsonOptions); + } + catch (JsonException ex) + { + _logger.LogError(ex, "Error deserializing {File}", path); + } } - catch (Exception ex) + else { - Logger.LogError(ex, "Error deserializing {File}", path); + _logger.LogDebug("Scheduled Task history file {Path} is empty. Skipping deserialization.", path); } } + _readFromFile = true; } } return _lastExecutionResult; } + private set { _lastExecutionResult = value; @@ -154,7 +177,9 @@ namespace Emby.Server.Implementations.ScheduledTasks lock (_lastExecutionResultSyncLock) { - JsonSerializer.SerializeToFile(value, path); + using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None); + using Utf8JsonWriter jsonStream = new Utf8JsonWriter(createStream); + JsonSerializer.Serialize(jsonStream, value, _jsonOptions); } } } @@ -178,7 +203,7 @@ namespace Emby.Server.Implementations.ScheduledTasks public string Category => ScheduledTask.Category; /// <summary> - /// Gets the current cancellation token + /// Gets or sets the current cancellation token. /// </summary> /// <value>The current cancellation token source.</value> private CancellationTokenSource CurrentCancellationTokenSource { get; set; } @@ -215,11 +240,7 @@ namespace Emby.Server.Implementations.ScheduledTasks public double? CurrentProgress { get; private set; } /// <summary> - /// The _triggers - /// </summary> - private Tuple<TaskTriggerInfo, ITaskTrigger>[] _triggers; - /// <summary> - /// Gets the triggers that define when the task will run + /// Gets or sets the triggers that define when the task will run. /// </summary> /// <value>The triggers.</value> private Tuple<TaskTriggerInfo, ITaskTrigger>[] InternalTriggers @@ -245,10 +266,10 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Gets the triggers that define when the task will run + /// Gets or sets the triggers that define when the task will run. /// </summary> /// <value>The triggers.</value> - /// <exception cref="ArgumentNullException">value</exception> + /// <exception cref="ArgumentNullException"><c>value</c> is <c>null</c>.</exception> public TaskTriggerInfo[] Triggers { get @@ -256,6 +277,7 @@ namespace Emby.Server.Implementations.ScheduledTasks var triggers = InternalTriggers; return triggers.Select(i => i.Item1).ToArray(); } + set { if (value == null) @@ -273,11 +295,6 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// The _id - /// </summary> - private string _id; - - /// <summary> /// Gets the unique id. /// </summary> /// <value>The unique id.</value> @@ -285,12 +302,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { get { - if (_id == null) - { - _id = ScheduledTask.GetType().FullName.GetMD5().ToString("N"); - } - - return _id; + return _id ??= ScheduledTask.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture); } } @@ -317,9 +329,9 @@ namespace Emby.Server.Implementations.ScheduledTasks trigger.Stop(); - trigger.Triggered -= trigger_Triggered; - trigger.Triggered += trigger_Triggered; - trigger.Start(LastExecutionResult, Logger, Name, isApplicationStartup); + trigger.Triggered -= OnTriggerTriggered; + trigger.Triggered += OnTriggerTriggered; + trigger.Start(LastExecutionResult, _logger, Name, isApplicationStartup); } } @@ -328,36 +340,32 @@ namespace Emby.Server.Implementations.ScheduledTasks /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> - async void trigger_Triggered(object sender, EventArgs e) + private async void OnTriggerTriggered(object sender, EventArgs e) { var trigger = (ITaskTrigger)sender; - var configurableTask = ScheduledTask as IConfigurableScheduledTask; - - if (configurableTask != null && !configurableTask.IsEnabled) + if (ScheduledTask is IConfigurableScheduledTask configurableTask && !configurableTask.IsEnabled) { return; } - Logger.LogInformation("{0} fired for task: {1}", trigger.GetType().Name, Name); + _logger.LogInformation("{0} fired for task: {1}", trigger.GetType().Name, Name); trigger.Stop(); - TaskManager.QueueScheduledTask(ScheduledTask, trigger.TaskOptions); + _taskManager.QueueScheduledTask(ScheduledTask, trigger.TaskOptions); await Task.Delay(1000).ConfigureAwait(false); - trigger.Start(LastExecutionResult, Logger, Name, false); + trigger.Start(LastExecutionResult, _logger, Name, false); } - private Task _currentTask; - /// <summary> - /// Executes the task + /// Executes the task. /// </summary> /// <param name="options">Task options.</param> /// <returns>Task.</returns> - /// <exception cref="InvalidOperationException">Cannot execute a Task that is already running</exception> + /// <exception cref="InvalidOperationException">Cannot execute a Task that is already running.</exception> public async Task Execute(TaskOptions options) { var task = Task.Run(async () => await ExecuteInternal(options).ConfigureAwait(false)); @@ -387,11 +395,11 @@ namespace Emby.Server.Implementations.ScheduledTasks CurrentCancellationTokenSource = new CancellationTokenSource(); - Logger.LogInformation("Executing {0}", Name); + _logger.LogInformation("Executing {0}", Name); - ((TaskManager)TaskManager).OnTaskExecuting(this); + ((TaskManager)_taskManager).OnTaskExecuting(this); - progress.ProgressChanged += progress_ProgressChanged; + progress.ProgressChanged += OnProgressChanged; TaskCompletionStatus status; CurrentExecutionStartTime = DateTime.UtcNow; @@ -415,7 +423,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } catch (Exception ex) { - Logger.LogError(ex, "Error"); + _logger.LogError(ex, "Error"); failureException = ex; @@ -425,7 +433,7 @@ namespace Emby.Server.Implementations.ScheduledTasks var startTime = CurrentExecutionStartTime; var endTime = DateTime.UtcNow; - progress.ProgressChanged -= progress_ProgressChanged; + progress.ProgressChanged -= OnProgressChanged; CurrentCancellationTokenSource.Dispose(); CurrentCancellationTokenSource = null; CurrentProgress = null; @@ -438,20 +446,17 @@ namespace Emby.Server.Implementations.ScheduledTasks /// </summary> /// <param name="sender">The sender.</param> /// <param name="e">The e.</param> - void progress_ProgressChanged(object sender, double e) + private void OnProgressChanged(object sender, double e) { e = Math.Min(e, 100); CurrentProgress = e; - TaskProgress?.Invoke(this, new GenericEventArgs<double> - { - Argument = e - }); + TaskProgress?.Invoke(this, new GenericEventArgs<double>(e)); } /// <summary> - /// Stops the task if it is currently executing + /// Stops the task if it is currently executing. /// </summary> /// <exception cref="InvalidOperationException">Cannot cancel a Task unless it is in the Running state.</exception> public void Cancel() @@ -471,7 +476,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { if (State == TaskState.Running) { - Logger.LogInformation("Attempting to cancel Scheduled Task {0}", Name); + _logger.LogInformation("Attempting to cancel Scheduled Task {0}", Name); CurrentCancellationTokenSource.Cancel(); } } @@ -482,7 +487,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// <returns>System.String.</returns> private string GetScheduledTasksConfigurationDirectory() { - return Path.Combine(ApplicationPaths.ConfigurationDirectoryPath, "ScheduledTasks"); + return Path.Combine(_applicationPaths.ConfigurationDirectoryPath, "ScheduledTasks"); } /// <summary> @@ -491,7 +496,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// <returns>System.String.</returns> private string GetScheduledTasksDataDirectory() { - return Path.Combine(ApplicationPaths.DataPath, "ScheduledTasks"); + return Path.Combine(_applicationPaths.DataPath, "ScheduledTasks"); } /// <summary> @@ -530,7 +535,8 @@ namespace Emby.Server.Implementations.ScheduledTasks TaskTriggerInfo[] list = null; if (File.Exists(path)) { - list = JsonSerializer.DeserializeFromFile<TaskTriggerInfo[]>(path); + var bytes = File.ReadAllBytes(path); + list = JsonSerializer.Deserialize<TaskTriggerInfo[]>(bytes, _jsonOptions); } // Return defaults if file doesn't exist. @@ -565,8 +571,9 @@ namespace Emby.Server.Implementations.ScheduledTasks var path = GetConfigurationFilePath(); Directory.CreateDirectory(Path.GetDirectoryName(path)); - - JsonSerializer.SerializeToFile(triggers, path); + using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None); + using Utf8JsonWriter jsonWriter = new Utf8JsonWriter(createStream); + JsonSerializer.Serialize(jsonWriter, triggers, _jsonOptions); } /// <summary> @@ -575,11 +582,12 @@ namespace Emby.Server.Implementations.ScheduledTasks /// <param name="startTime">The start time.</param> /// <param name="endTime">The end time.</param> /// <param name="status">The status.</param> + /// <param name="ex">The exception.</param> private void OnTaskCompleted(DateTime startTime, DateTime endTime, TaskCompletionStatus status, Exception ex) { var elapsedTime = endTime - startTime; - Logger.LogInformation("{0} {1} after {2} minute(s) and {3} seconds", Name, status, Math.Truncate(elapsedTime.TotalMinutes), elapsedTime.Seconds); + _logger.LogInformation("{0} {1} after {2} minute(s) and {3} seconds", Name, status, Math.Truncate(elapsedTime.TotalMinutes), elapsedTime.Seconds); var result = new TaskResult { @@ -600,7 +608,7 @@ namespace Emby.Server.Implementations.ScheduledTasks LastExecutionResult = result; - ((TaskManager)TaskManager).OnTaskCompleted(this, result); + ((TaskManager)_taskManager).OnTaskCompleted(this, result); } /// <summary> @@ -609,6 +617,7 @@ namespace Emby.Server.Implementations.ScheduledTasks public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// <summary> @@ -629,34 +638,35 @@ namespace Emby.Server.Implementations.ScheduledTasks { try { - Logger.LogInformation(Name + ": Cancelling"); + _logger.LogInformation(Name + ": Cancelling"); token.Cancel(); } catch (Exception ex) { - Logger.LogError(ex, "Error calling CancellationToken.Cancel();"); + _logger.LogError(ex, "Error calling CancellationToken.Cancel();"); } } + var task = _currentTask; if (task != null) { try { - Logger.LogInformation(Name + ": Waiting on Task"); - var exited = Task.WaitAll(new[] { task }, 2000); + _logger.LogInformation(Name + ": Waiting on Task"); + var exited = task.Wait(2000); if (exited) { - Logger.LogInformation(Name + ": Task exited"); + _logger.LogInformation(Name + ": Task exited"); } else { - Logger.LogInformation(Name + ": Timed out waiting for task to stop"); + _logger.LogInformation(Name + ": Timed out waiting for task to stop"); } } catch (Exception ex) { - Logger.LogError(ex, "Error calling Task.WaitAll();"); + _logger.LogError(ex, "Error calling Task.WaitAll();"); } } @@ -664,14 +674,15 @@ namespace Emby.Server.Implementations.ScheduledTasks { try { - Logger.LogDebug(Name + ": Disposing CancellationToken"); + _logger.LogDebug(Name + ": Disposing CancellationToken"); token.Dispose(); } catch (Exception ex) { - Logger.LogError(ex, "Error calling CancellationToken.Dispose();"); + _logger.LogError(ex, "Error calling CancellationToken.Dispose();"); } } + if (wassRunning) { OnTaskCompleted(startTime, DateTime.UtcNow, TaskCompletionStatus.Aborted, null); @@ -680,12 +691,11 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Converts a TaskTriggerInfo into a concrete BaseTaskTrigger + /// Converts a TaskTriggerInfo into a concrete BaseTaskTrigger. /// </summary> /// <param name="info">The info.</param> /// <returns>BaseTaskTrigger.</returns> - /// <exception cref="ArgumentNullException"></exception> - /// <exception cref="ArgumentException">Invalid trigger type: + info.Type</exception> + /// <exception cref="ArgumentException">Invalid trigger type: + info.Type.</exception> private ITaskTrigger GetTrigger(TaskTriggerInfo info) { var options = new TaskOptions @@ -693,21 +703,17 @@ namespace Emby.Server.Implementations.ScheduledTasks MaxRuntimeTicks = info.MaxRuntimeTicks }; - if (info.Type.Equals(typeof(DailyTrigger).Name, StringComparison.OrdinalIgnoreCase)) + if (info.Type.Equals(nameof(DailyTrigger), StringComparison.OrdinalIgnoreCase)) { if (!info.TimeOfDayTicks.HasValue) { throw new ArgumentException("Info did not contain a TimeOfDayTicks.", nameof(info)); } - return new DailyTrigger - { - TimeOfDay = TimeSpan.FromTicks(info.TimeOfDayTicks.Value), - TaskOptions = options - }; + return new DailyTrigger(TimeSpan.FromTicks(info.TimeOfDayTicks.Value), options); } - if (info.Type.Equals(typeof(WeeklyTrigger).Name, StringComparison.OrdinalIgnoreCase)) + if (info.Type.Equals(nameof(WeeklyTrigger), StringComparison.OrdinalIgnoreCase)) { if (!info.TimeOfDayTicks.HasValue) { @@ -719,45 +725,36 @@ namespace Emby.Server.Implementations.ScheduledTasks throw new ArgumentException("Info did not contain a DayOfWeek.", nameof(info)); } - return new WeeklyTrigger - { - TimeOfDay = TimeSpan.FromTicks(info.TimeOfDayTicks.Value), - DayOfWeek = info.DayOfWeek.Value, - TaskOptions = options - }; + return new WeeklyTrigger(TimeSpan.FromTicks(info.TimeOfDayTicks.Value), info.DayOfWeek.Value, options); } - if (info.Type.Equals(typeof(IntervalTrigger).Name, StringComparison.OrdinalIgnoreCase)) + if (info.Type.Equals(nameof(IntervalTrigger), StringComparison.OrdinalIgnoreCase)) { if (!info.IntervalTicks.HasValue) { throw new ArgumentException("Info did not contain a IntervalTicks.", nameof(info)); } - return new IntervalTrigger - { - Interval = TimeSpan.FromTicks(info.IntervalTicks.Value), - TaskOptions = options - }; + return new IntervalTrigger(TimeSpan.FromTicks(info.IntervalTicks.Value), options); } - if (info.Type.Equals(typeof(StartupTrigger).Name, StringComparison.OrdinalIgnoreCase)) + if (info.Type.Equals(nameof(StartupTrigger), StringComparison.OrdinalIgnoreCase)) { - return new StartupTrigger(); + return new StartupTrigger(options); } throw new ArgumentException("Unrecognized trigger type: " + info.Type); } /// <summary> - /// Disposes each trigger + /// Disposes each trigger. /// </summary> private void DisposeTriggers() { foreach (var triggerInfo in InternalTriggers) { var trigger = triggerInfo.Item2; - trigger.Triggered -= trigger_Triggered; + trigger.Triggered -= OnTriggerTriggered; trigger.Stop(); } } diff --git a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs index 595c3037d..0431858fc 100644 --- a/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs +++ b/Emby.Server.Implementations/ScheduledTasks/TaskManager.cs @@ -1,84 +1,65 @@ +#nullable disable + +#pragma warning disable CS1591 + using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Jellyfin.Data.Events; using MediaBrowser.Common.Configuration; -using MediaBrowser.Model.Events; -using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// <summary> - /// Class TaskManager + /// Class TaskManager. /// </summary> public class TaskManager : ITaskManager { - public event EventHandler<GenericEventArgs<IScheduledTaskWorker>> TaskExecuting; - public event EventHandler<TaskCompletionEventArgs> TaskCompleted; - - /// <summary> - /// Gets the list of Scheduled Tasks - /// </summary> - /// <value>The scheduled tasks.</value> - public IScheduledTaskWorker[] ScheduledTasks { get; private set; } - /// <summary> - /// The _task queue + /// The _task queue. /// </summary> private readonly ConcurrentQueue<Tuple<Type, TaskOptions>> _taskQueue = new ConcurrentQueue<Tuple<Type, TaskOptions>>(); - /// <summary> - /// Gets or sets the json serializer. - /// </summary> - /// <value>The json serializer.</value> - private IJsonSerializer JsonSerializer { get; set; } - - /// <summary> - /// Gets or sets the application paths. - /// </summary> - /// <value>The application paths.</value> - private IApplicationPaths ApplicationPaths { get; set; } - - /// <summary> - /// Gets the logger. - /// </summary> - /// <value>The logger.</value> - private ILogger Logger { get; set; } - private readonly IFileSystem _fileSystem; + private readonly IApplicationPaths _applicationPaths; + private readonly ILogger<TaskManager> _logger; /// <summary> /// Initializes a new instance of the <see cref="TaskManager" /> class. /// </summary> /// <param name="applicationPaths">The application paths.</param> - /// <param name="jsonSerializer">The json serializer.</param> - /// <param name="loggerFactory">The logger factory.</param> - /// <exception cref="System.ArgumentException">kernel</exception> + /// <param name="logger">The logger.</param> public TaskManager( IApplicationPaths applicationPaths, - IJsonSerializer jsonSerializer, - ILoggerFactory loggerFactory, - IFileSystem fileSystem) + ILogger<TaskManager> logger) { - ApplicationPaths = applicationPaths; - JsonSerializer = jsonSerializer; - Logger = loggerFactory.CreateLogger(nameof(TaskManager)); - _fileSystem = fileSystem; + _applicationPaths = applicationPaths; + _logger = logger; - ScheduledTasks = new IScheduledTaskWorker[] { }; + ScheduledTasks = Array.Empty<IScheduledTaskWorker>(); } + public event EventHandler<GenericEventArgs<IScheduledTaskWorker>> TaskExecuting; + + public event EventHandler<TaskCompletionEventArgs> TaskCompleted; + + /// <summary> + /// Gets the list of Scheduled Tasks. + /// </summary> + /// <value>The scheduled tasks.</value> + public IScheduledTaskWorker[] ScheduledTasks { get; private set; } + /// <summary> /// Cancels if running and queue. /// </summary> - /// <typeparam name="T"></typeparam> + /// <typeparam name="T">The task type.</typeparam> /// <param name="options">Task options.</param> public void CancelIfRunningAndQueue<T>(TaskOptions options) - where T : IScheduledTask + where T : IScheduledTask { var task = ScheduledTasks.First(t => t.ScheduledTask.GetType() == typeof(T)); ((ScheduledTaskWorker)task).CancelIfRunning(); @@ -93,9 +74,9 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Cancels if running + /// Cancels if running. /// </summary> - /// <typeparam name="T"></typeparam> + /// <typeparam name="T">The task type.</typeparam> public void CancelIfRunning<T>() where T : IScheduledTask { @@ -106,8 +87,8 @@ namespace Emby.Server.Implementations.ScheduledTasks /// <summary> /// Queues the scheduled task. /// </summary> - /// <typeparam name="T"></typeparam> - /// <param name="options">Task options</param> + /// <typeparam name="T">The task type.</typeparam> + /// <param name="options">Task options.</param> public void QueueScheduledTask<T>(TaskOptions options) where T : IScheduledTask { @@ -115,7 +96,7 @@ namespace Emby.Server.Implementations.ScheduledTasks if (scheduledTask == null) { - Logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", typeof(T).Name); + _logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", typeof(T).Name); } else { @@ -147,13 +128,13 @@ namespace Emby.Server.Implementations.ScheduledTasks if (scheduledTask == null) { - Logger.LogError("Unable to find scheduled task of type {0} in Execute.", typeof(T).Name); + _logger.LogError("Unable to find scheduled task of type {0} in Execute.", typeof(T).Name); } else { var type = scheduledTask.ScheduledTask.GetType(); - Logger.LogInformation("Queueing task {0}", type.Name); + _logger.LogInformation("Queuing task {0}", type.Name); lock (_taskQueue) { @@ -176,7 +157,7 @@ namespace Emby.Server.Implementations.ScheduledTasks if (scheduledTask == null) { - Logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", task.GetType().Name); + _logger.LogError("Unable to find scheduled task of type {0} in QueueScheduledTask.", task.GetType().Name); } else { @@ -193,7 +174,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { var type = task.ScheduledTask.GetType(); - Logger.LogInformation("Queueing task {0}", type.Name); + _logger.LogInformation("Queuing task {0}", type.Name); lock (_taskQueue) { @@ -213,7 +194,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// <param name="tasks">The tasks.</param> public void AddTasks(IEnumerable<IScheduledTask> tasks) { - var list = tasks.Select(t => new ScheduledTaskWorker(t, ApplicationPaths, this, JsonSerializer, Logger, _fileSystem)); + var list = tasks.Select(t => new ScheduledTaskWorker(t, _applicationPaths, this, _logger)); ScheduledTasks = ScheduledTasks.Concat(list).ToArray(); } @@ -224,6 +205,7 @@ namespace Emby.Server.Implementations.ScheduledTasks public void Dispose() { Dispose(true); + GC.SuppressFinalize(this); } /// <summary> @@ -254,10 +236,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// <param name="task">The task.</param> internal void OnTaskExecuting(IScheduledTaskWorker task) { - TaskExecuting?.Invoke(this, new GenericEventArgs<IScheduledTaskWorker> - { - Argument = task - }); + TaskExecuting?.Invoke(this, new GenericEventArgs<IScheduledTaskWorker>(task)); } /// <summary> @@ -267,11 +246,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// <param name="result">The result.</param> internal void OnTaskCompleted(IScheduledTaskWorker task, TaskResult result) { - TaskCompleted?.Invoke(task, new TaskCompletionEventArgs - { - Result = result, - Task = task - }); + TaskCompleted?.Invoke(task, new TaskCompletionEventArgs(task, result)); ExecuteQueuedTasks(); } @@ -281,7 +256,7 @@ namespace Emby.Server.Implementations.ScheduledTasks /// </summary> private void ExecuteQueuedTasks() { - Logger.LogInformation("ExecuteQueuedTasks"); + _logger.LogInformation("ExecuteQueuedTasks"); // Execute queued tasks lock (_taskQueue) diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs index 2f07ff15a..09ea6271d 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs @@ -12,23 +12,19 @@ using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Tasks; -using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// <summary> - /// Class ChapterImagesTask + /// Class ChapterImagesTask. /// </summary> public class ChapterImagesTask : IScheduledTask { /// <summary> - /// The _logger - /// </summary> - private readonly ILogger _logger; - /// <summary> - /// The _library manager + /// The _library manager. /// </summary> private readonly ILibraryManager _libraryManager; @@ -38,27 +34,50 @@ namespace Emby.Server.Implementations.ScheduledTasks private readonly IEncodingManager _encodingManager; private readonly IFileSystem _fileSystem; + private readonly ILocalizationManager _localization; /// <summary> /// Initializes a new instance of the <see cref="ChapterImagesTask" /> class. /// </summary> - public ChapterImagesTask(ILoggerFactory loggerFactory, ILibraryManager libraryManager, IItemRepository itemRepo, IApplicationPaths appPaths, IEncodingManager encodingManager, IFileSystem fileSystem) + /// <param name="libraryManager">The library manager.</param>. + /// <param name="itemRepo">The item repository.</param> + /// <param name="appPaths">The application paths.</param> + /// <param name="encodingManager">The encoding manager.</param> + /// <param name="fileSystem">The filesystem.</param> + /// <param name="localization">The localization manager.</param> + public ChapterImagesTask( + ILibraryManager libraryManager, + IItemRepository itemRepo, + IApplicationPaths appPaths, + IEncodingManager encodingManager, + IFileSystem fileSystem, + ILocalizationManager localization) { - _logger = loggerFactory.CreateLogger(GetType().Name); _libraryManager = libraryManager; _itemRepo = itemRepo; _appPaths = appPaths; _encodingManager = encodingManager; _fileSystem = fileSystem; + _localization = localization; } - /// <summary> - /// Creates the triggers that define when the task will run - /// </summary> + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskRefreshChapterImages"); + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskRefreshChapterImagesDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + + /// <inheritdoc /> + public string Key => "RefreshChapterImages"; + + /// <inheritdoc /> public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() { - return new[] { - + return new[] + { new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerDaily, @@ -69,7 +88,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Returns the task to be executed + /// Returns the task to be executed. /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress.</param> @@ -88,7 +107,6 @@ namespace Emby.Server.Implementations.ScheduledTasks SourceTypes = new SourceType[] { SourceType.Library }, HasChapterImages = false, IsVirtualItem = false - }) .OfType<Video>() .ToList(); @@ -104,7 +122,7 @@ namespace Emby.Server.Implementations.ScheduledTasks try { previouslyFailedImages = File.ReadAllText(failHistoryPath) - .Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries) + .Split('|', StringSplitOptions.RemoveEmptyEntries) .ToList(); } catch (IOException) @@ -117,7 +135,7 @@ namespace Emby.Server.Implementations.ScheduledTasks previouslyFailedImages = new List<string>(); } - var directoryService = new DirectoryService(_logger, _fileSystem); + var directoryService = new DirectoryService(_fileSystem); foreach (var video in videos) { @@ -138,10 +156,12 @@ namespace Emby.Server.Implementations.ScheduledTasks previouslyFailedImages.Add(key); var parentPath = Path.GetDirectoryName(failHistoryPath); + if (parentPath != null) + { + Directory.CreateDirectory(parentPath); + } - Directory.CreateDirectory(parentPath); - - string text = string.Join("|", previouslyFailedImages); + string text = string.Join('|', previouslyFailedImages); File.WriteAllText(failHistoryPath, text); } @@ -153,24 +173,10 @@ namespace Emby.Server.Implementations.ScheduledTasks } catch (ObjectDisposedException) { - //TODO Investigate and properly fix. + // TODO Investigate and properly fix. break; } } } - - public string Name => "Chapter image extraction"; - - public string Description => "Creates thumbnails for videos that have chapters."; - - public string Category => "Library"; - - public string Key => "RefreshChapterImages"; - - public bool IsHidden => false; - - public bool IsEnabled => true; - - public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs new file mode 100644 index 000000000..79886cb52 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanActivityLogTask.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks +{ + /// <summary> + /// Deletes old activity log entries. + /// </summary> + public class CleanActivityLogTask : IScheduledTask, IConfigurableScheduledTask + { + private readonly ILocalizationManager _localization; + private readonly IActivityManager _activityManager; + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="CleanActivityLogTask"/> class. + /// </summary> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> + /// <param name="activityManager">Instance of the <see cref="IActivityManager"/> interface.</param> + /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> + public CleanActivityLogTask( + ILocalizationManager localization, + IActivityManager activityManager, + IServerConfigurationManager serverConfigurationManager) + { + _localization = localization; + _activityManager = activityManager; + _serverConfigurationManager = serverConfigurationManager; + } + + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskCleanActivityLog"); + + /// <inheritdoc /> + public string Key => "CleanActivityLog"; + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskCleanActivityLogDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// <inheritdoc /> + public bool IsHidden => false; + + /// <inheritdoc /> + public bool IsEnabled => true; + + /// <inheritdoc /> + public bool IsLogged => true; + + /// <inheritdoc /> + public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + { + var retentionDays = _serverConfigurationManager.Configuration.ActivityLogRetentionDays; + if (!retentionDays.HasValue || retentionDays < 0) + { + throw new Exception($"Activity Log Retention days must be at least 0. Currently: {retentionDays}"); + } + + var startDate = DateTime.UtcNow.AddDays(-retentionDays.Value); + return _activityManager.CleanAsync(startDate); + } + + /// <inheritdoc /> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + return Enumerable.Empty<TaskTriggerInfo>(); + } + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index 6ec83b5c0..a575b260c 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; @@ -12,7 +13,7 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks.Tasks { /// <summary> - /// Deletes old cache files + /// Deletes old cache files. /// </summary> public class DeleteCacheFileTask : IScheduledTask, IConfigurableScheduledTask { @@ -20,37 +21,66 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks /// Gets or sets the application paths. /// </summary> /// <value>The application paths.</value> - private IApplicationPaths ApplicationPaths { get; set; } - - private readonly ILogger _logger; - + private readonly IApplicationPaths _applicationPaths; + private readonly ILogger<DeleteCacheFileTask> _logger; private readonly IFileSystem _fileSystem; + private readonly ILocalizationManager _localization; /// <summary> /// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class. /// </summary> - public DeleteCacheFileTask(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem) + /// <param name="appPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param> + /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> + public DeleteCacheFileTask( + IApplicationPaths appPaths, + ILogger<DeleteCacheFileTask> logger, + IFileSystem fileSystem, + ILocalizationManager localization) { - ApplicationPaths = appPaths; + _applicationPaths = appPaths; _logger = logger; _fileSystem = fileSystem; + _localization = localization; } + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskCleanCache"); + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskCleanCacheDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// <inheritdoc /> + public string Key => "DeleteCacheFiles"; + + /// <inheritdoc /> + public bool IsHidden => false; + + /// <inheritdoc /> + public bool IsEnabled => true; + + /// <inheritdoc /> + public bool IsLogged => true; + /// <summary> - /// Creates the triggers that define when the task will run + /// Creates the triggers that define when the task will run. /// </summary> /// <returns>IEnumerable{BaseTaskTrigger}.</returns> public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() { - return new[] { - + return new[] + { // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks } }; } /// <summary> - /// Returns the task to be executed + /// Returns the task to be executed. /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress.</param> @@ -61,7 +91,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks try { - DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.CachePath, minDateModified, progress); + DeleteCacheFilesFromDirectory(cancellationToken, _applicationPaths.CachePath, minDateModified, progress); } catch (DirectoryNotFoundException) { @@ -74,7 +104,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks try { - DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.TempDirectory, minDateModified, progress); + DeleteCacheFilesFromDirectory(cancellationToken, _applicationPaths.TempDirectory, minDateModified, progress); } catch (DirectoryNotFoundException) { @@ -84,9 +114,8 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks return Task.CompletedTask; } - /// <summary> - /// Deletes the cache files from directory with a last write time less than a given date + /// Deletes the cache files from directory with a last write time less than a given date. /// </summary> /// <param name="cancellationToken">The task cancellation token.</param> /// <param name="directory">The directory.</param> @@ -157,19 +186,5 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks _logger.LogError(ex, "Error deleting file {path}", path); } } - - public string Name => "Cache file cleanup"; - - public string Description => "Deletes cache files no longer needed by the system"; - - public string Category => "Maintenance"; - - public string Key => "DeleteCacheFiles"; - - public bool IsHidden => false; - - public bool IsEnabled => true; - - public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs index e468c301a..fedb5deb0 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs @@ -1,52 +1,76 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.Tasks; namespace Emby.Server.Implementations.ScheduledTasks.Tasks { /// <summary> - /// Deletes old log files + /// Deletes old log files. /// </summary> public class DeleteLogFileTask : IScheduledTask, IConfigurableScheduledTask { - /// <summary> - /// Gets or sets the configuration manager. - /// </summary> - /// <value>The configuration manager.</value> - private IConfigurationManager ConfigurationManager { get; set; } - + private readonly IConfigurationManager _configurationManager; private readonly IFileSystem _fileSystem; + private readonly ILocalizationManager _localization; /// <summary> /// Initializes a new instance of the <see cref="DeleteLogFileTask" /> class. /// </summary> /// <param name="configurationManager">The configuration manager.</param> - public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem) + /// <param name="fileSystem">The file system.</param> + /// <param name="localization">The localization manager.</param> + public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem, ILocalizationManager localization) { - ConfigurationManager = configurationManager; + _configurationManager = configurationManager; _fileSystem = fileSystem; + _localization = localization; } + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskCleanLogs"); + + /// <inheritdoc /> + public string Description => string.Format( + CultureInfo.InvariantCulture, + _localization.GetLocalizedString("TaskCleanLogsDescription"), + _configurationManager.CommonConfiguration.LogFileRetentionDays); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// <inheritdoc /> + public string Key => "CleanLogFiles"; + + /// <inheritdoc /> + public bool IsHidden => false; + + /// <inheritdoc /> + public bool IsEnabled => true; + + /// <inheritdoc /> + public bool IsLogged => true; + /// <summary> - /// Creates the triggers that define when the task will run + /// Creates the triggers that define when the task will run. /// </summary> /// <returns>IEnumerable{BaseTaskTrigger}.</returns> public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() { - return new[] { - - // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} + return new[] + { + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks } }; } /// <summary> - /// Returns the task to be executed + /// Returns the task to be executed. /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress.</param> @@ -54,12 +78,13 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) { // Delete log files more than n days old - var minDateModified = DateTime.UtcNow.AddDays(-ConfigurationManager.CommonConfiguration.LogFileRetentionDays); + var minDateModified = DateTime.UtcNow.AddDays(-_configurationManager.CommonConfiguration.LogFileRetentionDays); - // Only delete the .txt log files, the *.log files created by serilog get managed by itself - var filesToDelete = _fileSystem.GetFiles(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath, new[] { ".txt" }, true, true) - .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified) - .ToList(); + // Only delete files that serilog doesn't manage (anything that doesn't start with 'log_' + var filesToDelete = _fileSystem.GetFiles(_configurationManager.CommonApplicationPaths.LogDirectoryPath, true) + .Where(f => !f.Name.StartsWith("log_", StringComparison.Ordinal) + && _fileSystem.GetLastWriteTimeUtc(f) < minDateModified) + .ToList(); var index = 0; @@ -80,19 +105,5 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks return Task.CompletedTask; } - - public string Name => "Log file cleanup"; - - public string Description => string.Format("Deletes log files that are more than {0} days old.", ConfigurationManager.CommonConfiguration.LogFileRetentionDays); - - public string Category => "Maintenance"; - - public string Key => "CleanLogFiles"; - - public bool IsHidden => false; - - public bool IsEnabled => true; - - public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs new file mode 100644 index 000000000..b13fc7fc6 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs @@ -0,0 +1,170 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks +{ + /// <summary> + /// Deletes all transcoding temp files. + /// </summary> + public class DeleteTranscodeFileTask : IScheduledTask, IConfigurableScheduledTask + { + private readonly ILogger<DeleteTranscodeFileTask> _logger; + private readonly IConfigurationManager _configurationManager; + private readonly IFileSystem _fileSystem; + private readonly ILocalizationManager _localization; + + /// <summary> + /// Initializes a new instance of the <see cref="DeleteTranscodeFileTask"/> class. + /// </summary> + /// <param name="logger">Instance of the <see cref="ILogger{DeleteTranscodeFileTask}"/> interface.</param> + /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> + /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param> + /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param> + public DeleteTranscodeFileTask( + ILogger<DeleteTranscodeFileTask> logger, + IFileSystem fileSystem, + IConfigurationManager configurationManager, + ILocalizationManager localization) + { + _logger = logger; + _fileSystem = fileSystem; + _configurationManager = configurationManager; + _localization = localization; + } + + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskCleanTranscode"); + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskCleanTranscodeDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// <inheritdoc /> + public string Key => "DeleteTranscodeFiles"; + + /// <inheritdoc /> + public bool IsHidden => false; + + /// <inheritdoc /> + public bool IsEnabled => true; + + /// <inheritdoc /> + public bool IsLogged => true; + + /// <summary> + /// Creates the triggers that define when the task will run. + /// </summary> + /// <returns>IEnumerable{BaseTaskTrigger}.</returns> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + return new[] + { + new TaskTriggerInfo + { + Type = TaskTriggerInfo.TriggerInterval, + IntervalTicks = TimeSpan.FromHours(24).Ticks + } + }; + } + + /// <summary> + /// Returns the task to be executed. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + { + var minDateModified = DateTime.UtcNow.AddDays(-1); + progress.Report(50); + + DeleteTempFilesFromDirectory(cancellationToken, _configurationManager.GetTranscodePath(), minDateModified, progress); + + return Task.CompletedTask; + } + + /// <summary> + /// Deletes the transcoded temp files from directory with a last write time less than a given date. + /// </summary> + /// <param name="cancellationToken">The task cancellation token.</param> + /// <param name="directory">The directory.</param> + /// <param name="minDateModified">The min date modified.</param> + /// <param name="progress">The progress.</param> + private void DeleteTempFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress<double> progress) + { + var filesToDelete = _fileSystem.GetFiles(directory, true) + .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified) + .ToList(); + + var index = 0; + + foreach (var file in filesToDelete) + { + double percent = index; + percent /= filesToDelete.Count; + + progress.Report(100 * percent); + + cancellationToken.ThrowIfCancellationRequested(); + + DeleteFile(file.FullName); + + index++; + } + + DeleteEmptyFolders(directory); + + progress.Report(100); + } + + private void DeleteEmptyFolders(string parent) + { + foreach (var directory in _fileSystem.GetDirectoryPaths(parent)) + { + DeleteEmptyFolders(directory); + if (!_fileSystem.GetFileSystemEntryPaths(directory).Any()) + { + try + { + Directory.Delete(directory, false); + } + catch (UnauthorizedAccessException ex) + { + _logger.LogError(ex, "Error deleting directory {path}", directory); + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting directory {path}", directory); + } + } + } + } + + private void DeleteFile(string path) + { + try + { + _fileSystem.DeleteFile(path); + } + catch (UnauthorizedAccessException ex) + { + _logger.LogError(ex, "Error deleting file {path}", path); + } + catch (IOException ex) + { + _logger.LogError(ex, "Error deleting file {path}", path); + } + } + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs new file mode 100644 index 000000000..35a4aeef6 --- /dev/null +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/OptimizeDatabaseTask.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Server.Implementations; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace Emby.Server.Implementations.ScheduledTasks.Tasks +{ + /// <summary> + /// Optimizes Jellyfin's database by issuing a VACUUM command. + /// </summary> + public class OptimizeDatabaseTask : IScheduledTask, IConfigurableScheduledTask + { + private readonly ILogger<OptimizeDatabaseTask> _logger; + private readonly ILocalizationManager _localization; + private readonly JellyfinDbProvider _provider; + + /// <summary> + /// Initializes a new instance of the <see cref="OptimizeDatabaseTask" /> class. + /// </summary> + /// <param name="logger">The logger.</param> + /// <param name="localization">The localization manager.</param> + /// <param name="provider">The jellyfin DB context provider.</param> + public OptimizeDatabaseTask( + ILogger<OptimizeDatabaseTask> logger, + ILocalizationManager localization, + JellyfinDbProvider provider) + { + _logger = logger; + _localization = localization; + _provider = provider; + } + + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskOptimizeDatabase"); + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskOptimizeDatabaseDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksMaintenanceCategory"); + + /// <inheritdoc /> + public string Key => "OptimizeDatabaseTask"; + + /// <inheritdoc /> + public bool IsHidden => false; + + /// <inheritdoc /> + public bool IsEnabled => true; + + /// <inheritdoc /> + public bool IsLogged => true; + + /// <summary> + /// Creates the triggers that define when the task will run. + /// </summary> + /// <returns>IEnumerable{BaseTaskTrigger}.</returns> + public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() + { + return new[] + { + // Every so often + new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks } + }; + } + + /// <summary> + /// Returns the task to be executed. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="progress">The progress.</param> + /// <returns>Task.</returns> + public Task Execute(CancellationToken cancellationToken, IProgress<double> progress) + { + _logger.LogInformation("Optimizing and vacuuming jellyfin.db..."); + + try + { + using var context = _provider.CreateContext(); + if (context.Database.IsSqlite()) + { + context.Database.ExecuteSqlRaw("PRAGMA optimize"); + context.Database.ExecuteSqlRaw("VACUUM"); + _logger.LogInformation("jellyfin.db optimized successfully!"); + } + else + { + _logger.LogInformation("This database doesn't support optimization"); + } + } + catch (Exception e) + { + _logger.LogError(e, "Error while optimizing jellyfin.db"); + } + + return Task.CompletedTask; + } + } +} diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs index d70799c47..53c692a46 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs @@ -1,44 +1,59 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Controller; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Tasks; namespace Emby.Server.Implementations.ScheduledTasks { /// <summary> - /// Class PeopleValidationTask + /// Class PeopleValidationTask. /// </summary> public class PeopleValidationTask : IScheduledTask { /// <summary> - /// The _library manager + /// The library manager. /// </summary> private readonly ILibraryManager _libraryManager; - - private readonly IServerApplicationHost _appHost; + private readonly ILocalizationManager _localization; /// <summary> /// Initializes a new instance of the <see cref="PeopleValidationTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> - /// <param name="appHost">The server application host</param> - public PeopleValidationTask(ILibraryManager libraryManager, IServerApplicationHost appHost) + /// <param name="localization">The localization manager.</param> + public PeopleValidationTask(ILibraryManager libraryManager, ILocalizationManager localization) { _libraryManager = libraryManager; - _appHost = appHost; + _localization = localization; } + public string Name => _localization.GetLocalizedString("TaskRefreshPeople"); + + public string Description => _localization.GetLocalizedString("TaskRefreshPeopleDescription"); + + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + + public string Key => "RefreshPeople"; + + public bool IsHidden => false; + + public bool IsEnabled => true; + + public bool IsLogged => true; + /// <summary> - /// Creates the triggers that define when the task will run + /// Creates the triggers that define when the task will run. /// </summary> + /// <returns>An <see cref="IEnumerable{TaskTriggerInfo}"/> containing the default trigger infos for this task.</returns> public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() { return new[] { - // Every so often new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, @@ -48,7 +63,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Returns the task to be executed + /// Returns the task to be executed. /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress.</param> @@ -57,19 +72,5 @@ namespace Emby.Server.Implementations.ScheduledTasks { return _libraryManager.ValidatePeople(cancellationToken, progress); } - - public string Name => "Refresh people"; - - public string Description => "Updates metadata for actors and directors in your media library."; - - public string Category => "Library"; - - public string Key => "RefreshPeople"; - - public bool IsHidden => false; - - public bool IsEnabled => true; - - public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs index c6431c311..11a5fb79f 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs @@ -1,66 +1,85 @@ -using MediaBrowser.Common; -using MediaBrowser.Common.Updates; -using MediaBrowser.Model.Net; +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Common.Progress; +using MediaBrowser.Common.Updates; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// <summary> - /// Plugin Update Task + /// Plugin Update Task. /// </summary> public class PluginUpdateTask : IScheduledTask, IConfigurableScheduledTask { /// <summary> - /// The _logger + /// The _logger. /// </summary> - private readonly ILogger _logger; + private readonly ILogger<PluginUpdateTask> _logger; private readonly IInstallationManager _installationManager; + private readonly ILocalizationManager _localization; - private readonly IApplicationHost _appHost; - - public PluginUpdateTask(ILogger logger, IInstallationManager installationManager, IApplicationHost appHost) + public PluginUpdateTask(ILogger<PluginUpdateTask> logger, IInstallationManager installationManager, ILocalizationManager localization) { _logger = logger; _installationManager = installationManager; - _appHost = appHost; + _localization = localization; } + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskUpdatePlugins"); + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskUpdatePluginsDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksApplicationCategory"); + + /// <inheritdoc /> + public string Key => "PluginUpdates"; + + /// <inheritdoc /> + public bool IsHidden => false; + + /// <inheritdoc /> + public bool IsEnabled => true; + + /// <inheritdoc /> + public bool IsLogged => true; + /// <summary> - /// Creates the triggers that define when the task will run + /// Creates the triggers that define when the task will run. /// </summary> /// <returns>IEnumerable{BaseTaskTrigger}.</returns> public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() { - return new[] { - - // At startup - new TaskTriggerInfo {Type = TaskTriggerInfo.TriggerStartup}, - - // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} - }; + // At startup + yield return new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerStartup }; + + // Every so often + yield return new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks }; } /// <summary> - /// Update installed plugins + /// Update installed plugins. /// </summary> /// <param name="cancellationToken">The cancellation token.</param> /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> + /// <returns><see cref="Task" />.</returns> public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress) { progress.Report(0); - var packagesToInstall = (await _installationManager.GetAvailablePluginUpdates(typeof(PluginUpdateTask).Assembly.GetName().Version, true, cancellationToken).ConfigureAwait(false)).ToList(); + var packageFetchTask = _installationManager.GetAvailablePluginUpdates(cancellationToken); + var packagesToInstall = (await packageFetchTask.ConfigureAwait(false)).ToList(); progress.Report(10); @@ -72,7 +91,7 @@ namespace Emby.Server.Implementations.ScheduledTasks try { - await _installationManager.InstallPackage(package, true, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false); + await _installationManager.InstallPackage(package, cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -82,38 +101,23 @@ namespace Emby.Server.Implementations.ScheduledTasks throw; } } - catch (HttpException ex) + catch (HttpRequestException ex) { - _logger.LogError(ex, "Error downloading {0}", package.name); + _logger.LogError(ex, "Error downloading {0}", package.Name); } catch (IOException ex) { - _logger.LogError(ex, "Error updating {0}", package.name); + _logger.LogError(ex, "Error updating {0}", package.Name); } // Update progress lock (progress) { - numComplete++; - progress.Report(90.0 * numComplete / packagesToInstall.Count + 10); + progress.Report((90.0 * ++numComplete / packagesToInstall.Count) + 10); } } progress.Report(100); } - - public string Name => "Check for plugin updates"; - - public string Description => "Downloads and installs updates for plugins that are configured to update automatically."; - - public string Category => "Application"; - - public string Key => "PluginUpdates"; - - public bool IsHidden => false; - - public bool IsEnabled => true; - - public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs index 1a3d85ad7..2184b3d03 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs @@ -1,45 +1,60 @@ +#pragma warning disable CS1591 + using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Emby.Server.Implementations.Library; -using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; +using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Tasks; namespace Emby.Server.Implementations.ScheduledTasks { /// <summary> - /// Class RefreshMediaLibraryTask + /// Class RefreshMediaLibraryTask. /// </summary> public class RefreshMediaLibraryTask : IScheduledTask { /// <summary> - /// The _library manager + /// The _library manager. /// </summary> private readonly ILibraryManager _libraryManager; - private readonly IServerConfigurationManager _config; + private readonly ILocalizationManager _localization; /// <summary> /// Initializes a new instance of the <see cref="RefreshMediaLibraryTask" /> class. /// </summary> /// <param name="libraryManager">The library manager.</param> - public RefreshMediaLibraryTask(ILibraryManager libraryManager, IServerConfigurationManager config) + /// <param name="localization">The localization manager.</param> + public RefreshMediaLibraryTask(ILibraryManager libraryManager, ILocalizationManager localization) { _libraryManager = libraryManager; - _config = config; + _localization = localization; } + /// <inheritdoc /> + public string Name => _localization.GetLocalizedString("TaskRefreshLibrary"); + + /// <inheritdoc /> + public string Description => _localization.GetLocalizedString("TaskRefreshLibraryDescription"); + + /// <inheritdoc /> + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + + /// <inheritdoc /> + public string Key => "RefreshLibrary"; + /// <summary> - /// Creates the triggers that define when the task will run + /// Creates the triggers that define when the task will run. /// </summary> /// <returns>IEnumerable{BaseTaskTrigger}.</returns> public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() { - return new[] { - - // Every so often - new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(12).Ticks} + yield return new TaskTriggerInfo + { + Type = TaskTriggerInfo.TriggerInterval, + IntervalTicks = TimeSpan.FromHours(12).Ticks }; } @@ -57,19 +72,5 @@ namespace Emby.Server.Implementations.ScheduledTasks return ((LibraryManager)_libraryManager).ValidateMediaLibraryInternal(progress, cancellationToken); } - - public string Name => "Scan media library"; - - public string Description => "Scans your media library and refreshes metatata based on configuration."; - - public string Category => "Library"; - - public string Key => "RefreshLibrary"; - - public bool IsHidden => false; - - public bool IsEnabled => true; - - public bool IsLogged => true; } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs index 98685cebe..29ab6a73d 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.Threading; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; @@ -7,31 +6,40 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// <summary> - /// Represents a task trigger that fires everyday + /// Represents a task trigger that fires everyday. /// </summary> - public class DailyTrigger : ITaskTrigger + public sealed class DailyTrigger : ITaskTrigger { + private readonly TimeSpan _timeOfDay; + private Timer? _timer; + /// <summary> - /// Get the time of day to trigger the task to run + /// Initializes a new instance of the <see cref="DailyTrigger"/> class. /// </summary> - /// <value>The time of day.</value> - public TimeSpan TimeOfDay { get; set; } + /// <param name="timeofDay">The time of day to trigger the task to run.</param> + /// <param name="taskOptions">The options of this task.</param> + public DailyTrigger(TimeSpan timeofDay, TaskOptions taskOptions) + { + _timeOfDay = timeofDay; + TaskOptions = taskOptions; + } /// <summary> - /// Gets or sets the options of this task. + /// Occurs when [triggered]. /// </summary> - public TaskOptions TaskOptions { get; set; } + public event EventHandler<EventArgs>? Triggered; /// <summary> - /// Gets or sets the timer. + /// Gets the options of this task. /// </summary> - /// <value>The timer.</value> - private Timer Timer { get; set; } + public TaskOptions TaskOptions { get; } /// <summary> - /// Stars waiting for the trigger action + /// Stars waiting for the trigger action. /// </summary> /// <param name="lastResult">The last result.</param> + /// <param name="logger">The logger.</param> + /// <param name="taskName">The name of the task.</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) { @@ -39,18 +47,18 @@ namespace Emby.Server.Implementations.ScheduledTasks var now = DateTime.Now; - var triggerDate = now.TimeOfDay > TimeOfDay ? now.Date.AddDays(1) : now.Date; - triggerDate = triggerDate.Add(TimeOfDay); + var triggerDate = now.TimeOfDay > _timeOfDay ? now.Date.AddDays(1) : now.Date; + triggerDate = triggerDate.Add(_timeOfDay); var dueTime = triggerDate - now; - logger.LogInformation("Daily trigger for {0} set to fire at {1}, which is {2} minutes from now.", taskName, triggerDate.ToString(), dueTime.TotalMinutes.ToString(CultureInfo.InvariantCulture)); + logger.LogInformation("Daily trigger for {Task} set to fire at {TriggerDate:yyyy-MM-dd HH:mm:ss.fff zzz}, which is {DueTime:c} from now.", taskName, triggerDate, dueTime); - Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); + _timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); } /// <summary> - /// Stops waiting for the trigger action + /// Stops waiting for the trigger action. /// </summary> public void Stop() { @@ -62,26 +70,15 @@ namespace Emby.Server.Implementations.ScheduledTasks /// </summary> private void DisposeTimer() { - if (Timer != null) - { - Timer.Dispose(); - } + _timer?.Dispose(); } /// <summary> - /// Occurs when [triggered]. - /// </summary> - public event EventHandler<EventArgs> Triggered; - - /// <summary> /// Called when [triggered]. /// </summary> private void OnTriggered() { - if (Triggered != null) - { - Triggered(this, EventArgs.Empty); - } + Triggered?.Invoke(this, EventArgs.Empty); } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs index 3a34da3af..30568e809 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs @@ -7,33 +7,41 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// <summary> - /// Represents a task trigger that runs repeatedly on an interval + /// Represents a task trigger that runs repeatedly on an interval. /// </summary> - public class IntervalTrigger : ITaskTrigger + public sealed class IntervalTrigger : ITaskTrigger { + private readonly TimeSpan _interval; + private DateTime _lastStartDate; + private Timer? _timer; + /// <summary> - /// Gets or sets the interval. + /// Initializes a new instance of the <see cref="IntervalTrigger"/> class. /// </summary> - /// <value>The interval.</value> - public TimeSpan Interval { get; set; } + /// <param name="interval">The interval.</param> + /// <param name="taskOptions">The options of this task.</param> + public IntervalTrigger(TimeSpan interval, TaskOptions taskOptions) + { + _interval = interval; + TaskOptions = taskOptions; + } /// <summary> - /// Gets or sets the options of this task. + /// Occurs when [triggered]. /// </summary> - public TaskOptions TaskOptions { get; set; } + public event EventHandler<EventArgs>? Triggered; /// <summary> - /// Gets or sets the timer. + /// Gets the options of this task. /// </summary> - /// <value>The timer.</value> - private Timer Timer { get; set; } - - private DateTime _lastStartDate; + public TaskOptions TaskOptions { get; } /// <summary> - /// Stars waiting for the trigger action + /// Stars waiting for the trigger action. /// </summary> /// <param name="lastResult">The last result.</param> + /// <param name="logger">The logger.</param> + /// <param name="taskName">The name of the task.</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) { @@ -48,7 +56,7 @@ namespace Emby.Server.Implementations.ScheduledTasks } else { - triggerDate = new[] { lastResult.EndTimeUtc, _lastStartDate }.Max().Add(Interval); + triggerDate = new[] { lastResult.EndTimeUtc, _lastStartDate }.Max().Add(_interval); } if (DateTime.UtcNow > triggerDate) @@ -64,11 +72,11 @@ namespace Emby.Server.Implementations.ScheduledTasks dueTime = maxDueTime; } - Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); + _timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); } /// <summary> - /// Stops waiting for the trigger action + /// Stops waiting for the trigger action. /// </summary> public void Stop() { @@ -80,18 +88,10 @@ namespace Emby.Server.Implementations.ScheduledTasks /// </summary> private void DisposeTimer() { - if (Timer != null) - { - Timer.Dispose(); - } + _timer?.Dispose(); } /// <summary> - /// Occurs when [triggered]. - /// </summary> - public event EventHandler<EventArgs> Triggered; - - /// <summary> /// Called when [triggered]. /// </summary> private void OnTriggered() diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs index 08ff4f55f..18b9a8b75 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs @@ -1,3 +1,5 @@ +#pragma warning disable CS1591 + using System; using System.Threading.Tasks; using MediaBrowser.Model.Tasks; @@ -6,26 +8,37 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// <summary> - /// Class StartupTaskTrigger + /// Class StartupTaskTrigger. /// </summary> - public class StartupTrigger : ITaskTrigger + public sealed class StartupTrigger : ITaskTrigger { - public int DelayMs { get; set; } + public const int DelayMs = 3000; /// <summary> - /// Gets or sets the options of this task. + /// Initializes a new instance of the <see cref="StartupTrigger"/> class. /// </summary> - public TaskOptions TaskOptions { get; set; } - - public StartupTrigger() + /// <param name="taskOptions">The options of this task.</param> + public StartupTrigger(TaskOptions taskOptions) { - DelayMs = 3000; + TaskOptions = taskOptions; } /// <summary> - /// Stars waiting for the trigger action + /// Occurs when [triggered]. + /// </summary> + public event EventHandler<EventArgs>? Triggered; + + /// <summary> + /// Gets the options of this task. + /// </summary> + public TaskOptions TaskOptions { get; } + + /// <summary> + /// Stars waiting for the trigger action. /// </summary> /// <param name="lastResult">The last result.</param> + /// <param name="logger">The logger.</param> + /// <param name="taskName">The name of the task.</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) { @@ -38,26 +51,18 @@ namespace Emby.Server.Implementations.ScheduledTasks } /// <summary> - /// Stops waiting for the trigger action + /// Stops waiting for the trigger action. /// </summary> public void Stop() { } /// <summary> - /// Occurs when [triggered]. - /// </summary> - public event EventHandler<EventArgs> Triggered; - - /// <summary> /// Called when [triggered]. /// </summary> private void OnTriggered() { - if (Triggered != null) - { - Triggered(this, EventArgs.Empty); - } + Triggered?.Invoke(this, EventArgs.Empty); } } } diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs index 2a6a7b13c..36ae190b0 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs @@ -6,37 +6,43 @@ using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.ScheduledTasks { /// <summary> - /// Represents a task trigger that fires on a weekly basis + /// Represents a task trigger that fires on a weekly basis. /// </summary> - public class WeeklyTrigger : ITaskTrigger + public sealed 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; } + private readonly TimeSpan _timeOfDay; + private readonly DayOfWeek _dayOfWeek; + private Timer? _timer; /// <summary> - /// Gets or sets the day of week. + /// Initializes a new instance of the <see cref="WeeklyTrigger"/> class. /// </summary> - /// <value>The day of week.</value> - public DayOfWeek DayOfWeek { get; set; } + /// <param name="timeofDay">The time of day to trigger the task to run.</param> + /// <param name="dayOfWeek">The day of week.</param> + /// <param name="taskOptions">The options of this task.</param> + public WeeklyTrigger(TimeSpan timeofDay, DayOfWeek dayOfWeek, TaskOptions taskOptions) + { + _timeOfDay = timeofDay; + _dayOfWeek = dayOfWeek; + TaskOptions = taskOptions; + } /// <summary> - /// Gets or sets the options of this task. + /// Occurs when [triggered]. /// </summary> - public TaskOptions TaskOptions { get; set; } + public event EventHandler<EventArgs>? Triggered; /// <summary> - /// Gets or sets the timer. + /// Gets the options of this task. /// </summary> - /// <value>The timer.</value> - private Timer Timer { get; set; } + public TaskOptions TaskOptions { get; } /// <summary> - /// Stars waiting for the trigger action + /// Stars waiting for the trigger action. /// </summary> /// <param name="lastResult">The last result.</param> + /// <param name="logger">The logger.</param> + /// <param name="taskName">The name of the task.</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) { @@ -44,7 +50,7 @@ namespace Emby.Server.Implementations.ScheduledTasks var triggerDate = GetNextTriggerDateTime(); - Timer = new Timer(state => OnTriggered(), null, triggerDate - DateTime.Now, TimeSpan.FromMilliseconds(-1)); + _timer = new Timer(state => OnTriggered(), null, triggerDate - DateTime.Now, TimeSpan.FromMilliseconds(-1)); } /// <summary> @@ -56,26 +62,26 @@ namespace Emby.Server.Implementations.ScheduledTasks var now = DateTime.Now; // If it's on the same day - if (now.DayOfWeek == DayOfWeek) + 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); + 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) + while (triggerDate.DayOfWeek != _dayOfWeek) { triggerDate = triggerDate.AddDays(1); } // Return the trigger date plus the time offset - return triggerDate.Add(TimeOfDay); + return triggerDate.Add(_timeOfDay); } /// <summary> - /// Stops waiting for the trigger action + /// Stops waiting for the trigger action. /// </summary> public void Stop() { @@ -87,26 +93,15 @@ namespace Emby.Server.Implementations.ScheduledTasks /// </summary> private void DisposeTimer() { - if (Timer != null) - { - Timer.Dispose(); - } + _timer?.Dispose(); } /// <summary> - /// Occurs when [triggered]. - /// </summary> - public event EventHandler<EventArgs> Triggered; - - /// <summary> /// Called when [triggered]. /// </summary> private void OnTriggered() { - if (Triggered != null) - { - Triggered(this, EventArgs.Empty); - } + Triggered?.Invoke(this, EventArgs.Empty); } } } |
