mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-04-18 21:35:27 -04:00
Add v5 command endpoints
This commit is contained in:
131
src/Sonarr.Api.V5/Commands/CommandController.cs
Normal file
131
src/Sonarr.Api.V5/Commands/CommandController.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NzbDrone.Common.Composition;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
using NzbDrone.Core.MediaFiles.EpisodeImport.Manual;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.ProgressMessaging;
|
||||
using NzbDrone.SignalR;
|
||||
using Sonarr.Http;
|
||||
using Sonarr.Http.REST;
|
||||
using Sonarr.Http.REST.Attributes;
|
||||
using Sonarr.Http.Validation;
|
||||
using Debouncer = NzbDrone.Common.TPL.Debouncer;
|
||||
|
||||
namespace Sonarr.Api.V5.Commands;
|
||||
|
||||
[V5ApiController]
|
||||
public class CommandController : RestControllerWithSignalR<CommandResource, CommandModel>, IHandle<CommandUpdatedEvent>
|
||||
{
|
||||
private readonly IManageCommandQueue _commandQueueManager;
|
||||
private readonly KnownTypes _knownTypes;
|
||||
private readonly Debouncer _debouncer;
|
||||
private readonly Dictionary<int, CommandResource> _pendingUpdates;
|
||||
|
||||
private readonly CommandPriorityComparer _commandPriorityComparer = new();
|
||||
|
||||
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<int, CommandResource>();
|
||||
|
||||
PostValidator.RuleFor(c => c.Name).NotBlank();
|
||||
}
|
||||
|
||||
protected override CommandResource GetResourceById(int id)
|
||||
{
|
||||
return _commandQueueManager.Get(id).ToResource();
|
||||
}
|
||||
|
||||
[RestPostById]
|
||||
[Consumes("application/json")]
|
||||
[Produces("application/json")]
|
||||
public ActionResult<CommandResource> StartCommand([FromBody] 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;
|
||||
|
||||
var command = STJson.Deserialize(body, commandType) as Command;
|
||||
|
||||
if (command == null)
|
||||
{
|
||||
throw new BadRequestException("Invalid command body");
|
||||
}
|
||||
|
||||
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<CommandResource> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
117
src/Sonarr.Api.V5/Commands/CommandResource.cs
Normal file
117
src/Sonarr.Api.V5/Commands/CommandResource.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using Sonarr.Http.REST;
|
||||
|
||||
namespace Sonarr.Api.V5.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; }
|
||||
public CommandStatus Status { get; set; }
|
||||
public CommandResult Result { get; set; }
|
||||
public DateTime Queued { get; set; }
|
||||
public DateTime? Started { get; set; }
|
||||
public DateTime? Ended { get; set; }
|
||||
public TimeSpan? Duration { get; set; }
|
||||
public string? Exception { get; set; }
|
||||
public CommandTrigger Trigger { get; set; }
|
||||
|
||||
public string? ClientUserAgent { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string? CompletionMessage { get; set; }
|
||||
|
||||
public DateTime? StateChangeTime
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Started.HasValue)
|
||||
{
|
||||
return Started.Value;
|
||||
}
|
||||
|
||||
return Ended;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public bool SendUpdatesToClient
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Body != null)
|
||||
{
|
||||
return Body.SendUpdatesToClient;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public bool UpdateScheduledTask
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Body != null)
|
||||
{
|
||||
return Body.UpdateScheduledTask;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime? LastExecutionTime { get; set; }
|
||||
}
|
||||
|
||||
public static class CommandResourceMapper
|
||||
{
|
||||
public static CommandResource ToResource(this CommandModel model)
|
||||
{
|
||||
return new CommandResource
|
||||
{
|
||||
Id = model.Id,
|
||||
|
||||
Name = model.Name,
|
||||
CommandName = model.Name.SplitCamelCase(),
|
||||
Message = model.Message,
|
||||
Body = model.Body,
|
||||
Priority = model.Priority,
|
||||
Status = model.Status,
|
||||
Result = model.Result,
|
||||
Queued = model.QueuedAt,
|
||||
Started = model.StartedAt,
|
||||
Ended = model.EndedAt,
|
||||
Duration = model.Duration,
|
||||
Exception = model.Exception,
|
||||
Trigger = model.Trigger,
|
||||
|
||||
ClientUserAgent = UserAgentParser.SimplifyUserAgent(model.Body.ClientUserAgent),
|
||||
|
||||
CompletionMessage = model.Body.CompletionMessage,
|
||||
LastExecutionTime = model.Body.LastExecutionTime
|
||||
};
|
||||
}
|
||||
|
||||
public static List<CommandResource> ToResource(this IEnumerable<CommandModel> models)
|
||||
{
|
||||
return models.Select(ToResource).ToList();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user