diff --git a/NzbDrone.Api/Commands/CommandModule.cs b/NzbDrone.Api/Commands/CommandModule.cs index 371483cc3..beb6fe246 100644 --- a/NzbDrone.Api/Commands/CommandModule.cs +++ b/NzbDrone.Api/Commands/CommandModule.cs @@ -4,6 +4,7 @@ using Nancy; using NzbDrone.Api.Extensions; using NzbDrone.Common.Composition; using NzbDrone.Common.Messaging; +using NzbDrone.Common.Messaging.Manager; namespace NzbDrone.Api.Commands { @@ -11,14 +12,16 @@ namespace NzbDrone.Api.Commands { private readonly IMessageAggregator _messageAggregator; private readonly IContainer _container; + private readonly IManageCommands _commandManager; - public CommandModule(IMessageAggregator messageAggregator, IContainer container) + public CommandModule(IMessageAggregator messageAggregator, IContainer container, IManageCommands commandManager) { _messageAggregator = messageAggregator; _container = container; + _commandManager = commandManager; Post["/"] = x => RunCommand(ReadResourceFromRequest()); - + Get["/"] = x => GetAllCommands(); } private Response RunCommand(CommandResource resource) @@ -33,5 +36,10 @@ namespace NzbDrone.Api.Commands return resource.AsResponse(HttpStatusCode.Created); } + + private Response GetAllCommands() + { + return _commandManager.Items.AsResponse(); + } } } \ No newline at end of file diff --git a/NzbDrone.Common.Test/MessagingTests/CommandEqualityComparerFixture.cs b/NzbDrone.Common.Test/MessagingTests/CommandEqualityComparerFixture.cs new file mode 100644 index 000000000..2b2deadea --- /dev/null +++ b/NzbDrone.Common.Test/MessagingTests/CommandEqualityComparerFixture.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Common.Messaging; +using NzbDrone.Core.IndexerSearch; +using NzbDrone.Core.MediaFiles.Commands; + +namespace NzbDrone.Common.Test.MessagingTests +{ + [TestFixture] + public class CommandEqualityComparerFixture + { + [Test] + public void should_return_true_when_there_are_no_properties() + { + var command1 = new DownloadedEpisodesScanCommand(); + var command2 = new DownloadedEpisodesScanCommand(); + var comparer = new CommandEqualityComparer(); + + comparer.Equals(command1, command2).Should().BeTrue(); + } + + [Test] + public void should_return_true_when_single_property_matches() + { + var command1 = new EpisodeSearchCommand { EpisodeId = 1 }; + var command2 = new EpisodeSearchCommand { EpisodeId = 1 }; + var comparer = new CommandEqualityComparer(); + + comparer.Equals(command1, command2).Should().BeTrue(); + } + + [Test] + public void should_return_true_when_multiple_properties_match() + { + var command1 = new SeasonSearchCommand { SeriesId = 1, SeasonNumber = 1 }; + var command2 = new SeasonSearchCommand { SeriesId = 1, SeasonNumber = 1 }; + var comparer = new CommandEqualityComparer(); + + comparer.Equals(command1, command2).Should().BeTrue(); + } + + [Test] + public void should_return_false_when_single_property_doesnt_match() + { + var command1 = new EpisodeSearchCommand { EpisodeId = 1 }; + var command2 = new EpisodeSearchCommand { EpisodeId = 2 }; + var comparer = new CommandEqualityComparer(); + + comparer.Equals(command1, command2).Should().BeFalse(); + } + + [Test] + public void should_return_false_when_only_one_property_matches() + { + var command1 = new SeasonSearchCommand { SeriesId = 1, SeasonNumber = 1 }; + var command2 = new SeasonSearchCommand { SeriesId = 1, SeasonNumber = 2 }; + var comparer = new CommandEqualityComparer(); + + comparer.Equals(command1, command2).Should().BeFalse(); + } + + [Test] + public void should_return_false_when_no_properties_match() + { + var command1 = new SeasonSearchCommand { SeriesId = 1, SeasonNumber = 1 }; + var command2 = new SeasonSearchCommand { SeriesId = 2, SeasonNumber = 2 }; + var comparer = new CommandEqualityComparer(); + + comparer.Equals(command1, command2).Should().BeFalse(); + } + } +} diff --git a/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj b/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj index 4c22a7aeb..8fd34cbf6 100644 --- a/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj +++ b/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj @@ -69,6 +69,7 @@ + diff --git a/NzbDrone.Common/Cache/CacheManger.cs b/NzbDrone.Common/Cache/CacheManger.cs index d264eeb40..b5da3047c 100644 --- a/NzbDrone.Common/Cache/CacheManger.cs +++ b/NzbDrone.Common/Cache/CacheManger.cs @@ -28,7 +28,6 @@ namespace NzbDrone.Common.Cache return GetCache(host, host.FullName); } - public void Clear() { _cache.Clear(); diff --git a/NzbDrone.Common/Messaging/CommandEqualityComparer.cs b/NzbDrone.Common/Messaging/CommandEqualityComparer.cs new file mode 100644 index 000000000..390319704 --- /dev/null +++ b/NzbDrone.Common/Messaging/CommandEqualityComparer.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NzbDrone.Common.EnvironmentInfo; + +namespace NzbDrone.Common.Messaging +{ + public class CommandEqualityComparer : IEqualityComparer + { + public bool Equals(ICommand x, ICommand y) + { + var xProperties = x.GetType().GetProperties(); + var yProperties = y.GetType().GetProperties(); + + foreach (var xProperty in xProperties) + { + if (xProperty.Name == "CommandId") + { + continue; + } + + var yProperty = yProperties.SingleOrDefault(p => p.Name == xProperty.Name); + + if (yProperty == null) + { + continue; + } + + if (!xProperty.GetValue(x, null).Equals(yProperty.GetValue(y, null))) + { + return false; + } + } + + return true; + } + + public int GetHashCode(ICommand obj) + { + return obj.CommandId.GetHashCode(); + } + } +} diff --git a/NzbDrone.Common/Messaging/CommandCompletedEvent.cs b/NzbDrone.Common/Messaging/Events/CommandCompletedEvent.cs similarity index 82% rename from NzbDrone.Common/Messaging/CommandCompletedEvent.cs rename to NzbDrone.Common/Messaging/Events/CommandCompletedEvent.cs index 613800ae0..b65f25bf2 100644 --- a/NzbDrone.Common/Messaging/CommandCompletedEvent.cs +++ b/NzbDrone.Common/Messaging/Events/CommandCompletedEvent.cs @@ -1,4 +1,4 @@ -namespace NzbDrone.Common.Messaging +namespace NzbDrone.Common.Messaging.Events { public class CommandCompletedEvent : IEvent { diff --git a/NzbDrone.Common/Messaging/CommandExecutedEvent.cs b/NzbDrone.Common/Messaging/Events/CommandExecutedEvent.cs similarity index 82% rename from NzbDrone.Common/Messaging/CommandExecutedEvent.cs rename to NzbDrone.Common/Messaging/Events/CommandExecutedEvent.cs index 3cb4e7f55..e5e9120b3 100644 --- a/NzbDrone.Common/Messaging/CommandExecutedEvent.cs +++ b/NzbDrone.Common/Messaging/Events/CommandExecutedEvent.cs @@ -1,4 +1,4 @@ -namespace NzbDrone.Common.Messaging +namespace NzbDrone.Common.Messaging.Events { public class CommandExecutedEvent : IEvent { diff --git a/NzbDrone.Common/Messaging/CommandFailedEvent.cs b/NzbDrone.Common/Messaging/Events/CommandFailedEvent.cs similarity index 88% rename from NzbDrone.Common/Messaging/CommandFailedEvent.cs rename to NzbDrone.Common/Messaging/Events/CommandFailedEvent.cs index d33ab79f8..ef4934e41 100644 --- a/NzbDrone.Common/Messaging/CommandFailedEvent.cs +++ b/NzbDrone.Common/Messaging/Events/CommandFailedEvent.cs @@ -1,6 +1,6 @@ using System; -namespace NzbDrone.Common.Messaging +namespace NzbDrone.Common.Messaging.Events { public class CommandFailedEvent : IEvent { diff --git a/NzbDrone.Common/Messaging/CommandStartedEvent.cs b/NzbDrone.Common/Messaging/Events/CommandStartedEvent.cs similarity index 82% rename from NzbDrone.Common/Messaging/CommandStartedEvent.cs rename to NzbDrone.Common/Messaging/Events/CommandStartedEvent.cs index 8c1b4163d..762c9287c 100644 --- a/NzbDrone.Common/Messaging/CommandStartedEvent.cs +++ b/NzbDrone.Common/Messaging/Events/CommandStartedEvent.cs @@ -1,4 +1,4 @@ -namespace NzbDrone.Common.Messaging +namespace NzbDrone.Common.Messaging.Events { public class CommandStartedEvent : IEvent { diff --git a/NzbDrone.Common/Messaging/ICommand.cs b/NzbDrone.Common/Messaging/ICommand.cs index 005a84a68..e93f1ccaa 100644 --- a/NzbDrone.Common/Messaging/ICommand.cs +++ b/NzbDrone.Common/Messaging/ICommand.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace NzbDrone.Common.Messaging { diff --git a/NzbDrone.Common/Messaging/Manager/CommandManager.cs b/NzbDrone.Common/Messaging/Manager/CommandManager.cs new file mode 100644 index 000000000..4666e9cae --- /dev/null +++ b/NzbDrone.Common/Messaging/Manager/CommandManager.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common.Cache; +using NzbDrone.Common.Messaging.Events; + +namespace NzbDrone.Common.Messaging.Manager +{ + public interface IManageCommands + { + ICollection Items { get; } + Boolean ExistingItem(ICommand command); + } + + public class CommandManager : IManageCommands, + IHandle, + IHandle, + IHandle + { + private readonly ICached _cache; + + public CommandManager(ICacheManger cacheManger) + { + _cache = cacheManger.GetCache(GetType()); + } + + public void Handle(CommandStartedEvent message) + { + _cache.Set(message.Command.CommandId, new CommandManagerItem(message.Command, CommandState.Running)); + } + + public void Handle(CommandCompletedEvent message) + { + _cache.Set(message.Command.CommandId, new CommandManagerItem(message.Command, CommandState.Completed)); + } + + public void Handle(CommandFailedEvent message) + { + _cache.Set(message.Command.CommandId, new CommandManagerItem(message.Command, CommandState.Failed)); + } + + public ICollection Items + { + get + { + return _cache.Values; + } + } + + public bool ExistingItem(ICommand command) + { + var running = Items.Where(i => i.Type == command.GetType().FullName && i.State == CommandState.Running); + + var result = running.Select(r => r.Command).Contains(command, new CommandEqualityComparer()); + + return result; + } + } +} diff --git a/NzbDrone.Common/Messaging/Manager/CommandManagerItem.cs b/NzbDrone.Common/Messaging/Manager/CommandManagerItem.cs new file mode 100644 index 000000000..95c099151 --- /dev/null +++ b/NzbDrone.Common/Messaging/Manager/CommandManagerItem.cs @@ -0,0 +1,29 @@ +using System; + +namespace NzbDrone.Common.Messaging.Manager +{ + public class CommandManagerItem + { + public String Type { get; set; } + public ICommand Command { get; set; } + public CommandState State { get; set; } + + public CommandManagerItem() + { + } + + public CommandManagerItem(ICommand command, CommandState state) + { + Type = command.GetType().FullName; + Command = command; + State = state; + } + } + + public enum CommandState + { + Running = 0, + Completed = 1, + Failed = 2 + } +} diff --git a/NzbDrone.Common/Messaging/MessageAggregator.cs b/NzbDrone.Common/Messaging/MessageAggregator.cs index 59c831b71..30f300a70 100644 --- a/NzbDrone.Common/Messaging/MessageAggregator.cs +++ b/NzbDrone.Common/Messaging/MessageAggregator.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Threading.Tasks; using NLog; using NzbDrone.Common.EnsureThat; +using NzbDrone.Common.Messaging.Events; +using NzbDrone.Common.Messaging.Manager; using NzbDrone.Common.Serializer; using NzbDrone.Common.TPL; @@ -13,12 +15,14 @@ namespace NzbDrone.Common.Messaging { private readonly Logger _logger; private readonly IServiceFactory _serviceFactory; + private readonly IManageCommands _commandManager; private readonly TaskFactory _taskFactory; - public MessageAggregator(Logger logger, IServiceFactory serviceFactory) + public MessageAggregator(Logger logger, IServiceFactory serviceFactory, IManageCommands commandManager) { _logger = logger; _serviceFactory = serviceFactory; + _commandManager = commandManager; var scheduler = new LimitedConcurrencyLevelTaskScheduler(2); _taskFactory = new TaskFactory(scheduler); } @@ -86,6 +90,12 @@ namespace NzbDrone.Common.Messaging try { + if (_commandManager.ExistingItem(command)) + { + _logger.Info("Command is already in progress: {0}", command.GetType().Name); + return; + } + PublishEvent(new CommandStartedEvent(command)); handler.Execute(command); sw.Stop(); diff --git a/NzbDrone.Common/NzbDrone.Common.csproj b/NzbDrone.Common/NzbDrone.Common.csproj index c1b17236f..105c14d22 100644 --- a/NzbDrone.Common/NzbDrone.Common.csproj +++ b/NzbDrone.Common/NzbDrone.Common.csproj @@ -92,6 +92,10 @@ + + + + @@ -105,9 +109,9 @@ - - - + + + diff --git a/NzbDrone.Core/Jobs/TaskManager.cs b/NzbDrone.Core/Jobs/TaskManager.cs index 4c82da90a..83b079769 100644 --- a/NzbDrone.Core/Jobs/TaskManager.cs +++ b/NzbDrone.Core/Jobs/TaskManager.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using NLog; using NzbDrone.Common.Messaging; +using NzbDrone.Common.Messaging.Events; using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration.Events; using NzbDrone.Core.Indexers; diff --git a/NzbDrone.Core/Providers/XemProvider.cs b/NzbDrone.Core/Providers/XemProvider.cs index 931793dfb..f9d86f85c 100644 --- a/NzbDrone.Core/Providers/XemProvider.cs +++ b/NzbDrone.Core/Providers/XemProvider.cs @@ -131,8 +131,7 @@ namespace NzbDrone.Core.Providers catch (Exception ex) { - //TODO: We should increase this back to warn when caching is in place - _logger.TraceException("Error updating scene numbering mappings for: " + series, ex); + _logger.ErrorException("Error updating scene numbering mappings for: " + series, ex); } }