From 8cac7ed1cd67a16ad969fee4314f00d761c4ee82 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 12 Jun 2013 23:41:26 -0700 Subject: [PATCH] Notifications can be tested Notification ImplementationType was added for showing in UI (Humanized/Title cased of Implementation) --- .../Notifications/NotificationResource.cs | 1 + .../NotificationTests/PlexProviderTest.cs | 17 ++- .../NotificationTests/ProwlProviderTest.cs | 114 ++---------------- NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 1 - NzbDrone.Core/Notifications/Email/Email.cs | 9 +- .../{EmailProvider.cs => EmailService.cs} | 27 ++++- .../Notifications/Email/EmailSettings.cs | 8 +- .../Notifications/Email/TestEmailCommand.cs | 15 +++ NzbDrone.Core/Notifications/Growl/Growl.cs | 9 +- .../{GrowlProvider.cs => GrowlService.cs} | 47 +++++--- .../Notifications/Growl/TestGrowlCommand.cs | 11 ++ NzbDrone.Core/Notifications/INotification.cs | 1 + NzbDrone.Core/Notifications/Notification.cs | 1 + .../Notifications/NotificationBase.cs | 1 + .../Notifications/NotificationService.cs | 3 +- .../Notifications/Plex/PlexClient.cs | 9 +- .../Notifications/Plex/PlexProvider.cs | 88 -------------- .../Notifications/Plex/PlexServer.cs | 11 +- .../Notifications/Plex/PlexServerSettings.cs | 5 +- .../Notifications/Plex/PlexService.cs | 110 +++++++++++++++++ .../Plex/TestPlexClientCommand.cs | 12 ++ .../Plex/TestPlexServerCommand.cs | 10 ++ .../Prowl/InvalidApiKeyException.cs | 18 +++ NzbDrone.Core/Notifications/Prowl/Prowl.cs | 9 +- .../Notifications/Prowl/ProwlProvider.cs | 78 ------------ .../Notifications/Prowl/ProwlService.cs | 93 ++++++++++++++ .../Notifications/Prowl/TestProwlCommand.cs | 10 ++ NzbDrone.Core/Notifications/Xbmc/Xbmc.cs | 5 + NzbDrone.Core/NzbDrone.Core.csproj | 14 ++- .../Notifications/AddItemTemplate.html | 2 +- UI/Settings/Notifications/AddView.js | 1 - .../Notifications/CollectionTemplate.html | 1 + UI/Settings/Notifications/EditTemplate.html | 4 +- UI/Settings/Notifications/ItemTemplate.html | 1 + 34 files changed, 418 insertions(+), 328 deletions(-) rename NzbDrone.Core/Notifications/Email/{EmailProvider.cs => EmailService.cs} (61%) create mode 100644 NzbDrone.Core/Notifications/Email/TestEmailCommand.cs rename NzbDrone.Core/Notifications/Growl/{GrowlProvider.cs => GrowlService.cs} (64%) create mode 100644 NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs delete mode 100644 NzbDrone.Core/Notifications/Plex/PlexProvider.cs create mode 100644 NzbDrone.Core/Notifications/Plex/PlexService.cs create mode 100644 NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs create mode 100644 NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs create mode 100644 NzbDrone.Core/Notifications/Prowl/InvalidApiKeyException.cs delete mode 100644 NzbDrone.Core/Notifications/Prowl/ProwlProvider.cs create mode 100644 NzbDrone.Core/Notifications/Prowl/ProwlService.cs create mode 100644 NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs diff --git a/NzbDrone.Api/Notifications/NotificationResource.cs b/NzbDrone.Api/Notifications/NotificationResource.cs index 2d1776040..05a9c0162 100644 --- a/NzbDrone.Api/Notifications/NotificationResource.cs +++ b/NzbDrone.Api/Notifications/NotificationResource.cs @@ -8,6 +8,7 @@ namespace NzbDrone.Api.Notifications public class NotificationResource : RestResource { public String Name { get; set; } + public String ImplementationName { get; set; } public Boolean OnGrab { get; set; } public Boolean OnDownload { get; set; } public List Fields { get; set; } diff --git a/NzbDrone.Core.Test/NotificationTests/PlexProviderTest.cs b/NzbDrone.Core.Test/NotificationTests/PlexProviderTest.cs index fe2daa2a5..bb1dea1f0 100644 --- a/NzbDrone.Core.Test/NotificationTests/PlexProviderTest.cs +++ b/NzbDrone.Core.Test/NotificationTests/PlexProviderTest.cs @@ -42,8 +42,7 @@ namespace NzbDrone.Core.Test.NotificationTests Mocker.GetMock().Setup(s => s.DownloadStream("http://localhost:32400/library/sections", null)) .Returns(stream); - - var result = Mocker.Resolve().GetSectionKeys("localhost:32400"); + var result = Mocker.Resolve().GetSectionKeys(new PlexServerSettings { Host = "localhost", Port = 32400 }); result.Should().HaveCount(1); @@ -62,7 +61,7 @@ namespace NzbDrone.Core.Test.NotificationTests .Returns(stream); - var result = Mocker.Resolve().GetSectionKeys("localhost:32400"); + var result = Mocker.Resolve().GetSectionKeys(new PlexServerSettings { Host = "localhost", Port = 32400 }); result.Should().HaveCount(1); @@ -80,8 +79,8 @@ namespace NzbDrone.Core.Test.NotificationTests Mocker.GetMock().Setup(s => s.DownloadStream("http://localhost:32400/library/sections", null)) .Returns(stream); - - var result = Mocker.Resolve().GetSectionKeys("localhost:32400"); + + var result = Mocker.Resolve().GetSectionKeys(new PlexServerSettings { Host = "localhost", Port = 32400 }); result.Should().HaveCount(2); @@ -100,8 +99,8 @@ namespace NzbDrone.Core.Test.NotificationTests Mocker.GetMock().Setup(s => s.DownloadString("http://localhost:32400/library/sections/5/refresh")) .Returns(response); - - Mocker.Resolve().UpdateSection("localhost:32400", 5); + + Mocker.Resolve().UpdateSection(new PlexServerSettings { Host = "localhost", Port = 32400 }, 5); @@ -121,7 +120,7 @@ namespace NzbDrone.Core.Test.NotificationTests .Returns("ok"); - Mocker.Resolve().Notify(_clientSettings, header, message); + Mocker.Resolve().Notify(_clientSettings, header, message); fakeHttp.Verify(v => v.DownloadString(expectedUrl), Times.Once()); @@ -142,7 +141,7 @@ namespace NzbDrone.Core.Test.NotificationTests .Returns("ok"); - Mocker.Resolve().Notify(_clientSettings, header, message); + Mocker.Resolve().Notify(_clientSettings, header, message); fakeHttp.Verify(v => v.DownloadString(expectedUrl, "plex", "plex"), Times.Once()); diff --git a/NzbDrone.Core.Test/NotificationTests/ProwlProviderTest.cs b/NzbDrone.Core.Test/NotificationTests/ProwlProviderTest.cs index c9e79a5b1..397b4a56c 100644 --- a/NzbDrone.Core.Test/NotificationTests/ProwlProviderTest.cs +++ b/NzbDrone.Core.Test/NotificationTests/ProwlProviderTest.cs @@ -9,130 +9,40 @@ namespace NzbDrone.Core.Test.NotificationTests { [Explicit] [TestFixture] - public class ProwlProviderTest : CoreTest + public class ProwlProviderTest : CoreTest { - private const string _apiKey = "c3bdc0f48168f72d546cc6872925b160f5cbffc1"; - private const string _apiKey2 = "46a710a46b111b0b8633819b0d8a1e0272a3affa"; + private const string _apiKey = "66e9f688b512152eb2688f0486ae542c76e564a2"; private const string _badApiKey = "1234567890abcdefghijklmnopqrstuvwxyz1234"; [Test] - public void Verify_should_return_true_for_a_valid_apiKey() + public void Verify_should_not_throw_for_a_valid_apiKey() { - - - - - var result = Mocker.Resolve().Verify(_apiKey); - - - result.Should().BeTrue(); + Subject.Verify(_apiKey); + ExceptionVerification.ExpectedWarns(0); } [Test] - public void Verify_should_return_false_for_an_invalid_apiKey() + public void Verify_should_throw_for_an_invalid_apiKey() { - - - - - var result = Mocker.Resolve().Verify(_badApiKey); - + Assert.Throws(() => Subject.Verify(_badApiKey)); ExceptionVerification.ExpectedWarns(1); - result.Should().BeFalse(); } [Test] - public void SendNotification_should_return_true_for_a_valid_apiKey() + public void SendNotification_should_not_throw_for_a_valid_apiKey() { - - - - - var result = Mocker.Resolve().SendNotification("NzbDrone Test", "This is a test message from NzbDrone", _apiKey); - - - result.Should().BeTrue(); + Subject.SendNotification("NzbDrone Test", "This is a test message from NzbDrone", _apiKey); + ExceptionVerification.ExpectedWarns(0); } [Test] - public void SendNotification_should_return_false_for_an_invalid_apiKey() + public void SendNotification_should_log_a_warning_for_an_invalid_apiKey() { - - + Subject.SendNotification("NzbDrone Test", "This is a test message from NzbDrone", _badApiKey); - - var result = Mocker.Resolve().SendNotification("NzbDrone Test", "This is a test message from NzbDrone", _badApiKey); - - ExceptionVerification.ExpectedWarns(1); - result.Should().BeFalse(); - } - - [Test] - public void SendNotification_should_alert_with_high_priority() - { - - - - - var result = Mocker.Resolve().SendNotification("NzbDrone Test", "This is a test message from NzbDrone (High)", _apiKey, NotificationPriority.High); - - - result.Should().BeTrue(); - } - - [Test] - public void SendNotification_should_alert_with_VeryLow_priority() - { - - - - - var result = Mocker.Resolve().SendNotification("NzbDrone Test", "This is a test message from NzbDrone (VeryLow)", _apiKey, NotificationPriority.VeryLow); - - - result.Should().BeTrue(); - } - - [Test] - public void SendNotification_should_have_a_call_back_url() - { - - - - - var result = Mocker.Resolve().SendNotification("NzbDrone Test", "This is a test message from NzbDrone", _apiKey, NotificationPriority.Normal, "http://www.nzbdrone.com"); - - - result.Should().BeTrue(); - } - - [Test] - public void SendNotification_should_return_true_for_two_valid_apiKey() - { - - - - - var result = Mocker.Resolve().SendNotification("NzbDrone Test", "This is a test message from NzbDrone", _apiKey + ", " + _apiKey2); - - - result.Should().BeTrue(); - } - - [Test] - public void SendNotification_should_return_true_for_valid_apiKey_with_bad_apiKey() - { - - - - - var result = Mocker.Resolve().SendNotification("NzbDrone Test", "This is a test message from NzbDrone", _apiKey + ", " + _badApiKey); - - - result.Should().BeTrue(); } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index ac25a5495..9e2bb3696 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -185,7 +185,6 @@ - diff --git a/NzbDrone.Core/Notifications/Email/Email.cs b/NzbDrone.Core/Notifications/Email/Email.cs index 6e470bcb4..923712d06 100644 --- a/NzbDrone.Core/Notifications/Email/Email.cs +++ b/NzbDrone.Core/Notifications/Email/Email.cs @@ -5,9 +5,9 @@ namespace NzbDrone.Core.Notifications.Email { public class Email : NotificationBase { - private readonly EmailProvider _smtpProvider; + private readonly IEmailService _smtpProvider; - public Email(EmailProvider smtpProvider) + public Email(IEmailService smtpProvider) { _smtpProvider = smtpProvider; } @@ -17,6 +17,11 @@ namespace NzbDrone.Core.Notifications.Email get { return "Email"; } } + public override string ImplementationName + { + get { return "Email"; } + } + public override void OnGrab(string message) { const string subject = "NzbDrone [TV] - Grabbed"; diff --git a/NzbDrone.Core/Notifications/Email/EmailProvider.cs b/NzbDrone.Core/Notifications/Email/EmailService.cs similarity index 61% rename from NzbDrone.Core/Notifications/Email/EmailProvider.cs rename to NzbDrone.Core/Notifications/Email/EmailService.cs index 744ac1c7b..bd9b255f0 100644 --- a/NzbDrone.Core/Notifications/Email/EmailProvider.cs +++ b/NzbDrone.Core/Notifications/Email/EmailService.cs @@ -2,19 +2,26 @@ using System.Net; using System.Net.Mail; using NLog; +using NzbDrone.Common.Messaging; +using Omu.ValueInjecter; namespace NzbDrone.Core.Notifications.Email { - public class EmailProvider + public interface IEmailService + { + void SendEmail(EmailSettings settings, string subject, string body, bool htmlBody = false); + } + + public class EmailService : IEmailService, IExecute { private readonly Logger _logger; - public EmailProvider(Logger logger) + public EmailService(Logger logger) { _logger = logger; } - public virtual void SendEmail(EmailSettings settings, string subject, string body, bool htmlBody = false) + public void SendEmail(EmailSettings settings, string subject, string body, bool htmlBody = false) { var email = new MailMessage(); email.From = new MailAddress(settings.From); @@ -32,7 +39,7 @@ namespace NzbDrone.Core.Notifications.Email try { - Send(email, settings.Server, settings.Port, settings.UseSsl, credentials); + Send(email, settings.Server, settings.Port, settings.Ssl, credentials); } catch(Exception ex) { @@ -41,7 +48,7 @@ namespace NzbDrone.Core.Notifications.Email } } - public virtual void Send(MailMessage email, string server, int port, bool ssl, NetworkCredential credentials) + private void Send(MailMessage email, string server, int port, bool ssl, NetworkCredential credentials) { try { @@ -60,5 +67,15 @@ namespace NzbDrone.Core.Notifications.Email throw; } } + + public void Execute(TestEmailCommand message) + { + var settings = new EmailSettings(); + settings.InjectFrom(message); + + var body = "Success! You have properly configured your email notification settings"; + + SendEmail(settings, "NzbDrone - Test Notification", body); + } } } diff --git a/NzbDrone.Core/Notifications/Email/EmailSettings.cs b/NzbDrone.Core/Notifications/Email/EmailSettings.cs index 21a58eeea..e991265d7 100644 --- a/NzbDrone.Core/Notifications/Email/EmailSettings.cs +++ b/NzbDrone.Core/Notifications/Email/EmailSettings.cs @@ -11,16 +11,16 @@ namespace NzbDrone.Core.Notifications.Email [FieldDefinition(1, Label = "Port")] public Int32 Port { get; set; } - [FieldDefinition(2, Label = "Use SSL", HelpText = "Does your Email server use SSL?")] - public Boolean UseSsl { get; set; } + [FieldDefinition(2, Label = "SSL", Type = FieldType.Checkbox)] + public Boolean Ssl { get; set; } [FieldDefinition(3, Label = "Username")] public String Username { get; set; } - [FieldDefinition(4, Label = "Password")] + [FieldDefinition(4, Label = "Password", Type = FieldType.Password)] public String Password { get; set; } - [FieldDefinition(5, Label = "Sender Address")] + [FieldDefinition(5, Label = "From Address")] public String From { get; set; } [FieldDefinition(6, Label = "Recipient Address")] diff --git a/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs b/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs new file mode 100644 index 000000000..258884788 --- /dev/null +++ b/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs @@ -0,0 +1,15 @@ +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.Notifications.Email +{ + public class TestEmailCommand : ICommand + { + public string Server { get; set; } + public int Port { get; set; } + public bool Ssl { get; set; } + public string Username { get; set; } + public string Password { get; set; } + public string From { get; set; } + public string To { get; set; } + } +} diff --git a/NzbDrone.Core/Notifications/Growl/Growl.cs b/NzbDrone.Core/Notifications/Growl/Growl.cs index 5420ceabd..62609104e 100644 --- a/NzbDrone.Core/Notifications/Growl/Growl.cs +++ b/NzbDrone.Core/Notifications/Growl/Growl.cs @@ -7,9 +7,9 @@ namespace NzbDrone.Core.Notifications.Growl { public class Growl : NotificationBase { - private readonly GrowlProvider _growlProvider; + private readonly IGrowlService _growlProvider; - public Growl(GrowlProvider growlProvider) + public Growl(IGrowlService growlProvider) { _growlProvider = growlProvider; } @@ -19,6 +19,11 @@ namespace NzbDrone.Core.Notifications.Growl get { return "Growl"; } } + public override string ImplementationName + { + get { return "Growl"; } + } + public override void OnGrab(string message) { const string title = "Episode Grabbed"; diff --git a/NzbDrone.Core/Notifications/Growl/GrowlProvider.cs b/NzbDrone.Core/Notifications/Growl/GrowlService.cs similarity index 64% rename from NzbDrone.Core/Notifications/Growl/GrowlProvider.cs rename to NzbDrone.Core/Notifications/Growl/GrowlService.cs index 4d81e1f75..deed4b369 100644 --- a/NzbDrone.Core/Notifications/Growl/GrowlProvider.cs +++ b/NzbDrone.Core/Notifications/Growl/GrowlService.cs @@ -3,43 +3,33 @@ using System.Collections.Generic; using System.Linq; using Growl.Connector; using NLog; +using NzbDrone.Common.Messaging; using GrowlNotification = Growl.Connector.Notification; namespace NzbDrone.Core.Notifications.Growl { - public class GrowlProvider + public interface IGrowlService + { + void SendNotification(string title, string message, string notificationTypeName, string hostname, int port, string password); + } + + public class GrowlService : IGrowlService, IExecute { private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private readonly Application _growlApplication = new Application("NzbDrone"); private GrowlConnector _growlConnector; - private List _notificationTypes; + private readonly List _notificationTypes; - public GrowlProvider() + public GrowlService() { _notificationTypes = GetNotificationTypes(); _growlApplication.Icon = "https://github.com/NzbDrone/NzbDrone/raw/master/NzbDrone.Core/NzbDrone.jpg"; } - public virtual void Register(string hostname, int port, string password) - { - Logger.Trace("Registering NzbDrone with Growl host: {0}:{1}", hostname, port); - _growlConnector = new GrowlConnector(password, hostname, port); - _growlConnector.Register(_growlApplication, _notificationTypes.ToArray()); - } - - public virtual void TestNotification(string hostname, int port, string password) - { - const string title = "Test Notification"; - const string message = "This is a test message from NzbDrone"; - - SendNotification(title, message, "TEST", hostname, port, password); - } - - public virtual void SendNotification(string title, string message, string notificationTypeName, string hostname, int port, string password) + public void SendNotification(string title, string message, string notificationTypeName, string hostname, int port, string password) { var notificationType = _notificationTypes.Single(n => n.Name == notificationTypeName); - var notification = new GrowlNotification("NzbDrone", notificationType.Name, DateTime.Now.Ticks.ToString(), title, message); _growlConnector = new GrowlConnector(password, hostname, port); @@ -48,6 +38,13 @@ namespace NzbDrone.Core.Notifications.Growl _growlConnector.Notify(notification); } + private void Register(string host, int port, string password) + { + Logger.Trace("Registering NzbDrone with Growl host: {0}:{1}", host, port); + _growlConnector = new GrowlConnector(password, host, port); + _growlConnector.Register(_growlApplication, _notificationTypes.ToArray()); + } + private List GetNotificationTypes() { var notificationTypes = new List(); @@ -57,5 +54,15 @@ namespace NzbDrone.Core.Notifications.Growl return notificationTypes; } + + public void Execute(TestGrowlCommand message) + { + Register(message.Host, message.Port, message.Password); + + const string title = "Test Notification"; + const string body = "This is a test message from NzbDrone"; + + SendNotification(title, body, "TEST", message.Host, message.Port, message.Password); + } } } diff --git a/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs b/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs new file mode 100644 index 000000000..35890fff9 --- /dev/null +++ b/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs @@ -0,0 +1,11 @@ +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.Notifications.Growl +{ + public class TestGrowlCommand : ICommand + { + public string Host { get; set; } + public int Port { get; set; } + public string Password { get; set; } + } +} diff --git a/NzbDrone.Core/Notifications/INotification.cs b/NzbDrone.Core/Notifications/INotification.cs index 2280aa830..808d84135 100644 --- a/NzbDrone.Core/Notifications/INotification.cs +++ b/NzbDrone.Core/Notifications/INotification.cs @@ -9,6 +9,7 @@ namespace NzbDrone.Core.Notifications public interface INotification { string Name { get; } + string ImplementationName { get; } NotificationDefinition InstanceDefinition { get; set; } diff --git a/NzbDrone.Core/Notifications/Notification.cs b/NzbDrone.Core/Notifications/Notification.cs index 3f2aa09bb..bbd5cde06 100644 --- a/NzbDrone.Core/Notifications/Notification.cs +++ b/NzbDrone.Core/Notifications/Notification.cs @@ -9,6 +9,7 @@ namespace NzbDrone.Core.Notifications { public int Id { get; set; } public string Name { get; set; } + public string ImplementationName { get; set; } public bool OnGrab { get; set; } public bool OnDownload { get; set; } public INotifcationSettings Settings { get; set; } diff --git a/NzbDrone.Core/Notifications/NotificationBase.cs b/NzbDrone.Core/Notifications/NotificationBase.cs index 264716fe1..d8412a02a 100644 --- a/NzbDrone.Core/Notifications/NotificationBase.cs +++ b/NzbDrone.Core/Notifications/NotificationBase.cs @@ -8,6 +8,7 @@ namespace NzbDrone.Core.Notifications public abstract class NotificationBase : INotification where TSetting : class, INotifcationSettings, new() { public abstract string Name { get; } + public abstract string ImplementationName { get; } public NotificationDefinition InstanceDefinition { get; set; } diff --git a/NzbDrone.Core/Notifications/NotificationService.cs b/NzbDrone.Core/Notifications/NotificationService.cs index ba31661ac..c7b894f24 100644 --- a/NzbDrone.Core/Notifications/NotificationService.cs +++ b/NzbDrone.Core/Notifications/NotificationService.cs @@ -69,7 +69,7 @@ namespace NzbDrone.Core.Notifications var newNotification = new Notification(); newNotification.Instance = (INotification)_container.Resolve(type); newNotification.Id = i; - newNotification.Name = notification.Name; + newNotification.ImplementationName = notification.ImplementationName; var instanceType = newNotification.Instance.GetType(); var baseGenArgs = instanceType.BaseType.GetGenericArguments(); @@ -120,6 +120,7 @@ namespace NzbDrone.Core.Notifications notification.Instance = GetInstance(definition); notification.Name = definition.Name; notification.Implementation = definition.Implementation; + notification.ImplementationName = notification.Instance.ImplementationName; notification.Settings = ((dynamic)notification.Instance).ImportSettingsFromJson(definition.Settings); return notification; diff --git a/NzbDrone.Core/Notifications/Plex/PlexClient.cs b/NzbDrone.Core/Notifications/Plex/PlexClient.cs index d9089e2d5..846f1dbf0 100644 --- a/NzbDrone.Core/Notifications/Plex/PlexClient.cs +++ b/NzbDrone.Core/Notifications/Plex/PlexClient.cs @@ -6,9 +6,9 @@ namespace NzbDrone.Core.Notifications.Plex { public class PlexClient : NotificationBase { - private readonly PlexProvider _plexProvider; + private readonly IPlexService _plexProvider; - public PlexClient(PlexProvider plexProvider) + public PlexClient(IPlexService plexProvider) { _plexProvider = plexProvider; } @@ -18,6 +18,11 @@ namespace NzbDrone.Core.Notifications.Plex get { return "Plex Client"; } } + public override string ImplementationName + { + get { return "Plex Client"; } + } + public override void OnGrab(string message) { const string header = "NzbDrone [TV] - Grabbed"; diff --git a/NzbDrone.Core/Notifications/Plex/PlexProvider.cs b/NzbDrone.Core/Notifications/Plex/PlexProvider.cs deleted file mode 100644 index e9fedfb38..000000000 --- a/NzbDrone.Core/Notifications/Plex/PlexProvider.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Xml.Linq; -using NLog; -using NzbDrone.Common; -using NzbDrone.Core.Configuration; - -namespace NzbDrone.Core.Notifications.Plex -{ - public class PlexProvider - { - private readonly IHttpProvider _httpProvider; - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); - - public PlexProvider(IHttpProvider httpProvider) - { - _httpProvider = httpProvider; - } - - public virtual void Notify(PlexClientSettings settings, string header, string message) - { - try - { - var command = String.Format("ExecBuiltIn(Notification({0}, {1}))", header, message); - SendCommand(settings.Host, settings.Port, command, settings.Username, settings.Password); - } - catch(Exception ex) - { - logger.WarnException("Failed to send notification to Plex Client: " + settings.Host, ex); - } - } - - public virtual void UpdateLibrary(string host) - { - try - { - logger.Trace("Sending Update Request to Plex Server"); - var sections = GetSectionKeys(host); - sections.ForEach(s => UpdateSection(host, s)); - } - - catch(Exception ex) - { - logger.WarnException("Failed to Update Plex host: " + host, ex); - throw; - } - } - - public List GetSectionKeys(string host) - { - logger.Trace("Getting sections from Plex host: {0}", host); - var url = String.Format("http://{0}/library/sections", host); - var xmlStream = _httpProvider.DownloadStream(url, null); - var xDoc = XDocument.Load(xmlStream); - var mediaContainer = xDoc.Descendants("MediaContainer").FirstOrDefault(); - var directories = mediaContainer.Descendants("Directory").Where(x => x.Attribute("type").Value == "show"); - - return directories.Select(d => Int32.Parse(d.Attribute("key").Value)).ToList(); - } - - public void UpdateSection(string host, int key) - { - logger.Trace("Updating Plex host: {0}, Section: {1}", host, key); - var url = String.Format("http://{0}/library/sections/{1}/refresh", host, key); - _httpProvider.DownloadString(url); - } - - public virtual string SendCommand(string host, int port, string command, string username, string password) - { - var url = String.Format("http://{0}:{1}/xbmcCmds/xbmcHttp?command={2}", host, port, command); - - if (!String.IsNullOrEmpty(username)) - { - return _httpProvider.DownloadString(url, username, password); - } - - return _httpProvider.DownloadString(url); - } - - public virtual void TestNotification(string host, int port, string username, string password) - { - logger.Trace("Sending Test Notifcation to XBMC Host: {0}", host); - var command = String.Format("ExecBuiltIn(Notification({0}, {1}))", "Test Notification", "Success! Notifications are setup correctly"); - SendCommand(host, port, command, username, password); - } - } -} diff --git a/NzbDrone.Core/Notifications/Plex/PlexServer.cs b/NzbDrone.Core/Notifications/Plex/PlexServer.cs index f92271879..9b12099f5 100644 --- a/NzbDrone.Core/Notifications/Plex/PlexServer.cs +++ b/NzbDrone.Core/Notifications/Plex/PlexServer.cs @@ -6,9 +6,9 @@ namespace NzbDrone.Core.Notifications.Plex { public class PlexServer : NotificationBase { - private readonly PlexProvider _plexProvider; + private readonly IPlexService _plexProvider; - public PlexServer(PlexProvider plexProvider) + public PlexServer(IPlexService plexProvider) { _plexProvider = plexProvider; } @@ -18,6 +18,11 @@ namespace NzbDrone.Core.Notifications.Plex get { return "Plex Server"; } } + public override string ImplementationName + { + get { return "Plex Server"; } + } + public override void OnGrab(string message) { } @@ -36,7 +41,7 @@ namespace NzbDrone.Core.Notifications.Plex { if (Settings.UpdateLibrary) { - _plexProvider.UpdateLibrary(Settings.Host); + _plexProvider.UpdateLibrary(Settings); } } } diff --git a/NzbDrone.Core/Notifications/Plex/PlexServerSettings.cs b/NzbDrone.Core/Notifications/Plex/PlexServerSettings.cs index 51cccfdc5..de0e8bbfa 100644 --- a/NzbDrone.Core/Notifications/Plex/PlexServerSettings.cs +++ b/NzbDrone.Core/Notifications/Plex/PlexServerSettings.cs @@ -11,7 +11,10 @@ namespace NzbDrone.Core.Notifications.Plex [FieldDefinition(0, Label = "Host", HelpText = "Plex Server Host (IP or Hostname)")] public String Host { get; set; } - [FieldDefinition(1, Label = "Update Library", HelpText = "Update Library on Download/Rename")] + [FieldDefinition(1, Label = "Port")] + public Int32 Port { get; set; } + + [FieldDefinition(2, Label = "Update Library")] public Boolean UpdateLibrary { get; set; } public bool IsValid diff --git a/NzbDrone.Core/Notifications/Plex/PlexService.cs b/NzbDrone.Core/Notifications/Plex/PlexService.cs new file mode 100644 index 000000000..59158b496 --- /dev/null +++ b/NzbDrone.Core/Notifications/Plex/PlexService.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using NLog; +using NzbDrone.Common; +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Configuration; + +namespace NzbDrone.Core.Notifications.Plex +{ + public interface IPlexService + { + void Notify(PlexClientSettings settings, string header, string message); + void UpdateLibrary(PlexServerSettings settings); + } + + public class PlexService : IPlexService, IExecute, IExecute + { + private readonly IHttpProvider _httpProvider; + private readonly Logger _logger; + + public PlexService(IHttpProvider httpProvider, Logger logger) + { + _httpProvider = httpProvider; + _logger = logger; + } + + public void Notify(PlexClientSettings settings, string header, string message) + { + try + { + var command = String.Format("ExecBuiltIn(Notification({0}, {1}))", header, message); + SendCommand(settings.Host, settings.Port, command, settings.Username, settings.Password); + } + catch(Exception ex) + { + _logger.WarnException("Failed to send notification to Plex Client: " + settings.Host, ex); + } + } + + public void UpdateLibrary(PlexServerSettings settings) + { + try + { + _logger.Trace("Sending Update Request to Plex Server"); + var sections = GetSectionKeys(settings); + sections.ForEach(s => UpdateSection(settings, s)); + } + + catch(Exception ex) + { + _logger.WarnException("Failed to Update Plex host: " + settings.Host, ex); + throw; + } + } + + public List GetSectionKeys(PlexServerSettings settings) + { + _logger.Trace("Getting sections from Plex host: {0}", settings.Host); + var url = String.Format("http://{0}:{1}/library/sections", settings.Host, settings.Port); + var xmlStream = _httpProvider.DownloadStream(url, null); + var xDoc = XDocument.Load(xmlStream); + var mediaContainer = xDoc.Descendants("MediaContainer").FirstOrDefault(); + var directories = mediaContainer.Descendants("Directory").Where(x => x.Attribute("type").Value == "show"); + + return directories.Select(d => Int32.Parse(d.Attribute("key").Value)).ToList(); + } + + public void UpdateSection(PlexServerSettings settings, int key) + { + _logger.Trace("Updating Plex host: {0}, Section: {1}", settings.Host, key); + var url = String.Format("http://{0}:{1}/library/sections/{2}/refresh", settings.Host, settings.Port, key); + _httpProvider.DownloadString(url); + } + + public string SendCommand(string host, int port, string command, string username, string password) + { + var url = String.Format("http://{0}:{1}/xbmcCmds/xbmcHttp?command={2}", host, port, command); + + if (!String.IsNullOrEmpty(username)) + { + return _httpProvider.DownloadString(url, username, password); + } + + return _httpProvider.DownloadString(url); + } + + public void Execute(TestPlexClientCommand message) + { + _logger.Trace("Sending Test Notifcation to Plex Client: {0}", message.Host); + var command = String.Format("ExecBuiltIn(Notification({0}, {1}))", "Test Notification", "Success! Notifications are setup correctly"); + var result = SendCommand(message.Host, message.Port, command, message.Username, message.Password); + + if (String.IsNullOrWhiteSpace(result) || + result.IndexOf("error", StringComparison.InvariantCultureIgnoreCase) > -1) + { + throw new Exception("Unable to connect to Plex Client"); + } + } + + public void Execute(TestPlexServerCommand message) + { + if (!GetSectionKeys(new PlexServerSettings {Host = message.Host, Port = message.Port}).Any()) + { + throw new Exception("Unable to connect to Plex Server"); + } + } + } +} diff --git a/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs b/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs new file mode 100644 index 000000000..6df162ab4 --- /dev/null +++ b/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs @@ -0,0 +1,12 @@ +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.Notifications.Plex +{ + public class TestPlexClientCommand : ICommand + { + public string Host { get; set; } + public int Port { get; set; } + public string Username { get; set; } + public string Password { get; set; } + } +} diff --git a/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs b/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs new file mode 100644 index 000000000..49089afea --- /dev/null +++ b/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs @@ -0,0 +1,10 @@ +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.Notifications.Plex +{ + public class TestPlexServerCommand : ICommand + { + public string Host { get; set; } + public int Port { get; set; } + } +} diff --git a/NzbDrone.Core/Notifications/Prowl/InvalidApiKeyException.cs b/NzbDrone.Core/Notifications/Prowl/InvalidApiKeyException.cs new file mode 100644 index 000000000..90d9ea789 --- /dev/null +++ b/NzbDrone.Core/Notifications/Prowl/InvalidApiKeyException.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Notifications.Prowl +{ + public class InvalidApiKeyException : Exception + { + public InvalidApiKeyException() + { + } + + public InvalidApiKeyException(string message) : base(message) + { + } + } +} diff --git a/NzbDrone.Core/Notifications/Prowl/Prowl.cs b/NzbDrone.Core/Notifications/Prowl/Prowl.cs index a0d96d723..207104b6e 100644 --- a/NzbDrone.Core/Notifications/Prowl/Prowl.cs +++ b/NzbDrone.Core/Notifications/Prowl/Prowl.cs @@ -5,9 +5,9 @@ namespace NzbDrone.Core.Notifications.Prowl { public class Prowl : NotificationBase { - private readonly ProwlProvider _prowlProvider; + private readonly IProwlService _prowlProvider; - public Prowl(ProwlProvider prowlProvider) + public Prowl(IProwlService prowlProvider) { _prowlProvider = prowlProvider; } @@ -17,6 +17,11 @@ namespace NzbDrone.Core.Notifications.Prowl get { return "Prowl"; } } + public override string ImplementationName + { + get { return "Prowl"; } + } + public override void OnGrab(string message) { const string title = "Episode Grabbed"; diff --git a/NzbDrone.Core/Notifications/Prowl/ProwlProvider.cs b/NzbDrone.Core/Notifications/Prowl/ProwlProvider.cs deleted file mode 100644 index 4bbf9a220..000000000 --- a/NzbDrone.Core/Notifications/Prowl/ProwlProvider.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System; -using NLog; -using Prowlin; - -namespace NzbDrone.Core.Notifications.Prowl -{ - public class ProwlProvider - { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - - public virtual bool Verify(string apiKey) - { - try - { - var verificationRequest = new Verification(); - verificationRequest.ApiKey = apiKey; - - var client = new ProwlClient(); - - Logger.Trace("Verifying API Key: {0}", apiKey); - - var verificationResult = client.SendVerification(verificationRequest); - if (String.IsNullOrWhiteSpace(verificationResult.ErrorMessage) && verificationResult.ResultCode == "200") - return true; - } - - catch (Exception ex) - { - Logger.TraceException(ex.Message, ex); - Logger.Warn("Invalid API Key: {0}", apiKey); - } - - return false; - } - - public virtual bool SendNotification(string title, string message, string apiKey, NotificationPriority priority = NotificationPriority.Normal, string url = null) - { - try - { - var notification = new Prowlin.Notification - { - Application = "NzbDrone", - Description = message, - Event = title, - Priority = priority, - Url = url - }; - - notification.AddApiKey(apiKey.Trim()); - - var client = new ProwlClient(); - - Logger.Trace("Sending Prowl Notification"); - - var notificationResult = client.SendNotification(notification); - - if (String.IsNullOrWhiteSpace(notificationResult.ErrorMessage)) - return true; - } - - catch (Exception ex) - { - Logger.TraceException(ex.Message, ex); - Logger.Warn("Invalid API Key: {0}", apiKey); - } - - return false; - } - - public virtual void TestNotification(string apiKeys) - { - const string title = "Test Notification"; - const string message = "This is a test message from NzbDrone"; - - SendNotification(title, message, apiKeys); - } - } -} diff --git a/NzbDrone.Core/Notifications/Prowl/ProwlService.cs b/NzbDrone.Core/Notifications/Prowl/ProwlService.cs new file mode 100644 index 000000000..bf850215f --- /dev/null +++ b/NzbDrone.Core/Notifications/Prowl/ProwlService.cs @@ -0,0 +1,93 @@ +using System; +using NLog; +using NzbDrone.Common.Messaging; +using Prowlin; + +namespace NzbDrone.Core.Notifications.Prowl +{ + public interface IProwlService + { + void SendNotification(string title, string message, string apiKey, NotificationPriority priority = NotificationPriority.Normal, string url = null); + } + + public class ProwlService : IProwlService, IExecute + { + private readonly Logger _logger; + + public ProwlService(Logger logger) + { + _logger = logger; + } + + public void SendNotification(string title, string message, string apiKey, NotificationPriority priority = NotificationPriority.Normal, string url = null) + { + try + { + var notification = new Prowlin.Notification + { + Application = "NzbDrone", + Description = message, + Event = title, + Priority = priority, + Url = url + }; + + notification.AddApiKey(apiKey.Trim()); + + var client = new ProwlClient(); + + _logger.Trace("Sending Prowl Notification"); + + var notificationResult = client.SendNotification(notification); + + if (!String.IsNullOrWhiteSpace(notificationResult.ErrorMessage)) + { + throw new InvalidApiKeyException("API Key: " + apiKey + " is invalid"); + } + } + + catch (Exception ex) + { + _logger.TraceException(ex.Message, ex); + _logger.Warn("Invalid API Key: {0}", apiKey); + } + } + + public void Verify(string apiKey) + { + try + { + var verificationRequest = new Verification(); + verificationRequest.ApiKey = apiKey; + + var client = new ProwlClient(); + + _logger.Trace("Verifying API Key: {0}", apiKey); + + var verificationResult = client.SendVerification(verificationRequest); + if (!String.IsNullOrWhiteSpace(verificationResult.ErrorMessage) && + verificationResult.ResultCode != "200") + { + throw new InvalidApiKeyException("API Key: " + apiKey + " is invalid"); + } + } + + catch (Exception ex) + { + _logger.TraceException(ex.Message, ex); + _logger.Warn("Invalid API Key: {0}", apiKey); + throw new InvalidApiKeyException("API Key: " + apiKey + " is invalid"); + } + } + + public void Execute(TestProwlCommand message) + { + Verify(message.ApiKey); + + const string title = "Test Notification"; + const string body = "This is a test message from NzbDrone"; + + SendNotification(title, body, message.ApiKey); + } + } +} diff --git a/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs b/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs new file mode 100644 index 000000000..e58bf5a9c --- /dev/null +++ b/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs @@ -0,0 +1,10 @@ +using NzbDrone.Common.Messaging; + +namespace NzbDrone.Core.Notifications.Prowl +{ + public class TestProwlCommand : ICommand + { + public string ApiKey { get; set; } + public int Priority { get; set; } + } +} diff --git a/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs b/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs index 844adf0b1..0ef599573 100644 --- a/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs +++ b/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs @@ -17,6 +17,11 @@ namespace NzbDrone.Core.Notifications.Xbmc get { return "XBMC"; } } + public override string ImplementationName + { + get { return "XBMC"; } + } + public override void OnGrab(string message) { const string header = "NzbDrone [TV] - Grabbed"; diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 146b7d15d..0b7c893f9 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -266,7 +266,9 @@ + + @@ -324,11 +326,15 @@ + + + + @@ -454,7 +460,7 @@ Code - + Code @@ -472,11 +478,11 @@ Code - + Code - + Code @@ -492,7 +498,7 @@ Code - + Code diff --git a/UI/Settings/Notifications/AddItemTemplate.html b/UI/Settings/Notifications/AddItemTemplate.html index 057f17b6a..9c5eb644f 100644 --- a/UI/Settings/Notifications/AddItemTemplate.html +++ b/UI/Settings/Notifications/AddItemTemplate.html @@ -1,7 +1,7 @@ 
- {{name}} + {{implementationName}}
\ No newline at end of file diff --git a/UI/Settings/Notifications/AddView.js b/UI/Settings/Notifications/AddView.js index 3a0212beb..dbfc83f00 100644 --- a/UI/Settings/Notifications/AddView.js +++ b/UI/Settings/Notifications/AddView.js @@ -20,7 +20,6 @@ define([ addNotification: function () { this.model.set('id', undefined); - this.model.set('name', ''); var view = new NzbDrone.Settings.Notifications.EditView({ model: this.model, notificationCollection: this.notificationCollection }); NzbDrone.modalRegion.show(view); } diff --git a/UI/Settings/Notifications/CollectionTemplate.html b/UI/Settings/Notifications/CollectionTemplate.html index b8dfb46cd..839167b1e 100644 --- a/UI/Settings/Notifications/CollectionTemplate.html +++ b/UI/Settings/Notifications/CollectionTemplate.html @@ -2,6 +2,7 @@ Name + Type On Grab On Download Controls diff --git a/UI/Settings/Notifications/EditTemplate.html b/UI/Settings/Notifications/EditTemplate.html index 595637c29..825ad7bf9 100644 --- a/UI/Settings/Notifications/EditTemplate.html +++ b/UI/Settings/Notifications/EditTemplate.html @@ -1,9 +1,9 @@