diff --git a/README.md b/README.md index de14afb57..d39c11888 100644 --- a/README.md +++ b/README.md @@ -115,9 +115,7 @@ Radarr is currently undergoing rapid development and pull requests are actively * Open `NzbDrone.sln` in Visual Studio or run the build.sh script, if Mono is installed * Make sure `NzbDrone.Console` is set as the startup project -## Sponsors +### License -Thanks to [JetBrains](http://www.jetbrains.com) for providing us with free licenses to their great tools: -* [ReSharper](http://www.jetbrains.com/resharper) -* [WebStorm](http://www.jetbrains.com/webstorm) -* [TeamCity](http://www.jetbrains.com/teamcity) +* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html) +* Copyright 2010-2017 diff --git a/gulp/copy.js b/gulp/copy.js index ab380855d..9962defef 100644 --- a/gulp/copy.js +++ b/gulp/copy.js @@ -25,7 +25,7 @@ gulp.task('copyHtml', function () { }); gulp.task('copyContent', function () { - return gulp.src([paths.src.content + '**/*.*', '!**/*.less']) + return gulp.src([paths.src.content + '**/*.*', '!**/*.less', '!**/*.css']) .pipe(gulp.dest(paths.dest.content)) .pipe(livereload()); }); diff --git a/gulp/less.js b/gulp/less.js index e86f12f6c..eb836fdd4 100644 --- a/gulp/less.js +++ b/gulp/less.js @@ -16,6 +16,10 @@ gulp.task('less', function() { paths.src.content + 'bootstrap.less', paths.src.content + 'theme.less', paths.src.content + 'overrides.less', + paths.src.content + 'bootstrap.toggle-switch.css', + paths.src.content + 'fullcalendar.css', + paths.src.content + 'Messenger/messenger.css', + paths.src.content + 'Messenger/messenger.flat.css', paths.src.root + 'Series/series.less', paths.src.root + 'Activity/activity.less', paths.src.root + 'AddSeries/addSeries.less', diff --git a/src/NzbDrone.Api/Extensions/Pipelines/UrlBasePipeline.cs b/src/NzbDrone.Api/Extensions/Pipelines/UrlBasePipeline.cs new file mode 100644 index 000000000..d8c765e67 --- /dev/null +++ b/src/NzbDrone.Api/Extensions/Pipelines/UrlBasePipeline.cs @@ -0,0 +1,46 @@ +using System; +using Nancy; +using Nancy.Bootstrapper; +using Nancy.Responses; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Configuration; + +namespace NzbDrone.Api.Extensions.Pipelines +{ + public class UrlBasePipeline : IRegisterNancyPipeline + { + private readonly string _urlBase; + + public UrlBasePipeline(IConfigFileProvider configFileProvider) + { + _urlBase = configFileProvider.UrlBase; + } + + public int Order => 99; + + public void Register(IPipelines pipelines) + { + if (_urlBase.IsNotNullOrWhiteSpace()) + { + pipelines.BeforeRequest.AddItemToStartOfPipeline((Func) Handle); + } + } + + private Response Handle(NancyContext context) + { + var basePath = context.Request.Url.BasePath; + + if (basePath.IsNullOrWhiteSpace()) + { + return new RedirectResponse($"{_urlBase}{context.Request.Path}{context.Request.Url.Query}"); + } + + if (_urlBase != basePath) + { + return new NotFoundResponse(); + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/Frontend/StaticResourceModule.cs b/src/NzbDrone.Api/Frontend/StaticResourceModule.cs index 7ec5fe9d8..f58667c6c 100644 --- a/src/NzbDrone.Api/Frontend/StaticResourceModule.cs +++ b/src/NzbDrone.Api/Frontend/StaticResourceModule.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using Nancy.Responses; @@ -38,20 +38,6 @@ namespace NzbDrone.Api.Frontend return new NotFoundResponse(); } - //Redirect to the subfolder if the request went to the base URL - if (path.Equals("/")) - { - var urlBase = _configFileProvider.UrlBase; - - if (!string.IsNullOrEmpty(urlBase)) - { - if (Request.Url.BasePath != urlBase) - { - return new RedirectResponse(urlBase + "/"); - } - } - } - var mapper = _requestMappers.SingleOrDefault(m => m.CanHandle(path)); if (mapper != null) diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 2e44197df..c2bf53e79 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -113,6 +113,7 @@ + diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/Search/TorrentSeedingSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/Search/TorrentSeedingSpecificationFixture.cs new file mode 100644 index 000000000..56c4ac84f --- /dev/null +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/Search/TorrentSeedingSpecificationFixture.cs @@ -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 + { + private Series _series; + private RemoteEpisode _remoteEpisode; + private IndexerDefinition _indexerDefinition; + + [SetUp] + public void Setup() + { + _series = Builder.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() + .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() + .Setup(v => v.Get(It.IsAny())) + .Callback(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(); + } + } +} diff --git a/src/NzbDrone.Core.Test/IndexerTests/IPTorrentsTests/IPTorrentsFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/IPTorrentsTests/IPTorrentsFixture.cs index d48c06f6c..7b5b4fc1a 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/IPTorrentsTests/IPTorrentsFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/IPTorrentsTests/IPTorrentsFixture.cs @@ -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/" } }; } diff --git a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabCapabilitiesProviderFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabCapabilitiesProviderFixture.cs index e3a0e053c..f75ceab36 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabCapabilitiesProviderFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabCapabilitiesProviderFixture.cs @@ -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"); diff --git a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs index d8dd4bae3..56f5e7b88 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabFixture.cs @@ -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 } } }; diff --git a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabRequestGeneratorFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabRequestGeneratorFixture.cs index b64b099ec..0eccabc50 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabRequestGeneratorFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabRequestGeneratorFixture.cs @@ -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", diff --git a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabSettingFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabSettingFixture.cs index 4bd26817d..f3b409b1d 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabSettingFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/NewznabTests/NewznabSettingFixture.cs @@ -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 }; diff --git a/src/NzbDrone.Core.Test/IndexerTests/TorznabTests/TorznabFixture.cs b/src/NzbDrone.Core.Test/IndexerTests/TorznabTests/TorznabFixture.cs index 95963a75f..f303cbfaa 100644 --- a/src/NzbDrone.Core.Test/IndexerTests/TorznabTests/TorznabFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerTests/TorznabTests/TorznabFixture.cs @@ -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 } } }; diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 54b4a1b5f..5fd386f93 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -159,6 +159,7 @@ + diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs deleted file mode 100644 index 142e2009a..000000000 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/TorrentSeedingSpecification.cs +++ /dev/null @@ -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(); - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/TorrentSeedingSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/TorrentSeedingSpecification.cs new file mode 100644 index 000000000..58a15158e --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/TorrentSeedingSpecification.cs @@ -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(); + } + } +} diff --git a/src/NzbDrone.Core/Indexers/AwesomeHD/AwesomeHDSettings.cs b/src/NzbDrone.Core/Indexers/AwesomeHD/AwesomeHDSettings.cs index 1d1b259f1..ff94b3c8d 100644 --- a/src/NzbDrone.Core/Indexers/AwesomeHD/AwesomeHDSettings.cs +++ b/src/NzbDrone.Core/Indexers/AwesomeHD/AwesomeHDSettings.cs @@ -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)); diff --git a/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs b/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs index 38bfb7003..2dc5d6a93 100644 --- a/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs +++ b/src/NzbDrone.Core/Indexers/HDBits/HDBitsSettings.cs @@ -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 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)); diff --git a/src/NzbDrone.Core/Indexers/IIndexerSettings.cs b/src/NzbDrone.Core/Indexers/IIndexerSettings.cs new file mode 100644 index 000000000..87e7f03d2 --- /dev/null +++ b/src/NzbDrone.Core/Indexers/IIndexerSettings.cs @@ -0,0 +1,9 @@ +using NzbDrone.Core.ThingiProvider; + +namespace NzbDrone.Core.Indexers +{ + public interface IIndexerSettings : IProviderConfig + { + string BaseUrl { get; set; } + } +} diff --git a/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsRequestGenerator.cs b/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsRequestGenerator.cs index bd63b6f46..8537f51a6 100644 --- a/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsRequestGenerator.cs @@ -50,7 +50,7 @@ namespace NzbDrone.Core.Indexers.IPTorrents private IEnumerable GetRssRequests() { - yield return new IndexerRequest(Settings.Url, HttpAccept.Rss); + yield return new IndexerRequest(Settings.BaseUrl, HttpAccept.Rss); } } } diff --git a/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs b/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs index 4b82353a2..885da057e 100644 --- a/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs +++ b/src/NzbDrone.Core/Indexers/IPTorrents/IPTorrentsSettings.cs @@ -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() { diff --git a/src/NzbDrone.Core/Indexers/ITorrentIndexerSettings.cs b/src/NzbDrone.Core/Indexers/ITorrentIndexerSettings.cs new file mode 100644 index 000000000..9ac4fafcb --- /dev/null +++ b/src/NzbDrone.Core/Indexers/ITorrentIndexerSettings.cs @@ -0,0 +1,7 @@ +namespace NzbDrone.Core.Indexers +{ + public interface ITorrentIndexerSettings : IIndexerSettings + { + int MinimumSeeders { get; set; } + } +} diff --git a/src/NzbDrone.Core/Indexers/IndexerDefaults.cs b/src/NzbDrone.Core/Indexers/IndexerDefaults.cs new file mode 100644 index 000000000..134126fab --- /dev/null +++ b/src/NzbDrone.Core/Indexers/IndexerDefaults.cs @@ -0,0 +1,7 @@ +namespace NzbDrone.Core.Indexers +{ + public static class IndexerDefaults + { + public const int MINIMUM_SEEDERS = 1; + } +} diff --git a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs index d24aa5b20..f0ed6a7f8 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/Newznab.cs @@ -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()) { diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs index e17dd0225..f76565d6f 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabCapabilitiesProvider.cs @@ -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; diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs index aa0cd5ad2..8844b512a 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs @@ -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()) { diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs index 762e30c11..0dde1d7ab 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs @@ -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)); diff --git a/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs b/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs index 024021027..fbe78e14d 100644 --- a/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs +++ b/src/NzbDrone.Core/Indexers/Nyaa/NyaaSettings.cs @@ -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)); diff --git a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs index fe6217361..5f5fed9b1 100644 --- a/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs +++ b/src/NzbDrone.Core/Indexers/Omgwtfnzbs/OmgwtfnzbsSettings.cs @@ -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; } diff --git a/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornSettings.cs b/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornSettings.cs index 986052b42..990954a37 100644 --- a/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornSettings.cs +++ b/src/NzbDrone.Core/Indexers/PassThePopcorn/PassThePopcornSettings.cs @@ -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)); diff --git a/src/NzbDrone.Core/Indexers/Rarbg/RarbgSettings.cs b/src/NzbDrone.Core/Indexers/Rarbg/RarbgSettings.cs index c60616b27..1d4ce28fc 100644 --- a/src/NzbDrone.Core/Indexers/Rarbg/RarbgSettings.cs +++ b/src/NzbDrone.Core/Indexers/Rarbg/RarbgSettings.cs @@ -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)); diff --git a/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoSettings.cs b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoSettings.cs index a4f9b2951..db5d8fd89 100644 --- a/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoSettings.cs +++ b/src/NzbDrone.Core/Indexers/TorrentPotato/TorrentPotatoSettings.cs @@ -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)); diff --git a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs index ef2b74f9a..0cb0540e3 100644 --- a/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs +++ b/src/NzbDrone.Core/Indexers/TorrentRss/TorrentRssIndexerSettings.cs @@ -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)); diff --git a/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs b/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs index 19d2e3111..0678a12c2 100644 --- a/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs +++ b/src/NzbDrone.Core/Indexers/Torznab/Torznab.cs @@ -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()) { diff --git a/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs b/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs index 86d7be1a1..a5025d725 100644 --- a/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs +++ b/src/NzbDrone.Core/Indexers/Torznab/TorznabSettings.cs @@ -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)); diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs index e9895e542..e114d8bef 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs @@ -368,6 +368,11 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport decision = new ImportDecision(localEpisode, new Rejection("Unexpected error processing file")); } + if (decision == null) + { + _logger.Error("Unable to make a decision on {0}", file); + } + return decision; } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs index 671d3139a..1f117b525 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs @@ -160,7 +160,17 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual var importDecisions = _importDecisionMaker.GetImportDecisions(new List { file }, movie, null, SceneSource(movie, folder), true); - return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : null; + return importDecisions.Any() ? MapItem(importDecisions.First(), folder, downloadId) : new ManualImportItem + { + DownloadId = downloadId, + Path = file, + RelativePath = folder.GetRelativePath(file), + Name = Path.GetFileNameWithoutExtension(file), + Rejections = new List + { + new Rejection("Unable to process file") + } + }; } //private ManualImportItem ProcessFile(string file, string downloadId, string folder = null) diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 40f2693a3..49dfc097a 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -403,7 +403,7 @@ - + @@ -691,6 +691,7 @@ + @@ -706,6 +707,7 @@ + @@ -1285,6 +1287,7 @@ + diff --git a/src/UI/Content/Bootstrap/.csscomb.json b/src/UI/Content/Bootstrap/.csscomb.json old mode 100644 new mode 100755 diff --git a/src/UI/Content/Bootstrap/.csslintrc b/src/UI/Content/Bootstrap/.csslintrc old mode 100644 new mode 100755 diff --git a/src/UI/Content/Bootstrap/alerts.less b/src/UI/Content/Bootstrap/alerts.less old mode 100644 new mode 100755 diff --git a/src/UI/Content/Bootstrap/badges.less b/src/UI/Content/Bootstrap/badges.less old mode 100644 new mode 100755 diff --git a/src/UI/Content/Bootstrap/bootstrap.less b/src/UI/Content/Bootstrap/bootstrap.less old mode 100644 new mode 100755 index 4b9916e6c..f0aa08f3a --- a/src/UI/Content/Bootstrap/bootstrap.less +++ b/src/UI/Content/Bootstrap/bootstrap.less @@ -1,6 +1,6 @@ /*! - * Bootstrap v3.3.5 (http://getbootstrap.com) - * Copyright 2011-2015 Twitter, Inc. + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ diff --git a/src/UI/Content/Bootstrap/breadcrumbs.less b/src/UI/Content/Bootstrap/breadcrumbs.less old mode 100644 new mode 100755 diff --git a/src/UI/Content/Bootstrap/button-groups.less b/src/UI/Content/Bootstrap/button-groups.less old mode 100644 new mode 100755 index 6a0c5a865..16db0c613 --- a/src/UI/Content/Bootstrap/button-groups.less +++ b/src/UI/Content/Bootstrap/button-groups.less @@ -59,7 +59,7 @@ .border-right-radius(0); } } -// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it +// Need .dropdown-toggle since :last-child doesn't apply, given that a .dropdown-menu is used immediately after it .btn-group > .btn:last-child:not(:first-child), .btn-group > .dropdown-toggle:not(:first-child) { .border-left-radius(0); @@ -173,12 +173,12 @@ border-radius: 0; } &:first-child:not(:last-child) { - border-top-right-radius: @btn-border-radius-base; + .border-top-radius(@btn-border-radius-base); .border-bottom-radius(0); } &:last-child:not(:first-child) { - border-bottom-left-radius: @btn-border-radius-base; .border-top-radius(0); + .border-bottom-radius(@btn-border-radius-base); } } .btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { diff --git a/src/UI/Content/Bootstrap/buttons.less b/src/UI/Content/Bootstrap/buttons.less old mode 100644 new mode 100755 diff --git a/src/UI/Content/Bootstrap/carousel.less b/src/UI/Content/Bootstrap/carousel.less old mode 100644 new mode 100755 index 87ed6961d..252011e9e --- a/src/UI/Content/Bootstrap/carousel.less +++ b/src/UI/Content/Bootstrap/carousel.less @@ -101,6 +101,7 @@ color: @carousel-control-color; text-align: center; text-shadow: @carousel-text-shadow; + background-color: rgba(0, 0, 0, 0); // Fix IE9 click-thru bug // We can't have this transition here because WebKit cancels the carousel // animation if you trip this while in the middle of another animation. @@ -240,18 +241,18 @@ .glyphicon-chevron-right, .icon-prev, .icon-next { - width: 30px; - height: 30px; - margin-top: -15px; - font-size: 30px; + width: (@carousel-control-font-size * 1.5); + height: (@carousel-control-font-size * 1.5); + margin-top: (@carousel-control-font-size / -2); + font-size: (@carousel-control-font-size * 1.5); } .glyphicon-chevron-left, .icon-prev { - margin-left: -15px; + margin-left: (@carousel-control-font-size / -2); } .glyphicon-chevron-right, .icon-next { - margin-right: -15px; + margin-right: (@carousel-control-font-size / -2); } } diff --git a/src/UI/Content/Bootstrap/close.less b/src/UI/Content/Bootstrap/close.less old mode 100644 new mode 100755 diff --git a/src/UI/Content/Bootstrap/code.less b/src/UI/Content/Bootstrap/code.less old mode 100644 new mode 100755 diff --git a/src/UI/Content/Bootstrap/component-animations.less b/src/UI/Content/Bootstrap/component-animations.less old mode 100644 new mode 100755 diff --git a/src/UI/Content/Bootstrap/dropdowns.less b/src/UI/Content/Bootstrap/dropdowns.less old mode 100644 new mode 100755 diff --git a/src/UI/Content/Bootstrap/forms.less b/src/UI/Content/Bootstrap/forms.less old mode 100644 new mode 100755 index b064ede46..9377d3846 --- a/src/UI/Content/Bootstrap/forms.less +++ b/src/UI/Content/Bootstrap/forms.less @@ -132,6 +132,12 @@ output { // Placeholder .placeholder(); + // Unstyle the caret on `