From c96ba5efd33e87d17e50ab85e5aef34c4722d080 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 30 Aug 2013 20:08:19 -0700 Subject: [PATCH] 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 @@ namespace NzbDrone.Api.Commands .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 @@ namespace NzbDrone.Api.Extensions { 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 @@ namespace NzbDrone.Api.Extensions 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 @@ namespace NzbDrone.Common.Test.EventingTests 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 @@ namespace NzbDrone.Common.Test.EventingTests 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 @@ { 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 @@ namespace NzbDrone.Common.Messaging { 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 @@ namespace NzbDrone.Common.Messaging.Tracking 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 @@ namespace NzbDrone.Common.Messaging.Tracking public bool ExistingCommand(ICommand command) { - var running = AllTracked().Where(i => i.Type == command.GetType().FullName && i.State == CommandState.Running); + return FindExisting(command) != null; + } + + public TrackedCommand FindExisting(ICommand command) + { + var comparer = new CommandEqualityComparer(); + return Running(command.GetType()).SingleOrDefault(t => comparer.Equals(t.Command, command)); + } - var result = running.Select(r => r.Command).Contains(command, new CommandEqualityComparer()); + 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 result; + return running.ToList(); } private void Store(TrackedCommand trackedCommand) @@ -84,7 +116,7 @@ namespace NzbDrone.Common.Messaging.Tracking 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 @@ namespace NzbDrone.Common.Messaging.Tracking 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 @@ namespace NzbDrone.Core.Jobs 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' - }); - }); - - commandPromise.fail(function (options) { - if (options.readyState === 0 || options.status === 0) { - return; - } - - Messenger.show({ - message: 'Failed to test notification settings', - type : 'error' - }); + 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.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); + }); + }, + + _handleCommands: function () { + var self = this; + + _.each(this.trackedCommands, function (trackedCommand){ + if (trackedCommand.completed === true) { + return; + } - if (options.leaveIcon) { - options.element.removeClass('icon-spin'); + var options = trackedCommand.options; + var command = CommandCollection.find({ 'id': trackedCommand.id }); + + if (!command) { + return; } - else { - options.element.addClass(options.iconClass); - options.element.removeClass('icon-nd-spinner'); + if (command.get('state') === 'completed') { + trackedCommand.completed = true; + + self._onSuccess(options, command.get('id')); + self._onComplete(options); } - if (options.always) { - options.always.call(options.context); + 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); - } - }); - 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); - } + 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.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();