Added first implimentation of the Notification Service #8

Added tests to cover the notification service
pull/23/head
tidusjar 9 years ago
parent aa5304b8dd
commit b9a886d5dc

@ -4,6 +4,7 @@
{ {
public string EmailHost { get; set; } public string EmailHost { get; set; }
public int EmailPort { get; set; } public int EmailPort { get; set; }
public bool Ssl { get; set; }
public string RecipientEmail { get; set; } public string RecipientEmail { get; set; }
public string EmailUsername { get; set; } public string EmailUsername { get; set; }
public string EmailPassword { get; set; } public string EmailPassword { get; set; }

@ -0,0 +1,116 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: NotificationServiceTests.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Moq;
using NUnit.Framework;
using PlexRequests.Services.Notification;
namespace PlexRequests.Services.Tests
{
[TestFixture]
public class NotificationServiceTests
{
[Test]
public void SubscribeNewNotifier()
{
var notificationMock = new Mock<INotification>();
notificationMock.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.Subscribe(notificationMock.Object);
Assert.That(NotificationService.Observers["Notification1"], Is.Not.Null);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(1));
}
[Test]
public void SubscribeExistingNotifier()
{
var notificationMock1 = new Mock<INotification>();
var notificationMock2 = new Mock<INotification>();
notificationMock1.SetupGet(x => x.NotificationName).Returns("Notification1");
notificationMock2.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.Subscribe(notificationMock1.Object);
Assert.That(NotificationService.Observers["Notification1"], Is.Not.Null);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(1));
NotificationService.Subscribe(notificationMock2.Object);
Assert.That(NotificationService.Observers["Notification1"], Is.Not.Null);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(1));
}
[Test]
public void UnSubscribeMissingNotifier()
{
var notificationMock = new Mock<INotification>();
notificationMock.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.UnSubscribe(notificationMock.Object);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(0));
}
[Test]
public void UnSubscribeNotifier()
{
var notificationMock = new Mock<INotification>();
notificationMock.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.Subscribe(notificationMock.Object);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(1));
NotificationService.UnSubscribe(notificationMock.Object);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(0));
}
[Test]
public void PublishWithNoObservers()
{
Assert.DoesNotThrow(
() =>
{ NotificationService.Publish(string.Empty, string.Empty); });
}
[Test]
public void PublishAllNotifiers()
{
var notificationMock1 = new Mock<INotification>();
var notificationMock2 = new Mock<INotification>();
notificationMock1.SetupGet(x => x.NotificationName).Returns("Notification1");
notificationMock2.SetupGet(x => x.NotificationName).Returns("Notification2");
NotificationService.Subscribe(notificationMock1.Object);
NotificationService.Subscribe(notificationMock2.Object);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(2));
NotificationService.Publish("a","b");
notificationMock1.Verify(x => x.Notify("a","b"), Times.Once);
notificationMock2.Verify(x => x.Notify("a","b"), Times.Once);
}
}
}

@ -55,6 +55,7 @@
<Otherwise /> <Otherwise />
</Choose> </Choose>
<ItemGroup> <ItemGroup>
<Compile Include="NotificationServiceTests.cs" />
<Compile Include="PlexAvailabilityCheckerTests.cs" /> <Compile Include="PlexAvailabilityCheckerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>

