From e10cff541488c0a2b8ab3a4fa84db16d653d88d3 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Sat, 16 Jan 2021 21:13:32 +0100 Subject: [PATCH] Fixed parsing (duplicate) releases for series with multiple season number mappings --- .../Scene/SceneMappingServiceFixture.cs | 30 +++++++++---- .../DownloadDecisionMakerFixture.cs | 2 +- .../ParsingServiceTests/GetEpisodesFixture.cs | 10 ++--- .../ParsingServiceTests/GetSeriesFixture.cs | 2 +- .../ParsingServiceTests/MapFixture.cs | 4 +- .../Scene/SceneMappingService.cs | 44 ++++++++++++++----- .../DecisionEngine/DownloadDecisionMaker.cs | 2 +- .../IndexerSearch/NzbSearchService.cs | 4 +- src/NzbDrone.Core/Parser/ParsingService.cs | 6 +-- 9 files changed, 70 insertions(+), 34 deletions(-) diff --git a/src/NzbDrone.Core.Test/DataAugmentation/Scene/SceneMappingServiceFixture.cs b/src/NzbDrone.Core.Test/DataAugmentation/Scene/SceneMappingServiceFixture.cs index 1f1a6ff8e..754e43068 100644 --- a/src/NzbDrone.Core.Test/DataAugmentation/Scene/SceneMappingServiceFixture.cs +++ b/src/NzbDrone.Core.Test/DataAugmentation/Scene/SceneMappingServiceFixture.cs @@ -109,7 +109,7 @@ namespace NzbDrone.Core.Test.DataAugmentation.Scene [Test] public void should_refresh_cache_if_cache_is_empty_when_looking_for_tvdb_id() { - Subject.FindTvdbId("title"); + Subject.FindTvdbId("title", null, -1); Mocker.GetMock() .Verify(v => v.All(), Times.Once()); @@ -130,7 +130,7 @@ namespace NzbDrone.Core.Test.DataAugmentation.Scene Mocker.GetMock() .Verify(v => v.All(), Times.Once()); - Subject.FindTvdbId("title"); + Subject.FindTvdbId("title", null, -1); Mocker.GetMock() .Verify(v => v.All(), Times.Once()); @@ -195,7 +195,7 @@ namespace NzbDrone.Core.Test.DataAugmentation.Scene Mocker.GetMock().Setup(c => c.All()).Returns(mappings); - var tvdbId = Subject.FindTvdbId(parseTitle); + var tvdbId = Subject.FindTvdbId(parseTitle, null, -1); var seasonNumber = Subject.GetSceneSeasonNumber(parseTitle, null); tvdbId.Should().Be(100); @@ -328,8 +328,8 @@ namespace NzbDrone.Core.Test.DataAugmentation.Scene Mocker.GetMock().Setup(c => c.All()).Returns(mappings); - Subject.FindTvdbId("Amareto", "Amareto.S01E01.720p.WEB-DL-Viva").Should().Be(101); - Subject.FindTvdbId("Amareto", "Amareto.S01E01.720p.WEB-DL-DMO").Should().Be(100); + Subject.FindTvdbId("Amareto", "Amareto.S01E01.720p.WEB-DL-Viva", -1).Should().Be(101); + Subject.FindTvdbId("Amareto", "Amareto.S01E01.720p.WEB-DL-DMO", -1).Should().Be(100); } [Test] @@ -343,7 +343,7 @@ namespace NzbDrone.Core.Test.DataAugmentation.Scene Mocker.GetMock().Setup(c => c.All()).Returns(mappings); - Assert.Throws(() => Subject.FindTvdbId("Amareto", "Amareto.S01E01.720p.WEB-DL-Viva")); + Assert.Throws(() => Subject.FindTvdbId("Amareto", "Amareto.S01E01.720p.WEB-DL-Viva", -1)); } [Test] @@ -357,7 +357,21 @@ namespace NzbDrone.Core.Test.DataAugmentation.Scene Mocker.GetMock().Setup(c => c.All()).Returns(mappings); - Subject.FindTvdbId("Amareto", "Amareto.S01E01.720p.WEB-DL-Viva").Should().Be(100); + Subject.FindTvdbId("Amareto", "Amareto.S01E01.720p.WEB-DL-Viva", -1).Should().Be(100); + } + + [Test] + public void should_pick_best_season() + { + var mappings = new List + { + new SceneMapping { Title = "Amareto", ParseTerm = "amareto", SearchTerm = "Amareto", SceneSeasonNumber = 2, SeasonNumber = 3, TvdbId = 100 }, + new SceneMapping { Title = "Amareto", ParseTerm = "amareto", SearchTerm = "Amareto", SceneSeasonNumber = 3, SeasonNumber = 3, TvdbId = 101 } + }; + + Mocker.GetMock().Setup(c => c.All()).Returns(mappings); + + Subject.FindTvdbId("Amareto", "Amareto.S01E01.720p.WEB-DL-Viva", 4).Should().Be(101); } private void AssertNoUpdate() @@ -376,7 +390,7 @@ namespace NzbDrone.Core.Test.DataAugmentation.Scene foreach (var sceneMapping in _fakeMappings) { Subject.GetSceneNames(sceneMapping.TvdbId, _fakeMappings.Select(m => m.SeasonNumber.Value).Distinct().ToList(), new List()).Should().Contain(sceneMapping.SearchTerm); - Subject.FindTvdbId(sceneMapping.ParseTerm).Should().Be(sceneMapping.TvdbId); + Subject.FindTvdbId(sceneMapping.ParseTerm, null, sceneMapping.SceneSeasonNumber ?? -1).Should().Be(sceneMapping.TvdbId); } } } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs index 2ac449909..cab95e3a0 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/DownloadDecisionMakerFixture.cs @@ -336,7 +336,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests GivenSpecifications(_pass1, _pass2, _pass3); Mocker.GetMock() - .Setup(s => s.FindTvdbId(It.IsAny(), It.IsAny())) + .Setup(s => s.FindTvdbId(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(12345); _remoteEpisode.Series = null; diff --git a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs index 3f890eb75..0274ebabd 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs @@ -324,8 +324,8 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests const int tvdbSeasonNumber = 5; Mocker.GetMock() - .Setup(v => v.FindSceneMapping(It.IsAny(), It.IsAny())) - .Returns((s, r) => new SceneMapping { SceneSeasonNumber = 1, SeasonNumber = tvdbSeasonNumber }); + .Setup(v => v.FindSceneMapping(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((s, r, sn) => new SceneMapping { SceneSeasonNumber = 1, SeasonNumber = tvdbSeasonNumber }); Subject.GetEpisodes(_parsedEpisodeInfo, _series, true, null); @@ -342,8 +342,8 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests const int tvdbSeasonNumber = 5; Mocker.GetMock() - .Setup(v => v.FindSceneMapping(It.IsAny(), It.IsAny())) - .Returns((s, r) => new SceneMapping { SceneSeasonNumber = 101, SeasonNumber = tvdbSeasonNumber }); + .Setup(v => v.FindSceneMapping(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((s, r, sn) => new SceneMapping { SceneSeasonNumber = 101, SeasonNumber = tvdbSeasonNumber }); Subject.GetEpisodes(_parsedEpisodeInfo, _series, true, null); @@ -374,7 +374,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests const int tvdbSeasonNumber = -1; Mocker.GetMock() - .Setup(s => s.FindSceneMapping(_parsedEpisodeInfo.SeriesTitle, It.IsAny())) + .Setup(s => s.FindSceneMapping(_parsedEpisodeInfo.SeriesTitle, It.IsAny(), It.IsAny())) .Returns(new SceneMapping { SeasonNumber = tvdbSeasonNumber, SceneSeasonNumber = _parsedEpisodeInfo.SeasonNumber }); Subject.GetEpisodes(_parsedEpisodeInfo, _series, true, null); diff --git a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetSeriesFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetSeriesFixture.cs index b9ddd0e4f..6cf218e93 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetSeriesFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetSeriesFixture.cs @@ -51,7 +51,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests { var series = new Series { TvdbId = 100 }; Mocker.GetMock().Setup(v => v.FindByTitle("Welcome")).Returns(series); - Mocker.GetMock().Setup(v => v.FindTvdbId("Mairimashita", It.IsAny())).Returns(100); + Mocker.GetMock().Setup(v => v.FindTvdbId("Mairimashita", It.IsAny(), It.IsAny())).Returns(100); var result = Subject.GetSeries("Welcome (Mairimashita).S01E01.720p.WEB-DL-Viva"); diff --git a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs index d5a2202e3..1380327d3 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs @@ -118,7 +118,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests GivenMatchByTvRageId(); Mocker.GetMock() - .Setup(v => v.FindSceneMapping(It.IsAny(), It.IsAny())) + .Setup(v => v.FindSceneMapping(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new SceneMapping { TvdbId = 10 }); var result = Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId); @@ -199,7 +199,7 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests public void should_use_tvdbid_matching_when_alias_is_found() { Mocker.GetMock() - .Setup(s => s.FindTvdbId(It.IsAny(), It.IsAny())) + .Setup(s => s.FindTvdbId(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(_series.TvdbId); Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId, _singleEpisodeSearchCriteria); diff --git a/src/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs b/src/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs index 860f66920..4b8d29c6f 100644 --- a/src/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs +++ b/src/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs @@ -15,9 +15,9 @@ namespace NzbDrone.Core.DataAugmentation.Scene public interface ISceneMappingService { List GetSceneNames(int tvdbId, List seasonNumbers, List sceneSeasonNumbers); - int? FindTvdbId(string sceneTitle, string releaseTitle); + int? FindTvdbId(string sceneTitle, string releaseTitle, int sceneSeasonNumber); List FindByTvdbId(int tvdbId); - SceneMapping FindSceneMapping(string sceneTitle, string releaseTitle); + SceneMapping FindSceneMapping(string sceneTitle, string releaseTitle, int sceneSeasonNumber); int? GetSceneSeasonNumber(string seriesTitle, string releaseTitle); } @@ -65,14 +65,9 @@ namespace NzbDrone.Core.DataAugmentation.Scene return names; } - public int? FindTvdbId(string seriesTitle) + public int? FindTvdbId(string seriesTitle, string releaseTitle, int sceneSeasonNumber) { - return FindTvdbId(seriesTitle, null); - } - - public int? FindTvdbId(string seriesTitle, string releaseTitle) - { - return FindSceneMapping(seriesTitle, releaseTitle)?.TvdbId; + return FindSceneMapping(seriesTitle, releaseTitle, sceneSeasonNumber)?.TvdbId; } public List FindByTvdbId(int tvdbId) @@ -92,7 +87,7 @@ namespace NzbDrone.Core.DataAugmentation.Scene return mappings; } - public SceneMapping FindSceneMapping(string seriesTitle, string releaseTitle) + public SceneMapping FindSceneMapping(string seriesTitle, string releaseTitle, int sceneSeasonNumber) { var mappings = FindMappings(seriesTitle, releaseTitle); @@ -101,6 +96,8 @@ namespace NzbDrone.Core.DataAugmentation.Scene return null; } + mappings = FilterSceneMappings(mappings, sceneSeasonNumber); + var distinctMappings = mappings.DistinctBy(v => v.TvdbId).ToList(); if (distinctMappings.Count == 0) @@ -120,7 +117,7 @@ namespace NzbDrone.Core.DataAugmentation.Scene public int? GetSceneSeasonNumber(string seriesTitle, string releaseTitle) { - return FindSceneMapping(seriesTitle, releaseTitle)?.SceneSeasonNumber; + return FindSceneMapping(seriesTitle, releaseTitle, -1)?.SceneSeasonNumber; } private void UpdateMappings() @@ -239,6 +236,31 @@ namespace NzbDrone.Core.DataAugmentation.Scene return normalCandidates; } + private List FilterSceneMappings(List candidates, int sceneSeasonNumber) + { + var filteredCandidates = candidates.Where(v => (v.SceneSeasonNumber ?? -1) != -1 && (v.SeasonNumber ?? -1) != -1).ToList(); + var normalCandidates = candidates.Except(filteredCandidates).ToList(); + + if (sceneSeasonNumber == -1) + { + return normalCandidates; + } + + if (filteredCandidates.Any()) + { + filteredCandidates = filteredCandidates.Where(v => v.SceneSeasonNumber <= sceneSeasonNumber) + .GroupBy(v => v.Title) + .Select(d => d.OrderByDescending(v => v.SceneSeasonNumber) + .ThenByDescending(v => v.SeasonNumber) + .First()) + .ToList(); + + return filteredCandidates; + } + + return normalCandidates; + } + private bool IsEnglish(string title) { return title.All(c => c <= 255); diff --git a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs index 69a28e53f..d32ff0b5c 100644 --- a/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs +++ b/src/NzbDrone.Core/DecisionEngine/DownloadDecisionMaker.cs @@ -93,7 +93,7 @@ namespace NzbDrone.Core.DecisionEngine if (remoteEpisode.Series == null) { var reason = "Unknown Series"; - var matchingTvdbId = _sceneMappingService.FindTvdbId(parsedEpisodeInfo.SeriesTitle, parsedEpisodeInfo.ReleaseTitle); + var matchingTvdbId = _sceneMappingService.FindTvdbId(parsedEpisodeInfo.SeriesTitle, parsedEpisodeInfo.ReleaseTitle, parsedEpisodeInfo.SeasonNumber); if (matchingTvdbId.HasValue) { diff --git a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs index 3f3632ae1..b6ce7eff4 100644 --- a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs @@ -523,9 +523,9 @@ namespace NzbDrone.Core.IndexerSearch private List DeDupeDecisions(List decisions) { - // De-dupe reports by guid so duplicate results aren't returned. + // De-dupe reports by guid so duplicate results aren't returned. Pick the one with the least rejections. - return decisions.DistinctBy(d => d.RemoteEpisode.Release.Guid).ToList(); + return decisions.GroupBy(d => d.RemoteEpisode.Release.Guid).Select(d => d.OrderBy(v => v.Rejections.Count()).First()).ToList(); } } } diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index 8475b44c7..0078031d4 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -49,7 +49,7 @@ namespace NzbDrone.Core.Parser return _seriesService.FindByTitle(title); } - var tvdbId = _sceneMappingService.FindTvdbId(parsedEpisodeInfo.SeriesTitle, parsedEpisodeInfo.ReleaseTitle); + var tvdbId = _sceneMappingService.FindTvdbId(parsedEpisodeInfo.SeriesTitle, parsedEpisodeInfo.ReleaseTitle, parsedEpisodeInfo.SeasonNumber); if (tvdbId.HasValue) { @@ -85,7 +85,7 @@ namespace NzbDrone.Core.Parser if (series == null) { - tvdbId = _sceneMappingService.FindTvdbId(title, parsedEpisodeInfo.ReleaseTitle); + tvdbId = _sceneMappingService.FindTvdbId(title, parsedEpisodeInfo.ReleaseTitle, parsedEpisodeInfo.SeasonNumber); } if (!tvdbId.HasValue) @@ -137,7 +137,7 @@ namespace NzbDrone.Core.Parser private RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, Series series, SearchCriteriaBase searchCriteria) { - var sceneMapping = _sceneMappingService.FindSceneMapping(parsedEpisodeInfo.SeriesTitle, parsedEpisodeInfo.ReleaseTitle); + var sceneMapping = _sceneMappingService.FindSceneMapping(parsedEpisodeInfo.SeriesTitle, parsedEpisodeInfo.ReleaseTitle, parsedEpisodeInfo.SeasonNumber); var remoteEpisode = new RemoteEpisode {