Commands are stored in memory and prevents duplicate jobs
This commit is contained in:
parent
a86c131761
commit
c917cdc0cc
|
@ -4,6 +4,7 @@ using Nancy;
|
||||||
using NzbDrone.Api.Extensions;
|
using NzbDrone.Api.Extensions;
|
||||||
using NzbDrone.Common.Composition;
|
using NzbDrone.Common.Composition;
|
||||||
using NzbDrone.Common.Messaging;
|
using NzbDrone.Common.Messaging;
|
||||||
|
using NzbDrone.Common.Messaging.Manager;
|
||||||
|
|
||||||
namespace NzbDrone.Api.Commands
|
namespace NzbDrone.Api.Commands
|
||||||
{
|
{
|
||||||
|
@ -11,14 +12,16 @@ namespace NzbDrone.Api.Commands
|
||||||
{
|
{
|
||||||
private readonly IMessageAggregator _messageAggregator;
|
private readonly IMessageAggregator _messageAggregator;
|
||||||
private readonly IContainer _container;
|
private readonly IContainer _container;
|
||||||
|
private readonly IManageCommands _commandManager;
|
||||||
|
|
||||||
public CommandModule(IMessageAggregator messageAggregator, IContainer container)
|
public CommandModule(IMessageAggregator messageAggregator, IContainer container, IManageCommands commandManager)
|
||||||
{
|
{
|
||||||
_messageAggregator = messageAggregator;
|
_messageAggregator = messageAggregator;
|
||||||
_container = container;
|
_container = container;
|
||||||
|
_commandManager = commandManager;
|
||||||
|
|
||||||
Post["/"] = x => RunCommand(ReadResourceFromRequest());
|
Post["/"] = x => RunCommand(ReadResourceFromRequest());
|
||||||
|
Get["/"] = x => GetAllCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Response RunCommand(CommandResource resource)
|
private Response RunCommand(CommandResource resource)
|
||||||
|
@ -33,5 +36,10 @@ namespace NzbDrone.Api.Commands
|
||||||
|
|
||||||
return resource.AsResponse(HttpStatusCode.Created);
|
return resource.AsResponse(HttpStatusCode.Created);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Response GetAllCommands()
|
||||||
|
{
|
||||||
|
return _commandManager.Items.AsResponse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -69,6 +69,7 @@
|
||||||
<Compile Include="EnvironmentTests\EnvironmentProviderTest.cs" />
|
<Compile Include="EnvironmentTests\EnvironmentProviderTest.cs" />
|
||||||
<Compile Include="EventingTests\MessageAggregatorCommandTests.cs" />
|
<Compile Include="EventingTests\MessageAggregatorCommandTests.cs" />
|
||||||
<Compile Include="EventingTests\MessageAggregatorEventTests.cs" />
|
<Compile Include="EventingTests\MessageAggregatorEventTests.cs" />
|
||||||
|
<Compile Include="MessagingTests\CommandEqualityComparerFixture.cs" />
|
||||||
<Compile Include="ReflectionExtensions.cs" />
|
<Compile Include="ReflectionExtensions.cs" />
|
||||||
<Compile Include="PathExtensionFixture.cs" />
|
<Compile Include="PathExtensionFixture.cs" />
|
||||||
<Compile Include="DiskProviderTests\DiskProviderFixture.cs" />
|
<Compile Include="DiskProviderTests\DiskProviderFixture.cs" />
|
||||||
|
|
|
@ -28,7 +28,6 @@ namespace NzbDrone.Common.Cache
|
||||||
return GetCache<T>(host, host.FullName);
|
return GetCache<T>(host, host.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
_cache.Clear();
|
_cache.Clear();
|
||||||
|
|
|
@ -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<ICommand>
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
namespace NzbDrone.Common.Messaging
|
namespace NzbDrone.Common.Messaging.Events
|
||||||
{
|
{
|
||||||
public class CommandCompletedEvent : IEvent
|
public class CommandCompletedEvent : IEvent
|
||||||
{
|
{
|
|
@ -1,4 +1,4 @@
|
||||||
namespace NzbDrone.Common.Messaging
|
namespace NzbDrone.Common.Messaging.Events
|
||||||
{
|
{
|
||||||
public class CommandExecutedEvent : IEvent
|
public class CommandExecutedEvent : IEvent
|
||||||
{
|
{
|
|
@ -1,6 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace NzbDrone.Common.Messaging
|
namespace NzbDrone.Common.Messaging.Events
|
||||||
{
|
{
|
||||||
public class CommandFailedEvent : IEvent
|
public class CommandFailedEvent : IEvent
|
||||||
{
|
{
|
|
@ -1,4 +1,4 @@
|
||||||
namespace NzbDrone.Common.Messaging
|
namespace NzbDrone.Common.Messaging.Events
|
||||||
{
|
{
|
||||||
public class CommandStartedEvent : IEvent
|
public class CommandStartedEvent : IEvent
|
||||||
{
|
{
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace NzbDrone.Common.Messaging
|
namespace NzbDrone.Common.Messaging
|
||||||
{
|
{
|
||||||
|
|
|
@ -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<CommandManagerItem> Items { get; }
|
||||||
|
Boolean ExistingItem(ICommand command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CommandManager : IManageCommands,
|
||||||
|
IHandle<CommandStartedEvent>,
|
||||||
|
IHandle<CommandCompletedEvent>,
|
||||||
|
IHandle<CommandFailedEvent>
|
||||||
|
{
|
||||||
|
private readonly ICached<CommandManagerItem> _cache;
|
||||||
|
|
||||||
|
public CommandManager(ICacheManger cacheManger)
|
||||||
|
{
|
||||||
|
_cache = cacheManger.GetCache<CommandManagerItem>(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<CommandManagerItem> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.EnsureThat;
|
using NzbDrone.Common.EnsureThat;
|
||||||
|
using NzbDrone.Common.Messaging.Events;
|
||||||
|
using NzbDrone.Common.Messaging.Manager;
|
||||||
using NzbDrone.Common.Serializer;
|
using NzbDrone.Common.Serializer;
|
||||||
using NzbDrone.Common.TPL;
|
using NzbDrone.Common.TPL;
|
||||||
|
|
||||||
|
@ -13,12 +15,14 @@ namespace NzbDrone.Common.Messaging
|
||||||
{
|
{
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
private readonly IServiceFactory _serviceFactory;
|
private readonly IServiceFactory _serviceFactory;
|
||||||
|
private readonly IManageCommands _commandManager;
|
||||||
private readonly TaskFactory _taskFactory;
|
private readonly TaskFactory _taskFactory;
|
||||||
|
|
||||||
public MessageAggregator(Logger logger, IServiceFactory serviceFactory)
|
public MessageAggregator(Logger logger, IServiceFactory serviceFactory, IManageCommands commandManager)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_serviceFactory = serviceFactory;
|
_serviceFactory = serviceFactory;
|
||||||
|
_commandManager = commandManager;
|
||||||
var scheduler = new LimitedConcurrencyLevelTaskScheduler(2);
|
var scheduler = new LimitedConcurrencyLevelTaskScheduler(2);
|
||||||
_taskFactory = new TaskFactory(scheduler);
|
_taskFactory = new TaskFactory(scheduler);
|
||||||
}
|
}
|
||||||
|
@ -86,6 +90,12 @@ namespace NzbDrone.Common.Messaging
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (_commandManager.ExistingItem(command))
|
||||||
|
{
|
||||||
|
_logger.Info("Command is already in progress: {0}", command.GetType().Name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
PublishEvent(new CommandStartedEvent(command));
|
PublishEvent(new CommandStartedEvent(command));
|
||||||
handler.Execute(command);
|
handler.Execute(command);
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
|
|
|
@ -92,6 +92,10 @@
|
||||||
<Compile Include="IEnumerableExtensions.cs" />
|
<Compile Include="IEnumerableExtensions.cs" />
|
||||||
<Compile Include="Instrumentation\GlobalExceptionHandlers.cs" />
|
<Compile Include="Instrumentation\GlobalExceptionHandlers.cs" />
|
||||||
<Compile Include="Instrumentation\ExceptronTarget.cs" />
|
<Compile Include="Instrumentation\ExceptronTarget.cs" />
|
||||||
|
<Compile Include="Messaging\Manager\CommandManager.cs" />
|
||||||
|
<Compile Include="Messaging\Manager\CommandManagerItem.cs" />
|
||||||
|
<Compile Include="Messaging\Events\CommandStartedEvent.cs" />
|
||||||
|
<Compile Include="Messaging\CommandEqualityComparer.cs" />
|
||||||
<Compile Include="PathEqualityComparer.cs" />
|
<Compile Include="PathEqualityComparer.cs" />
|
||||||
<Compile Include="Services.cs" />
|
<Compile Include="Services.cs" />
|
||||||
<Compile Include="TPL\LimitedConcurrencyLevelTaskScheduler.cs" />
|
<Compile Include="TPL\LimitedConcurrencyLevelTaskScheduler.cs" />
|
||||||
|
@ -105,9 +109,9 @@
|
||||||
<Compile Include="Instrumentation\LogglyTarget.cs" />
|
<Compile Include="Instrumentation\LogglyTarget.cs" />
|
||||||
<Compile Include="Instrumentation\UpdateLogLayoutRenderer.cs" />
|
<Compile Include="Instrumentation\UpdateLogLayoutRenderer.cs" />
|
||||||
<Compile Include="Serializer\Json.cs" />
|
<Compile Include="Serializer\Json.cs" />
|
||||||
<Compile Include="Messaging\CommandCompletedEvent.cs" />
|
<Compile Include="Messaging\Events\CommandCompletedEvent.cs" />
|
||||||
<Compile Include="Messaging\CommandStartedEvent.cs" />
|
<Compile Include="Messaging\Events\CommandExecutedEvent.cs" />
|
||||||
<Compile Include="Messaging\CommandFailedEvent.cs" />
|
<Compile Include="Messaging\Events\CommandFailedEvent.cs" />
|
||||||
<Compile Include="Messaging\IExecute.cs" />
|
<Compile Include="Messaging\IExecute.cs" />
|
||||||
<Compile Include="Messaging\ICommand.cs" />
|
<Compile Include="Messaging\ICommand.cs" />
|
||||||
<Compile Include="Messaging\IMessage.cs" />
|
<Compile Include="Messaging\IMessage.cs" />
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Messaging;
|
using NzbDrone.Common.Messaging;
|
||||||
|
using NzbDrone.Common.Messaging.Events;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using NzbDrone.Core.Configuration.Events;
|
using NzbDrone.Core.Configuration.Events;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
|
|
|
@ -131,8 +131,7 @@ namespace NzbDrone.Core.Providers
|
||||||
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
//TODO: We should increase this back to warn when caching is in place
|
_logger.ErrorException("Error updating scene numbering mappings for: " + series, ex);
|
||||||
_logger.TraceException("Error updating scene numbering mappings for: " + series, ex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue