Merge branch 'ui-notifications' into develop

Conflicts:
	NzbDrone.Common/NzbDrone.Common.csproj
	UI/Series/Details/SeasonLayout.js
This commit is contained in:
Mark McDowall
2013-09-03 07:18:04 -07:00
87 changed files with 1322 additions and 294 deletions
-1
View File
@@ -28,7 +28,6 @@ namespace NzbDrone.Common.Cache
return GetCache<T>(host, host.FullName);
}
public void Clear()
{
_cache.Clear();
+6 -1
View File
@@ -61,6 +61,12 @@ namespace NzbDrone.Common.Cache
return value.Object;
}
public void Remove(string key)
{
CacheItem value;
_store.TryRemove(key, out value);
}
public T Get(string key, Func<T> function, TimeSpan? lifeTime = null)
{
Ensure.That(() => key).IsNotNullOrWhiteSpace();
@@ -81,7 +87,6 @@ namespace NzbDrone.Common.Cache
return value;
}
public void Clear()
{
_store.Clear();
+1
View File
@@ -13,6 +13,7 @@ namespace NzbDrone.Common.Cache
void Set(string key, T value, TimeSpan? lifetime = null);
T Get(string key, Func<T> function, TimeSpan? lifeTime = null);
T Find(string key);
void Remove(string key);
ICollection<T> Values { get; }
}
+2 -24
View File
@@ -34,31 +34,9 @@ namespace NzbDrone.Common
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)
@@ -13,7 +13,6 @@ namespace NzbDrone.Common.Instrumentation
return HashUtil.CalculateCrc(hashSeed);
}
public static string GetFormattedMessage(this LogEventInfo logEvent)
{
var message = logEvent.FormattedMessage;
@@ -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);
}
}
}
@@ -1,12 +0,0 @@
namespace NzbDrone.Common.Messaging
{
public class CommandCompletedEvent : IEvent
{
public ICommand Command { get; private set; }
public CommandCompletedEvent(ICommand command)
{
Command = command;
}
}
}
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Common.Messaging
{
public class CommandEqualityComparer : IEqualityComparer<ICommand>
{
public bool Equals(ICommand x, ICommand y)
{
var xProperties = x.GetType().GetProperties();
var yProperties = y.GetType().GetProperties();
foreach (var xProperty in xProperties)
{
if (xProperty.Name == "CommandId")
{
continue;
}
var yProperty = yProperties.SingleOrDefault(p => p.Name == xProperty.Name);
if (yProperty == null)
{
continue;
}
if (!xProperty.GetValue(x, null).Equals(yProperty.GetValue(y, null)))
{
return false;
}
}
return true;
}
public int GetHashCode(ICommand obj)
{
return obj.CommandId.GetHashCode();
}
}
}
@@ -1,16 +0,0 @@
using System;
namespace NzbDrone.Common.Messaging
{
public class CommandFailedEvent : IEvent
{
public ICommand Command { get; private set; }
public Exception Exception { get; private set; }
public CommandFailedEvent(ICommand command, Exception exception)
{
Command = command;
Exception = exception;
}
}
}
@@ -1,12 +0,0 @@
namespace NzbDrone.Common.Messaging
{
public class CommandExecutedEvent : IEvent
{
public ICommand Command { get; private set; }
public CommandExecutedEvent(ICommand command)
{
Command = command;
}
}
}
@@ -0,0 +1,14 @@
using NzbDrone.Common.Messaging.Tracking;
namespace NzbDrone.Common.Messaging.Events
{
public class CommandCompletedEvent : IEvent
{
public TrackedCommand TrackedCommand { get; private set; }
public CommandCompletedEvent(TrackedCommand trackedCommand)
{
TrackedCommand = trackedCommand;
}
}
}
@@ -0,0 +1,14 @@
using NzbDrone.Common.Messaging.Tracking;
namespace NzbDrone.Common.Messaging.Events
{
public class CommandExecutedEvent : IEvent
{
public TrackedCommand TrackedCommand { get; private set; }
public CommandExecutedEvent(TrackedCommand trackedCommand)
{
TrackedCommand = trackedCommand;
}
}
}
@@ -0,0 +1,17 @@
using System;
using NzbDrone.Common.Messaging.Tracking;
namespace NzbDrone.Common.Messaging.Events
{
public class CommandFailedEvent : IEvent
{
public TrackedCommand TrackedCommand { get; private set; }
public Exception Exception { get; private set; }
public CommandFailedEvent(TrackedCommand trackedCommand, Exception exception)
{
TrackedCommand = trackedCommand;
Exception = exception;
}
}
}
@@ -0,0 +1,14 @@
using NzbDrone.Common.Messaging.Tracking;
namespace NzbDrone.Common.Messaging.Events
{
public class CommandStartedEvent : IEvent
{
public TrackedCommand TrackedCommand { get; private set; }
public CommandStartedEvent(TrackedCommand trackedCommand)
{
TrackedCommand = trackedCommand;
}
}
}
+4
View File
@@ -1,6 +1,10 @@
using System;
using System.Collections.Generic;
namespace NzbDrone.Common.Messaging
{
public interface ICommand : IMessage
{
String CommandId { get; }
}
}
@@ -1,4 +1,6 @@
namespace NzbDrone.Common.Messaging
using NzbDrone.Common.Messaging.Tracking;
namespace NzbDrone.Common.Messaging
{
/// <summary>
/// Enables loosely-coupled publication of events.
@@ -7,6 +9,8 @@
{
void PublishEvent<TEvent>(TEvent @event) where TEvent : class, IEvent;
void PublishCommand<TCommand>(TCommand command) where TCommand : class, ICommand;
void PublishCommand(string commandType);
void PublishCommand(string commandTypeName);
TrackedCommand PublishCommandAsync<TCommand>(TCommand command) where TCommand : class, ICommand;
TrackedCommand PublishCommandAsync(string commandTypeName);
}
}
@@ -4,7 +4,6 @@
public interface IProcessMessageAsync : IProcessMessage { }
public interface IProcessMessage<TMessage> : IProcessMessage { }
public interface IProcessMessageAsync<TMessage> : IProcessMessageAsync { }
+70 -16
View File
@@ -4,6 +4,8 @@ using System.Linq;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Messaging.Events;
using NzbDrone.Common.Messaging.Tracking;
using NzbDrone.Common.Serializer;
using NzbDrone.Common.TPL;
@@ -13,12 +15,14 @@ namespace NzbDrone.Common.Messaging
{
private readonly Logger _logger;
private readonly IServiceFactory _serviceFactory;
private readonly ITrackCommands _trackCommands;
private readonly TaskFactory _taskFactory;
public MessageAggregator(Logger logger, IServiceFactory serviceFactory)
public MessageAggregator(Logger logger, IServiceFactory serviceFactory, ITrackCommands trackCommands)
{
_logger = logger;
_serviceFactory = serviceFactory;
_trackCommands = trackCommands;
var scheduler = new LimitedConcurrencyLevelTaskScheduler(2);
_taskFactory = new TaskFactory(scheduler);
}
@@ -60,7 +64,6 @@ namespace NzbDrone.Common.Messaging
}
}
private static string GetEventName(Type eventType)
{
if (!eventType.IsGenericType)
@@ -71,15 +74,69 @@ namespace NzbDrone.Common.Messaging
return string.Format("{0}<{1}>", eventType.Name.Remove(eventType.Name.IndexOf('`')), eventType.GetGenericArguments()[0].Name);
}
public void PublishCommand<TCommand>(TCommand command) where TCommand : class, ICommand
{
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<TCommand>(trackedCommand);
}
public void PublishCommand(string commandTypeName)
{
dynamic command = GetCommand(commandTypeName);
PublishCommand(command);
}
public TrackedCommand PublishCommandAsync<TCommand>(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<TCommand>(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<TCommand>(TrackedCommand trackedCommand) where TCommand : class, ICommand
{
var command = (TCommand)trackedCommand.Command;
var handlerContract = typeof(IExecute<>).MakeGenericType(command.GetType());
var handler = (IExecute<TCommand>)_serviceFactory.Build(handlerContract);
_logger.Debug("{0} -> {1}", command.GetType().Name, handler.GetType().Name);
@@ -88,30 +145,27 @@ namespace NzbDrone.Common.Messaging
try
{
MappedDiagnosticsContext.Set("CommandId", trackedCommand.Command.CommandId);
PublishEvent(new CommandStartedEvent(trackedCommand));
handler.Execute(command);
sw.Stop();
PublishEvent(new CommandCompletedEvent(command));
_trackCommands.Completed(trackedCommand, sw.Elapsed);
PublishEvent(new CommandCompletedEvent(trackedCommand));
}
catch (Exception 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);
}
}
}
+6 -4
View File
@@ -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; private set; }
public TestCommand()
{
Duration = 4000;
}
public int Duration { get; set; }
}
}
@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting;
using NzbDrone.Common.Cache;
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<TrackedCommand> AllTracked();
Boolean ExistingCommand(ICommand command);
TrackedCommand FindExisting(ICommand command);
}
public class TrackCommands : ITrackCommands, IExecute<TrackedCommandCleanupCommand>
{
private readonly ICached<TrackedCommand> _cache;
public TrackCommands(ICacheManger cacheManger)
{
_cache = cacheManger.GetCache<TrackedCommand>(GetType());
}
public TrackedCommand TrackIfNew(ICommand command)
{
if (ExistingCommand(command))
{
return null;
}
var trackedCommand = new TrackedCommand(command, ProcessState.Running);
Store(trackedCommand);
return trackedCommand;
}
public ExistingCommand TrackNewOrGet(ICommand command)
{
var trackedCommand = FindExisting(command);
if (trackedCommand == null)
{
trackedCommand = new TrackedCommand(command, ProcessState.Running);
Store(trackedCommand);
return new ExistingCommand(false, trackedCommand);
}
return new ExistingCommand(true, trackedCommand);
}
public TrackedCommand Completed(TrackedCommand trackedCommand, TimeSpan runtime)
{
trackedCommand.StateChangeTime = DateTime.UtcNow;
trackedCommand.State = ProcessState.Completed;
trackedCommand.Runtime = runtime;
Store(trackedCommand);
return trackedCommand;
}
public TrackedCommand Failed(TrackedCommand trackedCommand, Exception e)
{
trackedCommand.StateChangeTime = DateTime.UtcNow;
trackedCommand.State = ProcessState.Failed;
trackedCommand.Exception = e;
Store(trackedCommand);
return trackedCommand;
}
public List<TrackedCommand> AllTracked()
{
return _cache.Values.ToList();
}
public bool ExistingCommand(ICommand command)
{
return FindExisting(command) != null;
}
public TrackedCommand FindExisting(ICommand command)
{
var comparer = new CommandEqualityComparer();
return Running(command.GetType()).SingleOrDefault(t => comparer.Equals(t.Command, command));
}
private List<TrackedCommand> Running(Type type = null)
{
var running = AllTracked().Where(i => i.State == ProcessState.Running);
if (type != null)
{
return running.Where(t => t.Type == type.FullName).ToList();
}
return running.ToList();
}
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.State != ProcessState.Running && c.StateChangeTime < DateTime.UtcNow.AddMinutes(-5));
foreach (var trackedCommand in old)
{
_cache.Remove(trackedCommand.Command.CommandId);
}
}
}
}
@@ -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;
}
}
}
@@ -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
}
}
@@ -0,0 +1,30 @@
using System;
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 ProcessState State { get; set; }
public DateTime StateChangeTime { get; set; }
public TimeSpan Runtime { get; set; }
public Exception Exception { get; set; }
public TrackedCommand()
{
}
public TrackedCommand(ICommand command, ProcessState state)
{
Id = command.CommandId;
Name = command.GetType().Name;
Type = command.GetType().FullName;
Command = command;
State = state;
StateChangeTime = DateTime.UtcNow;
}
}
}
@@ -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();
}
}
}
+11 -3
View File
@@ -94,6 +94,14 @@
<Compile Include="Instrumentation\ExceptronTarget.cs" />
<Compile Include="Instrumentation\NzbDroneLogger.cs" />
<Compile Include="Instrumentation\LogTargets.cs" />
<Compile Include="Instrumentation\LoggerExtensions.cs" />
<Compile Include="Messaging\Tracking\ProcessState.cs" />
<Compile Include="Messaging\Tracking\CommandTrackingService.cs" />
<Compile Include="Messaging\Tracking\ExistingCommand.cs" />
<Compile Include="Messaging\Tracking\TrackedCommand.cs" />
<Compile Include="Messaging\Events\CommandStartedEvent.cs" />
<Compile Include="Messaging\CommandEqualityComparer.cs" />
<Compile Include="Messaging\Tracking\TrackedCommandCleanupCommand.cs" />
<Compile Include="PathEqualityComparer.cs" />
<Compile Include="Services.cs" />
<Compile Include="TPL\LimitedConcurrencyLevelTaskScheduler.cs" />
@@ -104,9 +112,9 @@
<Compile Include="Instrumentation\LogEventExtensions.cs" />
<Compile Include="Instrumentation\LogglyTarget.cs" />
<Compile Include="Serializer\Json.cs" />
<Compile Include="Messaging\CommandCompletedEvent.cs" />
<Compile Include="Messaging\CommandStartedEvent.cs" />
<Compile Include="Messaging\CommandFailedEvent.cs" />
<Compile Include="Messaging\Events\CommandCompletedEvent.cs" />
<Compile Include="Messaging\Events\CommandExecutedEvent.cs" />
<Compile Include="Messaging\Events\CommandFailedEvent.cs" />
<Compile Include="Messaging\IExecute.cs" />
<Compile Include="Messaging\ICommand.cs" />
<Compile Include="Messaging\IMessage.cs" />