@ -0,0 +1,108 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: EmailMessageNotification.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Net;
using System.Net.Mail;
using NLog;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
namespace PlexRequests.Services.Notification
{
public class EmailMessageNotification : INotification
{
public EmailMessageNotification(ISettingsService<EmailNotificationSettings> settings)
{
EmailNotificationSettings = settings;
}
private static Logger Log = LogManager.GetCurrentClassLogger();
private ISettingsService<EmailNotificationSettings> EmailNotificationSettings { get; }
public string NotificationName => "EmailMessageNotification";
public bool Notify(string title, string requester)
{
var configuration = GetConfiguration();
if (!ValidateConfiguration(configuration))
{
return false;
}
var message = new MailMessage
{
IsBodyHtml = true,
To = { new MailAddress(configuration.RecipientEmail) },
Body = $"User {requester} has requested {title}!",
From = new MailAddress(configuration.EmailUsername),
Subject = $"New Request for {title}!"
};
try
{
using (var smtp = new SmtpClient(configuration.EmailHost, configuration.EmailPort))
{
smtp.Credentials = new NetworkCredential(configuration.EmailUsername, configuration.EmailPassword);
smtp.EnableSsl = configuration.Ssl;
smtp.Send(message);
return true;
}
}
catch (SmtpException smtp)
{
Log.Fatal(smtp);
}
catch (Exception e)
{
Log.Fatal(e);
}
return false;
}
private EmailNotificationSettings GetConfiguration()
{
var settings = EmailNotificationSettings.GetSettings();
return settings;
}
private bool ValidateConfiguration(EmailNotificationSettings settings)
{
if (!settings.Enabled)
{
return false;
}
if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.EmailUsername)
|| string.IsNullOrEmpty(settings.EmailPassword) || string.IsNullOrEmpty(settings.RecipientEmail)
|| string.IsNullOrEmpty(settings.EmailPort.ToString()))
{
return false;
}
return true;
}
}
}

@ -0,0 +1,46 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: INotification.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
namespace PlexRequests.Services.Notification
{
public interface INotification
{
/// <summary>
/// Gets the name of the notification.
/// </summary>
/// <value>
/// The name of the notification.
/// </value>
string NotificationName { get; }
/// <summary>
/// Notifies the specified title.
/// </summary>
/// <param name="title">The title.</param>
/// <param name="requester">The requester.</param>
/// <returns></returns>
bool Notify(string title, string requester);
}
}

@ -0,0 +1,78 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: NotificationService.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Collections.Generic;
using System.Threading;
namespace PlexRequests.Services.Notification
{
public static class NotificationService
{
public static Dictionary<string, INotification> Observers { get; }
static NotificationService()
{
Observers = new Dictionary<string, INotification>();
}
public static void Publish(string title, string requester)
{
foreach (var observer in Observers)
{
var notification = observer.Value;
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
notification.Notify(title, requester);
}).Start();
}
}
public static void Subscribe(INotification notification)
{
INotification notificationValue;
if (Observers.TryGetValue(notification.NotificationName, out notificationValue))
{
// Observer already exists
return;
}
Observers[notification.NotificationName] = notification;
}
public static void UnSubscribe(INotification notification)
{
INotification notificationValue;
if (!Observers.TryGetValue(notification.NotificationName, out notificationValue))
{
// Observer doesn't exists
return;
}
Observers.Remove(notification.NotificationName);
}
}
}

@ -77,6 +77,9 @@
<Compile Include="Interfaces\IAvailabilityChecker.cs" /> <Compile Include="Interfaces\IAvailabilityChecker.cs" />
<Compile Include="Interfaces\IConfigurationReader.cs" /> <Compile Include="Interfaces\IConfigurationReader.cs" />
<Compile Include="Interfaces\IIntervals.cs" /> <Compile Include="Interfaces\IIntervals.cs" />
<Compile Include="Notification\INotification.cs" />
<Compile Include="Notification\EmailMessageNotification.cs" />
<Compile Include="Notification\NotificationService.cs" />
<Compile Include="PlexAvailabilityChecker.cs" /> <Compile Include="PlexAvailabilityChecker.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UpdateInterval.cs" /> <Compile Include="UpdateInterval.cs" />

