parent
9ae21cf7a1
commit
a6a4932b44
@ -1,65 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.IndexerSearch;
|
||||
using NzbDrone.Core.Model;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerSearchTests.DailyEpisodeSearchTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class IndexerDailyEpisodeSearchFixture : CoreTest<DailyEpisodeSearch>
|
||||
{
|
||||
private Series _series;
|
||||
private Episode _episode;
|
||||
private EpisodeParseResult _episodeParseResult;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_series = Builder<Series>
|
||||
.CreateNew()
|
||||
.Build();
|
||||
|
||||
_episode = Builder<Episode>
|
||||
.CreateNew()
|
||||
.With(e => e.SeriesId = _series.Id)
|
||||
.With(e => e.Series = _series)
|
||||
.Build();
|
||||
|
||||
_episodeParseResult = Builder<EpisodeParseResult>
|
||||
.CreateNew()
|
||||
.With(p => p.AirDate = _episode.AirDate)
|
||||
.With(p => p.Episodes = new List<Episode> { _episode })
|
||||
.With(p => p.Series = _series)
|
||||
.Build();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_WrongEpisode_is_parseResult_doesnt_have_airdate()
|
||||
{
|
||||
_episodeParseResult.AirDate = null;
|
||||
|
||||
Subject.IsEpisodeMatch(_series, new { Episode = _episode }, _episodeParseResult).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_WrongEpisode_is_parseResult_airdate_doesnt_match_episode()
|
||||
{
|
||||
_episodeParseResult.AirDate = _episode.AirDate.Value.AddDays(-10);
|
||||
|
||||
Subject.IsEpisodeMatch(_series, new { Episode = _episode }, _episodeParseResult)
|
||||
.Should()
|
||||
.BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_return_error_when_airDates_match()
|
||||
{
|
||||
Subject.IsEpisodeMatch(_series, new {Episode = _episode}, _episodeParseResult)
|
||||
.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.IndexerSearch;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerSearchTests.DailyEpisodeSearchTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class IndexerDailyEpisodeSearch_EpisodeMatch : IndexerSearchTestBase<DailyEpisodeSearch>
|
||||
{
|
||||
[Test]
|
||||
public void should_fetch_results_from_indexers()
|
||||
{
|
||||
WithValidIndexers();
|
||||
|
||||
|
||||
Subject.PerformSearch(_series, new List<Episode> { _episode }, notification)
|
||||
.Should()
|
||||
.HaveCount(20);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_log_error_when_fetching_from_indexer_fails()
|
||||
{
|
||||
WithBrokenIndexers();
|
||||
|
||||
Mocker.Resolve<DailyEpisodeSearch>()
|
||||
.PerformSearch(_series, new List<Episode> { _episode }, notification)
|
||||
.Should()
|
||||
.HaveCount(0);
|
||||
|
||||
ExceptionVerification.ExpectedErrors(2);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.IndexerSearch;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerSearchTests.EpisodeSearchTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class IndexerEpisodeSearchFixture : IndexerSearchTestBase<EpisodeSearch>
|
||||
{
|
||||
|
||||
[Test]
|
||||
public void should_fetch_results_from_indexers()
|
||||
{
|
||||
WithValidIndexers();
|
||||
|
||||
Subject
|
||||
.PerformSearch(_series, new List<Episode> { _episode }, notification)
|
||||
.Should()
|
||||
.HaveCount(20);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_log_error_when_fetching_from_indexer_fails()
|
||||
{
|
||||
WithBrokenIndexers();
|
||||
|
||||
Subject
|
||||
.PerformSearch(_series, new List<Episode> { _episode }, notification)
|
||||
.Should()
|
||||
.HaveCount(0);
|
||||
|
||||
ExceptionVerification.ExpectedErrors(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_scene_numbering_when_available()
|
||||
{
|
||||
_series.UseSceneNumbering = true;
|
||||
_episode.SceneEpisodeNumber = 5;
|
||||
_episode.SceneSeasonNumber = 10;
|
||||
|
||||
WithValidIndexers();
|
||||
|
||||
Subject
|
||||
.PerformSearch(_series, new List<Episode> { _episode }, notification)
|
||||
.Should()
|
||||
.HaveCount(20);
|
||||
|
||||
_indexer1.Verify(v => v.FetchEpisode(_series.Title, 10, 5), Times.Once());
|
||||
_indexer2.Verify(v => v.FetchEpisode(_series.Title, 10, 5), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_standard_numbering_when_scene_series_set_but_info_is_not_available()
|
||||
{
|
||||
_series.UseSceneNumbering = true;
|
||||
_episode.SceneEpisodeNumber = 0;
|
||||
_episode.SceneSeasonNumber = 0;
|
||||
|
||||
WithValidIndexers();
|
||||
|
||||
Subject
|
||||
.PerformSearch(_series, new List<Episode> { _episode }, notification)
|
||||
.Should()
|
||||
.HaveCount(20);
|
||||
|
||||
_indexer1.Verify(v => v.FetchEpisode(_series.Title, _episode.SeasonNumber, _episode.EpisodeNumber), Times.Once());
|
||||
_indexer2.Verify(v => v.FetchEpisode(_series.Title, _episode.SeasonNumber, _episode.EpisodeNumber), Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_standard_numbering_when_not_scene_series()
|
||||
{
|
||||
_series.UseSceneNumbering = false;
|
||||
|
||||
WithValidIndexers();
|
||||
|
||||
Subject
|
||||
.PerformSearch(_series, new List<Episode> { _episode }, notification)
|
||||
.Should()
|
||||
.HaveCount(20);
|
||||
|
||||
_indexer1.Verify(v => v.FetchEpisode(_series.Title, _episode.SeasonNumber, _episode.EpisodeNumber), Times.Once());
|
||||
_indexer2.Verify(v => v.FetchEpisode(_series.Title, _episode.SeasonNumber, _episode.EpisodeNumber), Times.Once());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.IndexerSearch;
|
||||
using NzbDrone.Core.Model;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerSearchTests.EpisodeSearchTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class IndexerEpisodeSearch_EpisodeMatch : TestBase
|
||||
{
|
||||
private Series _series;
|
||||
private Episode _episode;
|
||||
private EpisodeParseResult _episodeParseResult;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_series = Builder<Series>
|
||||
.CreateNew()
|
||||
.Build();
|
||||
|
||||
_episode = Builder<Episode>
|
||||
.CreateNew()
|
||||
.With(e => e.SeriesId = _series.Id)
|
||||
.With(e => e.Series = _series)
|
||||
.Build();
|
||||
|
||||
_episodeParseResult = Builder<EpisodeParseResult>
|
||||
.CreateNew()
|
||||
.With(p => p.SeasonNumber = 1)
|
||||
.With(p => p.EpisodeNumbers = new List<int>{ _episode.EpisodeNumber })
|
||||
.With(p => p.Episodes = new List<Episode> { _episode })
|
||||
.With(p => p.Series = _series)
|
||||
.Build();
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_WrongSeason_when_season_doesnt_match()
|
||||
{
|
||||
_episode.SeasonNumber = 10;
|
||||
|
||||
Mocker.Resolve<EpisodeSearch>()
|
||||
.IsEpisodeMatch(_series, new {Episode = _episode}, _episodeParseResult)
|
||||
.Should()
|
||||
.BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_WrongEpisode_when_episode_doesnt_match()
|
||||
{
|
||||
_episode.EpisodeNumber = 10;
|
||||
|
||||
Mocker.Resolve<EpisodeSearch>()
|
||||
.IsEpisodeMatch(_series, new { Episode = _episode }, _episodeParseResult)
|
||||
.Should()
|
||||
.BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_return_error_when_season_and_episode_match()
|
||||
{
|
||||
Mocker.Resolve<EpisodeSearch>()
|
||||
.IsEpisodeMatch(_series, new { Episode = _episode }, _episodeParseResult)
|
||||
.Should()
|
||||
.BeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_WrongSeason_when_season_doesnt_match_for_scene_series()
|
||||
{
|
||||
_series.UseSceneNumbering = true;
|
||||
_episode.SceneSeasonNumber = 10;
|
||||
_episode.SeasonNumber = 10;
|
||||
_episode.EpisodeNumber = 10;
|
||||
|
||||
Mocker.Resolve<EpisodeSearch>()
|
||||
.IsEpisodeMatch(_series, new { Episode = _episode }, _episodeParseResult)
|
||||
.Should()
|
||||
.BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_WrongEpisode_when_episode_doesnt_match_for_scene_series()
|
||||
{
|
||||
_series.UseSceneNumbering = true;
|
||||
_episode.SceneEpisodeNumber = 10;
|
||||
_episode.SeasonNumber = 10;
|
||||
_episode.EpisodeNumber = 10;
|
||||
|
||||
Mocker.Resolve<EpisodeSearch>()
|
||||
.IsEpisodeMatch(_series, new { Episode = _episode }, _episodeParseResult)
|
||||
.Should()
|
||||
.BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_return_error_when_season_and_episode_match_for_scene_series()
|
||||
{
|
||||
_series.UseSceneNumbering = true;
|
||||
_episode.SceneSeasonNumber = _episode.SeasonNumber;
|
||||
_episode.SceneEpisodeNumber = _episode.EpisodeNumber;
|
||||
_episode.SeasonNumber = 10;
|
||||
_episode.EpisodeNumber = 10;
|
||||
|
||||
Mocker.Resolve<EpisodeSearch>()
|
||||
.IsEpisodeMatch(_series, new { Episode = _episode }, _episodeParseResult)
|
||||
.Should()
|
||||
.BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.DataAugmentation;
|
||||
using NzbDrone.Core.DataAugmentation.Scene;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
{
|
||||
public class GetSearchTitleFixture : CoreTest<TestSearch>
|
||||
{
|
||||
private Series _series;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_series = Builder<Series>
|
||||
.CreateNew()
|
||||
.With(s => s.Title = "Hawaii Five-0")
|
||||
.Build();
|
||||
}
|
||||
|
||||
private void WithSceneMapping()
|
||||
{
|
||||
Mocker.GetMock<ISceneMappingService>()
|
||||
.Setup(s => s.GetSceneName(_series.Id, -1))
|
||||
.Returns("Hawaii Five 0 2010");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_series_title_when_there_is_no_scene_mapping()
|
||||
{
|
||||
Subject.GetSearchTitle(_series, 5).Should().Be(_series.Title);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_scene_mapping_when_one_exists()
|
||||
{
|
||||
WithSceneMapping();
|
||||
|
||||
Subject.GetSearchTitle(_series, 5).Should().Be("Hawaii Five 0 2010");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_season_scene_name_when_one_exists()
|
||||
{
|
||||
Mocker.GetMock<ISceneMappingService>()
|
||||
.Setup(s => s.GetSceneName(_series.Id, 5))
|
||||
.Returns("Hawaii Five 0 2010 - Season 5");
|
||||
|
||||
Subject.GetSearchTitle(_series, 5).Should().Be("Hawaii Five 0 2010 - Season 5");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_series_scene_name_when_one_for_season_does_not_exist()
|
||||
{
|
||||
WithSceneMapping();
|
||||
|
||||
Subject.GetSearchTitle(_series, 5).Should().Be("Hawaii Five 0 2010");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_replace_ampersand_with_and()
|
||||
{
|
||||
_series.Title = "Franklin & Bash";
|
||||
|
||||
Subject.GetSearchTitle(_series, 5).Should().Be("Franklin and Bash");
|
||||
}
|
||||
|
||||
[TestCase("Betty White's Off Their Rockers", "Betty Whites Off Their Rockers")]
|
||||
[TestCase("Star Wars: The Clone Wars", "Star Wars The Clone Wars")]
|
||||
[TestCase("Hawaii Five-0", "Hawaii Five-0")]
|
||||
public void should_replace_some_special_characters(string input, string expected)
|
||||
{
|
||||
_series.Title = input;
|
||||
|
||||
Mocker.GetMock<ISceneMappingService>()
|
||||
.Setup(s => s.GetSceneName(_series.Id, -1))
|
||||
.Returns("");
|
||||
|
||||
Subject.GetSearchTitle(_series, 5).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FizzWare.NBuilder;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.IndexerSearch;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Model;
|
||||
using NzbDrone.Core.Model.Notification;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
{
|
||||
public abstract class IndexerSearchTestBase<TSearch> : CoreTest<TSearch>
|
||||
where TSearch : IndexerSearchBase
|
||||
{
|
||||
protected Series _series;
|
||||
protected Episode _episode;
|
||||
protected ProgressNotification notification = new ProgressNotification("Testing");
|
||||
|
||||
protected Mock<IndexerBase> _indexer1;
|
||||
protected Mock<IndexerBase> _indexer2;
|
||||
protected List<IndexerBase> _indexers;
|
||||
protected IList<EpisodeParseResult> _parseResults;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_series = Builder<Series>
|
||||
.CreateNew()
|
||||
.Build();
|
||||
|
||||
_episode = Builder<Episode>
|
||||
.CreateNew()
|
||||
.With(e => e.SeriesId = _series.Id)
|
||||
.With(e => e.Series = _series)
|
||||
.Build();
|
||||
|
||||
|
||||
_parseResults = Builder<EpisodeParseResult>
|
||||
.CreateListOfSize(10)
|
||||
.Build();
|
||||
|
||||
_indexer1 = new Mock<IndexerBase>();
|
||||
_indexer2 = new Mock<IndexerBase>();
|
||||
_indexers = new List<IndexerBase> { _indexer1.Object, _indexer2.Object };
|
||||
|
||||
Mocker.GetMock<IIndexerService>()
|
||||
.Setup(c => c.GetEnabledIndexers())
|
||||
.Returns(_indexers);
|
||||
}
|
||||
|
||||
protected void WithValidIndexers()
|
||||
{
|
||||
_indexer1.Setup(c => c.FetchEpisode(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||
.Returns(_parseResults);
|
||||
_indexer1.Setup(c => c.FetchDailyEpisode(It.IsAny<string>(), It.IsAny<DateTime>()))
|
||||
.Returns(_parseResults);
|
||||
_indexer1.Setup(c => c.FetchPartialSeason(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||
.Returns(_parseResults);
|
||||
|
||||
_indexer2.Setup(c => c.FetchEpisode(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||
.Returns(_parseResults);
|
||||
_indexer2.Setup(c => c.FetchDailyEpisode(It.IsAny<string>(), It.IsAny<DateTime>()))
|
||||
.Returns(_parseResults);
|
||||
_indexer2.Setup(c => c.FetchPartialSeason(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||
.Returns(_parseResults);
|
||||
}
|
||||
|
||||
protected void WithBrokenIndexers()
|
||||
{
|
||||
_indexer1.Setup(c => c.FetchEpisode(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||
.Throws(new Exception());
|
||||
_indexer1.Setup(c => c.FetchDailyEpisode(It.IsAny<string>(), It.IsAny<DateTime>()))
|
||||
.Throws(new Exception());
|
||||
_indexer1.Setup(c => c.FetchPartialSeason(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||
.Throws(new Exception());
|
||||
|
||||
_indexer2.Setup(c => c.FetchEpisode(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||
.Throws(new Exception());
|
||||
_indexer2.Setup(c => c.FetchDailyEpisode(It.IsAny<string>(), It.IsAny<DateTime>()))
|
||||
.Throws(new Exception());
|
||||
_indexer2.Setup(c => c.FetchPartialSeason(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()))
|
||||
.Throws(new Exception());
|
||||
|
||||
_indexer1.SetupGet(c => c.Name).Returns("Indexer1");
|
||||
_indexer1.SetupGet(c => c.Name).Returns("Indexer2");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.IndexerSearch;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Test.Common;
|
||||
using System.Linq;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerSearchTests.PartialSeasonSearchTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class PartialSeasonSearchFixture : IndexerSearchTestBase<PartialSeasonSearch>
|
||||
{
|
||||
|
||||
[Test]
|
||||
public void should_fetch_results_from_indexers()
|
||||
{
|
||||
WithValidIndexers();
|
||||
|
||||
Subject.PerformSearch(_series, new List<Episode> { _episode }, notification)
|
||||
.Should()
|
||||
.HaveCount(20);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_log_error_when_fetching_from_indexer_fails()
|
||||
{
|
||||
WithBrokenIndexers();
|
||||
|
||||
Subject.PerformSearch(_series, new List<Episode> { _episode }, notification)
|
||||
.Should()
|
||||
.HaveCount(0);
|
||||
|
||||
ExceptionVerification.ExpectedErrors(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_hit_each_indexer_once_for_each_prefix()
|
||||
{
|
||||
WithValidIndexers();
|
||||
|
||||
|
||||
var episodes = Builder<Episode>.CreateListOfSize(4)
|
||||
.All()
|
||||
.With(c => c.SeasonNumber = 1)
|
||||
.Build();
|
||||
|
||||
episodes[0].EpisodeNumber = 1;
|
||||
episodes[1].EpisodeNumber = 5;
|
||||
episodes[2].EpisodeNumber = 10;
|
||||
episodes[3].EpisodeNumber = 15;
|
||||
|
||||
|
||||
Subject.PerformSearch(_series, episodes.ToList(), notification)
|
||||
.Should()
|
||||
.HaveCount(40);
|
||||
|
||||
_indexer1.Verify(v => v.FetchPartialSeason(_series.Title, 1, 0), Times.Once());
|
||||
_indexer1.Verify(v => v.FetchPartialSeason(_series.Title, 1, 1), Times.Once());
|
||||
_indexer2.Verify(v => v.FetchPartialSeason(_series.Title, 1, 0), Times.Once());
|
||||
_indexer2.Verify(v => v.FetchPartialSeason(_series.Title, 1, 1), Times.Once());
|
||||
|
||||
_indexer1.Verify(v => v.FetchPartialSeason(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()), Times.Exactly(2));
|
||||
_indexer2.Verify(v => v.FetchPartialSeason(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>()), Times.Exactly(2));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.IndexerSearch;
|
||||
using NzbDrone.Core.Model;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerSearchTests.PartialSeasonSearchTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class PartialSeasonSearch_EpisodeMatch : CoreTest<PartialSeasonSearch>
|
||||
{
|
||||
private Series _series;
|
||||
private List<Episode> _episodes;
|
||||
private EpisodeParseResult _episodeParseResult;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_series = Builder<Series>
|
||||
.CreateNew()
|
||||
.Build();
|
||||
|
||||
_episodes = Builder<Episode>
|
||||
.CreateListOfSize(10)
|
||||
.All()
|
||||
.With(e => e.SeriesId = _series.Id)
|
||||
.With(e => e.Series = _series)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
_episodeParseResult = Builder<EpisodeParseResult>
|
||||
.CreateNew()
|
||||
.With(p => p.SeasonNumber = 1)
|
||||
.Build();
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_wrongSeason_when_season_does_not_match()
|
||||
{
|
||||
Subject.IsEpisodeMatch(_series, new { SeasonNumber = 2, Episodes = _episodes }, _episodeParseResult)
|
||||
.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_return_error_when_season_matches()
|
||||
{
|
||||
Subject.IsEpisodeMatch(_series, new { SeasonNumber = 1, Episodes = _episodes }, _episodeParseResult)
|
||||
.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,236 +0,0 @@
|
||||
/*
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.Model;
|
||||
using NzbDrone.Core.Model.Notification;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Repository.Search;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.ProviderTests.SearchTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ProcessResultsFixture : CoreTest<TestSearch>
|
||||
{
|
||||
private Series _matchingSeries;
|
||||
private Series _mismatchedSeries;
|
||||
private Series _nullSeries = null;
|
||||
|
||||
private EpisodeSearchResult _episodeSearchResult;
|
||||
private ProgressNotification _notification;
|
||||
|
||||
private List<Episode> _episodes;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_matchingSeries = Builder<Series>.CreateNew()
|
||||
.With(s => s.Id = 79488)
|
||||
.With(s => s.Title = "30 Rock")
|
||||
.Build();
|
||||
|
||||
_mismatchedSeries = Builder<Series>.CreateNew()
|
||||
.With(s => s.Id = 12345)
|
||||
.With(s => s.Title = "Not 30 Rock")
|
||||
.Build();
|
||||
|
||||
_episodeSearchResult = new EpisodeSearchResult();
|
||||
_notification = new ProgressNotification("Test");
|
||||
|
||||
_episodes = Builder<Episode>
|
||||
.CreateListOfSize(1)
|
||||
.Build().ToList();
|
||||
|
||||
Mocker.GetMock<IEpisodeService>()
|
||||
.Setup(s => s.GetEpisodesByParseResult(It.IsAny<EpisodeParseResult>()))
|
||||
.Returns(_episodes);
|
||||
}
|
||||
|
||||
private void WithMatchingSeries()
|
||||
{
|
||||
Mocker.GetMock<ISeriesRepository>()
|
||||
.Setup(s => s.GetByTitle(It.IsAny<string>())).Returns(_matchingSeries);
|
||||
}
|
||||
|
||||
private void WithMisMatchedSeries()
|
||||
{
|
||||
Mocker.GetMock<ISeriesRepository>()
|
||||
.Setup(s => s.GetByTitle(It.IsAny<string>())).Returns(_mismatchedSeries);
|
||||
}
|
||||
|
||||
private void WithNullSeries()
|
||||
{
|
||||
Mocker.GetMock<ISeriesRepository>()
|
||||
.Setup(s => s.GetByTitle(It.IsAny<string>())).Returns(_nullSeries);
|
||||
}
|
||||
|
||||
private void WithSuccessfulDownload()
|
||||
{
|
||||
Mocker.GetMock<DownloadProvider>()
|
||||
.Setup(s => s.DownloadReport(It.IsAny<EpisodeParseResult>()))
|
||||
.Returns(true);
|
||||
}
|
||||
|
||||
private void WithFailingDownload()
|
||||
{
|
||||
Mocker.GetMock<DownloadProvider>()
|
||||
.Setup(s => s.DownloadReport(It.IsAny<EpisodeParseResult>()))
|
||||
.Returns(false);
|
||||
}
|
||||
|
||||
private void WithApprovedDecisions()
|
||||
{
|
||||
Mocker.GetMock<IDownloadDirector>()
|
||||
.Setup(s => s.GetDownloadDecision(It.IsAny<EpisodeParseResult>()))
|
||||
.Returns(new DownloadDecision(new string[0]));
|
||||
}
|
||||
|
||||
private void WithDeclinedDecisions()
|
||||
{
|
||||
Mocker.GetMock<IDownloadDirector>()
|
||||
.Setup(s => s.GetDownloadDecision(It.IsAny<EpisodeParseResult>()))
|
||||
.Returns(new DownloadDecision(new[] { "Rejection reason" }));
|
||||
}
|
||||
|
||||
Times.Once());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[Test]
|
||||
public void should_skip_if_series_does_not_match_searched_series()
|
||||
{
|
||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
||||
.All()
|
||||
.With(e => e.SeasonNumber = 1)
|
||||
.With(e => e.EpisodeNumbers = new List<int> { 1 })
|
||||
.With(e => e.Quality = new QualityModel(Quality.HDTV720p, false))
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
WithMisMatchedSeries();
|
||||
|
||||
|
||||
var result = Subject.ProcessReports(_matchingSeries, new { }, parseResults, _episodeSearchResult, _notification);
|
||||
|
||||
|
||||
result.SearchHistoryItems.Should().HaveCount(parseResults.Count);
|
||||
result.SearchHistoryItems.Should().NotContain(s => s.Success);
|
||||
|
||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
||||
Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_skip_if_episode_was_already_downloaded()
|
||||
{
|
||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(e => e.SeasonNumber = 1)
|
||||
.With(e => e.EpisodeNumbers = new List<int> { 5 })
|
||||
.With(c => c.Quality = new QualityModel(Quality.DVD, true))
|
||||
.TheLast(1)
|
||||
.With(e => e.EpisodeNumbers = new List<int> { 1, 2, 3, 4, 5 })
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
WithMatchingSeries();
|
||||
WithQualityNeeded();
|
||||
WithSuccessfulDownload();
|
||||
|
||||
|
||||
var result = Subject.ProcessReports(_matchingSeries, new { }, parseResults, _episodeSearchResult, _notification);
|
||||
|
||||
|
||||
result.SearchHistoryItems.Should().HaveCount(parseResults.Count);
|
||||
result.SearchHistoryItems.Should().Contain(s => s.Success);
|
||||
|
||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
||||
Times.Once());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_try_next_report_if_download_fails()
|
||||
{
|
||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(e => e.SeasonNumber = 1)
|
||||
.With(e => e.EpisodeNumbers = new List<int> { 1 })
|
||||
.With(c => c.Quality = new QualityModel(Quality.DVD, true))
|
||||
.TheLast(1)
|
||||
.With(c => c.Quality = new QualityModel(Quality.SDTV, true))
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
WithMatchingSeries();
|
||||
WithQualityNeeded();
|
||||
|
||||
Mocker.GetMock<DownloadProvider>()
|
||||
.Setup(s => s.DownloadReport(It.Is<EpisodeParseResult>(d => d.Quality.Quality == Quality.DVD)))
|
||||
.Returns(false);
|
||||
|
||||
Mocker.GetMock<DownloadProvider>()
|
||||
.Setup(s => s.DownloadReport(It.Is<EpisodeParseResult>(d => d.Quality.Quality == Quality.SDTV)))
|
||||
.Returns(true);
|
||||
|
||||
|
||||
var result = Subject.ProcessReports(_matchingSeries, new { }, parseResults, _episodeSearchResult, _notification);
|
||||
|
||||
|
||||
result.SearchHistoryItems.Should().HaveCount(parseResults.Count);
|
||||
result.SearchHistoryItems.Should().Contain(s => s.Success);
|
||||
|
||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
||||
Times.Exactly(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_valid_successes_when_one_or_more_downloaded()
|
||||
{
|
||||
var parseResults = Builder<EpisodeParseResult>.CreateListOfSize(5)
|
||||
.All()
|
||||
.With(e => e.SeasonNumber = 1)
|
||||
.With(e => e.EpisodeNumbers = new List<int> { 1 })
|
||||
.With(c => c.Quality = new QualityModel(Quality.DVD, true))
|
||||
.With(c => c.Age = 10)
|
||||
.Random(1)
|
||||
.With(c => c.Quality = new QualityModel(Quality.Bluray1080p, true))
|
||||
.With(c => c.Age = 100)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
WithMatchingSeries();
|
||||
WithSuccessfulDownload();
|
||||
|
||||
Mocker.GetMock<DownloadDirector>()
|
||||
.Setup(s => s.IsDownloadPermitted(It.Is<EpisodeParseResult>(d => d.Quality.Quality == Quality.Bluray1080p)))
|
||||
.Returns(ReportRejectionReasons.None);
|
||||
|
||||
|
||||
var result = Subject.ProcessReports(_matchingSeries, new { }, parseResults, _episodeSearchResult, _notification);
|
||||
|
||||
|
||||
result.Successes.Should().NotBeNull();
|
||||
result.Successes.Should().NotBeEmpty();
|
||||
|
||||
Mocker.GetMock<DownloadDirector>().Verify(c => c.IsDownloadPermitted(It.IsAny<EpisodeParseResult>()),
|
||||
Times.Once());
|
||||
Mocker.GetMock<DownloadProvider>().Verify(c => c.DownloadReport(It.IsAny<EpisodeParseResult>()),
|
||||
Times.Once());
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
{
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
{
|
||||
public class SearchDefinitionFixture : CoreTest<SingleEpisodeSearchDefinition>
|
||||
{
|
||||
[TestCase("Betty White's Off Their Rockers", Result = "Betty+Whites+Off+Their+Rockers")]
|
||||
[TestCase("Star Wars: The Clone Wars", Result = "Star+Wars+The+Clone+Wars")]
|
||||
[TestCase("Hawaii Five-0", Result = "Hawaii+Five+0")]
|
||||
[TestCase("Franklin & Bash", Result = "Franklin+and+Bash")]
|
||||
public string should_replace_some_special_characters(string input)
|
||||
{
|
||||
Subject.SceneTitle = input;
|
||||
return Subject.QueryTitle;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Core.DataAugmentation;
|
||||
using NzbDrone.Core.DataAugmentation.Scene;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.IndexerSearch;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Model;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.Test.IndexerSearchTests
|
||||
{
|
||||
public class TestSearch : IndexerSearchBase
|
||||
{
|
||||
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public TestSearch(IEpisodeService episodeService, IDownloadService downloadService,
|
||||
IIndexerService indexerService, ISceneMappingService sceneMappingService,
|
||||
IDownloadDirector downloadDirector, ISeriesRepository seriesRepository)
|
||||
: base(seriesRepository, episodeService, downloadService, indexerService, sceneMappingService,
|
||||
downloadDirector)
|
||||
{
|
||||
}
|
||||
|
||||
public override List<EpisodeParseResult> PerformSearch(Series series, List<Episode> episodes, Model.Notification.ProgressNotification notification)
|
||||
{
|
||||
var episode = episodes.Single();
|
||||
|
||||
var reports = new List<EpisodeParseResult>();
|
||||
var title = GetSearchTitle(series);
|
||||
|
||||
var seasonNumber = episode.SeasonNumber;
|
||||
var episodeNumber = episode.EpisodeNumber;
|
||||
|
||||
Parallel.ForEach(_indexerService.GetEnabledIndexers(), indexer =>
|
||||
{
|
||||
try
|
||||
{
|
||||
reports.AddRange(indexer.FetchEpisode(title, seasonNumber, episodeNumber));
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.ErrorException(String.Format("An error has occurred while searching for {0}-S{1:00}E{2:00} from: {3}",
|
||||
series.Title, episode.SeasonNumber, episode.EpisodeNumber, indexer.Name), e);
|
||||
}
|
||||
});
|
||||
|
||||
return reports;
|
||||
}
|
||||
|
||||
public override bool IsEpisodeMatch(Series series, dynamic options, EpisodeParseResult episodeParseResult)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
Copyright (c) 2010 Darren Cauthon
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
@ -1,24 +0,0 @@
|
||||
<ProjectConfiguration>
|
||||
<CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
|
||||
<ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing>
|
||||
<PreloadReferencedAssemblies>false</PreloadReferencedAssemblies>
|
||||
<AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
|
||||
<AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
|
||||
<IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely>
|
||||
<RunPreBuildEvents>true</RunPreBuildEvents>
|
||||
<RunPostBuildEvents>true</RunPostBuildEvents>
|
||||
<PreviouslyBuiltSuccessfully>true</PreviouslyBuiltSuccessfully>
|
||||
<InstrumentAssembly>true</InstrumentAssembly>
|
||||
<PreventSigningOfAssembly>false</PreventSigningOfAssembly>
|
||||
<AnalyseExecutionTimes>true</AnalyseExecutionTimes>
|
||||
<IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
|
||||
<DefaultTestTimeout>60000</DefaultTestTimeout>
|
||||
<UseBuildConfiguration></UseBuildConfiguration>
|
||||
<UseBuildPlatform></UseBuildPlatform>
|
||||
<ProxyProcessPath></ProxyProcessPath>
|
||||
<UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
|
||||
<MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
|
||||
<BuildProcessArchitecture>x86</BuildProcessArchitecture>
|
||||
<AdditionalFilesToInclude>..\Libraries\Sqlite\sqlite3.dll</AdditionalFilesToInclude>
|
||||
<HiddenWarnings>PostBuildEventDisabled;PreBuildEventDisabled</HiddenWarnings>
|
||||
</ProjectConfiguration>
|
@ -1,230 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TestRecord xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
||||
<Tests>
|
||||
<TestRecord Name="NzbDrone">
|
||||
<Tests>
|
||||
<TestRecord Name="Core">
|
||||
<Tests>
|
||||
<TestRecord Name="Test">
|
||||
<Tests>
|
||||
<TestRecord Name="ProviderTests">
|
||||
<Tests>
|
||||
<TestRecord Name="ReferenceDataProviderTest">
|
||||
<Results>
|
||||
<UnitTestResult>
|
||||
<TestDate>2013-02-16T21:37:42</TestDate>
|
||||
<Passed>0</Passed>
|
||||
<Errors>0</Errors>
|
||||
<Failures>0</Failures>
|
||||
<Inconclusive>0</Inconclusive>
|
||||
<NotRunnable>0</NotRunnable>
|
||||
<Skipped>0</Skipped>
|
||||
<Ignored>9</Ignored>
|
||||
<Time />
|
||||
<Message>Test successful
|
||||
|
||||
Execution time: 0.47ms</Message>
|
||||
</UnitTestResult>
|
||||
</Results>
|
||||
<Tests>
|
||||
<TestRecord Name="broken_service_should_not_cause_this_call_to_fail">
|
||||
<Results>
|
||||
<UnitTestResult>
|
||||
<TestDate>2013-02-16T21:37:42</TestDate>
|
||||
<Passed>0</Passed>
|
||||
<Errors>0</Errors>
|
||||
<Failures>0</Failures>
|
||||
<Inconclusive>0</Inconclusive>
|
||||
<NotRunnable>0</NotRunnable>
|
||||
<Skipped>0</Skipped>
|
||||
<Ignored>1</Ignored>
|
||||
<Time />
|
||||
<Message>SetUp : SqlCe is not supported in mono.</Message>
|
||||
<StackTrace> at NzbDrone.Core.Test.Framework.SqlCeTest.CoreTestSetup () [0x00000] in <filename unknown>:0
|
||||
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&)
|
||||
at System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x000d5] in C:\cygwin\tmp\monobuild\build\BUILD\mono-2.10.9\mcs\class\corlib\System.Reflection\MonoMethod.cs:226 </StackTrace>
|
||||
<ConsoleOutput />
|
||||
<ConsoleError />
|
||||
</UnitTestResult>
|
||||
</Results>
|
||||
</TestRecord>
|
||||
<TestRecord Name="GetDailySeriesIds_should_return_empty_list_of_int_when_server_is_unavailable">
|
||||
<Results>
|
||||
<UnitTestResult>
|
||||
<TestDate>2013-02-16T21:37:42</TestDate>
|
||||
<Passed>0</Passed>
|
||||
<Errors>0</Errors>
|
||||
<Failures>0</Failures>
|
||||
<Inconclusive>0</Inconclusive>
|
||||
<NotRunnable>0</NotRunnable>
|
||||
<Skipped>0</Skipped>
|
||||
<Ignored>1</Ignored>
|
||||
<Time />
|
||||
<Message>SetUp : SqlCe is not supported in mono.</Message>
|
||||
<StackTrace> at NzbDrone.Core.Test.Framework.SqlCeTest.CoreTestSetup () [0x00000] in <filename unknown>:0
|
||||
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&)
|
||||
at System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x000d5] in C:\cygwin\tmp\monobuild\build\BUILD\mono-2.10.9\mcs\class\corlib\System.Reflection\MonoMethod.cs:226 </StackTrace>
|
||||
<ConsoleOutput />
|
||||
<ConsoleError />
|
||||
</UnitTestResult>
|
||||
</Results>
|
||||
</TestRecord>
|
||||
<TestRecord Name="GetDailySeriesIds_should_return_empty_list_when_unable_to_parse">
|
||||
<Results>
|
||||
<UnitTestResult>
|
||||
<TestDate>2013-02-16T21:37:42</TestDate>
|
||||
<Passed>0</Passed>
|
||||
<Errors>0</Errors>
|
||||
<Failures>0</Failures>
|
||||
<Inconclusive>0</Inconclusive>
|
||||
<NotRunnable>0</NotRunnable>
|
||||
<Skipped>0</Skipped>
|
||||
<Ignored>1</Ignored>
|
||||
<Time />
|
||||
<Message>SetUp : SqlCe is not supported in mono.</Message>
|
||||
<StackTrace> at NzbDrone.Core.Test.Framework.SqlCeTest.CoreTestSetup () [0x00000] in <filename unknown>:0
|
||||
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&)
|
||||
at System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x000d5] in C:\cygwin\tmp\monobuild\build\BUILD\mono-2.10.9\mcs\class\corlib\System.Reflection\MonoMethod.cs:226 </StackTrace>
|
||||
<ConsoleOutput />
|
||||
<ConsoleError />
|
||||
</UnitTestResult>
|
||||
</Results>
|
||||
</TestRecord>
|
||||
<TestRecord Name="GetDailySeriesIds_should_return_list_of_int_when_all_are_valid">
|
||||
<Results>
|
||||
<UnitTestResult>
|
||||
<TestDate>2013-02-16T21:37:42</TestDate>
|
||||
<Passed>0</Passed>
|
||||
<Errors>0</Errors>
|
||||
<Failures>0</Failures>
|
||||
<Inconclusive>0</Inconclusive>
|
||||
<NotRunnable>0</NotRunnable>
|
||||
<Skipped>0</Skipped>
|
||||
<Ignored>1</Ignored>
|
||||
<Time />
|
||||
<Message>SetUp : SqlCe is not supported in mono.</Message>
|
||||
<StackTrace> at NzbDrone.Core.Test.Framework.SqlCeTest.CoreTestSetup () [0x00000] in <filename unknown>:0
|
||||
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&)
|
||||
at System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x000d5] in C:\cygwin\tmp\monobuild\build\BUILD\mono-2.10.9\mcs\class\corlib\System.Reflection\MonoMethod.cs:226 </StackTrace>
|
||||
<ConsoleOutput />
|
||||
<ConsoleError />
|
||||
</UnitTestResult>
|
||||
</Results>
|
||||
</TestRecord>
|
||||
<TestRecord Name="IsDailySeries_should_return_false">
|
||||
<Results>
|
||||
<UnitTestResult>
|
||||
<TestDate>2013-02-16T21:37:42</TestDate>
|
||||
<Passed>0</Passed>
|
||||
<Errors>0</Errors>
|
||||
<Failures>0</Failures>
|
||||
<Inconclusive>0</Inconclusive>
|
||||
<NotRunnable>0</NotRunnable>
|
||||
<Skipped>0</Skipped>
|
||||
<Ignored>1</Ignored>
|
||||
<Time />
|
||||
<Message>SetUp : SqlCe is not supported in mono.</Message>
|
||||
<StackTrace> at NzbDrone.Core.Test.Framework.SqlCeTest.CoreTestSetup () [0x00000] in <filename unknown>:0
|
||||
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&)
|
||||
at System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x000d5] in C:\cygwin\tmp\monobuild\build\BUILD\mono-2.10.9\mcs\class\corlib\System.Reflection\MonoMethod.cs:226 </StackTrace>
|
||||
<ConsoleOutput />
|
||||
<ConsoleError />
|
||||
</UnitTestResult>
|
||||
</Results>
|
||||
</TestRecord>
|
||||
<TestRecord Name="IsDailySeries_should_return_true">
|
||||
<Results>
|
||||
<UnitTestResult>
|
||||
<TestDate>2013-02-16T21:37:42</TestDate>
|
||||
<Passed>0</Passed>
|
||||
<Errors>0</Errors>
|
||||
<Failures>0</Failures>
|
||||
<Inconclusive>0</Inconclusive>
|
||||
<NotRunnable>0</NotRunnable>
|
||||
<Skipped>0</Skipped>
|
||||
<Ignored>1</Ignored>
|
||||
<Time />
|
||||
<Message>SetUp : SqlCe is not supported in mono.</Message>
|
||||
<StackTrace> at NzbDrone.Core.Test.Framework.SqlCeTest.CoreTestSetup () [0x00000] in <filename unknown>:0
|
||||
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&)
|
||||
at System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x000d5] in C:\cygwin\tmp\monobuild\build\BUILD\mono-2.10.9\mcs\class\corlib\System.Reflection\MonoMethod.cs:226 </StackTrace>
|
||||
<ConsoleOutput />
|
||||
<ConsoleError />
|
||||
</UnitTestResult>
|
||||
</Results>
|
||||
</TestRecord>
|
||||
<TestRecord Name="UpdateDailySeries_should_update_series_should_not_overwrite_existing_isDaily">
|
||||
<Results>
|
||||
<UnitTestResult>
|
||||
<TestDate>2013-02-16T21:37:42</TestDate>
|
||||
<Passed>0</Passed>
|
||||
<Errors>0</Errors>
|
||||
<Failures>0</Failures>
|
||||
<Inconclusive>0</Inconclusive>
|
||||
<NotRunnable>0</NotRunnable>
|
||||
<Skipped>0</Skipped>
|
||||
<Ignored>1</Ignored>
|
||||
<Time />
|
||||
<Message>SetUp : SqlCe is not supported in mono.</Message>
|
||||
<StackTrace> at NzbDrone.Core.Test.Framework.SqlCeTest.CoreTestSetup () [0x00000] in <filename unknown>:0
|
||||
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&)
|
||||
at System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x000d5] in C:\cygwin\tmp\monobuild\build\BUILD\mono-2.10.9\mcs\class\corlib\System.Reflection\MonoMethod.cs:226 </StackTrace>
|
||||
<ConsoleOutput />
|
||||
<ConsoleError />
|
||||
</UnitTestResult>
|
||||
</Results>
|
||||
</TestRecord>
|
||||
<TestRecord Name="UpdateDailySeries_should_update_series_should_skip_series_that_dont_match">
|
||||
<Results>
|
||||
<UnitTestResult>
|
||||
<TestDate>2013-02-16T21:37:42</TestDate>
|
||||
<Passed>0</Passed>
|
||||
<Errors>0</Errors>
|
||||
<Failures>0</Failures>
|
||||
<Inconclusive>0</Inconclusive>
|
||||
<NotRunnable>0</NotRunnable>
|
||||
<Skipped>0</Skipped>
|
||||
<Ignored>1</Ignored>
|
||||
<Time />
|
||||
<Message>SetUp : SqlCe is not supported in mono.</Message>
|
||||
<StackTrace> at NzbDrone.Core.Test.Framework.SqlCeTest.CoreTestSetup () [0x00000] in <filename unknown>:0
|
||||
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&)
|
||||
at System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x000d5] in C:\cygwin\tmp\monobuild\build\BUILD\mono-2.10.9\mcs\class\corlib\System.Reflection\MonoMethod.cs:226 </StackTrace>
|
||||
<ConsoleOutput />
|
||||
<ConsoleError />
|
||||
</UnitTestResult>
|
||||
</Results>
|
||||
</TestRecord>
|
||||
<TestRecord Name="UpdateDailySeries_should_update_series_that_match_daily_series_list">
|
||||
<Results>
|
||||
<UnitTestResult>
|
||||
<TestDate>2013-02-16T21:37:42</TestDate>
|
||||
<Passed>0</Passed>
|
||||
<Errors>0</Errors>
|
||||
<Failures>0</Failures>
|
||||
<Inconclusive>0</Inconclusive>
|
||||
<NotRunnable>0</NotRunnable>
|
||||
<Skipped>0</Skipped>
|
||||
<Ignored>1</Ignored>
|
||||
<Time />
|
||||
<Message>SetUp : SqlCe is not supported in mono.</Message>
|
||||
<StackTrace> at NzbDrone.Core.Test.Framework.SqlCeTest.CoreTestSetup () [0x00000] in <filename unknown>:0
|
||||
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&)
|
||||
at System.Reflection.MonoMethod.Invoke (System.Object obj, BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x000d5] in C:\cygwin\tmp\monobuild\build\BUILD\mono-2.10.9\mcs\class\corlib\System.Reflection\MonoMethod.cs:226 </StackTrace>
|
||||
<ConsoleOutput />
|
||||
<ConsoleError />
|
||||
</UnitTestResult>
|
||||
</Results>
|
||||
</TestRecord>
|
||||
</Tests>
|
||||
</TestRecord>
|
||||
</Tests>
|
||||
</TestRecord>
|
||||
</Tests>
|
||||
</TestRecord>
|
||||
</Tests>
|
||||
</TestRecord>
|
||||
</Tests>
|
||||
</TestRecord>
|
||||
</Tests>
|
||||
</TestRecord>
|
@ -0,0 +1,64 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.DecisionEngine.Specifications.Search;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
public interface IMakeDownloadDecision
|
||||
{
|
||||
IEnumerable<DownloadDecision> GetRssDecision(IEnumerable<EpisodeParseResult> episodeParseResults);
|
||||
IEnumerable<DownloadDecision> GetSearchDecision(IEnumerable<EpisodeParseResult> episodeParseResult, SearchDefinitionBase searchDefinitionBase);
|
||||
}
|
||||
|
||||
public class DownloadDecisionMaker : IMakeDownloadDecision
|
||||
{
|
||||
private readonly IEnumerable<IRejectWithReason> _specifications;
|
||||
|
||||
public DownloadDecisionMaker(IEnumerable<IRejectWithReason> specifications)
|
||||
{
|
||||
_specifications = specifications;
|
||||
}
|
||||
|
||||
public IEnumerable<DownloadDecision> GetRssDecision(IEnumerable<EpisodeParseResult> episodeParseResults)
|
||||
{
|
||||
foreach (var parseResult in episodeParseResults)
|
||||
{
|
||||
parseResult.Decision = new DownloadDecision(parseResult, GetGeneralRejectionReasons(parseResult).ToArray());
|
||||
yield return parseResult.Decision;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public IEnumerable<DownloadDecision> GetSearchDecision(IEnumerable<EpisodeParseResult> episodeParseResults, SearchDefinitionBase searchDefinitionBase)
|
||||
{
|
||||
foreach (var parseResult in episodeParseResults)
|
||||
{
|
||||
var generalReasons = GetGeneralRejectionReasons(parseResult);
|
||||
var searchReasons = GetSearchRejectionReasons(parseResult, searchDefinitionBase);
|
||||
|
||||
parseResult.Decision = new DownloadDecision(parseResult, generalReasons.Union(searchReasons).ToArray());
|
||||
|
||||
yield return parseResult.Decision;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private IEnumerable<string> GetGeneralRejectionReasons(EpisodeParseResult episodeParseResult)
|
||||
{
|
||||
return _specifications
|
||||
.OfType<IDecisionEngineSpecification>()
|
||||
.Where(spec => !spec.IsSatisfiedBy(episodeParseResult))
|
||||
.Select(spec => spec.RejectionReason);
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetSearchRejectionReasons(EpisodeParseResult episodeParseResult, SearchDefinitionBase searchDefinitionBase)
|
||||
{
|
||||
return _specifications
|
||||
.OfType<IDecisionEngineSearchSpecification>()
|
||||
.Where(spec => !spec.IsSatisfiedBy(episodeParseResult, searchDefinitionBase))
|
||||
.Select(spec => spec.RejectionReason);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
public interface IDownloadDirector
|
||||
{
|
||||
DownloadDecision GetDownloadDecision(EpisodeParseResult episodeParseResult);
|
||||
}
|
||||
|
||||
public class DownloadDirector : IDownloadDirector
|
||||
{
|
||||
private readonly IEnumerable<IFetchableSpecification> _specifications;
|
||||
|
||||
public DownloadDirector(IEnumerable<IFetchableSpecification> specifications)
|
||||
{
|
||||
_specifications = specifications;
|
||||
}
|
||||
|
||||
public DownloadDecision GetDownloadDecision(EpisodeParseResult episodeParseResult)
|
||||
{
|
||||
var rejections = _specifications
|
||||
.Where(spec => !spec.IsSatisfiedBy(episodeParseResult))
|
||||
.Select(spec => spec.RejectionReason).ToArray();
|
||||
|
||||
episodeParseResult.Decision = new DownloadDecision(rejections);
|
||||
|
||||
return episodeParseResult.Decision;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
public interface IRejectWithReason
|
||||
{
|
||||
string RejectionReason { get; }
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Model;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class DailyEpisodeMatchSpecification : IDecisionEngineSearchSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
|
||||
public DailyEpisodeMatchSpecification(Logger logger, IEpisodeService episodeService)
|
||||
{
|
||||
_logger = logger;
|
||||
_episodeService = episodeService;
|
||||
}
|
||||
|
||||
public string RejectionReason
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Episode doesn't match";
|
||||
}
|
||||
}
|
||||
public bool IsSatisfiedBy(EpisodeParseResult episodeParseResult, SearchDefinitionBase searchDefinitionBase)
|
||||
{
|
||||
var dailySearchSpec = searchDefinitionBase as DailyEpisodeSearchDefinition;
|
||||
|
||||
if (dailySearchSpec == null) return true;
|
||||
|
||||
var episode = _episodeService.GetEpisode(dailySearchSpec.SeriesId, dailySearchSpec.Airtime);
|
||||
|
||||
if (!episodeParseResult.AirDate.HasValue || episodeParseResult.AirDate.Value != episode.AirDate.Value)
|
||||
{
|
||||
_logger.Trace("Episode AirDate does not match searched episode number, skipping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
using NzbDrone.Core.IndexerSearch;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public interface IDecisionEngineSearchSpecification : IRejectWithReason
|
||||
{
|
||||
bool IsSatisfiedBy(EpisodeParseResult subject, SearchDefinitionBase searchDefinitionBase);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class SeasonMatchSpecification : IDecisionEngineSearchSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public SeasonMatchSpecification(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string RejectionReason
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Episode doesn't match";
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSatisfiedBy(EpisodeParseResult episodeParseResult, SearchDefinitionBase searchDefinitionBase)
|
||||
{
|
||||
var singleEpisodeSpec = searchDefinitionBase as SeasonSearchDefinition;
|
||||
if (singleEpisodeSpec == null) return true;
|
||||
|
||||
if (singleEpisodeSpec.SeasonNumber != episodeParseResult.SeasonNumber)
|
||||
{
|
||||
_logger.Trace("Season number does not match searched season number, skipping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
using NLog;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
|
||||
{
|
||||
public class SingleEpisodeMatchSpecification : IDecisionEngineSearchSpecification
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public SingleEpisodeMatchSpecification(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string RejectionReason
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Episode doesn't match";
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSatisfiedBy(EpisodeParseResult episodeParseResult, SearchDefinitionBase searchDefinitionBase)
|
||||
{
|
||||
var singleEpisodeSpec = searchDefinitionBase as SingleEpisodeSearchDefinition;
|
||||
if (singleEpisodeSpec == null) return true;
|
||||
|
||||
if (singleEpisodeSpec.SeasonNumber != episodeParseResult.SeasonNumber)
|
||||
{
|
||||
_logger.Trace("Season number does not match searched season number, skipping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!episodeParseResult.EpisodeNumbers.Contains(singleEpisodeSpec.EpisodeNumber))
|
||||
{
|
||||
_logger.Trace("Episode number does not match searched episode number, skipping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnsureThat;
|
||||
using NzbDrone.Core.DataAugmentation;
|
||||
using NzbDrone.Core.DataAugmentation.Scene;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Model;
|
||||
using NzbDrone.Core.Model.Notification;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
public class DailyEpisodeSearch : IndexerSearchBase
|
||||
{
|
||||
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public DailyEpisodeSearch(IEpisodeService episodeService, IDownloadService downloadService, IIndexerService indexerService,
|
||||
ISceneMappingService sceneMappingService, IDownloadDirector downloadDirector,
|
||||
ISeriesRepository seriesRepository)
|
||||
: base(seriesRepository, episodeService, downloadService, indexerService, sceneMappingService,
|
||||
downloadDirector)
|
||||
{
|
||||
}
|
||||
|
||||
public DailyEpisodeSearch()
|
||||
{
|
||||
}
|
||||
|
||||
public override List<EpisodeParseResult> PerformSearch(Series series, List<Episode> episodes, ProgressNotification notification)
|
||||
{
|
||||
var episode = episodes.Single();
|
||||
|
||||
notification.CurrentMessage = "Looking for " + episode;
|
||||
|
||||
var reports = new List<EpisodeParseResult>();
|
||||
var title = GetSearchTitle(series);
|
||||
|
||||
Parallel.ForEach(_indexerService.GetEnabledIndexers(), indexer =>
|
||||
{
|
||||
try
|
||||
{
|
||||
reports.AddRange(indexer.FetchDailyEpisode(title, episode.AirDate.Value));
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.ErrorException(String.Format("An error has occurred while searching for {0} - {1:yyyy-MM-dd} from: {2}",
|
||||
series.Title, episode.AirDate, indexer.Name), e);
|
||||
}
|
||||
});
|
||||
|
||||
return reports;
|
||||
}
|
||||
|
||||
public override bool IsEpisodeMatch(Series series, dynamic options, EpisodeParseResult episodeParseResult)
|
||||
{
|
||||
Episode episode = options.Episode;
|
||||
|
||||
if (!episodeParseResult.AirDate.HasValue || episodeParseResult.AirDate.Value != episode.AirDate.Value)
|
||||
{
|
||||
logger.Trace("Episode AirDate does not match searched episode number, skipping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
{
|
||||
public class DailyEpisodeSearchDefinition : SearchDefinitionBase
|
||||
{
|
||||
public DateTime Airtime { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[{0} : {1}", SceneTitle, Airtime);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
{
|
||||
public class PartialSeasonSearchDefinition : SeasonSearchDefinition
|
||||
{
|
||||
public int Prefix { get; set; }
|
||||
|
||||
public PartialSeasonSearchDefinition(SeasonSearchDefinition seasonSearch, int prefix)
|
||||
{
|
||||
Prefix = prefix;
|
||||
SceneTitle = seasonSearch.SceneTitle;
|
||||
SeasonNumber = seasonSearch.SeasonNumber;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[{0} : S{1:00}E{1:0}*]", SceneTitle, SeasonNumber, Prefix);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
{
|
||||
public abstract class SearchDefinitionBase
|
||||
{
|
||||
private static readonly Regex NoneWord = new Regex(@"[\W]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
private static readonly Regex BeginningThe = new Regex(@"^the\s", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
public int SeriesId { get; set; }
|
||||
public string SceneTitle { get; set; }
|
||||
|
||||
public string QueryTitle
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetQueryTitle(SceneTitle);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetQueryTitle(string title)
|
||||
{
|
||||
var cleanTitle = BeginningThe.Replace(title, String.Empty);
|
||||
|
||||
cleanTitle = cleanTitle
|
||||
.Replace("&", "and")
|
||||
.Replace("`", "")
|
||||
.Replace("'", "");
|
||||
|
||||
cleanTitle = NoneWord.Replace(cleanTitle, "+");
|
||||
|
||||
//remove any repeating +s
|
||||
cleanTitle = Regex.Replace(cleanTitle, @"\+{2,}", "+");
|
||||
return cleanTitle.Trim('+', ' ');
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
{
|
||||
public class SeasonSearchDefinition : SearchDefinitionBase
|
||||
{
|
||||
public int SeasonNumber { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[{0} : S{1:00}]", SceneTitle, SeasonNumber);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
{
|
||||
public class SingleEpisodeSearchDefinition : SearchDefinitionBase
|
||||
{
|
||||
|
||||
//TODO make sure these are populated with scene if required
|
||||
public int EpisodeNumber { get; set; }
|
||||
public int SeasonNumber { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("[{0} : S{1:00}E{2:00} ]", SceneTitle, SeasonNumber, EpisodeNumber);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Core.DataAugmentation;
|
||||
using NzbDrone.Core.DataAugmentation.Scene;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Model;
|
||||
using NzbDrone.Core.Model.Notification;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
public class EpisodeSearch : IndexerSearchBase
|
||||
{
|
||||
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public EpisodeSearch(IEpisodeService episodeService, IDownloadService downloadService, IIndexerService indexerService,
|
||||
ISceneMappingService sceneMappingService, IDownloadDirector downloadDirector,
|
||||
ISeriesRepository seriesRepository)
|
||||
: base(seriesRepository, episodeService, downloadService, indexerService, sceneMappingService,
|
||||
downloadDirector)
|
||||
{
|
||||
}
|
||||
|
||||
public EpisodeSearch()
|
||||
{
|
||||
}
|
||||
|
||||
public override List<EpisodeParseResult> PerformSearch(Series series, List<Episode> episodes, ProgressNotification notification)
|
||||
{
|
||||
//Todo: Daily and Anime or separate them out?
|
||||
//Todo: Epsiodes that use scene numbering
|
||||
|
||||
var episode = episodes.Single();
|
||||
|
||||
|
||||
var reports = new List<EpisodeParseResult>();
|
||||
var title = GetSearchTitle(series);
|
||||
|
||||
var seasonNumber = episode.SeasonNumber;
|
||||
var episodeNumber = episode.EpisodeNumber;
|
||||
|
||||
if (series.UseSceneNumbering)
|
||||
{
|
||||
if (episode.SceneSeasonNumber > 0 && episode.SceneEpisodeNumber > 0)
|
||||
{
|
||||
logger.Trace("Using Scene Numbering for: {0}", episode);
|
||||
seasonNumber = episode.SceneSeasonNumber;
|
||||
episodeNumber = episode.SceneEpisodeNumber;
|
||||
}
|
||||
}
|
||||
|
||||
Parallel.ForEach(_indexerService.GetEnabledIndexers(), indexer =>
|
||||
{
|
||||
try
|
||||
{
|
||||
reports.AddRange(indexer.FetchEpisode(title, seasonNumber, episodeNumber));
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.ErrorException(String.Format("An error has occurred while searching for {0}-S{1:00}E{2:00} from: {3}",
|
||||
series.Title, episode.SeasonNumber, episode.EpisodeNumber, indexer.Name), e);
|
||||
}
|
||||
});
|
||||
|
||||
return reports;
|
||||
}
|
||||
|
||||
public override bool IsEpisodeMatch(Series series, dynamic options, EpisodeParseResult episodeParseResult)
|
||||
{
|
||||
if (series.UseSceneNumbering && options.Episode.SeasonNumber > 0 && options.Episode.EpisodeNumber > 0)
|
||||
{
|
||||
if (options.Episode.SceneSeasonNumber != episodeParseResult.SeasonNumber)
|
||||
{
|
||||
logger.Trace("Season number does not match searched season number, skipping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!episodeParseResult.EpisodeNumbers.Contains(options.Episode.SceneEpisodeNumber))
|
||||
{
|
||||
logger.Trace("Episode number does not match searched episode number, skipping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (options.Episode.SeasonNumber != episodeParseResult.SeasonNumber)
|
||||
{
|
||||
logger.Trace("Season number does not match searched season number, skipping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!episodeParseResult.EpisodeNumbers.Contains(options.Episode.EpisodeNumber))
|
||||
{
|
||||
logger.Trace("Episode number does not match searched episode number, skipping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using NLog;
|
||||
using NzbDrone.Core.DataAugmentation;
|
||||
using NzbDrone.Core.DataAugmentation.Scene;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Model;
|
||||
using NzbDrone.Core.Model.Notification;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
public abstract class IndexerSearchBase
|
||||
{
|
||||
private readonly ISeriesRepository _seriesRepository;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly IDownloadService _downloadService;
|
||||
private readonly ISceneMappingService _sceneMappingService;
|
||||
private readonly IDownloadDirector DownloadDirector;
|
||||
|
||||
protected readonly IIndexerService _indexerService;
|
||||
|
||||
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
protected IndexerSearchBase(ISeriesRepository seriesRepository, IEpisodeService episodeService, IDownloadService downloadService,
|
||||
IIndexerService indexerService, ISceneMappingService sceneMappingService,
|
||||
IDownloadDirector downloadDirector)
|
||||
{
|
||||
_seriesRepository = seriesRepository;
|
||||
_episodeService = episodeService;
|
||||
_downloadService = downloadService;
|
||||
_indexerService = indexerService;
|
||||
_sceneMappingService = sceneMappingService;
|
||||
DownloadDirector = downloadDirector;
|
||||
}
|
||||
|
||||
protected IndexerSearchBase()
|
||||
{
|
||||
}
|
||||
|
||||
public abstract List<EpisodeParseResult> PerformSearch(Series series, List<Episode> episodes, ProgressNotification notification);
|
||||
public abstract bool IsEpisodeMatch(Series series, dynamic options, EpisodeParseResult episodeParseResult);
|
||||
|
||||
public virtual List<int> Search(Series series, dynamic options, ProgressNotification notification)
|
||||
{
|
||||
if (options == null)
|
||||
throw new ArgumentNullException(options);
|
||||
|
||||
|
||||
List<EpisodeParseResult> reports = PerformSearch(series, options, notification);
|
||||
|
||||
logger.Debug("Finished searching all indexers. Total {0}", reports.Count);
|
||||
notification.CurrentMessage = "Processing search results";
|
||||
|
||||
var result = ProcessReports(series, options, reports);
|
||||
|
||||
if (!result.Grabbed.Any())
|
||||
{
|
||||
logger.Warn("Unable to find {0} in any of indexers.", options.Episode);
|
||||
|
||||
notification.CurrentMessage = reports.Any() ? String.Format("Sorry, couldn't find {0}, that matches your preferences.", options.Episode)
|
||||
: String.Format("Sorry, couldn't find {0} in any of indexers.", options.Episode);
|
||||
}
|
||||
|
||||
return result.Grabbed;
|
||||
}
|
||||
|
||||
public void ProcessReports(Series series, dynamic options, List<EpisodeParseResult> episodeParseResults)
|
||||
{
|
||||
|
||||
var sortedResults = episodeParseResults.OrderByDescending(c => c.Quality)
|
||||
.ThenBy(c => c.EpisodeNumbers.MinOrDefault())
|
||||
.ThenBy(c => c.Age);
|
||||
|
||||
foreach (var episodeParseResult in sortedResults)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
logger.Trace("Analyzing report " + episodeParseResult);
|
||||
episodeParseResult.Series = _seriesRepository.GetByTitle(episodeParseResult.CleanTitle);
|
||||
|
||||
if (episodeParseResult.Series == null || episodeParseResult.Series.Id != series.Id)
|
||||
{
|
||||
episodeParseResult.Decision = new DownloadDecision("Invalid Series");
|
||||
continue;
|
||||
}
|
||||
|
||||
episodeParseResult.Episodes = _episodeService.GetEpisodesByParseResult(episodeParseResult);
|
||||
|
||||
|
||||
if (!IsEpisodeMatch(series, options, episodeParseResult))
|
||||
{
|
||||
episodeParseResult.Decision = new DownloadDecision("Incorrect Episode/Season");
|
||||
}
|
||||
|
||||
var downloadDecision = DownloadDirector.GetDownloadDecision(episodeParseResult);
|
||||
|
||||
if (downloadDecision.Approved)
|
||||
{
|
||||
DownloadReport(episodeParseResult);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.ErrorException("An error has occurred while processing parse result items from " + episodeParseResult, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Boolean DownloadReport(EpisodeParseResult episodeParseResult)
|
||||
{
|
||||
logger.Debug("Found '{0}'. Adding to download queue.", episodeParseResult);
|
||||
try
|
||||
{
|
||||
if (_downloadService.DownloadReport(episodeParseResult))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.ErrorException("Unable to add report to download queue." + episodeParseResult, e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual string GetSearchTitle(Series series, int seasonNumber = -1)
|
||||
{
|
||||
var seasonTitle = _sceneMappingService.GetSceneName(series.Id, seasonNumber);
|
||||
|
||||
if (!String.IsNullOrWhiteSpace(seasonTitle))
|
||||
return seasonTitle;
|
||||
|
||||
var title = _sceneMappingService.GetSceneName(series.Id);
|
||||
|
||||
if (String.IsNullOrWhiteSpace(title))
|
||||
{
|
||||
title = series.Title;
|
||||
title = title.Replace("&", "and");
|
||||
title = Regex.Replace(title, @"[^\w\d\s\-]", "");
|
||||
}
|
||||
|
||||
return title;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Core.DataAugmentation.Scene;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Model;
|
||||
using NzbDrone.Core.Tv;
|
||||
using System.Linq;
|
||||
|
||||
namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
public interface ISearchForNzb
|
||||
{
|
||||
List<DownloadDecision> SearchSingle(int seriesId, int seasonNumber, int episodeNumber);
|
||||
List<DownloadDecision> SearchDaily(int seriesId, DateTime airDate);
|
||||
List<DownloadDecision> SearchSeason(int seriesId, int seasonNumber);
|
||||
}
|
||||
|
||||
public class NzbSearchService : ISearchForNzb
|
||||
{
|
||||
private readonly IIndexerService _indexerService;
|
||||
private readonly IFetchFeedFromIndexers _feedFetcher;
|
||||
private readonly ISceneMappingService _sceneMapping;
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly IEpisodeService _episodeService;
|
||||
private readonly IMakeDownloadDecision _makeDownloadDecision;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public NzbSearchService(IIndexerService indexerService, IFetchFeedFromIndexers feedFetcher, ISceneMappingService sceneMapping, ISeriesService seriesService, IEpisodeService episodeService, IMakeDownloadDecision makeDownloadDecision, Logger logger)
|
||||
{
|
||||
_indexerService = indexerService;
|
||||
_feedFetcher = feedFetcher;
|
||||
_sceneMapping = sceneMapping;
|
||||
_seriesService = seriesService;
|
||||
_episodeService = episodeService;
|
||||
_makeDownloadDecision = makeDownloadDecision;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public List<DownloadDecision> SearchSingle(int seriesId, int seasonNumber, int episodeNumber)
|
||||
{
|
||||
var searchSpec = Get<SingleEpisodeSearchDefinition>(seriesId, seasonNumber);
|
||||
|
||||
if (_seriesService.GetSeries(seriesId).UseSceneNumbering)
|
||||
{
|
||||
var episode = _episodeService.GetEpisode(seriesId, seasonNumber, episodeNumber);
|
||||
searchSpec.EpisodeNumber = episode.SceneEpisodeNumber;
|
||||
searchSpec.SeasonNumber = episode.SceneSeasonNumber;
|
||||
}
|
||||
else
|
||||
{
|
||||
searchSpec.EpisodeNumber = episodeNumber;
|
||||
searchSpec.SeasonNumber = seasonNumber;
|
||||
}
|
||||
|
||||
return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
|
||||
}
|
||||
|
||||
public List<DownloadDecision> SearchDaily(int seriesId, DateTime airDate)
|
||||
{
|
||||
var searchSpec = Get<DailyEpisodeSearchDefinition>(seriesId);
|
||||
searchSpec.Airtime = airDate;
|
||||
|
||||
return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
|
||||
}
|
||||
|
||||
public List<DownloadDecision> SearchSeason(int seriesId, int seasonNumber)
|
||||
{
|
||||
var searchSpec = Get<SeasonSearchDefinition>(seriesId, seasonNumber);
|
||||
searchSpec.SeasonNumber = seasonNumber;
|
||||
|
||||
return Dispatch(indexer => _feedFetcher.Fetch(indexer, searchSpec), searchSpec);
|
||||
}
|
||||
|
||||
private List<DownloadDecision> PartialSeasonSearch(SeasonSearchDefinition search)
|
||||
{
|
||||
var episodesNumbers = _episodeService.GetEpisodesBySeason(search.SeriesId, search.SeasonNumber).Select(c => c.EpisodeNumber);
|
||||
var prefixes = episodesNumbers
|
||||
.Select(i => i / 10)
|
||||
.Distinct()
|
||||
.Select(prefix => new PartialSeasonSearchDefinition(search, prefix));
|
||||
|
||||
var result = new List<DownloadDecision>();
|
||||
|
||||
foreach (var partialSeasonSearchSpec in prefixes)
|
||||
{
|
||||
var spec = partialSeasonSearchSpec;
|
||||
result.AddRange(Dispatch(indexer => _feedFetcher.Fetch(indexer, spec), partialSeasonSearchSpec));
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private TSpec Get<TSpec>(int seriesId, int seasonNumber = -1) where TSpec : SearchDefinitionBase, new()
|
||||
{
|
||||
var spec = new TSpec();
|
||||
|
||||
spec.SeriesId = seriesId;
|
||||
spec.SceneTitle = _sceneMapping.GetSceneName(seriesId, seasonNumber);
|
||||
|
||||
return spec;
|
||||
}
|
||||
|
||||
private List<DownloadDecision> Dispatch(Func<IIndexerBase, IEnumerable<EpisodeParseResult>> searchAction, SearchDefinitionBase definitionBase)
|
||||
{
|
||||
var indexers = _indexerService.GetAvailableIndexers();
|
||||
var parseResults = new List<EpisodeParseResult>();
|
||||
|
||||
Parallel.ForEach(indexers, indexer =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var indexerReports = searchAction(indexer);
|
||||
lock (indexer)
|
||||
{
|
||||
parseResults.AddRange(indexerReports);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.ErrorException(String.Format("An error has occurred while searching for {0} from: {1}", definitionBase, indexer.Name), e);
|
||||
}
|
||||
});
|
||||
|
||||
_logger.Debug("Total of {0} reports were found for {1} in {2} indexers", parseResults.Count, definitionBase, indexers.Count);
|
||||
|
||||
return _makeDownloadDecision.GetSearchDecision(parseResults, definitionBase).ToList();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NLog;
|
||||
using NzbDrone.Core.DataAugmentation;
|
||||
using NzbDrone.Core.DataAugmentation.Scene;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Indexers;
|
||||
using NzbDrone.Core.Model;
|
||||
using NzbDrone.Core.Model.Notification;
|
||||
using NzbDrone.Core.Tv;
|
||||
|
||||
namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
public class PartialSeasonSearch : IndexerSearchBase
|
||||
{
|
||||
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public PartialSeasonSearch(IEpisodeService episodeService, IDownloadService downloadService, IIndexerService indexerService,
|
||||
ISceneMappingService sceneMappingService, IDownloadDirector downloadDirector,
|
||||
ISeriesRepository seriesRepository)
|
||||
: base(seriesRepository, episodeService, downloadService, indexerService, sceneMappingService,
|
||||
downloadDirector)
|
||||
{
|
||||
}
|
||||
|
||||
public PartialSeasonSearch()
|
||||
{
|
||||
}
|
||||
|
||||
public override List<EpisodeParseResult> PerformSearch(Series series, List<Episode> episodes, ProgressNotification notification)
|
||||
{
|
||||
var seasons = episodes.Select(c => c.SeasonNumber).Distinct().ToList();
|
||||
|
||||
if (seasons.Count > 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("episodes", "episode list contains episodes from more than one season");
|
||||
}
|
||||
|
||||
var seasonNumber = seasons[0];
|
||||
notification.CurrentMessage = String.Format("Looking for {0} - Season {1}", series.Title, seasonNumber);
|
||||
|
||||
var reports = new List<EpisodeParseResult>();
|
||||
object reportsLock = new object();
|
||||
|
||||
var title = GetSearchTitle(series);
|
||||
var prefixes = GetEpisodeNumberPrefixes(episodes.Select(e => e.EpisodeNumber));
|
||||
|
||||
foreach (var p in prefixes)
|
||||
{
|
||||
var prefix = p;
|
||||
|
||||
Parallel.ForEach(_indexerService.GetEnabledIndexers(), indexer =>
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (reportsLock)
|
||||
{
|
||||
reports.AddRange(indexer.FetchPartialSeason(title, seasonNumber, prefix));
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.ErrorException(
|
||||
String.Format(
|
||||
"An error has occurred while searching for {0} Season {1:00} Prefix: {2} from: {3}",
|
||||
series.Title, seasonNumber, prefix, indexer.Name),
|
||||
e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return reports;
|
||||
}
|
||||
|
||||
public override bool IsEpisodeMatch(Series series, dynamic options, EpisodeParseResult episodeParseResult)
|
||||
{
|
||||
if (options.SeasonNumber != episodeParseResult.SeasonNumber)
|
||||
{
|
||||
logger.Trace("Season number does not match searched season number, skipping.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private List<int> GetEpisodeNumberPrefixes(IEnumerable<int> episodeNumbers)
|
||||
{
|
||||
var results = new List<int>();
|
||||
|
||||
foreach (var i in episodeNumbers)
|
||||
{
|
||||
results.Add(i / 10);
|
||||
}
|
||||
|
||||
return results.Distinct().ToList();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
public interface IIndexerBase
|
||||
{
|
||||
string Name { get; }
|
||||
bool EnabledByDefault { get; }
|
||||
|
||||
IEnumerable<string> RecentFeed { get; }
|
||||
|
||||
IParseFeed Parser { get; }
|
||||
|
||||
IIndexerSetting Settings { get; }
|
||||
|
||||
IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber);
|
||||
IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date);
|
||||
IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber);
|
||||
IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard);
|
||||
}
|
||||
|
||||
public abstract class BaseIndexer : IIndexerBase
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
|
||||
public virtual bool EnabledByDefault
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IParseFeed Parser
|
||||
{
|
||||
get
|
||||
{
|
||||
return new BasicRssParser();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IIndexerSetting Settings
|
||||
{
|
||||
get
|
||||
{
|
||||
return new NullSetting();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract IEnumerable<string> RecentFeed { get; }
|
||||
|
||||
public abstract IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber);
|
||||
public abstract IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date);
|
||||
public abstract IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber);
|
||||
public abstract IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.ServiceModel.Syndication;
|
||||
using NLog;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
public interface IParseFeed
|
||||
{
|
||||
IEnumerable<EpisodeParseResult> Process(Stream source);
|
||||
}
|
||||
|
||||
public class BasicRssParser : IParseFeed
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public BasicRssParser()
|
||||
{
|
||||
_logger = LogManager.GetCurrentClassLogger();
|
||||
}
|
||||
|
||||
public IEnumerable<EpisodeParseResult> Process(Stream source)
|
||||
{
|
||||
var reader = new SyndicationFeedXmlReader(source);
|
||||
var feed = SyndicationFeed.Load(reader).Items;
|
||||
|
||||
var result = new List<EpisodeParseResult>();
|
||||
|
||||
foreach (var syndicationItem in feed)
|
||||
{
|
||||
try
|
||||
{
|
||||
var parsedEpisode = ParseFeed(syndicationItem);
|
||||
if (parsedEpisode != null)
|
||||
{
|
||||
parsedEpisode.NzbUrl = GetNzbUrl(syndicationItem);
|
||||
parsedEpisode.NzbInfoUrl = GetNzbUrl(syndicationItem);
|
||||
result.Add(parsedEpisode);
|
||||
}
|
||||
}
|
||||
catch (Exception itemEx)
|
||||
{
|
||||
itemEx.Data.Add("Item", syndicationItem.Title);
|
||||
_logger.ErrorException("An error occurred while processing feed item", itemEx);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
protected virtual string GetTitle(SyndicationItem syndicationItem)
|
||||
{
|
||||
return syndicationItem.Title.Text;
|
||||
}
|
||||
|
||||
protected virtual string GetNzbUrl(SyndicationItem item)
|
||||
{
|
||||
return item.Links[0].Uri.ToString();
|
||||
}
|
||||
|
||||
protected virtual string GetNzbInfoUrl(SyndicationItem item)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
protected virtual EpisodeParseResult PostProcessor(SyndicationItem item, EpisodeParseResult currentResult)
|
||||
{
|
||||
return currentResult;
|
||||
}
|
||||
|
||||
private EpisodeParseResult ParseFeed(SyndicationItem item)
|
||||
{
|
||||
var title = GetTitle(item);
|
||||
|
||||
var episodeParseResult = Parser.ParseTitle(title);
|
||||
if (episodeParseResult != null)
|
||||
{
|
||||
episodeParseResult.Age = DateTime.Now.Date.Subtract(item.PublishDate.Date).Days;
|
||||
episodeParseResult.OriginalString = title;
|
||||
episodeParseResult.SceneSource = true;
|
||||
}
|
||||
|
||||
_logger.Trace("Parsed: {0} from: {1}", episodeParseResult, item.Title.Text);
|
||||
|
||||
return PostProcessor(item, episodeParseResult);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.FileSharingTalk
|
||||
{
|
||||
public class FileSharingTalk : BaseIndexer
|
||||
{
|
||||
private readonly FileSharingTalkSetting _settings;
|
||||
|
||||
public FileSharingTalk(IProviderIndexerSetting settingProvider)
|
||||
{
|
||||
_settings = settingProvider.Get<FileSharingTalkSetting>(this);
|
||||
}
|
||||
|
||||
public override IEnumerable<string> RecentFeed
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return
|
||||
string.Format(
|
||||
"http://filesharingtalk.com/ng_rss.php?uid={0}&ps={1}&category=tv&subcategory=x264sd,x264720,xvid,webdl720,x2641080",
|
||||
_settings.Uid, _settings.Secret);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override IParseFeed Parser
|
||||
{
|
||||
get
|
||||
{
|
||||
return new FileSharingTalkParser();
|
||||
}
|
||||
}
|
||||
|
||||
public override IIndexerSetting Settings
|
||||
{
|
||||
get { return _settings; }
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get { return "FileSharingTalk"; }
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
using System.ServiceModel.Syndication;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.FileSharingTalk
|
||||
{
|
||||
public class FileSharingTalkParser : BasicRssParser
|
||||
{
|
||||
protected override string GetNzbInfoUrl(SyndicationItem item)
|
||||
{
|
||||
return item.Id;
|
||||
}
|
||||
|
||||
protected override EpisodeParseResult PostProcessor(SyndicationItem item, EpisodeParseResult currentResult)
|
||||
{
|
||||
if (currentResult != null)
|
||||
{
|
||||
currentResult.Size = 0;
|
||||
currentResult.Age = 0;
|
||||
}
|
||||
|
||||
return currentResult;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,238 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.ServiceModel.Syndication;
|
||||
using System.Text.RegularExpressions;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
public abstract class IndexerBase
|
||||
{
|
||||
protected readonly Logger _logger;
|
||||
protected readonly HttpProvider _httpProvider;
|
||||
protected readonly IConfigService _configService;
|
||||
|
||||
protected static readonly Regex TitleSearchRegex = new Regex(@"[\W]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
protected static readonly Regex RemoveThe = new Regex(@"^the\s", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
protected IndexerBase(HttpProvider httpProvider, IConfigService configService)
|
||||
{
|
||||
_httpProvider = httpProvider;
|
||||
_configService = configService;
|
||||
|
||||
_logger = LogManager.GetLogger(GetType().ToString());
|
||||
}
|
||||
|
||||
public IndexerBase()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name for the feed
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source URL for the feed
|
||||
/// </summary>
|
||||
protected abstract string[] Urls { get; }
|
||||
|
||||
public abstract bool IsConfigured { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Should the indexer be enabled by default?
|
||||
/// </summary>
|
||||
public virtual bool EnabledByDefault
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the credential.
|
||||
/// </summary>
|
||||
protected virtual NetworkCredential Credentials
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
protected abstract IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber);
|
||||
protected abstract IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date);
|
||||
protected abstract IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber);
|
||||
protected abstract IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard);
|
||||
|
||||
protected virtual EpisodeParseResult CustomParser(SyndicationItem item, EpisodeParseResult currentResult)
|
||||
{
|
||||
return currentResult;
|
||||
}
|
||||
|
||||
protected virtual string TitlePreParser(SyndicationItem item)
|
||||
{
|
||||
return item.Title.Text;
|
||||
}
|
||||
|
||||
protected abstract string NzbDownloadUrl(SyndicationItem item);
|
||||
|
||||
protected abstract string NzbInfoUrl(SyndicationItem item);
|
||||
|
||||
public virtual IList<EpisodeParseResult> FetchRss()
|
||||
{
|
||||
_logger.Debug("Fetching feeds from " + Name);
|
||||
|
||||
var result = new List<EpisodeParseResult>();
|
||||
|
||||
|
||||
result = Fetch(Urls);
|
||||
|
||||
|
||||
_logger.Debug("Finished processing feeds from " + Name);
|
||||
return result;
|
||||
}
|
||||
|
||||
public virtual IList<EpisodeParseResult> FetchSeason(string seriesTitle, int seasonNumber)
|
||||
{
|
||||
_logger.Debug("Searching {0} for {1} Season {2}", Name, seriesTitle, seasonNumber);
|
||||
|
||||
var searchUrls = GetSeasonSearchUrls(GetQueryTitle(seriesTitle), seasonNumber);
|
||||
var result = Fetch(searchUrls);
|
||||
|
||||
_logger.Info("Finished searching {0} for {1} Season {2}, Found {3}", Name, seriesTitle, seasonNumber, result.Count);
|
||||
return result;
|
||||
}
|
||||
|
||||
public virtual IList<EpisodeParseResult> FetchPartialSeason(string seriesTitle, int seasonNumber, int episodePrefix)
|
||||
{
|
||||
_logger.Debug("Searching {0} for {1} Season {2}, Prefix: {3}", Name, seriesTitle, seasonNumber, episodePrefix);
|
||||
|
||||
|
||||
var searchUrls = GetPartialSeasonSearchUrls(GetQueryTitle(seriesTitle), seasonNumber, episodePrefix);
|
||||
|
||||
var result = Fetch(searchUrls);
|
||||
|
||||
_logger.Info("Finished searching {0} for {1} Season {2}, Found {3}", Name, seriesTitle, seasonNumber, result.Count);
|
||||
return result;
|
||||
}
|
||||
|
||||
public virtual IList<EpisodeParseResult> FetchEpisode(string seriesTitle, int seasonNumber, int episodeNumber)
|
||||
{
|
||||
_logger.Debug("Searching {0} for {1}-S{2:00}E{3:00}", Name, seriesTitle, seasonNumber, episodeNumber);
|
||||
|
||||
var searchUrls = GetEpisodeSearchUrls(GetQueryTitle(seriesTitle), seasonNumber, episodeNumber);
|
||||
|
||||
var result = Fetch(searchUrls);
|
||||
|
||||
_logger.Info("Finished searching {0} for {1} S{2:00}E{3:00}, Found {4}", Name, seriesTitle, seasonNumber, episodeNumber, result.Count);
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
public virtual IList<EpisodeParseResult> FetchDailyEpisode(string seriesTitle, DateTime airDate)
|
||||
{
|
||||
_logger.Debug("Searching {0} for {1}-{2}", Name, seriesTitle, airDate.ToShortDateString());
|
||||
|
||||
var searchUrls = GetDailyEpisodeSearchUrls(GetQueryTitle(seriesTitle), airDate);
|
||||
|
||||
var result = Fetch(searchUrls);
|
||||
|
||||
_logger.Info("Finished searching {0} for {1}-{2}, Found {3}", Name, seriesTitle, airDate.ToShortDateString(), result.Count);
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
private List<EpisodeParseResult> Fetch(IEnumerable<string> urls)
|
||||
{
|
||||
var result = new List<EpisodeParseResult>();
|
||||
|
||||
if (!IsConfigured)
|
||||
{
|
||||
_logger.Warn("Indexer '{0}' isn't configured correctly. please reconfigure the indexer in settings page.", Name);
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var url in urls)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Trace("Downloading RSS " + url);
|
||||
|
||||
var reader = new SyndicationFeedXmlReader(_httpProvider.DownloadStream(url, Credentials));
|
||||
var feed = SyndicationFeed.Load(reader).Items;
|
||||
|
||||
foreach (var item in feed)
|
||||
{
|
||||
try
|
||||
{
|
||||
var parsedEpisode = ParseFeed(item);
|
||||
if (parsedEpisode != null)
|
||||
{
|
||||
parsedEpisode.NzbUrl = NzbDownloadUrl(item);
|
||||
parsedEpisode.NzbInfoUrl = NzbInfoUrl(item);
|
||||
parsedEpisode.Indexer = String.IsNullOrWhiteSpace(parsedEpisode.Indexer) ? Name : parsedEpisode.Indexer;
|
||||
result.Add(parsedEpisode);
|
||||
}
|
||||
}
|
||||
catch (Exception itemEx)
|
||||
{
|
||||
itemEx.Data.Add("FeedUrl", url);
|
||||
itemEx.Data.Add("Item", item.Title);
|
||||
_logger.ErrorException("An error occurred while processing feed item", itemEx);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (WebException webException)
|
||||
{
|
||||
if (webException.Message.Contains("503"))
|
||||
{
|
||||
_logger.Warn("{0} server is currently unavailable.{1} {2}", Name, url, webException.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
webException.Data.Add("FeedUrl", url);
|
||||
_logger.ErrorException("An error occurred while processing feed. " + url, webException);
|
||||
}
|
||||
}
|
||||
catch (Exception feedEx)
|
||||
{
|
||||
feedEx.Data.Add("FeedUrl", url);
|
||||
_logger.ErrorException("An error occurred while processing feed. " + url, feedEx);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public EpisodeParseResult ParseFeed(SyndicationItem item)
|
||||
{
|
||||
var title = TitlePreParser(item);
|
||||
|
||||
var episodeParseResult = Parser.ParseTitle(title);
|
||||
if (episodeParseResult != null)
|
||||
{
|
||||
episodeParseResult.Age = DateTime.Now.Date.Subtract(item.PublishDate.Date).Days;
|
||||
episodeParseResult.OriginalString = title;
|
||||
episodeParseResult.SceneSource = true;
|
||||
}
|
||||
|
||||
_logger.Trace("Parsed: {0} from: {1}", episodeParseResult, item.Title.Text);
|
||||
|
||||
return CustomParser(item, episodeParseResult);
|
||||
}
|
||||
|
||||
public virtual string GetQueryTitle(string title)
|
||||
{
|
||||
title = RemoveThe.Replace(title, string.Empty);
|
||||
|
||||
var cleanTitle = TitleSearchRegex.Replace(title, "+").Trim('+', ' ');
|
||||
|
||||
//remove any repeating +s
|
||||
cleanTitle = Regex.Replace(cleanTitle, @"\+{1,100}", "+");
|
||||
return cleanTitle;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Core.IndexerSearch.Definitions;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
{
|
||||
public interface IFetchFeedFromIndexers
|
||||
{
|
||||
IList<EpisodeParseResult> FetchRss(IIndexerBase indexer);
|
||||
|
||||
IList<EpisodeParseResult> Fetch(IIndexerBase indexer, SeasonSearchDefinition searchDefinition);
|
||||
IList<EpisodeParseResult> Fetch(IIndexerBase indexer, SingleEpisodeSearchDefinition searchDefinition);
|
||||
IList<EpisodeParseResult> Fetch(IIndexerBase indexer, PartialSeasonSearchDefinition searchDefinition);
|
||||
IList<EpisodeParseResult> Fetch(IIndexerBase indexer, DailyEpisodeSearchDefinition searchDefinition);
|
||||
}
|
||||
|
||||
public class FetchFeedService : IFetchFeedFromIndexers
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly HttpProvider _httpProvider;
|
||||
|
||||
|
||||
protected FetchFeedService(HttpProvider httpProvider, Logger logger)
|
||||
{
|
||||
_httpProvider = httpProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
public virtual IList<EpisodeParseResult> FetchRss(IIndexerBase indexer)
|
||||
{
|
||||
_logger.Debug("Fetching feeds from " + indexer.Name);
|
||||
|
||||
var result = Fetch(indexer, indexer.RecentFeed);
|
||||
|
||||
_logger.Debug("Finished processing feeds from " + indexer.Name);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public IList<EpisodeParseResult> Fetch(IIndexerBase indexer, SeasonSearchDefinition searchDefinition)
|
||||
{
|
||||
_logger.Debug("Searching for {0}", searchDefinition);
|
||||
|
||||
var searchUrls = indexer.GetSeasonSearchUrls(searchDefinition.SceneTitle, searchDefinition.SeasonNumber);
|
||||
var result = Fetch(indexer, searchUrls);
|
||||
|
||||
|
||||
_logger.Info("Finished searching {0} on {1}. Found {2}", indexer.Name, searchDefinition, result.Count);
|
||||
return result;
|
||||
}
|
||||
|
||||
public IList<EpisodeParseResult> Fetch(IIndexerBase indexer, SingleEpisodeSearchDefinition searchDefinition)
|
||||
{
|
||||
_logger.Debug("Searching for {0}", searchDefinition);
|
||||
|
||||
var searchUrls = indexer.GetEpisodeSearchUrls(searchDefinition.SceneTitle, searchDefinition.SeasonNumber, searchDefinition.EpisodeNumber);
|
||||
var result = Fetch(indexer, searchUrls);
|
||||
|
||||
|
||||
_logger.Info("Finished searching {0} on {1}. Found {2}", indexer.Name, searchDefinition, result.Count);
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
public IList<EpisodeParseResult> Fetch(IIndexerBase indexer, PartialSeasonSearchDefinition searchDefinition)
|
||||
{
|
||||
_logger.Debug("Searching for {0}", searchDefinition);
|
||||
|
||||
var searchUrls = indexer.GetSeasonSearchUrls(searchDefinition.SceneTitle, searchDefinition.SeasonNumber);
|
||||
var result = Fetch(indexer, searchUrls);
|
||||
|
||||
|
||||
_logger.Info("Finished searching {0} on {1}. Found {2}", indexer.Name, searchDefinition, result.Count);
|
||||
return result;
|
||||
}
|
||||
|
||||
public IList<EpisodeParseResult> Fetch(IIndexerBase indexer, DailyEpisodeSearchDefinition searchDefinition)
|
||||
{
|
||||
_logger.Debug("Searching for {0}", searchDefinition);
|
||||
|
||||
var searchUrls = indexer.GetDailyEpisodeSearchUrls(searchDefinition.SceneTitle, searchDefinition.Airtime);
|
||||
var result = Fetch(indexer, searchUrls);
|
||||
|
||||
_logger.Info("Finished searching {0} on {1}. Found {2}", indexer.Name, searchDefinition, result.Count);
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<EpisodeParseResult> Fetch(IIndexerBase indexer, IEnumerable<string> urls)
|
||||
{
|
||||
var result = new List<EpisodeParseResult>();
|
||||
|
||||
foreach (var url in urls)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Trace("Downloading Feed " + url);
|
||||
var stream = _httpProvider.DownloadStream(url);
|
||||
result.AddRange(indexer.Parser.Process(stream));
|
||||
}
|
||||
catch (WebException webException)
|
||||
{
|
||||
if (webException.Message.Contains("503"))
|
||||
{
|
||||
_logger.Warn("{0} server is currently unavailable.{1} {2}", indexer.Name, url, webException.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
webException.Data.Add("FeedUrl", url);
|
||||
_logger.ErrorException("An error occurred while processing feed. " + url, webException);
|
||||
}
|
||||
}
|
||||
catch (Exception feedEx)
|
||||
{
|
||||
feedEx.Data.Add("FeedUrl", url);
|
||||
_logger.ErrorException("An error occurred while processing feed. " + url, feedEx);
|
||||
}
|
||||
}
|
||||
|
||||
result.ForEach(c => c.Indexer = indexer.Name);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Common;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.NzbClub
|
||||
{
|
||||
public class NzbClub : BaseIndexer
|
||||
{
|
||||
public NzbClub(HttpProvider httpProvider, IConfigService configService)
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<string> RecentFeed
|
||||
{
|
||||
get
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
String.Format("http://www.nzbclub.com/nzbfeed.aspx?ig=2&gid=102952&st=1&ns=1&q=%23a.b.teevee"),
|
||||
String.Format("http://www.nzbclub.com/nzbfeed.aspx?ig=2&gid=5542&st=1&ns=1&q=")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get { return "NzbClub"; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
public override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
|
||||
{
|
||||
var searchUrls = new List<string>();
|
||||
|
||||
foreach (var url in RecentFeed)
|
||||
{
|
||||
searchUrls.Add(String.Format("{0}+{1}+s{2:00}e{3:00}", url, seriesTitle, seasonNumber, episodeNumber));
|
||||
}
|
||||
|
||||
return searchUrls;
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
|
||||
{
|
||||
var searchUrls = new List<string>();
|
||||
|
||||
foreach (var url in RecentFeed)
|
||||
{
|
||||
searchUrls.Add(String.Format("{0}+{1}+s{2:00}", url, seriesTitle, seasonNumber));
|
||||
}
|
||||
|
||||
return searchUrls;
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
|
||||
{
|
||||
var searchUrls = new List<String>();
|
||||
|
||||
foreach (var url in RecentFeed)
|
||||
{
|
||||
searchUrls.Add(String.Format("{0}+{1}+{2:yyyy MM dd}", url, seriesTitle, date));
|
||||
}
|
||||
|
||||
return searchUrls;
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
|
||||
{
|
||||
var searchUrls = new List<String>();
|
||||
|
||||
foreach (var url in RecentFeed)
|
||||
{
|
||||
searchUrls.Add(String.Format("{0}+{1}+S{2:00}E{3}", url, seriesTitle, seasonNumber, episodeWildcard));
|
||||
}
|
||||
|
||||
return searchUrls;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.ServiceModel.Syndication;
|
||||
using System.Text.RegularExpressions;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.NzbClub
|
||||
{
|
||||
public class NzbClubParser : BasicRssParser
|
||||
{
|
||||
protected override EpisodeParseResult PostProcessor(SyndicationItem item, EpisodeParseResult currentResult)
|
||||
{
|
||||
if (currentResult != null)
|
||||
{
|
||||
var sizeString = Regex.Match(item.Summary.Text, @"Size:\s\d+\.\d{1,2}\s\w{2}\s", RegexOptions.IgnoreCase | RegexOptions.Compiled).Value;
|
||||
currentResult.Size = Parser.GetReportSize(sizeString);
|
||||
}
|
||||
|
||||
return currentResult;
|
||||
}
|
||||
|
||||
protected override string GetTitle(SyndicationItem syndicationItem)
|
||||
{
|
||||
var title = Parser.ParseHeader(syndicationItem.Title.Text);
|
||||
|
||||
if (String.IsNullOrWhiteSpace(title))
|
||||
return syndicationItem.Title.Text;
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
protected override string GetNzbInfoUrl(SyndicationItem item)
|
||||
{
|
||||
return item.Links[1].Uri.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.NzbIndex
|
||||
{
|
||||
public class NzbIndex : BaseIndexer
|
||||
{
|
||||
public override IEnumerable<string> RecentFeed
|
||||
{
|
||||
get
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
String.Format("http://www.nzbindex.nl/rss/alt.binaries.teevee/?sort=agedesc&minsize=100&complete=1&max=50&more=1&q=%23a.b.teevee"),
|
||||
String.Format("http://www.nzbindex.nl/rss/alt.binaries.hdtv/?sort=agedesc&minsize=100&complete=1&max=50&more=1&q=")
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get { return "NzbIndex"; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
|
||||
{
|
||||
var searchUrls = new List<string>();
|
||||
|
||||
foreach (var url in RecentFeed)
|
||||
{
|
||||
searchUrls.Add(String.Format("{0}+{1}+s{2:00}e{3:00}", url, seriesTitle, seasonNumber, episodeNumber));
|
||||
}
|
||||
|
||||
return searchUrls;
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
|
||||
{
|
||||
var searchUrls = new List<string>();
|
||||
|
||||
foreach (var url in RecentFeed)
|
||||
{
|
||||
searchUrls.Add(String.Format("{0}+{1}+s{2:00}", url, seriesTitle, seasonNumber));
|
||||
}
|
||||
|
||||
return searchUrls;
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
|
||||
{
|
||||
var searchUrls = new List<String>();
|
||||
|
||||
foreach (var url in RecentFeed)
|
||||
{
|
||||
searchUrls.Add(String.Format("{0}+{1}+{2:yyyy MM dd}", url, seriesTitle, date));
|
||||
}
|
||||
|
||||
return searchUrls;
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
|
||||
{
|
||||
var searchUrls = new List<String>();
|
||||
|
||||
foreach (var url in RecentFeed)
|
||||
{
|
||||
searchUrls.Add(String.Format("{0}+{1}+S{2:00}E{3}", url, seriesTitle, seasonNumber, episodeWildcard));
|
||||
}
|
||||
|
||||
return searchUrls;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.ServiceModel.Syndication;
|
||||
using System.Text.RegularExpressions;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.NzbIndex
|
||||
{
|
||||
public class NzbIndexParser : BasicRssParser
|
||||
{
|
||||
|
||||
protected override string GetNzbUrl(SyndicationItem item)
|
||||
{
|
||||
return item.Links[1].Uri.ToString();
|
||||
}
|
||||
|
||||
protected override string GetNzbInfoUrl(SyndicationItem item)
|
||||
{
|
||||
return item.Links[0].Uri.ToString();
|
||||
}
|
||||
|
||||
protected override EpisodeParseResult PostProcessor(SyndicationItem item, EpisodeParseResult currentResult)
|
||||
{
|
||||
if (currentResult != null)
|
||||
{
|
||||
var sizeString = Regex.Match(item.Summary.Text, @"<b>\d+\.\d{1,2}\s\w{2}</b><br\s/>", RegexOptions.IgnoreCase | RegexOptions.Compiled).Value;
|
||||
currentResult.Size = Parser.GetReportSize(sizeString);
|
||||
}
|
||||
|
||||
return currentResult;
|
||||
}
|
||||
|
||||
protected override string GetTitle(SyndicationItem item)
|
||||
{
|
||||
var title = Parser.ParseHeader(item.Title.Text);
|
||||
|
||||
if (String.IsNullOrWhiteSpace(title))
|
||||
return item.Title.Text;
|
||||
|
||||
return title;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.NzbsRUs
|
||||
{
|
||||
public class Nzbsrus : BaseIndexer
|
||||
{
|
||||
private readonly NzbsrusSetting _setting;
|
||||
|
||||
public Nzbsrus(IProviderIndexerSetting settingProvider)
|
||||
{
|
||||
_setting = settingProvider.Get<NzbsrusSetting>(this);
|
||||
}
|
||||
|
||||
public override IEnumerable<string> RecentFeed
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return string.Format("https://www.nzbsrus.com/rssfeed.php?cat=91,75&i={0}&h={1}",
|
||||
_setting.Uid,
|
||||
_setting.Hash);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override IIndexerSetting Settings
|
||||
{
|
||||
get { return _setting; }
|
||||
}
|
||||
|
||||
public override string Name
|
||||
{
|
||||
get { return "NzbsRUs"; }
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
using System.ServiceModel.Syndication;
|
||||
using System.Text.RegularExpressions;
|
||||
using NzbDrone.Core.Model;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.NzbsRUs
|
||||
{
|
||||
public class NzbsrusParser : BasicRssParser
|
||||
{
|
||||
protected override EpisodeParseResult PostProcessor(SyndicationItem item, EpisodeParseResult currentResult)
|
||||
{
|
||||
if (currentResult != null)
|
||||
{
|
||||
var sizeString = Regex.Match(item.Summary.Text, @"\d+\.\d{1,2} \w{3}", RegexOptions.IgnoreCase).Value;
|
||||
currentResult.Size = Parser.GetReportSize(sizeString);
|
||||
}
|
||||
|
||||
return currentResult;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Nzbx
|
||||
{
|
||||
public class Nzbx : BaseIndexer
|
||||
{
|
||||
public override string Name
|
||||
{
|
||||
get { return "nzbx"; }
|
||||
}
|
||||
|
||||
public override IEnumerable<string> RecentFeed
|
||||
{
|
||||
get
|
||||
{
|
||||
return new[] { String.Format("https://nzbx.co/api/recent?category=tv") };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
|
||||
{
|
||||
yield return String.Format("https://nzbx.co/api/search?q={0}+S{1:00}E{2:00}", seriesTitle, seasonNumber, episodeNumber);
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
|
||||
{
|
||||
yield return String.Format("https://nzbx.co/api/search?q={0}+{1:yyyy MM dd}", seriesTitle, date);
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
|
||||
{
|
||||
yield return String.Format("https://nzbx.co/api/search?q={0}+S{1:00}", seriesTitle, seasonNumber);
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
|
||||
{
|
||||
yield return String.Format("https://nzbx.co/api/search?q={0}+S{1:00}E{2}", seriesTitle, seasonNumber, episodeWildcard);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using NLog;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Core.Model;
|
||||
using NzbDrone.Core.Model.Nzbx;
|
||||
|
||||
namespace NzbDrone.Core.Indexers.Nzbx
|
||||
{
|
||||
public class NzbxParser : IParseFeed
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
private readonly JsonSerializer _serializer;
|
||||
|
||||
public NzbxParser(Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_serializer = new JsonSerializer();
|
||||
}
|
||||
|
||||
public IEnumerable<EpisodeParseResult> Process(Stream source)
|
||||
{
|
||||
var result = new List<EpisodeParseResult>();
|
||||
var jsonReader = new JsonTextReader(new StreamReader(source));
|
||||
var feed = _serializer.Deserialize<List<NzbxRecentItem>>(jsonReader);
|
||||
|
||||
foreach (var item in feed)
|
||||
{
|
||||
try
|
||||
{
|
||||
var episodeParseResult = Parser.ParseTitle(item.Name);
|
||||
if (episodeParseResult != null)
|
||||
{
|
||||
episodeParseResult.Age = DateTime.Now.Date.Subtract(item.PostDate).Days;
|
||||
episodeParseResult.OriginalString = item.Name;
|
||||
episodeParseResult.SceneSource = true;
|
||||
episodeParseResult.NzbUrl = String.Format("http://nzbx.co/nzb?{0}*|*{1}", item.Guid, item.Name);
|
||||
episodeParseResult.NzbInfoUrl = String.Format("http://nzbx.co/d?{0}", item.Guid);
|
||||
episodeParseResult.Size = item.Size;
|
||||
|
||||
result.Add(episodeParseResult);
|
||||
}
|
||||
}
|
||||
catch (Exception itemEx)
|
||||
{
|
||||
itemEx.Data.Add("Item", item.Name);
|
||||
_logger.ErrorException("An error occurred while processing feed item", itemEx);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue