From 3bf547692253fd0e317ff92ef1ae0d3aba3d629d Mon Sep 17 00:00:00 2001 From: Qstick Date: Fri, 12 Jul 2019 23:56:58 -0400 Subject: [PATCH] Fixed: Correctly handle Repacks, restrict to same group --- .../RepackSpecificationFixture.cs | 174 ++++++++++++++++++ .../NzbDrone.Core.Test.csproj | 1 + .../ParserTests/QualityParserFixture.cs | 11 ++ .../Specifications/RepackSpecification.cs | 65 +++++++ .../Specifications/UpgradableSpecification.cs | 2 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + src/NzbDrone.Core/Parser/QualityParser.cs | 25 +-- src/NzbDrone.Core/Qualities/Revision.cs | 4 +- 8 files changed, 266 insertions(+), 17 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..444314057 --- /dev/null +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/RepackSpecificationFixture.cs @@ -0,0 +1,174 @@ +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.Movies; + +namespace NzbDrone.Core.Test.DecisionEngineTests +{ + [TestFixture] + public class RepackSpecificationFixture : CoreTest + { + private ParsedMovieInfo _parsedMovieInfo; + private Movie _movie; + + [SetUp] + public void Setup() + { + Mocker.Resolve(); + + _parsedMovieInfo = Builder.CreateNew() + .With(p => p.Quality = new QualityModel(Quality.SDTV, + new Revision(2, 0, false))) + .With(p => p.ReleaseGroup = "Radarr") + .Build(); + + _movie = Builder.CreateNew() + .With(e => e.MovieFileId = 0) + .Build(); + } + + [Test] + public void should_return_true_if_it_is_not_a_repack() + { + var remoteMovie = Builder.CreateNew() + .With(e => e.ParsedMovieInfo = _parsedMovieInfo) + .With(e => e.Movie = _movie) + .Build(); + + Subject.IsSatisfiedBy(remoteMovie, null) + .Accepted + .Should() + .BeTrue(); + } + + [Test] + public void should_return_true_if_there_are_is_no_movie_file() + { + _parsedMovieInfo.Quality.Revision.IsRepack = true; + + var remoteMovie = Builder.CreateNew() + .With(e => e.ParsedMovieInfo = _parsedMovieInfo) + .With(e => e.Movie = _movie) + .Build(); + + Subject.IsSatisfiedBy(remoteMovie, null) + .Accepted + .Should() + .BeTrue(); + } + + [Test] + public void should_return_true_if_is_a_repack_for_a_different_quality() + { + _parsedMovieInfo.Quality.Revision.IsRepack = true; + _movie.MovieFileId = 1; + _movie.MovieFile = Builder.CreateNew() + .With(e => e.Quality = new QualityModel(Quality.DVD)) + .With(e => e.ReleaseGroup = "Radarr") + .Build(); + + var remoteMovie = Builder.CreateNew() + .With(e => e.ParsedMovieInfo = _parsedMovieInfo) + .With(e => e.Movie = _movie) + .Build(); + + Subject.IsSatisfiedBy(remoteMovie, null) + .Accepted + .Should() + .BeTrue(); + } + + [Test] + public void should_return_true_if_is_a_repack_for_existing_file() + { + _parsedMovieInfo.Quality.Revision.IsRepack = true; + _movie.MovieFileId = 1; + _movie.MovieFile = Builder.CreateNew() + .With(e => e.Quality = new QualityModel(Quality.SDTV)) + .With(e => e.ReleaseGroup = "Radarr") + .Build(); + + var remoteMovie = Builder.CreateNew() + .With(e => e.ParsedMovieInfo = _parsedMovieInfo) + .With(e => e.Movie = _movie) + .Build(); + + Subject.IsSatisfiedBy(remoteMovie, null) + .Accepted + .Should() + .BeTrue(); + } + + [Test] + public void should_return_false_if_is_a_repack_for_a_different_file() + { + _parsedMovieInfo.Quality.Revision.IsRepack = true; + _movie.MovieFileId = 1; + _movie.MovieFile = Builder.CreateNew() + .With(e => e.Quality = new QualityModel(Quality.SDTV)) + .With(e => e.ReleaseGroup = "NotRadarr") + .Build(); + + var remoteMovie = Builder.CreateNew() + .With(e => e.ParsedMovieInfo = _parsedMovieInfo) + .With(e => e.Movie = _movie) + .Build(); + + Subject.IsSatisfiedBy(remoteMovie, null) + .Accepted + .Should() + .BeFalse(); + } + + [Test] + public void should_return_false_if_release_group_for_existing_file_is_unknown() + { + _parsedMovieInfo.Quality.Revision.IsRepack = true; + _movie.MovieFileId = 1; + _movie.MovieFile = Builder.CreateNew() + .With(e => e.Quality = new QualityModel(Quality.SDTV)) + .With(e => e.ReleaseGroup = "") + .Build(); + + var remoteMovie = Builder.CreateNew() + .With(e => e.ParsedMovieInfo = _parsedMovieInfo) + .With(e => e.Movie = _movie) + .Build(); + + Subject.IsSatisfiedBy(remoteMovie, null) + .Accepted + .Should() + .BeFalse(); + } + + [Test] + public void should_return_false_if_release_group_for_release_is_unknown() + { + _parsedMovieInfo.Quality.Revision.IsRepack = true; + _parsedMovieInfo.ReleaseGroup = null; + + _movie.MovieFileId = 1; + _movie.MovieFile = Builder.CreateNew() + .With(e => e.Quality = new QualityModel(Quality.SDTV)) + .With(e => e.ReleaseGroup = "Radarr") + .Build(); + + var remoteMovie = Builder.CreateNew() + .With(e => e.ParsedMovieInfo = _parsedMovieInfo) + .With(e => e.Movie = _movie) + .Build(); + + Subject.IsSatisfiedBy(remoteMovie, null) + .Accepted + .Should() + .BeFalse(); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index a202f6dd4..8afa60970 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -168,6 +168,7 @@ + diff --git a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs index 2ecd373b3..d07e382f9 100644 --- a/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/QualityParserFixture.cs @@ -349,6 +349,17 @@ namespace NzbDrone.Core.Test.ParserTests QualityParser.ParseQuality(postTitle).HardcodedSubs.Should().Be(sub); } + [TestCase("Movie Title 2018 REPACK 720p x264 aAF", true)] + [TestCase("Movie.Title.2018.REPACK.720p.x264-aAF", true)] + [TestCase("Movie.Title.2018.PROPER.720p.x264-aAF", false)] + [TestCase("Movie.Title.2018.RERIP.720p.BluRay.x264-DEMAND", true)] + 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, Source source, bool proper, Resolution resolution, Modifier modifier = Modifier.NONE) { 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..153198440 --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/RepackSpecification.cs @@ -0,0 +1,65 @@ +using System; +using System.Linq; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.DecisionEngine.Specifications +{ + public class RepackSpecification : IDecisionEngineSpecification + { + private readonly UpgradableSpecification _upgradableSpecification; + private readonly Logger _logger; + + public RepackSpecification(UpgradableSpecification upgradableSpecification, Logger logger) + { + _upgradableSpecification = upgradableSpecification; + _logger = logger; + } + + public SpecificationPriority Priority => SpecificationPriority.Database; + public RejectionType Type => RejectionType.Permanent; + + public Decision IsSatisfiedBy(RemoteMovie subject, SearchCriteriaBase searchCriteria) + { + if (!subject.ParsedMovieInfo.Quality.Revision.IsRepack) + { + return Decision.Accept(); + } + + if (subject.Movie.MovieFileId != 0) + { + var file = subject.Movie.MovieFile; + + if (_upgradableSpecification.IsRevisionUpgrade(file.Quality, subject.ParsedMovieInfo.Quality)) + { + var releaseGroup = subject.ParsedMovieInfo.ReleaseGroup; + var fileReleaseGroup = file.ReleaseGroup; + + if (fileReleaseGroup.IsNullOrWhiteSpace()) + { + return Decision.Reject("Unable to determine release group for the existing file"); + } + + if (releaseGroup.IsNullOrWhiteSpace()) + { + return Decision.Reject("Unable to determine release group for this release"); + } + + 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(); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs index 037c7e6ac..c444860ff 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/UpgradableSpecification.cs @@ -1,4 +1,3 @@ -using System.Linq; using NLog; using NzbDrone.Core.Configuration; using NzbDrone.Core.Profiles; @@ -74,6 +73,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications if (currentQuality.Quality == newQuality.Quality && compare > 0) { + _logger.Debug("New quality is a better revision for existing quality"); return true; } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 4dee0e068..f5fa02689 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -164,6 +164,7 @@ + diff --git a/src/NzbDrone.Core/Parser/QualityParser.cs b/src/NzbDrone.Core/Parser/QualityParser.cs index 0724982f9..964b7a2d0 100644 --- a/src/NzbDrone.Core/Parser/QualityParser.cs +++ b/src/NzbDrone.Core/Parser/QualityParser.cs @@ -15,20 +15,6 @@ namespace NzbDrone.Core.Parser { private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(QualityParser)); - //private static readonly Regex SourceRegex = new Regex(@"\b(?: - // (?BluRay|Blu-Ray|HDDVD|BD)| - // (?WEB[-_. ]DL|WEBDL|WebRip|iTunesHD|WebHD|[. ]WEB[. ](?:[xh]26[45]|DD5[. ]1)|\d+0p[. ]WEB[. ])| - // (?HDTV)| - // (?BDRip)| - // (?BRRip)| - // (?DVD|DVDRip|NTSC|PAL|xvidvd)| - // (?WS[-_. ]DSR|DSR)| - // (?PDTV)| - // (?SDTV)| - // (?TVRip) - // )\b", - // RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); - private static readonly Regex SourceRegex = new Regex(@"\b(?: (?M?BluRay|Blu-Ray|HDDVD|BD|BDISO|BD25|BD50|BR.?DISK)| (?WEB[-_. ]DL|HDRIP|WEBDL|WebRip|Web-Rip|iTunesHD|WebHD|[. ]WEB[. ](?:[xh]26[45]|DDP?5[. ]1)|\d+0p[-. ]WEB[-. ]|WEB-DLMux|\b\s\/\sWEB\s\/\s\b)| @@ -57,7 +43,10 @@ namespace NzbDrone.Core.Parser private static readonly Regex BRDISKRegex = new Regex(@"\b(COMPLETE|ISO|BDISO|BD25|BD50|BR.?DISK)\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)\b", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex RepackRegex = new Regex(@"\b(?repack|rerip)\b", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex VersionRegex = new Regex(@"\dv(?\d)\b|\[v(?\d)\]", @@ -406,6 +395,12 @@ namespace NzbDrone.Core.Parser result.Revision.Version = 2; } + if (RepackRegex.IsMatch(normalizedName)) + { + result.Revision.Version = 2; + result.Revision.IsRepack = true; + } + var versionRegexResult = VersionRegex.Match(normalizedName); if (versionRegexResult.Success) 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 @@ namespace NzbDrone.Core.Qualities { } - 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) {