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
This commit is contained in:
Mark McDowall 2014-07-04 01:09:48 -07:00
parent c5bd8b27fb
commit 7af782d353
70 changed files with 727 additions and 591 deletions

View File

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

View File

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

View File

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

View File

@ -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<NzbgetSettings>, IExecute<TestNzbgetCommand>
public class Nzbget : DownloadClientBase<NzbgetSettings>
{
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<ValidationFailure>();
var config = _proxy.GetConfig(settings);
var categories = GetCategories(config);
failures.AddIfNotNull(TestConnection());
failures.AddIfNotNull(TestCategory());
if (!settings.TvCategory.IsNullOrWhiteSpace() && !categories.Any(v => v.Name == settings.TvCategory))
{
throw new ApplicationException("Category does not exist");
}
return new ValidationResult(failures);
}
public void Execute(TestNzbgetCommand message)
private ValidationFailure TestConnection()
{
var settings = new NzbgetSettings();
settings.InjectFrom(message);
try
{
_proxy.GetVersion(Settings);
}
catch (Exception ex)
{
_logger.ErrorException(ex.Message, ex);
return new ValidationFailure("Host", "Unable to connect to NZBGet");
}
Test(settings);
return null;
}
private ValidationFailure TestCategory()
{
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");
}
return null;
}
// Javascript doesn't support 64 bit integers natively so json officially doesn't either.

View File

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

View File

@ -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<PneumaticSettings>, IExecute<TestPneumaticCommand>
public class Pneumatic : DownloadClientBase<PneumaticSettings>
{
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<ValidationFailure>();
failures.AddIfNotNull(TestWrite(Settings.NzbFolder, "NzbFolder"));
return new ValidationResult(failures);
}
private void PerformWriteTest(string folder)
private ValidationFailure TestWrite(String folder, String propertyName)
{
var testPath = Path.Combine(folder, "drone_test.txt");
_diskProvider.WriteAllText(testPath, DateTime.Now.ToString());
_diskProvider.DeleteFile(testPath);
}
if (!_diskProvider.FolderExists(folder))
{
return new ValidationFailure(propertyName, "Folder does not exist");
}
public void Execute(TestPneumaticCommand message)
{
var settings = new PneumaticSettings();
settings.InjectFrom(message);
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");
}
Test(settings);
return null;
}
}
}

View File

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

View File

@ -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<SabnzbdSettings>, IExecute<TestSabnzbdCommand>
public class Sabnzbd : DownloadClientBase<SabnzbdSettings>
{
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<ValidationFailure>();
if (!settings.TvCategory.IsNullOrWhiteSpace() && !categories.Any(v => v == settings.TvCategory))
{
throw new ApplicationException("Category does not exist");
}
failures.AddIfNotNull(TestConnection());
failures.AddIfNotNull(TestCategory());
return new ValidationResult(failures);
}
public void Execute(TestSabnzbdCommand message)
private ValidationFailure TestConnection()
{
var settings = new SabnzbdSettings();
settings.InjectFrom(message);
try
{
_proxy.GetCategories(Settings);
}
catch (Exception ex)
{
_logger.ErrorException(ex.Message, ex);
return new ValidationFailure("Host", "Unable to connect to SABnzbd");
}
Test(settings);
return null;
}
private ValidationFailure TestCategory()
{
var categories = _proxy.GetCategories(Settings);
if (!Settings.TvCategory.IsNullOrWhiteSpace() && !categories.Any(v => v == Settings.TvCategory))
{
return new ValidationFailure("TvCategory", "Category does not exist");
}
return null;
}
}
}

View File

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

View File

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

View File

@ -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<UsenetBlackholeSettings>, IExecute<TestUsenetBlackholeCommand>
public class UsenetBlackhole : DownloadClientBase<UsenetBlackholeSettings>
{
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<ValidationFailure>();
failures.AddIfNotNull(TestWrite(Settings.NzbFolder, "NzbFolder"));
failures.AddIfNotNull(TestWrite(Settings.WatchFolder, "WatchFolder"));
return new ValidationResult(failures);
}
private void PerformWriteTest(string folder)
private ValidationFailure TestWrite(String folder, String propertyName)
{
var testPath = Path.Combine(folder, "drone_test.txt");
_diskProvider.WriteAllText(testPath, DateTime.Now.ToString());
_diskProvider.DeleteFile(testPath);
}
if (!_diskProvider.FolderExists(folder))
{
return new ValidationFailure(propertyName, "Folder does not exist");
}
public void Execute(TestUsenetBlackholeCommand message)
{
var settings = new UsenetBlackholeSettings();
settings.InjectFrom(message);
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");
}
Test(settings);
return null;
}
}
}

