From 36b9c511633674628dc6fc89f67981f30a26baca Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 27 Aug 2018 20:27:45 -0700 Subject: [PATCH] Improved Command Queue New: Limit concurrent tasks that require disk access New: Prevent other tasks from running during application update --- src/NzbDrone.Api/Commands/CommandModule.cs | 3 +- .../Extensions/StringExtensions.cs | 7 + .../Commands/CommandExecutorFixture.cs | 6 +- .../Commands/CommandQueueManagerFixture.cs | 7 +- .../CheckForFinishedDownloadCommand.cs | 4 +- .../MediaFiles/Commands/RenameFilesCommand.cs | 3 +- .../Commands/RenameSeriesCommand.cs | 1 + .../Manual/ManualImportCommand.cs | 3 +- .../Messaging/Commands/Command.cs | 2 + .../Messaging/Commands/CommandQueue.cs | 188 ++++++++++++------ .../Messaging/Commands/CommandQueueManager.cs | 85 ++++---- .../Messaging/Commands/CommandRepository.cs | 2 +- .../Tv/Commands/BulkMoveSeriesCommand.cs | 1 + .../Tv/Commands/MoveSeriesCommand.cs | 1 + .../Commands/ApplicationUpdateCommand.cs | 3 +- src/Sonarr.Api.V3/Commands/CommandModule.cs | 45 ++++- src/Sonarr.Api.V3/Commands/CommandResource.cs | 36 +--- src/Sonarr.Api.V3/System/Tasks/TaskModule.cs | 7 +- 18 files changed, 251 insertions(+), 153 deletions(-) diff --git a/src/NzbDrone.Api/Commands/CommandModule.cs b/src/NzbDrone.Api/Commands/CommandModule.cs index bfd6ddf68..6a39316e8 100644 --- a/src/NzbDrone.Api/Commands/CommandModule.cs +++ b/src/NzbDrone.Api/Commands/CommandModule.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Sonarr.Http.Extensions; @@ -10,7 +10,6 @@ using NzbDrone.Core.ProgressMessaging; using NzbDrone.SignalR; using Sonarr.Http; -using Sonarr.Http.Mapping; using Sonarr.Http.Validation; diff --git a/src/NzbDrone.Common/Extensions/StringExtensions.cs b/src/NzbDrone.Common/Extensions/StringExtensions.cs index 324a76eeb..ec899af34 100644 --- a/src/NzbDrone.Common/Extensions/StringExtensions.cs +++ b/src/NzbDrone.Common/Extensions/StringExtensions.cs @@ -9,6 +9,8 @@ namespace NzbDrone.Common.Extensions { public static class StringExtensions { + private static readonly Regex CamelCaseRegex = new Regex("(? " " + match.Value); + } } } diff --git a/src/NzbDrone.Core.Test/Messaging/Commands/CommandExecutorFixture.cs b/src/NzbDrone.Core.Test/Messaging/Commands/CommandExecutorFixture.cs index 0f9435ae6..33b61c523 100644 --- a/src/NzbDrone.Core.Test/Messaging/Commands/CommandExecutorFixture.cs +++ b/src/NzbDrone.Core.Test/Messaging/Commands/CommandExecutorFixture.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; @@ -15,7 +15,7 @@ namespace NzbDrone.Core.Test.Messaging.Commands [TestFixture] public class CommandExecutorFixture : TestBase { - private BlockingCollection _commandQueue; + private CommandQueue _commandQueue; private Mock> _executorA; private Mock> _executorB; private bool _commandExecuted = false; @@ -46,7 +46,7 @@ public void TearDown() private void GivenCommandQueue() { - _commandQueue = new BlockingCollection(new CommandQueue()); + _commandQueue = new CommandQueue(); Mocker.GetMock() .Setup(s => s.Queue(It.IsAny())) diff --git a/src/NzbDrone.Core.Test/Messaging/Commands/CommandQueueManagerFixture.cs b/src/NzbDrone.Core.Test/Messaging/Commands/CommandQueueManagerFixture.cs index 16178a9cc..68ec47951 100644 --- a/src/NzbDrone.Core.Test/Messaging/Commands/CommandQueueManagerFixture.cs +++ b/src/NzbDrone.Core.Test/Messaging/Commands/CommandQueueManagerFixture.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using FluentAssertions; using Moq; @@ -42,6 +43,10 @@ public void should_not_remove_commands_for_five_minutes_after_they_end() { var command = Subject.Push(new CheckForFinishedDownloadCommand()); + // Start the command to mimic CommandQueue's behaviour + command.StartedAt = DateTime.Now; + command.Status = CommandStatus.Started; + Subject.Start(command); Subject.Complete(command, "All done"); Subject.CleanCommands(); diff --git a/src/NzbDrone.Core/Download/CheckForFinishedDownloadCommand.cs b/src/NzbDrone.Core/Download/CheckForFinishedDownloadCommand.cs index 7dc987d84..71f7f3d5e 100644 --- a/src/NzbDrone.Core/Download/CheckForFinishedDownloadCommand.cs +++ b/src/NzbDrone.Core/Download/CheckForFinishedDownloadCommand.cs @@ -1,9 +1,9 @@ -using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Download { public class CheckForFinishedDownloadCommand : Command { - + public override bool RequiresDiskAccess => true; } } diff --git a/src/NzbDrone.Core/MediaFiles/Commands/RenameFilesCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/RenameFilesCommand.cs index e0dc34e10..d836de604 100644 --- a/src/NzbDrone.Core/MediaFiles/Commands/RenameFilesCommand.cs +++ b/src/NzbDrone.Core/MediaFiles/Commands/RenameFilesCommand.cs @@ -9,6 +9,7 @@ public class RenameFilesCommand : Command public List Files { get; set; } public override bool SendUpdatesToClient => true; + public override bool RequiresDiskAccess => true; public RenameFilesCommand() { @@ -20,4 +21,4 @@ public RenameFilesCommand(int seriesId, List files) Files = files; } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs index 52c030fb6..65d8950d6 100644 --- a/src/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs +++ b/src/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs @@ -8,6 +8,7 @@ public class RenameSeriesCommand : Command public List SeriesIds { get; set; } public override bool SendUpdatesToClient => true; + public override bool RequiresDiskAccess => true; public RenameSeriesCommand() { diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportCommand.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportCommand.cs index 38ed485b7..4ee3ced94 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportCommand.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportCommand.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual @@ -8,6 +8,7 @@ public class ManualImportCommand : Command public List Files { get; set; } public override bool SendUpdatesToClient => true; + public override bool RequiresDiskAccess => true; public ImportMode ImportMode { get; set; } } diff --git a/src/NzbDrone.Core/Messaging/Commands/Command.cs b/src/NzbDrone.Core/Messaging/Commands/Command.cs index 0e82d8345..001143034 100644 --- a/src/NzbDrone.Core/Messaging/Commands/Command.cs +++ b/src/NzbDrone.Core/Messaging/Commands/Command.cs @@ -21,6 +21,8 @@ public virtual bool SendUpdatesToClient public virtual bool UpdateScheduledTask => true; public virtual string CompletionMessage => "Completed"; + public virtual bool RequiresDiskAccess => false; + public virtual bool IsExclusive => false; public string Name { get; private set; } public DateTime? LastExecutionTime { get; set; } diff --git a/src/NzbDrone.Core/Messaging/Commands/CommandQueue.cs b/src/NzbDrone.Core/Messaging/Commands/CommandQueue.cs index ad555fe6c..e566fcee8 100644 --- a/src/NzbDrone.Core/Messaging/Commands/CommandQueue.cs +++ b/src/NzbDrone.Core/Messaging/Commands/CommandQueue.cs @@ -1,27 +1,36 @@ -using System; +using System; using System.Collections; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace NzbDrone.Core.Messaging.Commands { - public class CommandQueue : IProducerConsumerCollection + public class CommandQueue : IEnumerable { - private object Mutex = new object(); - - private List _items; + private readonly object _mutex = new object(); + private readonly List _items; public CommandQueue() { _items = new List(); } + public int Count => _items.Count; + + public void Add(CommandModel item) + { + lock (_mutex) + { + _items.Add(item); + } + } + public IEnumerator GetEnumerator() { List copy = null; - lock (Mutex) + lock (_mutex) { copy = new List(_items); } @@ -34,77 +43,140 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } - public void CopyTo(Array array, int index) + public List All() { - lock (Mutex) + List rval = null; + + lock (_mutex) { - ((ICollection)_items).CopyTo(array, index); + rval = _items; + } + + return rval; + } + + public CommandModel Find(int id) + { + return All().FirstOrDefault(q => q.Id == id); + } + + public void RemoveMany(IEnumerable commands) + { + lock (_mutex) + { + foreach (var command in commands) + { + _items.Remove(command); + } + } + } + public bool RemoveIfQueued(int id) + { + var rval = false; + + lock (_mutex) + { + var command = _items.FirstOrDefault(q => q.Id == id); + + if (command?.Status == CommandStatus.Queued) + { + _items.Remove(command); + rval = true; + } + } + + return rval; + } + + public List QueuedOrStarted() + { + return All().Where(q => q.Status == CommandStatus.Queued || q.Status == CommandStatus.Started) + .ToList(); + } + + public IEnumerable GetConsumingEnumerable() + { + return GetConsumingEnumerable(CancellationToken.None); + } + + public IEnumerable GetConsumingEnumerable(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + if (TryGet(out var command)) + { + yield return command; + } + + Thread.Sleep(10); } } - public int Count => _items.Count; - - public object SyncRoot => Mutex; - - public bool IsSynchronized => true; - - public void CopyTo(CommandModel[] array, int index) + public bool TryGet(out CommandModel item) { - lock (Mutex) - { - _items.CopyTo(array, index); - } - } + var rval = true; + item = default(CommandModel); - public bool TryAdd(CommandModel item) - { - Add(item); - return true; - } - - public bool TryTake(out CommandModel item) - { - bool rval = true; - lock (Mutex) + lock (_mutex) { if (_items.Count == 0) { - item = default(CommandModel); rval = false; } else { - item = _items.Where(c => c.Status == CommandStatus.Queued) - .OrderByDescending(c => c.Priority) - .ThenBy(c => c.QueuedAt) - .First(); + var startedCommands = _items.Where(c => c.Status == CommandStatus.Started) + .ToList(); - _items.Remove(item); + var localItem = _items.Where(c => + { + // If an executing command requires disk access don't return a command that + // requires disk access. A lower priority or later queued task could be returned + // instead, but that will allow other tasks to execute whiule waiting for disk access. + if (startedCommands.Any(x => x.Body.RequiresDiskAccess)) + { + return c.Status == CommandStatus.Queued && + !c.Body.RequiresDiskAccess; + } + + return c.Status == CommandStatus.Queued; + }) + .OrderByDescending(c => c.Priority) + .ThenBy(c => c.QueuedAt) + .FirstOrDefault(); + + // Nothing queued that meets the requirements + if (localItem == null) + { + rval = false; + } + + // If any executing command is exclusive don't want return another command until it completes. + else if (startedCommands.Any(c => c.Body.IsExclusive)) + { + rval = false; + } + + // If the next command to execute is exclusive wait for executing commands to complete. + // This will prevent other tasks from starting so the exclusive task executes in the order it should. + else if (localItem.Body.IsExclusive && startedCommands.Any()) + { + rval = false; + } + + // A command ready to execute + else + { + localItem.StartedAt = DateTime.UtcNow; + localItem.Status = CommandStatus.Started; + + item = localItem; + } } } return rval; } - - public CommandModel[] ToArray() - { - CommandModel[] rval = null; - - lock (Mutex) - { - rval = _items.ToArray(); - } - - return rval; - } - - public void Add(CommandModel item) - { - lock (Mutex) - { - _items.Add(item); - } - } } } diff --git a/src/NzbDrone.Core/Messaging/Commands/CommandQueueManager.cs b/src/NzbDrone.Core/Messaging/Commands/CommandQueueManager.cs index eed192a6a..1a199991b 100644 --- a/src/NzbDrone.Core/Messaging/Commands/CommandQueueManager.cs +++ b/src/NzbDrone.Core/Messaging/Commands/CommandQueueManager.cs @@ -1,15 +1,15 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; using NLog; using NzbDrone.Common; -using NzbDrone.Common.Cache; using NzbDrone.Common.EnsureThat; using NzbDrone.Common.Serializer; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Messaging.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading; +using NzbDrone.Core.Exceptions; namespace NzbDrone.Core.Messaging.Commands { @@ -19,6 +19,7 @@ public interface IManageCommandQueue CommandModel Push(TCommand command, CommandPriority priority = CommandPriority.Normal, CommandTrigger trigger = CommandTrigger.Unspecified) where TCommand : Command; CommandModel Push(string commandName, DateTime? lastExecutionTime, CommandPriority priority = CommandPriority.Normal, CommandTrigger trigger = CommandTrigger.Unspecified); IEnumerable Queue(CancellationToken cancellationToken); + List All(); CommandModel Get(int id); List GetStarted(); void SetMessage(CommandModel command, string message); @@ -26,6 +27,7 @@ public interface IManageCommandQueue void Complete(CommandModel command, string message); void Fail(CommandModel command, string message, Exception e); void Requeue(); + void Cancel(int id); void CleanCommands(); } @@ -35,20 +37,17 @@ public class CommandQueueManager : IManageCommandQueue, IHandle _commandCache; - private readonly BlockingCollection _commandQueue; + private readonly CommandQueue _commandQueue; public CommandQueueManager(ICommandRepository repo, IServiceFactory serviceFactory, - ICacheManager cacheManager, Logger logger) { _repo = repo; _serviceFactory = serviceFactory; _logger = logger; - _commandCache = cacheManager.GetCache(GetType()); - _commandQueue = new BlockingCollection(new CommandQueue()); + _commandQueue = new CommandQueue(); } public List PushMany(List commands) where TCommand : Command @@ -56,8 +55,7 @@ public List PushMany(List commands) where TCom _logger.Trace("Publishing {0} commands", commands.Count); var commandModels = new List(); - var existingCommands = _commandCache.Values.Where(q => q.Status == CommandStatus.Queued || - q.Status == CommandStatus.Started).ToList(); + var existingCommands = _commandQueue.QueuedOrStarted(); foreach (var command in commands) { @@ -85,7 +83,6 @@ public List PushMany(List commands) where TCom foreach (var commandModel in commandModels) { - _commandCache.Set(commandModel.Id.ToString(), commandModel); _commandQueue.Add(commandModel); } @@ -122,7 +119,6 @@ public CommandModel Push(TCommand command, CommandPriority priority = _logger.Trace("Inserting new command: {0}", commandModel.Name); _repo.Insert(commandModel); - _commandCache.Set(commandModel.Id.ToString(), commandModel); _commandQueue.Add(commandModel); return commandModel; @@ -142,30 +138,39 @@ public IEnumerable Queue(CancellationToken cancellationToken) return _commandQueue.GetConsumingEnumerable(cancellationToken); } + public List All() + { + _logger.Trace("Getting all commands"); + return _commandQueue.All(); + } + public CommandModel Get(int id) { - return _commandCache.Get(id.ToString(), () => FindCommand(_repo.Get(id))); + var command = _commandQueue.Find(id); + + if (command == null) + { + command = _repo.Get(id); + } + + return command; } public List GetStarted() { _logger.Trace("Getting started commands"); - return _commandCache.Values.Where(c => c.Status == CommandStatus.Started).ToList(); + return _commandQueue.All().Where(c => c.Status == CommandStatus.Started).ToList(); } public void SetMessage(CommandModel command, string message) { command.Message = message; - _commandCache.Set(command.Id.ToString(), command); } public void Start(CommandModel command) { - command.StartedAt = DateTime.UtcNow; - command.Status = CommandStatus.Started; - + // Marks the command as started in the DB, the queue takes care of marking it as started on it's own _logger.Trace("Marking command as started: {0}", command.Name); - _commandCache.Set(command.Id.ToString(), command); _repo.Start(command); } @@ -189,16 +194,23 @@ public void Requeue() } } + public void Cancel(int id) + { + if (!_commandQueue.RemoveIfQueued(id)) + { + throw new NzbDroneClientException(HttpStatusCode.Conflict, "Unable to cancel task"); + } + } + public void CleanCommands() { _logger.Trace("Cleaning up old commands"); - var old = _commandCache.Values.Where(c => c.EndedAt < DateTime.UtcNow.AddMinutes(-5)); + var commands = _commandQueue.All() + .Where(c => c.EndedAt < DateTime.UtcNow.AddMinutes(-5)) + .ToList(); - foreach (var command in old) - { - _commandCache.Remove(command.Id.ToString()); - } + _commandQueue.RemoveMany(commands); _repo.Trim(); } @@ -213,18 +225,6 @@ private dynamic GetCommand(string commandName) return Json.Deserialize("{}", commandType); } - private CommandModel FindCommand(CommandModel command) - { - var cachedCommand = _commandCache.Find(command.Id.ToString()); - - if (cachedCommand != null) - { - command.Message = cachedCommand.Message; - } - - return command; - } - private void Update(CommandModel command, CommandStatus status, string message) { SetMessage(command, message); @@ -234,15 +234,14 @@ private void Update(CommandModel command, CommandStatus status, string message) command.Status = status; _logger.Trace("Updating command status"); - _commandCache.Set(command.Id.ToString(), command); _repo.End(command); } private List QueuedOrStarted(string name) { - return _commandCache.Values.Where(q => q.Name == name && - (q.Status == CommandStatus.Queued || - q.Status == CommandStatus.Started)).ToList(); + return _commandQueue.QueuedOrStarted() + .Where(q => q.Name == name) + .ToList(); } public void Handle(ApplicationStartedEvent message) diff --git a/src/NzbDrone.Core/Messaging/Commands/CommandRepository.cs b/src/NzbDrone.Core/Messaging/Commands/CommandRepository.cs index b873d28aa..3b7ff3e60 100644 --- a/src/NzbDrone.Core/Messaging/Commands/CommandRepository.cs +++ b/src/NzbDrone.Core/Messaging/Commands/CommandRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Data.SQLite; using NzbDrone.Core.Datastore; diff --git a/src/NzbDrone.Core/Tv/Commands/BulkMoveSeriesCommand.cs b/src/NzbDrone.Core/Tv/Commands/BulkMoveSeriesCommand.cs index d4a263ba2..3df2ba5c0 100644 --- a/src/NzbDrone.Core/Tv/Commands/BulkMoveSeriesCommand.cs +++ b/src/NzbDrone.Core/Tv/Commands/BulkMoveSeriesCommand.cs @@ -10,6 +10,7 @@ public class BulkMoveSeriesCommand : Command public string DestinationRootFolder { get; set; } public override bool SendUpdatesToClient => true; + public override bool RequiresDiskAccess => true; } public class BulkMoveSeries : IEquatable diff --git a/src/NzbDrone.Core/Tv/Commands/MoveSeriesCommand.cs b/src/NzbDrone.Core/Tv/Commands/MoveSeriesCommand.cs index 9971c6b9b..9399ffb61 100644 --- a/src/NzbDrone.Core/Tv/Commands/MoveSeriesCommand.cs +++ b/src/NzbDrone.Core/Tv/Commands/MoveSeriesCommand.cs @@ -9,5 +9,6 @@ public class MoveSeriesCommand : Command public string DestinationPath { get; set; } public override bool SendUpdatesToClient => true; + public override bool RequiresDiskAccess => true; } } diff --git a/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs b/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs index a6603d34c..0ca1d8074 100644 --- a/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs +++ b/src/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs @@ -1,10 +1,11 @@ -using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Update.Commands { public class ApplicationUpdateCommand : Command { public override bool SendUpdatesToClient => true; + public override bool IsExclusive => true; public override string CompletionMessage => null; } diff --git a/src/Sonarr.Api.V3/Commands/CommandModule.cs b/src/Sonarr.Api.V3/Commands/CommandModule.cs index 0d9a21906..6644fd395 100644 --- a/src/Sonarr.Api.V3/Commands/CommandModule.cs +++ b/src/Sonarr.Api.V3/Commands/CommandModule.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using NzbDrone.Common; +using NzbDrone.Common.TPL; using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; @@ -17,6 +18,8 @@ public class CommandModule : SonarrRestModuleWithSignalR _pendingUpdates; public CommandModule(IManageCommandQueue commandQueueManager, IBroadcastSignalRMessage signalRBroadcaster, @@ -26,9 +29,13 @@ public CommandModule(IManageCommandQueue commandQueueManager, _commandQueueManager = commandQueueManager; _serviceFactory = serviceFactory; + _debouncer = new Debouncer(SendUpdates, TimeSpan.FromSeconds(0.1)); + _pendingUpdates = new Dictionary(); + GetResourceById = GetCommand; CreateResource = StartCommand; GetResourceAll = GetStartedCommands; + DeleteResource = CancelCommand; PostValidator.RuleFor(c => c.Name).NotBlank(); } @@ -56,15 +63,45 @@ private int StartCommand(CommandResource commandResource) private List GetStartedCommands() { - return _commandQueueManager.GetStarted().ToResource(); + return _commandQueueManager.All().ToResource(); + } + + private void CancelCommand(int id) + { + _commandQueueManager.Cancel(id); } public void Handle(CommandUpdatedEvent message) { if (message.Command.Body.SendUpdatesToClient) { - BroadcastResourceChange(ModelAction.Updated, message.Command.ToResource()); + lock (_pendingUpdates) + { + _pendingUpdates[message.Command.Id] = message.Command.ToResource(); + } + + _debouncer.Execute(); + } + } + + private void SendUpdates() + { + lock (_pendingUpdates) + { + var pendingUpdates = _pendingUpdates.Values.ToArray(); + _pendingUpdates.Clear(); + + foreach (var pendingUpdate in pendingUpdates) + { + BroadcastResourceChange(ModelAction.Updated, pendingUpdate); + + if (pendingUpdate.Name == typeof(MessagingCleanupCommand).Name.Replace("Command", "") && + pendingUpdate.Status == CommandStatus.Completed) + { + BroadcastResourceChange(ModelAction.Sync); + } + } } } } -} \ No newline at end of file +} diff --git a/src/Sonarr.Api.V3/Commands/CommandResource.cs b/src/Sonarr.Api.V3/Commands/CommandResource.cs index 98c089f20..cbec03a7f 100644 --- a/src/Sonarr.Api.V3/Commands/CommandResource.cs +++ b/src/Sonarr.Api.V3/Commands/CommandResource.cs @@ -1,7 +1,8 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Messaging.Commands; using Sonarr.Http.REST; @@ -10,6 +11,7 @@ namespace Sonarr.Api.V3.Commands public class CommandResource : RestResource { public string Name { get; set; } + public string CommandName { get; set; } public string Message { get; set; } public Command Body { get; set; } public CommandPriority Priority { get; set; } @@ -24,37 +26,6 @@ public class CommandResource : RestResource [JsonIgnore] public string CompletionMessage { get; set; } - //Legacy - public CommandStatus State - { - get - { - return Status; - } - - set { } - } - - public bool Manual - { - get - { - return Trigger == CommandTrigger.Manual; - } - - set { } - } - - public DateTime StartedOn - { - get - { - return Queued; - } - - set { } - } - public DateTime? StateChangeTime { get @@ -106,6 +77,7 @@ public static CommandResource ToResource(this CommandModel model) Id = model.Id, Name = model.Name, + CommandName = model.Name.SplitCamelCase(), Message = model.Message, Body = model.Body, Priority = model.Priority, diff --git a/src/Sonarr.Api.V3/System/Tasks/TaskModule.cs b/src/Sonarr.Api.V3/System/Tasks/TaskModule.cs index c8840adeb..1081feb62 100644 --- a/src/Sonarr.Api.V3/System/Tasks/TaskModule.cs +++ b/src/Sonarr.Api.V3/System/Tasks/TaskModule.cs @@ -1,6 +1,7 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.Jobs; using NzbDrone.Core.Messaging.Events; @@ -13,8 +14,6 @@ public class TaskModule : SonarrRestModuleWithSignalR " " + match.Value), + Name = taskName.SplitCamelCase(), TaskName = taskName, Interval = scheduledTask.Interval, LastExecution = scheduledTask.LastExecution,