diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 92ff378f7..af1b5fe55 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -100,6 +100,7 @@ + diff --git a/src/NzbDrone.Api/Profiles/Delay/DelayProfileModule.cs b/src/NzbDrone.Api/Profiles/Delay/DelayProfileModule.cs index 6cea9b4f7..f18bdf6ef 100644 --- a/src/NzbDrone.Api/Profiles/Delay/DelayProfileModule.cs +++ b/src/NzbDrone.Api/Profiles/Delay/DelayProfileModule.cs @@ -24,6 +24,9 @@ namespace NzbDrone.Api.Profiles.Delay SharedValidator.RuleFor(d => d.Tags).NotEmpty().When(d => d.Id != 1); SharedValidator.RuleFor(d => d.Tags).EmptyCollection().When(d => d.Id == 1); SharedValidator.RuleFor(d => d.Tags).SetValidator(tagInUseValidator); + SharedValidator.RuleFor(d => d.UsenetDelay).GreaterThanOrEqualTo(0); + SharedValidator.RuleFor(d => d.TorrentDelay).GreaterThanOrEqualTo(0); + SharedValidator.RuleFor(d => d.Id).SetValidator(new DelayProfileValidator()); } private int Create(DelayProfileResource resource) diff --git a/src/NzbDrone.Api/Profiles/Delay/DelayProfileResource.cs b/src/NzbDrone.Api/Profiles/Delay/DelayProfileResource.cs index 00e5c8e44..bbc2fc67f 100644 --- a/src/NzbDrone.Api/Profiles/Delay/DelayProfileResource.cs +++ b/src/NzbDrone.Api/Profiles/Delay/DelayProfileResource.cs @@ -6,6 +6,8 @@ namespace NzbDrone.Api.Profiles.Delay { public class DelayProfileResource : RestResource { + public bool EnableUsenet { get; set; } + public bool EnableTorrent { get; set; } public DownloadProtocol PreferredProtocol { get; set; } public int UsenetDelay { get; set; } public int TorrentDelay { get; set; } diff --git a/src/NzbDrone.Api/Profiles/Delay/DelayProfileValidator.cs b/src/NzbDrone.Api/Profiles/Delay/DelayProfileValidator.cs new file mode 100644 index 000000000..b854d87a2 --- /dev/null +++ b/src/NzbDrone.Api/Profiles/Delay/DelayProfileValidator.cs @@ -0,0 +1,27 @@ +using FluentValidation.Validators; +using NzbDrone.Core.Profiles.Delay; +using Omu.ValueInjecter; + +namespace NzbDrone.Api.Profiles.Delay +{ + public class DelayProfileValidator : PropertyValidator + { + public DelayProfileValidator() + : base("Usenet or Torrent must be enabled") + { + } + + protected override bool IsValid(PropertyValidatorContext context) + { + var delayProfile = new DelayProfile(); + delayProfile.InjectFrom(context.ParentContext.InstanceToValidate); + + if (!delayProfile.EnableUsenet && !delayProfile.EnableTorrent) + { + return false; + } + + return true; + } + } +} diff --git a/src/NzbDrone.Core.Test/Datastore/Migration/070_delay_profileFixture.cs b/src/NzbDrone.Core.Test/Datastore/Migration/070_delay_profileFixture.cs index 7ade7e918..7f0cf4d1b 100644 --- a/src/NzbDrone.Core.Test/Datastore/Migration/070_delay_profileFixture.cs +++ b/src/NzbDrone.Core.Test/Datastore/Migration/070_delay_profileFixture.cs @@ -1,21 +1,20 @@ -using System; +using System.Collections.Generic; using System.Linq; using FluentAssertions; -using FluentMigrator; using NUnit.Framework; -using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore.Migration; -using NzbDrone.Core.Datastore.Migration.Framework; using NzbDrone.Core.Indexers; using NzbDrone.Core.Profiles.Delay; +using NzbDrone.Core.Tags; using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; namespace NzbDrone.Core.Test.Datastore.Migration { [TestFixture] public class delay_profileFixture : MigrationTest { - [TestCase] + [Test] public void should_migrate_old_delays() { WithTestDb(c => @@ -27,6 +26,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration Cutoff = "{}", Items = "{}" }); + c.Insert.IntoTable("Profiles").Row(new { GrabDelay = 2, @@ -36,17 +36,72 @@ namespace NzbDrone.Core.Test.Datastore.Migration }); }); - var allProfiles = Mocker.Resolve().All().ToList(); - allProfiles.Should().HaveCount(3); allProfiles.Should().OnlyContain(c => c.PreferredProtocol == DownloadProtocol.Usenet); allProfiles.Should().OnlyContain(c => c.TorrentDelay == 0); allProfiles.Should().Contain(c => c.UsenetDelay == 60); allProfiles.Should().Contain(c => c.UsenetDelay == 120); + } + [Test] + public void should_create_tag_for_delay_profile() + { + WithTestDb(c => + c.Insert.IntoTable("Profiles").Row(new + { + GrabDelay = 1, + Name = "OneHour", + Cutoff = "{}", + Items = "{}" + }) + ); + + var tags = Mocker.Resolve().All().ToList(); + + tags.Should().HaveCount(1); + tags.First().Label.Should().Be("delay-60"); + } + + [Test] + public void should_add_tag_to_series_that_had_a_profile_with_delay_attached() + { + WithTestDb(c => + { + c.Insert.IntoTable("Profiles").Row(new + { + GrabDelay = 1, + Name = "OneHour", + Cutoff = "{}", + Items = "{}" + }); + + c.Insert.IntoTable("Series").Row(new + { + TvdbId = 0, + TvRageId = 0, + Title = "Series", + TitleSlug = "series", + CleanTitle = "series", + Status = 0, + Images = "[]", + Path = @"C:\Test\Series", + Monitored = 1, + SeasonFolder = 1, + RunTime = 0, + SeriesType = 0, + UseSceneNumbering = 0, + Tags = "[1]" + }); + }); + + var tag = Mocker.Resolve().All().ToList().First(); + var series = Mocker.Resolve().All().ToList(); + + series.Should().HaveCount(1); + series.First().Tags.Should().HaveCount(1); + series.First().Tags.First().Should().Be(tag.Id); } } - -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/ProtocolSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/ProtocolSpecificationFixture.cs new file mode 100644 index 000000000..4bfaf34dc --- /dev/null +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/ProtocolSpecificationFixture.cs @@ -0,0 +1,75 @@ +using System.Collections.Generic; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.DecisionEngine.Specifications; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles.Delay; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.DecisionEngineTests +{ + [TestFixture] + public class ProtocolSpecificationFixture : CoreTest + { + private RemoteEpisode _remoteEpisode; + private DelayProfile _delayProfile; + + [SetUp] + public void Setup() + { + _remoteEpisode = new RemoteEpisode(); + _remoteEpisode.Release = new ReleaseInfo(); + _remoteEpisode.Series = new Series(); + + _delayProfile = new DelayProfile(); + + Mocker.GetMock() + .Setup(s => s.BestForTags(It.IsAny>())) + .Returns(_delayProfile); + } + + private void GivenProtocol(DownloadProtocol downloadProtocol) + { + _remoteEpisode.Release.DownloadProtocol = downloadProtocol; + } + + [Test] + public void should_be_true_if_usenet_and_usenet_is_enabled() + { + GivenProtocol(DownloadProtocol.Usenet); + _delayProfile.EnableUsenet = true; + + Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().Be(true); + } + + [Test] + public void should_be_true_if_torrent_and_torrent_is_enabled() + { + GivenProtocol(DownloadProtocol.Torrent); + _delayProfile.EnableTorrent = true; + + Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().Be(true); + } + + [Test] + public void should_be_false_if_usenet_and_usenet_is_disabled() + { + GivenProtocol(DownloadProtocol.Usenet); + _delayProfile.EnableUsenet = false; + + Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().Be(false); + } + + [Test] + public void should_be_false_if_torrent_and_torrent_is_disabled() + { + GivenProtocol(DownloadProtocol.Torrent); + _delayProfile.EnableTorrent = false; + + Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().Be(false); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 221b61979..02cbab637 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -124,6 +124,7 @@ + diff --git a/src/NzbDrone.Core/Datastore/Migration/070_delay_profile.cs b/src/NzbDrone.Core/Datastore/Migration/070_delay_profile.cs index a8add7616..8ef186064 100644 --- a/src/NzbDrone.Core/Datastore/Migration/070_delay_profile.cs +++ b/src/NzbDrone.Core/Datastore/Migration/070_delay_profile.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Data; using System.Linq; using FluentMigrator; -using NzbDrone.Common; using NzbDrone.Common.Extensions; using NzbDrone.Common.Serializer; using NzbDrone.Core.Datastore.Migration.Framework; @@ -16,15 +15,18 @@ namespace NzbDrone.Core.Datastore.Migration protected override void MainDbUpgrade() { Create.TableForModel("DelayProfiles") + .WithColumn("EnableUsenet").AsBoolean().NotNullable() + .WithColumn("EnableTorrent").AsBoolean().NotNullable() .WithColumn("PreferredProtocol").AsInt32().NotNullable() .WithColumn("UsenetDelay").AsInt32().NotNullable() .WithColumn("TorrentDelay").AsInt32().NotNullable() .WithColumn("Order").AsInt32().NotNullable() .WithColumn("Tags").AsString().NotNullable(); - Insert.IntoTable("DelayProfiles").Row(new { + EnableUsenet = 1, + EnableTorrent = 1, PreferredProtocol = 1, UsenetDelay = 0, TorrentDelay = 0, @@ -55,7 +57,7 @@ namespace NzbDrone.Core.Datastore.Migration using (IDbCommand insertDelayProfileCmd = conn.CreateCommand()) { insertDelayProfileCmd.Transaction = tran; - insertDelayProfileCmd.CommandText = "INSERT INTO DelayProfiles (PreferredProtocol, TorrentDelay, UsenetDelay, [Order], Tags) VALUES (1, 0, ?, ?, ?)"; + insertDelayProfileCmd.CommandText = "INSERT INTO DelayProfiles (EnableUsenet, EnableTorrent, PreferredProtocol, TorrentDelay, UsenetDelay, [Order], Tags) VALUES (1, 1, 1, 0, ?, ?, ?)"; insertDelayProfileCmd.AddParameter(profile.GrabDelay); insertDelayProfileCmd.AddParameter(order); insertDelayProfileCmd.AddParameter(tags); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/ProtocolSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/ProtocolSpecification.cs new file mode 100644 index 000000000..00f9e0083 --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/ProtocolSpecification.cs @@ -0,0 +1,45 @@ +using NLog; +using NzbDrone.Core.Download.Pending; +using NzbDrone.Core.Indexers; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Profiles.Delay; + +namespace NzbDrone.Core.DecisionEngine.Specifications +{ + public class ProtocolSpecification : IDecisionEngineSpecification + { + private readonly IPendingReleaseService _pendingReleaseService; + private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification; + private readonly IDelayProfileService _delayProfileService; + private readonly Logger _logger; + + public ProtocolSpecification(IDelayProfileService delayProfileService, + Logger logger) + { + _delayProfileService = delayProfileService; + _logger = logger; + } + + public RejectionType Type { get { return RejectionType.Temporary; } } + + public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) + { + var delayProfile = _delayProfileService.BestForTags(subject.Series.Tags); + + if (subject.Release.DownloadProtocol == DownloadProtocol.Usenet && !delayProfile.EnableUsenet) + { + _logger.Debug("[{0}] Usenet is not enabled for this series", subject.Release.Title); + return Decision.Reject("Usenet is not enabled for this series"); + } + + if (subject.Release.DownloadProtocol == DownloadProtocol.Torrent && !delayProfile.EnableTorrent) + { + _logger.Debug("[{0}] Torrent is not enabled for this series", subject.Release.Title); + return Decision.Reject("Torrent is not enabled for this series"); + } + + return Decision.Accept(); + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 876f10c94..b0a532c18 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -261,6 +261,7 @@ + diff --git a/src/NzbDrone.Core/Profiles/Delay/DelayProfile.cs b/src/NzbDrone.Core/Profiles/Delay/DelayProfile.cs index 65f403b5b..ef20bb6a5 100644 --- a/src/NzbDrone.Core/Profiles/Delay/DelayProfile.cs +++ b/src/NzbDrone.Core/Profiles/Delay/DelayProfile.cs @@ -6,6 +6,8 @@ namespace NzbDrone.Core.Profiles.Delay { public class DelayProfile : ModelBase { + public bool EnableUsenet { get; set; } + public bool EnableTorrent { get; set; } public DownloadProtocol PreferredProtocol { get; set; } public int UsenetDelay { get; set; } public int TorrentDelay { get; set; } diff --git a/src/NzbDrone.Core/Profiles/Delay/DelayProfileTagInUseValidator.cs b/src/NzbDrone.Core/Profiles/Delay/DelayProfileTagInUseValidator.cs index 80cb89294..3a5d2a5c7 100644 --- a/src/NzbDrone.Core/Profiles/Delay/DelayProfileTagInUseValidator.cs +++ b/src/NzbDrone.Core/Profiles/Delay/DelayProfileTagInUseValidator.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Linq; using FluentValidation.Validators; -using NzbDrone.Common; using NzbDrone.Common.Extensions; using Omu.ValueInjecter; diff --git a/src/UI/Mixins/TagInput.js b/src/UI/Mixins/TagInput.js index 9c6dd81dd..a03730532 100644 --- a/src/UI/Mixins/TagInput.js +++ b/src/UI/Mixins/TagInput.js @@ -26,22 +26,27 @@ define( if (existing) { originalAdd.call(this, existing, dontPushVal); - return; } - var newTag = new TagModel(); - newTag.set({ label: item.toLowerCase() }); - TagCollection.add(newTag); + else { + var newTag = new TagModel(); + newTag.set({ label: item.toLowerCase() }); + TagCollection.add(newTag); - newTag.save().done(function () { - item = newTag.toJSON(); - originalAdd.call(self, item, dontPushVal); - }); + newTag.save().done(function () { + item = newTag.toJSON(); + originalAdd.call(self, item, dontPushVal); + }); + } } else { originalAdd.call(this, item, dontPushVal); } + + if (this.options.tag) { + self.$input.typeahead('val', ''); + } }; $.fn.tagsinput.Constructor.prototype.remove = function (item, dontPushVal) { @@ -69,6 +74,11 @@ define( } }); + self.$input.on('focusout', function () { + self.add(self.$input.val()); + self.$input.val(''); + }); + originalBuild.call(this, options); }; diff --git a/src/UI/Settings/Profile/Delay/DelayProfileItemViewTemplate.hbs b/src/UI/Settings/Profile/Delay/DelayProfileItemViewTemplate.hbs index 2022ee3a1..4175db917 100644 --- a/src/UI/Settings/Profile/Delay/DelayProfileItemViewTemplate.hbs +++ b/src/UI/Settings/Profile/Delay/DelayProfileItemViewTemplate.hbs @@ -1,29 +1,47 @@ 
- {{TitleCase preferredProtocol}} + {{#if enableUsenet}} + {{#if enableTorrent}} + {{#if_eq preferredProtocol compare="usenet"}} + Prefer Usenet + {{else}} + Prefer Torrent + {{/if_eq}} + {{else}} + Only Usenet + {{/if}} + {{else}} + Only Torrent + {{/if}}
- - {{#if_eq usenetDelay compare="0"}} - No delay - {{else}} - {{#if_eq usenetDelay compare="1"}} - 1 minute + {{#if enableUsenet}} + {{#if_eq usenetDelay compare="0"}} + No delay {{else}} - {{usenetDelay}} minutes + {{#if_eq usenetDelay compare="1"}} + 1 minute + {{else}} + {{usenetDelay}} minutes + {{/if_eq}} {{/if_eq}} - {{/if_eq}} - + {{else}} + - + {{/if}}
- {{#if_eq torrentDelay compare="0"}} - No delay - {{else}} - {{#if_eq torrentDelay compare="1"}} - 1 minute + {{#if enableTorrent}} + {{#if_eq torrentDelay compare="0"}} + No delay {{else}} - {{torrentDelay}} minutes + {{#if_eq torrentDelay compare="1"}} + 1 minute + {{else}} + {{torrentDelay}} minutes + {{/if_eq}} {{/if_eq}} - {{/if_eq}} + {{else}} + - + {{/if}}
{{tagDisplay tags}} diff --git a/src/UI/Settings/Profile/Delay/DelayProfileLayoutTemplate.hbs b/src/UI/Settings/Profile/Delay/DelayProfileLayoutTemplate.hbs index 2edaa4a88..78acf91e9 100644 --- a/src/UI/Settings/Profile/Delay/DelayProfileLayoutTemplate.hbs +++ b/src/UI/Settings/Profile/Delay/DelayProfileLayoutTemplate.hbs @@ -5,7 +5,7 @@