View File

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

View File

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

View File

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

View File

@ -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<string>();
}
public override ValidationResult Test()
{
return new ValidationResult();
}
private String GetSearchQuery(string title, int absoluteEpisodeNumber)
{
var match = RemoveSingleCharacterRegex.Match(title);

View File

@ -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<string>();
}
public override ValidationResult Test()
{
return new ValidationResult();
}
private IEnumerable<String> GetTitleSearchStrings(string title, int absoluteEpisodeNumber)
{
var formats = new[] { "{0}%20{1:00}", "{0}%20-%20{1:00}" };

View File

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

View File

@ -4,8 +4,6 @@ namespace NzbDrone.Core.Indexers
{
public class IndexerDefinition : ProviderDefinition
{
public bool Enable { get; set; }
public DownloadProtocol Protocol { get; set; }
}
}
}

View File

@ -14,7 +14,6 @@ namespace NzbDrone.Core.Indexers
public class IndexerFactory : ProviderFactory<IIndexer, IndexerDefinition>, 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);

View File

@ -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<NewznabSettings>
{
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<ValidationFailure> { 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<ValidationFailure> { failure });
}
return new ValidationResult();
}
private static string NewsnabifyTitle(string title)
{
return title.Replace("+", "%20");

View File

@ -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<string>();
}
public override ValidationResult Test()
{
return new ValidationResult();
}
}
}

View File

@ -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<string>();
}
public override ValidationResult Test()
{
return new ValidationResult();
}
}
}

View File

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

View File

@ -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<MetadataFile> AfterRename(Series series, List<MetadataFile> existingMetadataFiles, List<EpisodeFile> episodeFiles);
public abstract MetadataFile FindMetadataFile(Series series, string path);

View File

@ -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<EmailSettings>
{
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<ValidationFailure>();
failures.AddIfNotNull(_emailService.Test(Settings));
return new ValidationResult(failures);
}
}
}

View File

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

View File

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

View File

@ -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<GrowlSettings>
{
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<ValidationFailure>();
failures.AddIfNotNull(_growlService.Test(Settings));
return new ValidationResult(failures);
}
}
}

View File

@ -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<TestGrowlCommand>
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<NotificationType> _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;
}
}
}

View File

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

View File

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

View File

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

View File

@ -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<NotifyMyAndroidSettings>
{
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<ValidationFailure>();
failures.AddIfNotNull(_proxy.Test(Settings));
return new ValidationResult(failures);
}
}
}

View File

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

View File

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

View File

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

View File

@ -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<PlexClientSettings>
{
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<ValidationFailure>();
failures.AddIfNotNull(_plexService.Test(Settings));
return new ValidationResult(failures);
}
}
}

View File

@ -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<PlexServerSettings>
{
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<ValidationFailure>();
failures.AddIfNotNull(_plexService.Test(Settings));
return new ValidationResult(failures);
}
}
}

View File

@ -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<TestPlexClientCommand>, IExecute<TestPlexServerCommand>
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
{
throw new Exception("Unable to connect to Plex Server");
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)
{
_logger.ErrorException("Unable to connect to Plex Server: " + ex.Message, ex);
return new ValidationFailure("Host", "Unable to connect to Plex Server");
}
return null;
}
}
}

View File

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

View File

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

View File

@ -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<ProwlSettings>
{
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<ValidationFailure>();
failures.AddIfNotNull(_prowlService.Test(Settings));
return new ValidationResult(failures);
}
}
}

View File

@ -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<TestProwlCommand>
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, message.ApiKey);
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");
}
return null;
}
}
}

View File

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

View File

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

View File

@ -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<PushBulletSettings>
{
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<ValidationFailure>();
failures.AddIfNotNull(_proxy.Test(Settings));
return new ValidationResult(failures);
}
}
}

View File

@ -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<TestPushBulletCommand>
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, message.ApiKey, message.DeviceId);
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");
}
return null;
}
}
}

View File

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

View File

@ -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<PushoverSettings>
{
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<ValidationFailure>();
failures.AddIfNotNull(_proxy.Test(Settings));
return new ValidationResult(failures);
}
}
}

View File

@ -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<TestPushoverCommand>
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";
try
{
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);
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;
}
}
}

View File

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

View File

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

View File

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

View File

@ -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<XbmcSettings>
{
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<ValidationFailure>();
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);
}
}
}

View File

