From 6ffa4e05684d22f83d894fba7deda99e66596bd2 Mon Sep 17 00:00:00 2001 From: Keivan Beigi Date: Mon, 18 Feb 2013 18:37:16 -0800 Subject: [PATCH] NzbDrone is getting on a bus! (added EventAggregator) --- NzbDrone.Common/Eventing/EventAggregator.cs | 174 +++++++++++++++++++ NzbDrone.Common/Eventing/IEventAggregator.cs | 47 +++++ NzbDrone.Common/Eventing/IHandle.cs | 22 +++ NzbDrone.Common/NzbDrone.Common.csproj | 3 + 4 files changed, 246 insertions(+) create mode 100644 NzbDrone.Common/Eventing/EventAggregator.cs create mode 100644 NzbDrone.Common/Eventing/IEventAggregator.cs create mode 100644 NzbDrone.Common/Eventing/IHandle.cs diff --git a/NzbDrone.Common/Eventing/EventAggregator.cs b/NzbDrone.Common/Eventing/EventAggregator.cs new file mode 100644 index 000000000..bb76f8479 --- /dev/null +++ b/NzbDrone.Common/Eventing/EventAggregator.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +//From http://caliburnmicro.codeplex.com/ + +namespace NzbDrone.Common.Eventing +{ + /// + /// Enables loosely-coupled publication of and subscription to events. + /// + public class EventAggregator : IEventAggregator + { + readonly List handlers = new List(); + + /// + /// The default thread marshaller used for publication; + /// + public static Action DefaultPublicationThreadMarshaller = action => action(); + + /// + /// Processing of handler results on publication thread. + /// + public static Action HandlerResultProcessing = (target, result) => { }; + + /// + /// Initializes a new instance of the class. + /// + public EventAggregator() + { + PublicationThreadMarshaller = DefaultPublicationThreadMarshaller; + } + + /// + /// Gets or sets the default publication thread marshaller. + /// + /// + /// The default publication thread marshaller. + /// + public Action PublicationThreadMarshaller { get; set; } + + /// + /// Subscribes an instance to all events declared through implementations of + /// + /// The instance to subscribe for event publication. + public virtual void Subscribe(object instance) + { + lock (handlers) + { + if (handlers.Any(x => x.Matches(instance))) + { + return; + } + + handlers.Add(new Handler(instance)); + } + } + + /// + /// Unsubscribes the instance from all events. + /// + /// The instance to unsubscribe. + public virtual void Unsubscribe(object instance) + { + lock (handlers) + { + var found = handlers.FirstOrDefault(x => x.Matches(instance)); + + if (found != null) + { + handlers.Remove(found); + } + } + } + + /// + /// Publishes a message. + /// + /// The message instance. + /// + /// Does not marshall the the publication to any special thread by default. + /// + public virtual void Publish(object message) + { + Publish(message, PublicationThreadMarshaller); + } + + /// + /// Publishes a message. + /// + /// The message instance. + /// Allows the publisher to provide a custom thread marshaller for the message publication. + public virtual void Publish(object message, Action marshal) + { + Handler[] toNotify; + lock (handlers) + { + toNotify = handlers.ToArray(); + } + + marshal(() => + { + var messageType = message.GetType(); + + var dead = toNotify + .Where(handler => !handler.Handle(messageType, message)) + .ToList(); + + if (dead.Any()) + { + lock (handlers) + { + foreach (var item in dead) + { + handlers.Remove(item); + } + } + } + }); + } + + + class Handler + { + readonly WeakReference reference; + readonly Dictionary supportedHandlers = new Dictionary(); + + public Handler(object handler) + { + reference = new WeakReference(handler); + + var interfaces = handler.GetType().GetInterfaces() + .Where(x => typeof(IHandle).IsAssignableFrom(x) && x.IsGenericType); + + foreach (var @interface in interfaces) + { + var type = @interface.GetGenericArguments()[0]; + var method = @interface.GetMethod("Handle"); + supportedHandlers[type] = method; + } + } + + public bool Matches(object instance) + { + return reference.Target == instance; + } + + public bool Handle(Type messageType, object message) + { + var target = reference.Target; + if (target == null) + { + return false; + } + + foreach (var pair in supportedHandlers) + { + if (pair.Key.IsAssignableFrom(messageType)) + { + var result = pair.Value.Invoke(target, new[] { message }); + if (result != null) + { + HandlerResultProcessing(target, result); + } + return true; + } + } + + return true; + } + } + } +} \ No newline at end of file diff --git a/NzbDrone.Common/Eventing/IEventAggregator.cs b/NzbDrone.Common/Eventing/IEventAggregator.cs new file mode 100644 index 000000000..369b8a8f1 --- /dev/null +++ b/NzbDrone.Common/Eventing/IEventAggregator.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq; + +namespace NzbDrone.Common.Eventing +{ + /// + /// Enables loosely-coupled publication of and subscription to events. + /// + public interface IEventAggregator + { + /// + /// Gets or sets the default publication thread marshaller. + /// + /// + /// The default publication thread marshaller. + /// + Action PublicationThreadMarshaller { get; set; } + + /// + /// Subscribes an instance to all events declared through implementations of + /// + /// The instance to subscribe for event publication. + void Subscribe(object instance); + + /// + /// Unsubscribes the instance from all events. + /// + /// The instance to unsubscribe. + void Unsubscribe(object instance); + + /// + /// Publishes a message. + /// + /// The message instance. + /// + /// Uses the default thread marshaller during publication. + /// + void Publish(object message); + + /// + /// Publishes a message. + /// + /// The message instance. + /// Allows the publisher to provide a custom thread marshaller for the message publication. + void Publish(object message, Action marshal); + } +} \ No newline at end of file diff --git a/NzbDrone.Common/Eventing/IHandle.cs b/NzbDrone.Common/Eventing/IHandle.cs new file mode 100644 index 000000000..afbf70fae --- /dev/null +++ b/NzbDrone.Common/Eventing/IHandle.cs @@ -0,0 +1,22 @@ +using System.Linq; + +namespace NzbDrone.Common.Eventing +{ + /// + /// Denotes a class which can handle a particular type of message. + /// + /// The type of message to handle. + public interface IHandle : IHandle + { + /// + /// Handles the message. + /// + /// The message. + void Handle(TMessage message); + } + + /// + /// A marker interface for classes that subscribe to messages. + /// + public interface IHandle { } +} \ No newline at end of file diff --git a/NzbDrone.Common/NzbDrone.Common.csproj b/NzbDrone.Common/NzbDrone.Common.csproj index db9143e3c..2f450d59c 100644 --- a/NzbDrone.Common/NzbDrone.Common.csproj +++ b/NzbDrone.Common/NzbDrone.Common.csproj @@ -110,6 +110,9 @@ + + +