Fixed: Empty Sabnzbd category is now properly handled. But added UI validation to recommend adding a category.

pull/3113/head
Taloth Saldono 10 years ago
parent a8e805fd5d
commit 6803e46782

@ -9,10 +9,10 @@ namespace NzbDrone.Api.DownloadClient
{ {
} }
protected override void Validate(DownloadClientDefinition definition) protected override void Validate(DownloadClientDefinition definition, bool includeWarnings)
{ {
if (!definition.Enable) return; if (!definition.Enable) return;
base.Validate(definition); base.Validate(definition, includeWarnings);
} }
} }
} }

@ -9,10 +9,10 @@ namespace NzbDrone.Api.Indexers
{ {
} }
protected override void Validate(IndexerDefinition definition) protected override void Validate(IndexerDefinition definition, bool includeWarnings)
{ {
if (!definition.Enable) return; if (!definition.Enable) return;
base.Validate(definition); base.Validate(definition, includeWarnings);
} }
} }
} }

@ -9,10 +9,10 @@ namespace NzbDrone.Api.Metadata
{ {
} }
protected override void Validate(MetadataDefinition definition) protected override void Validate(MetadataDefinition definition, bool includeWarnings)
{ {
if (!definition.Enable) return; if (!definition.Enable) return;
base.Validate(definition); base.Validate(definition, includeWarnings);
} }
} }
} }

@ -9,10 +9,10 @@ namespace NzbDrone.Api.Notifications
{ {
} }
protected override void Validate(NotificationDefinition definition) protected override void Validate(NotificationDefinition definition, bool includeWarnings)
{ {
if (!definition.OnGrab && !definition.OnDownload) return; if (!definition.OnGrab && !definition.OnDownload) return;
base.Validate(definition); base.Validate(definition, includeWarnings);
} }
} }
} }

@ -2,12 +2,14 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FluentValidation; using FluentValidation;
using FluentValidation.Results;
using Nancy; using Nancy;
using NzbDrone.Api.ClientSchema; using NzbDrone.Api.ClientSchema;
using NzbDrone.Api.Extensions; using NzbDrone.Api.Extensions;
using NzbDrone.Api.Mapping; using NzbDrone.Api.Mapping;
using NzbDrone.Common.Reflection; using NzbDrone.Common.Reflection;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
using Omu.ValueInjecter; using Omu.ValueInjecter;
namespace NzbDrone.Api namespace NzbDrone.Api
@ -72,12 +74,9 @@ namespace NzbDrone.Api
private int CreateProvider(TProviderResource providerResource) private int CreateProvider(TProviderResource providerResource)
{ {
var providerDefinition = GetDefinition(providerResource); var providerDefinition = GetDefinition(providerResource, false);
if (providerDefinition.Enable) Test(providerDefinition, false);
{
Test(providerDefinition);
}
providerDefinition = _providerFactory.Create(providerDefinition); providerDefinition = _providerFactory.Create(providerDefinition);
@ -86,12 +85,14 @@ namespace NzbDrone.Api
private void UpdateProvider(TProviderResource providerResource) private void UpdateProvider(TProviderResource providerResource)
{ {
var providerDefinition = GetDefinition(providerResource); var providerDefinition = GetDefinition(providerResource, false);
Test(providerDefinition, false);
_providerFactory.Update(providerDefinition); _providerFactory.Update(providerDefinition);
} }
private TProviderDefinition GetDefinition(TProviderResource providerResource) private TProviderDefinition GetDefinition(TProviderResource providerResource, bool includeWarnings = false)
{ {
var definition = new TProviderDefinition(); var definition = new TProviderDefinition();
@ -105,7 +106,7 @@ namespace NzbDrone.Api
var configContract = ReflectionExtensions.CoreAssembly.FindTypeByName(definition.ConfigContract); var configContract = ReflectionExtensions.CoreAssembly.FindTypeByName(definition.ConfigContract);
definition.Settings = (IProviderConfig)SchemaBuilder.ReadFormSchema(providerResource.Fields, configContract, preset); definition.Settings = (IProviderConfig)SchemaBuilder.ReadFormSchema(providerResource.Fields, configContract, preset);
Validate(definition); Validate(definition, includeWarnings);
return definition; return definition;
} }
@ -149,30 +150,41 @@ namespace NzbDrone.Api
private Response Test(TProviderResource providerResource) private Response Test(TProviderResource providerResource)
{ {
var providerDefinition = GetDefinition(providerResource); var providerDefinition = GetDefinition(providerResource, true);
Test(providerDefinition); Test(providerDefinition, true);
return "{}"; return "{}";
} }
private void Test(TProviderDefinition providerDefinition) protected virtual void Validate(TProviderDefinition definition, bool includeWarnings)
{ {
var result = _providerFactory.Test(providerDefinition); var validationResult = definition.Settings.Validate();
if (!result.IsValid) VerifyValidationResult(validationResult, includeWarnings);
{
throw new ValidationException(result.Errors);
}
} }
protected virtual void Validate(TProviderDefinition definition) protected virtual void Test(TProviderDefinition definition, bool includeWarnings)
{ {
var validationResult = definition.Settings.Validate(); if (!definition.Enable) return;
var validationResult = _providerFactory.Test(definition);
if (!validationResult.IsValid) VerifyValidationResult(validationResult, includeWarnings);
}
protected void VerifyValidationResult(ValidationResult validationResult, bool includeWarnings)
{
var result = new NzbDroneValidationResult(validationResult.Errors);
if (includeWarnings && (!result.IsValid || result.HasWarnings))
{ {
throw new ValidationException(validationResult.Errors); throw new ValidationException(result.Failures);
}
if (!result.IsValid)
{
throw new ValidationException(result.Errors);
} }
} }
} }

