Fixed: Commas in Must (Not) Contain regex

Closes #4672
This commit is contained in:
Mark McDowall 2021-10-02 17:54:52 -07:00
parent f6fbd3cfee
commit ada01a1116
10 changed files with 85 additions and 30 deletions

View File

@ -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 }) => {

View File

@ -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 {

View File

@ -90,7 +90,7 @@ class ReleaseProfile extends Component {
<div>
{
split(required).map((item) => {
required.map((item) => {
if (!item) {
return null;
}
@ -126,7 +126,7 @@ class ReleaseProfile extends Component {
<div>
{
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,

View File

@ -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<int>(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<int>(resource.Tags)
};
}

View File

@ -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<ITermMatcherService>(Mocker.Resolve<TermMatcherService>());
}
private void GivenRestictions(string required, string ignored)
private void GivenRestictions(List<string> required, List<string> ignored)
{
Mocker.GetMock<IReleaseProfileService>()
.Setup(s => s.EnabledForTags(It.IsAny<HashSet<int>>(), It.IsAny<int>()))
@ -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<string> { "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<string> { "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<string> { "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<string> { "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<HashSet<int>>(), It.IsAny<int>()))
.Returns(new List<ReleaseProfile>
{
new ReleaseProfile { Required = "x264", Ignored = "www.Speed.cd" }
new ReleaseProfile
{
Required = new List<string> { "x264" },
Ignored = new List<string> { "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);
}

View File

@ -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();
}
}
}
}
}
}
}

View File

@ -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())

View File

@ -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<string> Required { get; set; }
public List<string> Ignored { get; set; }
public List<KeyValuePair<string, int>> 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<string>();
Ignored = new List<string>();
Preferred = new List<KeyValuePair<string, int>>();
IncludePreferredWhenRenaming = true;
Tags = new HashSet<int>();

View File

@ -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");
}

View File

@ -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<string> Required { get; set; }
public List<string> Ignored { get; set; }
public List<KeyValuePair<string, int>> Preferred { get; set; }
public bool IncludePreferredWhenRenaming { get; set; }
public int IndexerId { get; set; }