mirror of
https://github.com/Sonarr/Sonarr.git
synced 2024-12-16 11:37:58 +02:00
Commands are stored in memory and prevents duplicate jobs
This commit is contained in:
parent
a86c131761
commit
c917cdc0cc
@ -4,6 +4,7 @@
|
||||
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 @@ public class CommandModule : NzbDroneRestModule<CommandResource>
|
||||
{
|
||||
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 @@ private Response RunCommand(CommandResource resource)
|
||||
|
||||
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="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" />
|
||||
|
@ -28,7 +28,6 @@ public ICached<T> GetCache<T>(Type host)
|
||||
return GetCache<T>(host, host.FullName);
|
||||
}
|
||||
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_cache.Clear();
|
||||
|
44
NzbDrone.Common/Messaging/CommandEqualityComparer.cs
Normal file
44
NzbDrone.Common/Messaging/CommandEqualityComparer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
namespace NzbDrone.Common.Messaging
|
||||
namespace NzbDrone.Common.Messaging.Events
|
||||
{
|
||||
public class CommandCompletedEvent : IEvent
|
||||
{
|
@ -1,4 +1,4 @@
|
||||
namespace NzbDrone.Common.Messaging
|
||||
namespace NzbDrone.Common.Messaging.Events
|
||||
{
|
||||
public class CommandExecutedEvent : IEvent
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Common.Messaging
|
||||
namespace NzbDrone.Common.Messaging.Events
|
||||
{
|
||||
public class CommandFailedEvent : IEvent
|
||||
{
|
@ -1,4 +1,4 @@
|
||||
namespace NzbDrone.Common.Messaging
|
||||
namespace NzbDrone.Common.Messaging.Events
|
||||
{
|
||||
public class CommandStartedEvent : IEvent
|
||||
{
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Common.Messaging
|
||||
{
|
||||
|
59
NzbDrone.Common/Messaging/Manager/CommandManager.cs
Normal file
59
NzbDrone.Common/Messaging/Manager/CommandManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
29
NzbDrone.Common/Messaging/Manager/CommandManagerItem.cs
Normal file
29
NzbDrone.Common/Messaging/Manager/CommandManagerItem.cs
Normal 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
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@
|
||||
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 @@ public class MessageAggregator : IMessageAggregator
|
||||
{
|
||||
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 @@ public void PublishCommand<TCommand>(TCommand command) where TCommand : class, I
|
||||
|
||||
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();
|
||||
|
@ -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" />
|
||||
|
@ -3,6 +3,7 @@
|
||||
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;
|
||||
|
@ -131,8 +131,7 @@ public void PerformUpdate(Series series)
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user