commit
beb4aee4c9
|
@ -117,13 +117,11 @@ namespace NzbDrone.Api.Test.MappingTests
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_map_tracked_command()
|
public void should_map_tracked_command()
|
||||||
{
|
{
|
||||||
var profileResource = new ApplicationUpdateCommand();
|
var commandResource = new CommandModel { Body = new ApplicationUpdateCommand() };
|
||||||
profileResource.InjectTo<CommandResource>();
|
commandResource.InjectTo<CommandResource>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,9 @@ using System.Linq;
|
||||||
using NzbDrone.Api.Extensions;
|
using NzbDrone.Api.Extensions;
|
||||||
using NzbDrone.Api.Mapping;
|
using NzbDrone.Api.Mapping;
|
||||||
using NzbDrone.Api.Validation;
|
using NzbDrone.Api.Validation;
|
||||||
using NzbDrone.Common.Composition;
|
using NzbDrone.Common;
|
||||||
using NzbDrone.Core.Datastore.Events;
|
using NzbDrone.Core.Datastore.Events;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Messaging.Commands.Tracking;
|
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.ProgressMessaging;
|
using NzbDrone.Core.ProgressMessaging;
|
||||||
using NzbDrone.SignalR;
|
using NzbDrone.SignalR;
|
||||||
|
@ -15,56 +14,53 @@ using NzbDrone.SignalR;
|
||||||
|
|
||||||
namespace NzbDrone.Api.Commands
|
namespace NzbDrone.Api.Commands
|
||||||
{
|
{
|
||||||
public class CommandModule : NzbDroneRestModuleWithSignalR<CommandResource, Command>, IHandle<CommandUpdatedEvent>
|
public class CommandModule : NzbDroneRestModuleWithSignalR<CommandResource, CommandModel>, IHandle<CommandUpdatedEvent>
|
||||||
{
|
{
|
||||||
private readonly ICommandExecutor _commandExecutor;
|
private readonly IManageCommandQueue _commandQueueManager;
|
||||||
private readonly IContainer _container;
|
private readonly IServiceFactory _serviceFactory;
|
||||||
private readonly ITrackCommands _trackCommands;
|
|
||||||
|
|
||||||
public CommandModule(ICommandExecutor commandExecutor,
|
public CommandModule(IManageCommandQueue commandQueueManager,
|
||||||
IBroadcastSignalRMessage signalRBroadcaster,
|
IBroadcastSignalRMessage signalRBroadcaster,
|
||||||
IContainer container,
|
IServiceFactory serviceFactory)
|
||||||
ITrackCommands trackCommands)
|
|
||||||
: base(signalRBroadcaster)
|
: base(signalRBroadcaster)
|
||||||
{
|
{
|
||||||
_commandExecutor = commandExecutor;
|
_commandQueueManager = commandQueueManager;
|
||||||
_container = container;
|
_serviceFactory = serviceFactory;
|
||||||
_trackCommands = trackCommands;
|
|
||||||
|
|
||||||
GetResourceById = GetCommand;
|
GetResourceById = GetCommand;
|
||||||
CreateResource = StartCommand;
|
CreateResource = StartCommand;
|
||||||
GetResourceAll = GetAllCommands;
|
GetResourceAll = GetStartedCommands;
|
||||||
|
|
||||||
PostValidator.RuleFor(c => c.Name).NotBlank();
|
PostValidator.RuleFor(c => c.Name).NotBlank();
|
||||||
}
|
}
|
||||||
|
|
||||||
private CommandResource GetCommand(int id)
|
private CommandResource GetCommand(int id)
|
||||||
{
|
{
|
||||||
return _trackCommands.GetById(id).InjectTo<CommandResource>();
|
return _commandQueueManager.Get(id).InjectTo<CommandResource>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int StartCommand(CommandResource commandResource)
|
private int StartCommand(CommandResource commandResource)
|
||||||
{
|
{
|
||||||
var commandType =
|
var commandType =
|
||||||
_container.GetImplementations(typeof(Command))
|
_serviceFactory.GetImplementations(typeof (Command))
|
||||||
.Single(c => c.Name.Replace("Command", "")
|
.Single(c => c.Name.Replace("Command", "")
|
||||||
.Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase));
|
.Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
|
||||||
dynamic command = Request.Body.FromJson(commandType);
|
dynamic command = Request.Body.FromJson(commandType);
|
||||||
command.Manual = true;
|
command.Trigger = CommandTrigger.Manual;
|
||||||
|
|
||||||
var trackedCommand = (Command)_commandExecutor.PublishCommandAsync(command);
|
var trackedCommand = _commandQueueManager.Push(command, CommandPriority.Normal, CommandTrigger.Manual);
|
||||||
return trackedCommand.Id;
|
return trackedCommand.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<CommandResource> GetAllCommands()
|
private List<CommandResource> GetStartedCommands()
|
||||||
{
|
{
|
||||||
return ToListResource(_trackCommands.RunningCommands);
|
return ToListResource(_commandQueueManager.GetStarted());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Handle(CommandUpdatedEvent message)
|
public void Handle(CommandUpdatedEvent message)
|
||||||
{
|
{
|
||||||
if (message.Command.SendUpdatesToClient)
|
if (message.Command.Body.SendUpdatesToClient)
|
||||||
{
|
{
|
||||||
BroadcastResourceChange(ModelAction.Updated, message.Command.Id);
|
BroadcastResourceChange(ModelAction.Updated, message.Command.Id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using NzbDrone.Api.REST;
|
using NzbDrone.Api.REST;
|
||||||
using NzbDrone.Core.Messaging.Commands.Tracking;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
|
||||||
namespace NzbDrone.Api.Commands
|
namespace NzbDrone.Api.Commands
|
||||||
{
|
{
|
||||||
|
@ -8,11 +9,75 @@ namespace NzbDrone.Api.Commands
|
||||||
{
|
{
|
||||||
public String Name { get; set; }
|
public String Name { get; set; }
|
||||||
public String Message { get; set; }
|
public String Message { get; set; }
|
||||||
public DateTime StartedOn { get; set; }
|
public Command Body { get; set; }
|
||||||
public DateTime StateChangeTime { get; set; }
|
public CommandPriority Priority { get; set; }
|
||||||
public Boolean SendUpdatesToClient { get; set; }
|
public CommandStatus Status { get; set; }
|
||||||
public CommandStatus State { 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; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public string CompletionMessage { get; set; }
|
||||||
|
|
||||||
|
//Legacy
|
||||||
|
public CommandStatus State
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean Manual
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Trigger == CommandTrigger.Manual;
|
||||||
|
}
|
||||||
|
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime StartedOn
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Queued;
|
||||||
|
}
|
||||||
|
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime? StateChangeTime
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
|
||||||
|
if (Started.HasValue) return Started.Value;
|
||||||
|
|
||||||
|
return Ended;
|
||||||
|
}
|
||||||
|
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean SendUpdatesToClient
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Body != null) return Body.SendUpdatesToClient;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
public DateTime? LastExecutionTime { get; set; }
|
public DateTime? LastExecutionTime { get; set; }
|
||||||
public Boolean Manual { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -100,6 +100,14 @@ namespace NzbDrone.Common.Cache
|
||||||
_store.Clear();
|
_store.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ClearExpired()
|
||||||
|
{
|
||||||
|
foreach (var cached in _store.Where(c => c.Value.IsExpired()))
|
||||||
|
{
|
||||||
|
Remove(cached.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ICollection<T> Values
|
public ICollection<T> Values
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
@ -6,6 +6,7 @@ namespace NzbDrone.Common.Cache
|
||||||
public interface ICached
|
public interface ICached
|
||||||
{
|
{
|
||||||
void Clear();
|
void Clear();
|
||||||
|
void ClearExpired();
|
||||||
void Remove(string key);
|
void Remove(string key);
|
||||||
int Count { get; }
|
int Count { get; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,8 +61,8 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||||
|
|
||||||
ExceptionVerification.ExpectedWarns(1);
|
ExceptionVerification.ExpectedWarns(1);
|
||||||
|
|
||||||
Mocker.GetMock<ICommandExecutor>()
|
Mocker.GetMock<IMediaFileTableCleanupService>()
|
||||||
.Verify(v => v.PublishCommand(It.IsAny<CleanMediaFileDb>()), Times.Never());
|
.Verify(v => v.Clean(It.IsAny<Series>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -80,8 +80,8 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
|
||||||
|
|
||||||
ExceptionVerification.ExpectedWarns(1);
|
ExceptionVerification.ExpectedWarns(1);
|
||||||
|
|
||||||
Mocker.GetMock<ICommandExecutor>()
|
Mocker.GetMock<IMediaFileTableCleanupService>()
|
||||||
.Verify(v => v.PublishCommand(It.IsAny<CleanMediaFileDb>()), Times.Never());
|
.Verify(v => v.Clean(It.IsAny<Series>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|
|
@ -6,7 +6,6 @@ using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.MediaFiles.Commands;
|
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
|
@ -16,6 +15,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
{
|
{
|
||||||
private const string DELETED_PATH = "ANY FILE WITH THIS PATH IS CONSIDERED DELETED!";
|
private const string DELETED_PATH = "ANY FILE WITH THIS PATH IS CONSIDERED DELETED!";
|
||||||
private List<Episode> _episodes;
|
private List<Episode> _episodes;
|
||||||
|
private Series _series;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp()
|
public void SetUp()
|
||||||
|
@ -24,9 +24,8 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
.Build()
|
.Build()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
Mocker.GetMock<ISeriesService>()
|
_series = Builder<Series>.CreateNew()
|
||||||
.Setup(s => s.GetSeries(It.IsAny<Int32>()))
|
.Build();
|
||||||
.Returns(Builder<Series>.CreateNew().Build());
|
|
||||||
|
|
||||||
Mocker.GetMock<IDiskProvider>()
|
Mocker.GetMock<IDiskProvider>()
|
||||||
.Setup(e => e.FileExists(It.Is<String>(c => !c.Contains(DELETED_PATH))))
|
.Setup(e => e.FileExists(It.Is<String>(c => !c.Contains(DELETED_PATH))))
|
||||||
|
@ -61,7 +60,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
|
|
||||||
GivenEpisodeFiles(episodeFiles);
|
GivenEpisodeFiles(episodeFiles);
|
||||||
|
|
||||||
Subject.Execute(new CleanMediaFileDb(0));
|
Subject.Clean(_series);
|
||||||
|
|
||||||
Mocker.GetMock<IEpisodeService>().Verify(c => c.UpdateEpisode(It.IsAny<Episode>()), Times.Never());
|
Mocker.GetMock<IEpisodeService>().Verify(c => c.UpdateEpisode(It.IsAny<Episode>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
@ -76,7 +75,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
|
|
||||||
GivenEpisodeFiles(episodeFiles);
|
GivenEpisodeFiles(episodeFiles);
|
||||||
|
|
||||||
Subject.Execute(new CleanMediaFileDb(0));
|
Subject.Clean(_series);
|
||||||
|
|
||||||
Mocker.GetMock<IMediaFileService>().Verify(c => c.Delete(It.Is<EpisodeFile>(e => e.RelativePath == DELETED_PATH), DeleteMediaFileReason.MissingFromDisk), Times.Exactly(2));
|
Mocker.GetMock<IMediaFileService>().Verify(c => c.Delete(It.Is<EpisodeFile>(e => e.RelativePath == DELETED_PATH), DeleteMediaFileReason.MissingFromDisk), Times.Exactly(2));
|
||||||
}
|
}
|
||||||
|
@ -92,7 +91,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
GivenEpisodeFiles(episodeFiles);
|
GivenEpisodeFiles(episodeFiles);
|
||||||
GivenFilesAreNotAttachedToEpisode();
|
GivenFilesAreNotAttachedToEpisode();
|
||||||
|
|
||||||
Subject.Execute(new CleanMediaFileDb(0));
|
Subject.Clean(_series);
|
||||||
|
|
||||||
Mocker.GetMock<IMediaFileService>().Verify(c => c.Delete(It.IsAny<EpisodeFile>(), DeleteMediaFileReason.NoLinkedEpisodes), Times.Exactly(10));
|
Mocker.GetMock<IMediaFileService>().Verify(c => c.Delete(It.IsAny<EpisodeFile>(), DeleteMediaFileReason.NoLinkedEpisodes), Times.Exactly(10));
|
||||||
}
|
}
|
||||||
|
@ -102,7 +101,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
{
|
{
|
||||||
GivenEpisodeFiles(new List<EpisodeFile>());
|
GivenEpisodeFiles(new List<EpisodeFile>());
|
||||||
|
|
||||||
Subject.Execute(new CleanMediaFileDb(0));
|
Subject.Clean(_series);
|
||||||
|
|
||||||
Mocker.GetMock<IEpisodeService>().Verify(c => c.UpdateEpisode(It.Is<Episode>(e => e.EpisodeFileId == 0)), Times.Exactly(10));
|
Mocker.GetMock<IEpisodeService>().Verify(c => c.UpdateEpisode(It.Is<Episode>(e => e.EpisodeFileId == 0)), Times.Exactly(10));
|
||||||
}
|
}
|
||||||
|
@ -117,7 +116,7 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
|
|
||||||
GivenEpisodeFiles(episodeFiles);
|
GivenEpisodeFiles(episodeFiles);
|
||||||
|
|
||||||
Subject.Execute(new CleanMediaFileDb(0));
|
Subject.Clean(_series);
|
||||||
|
|
||||||
Mocker.GetMock<IEpisodeService>().Verify(c => c.UpdateEpisode(It.IsAny<Episode>()), Times.Never());
|
Mocker.GetMock<IEpisodeService>().Verify(c => c.UpdateEpisode(It.IsAny<Episode>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,121 +1,121 @@
|
||||||
using System;
|
//using System;
|
||||||
using System.Collections.Generic;
|
//using System.Collections.Generic;
|
||||||
using Moq;
|
//using Moq;
|
||||||
using NUnit.Framework;
|
//using NUnit.Framework;
|
||||||
using NzbDrone.Common;
|
//using NzbDrone.Common;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
//using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Messaging.Commands.Tracking;
|
//using NzbDrone.Core.Messaging.Commands.Tracking;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
//using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Test.Common;
|
//using NzbDrone.Test.Common;
|
||||||
|
//
|
||||||
namespace NzbDrone.Core.Test.Messaging.Commands
|
//namespace NzbDrone.Core.Test.Messaging.Commands
|
||||||
{
|
//{
|
||||||
[TestFixture]
|
// [TestFixture]
|
||||||
public class CommandExecutorFixture : TestBase<CommandExecutor>
|
// public class CommandExecutorFixture : TestBase<CommandExecutor>
|
||||||
{
|
// {
|
||||||
private Mock<IExecute<CommandA>> _executorA;
|
// private Mock<IExecute<CommandA>> _executorA;
|
||||||
private Mock<IExecute<CommandB>> _executorB;
|
// private Mock<IExecute<CommandB>> _executorB;
|
||||||
|
//
|
||||||
[SetUp]
|
// [SetUp]
|
||||||
public void Setup()
|
// public void Setup()
|
||||||
{
|
// {
|
||||||
_executorA = new Mock<IExecute<CommandA>>();
|
// _executorA = new Mock<IExecute<CommandA>>();
|
||||||
_executorB = new Mock<IExecute<CommandB>>();
|
// _executorB = new Mock<IExecute<CommandB>>();
|
||||||
|
//
|
||||||
Mocker.GetMock<IServiceFactory>()
|
// Mocker.GetMock<IServiceFactory>()
|
||||||
.Setup(c => c.Build(typeof(IExecute<CommandA>)))
|
// .Setup(c => c.Build(typeof(IExecute<CommandA>)))
|
||||||
.Returns(_executorA.Object);
|
// .Returns(_executorA.Object);
|
||||||
|
//
|
||||||
Mocker.GetMock<IServiceFactory>()
|
// Mocker.GetMock<IServiceFactory>()
|
||||||
.Setup(c => c.Build(typeof(IExecute<CommandB>)))
|
// .Setup(c => c.Build(typeof(IExecute<CommandB>)))
|
||||||
.Returns(_executorB.Object);
|
// .Returns(_executorB.Object);
|
||||||
|
//
|
||||||
|
//
|
||||||
Mocker.GetMock<ITrackCommands>()
|
// Mocker.GetMock<ITrackCommands>()
|
||||||
.Setup(c => c.FindExisting(It.IsAny<Command>()))
|
// .Setup(c => c.FindExisting(It.IsAny<Command>()))
|
||||||
.Returns<Command>(null);
|
// .Returns<Command>(null);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
[Test]
|
// [Test]
|
||||||
public void should_publish_command_to_executor()
|
// public void should_publish_command_to_executor()
|
||||||
{
|
// {
|
||||||
var commandA = new CommandA();
|
// var commandA = new CommandA();
|
||||||
|
//
|
||||||
Subject.PublishCommand(commandA);
|
// Subject.Push(commandA);
|
||||||
|
//
|
||||||
_executorA.Verify(c => c.Execute(commandA), Times.Once());
|
// _executorA.Verify(c => c.Execute(commandA), Times.Once());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
[Test]
|
// [Test]
|
||||||
public void should_publish_command_by_with_optional_arg_using_name()
|
// public void should_publish_command_by_with_optional_arg_using_name()
|
||||||
{
|
// {
|
||||||
Mocker.GetMock<IServiceFactory>().Setup(c => c.GetImplementations(typeof(Command)))
|
// Mocker.GetMock<IServiceFactory>().Setup(c => c.GetImplementations(typeof(Command)))
|
||||||
.Returns(new List<Type> { typeof(CommandA), typeof(CommandB) });
|
// .Returns(new List<Type> { typeof(CommandA), typeof(CommandB) });
|
||||||
|
//
|
||||||
Subject.PublishCommand(typeof(CommandA).FullName);
|
// Subject.Push(typeof(CommandA).FullName);
|
||||||
_executorA.Verify(c => c.Execute(It.IsAny<CommandA>()), Times.Once());
|
// _executorA.Verify(c => c.Execute(It.IsAny<CommandA>()), Times.Once());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
[Test]
|
// [Test]
|
||||||
public void should_not_publish_to_incompatible_executor()
|
// public void should_not_publish_to_incompatible_executor()
|
||||||
{
|
// {
|
||||||
var commandA = new CommandA();
|
// var commandA = new CommandA();
|
||||||
|
//
|
||||||
Subject.PublishCommand(commandA);
|
// Subject.Push(commandA);
|
||||||
|
//
|
||||||
_executorA.Verify(c => c.Execute(commandA), Times.Once());
|
// _executorA.Verify(c => c.Execute(commandA), Times.Once());
|
||||||
_executorB.Verify(c => c.Execute(It.IsAny<CommandB>()), Times.Never());
|
// _executorB.Verify(c => c.Execute(It.IsAny<CommandB>()), Times.Never());
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
[Test]
|
// [Test]
|
||||||
public void broken_executor_should_throw_the_exception()
|
// public void broken_executor_should_throw_the_exception()
|
||||||
{
|
// {
|
||||||
var commandA = new CommandA();
|
// var commandA = new CommandA();
|
||||||
|
//
|
||||||
_executorA.Setup(c => c.Execute(It.IsAny<CommandA>()))
|
// _executorA.Setup(c => c.Execute(It.IsAny<CommandA>()))
|
||||||
.Throws(new NotImplementedException());
|
// .Throws(new NotImplementedException());
|
||||||
|
//
|
||||||
Assert.Throws<NotImplementedException>(() => Subject.PublishCommand(commandA));
|
// Assert.Throws<NotImplementedException>(() => Subject.Push(commandA));
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
[Test]
|
// [Test]
|
||||||
public void broken_executor_should_publish_executed_event()
|
// public void broken_executor_should_publish_executed_event()
|
||||||
{
|
// {
|
||||||
var commandA = new CommandA();
|
// var commandA = new CommandA();
|
||||||
|
//
|
||||||
_executorA.Setup(c => c.Execute(It.IsAny<CommandA>()))
|
// _executorA.Setup(c => c.Execute(It.IsAny<CommandA>()))
|
||||||
.Throws(new NotImplementedException());
|
// .Throws(new NotImplementedException());
|
||||||
|
//
|
||||||
Assert.Throws<NotImplementedException>(() => Subject.PublishCommand(commandA));
|
// Assert.Throws<NotImplementedException>(() => Subject.Push(commandA));
|
||||||
|
//
|
||||||
VerifyEventPublished<CommandExecutedEvent>();
|
// VerifyEventPublished<CommandExecutedEvent>();
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
[Test]
|
// [Test]
|
||||||
public void should_publish_executed_event_on_success()
|
// public void should_publish_executed_event_on_success()
|
||||||
{
|
// {
|
||||||
var commandA = new CommandA();
|
// var commandA = new CommandA();
|
||||||
Subject.PublishCommand(commandA);
|
// Subject.Push(commandA);
|
||||||
|
//
|
||||||
VerifyEventPublished<CommandExecutedEvent>();
|
// VerifyEventPublished<CommandExecutedEvent>();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
public class CommandA : Command
|
// public class CommandA : Command
|
||||||
{
|
// {
|
||||||
public CommandA(int id = 0)
|
// public CommandA(int id = 0)
|
||||||
{
|
// {
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
public class CommandB : Command
|
// public class CommandB : Command
|
||||||
{
|
// {
|
||||||
|
//
|
||||||
public CommandB()
|
// public CommandB()
|
||||||
{
|
// {
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
}
|
//}
|
|
@ -1,19 +0,0 @@
|
||||||
using FluentAssertions;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using NzbDrone.Core.Update.Commands;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.Messaging.Commands
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class CommandFixture
|
|
||||||
{
|
|
||||||
[Test]
|
|
||||||
public void default_values()
|
|
||||||
{
|
|
||||||
var command = new ApplicationUpdateCommand();
|
|
||||||
|
|
||||||
command.Id.Should().NotBe(0);
|
|
||||||
command.Name.Should().Be("ApplicationUpdate");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -257,7 +257,6 @@
|
||||||
<Compile Include="MediaFiles\UpgradeMediaFileServiceFixture.cs" />
|
<Compile Include="MediaFiles\UpgradeMediaFileServiceFixture.cs" />
|
||||||
<Compile Include="Messaging\Commands\CommandEqualityComparerFixture.cs" />
|
<Compile Include="Messaging\Commands\CommandEqualityComparerFixture.cs" />
|
||||||
<Compile Include="Messaging\Commands\CommandExecutorFixture.cs" />
|
<Compile Include="Messaging\Commands\CommandExecutorFixture.cs" />
|
||||||
<Compile Include="Messaging\Commands\CommandFixture.cs" />
|
|
||||||
<Compile Include="Messaging\Events\EventAggregatorFixture.cs" />
|
<Compile Include="Messaging\Events\EventAggregatorFixture.cs" />
|
||||||
<Compile Include="Metadata\Consumers\Roksbox\FindMetadataFileFixture.cs" />
|
<Compile Include="Metadata\Consumers\Roksbox\FindMetadataFileFixture.cs" />
|
||||||
<Compile Include="Metadata\Consumers\Wdtv\FindMetadataFileFixture.cs" />
|
<Compile Include="Metadata\Consumers\Wdtv\FindMetadataFileFixture.cs" />
|
||||||
|
|
|
@ -8,7 +8,6 @@ using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.MediaFiles.Events;
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Core.Tv.Events;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.TvTests.SeriesAddedHandlerTests
|
namespace NzbDrone.Core.Test.TvTests.SeriesAddedHandlerTests
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,7 +12,7 @@ using NzbDrone.Common.Http;
|
||||||
using NzbDrone.Common.Model;
|
using NzbDrone.Common.Model;
|
||||||
using NzbDrone.Common.Processes;
|
using NzbDrone.Common.Processes;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Exceptions;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using NzbDrone.Core.Update;
|
using NzbDrone.Core.Update;
|
||||||
using NzbDrone.Core.Update.Commands;
|
using NzbDrone.Core.Update.Commands;
|
||||||
|
@ -163,7 +163,7 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||||
{
|
{
|
||||||
Mocker.GetMock<IVerifyUpdates>().Setup(c => c.Verify(It.IsAny<UpdatePackage>(), It.IsAny<String>())).Returns(false);
|
Mocker.GetMock<IVerifyUpdates>().Setup(c => c.Verify(It.IsAny<UpdatePackage>(), It.IsAny<String>())).Returns(false);
|
||||||
|
|
||||||
Subject.Execute(new ApplicationUpdateCommand());
|
Assert.Throws<CommandFailedException>(() => Subject.Execute(new ApplicationUpdateCommand()));
|
||||||
|
|
||||||
Mocker.GetMock<IArchiveService>().Verify(v => v.Extract(It.IsAny<String>(), It.IsAny<String>()), Times.Never());
|
Mocker.GetMock<IArchiveService>().Verify(v => v.Extract(It.IsAny<String>(), It.IsAny<String>()), Times.Never());
|
||||||
}
|
}
|
||||||
|
@ -189,7 +189,7 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||||
|
|
||||||
GivenInstallScript("");
|
GivenInstallScript("");
|
||||||
|
|
||||||
Subject.Execute(new ApplicationUpdateCommand());
|
Assert.Throws<CommandFailedException>(() => Subject.Execute(new ApplicationUpdateCommand()));
|
||||||
|
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
ExceptionVerification.ExpectedErrors(1);
|
||||||
Mocker.GetMock<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), null, null), Times.Never());
|
Mocker.GetMock<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), null, null), Times.Never());
|
||||||
|
@ -203,7 +203,7 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||||
|
|
||||||
GivenInstallScript(null);
|
GivenInstallScript(null);
|
||||||
|
|
||||||
Subject.Execute(new ApplicationUpdateCommand());
|
Assert.Throws<CommandFailedException>(() => Subject.Execute(new ApplicationUpdateCommand()));
|
||||||
|
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
ExceptionVerification.ExpectedErrors(1);
|
||||||
Mocker.GetMock<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), null, null), Times.Never());
|
Mocker.GetMock<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), null, null), Times.Never());
|
||||||
|
@ -221,7 +221,7 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||||
.Setup(s => s.FileExists(scriptPath, StringComparison.Ordinal))
|
.Setup(s => s.FileExists(scriptPath, StringComparison.Ordinal))
|
||||||
.Returns(false);
|
.Returns(false);
|
||||||
|
|
||||||
Subject.Execute(new ApplicationUpdateCommand());
|
Assert.Throws<CommandFailedException>(() => Subject.Execute(new ApplicationUpdateCommand()));
|
||||||
|
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
ExceptionVerification.ExpectedErrors(1);
|
||||||
Mocker.GetMock<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), null, null), Times.Never());
|
Mocker.GetMock<IProcessProvider>().Verify(v => v.Start(scriptPath, It.IsAny<String>(), null, null), Times.Never());
|
||||||
|
@ -255,7 +255,7 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||||
Mocker.GetMock<IAppFolderInfo>().SetupGet(c => c.StartUpFolder).Returns(@"C:\NzbDrone".AsOsAgnostic);
|
Mocker.GetMock<IAppFolderInfo>().SetupGet(c => c.StartUpFolder).Returns(@"C:\NzbDrone".AsOsAgnostic);
|
||||||
Mocker.GetMock<IAppFolderInfo>().SetupGet(c => c.AppDataFolder).Returns(@"C:\NzbDrone\AppData".AsOsAgnostic);
|
Mocker.GetMock<IAppFolderInfo>().SetupGet(c => c.AppDataFolder).Returns(@"C:\NzbDrone\AppData".AsOsAgnostic);
|
||||||
|
|
||||||
Subject.Execute(new ApplicationUpdateCommand());
|
Assert.Throws<CommandFailedException>(() => Subject.Execute(new ApplicationUpdateCommand()));
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
ExceptionVerification.ExpectedErrors(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,7 +265,7 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||||
Mocker.GetMock<IAppFolderInfo>().SetupGet(c => c.StartUpFolder).Returns(@"C:\NzbDrone".AsOsAgnostic);
|
Mocker.GetMock<IAppFolderInfo>().SetupGet(c => c.StartUpFolder).Returns(@"C:\NzbDrone".AsOsAgnostic);
|
||||||
Mocker.GetMock<IAppFolderInfo>().SetupGet(c => c.AppDataFolder).Returns(@"C:\NzbDrone".AsOsAgnostic);
|
Mocker.GetMock<IAppFolderInfo>().SetupGet(c => c.AppDataFolder).Returns(@"C:\NzbDrone".AsOsAgnostic);
|
||||||
|
|
||||||
Subject.Execute(new ApplicationUpdateCommand());
|
Assert.Throws<CommandFailedException>(() => Subject.Execute(new ApplicationUpdateCommand()));
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
ExceptionVerification.ExpectedErrors(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,7 +278,7 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||||
|
|
||||||
var updateArchive = Path.Combine(_sandboxFolder, _updatePackage.FileName);
|
var updateArchive = Path.Combine(_sandboxFolder, _updatePackage.FileName);
|
||||||
|
|
||||||
Subject.Execute(new ApplicationUpdateCommand());
|
Assert.Throws<CommandFailedException>(() => Subject.Execute(new ApplicationUpdateCommand()));
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(_updatePackage.Url, updateArchive), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(_updatePackage.Url, updateArchive), Times.Never());
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
ExceptionVerification.ExpectedErrors(1);
|
||||||
|
@ -293,7 +293,7 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||||
|
|
||||||
var updateArchive = Path.Combine(_sandboxFolder, _updatePackage.FileName);
|
var updateArchive = Path.Combine(_sandboxFolder, _updatePackage.FileName);
|
||||||
|
|
||||||
Subject.Execute(new ApplicationUpdateCommand());
|
Assert.Throws<CommandFailedException>(() => Subject.Execute(new ApplicationUpdateCommand()));
|
||||||
|
|
||||||
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(_updatePackage.Url, updateArchive), Times.Never());
|
Mocker.GetMock<IHttpClient>().Verify(c => c.DownloadFile(_updatePackage.Url, updateArchive), Times.Never());
|
||||||
ExceptionVerification.ExpectedErrors(1);
|
ExceptionVerification.ExpectedErrors(1);
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
using System;
|
||||||
|
using Marr.Data.Converters;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Reflection;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Converters
|
||||||
|
{
|
||||||
|
public class CommandConverter : EmbeddedDocumentConverter
|
||||||
|
{
|
||||||
|
public override object FromDB(ConverterContext context)
|
||||||
|
{
|
||||||
|
if (context.DbValue == DBNull.Value)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringValue = (string)context.DbValue;
|
||||||
|
|
||||||
|
if (stringValue.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ordinal = context.DataRecord.GetOrdinal("Name");
|
||||||
|
var contract = context.DataRecord.GetString(ordinal);
|
||||||
|
var impType = typeof (Command).Assembly.FindTypeByName(contract + "Command");
|
||||||
|
|
||||||
|
if (impType == null)
|
||||||
|
{
|
||||||
|
throw new CommandNotFoundException(contract);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Json.Deserialize(stringValue, impType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using Marr.Data.Converters;
|
||||||
|
using Marr.Data.Mapping;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Converters
|
||||||
|
{
|
||||||
|
public class TimeSpanConverter : IConverter
|
||||||
|
{
|
||||||
|
public object FromDB(ConverterContext context)
|
||||||
|
{
|
||||||
|
if (context.DbValue == DBNull.Value)
|
||||||
|
{
|
||||||
|
return TimeSpan.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TimeSpan.Parse(context.DbValue.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public object FromDB(ColumnMap map, object dbValue)
|
||||||
|
{
|
||||||
|
if (dbValue == DBNull.Value)
|
||||||
|
{
|
||||||
|
return DBNull.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dbValue is TimeSpan)
|
||||||
|
{
|
||||||
|
return dbValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TimeSpan.Parse(dbValue.ToString(), CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ToDB(object clrValue)
|
||||||
|
{
|
||||||
|
if (clrValue.ToString().IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((TimeSpan)clrValue).ToString("c", CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Type DbType { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(78)]
|
||||||
|
public class add_commands_table : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Create.TableForModel("Commands")
|
||||||
|
.WithColumn("Name").AsString().NotNullable()
|
||||||
|
.WithColumn("Body").AsString().NotNullable()
|
||||||
|
.WithColumn("Priority").AsInt32().NotNullable()
|
||||||
|
.WithColumn("Status").AsInt32().NotNullable()
|
||||||
|
.WithColumn("QueuedAt").AsDateTime().NotNullable()
|
||||||
|
.WithColumn("StartedAt").AsDateTime().Nullable()
|
||||||
|
.WithColumn("EndedAt").AsDateTime().Nullable()
|
||||||
|
.WithColumn("Duration").AsString().Nullable()
|
||||||
|
.WithColumn("Exception").AsString().Nullable()
|
||||||
|
.WithColumn("Trigger").AsInt32().NotNullable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ using NzbDrone.Core.ThingiProvider;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Core.Authentication;
|
using NzbDrone.Core.Authentication;
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Datastore
|
namespace NzbDrone.Core.Datastore
|
||||||
{
|
{
|
||||||
|
@ -103,6 +104,8 @@ namespace NzbDrone.Core.Datastore
|
||||||
|
|
||||||
Mapper.Entity<DelayProfile>().RegisterModel("DelayProfiles");
|
Mapper.Entity<DelayProfile>().RegisterModel("DelayProfiles");
|
||||||
Mapper.Entity<User>().RegisterModel("Users");
|
Mapper.Entity<User>().RegisterModel("Users");
|
||||||
|
Mapper.Entity<CommandModel>().RegisterModel("Commands")
|
||||||
|
.Ignore(c => c.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RegisterMappers()
|
private static void RegisterMappers()
|
||||||
|
@ -125,6 +128,9 @@ namespace NzbDrone.Core.Datastore
|
||||||
MapRepository.Instance.RegisterTypeConverter(typeof(HashSet<Int32>), new EmbeddedDocumentConverter());
|
MapRepository.Instance.RegisterTypeConverter(typeof(HashSet<Int32>), new EmbeddedDocumentConverter());
|
||||||
MapRepository.Instance.RegisterTypeConverter(typeof(OsPath), new OsPathConverter());
|
MapRepository.Instance.RegisterTypeConverter(typeof(OsPath), new OsPathConverter());
|
||||||
MapRepository.Instance.RegisterTypeConverter(typeof(Guid), new GuidConverter());
|
MapRepository.Instance.RegisterTypeConverter(typeof(Guid), new GuidConverter());
|
||||||
|
MapRepository.Instance.RegisterTypeConverter(typeof(Command), new CommandConverter());
|
||||||
|
MapRepository.Instance.RegisterTypeConverter(typeof(TimeSpan), new TimeSpanConverter());
|
||||||
|
MapRepository.Instance.RegisterTypeConverter(typeof(TimeSpan?), new TimeSpanConverter());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RegisterProviderSettingConverter()
|
private static void RegisterProviderSettingConverter()
|
||||||
|
|
|
@ -12,14 +12,17 @@ namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
private readonly IEpisodeService _episodeService;
|
private readonly IEpisodeService _episodeService;
|
||||||
private readonly ICommandExecutor _commandExecutor;
|
private readonly IManageCommandQueue _commandQueueManager;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public RedownloadFailedDownloadService(IConfigService configService, IEpisodeService episodeService, ICommandExecutor commandExecutor, Logger logger)
|
public RedownloadFailedDownloadService(IConfigService configService,
|
||||||
|
IEpisodeService episodeService,
|
||||||
|
IManageCommandQueue commandQueueManager,
|
||||||
|
Logger logger)
|
||||||
{
|
{
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_episodeService = episodeService;
|
_episodeService = episodeService;
|
||||||
_commandExecutor = commandExecutor;
|
_commandQueueManager = commandQueueManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +38,7 @@ namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
_logger.Debug("Failed download only contains one episode, searching again");
|
_logger.Debug("Failed download only contains one episode, searching again");
|
||||||
|
|
||||||
_commandExecutor.PublishCommandAsync(new EpisodeSearchCommand(message.EpisodeIds));
|
_commandQueueManager.Push(new EpisodeSearchCommand(message.EpisodeIds));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -47,7 +50,7 @@ namespace NzbDrone.Core.Download
|
||||||
{
|
{
|
||||||
_logger.Debug("Failed download was entire season, searching again");
|
_logger.Debug("Failed download was entire season, searching again");
|
||||||
|
|
||||||
_commandExecutor.PublishCommandAsync(new SeasonSearchCommand
|
_commandQueueManager.Push(new SeasonSearchCommand
|
||||||
{
|
{
|
||||||
SeriesId = message.SeriesId,
|
SeriesId = message.SeriesId,
|
||||||
SeasonNumber = seasonNumber
|
SeasonNumber = seasonNumber
|
||||||
|
@ -58,7 +61,7 @@ namespace NzbDrone.Core.Download
|
||||||
|
|
||||||
_logger.Debug("Failed download contains multiple episodes, probably a double episode, searching again");
|
_logger.Debug("Failed download contains multiple episodes, probably a double episode, searching again");
|
||||||
|
|
||||||
_commandExecutor.PublishCommandAsync(new EpisodeSearchCommand(message.EpisodeIds));
|
_commandQueueManager.Push(new EpisodeSearchCommand(message.EpisodeIds));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@ namespace NzbDrone.Core.HealthCheck
|
||||||
|
|
||||||
public void Execute(CheckHealthCommand message)
|
public void Execute(CheckHealthCommand message)
|
||||||
{
|
{
|
||||||
PerformHealthCheck(c => message.Manual || c.CheckOnSchedule);
|
PerformHealthCheck(c => message.Trigger == CommandTrigger.Manual || c.CheckOnSchedule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
|
{
|
||||||
|
public class CleanupCommandQueue : IHousekeepingTask
|
||||||
|
{
|
||||||
|
private readonly IManageCommandQueue _commandQueueManager;
|
||||||
|
|
||||||
|
public CleanupCommandQueue(IManageCommandQueue commandQueueManager)
|
||||||
|
{
|
||||||
|
_commandQueueManager = commandQueueManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clean()
|
||||||
|
{
|
||||||
|
_commandQueueManager.CleanCommands();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
using NzbDrone.Core.Instrumentation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Housekeeping.Housekeepers
|
||||||
|
{
|
||||||
|
public class TrimLogDatabase : IHousekeepingTask
|
||||||
|
{
|
||||||
|
private readonly ILogRepository _logRepo;
|
||||||
|
|
||||||
|
public TrimLogDatabase(ILogRepository logRepo)
|
||||||
|
{
|
||||||
|
_logRepo = logRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clean()
|
||||||
|
{
|
||||||
|
_logRepo.Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,8 @@ using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Housekeeping
|
namespace NzbDrone.Core.Housekeeping
|
||||||
{
|
{
|
||||||
public class HousekeepingService : IExecute<HousekeepingCommand>, IHandleAsync<ApplicationStartedEvent>
|
public class HousekeepingService : IExecute<HousekeepingCommand>,
|
||||||
|
IHandleAsync<ApplicationStartedEvent>
|
||||||
{
|
{
|
||||||
private readonly IEnumerable<IHousekeepingTask> _housekeepers;
|
private readonly IEnumerable<IHousekeepingTask> _housekeepers;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
@ -40,7 +41,7 @@ namespace NzbDrone.Core.Housekeeping
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vacuuming the log db isn't needed since that's done hourly at the TrimLogCommand.
|
// Vacuuming the log db isn't needed since that's done in a separate housekeeping task
|
||||||
_logger.Debug("Compressing main database after housekeeping");
|
_logger.Debug("Compressing main database after housekeeping");
|
||||||
_mainDb.Vacuum();
|
_mainDb.Vacuum();
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,9 +112,9 @@ namespace NzbDrone.Core.IndexerSearch
|
||||||
{
|
{
|
||||||
List<Episode> episodes;
|
List<Episode> episodes;
|
||||||
|
|
||||||
if (message.SeriesId > 0)
|
if (message.SeriesId.HasValue)
|
||||||
{
|
{
|
||||||
episodes = _episodeService.GetEpisodeBySeries(message.SeriesId)
|
episodes = _episodeService.GetEpisodeBySeries(message.SeriesId.Value)
|
||||||
.Where(e => e.Monitored &&
|
.Where(e => e.Monitored &&
|
||||||
!e.HasFile &&
|
!e.HasFile &&
|
||||||
e.AirDateUtc.HasValue &&
|
e.AirDateUtc.HasValue &&
|
||||||
|
|
|
@ -4,7 +4,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||||
{
|
{
|
||||||
public class MissingEpisodeSearchCommand : Command
|
public class MissingEpisodeSearchCommand : Command
|
||||||
{
|
{
|
||||||
public int SeriesId { get; private set; }
|
public int? SeriesId { get; private set; }
|
||||||
|
|
||||||
public override bool SendUpdatesToClient
|
public override bool SendUpdatesToClient
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Instrumentation.Commands
|
|
||||||
{
|
|
||||||
public class TrimLogCommand : Command
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,7 +9,7 @@ namespace NzbDrone.Core.Instrumentation
|
||||||
PagingSpec<Log> Paged(PagingSpec<Log> pagingSpec);
|
PagingSpec<Log> Paged(PagingSpec<Log> pagingSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LogService : ILogService, IExecute<TrimLogCommand>, IExecute<ClearLogCommand>
|
public class LogService : ILogService, IExecute<ClearLogCommand>
|
||||||
{
|
{
|
||||||
private readonly ILogRepository _logRepository;
|
private readonly ILogRepository _logRepository;
|
||||||
|
|
||||||
|
@ -23,11 +23,6 @@ namespace NzbDrone.Core.Instrumentation
|
||||||
return _logRepository.GetPaged(pagingSpec);
|
return _logRepository.GetPaged(pagingSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Execute(TrimLogCommand message)
|
|
||||||
{
|
|
||||||
_logRepository.Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Execute(ClearLogCommand message)
|
public void Execute(ClearLogCommand message)
|
||||||
{
|
{
|
||||||
_logRepository.Purge(vacuum: true);
|
_logRepository.Purge(vacuum: true);
|
||||||
|
|
|
@ -12,7 +12,6 @@ namespace NzbDrone.Core.Jobs
|
||||||
void SetLastExecutionTime(int id, DateTime executionTime);
|
void SetLastExecutionTime(int id, DateTime executionTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class ScheduledTaskRepository : BasicRepository<ScheduledTask>, IScheduledTaskRepository
|
public class ScheduledTaskRepository : BasicRepository<ScheduledTask>, IScheduledTaskRepository
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
@ -15,15 +16,15 @@ namespace NzbDrone.Core.Jobs
|
||||||
IHandle<ApplicationShutdownRequested>
|
IHandle<ApplicationShutdownRequested>
|
||||||
{
|
{
|
||||||
private readonly ITaskManager _taskManager;
|
private readonly ITaskManager _taskManager;
|
||||||
private readonly ICommandExecutor _commandExecutor;
|
private readonly IManageCommandQueue _commandQueueManager;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
private static readonly Timer Timer = new Timer();
|
private static readonly Timer Timer = new Timer();
|
||||||
private static CancellationTokenSource _cancellationTokenSource;
|
private static CancellationTokenSource _cancellationTokenSource;
|
||||||
|
|
||||||
public Scheduler(ITaskManager taskManager, ICommandExecutor commandExecutor, Logger logger)
|
public Scheduler(ITaskManager taskManager, IManageCommandQueue commandQueueManager, Logger logger)
|
||||||
{
|
{
|
||||||
_taskManager = taskManager;
|
_taskManager = taskManager;
|
||||||
_commandExecutor = commandExecutor;
|
_commandQueueManager = commandQueueManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,24 +34,16 @@ namespace NzbDrone.Core.Jobs
|
||||||
{
|
{
|
||||||
Timer.Enabled = false;
|
Timer.Enabled = false;
|
||||||
|
|
||||||
var tasks = _taskManager.GetPending();
|
var tasks = _taskManager.GetPending().ToList();
|
||||||
|
|
||||||
_logger.Trace("Pending Tasks: {0}", tasks.Count);
|
_logger.Trace("Pending Tasks: {0}", tasks.Count);
|
||||||
|
|
||||||
foreach (var task in tasks)
|
foreach (var task in tasks)
|
||||||
{
|
{
|
||||||
_cancellationTokenSource.Token.ThrowIfCancellationRequested();
|
_commandQueueManager.Push(task.TypeName, task.LastExecution, CommandPriority.Low, CommandTrigger.Scheduled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_commandExecutor.PublishCommand(task.TypeName, task.LastExecution);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
_logger.ErrorException("Error occurred while executing task " + task.TypeName, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (!_cancellationTokenSource.IsCancellationRequested)
|
if (!_cancellationTokenSource.IsCancellationRequested)
|
||||||
|
|
|
@ -10,10 +10,9 @@ using NzbDrone.Core.Download;
|
||||||
using NzbDrone.Core.HealthCheck;
|
using NzbDrone.Core.HealthCheck;
|
||||||
using NzbDrone.Core.Housekeeping;
|
using NzbDrone.Core.Housekeeping;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
using NzbDrone.Core.Instrumentation.Commands;
|
|
||||||
using NzbDrone.Core.Lifecycle;
|
using NzbDrone.Core.Lifecycle;
|
||||||
using NzbDrone.Core.MediaFiles.Commands;
|
using NzbDrone.Core.MediaFiles.Commands;
|
||||||
using NzbDrone.Core.Messaging.Commands.Tracking;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Tv.Commands;
|
using NzbDrone.Core.Tv.Commands;
|
||||||
using NzbDrone.Core.Update.Commands;
|
using NzbDrone.Core.Update.Commands;
|
||||||
|
@ -62,10 +61,9 @@ namespace NzbDrone.Core.Jobs
|
||||||
{
|
{
|
||||||
var defaultTasks = new[]
|
var defaultTasks = new[]
|
||||||
{
|
{
|
||||||
new ScheduledTask{ Interval = 1, TypeName = typeof(TrackedCommandCleanupCommand).FullName},
|
|
||||||
new ScheduledTask{ Interval = 1, TypeName = typeof(CheckForFinishedDownloadCommand).FullName},
|
new ScheduledTask{ Interval = 1, TypeName = typeof(CheckForFinishedDownloadCommand).FullName},
|
||||||
|
new ScheduledTask{ Interval = 5, TypeName = typeof(MessagingCleanupCommand).FullName},
|
||||||
new ScheduledTask{ Interval = 6*60, TypeName = typeof(ApplicationUpdateCommand).FullName},
|
new ScheduledTask{ Interval = 6*60, TypeName = typeof(ApplicationUpdateCommand).FullName},
|
||||||
new ScheduledTask{ Interval = 1*60, TypeName = typeof(TrimLogCommand).FullName},
|
|
||||||
new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName},
|
new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName},
|
||||||
new ScheduledTask{ Interval = 6*60, TypeName = typeof(CheckHealthCommand).FullName},
|
new ScheduledTask{ Interval = 6*60, TypeName = typeof(CheckHealthCommand).FullName},
|
||||||
new ScheduledTask{ Interval = 12*60, TypeName = typeof(RefreshSeriesCommand).FullName},
|
new ScheduledTask{ Interval = 12*60, TypeName = typeof(RefreshSeriesCommand).FullName},
|
||||||
|
@ -127,7 +125,7 @@ namespace NzbDrone.Core.Jobs
|
||||||
|
|
||||||
public void Handle(CommandExecutedEvent message)
|
public void Handle(CommandExecutedEvent message)
|
||||||
{
|
{
|
||||||
var scheduledTask = _scheduledTaskRepository.All().SingleOrDefault(c => c.TypeName == message.Command.GetType().FullName);
|
var scheduledTask = _scheduledTaskRepository.All().SingleOrDefault(c => c.TypeName == message.Command.Body.GetType().FullName);
|
||||||
|
|
||||||
if (scheduledTask != null)
|
if (scheduledTask != null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles.Commands
|
|
||||||
{
|
|
||||||
public class CleanMediaFileDb : Command
|
|
||||||
{
|
|
||||||
public int SeriesId { get; private set; }
|
|
||||||
|
|
||||||
public CleanMediaFileDb(int seriesId)
|
|
||||||
{
|
|
||||||
SeriesId = seriesId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -33,27 +33,27 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly IMakeImportDecision _importDecisionMaker;
|
private readonly IMakeImportDecision _importDecisionMaker;
|
||||||
private readonly IImportApprovedEpisodes _importApprovedEpisodes;
|
private readonly IImportApprovedEpisodes _importApprovedEpisodes;
|
||||||
private readonly ICommandExecutor _commandExecutor;
|
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
private readonly ISeriesService _seriesService;
|
private readonly ISeriesService _seriesService;
|
||||||
|
private readonly IMediaFileTableCleanupService _mediaFileTableCleanupService;
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public DiskScanService(IDiskProvider diskProvider,
|
public DiskScanService(IDiskProvider diskProvider,
|
||||||
IMakeImportDecision importDecisionMaker,
|
IMakeImportDecision importDecisionMaker,
|
||||||
IImportApprovedEpisodes importApprovedEpisodes,
|
IImportApprovedEpisodes importApprovedEpisodes,
|
||||||
ICommandExecutor commandExecutor,
|
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
ISeriesService seriesService,
|
ISeriesService seriesService,
|
||||||
|
IMediaFileTableCleanupService mediaFileTableCleanupService,
|
||||||
IEventAggregator eventAggregator,
|
IEventAggregator eventAggregator,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_importDecisionMaker = importDecisionMaker;
|
_importDecisionMaker = importDecisionMaker;
|
||||||
_importApprovedEpisodes = importApprovedEpisodes;
|
_importApprovedEpisodes = importApprovedEpisodes;
|
||||||
_commandExecutor = commandExecutor;
|
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_seriesService = seriesService;
|
_seriesService = seriesService;
|
||||||
|
_mediaFileTableCleanupService = mediaFileTableCleanupService;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.ProgressInfo("Scanning disk for {0}", series.Title);
|
_logger.ProgressInfo("Scanning disk for {0}", series.Title);
|
||||||
_commandExecutor.PublishCommand(new CleanMediaFileDb(series.Id));
|
_mediaFileTableCleanupService.Clean(series);
|
||||||
|
|
||||||
if (!_diskProvider.FolderExists(series.Path))
|
if (!_diskProvider.FolderExists(series.Path))
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,19 +3,20 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Disk;
|
using NzbDrone.Common.Disk;
|
||||||
using NzbDrone.Core.MediaFiles.Commands;
|
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.MediaFiles
|
namespace NzbDrone.Core.MediaFiles
|
||||||
{
|
{
|
||||||
|
public interface IMediaFileTableCleanupService
|
||||||
|
{
|
||||||
|
void Clean(Series series);
|
||||||
|
}
|
||||||
|
|
||||||
public class MediaFileTableCleanupService : IExecute<CleanMediaFileDb>
|
public class MediaFileTableCleanupService : IMediaFileTableCleanupService
|
||||||
{
|
{
|
||||||
private readonly IMediaFileService _mediaFileService;
|
private readonly IMediaFileService _mediaFileService;
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
private readonly IEpisodeService _episodeService;
|
private readonly IEpisodeService _episodeService;
|
||||||
private readonly ISeriesService _seriesService;
|
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public MediaFileTableCleanupService(IMediaFileService mediaFileService,
|
public MediaFileTableCleanupService(IMediaFileService mediaFileService,
|
||||||
|
@ -27,15 +28,13 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
_mediaFileService = mediaFileService;
|
_mediaFileService = mediaFileService;
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
_episodeService = episodeService;
|
_episodeService = episodeService;
|
||||||
_seriesService = seriesService;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Execute(CleanMediaFileDb message)
|
public void Clean(Series series)
|
||||||
{
|
{
|
||||||
var seriesFile = _mediaFileService.GetFilesBySeries(message.SeriesId);
|
var seriesFile = _mediaFileService.GetFilesBySeries(series.Id);
|
||||||
var series = _seriesService.GetSeries(message.SeriesId);
|
var episodes = _episodeService.GetEpisodeBySeries(series.Id);
|
||||||
var episodes = _episodeService.GetEpisodeBySeries(message.SeriesId);
|
|
||||||
|
|
||||||
foreach (var episodeFile in seriesFile)
|
foreach (var episodeFile in seriesFile)
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
namespace NzbDrone.Core.Messaging.Commands
|
||||||
|
{
|
||||||
|
public class CleanupCommandMessagingService : IExecute<MessagingCleanupCommand>
|
||||||
|
{
|
||||||
|
private readonly IManageCommandQueue _commandQueueManager;
|
||||||
|
|
||||||
|
public CleanupCommandMessagingService(IManageCommandQueue commandQueueManager)
|
||||||
|
{
|
||||||
|
_commandQueueManager = commandQueueManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(MessagingCleanupCommand message)
|
||||||
|
{
|
||||||
|
_commandQueueManager.CleanCommands();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using FluentMigrator.Runner;
|
|
||||||
using NzbDrone.Common.Messaging;
|
|
||||||
using NzbDrone.Core.Datastore;
|
|
||||||
using NzbDrone.Core.Messaging.Commands.Tracking;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Messaging.Commands
|
namespace NzbDrone.Core.Messaging.Commands
|
||||||
{
|
{
|
||||||
public abstract class Command : ModelBase, IMessage
|
public abstract class Command
|
||||||
{
|
{
|
||||||
private static readonly object Mutex = new object();
|
|
||||||
private static int _idCounter;
|
|
||||||
private readonly StopWatch _stopWatch;
|
|
||||||
|
|
||||||
public CommandStatus State { get; private set; }
|
|
||||||
public DateTime StateChangeTime { get; private set; }
|
|
||||||
|
|
||||||
public virtual Boolean SendUpdatesToClient
|
public virtual Boolean SendUpdatesToClient
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -23,63 +12,21 @@ namespace NzbDrone.Core.Messaging.Commands
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeSpan Runtime
|
public virtual string CompletionMessage
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return _stopWatch.ElapsedTime();
|
return "Completed";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean Manual { get; set; }
|
|
||||||
public Exception Exception { get; private set; }
|
|
||||||
public String Message { get; private set; }
|
|
||||||
|
|
||||||
public String Name { get; private set; }
|
public String Name { get; private set; }
|
||||||
public DateTime? LastExecutionTime { get; set; }
|
public DateTime? LastExecutionTime { get; set; }
|
||||||
|
public CommandTrigger Trigger { get; set; }
|
||||||
|
|
||||||
protected Command()
|
public Command()
|
||||||
{
|
{
|
||||||
Name = GetType().Name.Replace("Command", "");
|
Name = GetType().Name.Replace("Command", "");
|
||||||
StateChangeTime = DateTime.UtcNow;
|
|
||||||
State = CommandStatus.Pending;
|
|
||||||
_stopWatch = new StopWatch();
|
|
||||||
Manual = false;
|
|
||||||
|
|
||||||
lock (Mutex)
|
|
||||||
{
|
|
||||||
Id = ++_idCounter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start()
|
|
||||||
{
|
|
||||||
_stopWatch.Start();
|
|
||||||
StateChangeTime = DateTime.UtcNow;
|
|
||||||
State = CommandStatus.Running;
|
|
||||||
SetMessage("Starting");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Failed(Exception exception, string message = "Failed")
|
|
||||||
{
|
|
||||||
_stopWatch.Stop();
|
|
||||||
StateChangeTime = DateTime.UtcNow;
|
|
||||||
State = CommandStatus.Failed;
|
|
||||||
Exception = exception;
|
|
||||||
SetMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Completed(string message = "Completed")
|
|
||||||
{
|
|
||||||
_stopWatch.Stop();
|
|
||||||
StateChangeTime = DateTime.UtcNow;
|
|
||||||
State = CommandStatus.Completed;
|
|
||||||
SetMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetMessage(string message)
|
|
||||||
{
|
|
||||||
Message = message;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -69,7 +69,7 @@ namespace NzbDrone.Core.Messaging.Commands
|
||||||
|
|
||||||
public int GetHashCode(Command obj)
|
public int GetHashCode(Command obj)
|
||||||
{
|
{
|
||||||
return obj.Id.GetHashCode();
|
return obj.GetHashCode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,117 +1,59 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
using NzbDrone.Common.EnsureThat;
|
using NzbDrone.Core.Lifecycle;
|
||||||
using NzbDrone.Common.Serializer;
|
|
||||||
using NzbDrone.Common.TPL;
|
|
||||||
using NzbDrone.Core.Messaging.Commands.Tracking;
|
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.ProgressMessaging;
|
using NzbDrone.Core.ProgressMessaging;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Messaging.Commands
|
namespace NzbDrone.Core.Messaging.Commands
|
||||||
{
|
{
|
||||||
public class CommandExecutor : ICommandExecutor
|
public class CommandExecutor : IHandle<ApplicationStartedEvent>,
|
||||||
|
IHandle<ApplicationShutdownRequested>
|
||||||
{
|
{
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
private readonly IServiceFactory _serviceFactory;
|
private readonly IServiceFactory _serviceFactory;
|
||||||
private readonly ITrackCommands _trackCommands;
|
private readonly IManageCommandQueue _commandQueueManager;
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly TaskFactory _taskFactory;
|
|
||||||
|
|
||||||
public CommandExecutor(Logger logger, IServiceFactory serviceFactory, ITrackCommands trackCommands, IEventAggregator eventAggregator)
|
private static CancellationTokenSource _cancellationTokenSource;
|
||||||
|
private const int THREAD_LIMIT = 3;
|
||||||
|
|
||||||
|
public CommandExecutor(IServiceFactory serviceFactory,
|
||||||
|
IManageCommandQueue commandQueueManager,
|
||||||
|
IEventAggregator eventAggregator,
|
||||||
|
Logger logger)
|
||||||
{
|
{
|
||||||
var scheduler = new LimitedConcurrencyLevelTaskScheduler(3);
|
|
||||||
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_serviceFactory = serviceFactory;
|
_serviceFactory = serviceFactory;
|
||||||
_trackCommands = trackCommands;
|
_commandQueueManager = commandQueueManager;
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_taskFactory = new TaskFactory(scheduler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PublishCommand<TCommand>(TCommand command) where TCommand : Command
|
private void ExecuteCommands()
|
||||||
{
|
{
|
||||||
Ensure.That(command, () => command).IsNotNull();
|
try
|
||||||
|
|
||||||
_logger.Trace("Publishing {0}", command.GetType().Name);
|
|
||||||
|
|
||||||
if (_trackCommands.FindExisting(command) != null)
|
|
||||||
{
|
{
|
||||||
_logger.Trace("Command is already in progress: {0}", command.GetType().Name);
|
foreach (var command in _commandQueueManager.Queue(_cancellationTokenSource.Token))
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_trackCommands.Store(command);
|
|
||||||
|
|
||||||
ExecuteCommand<TCommand>(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PublishCommand(string commandTypeName)
|
|
||||||
{
|
{
|
||||||
PublishCommand(commandTypeName, null);
|
try
|
||||||
}
|
|
||||||
|
|
||||||
public void PublishCommand(string commandTypeName, DateTime? lastExecutionTime)
|
|
||||||
{
|
{
|
||||||
dynamic command = GetCommand(commandTypeName);
|
ExecuteCommand((dynamic)command.Body, command);
|
||||||
command.LastExecutionTime = lastExecutionTime;
|
|
||||||
|
|
||||||
PublishCommand(command);
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
public Command PublishCommandAsync<TCommand>(TCommand command) where TCommand : Command
|
|
||||||
{
|
{
|
||||||
Ensure.That(command, () => command).IsNotNull();
|
_logger.ErrorException("Error occurred while executing task " + command.Name, ex);
|
||||||
|
}
|
||||||
_logger.Trace("Publishing {0}", command.GetType().Name);
|
}
|
||||||
|
}
|
||||||
var existingCommand = _trackCommands.FindExisting(command);
|
catch (ThreadAbortException ex)
|
||||||
|
|
||||||
if (existingCommand != null)
|
|
||||||
{
|
{
|
||||||
_logger.Trace("Command is already in progress: {0}", command.GetType().Name);
|
_logger.ErrorException(ex.Message, ex);
|
||||||
return existingCommand;
|
Thread.ResetAbort();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_trackCommands.Store(command);
|
private void ExecuteCommand<TCommand>(TCommand command, CommandModel commandModel) where TCommand : Command
|
||||||
|
|
||||||
// TODO: We should use async await (once we get 4.5) or normal Task Continuations on Command processing to prevent blocking the TaskScheduler.
|
|
||||||
// For now we use TaskCreationOptions 0x10, which is actually .net 4.5 HideScheduler.
|
|
||||||
// This will detach the scheduler from the thread, causing new Task creating in the command to be executed on the ThreadPool, avoiding a deadlock.
|
|
||||||
// Please note that the issue only shows itself on mono because since Microsoft .net implementation supports Task inlining on WaitAll.
|
|
||||||
if (Enum.IsDefined(typeof(TaskCreationOptions), (TaskCreationOptions)0x10))
|
|
||||||
{
|
|
||||||
_taskFactory.StartNew(() => ExecuteCommand<TCommand>(command)
|
|
||||||
, TaskCreationOptions.PreferFairness | (TaskCreationOptions)0x10)
|
|
||||||
.LogExceptions();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_taskFactory.StartNew(() => ExecuteCommand<TCommand>(command)
|
|
||||||
, TaskCreationOptions.PreferFairness)
|
|
||||||
.LogExceptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
return command;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Command PublishCommandAsync(string commandTypeName)
|
|
||||||
{
|
|
||||||
dynamic command = GetCommand(commandTypeName);
|
|
||||||
return PublishCommandAsync(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
private dynamic GetCommand(string commandTypeName)
|
|
||||||
{
|
|
||||||
var commandType = _serviceFactory.GetImplementations(typeof(Command))
|
|
||||||
.Single(c => c.FullName.Equals(commandTypeName, StringComparison.InvariantCultureIgnoreCase));
|
|
||||||
|
|
||||||
return Json.Deserialize("{}", commandType);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExecuteCommand<TCommand>(Command command) where TCommand : Command
|
|
||||||
{
|
{
|
||||||
var handlerContract = typeof(IExecute<>).MakeGenericType(command.GetType());
|
var handlerContract = typeof(IExecute<>).MakeGenericType(command.GetType());
|
||||||
var handler = (IExecute<TCommand>)_serviceFactory.Build(handlerContract);
|
var handler = (IExecute<TCommand>)_serviceFactory.Build(handlerContract);
|
||||||
|
@ -120,47 +62,67 @@ namespace NzbDrone.Core.Messaging.Commands
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_trackCommands.Start(command);
|
_commandQueueManager.Start(commandModel);
|
||||||
BroadcastCommandUpdate(command);
|
BroadcastCommandUpdate(commandModel);
|
||||||
|
|
||||||
if (!MappedDiagnosticsContext.Contains("CommandId") && command.SendUpdatesToClient)
|
if (!MappedDiagnosticsContext.Contains("CommandId"))
|
||||||
{
|
{
|
||||||
MappedDiagnosticsContext.Set("CommandId", command.Id.ToString());
|
MappedDiagnosticsContext.Set("CommandId", commandModel.Id.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.Execute((TCommand)command);
|
handler.Execute(command);
|
||||||
|
|
||||||
if (command.State == CommandStatus.Running)
|
_commandQueueManager.Complete(commandModel, command.CompletionMessage);
|
||||||
{
|
|
||||||
_trackCommands.Completed(command);
|
|
||||||
}
|
}
|
||||||
}
|
catch (CommandFailedException ex)
|
||||||
catch (Exception e)
|
|
||||||
{
|
{
|
||||||
_trackCommands.Failed(command, e);
|
_commandQueueManager.Fail(commandModel, ex.Message, ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_commandQueueManager.SetMessage(commandModel, "Failed");
|
||||||
|
_commandQueueManager.Fail(commandModel, "Failed", ex);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
BroadcastCommandUpdate(command);
|
BroadcastCommandUpdate(commandModel);
|
||||||
_eventAggregator.PublishEvent(new CommandExecutedEvent(command));
|
|
||||||
|
|
||||||
if (MappedDiagnosticsContext.Get("CommandId") == command.Id.ToString())
|
_eventAggregator.PublishEvent(new CommandExecutedEvent(commandModel));
|
||||||
|
|
||||||
|
if (MappedDiagnosticsContext.Get("CommandId") == commandModel.Id.ToString())
|
||||||
{
|
{
|
||||||
MappedDiagnosticsContext.Remove("CommandId");
|
MappedDiagnosticsContext.Remove("CommandId");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Trace("{0} <- {1} [{2}]", command.GetType().Name, handler.GetType().Name, command.Runtime.ToString(""));
|
_logger.Trace("{0} <- {1} [{2}]", command.GetType().Name, handler.GetType().Name, commandModel.Duration.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BroadcastCommandUpdate(CommandModel command)
|
||||||
private void BroadcastCommandUpdate(Command command)
|
|
||||||
{
|
{
|
||||||
if (command.SendUpdatesToClient)
|
if (command.Body.SendUpdatesToClient)
|
||||||
{
|
{
|
||||||
_eventAggregator.PublishEvent(new CommandUpdatedEvent(command));
|
_eventAggregator.PublishEvent(new CommandUpdatedEvent(command));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Handle(ApplicationStartedEvent message)
|
||||||
|
{
|
||||||
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
for (int i = 0; i < THREAD_LIMIT; i++)
|
||||||
|
{
|
||||||
|
var thread = new Thread(ExecuteCommands);
|
||||||
|
thread.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(ApplicationShutdownRequested message)
|
||||||
|
{
|
||||||
|
_logger.Info("Shutting down task execution");
|
||||||
|
_cancellationTokenSource.Cancel(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
using System;
|
||||||
|
using NzbDrone.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Messaging.Commands
|
||||||
|
{
|
||||||
|
public class CommandFailedException : NzbDroneException
|
||||||
|
{
|
||||||
|
public CommandFailedException(string message, params object[] args) : base(message, args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandFailedException(string message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandFailedException(string message, Exception innerException, params object[] args) : base(message, innerException, args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandFailedException(string message, Exception innerException) : base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandFailedException(Exception innerException)
|
||||||
|
: base("Failed", innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
using System;
|
||||||
|
using NzbDrone.Common.Messaging;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Messaging.Commands
|
||||||
|
{
|
||||||
|
public class CommandModel : ModelBase, IMessage
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public Command Body { get; set; }
|
||||||
|
public CommandPriority Priority { get; set; }
|
||||||
|
public CommandStatus Status { get; set; }
|
||||||
|
public DateTime QueuedAt { get; set; }
|
||||||
|
public DateTime? StartedAt { get; set; }
|
||||||
|
public DateTime? EndedAt { get; set; }
|
||||||
|
public TimeSpan? Duration { get; set; }
|
||||||
|
public string Exception { get; set; }
|
||||||
|
public CommandTrigger Trigger { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
using NzbDrone.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Messaging.Commands
|
||||||
|
{
|
||||||
|
public class CommandNotFoundException : NzbDroneException
|
||||||
|
{
|
||||||
|
public CommandNotFoundException(string contract)
|
||||||
|
: base("Couldn't find command " + contract)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Messaging.Commands
|
||||||
|
{
|
||||||
|
public enum CommandPriority
|
||||||
|
{
|
||||||
|
Low = -1,
|
||||||
|
Normal = 0,
|
||||||
|
High = 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Messaging.Commands
|
||||||
|
{
|
||||||
|
public class CommandQueue : IProducerConsumerCollection<CommandModel>
|
||||||
|
{
|
||||||
|
private object Mutex = new object();
|
||||||
|
|
||||||
|
private List<CommandModel> _items;
|
||||||
|
|
||||||
|
public CommandQueue()
|
||||||
|
{
|
||||||
|
_items = new List<CommandModel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<CommandModel> GetEnumerator()
|
||||||
|
{
|
||||||
|
List<CommandModel> copy = null;
|
||||||
|
|
||||||
|
lock (Mutex)
|
||||||
|
{
|
||||||
|
copy = new List<CommandModel>(_items);
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(Array array, int index)
|
||||||
|
{
|
||||||
|
lock (Mutex)
|
||||||
|
{
|
||||||
|
((ICollection)_items).CopyTo(array, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _items.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public object SyncRoot
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Mutex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsSynchronized
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyTo(CommandModel[] array, int index)
|
||||||
|
{
|
||||||
|
lock (Mutex)
|
||||||
|
{
|
||||||
|
_items.CopyTo(array, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryAdd(CommandModel item)
|
||||||
|
{
|
||||||
|
Add(item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryTake(out CommandModel item)
|
||||||
|
{
|
||||||
|
bool rval = true;
|
||||||
|
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();
|
||||||
|
|
||||||
|
_items.Remove(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,204 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Messaging.Commands
|
||||||
|
{
|
||||||
|
public interface IManageCommandQueue
|
||||||
|
{
|
||||||
|
CommandModel Push<TCommand>(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<CommandModel> Queue(CancellationToken cancellationToken);
|
||||||
|
CommandModel Get(int id);
|
||||||
|
List<CommandModel> GetStarted();
|
||||||
|
void SetMessage(CommandModel command, string message);
|
||||||
|
void Start(CommandModel command);
|
||||||
|
void Complete(CommandModel command, string message);
|
||||||
|
void Fail(CommandModel command, string message, Exception e);
|
||||||
|
void Requeue();
|
||||||
|
void CleanCommands();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CommandQueueManager : IManageCommandQueue, IHandle<ApplicationStartedEvent>
|
||||||
|
{
|
||||||
|
private readonly ICommandRepository _repo;
|
||||||
|
private readonly IServiceFactory _serviceFactory;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
private readonly ICached<CommandModel> _commandCache;
|
||||||
|
private readonly BlockingCollection<CommandModel> _commandQueue;
|
||||||
|
|
||||||
|
public CommandQueueManager(ICommandRepository repo,
|
||||||
|
IServiceFactory serviceFactory,
|
||||||
|
ICacheManager cacheManager,
|
||||||
|
Logger logger)
|
||||||
|
{
|
||||||
|
_repo = repo;
|
||||||
|
_serviceFactory = serviceFactory;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
|
_commandCache = cacheManager.GetCache<CommandModel>(GetType());
|
||||||
|
_commandQueue = new BlockingCollection<CommandModel>(new CommandQueue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandModel Push<TCommand>(TCommand command, CommandPriority priority = CommandPriority.Normal, CommandTrigger trigger = CommandTrigger.Unspecified) where TCommand : Command
|
||||||
|
{
|
||||||
|
Ensure.That(command, () => command).IsNotNull();
|
||||||
|
|
||||||
|
_logger.Trace("Publishing {0}", command.Name);
|
||||||
|
_logger.Trace("Checking if command is queued or started: {0}", command.Name);
|
||||||
|
|
||||||
|
var existingCommands = _repo.FindQueuedOrStarted(command.Name);
|
||||||
|
var existing = existingCommands.SingleOrDefault(c => CommandEqualityComparer.Instance.Equals(c.Body, command));
|
||||||
|
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
_logger.Trace("Command is already in progress: {0}", command.Name);
|
||||||
|
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandModel = new CommandModel
|
||||||
|
{
|
||||||
|
Name = command.Name,
|
||||||
|
Body = command,
|
||||||
|
QueuedAt = DateTime.UtcNow,
|
||||||
|
Trigger = trigger,
|
||||||
|
Priority = priority,
|
||||||
|
Status = CommandStatus.Queued
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.Trace("Inserting new command: {0}", commandModel.Name);
|
||||||
|
|
||||||
|
_repo.Insert(commandModel);
|
||||||
|
_commandQueue.Add(commandModel);
|
||||||
|
_commandCache.Set(commandModel.Id.ToString(), commandModel);
|
||||||
|
|
||||||
|
return commandModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandModel Push(string commandName, DateTime? lastExecutionTime, CommandPriority priority = CommandPriority.Normal, CommandTrigger trigger = CommandTrigger.Unspecified)
|
||||||
|
{
|
||||||
|
dynamic command = GetCommand(commandName);
|
||||||
|
command.LastExecutionTime = lastExecutionTime;
|
||||||
|
command.Trigger = trigger;
|
||||||
|
|
||||||
|
return Push(command, priority, trigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<CommandModel> Queue(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return _commandQueue.GetConsumingEnumerable(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandModel Get(int id)
|
||||||
|
{
|
||||||
|
return _commandCache.Get(id.ToString(), () => FindMessage(_repo.Get(id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CommandModel> GetStarted()
|
||||||
|
{
|
||||||
|
_logger.Trace("Getting started commands");
|
||||||
|
return _commandCache.Values.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;
|
||||||
|
|
||||||
|
_logger.Trace("Marking command as started: {0}", command.Name);
|
||||||
|
_repo.Update(command);
|
||||||
|
_commandCache.Set(command.Id.ToString(), command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Complete(CommandModel command, string message)
|
||||||
|
{
|
||||||
|
Update(command, CommandStatus.Completed, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Fail(CommandModel command, string message, Exception e)
|
||||||
|
{
|
||||||
|
command.Exception = e.ToString();
|
||||||
|
|
||||||
|
Update(command, CommandStatus.Failed, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Requeue()
|
||||||
|
{
|
||||||
|
foreach (var command in _repo.Queued())
|
||||||
|
{
|
||||||
|
_commandQueue.Add(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CleanCommands()
|
||||||
|
{
|
||||||
|
_logger.Trace("Cleaning up old commands");
|
||||||
|
_repo.Trim();
|
||||||
|
|
||||||
|
var old = _commandCache.Values.Where(c => c.EndedAt < DateTime.UtcNow.AddMinutes(5));
|
||||||
|
|
||||||
|
foreach (var command in old)
|
||||||
|
{
|
||||||
|
_commandCache.Remove(command.Id.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private dynamic GetCommand(string commandName)
|
||||||
|
{
|
||||||
|
commandName = commandName.Split('.').Last();
|
||||||
|
|
||||||
|
var commandType = _serviceFactory.GetImplementations(typeof(Command))
|
||||||
|
.Single(c => c.Name.Equals(commandName, StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
|
||||||
|
return Json.Deserialize("{}", commandType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CommandModel FindMessage(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);
|
||||||
|
|
||||||
|
command.EndedAt = DateTime.UtcNow;
|
||||||
|
command.Duration = command.EndedAt.Value.Subtract(command.StartedAt.Value);
|
||||||
|
command.Status = status;
|
||||||
|
|
||||||
|
_logger.Trace("Updating command status");
|
||||||
|
_repo.Update(command);
|
||||||
|
_commandCache.Set(command.Id.ToString(), command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(ApplicationStartedEvent message)
|
||||||
|
{
|
||||||
|
_logger.Trace("Orphaning incomplete commands");
|
||||||
|
_repo.OrphanStarted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data.SQLite;
|
||||||
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Messaging.Commands
|
||||||
|
{
|
||||||
|
public interface ICommandRepository : IBasicRepository<CommandModel>
|
||||||
|
{
|
||||||
|
void Trim();
|
||||||
|
void OrphanStarted();
|
||||||
|
List<CommandModel> FindCommands(string name);
|
||||||
|
List<CommandModel> FindQueuedOrStarted(string name);
|
||||||
|
List<CommandModel> Queued();
|
||||||
|
List<CommandModel> Started();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CommandRepository : BasicRepository<CommandModel>, ICommandRepository
|
||||||
|
{
|
||||||
|
private readonly IDatabase _database;
|
||||||
|
|
||||||
|
public CommandRepository(IDatabase database, IEventAggregator eventAggregator)
|
||||||
|
: base(database, eventAggregator)
|
||||||
|
{
|
||||||
|
_database = database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Trim()
|
||||||
|
{
|
||||||
|
var date = DateTime.UtcNow.AddDays(-1);
|
||||||
|
|
||||||
|
Delete(c => c.EndedAt < date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OrphanStarted()
|
||||||
|
{
|
||||||
|
var mapper = _database.GetDataMapper();
|
||||||
|
|
||||||
|
mapper.Parameters.Add(new SQLiteParameter("@orphaned", (int)CommandStatus.Orphaned));
|
||||||
|
mapper.Parameters.Add(new SQLiteParameter("@started", (int)CommandStatus.Started));
|
||||||
|
mapper.Parameters.Add(new SQLiteParameter("@ended", DateTime.UtcNow));
|
||||||
|
|
||||||
|
mapper.ExecuteNonQuery(@"UPDATE Commands
|
||||||
|
SET Status = @orphaned, EndedAt = @ended
|
||||||
|
WHERE Status = @started");
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CommandModel> FindCommands(string name)
|
||||||
|
{
|
||||||
|
return Query.Where(c => c.Name == name).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CommandModel> FindQueuedOrStarted(string name)
|
||||||
|
{
|
||||||
|
return Query.Where(c => c.Name == name)
|
||||||
|
.AndWhere("[Status] IN (0,1)")
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CommandModel> Queued()
|
||||||
|
{
|
||||||
|
return Query.Where(c => c.Status == CommandStatus.Queued);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CommandModel> Started()
|
||||||
|
{
|
||||||
|
return Query.Where(c => c.Status == CommandStatus.Started);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
namespace NzbDrone.Core.Messaging.Commands
|
||||||
|
{
|
||||||
|
public enum CommandStatus
|
||||||
|
{
|
||||||
|
Queued = 0,
|
||||||
|
Started = 1,
|
||||||
|
Completed = 2,
|
||||||
|
Failed = 3,
|
||||||
|
Aborted = 4,
|
||||||
|
Cancelled = 5,
|
||||||
|
Orphaned = 6
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace NzbDrone.Core.Messaging.Commands
|
||||||
|
{
|
||||||
|
public enum CommandTrigger
|
||||||
|
{
|
||||||
|
Unspecified = 0,
|
||||||
|
Manual = 1,
|
||||||
|
Scheduled = 2
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Messaging.Commands
|
|
||||||
{
|
|
||||||
public interface ICommandExecutor
|
|
||||||
{
|
|
||||||
void PublishCommand<TCommand>(TCommand command) where TCommand : Command;
|
|
||||||
void PublishCommand(string commandTypeName, DateTime? lastEecutionTime);
|
|
||||||
Command PublishCommandAsync<TCommand>(TCommand command) where TCommand : Command;
|
|
||||||
Command PublishCommandAsync(string commandTypeName);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace NzbDrone.Core.Messaging.Commands
|
||||||
|
{
|
||||||
|
public class MessagingCleanupCommand : Command
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
using NzbDrone.Core.Lifecycle;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Messaging.Commands
|
||||||
|
{
|
||||||
|
public class RequeueQueuedCommands : IHandle<ApplicationStartedEvent>
|
||||||
|
{
|
||||||
|
private readonly IManageCommandQueue _commandQueueManager;
|
||||||
|
|
||||||
|
public RequeueQueuedCommands(IManageCommandQueue commandQueueManager)
|
||||||
|
{
|
||||||
|
_commandQueueManager = commandQueueManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(ApplicationStartedEvent message)
|
||||||
|
{
|
||||||
|
_commandQueueManager.Requeue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
namespace NzbDrone.Core.Messaging.Commands.Tracking
|
|
||||||
{
|
|
||||||
public enum CommandStatus
|
|
||||||
{
|
|
||||||
Pending,
|
|
||||||
Running,
|
|
||||||
Completed,
|
|
||||||
Failed
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using NzbDrone.Common.Cache;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Messaging.Commands.Tracking
|
|
||||||
{
|
|
||||||
public interface ITrackCommands
|
|
||||||
{
|
|
||||||
Command GetById(int id);
|
|
||||||
Command GetById(string id);
|
|
||||||
void Completed(Command trackedCommand);
|
|
||||||
void Failed(Command trackedCommand, Exception e);
|
|
||||||
IEnumerable<Command> RunningCommands();
|
|
||||||
Command FindExisting(Command command);
|
|
||||||
void Store(Command command);
|
|
||||||
void Start(Command command);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CommandTrackingService : ITrackCommands, IExecute<TrackedCommandCleanupCommand>
|
|
||||||
{
|
|
||||||
private readonly ICached<Command> _cache;
|
|
||||||
|
|
||||||
public CommandTrackingService(ICacheManager cacheManager)
|
|
||||||
{
|
|
||||||
_cache = cacheManager.GetCache<Command>(GetType());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Command GetById(int id)
|
|
||||||
{
|
|
||||||
return _cache.Find(id.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Command GetById(string id)
|
|
||||||
{
|
|
||||||
return _cache.Find(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start(Command command)
|
|
||||||
{
|
|
||||||
command.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Completed(Command trackedCommand)
|
|
||||||
{
|
|
||||||
trackedCommand.Completed();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Failed(Command trackedCommand, Exception e)
|
|
||||||
{
|
|
||||||
trackedCommand.Failed(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<Command> RunningCommands()
|
|
||||||
{
|
|
||||||
return _cache.Values.Where(c => c.State == CommandStatus.Running);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Command FindExisting(Command command)
|
|
||||||
{
|
|
||||||
return RunningCommands().SingleOrDefault(t => CommandEqualityComparer.Instance.Equals(t, command));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Store(Command command)
|
|
||||||
{
|
|
||||||
if (command.GetType() == typeof(TrackedCommandCleanupCommand))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_cache.Set(command.Id.ToString(), command);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Execute(TrackedCommandCleanupCommand message)
|
|
||||||
{
|
|
||||||
var old = _cache.Values.Where(c => c.State != CommandStatus.Running && c.StateChangeTime < DateTime.UtcNow.AddMinutes(-5));
|
|
||||||
|
|
||||||
foreach (var trackedCommand in old)
|
|
||||||
{
|
|
||||||
_cache.Remove(trackedCommand.Id.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Messaging.Commands.Tracking
|
|
||||||
{
|
|
||||||
public class ExistingCommand
|
|
||||||
{
|
|
||||||
public Boolean Existing { get; set; }
|
|
||||||
public Command Command { get; set; }
|
|
||||||
|
|
||||||
public ExistingCommand(Boolean exisitng, Command trackedCommand)
|
|
||||||
{
|
|
||||||
Existing = exisitng;
|
|
||||||
Command = trackedCommand;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
namespace NzbDrone.Core.Messaging.Commands.Tracking
|
|
||||||
{
|
|
||||||
public class TrackedCommandCleanupCommand : Command
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
using NzbDrone.Common.Messaging;
|
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Messaging.Events
|
|
||||||
{
|
|
||||||
public class CommandCreatedEvent : IEvent
|
|
||||||
{
|
|
||||||
public Command Command { get; private set; }
|
|
||||||
|
|
||||||
public CommandCreatedEvent(Command trackedCommand)
|
|
||||||
{
|
|
||||||
Command = trackedCommand;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,11 +5,11 @@ namespace NzbDrone.Core.Messaging.Events
|
||||||
{
|
{
|
||||||
public class CommandExecutedEvent : IEvent
|
public class CommandExecutedEvent : IEvent
|
||||||
{
|
{
|
||||||
public Command Command { get; private set; }
|
public CommandModel Command { get; private set; }
|
||||||
|
|
||||||
public CommandExecutedEvent(Command trackedCommand)
|
public CommandExecutedEvent(CommandModel command)
|
||||||
{
|
{
|
||||||
Command = trackedCommand;
|
Command = command;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -156,9 +156,11 @@
|
||||||
<Compile Include="Datastore\Converters\BooleanIntConverter.cs" />
|
<Compile Include="Datastore\Converters\BooleanIntConverter.cs" />
|
||||||
<Compile Include="Datastore\Converters\EmbeddedDocumentConverter.cs" />
|
<Compile Include="Datastore\Converters\EmbeddedDocumentConverter.cs" />
|
||||||
<Compile Include="Datastore\Converters\EnumIntConverter.cs" />
|
<Compile Include="Datastore\Converters\EnumIntConverter.cs" />
|
||||||
|
<Compile Include="Datastore\Converters\TimeSpanConverter.cs" />
|
||||||
<Compile Include="Datastore\Converters\Int32Converter.cs" />
|
<Compile Include="Datastore\Converters\Int32Converter.cs" />
|
||||||
<Compile Include="Datastore\Converters\GuidConverter.cs" />
|
<Compile Include="Datastore\Converters\GuidConverter.cs" />
|
||||||
<Compile Include="Datastore\Converters\OsPathConverter.cs" />
|
<Compile Include="Datastore\Converters\OsPathConverter.cs" />
|
||||||
|
<Compile Include="Datastore\Converters\CommandConverter.cs" />
|
||||||
<Compile Include="Datastore\Converters\ProviderSettingConverter.cs" />
|
<Compile Include="Datastore\Converters\ProviderSettingConverter.cs" />
|
||||||
<Compile Include="Datastore\Converters\QualityIntConverter.cs" />
|
<Compile Include="Datastore\Converters\QualityIntConverter.cs" />
|
||||||
<Compile Include="Datastore\Converters\UtcConverter.cs" />
|
<Compile Include="Datastore\Converters\UtcConverter.cs" />
|
||||||
|
@ -235,6 +237,7 @@
|
||||||
<Compile Include="Datastore\Migration\060_remove_enable_from_indexers.cs" />
|
<Compile Include="Datastore\Migration\060_remove_enable_from_indexers.cs" />
|
||||||
<Compile Include="Datastore\Migration\062_convert_quality_models.cs" />
|
<Compile Include="Datastore\Migration\062_convert_quality_models.cs" />
|
||||||
<Compile Include="Datastore\Migration\065_make_scene_numbering_nullable.cs" />
|
<Compile Include="Datastore\Migration\065_make_scene_numbering_nullable.cs" />
|
||||||
|
<Compile Include="Datastore\Migration\078_add_commands_table.cs" />
|
||||||
<Compile Include="Datastore\Migration\068_add_release_restrictions.cs" />
|
<Compile Include="Datastore\Migration\068_add_release_restrictions.cs" />
|
||||||
<Compile Include="Datastore\Migration\066_add_tags.cs" />
|
<Compile Include="Datastore\Migration\066_add_tags.cs" />
|
||||||
<Compile Include="Datastore\Migration\067_add_added_to_series.cs" />
|
<Compile Include="Datastore\Migration\067_add_added_to_series.cs" />
|
||||||
|
@ -424,6 +427,7 @@
|
||||||
<Compile Include="History\HistoryRepository.cs" />
|
<Compile Include="History\HistoryRepository.cs" />
|
||||||
<Compile Include="History\HistoryService.cs" />
|
<Compile Include="History\HistoryService.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\CleanupAdditionalNamingSpecs.cs" />
|
<Compile Include="Housekeeping\Housekeepers\CleanupAdditionalNamingSpecs.cs" />
|
||||||
|
<Compile Include="Housekeeping\Housekeepers\CleanupCommandQueue.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\CleanupDuplicateMetadataFiles.cs" />
|
<Compile Include="Housekeeping\Housekeepers\CleanupDuplicateMetadataFiles.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedBlacklist.cs" />
|
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedBlacklist.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodeFiles.cs" />
|
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedEpisodeFiles.cs" />
|
||||||
|
@ -433,6 +437,7 @@
|
||||||
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedPendingReleases.cs" />
|
<Compile Include="Housekeeping\Housekeepers\CleanupOrphanedPendingReleases.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\DeleteBadMediaCovers.cs" />
|
<Compile Include="Housekeeping\Housekeepers\DeleteBadMediaCovers.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\FixFutureRunScheduledTasks.cs" />
|
<Compile Include="Housekeeping\Housekeepers\FixFutureRunScheduledTasks.cs" />
|
||||||
|
<Compile Include="Housekeeping\Housekeepers\TrimLogDatabase.cs" />
|
||||||
<Compile Include="Housekeeping\Housekeepers\UpdateCleanTitleForSeries.cs" />
|
<Compile Include="Housekeeping\Housekeepers\UpdateCleanTitleForSeries.cs" />
|
||||||
<Compile Include="Housekeeping\HousekeepingCommand.cs" />
|
<Compile Include="Housekeeping\HousekeepingCommand.cs" />
|
||||||
<Compile Include="Housekeeping\HousekeepingService.cs" />
|
<Compile Include="Housekeeping\HousekeepingService.cs" />
|
||||||
|
@ -525,14 +530,13 @@
|
||||||
<Compile Include="Instrumentation\Commands\ClearLogCommand.cs" />
|
<Compile Include="Instrumentation\Commands\ClearLogCommand.cs" />
|
||||||
<Compile Include="Instrumentation\Commands\DeleteLogFilesCommand.cs" />
|
<Compile Include="Instrumentation\Commands\DeleteLogFilesCommand.cs" />
|
||||||
<Compile Include="Instrumentation\Commands\DeleteUpdateLogFilesCommand.cs" />
|
<Compile Include="Instrumentation\Commands\DeleteUpdateLogFilesCommand.cs" />
|
||||||
<Compile Include="Instrumentation\Commands\TrimLogCommand.cs" />
|
|
||||||
<Compile Include="Instrumentation\DatabaseTarget.cs" />
|
<Compile Include="Instrumentation\DatabaseTarget.cs" />
|
||||||
<Compile Include="Instrumentation\DeleteLogFilesService.cs" />
|
<Compile Include="Instrumentation\DeleteLogFilesService.cs" />
|
||||||
<Compile Include="Instrumentation\Log.cs" />
|
<Compile Include="Instrumentation\Log.cs" />
|
||||||
<Compile Include="Instrumentation\LogRepository.cs" />
|
<Compile Include="Instrumentation\LogRepository.cs" />
|
||||||
<Compile Include="Instrumentation\LogService.cs" />
|
<Compile Include="Instrumentation\LogService.cs" />
|
||||||
<Compile Include="Instrumentation\ReconfigureLogging.cs" />
|
<Compile Include="Instrumentation\ReconfigureLogging.cs" />
|
||||||
<Compile Include="Jobs\JobRepository.cs" />
|
<Compile Include="Jobs\ScheduledTaskRepository.cs" />
|
||||||
<Compile Include="Jobs\ScheduledTask.cs" />
|
<Compile Include="Jobs\ScheduledTask.cs" />
|
||||||
<Compile Include="Jobs\Scheduler.cs" />
|
<Compile Include="Jobs\Scheduler.cs" />
|
||||||
<Compile Include="Jobs\TaskManager.cs" />
|
<Compile Include="Jobs\TaskManager.cs" />
|
||||||
|
@ -547,7 +551,6 @@
|
||||||
<Compile Include="MediaCover\MediaCoverService.cs" />
|
<Compile Include="MediaCover\MediaCoverService.cs" />
|
||||||
<Compile Include="MediaCover\MediaCoversUpdatedEvent.cs" />
|
<Compile Include="MediaCover\MediaCoversUpdatedEvent.cs" />
|
||||||
<Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" />
|
<Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" />
|
||||||
<Compile Include="MediaFiles\Commands\CleanMediaFileDb.cs" />
|
|
||||||
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
|
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
|
||||||
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
|
<Compile Include="MediaFiles\Commands\DownloadedEpisodesScanCommand.cs" />
|
||||||
<Compile Include="MediaFiles\Commands\RenameFilesCommand.cs" />
|
<Compile Include="MediaFiles\Commands\RenameFilesCommand.cs" />
|
||||||
|
@ -603,18 +606,24 @@
|
||||||
<Compile Include="MediaFiles\UpdateEpisodeFileService.cs" />
|
<Compile Include="MediaFiles\UpdateEpisodeFileService.cs" />
|
||||||
<Compile Include="MediaFiles\UpgradeMediaFileService.cs" />
|
<Compile Include="MediaFiles\UpgradeMediaFileService.cs" />
|
||||||
<Compile Include="Messaging\Commands\BackendCommandAttribute.cs" />
|
<Compile Include="Messaging\Commands\BackendCommandAttribute.cs" />
|
||||||
|
<Compile Include="Messaging\Commands\CleanupCommandMessagingService.cs" />
|
||||||
<Compile Include="Messaging\Commands\Command.cs" />
|
<Compile Include="Messaging\Commands\Command.cs" />
|
||||||
<Compile Include="Messaging\Commands\CommandEqualityComparer.cs" />
|
<Compile Include="Messaging\Commands\CommandEqualityComparer.cs" />
|
||||||
<Compile Include="Messaging\Commands\CommandExecutor.cs" />
|
<Compile Include="Messaging\Commands\CommandExecutor.cs" />
|
||||||
<Compile Include="Messaging\Commands\ICommandExecutor.cs" />
|
<Compile Include="Messaging\Commands\CommandFailedException.cs" />
|
||||||
|
<Compile Include="Messaging\Commands\MessagingCleanupCommand.cs" />
|
||||||
|
<Compile Include="Messaging\Commands\CommandModel.cs" />
|
||||||
|
<Compile Include="Messaging\Commands\CommandPriority.cs" />
|
||||||
|
<Compile Include="Messaging\Commands\CommandNotFoundException.cs" />
|
||||||
|
<Compile Include="Messaging\Commands\CommandQueue.cs" />
|
||||||
|
<Compile Include="Messaging\Commands\CommandStatus.cs" />
|
||||||
|
<Compile Include="Messaging\Commands\CommandRepository.cs" />
|
||||||
|
<Compile Include="Messaging\Commands\CommandQueueManager.cs" />
|
||||||
|
<Compile Include="Messaging\Commands\CommandTrigger.cs" />
|
||||||
<Compile Include="Messaging\Commands\IExecute.cs" />
|
<Compile Include="Messaging\Commands\IExecute.cs" />
|
||||||
|
<Compile Include="Messaging\Commands\RequeueQueuedCommands.cs" />
|
||||||
<Compile Include="Messaging\Commands\TestCommand.cs" />
|
<Compile Include="Messaging\Commands\TestCommand.cs" />
|
||||||
<Compile Include="Messaging\Commands\TestCommandExecutor.cs" />
|
<Compile Include="Messaging\Commands\TestCommandExecutor.cs" />
|
||||||
<Compile Include="Messaging\Commands\Tracking\CommandStatus.cs" />
|
|
||||||
<Compile Include="Messaging\Commands\Tracking\CommandTrackingService.cs" />
|
|
||||||
<Compile Include="Messaging\Commands\Tracking\ExistingCommand.cs" />
|
|
||||||
<Compile Include="Messaging\Commands\Tracking\TrackedCommandCleanupCommand.cs" />
|
|
||||||
<Compile Include="Messaging\Events\CommandCreatedEvent.cs" />
|
|
||||||
<Compile Include="Messaging\Events\CommandExecutedEvent.cs" />
|
<Compile Include="Messaging\Events\CommandExecutedEvent.cs" />
|
||||||
<Compile Include="Messaging\Events\EventAggregator.cs" />
|
<Compile Include="Messaging\Events\EventAggregator.cs" />
|
||||||
<Compile Include="Messaging\Events\IEventAggregator.cs" />
|
<Compile Include="Messaging\Events\IEventAggregator.cs" />
|
||||||
|
@ -853,6 +862,7 @@
|
||||||
<Compile Include="Tv\RefreshSeriesService.cs" />
|
<Compile Include="Tv\RefreshSeriesService.cs" />
|
||||||
<Compile Include="Tv\Season.cs" />
|
<Compile Include="Tv\Season.cs" />
|
||||||
<Compile Include="Tv\Series.cs" />
|
<Compile Include="Tv\Series.cs" />
|
||||||
|
<Compile Include="Tv\SeriesAddedHandler.cs" />
|
||||||
<Compile Include="Tv\SeriesScannedHandler.cs" />
|
<Compile Include="Tv\SeriesScannedHandler.cs" />
|
||||||
<Compile Include="Tv\SeriesEditedService.cs" />
|
<Compile Include="Tv\SeriesEditedService.cs" />
|
||||||
<Compile Include="Tv\SeriesRepository.cs" />
|
<Compile Include="Tv\SeriesRepository.cs" />
|
||||||
|
|
|
@ -5,9 +5,9 @@ namespace NzbDrone.Core.ProgressMessaging
|
||||||
{
|
{
|
||||||
public class CommandUpdatedEvent : IEvent
|
public class CommandUpdatedEvent : IEvent
|
||||||
{
|
{
|
||||||
public Command Command { get; set; }
|
public CommandModel Command { get; set; }
|
||||||
|
|
||||||
public CommandUpdatedEvent(Command command)
|
public CommandUpdatedEvent(CommandModel command)
|
||||||
{
|
{
|
||||||
Command = command;
|
Command = command;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
using NLog.Config;
|
using System;
|
||||||
|
using NLog.Config;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NLog.Targets;
|
using NLog.Targets;
|
||||||
using NzbDrone.Core.Lifecycle;
|
using NzbDrone.Core.Lifecycle;
|
||||||
using NzbDrone.Core.Messaging.Commands;
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
using NzbDrone.Core.Messaging.Commands.Tracking;
|
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
|
||||||
namespace NzbDrone.Core.ProgressMessaging
|
namespace NzbDrone.Core.ProgressMessaging
|
||||||
|
@ -11,13 +11,13 @@ namespace NzbDrone.Core.ProgressMessaging
|
||||||
public class ProgressMessageTarget : Target, IHandle<ApplicationStartedEvent>
|
public class ProgressMessageTarget : Target, IHandle<ApplicationStartedEvent>
|
||||||
{
|
{
|
||||||
private readonly IEventAggregator _eventAggregator;
|
private readonly IEventAggregator _eventAggregator;
|
||||||
private readonly ITrackCommands _trackCommands;
|
private readonly IManageCommandQueue _commandQueueManager;
|
||||||
private static LoggingRule _rule;
|
private static LoggingRule _rule;
|
||||||
|
|
||||||
public ProgressMessageTarget(IEventAggregator eventAggregator, ITrackCommands trackCommands)
|
public ProgressMessageTarget(IEventAggregator eventAggregator, IManageCommandQueue commandQueueManager)
|
||||||
{
|
{
|
||||||
_eventAggregator = eventAggregator;
|
_eventAggregator = eventAggregator;
|
||||||
_trackCommands = trackCommands;
|
_commandQueueManager = commandQueueManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Write(LogEventInfo logEvent)
|
protected override void Write(LogEventInfo logEvent)
|
||||||
|
@ -26,27 +26,26 @@ namespace NzbDrone.Core.ProgressMessaging
|
||||||
|
|
||||||
if (IsClientMessage(logEvent, command))
|
if (IsClientMessage(logEvent, command))
|
||||||
{
|
{
|
||||||
command.SetMessage(logEvent.FormattedMessage);
|
_commandQueueManager.SetMessage(command, logEvent.FormattedMessage);
|
||||||
_eventAggregator.PublishEvent(new CommandUpdatedEvent(command));
|
_eventAggregator.PublishEvent(new CommandUpdatedEvent(command));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CommandModel GetCurrentCommand()
|
||||||
private Command GetCurrentCommand()
|
|
||||||
{
|
{
|
||||||
var commandId = MappedDiagnosticsContext.Get("CommandId");
|
var commandId = MappedDiagnosticsContext.Get("CommandId");
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(commandId))
|
if (String.IsNullOrWhiteSpace(commandId))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _trackCommands.GetById(commandId);
|
return _commandQueueManager.Get(Convert.ToInt32(commandId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsClientMessage(LogEventInfo logEvent, Command command)
|
private bool IsClientMessage(LogEventInfo logEvent, CommandModel command)
|
||||||
{
|
{
|
||||||
if (command == null || !command.SendUpdatesToClient)
|
if (command == null || !command.Body.SendUpdatesToClient)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ using NzbDrone.Core.Tv.Events;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Tv
|
namespace NzbDrone.Core.Tv
|
||||||
{
|
{
|
||||||
public class RefreshSeriesService : IExecute<RefreshSeriesCommand>, IHandleAsync<SeriesAddedEvent>
|
public class RefreshSeriesService : IExecute<RefreshSeriesCommand>
|
||||||
{
|
{
|
||||||
private readonly IProvideSeriesInfo _seriesInfo;
|
private readonly IProvideSeriesInfo _seriesInfo;
|
||||||
private readonly ISeriesService _seriesService;
|
private readonly ISeriesService _seriesService;
|
||||||
|
@ -138,7 +138,7 @@ namespace NzbDrone.Core.Tv
|
||||||
|
|
||||||
foreach (var series in allSeries)
|
foreach (var series in allSeries)
|
||||||
{
|
{
|
||||||
if (message.Manual || _checkIfSeriesShouldBeRefreshed.ShouldRefresh(series))
|
if (message.Trigger == CommandTrigger.Manual || _checkIfSeriesShouldBeRefreshed.ShouldRefresh(series))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -165,10 +165,5 @@ namespace NzbDrone.Core.Tv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleAsync(SeriesAddedEvent message)
|
|
||||||
{
|
|
||||||
RefreshSeriesInfo(message.Series);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
using NzbDrone.Core.Messaging.Commands;
|
||||||
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Tv.Commands;
|
||||||
|
using NzbDrone.Core.Tv.Events;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Tv
|
||||||
|
{
|
||||||
|
public class SeriesAddedHandler : IHandle<SeriesAddedEvent>
|
||||||
|
{
|
||||||
|
private readonly IManageCommandQueue _commandQueueManager;
|
||||||
|
|
||||||
|
public SeriesAddedHandler(IManageCommandQueue commandQueueManager)
|
||||||
|
{
|
||||||
|
_commandQueueManager = commandQueueManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Handle(SeriesAddedEvent message)
|
||||||
|
{
|
||||||
|
_commandQueueManager.Push(new RefreshSeriesCommand(message.Series.Id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,18 +7,18 @@ namespace NzbDrone.Core.Tv
|
||||||
{
|
{
|
||||||
public class SeriesEditedService : IHandle<SeriesEditedEvent>
|
public class SeriesEditedService : IHandle<SeriesEditedEvent>
|
||||||
{
|
{
|
||||||
private readonly CommandExecutor _commandExecutor;
|
private readonly IManageCommandQueue _commandQueueManager;
|
||||||
|
|
||||||
public SeriesEditedService(CommandExecutor commandExecutor)
|
public SeriesEditedService(IManageCommandQueue commandQueueManager)
|
||||||
{
|
{
|
||||||
_commandExecutor = commandExecutor;
|
_commandQueueManager = commandQueueManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Handle(SeriesEditedEvent message)
|
public void Handle(SeriesEditedEvent message)
|
||||||
{
|
{
|
||||||
if (message.Series.SeriesType != message.OldSeries.SeriesType)
|
if (message.Series.SeriesType != message.OldSeries.SeriesType)
|
||||||
{
|
{
|
||||||
_commandExecutor.PublishCommandAsync(new RefreshSeriesCommand(message.Series.Id));
|
_commandQueueManager.Push(new RefreshSeriesCommand(message.Series.Id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,18 +15,18 @@ namespace NzbDrone.Core.Tv
|
||||||
{
|
{
|
||||||
private readonly ISeriesService _seriesService;
|
private readonly ISeriesService _seriesService;
|
||||||
private readonly IEpisodeService _episodeService;
|
private readonly IEpisodeService _episodeService;
|
||||||
private readonly ICommandExecutor _commandExecutor;
|
private readonly IManageCommandQueue _commandQueueManager;
|
||||||
|
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
|
||||||
public SeriesScannedHandler(ISeriesService seriesService,
|
public SeriesScannedHandler(ISeriesService seriesService,
|
||||||
IEpisodeService episodeService,
|
IEpisodeService episodeService,
|
||||||
ICommandExecutor commandExecutor,
|
IManageCommandQueue commandQueueManager,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
{
|
{
|
||||||
_seriesService = seriesService;
|
_seriesService = seriesService;
|
||||||
_episodeService = episodeService;
|
_episodeService = episodeService;
|
||||||
_commandExecutor = commandExecutor;
|
_commandQueueManager = commandQueueManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ namespace NzbDrone.Core.Tv
|
||||||
|
|
||||||
if (series.AddOptions.SearchForMissingEpisodes)
|
if (series.AddOptions.SearchForMissingEpisodes)
|
||||||
{
|
{
|
||||||
_commandExecutor.PublishCommand(new MissingEpisodeSearchCommand(series.Id));
|
_commandQueueManager.Push(new MissingEpisodeSearchCommand(series.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
series.AddOptions = null;
|
series.AddOptions = null;
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.EnsureThat;
|
using NzbDrone.Common.EnsureThat;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
|
|
@ -11,5 +11,13 @@ namespace NzbDrone.Core.Update.Commands
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string CompletionMessage
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return "Restarting Sonarr to apply updates";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -191,7 +191,7 @@ namespace NzbDrone.Core.Update
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (OsInfo.IsNotWindows && !_configFileProvider.UpdateAutomatically && !message.Manual)
|
if (OsInfo.IsNotWindows && !_configFileProvider.UpdateAutomatically && message.Trigger != CommandTrigger.Manual)
|
||||||
{
|
{
|
||||||
_logger.ProgressDebug("Auto-update not enabled, not installing available update.");
|
_logger.ProgressDebug("Auto-update not enabled, not installing available update.");
|
||||||
return;
|
return;
|
||||||
|
@ -200,23 +200,21 @@ namespace NzbDrone.Core.Update
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
InstallUpdate(latestAvailable);
|
InstallUpdate(latestAvailable);
|
||||||
|
|
||||||
message.Completed("Restarting Sonarr to apply updates");
|
|
||||||
}
|
}
|
||||||
catch (UpdateFolderNotWritableException ex)
|
catch (UpdateFolderNotWritableException ex)
|
||||||
{
|
{
|
||||||
_logger.ErrorException("Update process failed", ex);
|
_logger.ErrorException("Update process failed", ex);
|
||||||
message.Failed(ex, string.Format("Startup folder not writable by user '{0}'", Environment.UserName));
|
throw new CommandFailedException("Startup folder not writable by user '{0}'", ex, Environment.UserName);
|
||||||
}
|
}
|
||||||
catch (UpdateVerificationFailedException ex)
|
catch (UpdateVerificationFailedException ex)
|
||||||
{
|
{
|
||||||
_logger.ErrorException("Update process failed", ex);
|
_logger.ErrorException("Update process failed", ex);
|
||||||
message.Failed(ex, "Downloaded update package is corrupt");
|
throw new CommandFailedException("Downloaded update package is corrupt", ex);
|
||||||
}
|
}
|
||||||
catch (UpdateFailedException ex)
|
catch (UpdateFailedException ex)
|
||||||
{
|
{
|
||||||
_logger.ErrorException("Update process failed", ex);
|
_logger.ErrorException("Update process failed", ex);
|
||||||
message.Failed(ex);
|
throw new CommandFailedException(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,7 @@ var singleton = function() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
model.bind('change:state', function(model) {
|
model.bind('change:status', function(model) {
|
||||||
if (!model.isActive()) {
|
if (!model.isActive()) {
|
||||||
options.element.stopSpin();
|
options.element.stopSpin();
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,14 @@ module.exports = Backbone.Model.extend({
|
||||||
|
|
||||||
parse : function(response) {
|
parse : function(response) {
|
||||||
response.name = response.name.toLocaleLowerCase();
|
response.name = response.name.toLocaleLowerCase();
|
||||||
|
response.body.name = response.body.name.toLocaleLowerCase();
|
||||||
|
|
||||||
|
for (var key in response.body) {
|
||||||
|
response[key] = response.body[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
delete response.body;
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -33,10 +41,10 @@ module.exports = Backbone.Model.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
isActive : function() {
|
isActive : function() {
|
||||||
return this.get('state') !== 'completed' && this.get('state') !== 'failed';
|
return this.get('status') !== 'completed' && this.get('status') !== 'failed';
|
||||||
},
|
},
|
||||||
|
|
||||||
isComplete : function() {
|
isComplete : function() {
|
||||||
return this.get('state') === 'completed';
|
return this.get('status') === 'completed';
|
||||||
}
|
}
|
||||||
});
|
});
|
Loading…
Reference in New Issue