From a3961ffb69f42d9978b1185c84dc742482895e3a Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 27 Aug 2013 17:51:54 -0700 Subject: [PATCH 01/15] Added CommandId to commands --- .../MessageAggregatorCommandTests.cs | 8 +++++- NzbDrone.Common/HashUtil.cs | 26 ++----------------- NzbDrone.Common/Messaging/ICommand.cs | 3 +++ NzbDrone.Common/Messaging/TestCommand.cs | 10 ++++--- .../Scene/UpdateSceneMappingCommand.cs | 8 ++++++ .../IndexerSearch/EpisodeSearchCommand.cs | 10 ++++++- .../IndexerSearch/SeasonSearchCommand.cs | 10 ++++++- .../IndexerSearch/SeriesSearchCommand.cs | 10 ++++++- NzbDrone.Core/Indexers/RssSyncCommand.cs | 9 ++++++- .../Commands/ClearLogCommand.cs | 10 ++++++- .../Commands/DeleteLogFilesCommand.cs | 10 ++++++- .../Commands/TrimLogCommand.cs | 10 ++++++- .../MediaFiles/Commands/CleanMediaFileDb.cs | 9 +++++++ .../Commands/CleanUpRecycleBinCommand.cs | 8 ++++++ .../Commands/DownloadedEpisodesScanCommand.cs | 5 ++++ .../Commands/RenameSeasonCommand.cs | 10 +++++++ .../Commands/RenameSeriesCommand.cs | 9 +++++++ .../Notifications/Email/TestEmailCommand.cs | 10 ++++++- .../Notifications/Growl/TestGrowlCommand.cs | 10 ++++++- .../Plex/TestPlexClientCommand.cs | 10 ++++++- .../Plex/TestPlexServerCommand.cs | 10 ++++++- .../Notifications/Prowl/TestProwlCommand.cs | 10 ++++++- .../Pushover/TestPushoverCommand.cs | 10 ++++++- .../Notifications/Xbmc/TestXbmcCommand.cs | 10 ++++++- .../Providers/UpdateXemMappingsCommand.cs | 7 ++++- .../Tv/Commands/RefreshSeriesCommand.cs | 12 ++++++++- .../Commands/ApplicationUpdateCommand.cs | 10 ++++++- 27 files changed, 218 insertions(+), 46 deletions(-) diff --git a/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs b/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs index 1e450ae21..38ce954f3 100644 --- a/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs +++ b/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs @@ -76,17 +76,23 @@ public void broken_executor_should_throw_the_exception() public class CommandA : ICommand { + public String CommandId { get; set; } // ReSharper disable UnusedParameter.Local public CommandA(int id = 0) // ReSharper restore UnusedParameter.Local { - + CommandId = HashUtil.GenerateCommandId(); } } public class CommandB : ICommand { + public String CommandId { get; set; } + public CommandB() + { + CommandId = HashUtil.GenerateCommandId(); + } } } \ No newline at end of file diff --git a/NzbDrone.Common/HashUtil.cs b/NzbDrone.Common/HashUtil.cs index 0a127ee63..5777cc724 100644 --- a/NzbDrone.Common/HashUtil.cs +++ b/NzbDrone.Common/HashUtil.cs @@ -34,31 +34,9 @@ public static string CalculateCrc(string input) return String.Format("{0:x8}", mCrc); } - public static string GenerateUserId() + public static string GenerateCommandId() { - return GenerateId("u"); - } - - public static string GenerateAppId() - { - return GenerateId("a"); - } - - public static string GenerateApiToken() - { - return Guid.NewGuid().ToString().Replace("-", ""); - } - - public static string GenerateSecurityToken(int length) - { - var byteSize = (length / 4) * 3; - - var linkBytes = new byte[byteSize]; - var rngCrypto = new RNGCryptoServiceProvider(); - rngCrypto.GetBytes(linkBytes); - var base64String = Convert.ToBase64String(linkBytes); - - return base64String; + return GenerateId("c"); } private static string GenerateId(string prefix) diff --git a/NzbDrone.Common/Messaging/ICommand.cs b/NzbDrone.Common/Messaging/ICommand.cs index d9f8049ba..005a84a68 100644 --- a/NzbDrone.Common/Messaging/ICommand.cs +++ b/NzbDrone.Common/Messaging/ICommand.cs @@ -1,6 +1,9 @@ +using System; + namespace NzbDrone.Common.Messaging { public interface ICommand : IMessage { + String CommandId { get; } } } \ No newline at end of file diff --git a/NzbDrone.Common/Messaging/TestCommand.cs b/NzbDrone.Common/Messaging/TestCommand.cs index 1a54d5764..3ede823e7 100644 --- a/NzbDrone.Common/Messaging/TestCommand.cs +++ b/NzbDrone.Common/Messaging/TestCommand.cs @@ -1,13 +1,15 @@ -namespace NzbDrone.Common.Messaging +using System; + +namespace NzbDrone.Common.Messaging { public class TestCommand : ICommand { + public int Duration { get; set; } + public String CommandId { get; set; } + public TestCommand() { Duration = 4000; } - - public int Duration { get; set; } - } } \ No newline at end of file diff --git a/NzbDrone.Core/DataAugmentation/Scene/UpdateSceneMappingCommand.cs b/NzbDrone.Core/DataAugmentation/Scene/UpdateSceneMappingCommand.cs index 68356ab6e..965121626 100644 --- a/NzbDrone.Core/DataAugmentation/Scene/UpdateSceneMappingCommand.cs +++ b/NzbDrone.Core/DataAugmentation/Scene/UpdateSceneMappingCommand.cs @@ -1,8 +1,16 @@ +using System; +using NzbDrone.Common; using NzbDrone.Common.Messaging; namespace NzbDrone.Core.DataAugmentation.Scene { public class UpdateSceneMappingCommand : ICommand { + public String CommandId { get; set; } + + public UpdateSceneMappingCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/IndexerSearch/EpisodeSearchCommand.cs b/NzbDrone.Core/IndexerSearch/EpisodeSearchCommand.cs index b0dce2fd1..08d47672d 100644 --- a/NzbDrone.Core/IndexerSearch/EpisodeSearchCommand.cs +++ b/NzbDrone.Core/IndexerSearch/EpisodeSearchCommand.cs @@ -1,9 +1,17 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; namespace NzbDrone.Core.IndexerSearch { public class EpisodeSearchCommand : ICommand { + public String CommandId { get; set; } public int EpisodeId { get; set; } + + public EpisodeSearchCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/IndexerSearch/SeasonSearchCommand.cs b/NzbDrone.Core/IndexerSearch/SeasonSearchCommand.cs index cf9a8ba3e..a27d98306 100644 --- a/NzbDrone.Core/IndexerSearch/SeasonSearchCommand.cs +++ b/NzbDrone.Core/IndexerSearch/SeasonSearchCommand.cs @@ -1,10 +1,18 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; namespace NzbDrone.Core.IndexerSearch { public class SeasonSearchCommand : ICommand { + public String CommandId { get; set; } public int SeriesId { get; set; } public int SeasonNumber { get; set; } + + public SeasonSearchCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/IndexerSearch/SeriesSearchCommand.cs b/NzbDrone.Core/IndexerSearch/SeriesSearchCommand.cs index 4e309fde8..e9ade1e45 100644 --- a/NzbDrone.Core/IndexerSearch/SeriesSearchCommand.cs +++ b/NzbDrone.Core/IndexerSearch/SeriesSearchCommand.cs @@ -1,9 +1,17 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; namespace NzbDrone.Core.IndexerSearch { public class SeriesSearchCommand : ICommand { + public String CommandId { get; set; } public int SeriesId { get; set; } + + public SeriesSearchCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Indexers/RssSyncCommand.cs b/NzbDrone.Core/Indexers/RssSyncCommand.cs index 04a7a123b..467a7b030 100644 --- a/NzbDrone.Core/Indexers/RssSyncCommand.cs +++ b/NzbDrone.Core/Indexers/RssSyncCommand.cs @@ -1,9 +1,16 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; namespace NzbDrone.Core.Indexers { public class RssSyncCommand : ICommand { + public String CommandId { get; set; } + public RssSyncCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Instrumentation/Commands/ClearLogCommand.cs b/NzbDrone.Core/Instrumentation/Commands/ClearLogCommand.cs index 19776e76d..34e2812ba 100644 --- a/NzbDrone.Core/Instrumentation/Commands/ClearLogCommand.cs +++ b/NzbDrone.Core/Instrumentation/Commands/ClearLogCommand.cs @@ -1,8 +1,16 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; namespace NzbDrone.Core.Instrumentation.Commands { public class ClearLogCommand : ICommand { + public String CommandId { get; set; } + + public ClearLogCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs b/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs index 5d3228afb..ea16c67d0 100644 --- a/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs +++ b/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs @@ -1,8 +1,16 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; namespace NzbDrone.Core.Instrumentation.Commands { public class DeleteLogFilesCommand : ICommand { + public String CommandId { get; set; } + + public DeleteLogFilesCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Instrumentation/Commands/TrimLogCommand.cs b/NzbDrone.Core/Instrumentation/Commands/TrimLogCommand.cs index c00b27020..8221814ba 100644 --- a/NzbDrone.Core/Instrumentation/Commands/TrimLogCommand.cs +++ b/NzbDrone.Core/Instrumentation/Commands/TrimLogCommand.cs @@ -1,8 +1,16 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; namespace NzbDrone.Core.Instrumentation.Commands { public class TrimLogCommand : ICommand { + public String CommandId { get; set; } + + public TrimLogCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/MediaFiles/Commands/CleanMediaFileDb.cs b/NzbDrone.Core/MediaFiles/Commands/CleanMediaFileDb.cs index b873dcc5f..42822aae6 100644 --- a/NzbDrone.Core/MediaFiles/Commands/CleanMediaFileDb.cs +++ b/NzbDrone.Core/MediaFiles/Commands/CleanMediaFileDb.cs @@ -1,13 +1,22 @@ +using System; +using NzbDrone.Common; using NzbDrone.Common.Messaging; namespace NzbDrone.Core.MediaFiles.Commands { public class CleanMediaFileDb : ICommand { + public String CommandId { get; set; } public int SeriesId { get; private set; } + public CleanMediaFileDb() + { + CommandId = HashUtil.GenerateCommandId(); + } + public CleanMediaFileDb(int seriesId) { + CommandId = HashUtil.GenerateCommandId(); SeriesId = seriesId; } } diff --git a/NzbDrone.Core/MediaFiles/Commands/CleanUpRecycleBinCommand.cs b/NzbDrone.Core/MediaFiles/Commands/CleanUpRecycleBinCommand.cs index b2d16f231..66cbc07a9 100644 --- a/NzbDrone.Core/MediaFiles/Commands/CleanUpRecycleBinCommand.cs +++ b/NzbDrone.Core/MediaFiles/Commands/CleanUpRecycleBinCommand.cs @@ -1,8 +1,16 @@ +using System; +using NzbDrone.Common; using NzbDrone.Common.Messaging; namespace NzbDrone.Core.MediaFiles.Commands { public class CleanUpRecycleBinCommand : ICommand { + public String CommandId { get; set; } + + public CleanUpRecycleBinCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs b/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs index 0f03f2083..b0e52126a 100644 --- a/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs +++ b/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs @@ -1,11 +1,16 @@ +using System; +using NzbDrone.Common; using NzbDrone.Common.Messaging; namespace NzbDrone.Core.MediaFiles.Commands { public class DownloadedEpisodesScanCommand : ICommand { + public String CommandId { get; set; } + public DownloadedEpisodesScanCommand() { + CommandId = HashUtil.GenerateCommandId(); } } } \ No newline at end of file diff --git a/NzbDrone.Core/MediaFiles/Commands/RenameSeasonCommand.cs b/NzbDrone.Core/MediaFiles/Commands/RenameSeasonCommand.cs index 723c5d74b..894a1eb86 100644 --- a/NzbDrone.Core/MediaFiles/Commands/RenameSeasonCommand.cs +++ b/NzbDrone.Core/MediaFiles/Commands/RenameSeasonCommand.cs @@ -1,3 +1,5 @@ +using System; +using NzbDrone.Common; using NzbDrone.Common.Messaging; namespace NzbDrone.Core.MediaFiles.Commands @@ -7,8 +9,16 @@ public class RenameSeasonCommand : ICommand public int SeriesId { get; private set; } public int SeasonNumber { get; private set; } + public String CommandId { get; set; } + + public RenameSeasonCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } + public RenameSeasonCommand(int seriesId, int seasonNumber) { + CommandId = HashUtil.GenerateCommandId(); SeriesId = seriesId; SeasonNumber = seasonNumber; } diff --git a/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs b/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs index 7716c43c0..954d92ff1 100644 --- a/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs +++ b/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs @@ -1,13 +1,22 @@ +using System; +using NzbDrone.Common; using NzbDrone.Common.Messaging; namespace NzbDrone.Core.MediaFiles.Commands { public class RenameSeriesCommand : ICommand { + public String CommandId { get; set; } public int SeriesId { get; private set; } + public RenameSeriesCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } + public RenameSeriesCommand(int seriesId) { + CommandId = HashUtil.GenerateCommandId(); SeriesId = seriesId; } } diff --git a/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs b/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs index 258884788..4ee1133ed 100644 --- a/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs +++ b/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs @@ -1,9 +1,12 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; namespace NzbDrone.Core.Notifications.Email { public class TestEmailCommand : ICommand { + public String CommandId { get; set; } public string Server { get; set; } public int Port { get; set; } public bool Ssl { get; set; } @@ -11,5 +14,10 @@ public class TestEmailCommand : ICommand public string Password { get; set; } public string From { get; set; } public string To { get; set; } + + public TestEmailCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } } } diff --git a/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs b/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs index 35890fff9..71453e252 100644 --- a/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs +++ b/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs @@ -1,11 +1,19 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; namespace NzbDrone.Core.Notifications.Growl { public class TestGrowlCommand : ICommand { + public String CommandId { get; set; } public string Host { get; set; } public int Port { get; set; } public string Password { get; set; } + + public TestGrowlCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } } } diff --git a/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs b/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs index 6df162ab4..bf456754e 100644 --- a/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs +++ b/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs @@ -1,12 +1,20 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; namespace NzbDrone.Core.Notifications.Plex { public class TestPlexClientCommand : ICommand { + public String CommandId { get; set; } public string Host { get; set; } public int Port { get; set; } public string Username { get; set; } public string Password { get; set; } + + public TestPlexClientCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } } } diff --git a/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs b/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs index 49089afea..3921890bb 100644 --- a/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs +++ b/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs @@ -1,10 +1,18 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; namespace NzbDrone.Core.Notifications.Plex { public class TestPlexServerCommand : ICommand { + public String CommandId { get; set; } public string Host { get; set; } public int Port { get; set; } + + public TestPlexServerCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } } } diff --git a/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs b/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs index e58bf5a9c..e30fa4c6b 100644 --- a/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs +++ b/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs @@ -1,10 +1,18 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; namespace NzbDrone.Core.Notifications.Prowl { public class TestProwlCommand : ICommand { + public String CommandId { get; set; } public string ApiKey { get; set; } public int Priority { get; set; } + + public TestProwlCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } } } diff --git a/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs b/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs index 31bc034f9..55c6aad8f 100644 --- a/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs +++ b/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs @@ -1,10 +1,18 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; namespace NzbDrone.Core.Notifications.Pushover { public class TestPushoverCommand : ICommand { + public String CommandId { get; set; } public string UserKey { get; set; } public int Priority { get; set; } + + public TestPushoverCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } } } diff --git a/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs b/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs index 56eb75ee8..17b9d1f72 100644 --- a/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs +++ b/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs @@ -1,13 +1,21 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; namespace NzbDrone.Core.Notifications.Xbmc { public class TestXbmcCommand : ICommand { + public String CommandId { get; set; } public string Host { get; set; } public int Port { get; set; } public string Username { get; set; } public string Password { get; set; } public int DisplayTime { get; set; } + + public TestXbmcCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } } } diff --git a/NzbDrone.Core/Providers/UpdateXemMappingsCommand.cs b/NzbDrone.Core/Providers/UpdateXemMappingsCommand.cs index da4b4b53f..0660cff7b 100644 --- a/NzbDrone.Core/Providers/UpdateXemMappingsCommand.cs +++ b/NzbDrone.Core/Providers/UpdateXemMappingsCommand.cs @@ -1,13 +1,18 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; namespace NzbDrone.Core.Providers { public class UpdateXemMappingsCommand : ICommand { + public String CommandId { get; set; } public int? SeriesId { get; private set; } public UpdateXemMappingsCommand(int? seriesId) { + CommandId = HashUtil.GenerateCommandId(); + SeriesId = seriesId; } } diff --git a/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs b/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs index f38af963c..941ce42dd 100644 --- a/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs +++ b/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs @@ -1,13 +1,23 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; namespace NzbDrone.Core.Tv.Commands { public class RefreshSeriesCommand : ICommand { + public String CommandId { get; set; } public int? SeriesId { get; private set; } + public RefreshSeriesCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } + public RefreshSeriesCommand(int? seriesId) { + CommandId = HashUtil.GenerateCommandId(); + SeriesId = seriesId; } } diff --git a/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs b/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs index bbbf6a1f3..e7cfa0a9d 100644 --- a/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs +++ b/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs @@ -1,8 +1,16 @@ -using NzbDrone.Common.Messaging; +using System; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; namespace NzbDrone.Core.Update.Commands { public class ApplicationUpdateCommand : ICommand { + public String CommandId { get; set; } + + public ApplicationUpdateCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } } } \ No newline at end of file From a86c131761d6040fe8ed23be878bf7ec783f4247 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 27 Aug 2013 17:54:26 -0700 Subject: [PATCH 02/15] Added CommandStartedEvent --- NzbDrone.Common/Messaging/CommandExecutedEvent.cs | 12 ++++++++++++ NzbDrone.Common/Messaging/CommandStartedEvent.cs | 4 ++-- NzbDrone.Common/Messaging/MessageAggregator.cs | 3 +-- 3 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 NzbDrone.Common/Messaging/CommandExecutedEvent.cs diff --git a/NzbDrone.Common/Messaging/CommandExecutedEvent.cs b/NzbDrone.Common/Messaging/CommandExecutedEvent.cs new file mode 100644 index 000000000..3cb4e7f55 --- /dev/null +++ b/NzbDrone.Common/Messaging/CommandExecutedEvent.cs @@ -0,0 +1,12 @@ +namespace NzbDrone.Common.Messaging +{ + public class CommandExecutedEvent : IEvent + { + public ICommand Command { get; private set; } + + public CommandExecutedEvent(ICommand command) + { + Command = command; + } + } +} \ No newline at end of file diff --git a/NzbDrone.Common/Messaging/CommandStartedEvent.cs b/NzbDrone.Common/Messaging/CommandStartedEvent.cs index 3cb4e7f55..8c1b4163d 100644 --- a/NzbDrone.Common/Messaging/CommandStartedEvent.cs +++ b/NzbDrone.Common/Messaging/CommandStartedEvent.cs @@ -1,10 +1,10 @@ namespace NzbDrone.Common.Messaging { - public class CommandExecutedEvent : IEvent + public class CommandStartedEvent : IEvent { public ICommand Command { get; private set; } - public CommandExecutedEvent(ICommand command) + public CommandStartedEvent(ICommand command) { Command = command; } diff --git a/NzbDrone.Common/Messaging/MessageAggregator.cs b/NzbDrone.Common/Messaging/MessageAggregator.cs index 725f84ccf..59c831b71 100644 --- a/NzbDrone.Common/Messaging/MessageAggregator.cs +++ b/NzbDrone.Common/Messaging/MessageAggregator.cs @@ -60,7 +60,6 @@ public void PublishEvent(TEvent @event) where TEvent : class ,IEvent } } - private static string GetEventName(Type eventType) { if (!eventType.IsGenericType) @@ -71,7 +70,6 @@ private static string GetEventName(Type eventType) return string.Format("{0}<{1}>", eventType.Name.Remove(eventType.Name.IndexOf('`')), eventType.GetGenericArguments()[0].Name); } - public void PublishCommand(TCommand command) where TCommand : class, ICommand { Ensure.That(() => command).IsNotNull(); @@ -88,6 +86,7 @@ public void PublishCommand(TCommand command) where TCommand : class, I try { + PublishEvent(new CommandStartedEvent(command)); handler.Execute(command); sw.Stop(); PublishEvent(new CommandCompletedEvent(command)); From c917cdc0ccd679b8767a81fcb9174ab5f450751c Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 27 Aug 2013 23:51:42 -0700 Subject: [PATCH 03/15] Commands are stored in memory and prevents duplicate jobs --- NzbDrone.Api/Commands/CommandModule.cs | 12 ++- .../CommandEqualityComparerFixture.cs | 76 +++++++++++++++++++ .../NzbDrone.Common.Test.csproj | 1 + NzbDrone.Common/Cache/CacheManger.cs | 1 - .../Messaging/CommandEqualityComparer.cs | 44 +++++++++++ .../{ => Events}/CommandCompletedEvent.cs | 2 +- .../{ => Events}/CommandExecutedEvent.cs | 2 +- .../{ => Events}/CommandFailedEvent.cs | 2 +- .../{ => Events}/CommandStartedEvent.cs | 2 +- NzbDrone.Common/Messaging/ICommand.cs | 1 + .../Messaging/Manager/CommandManager.cs | 59 ++++++++++++++ .../Messaging/Manager/CommandManagerItem.cs | 29 +++++++ .../Messaging/MessageAggregator.cs | 12 ++- NzbDrone.Common/NzbDrone.Common.csproj | 10 ++- NzbDrone.Core/Jobs/TaskManager.cs | 1 + NzbDrone.Core/Providers/XemProvider.cs | 3 +- 16 files changed, 244 insertions(+), 13 deletions(-) create mode 100644 NzbDrone.Common.Test/MessagingTests/CommandEqualityComparerFixture.cs create mode 100644 NzbDrone.Common/Messaging/CommandEqualityComparer.cs rename NzbDrone.Common/Messaging/{ => Events}/CommandCompletedEvent.cs (82%) rename NzbDrone.Common/Messaging/{ => Events}/CommandExecutedEvent.cs (82%) rename NzbDrone.Common/Messaging/{ => Events}/CommandFailedEvent.cs (89%) rename NzbDrone.Common/Messaging/{ => Events}/CommandStartedEvent.cs (82%) create mode 100644 NzbDrone.Common/Messaging/Manager/CommandManager.cs create mode 100644 NzbDrone.Common/Messaging/Manager/CommandManagerItem.cs 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 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 { 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(); + } } } \ 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 @@ public ICached GetCache(Type host) 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 89% 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.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 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(); 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.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 @@ 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); } } From bb103947a21cfbeb87fcc143add41cdef61983a4 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 28 Aug 2013 08:05:41 -0700 Subject: [PATCH 04/15] Type and Command have private setters --- NzbDrone.Common/Messaging/Manager/CommandManagerItem.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/NzbDrone.Common/Messaging/Manager/CommandManagerItem.cs b/NzbDrone.Common/Messaging/Manager/CommandManagerItem.cs index 95c099151..ddfca397b 100644 --- a/NzbDrone.Common/Messaging/Manager/CommandManagerItem.cs +++ b/NzbDrone.Common/Messaging/Manager/CommandManagerItem.cs @@ -4,14 +4,10 @@ namespace NzbDrone.Common.Messaging.Manager { public class CommandManagerItem { - public String Type { get; set; } - public ICommand Command { get; set; } + public String Type { get; private set; } + public ICommand Command { get; private set; } public CommandState State { get; set; } - public CommandManagerItem() - { - } - public CommandManagerItem(ICommand command, CommandState state) { Type = command.GetType().FullName; From 3c632743a146d4c9b1a399cab8e3b64aad67e79d Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 28 Aug 2013 21:43:26 -0700 Subject: [PATCH 05/15] Better names, more info, not using events --- NzbDrone.Api/Commands/CommandModule.cs | 10 +-- .../Messaging/Manager/CommandManager.cs | 59 -------------- .../Messaging/MessageAggregator.cs | 20 +++-- .../Tracking/CommandTrackingService.cs | 78 +++++++++++++++++++ .../TrackedCommand.cs} | 10 ++- NzbDrone.Common/NzbDrone.Common.csproj | 4 +- 6 files changed, 107 insertions(+), 74 deletions(-) delete mode 100644 NzbDrone.Common/Messaging/Manager/CommandManager.cs create mode 100644 NzbDrone.Common/Messaging/Tracking/CommandTrackingService.cs rename NzbDrone.Common/Messaging/{Manager/CommandManagerItem.cs => Tracking/TrackedCommand.cs} (55%) diff --git a/NzbDrone.Api/Commands/CommandModule.cs b/NzbDrone.Api/Commands/CommandModule.cs index beb6fe246..db15b223f 100644 --- a/NzbDrone.Api/Commands/CommandModule.cs +++ b/NzbDrone.Api/Commands/CommandModule.cs @@ -4,7 +4,7 @@ using NzbDrone.Api.Extensions; using NzbDrone.Common.Composition; using NzbDrone.Common.Messaging; -using NzbDrone.Common.Messaging.Manager; +using NzbDrone.Common.Messaging.Tracking; namespace NzbDrone.Api.Commands { @@ -12,13 +12,13 @@ public class CommandModule : NzbDroneRestModule { private readonly IMessageAggregator _messageAggregator; private readonly IContainer _container; - private readonly IManageCommands _commandManager; + private readonly ITrackCommands _trackCommands; - public CommandModule(IMessageAggregator messageAggregator, IContainer container, IManageCommands commandManager) + public CommandModule(IMessageAggregator messageAggregator, IContainer container, ITrackCommands trackCommands) { _messageAggregator = messageAggregator; _container = container; - _commandManager = commandManager; + _trackCommands = trackCommands; Post["/"] = x => RunCommand(ReadResourceFromRequest()); Get["/"] = x => GetAllCommands(); @@ -39,7 +39,7 @@ private Response RunCommand(CommandResource resource) private Response GetAllCommands() { - return _commandManager.Items.AsResponse(); + return _trackCommands.AllTracked.AsResponse(); } } } \ No newline at end of file diff --git a/NzbDrone.Common/Messaging/Manager/CommandManager.cs b/NzbDrone.Common/Messaging/Manager/CommandManager.cs deleted file mode 100644 index 4666e9cae..000000000 --- a/NzbDrone.Common/Messaging/Manager/CommandManager.cs +++ /dev/null @@ -1,59 +0,0 @@ -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/MessageAggregator.cs b/NzbDrone.Common/Messaging/MessageAggregator.cs index 30f300a70..84d16012d 100644 --- a/NzbDrone.Common/Messaging/MessageAggregator.cs +++ b/NzbDrone.Common/Messaging/MessageAggregator.cs @@ -5,7 +5,7 @@ using NLog; using NzbDrone.Common.EnsureThat; using NzbDrone.Common.Messaging.Events; -using NzbDrone.Common.Messaging.Manager; +using NzbDrone.Common.Messaging.Tracking; using NzbDrone.Common.Serializer; using NzbDrone.Common.TPL; @@ -15,14 +15,14 @@ public class MessageAggregator : IMessageAggregator { private readonly Logger _logger; private readonly IServiceFactory _serviceFactory; - private readonly IManageCommands _commandManager; + private readonly ITrackCommands _trackCommands; private readonly TaskFactory _taskFactory; - public MessageAggregator(Logger logger, IServiceFactory serviceFactory, IManageCommands commandManager) + public MessageAggregator(Logger logger, IServiceFactory serviceFactory, ITrackCommands trackCommands) { _logger = logger; _serviceFactory = serviceFactory; - _commandManager = commandManager; + _trackCommands = trackCommands; var scheduler = new LimitedConcurrencyLevelTaskScheduler(2); _taskFactory = new TaskFactory(scheduler); } @@ -87,10 +87,13 @@ public void PublishCommand(TCommand command) where TCommand : class, I _logger.Debug("{0} -> {1}", command.GetType().Name, handler.GetType().Name); var sw = Stopwatch.StartNew(); + TrackedCommand queuedCommand = null; try { - if (_commandManager.ExistingItem(command)) + queuedCommand = _trackCommands.TrackIfNew(command); + + if (queuedCommand == null) { _logger.Info("Command is already in progress: {0}", command.GetType().Name); return; @@ -99,10 +102,17 @@ public void PublishCommand(TCommand command) where TCommand : class, I PublishEvent(new CommandStartedEvent(command)); handler.Execute(command); sw.Stop(); + + _trackCommands.Completed(queuedCommand, sw.Elapsed); PublishEvent(new CommandCompletedEvent(command)); } catch (Exception e) { + if (queuedCommand != null) + { + _trackCommands.Failed(queuedCommand, e); + } + PublishEvent(new CommandFailedEvent(command, e)); throw; } diff --git a/NzbDrone.Common/Messaging/Tracking/CommandTrackingService.cs b/NzbDrone.Common/Messaging/Tracking/CommandTrackingService.cs new file mode 100644 index 000000000..29f0fc0de --- /dev/null +++ b/NzbDrone.Common/Messaging/Tracking/CommandTrackingService.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common.Cache; + +namespace NzbDrone.Common.Messaging.Tracking +{ + public interface ITrackCommands + { + TrackedCommand TrackIfNew(ICommand command); + TrackedCommand Completed(TrackedCommand trackedCommand, TimeSpan runtime); + TrackedCommand Failed(TrackedCommand trackedCommand, Exception e); + ICollection AllTracked { get; } + Boolean ExistingCommand(ICommand command); + } + + public class TrackCommands : ITrackCommands + { + private readonly ICached _cache; + + public TrackCommands(ICacheManger cacheManger) + { + _cache = cacheManger.GetCache(GetType()); + } + + public TrackedCommand TrackIfNew(ICommand command) + { + if (ExistingCommand(command)) + { + return null; + } + + var trackedCommand = new TrackedCommand(command, CommandState.Running); + _cache.Set(command.CommandId, trackedCommand); + + return trackedCommand; + } + + public TrackedCommand Completed(TrackedCommand trackedCommand, TimeSpan runtime) + { + trackedCommand.StateChangeTime = DateTime.UtcNow; + trackedCommand.State = CommandState.Completed; + trackedCommand.Runtime = runtime; + + _cache.Set(trackedCommand.Command.CommandId, trackedCommand); + + return trackedCommand; + } + + public TrackedCommand Failed(TrackedCommand trackedCommand, Exception e) + { + trackedCommand.StateChangeTime = DateTime.UtcNow; + trackedCommand.State = CommandState.Failed; + trackedCommand.Exception = e; + + _cache.Set(trackedCommand.Command.CommandId, trackedCommand); + + return trackedCommand; + } + + public ICollection AllTracked + { + get + { + return _cache.Values; + } + } + + public bool ExistingCommand(ICommand command) + { + var running = AllTracked.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/Tracking/TrackedCommand.cs similarity index 55% rename from NzbDrone.Common/Messaging/Manager/CommandManagerItem.cs rename to NzbDrone.Common/Messaging/Tracking/TrackedCommand.cs index ddfca397b..41f983d13 100644 --- a/NzbDrone.Common/Messaging/Manager/CommandManagerItem.cs +++ b/NzbDrone.Common/Messaging/Tracking/TrackedCommand.cs @@ -1,18 +1,22 @@ using System; -namespace NzbDrone.Common.Messaging.Manager +namespace NzbDrone.Common.Messaging.Tracking { - public class CommandManagerItem + public class TrackedCommand { public String Type { get; private set; } public ICommand Command { get; private set; } public CommandState State { get; set; } + public DateTime StateChangeTime { get; set; } + public TimeSpan Runtime { get; set; } + public Exception Exception { get; set; } - public CommandManagerItem(ICommand command, CommandState state) + public TrackedCommand(ICommand command, CommandState state) { Type = command.GetType().FullName; Command = command; State = state; + StateChangeTime = DateTime.UtcNow; } } diff --git a/NzbDrone.Common/NzbDrone.Common.csproj b/NzbDrone.Common/NzbDrone.Common.csproj index 105c14d22..12ed1b7a9 100644 --- a/NzbDrone.Common/NzbDrone.Common.csproj +++ b/NzbDrone.Common/NzbDrone.Common.csproj @@ -92,8 +92,8 @@ - - + + From 780e374122b3e8f76e25ce63742cf50dd7c40d0d Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 30 Aug 2013 09:18:12 -0700 Subject: [PATCH 06/15] Added ProgressMessaging through nlog --- NzbDrone.Api/NancyBootstrapper.cs | 5 +- .../Messaging/MessageAggregator.cs | 14 ++-- .../ParserTests/ParserFixture.cs | 1 + .../Lifecycle/ApplicationStartedEvent.cs | 1 - NzbDrone.Core/NzbDrone.Core.csproj | 3 + .../NewProgressMessageEvent.cs | 18 ++++ .../ProgressMessaging/ProgressMessage.cs | 14 ++++ .../ProgressMessagingTarget.cs | 82 +++++++++++++++++++ UI/Router.js | 2 +- 9 files changed, 128 insertions(+), 12 deletions(-) create mode 100644 NzbDrone.Core/ProgressMessaging/NewProgressMessageEvent.cs create mode 100644 NzbDrone.Core/ProgressMessaging/ProgressMessage.cs create mode 100644 NzbDrone.Core/ProgressMessaging/ProgressMessagingTarget.cs diff --git a/NzbDrone.Api/NancyBootstrapper.cs b/NzbDrone.Api/NancyBootstrapper.cs index 5f65c80e2..ed1463a23 100644 --- a/NzbDrone.Api/NancyBootstrapper.cs +++ b/NzbDrone.Api/NancyBootstrapper.cs @@ -9,6 +9,7 @@ using NzbDrone.Common.Messaging; using NzbDrone.Core.Instrumentation; using NzbDrone.Core.Lifecycle; +using NzbDrone.Core.ProgressMessaging; using TinyIoC; namespace NzbDrone.Api @@ -28,14 +29,12 @@ protected override void ApplicationStartup(TinyIoCContainer container, IPipeline { _logger.Info("Starting NzbDrone API"); - RegisterPipelines(pipelines); container.Resolve().Register(); container.Resolve().Register(pipelines); container.Resolve().PublishEvent(new ApplicationStartedEvent()); - ApplicationPipelines.OnError.AddItemToEndOfPipeline(container.Resolve().HandleException); } @@ -47,10 +46,8 @@ private void RegisterPipelines(IPipelines pipelines) { registerNancyPipeline.Register(pipelines); } - } - protected override TinyIoCContainer GetApplicationContainer() { return _tinyIoCContainer; diff --git a/NzbDrone.Common/Messaging/MessageAggregator.cs b/NzbDrone.Common/Messaging/MessageAggregator.cs index 84d16012d..0be1a0951 100644 --- a/NzbDrone.Common/Messaging/MessageAggregator.cs +++ b/NzbDrone.Common/Messaging/MessageAggregator.cs @@ -87,30 +87,32 @@ public void PublishCommand(TCommand command) where TCommand : class, I _logger.Debug("{0} -> {1}", command.GetType().Name, handler.GetType().Name); var sw = Stopwatch.StartNew(); - TrackedCommand queuedCommand = null; + TrackedCommand trackedCommand = null; try { - queuedCommand = _trackCommands.TrackIfNew(command); + trackedCommand = _trackCommands.TrackIfNew(command); - if (queuedCommand == null) + if (trackedCommand == null) { _logger.Info("Command is already in progress: {0}", command.GetType().Name); return; } + MappedDiagnosticsContext.Set("CommandId", trackedCommand.Command.CommandId); + PublishEvent(new CommandStartedEvent(command)); handler.Execute(command); sw.Stop(); - _trackCommands.Completed(queuedCommand, sw.Elapsed); + _trackCommands.Completed(trackedCommand, sw.Elapsed); PublishEvent(new CommandCompletedEvent(command)); } catch (Exception e) { - if (queuedCommand != null) + if (trackedCommand != null) { - _trackCommands.Failed(queuedCommand, e); + _trackCommands.Failed(trackedCommand, e); } PublishEvent(new CommandFailedEvent(command, e)); diff --git a/NzbDrone.Core.Test/ParserTests/ParserFixture.cs b/NzbDrone.Core.Test/ParserTests/ParserFixture.cs index 192ff7380..ac797bba8 100644 --- a/NzbDrone.Core.Test/ParserTests/ParserFixture.cs +++ b/NzbDrone.Core.Test/ParserTests/ParserFixture.cs @@ -80,6 +80,7 @@ public class ParserFixture : CoreTest [TestCase("Top_Gear.19x06.720p_HDTV_x264-FoV", "Top Gear", 19, 6)] [TestCase("Portlandia.S03E10.Alexandra.720p.WEB-DL.AAC2.0.H.264-CROM.mkv", "Portlandia", 3, 10)] [TestCase("(Game of Thrones s03 e - \"Game of Thrones Season 3 Episode 10\"", "Game of Thrones", 3, 10)] + [TestCase("House.Hunters.International.S05E607.720p.hdtv.x264", "House.Hunters.International", 5, 607)] public void ParseTitle_single(string postTitle, string title, int seasonNumber, int episodeNumber) { var result = Parser.Parser.ParseTitle(postTitle); diff --git a/NzbDrone.Core/Lifecycle/ApplicationStartedEvent.cs b/NzbDrone.Core/Lifecycle/ApplicationStartedEvent.cs index 985986b05..f66622dd3 100644 --- a/NzbDrone.Core/Lifecycle/ApplicationStartedEvent.cs +++ b/NzbDrone.Core/Lifecycle/ApplicationStartedEvent.cs @@ -6,5 +6,4 @@ public class ApplicationStartedEvent : IEvent { } - } \ No newline at end of file diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 3f95a1ab6..6a13f8b82 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -219,6 +219,9 @@ + + + diff --git a/NzbDrone.Core/ProgressMessaging/NewProgressMessageEvent.cs b/NzbDrone.Core/ProgressMessaging/NewProgressMessageEvent.cs new file mode 100644 index 000000000..0b2905312 --- /dev/null +++ b/NzbDrone.Core/ProgressMessaging/NewProgressMessageEvent.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.ProgressMessaging +{ + public class NewProgressMessageEvent : IEvent + { + public ProgressMessage ProgressMessage { get; set; } + + public NewProgressMessageEvent(ProgressMessage progressMessage) + { + ProgressMessage = progressMessage; + } + } +} diff --git a/NzbDrone.Core/ProgressMessaging/ProgressMessage.cs b/NzbDrone.Core/ProgressMessaging/ProgressMessage.cs new file mode 100644 index 000000000..876087efb --- /dev/null +++ b/NzbDrone.Core/ProgressMessaging/ProgressMessage.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.ProgressMessaging +{ + public class ProgressMessage + { + public DateTime Time { get; set; } + public String CommandId { get; set; } + public String Message { get; set; } + } +} diff --git a/NzbDrone.Core/ProgressMessaging/ProgressMessagingTarget.cs b/NzbDrone.Core/ProgressMessaging/ProgressMessagingTarget.cs new file mode 100644 index 000000000..247fa7686 --- /dev/null +++ b/NzbDrone.Core/ProgressMessaging/ProgressMessagingTarget.cs @@ -0,0 +1,82 @@ +using System; +using NLog.Config; +using NLog; +using NLog.Layouts; +using NLog.Targets; +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Lifecycle; + +namespace NzbDrone.Core.ProgressMessaging +{ + + public class ProgressMessagingTarget : TargetWithLayout, IHandle, IHandle + { + private readonly IMessageAggregator _messageAggregator; + public LoggingRule Rule { get; set; } + + public ProgressMessagingTarget(IMessageAggregator messageAggregator) + { + _messageAggregator = messageAggregator; + } + + public void Register() + { + Layout = new SimpleLayout("${callsite:className=false:fileName=false:includeSourcePath=false:methodName=true}"); + + Rule = new LoggingRule("*", this); + Rule.EnableLoggingForLevel(LogLevel.Info); + + LogManager.Configuration.AddTarget("ProgressMessagingLogger", this); + LogManager.Configuration.LoggingRules.Add(Rule); + LogManager.ConfigurationReloaded += OnLogManagerOnConfigurationReloaded; + LogManager.ReconfigExistingLoggers(); + } + + public void UnRegister() + { + LogManager.ConfigurationReloaded -= OnLogManagerOnConfigurationReloaded; + LogManager.Configuration.RemoveTarget("ProgressMessagingLogger"); + LogManager.Configuration.LoggingRules.Remove(Rule); + LogManager.ReconfigExistingLoggers(); + Dispose(); + } + + private void OnLogManagerOnConfigurationReloaded(object sender, LoggingConfigurationReloadedEventArgs args) + { + Register(); + } + + protected override void Write(LogEventInfo logEvent) + { + var commandId = MappedDiagnosticsContext.Get("CommandId"); + + if (String.IsNullOrWhiteSpace(commandId)) + { + return; + } + + var message = new ProgressMessage(); + message.Time = logEvent.TimeStamp; + message.CommandId = commandId; + message.Message = logEvent.FormattedMessage; + + _messageAggregator.PublishEvent(new NewProgressMessageEvent(message)); + } + + public void Handle(ApplicationStartedEvent message) + { + if (!LogManager.Configuration.LoggingRules.Contains(Rule)) + { + Register(); + } + } + + public void Handle(ApplicationShutdownRequested message) + { + if (LogManager.Configuration.LoggingRules.Contains(Rule)) + { + UnRegister(); + } + } + } +} \ No newline at end of file diff --git a/UI/Router.js b/UI/Router.js index 76513c471..984bf94ec 100644 --- a/UI/Router.js +++ b/UI/Router.js @@ -42,7 +42,7 @@ require( RouterBinder.bind(App.Router); App.navbarRegion.show(new NavbarView()); $('body').addClass('started'); - }) + }); }); return App.Router; From 56cd80d24a7ced98ea009af0b55f259a6ff92e9b Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 30 Aug 2013 10:27:17 -0700 Subject: [PATCH 07/15] TrackedCommands are cleaned up automatically TrackedCommandCleanupCommand is not tracked --- NzbDrone.Api/Commands/CommandModule.cs | 2 +- NzbDrone.Common/Cache/Cached.cs | 14 ++++++- NzbDrone.Common/Cache/ICached.cs | 1 + .../Tracking/CommandTrackingService.cs | 39 +++++++++++++------ .../Tracking/TrackedCommandCleanupCommand.cs | 17 ++++++++ NzbDrone.Common/NzbDrone.Common.csproj | 1 + NzbDrone.Core/Jobs/TaskManager.cs | 4 +- 7 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 NzbDrone.Common/Messaging/Tracking/TrackedCommandCleanupCommand.cs diff --git a/NzbDrone.Api/Commands/CommandModule.cs b/NzbDrone.Api/Commands/CommandModule.cs index db15b223f..3f9a2e220 100644 --- a/NzbDrone.Api/Commands/CommandModule.cs +++ b/NzbDrone.Api/Commands/CommandModule.cs @@ -39,7 +39,7 @@ private Response RunCommand(CommandResource resource) private Response GetAllCommands() { - return _trackCommands.AllTracked.AsResponse(); + return _trackCommands.AllTracked().AsResponse(); } } } \ No newline at end of file diff --git a/NzbDrone.Common/Cache/Cached.cs b/NzbDrone.Common/Cache/Cached.cs index eef89f317..61d4733e4 100644 --- a/NzbDrone.Common/Cache/Cached.cs +++ b/NzbDrone.Common/Cache/Cached.cs @@ -61,6 +61,19 @@ public T Find(string key) return value.Object; } + public T Remove(string key) + { + CacheItem value; + _store.TryRemove(key, out value); + + if (value == null) + { + return default(T); + } + + return value.Object; + } + public T Get(string key, Func function, TimeSpan? lifeTime = null) { Ensure.That(() => key).IsNotNullOrWhiteSpace(); @@ -81,7 +94,6 @@ public T Get(string key, Func function, TimeSpan? lifeTime = null) return value; } - public void Clear() { _store.Clear(); diff --git a/NzbDrone.Common/Cache/ICached.cs b/NzbDrone.Common/Cache/ICached.cs index 3708b72af..7e68b395b 100644 --- a/NzbDrone.Common/Cache/ICached.cs +++ b/NzbDrone.Common/Cache/ICached.cs @@ -13,6 +13,7 @@ public interface ICached : ICached void Set(string key, T value, TimeSpan? lifetime = null); T Get(string key, Func function, TimeSpan? lifeTime = null); T Find(string key); + T Remove(string key); ICollection Values { get; } } diff --git a/NzbDrone.Common/Messaging/Tracking/CommandTrackingService.cs b/NzbDrone.Common/Messaging/Tracking/CommandTrackingService.cs index 29f0fc0de..5962cabd2 100644 --- a/NzbDrone.Common/Messaging/Tracking/CommandTrackingService.cs +++ b/NzbDrone.Common/Messaging/Tracking/CommandTrackingService.cs @@ -10,11 +10,11 @@ public interface ITrackCommands TrackedCommand TrackIfNew(ICommand command); TrackedCommand Completed(TrackedCommand trackedCommand, TimeSpan runtime); TrackedCommand Failed(TrackedCommand trackedCommand, Exception e); - ICollection AllTracked { get; } + List AllTracked(); Boolean ExistingCommand(ICommand command); } - public class TrackCommands : ITrackCommands + public class TrackCommands : ITrackCommands, IExecute { private readonly ICached _cache; @@ -31,7 +31,7 @@ public TrackedCommand TrackIfNew(ICommand command) } var trackedCommand = new TrackedCommand(command, CommandState.Running); - _cache.Set(command.CommandId, trackedCommand); + Store(trackedCommand); return trackedCommand; } @@ -42,7 +42,7 @@ public TrackedCommand Completed(TrackedCommand trackedCommand, TimeSpan runtime) trackedCommand.State = CommandState.Completed; trackedCommand.Runtime = runtime; - _cache.Set(trackedCommand.Command.CommandId, trackedCommand); + Store(trackedCommand); return trackedCommand; } @@ -53,26 +53,43 @@ public TrackedCommand Failed(TrackedCommand trackedCommand, Exception e) trackedCommand.State = CommandState.Failed; trackedCommand.Exception = e; - _cache.Set(trackedCommand.Command.CommandId, trackedCommand); + Store(trackedCommand); return trackedCommand; } - public ICollection AllTracked + public List AllTracked() { - get - { - return _cache.Values; - } + return _cache.Values.ToList(); } public bool ExistingCommand(ICommand command) { - var running = AllTracked.Where(i => i.Type == command.GetType().FullName && i.State == CommandState.Running); + var running = AllTracked().Where(i => i.Type == command.GetType().FullName && i.State == CommandState.Running); var result = running.Select(r => r.Command).Contains(command, new CommandEqualityComparer()); return result; } + + private void Store(TrackedCommand trackedCommand) + { + if (trackedCommand.Command.GetType() == typeof(TrackedCommandCleanupCommand)) + { + return; + } + + _cache.Set(trackedCommand.Command.CommandId, trackedCommand); + } + + public void Execute(TrackedCommandCleanupCommand message) + { + var old = AllTracked().Where(c => c.StateChangeTime < DateTime.UtcNow.AddMinutes(-15)); + + foreach (var trackedCommand in old) + { + _cache.Remove(trackedCommand.Command.CommandId); + } + } } } diff --git a/NzbDrone.Common/Messaging/Tracking/TrackedCommandCleanupCommand.cs b/NzbDrone.Common/Messaging/Tracking/TrackedCommandCleanupCommand.cs new file mode 100644 index 000000000..830ba7fb5 --- /dev/null +++ b/NzbDrone.Common/Messaging/Tracking/TrackedCommandCleanupCommand.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Common.Messaging.Tracking +{ + public class TrackedCommandCleanupCommand : ICommand + { + public string CommandId { get; private set; } + + public TrackedCommandCleanupCommand() + { + CommandId = HashUtil.GenerateCommandId(); + } + } +} diff --git a/NzbDrone.Common/NzbDrone.Common.csproj b/NzbDrone.Common/NzbDrone.Common.csproj index 12ed1b7a9..f980d4610 100644 --- a/NzbDrone.Common/NzbDrone.Common.csproj +++ b/NzbDrone.Common/NzbDrone.Common.csproj @@ -96,6 +96,7 @@ + diff --git a/NzbDrone.Core/Jobs/TaskManager.cs b/NzbDrone.Core/Jobs/TaskManager.cs index 83b079769..57a894629 100644 --- a/NzbDrone.Core/Jobs/TaskManager.cs +++ b/NzbDrone.Core/Jobs/TaskManager.cs @@ -4,6 +4,7 @@ using NLog; using NzbDrone.Common.Messaging; using NzbDrone.Common.Messaging.Events; +using NzbDrone.Common.Messaging.Tracking; using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration.Events; using NzbDrone.Core.Indexers; @@ -48,7 +49,8 @@ public void Handle(ApplicationStartedEvent message) new ScheduledTask{ Interval = 12*60, TypeName = typeof(RefreshSeriesCommand).FullName}, new ScheduledTask{ Interval = 1, TypeName = typeof(DownloadedEpisodesScanCommand).FullName}, new ScheduledTask{ Interval = 60, TypeName = typeof(ApplicationUpdateCommand).FullName}, - new ScheduledTask{ Interval = 1*60, TypeName = typeof(TrimLogCommand).FullName} + new ScheduledTask{ Interval = 1*60, TypeName = typeof(TrimLogCommand).FullName}, + new ScheduledTask{ Interval = 5, TypeName = typeof(TrackedCommandCleanupCommand).FullName} }; var currentTasks = _scheduledTaskRepository.All(); From 772ab3c92132196b63b15c92a262acc0bcce228f Mon Sep 17 00:00:00 2001 From: Keivan Beigi Date: Fri, 30 Aug 2013 15:14:44 -0700 Subject: [PATCH 08/15] Cahce.Remove is now void Added tests for Cache.Remove --- .../CacheTests/CachedFixture.cs | 17 +++++++++++++++++ NzbDrone.Common/Cache/Cached.cs | 9 +-------- NzbDrone.Common/Cache/ICached.cs | 2 +- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/NzbDrone.Common.Test/CacheTests/CachedFixture.cs b/NzbDrone.Common.Test/CacheTests/CachedFixture.cs index 4a1cf9021..1d91558a6 100644 --- a/NzbDrone.Common.Test/CacheTests/CachedFixture.cs +++ b/NzbDrone.Common.Test/CacheTests/CachedFixture.cs @@ -48,6 +48,23 @@ public void should_be_able_to_update_key() _cachedString.Find("Key").Should().Be("New"); } + + [Test] + public void should_be_able_to_remove_key() + { + _cachedString.Set("Key", "Value"); + + _cachedString.Remove("Key"); + + _cachedString.Find("Key").Should().BeNull(); + } + + [Test] + public void should_be_able_to_remove_non_existing_key() + { + _cachedString.Remove("Key"); + } + [Test] public void should_store_null() { diff --git a/NzbDrone.Common/Cache/Cached.cs b/NzbDrone.Common/Cache/Cached.cs index 61d4733e4..7269a9a53 100644 --- a/NzbDrone.Common/Cache/Cached.cs +++ b/NzbDrone.Common/Cache/Cached.cs @@ -61,17 +61,10 @@ public T Find(string key) return value.Object; } - public T Remove(string key) + public void Remove(string key) { CacheItem value; _store.TryRemove(key, out value); - - if (value == null) - { - return default(T); - } - - return value.Object; } public T Get(string key, Func function, TimeSpan? lifeTime = null) diff --git a/NzbDrone.Common/Cache/ICached.cs b/NzbDrone.Common/Cache/ICached.cs index 7e68b395b..1c9e50812 100644 --- a/NzbDrone.Common/Cache/ICached.cs +++ b/NzbDrone.Common/Cache/ICached.cs @@ -13,7 +13,7 @@ public interface ICached : ICached void Set(string key, T value, TimeSpan? lifetime = null); T Get(string key, Func function, TimeSpan? lifeTime = null); T Find(string key); - T Remove(string key); + void Remove(string key); ICollection Values { get; } } From c96ba5efd33e87d17e50ab85e5aef34c4722d080 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 30 Aug 2013 20:08:19 -0700 Subject: [PATCH 09/15] Commands return immediately and signalr is used to control the UI --- NzbDrone.Api/Commands/CommandConnection.cs | 42 +++++ NzbDrone.Api/Commands/CommandModule.cs | 6 +- NzbDrone.Api/Extensions/ReqResExtensions.cs | 2 - NzbDrone.Api/NzbDrone.Api.csproj | 1 + .../MessageAggregatorCommandTests.cs | 4 +- .../Messaging/Events/CommandCompletedEvent.cs | 8 +- .../Messaging/Events/CommandExecutedEvent.cs | 8 +- .../Messaging/Events/CommandFailedEvent.cs | 5 +- .../Messaging/Events/CommandStartedEvent.cs | 8 +- .../Messaging/IMessageAggregator.cs | 8 +- .../Messaging/MessageAggregator.cs | 89 ++++++---- NzbDrone.Common/Messaging/TestCommand.cs | 2 +- .../Tracking/CommandTrackingService.cs | 40 ++++- .../Messaging/Tracking/ExistingCommand.cs | 19 +++ .../Messaging/Tracking/TrackedCommand.cs | 4 + NzbDrone.Common/NzbDrone.Common.csproj | 1 + .../Scene/UpdateSceneMappingCommand.cs | 2 +- .../IndexerSearch/EpisodeSearchCommand.cs | 2 +- .../IndexerSearch/SeasonSearchCommand.cs | 2 +- .../IndexerSearch/SeriesSearchCommand.cs | 2 +- NzbDrone.Core/Indexers/RssSyncCommand.cs | 2 +- .../Commands/ClearLogCommand.cs | 2 +- .../Commands/DeleteLogFilesCommand.cs | 2 +- .../Commands/TrimLogCommand.cs | 2 +- NzbDrone.Core/Jobs/TaskManager.cs | 2 +- .../MediaFiles/Commands/CleanMediaFileDb.cs | 2 +- .../Commands/CleanUpRecycleBinCommand.cs | 2 +- .../Commands/DownloadedEpisodesScanCommand.cs | 2 +- .../Commands/RenameSeasonCommand.cs | 6 +- .../Commands/RenameSeriesCommand.cs | 4 +- .../Notifications/Email/TestEmailCommand.cs | 2 +- .../Notifications/Growl/TestGrowlCommand.cs | 2 +- .../Plex/TestPlexClientCommand.cs | 2 +- .../Plex/TestPlexServerCommand.cs | 2 +- .../Notifications/Prowl/TestProwlCommand.cs | 2 +- .../Pushover/TestPushoverCommand.cs | 2 +- .../Notifications/Xbmc/TestXbmcCommand.cs | 2 +- .../Providers/UpdateXemMappingsCommand.cs | 5 +- .../Tv/Commands/RefreshSeriesCommand.cs | 4 +- .../Commands/ApplicationUpdateCommand.cs | 2 +- UI/Commands/CommandCollection.js | 17 ++ UI/Commands/CommandModel.js | 8 + UI/Episode/Search/Layout.js | 15 +- UI/Router.js | 3 +- UI/SeasonPass/SeriesLayout.js | 2 + UI/Series/Details/SeasonLayout.js | 18 +- UI/Series/Details/SeasonMenu/ItemView.js | 2 +- UI/Series/Details/SeriesDetailsLayout.js | 10 +- UI/Series/Index/SeriesIndexLayout.js | 3 - UI/Settings/Notifications/EditTemplate.html | 2 +- UI/Settings/Notifications/EditView.js | 45 ++--- UI/Shared/Actioneer.js | 158 ++++++++++++++---- UI/Shared/Messenger.js | 8 +- UI/Shared/Toolbar/Button/ButtonView.js | 77 ++------- UI/Shared/Toolbar/ToolbarLayout.js | 3 - 55 files changed, 439 insertions(+), 238 deletions(-) create mode 100644 NzbDrone.Api/Commands/CommandConnection.cs create mode 100644 NzbDrone.Common/Messaging/Tracking/ExistingCommand.cs create mode 100644 UI/Commands/CommandCollection.js create mode 100644 UI/Commands/CommandModel.js diff --git a/NzbDrone.Api/Commands/CommandConnection.cs b/NzbDrone.Api/Commands/CommandConnection.cs new file mode 100644 index 000000000..a09125734 --- /dev/null +++ b/NzbDrone.Api/Commands/CommandConnection.cs @@ -0,0 +1,42 @@ +using System; +using Microsoft.AspNet.SignalR; +using Microsoft.AspNet.SignalR.Infrastructure; +using NzbDrone.Api.SignalR; +using NzbDrone.Common.Messaging; +using NzbDrone.Common.Messaging.Events; +using NzbDrone.Common.Messaging.Tracking; + +namespace NzbDrone.Api.Commands +{ + public class CommandConnection : NzbDronePersistentConnection, + IHandleAsync, + IHandleAsync, + IHandle + { + public override string Resource + { + get { return "/Command"; } + } + + public void HandleAsync(CommandStartedEvent message) + { + BroadcastMessage(message.Command); + } + + public void HandleAsync(CommandCompletedEvent message) + { + BroadcastMessage(message.Command); + } + + public void Handle(CommandFailedEvent message) + { + BroadcastMessage(message.Command); + } + + private void BroadcastMessage(TrackedCommand trackedCommand) + { + var context = ((ConnectionManager)GlobalHost.ConnectionManager).GetConnection(GetType()); + context.Connection.Broadcast(trackedCommand); + } + } +} diff --git a/NzbDrone.Api/Commands/CommandModule.cs b/NzbDrone.Api/Commands/CommandModule.cs index 3f9a2e220..8ad4a5475 100644 --- a/NzbDrone.Api/Commands/CommandModule.cs +++ b/NzbDrone.Api/Commands/CommandModule.cs @@ -2,6 +2,7 @@ using System.Linq; using Nancy; using NzbDrone.Api.Extensions; +using NzbDrone.Api.Mapping; using NzbDrone.Common.Composition; using NzbDrone.Common.Messaging; using NzbDrone.Common.Messaging.Tracking; @@ -32,9 +33,10 @@ private Response RunCommand(CommandResource resource) .Equals(resource.Command, StringComparison.InvariantCultureIgnoreCase)); dynamic command = Request.Body.FromJson(commandType); - _messageAggregator.PublishCommand(command); - return resource.AsResponse(HttpStatusCode.Created); + var response = (TrackedCommand) _messageAggregator.PublishCommandAsync(command); + + return response.AsResponse(HttpStatusCode.Created); } private Response GetAllCommands() diff --git a/NzbDrone.Api/Extensions/ReqResExtensions.cs b/NzbDrone.Api/Extensions/ReqResExtensions.cs index fd9980347..1f1d89180 100644 --- a/NzbDrone.Api/Extensions/ReqResExtensions.cs +++ b/NzbDrone.Api/Extensions/ReqResExtensions.cs @@ -12,7 +12,6 @@ public static class ReqResExtensions { private static readonly NancyJsonSerializer NancySerializer = new NancyJsonSerializer(); - public static readonly string LastModified = BuildInfo.BuildDateTime.ToString("r"); public static T FromJson(this Stream body) where T : class, new() @@ -25,7 +24,6 @@ public static T FromJson(this Stream body, Type type) return (T)FromJson(body, type); } - public static object FromJson(this Stream body, Type type) { var reader = new StreamReader(body, true); diff --git a/NzbDrone.Api/NzbDrone.Api.csproj b/NzbDrone.Api/NzbDrone.Api.csproj index d0a03bd17..fe4ffcc5a 100644 --- a/NzbDrone.Api/NzbDrone.Api.csproj +++ b/NzbDrone.Api/NzbDrone.Api.csproj @@ -83,6 +83,7 @@ + diff --git a/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs b/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs index 38ce954f3..a0534a1ba 100644 --- a/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs +++ b/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs @@ -76,7 +76,7 @@ public void broken_executor_should_throw_the_exception() public class CommandA : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } // ReSharper disable UnusedParameter.Local public CommandA(int id = 0) // ReSharper restore UnusedParameter.Local @@ -87,7 +87,7 @@ public CommandA(int id = 0) public class CommandB : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public CommandB() { diff --git a/NzbDrone.Common/Messaging/Events/CommandCompletedEvent.cs b/NzbDrone.Common/Messaging/Events/CommandCompletedEvent.cs index b65f25bf2..2c1f4f312 100644 --- a/NzbDrone.Common/Messaging/Events/CommandCompletedEvent.cs +++ b/NzbDrone.Common/Messaging/Events/CommandCompletedEvent.cs @@ -1,10 +1,12 @@ -namespace NzbDrone.Common.Messaging.Events +using NzbDrone.Common.Messaging.Tracking; + +namespace NzbDrone.Common.Messaging.Events { public class CommandCompletedEvent : IEvent { - public ICommand Command { get; private set; } + public TrackedCommand Command { get; private set; } - public CommandCompletedEvent(ICommand command) + public CommandCompletedEvent(TrackedCommand command) { Command = command; } diff --git a/NzbDrone.Common/Messaging/Events/CommandExecutedEvent.cs b/NzbDrone.Common/Messaging/Events/CommandExecutedEvent.cs index e5e9120b3..8ed4b5f75 100644 --- a/NzbDrone.Common/Messaging/Events/CommandExecutedEvent.cs +++ b/NzbDrone.Common/Messaging/Events/CommandExecutedEvent.cs @@ -1,10 +1,12 @@ -namespace NzbDrone.Common.Messaging.Events +using NzbDrone.Common.Messaging.Tracking; + +namespace NzbDrone.Common.Messaging.Events { public class CommandExecutedEvent : IEvent { - public ICommand Command { get; private set; } + public TrackedCommand Command { get; private set; } - public CommandExecutedEvent(ICommand command) + public CommandExecutedEvent(TrackedCommand command) { Command = command; } diff --git a/NzbDrone.Common/Messaging/Events/CommandFailedEvent.cs b/NzbDrone.Common/Messaging/Events/CommandFailedEvent.cs index ef4934e41..f796f0f03 100644 --- a/NzbDrone.Common/Messaging/Events/CommandFailedEvent.cs +++ b/NzbDrone.Common/Messaging/Events/CommandFailedEvent.cs @@ -1,13 +1,14 @@ using System; +using NzbDrone.Common.Messaging.Tracking; namespace NzbDrone.Common.Messaging.Events { public class CommandFailedEvent : IEvent { - public ICommand Command { get; private set; } + public TrackedCommand Command { get; private set; } public Exception Exception { get; private set; } - public CommandFailedEvent(ICommand command, Exception exception) + public CommandFailedEvent(TrackedCommand command, Exception exception) { Command = command; Exception = exception; diff --git a/NzbDrone.Common/Messaging/Events/CommandStartedEvent.cs b/NzbDrone.Common/Messaging/Events/CommandStartedEvent.cs index 762c9287c..14296c02a 100644 --- a/NzbDrone.Common/Messaging/Events/CommandStartedEvent.cs +++ b/NzbDrone.Common/Messaging/Events/CommandStartedEvent.cs @@ -1,10 +1,12 @@ -namespace NzbDrone.Common.Messaging.Events +using NzbDrone.Common.Messaging.Tracking; + +namespace NzbDrone.Common.Messaging.Events { public class CommandStartedEvent : IEvent { - public ICommand Command { get; private set; } + public TrackedCommand Command { get; private set; } - public CommandStartedEvent(ICommand command) + public CommandStartedEvent(TrackedCommand command) { Command = command; } diff --git a/NzbDrone.Common/Messaging/IMessageAggregator.cs b/NzbDrone.Common/Messaging/IMessageAggregator.cs index 6de5ac3c8..9edd5b165 100644 --- a/NzbDrone.Common/Messaging/IMessageAggregator.cs +++ b/NzbDrone.Common/Messaging/IMessageAggregator.cs @@ -1,4 +1,6 @@ -namespace NzbDrone.Common.Messaging +using NzbDrone.Common.Messaging.Tracking; + +namespace NzbDrone.Common.Messaging { /// /// Enables loosely-coupled publication of events. @@ -7,6 +9,8 @@ public interface IMessageAggregator { void PublishEvent(TEvent @event) where TEvent : class, IEvent; void PublishCommand(TCommand command) where TCommand : class, ICommand; - void PublishCommand(string commandType); + void PublishCommand(string commandTypeName); + TrackedCommand PublishCommandAsync(TCommand command) where TCommand : class, ICommand; + TrackedCommand PublishCommandAsync(string commandTypeName); } } \ No newline at end of file diff --git a/NzbDrone.Common/Messaging/MessageAggregator.cs b/NzbDrone.Common/Messaging/MessageAggregator.cs index 0be1a0951..b8ebd3690 100644 --- a/NzbDrone.Common/Messaging/MessageAggregator.cs +++ b/NzbDrone.Common/Messaging/MessageAggregator.cs @@ -78,61 +78,94 @@ public void PublishCommand(TCommand command) where TCommand : class, I { Ensure.That(() => command).IsNotNull(); - var handlerContract = typeof(IExecute<>).MakeGenericType(command.GetType()); + _logger.Trace("Publishing {0}", command.GetType().Name); + + var trackedCommand = _trackCommands.TrackIfNew(command); + + if (trackedCommand == null) + { + _logger.Info("Command is already in progress: {0}", command.GetType().Name); + return; + } + + ExecuteCommand(trackedCommand); + } + + public void PublishCommand(string commandTypeName) + { + dynamic command = GetCommand(commandTypeName); + PublishCommand(command); + } + + public TrackedCommand PublishCommandAsync(TCommand command) where TCommand : class, ICommand + { + Ensure.That(() => command).IsNotNull(); _logger.Trace("Publishing {0}", command.GetType().Name); + var existingCommand = _trackCommands.TrackNewOrGet(command); + + if (existingCommand.Existing) + { + _logger.Info("Command is already in progress: {0}", command.GetType().Name); + return existingCommand.TrackedCommand; + } + + _taskFactory.StartNew(() => ExecuteCommand(existingCommand.TrackedCommand) + , TaskCreationOptions.PreferFairness) + .LogExceptions(); + + return existingCommand.TrackedCommand; + } + + public TrackedCommand PublishCommandAsync(string commandTypeName) + { + dynamic command = GetCommand(commandTypeName); + return PublishCommandAsync(command); + } + + private dynamic GetCommand(string commandTypeName) + { + var commandType = _serviceFactory.GetImplementations(typeof(ICommand)) + .Single(c => c.FullName.Equals(commandTypeName, StringComparison.InvariantCultureIgnoreCase)); + + return Json.Deserialize("{}", commandType); + } + + private void ExecuteCommand(TrackedCommand trackedCommand) where TCommand : class, ICommand + { + var command = (TCommand)trackedCommand.Command; + + var handlerContract = typeof(IExecute<>).MakeGenericType(command.GetType()); var handler = (IExecute)_serviceFactory.Build(handlerContract); _logger.Debug("{0} -> {1}", command.GetType().Name, handler.GetType().Name); var sw = Stopwatch.StartNew(); - TrackedCommand trackedCommand = null; try { - trackedCommand = _trackCommands.TrackIfNew(command); - - if (trackedCommand == null) - { - _logger.Info("Command is already in progress: {0}", command.GetType().Name); - return; - } - MappedDiagnosticsContext.Set("CommandId", trackedCommand.Command.CommandId); - PublishEvent(new CommandStartedEvent(command)); + PublishEvent(new CommandStartedEvent(trackedCommand)); handler.Execute(command); sw.Stop(); _trackCommands.Completed(trackedCommand, sw.Elapsed); - PublishEvent(new CommandCompletedEvent(command)); + PublishEvent(new CommandCompletedEvent(trackedCommand)); } catch (Exception e) { - if (trackedCommand != null) - { - _trackCommands.Failed(trackedCommand, e); - } - - PublishEvent(new CommandFailedEvent(command, e)); + _trackCommands.Failed(trackedCommand, e); + PublishEvent(new CommandFailedEvent(trackedCommand, e)); throw; } finally { - PublishEvent(new CommandExecutedEvent(command)); + PublishEvent(new CommandExecutedEvent(trackedCommand)); } _logger.Debug("{0} <- {1} [{2}]", command.GetType().Name, handler.GetType().Name, sw.Elapsed.ToString("")); } - - public void PublishCommand(string commandTypeName) - { - var commandType = _serviceFactory.GetImplementations(typeof(ICommand)) - .Single(c => c.FullName.Equals(commandTypeName, StringComparison.InvariantCultureIgnoreCase)); - - dynamic command = Json.Deserialize("{}", commandType); - PublishCommand(command); - } } } diff --git a/NzbDrone.Common/Messaging/TestCommand.cs b/NzbDrone.Common/Messaging/TestCommand.cs index 3ede823e7..c24a89780 100644 --- a/NzbDrone.Common/Messaging/TestCommand.cs +++ b/NzbDrone.Common/Messaging/TestCommand.cs @@ -5,7 +5,7 @@ namespace NzbDrone.Common.Messaging public class TestCommand : ICommand { public int Duration { get; set; } - public String CommandId { get; set; } + public String CommandId { get; private set; } public TestCommand() { diff --git a/NzbDrone.Common/Messaging/Tracking/CommandTrackingService.cs b/NzbDrone.Common/Messaging/Tracking/CommandTrackingService.cs index 5962cabd2..2cb3b65ab 100644 --- a/NzbDrone.Common/Messaging/Tracking/CommandTrackingService.cs +++ b/NzbDrone.Common/Messaging/Tracking/CommandTrackingService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.Remoting; using NzbDrone.Common.Cache; namespace NzbDrone.Common.Messaging.Tracking @@ -8,10 +9,12 @@ namespace NzbDrone.Common.Messaging.Tracking public interface ITrackCommands { TrackedCommand TrackIfNew(ICommand command); + ExistingCommand TrackNewOrGet(ICommand command); TrackedCommand Completed(TrackedCommand trackedCommand, TimeSpan runtime); TrackedCommand Failed(TrackedCommand trackedCommand, Exception e); List AllTracked(); Boolean ExistingCommand(ICommand command); + TrackedCommand FindExisting(ICommand command); } public class TrackCommands : ITrackCommands, IExecute @@ -36,6 +39,21 @@ public TrackedCommand TrackIfNew(ICommand command) return trackedCommand; } + public ExistingCommand TrackNewOrGet(ICommand command) + { + var trackedCommand = FindExisting(command); + + if (trackedCommand == null) + { + trackedCommand = new TrackedCommand(command, CommandState.Running); + Store(trackedCommand); + + return new ExistingCommand(false, trackedCommand); + } + + return new ExistingCommand(true, trackedCommand); + } + public TrackedCommand Completed(TrackedCommand trackedCommand, TimeSpan runtime) { trackedCommand.StateChangeTime = DateTime.UtcNow; @@ -65,11 +83,25 @@ public List AllTracked() public bool ExistingCommand(ICommand command) { - var running = AllTracked().Where(i => i.Type == command.GetType().FullName && i.State == CommandState.Running); + return FindExisting(command) != null; + } - var result = running.Select(r => r.Command).Contains(command, new CommandEqualityComparer()); + public TrackedCommand FindExisting(ICommand command) + { + var comparer = new CommandEqualityComparer(); + return Running(command.GetType()).SingleOrDefault(t => comparer.Equals(t.Command, command)); + } - return result; + private List Running(Type type = null) + { + var running = AllTracked().Where(i => i.State == CommandState.Running); + + if (type != null) + { + return running.Where(t => t.Type == type.FullName).ToList(); + } + + return running.ToList(); } private void Store(TrackedCommand trackedCommand) @@ -84,7 +116,7 @@ private void Store(TrackedCommand trackedCommand) public void Execute(TrackedCommandCleanupCommand message) { - var old = AllTracked().Where(c => c.StateChangeTime < DateTime.UtcNow.AddMinutes(-15)); + var old = AllTracked().Where(c => c.State != CommandState.Running && c.StateChangeTime < DateTime.UtcNow.AddMinutes(-5)); foreach (var trackedCommand in old) { diff --git a/NzbDrone.Common/Messaging/Tracking/ExistingCommand.cs b/NzbDrone.Common/Messaging/Tracking/ExistingCommand.cs new file mode 100644 index 000000000..7005585ef --- /dev/null +++ b/NzbDrone.Common/Messaging/Tracking/ExistingCommand.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Common.Messaging.Tracking +{ + public class ExistingCommand + { + public Boolean Existing { get; set; } + public TrackedCommand TrackedCommand { get; set; } + + public ExistingCommand(Boolean exisitng, TrackedCommand trackedCommand) + { + Existing = exisitng; + TrackedCommand = trackedCommand; + } + } +} diff --git a/NzbDrone.Common/Messaging/Tracking/TrackedCommand.cs b/NzbDrone.Common/Messaging/Tracking/TrackedCommand.cs index 41f983d13..cb00dd8c7 100644 --- a/NzbDrone.Common/Messaging/Tracking/TrackedCommand.cs +++ b/NzbDrone.Common/Messaging/Tracking/TrackedCommand.cs @@ -4,6 +4,8 @@ namespace NzbDrone.Common.Messaging.Tracking { public class TrackedCommand { + public String Id { get; private set; } + public String Name { get; private set; } public String Type { get; private set; } public ICommand Command { get; private set; } public CommandState State { get; set; } @@ -13,6 +15,8 @@ public class TrackedCommand public TrackedCommand(ICommand command, CommandState state) { + Id = command.CommandId; + Name = command.GetType().Name; Type = command.GetType().FullName; Command = command; State = state; diff --git a/NzbDrone.Common/NzbDrone.Common.csproj b/NzbDrone.Common/NzbDrone.Common.csproj index f980d4610..75b255d08 100644 --- a/NzbDrone.Common/NzbDrone.Common.csproj +++ b/NzbDrone.Common/NzbDrone.Common.csproj @@ -93,6 +93,7 @@ + diff --git a/NzbDrone.Core/DataAugmentation/Scene/UpdateSceneMappingCommand.cs b/NzbDrone.Core/DataAugmentation/Scene/UpdateSceneMappingCommand.cs index 965121626..8b886d009 100644 --- a/NzbDrone.Core/DataAugmentation/Scene/UpdateSceneMappingCommand.cs +++ b/NzbDrone.Core/DataAugmentation/Scene/UpdateSceneMappingCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.DataAugmentation.Scene { public class UpdateSceneMappingCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public UpdateSceneMappingCommand() { diff --git a/NzbDrone.Core/IndexerSearch/EpisodeSearchCommand.cs b/NzbDrone.Core/IndexerSearch/EpisodeSearchCommand.cs index 08d47672d..720955178 100644 --- a/NzbDrone.Core/IndexerSearch/EpisodeSearchCommand.cs +++ b/NzbDrone.Core/IndexerSearch/EpisodeSearchCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.IndexerSearch { public class EpisodeSearchCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public int EpisodeId { get; set; } public EpisodeSearchCommand() diff --git a/NzbDrone.Core/IndexerSearch/SeasonSearchCommand.cs b/NzbDrone.Core/IndexerSearch/SeasonSearchCommand.cs index a27d98306..749fefae9 100644 --- a/NzbDrone.Core/IndexerSearch/SeasonSearchCommand.cs +++ b/NzbDrone.Core/IndexerSearch/SeasonSearchCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.IndexerSearch { public class SeasonSearchCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public int SeriesId { get; set; } public int SeasonNumber { get; set; } diff --git a/NzbDrone.Core/IndexerSearch/SeriesSearchCommand.cs b/NzbDrone.Core/IndexerSearch/SeriesSearchCommand.cs index e9ade1e45..8a8de6184 100644 --- a/NzbDrone.Core/IndexerSearch/SeriesSearchCommand.cs +++ b/NzbDrone.Core/IndexerSearch/SeriesSearchCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.IndexerSearch { public class SeriesSearchCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public int SeriesId { get; set; } public SeriesSearchCommand() diff --git a/NzbDrone.Core/Indexers/RssSyncCommand.cs b/NzbDrone.Core/Indexers/RssSyncCommand.cs index 467a7b030..0d073c977 100644 --- a/NzbDrone.Core/Indexers/RssSyncCommand.cs +++ b/NzbDrone.Core/Indexers/RssSyncCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.Indexers { public class RssSyncCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public RssSyncCommand() { diff --git a/NzbDrone.Core/Instrumentation/Commands/ClearLogCommand.cs b/NzbDrone.Core/Instrumentation/Commands/ClearLogCommand.cs index 34e2812ba..cabaffcb5 100644 --- a/NzbDrone.Core/Instrumentation/Commands/ClearLogCommand.cs +++ b/NzbDrone.Core/Instrumentation/Commands/ClearLogCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.Instrumentation.Commands { public class ClearLogCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public ClearLogCommand() { diff --git a/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs b/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs index ea16c67d0..8ae4d03d1 100644 --- a/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs +++ b/NzbDrone.Core/Instrumentation/Commands/DeleteLogFilesCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.Instrumentation.Commands { public class DeleteLogFilesCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public DeleteLogFilesCommand() { diff --git a/NzbDrone.Core/Instrumentation/Commands/TrimLogCommand.cs b/NzbDrone.Core/Instrumentation/Commands/TrimLogCommand.cs index 8221814ba..7e710f294 100644 --- a/NzbDrone.Core/Instrumentation/Commands/TrimLogCommand.cs +++ b/NzbDrone.Core/Instrumentation/Commands/TrimLogCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.Instrumentation.Commands { public class TrimLogCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public TrimLogCommand() { diff --git a/NzbDrone.Core/Jobs/TaskManager.cs b/NzbDrone.Core/Jobs/TaskManager.cs index 57a894629..2f5314d1f 100644 --- a/NzbDrone.Core/Jobs/TaskManager.cs +++ b/NzbDrone.Core/Jobs/TaskManager.cs @@ -50,7 +50,7 @@ public void Handle(ApplicationStartedEvent message) new ScheduledTask{ Interval = 1, TypeName = typeof(DownloadedEpisodesScanCommand).FullName}, new ScheduledTask{ Interval = 60, TypeName = typeof(ApplicationUpdateCommand).FullName}, new ScheduledTask{ Interval = 1*60, TypeName = typeof(TrimLogCommand).FullName}, - new ScheduledTask{ Interval = 5, TypeName = typeof(TrackedCommandCleanupCommand).FullName} + new ScheduledTask{ Interval = 1, TypeName = typeof(TrackedCommandCleanupCommand).FullName} }; var currentTasks = _scheduledTaskRepository.All(); diff --git a/NzbDrone.Core/MediaFiles/Commands/CleanMediaFileDb.cs b/NzbDrone.Core/MediaFiles/Commands/CleanMediaFileDb.cs index 42822aae6..7a195685a 100644 --- a/NzbDrone.Core/MediaFiles/Commands/CleanMediaFileDb.cs +++ b/NzbDrone.Core/MediaFiles/Commands/CleanMediaFileDb.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.MediaFiles.Commands { public class CleanMediaFileDb : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public int SeriesId { get; private set; } public CleanMediaFileDb() diff --git a/NzbDrone.Core/MediaFiles/Commands/CleanUpRecycleBinCommand.cs b/NzbDrone.Core/MediaFiles/Commands/CleanUpRecycleBinCommand.cs index 66cbc07a9..ef27ad213 100644 --- a/NzbDrone.Core/MediaFiles/Commands/CleanUpRecycleBinCommand.cs +++ b/NzbDrone.Core/MediaFiles/Commands/CleanUpRecycleBinCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.MediaFiles.Commands { public class CleanUpRecycleBinCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public CleanUpRecycleBinCommand() { diff --git a/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs b/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs index b0e52126a..a6caf5bee 100644 --- a/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs +++ b/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.MediaFiles.Commands { public class DownloadedEpisodesScanCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public DownloadedEpisodesScanCommand() { diff --git a/NzbDrone.Core/MediaFiles/Commands/RenameSeasonCommand.cs b/NzbDrone.Core/MediaFiles/Commands/RenameSeasonCommand.cs index 894a1eb86..5a61e100a 100644 --- a/NzbDrone.Core/MediaFiles/Commands/RenameSeasonCommand.cs +++ b/NzbDrone.Core/MediaFiles/Commands/RenameSeasonCommand.cs @@ -6,10 +6,10 @@ namespace NzbDrone.Core.MediaFiles.Commands { public class RenameSeasonCommand : ICommand { - public int SeriesId { get; private set; } - public int SeasonNumber { get; private set; } + public int SeriesId { get; set; } + public int SeasonNumber { get; set; } - public String CommandId { get; set; } + public String CommandId { get; private set; } public RenameSeasonCommand() { diff --git a/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs b/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs index 954d92ff1..f7e99512f 100644 --- a/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs +++ b/NzbDrone.Core/MediaFiles/Commands/RenameSeriesCommand.cs @@ -6,8 +6,8 @@ namespace NzbDrone.Core.MediaFiles.Commands { public class RenameSeriesCommand : ICommand { - public String CommandId { get; set; } - public int SeriesId { get; private set; } + public String CommandId { get; private set; } + public int SeriesId { get; set; } public RenameSeriesCommand() { diff --git a/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs b/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs index 4ee1133ed..26bf91c94 100644 --- a/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs +++ b/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.Notifications.Email { public class TestEmailCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public string Server { get; set; } public int Port { get; set; } public bool Ssl { get; set; } diff --git a/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs b/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs index 71453e252..8494e0d9d 100644 --- a/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs +++ b/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.Notifications.Growl { public class TestGrowlCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public string Host { get; set; } public int Port { get; set; } public string Password { get; set; } diff --git a/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs b/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs index bf456754e..365add8a2 100644 --- a/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs +++ b/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.Notifications.Plex { public class TestPlexClientCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public string Host { get; set; } public int Port { get; set; } public string Username { get; set; } diff --git a/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs b/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs index 3921890bb..7306e5a10 100644 --- a/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs +++ b/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.Notifications.Plex { public class TestPlexServerCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public string Host { get; set; } public int Port { get; set; } diff --git a/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs b/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs index e30fa4c6b..869123be0 100644 --- a/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs +++ b/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.Notifications.Prowl { public class TestProwlCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public string ApiKey { get; set; } public int Priority { get; set; } diff --git a/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs b/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs index 55c6aad8f..0c6ec8912 100644 --- a/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs +++ b/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.Notifications.Pushover { public class TestPushoverCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public string UserKey { get; set; } public int Priority { get; set; } diff --git a/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs b/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs index 17b9d1f72..02c594e8d 100644 --- a/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs +++ b/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.Notifications.Xbmc { public class TestXbmcCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public string Host { get; set; } public int Port { get; set; } public string Username { get; set; } diff --git a/NzbDrone.Core/Providers/UpdateXemMappingsCommand.cs b/NzbDrone.Core/Providers/UpdateXemMappingsCommand.cs index 0660cff7b..0dd28372d 100644 --- a/NzbDrone.Core/Providers/UpdateXemMappingsCommand.cs +++ b/NzbDrone.Core/Providers/UpdateXemMappingsCommand.cs @@ -6,13 +6,12 @@ namespace NzbDrone.Core.Providers { public class UpdateXemMappingsCommand : ICommand { - public String CommandId { get; set; } - public int? SeriesId { get; private set; } + public String CommandId { get; private set; } + public int? SeriesId { get; set; } public UpdateXemMappingsCommand(int? seriesId) { CommandId = HashUtil.GenerateCommandId(); - SeriesId = seriesId; } } diff --git a/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs b/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs index 941ce42dd..a58fca783 100644 --- a/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs +++ b/NzbDrone.Core/Tv/Commands/RefreshSeriesCommand.cs @@ -6,8 +6,8 @@ namespace NzbDrone.Core.Tv.Commands { public class RefreshSeriesCommand : ICommand { - public String CommandId { get; set; } - public int? SeriesId { get; private set; } + public String CommandId { get; private set; } + public int? SeriesId { get; set; } public RefreshSeriesCommand() { diff --git a/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs b/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs index e7cfa0a9d..dd1b97d6b 100644 --- a/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs +++ b/NzbDrone.Core/Update/Commands/ApplicationUpdateCommand.cs @@ -6,7 +6,7 @@ namespace NzbDrone.Core.Update.Commands { public class ApplicationUpdateCommand : ICommand { - public String CommandId { get; set; } + public String CommandId { get; private set; } public ApplicationUpdateCommand() { diff --git a/UI/Commands/CommandCollection.js b/UI/Commands/CommandCollection.js new file mode 100644 index 000000000..72939381b --- /dev/null +++ b/UI/Commands/CommandCollection.js @@ -0,0 +1,17 @@ +'use strict'; +define( + [ + 'backbone', + 'Commands/CommandModel', + 'Mixins/backbone.signalr.mixin' + ], function (Backbone, CommandModel) { + + var CommandCollection = Backbone.Collection.extend({ + url : window.ApiRoot + '/command', + model: CommandModel + }); + + var collection = new CommandCollection().bindSignalR(); + + return collection; + }); diff --git a/UI/Commands/CommandModel.js b/UI/Commands/CommandModel.js new file mode 100644 index 000000000..33a6217c8 --- /dev/null +++ b/UI/Commands/CommandModel.js @@ -0,0 +1,8 @@ +'use strict'; +define( + [ + 'backbone' + ], function (Backbone) { + return Backbone.Model.extend({ + }); + }); diff --git a/UI/Episode/Search/Layout.js b/UI/Episode/Search/Layout.js index 15b89f79a..aa24febf5 100644 --- a/UI/Episode/Search/Layout.js +++ b/UI/Episode/Search/Layout.js @@ -9,9 +9,9 @@ define( 'Series/SeriesCollection', 'Shared/LoadingView', 'Shared/Messenger', - 'Commands/CommandController', + 'Shared/Actioneer', 'Shared/FormatHelpers' - ], function (App, Marionette, ButtonsView, ManualSearchLayout, ReleaseCollection, SeriesCollection, LoadingView, Messenger, CommandController, FormatHelpers) { + ], function (App, Marionette, ButtonsView, ManualSearchLayout, ReleaseCollection, SeriesCollection, LoadingView, Messenger, Actioneer, FormatHelpers) { return Marionette.Layout.extend({ template: 'Episode/Search/LayoutTemplate', @@ -39,16 +39,19 @@ define( e.preventDefault(); } - CommandController.Execute('episodeSearch', { episodeId: this.model.get('id') }); - var series = SeriesCollection.get(this.model.get('seriesId')); var seriesTitle = series.get('title'); var season = this.model.get('seasonNumber'); var episode = this.model.get('episodeNumber'); var message = seriesTitle + ' - ' + season + 'x' + FormatHelpers.pad(episode, 2); - Messenger.show({ - message: 'Search started for: ' + message + Actioneer.ExecuteCommand({ + command : 'episodeSearch', + properties : { + episodeId: this.model.get('id') + }, + errorMessage: 'Search failed for: ' + message, + startMessage: 'Search started for: ' + message }); App.vent.trigger(App.Commands.CloseModalCommand); diff --git a/UI/Router.js b/UI/Router.js index 984bf94ec..86b42c1ee 100644 --- a/UI/Router.js +++ b/UI/Router.js @@ -5,10 +5,11 @@ require( 'marionette', 'Controller', 'Series/SeriesCollection', + 'Shared/Actioneer', 'Navbar/NavbarView', 'jQuery/RouteBinder', 'jquery' - ], function (App, Marionette, Controller, SeriesCollection, NavbarView, RouterBinder, $) { + ], function (App, Marionette, Controller, SeriesCollection, Actioneer, NavbarView, RouterBinder, $) { var Router = Marionette.AppRouter.extend({ diff --git a/UI/SeasonPass/SeriesLayout.js b/UI/SeasonPass/SeriesLayout.js index 5de36f404..fd4d6b985 100644 --- a/UI/SeasonPass/SeriesLayout.js +++ b/UI/SeasonPass/SeriesLayout.js @@ -113,6 +113,8 @@ define( }, _setMonitored: function (seasonNumber) { + //TODO: use Actioneer? + var self = this; var promise = $.ajax({ diff --git a/UI/Series/Details/SeasonLayout.js b/UI/Series/Details/SeasonLayout.js index 1244050e8..facc530f1 100644 --- a/UI/Series/Details/SeasonLayout.js +++ b/UI/Series/Details/SeasonLayout.js @@ -7,9 +7,8 @@ define( 'Cells/EpisodeTitleCell', 'Cells/RelativeDateCell', 'Cells/EpisodeStatusCell', - 'Commands/CommandController', 'Shared/Actioneer' - ], function ( Marionette, Backgrid, ToggleCell, EpisodeTitleCell, RelativeDateCell, EpisodeStatusCell, CommandController, Actioneer) { + ], function ( Marionette, Backgrid, ToggleCell, EpisodeTitleCell, RelativeDateCell, EpisodeStatusCell, Actioneer) { return Marionette.Layout.extend({ template: 'Series/Details/SeasonLayoutTemplate', @@ -101,9 +100,10 @@ define( seriesId : this.model.get('seriesId'), seasonNumber: this.model.get('seasonNumber') }, - element : this.ui.seasonSearch, - failMessage : 'Search for season {0} failed'.format(this.model.get('seasonNumber')), - startMessage: 'Search for season {0} started'.format(this.model.get('seasonNumber')) + element : this.ui.seasonSearch, + errorMessage : 'Search for season {0} failed'.format(this.model.get('seasonNumber')), + startMessage : 'Search for season {0} started'.format(this.model.get('seasonNumber')), + successMessage: 'Search for season {0} completed'.format(this.model.get('seasonNumber')) }); }, @@ -143,13 +143,13 @@ define( _seasonRename: function () { Actioneer.ExecuteCommand({ - command : 'renameSeason', - properties : { + command : 'renameSeason', + properties : { seriesId : this.model.get('seriesId'), seasonNumber: this.model.get('seasonNumber') }, - element : this.ui.seasonRename, - failMessage: 'Season rename failed' + element : this.ui.seasonRename, + errorMessage: 'Season rename failed' }); } }); diff --git a/UI/Series/Details/SeasonMenu/ItemView.js b/UI/Series/Details/SeasonMenu/ItemView.js index 2ffe52418..1ab230734 100644 --- a/UI/Series/Details/SeasonMenu/ItemView.js +++ b/UI/Series/Details/SeasonMenu/ItemView.js @@ -51,7 +51,7 @@ define( seasonNumber: this.model.get('seasonNumber') }, element : this.ui.seasonSearch, - failMessage : 'Search for season {0} failed'.format(this.model.get('seasonNumber')), + errorMessage: 'Search for season {0} failed'.format(this.model.get('seasonNumber')), startMessage: 'Search for season {0} started'.format(this.model.get('seasonNumber')) }); }, diff --git a/UI/Series/Details/SeriesDetailsLayout.js b/UI/Series/Details/SeriesDetailsLayout.js index 8f073cc96..0d614e281 100644 --- a/UI/Series/Details/SeriesDetailsLayout.js +++ b/UI/Series/Details/SeriesDetailsLayout.js @@ -154,10 +154,10 @@ define( properties : { seriesId: this.model.get('id') }, - element : this.ui.rename, - context : this, - onSuccess : this._refetchEpisodeFiles, - failMessage: 'Series search failed' + element : this.ui.rename, + context : this, + onSuccess : this._refetchEpisodeFiles, + errorMessage: 'Series search failed' }); }, @@ -168,7 +168,7 @@ define( seriesId: this.model.get('id') }, element : this.ui.search, - failMessage : 'Series search failed', + errorMessage: 'Series search failed', startMessage: 'Search for {0} started'.format(this.model.get('title')) }); }, diff --git a/UI/Series/Index/SeriesIndexLayout.js b/UI/Series/Index/SeriesIndexLayout.js index b35234589..59ae9163b 100644 --- a/UI/Series/Index/SeriesIndexLayout.js +++ b/UI/Series/Index/SeriesIndexLayout.js @@ -140,7 +140,6 @@ define( this._fetchCollection(); }, - initialize: function () { this.seriesCollection = SeriesCollection; @@ -148,7 +147,6 @@ define( this.listenTo(SeriesCollection, 'remove', this._renderView); }, - _renderView: function () { if (SeriesCollection.length === 0) { @@ -164,7 +162,6 @@ define( } }, - onShow: function () { this._showToolbar(); this._renderView(); diff --git a/UI/Settings/Notifications/EditTemplate.html b/UI/Settings/Notifications/EditTemplate.html index dba9e30f9..91e9ecdb7 100644 --- a/UI/Settings/Notifications/EditTemplate.html +++ b/UI/Settings/Notifications/EditTemplate.html @@ -66,7 +66,7 @@ {{/if}} - +
diff --git a/UI/Settings/Notifications/EditView.js b/UI/Settings/Notifications/EditView.js index 312e48b88..be47a7db6 100644 --- a/UI/Settings/Notifications/EditView.js +++ b/UI/Settings/Notifications/EditView.js @@ -6,11 +6,11 @@ define([ 'Settings/Notifications/Model', 'Settings/Notifications/DeleteView', 'Shared/Messenger', - 'Commands/CommandController', + 'Shared/Actioneer', 'Mixins/AsModelBoundView', 'Form/FormBuilder' -], function (App, Marionette, NotificationModel, DeleteView, Messenger, CommandController, AsModelBoundView) { +], function (App, Marionette, NotificationModel, DeleteView, Messenger, Actioneer, AsModelBoundView) { var model = Marionette.ItemView.extend({ template: 'Settings/Notifications/EditTemplate', @@ -70,41 +70,28 @@ define([ var testCommand = this.model.get('testCommand'); if (testCommand) { this.idle = false; - this.ui.testButton.addClass('disabled'); - this.ui.testIcon.addClass('icon-spinner icon-spin'); - var properties = {}; _.each(this.model.get('fields'), function (field) { properties[field.name] = field.value; }); - var self = this; - var commandPromise = CommandController.Execute(testCommand, properties); - commandPromise.done(function () { - Messenger.show({ - message: 'Notification settings tested successfully' - }); + Actioneer.ExecuteCommand({ + command : testCommand, + properties : properties, + button : this.ui.testButton, + element : this.ui.testIcon, + errorMessage : 'Failed to test notification settings', + successMessage: 'Notification settings tested successfully', + always : this._testOnAlways, + context : this }); + } + }, - commandPromise.fail(function (options) { - if (options.readyState === 0 || options.status === 0) { - return; - } - - Messenger.show({ - message: 'Failed to test notification settings', - type : 'error' - }); - }); - - commandPromise.always(function () { - if (!self.isClosed) { - self.ui.testButton.removeClass('disabled'); - self.ui.testIcon.removeClass('icon-spinner icon-spin'); - self.idle = true; - } - }); + _testOnAlways: function () { + if (!this.isClosed) { + this.idle = true; } } }); diff --git a/UI/Shared/Actioneer.js b/UI/Shared/Actioneer.js index 929147ff1..ac59a7ff8 100644 --- a/UI/Shared/Actioneer.js +++ b/UI/Shared/Actioneer.js @@ -1,15 +1,30 @@ 'use strict'; -define(['Commands/CommandController', 'Shared/Messenger'], - function(CommandController, Messenger) { - return { +define( + [ + 'Commands/CommandController', + 'Commands/CommandCollection', + 'Shared/Messenger'], + function(CommandController, CommandCollection, Messenger) { + + var actioneer = Marionette.AppRouter.extend({ + + initialize: function () { + this.trackedCommands = []; + CommandCollection.fetch(); + this.listenTo(CommandCollection, 'sync', this._handleCommands); + }, + ExecuteCommand: function (options) { options.iconClass = this._getIconClass(options.element); - this._showStartMessage(options); + if (options.button) { + options.button.addClass('disable'); + } + this._setSpinnerOnElement(options); var promise = CommandController.Execute(options.command, options.properties); - this._handlePromise(promise, options); + this._showStartMessage(options, promise); }, SaveModel: function (options) { @@ -24,15 +39,7 @@ define(['Commands/CommandController', 'Shared/Messenger'], _handlePromise: function (promise, options) { promise.done(function () { - if (options.successMessage) { - Messenger.show({ - message: options.successMessage - }); - } - - if (options.onSuccess) { - options.onSuccess.call(options.context); - } + self._onSuccess(options); }); promise.fail(function (ajaxOptions) { @@ -40,31 +47,41 @@ define(['Commands/CommandController', 'Shared/Messenger'], return; } - if (options.failMessage) { - Messenger.show({ - message: options.failMessage, - type : 'error' - }); - } - - if (options.onError) { - options.onError.call(options.context); - } + self._onError(options); }); promise.always(function () { + self._onComplete(options); + }); + }, - if (options.leaveIcon) { - options.element.removeClass('icon-spin'); + _handleCommands: function () { + var self = this; + + _.each(this.trackedCommands, function (trackedCommand){ + if (trackedCommand.completed === true) { + return; } - else { - options.element.addClass(options.iconClass); - options.element.removeClass('icon-nd-spinner'); + var options = trackedCommand.options; + var command = CommandCollection.find({ 'id': trackedCommand.id }); + + if (!command) { + return; } - if (options.always) { - options.always.call(options.context); + if (command.get('state') === 'completed') { + trackedCommand.completed = true; + + self._onSuccess(options, command.get('id')); + self._onComplete(options); + } + + if (command.get('state') === 'failed') { + trackedCommand.completed = true; + + self._onError(options, command.get('id')); + self._onComplete(options); } }); }, @@ -74,6 +91,10 @@ define(['Commands/CommandController', 'Shared/Messenger'], }, _setSpinnerOnElement: function (options) { + if (!options.element) { + return; + } + if (options.leaveIcon) { options.element.addClass('icon-spin'); } @@ -84,12 +105,79 @@ define(['Commands/CommandController', 'Shared/Messenger'], } }, - _showStartMessage: function (options) { - if (options.startMessage) { + _onSuccess: function (options, id) { + if (options.successMessage) { Messenger.show({ - message: options.startMessage + id : id, + message: options.successMessage, + type : 'success' }); } + + if (options.onSuccess) { + options.onSuccess.call(options.context); + } + }, + + _onError: function (options, id) { + if (options.errorMessage) { + Messenger.show({ + id : id, + message: options.errorMessage, + type : 'error' + }); + } + + if (options.onError) { + options.onError.call(options.context); + } + }, + + _onComplete: function (options) { + if (options.button) { + options.button.removeClass('disable'); + } + + if (options.leaveIcon) { + options.element.removeClass('icon-spin'); + } + + else { + options.element.addClass(options.iconClass); + options.element.removeClass('icon-nd-spinner'); + options.element.removeClass('icon-spin'); + } + + if (options.always) { + options.always.call(options.context); + } + }, + + _showStartMessage: function (options, promise) { + var self = this; + + if (!promise) { + if (options.startMessage) { + Messenger.show({ + message: options.startMessage + }); + } + + return; + } + + promise.done(function (data) { + self.trackedCommands.push({ id: data.id, options: options }); + + if (options.startMessage) { + Messenger.show({ + id : data.id, + message: options.startMessage + }); + } + }); } - } + }); + + return new actioneer(); }); diff --git a/UI/Shared/Messenger.js b/UI/Shared/Messenger.js index 8f04009d9..1188e0fe5 100644 --- a/UI/Shared/Messenger.js +++ b/UI/Shared/Messenger.js @@ -13,6 +13,10 @@ define(function () { options.hideAfter = 5; break; + case 'success': + options.hideAfter = 5; + break; + default : options.hideAfter = 0; } @@ -22,11 +26,11 @@ define(function () { message : options.message, type : options.type, showCloseButton: true, - hideAfter : options.hideAfter + hideAfter : options.hideAfter, + id : options.id }); }, - monitor: function (options) { if (!options.promise) { diff --git a/UI/Shared/Toolbar/Button/ButtonView.js b/UI/Shared/Toolbar/Button/ButtonView.js index 32e8b3fbc..04814ccd3 100644 --- a/UI/Shared/Toolbar/Button/ButtonView.js +++ b/UI/Shared/Toolbar/Button/ButtonView.js @@ -3,9 +3,9 @@ define( [ 'app', 'marionette', - 'Commands/CommandController', + 'Shared/Actioneer', 'Shared/Messenger' - ], function (App, Marionette, CommandController, Messenger) { + ], function (App, Marionette, Actioneer, Messenger) { return Marionette.ItemView.extend({ template : 'Shared/Toolbar/ButtonTemplate', @@ -19,7 +19,6 @@ define( icon: '.x-icon' }, - initialize: function () { this.storageKey = this.model.get('menuKey') + ':' + this.model.get('key'); this.idle = true; @@ -45,68 +44,19 @@ define( }, invokeCommand: function () { - //TODO: Use Actioneer to handle icon swapping - var command = this.model.get('command'); if (command) { this.idle = false; - this.$el.addClass('disabled'); - this.ui.icon.addClass('icon-spinner icon-spin'); - var self = this; - var commandPromise = CommandController.Execute(command); - commandPromise.done(function () { - if (self.model.get('successMessage')) { - Messenger.show({ - message: self.model.get('successMessage') - }); - } - - if (self.model.get('onSuccess')) { - if (!self.model.ownerContext) { - throw 'ownerContext must be set.'; - } - - self.model.get('onSuccess').call(self.model.ownerContext); - } + Actioneer.ExecuteCommand({ + command : command, + button : this.$el, + element : this.ui.icon, + errorMessage : this.model.get('errorMessage'), + successMessage: this.model.get('successMessage'), + always : this._commandAlways, + context : this }); - - commandPromise.fail(function (options) { - if (options.readyState === 0 || options.status === 0) { - return; - } - - if (self.model.get('errorMessage')) { - Messenger.show({ - message: self.model.get('errorMessage'), - type : 'error' - }); - } - - if (self.model.get('onError')) { - if (!self.model.ownerContext) { - throw 'ownerContext must be set.'; - } - - self.model.get('onError').call(self.model.ownerContext); - } - }); - - commandPromise.always(function () { - if (!self.isClosed) { - self.$el.removeClass('disabled'); - self.ui.icon.removeClass('icon-spinner icon-spin'); - self.idle = true; - } - }); - - if (self.model.get('always')) { - if (!self.model.ownerContext) { - throw 'ownerContext must be set.'; - } - - self.model.get('always').call(self.model.ownerContext); - } } }, @@ -133,8 +83,13 @@ define( if (callback) { callback.call(this.model.ownerContext); } - } + }, + _commandAlways: function () { + if (!this.isClosed) { + this.idle = true; + } + } }); }); diff --git a/UI/Shared/Toolbar/ToolbarLayout.js b/UI/Shared/Toolbar/ToolbarLayout.js index 701fdf07d..8945337a2 100644 --- a/UI/Shared/Toolbar/ToolbarLayout.js +++ b/UI/Shared/Toolbar/ToolbarLayout.js @@ -30,10 +30,8 @@ define( this.left = options.left; this.right = options.right; this.toolbarContext = options.context; - }, - onShow: function () { if (this.left) { _.each(this.left, this._showToolbarLeft, this); @@ -51,7 +49,6 @@ define( this._showToolbar(element, index, 'right'); }, - _showToolbar: function (buttonGroup, index, position) { var groupCollection = new ButtonCollection(); From 1e7db2287e3729c3edd39101bf8f3aa29b1e5c04 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 30 Aug 2013 20:39:05 -0700 Subject: [PATCH 10/15] ScheduledTasks properly set last run time now --- NzbDrone.Api/Commands/CommandConnection.cs | 6 +++--- NzbDrone.Common/Messaging/Events/CommandCompletedEvent.cs | 6 +++--- NzbDrone.Common/Messaging/Events/CommandExecutedEvent.cs | 6 +++--- NzbDrone.Common/Messaging/Events/CommandFailedEvent.cs | 6 +++--- NzbDrone.Common/Messaging/Events/CommandStartedEvent.cs | 6 +++--- NzbDrone.Core/Jobs/TaskManager.cs | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/NzbDrone.Api/Commands/CommandConnection.cs b/NzbDrone.Api/Commands/CommandConnection.cs index a09125734..1d9285d0b 100644 --- a/NzbDrone.Api/Commands/CommandConnection.cs +++ b/NzbDrone.Api/Commands/CommandConnection.cs @@ -20,17 +20,17 @@ public override string Resource public void HandleAsync(CommandStartedEvent message) { - BroadcastMessage(message.Command); + BroadcastMessage(message.TrackedCommand); } public void HandleAsync(CommandCompletedEvent message) { - BroadcastMessage(message.Command); + BroadcastMessage(message.TrackedCommand); } public void Handle(CommandFailedEvent message) { - BroadcastMessage(message.Command); + BroadcastMessage(message.TrackedCommand); } private void BroadcastMessage(TrackedCommand trackedCommand) diff --git a/NzbDrone.Common/Messaging/Events/CommandCompletedEvent.cs b/NzbDrone.Common/Messaging/Events/CommandCompletedEvent.cs index 2c1f4f312..f4831361e 100644 --- a/NzbDrone.Common/Messaging/Events/CommandCompletedEvent.cs +++ b/NzbDrone.Common/Messaging/Events/CommandCompletedEvent.cs @@ -4,11 +4,11 @@ namespace NzbDrone.Common.Messaging.Events { public class CommandCompletedEvent : IEvent { - public TrackedCommand Command { get; private set; } + public TrackedCommand TrackedCommand { get; private set; } - public CommandCompletedEvent(TrackedCommand command) + public CommandCompletedEvent(TrackedCommand trackedCommand) { - Command = command; + TrackedCommand = trackedCommand; } } } \ No newline at end of file diff --git a/NzbDrone.Common/Messaging/Events/CommandExecutedEvent.cs b/NzbDrone.Common/Messaging/Events/CommandExecutedEvent.cs index 8ed4b5f75..b93a5597c 100644 --- a/NzbDrone.Common/Messaging/Events/CommandExecutedEvent.cs +++ b/NzbDrone.Common/Messaging/Events/CommandExecutedEvent.cs @@ -4,11 +4,11 @@ namespace NzbDrone.Common.Messaging.Events { public class CommandExecutedEvent : IEvent { - public TrackedCommand Command { get; private set; } + public TrackedCommand TrackedCommand { get; private set; } - public CommandExecutedEvent(TrackedCommand command) + public CommandExecutedEvent(TrackedCommand trackedCommand) { - Command = command; + TrackedCommand = trackedCommand; } } } \ No newline at end of file diff --git a/NzbDrone.Common/Messaging/Events/CommandFailedEvent.cs b/NzbDrone.Common/Messaging/Events/CommandFailedEvent.cs index f796f0f03..5749ca00a 100644 --- a/NzbDrone.Common/Messaging/Events/CommandFailedEvent.cs +++ b/NzbDrone.Common/Messaging/Events/CommandFailedEvent.cs @@ -5,12 +5,12 @@ namespace NzbDrone.Common.Messaging.Events { public class CommandFailedEvent : IEvent { - public TrackedCommand Command { get; private set; } + public TrackedCommand TrackedCommand { get; private set; } public Exception Exception { get; private set; } - public CommandFailedEvent(TrackedCommand command, Exception exception) + public CommandFailedEvent(TrackedCommand trackedCommand, Exception exception) { - Command = command; + TrackedCommand = trackedCommand; Exception = exception; } } diff --git a/NzbDrone.Common/Messaging/Events/CommandStartedEvent.cs b/NzbDrone.Common/Messaging/Events/CommandStartedEvent.cs index 14296c02a..9247fbd45 100644 --- a/NzbDrone.Common/Messaging/Events/CommandStartedEvent.cs +++ b/NzbDrone.Common/Messaging/Events/CommandStartedEvent.cs @@ -4,11 +4,11 @@ namespace NzbDrone.Common.Messaging.Events { public class CommandStartedEvent : IEvent { - public TrackedCommand Command { get; private set; } + public TrackedCommand TrackedCommand { get; private set; } - public CommandStartedEvent(TrackedCommand command) + public CommandStartedEvent(TrackedCommand trackedCommand) { - Command = command; + TrackedCommand = trackedCommand; } } } \ No newline at end of file diff --git a/NzbDrone.Core/Jobs/TaskManager.cs b/NzbDrone.Core/Jobs/TaskManager.cs index 2f5314d1f..4dcfdd56c 100644 --- a/NzbDrone.Core/Jobs/TaskManager.cs +++ b/NzbDrone.Core/Jobs/TaskManager.cs @@ -79,7 +79,7 @@ public void Handle(ApplicationStartedEvent message) public void HandleAsync(CommandExecutedEvent message) { - var scheduledTask = _scheduledTaskRepository.All().SingleOrDefault(c => c.TypeName == message.Command.GetType().FullName); + var scheduledTask = _scheduledTaskRepository.All().SingleOrDefault(c => c.TypeName == message.TrackedCommand.Command.GetType().FullName); if (scheduledTask != null) { From 0894fc0e777b1f49481a39d7e19f614599605199 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 30 Aug 2013 23:19:19 -0700 Subject: [PATCH 11/15] Fixing CommandIntegrationTest - for now --- .../Messaging/Tracking/TrackedCommand.cs | 6 +++- .../CommandIntegerationTests.cs | 29 +++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/NzbDrone.Common/Messaging/Tracking/TrackedCommand.cs b/NzbDrone.Common/Messaging/Tracking/TrackedCommand.cs index cb00dd8c7..37f69de88 100644 --- a/NzbDrone.Common/Messaging/Tracking/TrackedCommand.cs +++ b/NzbDrone.Common/Messaging/Tracking/TrackedCommand.cs @@ -12,7 +12,11 @@ public class TrackedCommand public DateTime StateChangeTime { get; set; } public TimeSpan Runtime { get; set; } public Exception Exception { get; set; } - + + public TrackedCommand() + { + } + public TrackedCommand(ICommand command, CommandState state) { Id = command.CommandId; diff --git a/NzbDrone.Integration.Test/CommandIntegerationTests.cs b/NzbDrone.Integration.Test/CommandIntegerationTests.cs index c1e06bac1..af3342c2b 100644 --- a/NzbDrone.Integration.Test/CommandIntegerationTests.cs +++ b/NzbDrone.Integration.Test/CommandIntegerationTests.cs @@ -1,5 +1,10 @@ -using NUnit.Framework; +using System.Net; +using FluentAssertions; +using NUnit.Framework; using NzbDrone.Api.Commands; +using NzbDrone.Common.Messaging.Tracking; +using NzbDrone.Common.Serializer; +using RestSharp; namespace NzbDrone.Integration.Test { @@ -9,7 +14,27 @@ public class CommandIntegrationTest : IntegrationTest [Test] public void should_be_able_to_run_rss_sync() { - Commands.Post(new CommandResource {Command = "rsssync"}); + var request = new RestRequest("command") + { + RequestFormat = DataFormat.Json, + Method = Method.POST + }; + + request.AddBody(new CommandResource {Command = "rsssync"}); + + var restClient = new RestClient("http://localhost:8989/api"); + var response = restClient.Execute(request); + + if (response.ErrorException != null) + { + throw response.ErrorException; + } + + response.ErrorMessage.Should().BeBlank(); + response.StatusCode.Should().Be(HttpStatusCode.Created); + + var trackedCommand = Json.Deserialize(response.Content); + trackedCommand.Id.Should().NotBeNullOrEmpty(); } } } \ No newline at end of file From e68fe9033d535edd82a4ba08d59e43d4f96fe0c9 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sat, 31 Aug 2013 09:12:37 -0700 Subject: [PATCH 12/15] If nzbdrone restarts mid command the client will treat it as failed --- UI/Shared/Actioneer.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/UI/Shared/Actioneer.js b/UI/Shared/Actioneer.js index ac59a7ff8..516a0e7b7 100644 --- a/UI/Shared/Actioneer.js +++ b/UI/Shared/Actioneer.js @@ -67,6 +67,10 @@ define( var command = CommandCollection.find({ 'id': trackedCommand.id }); if (!command) { + trackedCommand.completed = true; + + self._onError(options, trackedCommand.id); + self._onComplete(options); return; } @@ -75,6 +79,7 @@ define( self._onSuccess(options, command.get('id')); self._onComplete(options); + return; } if (command.get('state') === 'failed') { From eeda4e83f9de2eef76ea97560dbc921e54c290af Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sat, 31 Aug 2013 21:23:21 -0700 Subject: [PATCH 13/15] Fixed the broken tests --- .../MessageAggregatorCommandTests.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs b/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs index a0534a1ba..35d7d03d1 100644 --- a/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs +++ b/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs @@ -3,6 +3,7 @@ using Moq; using NUnit.Framework; using NzbDrone.Common.Messaging; +using NzbDrone.Common.Messaging.Tracking; using NzbDrone.Test.Common; namespace NzbDrone.Common.Test.EventingTests @@ -27,6 +28,13 @@ public void Setup() .Setup(c => c.Build(typeof(IExecute))) .Returns(_executorB.Object); + Mocker.GetMock() + .Setup(c => c.TrackIfNew(It.IsAny())) + .Returns(new TrackedCommand(new CommandA(), CommandState.Running)); + + Mocker.GetMock() + .Setup(c => c.TrackIfNew(It.IsAny())) + .Returns(new TrackedCommand(new CommandB(), CommandState.Running)); } [Test] @@ -34,6 +42,10 @@ public void should_publish_command_to_executor() { var commandA = new CommandA(); + Mocker.GetMock() + .Setup(c => c.TrackIfNew(commandA)) + .Returns(new TrackedCommand(commandA, CommandState.Running)); + Subject.PublishCommand(commandA); _executorA.Verify(c => c.Execute(commandA), Times.Once()); @@ -55,6 +67,9 @@ public void should_not_publish_to_incompatible_executor() { var commandA = new CommandA(); + Mocker.GetMock() + .Setup(c => c.TrackIfNew(commandA)) + .Returns(new TrackedCommand(commandA, CommandState.Running)); Subject.PublishCommand(commandA); From c928ccb20167c0846b66fe27ccc5292fae81ccc9 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 1 Sep 2013 19:55:45 -0700 Subject: [PATCH 14/15] Added progress messaging, using info logging Also extension methods for complete and failed (for coloured UI messaging) --- NzbDrone.Api/Commands/CommandConnection.cs | 4 +- NzbDrone.Api/NzbDrone.Api.csproj | 3 ++ .../ProgressMessageConnection.cs | 24 +++++++++++ .../ProgressMessageModule.cs | 21 ++++++++++ .../ProgressMessageResource.cs | 12 ++++++ .../MessageAggregatorCommandTests.cs | 8 ++-- .../Instrumentation/LogEventExtensions.cs | 1 - .../Instrumentation/LoggerExtensions.cs | 40 +++++++++++++++++++ NzbDrone.Common/Messaging/IProcessMessage.cs | 1 - .../Tracking/CommandTrackingService.cs | 12 +++--- .../Messaging/Tracking/ProcessState.cs | 14 +++++++ .../Messaging/Tracking/TrackedCommand.cs | 11 +---- NzbDrone.Common/NzbDrone.Common.csproj | 2 + NzbDrone.Core/Indexers/RssSyncService.cs | 3 +- NzbDrone.Core/NzbDrone.Core.csproj | 2 +- .../ProgressMessaging/ProgressMessage.cs | 5 +-- ...gingTarget.cs => ProgressMessageTarget.cs} | 8 +++- .../ProgressMessageCollection.js | 40 +++++++++++++++++++ UI/ProgressMessaging/ProgressMessageModel.js | 8 ++++ UI/Router.js | 3 +- UI/Series/Index/SeriesIndexLayout.js | 1 - 21 files changed, 191 insertions(+), 32 deletions(-) create mode 100644 NzbDrone.Api/ProgressMessaging/ProgressMessageConnection.cs create mode 100644 NzbDrone.Api/ProgressMessaging/ProgressMessageModule.cs create mode 100644 NzbDrone.Api/ProgressMessaging/ProgressMessageResource.cs create mode 100644 NzbDrone.Common/Instrumentation/LoggerExtensions.cs create mode 100644 NzbDrone.Common/Messaging/Tracking/ProcessState.cs rename NzbDrone.Core/ProgressMessaging/{ProgressMessagingTarget.cs => ProgressMessageTarget.cs} (85%) create mode 100644 UI/ProgressMessaging/ProgressMessageCollection.js create mode 100644 UI/ProgressMessaging/ProgressMessageModel.js diff --git a/NzbDrone.Api/Commands/CommandConnection.cs b/NzbDrone.Api/Commands/CommandConnection.cs index 1d9285d0b..227c69bec 100644 --- a/NzbDrone.Api/Commands/CommandConnection.cs +++ b/NzbDrone.Api/Commands/CommandConnection.cs @@ -11,7 +11,7 @@ namespace NzbDrone.Api.Commands public class CommandConnection : NzbDronePersistentConnection, IHandleAsync, IHandleAsync, - IHandle + IHandleAsync { public override string Resource { @@ -28,7 +28,7 @@ public void HandleAsync(CommandCompletedEvent message) BroadcastMessage(message.TrackedCommand); } - public void Handle(CommandFailedEvent message) + public void HandleAsync(CommandFailedEvent message) { BroadcastMessage(message.TrackedCommand); } diff --git a/NzbDrone.Api/NzbDrone.Api.csproj b/NzbDrone.Api/NzbDrone.Api.csproj index fe4ffcc5a..0be2d8ce9 100644 --- a/NzbDrone.Api/NzbDrone.Api.csproj +++ b/NzbDrone.Api/NzbDrone.Api.csproj @@ -86,6 +86,9 @@ + + + diff --git a/NzbDrone.Api/ProgressMessaging/ProgressMessageConnection.cs b/NzbDrone.Api/ProgressMessaging/ProgressMessageConnection.cs new file mode 100644 index 000000000..a5b4afdb0 --- /dev/null +++ b/NzbDrone.Api/ProgressMessaging/ProgressMessageConnection.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.AspNet.SignalR; +using Microsoft.AspNet.SignalR.Infrastructure; +using NzbDrone.Api.SignalR; +using NzbDrone.Common.Messaging; +using NzbDrone.Core.ProgressMessaging; + +namespace NzbDrone.Api.ProgressMessaging +{ + public class ProgressMessageConnection : NzbDronePersistentConnection, + IHandleAsync + { + public override string Resource + { + get { return "/ProgressMessage"; } + } + + public void HandleAsync(NewProgressMessageEvent message) + { + var context = ((ConnectionManager)GlobalHost.ConnectionManager).GetConnection(GetType()); + context.Connection.Broadcast(message.ProgressMessage); + } + } +} diff --git a/NzbDrone.Api/ProgressMessaging/ProgressMessageModule.cs b/NzbDrone.Api/ProgressMessaging/ProgressMessageModule.cs new file mode 100644 index 000000000..a06db9c24 --- /dev/null +++ b/NzbDrone.Api/ProgressMessaging/ProgressMessageModule.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Nancy; +using NzbDrone.Api.Extensions; + +namespace NzbDrone.Api.ProgressMessaging +{ + public class ProgressMessageModule : NzbDroneRestModule + { + public ProgressMessageModule() + { + Get["/"] = x => GetAllMessages(); + } + + private Response GetAllMessages() + { + return new List().AsResponse(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Api/ProgressMessaging/ProgressMessageResource.cs b/NzbDrone.Api/ProgressMessaging/ProgressMessageResource.cs new file mode 100644 index 000000000..3117eb142 --- /dev/null +++ b/NzbDrone.Api/ProgressMessaging/ProgressMessageResource.cs @@ -0,0 +1,12 @@ +using System; +using NzbDrone.Api.REST; + +namespace NzbDrone.Api.ProgressMessaging +{ + public class ProgressMessageResource : RestResource + { + public DateTime Time { get; set; } + public String CommandId { get; set; } + public String Message { get; set; } + } +} \ No newline at end of file diff --git a/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs b/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs index 35d7d03d1..b5c45bef0 100644 --- a/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs +++ b/NzbDrone.Common.Test/EventingTests/MessageAggregatorCommandTests.cs @@ -30,11 +30,11 @@ public void Setup() Mocker.GetMock() .Setup(c => c.TrackIfNew(It.IsAny())) - .Returns(new TrackedCommand(new CommandA(), CommandState.Running)); + .Returns(new TrackedCommand(new CommandA(), ProcessState.Running)); Mocker.GetMock() .Setup(c => c.TrackIfNew(It.IsAny())) - .Returns(new TrackedCommand(new CommandB(), CommandState.Running)); + .Returns(new TrackedCommand(new CommandB(), ProcessState.Running)); } [Test] @@ -44,7 +44,7 @@ public void should_publish_command_to_executor() Mocker.GetMock() .Setup(c => c.TrackIfNew(commandA)) - .Returns(new TrackedCommand(commandA, CommandState.Running)); + .Returns(new TrackedCommand(commandA, ProcessState.Running)); Subject.PublishCommand(commandA); @@ -69,7 +69,7 @@ public void should_not_publish_to_incompatible_executor() Mocker.GetMock() .Setup(c => c.TrackIfNew(commandA)) - .Returns(new TrackedCommand(commandA, CommandState.Running)); + .Returns(new TrackedCommand(commandA, ProcessState.Running)); Subject.PublishCommand(commandA); diff --git a/NzbDrone.Common/Instrumentation/LogEventExtensions.cs b/NzbDrone.Common/Instrumentation/LogEventExtensions.cs index cb2fe44f2..373aa9201 100644 --- a/NzbDrone.Common/Instrumentation/LogEventExtensions.cs +++ b/NzbDrone.Common/Instrumentation/LogEventExtensions.cs @@ -13,7 +13,6 @@ public static string GetHash(this LogEventInfo logEvent) return HashUtil.CalculateCrc(hashSeed); } - public static string GetFormattedMessage(this LogEventInfo logEvent) { var message = logEvent.FormattedMessage; diff --git a/NzbDrone.Common/Instrumentation/LoggerExtensions.cs b/NzbDrone.Common/Instrumentation/LoggerExtensions.cs new file mode 100644 index 000000000..9816965c7 --- /dev/null +++ b/NzbDrone.Common/Instrumentation/LoggerExtensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using NLog; +using NzbDrone.Common.Messaging.Tracking; + +namespace NzbDrone.Common.Instrumentation +{ + public static class LoggerExtensions + { + public static void Complete(this Logger logger, string message) + { + var logEvent = new LogEventInfo(LogLevel.Info, logger.Name, message); + logEvent.Properties.Add("Status", ProcessState.Completed); + + logger.Log(logEvent); + } + + public static void Complete(this Logger logger, string message, params object[] args) + { + var formattedMessage = String.Format(message, args); + Complete(logger, formattedMessage); + } + + public static void Failed(this Logger logger, string message) + { + var logEvent = new LogEventInfo(LogLevel.Info, logger.Name, message); + logEvent.Properties.Add("Status", ProcessState.Failed); + + logger.Log(logEvent); + } + + public static void Failed(this Logger logger, string message, params object[] args) + { + var formattedMessage = String.Format(message, args); + Failed(logger, formattedMessage); + } + } +} diff --git a/NzbDrone.Common/Messaging/IProcessMessage.cs b/NzbDrone.Common/Messaging/IProcessMessage.cs index b008d9e9b..d22eeea64 100644 --- a/NzbDrone.Common/Messaging/IProcessMessage.cs +++ b/NzbDrone.Common/Messaging/IProcessMessage.cs @@ -4,7 +4,6 @@ public interface IProcessMessage { } public interface IProcessMessageAsync : IProcessMessage { } - public interface IProcessMessage : IProcessMessage { } public interface IProcessMessageAsync : IProcessMessageAsync { } diff --git a/NzbDrone.Common/Messaging/Tracking/CommandTrackingService.cs b/NzbDrone.Common/Messaging/Tracking/CommandTrackingService.cs index 2cb3b65ab..27c0c8bf1 100644 --- a/NzbDrone.Common/Messaging/Tracking/CommandTrackingService.cs +++ b/NzbDrone.Common/Messaging/Tracking/CommandTrackingService.cs @@ -33,7 +33,7 @@ public TrackedCommand TrackIfNew(ICommand command) return null; } - var trackedCommand = new TrackedCommand(command, CommandState.Running); + var trackedCommand = new TrackedCommand(command, ProcessState.Running); Store(trackedCommand); return trackedCommand; @@ -45,7 +45,7 @@ public ExistingCommand TrackNewOrGet(ICommand command) if (trackedCommand == null) { - trackedCommand = new TrackedCommand(command, CommandState.Running); + trackedCommand = new TrackedCommand(command, ProcessState.Running); Store(trackedCommand); return new ExistingCommand(false, trackedCommand); @@ -57,7 +57,7 @@ public ExistingCommand TrackNewOrGet(ICommand command) public TrackedCommand Completed(TrackedCommand trackedCommand, TimeSpan runtime) { trackedCommand.StateChangeTime = DateTime.UtcNow; - trackedCommand.State = CommandState.Completed; + trackedCommand.State = ProcessState.Completed; trackedCommand.Runtime = runtime; Store(trackedCommand); @@ -68,7 +68,7 @@ public TrackedCommand Completed(TrackedCommand trackedCommand, TimeSpan runtime) public TrackedCommand Failed(TrackedCommand trackedCommand, Exception e) { trackedCommand.StateChangeTime = DateTime.UtcNow; - trackedCommand.State = CommandState.Failed; + trackedCommand.State = ProcessState.Failed; trackedCommand.Exception = e; Store(trackedCommand); @@ -94,7 +94,7 @@ public TrackedCommand FindExisting(ICommand command) private List Running(Type type = null) { - var running = AllTracked().Where(i => i.State == CommandState.Running); + var running = AllTracked().Where(i => i.State == ProcessState.Running); if (type != null) { @@ -116,7 +116,7 @@ private void Store(TrackedCommand trackedCommand) public void Execute(TrackedCommandCleanupCommand message) { - var old = AllTracked().Where(c => c.State != CommandState.Running && c.StateChangeTime < DateTime.UtcNow.AddMinutes(-5)); + var old = AllTracked().Where(c => c.State != ProcessState.Running && c.StateChangeTime < DateTime.UtcNow.AddMinutes(-5)); foreach (var trackedCommand in old) { diff --git a/NzbDrone.Common/Messaging/Tracking/ProcessState.cs b/NzbDrone.Common/Messaging/Tracking/ProcessState.cs new file mode 100644 index 000000000..dc79c37f4 --- /dev/null +++ b/NzbDrone.Common/Messaging/Tracking/ProcessState.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Common.Messaging.Tracking +{ + public enum ProcessState + { + Running, + Completed, + Failed + } +} diff --git a/NzbDrone.Common/Messaging/Tracking/TrackedCommand.cs b/NzbDrone.Common/Messaging/Tracking/TrackedCommand.cs index 37f69de88..35b0e05ac 100644 --- a/NzbDrone.Common/Messaging/Tracking/TrackedCommand.cs +++ b/NzbDrone.Common/Messaging/Tracking/TrackedCommand.cs @@ -8,7 +8,7 @@ public class TrackedCommand public String Name { get; private set; } public String Type { get; private set; } public ICommand Command { get; private set; } - public CommandState State { get; set; } + public ProcessState State { get; set; } public DateTime StateChangeTime { get; set; } public TimeSpan Runtime { get; set; } public Exception Exception { get; set; } @@ -17,7 +17,7 @@ public TrackedCommand() { } - public TrackedCommand(ICommand command, CommandState state) + public TrackedCommand(ICommand command, ProcessState state) { Id = command.CommandId; Name = command.GetType().Name; @@ -27,11 +27,4 @@ public TrackedCommand(ICommand command, CommandState state) StateChangeTime = DateTime.UtcNow; } } - - public enum CommandState - { - Running = 0, - Completed = 1, - Failed = 2 - } } diff --git a/NzbDrone.Common/NzbDrone.Common.csproj b/NzbDrone.Common/NzbDrone.Common.csproj index 75b255d08..d25e5eb61 100644 --- a/NzbDrone.Common/NzbDrone.Common.csproj +++ b/NzbDrone.Common/NzbDrone.Common.csproj @@ -92,6 +92,8 @@ + + diff --git a/NzbDrone.Core/Indexers/RssSyncService.cs b/NzbDrone.Core/Indexers/RssSyncService.cs index baf414fac..dcbf340b1 100644 --- a/NzbDrone.Core/Indexers/RssSyncService.cs +++ b/NzbDrone.Core/Indexers/RssSyncService.cs @@ -1,5 +1,6 @@ using System.Linq; using NLog; +using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Messaging; using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.Download; @@ -38,7 +39,7 @@ public void Sync() var decisions = _downloadDecisionMaker.GetRssDecision(reports); var qualifiedReports = _downloadApprovedReports.DownloadApproved(decisions); - _logger.Info("RSS Sync Completed. Reports found: {0}, Reports downloaded: {1}", reports.Count, qualifiedReports.Count()); + _logger.Complete("RSS Sync Completed. Reports found: {0}, Reports downloaded: {1}", reports.Count, qualifiedReports.Count()); } public void Execute(RssSyncCommand message) diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 6a13f8b82..6cb78cca8 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -221,7 +221,7 @@ - + diff --git a/NzbDrone.Core/ProgressMessaging/ProgressMessage.cs b/NzbDrone.Core/ProgressMessaging/ProgressMessage.cs index 876087efb..b9bc8fc6d 100644 --- a/NzbDrone.Core/ProgressMessaging/ProgressMessage.cs +++ b/NzbDrone.Core/ProgressMessaging/ProgressMessage.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using NzbDrone.Common.Messaging.Tracking; namespace NzbDrone.Core.ProgressMessaging { @@ -10,5 +8,6 @@ public class ProgressMessage public DateTime Time { get; set; } public String CommandId { get; set; } public String Message { get; set; } + public ProcessState Status { get; set; } } } diff --git a/NzbDrone.Core/ProgressMessaging/ProgressMessagingTarget.cs b/NzbDrone.Core/ProgressMessaging/ProgressMessageTarget.cs similarity index 85% rename from NzbDrone.Core/ProgressMessaging/ProgressMessagingTarget.cs rename to NzbDrone.Core/ProgressMessaging/ProgressMessageTarget.cs index 247fa7686..ee99f7599 100644 --- a/NzbDrone.Core/ProgressMessaging/ProgressMessagingTarget.cs +++ b/NzbDrone.Core/ProgressMessaging/ProgressMessageTarget.cs @@ -4,17 +4,18 @@ using NLog.Layouts; using NLog.Targets; using NzbDrone.Common.Messaging; +using NzbDrone.Common.Messaging.Tracking; using NzbDrone.Core.Lifecycle; namespace NzbDrone.Core.ProgressMessaging { - public class ProgressMessagingTarget : TargetWithLayout, IHandle, IHandle + public class ProgressMessageTarget : TargetWithLayout, IHandle, IHandle { private readonly IMessageAggregator _messageAggregator; public LoggingRule Rule { get; set; } - public ProgressMessagingTarget(IMessageAggregator messageAggregator) + public ProgressMessageTarget(IMessageAggregator messageAggregator) { _messageAggregator = messageAggregator; } @@ -55,10 +56,13 @@ protected override void Write(LogEventInfo logEvent) return; } + var status = logEvent.Properties.ContainsKey("Status") ? (ProcessState)logEvent.Properties["Status"] : ProcessState.Running; + var message = new ProgressMessage(); message.Time = logEvent.TimeStamp; message.CommandId = commandId; message.Message = logEvent.FormattedMessage; + message.Status = status; _messageAggregator.PublishEvent(new NewProgressMessageEvent(message)); } diff --git a/UI/ProgressMessaging/ProgressMessageCollection.js b/UI/ProgressMessaging/ProgressMessageCollection.js new file mode 100644 index 000000000..063307f10 --- /dev/null +++ b/UI/ProgressMessaging/ProgressMessageCollection.js @@ -0,0 +1,40 @@ +'use strict'; +define( + [ + 'backbone', + 'ProgressMessaging/ProgressMessageModel', + 'Shared/Messenger', + 'Mixins/backbone.signalr.mixin' + ], function (Backbone, ProgressMessageModel, Messenger) { + + var ProgressMessageCollection = Backbone.Collection.extend({ + url : window.ApiRoot + '/progressmessage', + model: ProgressMessageModel + }); + + var collection = new ProgressMessageCollection().bindSignalR(); + + collection.signalRconnection.received(function (message) { + + var type = getMessengerType(message.status); + + Messenger.show({ + id : message.commandId, + message: message.message, + type : type + }); + }); + + var getMessengerType = function (status) { + switch (status) { + case 'completed': + return 'success'; + case 'failed': + return 'error'; + default: + return 'info'; + } + } + + return collection; + }); diff --git a/UI/ProgressMessaging/ProgressMessageModel.js b/UI/ProgressMessaging/ProgressMessageModel.js new file mode 100644 index 000000000..33a6217c8 --- /dev/null +++ b/UI/ProgressMessaging/ProgressMessageModel.js @@ -0,0 +1,8 @@ +'use strict'; +define( + [ + 'backbone' + ], function (Backbone) { + return Backbone.Model.extend({ + }); + }); diff --git a/UI/Router.js b/UI/Router.js index 86b42c1ee..4388c4f69 100644 --- a/UI/Router.js +++ b/UI/Router.js @@ -5,11 +5,12 @@ require( 'marionette', 'Controller', 'Series/SeriesCollection', + 'ProgressMessaging/ProgressMessageCollection', 'Shared/Actioneer', 'Navbar/NavbarView', 'jQuery/RouteBinder', 'jquery' - ], function (App, Marionette, Controller, SeriesCollection, Actioneer, NavbarView, RouterBinder, $) { + ], function (App, Marionette, Controller, SeriesCollection, ProgressMessageCollection, Actioneer, NavbarView, RouterBinder, $) { var Router = Marionette.AppRouter.extend({ diff --git a/UI/Series/Index/SeriesIndexLayout.js b/UI/Series/Index/SeriesIndexLayout.js index 59ae9163b..dd44e3cb6 100644 --- a/UI/Series/Index/SeriesIndexLayout.js +++ b/UI/Series/Index/SeriesIndexLayout.js @@ -105,7 +105,6 @@ define( title : 'RSS Sync', icon : 'icon-rss', command : 'rsssync', - successMessage: 'RSS Sync Completed', errorMessage : 'RSS Sync Failed!' }, { From e0735de9dab525f4dc7408c1cb1f36b5e59dda77 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sun, 1 Sep 2013 20:46:43 -0700 Subject: [PATCH 15/15] semi-colon required --- UI/ProgressMessaging/ProgressMessageCollection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UI/ProgressMessaging/ProgressMessageCollection.js b/UI/ProgressMessaging/ProgressMessageCollection.js index 063307f10..eb7317b4d 100644 --- a/UI/ProgressMessaging/ProgressMessageCollection.js +++ b/UI/ProgressMessaging/ProgressMessageCollection.js @@ -34,7 +34,7 @@ define( default: return 'info'; } - } + }; return collection; });