@ -44,6 +44,7 @@ using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using PlexRequests.Services; using PlexRequests.Services;
using PlexRequests.Services.Interfaces; using PlexRequests.Services.Interfaces;
using PlexRequests.Services.Notification;
using PlexRequests.Store; using PlexRequests.Store;
using PlexRequests.Store.Repository; using PlexRequests.Store.Repository;
using PlexRequests.UI.Jobs; using PlexRequests.UI.Jobs;
@ -86,19 +87,20 @@ namespace PlexRequests.UI
container.Register<IPlexApi, PlexApi>(); container.Register<IPlexApi, PlexApi>();
SubscribeAllObservers(container);
base.ConfigureRequestContainer(container, context); base.ConfigureRequestContainer(container, context);
} }
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines) protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{ {
TaskManager.TaskFactory = new Jobs.PlexTaskFactory(); TaskManager.TaskFactory = new PlexTaskFactory();
TaskManager.Initialize(new PlexRegistry()); TaskManager.Initialize(new PlexRegistry());
CookieBasedSessions.Enable(pipelines, CryptographyConfiguration.Default); CookieBasedSessions.Enable(pipelines, CryptographyConfiguration.Default);
StaticConfiguration.DisableErrorTraces = false; StaticConfiguration.DisableErrorTraces = false;
base.ApplicationStartup(container, pipelines); base.ApplicationStartup(container, pipelines);
// Enable forms auth // Enable forms auth
@ -109,8 +111,20 @@ namespace PlexRequests.UI
}; };
FormsAuthentication.Enable(pipelines, formsAuthConfiguration); FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
} }
protected override DiagnosticsConfiguration DiagnosticsConfiguration => new DiagnosticsConfiguration { Password = @"password" }; protected override DiagnosticsConfiguration DiagnosticsConfiguration => new DiagnosticsConfiguration { Password = @"password" };
private void SubscribeAllObservers(TinyIoCContainer container)
{
var emailSettingsService = container.Resolve<ISettingsService<EmailNotificationSettings>>();
var emailSettings = emailSettingsService.GetSettings();
if (emailSettings.Enabled)
{
NotificationService.Subscribe(new EmailMessageNotification(emailSettingsService));
}
}
} }
} }

@ -40,6 +40,7 @@ using PlexRequests.Api.Interfaces;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using PlexRequests.Services.Notification;
using PlexRequests.UI.Helpers; using PlexRequests.UI.Helpers;
using PlexRequests.UI.Models; using PlexRequests.UI.Models;
@ -302,8 +303,13 @@ namespace PlexRequests.UI.Modules
Log.Trace(settings.DumpJson()); Log.Trace(settings.DumpJson());
var result = EmailService.SaveSettings(settings); var result = EmailService.SaveSettings(settings);
NotificationService.Subscribe(new EmailMessageNotification(EmailService));
Log.Info("Saved email settings, result: {0}", result); Log.Info("Saved email settings, result: {0}", result);
return Context.GetRedirect("~/admin/emailnotification"); return Response.AsJson(result
? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Email Notifications!" }
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
} }
private Negotiator Status() private Negotiator Status()

@ -37,6 +37,7 @@ using PlexRequests.Core;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using PlexRequests.Services.Interfaces; using PlexRequests.Services.Interfaces;
using PlexRequests.Services.Notification;
using PlexRequests.Store; using PlexRequests.Store;
using PlexRequests.UI.Models; using PlexRequests.UI.Models;
@ -235,7 +236,8 @@ namespace PlexRequests.UI.Modules
{ {
Log.Debug("Adding movie to database requests"); Log.Debug("Adding movie to database requests");
var id = RequestService.AddRequest(movieId, model); var id = RequestService.AddRequest(movieId, model);
//BackgroundJob.Enqueue(() => Checker.CheckAndUpdate(model.Title, (int)id));
NotificationService.Publish(model.Title, model.RequestedBy);
return Response.AsJson(new JsonResponseModel { Result = true }); return Response.AsJson(new JsonResponseModel { Result = true });
} }
@ -291,7 +293,6 @@ namespace PlexRequests.UI.Modules
LatestTv = latest LatestTv = latest
}; };
RequestService.AddRequest(showId, model);
var settings = PrService.GetSettings(); var settings = PrService.GetSettings();
if (!settings.RequireApproval) if (!settings.RequireApproval)
@ -302,11 +303,20 @@ namespace PlexRequests.UI.Modules
var result = SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile, var result = SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile,
sonarrSettings.SeasonFolders, sonarrSettings.RootPath, model.LatestTv, sonarrSettings.ApiKey, sonarrSettings.SeasonFolders, sonarrSettings.RootPath, model.LatestTv, sonarrSettings.ApiKey,
sonarrSettings.FullUri); sonarrSettings.FullUri);
Log.Info("Added series {0} to Sonarr, Result: {1}", model.Title, result); if (result != null)
Log.Trace("Model sent to Sonarr: "); {
Log.Trace(model.DumpJson()); model.Approved = true;
Log.Debug("Adding tv to database requests (No approval required)");
RequestService.AddRequest(showId, model);
return Response.AsJson(new JsonResponseModel { Result = true });
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something went wrong adding the movie to CouchPotato! Please check your settings." });
} }
RequestService.AddRequest(showId, model);
NotificationService.Publish(model.Title, model.RequestedBy);
return Response.AsJson(new { Result = true }); return Response.AsJson(new { Result = true });
} }
private string GetTvDbAuthToken(TheTvDbApi api) private string GetTvDbAuthToken(TheTvDbApi api)

