From 53c2962d2aac7b5e2ab0d2c3d30b40225cd2118b Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 27 Oct 2014 21:37:35 -0700 Subject: [PATCH] Release restrictions New: Required terms assignable to series via tags New: Ignored terms assignable to series via tagss --- .../Config/IndexerConfigResource.cs | 1 - src/NzbDrone.Api/NzbDrone.Api.csproj | 2 + .../Restrictions/RestrictionModule.cs | 49 ++++++++ .../Restrictions/RestrictionResource.cs | 19 +++ src/NzbDrone.Common/IEnumerableExtensions.cs | 10 ++ ...otRestrictedReleaseSpecificationFixture.cs | 60 --------- ...ReleaseRestrictionsSpecificationFixture.cs | 114 ++++++++++++++++++ .../NzbDrone.Core.Test.csproj | 2 +- .../Configuration/ConfigService.cs | 8 -- .../Configuration/IConfigService.cs | 1 - .../Migration/068_add_release_restrictions.cs | 49 ++++++++ src/NzbDrone.Core/Datastore/TableMapping.cs | 2 + .../NotRestrictedReleaseSpecification.cs | 49 -------- .../ReleaseRestrictionsSpecification.cs | 66 ++++++++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 6 +- src/NzbDrone.Core/Restrictions/Restriction.cs | 19 +++ .../Restrictions/RestrictionRepository.cs | 17 +++ .../Restrictions/RestrictionService.cs | 66 ++++++++++ src/UI/Mixins/TagInput.js | 41 ++++++- src/UI/Series/Edit/EditSeriesView.js | 1 - ...emotePathMappingCollectionViewTemplate.hbs | 8 +- .../DownloadClient/downloadclient.less | 27 ----- src/UI/Settings/Indexers/IndexerLayout.js | 17 ++- .../Indexers/IndexerLayoutTemplate.hbs | 1 + .../Options/IndexerOptionsViewTemplate.hbs | 13 -- .../Restriction/RestrictionCollection.js | 11 ++ .../Restriction/RestrictionCollectionView.js | 27 +++++ .../RestrictionCollectionViewTemplate.hbs | 24 ++++ .../Restriction/RestrictionDeleteView.js | 23 ++++ .../RestrictionDeleteViewTemplate.hbs | 13 ++ .../Restriction/RestrictionEditView.js | 61 ++++++++++ .../RestrictionEditViewTemplate.hbs | 60 +++++++++ .../Restriction/RestrictionItemView.js | 30 +++++ .../RestrictionItemViewTemplate.hbs | 12 ++ .../Indexers/Restriction/RestrictionModel.js | 10 ++ src/UI/Settings/settings.less | 31 +++++ src/UI/Tags/TagHelpers.js | 28 +++-- src/UI/Tags/TagInputPartial.hbs | 1 - 38 files changed, 794 insertions(+), 185 deletions(-) create mode 100644 src/NzbDrone.Api/Restrictions/RestrictionModule.cs create mode 100644 src/NzbDrone.Api/Restrictions/RestrictionResource.cs delete mode 100644 src/NzbDrone.Core.Test/DecisionEngineTests/NotRestrictedReleaseSpecificationFixture.cs create mode 100644 src/NzbDrone.Core.Test/DecisionEngineTests/ReleaseRestrictionsSpecificationFixture.cs create mode 100644 src/NzbDrone.Core/Datastore/Migration/068_add_release_restrictions.cs delete mode 100644 src/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs create mode 100644 src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs create mode 100644 src/NzbDrone.Core/Restrictions/Restriction.cs create mode 100644 src/NzbDrone.Core/Restrictions/RestrictionRepository.cs create mode 100644 src/NzbDrone.Core/Restrictions/RestrictionService.cs create mode 100644 src/UI/Settings/Indexers/Restriction/RestrictionCollection.js create mode 100644 src/UI/Settings/Indexers/Restriction/RestrictionCollectionView.js create mode 100644 src/UI/Settings/Indexers/Restriction/RestrictionCollectionViewTemplate.hbs create mode 100644 src/UI/Settings/Indexers/Restriction/RestrictionDeleteView.js create mode 100644 src/UI/Settings/Indexers/Restriction/RestrictionDeleteViewTemplate.hbs create mode 100644 src/UI/Settings/Indexers/Restriction/RestrictionEditView.js create mode 100644 src/UI/Settings/Indexers/Restriction/RestrictionEditViewTemplate.hbs create mode 100644 src/UI/Settings/Indexers/Restriction/RestrictionItemView.js create mode 100644 src/UI/Settings/Indexers/Restriction/RestrictionItemViewTemplate.hbs create mode 100644 src/UI/Settings/Indexers/Restriction/RestrictionModel.js delete mode 100644 src/UI/Tags/TagInputPartial.hbs diff --git a/src/NzbDrone.Api/Config/IndexerConfigResource.cs b/src/NzbDrone.Api/Config/IndexerConfigResource.cs index aeb9706e3..e902b538f 100644 --- a/src/NzbDrone.Api/Config/IndexerConfigResource.cs +++ b/src/NzbDrone.Api/Config/IndexerConfigResource.cs @@ -7,6 +7,5 @@ namespace NzbDrone.Api.Config { public Int32 Retention { get; set; } public Int32 RssSyncInterval { get; set; } - public String ReleaseRestrictions { get; set; } } } diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index aa4e9dbc3..03681c89b 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -197,6 +197,8 @@ + + diff --git a/src/NzbDrone.Api/Restrictions/RestrictionModule.cs b/src/NzbDrone.Api/Restrictions/RestrictionModule.cs new file mode 100644 index 000000000..376db6e13 --- /dev/null +++ b/src/NzbDrone.Api/Restrictions/RestrictionModule.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using NzbDrone.Api.Mapping; +using NzbDrone.Core.Restrictions; + +namespace NzbDrone.Api.Restrictions +{ + public class RestrictionModule : NzbDroneRestModule + { + private readonly IRestrictionService _restrictionService; + + + public RestrictionModule(IRestrictionService restrictionService) + { + _restrictionService = restrictionService; + + GetResourceById = Get; + GetResourceAll = GetAll; + CreateResource = Create; + UpdateResource = Update; + DeleteResource = Delete; + } + + private RestrictionResource Get(Int32 id) + { + return _restrictionService.Get(id).InjectTo(); + } + + private List GetAll() + { + return ToListResource(_restrictionService.All); + } + + private Int32 Create(RestrictionResource resource) + { + return _restrictionService.Add(resource.InjectTo()).Id; + } + + private void Update(RestrictionResource resource) + { + _restrictionService.Update(resource.InjectTo()); + } + + private void Delete(Int32 id) + { + _restrictionService.Delete(id); + } + } +} diff --git a/src/NzbDrone.Api/Restrictions/RestrictionResource.cs b/src/NzbDrone.Api/Restrictions/RestrictionResource.cs new file mode 100644 index 000000000..4e0f71db1 --- /dev/null +++ b/src/NzbDrone.Api/Restrictions/RestrictionResource.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using NzbDrone.Api.REST; + +namespace NzbDrone.Api.Restrictions +{ + public class RestrictionResource : RestResource + { + public String Required { get; set; } + public String Preferred { get; set; } + public String Ignored { get; set; } + public HashSet Tags { get; set; } + + public RestrictionResource() + { + Tags = new HashSet(); + } + } +} diff --git a/src/NzbDrone.Common/IEnumerableExtensions.cs b/src/NzbDrone.Common/IEnumerableExtensions.cs index 4ccbcae6d..502086c27 100644 --- a/src/NzbDrone.Common/IEnumerableExtensions.cs +++ b/src/NzbDrone.Common/IEnumerableExtensions.cs @@ -27,5 +27,15 @@ namespace NzbDrone.Common { return !source.Any(); } + + public static bool None(this IEnumerable source, Func predicate) + { + return !source.Any(predicate); + } + + public static bool NotAll(this IEnumerable source, Func predicate) + { + return !source.All(predicate); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/NotRestrictedReleaseSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/NotRestrictedReleaseSpecificationFixture.cs deleted file mode 100644 index c0cab92c3..000000000 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/NotRestrictedReleaseSpecificationFixture.cs +++ /dev/null @@ -1,60 +0,0 @@ -using FluentAssertions; -using NUnit.Framework; -using NzbDrone.Core.Configuration; -using NzbDrone.Core.DecisionEngine.Specifications; -using NzbDrone.Core.Parser.Model; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.DecisionEngineTests -{ - [TestFixture] - public class NotRestrictedReleaseSpecificationFixture : CoreTest - { - private RemoteEpisode _parseResult; - - [SetUp] - public void Setup() - { - _parseResult = new RemoteEpisode - { - Release = new ReleaseInfo - { - Title = "Dexter.S08E01.EDITED.WEBRip.x264-KYR" - } - }; - } - - [Test] - public void should_be_true_when_restrictions_are_empty() - { - Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeTrue(); - } - - [TestCase("KYR")] - [TestCase("EDITED")] - [TestCase("edited")] - [TestCase("2HD\nKYR")] - [TestCase("2HD\nkyr")] - public void should_be_false_when_nzb_contains_a_restricted_term(string restrictions) - { - Mocker.GetMock().SetupGet(c => c.ReleaseRestrictions).Returns(restrictions); - Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeFalse(); - } - - [TestCase("NotReal")] - [TestCase("LoL")] - [TestCase("Hello\nWorld")] - public void should_be_true_when_nzb_does_not_contain_a_restricted_term(string restrictions) - { - Mocker.GetMock().SetupGet(c => c.ReleaseRestrictions).Returns(restrictions); - Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeTrue(); - } - - [Test] - public void should_not_try_to_find_empty_string_as_a_match() - { - Mocker.GetMock().SetupGet(c => c.ReleaseRestrictions).Returns("test\n"); - Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeTrue(); - } - } -} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/ReleaseRestrictionsSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/ReleaseRestrictionsSpecificationFixture.cs new file mode 100644 index 000000000..0beb24e40 --- /dev/null +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/ReleaseRestrictionsSpecificationFixture.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.DecisionEngine.Specifications; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Restrictions; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.DecisionEngineTests +{ + [TestFixture] + public class ReleaseRestrictionsSpecificationFixture : CoreTest + { + private RemoteEpisode _parseResult; + + [SetUp] + public void Setup() + { + _parseResult = new RemoteEpisode + { + Series = new Series + { + Tags = new HashSet() + }, + Release = new ReleaseInfo + { + Title = "Dexter.S08E01.EDITED.WEBRip.x264-KYR" + } + }; + } + + private void GivenRestictions(String required, String ignored) + { + Mocker.GetMock() + .Setup(s => s.AllForTags(It.IsAny>())) + .Returns(new List + { + new Restriction + { + Required = required, + Ignored = ignored + } + }); + } + + [Test] + public void should_be_true_when_restrictions_are_empty() + { + Mocker.GetMock() + .Setup(s => s.AllForTags(It.IsAny>())) + .Returns(new List()); + + Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_be_true_when_title_contains_one_required_term() + { + GivenRestictions("WEBRip", null); + + Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_be_false_when_title_does_not_contain_any_required_terms() + { + GivenRestictions("doesnt,exist", null); + + Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeFalse(); + } + + [Test] + public void should_be_true_when_title_does_not_contain_any_ignored_terms() + { + GivenRestictions(null, "ignored"); + + Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeTrue(); + } + + [Test] + public void should_be_false_when_title_contains_one_anded_ignored_terms() + { + GivenRestictions(null, "edited"); + + Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeFalse(); + } + + [TestCase("EdiTED")] + [TestCase("webrip")] + [TestCase("X264")] + [TestCase("X264,NOTTHERE")] + public void should_ignore_case_when_matching_required(String required) + { + GivenRestictions(required, null); + + Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeTrue(); + } + + [TestCase("EdiTED")] + [TestCase("webrip")] + [TestCase("X264")] + [TestCase("X264,NOTTHERE")] + public void should_ignore_case_when_matching_ignored(String ignored) + { + GivenRestictions(null, ignored); + + Subject.IsSatisfiedBy(_parseResult, null).Accepted.Should().BeFalse(); + } + } +} diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 65f3eba77..91dde053d 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -129,7 +129,7 @@ - + diff --git a/src/NzbDrone.Core/Configuration/ConfigService.cs b/src/NzbDrone.Core/Configuration/ConfigService.cs index 06068e5db..8293d066a 100644 --- a/src/NzbDrone.Core/Configuration/ConfigService.cs +++ b/src/NzbDrone.Core/Configuration/ConfigService.cs @@ -7,8 +7,6 @@ using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Configuration.Events; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Messaging.Events; -using NzbDrone.Core.Update; - namespace NzbDrone.Core.Configuration { @@ -103,12 +101,6 @@ namespace NzbDrone.Core.Configuration set { SetValue("RecycleBin", value); } } - public string ReleaseRestrictions - { - get { return GetValue("ReleaseRestrictions", String.Empty).Trim('\r', '\n'); } - set { SetValue("ReleaseRestrictions", value.Trim('\r', '\n')); } - } - public Int32 RssSyncInterval { get { return GetValueInt("RssSyncInterval", 15); } diff --git a/src/NzbDrone.Core/Configuration/IConfigService.cs b/src/NzbDrone.Core/Configuration/IConfigService.cs index e21185bb7..5dd81fc4b 100644 --- a/src/NzbDrone.Core/Configuration/IConfigService.cs +++ b/src/NzbDrone.Core/Configuration/IConfigService.cs @@ -47,7 +47,6 @@ namespace NzbDrone.Core.Configuration //Indexers Int32 Retention { get; set; } Int32 RssSyncInterval { get; set; } - String ReleaseRestrictions { get; set; } //UI Int32 FirstDayOfWeek { get; set; } diff --git a/src/NzbDrone.Core/Datastore/Migration/068_add_release_restrictions.cs b/src/NzbDrone.Core/Datastore/Migration/068_add_release_restrictions.cs new file mode 100644 index 000000000..c643e29ab --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/068_add_release_restrictions.cs @@ -0,0 +1,49 @@ +using System.Data; +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(68)] + public class add_release_restrictions : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Create.TableForModel("Restrictions") + .WithColumn("Required").AsString().Nullable() + .WithColumn("Preferred").AsString().Nullable() + .WithColumn("Ignored").AsString().Nullable() + .WithColumn("Tags").AsString().NotNullable(); + + Execute.WithConnection(ConvertRestrictions); + Execute.Sql("DELETE FROM Config WHERE [Key] = 'releaserestrictions'"); + } + + private void ConvertRestrictions(IDbConnection conn, IDbTransaction tran) + { + using (IDbCommand getRestictionsCmd = conn.CreateCommand()) + { + getRestictionsCmd.Transaction = tran; + getRestictionsCmd.CommandText = @"SELECT [Value] FROM Config WHERE [Key] = 'releaserestrictions'"; + + using (IDataReader configReader = getRestictionsCmd.ExecuteReader()) + { + while (configReader.Read()) + { + var restrictions = configReader.GetString(0); + restrictions = restrictions.Replace("\n", ","); + + using (IDbCommand insertCmd = conn.CreateCommand()) + { + insertCmd.Transaction = tran; + insertCmd.CommandText = "INSERT INTO Restrictions (Ignored, Tags) VALUES ('?', '[]')"; + insertCmd.AddParameter(restrictions); + + insertCmd.ExecuteNonQuery(); + } + } + } + } + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index b30b350ec..875ff2420 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -22,6 +22,7 @@ using NzbDrone.Core.Organizer; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Profiles; using NzbDrone.Core.Qualities; +using NzbDrone.Core.Restrictions; using NzbDrone.Core.RootFolders; using NzbDrone.Core.SeriesStats; using NzbDrone.Core.Tags; @@ -92,6 +93,7 @@ namespace NzbDrone.Core.Datastore Mapper.Entity().RegisterModel("RemotePathMappings"); Mapper.Entity().RegisterModel("Tags"); + Mapper.Entity().RegisterModel("Restrictions"); } private static void RegisterMappers() diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs deleted file mode 100644 index deb8428b9..000000000 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/NotRestrictedReleaseSpecification.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using NLog; -using NzbDrone.Core.Configuration; -using NzbDrone.Core.IndexerSearch.Definitions; -using NzbDrone.Core.Parser.Model; - -namespace NzbDrone.Core.DecisionEngine.Specifications -{ - public class NotRestrictedReleaseSpecification : IDecisionEngineSpecification - { - private readonly IConfigService _configService; - private readonly Logger _logger; - - public NotRestrictedReleaseSpecification(IConfigService configService, Logger logger) - { - _configService = configService; - _logger = logger; - } - - public RejectionType Type { get { return RejectionType.Permanent; } } - - public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) - { - _logger.Debug("Checking if release contains any restricted terms: {0}", subject); - - var restrictionsString = _configService.ReleaseRestrictions; - - if (String.IsNullOrWhiteSpace(restrictionsString)) - { - _logger.Debug("No restrictions configured, allowing: {0}", subject); - return Decision.Accept(); - } - - var restrictions = restrictionsString.Split(new []{ '\n' }, StringSplitOptions.RemoveEmptyEntries); - - foreach (var restriction in restrictions) - { - if (subject.Release.Title.ToLowerInvariant().Contains(restriction.ToLowerInvariant())) - { - _logger.Debug("{0} is restricted: {1}", subject, restriction); - return Decision.Reject("Contains restricted term: {0}", restriction); - } - } - - _logger.Debug("No restrictions apply, allowing: {0}", subject); - return Decision.Accept(); - } - } -} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs new file mode 100644 index 000000000..4a2d2b32b --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Common; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Restrictions; + +namespace NzbDrone.Core.DecisionEngine.Specifications +{ + public class ReleaseRestrictionsSpecification : IDecisionEngineSpecification + { + private readonly IRestrictionService _restrictionService; + private readonly Logger _logger; + + public ReleaseRestrictionsSpecification(IRestrictionService restrictionService, Logger logger) + { + _restrictionService = restrictionService; + _logger = logger; + } + + public RejectionType Type { get { return RejectionType.Permanent; } } + + public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) + { + _logger.Debug("Checking if release meets restrictions: {0}", subject); + + var title = subject.Release.Title; + var restrictions = _restrictionService.AllForTags(subject.Series.Tags); + + var required = restrictions.Where(r => r.Required.IsNotNullOrWhiteSpace()); + var ignored = restrictions.Where(r => r.Ignored.IsNotNullOrWhiteSpace()); + + foreach (var r in required) + { + var split = r.Required.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).ToList(); + + if (!ContainsAny(split, title)) + { + _logger.Debug("[{0}] does not contain one of the required terms: {1}", title, r.Required); + return Decision.Reject("Does not contain one of the required terms: {0}", r.Required); + } + } + + foreach (var r in ignored) + { + var split = r.Ignored.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); + + if (ContainsAny(split, title)) + { + _logger.Debug("[{0}] contains one or more ignored terms: {1}", title, r.Ignored); + return Decision.Reject("Contains one or more ignored terms: {0}", r.Ignored); + } + } + + _logger.Debug("[{0}] No restrictions apply, allowing", subject); + return Decision.Accept(); + } + + private static Boolean ContainsAny(List terms, String title) + { + return terms.Any(t => title.ToLowerInvariant().Contains(t.ToLowerInvariant())); + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 34b9a5c19..fd11dd1bd 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -230,6 +230,7 @@ + @@ -263,7 +264,7 @@ - + @@ -711,6 +712,9 @@ + + + diff --git a/src/NzbDrone.Core/Restrictions/Restriction.cs b/src/NzbDrone.Core/Restrictions/Restriction.cs new file mode 100644 index 000000000..c3944f444 --- /dev/null +++ b/src/NzbDrone.Core/Restrictions/Restriction.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using NzbDrone.Core.Datastore; + +namespace NzbDrone.Core.Restrictions +{ + public class Restriction : ModelBase + { + public String Required { get; set; } + public String Preferred { get; set; } + public String Ignored { get; set; } + public HashSet Tags { get; set; } + + public Restriction() + { + Tags = new HashSet(); + } + } +} diff --git a/src/NzbDrone.Core/Restrictions/RestrictionRepository.cs b/src/NzbDrone.Core/Restrictions/RestrictionRepository.cs new file mode 100644 index 000000000..98b3a2acc --- /dev/null +++ b/src/NzbDrone.Core/Restrictions/RestrictionRepository.cs @@ -0,0 +1,17 @@ +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Messaging.Events; + +namespace NzbDrone.Core.Restrictions +{ + public interface IRestrictionRepository : IBasicRepository + { + } + + public class RestrictionRepository : BasicRepository, IRestrictionRepository + { + public RestrictionRepository(IDatabase database, IEventAggregator eventAggregator) + : base(database, eventAggregator) + { + } + } +} diff --git a/src/NzbDrone.Core/Restrictions/RestrictionService.cs b/src/NzbDrone.Core/Restrictions/RestrictionService.cs new file mode 100644 index 000000000..c06341b51 --- /dev/null +++ b/src/NzbDrone.Core/Restrictions/RestrictionService.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Common; + +namespace NzbDrone.Core.Restrictions +{ + public interface IRestrictionService + { + List All(); + List AllForTag(Int32 tagId); + List AllForTags(HashSet tagIds); + Restriction Get(Int32 id); + void Delete(Int32 id); + Restriction Add(Restriction restriction); + Restriction Update(Restriction restriction); + } + + public class RestrictionService : IRestrictionService + { + private readonly IRestrictionRepository _repo; + private readonly Logger _logger; + + public RestrictionService(IRestrictionRepository repo, Logger logger) + { + _repo = repo; + _logger = logger; + } + + public List All() + { + return _repo.All().ToList(); + } + + public List AllForTag(Int32 tagId) + { + return _repo.All().Where(r => r.Tags.Contains(tagId) || r.Tags.Empty()).ToList(); + } + + public List AllForTags(HashSet tagIds) + { + return _repo.All().Where(r => r.Tags.Intersect(tagIds).Any() || r.Tags.Empty()).ToList(); + } + + public Restriction Get(Int32 id) + { + return _repo.Get(id); + } + + public void Delete(Int32 id) + { + _repo.Delete(id); + } + + public Restriction Add(Restriction restriction) + { + return _repo.Insert(restriction); + } + + public Restriction Update(Restriction restriction) + { + return _repo.Update(restriction); + } + } +} diff --git a/src/UI/Mixins/TagInput.js b/src/UI/Mixins/TagInput.js index ce5315605..9c6dd81dd 100644 --- a/src/UI/Mixins/TagInput.js +++ b/src/UI/Mixins/TagInput.js @@ -9,12 +9,14 @@ define( ], function ($, _, TagCollection, TagModel) { var originalAdd = $.fn.tagsinput.Constructor.prototype.add; + var originalRemove = $.fn.tagsinput.Constructor.prototype.remove; + var originalBuild = $.fn.tagsinput.Constructor.prototype.build; $.fn.tagsinput.Constructor.prototype.add = function (item, dontPushVal) { var self = this; var tagLimitations = new RegExp('[^-_a-z0-9]', 'i'); - if (typeof item === 'string') { + if (typeof item === 'string' && this.options.tag) { if (item === null || item === '' || tagLimitations.test(item)) { return; @@ -42,6 +44,34 @@ define( } }; + $.fn.tagsinput.Constructor.prototype.remove = function (item, dontPushVal) { + if (item === null) { + return; + } + + originalRemove.call(this, item, dontPushVal); + }; + + $.fn.tagsinput.Constructor.prototype.build = function (options) { + var self = this; + var defaults = { + confirmKeys : [9, 13, 32, 44, 59] //tab, enter, space, comma, semi-colon + }; + + options = $.extend({}, defaults, options); + + self.$input.on('keydown', function (event) { + if (event.which === 9) { + var e = $.Event('keypress'); + e.which = 9; + self.$input.trigger(e); + event.preventDefault(); + } + }); + + originalBuild.call(this, options); + }; + $.fn.tagInput = function (options) { var input = this; var model = options.model; @@ -49,10 +79,11 @@ define( var tags = getExistingTags(model.get(property)); var tagInput = $(this).tagsinput({ - freeInput: true, - itemValue : 'id', - itemText : 'label', - trimValue : true, + tag : true, + freeInput : true, + itemValue : 'id', + itemText : 'label', + trimValue : true, typeaheadjs : { name: 'tags', displayKey: 'label', diff --git a/src/UI/Series/Edit/EditSeriesView.js b/src/UI/Series/Edit/EditSeriesView.js index 963512ff4..f56876310 100644 --- a/src/UI/Series/Edit/EditSeriesView.js +++ b/src/UI/Series/Edit/EditSeriesView.js @@ -24,7 +24,6 @@ define( 'click .x-remove': '_removeSeries' }, - initialize: function () { this.model.set('profiles', Profiles); }, diff --git a/src/UI/Settings/DownloadClient/RemotePathMapping/RemotePathMappingCollectionViewTemplate.hbs b/src/UI/Settings/DownloadClient/RemotePathMapping/RemotePathMappingCollectionViewTemplate.hbs index 846dee402..ec332b59f 100644 --- a/src/UI/Settings/DownloadClient/RemotePathMapping/RemotePathMappingCollectionViewTemplate.hbs +++ b/src/UI/Settings/DownloadClient/RemotePathMapping/RemotePathMappingCollectionViewTemplate.hbs @@ -2,8 +2,8 @@ Remote Path Mappings
-
-