diff --git a/src/Libraries/Growl.Connector.dll b/src/Libraries/Growl.Connector.dll new file mode 100644 index 000000000..ba848ae44 Binary files /dev/null and b/src/Libraries/Growl.Connector.dll differ diff --git a/src/Libraries/Growl.CoreLibrary.dll b/src/Libraries/Growl.CoreLibrary.dll new file mode 100644 index 000000000..5e67e08f4 Binary files /dev/null and b/src/Libraries/Growl.CoreLibrary.dll differ diff --git a/src/NzbDrone.Core/Notifications/Growl/GrowlService.cs b/src/NzbDrone.Core/Notifications/Growl/GrowlService.cs index 88fd7a337..b14991721 100644 --- a/src/NzbDrone.Core/Notifications/Growl/GrowlService.cs +++ b/src/NzbDrone.Core/Notifications/Growl/GrowlService.cs @@ -2,11 +2,16 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Drawing; +using System.Drawing.Imaging; using FluentValidation.Results; +using Growl.CoreLibrary; using Growl.Connector; using NLog; using NzbDrone.Common.Instrumentation; using GrowlNotification = Growl.Connector.Notification; +using System.Reflection; +using System.IO; namespace NzbDrone.Core.Notifications.Growl { @@ -21,42 +26,121 @@ namespace NzbDrone.Core.Notifications.Growl private readonly Logger _logger; private readonly Application _growlApplication = new Application("NzbDrone"); - private GrowlConnector _growlConnector; - private readonly List _notificationTypes; + private readonly NotificationType[] _notificationTypes; + + private class GrowlRequestState + { + private AutoResetEvent _autoEvent = new AutoResetEvent(false); + private bool _isError = false; + private int _code; + private string _description; + + public void Wait(int timeoutMs) + { + try + { + if (!_autoEvent.WaitOne(timeoutMs)) + { + throw new GrowlException(ErrorCode.TIMED_OUT, ErrorDescription.TIMED_OUT, null); + } + if (_isError) + { + throw new GrowlException(_code, _description, null); + } + } + finally + { + _autoEvent.Reset(); + _isError = false; + _code = 0; + _description = null; + } + } + + public void Update() + { + _autoEvent.Set(); + } + + public void Update(int code, string description) + { + _code = code; + _description = description; + _isError = true; + _autoEvent.Set(); + } + } public GrowlService(Logger logger) { _logger = logger; _notificationTypes = GetNotificationTypes(); - _growlApplication.Icon = "https://raw.github.com/NzbDrone/NzbDrone/master/Logo/64.png"; + + var icon = Properties.Resources.Icon64; + var stream = new MemoryStream(); + icon.Save(stream, ImageFormat.Bmp); + _growlApplication.Icon = new BinaryData(stream.ToArray()); + } + + private GrowlConnector GetGrowlConnector(string hostname, int port, string password) + { + var growlConnector = new GrowlConnector(password, hostname, port); + growlConnector.OKResponse += GrowlOKResponse; + growlConnector.ErrorResponse += GrowlErrorResponse; + return growlConnector; } public void SendNotification(string title, string message, string notificationTypeName, string hostname, int port, string password) { + _logger.Debug("Sending Notification to: {0}:{1}", hostname, port); + var notificationType = _notificationTypes.Single(n => n.Name == notificationTypeName); - var notification = new GrowlNotification("NzbDrone", notificationType.Name, DateTime.Now.Ticks.ToString(), title, message); + var notification = new GrowlNotification(_growlApplication.Name, notificationType.Name, DateTime.Now.Ticks.ToString(), title, message); - _growlConnector = new GrowlConnector(password, hostname, port); + var growlConnector = GetGrowlConnector(hostname, port, password); - _logger.Debug("Sending Notification to: {0}:{1}", hostname, port); - _growlConnector.Notify(notification); + var requestState = new GrowlRequestState(); + growlConnector.Notify(notification, requestState); + requestState.Wait(5000); } private void Register(string host, int port, string password) { _logger.Debug("Registering NzbDrone with Growl host: {0}:{1}", host, port); - _growlConnector = new GrowlConnector(password, host, port); - _growlConnector.Register(_growlApplication, _notificationTypes.ToArray()); + + var growlConnector = GetGrowlConnector(host, port, password); + + var requestState = new GrowlRequestState(); + growlConnector.Register(_growlApplication, _notificationTypes, requestState); + requestState.Wait(5000); + } + + private void GrowlErrorResponse(Response response, object state) + { + var requestState = state as GrowlRequestState; + if (requestState != null) + { + requestState.Update(response.ErrorCode, response.ErrorDescription); + } + } + + private void GrowlOKResponse(Response response, object state) + { + var requestState = state as GrowlRequestState; + if (requestState != null) + { + requestState.Update(); + } } - private List GetNotificationTypes() + private NotificationType[] GetNotificationTypes() { var notificationTypes = new List(); notificationTypes.Add(new NotificationType("TEST", "Test")); notificationTypes.Add(new NotificationType("GRAB", "Episode Grabbed")); notificationTypes.Add(new NotificationType("DOWNLOAD", "Episode Complete")); - return notificationTypes; + return notificationTypes.ToArray(); } public ValidationFailure Test(GrowlSettings settings) @@ -68,8 +152,6 @@ namespace NzbDrone.Core.Notifications.Growl const string title = "Test Notification"; const string body = "This is a test message from NzbDrone"; - Thread.Sleep(5000); - SendNotification(title, body, "TEST", settings.Host, settings.Port, settings.Password); } catch (Exception ex) diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 88d5299eb..399c6735a 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -52,6 +52,14 @@ 4 + + False + ..\Libraries\Growl.Connector.dll + + + False + ..\Libraries\Growl.CoreLibrary.dll + @@ -70,12 +78,6 @@ ..\packages\FluentValidation.5.0.0.1\lib\Net40\FluentValidation.dll - - ..\packages\Growl.0.6\lib\Growl.Connector.dll - - - ..\packages\Growl.0.6\lib\Growl.CoreLibrary.dll - ..\packages\MediaInfoNet.0.3\lib\MediaInfoDotNet.dll @@ -661,6 +663,11 @@ + + True + True + Resources.resx + @@ -795,8 +802,14 @@ Always + + + + + ResXFileCodeGenerator + Resources.Designer.cs + - diff --git a/src/NzbDrone.Core/Properties/Resources.Designer.cs b/src/NzbDrone.Core/Properties/Resources.Designer.cs new file mode 100644 index 000000000..a995b6ddd --- /dev/null +++ b/src/NzbDrone.Core/Properties/Resources.Designer.cs @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.18444 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace NzbDrone.Core.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NzbDrone.Core.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap Icon64 { + get { + object obj = ResourceManager.GetObject("Icon64", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + } +} diff --git a/src/NzbDrone.Core/Properties/Resources.resx b/src/NzbDrone.Core/Properties/Resources.resx new file mode 100644 index 000000000..d22523afe --- /dev/null +++ b/src/NzbDrone.Core/Properties/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\64.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/src/NzbDrone.Core/Resources/64.png b/src/NzbDrone.Core/Resources/64.png new file mode 100644 index 000000000..33387d7f9 Binary files /dev/null and b/src/NzbDrone.Core/Resources/64.png differ diff --git a/src/NzbDrone.Core/packages.config b/src/NzbDrone.Core/packages.config index f657add80..e035259dc 100644 --- a/src/NzbDrone.Core/packages.config +++ b/src/NzbDrone.Core/packages.config @@ -2,7 +2,6 @@ -