From 7af782d353fdea65f7e96ee76ef5deb87c5f8aa0 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 4 Jul 2014 01:09:48 -0700 Subject: [PATCH] Provider testing improvements New: Test button for indexers in UI Fixed: Testing download clients shows error messages in UI Fixed: Testing notifications shows error messages in UI --- src/NzbDrone.Api/ProviderModuleBase.cs | 42 +++++++++++-- src/NzbDrone.Api/REST/RestModule.cs | 2 +- .../IndexerTests/IndexerServiceFixture.cs | 2 - .../Download/Clients/Nzbget/Nzbget.cs | 42 +++++++++---- .../Clients/Nzbget/TestNzbgetCommand.cs | 26 -------- .../Download/Clients/Pneumatic/Pneumatic.cs | 47 ++++++++------ .../Clients/Pneumatic/TestPneumaticCommand.cs | 18 ------ .../Download/Clients/Sabnzbd/Sabnzbd.cs | 40 ++++++++---- .../Clients/Sabnzbd/TestSabnzbdCommand.cs | 27 -------- .../TestUsenetBlackholeCommand.cs | 19 ------ .../UsenetBlackhole/UsenetBlackhole.cs | 42 ++++++++----- .../UsenetBlackholeSettings.cs | 2 - .../Download/DownloadClientBase.cs | 7 +-- .../Download/DownloadClientDefinition.cs | 4 +- src/NzbDrone.Core/Indexers/Animezb/Animezb.cs | 6 ++ src/NzbDrone.Core/Indexers/Fanzub/Fanzub.cs | 6 ++ src/NzbDrone.Core/Indexers/IndexerBase.cs | 2 + .../Indexers/IndexerDefinition.cs | 4 +- src/NzbDrone.Core/Indexers/IndexerFactory.cs | 13 ---- src/NzbDrone.Core/Indexers/Newznab/Newznab.cs | 58 ++++++++++++++++- .../Indexers/Omgwtfnzbs/Omgwtfnzbs.cs | 7 +++ src/NzbDrone.Core/Indexers/Wombles/Wombles.cs | 7 +++ .../MetaData/MetadataDefinition.cs | 4 +- src/NzbDrone.Core/Metadata/MetadataBase.cs | 7 +++ .../Notifications/Email/Email.cs | 24 +++++-- .../Notifications/Email/EmailService.cs | 23 ++++--- .../Notifications/Email/TestEmailCommand.cs | 23 ------- .../Notifications/Growl/Growl.cs | 24 +++++-- .../Notifications/Growl/GrowlService.cs | 36 +++++++---- .../Notifications/Growl/TestGrowlCommand.cs | 18 ------ .../Notifications/NotificationBase.cs | 3 + .../Notifications/NotificationDefinition.cs | 10 ++- .../NotifyMyAndroid/NotifyMyAndroid.cs | 26 ++++++-- .../NotifyMyAndroid/NotifyMyAndroidProxy.cs | 32 +++++++--- .../NotifyMyAndroidSettings.cs | 2 +- .../TestNotifyMyAndroidCommand.cs | 18 ------ .../Notifications/Plex/PlexClient.cs | 24 +++++-- .../Notifications/Plex/PlexServer.cs | 22 +++++-- .../Notifications/Plex/PlexService.cs | 63 ++++++++++++------- .../Plex/TestPlexClientCommand.cs | 19 ------ .../Plex/TestPlexServerCommand.cs | 20 ------ .../Notifications/Prowl/Prowl.cs | 24 +++++-- .../Notifications/Prowl/ProwlService.cs | 25 +++++--- .../Notifications/Prowl/ProwlSettings.cs | 2 +- .../Notifications/Prowl/TestProwlCommand.cs | 17 ----- .../Notifications/PushBullet/PushBullet.cs | 24 +++++-- .../PushBullet/PushBulletProxy.cs | 42 +++++++++++-- .../PushBullet/TestPushBulletCommand.cs | 18 ------ .../Notifications/Pushover/Pushover.cs | 24 +++++-- .../Notifications/Pushover/PushoverService.cs | 35 ++++++++--- .../Pushover/PushoverSettings.cs | 2 +- .../Pushover/TestPushoverCommand.cs | 21 ------- .../Notifications/Xbmc/TestXbmcCommand.cs | 21 ------- src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs | 28 ++++++--- .../Notifications/Xbmc/XbmcService.cs | 48 +++++++------- src/NzbDrone.Core/NzbDrone.Core.csproj | 13 ---- src/NzbDrone.Core/ThingiProvider/IProvider.cs | 5 +- .../ThingiProvider/IProviderFactory.cs | 9 +-- .../ThingiProvider/ProviderDefinition.cs | 12 ++-- .../ThingiProvider/ProviderFactory.cs | 6 ++ .../DownloadClient/DownloadClientModel.js | 6 +- .../Edit/DownloadClientEditView.js | 9 +-- .../Settings/Indexers/Edit/IndexerEditView.js | 9 +-- .../Edit/IndexerEditViewTemplate.html | 3 +- src/UI/Settings/Indexers/IndexerModel.js | 6 +- src/UI/Settings/Metadata/MetadataModel.js | 12 ++-- .../Edit/NotificationEditView.js | 9 +-- .../Notifications/NotificationModel.js | 9 ++- src/UI/Settings/ProviderSettingsModelBase.js | 36 +++++++++++ src/UI/jQuery/jquery.validation.js | 12 ++-- 70 files changed, 722 insertions(+), 586 deletions(-) delete mode 100644 src/NzbDrone.Core/Download/Clients/Nzbget/TestNzbgetCommand.cs delete mode 100644 src/NzbDrone.Core/Download/Clients/Pneumatic/TestPneumaticCommand.cs delete mode 100644 src/NzbDrone.Core/Download/Clients/Sabnzbd/TestSabnzbdCommand.cs delete mode 100644 src/NzbDrone.Core/Download/Clients/UsenetBlackhole/TestUsenetBlackholeCommand.cs delete mode 100644 src/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs delete mode 100644 src/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs delete mode 100644 src/NzbDrone.Core/Notifications/NotifyMyAndroid/TestNotifyMyAndroidCommand.cs delete mode 100644 src/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs delete mode 100644 src/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs delete mode 100644 src/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs delete mode 100644 src/NzbDrone.Core/Notifications/PushBullet/TestPushBulletCommand.cs delete mode 100644 src/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs delete mode 100644 src/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs create mode 100644 src/UI/Settings/ProviderSettingsModelBase.js diff --git a/src/NzbDrone.Api/ProviderModuleBase.cs b/src/NzbDrone.Api/ProviderModuleBase.cs index 943f27077..a35858633 100644 --- a/src/NzbDrone.Api/ProviderModuleBase.cs +++ b/src/NzbDrone.Api/ProviderModuleBase.cs @@ -23,7 +23,10 @@ namespace NzbDrone.Api : base(resource) { _providerFactory = providerFactory; + Get["schema"] = x => GetTemplates(); + Post["test"] = x => Test(ReadResourceFromRequest()); + GetResourceAll = GetAll; GetResourceById = GetProviderById; CreateResource = CreateProvider; @@ -63,16 +66,26 @@ namespace NzbDrone.Api private int CreateProvider(TProviderResource providerResource) { - var provider = GetDefinition(providerResource); - provider = _providerFactory.Create(provider); - return provider.Id; + var providerDefinition = GetDefinition(providerResource); + + if (providerDefinition.Enable) + { + Test(providerDefinition); + } + + providerDefinition = _providerFactory.Create(providerDefinition); + + return providerDefinition.Id; } private void UpdateProvider(TProviderResource providerResource) { var providerDefinition = GetDefinition(providerResource); - Validate(providerDefinition); + if (providerDefinition.Enable) + { + Test(providerDefinition); + } _providerFactory.Update(providerDefinition); } @@ -133,6 +146,25 @@ namespace NzbDrone.Api return result.AsResponse(); } + private Response Test(TProviderResource providerResource) + { + var providerDefinition = GetDefinition(providerResource); + + Test(providerDefinition); + + return "{}"; + } + + private void Test(TProviderDefinition providerDefinition) + { + var result = _providerFactory.Test(providerDefinition); + + if (!result.IsValid) + { + throw new ValidationException(result.Errors); + } + } + protected virtual void Validate(TProviderDefinition definition) { var validationResult = definition.Settings.Validate(); @@ -143,4 +175,4 @@ namespace NzbDrone.Api } } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Api/REST/RestModule.cs b/src/NzbDrone.Api/REST/RestModule.cs index 49cd14fa9..e7acab8f6 100644 --- a/src/NzbDrone.Api/REST/RestModule.cs +++ b/src/NzbDrone.Api/REST/RestModule.cs @@ -194,7 +194,7 @@ namespace NzbDrone.Api.REST var errors = SharedValidator.Validate(resource).Errors.ToList(); - if (Request.Method.Equals("POST", StringComparison.InvariantCultureIgnoreCase)) + if (Request.Method.Equals("POST", StringComparison.InvariantCultureIgnoreCase) && !Request.Url.Path.EndsWith("/test", StringComparison.InvariantCultureIgnoreCase)) { errors.AddRange(PostValidator.Validate(resource).Errors); } diff --git a/src/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs index ae5915799..154f14b86 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/IndexerServiceFixture.cs @@ -1,8 +1,6 @@ using System.Collections.Generic; -using System.Linq; using FizzWare.NBuilder; using FluentAssertions; -using Moq; using NUnit.Framework; using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers.Newznab; diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs index 6dd047f4c..12a2e0144 100644 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs +++ b/src/NzbDrone.Core/Download/Clients/Nzbget/Nzbget.cs @@ -2,19 +2,18 @@ using System.IO; using System.Linq; using System.Collections.Generic; +using FluentValidation.Results; using NLog; using NzbDrone.Common; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; -using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; -using Omu.ValueInjecter; namespace NzbDrone.Core.Download.Clients.Nzbget { - public class Nzbget : DownloadClientBase, IExecute + public class Nzbget : DownloadClientBase { private readonly INzbgetProxy _proxy; private readonly IHttpProvider _httpProvider; @@ -265,25 +264,42 @@ namespace NzbDrone.Core.Download.Clients.Nzbget return _proxy.GetVersion(Settings); } - public override void Test(NzbgetSettings settings) + public override ValidationResult Test() { - _proxy.GetVersion(settings); + var failures = new List(); - var config = _proxy.GetConfig(settings); - var categories = GetCategories(config); + failures.AddIfNotNull(TestConnection()); + failures.AddIfNotNull(TestCategory()); + + return new ValidationResult(failures); + } - if (!settings.TvCategory.IsNullOrWhiteSpace() && !categories.Any(v => v.Name == settings.TvCategory)) + private ValidationFailure TestConnection() + { + try + { + _proxy.GetVersion(Settings); + } + catch (Exception ex) { - throw new ApplicationException("Category does not exist"); + _logger.ErrorException(ex.Message, ex); + return new ValidationFailure("Host", "Unable to connect to NZBGet"); } + + return null; } - public void Execute(TestNzbgetCommand message) + private ValidationFailure TestCategory() { - var settings = new NzbgetSettings(); - settings.InjectFrom(message); + var config = _proxy.GetConfig(Settings); + var categories = GetCategories(config); + + if (!Settings.TvCategory.IsNullOrWhiteSpace() && !categories.Any(v => v.Name == Settings.TvCategory)) + { + return new ValidationFailure("TvCategory", "Category does not exist"); + } - Test(settings); + return null; } // Javascript doesn't support 64 bit integers natively so json officially doesn't either. diff --git a/src/NzbDrone.Core/Download/Clients/Nzbget/TestNzbgetCommand.cs b/src/NzbDrone.Core/Download/Clients/Nzbget/TestNzbgetCommand.cs deleted file mode 100644 index f596d6b25..000000000 --- a/src/NzbDrone.Core/Download/Clients/Nzbget/TestNzbgetCommand.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using NzbDrone.Core.Messaging.Commands; - -namespace NzbDrone.Core.Download.Clients.Nzbget -{ - public class TestNzbgetCommand : Command - { - public override bool SendUpdatesToClient - { - get - { - return true; - } - } - - - public String Host { get; set; } - public Int32 Port { get; set; } - public String Username { get; set; } - public String Password { get; set; } - public String TvCategory { get; set; } - public Int32 RecentTvPriority { get; set; } - public Int32 OlderTvPriority { get; set; } - public Boolean UseSsl { get; set; } - } -} diff --git a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs index e8f0b7a09..8ea07c39b 100644 --- a/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs +++ b/src/NzbDrone.Core/Download/Clients/Pneumatic/Pneumatic.cs @@ -1,28 +1,24 @@ using System; using System.Collections.Generic; using System.IO; +using FluentValidation.Results; using NLog; using NzbDrone.Common; using NzbDrone.Common.Disk; using NzbDrone.Common.Http; -using NzbDrone.Common.Instrumentation; using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; -using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; -using Omu.ValueInjecter; namespace NzbDrone.Core.Download.Clients.Pneumatic { - public class Pneumatic : DownloadClientBase, IExecute + public class Pneumatic : DownloadClientBase { private readonly IHttpProvider _httpProvider; private readonly IDiskProvider _diskProvider; - private static readonly Logger logger = NzbDroneLogger.GetLogger(); - public Pneumatic(IHttpProvider httpProvider, IDiskProvider diskProvider, IConfigService configService, @@ -57,10 +53,10 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic //Save to the Pneumatic directory (The user will need to ensure its accessible by XBMC) var filename = Path.Combine(Settings.NzbFolder, title + ".nzb"); - logger.Debug("Downloading NZB from: {0} to: {1}", url, filename); + _logger.Debug("Downloading NZB from: {0} to: {1}", url, filename); _httpProvider.DownloadFile(url, filename); - logger.Debug("NZB Download succeeded, saved to: {0}", filename); + _logger.Debug("NZB Download succeeded, saved to: {0}", filename); var contents = String.Format("plugin://plugin.program.pneumatic/?mode=strm&type=add_file&nzb={0}&nzbname={1}", filename, title); _diskProvider.WriteAllText(Path.Combine(_configService.DownloadedEpisodesFolder, title + ".strm"), contents); @@ -101,24 +97,35 @@ namespace NzbDrone.Core.Download.Clients.Pneumatic return status; } - public override void Test(PneumaticSettings settings) + public override ValidationResult Test() { - PerformWriteTest(settings.NzbFolder); - } + var failures = new List(); - private void PerformWriteTest(string folder) - { - var testPath = Path.Combine(folder, "drone_test.txt"); - _diskProvider.WriteAllText(testPath, DateTime.Now.ToString()); - _diskProvider.DeleteFile(testPath); + failures.AddIfNotNull(TestWrite(Settings.NzbFolder, "NzbFolder")); + + return new ValidationResult(failures); } - public void Execute(TestPneumaticCommand message) + private ValidationFailure TestWrite(String folder, String propertyName) { - var settings = new PneumaticSettings(); - settings.InjectFrom(message); + if (!_diskProvider.FolderExists(folder)) + { + return new ValidationFailure(propertyName, "Folder does not exist"); + } - Test(settings); + try + { + var testPath = Path.Combine(folder, "drone_test.txt"); + _diskProvider.WriteAllText(testPath, DateTime.Now.ToString()); + _diskProvider.DeleteFile(testPath); + } + catch (Exception ex) + { + _logger.ErrorException(ex.Message, ex); + return new ValidationFailure(propertyName, "Unable to write to folder"); + } + + return null; } } } diff --git a/src/NzbDrone.Core/Download/Clients/Pneumatic/TestPneumaticCommand.cs b/src/NzbDrone.Core/Download/Clients/Pneumatic/TestPneumaticCommand.cs deleted file mode 100644 index 097a39aa5..000000000 --- a/src/NzbDrone.Core/Download/Clients/Pneumatic/TestPneumaticCommand.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using NzbDrone.Core.Messaging.Commands; - -namespace NzbDrone.Core.Download.Clients.Pneumatic -{ - public class TestPneumaticCommand : Command - { - public override bool SendUpdatesToClient - { - get - { - return true; - } - } - - public String Folder { get; set; } - } -} diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs index 2ce1bef1f..ff4d9d0c2 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Sabnzbd.cs @@ -2,19 +2,18 @@ using System.IO; using System.Collections.Generic; using System.Linq; +using FluentValidation.Results; using NLog; using NzbDrone.Common; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; -using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; -using Omu.ValueInjecter; namespace NzbDrone.Core.Download.Clients.Sabnzbd { - public class Sabnzbd : DownloadClientBase, IExecute + public class Sabnzbd : DownloadClientBase { private readonly IHttpProvider _httpProvider; private readonly ISabnzbdProxy _proxy; @@ -218,22 +217,41 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd return status; } - public override void Test(SabnzbdSettings settings) + public override ValidationResult Test() { - var categories = _proxy.GetCategories(settings); + var failures = new List(); - if (!settings.TvCategory.IsNullOrWhiteSpace() && !categories.Any(v => v == settings.TvCategory)) + failures.AddIfNotNull(TestConnection()); + failures.AddIfNotNull(TestCategory()); + + return new ValidationResult(failures); + } + + private ValidationFailure TestConnection() + { + try + { + _proxy.GetCategories(Settings); + } + catch (Exception ex) { - throw new ApplicationException("Category does not exist"); + _logger.ErrorException(ex.Message, ex); + return new ValidationFailure("Host", "Unable to connect to SABnzbd"); } + + return null; } - public void Execute(TestSabnzbdCommand message) + private ValidationFailure TestCategory() { - var settings = new SabnzbdSettings(); - settings.InjectFrom(message); + var categories = _proxy.GetCategories(Settings); + + if (!Settings.TvCategory.IsNullOrWhiteSpace() && !categories.Any(v => v == Settings.TvCategory)) + { + return new ValidationFailure("TvCategory", "Category does not exist"); + } - Test(settings); + return null; } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/TestSabnzbdCommand.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/TestSabnzbdCommand.cs deleted file mode 100644 index 458b62f3a..000000000 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/TestSabnzbdCommand.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using NzbDrone.Core.Messaging.Commands; - -namespace NzbDrone.Core.Download.Clients.Sabnzbd -{ - public class TestSabnzbdCommand : Command - { - public override bool SendUpdatesToClient - { - get - { - return true; - } - } - - - public String Host { get; set; } - public Int32 Port { get; set; } - public String ApiKey { get; set; } - public String Username { get; set; } - public String Password { get; set; } - public String TvCategory { get; set; } - public Int32 RecentTvPriority { get; set; } - public Int32 OlderTvPriority { get; set; } - public Boolean UseSsl { get; set; } - } -} diff --git a/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/TestUsenetBlackholeCommand.cs b/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/TestUsenetBlackholeCommand.cs deleted file mode 100644 index e4db46d4a..000000000 --- a/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/TestUsenetBlackholeCommand.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using NzbDrone.Core.Messaging.Commands; - -namespace NzbDrone.Core.Download.Clients.UsenetBlackhole -{ - public class TestUsenetBlackholeCommand : Command - { - public override bool SendUpdatesToClient - { - get - { - return true; - } - } - - public String NzbFolder { get; set; } - public String WatchFolder { get; set; } - } -} diff --git a/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs b/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs index 290eb8564..d896ba467 100644 --- a/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs +++ b/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackhole.cs @@ -2,22 +2,21 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using FluentValidation.Results; using NLog; using NzbDrone.Common; using NzbDrone.Common.Disk; using NzbDrone.Common.Http; using NzbDrone.Core.Configuration; using NzbDrone.Core.Indexers; -using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.MediaFiles; -using Omu.ValueInjecter; namespace NzbDrone.Core.Download.Clients.UsenetBlackhole { - public class UsenetBlackhole : DownloadClientBase, IExecute + public class UsenetBlackhole : DownloadClientBase { private readonly IDiskProvider _diskProvider; private readonly IDiskScanService _diskScanService; @@ -146,25 +145,36 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole }; } - public override void Test(UsenetBlackholeSettings settings) + public override ValidationResult Test() { - PerformWriteTest(settings.NzbFolder); - PerformWriteTest(settings.WatchFolder); - } + var failures = new List(); - private void PerformWriteTest(string folder) - { - var testPath = Path.Combine(folder, "drone_test.txt"); - _diskProvider.WriteAllText(testPath, DateTime.Now.ToString()); - _diskProvider.DeleteFile(testPath); + failures.AddIfNotNull(TestWrite(Settings.NzbFolder, "NzbFolder")); + failures.AddIfNotNull(TestWrite(Settings.WatchFolder, "WatchFolder")); + + return new ValidationResult(failures); } - public void Execute(TestUsenetBlackholeCommand message) + private ValidationFailure TestWrite(String folder, String propertyName) { - var settings = new UsenetBlackholeSettings(); - settings.InjectFrom(message); + if (!_diskProvider.FolderExists(folder)) + { + return new ValidationFailure(propertyName, "Folder does not exist"); + } - Test(settings); + try + { + var testPath = Path.Combine(folder, "drone_test.txt"); + _diskProvider.WriteAllText(testPath, DateTime.Now.ToString()); + _diskProvider.DeleteFile(testPath); + } + catch (Exception ex) + { + _logger.ErrorException(ex.Message, ex); + return new ValidationFailure(propertyName, "Unable to write to folder"); + } + + return null; } } } diff --git a/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackholeSettings.cs b/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackholeSettings.cs index dd5371af8..90d99484a 100644 --- a/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackholeSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/UsenetBlackhole/UsenetBlackholeSettings.cs @@ -1,7 +1,6 @@ using System; using FluentValidation; using FluentValidation.Results; -using NzbDrone.Common.Disk; using NzbDrone.Core.Annotations; using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Validation.Paths; @@ -12,7 +11,6 @@ namespace NzbDrone.Core.Download.Clients.UsenetBlackhole { public UsenetBlackholeSettingsValidator() { - //Todo: Validate that the path actually exists RuleFor(c => c.NzbFolder).IsValidPath(); RuleFor(c => c.WatchFolder).IsValidPath(); } diff --git a/src/NzbDrone.Core/Download/DownloadClientBase.cs b/src/NzbDrone.Core/Download/DownloadClientBase.cs index 2176ba21f..e0272781b 100644 --- a/src/NzbDrone.Core/Download/DownloadClientBase.cs +++ b/src/NzbDrone.Core/Download/DownloadClientBase.cs @@ -1,6 +1,6 @@ using System; -using System.Linq; using System.Collections.Generic; +using FluentValidation.Results; using NzbDrone.Core.Indexers; using NzbDrone.Core.Parser; using NzbDrone.Core.Parser.Model; @@ -34,6 +34,7 @@ namespace NzbDrone.Core.Download } public ProviderDefinition Definition { get; set; } + public abstract ValidationResult Test(); protected TSettings Settings { @@ -55,8 +56,6 @@ namespace NzbDrone.Core.Download return GetType().Name; } - - public abstract DownloadProtocol Protocol { get; @@ -68,8 +67,6 @@ namespace NzbDrone.Core.Download public abstract void RetryDownload(string id); public abstract DownloadClientStatus GetStatus(); - public abstract void Test(TSettings settings); - protected RemoteEpisode GetRemoteEpisode(String title) { var parsedEpisodeInfo = Parser.Parser.ParseTitle(title); diff --git a/src/NzbDrone.Core/Download/DownloadClientDefinition.cs b/src/NzbDrone.Core/Download/DownloadClientDefinition.cs index 479d10925..b81536a53 100644 --- a/src/NzbDrone.Core/Download/DownloadClientDefinition.cs +++ b/src/NzbDrone.Core/Download/DownloadClientDefinition.cs @@ -1,12 +1,10 @@ -using System; -using NzbDrone.Core.Indexers; +using NzbDrone.Core.Indexers; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Download { public class DownloadClientDefinition : ProviderDefinition { - public Boolean Enable { get; set; } public DownloadProtocol Protocol { get; set; } } } diff --git a/src/NzbDrone.Core/Indexers/Animezb/Animezb.cs b/src/NzbDrone.Core/Indexers/Animezb/Animezb.cs index f8a17ab71..b465c6df3 100644 --- a/src/NzbDrone.Core/Indexers/Animezb/Animezb.cs +++ b/src/NzbDrone.Core/Indexers/Animezb/Animezb.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using FluentValidation.Results; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Indexers.Animezb @@ -72,6 +73,11 @@ namespace NzbDrone.Core.Indexers.Animezb return new List(); } + public override ValidationResult Test() + { + return new ValidationResult(); + } + private String GetSearchQuery(string title, int absoluteEpisodeNumber) { var match = RemoveSingleCharacterRegex.Match(title); diff --git a/src/NzbDrone.Core/Indexers/Fanzub/Fanzub.cs b/src/NzbDrone.Core/Indexers/Fanzub/Fanzub.cs index de1765319..848811f29 100644 --- a/src/NzbDrone.Core/Indexers/Fanzub/Fanzub.cs +++ b/src/NzbDrone.Core/Indexers/Fanzub/Fanzub.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; +using FluentValidation.Results; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Indexers.Fanzub @@ -69,6 +70,11 @@ namespace NzbDrone.Core.Indexers.Fanzub return new List(); } + public override ValidationResult Test() + { + return new ValidationResult(); + } + private IEnumerable GetTitleSearchStrings(string title, int absoluteEpisodeNumber) { var formats = new[] { "{0}%20{1:00}", "{0}%20-%20{1:00}" }; diff --git a/src/NzbDrone.Core/Indexers/IndexerBase.cs b/src/NzbDrone.Core/Indexers/IndexerBase.cs index c82c5a51b..21754cd44 100644 --- a/src/NzbDrone.Core/Indexers/IndexerBase.cs +++ b/src/NzbDrone.Core/Indexers/IndexerBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using FluentValidation.Results; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Indexers @@ -32,6 +33,7 @@ namespace NzbDrone.Core.Indexers public virtual ProviderDefinition Definition { get; set; } + public abstract ValidationResult Test(); public abstract DownloadProtocol Protocol { get; } public virtual Boolean SupportsFeed { get { return true; } } diff --git a/src/NzbDrone.Core/Indexers/IndexerDefinition.cs b/src/NzbDrone.Core/Indexers/IndexerDefinition.cs index c1509952e..03d82ae49 100644 --- a/src/NzbDrone.Core/Indexers/IndexerDefinition.cs +++ b/src/NzbDrone.Core/Indexers/IndexerDefinition.cs @@ -4,8 +4,6 @@ namespace NzbDrone.Core.Indexers { public class IndexerDefinition : ProviderDefinition { - public bool Enable { get; set; } - public DownloadProtocol Protocol { get; set; } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/Indexers/IndexerFactory.cs b/src/NzbDrone.Core/Indexers/IndexerFactory.cs index 2b214ed7c..45bb0b705 100644 --- a/src/NzbDrone.Core/Indexers/IndexerFactory.cs +++ b/src/NzbDrone.Core/Indexers/IndexerFactory.cs @@ -14,7 +14,6 @@ namespace NzbDrone.Core.Indexers public class IndexerFactory : ProviderFactory, IIndexerFactory { - private readonly IIndexerRepository _providerRepository; private readonly INewznabTestService _newznabTestService; public IndexerFactory(IIndexerRepository providerRepository, @@ -25,7 +24,6 @@ namespace NzbDrone.Core.Indexers Logger logger) : base(providerRepository, providers, container, eventAggregator, logger) { - _providerRepository = providerRepository; _newznabTestService = newznabTestService; } @@ -39,17 +37,6 @@ namespace NzbDrone.Core.Indexers return base.Active().Where(c => c.Enable).ToList(); } - public override IndexerDefinition Create(IndexerDefinition definition) - { - if (definition.Implementation == typeof(Newznab.Newznab).Name) - { - var indexer = GetInstance(definition); - _newznabTestService.Test(indexer); - } - - return base.Create(definition); - } - protected override IndexerDefinition GetProviderCharacteristics(IIndexer provider, IndexerDefinition definition) { definition = base.GetProviderCharacteristics(provider, definition); diff --git a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs index f2b70e2ee..78ed2cd33 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs @@ -1,14 +1,35 @@ using System; using System.Collections.Generic; -using System.Diagnostics.Eventing.Reader; using System.Linq; +using FluentValidation; +using FluentValidation.Results; +using NLog; using NzbDrone.Common; +using NzbDrone.Common.Http; +using NzbDrone.Core.Indexers.Exceptions; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Indexers.Newznab { public class Newznab : IndexerBase { + private readonly IFetchFeedFromIndexers _feedFetcher; + private readonly HttpProvider _httpProvider; + private readonly Logger _logger; + + + + public Newznab(IFetchFeedFromIndexers feedFetcher, HttpProvider httpProvider, Logger logger) + { + _feedFetcher = feedFetcher; + _httpProvider = httpProvider; + _logger = logger; + } + + public Newznab() + { + } + public override DownloadProtocol Protocol { get { return DownloadProtocol.Usenet; } } public override Int32 SupportedPageSize { get { return 100; } } @@ -169,6 +190,41 @@ namespace NzbDrone.Core.Indexers.Newznab return RecentFeed.Select(url => String.Format("{0}&offset={1}&limit=100&q={2}", url.Replace("t=tvsearch", "t=search"), offset, query)); } + public override ValidationResult Test() + { + var releases = _feedFetcher.FetchRss(this); + + if (releases.Any()) return new ValidationResult(); + + try + { + var url = RecentFeed.First(); + var xml = _httpProvider.DownloadString(url); + + NewznabPreProcessor.Process(xml, url); + } + catch (ApiKeyException) + { + _logger.Warn("Indexer returned result for Newznab RSS URL, API Key appears to be invalid"); + + var apiKeyFailure = new ValidationFailure("ApiKey", "Invalid API Key"); + return new ValidationResult(new List { apiKeyFailure }); + } + catch (RequestLimitReachedException) + { + _logger.Warn("Request limit reached"); + } + catch (Exception ex) + { + _logger.WarnException("Unable to connect to indexer: " + ex.Message, ex); + + var failure = new ValidationFailure("Url", "Unable to connect to indexer, check the log for more details"); + return new ValidationResult(new List { failure }); + } + + return new ValidationResult(); + } + private static string NewsnabifyTitle(string title) { return title.Replace("+", "%20"); diff --git a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs index be81e3116..77161f28e 100644 --- a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs +++ b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/Omgwtfnzbs.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using FluentValidation.Results; namespace NzbDrone.Core.Indexers.Omgwtfnzbs { @@ -79,5 +81,10 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs { return new List(); } + + public override ValidationResult Test() + { + return new ValidationResult(); + } } } diff --git a/src/NzbDrone.Core/Indexers/Wombles/Wombles.cs b/src/NzbDrone.Core/Indexers/Wombles/Wombles.cs index a4dac0d2b..ebb9dac40 100644 --- a/src/NzbDrone.Core/Indexers/Wombles/Wombles.cs +++ b/src/NzbDrone.Core/Indexers/Wombles/Wombles.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using FluentValidation.Results; using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Indexers.Wombles @@ -46,5 +48,10 @@ namespace NzbDrone.Core.Indexers.Wombles { return new List(); } + + public override ValidationResult Test() + { + return new ValidationResult(); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MetaData/MetadataDefinition.cs b/src/NzbDrone.Core/MetaData/MetadataDefinition.cs index c796eb8ab..0784037dc 100644 --- a/src/NzbDrone.Core/MetaData/MetadataDefinition.cs +++ b/src/NzbDrone.Core/MetaData/MetadataDefinition.cs @@ -1,10 +1,8 @@ -using System; -using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.ThingiProvider; namespace NzbDrone.Core.Metadata { public class MetadataDefinition : ProviderDefinition { - public Boolean Enable { get; set; } } } diff --git a/src/NzbDrone.Core/Metadata/MetadataBase.cs b/src/NzbDrone.Core/Metadata/MetadataBase.cs index 8aa0e2764..bb5d4d23a 100644 --- a/src/NzbDrone.Core/Metadata/MetadataBase.cs +++ b/src/NzbDrone.Core/Metadata/MetadataBase.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using FluentValidation.Results; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Metadata.Files; using NzbDrone.Core.ThingiProvider; @@ -27,6 +29,11 @@ namespace NzbDrone.Core.Metadata public ProviderDefinition Definition { get; set; } + public ValidationResult Test() + { + return new ValidationResult(); + } + public abstract List AfterRename(Series series, List existingMetadataFiles, List episodeFiles); public abstract MetadataFile FindMetadataFile(Series series, string path); diff --git a/src/NzbDrone.Core/Notifications/Email/Email.cs b/src/NzbDrone.Core/Notifications/Email/Email.cs index d92c21a18..9624f4165 100644 --- a/src/NzbDrone.Core/Notifications/Email/Email.cs +++ b/src/NzbDrone.Core/Notifications/Email/Email.cs @@ -1,15 +1,18 @@ using System; +using System.Collections.Generic; +using FluentValidation.Results; +using NzbDrone.Common; using NzbDrone.Core.Tv; namespace NzbDrone.Core.Notifications.Email { public class Email : NotificationBase { - private readonly IEmailService _smtpProvider; + private readonly IEmailService _emailService; - public Email(IEmailService smtpProvider) + public Email(IEmailService emailService) { - _smtpProvider = smtpProvider; + _emailService = emailService; } public override string Link @@ -20,9 +23,9 @@ namespace NzbDrone.Core.Notifications.Email public override void OnGrab(string message) { const string subject = "NzbDrone [TV] - Grabbed"; - var body = String.Format("{0} sent to SABnzbd queue.", message); + var body = String.Format("{0} sent to queue.", message); - _smtpProvider.SendEmail(Settings, subject, body); + _emailService.SendEmail(Settings, subject, body); } public override void OnDownload(DownloadMessage message) @@ -30,11 +33,20 @@ namespace NzbDrone.Core.Notifications.Email const string subject = "NzbDrone [TV] - Downloaded"; var body = String.Format("{0} Downloaded and sorted.", message.Message); - _smtpProvider.SendEmail(Settings, subject, body); + _emailService.SendEmail(Settings, subject, body); } public override void AfterRename(Series series) { } + + public override ValidationResult Test() + { + var failures = new List(); + + failures.AddIfNotNull(_emailService.Test(Settings)); + + return new ValidationResult(failures); + } } } diff --git a/src/NzbDrone.Core/Notifications/Email/EmailService.cs b/src/NzbDrone.Core/Notifications/Email/EmailService.cs index 7e3e5b504..27655faeb 100644 --- a/src/NzbDrone.Core/Notifications/Email/EmailService.cs +++ b/src/NzbDrone.Core/Notifications/Email/EmailService.cs @@ -1,18 +1,18 @@ using System; using System.Net; using System.Net.Mail; +using FluentValidation.Results; using NLog; -using NzbDrone.Core.Messaging.Commands; -using Omu.ValueInjecter; namespace NzbDrone.Core.Notifications.Email { public interface IEmailService { void SendEmail(EmailSettings settings, string subject, string body, bool htmlBody = false); + ValidationFailure Test(EmailSettings settings); } - public class EmailService : IEmailService, IExecute + public class EmailService : IEmailService { private readonly Logger _logger; @@ -66,14 +66,21 @@ namespace NzbDrone.Core.Notifications.Email } } - public void Execute(TestEmailCommand message) + public ValidationFailure Test(EmailSettings settings) { - var settings = new EmailSettings(); - settings.InjectFrom(message); - const string body = "Success! You have properly configured your email notification settings"; - SendEmail(settings, "NzbDrone - Test Notification", body); + try + { + SendEmail(settings, "NzbDrone - Test Notification", body); + } + catch (Exception ex) + { + _logger.ErrorException("Unable to send test email: " + ex.Message, ex); + return new ValidationFailure("Server", "Unable to send test email"); + } + + return null; } } } diff --git a/src/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs b/src/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs deleted file mode 100644 index 3d9ec3797..000000000 --- a/src/NzbDrone.Core/Notifications/Email/TestEmailCommand.cs +++ /dev/null @@ -1,23 +0,0 @@ -using NzbDrone.Core.Messaging.Commands; - -namespace NzbDrone.Core.Notifications.Email -{ - public class TestEmailCommand : Command - { - public override bool SendUpdatesToClient - { - get - { - return true; - } - } - - 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/src/NzbDrone.Core/Notifications/Growl/Growl.cs b/src/NzbDrone.Core/Notifications/Growl/Growl.cs index 9682a5acb..217e26e20 100644 --- a/src/NzbDrone.Core/Notifications/Growl/Growl.cs +++ b/src/NzbDrone.Core/Notifications/Growl/Growl.cs @@ -1,14 +1,17 @@ -using NzbDrone.Core.Tv; +using System.Collections.Generic; +using FluentValidation.Results; +using NzbDrone.Common; +using NzbDrone.Core.Tv; namespace NzbDrone.Core.Notifications.Growl { public class Growl : NotificationBase { - private readonly IGrowlService _growlProvider; + private readonly IGrowlService _growlService; - public Growl(IGrowlService growlProvider) + public Growl(IGrowlService growlService) { - _growlProvider = growlProvider; + _growlService = growlService; } public override string Link @@ -20,18 +23,27 @@ namespace NzbDrone.Core.Notifications.Growl { const string title = "Episode Grabbed"; - _growlProvider.SendNotification(title, message, "GRAB", Settings.Host, Settings.Port, Settings.Password); + _growlService.SendNotification(title, message, "GRAB", Settings.Host, Settings.Port, Settings.Password); } public override void OnDownload(DownloadMessage message) { const string title = "Episode Downloaded"; - _growlProvider.SendNotification(title, message.Message, "DOWNLOAD", Settings.Host, Settings.Port, Settings.Password); + _growlService.SendNotification(title, message.Message, "DOWNLOAD", Settings.Host, Settings.Port, Settings.Password); } public override void AfterRename(Series series) { } + + public override ValidationResult Test() + { + var failures = new List(); + + failures.AddIfNotNull(_growlService.Test(Settings)); + + return new ValidationResult(failures); + } } } diff --git a/src/NzbDrone.Core/Notifications/Growl/GrowlService.cs b/src/NzbDrone.Core/Notifications/Growl/GrowlService.cs index d7d8686f3..88fd7a337 100644 --- a/src/NzbDrone.Core/Notifications/Growl/GrowlService.cs +++ b/src/NzbDrone.Core/Notifications/Growl/GrowlService.cs @@ -2,10 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using FluentValidation.Results; using Growl.Connector; using NLog; using NzbDrone.Common.Instrumentation; -using NzbDrone.Core.Messaging.Commands; using GrowlNotification = Growl.Connector.Notification; namespace NzbDrone.Core.Notifications.Growl @@ -13,18 +13,20 @@ namespace NzbDrone.Core.Notifications.Growl public interface IGrowlService { void SendNotification(string title, string message, string notificationTypeName, string hostname, int port, string password); + ValidationFailure Test(GrowlSettings settings); } - public class GrowlService : IGrowlService, IExecute + public class GrowlService : IGrowlService { - private static readonly Logger Logger = NzbDroneLogger.GetLogger(); + private readonly Logger _logger; private readonly Application _growlApplication = new Application("NzbDrone"); private GrowlConnector _growlConnector; private readonly List _notificationTypes; - public GrowlService() + public GrowlService(Logger logger) { + _logger = logger; _notificationTypes = GetNotificationTypes(); _growlApplication.Icon = "https://raw.github.com/NzbDrone/NzbDrone/master/Logo/64.png"; } @@ -36,13 +38,13 @@ namespace NzbDrone.Core.Notifications.Growl _growlConnector = new GrowlConnector(password, hostname, port); - Logger.Debug("Sending Notification to: {0}:{1}", hostname, port); + _logger.Debug("Sending Notification to: {0}:{1}", hostname, port); _growlConnector.Notify(notification); } private void Register(string host, int port, string password) { - Logger.Debug("Registering NzbDrone with Growl host: {0}:{1}", host, port); + _logger.Debug("Registering NzbDrone with Growl host: {0}:{1}", host, port); _growlConnector = new GrowlConnector(password, host, port); _growlConnector.Register(_growlApplication, _notificationTypes.ToArray()); } @@ -57,16 +59,26 @@ namespace NzbDrone.Core.Notifications.Growl return notificationTypes; } - public void Execute(TestGrowlCommand message) + public ValidationFailure Test(GrowlSettings settings) { - Register(message.Host, message.Port, message.Password); + try + { + Register(settings.Host, settings.Port, settings.Password); - const string title = "Test Notification"; - const string body = "This is a test message from NzbDrone"; + const string title = "Test Notification"; + const string body = "This is a test message from NzbDrone"; - Thread.Sleep(5000); + Thread.Sleep(5000); - SendNotification(title, body, "TEST", message.Host, message.Port, message.Password); + SendNotification(title, body, "TEST", settings.Host, settings.Port, settings.Password); + } + catch (Exception ex) + { + _logger.ErrorException("Unable to send test message: " + ex.Message, ex); + return new ValidationFailure("Host", "Unable to send test message"); + } + + return null; } } } diff --git a/src/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs b/src/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs deleted file mode 100644 index c1d880541..000000000 --- a/src/NzbDrone.Core/Notifications/Growl/TestGrowlCommand.cs +++ /dev/null @@ -1,18 +0,0 @@ -using NzbDrone.Core.Messaging.Commands; - -namespace NzbDrone.Core.Notifications.Growl -{ - public class TestGrowlCommand : Command - { - public override bool SendUpdatesToClient - { - get - { - return true; - } - } - public string Host { get; set; } - public int Port { get; set; } - public string Password { get; set; } - } -} diff --git a/src/NzbDrone.Core/Notifications/NotificationBase.cs b/src/NzbDrone.Core/Notifications/NotificationBase.cs index 61377a483..70d5e7b85 100644 --- a/src/NzbDrone.Core/Notifications/NotificationBase.cs +++ b/src/NzbDrone.Core/Notifications/NotificationBase.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using FluentValidation.Results; using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Tv; @@ -24,6 +26,7 @@ namespace NzbDrone.Core.Notifications } public ProviderDefinition Definition { get; set; } + public abstract ValidationResult Test(); public abstract string Link { get; } diff --git a/src/NzbDrone.Core/Notifications/NotificationDefinition.cs b/src/NzbDrone.Core/Notifications/NotificationDefinition.cs index b1143db3f..957a5ea45 100644 --- a/src/NzbDrone.Core/Notifications/NotificationDefinition.cs +++ b/src/NzbDrone.Core/Notifications/NotificationDefinition.cs @@ -8,5 +8,13 @@ namespace NzbDrone.Core.Notifications public Boolean OnGrab { get; set; } public Boolean OnDownload { get; set; } public Boolean OnUpgrade { get; set; } + + public override Boolean Enable + { + get + { + return OnGrab || OnDownload || OnUpgrade; + } + } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroid.cs b/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroid.cs index ffc1aa6ab..ac37b7307 100644 --- a/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroid.cs +++ b/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroid.cs @@ -1,14 +1,19 @@ -using NzbDrone.Core.Tv; + +using System.Collections.Generic; +using FluentValidation.Results; +using NLog; +using NzbDrone.Common; +using NzbDrone.Core.Tv; namespace NzbDrone.Core.Notifications.NotifyMyAndroid { public class NotifyMyAndroid : NotificationBase { - private readonly INotifyMyAndroidProxy _notifyMyAndroidProxy; + private readonly INotifyMyAndroidProxy _proxy; - public NotifyMyAndroid(INotifyMyAndroidProxy notifyMyAndroidProxy) + public NotifyMyAndroid(INotifyMyAndroidProxy proxy) { - _notifyMyAndroidProxy = notifyMyAndroidProxy; + _proxy = proxy; } public override string Link @@ -20,18 +25,27 @@ namespace NzbDrone.Core.Notifications.NotifyMyAndroid { const string title = "Episode Grabbed"; - _notifyMyAndroidProxy.SendNotification(title, message, Settings.ApiKey, (NotifyMyAndroidPriority)Settings.Priority); + _proxy.SendNotification(title, message, Settings.ApiKey, (NotifyMyAndroidPriority)Settings.Priority); } public override void OnDownload(DownloadMessage message) { const string title = "Episode Downloaded"; - _notifyMyAndroidProxy.SendNotification(title, message.Message, Settings.ApiKey, (NotifyMyAndroidPriority)Settings.Priority); + _proxy.SendNotification(title, message.Message, Settings.ApiKey, (NotifyMyAndroidPriority)Settings.Priority); } public override void AfterRename(Series series) { } + + public override ValidationResult Test() + { + var failures = new List(); + + failures.AddIfNotNull(_proxy.Test(Settings)); + + return new ValidationResult(failures); + } } } diff --git a/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroidProxy.cs b/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroidProxy.cs index 76ae52d98..f51c03062 100644 --- a/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroidProxy.cs +++ b/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroidProxy.cs @@ -2,8 +2,9 @@ using System.Linq; using System.Net; using System.Xml.Linq; +using FluentValidation.Results; +using NLog; using NzbDrone.Core.Exceptions; -using NzbDrone.Core.Messaging.Commands; using RestSharp; using NzbDrone.Core.Rest; @@ -12,12 +13,19 @@ namespace NzbDrone.Core.Notifications.NotifyMyAndroid public interface INotifyMyAndroidProxy { void SendNotification(string title, string message, string apiKye, NotifyMyAndroidPriority priority); + ValidationFailure Test(NotifyMyAndroidSettings settings); } - public class NotifyMyAndroidProxy : INotifyMyAndroidProxy, IExecute + public class NotifyMyAndroidProxy : INotifyMyAndroidProxy { + private readonly Logger _logger; private const string URL = "https://www.notifymyandroid.com/publicapi"; + public NotifyMyAndroidProxy(Logger logger) + { + _logger = logger; + } + public void SendNotification(string title, string message, string apiKey, NotifyMyAndroidPriority priority) { var client = new RestClient(URL); @@ -56,12 +64,22 @@ namespace NzbDrone.Core.Notifications.NotifyMyAndroid } } - public void Execute(TestNotifyMyAndroidCommand message) + public ValidationFailure Test(NotifyMyAndroidSettings settings) { - const string title = "Test Notification"; - const string body = "This is a test message from NzbDrone"; - Verify(message.ApiKey); - SendNotification(title, body, message.ApiKey, (NotifyMyAndroidPriority)message.Priority); + try + { + const string title = "Test Notification"; + const string body = "This is a test message from NzbDrone"; + Verify(settings.ApiKey); + SendNotification(title, body, settings.ApiKey, (NotifyMyAndroidPriority)settings.Priority); + } + catch (Exception ex) + { + _logger.ErrorException("Unable to send test message: " + ex.Message, ex); + return new ValidationFailure("ApiKey", "Unable to send test message"); + } + + return null; } } } diff --git a/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroidSettings.cs b/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroidSettings.cs index 1a05d92b3..f476f071c 100644 --- a/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroidSettings.cs +++ b/src/NzbDrone.Core/Notifications/NotifyMyAndroid/NotifyMyAndroidSettings.cs @@ -28,7 +28,7 @@ namespace NzbDrone.Core.Notifications.NotifyMyAndroid { get { - return !String.IsNullOrWhiteSpace(ApiKey) && Priority != null & Priority >= -1 && Priority <= 2; + return !String.IsNullOrWhiteSpace(ApiKey) && Priority >= -1 && Priority <= 2; } } diff --git a/src/NzbDrone.Core/Notifications/NotifyMyAndroid/TestNotifyMyAndroidCommand.cs b/src/NzbDrone.Core/Notifications/NotifyMyAndroid/TestNotifyMyAndroidCommand.cs deleted file mode 100644 index bb93b8fdd..000000000 --- a/src/NzbDrone.Core/Notifications/NotifyMyAndroid/TestNotifyMyAndroidCommand.cs +++ /dev/null @@ -1,18 +0,0 @@ -using NzbDrone.Core.Messaging.Commands; - -namespace NzbDrone.Core.Notifications.NotifyMyAndroid -{ - public class TestNotifyMyAndroidCommand : Command - { - - public override bool SendUpdatesToClient - { - get - { - return true; - } - } - public string ApiKey { get; set; } - public int Priority { get; set; } - } -} diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexClient.cs b/src/NzbDrone.Core/Notifications/Plex/PlexClient.cs index 322af1f06..0b2ffa23e 100644 --- a/src/NzbDrone.Core/Notifications/Plex/PlexClient.cs +++ b/src/NzbDrone.Core/Notifications/Plex/PlexClient.cs @@ -1,14 +1,17 @@ -using NzbDrone.Core.Tv; +using System.Collections.Generic; +using FluentValidation.Results; +using NzbDrone.Common; +using NzbDrone.Core.Tv; namespace NzbDrone.Core.Notifications.Plex { public class PlexClient : NotificationBase { - private readonly IPlexService _plexProvider; + private readonly IPlexService _plexService; - public PlexClient(IPlexService plexProvider) + public PlexClient(IPlexService plexService) { - _plexProvider = plexProvider; + _plexService = plexService; } public override string Link @@ -19,17 +22,26 @@ namespace NzbDrone.Core.Notifications.Plex public override void OnGrab(string message) { const string header = "NzbDrone [TV] - Grabbed"; - _plexProvider.Notify(Settings, header, message); + _plexService.Notify(Settings, header, message); } public override void OnDownload(DownloadMessage message) { const string header = "NzbDrone [TV] - Downloaded"; - _plexProvider.Notify(Settings, header, message.Message); + _plexService.Notify(Settings, header, message.Message); } public override void AfterRename(Series series) { } + + public override ValidationResult Test() + { + var failures = new List(); + + failures.AddIfNotNull(_plexService.Test(Settings)); + + return new ValidationResult(failures); + } } } diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexServer.cs b/src/NzbDrone.Core/Notifications/Plex/PlexServer.cs index 31ff31aaf..267099479 100644 --- a/src/NzbDrone.Core/Notifications/Plex/PlexServer.cs +++ b/src/NzbDrone.Core/Notifications/Plex/PlexServer.cs @@ -1,14 +1,17 @@ -using NzbDrone.Core.Tv; +using System.Collections.Generic; +using FluentValidation.Results; +using NzbDrone.Common; +using NzbDrone.Core.Tv; namespace NzbDrone.Core.Notifications.Plex { public class PlexServer : NotificationBase { - private readonly IPlexService _plexProvider; + private readonly IPlexService _plexService; - public PlexServer(IPlexService plexProvider) + public PlexServer(IPlexService plexService) { - _plexProvider = plexProvider; + _plexService = plexService; } public override string Link @@ -34,8 +37,17 @@ namespace NzbDrone.Core.Notifications.Plex { if (Settings.UpdateLibrary) { - _plexProvider.UpdateLibrary(Settings); + _plexService.UpdateLibrary(Settings); } } + + public override ValidationResult Test() + { + var failures = new List(); + + failures.AddIfNotNull(_plexService.Test(Settings)); + + return new ValidationResult(failures); + } } } diff --git a/src/NzbDrone.Core/Notifications/Plex/PlexService.cs b/src/NzbDrone.Core/Notifications/Plex/PlexService.cs index 357668aae..011a70963 100644 --- a/src/NzbDrone.Core/Notifications/Plex/PlexService.cs +++ b/src/NzbDrone.Core/Notifications/Plex/PlexService.cs @@ -1,12 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; -using System.Xml.Linq; +using FluentValidation.Results; using NLog; -using NzbDrone.Common; using NzbDrone.Common.Http; -using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.Notifications.Plex { @@ -14,9 +11,11 @@ namespace NzbDrone.Core.Notifications.Plex { void Notify(PlexClientSettings settings, string header, string message); void UpdateLibrary(PlexServerSettings settings); + ValidationFailure Test(PlexClientSettings settings); + ValidationFailure Test(PlexServerSettings settings); } - public class PlexService : IPlexService, IExecute, IExecute + public class PlexService : IPlexService { private readonly IHttpProvider _httpProvider; private readonly IPlexServerProxy _plexServerProxy; @@ -84,31 +83,51 @@ namespace NzbDrone.Core.Notifications.Plex return _httpProvider.DownloadString(url); } - public void Execute(TestPlexClientCommand message) + public ValidationFailure Test(PlexClientSettings settings) { - _logger.Debug("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) + try { - throw new Exception("Unable to connect to Plex Client"); + _logger.Debug("Sending Test Notifcation to Plex Client: {0}", settings.Host); + var command = String.Format("ExecBuiltIn(Notification({0}, {1}))", "Test Notification", "Success! Notifications are setup correctly"); + var result = SendCommand(settings.Host, settings.Port, command, settings.Username, settings.Password); + + if (String.IsNullOrWhiteSpace(result) || + result.IndexOf("error", StringComparison.InvariantCultureIgnoreCase) > -1) + { + throw new Exception("Unable to connect to Plex Client"); + } } + catch (Exception ex) + { + _logger.ErrorException("Unable to send test message: " + ex.Message, ex); + return new ValidationFailure("Host", "Unable to send test message"); + } + + return null; } - public void Execute(TestPlexServerCommand message) + public ValidationFailure Test(PlexServerSettings settings) { - if (!GetSectionKeys(new PlexServerSettings - { - Host = message.Host, - Port = message.Port, - Username = message.Username, - Password = message.Password - }).Any()) + try + { + if (!GetSectionKeys(new PlexServerSettings + { + Host = settings.Host, + Port = settings.Port, + Username = settings.Username, + Password = settings.Password + }).Any()) + { + throw new Exception("Unable to connect to Plex Server"); + } + } + catch (Exception ex) { - throw new Exception("Unable to connect to Plex Server"); + _logger.ErrorException("Unable to connect to Plex Server: " + ex.Message, ex); + return new ValidationFailure("Host", "Unable to connect to Plex Server"); } + + return null; } } } diff --git a/src/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs b/src/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs deleted file mode 100644 index 6de213789..000000000 --- a/src/NzbDrone.Core/Notifications/Plex/TestPlexClientCommand.cs +++ /dev/null @@ -1,19 +0,0 @@ -using NzbDrone.Core.Messaging.Commands; - -namespace NzbDrone.Core.Notifications.Plex -{ - public class TestPlexClientCommand : Command - { - public override bool SendUpdatesToClient - { - get - { - return true; - } - } - public string Host { get; set; } - public int Port { get; set; } - public string Username { get; set; } - public string Password { get; set; } - } -} diff --git a/src/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs b/src/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs deleted file mode 100644 index de8394e97..000000000 --- a/src/NzbDrone.Core/Notifications/Plex/TestPlexServerCommand.cs +++ /dev/null @@ -1,20 +0,0 @@ -using NzbDrone.Core.Messaging.Commands; - -namespace NzbDrone.Core.Notifications.Plex -{ - public class TestPlexServerCommand : Command - { - public override bool SendUpdatesToClient - { - get - { - return true; - } - } - - public string Host { get; set; } - public int Port { get; set; } - public string Username { get; set; } - public string Password { get; set; } - } -} diff --git a/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs b/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs index b5264412a..006e301e1 100644 --- a/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs +++ b/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs @@ -1,15 +1,18 @@ -using NzbDrone.Core.Tv; +using System.Collections.Generic; +using FluentValidation.Results; +using NzbDrone.Common; +using NzbDrone.Core.Tv; using Prowlin; namespace NzbDrone.Core.Notifications.Prowl { public class Prowl : NotificationBase { - private readonly IProwlService _prowlProvider; + private readonly IProwlService _prowlService; - public Prowl(IProwlService prowlProvider) + public Prowl(IProwlService prowlService) { - _prowlProvider = prowlProvider; + _prowlService = prowlService; } public override string Link @@ -21,18 +24,27 @@ namespace NzbDrone.Core.Notifications.Prowl { const string title = "Episode Grabbed"; - _prowlProvider.SendNotification(title, message, Settings.ApiKey, (NotificationPriority)Settings.Priority); + _prowlService.SendNotification(title, message, Settings.ApiKey, (NotificationPriority)Settings.Priority); } public override void OnDownload(DownloadMessage message) { const string title = "Episode Downloaded"; - _prowlProvider.SendNotification(title, message.Message, Settings.ApiKey, (NotificationPriority)Settings.Priority); + _prowlService.SendNotification(title, message.Message, Settings.ApiKey, (NotificationPriority)Settings.Priority); } public override void AfterRename(Series series) { } + + public override ValidationResult Test() + { + var failures = new List(); + + failures.AddIfNotNull(_prowlService.Test(Settings)); + + return new ValidationResult(failures); + } } } diff --git a/src/NzbDrone.Core/Notifications/Prowl/ProwlService.cs b/src/NzbDrone.Core/Notifications/Prowl/ProwlService.cs index 7742b2f05..29a8d5af0 100644 --- a/src/NzbDrone.Core/Notifications/Prowl/ProwlService.cs +++ b/src/NzbDrone.Core/Notifications/Prowl/ProwlService.cs @@ -1,6 +1,6 @@ using System; +using FluentValidation.Results; using NLog; -using NzbDrone.Core.Messaging.Commands; using Prowlin; namespace NzbDrone.Core.Notifications.Prowl @@ -8,9 +8,10 @@ namespace NzbDrone.Core.Notifications.Prowl public interface IProwlService { void SendNotification(string title, string message, string apiKey, NotificationPriority priority = NotificationPriority.Normal, string url = null); + ValidationFailure Test(ProwlSettings settings); } - public class ProwlService : IProwlService, IExecute + public class ProwlService : IProwlService { private readonly Logger _logger; @@ -80,14 +81,24 @@ namespace NzbDrone.Core.Notifications.Prowl } } - public void Execute(TestProwlCommand message) + public ValidationFailure Test(ProwlSettings settings) { - Verify(message.ApiKey); + try + { + Verify(settings.ApiKey); - const string title = "Test Notification"; - const string body = "This is a test message from NzbDrone"; + const string title = "Test Notification"; + const string body = "This is a test message from NzbDrone"; + + SendNotification(title, body, settings.ApiKey); + } + catch (Exception ex) + { + _logger.ErrorException("Unable to send test message: " + ex.Message, ex); + return new ValidationFailure("ApiKey", "Unable to send test message"); + } - SendNotification(title, body, message.ApiKey); + return null; } } } diff --git a/src/NzbDrone.Core/Notifications/Prowl/ProwlSettings.cs b/src/NzbDrone.Core/Notifications/Prowl/ProwlSettings.cs index 3d68d3674..14f412402 100644 --- a/src/NzbDrone.Core/Notifications/Prowl/ProwlSettings.cs +++ b/src/NzbDrone.Core/Notifications/Prowl/ProwlSettings.cs @@ -28,7 +28,7 @@ namespace NzbDrone.Core.Notifications.Prowl { get { - return !string.IsNullOrWhiteSpace(ApiKey) && Priority != null & Priority >= -2 && Priority <= 2; + return !string.IsNullOrWhiteSpace(ApiKey) && Priority >= -2 && Priority <= 2; } } diff --git a/src/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs b/src/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs deleted file mode 100644 index 50d02677e..000000000 --- a/src/NzbDrone.Core/Notifications/Prowl/TestProwlCommand.cs +++ /dev/null @@ -1,17 +0,0 @@ -using NzbDrone.Core.Messaging.Commands; - -namespace NzbDrone.Core.Notifications.Prowl -{ - public class TestProwlCommand : Command - { - public override bool SendUpdatesToClient - { - get - { - return true; - } - } - public string ApiKey { get; set; } - public int Priority { get; set; } - } -} diff --git a/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs b/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs index 5f3931075..4d58a8e17 100644 --- a/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs +++ b/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs @@ -1,14 +1,17 @@ -using NzbDrone.Core.Tv; +using System.Collections.Generic; +using FluentValidation.Results; +using NzbDrone.Common; +using NzbDrone.Core.Tv; namespace NzbDrone.Core.Notifications.PushBullet { public class PushBullet : NotificationBase { - private readonly IPushBulletProxy _pushBulletProxy; + private readonly IPushBulletProxy _proxy; - public PushBullet(IPushBulletProxy pushBulletProxy) + public PushBullet(IPushBulletProxy proxy) { - _pushBulletProxy = pushBulletProxy; + _proxy = proxy; } public override string Link @@ -20,18 +23,27 @@ namespace NzbDrone.Core.Notifications.PushBullet { const string title = "Episode Grabbed"; - _pushBulletProxy.SendNotification(title, message, Settings.ApiKey, Settings.DeviceId); + _proxy.SendNotification(title, message, Settings.ApiKey, Settings.DeviceId); } public override void OnDownload(DownloadMessage message) { const string title = "Episode Downloaded"; - _pushBulletProxy.SendNotification(title, message.Message, Settings.ApiKey, Settings.DeviceId); + _proxy.SendNotification(title, message.Message, Settings.ApiKey, Settings.DeviceId); } public override void AfterRename(Series series) { } + + public override ValidationResult Test() + { + var failures = new List(); + + failures.AddIfNotNull(_proxy.Test(Settings)); + + return new ValidationResult(failures); + } } } diff --git a/src/NzbDrone.Core/Notifications/PushBullet/PushBulletProxy.cs b/src/NzbDrone.Core/Notifications/PushBullet/PushBulletProxy.cs index eb85e573f..5f4ddf5ab 100644 --- a/src/NzbDrone.Core/Notifications/PushBullet/PushBulletProxy.cs +++ b/src/NzbDrone.Core/Notifications/PushBullet/PushBulletProxy.cs @@ -1,5 +1,7 @@ using System; -using NzbDrone.Core.Messaging.Commands; +using System.Net; +using FluentValidation.Results; +using NLog; using RestSharp; using NzbDrone.Core.Rest; @@ -8,12 +10,19 @@ namespace NzbDrone.Core.Notifications.PushBullet public interface IPushBulletProxy { void SendNotification(string title, string message, string apiKey, string deviceId); + ValidationFailure Test(PushBulletSettings settings); } - public class PushBulletProxy : IPushBulletProxy, IExecute + public class PushBulletProxy : IPushBulletProxy { + private readonly Logger _logger; private const string URL = "https://api.pushbullet.com/api/pushes"; + public PushBulletProxy(Logger logger) + { + _logger = logger; + } + public void SendNotification(string title, string message, string apiKey, string deviceId) { var client = new RestClient(URL); @@ -45,12 +54,33 @@ namespace NzbDrone.Core.Notifications.PushBullet return request; } - public void Execute(TestPushBulletCommand message) + public ValidationFailure Test(PushBulletSettings settings) { - const string title = "Test Notification"; - const string body = "This is a test message from NzbDrone"; + try + { + const string title = "Test Notification"; + const string body = "This is a test message from NzbDrone"; + + SendNotification(title, body, settings.ApiKey, settings.DeviceId); + } + catch (RestException ex) + { + if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) + { + _logger.ErrorException("API Key is invalid: " + ex.Message, ex); + return new ValidationFailure("ApiKey", "API Key is invalid"); + } + + _logger.ErrorException("Unable to send test message: " + ex.Message, ex); + return new ValidationFailure("ApiKey", "Unable to send test message"); + } + catch (Exception ex) + { + _logger.ErrorException("Unable to send test message: " + ex.Message, ex); + return new ValidationFailure("", "Unable to send test message"); + } - SendNotification(title, body, message.ApiKey, message.DeviceId); + return null; } } } diff --git a/src/NzbDrone.Core/Notifications/PushBullet/TestPushBulletCommand.cs b/src/NzbDrone.Core/Notifications/PushBullet/TestPushBulletCommand.cs deleted file mode 100644 index fd0f6732d..000000000 --- a/src/NzbDrone.Core/Notifications/PushBullet/TestPushBulletCommand.cs +++ /dev/null @@ -1,18 +0,0 @@ -using NzbDrone.Core.Messaging.Commands; - -namespace NzbDrone.Core.Notifications.PushBullet -{ - public class TestPushBulletCommand : Command - { - - public override bool SendUpdatesToClient - { - get - { - return true; - } - } - public string ApiKey { get; set; } - public string DeviceId { get; set; } - } -} diff --git a/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs b/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs index 1e14e34fb..8b6f7c3de 100644 --- a/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs +++ b/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs @@ -1,14 +1,17 @@ -using NzbDrone.Core.Tv; +using System.Collections.Generic; +using FluentValidation.Results; +using NzbDrone.Common; +using NzbDrone.Core.Tv; namespace NzbDrone.Core.Notifications.Pushover { public class Pushover : NotificationBase { - private readonly IPushoverProxy _pushoverProxy; + private readonly IPushoverProxy _proxy; - public Pushover(IPushoverProxy pushoverProxy) + public Pushover(IPushoverProxy proxy) { - _pushoverProxy = pushoverProxy; + _proxy = proxy; } public override string Link @@ -20,18 +23,27 @@ namespace NzbDrone.Core.Notifications.Pushover { const string title = "Episode Grabbed"; - _pushoverProxy.SendNotification(title, message, Settings.ApiKey, Settings.UserKey, (PushoverPriority)Settings.Priority, Settings.Sound); + _proxy.SendNotification(title, message, Settings.ApiKey, Settings.UserKey, (PushoverPriority)Settings.Priority, Settings.Sound); } public override void OnDownload(DownloadMessage message) { const string title = "Episode Downloaded"; - _pushoverProxy.SendNotification(title, message.Message, Settings.ApiKey, Settings.UserKey, (PushoverPriority)Settings.Priority, Settings.Sound); + _proxy.SendNotification(title, message.Message, Settings.ApiKey, Settings.UserKey, (PushoverPriority)Settings.Priority, Settings.Sound); } public override void AfterRename(Series series) { } + + public override ValidationResult Test() + { + var failures = new List(); + + failures.AddIfNotNull(_proxy.Test(Settings)); + + return new ValidationResult(failures); + } } } diff --git a/src/NzbDrone.Core/Notifications/Pushover/PushoverService.cs b/src/NzbDrone.Core/Notifications/Pushover/PushoverService.cs index cf7b055f0..484fb1146 100644 --- a/src/NzbDrone.Core/Notifications/Pushover/PushoverService.cs +++ b/src/NzbDrone.Core/Notifications/Pushover/PushoverService.cs @@ -1,5 +1,7 @@ -using NzbDrone.Common; -using NzbDrone.Core.Messaging.Commands; +using System; +using FluentValidation.Results; +using NLog; +using NzbDrone.Common; using RestSharp; using NzbDrone.Core.Rest; @@ -8,12 +10,19 @@ namespace NzbDrone.Core.Notifications.Pushover public interface IPushoverProxy { void SendNotification(string title, string message, string apiKey, string userKey, PushoverPriority priority, string sound); + ValidationFailure Test(PushoverSettings settings); } - public class PushoverProxy : IPushoverProxy, IExecute + public class PushoverProxy : IPushoverProxy { + private readonly Logger _logger; private const string URL = "https://api.pushover.net/1/messages.json"; + public PushoverProxy(Logger logger) + { + _logger = logger; + } + public void SendNotification(string title, string message, string apiKey, string userKey, PushoverPriority priority, string sound) { var client = new RestClient(URL); @@ -30,12 +39,22 @@ namespace NzbDrone.Core.Notifications.Pushover client.ExecuteAndValidate(request); } - public void Execute(TestPushoverCommand message) + public ValidationFailure Test(PushoverSettings settings) { - const string title = "Test Notification"; - const string body = "This is a test message from NzbDrone"; - - SendNotification(title, body, message.ApiKey, message.UserKey, (PushoverPriority)message.Priority, message.Sound); + try + { + const string title = "Test Notification"; + const string body = "This is a test message from NzbDrone"; + + SendNotification(title, body, settings.ApiKey, settings.UserKey, (PushoverPriority)settings.Priority, settings.Sound); + } + catch (Exception ex) + { + _logger.ErrorException("Unable to send test message: " + ex.Message, ex); + return new ValidationFailure("ApiKey", "Unable to send test message"); + } + + return null; } } } diff --git a/src/NzbDrone.Core/Notifications/Pushover/PushoverSettings.cs b/src/NzbDrone.Core/Notifications/Pushover/PushoverSettings.cs index db5197022..2bd17b79d 100644 --- a/src/NzbDrone.Core/Notifications/Pushover/PushoverSettings.cs +++ b/src/NzbDrone.Core/Notifications/Pushover/PushoverSettings.cs @@ -34,7 +34,7 @@ namespace NzbDrone.Core.Notifications.Pushover { get { - return !string.IsNullOrWhiteSpace(UserKey) && Priority != null & Priority >= -1 && Priority <= 2; + return !string.IsNullOrWhiteSpace(UserKey) && Priority >= -1 && Priority <= 2; } } diff --git a/src/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs b/src/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs deleted file mode 100644 index d216e08ce..000000000 --- a/src/NzbDrone.Core/Notifications/Pushover/TestPushoverCommand.cs +++ /dev/null @@ -1,21 +0,0 @@ -using NzbDrone.Core.Messaging.Commands; - -namespace NzbDrone.Core.Notifications.Pushover -{ - public class TestPushoverCommand : Command - { - - public override bool SendUpdatesToClient - { - get - { - return true; - } - } - - public string ApiKey { get; set; } - public string UserKey { get; set; } - public int Priority { get; set; } - public string Sound { get; set; } - } -} diff --git a/src/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs b/src/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs deleted file mode 100644 index cb8acb361..000000000 --- a/src/NzbDrone.Core/Notifications/Xbmc/TestXbmcCommand.cs +++ /dev/null @@ -1,21 +0,0 @@ -using NzbDrone.Core.Messaging.Commands; - -namespace NzbDrone.Core.Notifications.Xbmc -{ - public class TestXbmcCommand : Command - { - public override bool SendUpdatesToClient - { - get - { - return true; - } - } - - public string Host { get; set; } - public int Port { get; set; } - public string Username { get; set; } - public string Password { get; set; } - public int DisplayTime { get; set; } - } -} diff --git a/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs b/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs index f111ae7b4..dcf616358 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs @@ -1,15 +1,18 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; +using FluentValidation.Results; +using NzbDrone.Common; using NzbDrone.Core.Tv; namespace NzbDrone.Core.Notifications.Xbmc { public class Xbmc : NotificationBase { - private readonly IXbmcService _xbmcProvider; + private readonly IXbmcService _xbmcService; - public Xbmc(IXbmcService xbmcProvider) + public Xbmc(IXbmcService xbmcService) { - _xbmcProvider = xbmcProvider; + _xbmcService = xbmcService; } public override string Link @@ -23,7 +26,7 @@ namespace NzbDrone.Core.Notifications.Xbmc if (Settings.Notify) { - _xbmcProvider.Notify(Settings, header, message); + _xbmcService.Notify(Settings, header, message); } } @@ -33,7 +36,7 @@ namespace NzbDrone.Core.Notifications.Xbmc if (Settings.Notify) { - _xbmcProvider.Notify(Settings, header, message.Message); + _xbmcService.Notify(Settings, header, message.Message); } UpdateAndClean(message.Series, message.OldFiles.Any()); @@ -44,16 +47,25 @@ namespace NzbDrone.Core.Notifications.Xbmc UpdateAndClean(series); } + public override ValidationResult Test() + { + var failures = new List(); + + failures.AddIfNotNull(_xbmcService.Test(Settings)); + + return new ValidationResult(failures); + } + private void UpdateAndClean(Series series, bool clean = true) { if (Settings.UpdateLibrary) { - _xbmcProvider.Update(Settings, series); + _xbmcService.Update(Settings, series); } if (clean && Settings.CleanLibrary) { - _xbmcProvider.Clean(Settings); + _xbmcService.Clean(Settings); } } } diff --git a/src/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs b/src/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs index ce94da593..de38e5fed 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/XbmcService.cs @@ -1,13 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; +using FluentValidation.Results; using Newtonsoft.Json.Linq; using NLog; -using NzbDrone.Common; using NzbDrone.Common.Http; using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Serializer; -using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Notifications.Xbmc.Model; using NzbDrone.Core.Tv; @@ -19,18 +18,20 @@ namespace NzbDrone.Core.Notifications.Xbmc void Update(XbmcSettings settings, Series series); void Clean(XbmcSettings settings); XbmcVersion GetJsonVersion(XbmcSettings settings); + ValidationFailure Test(XbmcSettings settings); } - public class XbmcService : IXbmcService, IExecute + public class XbmcService : IXbmcService { - private static readonly Logger Logger = NzbDroneLogger.GetLogger(); private readonly IHttpProvider _httpProvider; private readonly IEnumerable _apiProviders; + private readonly Logger _logger; - public XbmcService(IHttpProvider httpProvider, IEnumerable apiProviders) + public XbmcService(IHttpProvider httpProvider, IEnumerable apiProviders, Logger logger) { _httpProvider = httpProvider; _apiProviders = apiProviders; + _logger = logger; } public void Notify(XbmcSettings settings, string title, string message) @@ -62,7 +63,7 @@ namespace NzbDrone.Core.Notifications.Xbmc var response = _httpProvider.PostCommand(settings.Address, settings.Username, settings.Password, postJson.ToString()); - Logger.Debug("Getting version from response: " + response); + _logger.Debug("Getting version from response: " + response); var result = Json.Deserialize>(response); var versionObject = result.Result.Property("version"); @@ -78,7 +79,7 @@ namespace NzbDrone.Core.Notifications.Xbmc catch (Exception ex) { - Logger.DebugException(ex.Message, ex); + _logger.DebugException(ex.Message, ex); } return new XbmcVersion(); @@ -98,27 +99,28 @@ namespace NzbDrone.Core.Notifications.Xbmc return apiProvider; } - public void Execute(TestXbmcCommand message) + public ValidationFailure Test(XbmcSettings settings) { - var settings = new XbmcSettings - { - Host = message.Host, - Port = message.Port, - Username = message.Username, - Password = message.Password, - DisplayTime = message.DisplayTime - }; - - Logger.Debug("Determining version of XBMC Host: {0}", settings.Address); - var version = GetJsonVersion(settings); - Logger.Debug("Version is: {0}", version); + try + { + _logger.Debug("Determining version of XBMC Host: {0}", settings.Address); + var version = GetJsonVersion(settings); + _logger.Debug("Version is: {0}", version); - if (version == new XbmcVersion(0)) + if (version == new XbmcVersion(0)) + { + throw new InvalidXbmcVersionException("Verion received from XBMC is invalid, please correct your settings."); + } + + Notify(settings, "Test Notification", "Success! XBMC has been successfully configured!"); + } + catch (Exception ex) { - throw new InvalidXbmcVersionException("Verion received from XBMC is invalid, please correct your settings."); + _logger.ErrorException("Unable to send test message: " + ex.Message, ex); + return new ValidationFailure("Host", "Unable to send test message"); } - Notify(settings, "Test Notification", "Success! XBMC has been successfully configured!"); + return null; } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 9e4e64c3f..fbba0c940 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -253,21 +253,17 @@ - - - - @@ -419,12 +415,10 @@ - - @@ -480,9 +474,7 @@ - - @@ -523,27 +515,22 @@ - - - - - diff --git a/src/NzbDrone.Core/ThingiProvider/IProvider.cs b/src/NzbDrone.Core/ThingiProvider/IProvider.cs index 4947c3d8b..bd30c0ab7 100644 --- a/src/NzbDrone.Core/ThingiProvider/IProvider.cs +++ b/src/NzbDrone.Core/ThingiProvider/IProvider.cs @@ -1,6 +1,6 @@ - -using System; +using System; using System.Collections.Generic; +using FluentValidation.Results; namespace NzbDrone.Core.ThingiProvider { @@ -10,5 +10,6 @@ namespace NzbDrone.Core.ThingiProvider IEnumerable DefaultDefinitions { get; } ProviderDefinition Definition { get; set; } + ValidationResult Test(); } } \ No newline at end of file diff --git a/src/NzbDrone.Core/ThingiProvider/IProviderFactory.cs b/src/NzbDrone.Core/ThingiProvider/IProviderFactory.cs index b70b1e06a..eb960f26a 100644 --- a/src/NzbDrone.Core/ThingiProvider/IProviderFactory.cs +++ b/src/NzbDrone.Core/ThingiProvider/IProviderFactory.cs @@ -1,5 +1,5 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; +using FluentValidation.Results; namespace NzbDrone.Core.ThingiProvider { @@ -10,10 +10,11 @@ namespace NzbDrone.Core.ThingiProvider List All(); List GetAvailableProviders(); TProviderDefinition Get(int id); - TProviderDefinition Create(TProviderDefinition indexer); - void Update(TProviderDefinition indexer); + TProviderDefinition Create(TProviderDefinition definition); + void Update(TProviderDefinition definition); void Delete(int id); IEnumerable GetDefaultDefinitions(); IEnumerable GetPresetDefinitions(TProviderDefinition providerDefinition); + ValidationResult Test(TProviderDefinition definition); } } \ No newline at end of file diff --git a/src/NzbDrone.Core/ThingiProvider/ProviderDefinition.cs b/src/NzbDrone.Core/ThingiProvider/ProviderDefinition.cs index c9b20fe47..4ed723e4f 100644 --- a/src/NzbDrone.Core/ThingiProvider/ProviderDefinition.cs +++ b/src/NzbDrone.Core/ThingiProvider/ProviderDefinition.cs @@ -1,14 +1,16 @@ -using NzbDrone.Core.Datastore; +using System; +using NzbDrone.Core.Datastore; namespace NzbDrone.Core.ThingiProvider { public abstract class ProviderDefinition : ModelBase { private IProviderConfig _settings; - public string Name { get; set; } - public string Implementation { get; set; } - public string ConfigContract { get; set; } + public String Name { get; set; } + public String Implementation { get; set; } + public String ConfigContract { get; set; } + public virtual Boolean Enable { get; set; } public IProviderConfig Settings { @@ -26,4 +28,4 @@ namespace NzbDrone.Core.ThingiProvider } } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs b/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs index fa9cd32c2..a8dda0bec 100644 --- a/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs +++ b/src/NzbDrone.Core/ThingiProvider/ProviderFactory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using FluentValidation.Results; using NLog; using NzbDrone.Common.Composition; using NzbDrone.Core.Lifecycle; @@ -75,6 +76,11 @@ namespace NzbDrone.Core.ThingiProvider return definitions; } + public ValidationResult Test(TProviderDefinition definition) + { + return GetInstance(definition).Test(); + } + public List GetAvailableProviders() { return Active().Select(GetInstance).ToList(); diff --git a/src/UI/Settings/DownloadClient/DownloadClientModel.js b/src/UI/Settings/DownloadClient/DownloadClientModel.js index 3702cf7dc..9d88dd7ea 100644 --- a/src/UI/Settings/DownloadClient/DownloadClientModel.js +++ b/src/UI/Settings/DownloadClient/DownloadClientModel.js @@ -1,9 +1,9 @@ 'use strict'; define([ - 'backbone.deepmodel' -], function (DeepModel) { - return DeepModel.DeepModel.extend({ + 'Settings/ProviderSettingsModelBase' +], function (ProviderSettingsModelBase) { + return ProviderSettingsModelBase.extend({ }); }); diff --git a/src/UI/Settings/DownloadClient/Edit/DownloadClientEditView.js b/src/UI/Settings/DownloadClient/Edit/DownloadClientEditView.js index 98fa8eea0..31bd5f194 100644 --- a/src/UI/Settings/DownloadClient/Edit/DownloadClientEditView.js +++ b/src/UI/Settings/DownloadClient/Edit/DownloadClientEditView.js @@ -78,14 +78,7 @@ define([ }, _test: function () { - var testCommand = 'test{0}'.format(this.model.get('implementation')); - var properties = {}; - - _.each(this.model.get('fields'), function (field) { - properties[field.name] = field.value; - }); - - CommandController.Execute(testCommand, properties); + this.model.test(); } }); diff --git a/src/UI/Settings/Indexers/Edit/IndexerEditView.js b/src/UI/Settings/Indexers/Edit/IndexerEditView.js index 4a77072d7..c67fb9260 100644 --- a/src/UI/Settings/Indexers/Edit/IndexerEditView.js +++ b/src/UI/Settings/Indexers/Edit/IndexerEditView.js @@ -82,14 +82,7 @@ define([ }, _test: function () { - var testCommand = 'test{0}'.format(this.model.get('implementation')); - var properties = {}; - - _.each(this.model.get('fields'), function (field) { - properties[field.name] = field.value; - }); - - CommandController.Execute(testCommand, properties); + this.model.test(); } }); diff --git a/src/UI/Settings/Indexers/Edit/IndexerEditViewTemplate.html b/src/UI/Settings/Indexers/Edit/IndexerEditViewTemplate.html index 75ec1d284..3dc1f3eda 100644 --- a/src/UI/Settings/Indexers/Edit/IndexerEditViewTemplate.html +++ b/src/UI/Settings/Indexers/Edit/IndexerEditViewTemplate.html @@ -46,8 +46,7 @@ {{/if}} - - +
diff --git a/src/UI/Settings/Indexers/IndexerModel.js b/src/UI/Settings/Indexers/IndexerModel.js index 3702cf7dc..9d88dd7ea 100644 --- a/src/UI/Settings/Indexers/IndexerModel.js +++ b/src/UI/Settings/Indexers/IndexerModel.js @@ -1,9 +1,9 @@ 'use strict'; define([ - 'backbone.deepmodel' -], function (DeepModel) { - return DeepModel.DeepModel.extend({ + 'Settings/ProviderSettingsModelBase' +], function (ProviderSettingsModelBase) { + return ProviderSettingsModelBase.extend({ }); }); diff --git a/src/UI/Settings/Metadata/MetadataModel.js b/src/UI/Settings/Metadata/MetadataModel.js index 5e08858af..c5b01ab1c 100644 --- a/src/UI/Settings/Metadata/MetadataModel.js +++ b/src/UI/Settings/Metadata/MetadataModel.js @@ -1,10 +1,10 @@ 'use strict'; -define( - [ - 'backbone.deepmodel' - ], function (DeepModel) { - return DeepModel.DeepModel.extend({ - }); +define([ + 'Settings/ProviderSettingsModelBase' +], function (ProviderSettingsModelBase) { + return ProviderSettingsModelBase.extend({ + }); +}); diff --git a/src/UI/Settings/Notifications/Edit/NotificationEditView.js b/src/UI/Settings/Notifications/Edit/NotificationEditView.js index 0855d5482..cb5ff873c 100644 --- a/src/UI/Settings/Notifications/Edit/NotificationEditView.js +++ b/src/UI/Settings/Notifications/Edit/NotificationEditView.js @@ -83,14 +83,7 @@ define([ }, _test: function () { - var testCommand = 'test{0}'.format(this.model.get('implementation')); - var properties = {}; - - _.each(this.model.get('fields'), function (field) { - properties[field.name] = field.value; - }); - - CommandController.Execute(testCommand, properties); + this.model.test(); }, _onDownloadChanged: function () { diff --git a/src/UI/Settings/Notifications/NotificationModel.js b/src/UI/Settings/Notifications/NotificationModel.js index 9eb8e5552..4aaabc948 100644 --- a/src/UI/Settings/Notifications/NotificationModel.js +++ b/src/UI/Settings/Notifications/NotificationModel.js @@ -1,10 +1,9 @@ 'use strict'; + define([ - 'Settings/SettingsModelBase' -], function (ModelBase) { - return ModelBase.extend({ + 'Settings/ProviderSettingsModelBase' +], function (ProviderSettingsModelBase) { + return ProviderSettingsModelBase.extend({ - successMessage: 'Notification Saved', - errorMessage : 'Couldn\'t save notification' }); }); diff --git a/src/UI/Settings/ProviderSettingsModelBase.js b/src/UI/Settings/ProviderSettingsModelBase.js new file mode 100644 index 000000000..256caef80 --- /dev/null +++ b/src/UI/Settings/ProviderSettingsModelBase.js @@ -0,0 +1,36 @@ +'use strict'; + +define([ + 'jquery', + 'backbone.deepmodel', + 'Shared/Messenger' +], function ($, DeepModel, Messenger) { + return DeepModel.DeepModel.extend({ + + test: function () { + var self = this; + + this.trigger('validation:sync'); + + var params = {}; + + params.url = this.collection.url + '/test'; + params.contentType = 'application/json'; + params.data = JSON.stringify(this.toJSON()); + params.type = 'POST'; + params.isValidatedCall = true; + + var promise = $.ajax(params); + + Messenger.monitor({ + promise : promise, + successMessage : 'Testing \'{0}\' completed'.format(this.get('name')), + errorMessage : 'Testing \'{0}\' failed'.format(this.get('name')) + }); + + promise.fail(function (response) { + self.trigger('validation:failed', response); + }); + } + }); +}); diff --git a/src/UI/jQuery/jquery.validation.js b/src/UI/jQuery/jquery.validation.js index 210f94d69..00f07e24b 100644 --- a/src/UI/jQuery/jquery.validation.js +++ b/src/UI/jQuery/jquery.validation.js @@ -57,13 +57,17 @@ define( }; $.fn.addFormError = function (error) { - var t1 = this.find('.form-horizontal'); - var t2 = this.find('.form-horizontal').parent(); - - this.prepend('
' + error.errorMessage + '
'); + if (this.find('.modal-body')) { + this.find('.modal-body').prepend('
' + error.errorMessage + '
'); + } + + else { + this.prepend('
' + error.errorMessage + '
'); + } }; $.fn.removeAllErrors = function () { + this.find('.has-error').removeClass('has-error'); this.find('.error').removeClass('error'); this.find('.validation-errors').removeClass('alert').removeClass('alert-danger').html(''); this.find('.validation-error').remove();