@ -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<TestXbmcCommand>
public class XbmcService : IXbmcService
{
private static readonly Logger Logger = NzbDroneLogger.GetLogger();
private readonly IHttpProvider _httpProvider;
private readonly IEnumerable<IApiProvider> _apiProviders;
private readonly Logger _logger;
public XbmcService(IHttpProvider httpProvider, IEnumerable<IApiProvider> apiProviders)
public XbmcService(IHttpProvider httpProvider, IEnumerable<IApiProvider> 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<XbmcJsonResult<JObject>>(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);
if (version == new XbmcVersion(0))
try
{
throw new InvalidXbmcVersionException("Verion received from XBMC is invalid, please correct your settings.");
_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))
{
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)
{
_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;
}
}
}

View File

@ -253,21 +253,17 @@
<Compile Include="Download\Clients\Nzbget\NzbgetPostQueueItem.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabnzbdDownloadStatus.cs" />
<Compile Include="Download\Clients\UsenetBlackhole\UsenetBlackhole.cs" />
<Compile Include="Download\Clients\UsenetBlackhole\TestUsenetBlackholeCommand.cs" />
<Compile Include="Download\Clients\UsenetBlackhole\UsenetBlackholeSettings.cs" />
<Compile Include="Download\Clients\DownloadClientException.cs" />
<Compile Include="Download\Clients\Pneumatic\PneumaticSettings.cs" />
<Compile Include="Download\Clients\Nzbget\NzbgetHistoryItem.cs" />
<Compile Include="Download\Clients\Nzbget\NzbgetParameter.cs" />
<Compile Include="Download\Clients\Nzbget\NzbgetSettings.cs" />
<Compile Include="Download\Clients\Nzbget\TestNzbgetCommand.cs" />
<Compile Include="Download\Clients\Pneumatic\Pneumatic.cs" />
<Compile Include="Download\Clients\Pneumatic\TestPneumaticCommand.cs" />
<Compile Include="Download\Clients\Sabnzbd\Responses\SabnzbdAddResponse.cs" />
<Compile Include="Download\Clients\Sabnzbd\Responses\SabnzbdCategoryResponse.cs" />
<Compile Include="Download\Clients\Sabnzbd\Responses\SabnzbdVersionResponse.cs" />
<Compile Include="Download\Clients\Sabnzbd\SabnzbdSettings.cs" />
<Compile Include="Download\Clients\Sabnzbd\TestSabnzbdCommand.cs" />
<Compile Include="Download\CompletedDownloadService.cs" />
<Compile Include="Download\DownloadClientBase.cs" />
<Compile Include="Download\DownloadClientDefinition.cs" />
@ -419,12 +415,10 @@
<Compile Include="Notifications\PushBullet\PushBullet.cs" />
<Compile Include="Notifications\PushBullet\PushBulletProxy.cs" />
<Compile Include="Notifications\PushBullet\PushBulletSettings.cs" />
<Compile Include="Notifications\PushBullet\TestPushBulletCommand.cs" />
<Compile Include="Notifications\NotifyMyAndroid\NotifyMyAndroid.cs" />
<Compile Include="Notifications\NotifyMyAndroid\NotifyMyAndroidPriority.cs" />
<Compile Include="Notifications\NotifyMyAndroid\NotifyMyAndroidProxy.cs" />
<Compile Include="Notifications\NotifyMyAndroid\NotifyMyAndroidSettings.cs" />
<Compile Include="Notifications\NotifyMyAndroid\TestNotifyMyAndroidCommand.cs" />
<Compile Include="MediaFiles\Commands\BackendCommandAttribute.cs" />
<Compile Include="Messaging\Commands\BackendCommandAttribute.cs" />
<Compile Include="Messaging\Commands\Command.cs" />
@ -480,9 +474,7 @@
<Compile Include="MediaFiles\RenameEpisodeFileService.cs" />
<Compile Include="MediaFiles\SameFilenameException.cs" />
<Compile Include="MediaFiles\UpgradeMediaFileService.cs" />
<Compile Include="Notifications\Email\TestEmailCommand.cs" />
<Compile Include="Notifications\Growl\GrowlSettings.cs" />
<Compile Include="Notifications\Growl\TestGrowlCommand.cs" />
<Compile Include="Notifications\INotification.cs" />
<Compile Include="Notifications\NotificationRepository.cs" />
<Compile Include="Fluent.cs" />
@ -523,27 +515,22 @@
<Compile Include="MetadataSource\Trakt\Images.cs" />
<Compile Include="MetadataSource\Trakt\Season.cs" />
<Compile Include="MetadataSource\Trakt\FullShow.cs" />
<Compile Include="Notifications\Plex\TestPlexServerCommand.cs" />
<Compile Include="Notifications\Plex\PlexServer.cs" />
<Compile Include="Notifications\Plex\PlexClientSettings.cs" />
<Compile Include="Notifications\Plex\PlexServerSettings.cs" />
<Compile Include="Notifications\Plex\TestPlexClientCommand.cs" />
<Compile Include="Notifications\Prowl\InvalidApiKeyException.cs" />
<Compile Include="Notifications\Prowl\ProwlPriority.cs" />
<Compile Include="Notifications\Prowl\ProwlSettings.cs" />
<Compile Include="Notifications\Email\EmailSettings.cs" />
<Compile Include="Notifications\Prowl\TestProwlCommand.cs" />
<Compile Include="Notifications\Pushover\InvalidResponseException.cs" />
<Compile Include="Notifications\Pushover\Pushover.cs" />
<Compile Include="Notifications\Pushover\PushoverPriority.cs" />
<Compile Include="Notifications\Pushover\PushoverService.cs" />
<Compile Include="Notifications\Pushover\PushoverSettings.cs" />
<Compile Include="Notifications\Pushover\TestPushoverCommand.cs" />
<Compile Include="Notifications\Xbmc\HttpApiProvider.cs" />
<Compile Include="Notifications\Xbmc\IApiProvider.cs" />
<Compile Include="Notifications\Xbmc\InvalidXbmcVersionException.cs" />
<Compile Include="Notifications\Xbmc\JsonApiProvider.cs" />
<Compile Include="Notifications\Xbmc\TestXbmcCommand.cs" />
<Compile Include="Notifications\Xbmc\XbmcSettings.cs" />
<Compile Include="Organizer\EpisodeSortingType.cs" />
<Compile Include="Organizer\FileNameBuilder.cs" />

