mirror of
https://github.com/Sonarr/Sonarr.git
synced 2024-11-26 04:33:01 +01:00
New: Support in services for multiple scene naming/numbering exceptions
This commit is contained in:
parent
ed2bb0d73a
commit
772448b41b
@ -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={
|
||||
<SceneInfo
|
||||
seasonNumber={seasonNumber}
|
||||
episodeNumber={episodeNumber}
|
||||
sceneSeasonNumber={sceneSeasonNumber}
|
||||
sceneEpisodeNumber={sceneEpisodeNumber}
|
||||
sceneAbsoluteEpisodeNumber={sceneAbsoluteEpisodeNumber}
|
||||
|
@ -15,3 +15,8 @@
|
||||
|
||||
margin-left: 100px;
|
||||
}
|
||||
|
||||
.comment {
|
||||
color: $darkGray;
|
||||
font-size: $smallFontSize;
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ import styles from './SceneInfo.css';
|
||||
|
||||
function SceneInfo(props) {
|
||||
const {
|
||||
seasonNumber,
|
||||
episodeNumber,
|
||||
sceneSeasonNumber,
|
||||
sceneEpisodeNumber,
|
||||
sceneAbsoluteEpisodeNumber,
|
||||
@ -56,14 +58,33 @@ function SceneInfo(props) {
|
||||
<div>
|
||||
{
|
||||
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 (
|
||||
<div
|
||||
key={alternateTitle.title}
|
||||
>
|
||||
{alternateTitle.title}
|
||||
{
|
||||
alternateTitle.sceneSeasonNumber !== -1 &&
|
||||
<span> (S{padNumber(alternateTitle.sceneSeasonNumber, 2)})</span>
|
||||
suffix &&
|
||||
<span> ({suffix})</span>
|
||||
}
|
||||
{
|
||||
alternateTitle.comment &&
|
||||
<span className={styles.comment}> {alternateTitle.comment}</span>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
@ -78,6 +99,8 @@ function SceneInfo(props) {
|
||||
}
|
||||
|
||||
SceneInfo.propTypes = {
|
||||
seasonNumber: PropTypes.number,
|
||||
episodeNumber: PropTypes.number,
|
||||
sceneSeasonNumber: PropTypes.number,
|
||||
sceneEpisodeNumber: PropTypes.number,
|
||||
sceneAbsoluteEpisodeNumber: PropTypes.number,
|
||||
|
@ -1,3 +1,8 @@
|
||||
.alternateTitle {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.comment {
|
||||
color: $darkGray;
|
||||
font-size: $smallFontSize;
|
||||
}
|
||||
|
@ -9,10 +9,14 @@ function SeriesAlternateTitles({ alternateTitles }) {
|
||||
alternateTitles.map((alternateTitle) => {
|
||||
return (
|
||||
<li
|
||||
key={alternateTitle}
|
||||
key={alternateTitle.title}
|
||||
className={styles.alternateTitle}
|
||||
>
|
||||
{alternateTitle}
|
||||
{alternateTitle.title}
|
||||
{
|
||||
alternateTitle.comment &&
|
||||
<span className={styles.comment}> {alternateTitle.comment}</span>
|
||||
}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
|
@ -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;
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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<ISceneMappingService>()
|
||||
.Setup(v => v.GetTvdbSeasonNumber(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()))
|
||||
.Returns<string, string, int>((s, r, i) => i);
|
||||
}
|
||||
|
||||
private void GivenMapping(int sceneSeasonNumber, int seasonNumber)
|
||||
{
|
||||
Mocker.GetMock<ISceneMappingService>()
|
||||
.Setup(v => v.GetTvdbSeasonNumber(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()))
|
||||
.Returns<string, string, int>((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();
|
||||
}
|
||||
|
@ -85,6 +85,10 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
.Setup(s => s.GetSeries(It.IsAny<IEnumerable<int>>()))
|
||||
.Returns(new List<Series> { _series });
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Series>()))
|
||||
.Returns(new RemoteEpisode { Episodes = new List<Episode> { _episode } });
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.GetEpisodes(It.IsAny<ParsedEpisodeInfo>(), _series, true, null))
|
||||
.Returns(new List<Episode> {_episode});
|
||||
|
@ -42,6 +42,10 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
.Setup(s => s.GetSeries(It.IsAny<IEnumerable<int>>()))
|
||||
.Returns(new List<Series> { new Series() });
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Series>()))
|
||||
.Returns(new RemoteEpisode { Episodes = new List<Episode> { _episode } });
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.GetEpisodes(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Series>(), It.IsAny<bool>(), null))
|
||||
.Returns(new List<Episode>{ _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 });
|
||||
|
@ -77,6 +77,10 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||
.Setup(s => s.GetSeries(It.IsAny<IEnumerable<int>>()))
|
||||
.Returns(new List<Series> { _series });
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.Map(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Series>()))
|
||||
.Returns(new RemoteEpisode { Episodes = new List<Episode> { _episode } });
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
.Setup(s => s.GetEpisodes(It.IsAny<ParsedEpisodeInfo>(), _series, true, null))
|
||||
.Returns(new List<Episode> {_episode});
|
||||
|
@ -44,7 +44,8 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
|
||||
{
|
||||
SeriesTitle = "TV Series",
|
||||
SeasonNumber = 1
|
||||
}
|
||||
},
|
||||
MappedSeasonNumber = 1
|
||||
};
|
||||
|
||||
Mocker.GetMock<IParsingService>()
|
||||
@ -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<IHistoryService>()
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,8 +53,8 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
.Returns<int, int>((i, j) => _xemEpisodes.Where(d => d.SeasonNumber == j).ToList());
|
||||
|
||||
Mocker.GetMock<ISceneMappingService>()
|
||||
.Setup(s => s.GetSceneNames(It.IsAny<int>(), It.IsAny<List<int>>(), It.IsAny<List<int>>()))
|
||||
.Returns(new List<string>());
|
||||
.Setup(s => s.FindByTvdbId(It.IsAny<int>()))
|
||||
.Returns(new List<SceneMapping>());
|
||||
}
|
||||
|
||||
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<AnimeEpisodeSearchCriteria>().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<DailySeasonSearchCriteria>().ToList();
|
||||
var criteria2 = allCriteria.OfType<DailyEpisodeSearchCriteria>().ToList();
|
||||
@ -373,7 +373,11 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
Subject.SeasonSearch(_xemSeries.Id, 7, false, false, true, false);
|
||||
|
||||
Mocker.GetMock<ISceneMappingService>()
|
||||
.Verify(v => v.GetSceneNames(_xemSeries.Id, It.Is<List<int>>(l => l.Contains(7)), It.Is<List<int>>(l => l.Contains(7))), Times.Once());
|
||||
.Verify(v => v.FindByTvdbId(_xemSeries.Id), Times.Once());
|
||||
|
||||
allCriteria.Should().HaveCount(1);
|
||||
allCriteria.First().Should().BeOfType<SeasonSearchCriteria>();
|
||||
allCriteria.First().As<SeasonSearchCriteria>().SeasonNumber.Should().Be(7);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,10 +55,6 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||
Mocker.GetMock<ISeriesService>()
|
||||
.Setup(s => s.FindByTitle(It.IsAny<string>()))
|
||||
.Returns(_series);
|
||||
|
||||
Mocker.GetMock<ISceneMappingService>()
|
||||
.Setup(v => v.GetTvdbSeasonNumber(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()))
|
||||
.Returns<string, string, int>((s, r, i) => i);
|
||||
}
|
||||
|
||||
private void GivenDailySeries()
|
||||
@ -328,8 +324,8 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||
const int tvdbSeasonNumber = 5;
|
||||
|
||||
Mocker.GetMock<ISceneMappingService>()
|
||||
.Setup(v => v.GetTvdbSeasonNumber(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()))
|
||||
.Returns<string, string, int>((s, r, i) => tvdbSeasonNumber);
|
||||
.Setup(v => v.FindSceneMapping(It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Returns<string, string>((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<ISceneMappingService>()
|
||||
.Setup(s => s.FindSceneMapping(_parsedEpisodeInfo.SeriesTitle, It.IsAny<string>()))
|
||||
.Returns(new SceneMapping { SeasonNumber = tvdbSeasonNumber, SceneSeasonNumber = _parsedEpisodeInfo.SeasonNumber + 100 });
|
||||
.Setup(v => v.FindSceneMapping(It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Returns<string, string>((s, r) => new SceneMapping { SceneSeasonNumber = 101, SeasonNumber = tvdbSeasonNumber });
|
||||
|
||||
Subject.GetEpisodes(_parsedEpisodeInfo, _series, true, null);
|
||||
|
||||
|
@ -51,10 +51,6 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||
SeasonNumber = _episodes.First().SeasonNumber,
|
||||
Episodes = _episodes
|
||||
};
|
||||
|
||||
Mocker.GetMock<ISceneMappingService>()
|
||||
.Setup(v => v.GetTvdbSeasonNumber(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()))
|
||||
.Returns<string, string, int>((s, r, i) => i);
|
||||
}
|
||||
|
||||
private void GivenMatchBySeriesTitle()
|
||||
@ -122,8 +118,8 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||
GivenMatchByTvRageId();
|
||||
|
||||
Mocker.GetMock<ISceneMappingService>()
|
||||
.Setup(v => v.FindTvdbId(It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Returns(10);
|
||||
.Setup(v => v.FindSceneMapping(It.IsAny<string>(), It.IsAny<string>()))
|
||||
.Returns(new SceneMapping { TvdbId = 10 });
|
||||
|
||||
var result = Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
|
||||
|
||||
|
@ -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; }
|
||||
|
@ -15,14 +15,10 @@ namespace NzbDrone.Core.DataAugmentation.Scene
|
||||
public interface ISceneMappingService
|
||||
{
|
||||
List<string> GetSceneNames(int tvdbId, List<int> seasonNumbers, List<int> sceneSeasonNumbers);
|
||||
List<SceneMapping> GetSceneMappings(int tvdbId, List<int> seasonNumbers);
|
||||
int? FindTvdbId(string sceneTitle, string releaseTitle);
|
||||
List<SceneMapping> 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<string>();
|
||||
}
|
||||
|
||||
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<SceneMapping> GetSceneMappings(int tvdbId, List<int> seasonNumbers)
|
||||
{
|
||||
var mappings = FindByTvdbId(tvdbId);
|
||||
|
||||
if (mappings == null)
|
||||
{
|
||||
return new List<SceneMapping>();
|
||||
}
|
||||
|
||||
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<string> FilterNonEnglish(List<string> titles)
|
||||
{
|
||||
return titles.Where(IsEnglish).ToList();
|
||||
}
|
||||
|
||||
private bool IsEnglish(string title)
|
||||
{
|
||||
return title.All(c => c <= 255);
|
||||
|
12
src/NzbDrone.Core/DataAugmentation/Scene/SearchMode.cs
Normal file
12
src/NzbDrone.Core/DataAugmentation/Scene/SearchMode.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.DataAugmentation.Scene
|
||||
{
|
||||
[Flags]
|
||||
public enum SearchMode
|
||||
{
|
||||
Default = 0,
|
||||
SearchID = 1,
|
||||
SearchTitle = 2
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
|
@ -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");
|
||||
|
@ -291,33 +291,33 @@ 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<Episode> episodes;
|
||||
|
||||
RemoteEpisode knownRemoteEpisode;
|
||||
if (knownRemoteEpisodes != null && knownRemoteEpisodes.TryGetValue(release.Release.Title, out knownRemoteEpisode))
|
||||
{
|
||||
episodes = knownRemoteEpisode.Episodes;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ValidateParsedEpisodeInfo.ValidateForSeriesType(release.ParsedEpisodeInfo, series))
|
||||
{
|
||||
episodes = _parsingService.GetEpisodes(release.ParsedEpisodeInfo, series, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
episodes = new List<Episode>();
|
||||
}
|
||||
}
|
||||
|
||||
release.RemoteEpisode = new RemoteEpisode
|
||||
{
|
||||
Series = series,
|
||||
Episodes = episodes,
|
||||
ParsedEpisodeInfo = release.ParsedEpisodeInfo,
|
||||
Release = release.Release
|
||||
};
|
||||
|
||||
RemoteEpisode knownRemoteEpisode;
|
||||
if (knownRemoteEpisodes != null && knownRemoteEpisodes.TryGetValue(release.Release.Title, out knownRemoteEpisode))
|
||||
{
|
||||
release.RemoteEpisode.MappedSeasonNumber = knownRemoteEpisode.MappedSeasonNumber;
|
||||
release.RemoteEpisode.Episodes = knownRemoteEpisode.Episodes;
|
||||
}
|
||||
else if (ValidateParsedEpisodeInfo.ValidateForSeriesType(release.ParsedEpisodeInfo, series))
|
||||
{
|
||||
var remoteEpisode = _parsingService.Map(release.ParsedEpisodeInfo, series);
|
||||
|
||||
release.RemoteEpisode.MappedSeasonNumber = remoteEpisode.MappedSeasonNumber;
|
||||
release.RemoteEpisode.Episodes = remoteEpisode.Episodes;
|
||||
}
|
||||
else
|
||||
{
|
||||
release.RemoteEpisode.MappedSeasonNumber = release.ParsedEpisodeInfo.SeasonNumber;
|
||||
release.RemoteEpisode.Episodes = new List<Episode>();
|
||||
}
|
||||
|
||||
result.Add(release);
|
||||
}
|
||||
|
||||
|
@ -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<string> 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Episode> Episodes { get; set; }
|
||||
public SceneEpisodeMapping EpisodeMapping { get; set; }
|
||||
public SearchMode SearchMode { get; set; }
|
||||
public List<string> SceneTitles { get; set; }
|
||||
public int SeasonNumber { get; set; }
|
||||
}
|
||||
}
|
@ -16,8 +16,8 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
|
||||
public Series Series { get; set; }
|
||||
public List<string> SceneTitles { get; set; }
|
||||
public List<SceneMapping> SceneMappings { get; set; }
|
||||
public List<Episode> 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; }
|
||||
|
@ -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> { episode }, userInvokedSearch, interactiveSearch);
|
||||
return SearchSpecial(series, new List<Episode> { 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> { episode }, userInvokedSearch, interactiveSearch);
|
||||
return SearchSpecial(series, new List<Episode> { episode }, false, userInvokedSearch, interactiveSearch);
|
||||
}
|
||||
|
||||
return SearchSingle(series, episode, userInvokedSearch, interactiveSearch);
|
||||
return SearchSingle(series, episode, false, userInvokedSearch, interactiveSearch);
|
||||
}
|
||||
|
||||
public List<DownloadDecision> SeasonSearch(int seriesId, int seasonNumber, bool missingOnly, bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch)
|
||||
@ -104,77 +104,195 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
|
||||
return SeasonSearch(seriesId, seasonNumber, episodes, monitoredOnly, userInvokedSearch, interactiveSearch);
|
||||
}
|
||||
|
||||
public List<DownloadDecision> SeasonSearch(int seriesId, int seasonNumber, List<Episode> 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<DownloadDecision>();
|
||||
|
||||
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 searchSpec = Get<SingleEpisodeSearchCriteria>(series, mapping, monitoredOnly, userInvokedSearch, interactiveSearch);
|
||||
searchSpec.SeasonNumber = mapping.SeasonNumber;
|
||||
searchSpec.EpisodeNumber = mapping.EpisodeMapping.EpisodeNumber;
|
||||
|
||||
var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
||||
downloadDecisions.AddRange(decisions);
|
||||
}
|
||||
else
|
||||
{
|
||||
var searchSpec = Get<SeasonSearchCriteria>(series, mapping, monitoredOnly, userInvokedSearch, interactiveSearch);
|
||||
searchSpec.SeasonNumber = mapping.SeasonNumber;
|
||||
|
||||
var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
||||
downloadDecisions.AddRange(decisions);
|
||||
}
|
||||
}
|
||||
|
||||
return downloadDecisions;
|
||||
}
|
||||
|
||||
private List<SceneSeasonMapping> GetSceneSeasonMappings(Series series, List<Episode> episodes)
|
||||
{
|
||||
var dict = new Dictionary<SceneSeasonMapping, SceneSeasonMapping>();
|
||||
|
||||
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
|
||||
{
|
||||
var episode = sceneSeasonEpisodes.First();
|
||||
var searchSpec = Get<SingleEpisodeSearchCriteria>(series, sceneSeasonEpisodes.ToList(), userInvokedSearch, interactiveSearch);
|
||||
Episodes = groupedEpisode.ToList(),
|
||||
EpisodeMapping = episodeMapping,
|
||||
SceneTitles = episodeMapping.SceneTitles,
|
||||
SearchMode = episodeMapping.SearchMode,
|
||||
SeasonNumber = episodeMapping.SeasonNumber
|
||||
};
|
||||
|
||||
searchSpec.SeasonNumber = sceneSeasonEpisodes.Key;
|
||||
searchSpec.MonitoredEpisodesOnly = monitoredOnly;
|
||||
|
||||
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);
|
||||
if (dict.TryGetValue(seasonMapping, out var existing))
|
||||
{
|
||||
existing.Episodes.AddRange(seasonMapping.Episodes);
|
||||
existing.SceneTitles.AddRange(seasonMapping.SceneTitles);
|
||||
}
|
||||
else
|
||||
{
|
||||
var searchSpec = Get<SeasonSearchCriteria>(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<SeasonSearchCriteria>(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();
|
||||
}
|
||||
|
||||
return dict.Values.ToList();
|
||||
}
|
||||
|
||||
private List<SceneEpisodeMapping> GetSceneEpisodeMappings(Series series, Episode episode)
|
||||
{
|
||||
var dict = new Dictionary<SceneEpisodeMapping, SceneEpisodeMapping>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in dict)
|
||||
{
|
||||
item.Value.SceneTitles = item.Value.SceneTitles.Distinct().ToList();
|
||||
}
|
||||
|
||||
return dict.Values.ToList();
|
||||
}
|
||||
|
||||
private IEnumerable<SceneEpisodeMapping> GetSceneEpisodeMappings(Series series, Episode episode, List<SceneMapping> sceneMappings)
|
||||
{
|
||||
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<string> { 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<string> { 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 (includeGlobal)
|
||||
{
|
||||
yield return new SceneEpisodeMapping
|
||||
{
|
||||
Episode = episode,
|
||||
SearchMode = SearchMode.Default,
|
||||
SceneTitles = new List<string> { series.Title },
|
||||
SeasonNumber = episode.SceneSeasonNumber ?? episode.SeasonNumber,
|
||||
EpisodeNumber = episode.SceneEpisodeNumber ?? episode.EpisodeNumber,
|
||||
AbsoluteEpisodeNumber = episode.SceneSeasonNumber ?? episode.AbsoluteEpisodeNumber
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private List<DownloadDecision> SearchSingle(Series series, Episode episode, bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch)
|
||||
{
|
||||
var mappings = GetSceneEpisodeMappings(series, episode);
|
||||
|
||||
var downloadDecisions = new List<DownloadDecision>();
|
||||
|
||||
foreach (var mapping in mappings)
|
||||
{
|
||||
var searchSpec = Get<SingleEpisodeSearchCriteria>(series, mapping, monitoredOnly, userInvokedSearch, interactiveSearch);
|
||||
searchSpec.SeasonNumber = mapping.SeasonNumber;
|
||||
searchSpec.EpisodeNumber = mapping.EpisodeNumber;
|
||||
|
||||
var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
||||
downloadDecisions.AddRange(decisions);
|
||||
@ -183,36 +301,18 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
return downloadDecisions;
|
||||
}
|
||||
|
||||
private List<DownloadDecision> SearchSingle(Series series, Episode episode, bool userInvokedSearch, bool interactiveSearch)
|
||||
{
|
||||
var searchSpec = Get<SingleEpisodeSearchCriteria>(series, new List<Episode> { episode }, userInvokedSearch, interactiveSearch);
|
||||
|
||||
if (series.UseSceneNumbering && episode.SceneSeasonNumber.HasValue && episode.SceneEpisodeNumber.HasValue)
|
||||
{
|
||||
searchSpec.EpisodeNumber = episode.SceneEpisodeNumber.Value;
|
||||
searchSpec.SeasonNumber = episode.SceneSeasonNumber.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
searchSpec.EpisodeNumber = episode.EpisodeNumber;
|
||||
searchSpec.SeasonNumber = episode.SeasonNumber;
|
||||
}
|
||||
|
||||
return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
||||
}
|
||||
|
||||
private List<DownloadDecision> SearchDaily(Series series, Episode episode, bool userInvokedSearch, bool interactiveSearch)
|
||||
private List<DownloadDecision> 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<DailyEpisodeSearchCriteria>(series, new List<Episode> { episode }, userInvokedSearch, interactiveSearch);
|
||||
var searchSpec = Get<DailyEpisodeSearchCriteria>(series, new List<Episode> { episode }, monitoredOnly, userInvokedSearch, interactiveSearch);
|
||||
searchSpec.AirDate = airDate;
|
||||
|
||||
return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
||||
}
|
||||
|
||||
private List<DownloadDecision> SearchAnime(Series series, Episode episode, bool userInvokedSearch, bool interactiveSearch, bool isSeasonSearch = false)
|
||||
private List<DownloadDecision> SearchAnime(Series series, Episode episode, bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch, bool isSeasonSearch = false)
|
||||
{
|
||||
var searchSpec = Get<AnimeEpisodeSearchCriteria>(series, new List<Episode> { episode }, userInvokedSearch, interactiveSearch);
|
||||
var searchSpec = Get<AnimeEpisodeSearchCriteria>(series, new List<Episode> { episode }, monitoredOnly, userInvokedSearch, interactiveSearch);
|
||||
|
||||
searchSpec.IsSeasonSearch = isSeasonSearch;
|
||||
|
||||
@ -233,11 +333,11 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
||||
}
|
||||
|
||||
private List<DownloadDecision> SearchSpecial(Series series, List<Episode> episodes, bool userInvokedSearch, bool interactiveSearch)
|
||||
private List<DownloadDecision> SearchSpecial(Series series, List<Episode> episodes,bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch)
|
||||
{
|
||||
var downloadDecisions = new List<DownloadDecision>();
|
||||
|
||||
var searchSpec = Get<SpecialEpisodeSearchCriteria>(series, episodes, userInvokedSearch, interactiveSearch);
|
||||
var searchSpec = Get<SpecialEpisodeSearchCriteria>(series, episodes, monitoredOnly, userInvokedSearch, interactiveSearch);
|
||||
// build list of queries for each episode in the form: "<series> <episode-title>"
|
||||
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<DownloadDecision> SearchAnimeSeason(Series series, List<Episode> episodes, bool userInvokedSearch, bool interactiveSearch)
|
||||
private List<DownloadDecision> SearchAnimeSeason(Series series, List<Episode> episodes, bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch)
|
||||
{
|
||||
var downloadDecisions = new List<DownloadDecision>();
|
||||
|
||||
|
||||
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<DownloadDecision> SearchDailySeason(Series series, List<Episode> episodes, bool userInvokedSearch, bool interactiveSearch)
|
||||
private List<DownloadDecision> SearchDailySeason(Series series, List<Episode> episodes, bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch)
|
||||
{
|
||||
var downloadDecisions = new List<DownloadDecision>();
|
||||
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<DailySeasonSearchCriteria>(series, yearEpisodes, userInvokedSearch, interactiveSearch);
|
||||
var searchSpec = Get<DailySeasonSearchCriteria>(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<TSpec>(Series series, List<Episode> episodes, bool userInvokedSearch, bool interactiveSearch) where TSpec : SearchCriteriaBase, new()
|
||||
private TSpec Get<TSpec>(Series series, List<Episode> 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());
|
||||
|
||||
|
||||
if (!spec.SceneTitles.Contains(series.Title))
|
||||
{
|
||||
spec.SceneTitles.Add(series.Title);
|
||||
}
|
||||
|
||||
|
||||
spec.Episodes = episodes;
|
||||
spec.MonitoredEpisodesOnly = monitoredOnly;
|
||||
spec.UserInvokedSearch = userInvokedSearch;
|
||||
spec.InteractiveSearch = interactiveSearch;
|
||||
|
||||
return spec;
|
||||
}
|
||||
|
||||
private TSpec Get<TSpec>(Series series, SceneEpisodeMapping 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 = new List<Episode> { mapping.Episode };
|
||||
spec.MonitoredEpisodesOnly = monitoredOnly;
|
||||
spec.UserInvokedSearch = userInvokedSearch;
|
||||
spec.InteractiveSearch = interactiveSearch;
|
||||
|
||||
return spec;
|
||||
}
|
||||
|
||||
private TSpec Get<TSpec>(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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
AddSceneTitlePageableRequests(pageableRequests, Settings.Categories, searchCriteria,
|
||||
m => string.Format("&season={0}&ep={1}",
|
||||
m.SceneSeasonNumber,
|
||||
searchCriteria.EpisodeNumber));
|
||||
if (searchCriteria.SearchMode.HasFlag(SearchMode.SearchTitle))
|
||||
{
|
||||
AddTitlePageableRequests(pageableRequests, Settings.Categories, searchCriteria,
|
||||
string.Format("&season={0}&ep={1}",
|
||||
searchCriteria.SeasonNumber,
|
||||
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<int> 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<int> categories, SearchCriteriaBase searchCriteria, Func<SceneMapping, string> 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<IndexerRequest> GetPagedRequests(int maxPages, IEnumerable<int> categories, string searchType, string parameters)
|
||||
{
|
||||
if (categories.Empty())
|
||||
|
@ -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<Episode> Episodes { get; set; }
|
||||
public bool DownloadAllowed { get; set; }
|
||||
|
@ -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<int> episodeIds);
|
||||
List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null);
|
||||
ParsedEpisodeInfo ParseSpecialEpisodeTitle(ParsedEpisodeInfo parsedEpisodeInfo, string releaseTitle, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
|
||||
@ -116,30 +117,12 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null)
|
||||
{
|
||||
var remoteEpisode = new RemoteEpisode
|
||||
{
|
||||
ParsedEpisodeInfo = parsedEpisodeInfo,
|
||||
};
|
||||
return Map(parsedEpisodeInfo, tvdbId, tvRageId, null, searchCriteria);
|
||||
}
|
||||
|
||||
var series = GetSeries(parsedEpisodeInfo, tvdbId, tvRageId, searchCriteria);
|
||||
|
||||
if (series == null)
|
||||
{
|
||||
return remoteEpisode;
|
||||
}
|
||||
|
||||
remoteEpisode.Series = series;
|
||||
|
||||
if (ValidateParsedEpisodeInfo.ValidateForSeriesType(parsedEpisodeInfo, series))
|
||||
{
|
||||
remoteEpisode.Episodes = GetEpisodes(parsedEpisodeInfo, series, true, searchCriteria);
|
||||
}
|
||||
else
|
||||
{
|
||||
remoteEpisode.Episodes = new List<Episode>();
|
||||
}
|
||||
|
||||
return remoteEpisode;
|
||||
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Series series)
|
||||
{
|
||||
return Map(parsedEpisodeInfo, 0, 0, series, null);
|
||||
}
|
||||
|
||||
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds)
|
||||
@ -152,11 +135,72 @@ 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 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)
|
||||
{
|
||||
remoteEpisode.MappedSeasonNumber += sceneMapping.SeasonNumber.Value - sceneMapping.SceneSeasonNumber.Value;
|
||||
}
|
||||
|
||||
if (sceneMapping.SceneOrigin == "tvdb")
|
||||
{
|
||||
sceneSource = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (series == null)
|
||||
{
|
||||
series = GetSeries(parsedEpisodeInfo, tvdbId, tvRageId, sceneMapping, searchCriteria);
|
||||
}
|
||||
|
||||
if (series != null)
|
||||
{
|
||||
remoteEpisode.Series = series;
|
||||
|
||||
if (ValidateParsedEpisodeInfo.ValidateForSeriesType(parsedEpisodeInfo, series))
|
||||
{
|
||||
remoteEpisode.Episodes = GetEpisodes(parsedEpisodeInfo, series, remoteEpisode.MappedSeasonNumber, sceneSource, searchCriteria);
|
||||
}
|
||||
}
|
||||
|
||||
if (remoteEpisode.Episodes == null)
|
||||
{
|
||||
remoteEpisode.Episodes = new List<Episode>();
|
||||
}
|
||||
|
||||
return remoteEpisode;
|
||||
}
|
||||
|
||||
public List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null)
|
||||
{
|
||||
if (sceneSource)
|
||||
{
|
||||
var remoteEpisode = Map(parsedEpisodeInfo, 0, 0, series, searchCriteria);
|
||||
|
||||
return remoteEpisode.Episodes;
|
||||
}
|
||||
|
||||
return GetEpisodes(parsedEpisodeInfo, series, parsedEpisodeInfo.SeasonNumber, sceneSource, searchCriteria);
|
||||
}
|
||||
|
||||
private List<Episode> 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<Episode> GetAnimeEpisodes(Series series, ParsedEpisodeInfo parsedEpisodeInfo, bool sceneSource)
|
||||
private List<Episode> GetAnimeEpisodes(Series series, ParsedEpisodeInfo parsedEpisodeInfo, int seasonNumber, bool sceneSource, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var result = new List<Episode>();
|
||||
|
||||
@ -448,17 +491,9 @@ namespace NzbDrone.Core.Parser
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Episode> GetStandardEpisodes(Series series, ParsedEpisodeInfo parsedEpisodeInfo, bool sceneSource, SearchCriteriaBase searchCriteria)
|
||||
private List<Episode> GetStandardEpisodes(Series series, ParsedEpisodeInfo parsedEpisodeInfo, int mappedSeasonNumber, bool sceneSource, SearchCriteriaBase searchCriteria)
|
||||
{
|
||||
var result = new List<Episode>();
|
||||
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)
|
||||
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user