From e70d92f6702a3fe898f8fd99c84a34267f4639e9 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 29 Apr 2019 23:38:18 -0700 Subject: [PATCH] New: Restrict repack upgrades to the same release group Closes #946 --- .../RepackSpecificationFixture.cs | 106 ++++++++++++++++++ .../NzbDrone.Core.Test.csproj | 1 + .../ParserTests/QualityParserFixture.cs | 10 ++ .../Specifications/RepackSpecification.cs | 43 +++++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + src/NzbDrone.Core/Parser/QualityParser.cs | 13 ++- src/NzbDrone.Core/Qualities/Revision.cs | 4 +- 7 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 src/NzbDrone.Core.Test/DecisionEngineTests/RepackSpecificationFixture.cs create mode 100644 src/NzbDrone.Core/DecisionEngine/Specifications/RepackSpecification.cs diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/RepackSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/RepackSpecificationFixture.cs new file mode 100644 index 000000000..181095020 --- /dev/null +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RepackSpecificationFixture.cs @@ -0,0 +1,106 @@ +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.DecisionEngine.Specifications; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Test.DecisionEngineTests +{ + [TestFixture] + public class RepackSpecificationFixture : CoreTest + { + private ParsedEpisodeInfo _parsedEpisodeInfo; + private List _episodes; + + [SetUp] + public void Setup() + { + _parsedEpisodeInfo = Builder.CreateNew() + .With(p => p.Quality = new QualityModel(Quality.SDTV, + new Revision(2, 0, false))) + .With(p => p.ReleaseGroup = "Sonarr") + .Build(); + + _episodes = Builder.CreateListOfSize(1) + .All() + .With(e => e.EpisodeFileId = 0) + .BuildList(); + } + + [Test] + public void should_return_true_if_it_is_not_a_repack() + { + var remoteEpisode = Builder.CreateNew() + .With(e => e.ParsedEpisodeInfo = _parsedEpisodeInfo) + .With(e => e.Episodes = _episodes) + .Build(); + + Subject.IsSatisfiedBy(remoteEpisode, null) + .Accepted + .Should() + .BeTrue(); + } + + [Test] + public void should_return_true_if_there_are_is_no_episode_file() + { + _parsedEpisodeInfo.Quality.Revision.IsRepack = true; + + var remoteEpisode = Builder.CreateNew() + .With(e => e.ParsedEpisodeInfo = _parsedEpisodeInfo) + .With(e => e.Episodes = _episodes) + .Build(); + + Subject.IsSatisfiedBy(remoteEpisode, null) + .Accepted + .Should() + .BeTrue(); + } + + [Test] + public void should_return_true_if_is_a_repack_for_existing_file() + { + _parsedEpisodeInfo.Quality.Revision.IsRepack = true; + _episodes.First().EpisodeFileId = 1; + _episodes.First().EpisodeFile = Builder.CreateNew() + .With(e => e.ReleaseGroup = "Sonarr") + .Build(); + + var remoteEpisode = Builder.CreateNew() + .With(e => e.ParsedEpisodeInfo = _parsedEpisodeInfo) + .With(e => e.Episodes = _episodes) + .Build(); + + Subject.IsSatisfiedBy(remoteEpisode, null) + .Accepted + .Should() + .BeTrue(); + } + + [Test] + public void should_return_false_if_is_a_repack_for_existing_file() + { + _parsedEpisodeInfo.Quality.Revision.IsRepack = true; + _episodes.First().EpisodeFileId = 1; + _episodes.First().EpisodeFile = Builder.CreateNew() + .With(e => e.ReleaseGroup = "NotSonarr") + .Build(); + + var remoteEpisode = Builder.CreateNew() + .With(e => e.ParsedEpisodeInfo = _parsedEpisodeInfo) + .With(e => e.Episodes = _episodes) + .Build(); + + Subject.IsSatisfiedBy(remoteEpisode, 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 dee8dd2b9..46893e154 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -159,6 +159,7 @@ + diff --git a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs index b7f36b569..873294657 100644 --- a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs @@ -374,6 +374,16 @@ public void should_parse_quality_from_extension(string title) QualityParser.ParseQuality(title).QualityDetectionSource.Should().Be(QualityDetectionSource.Extension); } + [TestCase("Series Title S04E87 REPACK 720p HDTV x264 aAF", true)] + [TestCase("Series.Title.S04E87.REPACK.720p.HDTV.x264-aAF", true)] + [TestCase("Series.Title.S04E87.PROPER.720p.HDTV.x264-aAF", false)] + public void should_be_able_to_parse_repack(string title, bool isRepack) + { + var result = QualityParser.ParseQuality(title); + result.Revision.Version.Should().Be(2); + result.Revision.IsRepack.Should().Be(isRepack); + } + private void ParseAndVerifyQuality(string title, Quality quality, bool proper) { var result = QualityParser.ParseQuality(title); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/RepackSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/RepackSpecification.cs new file mode 100644 index 000000000..53daa70f6 --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RepackSpecification.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; +using NLog; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.DecisionEngine.Specifications +{ + public class RepackSpecification : IDecisionEngineSpecification + { + private readonly Logger _logger; + + public RepackSpecification(Logger logger) + { + _logger = logger; + } + + public SpecificationPriority Priority => SpecificationPriority.Database; + public RejectionType Type => RejectionType.Permanent; + + public Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) + { + if (!subject.ParsedEpisodeInfo.Quality.Revision.IsRepack) + { + return Decision.Accept(); + } + + foreach (var file in subject.Episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFile.Value)) + { + var releaseGroup = subject.ParsedEpisodeInfo.ReleaseGroup; + var fileReleaseGroup = file.ReleaseGroup; + + if (!fileReleaseGroup.Equals(releaseGroup, StringComparison.InvariantCultureIgnoreCase)) + { + _logger.Debug("Release is a repack for a different release group. Release Group: {0}. File release group: {0}", releaseGroup, fileReleaseGroup); + return Decision.Reject("Release is a repack for a different release group. Release Group: {0}. File release group: {0}", releaseGroup, fileReleaseGroup); + } + } + + return Decision.Accept(); + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 6a03f4386..d1cc5e1a2 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -145,6 +145,7 @@ + diff --git a/src/NzbDrone.Core/Parser/QualityParser.cs b/src/NzbDrone.Core/Parser/QualityParser.cs index e3404c8ba..261d5964e 100644 --- a/src/NzbDrone.Core/Parser/QualityParser.cs +++ b/src/NzbDrone.Core/Parser/QualityParser.cs @@ -32,9 +32,12 @@ public class QualityParser private static readonly Regex RawHDRegex = new Regex(@"\b(?RawHD|1080i[-_. ]HDTV|Raw[-_. ]HD|MPEG[-_. ]?2)\b", RegexOptions.Compiled | RegexOptions.IgnoreCase); - private static readonly Regex ProperRegex = new Regex(@"\b(?proper|repack|rerip)\b", + private static readonly Regex ProperRegex = new Regex(@"\b(?proper|rerip)\b", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex RepackRegex = new Regex(@"\b(?repack)\b", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex VersionRegex = new Regex(@"\dv(?\d)\b|\[v(?\d)\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); @@ -425,6 +428,12 @@ private static QualityModel ParseQualityModifiers(string name, string normalized result.Revision.Version = 2; } + if (RepackRegex.IsMatch(normalizedName)) + { + result.Revision.Version = 2; + result.Revision.IsRepack = true; + } + var versionRegexResult = VersionRegex.Match(normalizedName); if (versionRegexResult.Success) @@ -433,7 +442,7 @@ private static QualityModel ParseQualityModifiers(string name, string normalized } // TODO: re-enable this when we have a reliable way to determine real - // TODO: Only treat it as a real if it comes AFTER the season/epsiode number + // TODO: Only treat it as a real if it comes AFTER the season/episode number var realRegexResult = RealRegex.Matches(name); if (realRegexResult.Count > 0) diff --git a/src/NzbDrone.Core/Qualities/Revision.cs b/src/NzbDrone.Core/Qualities/Revision.cs index 7ec095cda..e8e7108e5 100644 --- a/src/NzbDrone.Core/Qualities/Revision.cs +++ b/src/NzbDrone.Core/Qualities/Revision.cs @@ -9,14 +9,16 @@ private Revision() { } - public Revision(int version = 1, int real = 0) + public Revision(int version = 1, int real = 0, bool isRepack = false) { Version = version; Real = real; + IsRepack = isRepack; } public int Version { get; set; } public int Real { get; set; } + public bool IsRepack { get; set; } public bool Equals(Revision other) {