Notifications can be tested

Notification ImplementationType was added for showing in UI (Humanized/Title cased of Implementation)
pull/6/head
Mark McDowall 12 years ago
parent 1f4cf0034e
commit 8cac7ed1cd

@ -8,6 +8,7 @@ namespace NzbDrone.Api.Notifications
public class NotificationResource : RestResource public class NotificationResource : RestResource
{ {
public String Name { get; set; } public String Name { get; set; }
public String ImplementationName { get; set; }
public Boolean OnGrab { get; set; } public Boolean OnGrab { get; set; }
public Boolean OnDownload { get; set; } public Boolean OnDownload { get; set; }
public List<Field> Fields { get; set; } public List<Field> Fields { get; set; }

@ -42,8 +42,7 @@ namespace NzbDrone.Core.Test.NotificationTests
Mocker.GetMock<IHttpProvider>().Setup(s => s.DownloadStream("http://localhost:32400/library/sections", null)) Mocker.GetMock<IHttpProvider>().Setup(s => s.DownloadStream("http://localhost:32400/library/sections", null))
.Returns(stream); .Returns(stream);
var result = Mocker.Resolve<PlexService>().GetSectionKeys(new PlexServerSettings { Host = "localhost", Port = 32400 });
var result = Mocker.Resolve<PlexProvider>().GetSectionKeys("localhost:32400");
result.Should().HaveCount(1); result.Should().HaveCount(1);
@ -62,7 +61,7 @@ namespace NzbDrone.Core.Test.NotificationTests
.Returns(stream); .Returns(stream);
var result = Mocker.Resolve<PlexProvider>().GetSectionKeys("localhost:32400"); var result = Mocker.Resolve<PlexService>().GetSectionKeys(new PlexServerSettings { Host = "localhost", Port = 32400 });
result.Should().HaveCount(1); result.Should().HaveCount(1);
@ -80,8 +79,8 @@ namespace NzbDrone.Core.Test.NotificationTests
Mocker.GetMock<IHttpProvider>().Setup(s => s.DownloadStream("http://localhost:32400/library/sections", null)) Mocker.GetMock<IHttpProvider>().Setup(s => s.DownloadStream("http://localhost:32400/library/sections", null))
.Returns(stream); .Returns(stream);
var result = Mocker.Resolve<PlexProvider>().GetSectionKeys("localhost:32400"); var result = Mocker.Resolve<PlexService>().GetSectionKeys(new PlexServerSettings { Host = "localhost", Port = 32400 });
result.Should().HaveCount(2); result.Should().HaveCount(2);
@ -100,8 +99,8 @@ namespace NzbDrone.Core.Test.NotificationTests
Mocker.GetMock<IHttpProvider>().Setup(s => s.DownloadString("http://localhost:32400/library/sections/5/refresh")) Mocker.GetMock<IHttpProvider>().Setup(s => s.DownloadString("http://localhost:32400/library/sections/5/refresh"))
.Returns(response); .Returns(response);
Mocker.Resolve<PlexProvider>().UpdateSection("localhost:32400", 5); Mocker.Resolve<PlexService>().UpdateSection(new PlexServerSettings { Host = "localhost", Port = 32400 }, 5);
@ -121,7 +120,7 @@ namespace NzbDrone.Core.Test.NotificationTests
.Returns("ok"); .Returns("ok");
Mocker.Resolve<PlexProvider>().Notify(_clientSettings, header, message); Mocker.Resolve<PlexService>().Notify(_clientSettings, header, message);
fakeHttp.Verify(v => v.DownloadString(expectedUrl), Times.Once()); fakeHttp.Verify(v => v.DownloadString(expectedUrl), Times.Once());
@ -142,7 +141,7 @@ namespace NzbDrone.Core.Test.NotificationTests
.Returns("ok"); .Returns("ok");
Mocker.Resolve<PlexProvider>().Notify(_clientSettings, header, message); Mocker.Resolve<PlexService>().Notify(_clientSettings, header, message);
fakeHttp.Verify(v => v.DownloadString(expectedUrl, "plex", "plex"), Times.Once()); fakeHttp.Verify(v => v.DownloadString(expectedUrl, "plex", "plex"), Times.Once());

@ -9,130 +9,40 @@ namespace NzbDrone.Core.Test.NotificationTests
{ {
[Explicit] [Explicit]
[TestFixture] [TestFixture]
public class ProwlProviderTest : CoreTest public class ProwlProviderTest : CoreTest<ProwlService>
{ {
private const string _apiKey = "c3bdc0f48168f72d546cc6872925b160f5cbffc1"; private const string _apiKey = "66e9f688b512152eb2688f0486ae542c76e564a2";
private const string _apiKey2 = "46a710a46b111b0b8633819b0d8a1e0272a3affa";
private const string _badApiKey = "1234567890abcdefghijklmnopqrstuvwxyz1234"; private const string _badApiKey = "1234567890abcdefghijklmnopqrstuvwxyz1234";
[Test] [Test]
public void Verify_should_return_true_for_a_valid_apiKey() public void Verify_should_not_throw_for_a_valid_apiKey()
{ {
Subject.Verify(_apiKey);
ExceptionVerification.ExpectedWarns(0);
var result = Mocker.Resolve<ProwlProvider>().Verify(_apiKey);
result.Should().BeTrue();
} }
[Test] [Test]
public void Verify_should_return_false_for_an_invalid_apiKey() public void Verify_should_throw_for_an_invalid_apiKey()
{ {
Assert.Throws<InvalidApiKeyException>(() => Subject.Verify(_badApiKey));
var result = Mocker.Resolve<ProwlProvider>().Verify(_badApiKey);
ExceptionVerification.ExpectedWarns(1); ExceptionVerification.ExpectedWarns(1);
result.Should().BeFalse();
} }
[Test] [Test]
public void SendNotification_should_return_true_for_a_valid_apiKey() public void SendNotification_should_not_throw_for_a_valid_apiKey()
{ {
Subject.SendNotification("NzbDrone Test", "This is a test message from NzbDrone", _apiKey);
ExceptionVerification.ExpectedWarns(0);
var result = Mocker.Resolve<ProwlProvider>().SendNotification("NzbDrone Test", "This is a test message from NzbDrone", _apiKey);
result.Should().BeTrue();
} }
[Test] [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<ProwlProvider>().SendNotification("NzbDrone Test", "This is a test message from NzbDrone", _badApiKey);
ExceptionVerification.ExpectedWarns(1); ExceptionVerification.ExpectedWarns(1);
result.Should().BeFalse();
}
[Test]
public void SendNotification_should_alert_with_high_priority()
{
var result = Mocker.Resolve<ProwlProvider>().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<ProwlProvider>().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<ProwlProvider>().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<ProwlProvider>().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<ProwlProvider>().SendNotification("NzbDrone Test", "This is a test message from NzbDrone", _apiKey + ", " + _badApiKey);
result.Should().BeTrue();
} }
} }
} }

@ -185,7 +185,6 @@
<Compile Include="DecisionEngineTests\QualityUpgradableSpecificationFixture.cs" /> <Compile Include="DecisionEngineTests\QualityUpgradableSpecificationFixture.cs" />
<Compile Include="ProviderTests\DiskProviderTests\FreeDiskSpaceTest.cs" /> <Compile Include="ProviderTests\DiskProviderTests\FreeDiskSpaceTest.cs" />
<Compile Include="NotificationTests\ProwlProviderTest.cs" /> <Compile Include="NotificationTests\ProwlProviderTest.cs" />
<Compile Include="NotificationTests\GrowlProviderTest.cs" />
<Compile Include="ProviderTests\DiskProviderTests\ExtractArchiveFixture.cs" /> <Compile Include="ProviderTests\DiskProviderTests\ExtractArchiveFixture.cs" />
<Compile Include="ProviderTests\PostDownloadProviderTests\DropFolderImportServiceFixture.cs" /> <Compile Include="ProviderTests\PostDownloadProviderTests\DropFolderImportServiceFixture.cs" />
<Compile Include="SeriesStatsTests\SeriesStatisticsFixture.cs" /> <Compile Include="SeriesStatsTests\SeriesStatisticsFixture.cs" />

@ -5,9 +5,9 @@ namespace NzbDrone.Core.Notifications.Email
{ {
public class Email : NotificationBase<EmailSettings> public class Email : NotificationBase<EmailSettings>
{ {
private readonly EmailProvider _smtpProvider; private readonly IEmailService _smtpProvider;
public Email(EmailProvider smtpProvider) public Email(IEmailService smtpProvider)
{ {
_smtpProvider = smtpProvider; _smtpProvider = smtpProvider;
} }
@ -17,6 +17,11 @@ namespace NzbDrone.Core.Notifications.Email
get { return "Email"; } get { return "Email"; }
} }
public override string ImplementationName
{
get { return "Email"; }
}
public override void OnGrab(string message) public override void OnGrab(string message)
{ {
const string subject = "NzbDrone [TV] - Grabbed"; const string subject = "NzbDrone [TV] - Grabbed";

@ -2,19 +2,26 @@
using System.Net; using System.Net;
using System.Net.Mail; using System.Net.Mail;
using NLog; using NLog;
using NzbDrone.Common.Messaging;
using Omu.ValueInjecter;
namespace NzbDrone.Core.Notifications.Email 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<TestEmailCommand>
{ {
private readonly Logger _logger; private readonly Logger _logger;
public EmailProvider(Logger logger) public EmailService(Logger logger)
{ {
_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(); var email = new MailMessage();
email.From = new MailAddress(settings.From); email.From = new MailAddress(settings.From);
@ -32,7 +39,7 @@ namespace NzbDrone.Core.Notifications.Email
try try
{ {
Send(email, settings.Server, settings.Port, settings.UseSsl, credentials); Send(email, settings.Server, settings.Port, settings.Ssl, credentials);
} }
catch(Exception ex) 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 try
{ {
@ -60,5 +67,15 @@ namespace NzbDrone.Core.Notifications.Email
throw; 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);
}
} }
} }

@ -11,16 +11,16 @@ namespace NzbDrone.Core.Notifications.Email
[FieldDefinition(1, Label = "Port")] [FieldDefinition(1, Label = "Port")]
public Int32 Port { get; set; } public Int32 Port { get; set; }
[FieldDefinition(2, Label = "Use SSL", HelpText = "Does your Email server use SSL?")] [FieldDefinition(2, Label = "SSL", Type = FieldType.Checkbox)]
public Boolean UseSsl { get; set; } public Boolean Ssl { get; set; }
[FieldDefinition(3, Label = "Username")] [FieldDefinition(3, Label = "Username")]
public String Username { get; set; } public String Username { get; set; }
[FieldDefinition(4, Label = "Password")] [FieldDefinition(4, Label = "Password", Type = FieldType.Password)]
public String Password { get; set; } public String Password { get; set; }
[FieldDefinition(5, Label = "Sender Address")] [FieldDefinition(5, Label = "From Address")]
public String From { get; set; } public String From { get; set; }
[FieldDefinition(6, Label = "Recipient Address")] [FieldDefinition(6, Label = "Recipient Address")]

@ -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; }
}
}

@ -7,9 +7,9 @@ namespace NzbDrone.Core.Notifications.Growl
{ {
public class Growl : NotificationBase<GrowlSettings> public class Growl : NotificationBase<GrowlSettings>
{ {
private readonly GrowlProvider _growlProvider; private readonly IGrowlService _growlProvider;
public Growl(GrowlProvider growlProvider) public Growl(IGrowlService growlProvider)
{ {
_growlProvider = growlProvider; _growlProvider = growlProvider;
} }
@ -19,6 +19,11 @@ namespace NzbDrone.Core.Notifications.Growl
get { return "Growl"; } get { return "Growl"; }
} }
public override string ImplementationName
{
get { return "Growl"; }
}
public override void OnGrab(string message) public override void OnGrab(string message)
{ {
const string title = "Episode Grabbed"; const string title = "Episode Grabbed";

@ -3,43 +3,33 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Growl.Connector; using Growl.Connector;
using NLog; using NLog;
using NzbDrone.Common.Messaging;
using GrowlNotification = Growl.Connector.Notification; using GrowlNotification = Growl.Connector.Notification;
namespace NzbDrone.Core.Notifications.Growl 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<TestGrowlCommand>
{ {
private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly Application _growlApplication = new Application("NzbDrone"); private readonly Application _growlApplication = new Application("NzbDrone");
private GrowlConnector _growlConnector; private GrowlConnector _growlConnector;
private List<NotificationType> _notificationTypes; private readonly List<NotificationType> _notificationTypes;
public GrowlProvider() public GrowlService()
{ {
_notificationTypes = GetNotificationTypes(); _notificationTypes = GetNotificationTypes();
_growlApplication.Icon = "https://github.com/NzbDrone/NzbDrone/raw/master/NzbDrone.Core/NzbDrone.jpg"; _growlApplication.Icon = "https://github.com/NzbDrone/NzbDrone/raw/master/NzbDrone.Core/NzbDrone.jpg";
} }
public virtual void Register(string hostname, int port, string password) public void SendNotification(string title, string message, string notificationTypeName, 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)
{ {
var notificationType = _notificationTypes.Single(n => n.Name == notificationTypeName); 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("NzbDrone", notificationType.Name, DateTime.Now.Ticks.ToString(), title, message);
_growlConnector = new GrowlConnector(password, hostname, port); _growlConnector = new GrowlConnector(password, hostname, port);
@ -48,6 +38,13 @@ namespace NzbDrone.Core.Notifications.Growl
_growlConnector.Notify(notification); _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<NotificationType> GetNotificationTypes() private List<NotificationType> GetNotificationTypes()
{ {
var notificationTypes = new List<NotificationType>(); var notificationTypes = new List<NotificationType>();
@ -57,5 +54,15 @@ namespace NzbDrone.Core.Notifications.Growl
return notificationTypes; 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);
}
} }
} }

@ -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; }
}
}

@ -9,6 +9,7 @@ namespace NzbDrone.Core.Notifications
public interface INotification public interface INotification
{ {
string Name { get; } string Name { get; }
string ImplementationName { get; }
NotificationDefinition InstanceDefinition { get; set; } NotificationDefinition InstanceDefinition { get; set; }

@ -9,6 +9,7 @@ namespace NzbDrone.Core.Notifications
{ {
public int Id { get; set; } public int Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string ImplementationName { get; set; }
public bool OnGrab { get; set; } public bool OnGrab { get; set; }
public bool OnDownload { get; set; } public bool OnDownload { get; set; }
public INotifcationSettings Settings { get; set; } public INotifcationSettings Settings { get; set; }

@ -8,6 +8,7 @@ namespace NzbDrone.Core.Notifications
public abstract class NotificationBase<TSetting> : INotification where TSetting : class, INotifcationSettings, new() public abstract class NotificationBase<TSetting> : INotification where TSetting : class, INotifcationSettings, new()
{ {
public abstract string Name { get; } public abstract string Name { get; }
public abstract string ImplementationName { get; }
public NotificationDefinition InstanceDefinition { get; set; } public NotificationDefinition InstanceDefinition { get; set; }

@ -69,7 +69,7 @@ namespace NzbDrone.Core.Notifications
var newNotification = new Notification(); var newNotification = new Notification();
newNotification.Instance = (INotification)_container.Resolve(type); newNotification.Instance = (INotification)_container.Resolve(type);
newNotification.Id = i; newNotification.Id = i;
newNotification.Name = notification.Name; newNotification.ImplementationName = notification.ImplementationName;
var instanceType = newNotification.Instance.GetType(); var instanceType = newNotification.Instance.GetType();
var baseGenArgs = instanceType.BaseType.GetGenericArguments(); var baseGenArgs = instanceType.BaseType.GetGenericArguments();
@ -120,6 +120,7 @@ namespace NzbDrone.Core.Notifications
notification.Instance = GetInstance(definition); notification.Instance = GetInstance(definition);
notification.Name = definition.Name; notification.Name = definition.Name;
notification.Implementation = definition.Implementation; notification.Implementation = definition.Implementation;
notification.ImplementationName = notification.Instance.ImplementationName;
notification.Settings = ((dynamic)notification.Instance).ImportSettingsFromJson(definition.Settings); notification.Settings = ((dynamic)notification.Instance).ImportSettingsFromJson(definition.Settings);
return notification; return notification;

@ -6,9 +6,9 @@ namespace NzbDrone.Core.Notifications.Plex
{ {
public class PlexClient : NotificationBase<PlexClientSettings> public class PlexClient : NotificationBase<PlexClientSettings>
{ {
private readonly PlexProvider _plexProvider; private readonly IPlexService _plexProvider;
public PlexClient(PlexProvider plexProvider) public PlexClient(IPlexService plexProvider)
{ {
_plexProvider = plexProvider; _plexProvider = plexProvider;
} }
@ -18,6 +18,11 @@ namespace NzbDrone.Core.Notifications.Plex
get { return "Plex Client"; } get { return "Plex Client"; }
} }
public override string ImplementationName
{
get { return "Plex Client"; }
}
public override void OnGrab(string message) public override void OnGrab(string message)
{ {
const string header = "NzbDrone [TV] - Grabbed"; const string header = "NzbDrone [TV] - Grabbed";

@ -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<int> 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);
}
}
}

@ -6,9 +6,9 @@ namespace NzbDrone.Core.Notifications.Plex
{ {
public class PlexServer : NotificationBase<PlexServerSettings> public class PlexServer : NotificationBase<PlexServerSettings>
{ {
private readonly PlexProvider _plexProvider; private readonly IPlexService _plexProvider;
public PlexServer(PlexProvider plexProvider) public PlexServer(IPlexService plexProvider)
{ {
_plexProvider = plexProvider; _plexProvider = plexProvider;
} }
@ -18,6 +18,11 @@ namespace NzbDrone.Core.Notifications.Plex
get { return "Plex Server"; } get { return "Plex Server"; }
} }
public override string ImplementationName
{
get { return "Plex Server"; }
}
public override void OnGrab(string message) public override void OnGrab(string message)
{ {
} }
@ -36,7 +41,7 @@ namespace NzbDrone.Core.Notifications.Plex
{ {
if (Settings.UpdateLibrary) if (Settings.UpdateLibrary)
{ {
_plexProvider.UpdateLibrary(Settings.Host); _plexProvider.UpdateLibrary(Settings);
} }
} }
} }

@ -11,7 +11,10 @@ namespace NzbDrone.Core.Notifications.Plex
[FieldDefinition(0, Label = "Host", HelpText = "Plex Server Host (IP or Hostname)")] [FieldDefinition(0, Label = "Host", HelpText = "Plex Server Host (IP or Hostname)")]
public String Host { get; set; } 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 Boolean UpdateLibrary { get; set; }
public bool IsValid public bool IsValid

@ -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<TestPlexClientCommand>, IExecute<TestPlexServerCommand>
{
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<int> 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");
}
}
}
}

@ -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; }
}
}

@ -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; }
}
}

