diff --git a/frontend/src/Components/Form/TextTagInputConnector.js b/frontend/src/Components/Form/TextTagInputConnector.js index 587066610..e0df9342d 100644 --- a/frontend/src/Components/Form/TextTagInputConnector.js +++ b/frontend/src/Components/Form/TextTagInputConnector.js @@ -46,13 +46,13 @@ class TextTagInputConnector extends Component { // to oddities with restrictions (as an example). const newValue = [...valueArray]; - const newTags = split(tag.name); + const newTags = tag.name.startsWith('/') ? [tag.name] : split(tag.name); newTags.forEach((newTag) => { newValue.push(newTag.trim()); }); - onChange({ name, value: newValue.join(',') }); + onChange({ name, value: newValue }); } onTagDelete = ({ index }) => { diff --git a/frontend/src/Settings/Profiles/Release/EditReleaseProfileModalContent.js b/frontend/src/Settings/Profiles/Release/EditReleaseProfileModalContent.js index 1c90faa36..cbc720a89 100644 --- a/frontend/src/Settings/Profiles/Release/EditReleaseProfileModalContent.js +++ b/frontend/src/Settings/Profiles/Release/EditReleaseProfileModalContent.js @@ -13,7 +13,7 @@ import FormLabel from 'Components/Form/FormLabel'; import FormInputGroup from 'Components/Form/FormInputGroup'; import styles from './EditReleaseProfileModalContent.css'; -const tagInputDelimiters = ['Tab', 'Enter', ',']; +const tagInputDelimiters = ['Tab', 'Enter']; function EditReleaseProfileModalContent(props) { const { diff --git a/frontend/src/Settings/Profiles/Release/ReleaseProfile.js b/frontend/src/Settings/Profiles/Release/ReleaseProfile.js index 8daef52a3..f9dfea1f0 100644 --- a/frontend/src/Settings/Profiles/Release/ReleaseProfile.js +++ b/frontend/src/Settings/Profiles/Release/ReleaseProfile.js @@ -90,7 +90,7 @@ class ReleaseProfile extends Component {
{ - split(required).map((item) => { + required.map((item) => { if (!item) { return null; } @@ -126,7 +126,7 @@ class ReleaseProfile extends Component {
{ - split(ignored).map((item) => { + ignored.map((item) => { if (!item) { return null; } @@ -195,8 +195,8 @@ ReleaseProfile.propTypes = { id: PropTypes.number.isRequired, name: PropTypes.string, enabled: PropTypes.bool.isRequired, - required: PropTypes.string.isRequired, - ignored: PropTypes.string.isRequired, + required: PropTypes.arrayOf(PropTypes.string).isRequired, + ignored: PropTypes.arrayOf(PropTypes.string).isRequired, preferred: PropTypes.arrayOf(PropTypes.object).isRequired, tags: PropTypes.arrayOf(PropTypes.number).isRequired, indexerId: PropTypes.number.isRequired, diff --git a/src/NzbDrone.Api/Restrictions/RestrictionResource.cs b/src/NzbDrone.Api/Restrictions/RestrictionResource.cs index 2849a8d34..3989eefa2 100644 --- a/src/NzbDrone.Api/Restrictions/RestrictionResource.cs +++ b/src/NzbDrone.Api/Restrictions/RestrictionResource.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using NzbDrone.Core.Profiles.Releases; @@ -27,8 +28,8 @@ namespace NzbDrone.Api.Restrictions { Id = model.Id, - Required = model.Required, - Ignored = model.Ignored, + Required = string.Join(",", model.Required), + Ignored = string.Join(",", model.Ignored), Tags = new HashSet(model.Tags) }; } @@ -41,8 +42,8 @@ namespace NzbDrone.Api.Restrictions { Id = resource.Id, - Required = resource.Required, - Ignored = resource.Ignored, + Required = resource.Required.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), + Ignored = resource.Ignored.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(), Tags = new HashSet(resource.Tags) }; } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/ReleaseRestrictionsSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/ReleaseRestrictionsSpecificationFixture.cs index 208782c6a..838aeb416 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/ReleaseRestrictionsSpecificationFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/ReleaseRestrictionsSpecificationFixture.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using FluentAssertions; using Moq; using NUnit.Framework; @@ -33,7 +34,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests Mocker.SetConstant(Mocker.Resolve()); } - private void GivenRestictions(string required, string ignored) + private void GivenRestictions(List required, List ignored) { Mocker.GetMock() .Setup(s => s.EnabledForTags(It.IsAny>(), It.IsAny())) @@ -60,7 +61,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_be_true_when_title_contains_one_required_term() { - GivenRestictions("WEBRip", null); + GivenRestictions(new List { "WEBRip" }, null); Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); } @@ -68,7 +69,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_be_false_when_title_does_not_contain_any_required_terms() { - GivenRestictions("doesnt,exist", null); + GivenRestictions(new List { "doesnt", "exist" }, null); Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse(); } @@ -76,7 +77,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_be_true_when_title_does_not_contain_any_ignored_terms() { - GivenRestictions(null, "ignored"); + GivenRestictions(null, new List { "ignored" }); Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); } @@ -84,7 +85,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [Test] public void should_be_false_when_title_contains_one_anded_ignored_terms() { - GivenRestictions(null, "edited"); + GivenRestictions(null, new List { "edited" }); Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse(); } @@ -95,7 +96,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [TestCase("X264,NOTTHERE")] public void should_ignore_case_when_matching_required(string required) { - GivenRestictions(required, null); + GivenRestictions(required.Split(',').ToList(), null); Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue(); } @@ -106,7 +107,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [TestCase("X264,NOTTHERE")] public void should_ignore_case_when_matching_ignored(string ignored) { - GivenRestictions(null, ignored); + GivenRestictions(null, ignored.Split(',').ToList()); Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse(); } @@ -120,7 +121,11 @@ namespace NzbDrone.Core.Test.DecisionEngineTests .Setup(s => s.EnabledForTags(It.IsAny>(), It.IsAny())) .Returns(new List { - new ReleaseProfile { Required = "x264", Ignored = "www.Speed.cd" } + new ReleaseProfile + { + Required = new List { "x264" }, + Ignored = new List { "www.Speed.cd" } + } }); Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse(); @@ -132,7 +137,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests [TestCase(@"/\.WEB/", true)] public void should_match_perl_regex(string pattern, bool expected) { - GivenRestictions(pattern, null); + GivenRestictions(pattern.Split(',').ToList(), null); Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().Be(expected); } diff --git a/src/NzbDrone.Core/Datastore/Migration/162_release_profile_to_array.cs b/src/NzbDrone.Core/Datastore/Migration/162_release_profile_to_array.cs new file mode 100644 index 000000000..3c3e48252 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/162_release_profile_to_array.cs @@ -0,0 +1,48 @@ +using System; +using System.Data; +using System.Linq; +using FluentMigrator; +using NzbDrone.Common.Serializer; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(162)] + public class release_profile_to_array : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Execute.WithConnection(ChangeRequiredIgnoredTypes); + } + + private void ChangeRequiredIgnoredTypes(IDbConnection conn, IDbTransaction tran) + { + using (var getEmailCmd = conn.CreateCommand()) + { + getEmailCmd.Transaction = tran; + getEmailCmd.CommandText = "SELECT Id, Required, Ignored FROM ReleaseProfiles"; + + using (var reader = getEmailCmd.ExecuteReader()) + { + while (reader.Read()) + { + var id = reader.GetInt32(0); + var required = reader.GetString(1).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + var ignored = reader.GetString(2).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + using (var updateCmd = conn.CreateCommand()) + { + updateCmd.Transaction = tran; + updateCmd.CommandText = "UPDATE ReleaseProfiles SET Required = ?, Ignored = ? WHERE Id = ?"; + updateCmd.AddParameter(required.ToJson()); + updateCmd.AddParameter(ignored.ToJson()); + updateCmd.AddParameter(id); + + updateCmd.ExecuteNonQuery(); + } + } + } + } + } + } +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs index e2bed10c5..c48d64f0d 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/ReleaseRestrictionsSpecification.cs @@ -32,12 +32,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications var title = subject.Release.Title; var releaseProfiles = _releaseProfileService.EnabledForTags(subject.Series.Tags, subject.Release.IndexerId); - var required = releaseProfiles.Where(r => r.Required.IsNotNullOrWhiteSpace()); - var ignored = releaseProfiles.Where(r => r.Ignored.IsNotNullOrWhiteSpace()); + var required = releaseProfiles.Where(r => r.Required.Any()); + var ignored = releaseProfiles.Where(r => r.Ignored.Any()); foreach (var r in required) { - var requiredTerms = r.Required.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).ToList(); + var requiredTerms = r.Required; var foundTerms = ContainsAny(requiredTerms, title); if (foundTerms.Empty()) @@ -50,7 +50,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications foreach (var r in ignored) { - var ignoredTerms = r.Ignored.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(); + var ignoredTerms = r.Ignored; var foundTerms = ContainsAny(ignoredTerms, title); if (foundTerms.Any()) diff --git a/src/NzbDrone.Core/Profiles/Releases/ReleaseProfile.cs b/src/NzbDrone.Core/Profiles/Releases/ReleaseProfile.cs index 527ca483a..bd29da39f 100644 --- a/src/NzbDrone.Core/Profiles/Releases/ReleaseProfile.cs +++ b/src/NzbDrone.Core/Profiles/Releases/ReleaseProfile.cs @@ -7,8 +7,8 @@ namespace NzbDrone.Core.Profiles.Releases { public string Name { get; set; } public bool Enabled { get; set; } - public string Required { get; set; } - public string Ignored { get; set; } + public List Required { get; set; } + public List Ignored { get; set; } public List> Preferred { get; set; } public bool IncludePreferredWhenRenaming { get; set; } public int IndexerId { get; set; } @@ -17,6 +17,8 @@ namespace NzbDrone.Core.Profiles.Releases public ReleaseProfile() { Enabled = true; + Required = new List(); + Ignored = new List(); Preferred = new List>(); IncludePreferredWhenRenaming = true; Tags = new HashSet(); diff --git a/src/Sonarr.Api.V3/Profiles/Release/ReleaseProfileModule.cs b/src/Sonarr.Api.V3/Profiles/Release/ReleaseProfileModule.cs index ed8587083..d3fed8e8a 100644 --- a/src/Sonarr.Api.V3/Profiles/Release/ReleaseProfileModule.cs +++ b/src/Sonarr.Api.V3/Profiles/Release/ReleaseProfileModule.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Linq; using FluentValidation; -using FluentValidation.Results; using NzbDrone.Common.Extensions; using NzbDrone.Core.Indexers; using NzbDrone.Core.Profiles.Releases; @@ -28,7 +27,7 @@ namespace Sonarr.Api.V3.Profiles.Release SharedValidator.RuleFor(d => d).Custom((restriction, context) => { - if (restriction.Ignored.IsNullOrWhiteSpace() && restriction.Required.IsNullOrWhiteSpace() && restriction.Preferred.Empty()) + if (restriction.Ignored.Empty() && restriction.Required.Empty() && restriction.Preferred.Empty()) { context.AddFailure("'Must contain', 'Must not contain' or 'Preferred' is required"); } diff --git a/src/Sonarr.Api.V3/Profiles/Release/ReleaseProfileResource.cs b/src/Sonarr.Api.V3/Profiles/Release/ReleaseProfileResource.cs index d9c814a3a..9a5f5187c 100644 --- a/src/Sonarr.Api.V3/Profiles/Release/ReleaseProfileResource.cs +++ b/src/Sonarr.Api.V3/Profiles/Release/ReleaseProfileResource.cs @@ -9,8 +9,8 @@ namespace Sonarr.Api.V3.Profiles.Release { public string Name { get; set; } public bool Enabled { get; set; } - public string Required { get; set; } - public string Ignored { get; set; } + public List Required { get; set; } + public List Ignored { get; set; } public List> Preferred { get; set; } public bool IncludePreferredWhenRenaming { get; set; } public int IndexerId { get; set; }