diff --git a/frontend/src/Episode/EpisodeNumber.js b/frontend/src/Episode/EpisodeNumber.js index 6c0a62fb0..8e9891dc7 100644 --- a/frontend/src/Episode/EpisodeNumber.js +++ b/frontend/src/Episode/EpisodeNumber.js @@ -13,6 +13,10 @@ function getAlternateTitles(seasonNumber, sceneSeasonNumber, alternateTitles) { return true; } + if (alternateTitle.sceneSeasonNumber === undefined && alternateTitle.sceneOrigin === 'tvdb') { + return true; + } + return seasonNumber === alternateTitle.seasonNumber; }); } @@ -81,6 +85,8 @@ function EpisodeNumber(props) { title="Scene Information" body={ { alternateTitles.map((alternateTitle) => { + let suffix = ''; + + const altSceneSeasonNumber = sceneSeasonNumber === undefined ? seasonNumber : sceneSeasonNumber; + const altSceneEpisodeNumber = sceneEpisodeNumber === undefined ? episodeNumber : sceneEpisodeNumber; + + const mappingSeasonNumber = alternateTitle.sceneOrigin === 'tvdb' ? seasonNumber : altSceneSeasonNumber; + const altSeasonNumber = (alternateTitle.sceneSeasonNumber !== -1 && alternateTitle.sceneSeasonNumber !== undefined) ? alternateTitle.sceneSeasonNumber : mappingSeasonNumber; + const altEpisodeNumber = alternateTitle.sceneOrigin === 'tvdb' ? episodeNumber : altSceneEpisodeNumber; + + if (altEpisodeNumber !== altSceneEpisodeNumber) { + suffix = `S${padNumber(altSeasonNumber, 2)}E${padNumber(altEpisodeNumber, 2)}`; + } else if (altSeasonNumber !== altSceneSeasonNumber) { + suffix = `S${padNumber(altSeasonNumber, 2)}`; + } + return (
{alternateTitle.title} { - alternateTitle.sceneSeasonNumber !== -1 && - (S{padNumber(alternateTitle.sceneSeasonNumber, 2)}) + suffix && + ({suffix}) + } + { + alternateTitle.comment && + {alternateTitle.comment} }
); @@ -78,6 +99,8 @@ function SceneInfo(props) { } SceneInfo.propTypes = { + seasonNumber: PropTypes.number, + episodeNumber: PropTypes.number, sceneSeasonNumber: PropTypes.number, sceneEpisodeNumber: PropTypes.number, sceneAbsoluteEpisodeNumber: PropTypes.number, diff --git a/frontend/src/Series/Details/SeriesAlternateTitles.css b/frontend/src/Series/Details/SeriesAlternateTitles.css index 1af1ae68b..758081f7f 100644 --- a/frontend/src/Series/Details/SeriesAlternateTitles.css +++ b/frontend/src/Series/Details/SeriesAlternateTitles.css @@ -1,3 +1,8 @@ .alternateTitle { white-space: nowrap; } + +.comment { + color: $darkGray; + font-size: $smallFontSize; +} diff --git a/frontend/src/Series/Details/SeriesAlternateTitles.js b/frontend/src/Series/Details/SeriesAlternateTitles.js index 18d016579..d61c32dad 100644 --- a/frontend/src/Series/Details/SeriesAlternateTitles.js +++ b/frontend/src/Series/Details/SeriesAlternateTitles.js @@ -9,10 +9,14 @@ function SeriesAlternateTitles({ alternateTitles }) { alternateTitles.map((alternateTitle) => { return (
  • - {alternateTitle} + {alternateTitle.title} + { + alternateTitle.comment && + {alternateTitle.comment} + }
  • ); }) diff --git a/frontend/src/Series/Details/SeriesDetailsConnector.js b/frontend/src/Series/Details/SeriesDetailsConnector.js index 25bf13de1..28aa2f471 100644 --- a/frontend/src/Series/Details/SeriesDetailsConnector.js +++ b/frontend/src/Series/Details/SeriesDetailsConnector.js @@ -111,8 +111,9 @@ function createMapStateToProps() { const isPopulated = isEpisodesPopulated && isEpisodeFilesPopulated; const alternateTitles = _.reduce(series.alternateTitles, (acc, alternateTitle) => { if ((alternateTitle.seasonNumber === -1 || alternateTitle.seasonNumber === undefined) && - (alternateTitle.sceneSeasonNumber === -1 || alternateTitle.sceneSeasonNumber === undefined)) { - acc.push(alternateTitle.title); + (alternateTitle.sceneSeasonNumber === -1 || alternateTitle.sceneSeasonNumber === undefined) && + (alternateTitle.title !== series.title)) { + acc.push(alternateTitle); } return acc; diff --git a/src/NzbDrone.Api/Series/AlternateTitleResource.cs b/src/NzbDrone.Api/Series/AlternateTitleResource.cs index b1d6cc22c..0df82ff02 100644 --- a/src/NzbDrone.Api/Series/AlternateTitleResource.cs +++ b/src/NzbDrone.Api/Series/AlternateTitleResource.cs @@ -5,5 +5,7 @@ public string Title { get; set; } public int? SeasonNumber { get; set; } public int? SceneSeasonNumber { get; set; } + public string SceneOrigin { get; set; } + public string Comment { get; set; } } } diff --git a/src/NzbDrone.Api/Series/SeriesModule.cs b/src/NzbDrone.Api/Series/SeriesModule.cs index abf99c430..d565b7b2a 100644 --- a/src/NzbDrone.Api/Series/SeriesModule.cs +++ b/src/NzbDrone.Api/Series/SeriesModule.cs @@ -226,7 +226,9 @@ namespace NzbDrone.Api.Series { Title = v.Title, SeasonNumber = v.SeasonNumber, - SceneSeasonNumber = v.SceneSeasonNumber + SceneSeasonNumber = v.SceneSeasonNumber, + SceneOrigin = v.SceneOrigin, + Comment = v.Comment }).ToList(); } diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/Search/SingleEpisodeSearchMatchSpecificationTests/StandardEpisodeSearch.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/Search/SingleEpisodeSearchMatchSpecificationTests/StandardEpisodeSearch.cs index 671d794e6..d277afee4 100644 --- a/src/NzbDrone.Core.Test/DecisionEngineTests/Search/SingleEpisodeSearchMatchSpecificationTests/StandardEpisodeSearch.cs +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/Search/SingleEpisodeSearchMatchSpecificationTests/StandardEpisodeSearch.cs @@ -22,26 +22,17 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.Search.SingleEpisodeSearchMatch _remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo(); _remoteEpisode.ParsedEpisodeInfo.SeasonNumber = 5; _remoteEpisode.ParsedEpisodeInfo.EpisodeNumbers = new[] { 1 }; + _remoteEpisode.MappedSeasonNumber = 5; _searchCriteria.SeasonNumber = 5; _searchCriteria.EpisodeNumber = 1; - - Mocker.GetMock() - .Setup(v => v.GetTvdbSeasonNumber(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((s, r, i) => i); - } - - private void GivenMapping(int sceneSeasonNumber, int seasonNumber) - { - Mocker.GetMock() - .Setup(v => v.GetTvdbSeasonNumber(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((s, r, i) => i >= sceneSeasonNumber ? (seasonNumber + i - sceneSeasonNumber) : i); } [Test] public void should_return_false_if_season_does_not_match() { _remoteEpisode.ParsedEpisodeInfo.SeasonNumber = 10; + _remoteEpisode.MappedSeasonNumber = 10; Subject.IsSatisfiedBy(_remoteEpisode, _searchCriteria).Accepted.Should().BeFalse(); } @@ -50,8 +41,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.Search.SingleEpisodeSearchMatch public void should_return_true_if_season_matches_after_scenemapping() { _remoteEpisode.ParsedEpisodeInfo.SeasonNumber = 10; - - GivenMapping(10, 5); + _remoteEpisode.MappedSeasonNumber = 5; // 10 -> 5 mapping Subject.IsSatisfiedBy(_remoteEpisode, _searchCriteria).Accepted.Should().BeTrue(); } @@ -60,8 +50,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.Search.SingleEpisodeSearchMatch public void should_return_false_if_season_does_not_match_after_scenemapping() { _remoteEpisode.ParsedEpisodeInfo.SeasonNumber = 10; - - GivenMapping(9, 5); + _remoteEpisode.MappedSeasonNumber = 6; // 9 -> 5 mapping Subject.IsSatisfiedBy(_remoteEpisode, _searchCriteria).Accepted.Should().BeFalse(); } diff --git a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveGrabbedFixture.cs b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveGrabbedFixture.cs index 474af4cbb..e71537612 100644 --- a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveGrabbedFixture.cs +++ b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveGrabbedFixture.cs @@ -85,6 +85,10 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests .Setup(s => s.GetSeries(It.IsAny>())) .Returns(new List { _series }); + Mocker.GetMock() + .Setup(s => s.Map(It.IsAny(), It.IsAny())) + .Returns(new RemoteEpisode { Episodes = new List { _episode } }); + Mocker.GetMock() .Setup(s => s.GetEpisodes(It.IsAny(), _series, true, null)) .Returns(new List {_episode}); diff --git a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemovePendingFixture.cs b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemovePendingFixture.cs index d5c5058b1..d0629ad40 100644 --- a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemovePendingFixture.cs +++ b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemovePendingFixture.cs @@ -42,6 +42,10 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests .Setup(s => s.GetSeries(It.IsAny>())) .Returns(new List { new Series() }); + Mocker.GetMock() + .Setup(s => s.Map(It.IsAny(), It.IsAny())) + .Returns(new RemoteEpisode { Episodes = new List { _episode } }); + Mocker.GetMock() .Setup(s => s.GetEpisodes(It.IsAny(), It.IsAny(), It.IsAny(), null)) .Returns(new List{ _episode }); @@ -84,7 +88,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests } [Test] - public void should_not_remove_diffrent_season() + public void should_not_remove_different_season() { AddPending(id: 1, seasonNumber: 2, episodes: new[] { 1 }); AddPending(id: 2, seasonNumber: 2, episodes: new[] { 1 }); @@ -99,7 +103,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests } [Test] - public void should_not_remove_diffrent_episodes() + public void should_not_remove_different_episodes() { AddPending(id: 1, seasonNumber: 2, episodes: new[] { 1 }); AddPending(id: 2, seasonNumber: 2, episodes: new[] { 1 }); diff --git a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveRejectedFixture.cs b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveRejectedFixture.cs index 642568797..ed1d9ef2c 100644 --- a/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveRejectedFixture.cs +++ b/src/NzbDrone.Core.Test/Download/Pending/PendingReleaseServiceTests/RemoveRejectedFixture.cs @@ -77,6 +77,10 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests .Setup(s => s.GetSeries(It.IsAny>())) .Returns(new List { _series }); + Mocker.GetMock() + .Setup(s => s.Map(It.IsAny(), It.IsAny())) + .Returns(new RemoteEpisode { Episodes = new List { _episode } }); + Mocker.GetMock() .Setup(s => s.GetEpisodes(It.IsAny(), _series, true, null)) .Returns(new List {_episode}); diff --git a/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs index 70f9bc16c..fcb7110c0 100644 --- a/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Download/TrackedDownloads/TrackedDownloadServiceFixture.cs @@ -44,7 +44,8 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads { SeriesTitle = "TV Series", SeasonNumber = 1 - } + }, + MappedSeasonNumber = 1 }; Mocker.GetMock() @@ -77,6 +78,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads trackedDownload.RemoteEpisode.Series.Id.Should().Be(5); trackedDownload.RemoteEpisode.Episodes.First().Id.Should().Be(4); trackedDownload.RemoteEpisode.ParsedEpisodeInfo.SeasonNumber.Should().Be(1); + trackedDownload.RemoteEpisode.MappedSeasonNumber.Should().Be(1); } [Test] @@ -91,7 +93,8 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads SeriesTitle = "TV Series", SeasonNumber = 0, EpisodeNumbers = new []{ 1 } - } + }, + MappedSeasonNumber = 0 }; Mocker.GetMock() @@ -139,6 +142,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads trackedDownload.RemoteEpisode.Series.Id.Should().Be(5); trackedDownload.RemoteEpisode.Episodes.First().Id.Should().Be(4); trackedDownload.RemoteEpisode.ParsedEpisodeInfo.SeasonNumber.Should().Be(0); + trackedDownload.RemoteEpisode.MappedSeasonNumber.Should().Be(0); } } } diff --git a/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs b/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs index 9afa6c64c..73f91e790 100644 --- a/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs +++ b/src/NzbDrone.Core.Test/IndexerSearchTests/NzbSearchServiceFixture.cs @@ -53,8 +53,8 @@ namespace NzbDrone.Core.Test.IndexerSearchTests .Returns((i, j) => _xemEpisodes.Where(d => d.SeasonNumber == j).ToList()); Mocker.GetMock() - .Setup(s => s.GetSceneNames(It.IsAny(), It.IsAny>(), It.IsAny>())) - .Returns(new List()); + .Setup(s => s.FindByTvdbId(It.IsAny())) + .Returns(new List()); } private void WithEpisode(int seasonNumber, int episodeNumber, int? sceneSeasonNumber, int? sceneEpisodeNumber, string airDate = null) @@ -241,7 +241,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var seasonNumber = 1; var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, seasonNumber, false, false, true, false); + Subject.SeasonSearch(_xemSeries.Id, seasonNumber, false, true, true, false); var criteria = allCriteria.OfType().ToList(); @@ -354,7 +354,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests var allCriteria = WatchForSearchCriteria(); - Subject.SeasonSearch(_xemSeries.Id, 1, false, false, true, false); + Subject.SeasonSearch(_xemSeries.Id, 1, false, true, true, false); var criteria1 = allCriteria.OfType().ToList(); var criteria2 = allCriteria.OfType().ToList(); @@ -373,7 +373,11 @@ namespace NzbDrone.Core.Test.IndexerSearchTests Subject.SeasonSearch(_xemSeries.Id, 7, false, false, true, false); Mocker.GetMock() - .Verify(v => v.GetSceneNames(_xemSeries.Id, It.Is>(l => l.Contains(7)), It.Is>(l => l.Contains(7))), Times.Once()); + .Verify(v => v.FindByTvdbId(_xemSeries.Id), Times.Once()); + + allCriteria.Should().HaveCount(1); + allCriteria.First().Should().BeOfType(); + allCriteria.First().As().SeasonNumber.Should().Be(7); } } } diff --git a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs index bfe87a693..3f890eb75 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/GetEpisodesFixture.cs @@ -55,10 +55,6 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests Mocker.GetMock() .Setup(s => s.FindByTitle(It.IsAny())) .Returns(_series); - - Mocker.GetMock() - .Setup(v => v.GetTvdbSeasonNumber(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((s, r, i) => i); } private void GivenDailySeries() @@ -328,8 +324,8 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests const int tvdbSeasonNumber = 5; Mocker.GetMock() - .Setup(v => v.GetTvdbSeasonNumber(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((s, r, i) => tvdbSeasonNumber); + .Setup(v => v.FindSceneMapping(It.IsAny(), It.IsAny())) + .Returns((s, r) => new SceneMapping { SceneSeasonNumber = 1, SeasonNumber = tvdbSeasonNumber }); Subject.GetEpisodes(_parsedEpisodeInfo, _series, true, null); @@ -346,8 +342,8 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests const int tvdbSeasonNumber = 5; Mocker.GetMock() - .Setup(s => s.FindSceneMapping(_parsedEpisodeInfo.SeriesTitle, It.IsAny())) - .Returns(new SceneMapping { SeasonNumber = tvdbSeasonNumber, SceneSeasonNumber = _parsedEpisodeInfo.SeasonNumber + 100 }); + .Setup(v => v.FindSceneMapping(It.IsAny(), It.IsAny())) + .Returns((s, r) => new SceneMapping { SceneSeasonNumber = 101, SeasonNumber = tvdbSeasonNumber }); Subject.GetEpisodes(_parsedEpisodeInfo, _series, true, null); diff --git a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs index 3f62300f3..d5a2202e3 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ParsingServiceTests/MapFixture.cs @@ -51,10 +51,6 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests SeasonNumber = _episodes.First().SeasonNumber, Episodes = _episodes }; - - Mocker.GetMock() - .Setup(v => v.GetTvdbSeasonNumber(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((s, r, i) => i); } private void GivenMatchBySeriesTitle() @@ -122,8 +118,8 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests GivenMatchByTvRageId(); Mocker.GetMock() - .Setup(v => v.FindTvdbId(It.IsAny(), It.IsAny())) - .Returns(10); + .Setup(v => v.FindSceneMapping(It.IsAny(), It.IsAny())) + .Returns(new SceneMapping { TvdbId = 10 }); var result = Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId); diff --git a/src/NzbDrone.Core/DataAugmentation/Scene/SceneMapping.cs b/src/NzbDrone.Core/DataAugmentation/Scene/SceneMapping.cs index d45c92d7d..52cb21160 100644 --- a/src/NzbDrone.Core/DataAugmentation/Scene/SceneMapping.cs +++ b/src/NzbDrone.Core/DataAugmentation/Scene/SceneMapping.cs @@ -18,6 +18,10 @@ namespace NzbDrone.Core.DataAugmentation.Scene public int? SceneSeasonNumber { get; set; } + public string SceneOrigin { get; set; } + public SearchMode? SearchMode { get; set; } + public string Comment { get; set; } + public string FilterRegex { get; set; } public string Type { get; set; } diff --git a/src/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs b/src/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs index 2bb9af045..860f66920 100644 --- a/src/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs +++ b/src/NzbDrone.Core/DataAugmentation/Scene/SceneMappingService.cs @@ -15,14 +15,10 @@ namespace NzbDrone.Core.DataAugmentation.Scene public interface ISceneMappingService { List GetSceneNames(int tvdbId, List seasonNumbers, List sceneSeasonNumbers); - List GetSceneMappings(int tvdbId, List seasonNumbers); int? FindTvdbId(string sceneTitle, string releaseTitle); List FindByTvdbId(int tvdbId); SceneMapping FindSceneMapping(string sceneTitle, string releaseTitle); int? GetSceneSeasonNumber(string seriesTitle, string releaseTitle); - int? GetTvdbSeasonNumber(string seriesTitle, string releaseTitle); - int GetTvdbSeasonNumber(string seriesTitle, string releaseTitle, int sceneSeasonNumber); - int? GetSceneSeasonNumber(int tvdbId, int seasonNumber); } public class SceneMappingService : ISceneMappingService, @@ -60,27 +56,13 @@ namespace NzbDrone.Core.DataAugmentation.Scene return new List(); } - var names = mappings.Where(n => n.SeasonNumber.HasValue && seasonNumbers.Contains(n.SeasonNumber.Value) || - n.SceneSeasonNumber.HasValue && sceneSeasonNumbers.Contains(n.SceneSeasonNumber.Value) || - (n.SeasonNumber ?? -1) == -1 && (n.SceneSeasonNumber ?? -1) == -1) + var names = mappings.Where(n => seasonNumbers.Contains(n.SeasonNumber ?? -1) || + sceneSeasonNumbers.Contains(n.SceneSeasonNumber ?? -1) || + (n.SeasonNumber ?? -1) == -1 && (n.SceneSeasonNumber ?? -1) == -1 && n.SceneOrigin != "tvdb") + .Where(n => IsEnglish(n.SearchTerm)) .Select(n => n.SearchTerm).Distinct().ToList(); - return FilterNonEnglish(names); - } - - public List GetSceneMappings(int tvdbId, List seasonNumbers) - { - var mappings = FindByTvdbId(tvdbId); - - if (mappings == null) - { - return new List(); - } - - return mappings.Where(n => seasonNumbers.Contains(n.SeasonNumber ?? -1) && - (n.SceneSeasonNumber ?? -1) != -1) - .Where(n => IsEnglish(n.SearchTerm)) - .ToList(); + return names; } public int? FindTvdbId(string seriesTitle) @@ -141,44 +123,6 @@ namespace NzbDrone.Core.DataAugmentation.Scene return FindSceneMapping(seriesTitle, releaseTitle)?.SceneSeasonNumber; } - public int? GetTvdbSeasonNumber(string seriesTitle, string releaseTitle) - { - return FindSceneMapping(seriesTitle, releaseTitle)?.SeasonNumber; - } - - public int GetTvdbSeasonNumber(string seriesTitle, string releaseTitle, int sceneSeasonNumber) - { - var sceneMapping = FindSceneMapping(seriesTitle, releaseTitle); - - if (sceneMapping != null && sceneMapping.SeasonNumber.HasValue && sceneMapping.SeasonNumber.Value >= 0 && - sceneMapping.SceneSeasonNumber <= sceneSeasonNumber) - { - var offset = sceneSeasonNumber - sceneMapping.SceneSeasonNumber.Value; - return sceneMapping.SeasonNumber.Value + offset; - } - - return sceneSeasonNumber; - } - - public int? GetSceneSeasonNumber(int tvdbId, int seasonNumber) - { - var mappings = FindByTvdbId(tvdbId); - - if (mappings == null) - { - return null; - } - - var mapping = mappings.FirstOrDefault(e => e.SeasonNumber == seasonNumber && e.SceneSeasonNumber.HasValue); - - if (mapping == null) - { - return null; - } - - return mapping.SceneSeasonNumber; - } - private void UpdateMappings() { _logger.Info("Updating Scene mappings"); @@ -295,11 +239,6 @@ namespace NzbDrone.Core.DataAugmentation.Scene return normalCandidates; } - private List FilterNonEnglish(List titles) - { - return titles.Where(IsEnglish).ToList(); - } - private bool IsEnglish(string title) { return title.All(c => c <= 255); diff --git a/src/NzbDrone.Core/DataAugmentation/Scene/SearchMode.cs b/src/NzbDrone.Core/DataAugmentation/Scene/SearchMode.cs new file mode 100644 index 000000000..01136a645 --- /dev/null +++ b/src/NzbDrone.Core/DataAugmentation/Scene/SearchMode.cs @@ -0,0 +1,12 @@ +using System; + +namespace NzbDrone.Core.DataAugmentation.Scene +{ + [Flags] + public enum SearchMode + { + Default = 0, + SearchID = 1, + SearchTitle = 2 + } +} diff --git a/src/NzbDrone.Core/Datastore/Converters/EnumIntConverter.cs b/src/NzbDrone.Core/Datastore/Converters/EnumIntConverter.cs index 22d6b5336..3f501fea6 100644 --- a/src/NzbDrone.Core/Datastore/Converters/EnumIntConverter.cs +++ b/src/NzbDrone.Core/Datastore/Converters/EnumIntConverter.cs @@ -25,7 +25,7 @@ namespace NzbDrone.Core.Datastore.Converters public object ToDB(object clrValue) { - if (clrValue != null) + if (clrValue != null && clrValue != DBNull.Value) { return (int)clrValue; } diff --git a/src/NzbDrone.Core/Datastore/Migration/150_add_scene_mapping_origin.cs b/src/NzbDrone.Core/Datastore/Migration/150_add_scene_mapping_origin.cs new file mode 100644 index 000000000..2189aab85 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/150_add_scene_mapping_origin.cs @@ -0,0 +1,21 @@ +using System; +using System.Data; +using FluentMigrator; +using NzbDrone.Common.Disk; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(150)] + public class add_scene_mapping_origin : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("SceneMappings") + .AddColumn("SceneOrigin").AsString().Nullable() + .AddColumn("SearchMode").AsInt32().Nullable() + .AddColumn("Comment").AsString().Nullable(); + } + } +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs index a8bca8c0e..2c6e0686c 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SeasonMatchSpecification.cs @@ -29,11 +29,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search var singleEpisodeSpec = searchCriteria as SeasonSearchCriteria; if (singleEpisodeSpec == null) return Decision.Accept(); - var seasonNumber = _sceneMappingService.GetTvdbSeasonNumber(remoteEpisode.ParsedEpisodeInfo.SeriesTitle, - remoteEpisode.ParsedEpisodeInfo.ReleaseTitle, - remoteEpisode.ParsedEpisodeInfo.SeasonNumber); - - if (singleEpisodeSpec.SeasonNumber != seasonNumber) + if (singleEpisodeSpec.SeasonNumber != remoteEpisode.MappedSeasonNumber) { _logger.Debug("Season number does not match searched season number, skipping."); return Decision.Reject("Wrong season"); diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs index d888cb522..de6cdfa12 100644 --- a/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/Search/SingleEpisodeSearchMatchSpecification.cs @@ -38,11 +38,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search private Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SingleEpisodeSearchCriteria singleEpisodeSpec) { - var seasonNumber = _sceneMappingService.GetTvdbSeasonNumber(remoteEpisode.ParsedEpisodeInfo.SeriesTitle, - remoteEpisode.ParsedEpisodeInfo.ReleaseTitle, - remoteEpisode.ParsedEpisodeInfo.SeasonNumber); - - if (singleEpisodeSpec.SeasonNumber != seasonNumber) + if (singleEpisodeSpec.SeasonNumber != remoteEpisode.MappedSeasonNumber) { _logger.Debug("Season number does not match searched season number, skipping."); return Decision.Reject("Wrong season"); diff --git a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs index f4e3f1a65..d0ca1be47 100644 --- a/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs +++ b/src/NzbDrone.Core/Download/Pending/PendingReleaseService.cs @@ -291,32 +291,32 @@ namespace NzbDrone.Core.Download.Pending // Just in case the series was removed, but wasn't cleaned up yet (housekeeper will clean it up) if (series == null) return null; - List episodes; + + release.RemoteEpisode = new RemoteEpisode + { + Series = series, + ParsedEpisodeInfo = release.ParsedEpisodeInfo, + Release = release.Release + }; RemoteEpisode knownRemoteEpisode; if (knownRemoteEpisodes != null && knownRemoteEpisodes.TryGetValue(release.Release.Title, out knownRemoteEpisode)) { - episodes = knownRemoteEpisode.Episodes; + release.RemoteEpisode.MappedSeasonNumber = knownRemoteEpisode.MappedSeasonNumber; + release.RemoteEpisode.Episodes = knownRemoteEpisode.Episodes; } - else + else if (ValidateParsedEpisodeInfo.ValidateForSeriesType(release.ParsedEpisodeInfo, series)) { - if (ValidateParsedEpisodeInfo.ValidateForSeriesType(release.ParsedEpisodeInfo, series)) - { - episodes = _parsingService.GetEpisodes(release.ParsedEpisodeInfo, series, true); - } - else - { - episodes = new List(); - } - } + var remoteEpisode = _parsingService.Map(release.ParsedEpisodeInfo, series); - release.RemoteEpisode = new RemoteEpisode + release.RemoteEpisode.MappedSeasonNumber = remoteEpisode.MappedSeasonNumber; + release.RemoteEpisode.Episodes = remoteEpisode.Episodes; + } + else { - Series = series, - Episodes = episodes, - ParsedEpisodeInfo = release.ParsedEpisodeInfo, - Release = release.Release - }; + release.RemoteEpisode.MappedSeasonNumber = release.ParsedEpisodeInfo.SeasonNumber; + release.RemoteEpisode.Episodes = new List(); + } result.Add(release); } diff --git a/src/NzbDrone.Core/IndexerSearch/Definitions/SceneEpisodeMapping.cs b/src/NzbDrone.Core/IndexerSearch/Definitions/SceneEpisodeMapping.cs new file mode 100644 index 000000000..08fd734b1 --- /dev/null +++ b/src/NzbDrone.Core/IndexerSearch/Definitions/SceneEpisodeMapping.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using NzbDrone.Core.DataAugmentation.Scene; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.IndexerSearch.Definitions +{ + public class SceneEpisodeMapping + { + public Episode Episode { get; set; } + public SearchMode SearchMode { get; set; } + public List SceneTitles { get; set; } + public int SeasonNumber { get; set; } + public int EpisodeNumber { get; set; } + public int? AbsoluteEpisodeNumber { get; set; } + + public override int GetHashCode() + { + return SearchMode.GetHashCode() ^ SeasonNumber.GetHashCode() ^ EpisodeNumber.GetHashCode(); + } + + public override bool Equals(object obj) + { + var other = obj as SceneEpisodeMapping; + + if (object.ReferenceEquals(other, null)) return false; + + return SeasonNumber == other.SeasonNumber && EpisodeNumber == other.EpisodeNumber && SearchMode == other.SearchMode; + } + } +} diff --git a/src/NzbDrone.Core/IndexerSearch/Definitions/SceneSeasonMapping.cs b/src/NzbDrone.Core/IndexerSearch/Definitions/SceneSeasonMapping.cs new file mode 100644 index 000000000..66619caed --- /dev/null +++ b/src/NzbDrone.Core/IndexerSearch/Definitions/SceneSeasonMapping.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using NzbDrone.Core.DataAugmentation.Scene; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.IndexerSearch.Definitions +{ + public class SceneSeasonMapping + { + public List Episodes { get; set; } + public SceneEpisodeMapping EpisodeMapping { get; set; } + public SearchMode SearchMode { get; set; } + public List SceneTitles { get; set; } + public int SeasonNumber { get; set; } + } +} diff --git a/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs b/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs index dc2a4cdaf..ab3063f82 100644 --- a/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs +++ b/src/NzbDrone.Core/IndexerSearch/Definitions/SearchCriteriaBase.cs @@ -16,8 +16,8 @@ namespace NzbDrone.Core.IndexerSearch.Definitions public Series Series { get; set; } public List SceneTitles { get; set; } - public List SceneMappings { get; set; } public List Episodes { get; set; } + public SearchMode SearchMode { get; set; } public virtual bool MonitoredEpisodesOnly { get; set; } public virtual bool UserInvokedSearch { get; set; } public virtual bool InteractiveSearch { get; set; } diff --git a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs index f1d7adfaf..70dcd2626 100644 --- a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs @@ -68,7 +68,7 @@ namespace NzbDrone.Core.IndexerSearch throw new SearchFailedException("Air date is missing"); } - return SearchDaily(series, episode, userInvokedSearch, interactiveSearch); + return SearchDaily(series, episode, false, userInvokedSearch, interactiveSearch); } if (series.SeriesType == SeriesTypes.Anime) @@ -78,19 +78,19 @@ namespace NzbDrone.Core.IndexerSearch episode.AbsoluteEpisodeNumber == null) { // Search for special episodes in season 0 that don't have absolute episode numbers - return SearchSpecial(series, new List { episode }, userInvokedSearch, interactiveSearch); + return SearchSpecial(series, new List { episode }, false, userInvokedSearch, interactiveSearch); } - return SearchAnime(series, episode, userInvokedSearch, interactiveSearch); + return SearchAnime(series, episode, false, userInvokedSearch, interactiveSearch); } if (episode.SeasonNumber == 0) { // Search for special episodes in season 0 - return SearchSpecial(series, new List { episode }, userInvokedSearch, interactiveSearch); + return SearchSpecial(series, new List { episode }, false, userInvokedSearch, interactiveSearch); } - return SearchSingle(series, episode, userInvokedSearch, interactiveSearch); + return SearchSingle(series, episode, false, userInvokedSearch, interactiveSearch); } public List SeasonSearch(int seriesId, int seasonNumber, bool missingOnly, bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch) @@ -104,115 +104,215 @@ namespace NzbDrone.Core.IndexerSearch return SeasonSearch(seriesId, seasonNumber, episodes, monitoredOnly, userInvokedSearch, interactiveSearch); } + public List SeasonSearch(int seriesId, int seasonNumber, List episodes, bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch) { var series = _seriesService.GetSeries(seriesId); if (series.SeriesType == SeriesTypes.Anime) { - return SearchAnimeSeason(series, episodes, userInvokedSearch, interactiveSearch); + return SearchAnimeSeason(series, episodes, monitoredOnly, userInvokedSearch, interactiveSearch); } if (series.SeriesType == SeriesTypes.Daily) { - return SearchDailySeason(series, episodes, userInvokedSearch, interactiveSearch); + return SearchDailySeason(series, episodes, monitoredOnly, userInvokedSearch, interactiveSearch); } - if (seasonNumber == 0) - { - // search for special episodes in season 0 - return SearchSpecial(series, episodes, userInvokedSearch, interactiveSearch); - } + var mappings = GetSceneSeasonMappings(series, episodes); var downloadDecisions = new List(); - if (series.UseSceneNumbering) + foreach (var mapping in mappings) { - var sceneSeasonGroups = episodes.GroupBy(v => + if (mapping.SeasonNumber == 0) { - if (v.SceneSeasonNumber.HasValue && v.SceneEpisodeNumber.HasValue) - { - return v.SceneSeasonNumber.Value; - } - return v.SeasonNumber; - }).Distinct(); + // search for special episodes in season 0 + downloadDecisions.AddRange(SearchSpecial(series, mapping.Episodes, monitoredOnly, userInvokedSearch, interactiveSearch)); + continue; + } - foreach (var sceneSeasonEpisodes in sceneSeasonGroups) + if (mapping.Episodes.Count == 1) { - if (sceneSeasonEpisodes.Count() == 1) - { - var episode = sceneSeasonEpisodes.First(); - var searchSpec = Get(series, sceneSeasonEpisodes.ToList(), userInvokedSearch, interactiveSearch); + var searchSpec = Get(series, mapping, monitoredOnly, userInvokedSearch, interactiveSearch); + searchSpec.SeasonNumber = mapping.SeasonNumber; + searchSpec.EpisodeNumber = mapping.EpisodeMapping.EpisodeNumber; - searchSpec.SeasonNumber = sceneSeasonEpisodes.Key; - searchSpec.MonitoredEpisodesOnly = monitoredOnly; + var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); + downloadDecisions.AddRange(decisions); + } + else + { + var searchSpec = Get(series, mapping, monitoredOnly, userInvokedSearch, interactiveSearch); + searchSpec.SeasonNumber = mapping.SeasonNumber; - if (episode.SceneSeasonNumber.HasValue && episode.SceneEpisodeNumber.HasValue) - { - searchSpec.EpisodeNumber = episode.SceneEpisodeNumber.Value; - } - else - { - searchSpec.EpisodeNumber = episode.EpisodeNumber; - } + var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); + downloadDecisions.AddRange(decisions); + } + } + + return downloadDecisions; + } + + private List GetSceneSeasonMappings(Series series, List episodes) + { + var dict = new Dictionary(); - var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); - downloadDecisions.AddRange(decisions); + var sceneMappings = _sceneMapping.FindByTvdbId(series.TvdbId); + + // Group the episode by SceneSeasonNumber/SeasonNumber, in 99% of cases this will result in 1 groupedEpisode + var groupedEpisodes = episodes.ToLookup(v => (v.SceneSeasonNumber ?? v.SeasonNumber) * 100000 + v.SeasonNumber); + + foreach (var groupedEpisode in groupedEpisodes) + { + var episodeMappings = GetSceneEpisodeMappings(series, groupedEpisode.First(), sceneMappings); + + foreach (var episodeMapping in episodeMappings) + { + var seasonMapping = new SceneSeasonMapping + { + Episodes = groupedEpisode.ToList(), + EpisodeMapping = episodeMapping, + SceneTitles = episodeMapping.SceneTitles, + SearchMode = episodeMapping.SearchMode, + SeasonNumber = episodeMapping.SeasonNumber + }; + + if (dict.TryGetValue(seasonMapping, out var existing)) + { + existing.Episodes.AddRange(seasonMapping.Episodes); + existing.SceneTitles.AddRange(seasonMapping.SceneTitles); } else { - var searchSpec = Get(series, sceneSeasonEpisodes.ToList(), userInvokedSearch, interactiveSearch); - searchSpec.SeasonNumber = sceneSeasonEpisodes.Key; - searchSpec.MonitoredEpisodesOnly = monitoredOnly; - - var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); - downloadDecisions.AddRange(decisions); + dict[seasonMapping] = seasonMapping; } } } - else + + foreach (var item in dict) { - var searchSpec = Get(series, episodes, userInvokedSearch, interactiveSearch); - searchSpec.SeasonNumber = seasonNumber; - searchSpec.MonitoredEpisodesOnly = monitoredOnly; + item.Value.Episodes = item.Value.Episodes.Distinct().ToList(); + item.Value.SceneTitles = item.Value.SceneTitles.Distinct().ToList(); + } - var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); - downloadDecisions.AddRange(decisions); + return dict.Values.ToList(); + } + + private List GetSceneEpisodeMappings(Series series, Episode episode) + { + var dict = new Dictionary(); + + var sceneMappings = _sceneMapping.FindByTvdbId(series.TvdbId); + + var episodeMappings = GetSceneEpisodeMappings(series, episode, sceneMappings); + + foreach (var episodeMapping in episodeMappings) + { + if (dict.TryGetValue(episodeMapping, out var existing)) + { + existing.SceneTitles.AddRange(episodeMapping.SceneTitles); + } + else + { + dict[episodeMapping] = episodeMapping; + } } - return downloadDecisions; + foreach (var item in dict) + { + item.Value.SceneTitles = item.Value.SceneTitles.Distinct().ToList(); + } + + return dict.Values.ToList(); } - private List SearchSingle(Series series, Episode episode, bool userInvokedSearch, bool interactiveSearch) + private IEnumerable GetSceneEpisodeMappings(Series series, Episode episode, List sceneMappings) { - var searchSpec = Get(series, new List { episode }, userInvokedSearch, interactiveSearch); + var includeGlobal = true; + + foreach (var sceneMapping in sceneMappings) + { + if (sceneMapping.ParseTerm == series.CleanTitle && sceneMapping.FilterRegex.IsNotNullOrWhiteSpace()) + { + // Disable the implied mapping if we have an explicit mapping by the same name + includeGlobal = false; + } + + // By default we do a alt title search in case indexers don't have the release properly indexed. Services can override this behavior. + var searchMode = sceneMapping.SearchMode ?? ((sceneMapping.SceneSeasonNumber ?? -1) != -1 ? SearchMode.SearchTitle : SearchMode.Default); + + if (sceneMapping.SceneOrigin == "tvdb") + { + yield return new SceneEpisodeMapping + { + Episode = episode, + SearchMode = searchMode, + SceneTitles = new List { sceneMapping.SearchTerm }, + SeasonNumber = (sceneMapping.SceneSeasonNumber ?? -1) == -1 ? episode.SeasonNumber : sceneMapping.SceneSeasonNumber.Value, + EpisodeNumber = episode.EpisodeNumber, + AbsoluteEpisodeNumber = episode.AbsoluteEpisodeNumber + }; + } + else + { + yield return new SceneEpisodeMapping + { + Episode = episode, + SearchMode = searchMode, + SceneTitles = new List { sceneMapping.SearchTerm }, + SeasonNumber = (sceneMapping.SceneSeasonNumber ?? -1) == -1 ? (episode.SceneSeasonNumber ?? episode.SeasonNumber) : sceneMapping.SceneSeasonNumber.Value, + EpisodeNumber = episode.SceneEpisodeNumber ?? episode.EpisodeNumber, + AbsoluteEpisodeNumber = episode.SceneAbsoluteEpisodeNumber ?? episode.AbsoluteEpisodeNumber + }; + } + } - if (series.UseSceneNumbering && episode.SceneSeasonNumber.HasValue && episode.SceneEpisodeNumber.HasValue) + if (includeGlobal) { - searchSpec.EpisodeNumber = episode.SceneEpisodeNumber.Value; - searchSpec.SeasonNumber = episode.SceneSeasonNumber.Value; + yield return new SceneEpisodeMapping + { + Episode = episode, + SearchMode = SearchMode.Default, + SceneTitles = new List { series.Title }, + SeasonNumber = episode.SceneSeasonNumber ?? episode.SeasonNumber, + EpisodeNumber = episode.SceneEpisodeNumber ?? episode.EpisodeNumber, + AbsoluteEpisodeNumber = episode.SceneSeasonNumber ?? episode.AbsoluteEpisodeNumber + }; } - else + } + + private List SearchSingle(Series series, Episode episode, bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch) + { + var mappings = GetSceneEpisodeMappings(series, episode); + + var downloadDecisions = new List(); + + foreach (var mapping in mappings) { - searchSpec.EpisodeNumber = episode.EpisodeNumber; - searchSpec.SeasonNumber = episode.SeasonNumber; + var searchSpec = Get(series, mapping, monitoredOnly, userInvokedSearch, interactiveSearch); + searchSpec.SeasonNumber = mapping.SeasonNumber; + searchSpec.EpisodeNumber = mapping.EpisodeNumber; + + var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); + downloadDecisions.AddRange(decisions); } - return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); + return downloadDecisions; } - private List SearchDaily(Series series, Episode episode, bool userInvokedSearch, bool interactiveSearch) + private List SearchDaily(Series series, Episode episode, bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch) { var airDate = DateTime.ParseExact(episode.AirDate, Episode.AIR_DATE_FORMAT, CultureInfo.InvariantCulture); - var searchSpec = Get(series, new List { episode }, userInvokedSearch, interactiveSearch); + var searchSpec = Get(series, new List { episode }, monitoredOnly, userInvokedSearch, interactiveSearch); searchSpec.AirDate = airDate; return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); } - private List SearchAnime(Series series, Episode episode, bool userInvokedSearch, bool interactiveSearch, bool isSeasonSearch = false) + private List SearchAnime(Series series, Episode episode, bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch, bool isSeasonSearch = false) { - var searchSpec = Get(series, new List { episode }, userInvokedSearch, interactiveSearch); + var searchSpec = Get(series, new List { episode }, monitoredOnly, userInvokedSearch, interactiveSearch); searchSpec.IsSeasonSearch = isSeasonSearch; @@ -233,11 +333,11 @@ namespace NzbDrone.Core.IndexerSearch return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec); } - private List SearchSpecial(Series series, List episodes, bool userInvokedSearch, bool interactiveSearch) + private List SearchSpecial(Series series, List episodes,bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch) { var downloadDecisions = new List(); - var searchSpec = Get(series, episodes, userInvokedSearch, interactiveSearch); + var searchSpec = Get(series, episodes, monitoredOnly, userInvokedSearch, interactiveSearch); // build list of queries for each episode in the form: " " searchSpec.EpisodeQueryTitles = episodes.Where(e => !string.IsNullOrWhiteSpace(e.Title)) .SelectMany(e => searchSpec.QueryTitles.Select(title => title + " " + SearchCriteriaBase.GetQueryTitle(e.Title))) @@ -248,62 +348,69 @@ namespace NzbDrone.Core.IndexerSearch // Search for each episode by season/episode number as well foreach (var episode in episodes) { - downloadDecisions.AddRange(SearchSingle(series, episode, userInvokedSearch, interactiveSearch)); + // Episode needs to be monitored if it's not an interactive search + if (!interactiveSearch && monitoredOnly && !episode.Monitored) + { + continue; + } + + downloadDecisions.AddRange(SearchSingle(series, episode, monitoredOnly, userInvokedSearch, interactiveSearch)); } return downloadDecisions; } - private List SearchAnimeSeason(Series series, List episodes, bool userInvokedSearch, bool interactiveSearch) + private List SearchAnimeSeason(Series series, List episodes, bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch) { var downloadDecisions = new List(); - - var episodesToSearch = episodes.Where(e => - { - // Episode needs to be monitored if it's not an interactive search - if (!interactiveSearch && !e.Monitored) - { - return false; - } - - // Ensure episode has an airdate and has already aired - return e.AirDateUtc.HasValue && e.AirDateUtc.Value.Before(DateTime.UtcNow); - }); + // Episode needs to be monitored if it's not an interactive search + // and Ensure episode has an airdate and has already aired + var episodesToSearch = episodes + .Where(ep => interactiveSearch || !monitoredOnly || ep.Monitored) + .Where(ep => ep.AirDateUtc.HasValue && ep.AirDateUtc.Value.Before(DateTime.UtcNow)) + .ToList(); foreach (var episode in episodesToSearch) { - downloadDecisions.AddRange(SearchAnime(series, episode, userInvokedSearch, interactiveSearch, true)); + downloadDecisions.AddRange(SearchAnime(series, episode, monitoredOnly, userInvokedSearch, interactiveSearch, true)); } return downloadDecisions; } - private List SearchDailySeason(Series series, List episodes, bool userInvokedSearch, bool interactiveSearch) + private List SearchDailySeason(Series series, List episodes, bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch) { var downloadDecisions = new List(); - foreach (var yearGroup in episodes.Where(v => v.Monitored && v.AirDate.IsNotNullOrWhiteSpace()) - .GroupBy(v => DateTime.ParseExact(v.AirDate, Episode.AIR_DATE_FORMAT, CultureInfo.InvariantCulture).Year)) + + // Episode needs to be monitored if it's not an interactive search + // and Ensure episode has an airdate + var episodesToSearch = episodes + .Where(ep => interactiveSearch || !monitoredOnly || ep.Monitored) + .Where(ep => ep.AirDate.IsNotNullOrWhiteSpace()) + .ToList(); + + foreach (var yearGroup in episodesToSearch.GroupBy(v => DateTime.ParseExact(v.AirDate, Episode.AIR_DATE_FORMAT, CultureInfo.InvariantCulture).Year)) { var yearEpisodes = yearGroup.ToList(); if (yearEpisodes.Count > 1) { - var searchSpec = Get(series, yearEpisodes, userInvokedSearch, interactiveSearch); + var searchSpec = Get(series, yearEpisodes, monitoredOnly, userInvokedSearch, interactiveSearch); searchSpec.Year = yearGroup.Key; downloadDecisions.AddRange(Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec)); } else { - downloadDecisions.AddRange(SearchDaily(series, yearEpisodes.First(), userInvokedSearch, interactiveSearch)); + downloadDecisions.AddRange(SearchDaily(series, yearEpisodes.First(), monitoredOnly, userInvokedSearch, interactiveSearch)); } } return downloadDecisions; } - private TSpec Get(Series series, List episodes, bool userInvokedSearch, bool interactiveSearch) where TSpec : SearchCriteriaBase, new() + private TSpec Get(Series series, List episodes, bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch) where TSpec : SearchCriteriaBase, new() { var spec = new TSpec(); @@ -311,16 +418,41 @@ namespace NzbDrone.Core.IndexerSearch spec.SceneTitles = _sceneMapping.GetSceneNames(series.TvdbId, episodes.Select(e => e.SeasonNumber).Distinct().ToList(), episodes.Select(e => e.SceneSeasonNumber ?? e.SeasonNumber).Distinct().ToList()); - spec.SceneMappings = _sceneMapping.GetSceneMappings(series.TvdbId, - episodes.Select(e => e.SeasonNumber).Distinct().ToList()); + + spec.Episodes = episodes; + spec.MonitoredEpisodesOnly = monitoredOnly; + spec.UserInvokedSearch = userInvokedSearch; + spec.InteractiveSearch = interactiveSearch; + return spec; + } - if (!spec.SceneTitles.Contains(series.Title)) - { - spec.SceneTitles.Add(series.Title); - } + private TSpec Get(Series series, SceneEpisodeMapping mapping, bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch) where TSpec : SearchCriteriaBase, new() + { + var spec = new TSpec(); - spec.Episodes = episodes; + spec.Series = series; + spec.SceneTitles = mapping.SceneTitles; + spec.SearchMode = mapping.SearchMode; + + spec.Episodes = new List { mapping.Episode }; + spec.MonitoredEpisodesOnly = monitoredOnly; + spec.UserInvokedSearch = userInvokedSearch; + spec.InteractiveSearch = interactiveSearch; + + return spec; + } + + private TSpec Get(Series series, SceneSeasonMapping mapping, bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch) where TSpec : SearchCriteriaBase, new() + { + var spec = new TSpec(); + + spec.Series = series; + spec.SceneTitles = mapping.SceneTitles; + spec.SearchMode = mapping.SearchMode; + + spec.Episodes = mapping.Episodes; + spec.MonitoredEpisodesOnly = monitoredOnly; spec.UserInvokedSearch = userInvokedSearch; spec.InteractiveSearch = interactiveSearch; diff --git a/src/NzbDrone.Core/Indexers/FileList/FileListRequestGenerator.cs b/src/NzbDrone.Core/Indexers/FileList/FileListRequestGenerator.cs index b760c2ce2..89a900988 100644 --- a/src/NzbDrone.Core/Indexers/FileList/FileListRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/FileList/FileListRequestGenerator.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; +using NzbDrone.Core.DataAugmentation.Scene; using NzbDrone.Core.IndexerSearch.Definitions; namespace NzbDrone.Core.Indexers.FileList @@ -24,11 +25,22 @@ namespace NzbDrone.Core.Indexers.FileList { var pageableRequests = new IndexerPageableRequestChain(); - AddImdbRequests(pageableRequests, searchCriteria, "search-torrents", Settings.Categories, $"&season={searchCriteria.SeasonNumber}&episode={searchCriteria.EpisodeNumber}"); + if (searchCriteria.SearchMode.HasFlag(SearchMode.SearchID) || searchCriteria.SearchMode == SearchMode.Default) + { + AddImdbRequests(pageableRequests, searchCriteria, "search-torrents", Settings.Categories, $"&season={searchCriteria.SeasonNumber}&episode={searchCriteria.EpisodeNumber}"); + } + + if (searchCriteria.SearchMode.HasFlag(SearchMode.SearchTitle)) + { + AddNameRequests(pageableRequests, searchCriteria, "search-torrents", Settings.Categories, $"&season={searchCriteria.SeasonNumber}&episode={searchCriteria.EpisodeNumber}"); + } pageableRequests.AddTier(); - AddNameRequests(pageableRequests, searchCriteria, "search-torrents", Settings.Categories, $"&season={searchCriteria.SeasonNumber}&episode={searchCriteria.EpisodeNumber}"); + if (searchCriteria.SearchMode == SearchMode.Default) + { + AddNameRequests(pageableRequests, searchCriteria, "search-torrents", Settings.Categories, $"&season={searchCriteria.SeasonNumber}&episode={searchCriteria.EpisodeNumber}"); + } return pageableRequests; } @@ -37,11 +49,22 @@ namespace NzbDrone.Core.Indexers.FileList { var pageableRequests = new IndexerPageableRequestChain(); - AddImdbRequests(pageableRequests, searchCriteria, "search-torrents", Settings.Categories, $"&season={searchCriteria.SeasonNumber}"); + if (searchCriteria.SearchMode.HasFlag(SearchMode.SearchID) || searchCriteria.SearchMode == SearchMode.Default) + { + AddImdbRequests(pageableRequests, searchCriteria, "search-torrents", Settings.Categories, $"&season={searchCriteria.SeasonNumber}"); + } + + if (searchCriteria.SearchMode.HasFlag(SearchMode.SearchTitle)) + { + AddNameRequests(pageableRequests, searchCriteria, "search-torrents", Settings.Categories, $"&season={searchCriteria.SeasonNumber}"); + } pageableRequests.AddTier(); - AddNameRequests(pageableRequests, searchCriteria, "search-torrents", Settings.Categories, $"&season={searchCriteria.SeasonNumber}"); + if (searchCriteria.SearchMode == SearchMode.Default) + { + AddNameRequests(pageableRequests, searchCriteria, "search-torrents", Settings.Categories, $"&season={searchCriteria.SeasonNumber}"); + } return pageableRequests; } diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs index 0f2ac3877..20e442e18 100644 --- a/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs +++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabRequestGenerator.cs @@ -143,15 +143,31 @@ namespace NzbDrone.Core.Indexers.Newznab { var pageableRequests = new IndexerPageableRequestChain(); - AddTvIdPageableRequests(pageableRequests, Settings.Categories, searchCriteria, - string.Format("&season={0}&ep={1}", - searchCriteria.SeasonNumber, - searchCriteria.EpisodeNumber)); + if (searchCriteria.SearchMode.HasFlag(SearchMode.SearchID) || searchCriteria.SearchMode == SearchMode.Default) + { + AddTvIdPageableRequests(pageableRequests, Settings.Categories, searchCriteria, + string.Format("&season={0}&ep={1}", + searchCriteria.SeasonNumber, + searchCriteria.EpisodeNumber)); + } + + if (searchCriteria.SearchMode.HasFlag(SearchMode.SearchTitle)) + { + AddTitlePageableRequests(pageableRequests, Settings.Categories, searchCriteria, + string.Format("&season={0}&ep={1}", + searchCriteria.SeasonNumber, + searchCriteria.EpisodeNumber)); + } - AddSceneTitlePageableRequests(pageableRequests, Settings.Categories, searchCriteria, - m => string.Format("&season={0}&ep={1}", - m.SceneSeasonNumber, - searchCriteria.EpisodeNumber)); + pageableRequests.AddTier(); + + if (searchCriteria.SearchMode == SearchMode.Default) + { + AddTitlePageableRequests(pageableRequests, Settings.Categories, searchCriteria, + string.Format("&season={0}&ep={1}", + searchCriteria.SeasonNumber, + searchCriteria.EpisodeNumber)); + } return pageableRequests; } @@ -160,13 +176,28 @@ namespace NzbDrone.Core.Indexers.Newznab { var pageableRequests = new IndexerPageableRequestChain(); - AddTvIdPageableRequests(pageableRequests, Settings.Categories, searchCriteria, - string.Format("&season={0}", - searchCriteria.SeasonNumber)); + if (searchCriteria.SearchMode.HasFlag(SearchMode.SearchID) || searchCriteria.SearchMode == SearchMode.Default) + { + AddTvIdPageableRequests(pageableRequests, Settings.Categories, searchCriteria, + string.Format("&season={0}", + searchCriteria.SeasonNumber)); + } - AddSceneTitlePageableRequests(pageableRequests, Settings.Categories, searchCriteria, - m => string.Format("&season={0}", - m.SceneSeasonNumber)); + if (searchCriteria.SearchMode.HasFlag(SearchMode.SearchTitle)) + { + AddTitlePageableRequests(pageableRequests, Settings.Categories, searchCriteria, + string.Format("&season={0}", + searchCriteria.SeasonNumber)); + } + + pageableRequests.AddTier(); + + if (searchCriteria.SearchMode == SearchMode.Default) + { + AddTitlePageableRequests(pageableRequests, Settings.Categories, searchCriteria, + string.Format("&season={0}", + searchCriteria.SeasonNumber)); + } return pageableRequests; } @@ -287,11 +318,12 @@ namespace NzbDrone.Core.Indexers.Newznab string.Format("&tvmazeid={0}{1}", searchCriteria.Series.TvMazeId, parameters))); } } + } - + private void AddTitlePageableRequests(IndexerPageableRequestChain chain, IEnumerable categories, SearchCriteriaBase searchCriteria, string parameters) + { if (SupportsTvTitleSearch) { - chain.AddTier(); foreach (var searchTerm in searchCriteria.SceneTitles) { chain.Add(GetPagedRequests(MaxPages, Settings.Categories, "tvsearch", @@ -302,7 +334,6 @@ namespace NzbDrone.Core.Indexers.Newznab } else if (SupportsTvSearch) { - chain.AddTier(); foreach (var queryTitle in searchCriteria.QueryTitles) { chain.Add(GetPagedRequests(MaxPages, Settings.Categories, "tvsearch", @@ -313,35 +344,6 @@ namespace NzbDrone.Core.Indexers.Newznab } } - private void AddSceneTitlePageableRequests(IndexerPageableRequestChain chain, IEnumerable categories, SearchCriteriaBase searchCriteria, Func parametersFunc) - { - if (searchCriteria.SceneMappings != null) - { - foreach (var sceneMappingGroup in searchCriteria.SceneMappings.GroupBy(v => v.SceneSeasonNumber)) - { - var parameters = parametersFunc(sceneMappingGroup.First()); - - foreach (var searchTerm in sceneMappingGroup.Select(v => v.SearchTerm).Distinct()) - { - if (SupportsTvTitleSearch) - { - chain.AddToTier(0, GetPagedRequests(MaxPages, Settings.Categories, "tvsearch", - string.Format("&title={0}{1}", - Uri.EscapeDataString(searchTerm), - parameters))); - } - else if (SupportsTvSearch) - { - chain.AddToTier(0, GetPagedRequests(MaxPages, Settings.Categories, "tvsearch", - string.Format("&q={0}{1}", - NewsnabifyTitle(searchTerm), - parameters))); - } - } - } - } - } - private IEnumerable GetPagedRequests(int maxPages, IEnumerable categories, string searchType, string parameters) { if (categories.Empty()) diff --git a/src/NzbDrone.Core/Parser/Model/RemoteEpisode.cs b/src/NzbDrone.Core/Parser/Model/RemoteEpisode.cs index 5cd305016..6d4b601a9 100644 --- a/src/NzbDrone.Core/Parser/Model/RemoteEpisode.cs +++ b/src/NzbDrone.Core/Parser/Model/RemoteEpisode.cs @@ -10,6 +10,8 @@ namespace NzbDrone.Core.Parser.Model { public ReleaseInfo Release { get; set; } public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; } + public int MappedSeasonNumber { get; set; } + public Series Series { get; set; } public List Episodes { get; set; } public bool DownloadAllowed { get; set; } diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs index ffdfd7f51..d03a012bc 100644 --- a/src/NzbDrone.Core/Parser/ParsingService.cs +++ b/src/NzbDrone.Core/Parser/ParsingService.cs @@ -15,6 +15,7 @@ namespace NzbDrone.Core.Parser { Series GetSeries(string title); RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null); + RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Series series); RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable episodeIds); List GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null); ParsedEpisodeInfo ParseSpecialEpisodeTitle(ParsedEpisodeInfo parsedEpisodeInfo, string releaseTitle, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null); @@ -116,25 +117,66 @@ namespace NzbDrone.Core.Parser public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null) { + return Map(parsedEpisodeInfo, tvdbId, tvRageId, null, searchCriteria); + } + + public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Series series) + { + return Map(parsedEpisodeInfo, 0, 0, series, null); + } + + public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable episodeIds) + { + return new RemoteEpisode + { + ParsedEpisodeInfo = parsedEpisodeInfo, + Series = _seriesService.GetSeries(seriesId), + Episodes = _episodeService.GetEpisodes(episodeIds) + }; + } + + private RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, Series series, SearchCriteriaBase searchCriteria) + { + var sceneMapping = _sceneMappingService.FindSceneMapping(parsedEpisodeInfo.SeriesTitle, parsedEpisodeInfo.ReleaseTitle); + var remoteEpisode = new RemoteEpisode + { + ParsedEpisodeInfo = parsedEpisodeInfo, + MappedSeasonNumber = parsedEpisodeInfo.SeasonNumber + }; + + // For now we just detect tvdb vs scene, but we can do multiple 'origins' in the future. + var sceneSource = true; + if (sceneMapping != null) + { + if (sceneMapping.SeasonNumber.HasValue && sceneMapping.SeasonNumber.Value >= 0 && + sceneMapping.SceneSeasonNumber <= parsedEpisodeInfo.SeasonNumber) { - ParsedEpisodeInfo = parsedEpisodeInfo, - }; + remoteEpisode.MappedSeasonNumber += sceneMapping.SeasonNumber.Value - sceneMapping.SceneSeasonNumber.Value; + } - var series = GetSeries(parsedEpisodeInfo, tvdbId, tvRageId, searchCriteria); + if (sceneMapping.SceneOrigin == "tvdb") + { + sceneSource = false; + } + } if (series == null) { - return remoteEpisode; + series = GetSeries(parsedEpisodeInfo, tvdbId, tvRageId, sceneMapping, searchCriteria); } - remoteEpisode.Series = series; - - if (ValidateParsedEpisodeInfo.ValidateForSeriesType(parsedEpisodeInfo, series)) + if (series != null) { - remoteEpisode.Episodes = GetEpisodes(parsedEpisodeInfo, series, true, searchCriteria); + remoteEpisode.Series = series; + + if (ValidateParsedEpisodeInfo.ValidateForSeriesType(parsedEpisodeInfo, series)) + { + remoteEpisode.Episodes = GetEpisodes(parsedEpisodeInfo, series, remoteEpisode.MappedSeasonNumber, sceneSource, searchCriteria); + } } - else + + if (remoteEpisode.Episodes == null) { remoteEpisode.Episodes = new List(); } @@ -142,21 +184,23 @@ namespace NzbDrone.Core.Parser return remoteEpisode; } - public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable episodeIds) + public List GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null) { - return new RemoteEpisode - { - ParsedEpisodeInfo = parsedEpisodeInfo, - Series = _seriesService.GetSeries(seriesId), - Episodes = _episodeService.GetEpisodes(episodeIds) - }; + if (sceneSource) + { + var remoteEpisode = Map(parsedEpisodeInfo, 0, 0, series, searchCriteria); + + return remoteEpisode.Episodes; + } + + return GetEpisodes(parsedEpisodeInfo, series, parsedEpisodeInfo.SeasonNumber, sceneSource, searchCriteria); } - public List GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null) + private List GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, int mappedSeasonNumber, bool sceneSource, SearchCriteriaBase searchCriteria) { if (parsedEpisodeInfo.FullSeason) { - return _episodeService.GetEpisodesBySeason(series.Id, parsedEpisodeInfo.SeasonNumber); + return _episodeService.GetEpisodesBySeason(series.Id, mappedSeasonNumber); } if (parsedEpisodeInfo.IsDaily) @@ -173,10 +217,10 @@ namespace NzbDrone.Core.Parser if (parsedEpisodeInfo.IsAbsoluteNumbering) { - return GetAnimeEpisodes(series, parsedEpisodeInfo, sceneSource); + return GetAnimeEpisodes(series, parsedEpisodeInfo, mappedSeasonNumber, sceneSource, searchCriteria); } - return GetStandardEpisodes(series, parsedEpisodeInfo, sceneSource, searchCriteria); + return GetStandardEpisodes(series, parsedEpisodeInfo, mappedSeasonNumber, sceneSource, searchCriteria); } public ParsedEpisodeInfo ParseSpecialEpisodeTitle(ParsedEpisodeInfo parsedEpisodeInfo, string releaseTitle, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null) @@ -261,19 +305,18 @@ namespace NzbDrone.Core.Parser return null; } - private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria) + private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SceneMapping sceneMapping, SearchCriteriaBase searchCriteria) { Series series = null; - var sceneMappingTvdbId = _sceneMappingService.FindTvdbId(parsedEpisodeInfo.SeriesTitle, parsedEpisodeInfo.ReleaseTitle); - if (sceneMappingTvdbId.HasValue) + if (sceneMapping != null) { - if (searchCriteria != null && searchCriteria.Series.TvdbId == sceneMappingTvdbId.Value) + if (searchCriteria != null && searchCriteria.Series.TvdbId == sceneMapping.TvdbId) { return searchCriteria.Series; } - series = _seriesService.FindByTvdbId(sceneMappingTvdbId.Value); + series = _seriesService.FindByTvdbId(sceneMapping.TvdbId); if (series == null) { @@ -385,7 +428,7 @@ namespace NzbDrone.Core.Parser return episodeInfo; } - private List GetAnimeEpisodes(Series series, ParsedEpisodeInfo parsedEpisodeInfo, bool sceneSource) + private List GetAnimeEpisodes(Series series, ParsedEpisodeInfo parsedEpisodeInfo, int seasonNumber, bool sceneSource, SearchCriteriaBase searchCriteria) { var result = new List(); @@ -448,17 +491,9 @@ namespace NzbDrone.Core.Parser return result; } - private List GetStandardEpisodes(Series series, ParsedEpisodeInfo parsedEpisodeInfo, bool sceneSource, SearchCriteriaBase searchCriteria) + private List GetStandardEpisodes(Series series, ParsedEpisodeInfo parsedEpisodeInfo, int mappedSeasonNumber, bool sceneSource, SearchCriteriaBase searchCriteria) { var result = new List(); - var seasonNumber = parsedEpisodeInfo.SeasonNumber; - - if (sceneSource) - { - seasonNumber = _sceneMappingService.GetTvdbSeasonNumber(parsedEpisodeInfo.SeriesTitle, - parsedEpisodeInfo.ReleaseTitle, - parsedEpisodeInfo.SeasonNumber); - } if (parsedEpisodeInfo.EpisodeNumbers == null) { @@ -479,7 +514,7 @@ namespace NzbDrone.Core.Parser if (!episodes.Any()) { - episodes = _episodeService.FindEpisodesBySceneNumbering(series.Id, seasonNumber, episodeNumber); + episodes = _episodeService.FindEpisodesBySceneNumbering(series.Id, mappedSeasonNumber, episodeNumber); } if (episodes != null && episodes.Any()) @@ -499,12 +534,12 @@ namespace NzbDrone.Core.Parser if (searchCriteria != null) { - episodeInfo = searchCriteria.Episodes.SingleOrDefault(e => e.SeasonNumber == seasonNumber && e.EpisodeNumber == episodeNumber); + episodeInfo = searchCriteria.Episodes.SingleOrDefault(e => e.SeasonNumber == mappedSeasonNumber && e.EpisodeNumber == episodeNumber); } if (episodeInfo == null) { - episodeInfo = _episodeService.FindEpisode(series.Id, seasonNumber, episodeNumber); + episodeInfo = _episodeService.FindEpisode(series.Id, mappedSeasonNumber, episodeNumber); } if (episodeInfo != null) diff --git a/src/Sonarr.Api.V3/Series/AlternateTitleResource.cs b/src/Sonarr.Api.V3/Series/AlternateTitleResource.cs index 67bd5819d..90648fe95 100644 --- a/src/Sonarr.Api.V3/Series/AlternateTitleResource.cs +++ b/src/Sonarr.Api.V3/Series/AlternateTitleResource.cs @@ -5,5 +5,7 @@ public string Title { get; set; } public int? SeasonNumber { get; set; } public int? SceneSeasonNumber { get; set; } + public string SceneOrigin { get; set; } + public string Comment { get; set; } } } diff --git a/src/Sonarr.Api.V3/Series/SeriesModule.cs b/src/Sonarr.Api.V3/Series/SeriesModule.cs index c4e8dc431..7ee24a789 100644 --- a/src/Sonarr.Api.V3/Series/SeriesModule.cs +++ b/src/Sonarr.Api.V3/Series/SeriesModule.cs @@ -240,7 +240,13 @@ namespace Sonarr.Api.V3.Series if (mappings == null) return; - resource.AlternateTitles = mappings.Select(v => new AlternateTitleResource { Title = v.Title, SeasonNumber = v.SeasonNumber, SceneSeasonNumber = v.SceneSeasonNumber }).ToList(); + resource.AlternateTitles = mappings.ConvertAll(v => new AlternateTitleResource { + Title = v.Title, + SeasonNumber = v.SeasonNumber, + SceneSeasonNumber = v.SceneSeasonNumber, + SceneOrigin = v.SceneOrigin, + Comment = v.Comment + }); } private void LinkRootFolderPath(SeriesResource resource)