using System; using System.Collections.Generic; using System.IO; using System.Linq; using Lidarr.Http; using Lidarr.Http.REST; using Lidarr.Http.REST.Attributes; using Lidarr.Http.Validation; using Microsoft.AspNetCore.Mvc; using NzbDrone.Common.Composition; using NzbDrone.Common.Serializer; using NzbDrone.Common.TPL; using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.MediaFiles.TrackImport.Manual; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.ProgressMessaging; using NzbDrone.SignalR; namespace Lidarr.Api.V1.Commands { [V1ApiController] public class CommandController : RestControllerWithSignalR, IHandle { private readonly IManageCommandQueue _commandQueueManager; private readonly KnownTypes _knownTypes; private readonly Debouncer _debouncer; private readonly Dictionary _pendingUpdates; private readonly CommandPriorityComparer _commandPriorityComparer = new CommandPriorityComparer(); public CommandController(IManageCommandQueue commandQueueManager, IBroadcastSignalRMessage signalRBroadcaster, KnownTypes knownTypes) : base(signalRBroadcaster) { _commandQueueManager = commandQueueManager; _knownTypes = knownTypes; _debouncer = new Debouncer(SendUpdates, TimeSpan.FromSeconds(0.1)); _pendingUpdates = new Dictionary(); PostValidator.RuleFor(c => c.Name).NotBlank(); } public override CommandResource GetResourceById(int id) { return _commandQueueManager.Get(id).ToResource(); } [RestPostById] [Consumes("application/json")] [Produces("application/json")] public ActionResult StartCommand(CommandResource commandResource) { var commandType = _knownTypes.GetImplementations(typeof(Command)) .Single(c => c.Name.Replace("Command", "") .Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase)); Request.Body.Seek(0, SeekOrigin.Begin); using (var reader = new StreamReader(Request.Body)) { var body = reader.ReadToEnd(); var priority = commandType == typeof(ManualImportCommand) ? CommandPriority.High : CommandPriority.Normal; dynamic command = STJson.Deserialize(body, commandType); command.Trigger = CommandTrigger.Manual; command.SuppressMessages = !command.SendUpdatesToClient; command.SendUpdatesToClient = true; command.ClientUserAgent = Request.Headers["UserAgent"]; var trackedCommand = _commandQueueManager.Push(command, priority, CommandTrigger.Manual); return Created(trackedCommand.Id); } } [HttpGet] [Produces("application/json")] public List GetStartedCommands() { return _commandQueueManager.All() .OrderBy(c => c.Status, _commandPriorityComparer) .ThenByDescending(c => c.Priority) .ToResource(); } [RestDeleteById] public void CancelCommand(int id) { _commandQueueManager.Cancel(id); } [NonAction] public void Handle(CommandUpdatedEvent message) { if (message.Command.Body.SendUpdatesToClient) { 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); } } } } } }