mirror of
https://github.com/Sonarr/Sonarr.git
synced 2024-12-14 11:23:42 +02:00
New: Required/Ignored restrictions now support /pattern/ regular expressions.
This commit is contained in:
parent
ff885ab3bd
commit
940f59468a
@ -29,6 +29,8 @@ public void Setup()
|
|||||||
Title = "Dexter.S08E01.EDITED.WEBRip.x264-KYR"
|
Title = "Dexter.S08E01.EDITED.WEBRip.x264-KYR"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Mocker.SetConstant<ITermMatcher>(Mocker.Resolve<TermMatcher>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GivenRestictions(string required, string ignored)
|
private void GivenRestictions(string required, string ignored)
|
||||||
@ -123,5 +125,16 @@ public void should_be_false_when_release_contains_one_restricted_word_and_one_re
|
|||||||
|
|
||||||
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();
|
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase("/WEB/", true)]
|
||||||
|
[TestCase("/WEB\b/", false)]
|
||||||
|
[TestCase("/WEb/", false)]
|
||||||
|
[TestCase(@"/\.WEB/", true)]
|
||||||
|
public void should_match_perl_regex(string pattern, bool expected)
|
||||||
|
{
|
||||||
|
GivenRestictions(pattern, null);
|
||||||
|
|
||||||
|
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().Be(expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,15 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
|
|||||||
{
|
{
|
||||||
public class ReleaseRestrictionsSpecification : IDecisionEngineSpecification
|
public class ReleaseRestrictionsSpecification : IDecisionEngineSpecification
|
||||||
{
|
{
|
||||||
private readonly IRestrictionService _restrictionService;
|
|
||||||
private readonly Logger _logger;
|
private readonly Logger _logger;
|
||||||
|
private readonly IRestrictionService _restrictionService;
|
||||||
|
private readonly ITermMatcher _termMatcher;
|
||||||
|
|
||||||
public ReleaseRestrictionsSpecification(IRestrictionService restrictionService, Logger logger)
|
public ReleaseRestrictionsSpecification(ITermMatcher termMatcher, IRestrictionService restrictionService, Logger logger)
|
||||||
{
|
{
|
||||||
_restrictionService = restrictionService;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_restrictionService = restrictionService;
|
||||||
|
_termMatcher = termMatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpecificationPriority Priority => SpecificationPriority.Default;
|
public SpecificationPriority Priority => SpecificationPriority.Default;
|
||||||
@ -63,9 +65,9 @@ public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase
|
|||||||
return Decision.Accept();
|
return Decision.Accept();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<string> ContainsAny(List<string> terms, string title)
|
private List<string> ContainsAny(List<string> terms, string title)
|
||||||
{
|
{
|
||||||
return terms.Where(t => title.ToLowerInvariant().Contains(t.ToLowerInvariant())).ToList();
|
return terms.Where(t => _termMatcher.IsMatch(t, title)).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1087,9 +1087,11 @@
|
|||||||
<Compile Include="Queue\Queue.cs" />
|
<Compile Include="Queue\Queue.cs" />
|
||||||
<Compile Include="Queue\QueueService.cs" />
|
<Compile Include="Queue\QueueService.cs" />
|
||||||
<Compile Include="Queue\QueueUpdatedEvent.cs" />
|
<Compile Include="Queue\QueueUpdatedEvent.cs" />
|
||||||
|
<Compile Include="Restrictions\PerlRegexFactory.cs" />
|
||||||
<Compile Include="Restrictions\Restriction.cs" />
|
<Compile Include="Restrictions\Restriction.cs" />
|
||||||
<Compile Include="Restrictions\RestrictionRepository.cs" />
|
<Compile Include="Restrictions\RestrictionRepository.cs" />
|
||||||
<Compile Include="Restrictions\RestrictionService.cs" />
|
<Compile Include="Restrictions\RestrictionService.cs" />
|
||||||
|
<Compile Include="Restrictions\TermMatcher.cs" />
|
||||||
<Compile Include="Rest\JsonNetSerializer.cs" />
|
<Compile Include="Rest\JsonNetSerializer.cs" />
|
||||||
<Compile Include="Rest\RestClientFactory.cs" />
|
<Compile Include="Rest\RestClientFactory.cs" />
|
||||||
<Compile Include="Rest\RestException.cs" />
|
<Compile Include="Rest\RestException.cs" />
|
||||||
|
72
src/NzbDrone.Core/Restrictions/PerlRegexFactory.cs
Normal file
72
src/NzbDrone.Core/Restrictions/PerlRegexFactory.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using NzbDrone.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Restrictions
|
||||||
|
{
|
||||||
|
public static class PerlRegexFactory
|
||||||
|
{
|
||||||
|
private static Regex _perlRegexFormat = new Regex(@"/(?<pattern>.*)/(?<modifiers>[a-z]*)", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
public static bool TryCreateRegex(string pattern, out Regex regex)
|
||||||
|
{
|
||||||
|
var match = _perlRegexFormat.Match(pattern);
|
||||||
|
|
||||||
|
if (!match.Success)
|
||||||
|
{
|
||||||
|
regex = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
regex = CreateRegex(match.Groups["pattern"].Value, match.Groups["modifiers"].Value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Regex CreateRegex(string pattern, string modifiers)
|
||||||
|
{
|
||||||
|
var options = GetOptions(modifiers);
|
||||||
|
|
||||||
|
// For now we simply expect the pattern to be .net compliant. We should probably check and reject perl-specific constructs.
|
||||||
|
return new Regex(pattern, options | RegexOptions.Compiled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RegexOptions GetOptions(string modifiers)
|
||||||
|
{
|
||||||
|
var options = RegexOptions.None;
|
||||||
|
|
||||||
|
foreach (var modifier in modifiers)
|
||||||
|
{
|
||||||
|
switch (modifier)
|
||||||
|
{
|
||||||
|
case 'm':
|
||||||
|
options |= RegexOptions.Multiline;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 's':
|
||||||
|
options |= RegexOptions.Singleline;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'i':
|
||||||
|
options |= RegexOptions.IgnoreCase;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'x':
|
||||||
|
options |= RegexOptions.IgnorePatternWhitespace;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
options |= RegexOptions.ExplicitCapture;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new ArgumentException("Unknown or unsupported perl regex modifier: " + modifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
63
src/NzbDrone.Core/Restrictions/TermMatcher.cs
Normal file
63
src/NzbDrone.Core/Restrictions/TermMatcher.cs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using NzbDrone.Common.Cache;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Restrictions
|
||||||
|
{
|
||||||
|
public interface ITermMatcher
|
||||||
|
{
|
||||||
|
bool IsMatch(string term, string value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TermMatcher : ITermMatcher
|
||||||
|
{
|
||||||
|
private ICached<Predicate<string>> _matcherCache;
|
||||||
|
|
||||||
|
public TermMatcher(ICacheManager cacheManager)
|
||||||
|
{
|
||||||
|
_matcherCache = cacheManager.GetCache<Predicate<string>>(GetType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsMatch(string term, string value)
|
||||||
|
{
|
||||||
|
return GetMatcher(term)(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Predicate<string> GetMatcher(string term)
|
||||||
|
{
|
||||||
|
return _matcherCache.Get(term, () => CreateMatcherInternal(term), TimeSpan.FromHours(24));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Predicate<string> CreateMatcherInternal(string term)
|
||||||
|
{
|
||||||
|
Regex regex;
|
||||||
|
if (PerlRegexFactory.TryCreateRegex(term, out regex))
|
||||||
|
{
|
||||||
|
return regex.IsMatch;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new CaseInsensitiveTermMatcher(term).IsMatch;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class CaseInsensitiveTermMatcher
|
||||||
|
{
|
||||||
|
private readonly string _term;
|
||||||
|
|
||||||
|
public CaseInsensitiveTermMatcher(string term)
|
||||||
|
{
|
||||||
|
_term = term.ToLowerInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsMatch(string value)
|
||||||
|
{
|
||||||
|
return value.ToLowerInvariant().Contains(_term);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user