@ -29,6 +29,20 @@
</label> </label>
</div> </div>
</div> </div>
<div class="form-group">
<div class="checkbox">
<label>
@if (Model.Ssl)
{
<input type="checkbox" id="Ssl" name="Ssl" checked="checked"><text>SSL Enabled</text>
}
else
{
<input type="checkbox" id="Ssl" name="Ssl"><text>SSL Enabled</text>
}
</label>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="EmailHost" class="control-label">SMTP Hostname or IP</label> <label for="EmailHost" class="control-label">SMTP Hostname or IP</label>
<div class=""> <div class="">

@ -46,14 +46,14 @@
} }
@*<a class="list-group-item" href="/admin/sickbeard">Sickbeard Settings</a>*@ @*<a class="list-group-item" href="/admin/sickbeard">Sickbeard Settings</a>*@
@*@if (Context.Request.Path == "/admin/emailnotification") @if (Context.Request.Path == "/admin/emailnotification")
{ {
<a class="list-group-item active" href="/admin/emailnotification">Email Notifications</a> <a class="list-group-item active" href="/admin/emailnotification">Email Notifications</a>
} }
else else
{ {
<a class="list-group-item" href="/admin/emailnotification">Email Notifications</a> <a class="list-group-item" href="/admin/emailnotification">Email Notifications</a>
}*@ }
@if (Context.Request.Path == "/admin/status") @if (Context.Request.Path == "/admin/status")
{ {

@ -3,6 +3,9 @@
[![Gitter](https://badges.gitter.im/tidusjar/PlexRequest.NET.svg)](https://gitter.im/tidusjar/PlexRequests.Net?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Gitter](https://badges.gitter.im/tidusjar/PlexRequest.NET.svg)](https://gitter.im/tidusjar/PlexRequests.Net?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex) [![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex)
[![Linux Status](https://travis-ci.org/tidusjar/PlexRequests.Net.svg)](https://travis-ci.org/tidusjar/PlexRequests.Net) [![Linux Status](https://travis-ci.org/tidusjar/PlexRequests.Net.svg)](https://travis-ci.org/tidusjar/PlexRequests.Net)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tidusjar/plexrequests.net.svg)](http://isitmaintained.com/project/tidusjar/plexrequests.net "Average time to resolve an issue")
[![Percentage of issues still open](http://isitmaintained.com/badge/open/tidusjar/plexrequests.net.svg)](http://isitmaintained.com/project/tidusjar/plexrequests.net "Percentage of issues still open")
[![Github All Releases](https://img.shields.io/github/downloads/tidusjar/PlexRequests.net/total.svg)]()
This is based off [Plex Requests by lokenx](https://github.com/lokenx/plexrequests-meteor) so big props to that guy! This is based off [Plex Requests by lokenx](https://github.com/lokenx/plexrequests-meteor) so big props to that guy!
I wanted to write a similar application in .Net! I wanted to write a similar application in .Net!

Loading…
Cancel
Save