View File

@ -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<ProviderDefinition> DefaultDefinitions { get; }
ProviderDefinition Definition { get; set; }
ValidationResult Test();
}
}

View File

@ -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<TProviderDefinition> All();
List<TProvider> 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<TProviderDefinition> GetDefaultDefinitions();
IEnumerable<TProviderDefinition> GetPresetDefinitions(TProviderDefinition providerDefinition);
ValidationResult Test(TProviderDefinition definition);
}
}

View File

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

View File

@ -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<TProvider> GetAvailableProviders()
{
return Active().Select(GetInstance).ToList();

View File

@ -1,9 +1,9 @@
'use strict';
define([
'backbone.deepmodel'
], function (DeepModel) {
return DeepModel.DeepModel.extend({
'Settings/ProviderSettingsModelBase'
], function (ProviderSettingsModelBase) {
return ProviderSettingsModelBase.extend({
});
});

View File

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

View File

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

View File

@ -46,8 +46,7 @@
<button class="btn pull-left x-back">back</button>
{{/if}}
<!-- Testing is currently not yet supported for indexers, but leaving the infrastructure for later -->
<!-- <button class="btn x-test">test <i class="x-test-icon icon-nd-test"/></button> -->
<button class="btn x-test">test <i class="x-test-icon icon-nd-test"/></button>
<button class="btn x-close">cancel</button>
<div class="btn-group">

View File

@ -1,9 +1,9 @@
'use strict';
define([
'backbone.deepmodel'
], function (DeepModel) {
return DeepModel.DeepModel.extend({
'Settings/ProviderSettingsModelBase'
], function (ProviderSettingsModelBase) {
return ProviderSettingsModelBase.extend({
});
});

View File

@ -1,10 +1,10 @@
'use strict';
define(
[
'backbone.deepmodel'
], function (DeepModel) {
return DeepModel.DeepModel.extend({
});
define([
'Settings/ProviderSettingsModelBase'
], function (ProviderSettingsModelBase) {
return ProviderSettingsModelBase.extend({
});
});

View File

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

View File

@ -1,10 +1,9 @@
'use strict';
define([
'Settings/SettingsModelBase'
], function (ModelBase) {
return ModelBase.extend({
successMessage: 'Notification Saved',
errorMessage : 'Couldn\'t save notification'
define([
'Settings/ProviderSettingsModelBase'
], function (ProviderSettingsModelBase) {
return ProviderSettingsModelBase.extend({
});
});

View File

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

View File

@ -57,13 +57,17 @@ define(
};
$.fn.addFormError = function (error) {
var t1 = this.find('.form-horizontal');
var t2 = this.find('.form-horizontal').parent();
this.prepend('<div class="alert alert-danger validation-error">' + error.errorMessage + '</div>');
if (this.find('.modal-body')) {
this.find('.modal-body').prepend('<div class="alert alert-danger validation-error">' + error.errorMessage + '</div>');
}
else {
this.prepend('<div class="alert alert-danger validation-error">' + error.errorMessage + '</div>');
}
};
$.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();