Commands are stored in memory and prevents duplicate jobs

This commit is contained in:
Mark McDowall 2013-08-27 23:51:42 -07:00
parent a86c131761
commit c917cdc0cc
16 changed files with 244 additions and 13 deletions

View File

@ -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();
}
}
}

View File

@ -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();
}
}
}

View File

@ -69,6 +69,7 @@
<Compile Include="EnvironmentTests\EnvironmentProviderTest.cs" />
<Compile Include="EventingTests\MessageAggregatorCommandTests.cs" />
<Compile Include="EventingTests\MessageAggregatorEventTests.cs" />
<Compile Include="MessagingTests\CommandEqualityComparerFixture.cs" />
<Compile Include="ReflectionExtensions.cs" />
<Compile Include="PathExtensionFixture.cs" />
<Compile Include="DiskProviderTests\DiskProviderFixture.cs" />

View File

@ -28,7 +28,6 @@ namespace NzbDrone.Common.Cache
return GetCache<T>(host, host.FullName);
}
public void Clear()
{
_cache.Clear();

View File

@ -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();
}
}
}

View File

@ -1,4 +1,4 @@
namespace NzbDrone.Common.Messaging
namespace NzbDrone.Common.Messaging.Events
{
public class CommandCompletedEvent : IEvent
{

View File

@ -1,4 +1,4 @@
namespace NzbDrone.Common.Messaging
namespace NzbDrone.Common.Messaging.Events
{
public class CommandExecutedEvent : IEvent
{

View File

@ -1,6 +1,6 @@
using System;
namespace NzbDrone.Common.Messaging
namespace NzbDrone.Common.Messaging.Events
{
public class CommandFailedEvent : IEvent
{

View File

@ -1,4 +1,4 @@
namespace NzbDrone.Common.Messaging
namespace NzbDrone.Common.Messaging.Events
{
public class CommandStartedEvent : IEvent
{

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
namespace NzbDrone.Common.Messaging
{

View File

@ -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;
}
}
}

View File

@ -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
}
}

View File

@ -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();

View File

@ -92,6 +92,10 @@
<Compile Include="IEnumerableExtensions.cs" />
<Compile Include="Instrumentation\GlobalExceptionHandlers.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="Services.cs" />
<Compile Include="TPL\LimitedConcurrencyLevelTaskScheduler.cs" />
@ -105,9 +109,9 @@
<Compile Include="Instrumentation\LogglyTarget.cs" />
<Compile Include="Instrumentation\UpdateLogLayoutRenderer.cs" />
<Compile Include="Serializer\Json.cs" />
<Compile Include="Messaging\CommandCompletedEvent.cs" />
<Compile Include="Messaging\CommandStartedEvent.cs" />
<Compile Include="Messaging\CommandFailedEvent.cs" />
<Compile Include="Messaging\Events\CommandCompletedEvent.cs" />
<Compile Include="Messaging\Events\CommandExecutedEvent.cs" />
<Compile Include="Messaging\Events\CommandFailedEvent.cs" />
<Compile Include="Messaging\IExecute.cs" />
<Compile Include="Messaging\ICommand.cs" />
<Compile Include="Messaging\IMessage.cs" />

View File

@ -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;

View File

@ -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);
}
}