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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (alternateTitle.sceneSeasonNumber === undefined && alternateTitle.sceneOrigin === 'tvdb') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return seasonNumber === alternateTitle.seasonNumber;
|
return seasonNumber === alternateTitle.seasonNumber;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -81,6 +85,8 @@ function EpisodeNumber(props) {
|
||||||
title="Scene Information"
|
title="Scene Information"
|
||||||
body={
|
body={
|
||||||
<SceneInfo
|
<SceneInfo
|
||||||
|
seasonNumber={seasonNumber}
|
||||||
|
episodeNumber={episodeNumber}
|
||||||
sceneSeasonNumber={sceneSeasonNumber}
|
sceneSeasonNumber={sceneSeasonNumber}
|
||||||
sceneEpisodeNumber={sceneEpisodeNumber}
|
sceneEpisodeNumber={sceneEpisodeNumber}
|
||||||
sceneAbsoluteEpisodeNumber={sceneAbsoluteEpisodeNumber}
|
sceneAbsoluteEpisodeNumber={sceneAbsoluteEpisodeNumber}
|
||||||
|
|
|
@ -15,3 +15,8 @@
|
||||||
|
|
||||||
margin-left: 100px;
|
margin-left: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comment {
|
||||||
|
color: $darkGray;
|
||||||
|
font-size: $smallFontSize;
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import styles from './SceneInfo.css';
|
||||||
|
|
||||||
function SceneInfo(props) {
|
function SceneInfo(props) {
|
||||||
const {
|
const {
|
||||||
|
seasonNumber,
|
||||||
|
episodeNumber,
|
||||||
sceneSeasonNumber,
|
sceneSeasonNumber,
|
||||||
sceneEpisodeNumber,
|
sceneEpisodeNumber,
|
||||||
sceneAbsoluteEpisodeNumber,
|
sceneAbsoluteEpisodeNumber,
|
||||||
|
@ -56,14 +58,33 @@ function SceneInfo(props) {
|
||||||
<div>
|
<div>
|
||||||
{
|
{
|
||||||
alternateTitles.map((alternateTitle) => {
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
key={alternateTitle.title}
|
key={alternateTitle.title}
|
||||||
>
|
>
|
||||||
{alternateTitle.title}
|
{alternateTitle.title}
|
||||||
{
|
{
|
||||||
alternateTitle.sceneSeasonNumber !== -1 &&
|
suffix &&
|
||||||
<span> (S{padNumber(alternateTitle.sceneSeasonNumber, 2)})</span>
|
<span> ({suffix})</span>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
alternateTitle.comment &&
|
||||||
|
<span className={styles.comment}> {alternateTitle.comment}</span>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -78,6 +99,8 @@ function SceneInfo(props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SceneInfo.propTypes = {
|
SceneInfo.propTypes = {
|
||||||
|
seasonNumber: PropTypes.number,
|
||||||
|
episodeNumber: PropTypes.number,
|
||||||
sceneSeasonNumber: PropTypes.number,
|
sceneSeasonNumber: PropTypes.number,
|
||||||
sceneEpisodeNumber: PropTypes.number,
|
sceneEpisodeNumber: PropTypes.number,
|
||||||
sceneAbsoluteEpisodeNumber: PropTypes.number,
|
sceneAbsoluteEpisodeNumber: PropTypes.number,
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
.alternateTitle {
|
.alternateTitle {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comment {
|
||||||
|
color: $darkGray;
|
||||||
|
font-size: $smallFontSize;
|
||||||
|
}
|
||||||
|
|
|
@ -9,10 +9,14 @@ function SeriesAlternateTitles({ alternateTitles }) {
|
||||||
alternateTitles.map((alternateTitle) => {
|
alternateTitles.map((alternateTitle) => {
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={alternateTitle}
|
key={alternateTitle.title}
|
||||||
className={styles.alternateTitle}
|
className={styles.alternateTitle}
|
||||||
>
|
>
|
||||||
{alternateTitle}
|
{alternateTitle.title}
|
||||||
|
{
|
||||||
|
alternateTitle.comment &&
|
||||||
|
<span className={styles.comment}> {alternateTitle.comment}</span>
|
||||||
|
}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|
|
@ -111,8 +111,9 @@ function createMapStateToProps() {
|
||||||
const isPopulated = isEpisodesPopulated && isEpisodeFilesPopulated;
|
const isPopulated = isEpisodesPopulated && isEpisodeFilesPopulated;
|
||||||
const alternateTitles = _.reduce(series.alternateTitles, (acc, alternateTitle) => {
|
const alternateTitles = _.reduce(series.alternateTitles, (acc, alternateTitle) => {
|
||||||
if ((alternateTitle.seasonNumber === -1 || alternateTitle.seasonNumber === undefined) &&
|
if ((alternateTitle.seasonNumber === -1 || alternateTitle.seasonNumber === undefined) &&
|
||||||
(alternateTitle.sceneSeasonNumber === -1 || alternateTitle.sceneSeasonNumber === undefined)) {
|
(alternateTitle.sceneSeasonNumber === -1 || alternateTitle.sceneSeasonNumber === undefined) &&
|
||||||
acc.push(alternateTitle.title);
|
(alternateTitle.title !== series.title)) {
|
||||||
|
acc.push(alternateTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
|
|
|
@ -5,5 +5,7 @@
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public int? SeasonNumber { get; set; }
|
public int? SeasonNumber { get; set; }
|
||||||
public int? SceneSeasonNumber { 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,
|
Title = v.Title,
|
||||||
SeasonNumber = v.SeasonNumber,
|
SeasonNumber = v.SeasonNumber,
|
||||||
SceneSeasonNumber = v.SceneSeasonNumber
|
SceneSeasonNumber = v.SceneSeasonNumber,
|
||||||
|
SceneOrigin = v.SceneOrigin,
|
||||||
|
Comment = v.Comment
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,26 +22,17 @@ namespace NzbDrone.Core.Test.DecisionEngineTests.Search.SingleEpisodeSearchMatch
|
||||||
_remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo();
|
_remoteEpisode.ParsedEpisodeInfo = new ParsedEpisodeInfo();
|
||||||
_remoteEpisode.ParsedEpisodeInfo.SeasonNumber = 5;
|
_remoteEpisode.ParsedEpisodeInfo.SeasonNumber = 5;
|
||||||
_remoteEpisode.ParsedEpisodeInfo.EpisodeNumbers = new[] { 1 };
|
_remoteEpisode.ParsedEpisodeInfo.EpisodeNumbers = new[] { 1 };
|
||||||
|
_remoteEpisode.MappedSeasonNumber = 5;
|
||||||
|
|
||||||
_searchCriteria.SeasonNumber = 5;
|
_searchCriteria.SeasonNumber = 5;
|
||||||
_searchCriteria.EpisodeNumber = 1;
|
_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]
|
[Test]
|
||||||
public void should_return_false_if_season_does_not_match()
|
public void should_return_false_if_season_does_not_match()
|
||||||
{
|
{
|
||||||
_remoteEpisode.ParsedEpisodeInfo.SeasonNumber = 10;
|
_remoteEpisode.ParsedEpisodeInfo.SeasonNumber = 10;
|
||||||
|
_remoteEpisode.MappedSeasonNumber = 10;
|
||||||
|
|
||||||
Subject.IsSatisfiedBy(_remoteEpisode, _searchCriteria).Accepted.Should().BeFalse();
|
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()
|
public void should_return_true_if_season_matches_after_scenemapping()
|
||||||
{
|
{
|
||||||
_remoteEpisode.ParsedEpisodeInfo.SeasonNumber = 10;
|
_remoteEpisode.ParsedEpisodeInfo.SeasonNumber = 10;
|
||||||
|
_remoteEpisode.MappedSeasonNumber = 5; // 10 -> 5 mapping
|
||||||
GivenMapping(10, 5);
|
|
||||||
|
|
||||||
Subject.IsSatisfiedBy(_remoteEpisode, _searchCriteria).Accepted.Should().BeTrue();
|
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()
|
public void should_return_false_if_season_does_not_match_after_scenemapping()
|
||||||
{
|
{
|
||||||
_remoteEpisode.ParsedEpisodeInfo.SeasonNumber = 10;
|
_remoteEpisode.ParsedEpisodeInfo.SeasonNumber = 10;
|
||||||
|
_remoteEpisode.MappedSeasonNumber = 6; // 9 -> 5 mapping
|
||||||
GivenMapping(9, 5);
|
|
||||||
|
|
||||||
Subject.IsSatisfiedBy(_remoteEpisode, _searchCriteria).Accepted.Should().BeFalse();
|
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>>()))
|
.Setup(s => s.GetSeries(It.IsAny<IEnumerable<int>>()))
|
||||||
.Returns(new List<Series> { _series });
|
.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>()
|
Mocker.GetMock<IParsingService>()
|
||||||
.Setup(s => s.GetEpisodes(It.IsAny<ParsedEpisodeInfo>(), _series, true, null))
|
.Setup(s => s.GetEpisodes(It.IsAny<ParsedEpisodeInfo>(), _series, true, null))
|
||||||
.Returns(new List<Episode> {_episode});
|
.Returns(new List<Episode> {_episode});
|
||||||
|
|
|
@ -42,6 +42,10 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||||
.Setup(s => s.GetSeries(It.IsAny<IEnumerable<int>>()))
|
.Setup(s => s.GetSeries(It.IsAny<IEnumerable<int>>()))
|
||||||
.Returns(new List<Series> { new Series() });
|
.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>()
|
Mocker.GetMock<IParsingService>()
|
||||||
.Setup(s => s.GetEpisodes(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Series>(), It.IsAny<bool>(), null))
|
.Setup(s => s.GetEpisodes(It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Series>(), It.IsAny<bool>(), null))
|
||||||
.Returns(new List<Episode>{ _episode });
|
.Returns(new List<Episode>{ _episode });
|
||||||
|
@ -84,7 +88,7 @@ namespace NzbDrone.Core.Test.Download.Pending.PendingReleaseServiceTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[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: 1, seasonNumber: 2, episodes: new[] { 1 });
|
||||||
AddPending(id: 2, 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]
|
[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: 1, seasonNumber: 2, episodes: new[] { 1 });
|
||||||
AddPending(id: 2, 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>>()))
|
.Setup(s => s.GetSeries(It.IsAny<IEnumerable<int>>()))
|
||||||
.Returns(new List<Series> { _series });
|
.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>()
|
Mocker.GetMock<IParsingService>()
|
||||||
.Setup(s => s.GetEpisodes(It.IsAny<ParsedEpisodeInfo>(), _series, true, null))
|
.Setup(s => s.GetEpisodes(It.IsAny<ParsedEpisodeInfo>(), _series, true, null))
|
||||||
.Returns(new List<Episode> {_episode});
|
.Returns(new List<Episode> {_episode});
|
||||||
|
|
|
@ -44,7 +44,8 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
|
||||||
{
|
{
|
||||||
SeriesTitle = "TV Series",
|
SeriesTitle = "TV Series",
|
||||||
SeasonNumber = 1
|
SeasonNumber = 1
|
||||||
}
|
},
|
||||||
|
MappedSeasonNumber = 1
|
||||||
};
|
};
|
||||||
|
|
||||||
Mocker.GetMock<IParsingService>()
|
Mocker.GetMock<IParsingService>()
|
||||||
|
@ -77,6 +78,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
|
||||||
trackedDownload.RemoteEpisode.Series.Id.Should().Be(5);
|
trackedDownload.RemoteEpisode.Series.Id.Should().Be(5);
|
||||||
trackedDownload.RemoteEpisode.Episodes.First().Id.Should().Be(4);
|
trackedDownload.RemoteEpisode.Episodes.First().Id.Should().Be(4);
|
||||||
trackedDownload.RemoteEpisode.ParsedEpisodeInfo.SeasonNumber.Should().Be(1);
|
trackedDownload.RemoteEpisode.ParsedEpisodeInfo.SeasonNumber.Should().Be(1);
|
||||||
|
trackedDownload.RemoteEpisode.MappedSeasonNumber.Should().Be(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -91,7 +93,8 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
|
||||||
SeriesTitle = "TV Series",
|
SeriesTitle = "TV Series",
|
||||||
SeasonNumber = 0,
|
SeasonNumber = 0,
|
||||||
EpisodeNumbers = new []{ 1 }
|
EpisodeNumbers = new []{ 1 }
|
||||||
}
|
},
|
||||||
|
MappedSeasonNumber = 0
|
||||||
};
|
};
|
||||||
|
|
||||||
Mocker.GetMock<IHistoryService>()
|
Mocker.GetMock<IHistoryService>()
|
||||||
|
@ -139,6 +142,7 @@ namespace NzbDrone.Core.Test.Download.TrackedDownloads
|
||||||
trackedDownload.RemoteEpisode.Series.Id.Should().Be(5);
|
trackedDownload.RemoteEpisode.Series.Id.Should().Be(5);
|
||||||
trackedDownload.RemoteEpisode.Episodes.First().Id.Should().Be(4);
|
trackedDownload.RemoteEpisode.Episodes.First().Id.Should().Be(4);
|
||||||
trackedDownload.RemoteEpisode.ParsedEpisodeInfo.SeasonNumber.Should().Be(0);
|
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());
|
.Returns<int, int>((i, j) => _xemEpisodes.Where(d => d.SeasonNumber == j).ToList());
|
||||||
|
|
||||||
Mocker.GetMock<ISceneMappingService>()
|
Mocker.GetMock<ISceneMappingService>()
|
||||||
.Setup(s => s.GetSceneNames(It.IsAny<int>(), It.IsAny<List<int>>(), It.IsAny<List<int>>()))
|
.Setup(s => s.FindByTvdbId(It.IsAny<int>()))
|
||||||
.Returns(new List<string>());
|
.Returns(new List<SceneMapping>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WithEpisode(int seasonNumber, int episodeNumber, int? sceneSeasonNumber, int? sceneEpisodeNumber, string airDate = null)
|
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 seasonNumber = 1;
|
||||||
var allCriteria = WatchForSearchCriteria();
|
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();
|
var criteria = allCriteria.OfType<AnimeEpisodeSearchCriteria>().ToList();
|
||||||
|
|
||||||
|
@ -354,7 +354,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||||
|
|
||||||
var allCriteria = WatchForSearchCriteria();
|
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 criteria1 = allCriteria.OfType<DailySeasonSearchCriteria>().ToList();
|
||||||
var criteria2 = allCriteria.OfType<DailyEpisodeSearchCriteria>().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);
|
Subject.SeasonSearch(_xemSeries.Id, 7, false, false, true, false);
|
||||||
|
|
||||||
Mocker.GetMock<ISceneMappingService>()
|
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>()
|
Mocker.GetMock<ISeriesService>()
|
||||||
.Setup(s => s.FindByTitle(It.IsAny<string>()))
|
.Setup(s => s.FindByTitle(It.IsAny<string>()))
|
||||||
.Returns(_series);
|
.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()
|
private void GivenDailySeries()
|
||||||
|
@ -328,8 +324,8 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||||
const int tvdbSeasonNumber = 5;
|
const int tvdbSeasonNumber = 5;
|
||||||
|
|
||||||
Mocker.GetMock<ISceneMappingService>()
|
Mocker.GetMock<ISceneMappingService>()
|
||||||
.Setup(v => v.GetTvdbSeasonNumber(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<int>()))
|
.Setup(v => v.FindSceneMapping(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Returns<string, string, int>((s, r, i) => tvdbSeasonNumber);
|
.Returns<string, string>((s, r) => new SceneMapping { SceneSeasonNumber = 1, SeasonNumber = tvdbSeasonNumber });
|
||||||
|
|
||||||
Subject.GetEpisodes(_parsedEpisodeInfo, _series, true, null);
|
Subject.GetEpisodes(_parsedEpisodeInfo, _series, true, null);
|
||||||
|
|
||||||
|
@ -346,8 +342,8 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||||
const int tvdbSeasonNumber = 5;
|
const int tvdbSeasonNumber = 5;
|
||||||
|
|
||||||
Mocker.GetMock<ISceneMappingService>()
|
Mocker.GetMock<ISceneMappingService>()
|
||||||
.Setup(s => s.FindSceneMapping(_parsedEpisodeInfo.SeriesTitle, It.IsAny<string>()))
|
.Setup(v => v.FindSceneMapping(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Returns(new SceneMapping { SeasonNumber = tvdbSeasonNumber, SceneSeasonNumber = _parsedEpisodeInfo.SeasonNumber + 100 });
|
.Returns<string, string>((s, r) => new SceneMapping { SceneSeasonNumber = 101, SeasonNumber = tvdbSeasonNumber });
|
||||||
|
|
||||||
Subject.GetEpisodes(_parsedEpisodeInfo, _series, true, null);
|
Subject.GetEpisodes(_parsedEpisodeInfo, _series, true, null);
|
||||||
|
|
||||||
|
|
|
@ -51,10 +51,6 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||||
SeasonNumber = _episodes.First().SeasonNumber,
|
SeasonNumber = _episodes.First().SeasonNumber,
|
||||||
Episodes = _episodes
|
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()
|
private void GivenMatchBySeriesTitle()
|
||||||
|
@ -122,8 +118,8 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||||
GivenMatchByTvRageId();
|
GivenMatchByTvRageId();
|
||||||
|
|
||||||
Mocker.GetMock<ISceneMappingService>()
|
Mocker.GetMock<ISceneMappingService>()
|
||||||
.Setup(v => v.FindTvdbId(It.IsAny<string>(), It.IsAny<string>()))
|
.Setup(v => v.FindSceneMapping(It.IsAny<string>(), It.IsAny<string>()))
|
||||||
.Returns(10);
|
.Returns(new SceneMapping { TvdbId = 10 });
|
||||||
|
|
||||||
var result = Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
|
var result = Subject.Map(_parsedEpisodeInfo, _series.TvdbId, _series.TvRageId);
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,10 @@ namespace NzbDrone.Core.DataAugmentation.Scene
|
||||||
|
|
||||||
public int? SceneSeasonNumber { get; set; }
|
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 FilterRegex { get; set; }
|
||||||
|
|
||||||
public string Type { get; set; }
|
public string Type { get; set; }
|
||||||
|
|
|
@ -15,14 +15,10 @@ namespace NzbDrone.Core.DataAugmentation.Scene
|
||||||
public interface ISceneMappingService
|
public interface ISceneMappingService
|
||||||
{
|
{
|
||||||
List<string> GetSceneNames(int tvdbId, List<int> seasonNumbers, List<int> sceneSeasonNumbers);
|
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);
|
int? FindTvdbId(string sceneTitle, string releaseTitle);
|
||||||
List<SceneMapping> FindByTvdbId(int tvdbId);
|
List<SceneMapping> FindByTvdbId(int tvdbId);
|
||||||
SceneMapping FindSceneMapping(string sceneTitle, string releaseTitle);
|
SceneMapping FindSceneMapping(string sceneTitle, string releaseTitle);
|
||||||
int? GetSceneSeasonNumber(string seriesTitle, 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,
|
public class SceneMappingService : ISceneMappingService,
|
||||||
|
@ -60,27 +56,13 @@ namespace NzbDrone.Core.DataAugmentation.Scene
|
||||||
return new List<string>();
|
return new List<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var names = mappings.Where(n => n.SeasonNumber.HasValue && seasonNumbers.Contains(n.SeasonNumber.Value) ||
|
var names = mappings.Where(n => seasonNumbers.Contains(n.SeasonNumber ?? -1) ||
|
||||||
n.SceneSeasonNumber.HasValue && sceneSeasonNumbers.Contains(n.SceneSeasonNumber.Value) ||
|
sceneSeasonNumbers.Contains(n.SceneSeasonNumber ?? -1) ||
|
||||||
(n.SeasonNumber ?? -1) == -1 && (n.SceneSeasonNumber ?? -1) == -1)
|
(n.SeasonNumber ?? -1) == -1 && (n.SceneSeasonNumber ?? -1) == -1 && n.SceneOrigin != "tvdb")
|
||||||
|
.Where(n => IsEnglish(n.SearchTerm))
|
||||||
.Select(n => n.SearchTerm).Distinct().ToList();
|
.Select(n => n.SearchTerm).Distinct().ToList();
|
||||||
|
|
||||||
return FilterNonEnglish(names);
|
return 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int? FindTvdbId(string seriesTitle)
|
public int? FindTvdbId(string seriesTitle)
|
||||||
|
@ -141,44 +123,6 @@ namespace NzbDrone.Core.DataAugmentation.Scene
|
||||||
return FindSceneMapping(seriesTitle, releaseTitle)?.SceneSeasonNumber;
|
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()
|
private void UpdateMappings()
|
||||||
{
|
{
|
||||||
_logger.Info("Updating Scene mappings");
|
_logger.Info("Updating Scene mappings");
|
||||||
|
@ -295,11 +239,6 @@ namespace NzbDrone.Core.DataAugmentation.Scene
|
||||||
return normalCandidates;
|
return normalCandidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> FilterNonEnglish(List<string> titles)
|
|
||||||
{
|
|
||||||
return titles.Where(IsEnglish).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsEnglish(string title)
|
private bool IsEnglish(string title)
|
||||||
{
|
{
|
||||||
return title.All(c => c <= 255);
|
return title.All(c => c <= 255);
|
||||||
|
|
|
@ -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)
|
public object ToDB(object clrValue)
|
||||||
{
|
{
|
||||||
if (clrValue != null)
|
if (clrValue != null && clrValue != DBNull.Value)
|
||||||
{
|
{
|
||||||
return (int)clrValue;
|
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;
|
var singleEpisodeSpec = searchCriteria as SeasonSearchCriteria;
|
||||||
if (singleEpisodeSpec == null) return Decision.Accept();
|
if (singleEpisodeSpec == null) return Decision.Accept();
|
||||||
|
|
||||||
var seasonNumber = _sceneMappingService.GetTvdbSeasonNumber(remoteEpisode.ParsedEpisodeInfo.SeriesTitle,
|
if (singleEpisodeSpec.SeasonNumber != remoteEpisode.MappedSeasonNumber)
|
||||||
remoteEpisode.ParsedEpisodeInfo.ReleaseTitle,
|
|
||||||
remoteEpisode.ParsedEpisodeInfo.SeasonNumber);
|
|
||||||
|
|
||||||
if (singleEpisodeSpec.SeasonNumber != seasonNumber)
|
|
||||||
{
|
{
|
||||||
_logger.Debug("Season number does not match searched season number, skipping.");
|
_logger.Debug("Season number does not match searched season number, skipping.");
|
||||||
return Decision.Reject("Wrong season");
|
return Decision.Reject("Wrong season");
|
||||||
|
|
|
@ -38,11 +38,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||||
|
|
||||||
private Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SingleEpisodeSearchCriteria singleEpisodeSpec)
|
private Decision IsSatisfiedBy(RemoteEpisode remoteEpisode, SingleEpisodeSearchCriteria singleEpisodeSpec)
|
||||||
{
|
{
|
||||||
var seasonNumber = _sceneMappingService.GetTvdbSeasonNumber(remoteEpisode.ParsedEpisodeInfo.SeriesTitle,
|
if (singleEpisodeSpec.SeasonNumber != remoteEpisode.MappedSeasonNumber)
|
||||||
remoteEpisode.ParsedEpisodeInfo.ReleaseTitle,
|
|
||||||
remoteEpisode.ParsedEpisodeInfo.SeasonNumber);
|
|
||||||
|
|
||||||
if (singleEpisodeSpec.SeasonNumber != seasonNumber)
|
|
||||||
{
|
{
|
||||||
_logger.Debug("Season number does not match searched season number, skipping.");
|
_logger.Debug("Season number does not match searched season number, skipping.");
|
||||||
return Decision.Reject("Wrong season");
|
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)
|
// Just in case the series was removed, but wasn't cleaned up yet (housekeeper will clean it up)
|
||||||
if (series == null) return null;
|
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
|
release.RemoteEpisode = new RemoteEpisode
|
||||||
{
|
{
|
||||||
Series = series,
|
Series = series,
|
||||||
Episodes = episodes,
|
|
||||||
ParsedEpisodeInfo = release.ParsedEpisodeInfo,
|
ParsedEpisodeInfo = release.ParsedEpisodeInfo,
|
||||||
Release = release.Release
|
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);
|
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 Series Series { get; set; }
|
||||||
public List<string> SceneTitles { get; set; }
|
public List<string> SceneTitles { get; set; }
|
||||||
public List<SceneMapping> SceneMappings { get; set; }
|
|
||||||
public List<Episode> Episodes { get; set; }
|
public List<Episode> Episodes { get; set; }
|
||||||
|
public SearchMode SearchMode { get; set; }
|
||||||
public virtual bool MonitoredEpisodesOnly { get; set; }
|
public virtual bool MonitoredEpisodesOnly { get; set; }
|
||||||
public virtual bool UserInvokedSearch { get; set; }
|
public virtual bool UserInvokedSearch { get; set; }
|
||||||
public virtual bool InteractiveSearch { get; set; }
|
public virtual bool InteractiveSearch { get; set; }
|
||||||
|
|
|
@ -68,7 +68,7 @@ namespace NzbDrone.Core.IndexerSearch
|
||||||
throw new SearchFailedException("Air date is missing");
|
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)
|
if (series.SeriesType == SeriesTypes.Anime)
|
||||||
|
@ -78,19 +78,19 @@ namespace NzbDrone.Core.IndexerSearch
|
||||||
episode.AbsoluteEpisodeNumber == null)
|
episode.AbsoluteEpisodeNumber == null)
|
||||||
{
|
{
|
||||||
// Search for special episodes in season 0 that don't have absolute episode numbers
|
// 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)
|
if (episode.SeasonNumber == 0)
|
||||||
{
|
{
|
||||||
// Search for special episodes in season 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)
|
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);
|
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)
|
public List<DownloadDecision> SeasonSearch(int seriesId, int seasonNumber, List<Episode> episodes, bool monitoredOnly, bool userInvokedSearch, bool interactiveSearch)
|
||||||
{
|
{
|
||||||
var series = _seriesService.GetSeries(seriesId);
|
var series = _seriesService.GetSeries(seriesId);
|
||||||
|
|
||||||
if (series.SeriesType == SeriesTypes.Anime)
|
if (series.SeriesType == SeriesTypes.Anime)
|
||||||
{
|
{
|
||||||
return SearchAnimeSeason(series, episodes, userInvokedSearch, interactiveSearch);
|
return SearchAnimeSeason(series, episodes, monitoredOnly, userInvokedSearch, interactiveSearch);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (series.SeriesType == SeriesTypes.Daily)
|
if (series.SeriesType == SeriesTypes.Daily)
|
||||||
{
|
{
|
||||||
return SearchDailySeason(series, episodes, userInvokedSearch, interactiveSearch);
|
return SearchDailySeason(series, episodes, monitoredOnly, userInvokedSearch, interactiveSearch);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seasonNumber == 0)
|
var mappings = GetSceneSeasonMappings(series, episodes);
|
||||||
{
|
|
||||||
// search for special episodes in season 0
|
|
||||||
return SearchSpecial(series, episodes, userInvokedSearch, interactiveSearch);
|
|
||||||
}
|
|
||||||
|
|
||||||
var downloadDecisions = new List<DownloadDecision>();
|
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)
|
// search for special episodes in season 0
|
||||||
{
|
downloadDecisions.AddRange(SearchSpecial(series, mapping.Episodes, monitoredOnly, userInvokedSearch, interactiveSearch));
|
||||||
return v.SceneSeasonNumber.Value;
|
continue;
|
||||||
}
|
}
|
||||||
return v.SeasonNumber;
|
|
||||||
}).Distinct();
|
|
||||||
|
|
||||||
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();
|
Episodes = groupedEpisode.ToList(),
|
||||||
var searchSpec = Get<SingleEpisodeSearchCriteria>(series, sceneSeasonEpisodes.ToList(), userInvokedSearch, interactiveSearch);
|
EpisodeMapping = episodeMapping,
|
||||||
|
SceneTitles = episodeMapping.SceneTitles,
|
||||||
|
SearchMode = episodeMapping.SearchMode,
|
||||||
|
SeasonNumber = episodeMapping.SeasonNumber
|
||||||
|
};
|
||||||
|
|
||||||
searchSpec.SeasonNumber = sceneSeasonEpisodes.Key;
|
if (dict.TryGetValue(seasonMapping, out var existing))
|
||||||
searchSpec.MonitoredEpisodesOnly = monitoredOnly;
|
{
|
||||||
|
existing.Episodes.AddRange(seasonMapping.Episodes);
|
||||||
if (episode.SceneSeasonNumber.HasValue && episode.SceneEpisodeNumber.HasValue)
|
existing.SceneTitles.AddRange(seasonMapping.SceneTitles);
|
||||||
{
|
|
||||||
searchSpec.EpisodeNumber = episode.SceneEpisodeNumber.Value;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
searchSpec.EpisodeNumber = episode.EpisodeNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
|
||||||
downloadDecisions.AddRange(decisions);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var searchSpec = Get<SeasonSearchCriteria>(series, sceneSeasonEpisodes.ToList(), userInvokedSearch, interactiveSearch);
|
dict[seasonMapping] = seasonMapping;
|
||||||
searchSpec.SeasonNumber = sceneSeasonEpisodes.Key;
|
|
||||||
searchSpec.MonitoredEpisodesOnly = monitoredOnly;
|
|
||||||
|
|
||||||
var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
|
||||||
downloadDecisions.AddRange(decisions);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
foreach (var item in dict)
|
||||||
{
|
{
|
||||||
var searchSpec = Get<SeasonSearchCriteria>(series, episodes, userInvokedSearch, interactiveSearch);
|
item.Value.Episodes = item.Value.Episodes.Distinct().ToList();
|
||||||
searchSpec.SeasonNumber = seasonNumber;
|
item.Value.SceneTitles = item.Value.SceneTitles.Distinct().ToList();
|
||||||
searchSpec.MonitoredEpisodesOnly = monitoredOnly;
|
}
|
||||||
|
|
||||||
|
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);
|
var decisions = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
||||||
downloadDecisions.AddRange(decisions);
|
downloadDecisions.AddRange(decisions);
|
||||||
|
@ -183,36 +301,18 @@ namespace NzbDrone.Core.IndexerSearch
|
||||||
return downloadDecisions;
|
return downloadDecisions;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<DownloadDecision> SearchSingle(Series series, Episode episode, bool userInvokedSearch, bool interactiveSearch)
|
private List<DownloadDecision> SearchDaily(Series series, Episode episode, bool monitoredOnly, 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)
|
|
||||||
{
|
{
|
||||||
var airDate = DateTime.ParseExact(episode.AirDate, Episode.AIR_DATE_FORMAT, CultureInfo.InvariantCulture);
|
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;
|
searchSpec.AirDate = airDate;
|
||||||
|
|
||||||
return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
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;
|
searchSpec.IsSeasonSearch = isSeasonSearch;
|
||||||
|
|
||||||
|
@ -233,11 +333,11 @@ namespace NzbDrone.Core.IndexerSearch
|
||||||
return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
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 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>"
|
// build list of queries for each episode in the form: "<series> <episode-title>"
|
||||||
searchSpec.EpisodeQueryTitles = episodes.Where(e => !string.IsNullOrWhiteSpace(e.Title))
|
searchSpec.EpisodeQueryTitles = episodes.Where(e => !string.IsNullOrWhiteSpace(e.Title))
|
||||||
.SelectMany(e => searchSpec.QueryTitles.Select(title => title + " " + SearchCriteriaBase.GetQueryTitle(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
|
// Search for each episode by season/episode number as well
|
||||||
foreach (var episode in episodes)
|
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;
|
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 downloadDecisions = new List<DownloadDecision>();
|
||||||
|
|
||||||
|
// Episode needs to be monitored if it's not an interactive search
|
||||||
var episodesToSearch = episodes.Where(e =>
|
// and Ensure episode has an airdate and has already aired
|
||||||
{
|
var episodesToSearch = episodes
|
||||||
// Episode needs to be monitored if it's not an interactive search
|
.Where(ep => interactiveSearch || !monitoredOnly || ep.Monitored)
|
||||||
if (!interactiveSearch && !e.Monitored)
|
.Where(ep => ep.AirDateUtc.HasValue && ep.AirDateUtc.Value.Before(DateTime.UtcNow))
|
||||||
{
|
.ToList();
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure episode has an airdate and has already aired
|
|
||||||
return e.AirDateUtc.HasValue && e.AirDateUtc.Value.Before(DateTime.UtcNow);
|
|
||||||
});
|
|
||||||
|
|
||||||
foreach (var episode in episodesToSearch)
|
foreach (var episode in episodesToSearch)
|
||||||
{
|
{
|
||||||
downloadDecisions.AddRange(SearchAnime(series, episode, userInvokedSearch, interactiveSearch, true));
|
downloadDecisions.AddRange(SearchAnime(series, episode, monitoredOnly, userInvokedSearch, interactiveSearch, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
return downloadDecisions;
|
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>();
|
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();
|
var yearEpisodes = yearGroup.ToList();
|
||||||
|
|
||||||
if (yearEpisodes.Count > 1)
|
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;
|
searchSpec.Year = yearGroup.Key;
|
||||||
|
|
||||||
downloadDecisions.AddRange(Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec));
|
downloadDecisions.AddRange(Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
downloadDecisions.AddRange(SearchDaily(series, yearEpisodes.First(), userInvokedSearch, interactiveSearch));
|
downloadDecisions.AddRange(SearchDaily(series, yearEpisodes.First(), monitoredOnly, userInvokedSearch, interactiveSearch));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return downloadDecisions;
|
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();
|
var spec = new TSpec();
|
||||||
|
|
||||||
|
@ -311,16 +418,41 @@ namespace NzbDrone.Core.IndexerSearch
|
||||||
spec.SceneTitles = _sceneMapping.GetSceneNames(series.TvdbId,
|
spec.SceneTitles = _sceneMapping.GetSceneNames(series.TvdbId,
|
||||||
episodes.Select(e => e.SeasonNumber).Distinct().ToList(),
|
episodes.Select(e => e.SeasonNumber).Distinct().ToList(),
|
||||||
episodes.Select(e => e.SceneSeasonNumber ?? 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.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.UserInvokedSearch = userInvokedSearch;
|
||||||
spec.InteractiveSearch = interactiveSearch;
|
spec.InteractiveSearch = interactiveSearch;
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.DataAugmentation.Scene;
|
||||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Indexers.FileList
|
namespace NzbDrone.Core.Indexers.FileList
|
||||||
|
@ -24,11 +25,22 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||||
{
|
{
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
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();
|
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;
|
return pageableRequests;
|
||||||
}
|
}
|
||||||
|
@ -37,11 +49,22 @@ namespace NzbDrone.Core.Indexers.FileList
|
||||||
{
|
{
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
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();
|
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;
|
return pageableRequests;
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,15 +143,31 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||||
{
|
{
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
var pageableRequests = new IndexerPageableRequestChain();
|
||||||
|
|
||||||
AddTvIdPageableRequests(pageableRequests, Settings.Categories, searchCriteria,
|
if (searchCriteria.SearchMode.HasFlag(SearchMode.SearchID) || searchCriteria.SearchMode == SearchMode.Default)
|
||||||
string.Format("&season={0}&ep={1}",
|
{
|
||||||
searchCriteria.SeasonNumber,
|
AddTvIdPageableRequests(pageableRequests, Settings.Categories, searchCriteria,
|
||||||
searchCriteria.EpisodeNumber));
|
string.Format("&season={0}&ep={1}",
|
||||||
|
searchCriteria.SeasonNumber,
|
||||||
|
searchCriteria.EpisodeNumber));
|
||||||
|
}
|
||||||
|
|
||||||
AddSceneTitlePageableRequests(pageableRequests, Settings.Categories, searchCriteria,
|
if (searchCriteria.SearchMode.HasFlag(SearchMode.SearchTitle))
|
||||||
m => string.Format("&season={0}&ep={1}",
|
{
|
||||||
m.SceneSeasonNumber,
|
AddTitlePageableRequests(pageableRequests, Settings.Categories, searchCriteria,
|
||||||
searchCriteria.EpisodeNumber));
|
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;
|
return pageableRequests;
|
||||||
}
|
}
|
||||||
|
@ -160,13 +176,28 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||||
{
|
{
|
||||||
var pageableRequests = new IndexerPageableRequestChain();
|
var pageableRequests = new IndexerPageableRequestChain();
|
||||||
|
|
||||||
AddTvIdPageableRequests(pageableRequests, Settings.Categories, searchCriteria,
|
if (searchCriteria.SearchMode.HasFlag(SearchMode.SearchID) || searchCriteria.SearchMode == SearchMode.Default)
|
||||||
string.Format("&season={0}",
|
{
|
||||||
searchCriteria.SeasonNumber));
|
AddTvIdPageableRequests(pageableRequests, Settings.Categories, searchCriteria,
|
||||||
|
string.Format("&season={0}",
|
||||||
|
searchCriteria.SeasonNumber));
|
||||||
|
}
|
||||||
|
|
||||||
AddSceneTitlePageableRequests(pageableRequests, Settings.Categories, searchCriteria,
|
if (searchCriteria.SearchMode.HasFlag(SearchMode.SearchTitle))
|
||||||
m => string.Format("&season={0}",
|
{
|
||||||
m.SceneSeasonNumber));
|
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;
|
return pageableRequests;
|
||||||
}
|
}
|
||||||
|
@ -287,11 +318,12 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||||
string.Format("&tvmazeid={0}{1}", searchCriteria.Series.TvMazeId, parameters)));
|
string.Format("&tvmazeid={0}{1}", searchCriteria.Series.TvMazeId, parameters)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddTitlePageableRequests(IndexerPageableRequestChain chain, IEnumerable<int> categories, SearchCriteriaBase searchCriteria, string parameters)
|
||||||
|
{
|
||||||
if (SupportsTvTitleSearch)
|
if (SupportsTvTitleSearch)
|
||||||
{
|
{
|
||||||
chain.AddTier();
|
|
||||||
foreach (var searchTerm in searchCriteria.SceneTitles)
|
foreach (var searchTerm in searchCriteria.SceneTitles)
|
||||||
{
|
{
|
||||||
chain.Add(GetPagedRequests(MaxPages, Settings.Categories, "tvsearch",
|
chain.Add(GetPagedRequests(MaxPages, Settings.Categories, "tvsearch",
|
||||||
|
@ -302,7 +334,6 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||||
}
|
}
|
||||||
else if (SupportsTvSearch)
|
else if (SupportsTvSearch)
|
||||||
{
|
{
|
||||||
chain.AddTier();
|
|
||||||
foreach (var queryTitle in searchCriteria.QueryTitles)
|
foreach (var queryTitle in searchCriteria.QueryTitles)
|
||||||
{
|
{
|
||||||
chain.Add(GetPagedRequests(MaxPages, Settings.Categories, "tvsearch",
|
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)
|
private IEnumerable<IndexerRequest> GetPagedRequests(int maxPages, IEnumerable<int> categories, string searchType, string parameters)
|
||||||
{
|
{
|
||||||
if (categories.Empty())
|
if (categories.Empty())
|
||||||
|
|
|
@ -10,6 +10,8 @@ namespace NzbDrone.Core.Parser.Model
|
||||||
{
|
{
|
||||||
public ReleaseInfo Release { get; set; }
|
public ReleaseInfo Release { get; set; }
|
||||||
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
|
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
|
||||||
|
public int MappedSeasonNumber { get; set; }
|
||||||
|
|
||||||
public Series Series { get; set; }
|
public Series Series { get; set; }
|
||||||
public List<Episode> Episodes { get; set; }
|
public List<Episode> Episodes { get; set; }
|
||||||
public bool DownloadAllowed { get; set; }
|
public bool DownloadAllowed { get; set; }
|
||||||
|
|
|
@ -15,6 +15,7 @@ namespace NzbDrone.Core.Parser
|
||||||
{
|
{
|
||||||
Series GetSeries(string title);
|
Series GetSeries(string title);
|
||||||
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
|
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);
|
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds);
|
||||||
List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null);
|
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);
|
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)
|
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null)
|
||||||
{
|
{
|
||||||
var remoteEpisode = new RemoteEpisode
|
return Map(parsedEpisodeInfo, tvdbId, tvRageId, null, searchCriteria);
|
||||||
{
|
}
|
||||||
ParsedEpisodeInfo = parsedEpisodeInfo,
|
|
||||||
};
|
|
||||||
|
|
||||||
var series = GetSeries(parsedEpisodeInfo, tvdbId, tvRageId, searchCriteria);
|
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Series series)
|
||||||
|
{
|
||||||
if (series == null)
|
return Map(parsedEpisodeInfo, 0, 0, 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, int seriesId, IEnumerable<int> episodeIds)
|
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)
|
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)
|
if (parsedEpisodeInfo.FullSeason)
|
||||||
{
|
{
|
||||||
return _episodeService.GetEpisodesBySeason(series.Id, parsedEpisodeInfo.SeasonNumber);
|
return _episodeService.GetEpisodesBySeason(series.Id, mappedSeasonNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsedEpisodeInfo.IsDaily)
|
if (parsedEpisodeInfo.IsDaily)
|
||||||
|
@ -173,10 +217,10 @@ namespace NzbDrone.Core.Parser
|
||||||
|
|
||||||
if (parsedEpisodeInfo.IsAbsoluteNumbering)
|
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)
|
public ParsedEpisodeInfo ParseSpecialEpisodeTitle(ParsedEpisodeInfo parsedEpisodeInfo, string releaseTitle, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null)
|
||||||
|
@ -261,19 +305,18 @@ namespace NzbDrone.Core.Parser
|
||||||
return null;
|
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;
|
Series series = null;
|
||||||
|
|
||||||
var sceneMappingTvdbId = _sceneMappingService.FindTvdbId(parsedEpisodeInfo.SeriesTitle, parsedEpisodeInfo.ReleaseTitle);
|
if (sceneMapping != null)
|
||||||
if (sceneMappingTvdbId.HasValue)
|
|
||||||
{
|
{
|
||||||
if (searchCriteria != null && searchCriteria.Series.TvdbId == sceneMappingTvdbId.Value)
|
if (searchCriteria != null && searchCriteria.Series.TvdbId == sceneMapping.TvdbId)
|
||||||
{
|
{
|
||||||
return searchCriteria.Series;
|
return searchCriteria.Series;
|
||||||
}
|
}
|
||||||
|
|
||||||
series = _seriesService.FindByTvdbId(sceneMappingTvdbId.Value);
|
series = _seriesService.FindByTvdbId(sceneMapping.TvdbId);
|
||||||
|
|
||||||
if (series == null)
|
if (series == null)
|
||||||
{
|
{
|
||||||
|
@ -385,7 +428,7 @@ namespace NzbDrone.Core.Parser
|
||||||
return episodeInfo;
|
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>();
|
var result = new List<Episode>();
|
||||||
|
|
||||||
|
@ -448,17 +491,9 @@ namespace NzbDrone.Core.Parser
|
||||||
return result;
|
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 result = new List<Episode>();
|
||||||
var seasonNumber = parsedEpisodeInfo.SeasonNumber;
|
|
||||||
|
|
||||||
if (sceneSource)
|
|
||||||
{
|
|
||||||
seasonNumber = _sceneMappingService.GetTvdbSeasonNumber(parsedEpisodeInfo.SeriesTitle,
|
|
||||||
parsedEpisodeInfo.ReleaseTitle,
|
|
||||||
parsedEpisodeInfo.SeasonNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parsedEpisodeInfo.EpisodeNumbers == null)
|
if (parsedEpisodeInfo.EpisodeNumbers == null)
|
||||||
{
|
{
|
||||||
|
@ -479,7 +514,7 @@ namespace NzbDrone.Core.Parser
|
||||||
|
|
||||||
if (!episodes.Any())
|
if (!episodes.Any())
|
||||||
{
|
{
|
||||||
episodes = _episodeService.FindEpisodesBySceneNumbering(series.Id, seasonNumber, episodeNumber);
|
episodes = _episodeService.FindEpisodesBySceneNumbering(series.Id, mappedSeasonNumber, episodeNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (episodes != null && episodes.Any())
|
if (episodes != null && episodes.Any())
|
||||||
|
@ -499,12 +534,12 @@ namespace NzbDrone.Core.Parser
|
||||||
|
|
||||||
if (searchCriteria != null)
|
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)
|
if (episodeInfo == null)
|
||||||
{
|
{
|
||||||
episodeInfo = _episodeService.FindEpisode(series.Id, seasonNumber, episodeNumber);
|
episodeInfo = _episodeService.FindEpisode(series.Id, mappedSeasonNumber, episodeNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (episodeInfo != null)
|
if (episodeInfo != null)
|
||||||
|
|
|
@ -5,5 +5,7 @@
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public int? SeasonNumber { get; set; }
|
public int? SeasonNumber { get; set; }
|
||||||
public int? SceneSeasonNumber { 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;
|
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)
|
private void LinkRootFolderPath(SeriesResource resource)
|
||||||
|
|
Loading…
Reference in New Issue