@ -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)
{
}
}
}

@ -5,9 +5,9 @@ namespace NzbDrone.Core.Notifications.Prowl
{ {
public class Prowl : NotificationBase<ProwlSettings> public class Prowl : NotificationBase<ProwlSettings>
{ {
private readonly ProwlProvider _prowlProvider; private readonly IProwlService _prowlProvider;
public Prowl(ProwlProvider prowlProvider) public Prowl(IProwlService prowlProvider)
{ {
_prowlProvider = prowlProvider; _prowlProvider = prowlProvider;
} }
@ -17,6 +17,11 @@ namespace NzbDrone.Core.Notifications.Prowl
get { return "Prowl"; } get { return "Prowl"; }
} }
public override string ImplementationName
{
get { return "Prowl"; }
}
public override void OnGrab(string message) public override void OnGrab(string message)
{ {
const string title = "Episode Grabbed"; const string title = "Episode Grabbed";

@ -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);
}
}
}

@ -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<TestProwlCommand>
{
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);
}
}
}

@ -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; }
}
}

@ -17,6 +17,11 @@ namespace NzbDrone.Core.Notifications.Xbmc
get { return "XBMC"; } get { return "XBMC"; }
} }
public override string ImplementationName
{
get { return "XBMC"; }
}
public override void OnGrab(string message) public override void OnGrab(string message)
{ {
const string header = "NzbDrone [TV] - Grabbed"; const string header = "NzbDrone [TV] - Grabbed";

@ -266,7 +266,9 @@
<Compile Include="MediaFiles\Events\EpisodeDownloadedEvent.cs" /> <Compile Include="MediaFiles\Events\EpisodeDownloadedEvent.cs" />
<Compile Include="Download\EpisodeGrabbedEvent.cs" /> <Compile Include="Download\EpisodeGrabbedEvent.cs" />
<Compile Include="Download\SeriesRenamedEvent.cs" /> <Compile Include="Download\SeriesRenamedEvent.cs" />
<Compile Include="Notifications\Email\TestEmailCommand.cs" />
<Compile Include="Notifications\Growl\GrowlSettings.cs" /> <Compile Include="Notifications\Growl\GrowlSettings.cs" />
<Compile Include="Notifications\Growl\TestGrowlCommand.cs" />
<Compile Include="Notifications\NotificationSettingsProvider.cs" /> <Compile Include="Notifications\NotificationSettingsProvider.cs" />
<Compile Include="Notifications\INotification.cs" /> <Compile Include="Notifications\INotification.cs" />
<Compile Include="Notifications\Notification.cs" /> <Compile Include="Notifications\Notification.cs" />
@ -324,11 +326,15 @@
<Compile Include="MetadataSource\Trakt\Season.cs" /> <Compile Include="MetadataSource\Trakt\Season.cs" />
<Compile Include="MetadataSource\Trakt\Show.cs" /> <Compile Include="MetadataSource\Trakt\Show.cs" />
<Compile Include="Notifications\INotifcationSettings.cs" /> <Compile Include="Notifications\INotifcationSettings.cs" />
<Compile Include="Notifications\Plex\TestPlexServerCommand.cs" />
<Compile Include="Notifications\Plex\PlexServer.cs" /> <Compile Include="Notifications\Plex\PlexServer.cs" />
<Compile Include="Notifications\Plex\PlexClientSettings.cs" /> <Compile Include="Notifications\Plex\PlexClientSettings.cs" />
<Compile Include="Notifications\Plex\PlexServerSettings.cs" /> <Compile Include="Notifications\Plex\PlexServerSettings.cs" />
<Compile Include="Notifications\Plex\TestPlexClientCommand.cs" />
<Compile Include="Notifications\Prowl\InvalidApiKeyException.cs" />
<Compile Include="Notifications\Prowl\ProwlSettings.cs" /> <Compile Include="Notifications\Prowl\ProwlSettings.cs" />
<Compile Include="Notifications\Email\EmailSettings.cs" /> <Compile Include="Notifications\Email\EmailSettings.cs" />
<Compile Include="Notifications\Prowl\TestProwlCommand.cs" />
<Compile Include="Notifications\Xbmc\HttpApiProvider.cs" /> <Compile Include="Notifications\Xbmc\HttpApiProvider.cs" />
<Compile Include="Notifications\Xbmc\IApiProvider.cs" /> <Compile Include="Notifications\Xbmc\IApiProvider.cs" />
<Compile Include="Notifications\Xbmc\InvalidXbmcVersionException.cs" /> <Compile Include="Notifications\Xbmc\InvalidXbmcVersionException.cs" />
@ -454,7 +460,7 @@
<Compile Include="Notifications\Xbmc\Xbmc.cs"> <Compile Include="Notifications\Xbmc\Xbmc.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="Notifications\Growl\GrowlProvider.cs"> <Compile Include="Notifications\Growl\GrowlService.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="History\HistoryService.cs"> <Compile Include="History\HistoryService.cs">
@ -472,11 +478,11 @@
<Compile Include="MediaFiles\MediaFileService.cs"> <Compile Include="MediaFiles\MediaFileService.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="Notifications\Plex\PlexProvider.cs" /> <Compile Include="Notifications\Plex\PlexService.cs" />
<Compile Include="MediaFiles\DownloadedEpisodesImportService.cs"> <Compile Include="MediaFiles\DownloadedEpisodesImportService.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="Notifications\Prowl\ProwlProvider.cs"> <Compile Include="Notifications\Prowl\ProwlService.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="Qualities\QualityProfileService.cs"> <Compile Include="Qualities\QualityProfileService.cs">
@ -492,7 +498,7 @@
<Compile Include="Tv\SeriesService.cs"> <Compile Include="Tv\SeriesService.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="Notifications\Email\EmailProvider.cs"> <Compile Include="Notifications\Email\EmailService.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="Notifications\Xbmc\XbmcService.cs"> <Compile Include="Notifications\Xbmc\XbmcService.cs">

@ -1,7 +1,7 @@
<div class="add-notification-item span3"> <div class="add-notification-item span3">
<div class="row"> <div class="row">
<div class="span3"> <div class="span3">
{{name}} {{implementationName}}
</div> </div>
</div> </div>
</div> </div>

@ -20,7 +20,6 @@ define([
addNotification: function () { addNotification: function () {
this.model.set('id', undefined); this.model.set('id', undefined);
this.model.set('name', '');
var view = new NzbDrone.Settings.Notifications.EditView({ model: this.model, notificationCollection: this.notificationCollection }); var view = new NzbDrone.Settings.Notifications.EditView({ model: this.model, notificationCollection: this.notificationCollection });
NzbDrone.modalRegion.show(view); NzbDrone.modalRegion.show(view);
} }

@ -2,6 +2,7 @@
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Type</th>
<th>On Grab</th> <th>On Grab</th>
<th>On Download</th> <th>On Download</th>
<th>Controls</th> <th>Controls</th>

@ -1,9 +1,9 @@
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
{{#if id}} {{#if id}}
<h3>Edit</h3> <h3>Edit - {{implementationName}}</h3>
{{else}} {{else}}
<h3>Add</h3> <h3>Add - {{implementationName}}</h3>
{{/if}} {{/if}}
</div> </div>
<div class="modal-body"> <div class="modal-body">

@ -1,4 +1,5 @@
<td name="name"></td> <td name="name"></td>
<td name="implementationName"></td>
<td name="onGrab"></td> <td name="onGrab"></td>
<td name="onDownload"></td> <td name="onDownload"></td>
<td name="cutoff.name"></td> <td name="cutoff.name"></td>

Loading…
Cancel
Save