@ -38,7 +38,7 @@ namespace NzbDrone.Core.Download.Clients.Deluge
[FieldDefinition(2, Label = "Password", Type = FieldType.Password)] [FieldDefinition(2, Label = "Password", Type = FieldType.Password)]
public String Password { get; set; } public String Password { get; set; }
[FieldDefinition(3, Label = "Category", Type = FieldType.Textbox)] [FieldDefinition(3, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional")]
public String TvCategory { get; set; } public String TvCategory { get; set; }
[FieldDefinition(4, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] [FieldDefinition(4, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(DelugePriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]

@ -3,6 +3,7 @@ using FluentValidation;
using FluentValidation.Results; using FluentValidation.Results;
using NzbDrone.Core.Annotations; using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths; using NzbDrone.Core.Validation.Paths;
namespace NzbDrone.Core.Download.Clients.Nzbget namespace NzbDrone.Core.Download.Clients.Nzbget
@ -18,6 +19,8 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
RuleFor(c => c.TvCategory).NotEmpty().When(c => !String.IsNullOrWhiteSpace(c.TvCategoryLocalPath)); RuleFor(c => c.TvCategory).NotEmpty().When(c => !String.IsNullOrWhiteSpace(c.TvCategoryLocalPath));
RuleFor(c => c.TvCategoryLocalPath).IsValidPath().When(c => !String.IsNullOrWhiteSpace(c.TvCategoryLocalPath)); RuleFor(c => c.TvCategoryLocalPath).IsValidPath().When(c => !String.IsNullOrWhiteSpace(c.TvCategoryLocalPath));
RuleFor(c => c.TvCategory).NotEmpty().WithMessage("A category is recommended").AsWarning();
} }
} }
@ -46,7 +49,7 @@ namespace NzbDrone.Core.Download.Clients.Nzbget
[FieldDefinition(3, Label = "Password", Type = FieldType.Password)] [FieldDefinition(3, Label = "Password", Type = FieldType.Password)]
public String Password { get; set; } public String Password { get; set; }
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox)] [FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional")]
public String TvCategory { get; set; } public String TvCategory { get; set; }
// TODO: Remove around January 2015, this setting was superceded by the RemotePathMappingService, but has to remain for a while to properly migrate. // TODO: Remove around January 2015, this setting was superceded by the RemotePathMappingService, but has to remain for a while to properly migrate.

@ -181,7 +181,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
foreach (var downloadClientItem in GetQueue().Concat(GetHistory())) foreach (var downloadClientItem in GetQueue().Concat(GetHistory()))
{ {
if (downloadClientItem.Category == Settings.TvCategory) if (downloadClientItem.Category == Settings.TvCategory || downloadClientItem.Category == "*" && Settings.TvCategory.IsNullOrWhiteSpace())
{ {
yield return downloadClientItem; yield return downloadClientItem;
} }

@ -3,6 +3,7 @@ using FluentValidation;
using FluentValidation.Results; using FluentValidation.Results;
using NzbDrone.Core.Annotations; using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Download.Clients.Sabnzbd namespace NzbDrone.Core.Download.Clients.Sabnzbd
{ {
@ -24,6 +25,10 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
RuleFor(c => c.Password).NotEmpty() RuleFor(c => c.Password).NotEmpty()
.WithMessage("Password is required when API key is not configured") .WithMessage("Password is required when API key is not configured")
.When(c => String.IsNullOrWhiteSpace(c.ApiKey)); .When(c => String.IsNullOrWhiteSpace(c.ApiKey));
RuleFor(c => c.TvCategory).NotEmpty()
.WithMessage("A category is recommended")
.AsWarning();
} }
} }
@ -55,7 +60,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
[FieldDefinition(4, Label = "Password", Type = FieldType.Password)] [FieldDefinition(4, Label = "Password", Type = FieldType.Password)]
public String Password { get; set; } public String Password { get; set; }
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox)] [FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional")]
public String TvCategory { get; set; } public String TvCategory { get; set; }
// TODO: Remove around January 2015, this setting was superceded by the RemotePathMappingService, but has to remain for a while to properly migrate. // TODO: Remove around January 2015, this setting was superceded by the RemotePathMappingService, but has to remain for a while to properly migrate.

@ -42,7 +42,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
[FieldDefinition(4, Label = "Password", Type = FieldType.Password)] [FieldDefinition(4, Label = "Password", Type = FieldType.Password)]
public String Password { get; set; } public String Password { get; set; }
[FieldDefinition(5, Label = "Category", Type = FieldType.Textbox)] [FieldDefinition(5, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional. Creates a .[category] subdirectory in the output directory.")]
public String TvCategory { get; set; } public String TvCategory { get; set; }
[FieldDefinition(6, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] [FieldDefinition(6, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(TransmissionPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]

@ -39,7 +39,7 @@ namespace NzbDrone.Core.Download.Clients.UTorrent
[FieldDefinition(3, Label = "Password", Type = FieldType.Password)] [FieldDefinition(3, Label = "Password", Type = FieldType.Password)]
public String Password { get; set; } public String Password { get; set; }
[FieldDefinition(4, Label = "Category", Type = FieldType.Textbox)] [FieldDefinition(4, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional")]
public String TvCategory { get; set; } public String TvCategory { get; set; }
[FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] [FieldDefinition(5, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(UTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")]

@ -869,8 +869,10 @@
<Compile Include="Update\UpdateVerificationFailedException.cs" /> <Compile Include="Update\UpdateVerificationFailedException.cs" />
<Compile Include="Validation\FolderValidator.cs" /> <Compile Include="Validation\FolderValidator.cs" />
<Compile Include="Validation\IpValidation.cs" /> <Compile Include="Validation\IpValidation.cs" />
<Compile Include="Validation\LangaugeValidator.cs" /> <Compile Include="Validation\LanguageValidator.cs" />
<Compile Include="Validation\NzbDroneValidationFailure.cs" /> <Compile Include="Validation\NzbDroneValidationFailure.cs" />
<Compile Include="Validation\NzbDroneValidationResult.cs" />
<Compile Include="Validation\NzbDroneValidationState.cs" />
<Compile Include="Validation\Paths\DroneFactoryValidator.cs" /> <Compile Include="Validation\Paths\DroneFactoryValidator.cs" />
<Compile Include="Validation\Paths\PathExistsValidator.cs" /> <Compile Include="Validation\Paths\PathExistsValidator.cs" />
<Compile Include="Validation\Paths\PathValidator.cs" /> <Compile Include="Validation\Paths\PathValidator.cs" />

@ -2,9 +2,9 @@
namespace NzbDrone.Core.Validation namespace NzbDrone.Core.Validation
{ {
public class LangaugeValidator : PropertyValidator public class LanguageValidator : PropertyValidator
{ {
public LangaugeValidator() public LanguageValidator()
: base("Unknown Language") : base("Unknown Language")
{ {
} }

@ -5,13 +5,29 @@ namespace NzbDrone.Core.Validation
{ {
public class NzbDroneValidationFailure : ValidationFailure public class NzbDroneValidationFailure : ValidationFailure
{ {
public String DetailedDescription { get; set; } public bool IsWarning { get; set; }
public String InfoLink { get; set; } public string DetailedDescription { get; set; }
public string InfoLink { get; set; }
public NzbDroneValidationFailure(String propertyName, String error) public NzbDroneValidationFailure(string propertyName, string error)
: base(propertyName, error) : base(propertyName, error)
{ {
} }
public NzbDroneValidationFailure(string propertyName, string error, object attemptedValue)
: base(propertyName, error, attemptedValue)
{
}
public NzbDroneValidationFailure(ValidationFailure validationFailure)
: base(validationFailure.PropertyName, validationFailure.ErrorMessage, validationFailure.AttemptedValue)
{
CustomState = validationFailure.CustomState;
var state = validationFailure.CustomState as NzbDroneValidationState;
IsWarning = state != null && state.IsWarning;
}
} }
} }

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentValidation;
using FluentValidation.Results;
namespace NzbDrone.Core.Validation
{
public class NzbDroneValidationResult : ValidationResult
{
public NzbDroneValidationResult()
{
}
public NzbDroneValidationResult(IEnumerable<ValidationFailure> failures)
{
var errors = new List<NzbDroneValidationFailure>();
var warnings = new List<NzbDroneValidationFailure>();
foreach (var failureBase in failures)
{
var failure = failureBase as NzbDroneValidationFailure;
if (failure == null)
{
failure = new NzbDroneValidationFailure(failureBase);
}
if (failure.IsWarning)
{
warnings.Add(failure);
}
else
{
errors.Add(failure);
}
}
Failures = errors.Concat(warnings).ToList();
Errors = errors;
errors.ForEach(base.Errors.Add);
Warnings = warnings;
}
public IList<NzbDroneValidationFailure> Failures { get; private set; }
public new IList<NzbDroneValidationFailure> Errors { get; private set; }
public IList<NzbDroneValidationFailure> Warnings { get; private set; }
public virtual bool HasWarnings
{
get { return Warnings.Any(); }
}
}
}

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentValidation;
using FluentValidation.Results;
namespace NzbDrone.Core.Validation
{
public class NzbDroneValidationState
{
public static NzbDroneValidationState Warning = new NzbDroneValidationState { IsWarning = true };
public bool IsWarning { get; set; }
}
}

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentValidation;
using FluentValidation.Results;
namespace NzbDrone.Core.Validation
{
public abstract class NzbDroneValidator<T> : AbstractValidator<T>
{
public override ValidationResult Validate(T instance)
{
return new NzbDroneValidationResult(base.Validate(instance).Errors);
}
}
}

@ -35,7 +35,12 @@ namespace NzbDrone.Core.Validation
public static IRuleBuilderOptions<T, Language> ValidLanguage<T>(this IRuleBuilder<T, Language> ruleBuilder) public static IRuleBuilderOptions<T, Language> ValidLanguage<T>(this IRuleBuilder<T, Language> ruleBuilder)
{ {
return ruleBuilder.SetValidator(new LangaugeValidator()); return ruleBuilder.SetValidator(new LanguageValidator());
}
public static IRuleBuilderOptions<T, TProp> AsWarning<T, TProp>(this IRuleBuilderOptions<T, TProp> ruleBuilder)
{
return ruleBuilder.WithState(v => NzbDroneValidationState.Warning);
} }
} }
} }

@ -102,6 +102,20 @@ h3 {
} }
} }
.has-warning {
.help-inline {
color: orange;
margin-left: 0px;
}
}
.validation-warning {
i {
text-decoration: none;
color: orange;
}
}
// Tooltips // Tooltips
.help-inline-checkbox, .help-inline { .help-inline-checkbox, .help-inline {

@ -37,16 +37,21 @@ module.exports = function() {
} else { } else {
var inputGroup = formGroup.find('.input-group'); var inputGroup = formGroup.find('.input-group');
var validationClass = error.isWarning ? 'validation-warning' : 'validation-error';
if (inputGroup.length === 0) { if (inputGroup.length === 0) {
formGroup.append('<span class="help-inline validation-error">' + errorMessage + '</span>'); formGroup.append('<span class="help-inline {0}">{1}</span>'.format(validationClass, errorMessage));
} }
else { else {
inputGroup.parent().append('<span class="help-block validation-error">' + errorMessage + '</span>'); inputGroup.parent().append('<span class="help-block {0}">{1}</span>'.format(validationClass, errorMessage));
} }
} }
formGroup.addClass('has-error'); if (error.isWarning) {
formGroup.addClass('has-warning');
} else {
formGroup.addClass('has-error');
}
return formGroup.find('.help-inline').text(); return formGroup.find('.help-inline').text();
}; };
@ -59,20 +64,23 @@ module.exports = function() {
var errorMessage = this.formatErrorMessage(error); var errorMessage = this.formatErrorMessage(error);
if (this.find('.modal-body')) { var target = this.find('.modal-body');
this.find('.modal-body').prepend('<div class="alert alert-danger validation-error">' + errorMessage + '</div>'); if (!target.length) {
target = this;
} }
else { var validationClass = error.isWarning ? 'alert alert-warning validation-warning' : 'alert alert-danger validation-error';
this.prepend('<div class="alert alert-danger validation-error">' + errorMessage + '</div>');
} target.prepend('<div class="{0}">{1}</div>'.format(validationClass, errorMessage));
}; };
$.fn.removeAllErrors = function() { $.fn.removeAllErrors = function() {
this.find('.has-error').removeClass('has-error'); this.find('.has-error').removeClass('has-error');
this.find('.has-warning').removeClass('has-warning');
this.find('.error').removeClass('error'); this.find('.error').removeClass('error');
this.find('.validation-errors').removeClass('alert').removeClass('alert-danger').html(''); this.find('.validation-errors').removeClass('alert').removeClass('alert-danger').removeClass('alert-warning').html('');
this.find('.validation-error').remove(); this.find('.validation-error').remove();
this.find('.validation-warning').remove();
return this.find('.help-inline.error-message').remove(); return this.find('.help-inline.error-message').remove();
}; };

Loading…
Cancel
Save