Added: Allow minimum seeders to be set on a per indexer basis. Pulled from Sonarr Upstream (#1624)

This commit is contained in:
Leonardo Galli 2017-06-12 14:47:05 +02:00 committed by GitHub
parent a460e89a8d
commit 9f4ce50dd6
30 changed files with 328 additions and 104 deletions

View File

@ -0,0 +1,111 @@
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine.Specifications.Search;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.TorrentRss;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.DecisionEngineTests.Search
{
[TestFixture]
public class TorrentSeedingSpecificationFixture : TestBase<TorrentSeedingSpecification>
{
private Series _series;
private RemoteEpisode _remoteEpisode;
private IndexerDefinition _indexerDefinition;
[SetUp]
public void Setup()
{
_series = Builder<Series>.CreateNew().With(s => s.Id = 1).Build();
_remoteEpisode = new RemoteEpisode
{
Series = _series,
Release = new TorrentInfo
{
IndexerId = 1,
Title = "Series.Title.S01.720p.BluRay.X264-RlsGrp",
Seeders = 0
}
};
_indexerDefinition = new IndexerDefinition
{
Settings = new TorrentRssIndexerSettings { MinimumSeeders = 5 }
};
Mocker.GetMock<IIndexerFactory>()
.Setup(v => v.Get(1))
.Returns(_indexerDefinition);
}
private void GivenReleaseSeeders(int? seeders)
{
(_remoteEpisode.Release as TorrentInfo).Seeders = seeders;
}
[Test]
public void should_return_true_if_not_torrent()
{
_remoteEpisode.Release = new ReleaseInfo
{
IndexerId = 1,
Title = "Series.Title.S01.720p.BluRay.X264-RlsGrp"
};
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_true_if_indexer_not_specified()
{
_remoteEpisode.Release.IndexerId = 0;
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_true_if_indexer_no_longer_exists()
{
Mocker.GetMock<IIndexerFactory>()
.Setup(v => v.Get(It.IsAny<int>()))
.Callback<int>(i => { throw new ModelNotFoundException(typeof(IndexerDefinition), i); });
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_true_if_seeds_unknown()
{
GivenReleaseSeeders(null);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[TestCase(5)]
[TestCase(6)]
public void should_return_true_if_seeds_above_or_equal_to_limit(int seeders)
{
GivenReleaseSeeders(seeders);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[TestCase(0)]
[TestCase(4)]
public void should_return_false_if_seeds_belove_limit(int seeders)
{
GivenReleaseSeeders(seeders);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();
}
}
}

View File

@ -20,7 +20,7 @@ namespace NzbDrone.Core.Test.IndexerTests.IPTorrentsTests
Subject.Definition = new IndexerDefinition()
{
Name = "IPTorrents",
Settings = new IPTorrentsSettings() { Url = "http://fake.com/" }
Settings = new IPTorrentsSettings() { BaseUrl = "http://fake.com/" }
};
}

View File

@ -21,7 +21,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
{
_settings = new NewznabSettings()
{
Url = "http://indxer.local"
BaseUrl = "http://indxer.local"
};
_caps = ReadAllText("Files/Indexers/Newznab/newznab_caps.xml");

View File

@ -24,7 +24,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
Name = "Newznab",
Settings = new NewznabSettings()
{
Url = "http://indexer.local/",
BaseUrl = "http://indexer.local/",
Categories = new int[] { 1 }
}
};

View File

@ -19,7 +19,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
{
Subject.Settings = new NewznabSettings()
{
Url = "http://127.0.0.1:1234/",
BaseUrl = "http://127.0.0.1:1234/",
Categories = new [] { 1, 2 },
AnimeCategories = new [] { 3, 4 },
ApiKey = "abcd",

View File

@ -15,7 +15,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
var setting = new NewznabSettings()
{
ApiKey = "",
Url = url
BaseUrl = url
};
@ -32,13 +32,13 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
var setting = new NewznabSettings
{
ApiKey = "",
Url = url
BaseUrl = url
};
setting.Validate().IsValid.Should().BeFalse();
setting.Validate().Errors.Should().NotContain(c => c.PropertyName == "ApiKey");
setting.Validate().Errors.Should().Contain(c => c.PropertyName == "Url");
setting.Validate().Errors.Should().Contain(c => c.PropertyName == "BaseUrl");
}
@ -49,7 +49,7 @@ namespace NzbDrone.Core.Test.IndexerTests.NewznabTests
var setting = new NewznabSettings()
{
ApiKey = "",
Url = url
BaseUrl = url
};

View File

@ -25,7 +25,7 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
Name = "Torznab",
Settings = new TorznabSettings()
{
Url = "http://indexer.local/",
BaseUrl = "http://indexer.local/",
Categories = new int[] { 1 }
}
};

View File

@ -159,6 +159,7 @@
<Compile Include="DecisionEngineTests\RetentionSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\RssSync\DelaySpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\RssSync\ProperSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\Search\TorrentSeedingSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\Search\SeriesSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\SameEpisodesSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\RawDiskSpecificationFixture.cs" />

View File

@ -1,55 +0,0 @@
using NLog;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
{
public class TorrentSeedingSpecification : IDecisionEngineSpecification
{
private readonly Logger _logger;
public TorrentSeedingSpecification(Logger logger)
{
_logger = logger;
}
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
{
var torrentInfo = remoteEpisode.Release as TorrentInfo;
if (torrentInfo == null)
{
return Decision.Accept();
}
if (torrentInfo.Seeders != null && torrentInfo.Seeders < 1)
{
_logger.Debug("Not enough seeders. ({0})", torrentInfo.Seeders);
return Decision.Reject("Not enough seeders. ({0})", torrentInfo.Seeders);
}
return Decision.Accept();
}
public Decision IsSatisfiedBy(RemoteMovie remoteEpisode, SearchCriteriaBase searchCriteria)
{
var torrentInfo = remoteEpisode.Release as TorrentInfo;
if (torrentInfo == null)
{
return Decision.Accept();
}
if (torrentInfo.Seeders != null && torrentInfo.Seeders < 1)
{
_logger.Debug("Not enough seeders. ({0})", torrentInfo.Seeders);
return Decision.Reject("Not enough seeders. ({0})", torrentInfo.Seeders);
}
return Decision.Accept();
}
}
}

View File

@ -0,0 +1,96 @@
using NLog;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
{
public class TorrentSeedingSpecification : IDecisionEngineSpecification
{
private readonly IIndexerFactory _indexerFactory;
private readonly Logger _logger;
public TorrentSeedingSpecification(IIndexerFactory indexerFactory, Logger logger)
{
_indexerFactory = indexerFactory;
_logger = logger;
}
//public SpecificationPriority Priority => SpecificationPriority.Default;
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SearchCriteriaBase searchCriteria)
{
var torrentInfo = remoteEpisode.Release as TorrentInfo;
if (torrentInfo == null || torrentInfo.IndexerId == 0)
{
return Decision.Accept();
}
IndexerDefinition indexer;
try
{
indexer = _indexerFactory.Get(torrentInfo.IndexerId);
}
catch (ModelNotFoundException)
{
_logger.Debug("Indexer with id {0} does not exist, skipping seeders check", torrentInfo.IndexerId);
return Decision.Accept();
}
var torrentIndexerSettings = indexer.Settings as ITorrentIndexerSettings;
if (torrentIndexerSettings != null)
{
var minimumSeeders = torrentIndexerSettings.MinimumSeeders;
if (torrentInfo.Seeders.HasValue && torrentInfo.Seeders.Value < minimumSeeders)
{
_logger.Debug("Not enough seeders: {0}. Minimum seeders: {1}", torrentInfo.Seeders, minimumSeeders);
return Decision.Reject("Not enough seeders: {0}. Minimum seeders: {1}", torrentInfo.Seeders, minimumSeeders);
}
}
return Decision.Accept();
}
public Decision IsSatisfiedBy(RemoteMovie remoteEpisode, SearchCriteriaBase searchCriteria)
{
var torrentInfo = remoteEpisode.Release as TorrentInfo;
if (torrentInfo == null || torrentInfo.IndexerId == 0)
{
return Decision.Accept();
}
IndexerDefinition indexer;
try
{
indexer = _indexerFactory.Get(torrentInfo.IndexerId);
}
catch (ModelNotFoundException)
{
_logger.Debug("Indexer with id {0} does not exist, skipping seeders check", torrentInfo.IndexerId);
return Decision.Accept();
}
var torrentIndexerSettings = indexer.Settings as ITorrentIndexerSettings;
if (torrentIndexerSettings != null)
{
var minimumSeeders = torrentIndexerSettings.MinimumSeeders;
if (torrentInfo.Seeders.HasValue && torrentInfo.Seeders.Value < minimumSeeders)
{
_logger.Debug("Not enough seeders: {0}. Minimum seeders: {1}", torrentInfo.Seeders, minimumSeeders);
return Decision.Reject("Not enough seeders: {0}. Minimum seeders: {1}", torrentInfo.Seeders, minimumSeeders);
}
}
return Decision.Accept();
}
}
}

View File

@ -14,13 +14,14 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
}
}
public class AwesomeHDSettings : IProviderConfig
public class AwesomeHDSettings : ITorrentIndexerSettings
{
private static readonly AwesomeHDSettingsValidator Validator = new AwesomeHDSettingsValidator();
public AwesomeHDSettings()
{
BaseUrl = "https://awesome-hd.me";
MinimumSeeders = 0;
}
[FieldDefinition(0, Label = "API URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since you Passkey will be sent to that host.")]
@ -32,6 +33,9 @@ namespace NzbDrone.Core.Indexers.AwesomeHD
[FieldDefinition(2, Type = FieldType.Checkbox, Label = "Require Internal", HelpText = "Will only include internal releases for RSS Sync.")]
public bool Internal { get; set; }
[FieldDefinition(3, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@ -2,7 +2,6 @@
using System.Linq;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
using System.Linq.Expressions;
using FluentValidation.Results;
@ -19,13 +18,14 @@ namespace NzbDrone.Core.Indexers.HDBits
}
}
public class HDBitsSettings : IProviderConfig
public class HDBitsSettings : ITorrentIndexerSettings
{
private static readonly HDBitsSettingsValidator Validator = new HDBitsSettingsValidator();
public HDBitsSettings()
{
BaseUrl = "https://hdbits.org";
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
Categories = new int[] { (int)HdBitsCategory.Movie };
Codecs = new int[0];
@ -56,6 +56,9 @@ namespace NzbDrone.Core.Indexers.HDBits
[FieldDefinition(7, Label = "Mediums", Type = FieldType.Tag, SelectOptions = typeof(HdBitsMedium), Advanced = true, HelpText = "Options: BluRay, Encode, Capture, Remux, WebDL. If unspecified, all options are used.")]
public IEnumerable<int> Mediums { get; set; }
[FieldDefinition(8, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@ -0,0 +1,9 @@
using NzbDrone.Core.ThingiProvider;
namespace NzbDrone.Core.Indexers
{
public interface IIndexerSettings : IProviderConfig
{
string BaseUrl { get; set; }
}
}

View File

@ -50,7 +50,7 @@ namespace NzbDrone.Core.Indexers.IPTorrents
private IEnumerable<IndexerRequest> GetRssRequests()
{
yield return new IndexerRequest(Settings.Url, HttpAccept.Rss);
yield return new IndexerRequest(Settings.BaseUrl, HttpAccept.Rss);
}
}
}

View File

@ -1,4 +1,4 @@
using System.Text.RegularExpressions;
using System.Text.RegularExpressions;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
@ -11,26 +11,31 @@ namespace NzbDrone.Core.Indexers.IPTorrents
{
public IPTorrentsSettingsValidator()
{
RuleFor(c => c.Url).ValidRootUrl();
RuleFor(c => c.BaseUrl).ValidRootUrl();
RuleFor(c => c.Url).Matches(@"/rss\?.+$");
RuleFor(c => c.BaseUrl).Matches(@"/rss\?.+$");
RuleFor(c => c.Url).Matches(@"/rss\?.+;download(?:;|$)")
RuleFor(c => c.BaseUrl).Matches(@"/rss\?.+;download(?:;|$)")
.WithMessage("Use Direct Download Url (;download)")
.When(v => v.Url.IsNotNullOrWhiteSpace() && Regex.IsMatch(v.Url, @"/rss\?.+$"));
.When(v => v.BaseUrl.IsNotNullOrWhiteSpace() && Regex.IsMatch(v.BaseUrl, @"/rss\?.+$"));
}
}
public class IPTorrentsSettings : IProviderConfig
public class IPTorrentsSettings : ITorrentIndexerSettings
{
private static readonly IPTorrentsSettingsValidator Validator = new IPTorrentsSettingsValidator();
public IPTorrentsSettings()
{
BaseUrl = string.Empty;
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
}
[FieldDefinition(0, Label = "Feed URL", HelpText = "The full RSS feed url generated by IPTorrents, using only the categories you selected (HD, SD, x264, etc ...)")]
public string Url { get; set; }
public string BaseUrl { get; set; }
[FieldDefinition(1, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
public NzbDroneValidationResult Validate()
{

View File

@ -0,0 +1,7 @@
namespace NzbDrone.Core.Indexers
{
public interface ITorrentIndexerSettings : IIndexerSettings
{
int MinimumSeeders { get; set; }
}
}

View File

@ -0,0 +1,7 @@
namespace NzbDrone.Core.Indexers
{
public static class IndexerDefaults
{
public const int MINIMUM_SEEDERS = 1;
}
}

View File

@ -76,7 +76,7 @@ namespace NzbDrone.Core.Indexers.Newznab
private NewznabSettings GetSettings(string url, params int[] categories)
{
var settings = new NewznabSettings { Url = url };
var settings = new NewznabSettings { BaseUrl = url };
if (categories.Any())
{

View File

@ -42,7 +42,7 @@ namespace NzbDrone.Core.Indexers.Newznab
{
var capabilities = new NewznabCapabilities();
var url = string.Format("{0}/api?t=caps", indexerSettings.Url.TrimEnd('/'));
var url = string.Format("{0}/api?t=caps", indexerSettings.BaseUrl.TrimEnd('/'));
if (indexerSettings.ApiKey.IsNotNullOrWhiteSpace())
{
@ -59,7 +59,7 @@ namespace NzbDrone.Core.Indexers.Newznab
}
catch (Exception ex)
{
_logger.Debug(ex, "Failed to get newznab api capabilities from {0}", indexerSettings.Url);
_logger.Debug(ex, "Failed to get newznab api capabilities from {0}", indexerSettings.BaseUrl);
throw;
}
@ -69,12 +69,12 @@ namespace NzbDrone.Core.Indexers.Newznab
}
catch (XmlException ex)
{
_logger.Debug(ex, "Failed to parse newznab api capabilities for {0}.", indexerSettings.Url);
_logger.Debug(ex, "Failed to parse newznab api capabilities for {0}.", indexerSettings.BaseUrl);
throw;
}
catch (Exception ex)
{
_logger.Error(ex, "Failed to determine newznab api capabilities for {0}, using the defaults instead till Sonarr restarts.", indexerSettings.Url);
_logger.Error(ex, "Failed to determine newznab api capabilities for {0}, using the defaults instead till Sonarr restarts.", indexerSettings.BaseUrl);
}
return capabilities;

View File

@ -85,7 +85,7 @@ namespace NzbDrone.Core.Indexers.Newznab
var categoriesQuery = string.Join(",", categories.Distinct());
var baseUrl = string.Format("{0}/api?t={1}&cat={2}&extended=1{3}", Settings.Url.TrimEnd('/'), searchType, categoriesQuery, Settings.AdditionalParameters);
var baseUrl = string.Format("{0}/api?t={1}&cat={2}&extended=1{3}", Settings.BaseUrl.TrimEnd('/'), searchType, categoriesQuery, Settings.AdditionalParameters);
if (Settings.ApiKey.IsNotNullOrWhiteSpace())
{

View File

@ -25,12 +25,12 @@ namespace NzbDrone.Core.Indexers.Newznab
private static bool ShouldHaveApiKey(NewznabSettings settings)
{
if (settings.Url == null)
if (settings.BaseUrl == null)
{
return false;
}
return ApiKeyWhiteList.Any(c => settings.Url.ToLowerInvariant().Contains(c));
return ApiKeyWhiteList.Any(c => settings.BaseUrl.ToLowerInvariant().Contains(c));
}
private static readonly Regex AdditionalParametersRegex = new Regex(@"(&.+?\=.+?)+", RegexOptions.Compiled);
@ -47,14 +47,14 @@ namespace NzbDrone.Core.Indexers.Newznab
return null;
});
RuleFor(c => c.Url).ValidRootUrl();
RuleFor(c => c.BaseUrl).ValidRootUrl();
RuleFor(c => c.ApiKey).NotEmpty().When(ShouldHaveApiKey);
RuleFor(c => c.AdditionalParameters).Matches(AdditionalParametersRegex)
.When(c => !c.AdditionalParameters.IsNullOrWhiteSpace());
}
}
public class NewznabSettings : IProviderConfig
public class NewznabSettings : IIndexerSettings
{
private static readonly NewznabSettingsValidator Validator = new NewznabSettingsValidator();
@ -65,7 +65,7 @@ namespace NzbDrone.Core.Indexers.Newznab
}
[FieldDefinition(0, Label = "URL")]
public string Url { get; set; }
public string BaseUrl { get; set; }
[FieldDefinition(1, Label = "API Key")]
public string ApiKey { get; set; }
@ -79,6 +79,9 @@ namespace NzbDrone.Core.Indexers.Newznab
[FieldDefinition(4, Label = "Additional Parameters", HelpText = "Additional Newznab parameters", Advanced = true)]
public string AdditionalParameters { get; set; }
// Field 5 is used by TorznabSettings MinimumSeeders
// If you need to add another field here, update TorznabSettings as well and this comment
public virtual NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@ -1,6 +1,5 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
using System.Text.RegularExpressions;
namespace NzbDrone.Core.Indexers.Nyaa
@ -14,7 +13,7 @@ namespace NzbDrone.Core.Indexers.Nyaa
}
}
public class NyaaSettings : IProviderConfig
public class NyaaSettings : ITorrentIndexerSettings
{
private static readonly NyaaSettingsValidator Validator = new NyaaSettingsValidator();
@ -22,6 +21,7 @@ namespace NzbDrone.Core.Indexers.Nyaa
{
BaseUrl = "http://www.nyaa.se";
AdditionalParameters = "&cats=1_37&filter=1";
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
}
[FieldDefinition(0, Label = "Website URL")]
@ -30,6 +30,9 @@ namespace NzbDrone.Core.Indexers.Nyaa
[FieldDefinition(1, Label = "Additional Parameters", Advanced = true, HelpText = "Please note if you change the category you will have to add required/restricted rules about the subgroups to avoid foreign language releases.")]
public string AdditionalParameters { get; set; }
[FieldDefinition(2, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@ -15,7 +15,7 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs
}
}
public class OmgwtfnzbsSettings : IProviderConfig
public class OmgwtfnzbsSettings : IIndexerSettings
{
private static readonly OmgwtfnzbsSettingsValidator Validator = new OmgwtfnzbsSettingsValidator();
@ -24,6 +24,9 @@ namespace NzbDrone.Core.Indexers.Omgwtfnzbs
Delay = 30;
}
// Unused since Omg has a hardcoded url.
public string BaseUrl { get; set; }
[FieldDefinition(0, Label = "Username")]
public string Username { get; set; }

View File

@ -17,13 +17,14 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
}
}
public class PassThePopcornSettings : IProviderConfig
public class PassThePopcornSettings : ITorrentIndexerSettings
{
private static readonly PassThePopcornSettingsValidator Validator = new PassThePopcornSettingsValidator();
public PassThePopcornSettings()
{
BaseUrl = "https://passthepopcorn.me";
MinimumSeeders = 0;
}
[FieldDefinition(0, Label = "URL", Advanced = true, HelpText = "Do not change this unless you know what you're doing. Since your cookie will be sent to that host.")]
@ -50,6 +51,9 @@ namespace NzbDrone.Core.Indexers.PassThePopcorn
[FieldDefinition(7, Label = "Require Golden", Type = FieldType.Checkbox, HelpText = "Require Golden Popcorn-releases for releases to be accepted.")]
public bool RequireGolden { get; set; }
[FieldDefinition(8, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@ -1,6 +1,5 @@
using FluentValidation;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.Rarbg
@ -13,7 +12,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
}
}
public class RarbgSettings : IProviderConfig
public class RarbgSettings : ITorrentIndexerSettings
{
private static readonly RarbgSettingsValidator Validator = new RarbgSettingsValidator();
@ -21,6 +20,7 @@ namespace NzbDrone.Core.Indexers.Rarbg
{
BaseUrl = "https://torrentapi.org";
RankedOnly = false;
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
}
[FieldDefinition(0, Label = "API URL", HelpText = "URL to Rarbg api, not the website.")]
@ -32,6 +32,9 @@ namespace NzbDrone.Core.Indexers.Rarbg
[FieldDefinition(2, Type = FieldType.Captcha, Label = "CAPTCHA Token", HelpText = "CAPTCHA Clearance token used to handle CloudFlare Anti-DDOS measures on shared-ip VPNs.")]
public string CaptchaToken { get; set; }
[FieldDefinition(3, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@ -13,13 +13,14 @@ namespace NzbDrone.Core.Indexers.TorrentPotato
}
}
public class TorrentPotatoSettings : IProviderConfig
public class TorrentPotatoSettings : ITorrentIndexerSettings
{
private static readonly TorrentPotatoSettingsValidator Validator = new TorrentPotatoSettingsValidator();
public TorrentPotatoSettings()
{
BaseUrl = "http://127.0.0.1";
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
}
[FieldDefinition(0, Label = "API URL", HelpText = "URL to TorrentPotato api.")]
@ -30,6 +31,10 @@ namespace NzbDrone.Core.Indexers.TorrentPotato
[FieldDefinition(2, Label = "Passkey", HelpText = "The password you use at your Indexer.")]
public string Passkey { get; set; }
[FieldDefinition(3, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@ -1,6 +1,5 @@
using FluentValidation;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Indexers.TorrentRss
@ -13,7 +12,7 @@ namespace NzbDrone.Core.Indexers.TorrentRss
}
}
public class TorrentRssIndexerSettings : IProviderConfig
public class TorrentRssIndexerSettings : ITorrentIndexerSettings
{
private static readonly TorrentRssIndexerSettingsValidator validator = new TorrentRssIndexerSettingsValidator();
@ -21,6 +20,7 @@ namespace NzbDrone.Core.Indexers.TorrentRss
{
BaseUrl = string.Empty;
AllowZeroSize = false;
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
}
[FieldDefinition(0, Label = "Full RSS Feed URL")]
@ -32,6 +32,9 @@ namespace NzbDrone.Core.Indexers.TorrentRss
[FieldDefinition(2, Type = FieldType.Checkbox, Label = "Allow Zero Size", HelpText="Enabling this will allow you to use feeds that don't specify release size, but be careful, size related checks will not be performed.")]
public bool AllowZeroSize { get; set; }
[FieldDefinition(3, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(validator.Validate(this));

View File

@ -66,7 +66,7 @@ namespace NzbDrone.Core.Indexers.Torznab
private TorznabSettings GetSettings(string url, params int[] categories)
{
var settings = new TorznabSettings { Url = url };
var settings = new TorznabSettings { BaseUrl = url };
if (categories.Any())
{

View File

@ -1,8 +1,9 @@
using System.Linq;
using System.Linq;
using System.Text.RegularExpressions;
using FluentValidation;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.Validation;
@ -17,12 +18,12 @@ namespace NzbDrone.Core.Indexers.Torznab
private static bool ShouldHaveApiKey(TorznabSettings settings)
{
if (settings.Url == null)
if (settings.BaseUrl == null)
{
return false;
}
return ApiKeyWhiteList.Any(c => settings.Url.ToLowerInvariant().Contains(c));
return ApiKeyWhiteList.Any(c => settings.BaseUrl.ToLowerInvariant().Contains(c));
}
private static readonly Regex AdditionalParametersRegex = new Regex(@"(&.+?\=.+?)+", RegexOptions.Compiled);
@ -39,17 +40,25 @@ namespace NzbDrone.Core.Indexers.Torznab
return null;
});
RuleFor(c => c.Url).ValidRootUrl();
RuleFor(c => c.BaseUrl).ValidRootUrl();
RuleFor(c => c.ApiKey).NotEmpty().When(ShouldHaveApiKey);
RuleFor(c => c.AdditionalParameters).Matches(AdditionalParametersRegex)
.When(c => !c.AdditionalParameters.IsNullOrWhiteSpace());
}
}
public class TorznabSettings : NewznabSettings
public class TorznabSettings : NewznabSettings, ITorrentIndexerSettings
{
private static readonly TorznabSettingsValidator Validator = new TorznabSettingsValidator();
public TorznabSettings()
{
MinimumSeeders = IndexerDefaults.MINIMUM_SEEDERS;
}
[FieldDefinition(6, Type = FieldType.Textbox, Label = "Minimum Seeders", HelpText = "Minimum number of seeders required.", Advanced = true)]
public int MinimumSeeders { get; set; }
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

View File

@ -403,7 +403,7 @@
<Compile Include="DecisionEngine\Specifications\Search\SeasonMatchSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\Search\SeriesSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\Search\SingleEpisodeSearchMatchSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\Search\TorrentSeedingSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\TorrentSeedingSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\SameEpisodesGrabSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\RawDiskSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\UpgradeDiskSpecification.cs" />
@ -691,6 +691,7 @@
<Compile Include="Indexers\IIndexer.cs" />
<Compile Include="Indexers\IIndexerRequestGenerator.cs" />
<Compile Include="Indexers\IndexerBase.cs" />
<Compile Include="Indexers\IndexerDefaults.cs" />
<Compile Include="Indexers\IndexerDefinition.cs" />
<Compile Include="Indexers\IndexerFactory.cs" />
<Compile Include="Indexers\IndexerPageableRequest.cs" />
@ -706,6 +707,7 @@
<Compile Include="Indexers\IPTorrents\IPTorrentsRequestGenerator.cs" />
<Compile Include="Indexers\IPTorrents\IPTorrents.cs" />
<Compile Include="Indexers\IPTorrents\IPTorrentsSettings.cs" />
<Compile Include="Indexers\ITorrentIndexerSettings.cs" />
<Compile Include="Indexers\Newznab\Newznab.cs" />
<Compile Include="Indexers\Newznab\NewznabCapabilities.cs" />
<Compile Include="Indexers\Newznab\NewznabCapabilitiesProvider.cs" />
@ -1285,6 +1287,7 @@
<Compile Include="NetImport\ImportExclusions\ImportExclusionsRepository.cs" />
<Compile Include="NetImport\ImportExclusions\ImportExclusionsService.cs" />
<Compile Include="Datastore\Migration\138_add_physical_release_note.cs" />
<Compile Include="Indexers\IIndexerSettings.cs" />
<Compile Include="NetImport\Radarr\RadarrProxied.cs" />
<Compile Include="NetImport\Radarr\RadarrParser.cs" />
<Compile Include="NetImport\Radarr\RadarrRequestGenerator.cs" />