rewrite of indexer/episode search

pull/23/head
kay.one 11 years ago
parent 9ae21cf7a1
commit a6a4932b44

@ -49,7 +49,7 @@ namespace NzbDrone.Common
}
}
public virtual Stream DownloadStream(string url, NetworkCredential credential)
public virtual Stream DownloadStream(string url, NetworkCredential credential = null)
{
var request = (HttpWebRequest)WebRequest.Create(url);
request.UserAgent = _userAgent;

@ -1,90 +0,0 @@
using System;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore
{
public class SampleType : ModelBase
{
public string Name { get; set; }
public string Tilte { get; set; }
public string Address { get; set; }
}
[TestFixture]
public class BaiscRepositoryFixture : ObjectDbTest<BasicRepository<SampleType>,SampleType>
{
private SampleType sampleType;
[SetUp]
public void Setup()
{
sampleType = Builder<SampleType>
.CreateNew()
.With(c => c.Id = 0)
.Build();
}
[Test]
public void should_be_able_to_add()
{
Subject.Insert(sampleType);
Subject.All().Should().HaveCount(1);
}
[Test]
public void should_be_able_to_delete_model()
{
Subject.Insert(sampleType);
Subject.All().Should().HaveCount(1);
Subject.Delete(sampleType.Id);
Subject.All().Should().BeEmpty();
}
[Test]
public void should_be_able_to_find_by_id()
{
Subject.Insert(sampleType);
Subject.Get(sampleType.Id)
.ShouldHave()
.AllProperties()
.EqualTo(sampleType);
}
[Test]
public void should_be_able_to_update_existing_model()
{
Subject.Insert(sampleType);
sampleType.Address = "newAddress";
Subject.Update(sampleType);
Subject.Get(sampleType.Id).Address.Should().Be(sampleType.Address);
}
[Test]
public void getting_model_with_invalid_id_should_throw()
{
Assert.Throws<InvalidOperationException>(() => Subject.Get(12));
}
[Test]
public void get_all_with_empty_db_should_return_empty_list()
{
Subject.All().Should().BeEmpty();
}
}
}

@ -1,4 +1,5 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
@ -9,28 +10,28 @@ using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.DecisionEngineTests
{
[TestFixture]
public class AllowedDownloadSpecificationFixture : CoreTest<DownloadDirector>
public class AllowedDownloadSpecificationFixture : CoreTest<DownloadDecisionMaker>
{
private EpisodeParseResult _parseResult;
private List<EpisodeParseResult> _parseResults;
private Mock<IFetchableSpecification> _pass1;
private Mock<IFetchableSpecification> _pass2;
private Mock<IFetchableSpecification> _pass3;
private Mock<IDecisionEngineSpecification> _pass1;
private Mock<IDecisionEngineSpecification> _pass2;
private Mock<IDecisionEngineSpecification> _pass3;
private Mock<IFetchableSpecification> _fail1;
private Mock<IFetchableSpecification> _fail2;
private Mock<IFetchableSpecification> _fail3;
private Mock<IDecisionEngineSpecification> _fail1;
private Mock<IDecisionEngineSpecification> _fail2;
private Mock<IDecisionEngineSpecification> _fail3;
[SetUp]
public void Setup()
{
_pass1 = new Mock<IFetchableSpecification>();
_pass2 = new Mock<IFetchableSpecification>();
_pass3 = new Mock<IFetchableSpecification>();
_pass1 = new Mock<IDecisionEngineSpecification>();
_pass2 = new Mock<IDecisionEngineSpecification>();
_pass3 = new Mock<IDecisionEngineSpecification>();
_fail1 = new Mock<IFetchableSpecification>();
_fail2 = new Mock<IFetchableSpecification>();
_fail3 = new Mock<IFetchableSpecification>();
_fail1 = new Mock<IDecisionEngineSpecification>();
_fail2 = new Mock<IDecisionEngineSpecification>();
_fail3 = new Mock<IDecisionEngineSpecification>();
_pass1.Setup(c => c.IsSatisfiedBy(It.IsAny<EpisodeParseResult>())).Returns(true);
_pass2.Setup(c => c.IsSatisfiedBy(It.IsAny<EpisodeParseResult>())).Returns(true);
@ -40,11 +41,11 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_fail2.Setup(c => c.IsSatisfiedBy(It.IsAny<EpisodeParseResult>())).Returns(false);
_fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<EpisodeParseResult>())).Returns(false);
_parseResult = new EpisodeParseResult();
_parseResults = new List<EpisodeParseResult>() { new EpisodeParseResult() };
}
private void GivenSpecifications(params Mock<IFetchableSpecification>[] mocks)
private void GivenSpecifications(params Mock<IDecisionEngineSpecification>[] mocks)
{
Mocker.SetConstant(mocks.Select(c => c.Object));
}
@ -54,14 +55,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
Subject.GetDownloadDecision(_parseResult);
Subject.GetRssDecision(_parseResults);
_fail1.Verify(c => c.IsSatisfiedBy(_parseResult), Times.Once());
_fail2.Verify(c => c.IsSatisfiedBy(_parseResult), Times.Once());
_fail3.Verify(c => c.IsSatisfiedBy(_parseResult), Times.Once());
_pass1.Verify(c => c.IsSatisfiedBy(_parseResult), Times.Once());
_pass2.Verify(c => c.IsSatisfiedBy(_parseResult), Times.Once());
_pass3.Verify(c => c.IsSatisfiedBy(_parseResult), Times.Once());
_fail1.Verify(c => c.IsSatisfiedBy(_parseResults[0]), Times.Once());
_fail2.Verify(c => c.IsSatisfiedBy(_parseResults[0]), Times.Once());
_fail3.Verify(c => c.IsSatisfiedBy(_parseResults[0]), Times.Once());
_pass1.Verify(c => c.IsSatisfiedBy(_parseResults[0]), Times.Once());
_pass2.Verify(c => c.IsSatisfiedBy(_parseResults[0]), Times.Once());
_pass3.Verify(c => c.IsSatisfiedBy(_parseResults[0]), Times.Once());
}
[Test]
@ -69,9 +70,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
GivenSpecifications(_pass1, _fail1, _pass2, _pass3);
var result = Subject.GetDownloadDecision(_parseResult);
var result = Subject.GetRssDecision(_parseResults);
result.Approved.Should().BeFalse();
result.Single().Approved.Should().BeFalse();
}
[Test]
@ -79,9 +80,9 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
GivenSpecifications(_pass1, _pass2, _pass3);
var result = Subject.GetDownloadDecision(_parseResult);
var result = Subject.GetRssDecision(_parseResults);
result.Approved.Should().BeTrue();
result.Single().Approved.Should().BeTrue();
}
[Test]
@ -89,8 +90,18 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
var result = Subject.GetDownloadDecision(_parseResult);
result.Rejections.Should().HaveCount(3);
var result = Subject.GetRssDecision(_parseResults);
result.Single().Rejections.Should().HaveCount(3);
}
[Test]
public void parse_result_should_be_attached_to_decision()
{
GivenSpecifications(_pass1, _pass2, _pass3, _fail1, _fail2, _fail3);
var result = Subject.GetRssDecision(_parseResults);
result.Single().ParseResult.Should().Be(_parseResults.Single());
}
}

@ -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,490 +0,0 @@
using System;
using System.Globalization;
using System.Linq;
using System.Net;
using System.ServiceModel.Syndication;
using System.Threading;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Test.Indexers;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.IndexerTests
{
[TestFixture]
public class IndexerFixture : CoreTest
{
private void WithConfiguredIndexers()
{
Mocker.GetMock<IConfigService>().SetupGet(c => c.NzbsOrgHash).Returns("MockedConfigValue");
Mocker.GetMock<IConfigService>().SetupGet(c => c.NzbsOrgUId).Returns("MockedConfigValue");
Mocker.GetMock<IConfigService>().SetupGet(c => c.NzbsrusHash).Returns("MockedConfigValue");
Mocker.GetMock<IConfigService>().SetupGet(c => c.NzbsrusUId).Returns("MockedConfigValue");
Mocker.GetMock<IConfigService>().SetupGet(c => c.FileSharingTalkUid).Returns("MockedConfigValue");
Mocker.GetMock<IConfigService>().SetupGet(c => c.FileSharingTalkSecret).Returns("MockedConfigValue");
Mocker.GetMock<IConfigService>().SetupGet(c => c.OmgwtfnzbsUsername).Returns("MockedConfigValue");
Mocker.GetMock<IConfigService>().SetupGet(c => c.OmgwtfnzbsApiKey).Returns("MockedConfigValue");
}
[TestCase("nzbsrus.xml")]
[TestCase("newznab.xml")]
[TestCase("wombles.xml")]
[TestCase("filesharingtalk.xml")]
[TestCase("nzbindex.xml")]
[TestCase("nzbclub.xml")]
[TestCase("omgwtfnzbs.xml")]
public void parse_feed_xml(string fileName)
{
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream(It.IsAny<String>(), It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", fileName));
var fakeSettings = Builder<Indexer>.CreateNew().Build();
Mocker.GetMock<IIndexerService>()
.Setup(c => c.GetSettings(It.IsAny<Type>()))
.Returns(fakeSettings);
var mockIndexer = Mocker.Resolve<MockIndexer>();
var parseResults = mockIndexer.FetchRss();
foreach (var episodeParseResult in parseResults)
{
var Uri = new Uri(episodeParseResult.NzbUrl);
Uri.PathAndQuery.Should().NotContain("//");
}
parseResults.Should().NotBeEmpty();
parseResults.Should().OnlyContain(s => s.Indexer == mockIndexer.Name);
parseResults.Should().OnlyContain(s => !String.IsNullOrEmpty(s.OriginalString));
parseResults.Should().OnlyContain(s => s.Age >= 0);
}
[Test]
public void custom_parser_partial_success()
{
const string title = "Adventure.Inc.S03E19.DVDRip.XviD-OSiTV";
const int season = 3;
const int episode = 19;
var quality = Quality.DVD;
const string summary = "My fake summary";
var fakeSettings = Builder<Indexer>.CreateNew().Build();
Mocker.GetMock<IIndexerService>()
.Setup(c => c.GetSettings(It.IsAny<Type>()))
.Returns(fakeSettings);
var fakeRssItem = Builder<SyndicationItem>.CreateNew()
.With(c => c.Title = new TextSyndicationContent(title))
.With(c => c.Summary = new TextSyndicationContent(summary))
.Build();
var result = Mocker.Resolve<CustomParserIndexer>().ParseFeed(fakeRssItem);
Assert.IsNotNull(result);
Assert.AreEqual(LanguageType.Finnish, result.Language);
Assert.AreEqual(season, result.SeasonNumber);
Assert.AreEqual(episode, result.EpisodeNumbers[0]);
Assert.AreEqual(quality, result.Quality.Quality);
}
[TestCase("Adventure.Inc.DVDRip.XviD-OSiTV")]
public void custom_parser_full_parse(string title)
{
const string summary = "My fake summary";
var fakeSettings = Builder<Indexer>.CreateNew().Build();
Mocker.GetMock<IIndexerService>()
.Setup(c => c.GetSettings(It.IsAny<Type>()))
.Returns(fakeSettings);
var fakeRssItem = Builder<SyndicationItem>.CreateNew()
.With(c => c.Title = new TextSyndicationContent(title))
.With(c => c.Summary = new TextSyndicationContent(summary))
.Build();
var result = Mocker.Resolve<CustomParserIndexer>().ParseFeed(fakeRssItem);
Assert.IsNotNull(result);
Assert.AreEqual(LanguageType.Finnish, result.Language);
}
[TestCase("hawaii five-0 (2010)", "hawaii+five+0+2010")]
[TestCase("this& that", "this+that")]
[TestCase("this& that", "this+that")]
[TestCase("grey's anatomy", "grey+s+anatomy")]
public void get_query_title(string raw, string clean)
{
var mock = new Mock<IndexerBase>();
mock.CallBase = true;
var result = mock.Object.GetQueryTitle(raw);
result.Should().Be(clean);
}
[Test]
public void size_nzbsrus()
{
WithConfiguredIndexers();
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream(It.IsAny<String>(), It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "SizeParsing", "nzbsrus.xml"));
var parseResults = Mocker.Resolve<NzbsRUs>().FetchRss();
parseResults.Should().HaveCount(1);
parseResults[0].Size.Should().Be(1793148846);
}
[Test]
public void size_newznab()
{
WithConfiguredIndexers();
var newznabDefs = Builder<NewznabDefinition>.CreateListOfSize(1)
.All()
.With(n => n.ApiKey = String.Empty)
.Build();
Mocker.GetMock<INewznabService>().Setup(s => s.Enabled()).Returns(newznabDefs.ToList());
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream(It.IsAny<String>(), It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "SizeParsing", "newznab.xml"));
var parseResults = Mocker.Resolve<Newznab>().FetchRss();
parseResults[0].Size.Should().Be(1183105773);
}
[Test]
public void size_nzbindex()
{
WithConfiguredIndexers();
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream("http://www.nzbindex.nl/rss/alt.binaries.teevee/?sort=agedesc&minsize=100&complete=1&max=50&more=1&q=%23a.b.teevee", It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "SizeParsing", "nzbindex.xml"));
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream("http://www.nzbindex.nl/rss/alt.binaries.hdtv/?sort=agedesc&minsize=100&complete=1&max=50&more=1&q=", It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "SizeParsing", "nzbindex.xml"));
var parseResults = Mocker.Resolve<NzbIndex>().FetchRss();
parseResults[0].Size.Should().Be(587328389);
}
[Test]
public void size_nzbclub()
{
WithConfiguredIndexers();
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream("http://www.nzbclub.com/nzbfeed.aspx?ig=2&gid=102952&st=1&ns=1&q=%23a.b.teevee", It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "SizeParsing", "nzbclub.xml"));
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream("http://www.nzbclub.com/nzbfeed.aspx?ig=2&gid=5542&st=1&ns=1&q=", It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "SizeParsing", "nzbclub.xml"));
var parseResults = Mocker.Resolve<NzbClub>().FetchRss();
parseResults.Should().HaveCount(2);
parseResults[0].Size.Should().Be(2652142305);
}
[Test]
public void size_omgwtfnzbs()
{
WithConfiguredIndexers();
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream("http://rss.omgwtfnzbs.org/rss-search.php?catid=19,20&user=MockedConfigValue&api=MockedConfigValue&eng=1", It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "SizeParsing", "omgwtfnzbs.xml"));
var parseResults = Mocker.Resolve<Omgwtfnzbs>().FetchRss();
parseResults.Should().HaveCount(1);
parseResults[0].Size.Should().Be(236820890);
}
[Test]
public void Server_Unavailable_503_should_not_log_exception()
{
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream(It.IsAny<String>(), It.IsAny<NetworkCredential>()))
.Throws(new WebException("503"));
Mocker.Resolve<NzbsRUs>().FetchRss();
ExceptionVerification.ExpectedErrors(0);
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void none_503_server_error_should_still_log_error()
{
WithConfiguredIndexers();
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream(It.IsAny<String>(), It.IsAny<NetworkCredential>()))
.Throws(new WebException("some other server error"));
Mocker.Resolve<NzbsRUs>().FetchRss();
ExceptionVerification.ExpectedErrors(1);
ExceptionVerification.ExpectedWarns(0);
}
[Test]
public void indexer_that_isnt_configured_shouldnt_make_an_http_call()
{
Mocker.Resolve<NotConfiguredIndexer>().FetchRss();
Mocker.GetMock<HttpProvider>()
.Verify(c => c.DownloadFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never());
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void newznab_link_should_be_link_to_nzb_not_details()
{
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream(It.IsAny<String>(), It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "newznab.xml"));
var fakeSettings = Builder<Indexer>.CreateNew().Build();
Mocker.GetMock<IIndexerService>()
.Setup(c => c.GetSettings(It.IsAny<Type>()))
.Returns(fakeSettings);
var mockIndexer = Mocker.Resolve<MockIndexer>();
var parseResults = mockIndexer.FetchRss();
parseResults.Should().NotBeEmpty();
parseResults.Should().OnlyContain(s => s.NzbUrl.Contains("getnzb"));
parseResults.Should().NotContain(s => s.NzbUrl.Contains("details"));
}
private static void Mark500Inconclusive()
{
ExceptionVerification.MarkInconclusive(typeof(WebException));
ExceptionVerification.MarkInconclusive("System.Net.WebException");
ExceptionVerification.MarkInconclusive("(503) Server Unavailable.");
ExceptionVerification.MarkInconclusive("(500) Internal Server Error.");
}
[TestCase("wombles.xml", "de-de")]
public void dateTime_should_parse_when_using_other_cultures(string fileName, string culture)
{
var currentCulture = Thread.CurrentThread.CurrentCulture;
Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream(It.IsAny<String>(), It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "" + fileName));
var fakeSettings = Builder<Indexer>.CreateNew().Build();
Mocker.GetMock<IIndexerService>()
.Setup(c => c.GetSettings(It.IsAny<Type>()))
.Returns(fakeSettings);
var mockIndexer = Mocker.Resolve<MockIndexer>();
var parseResults = mockIndexer.FetchRss();
foreach (var episodeParseResult in parseResults)
{
var Uri = new Uri(episodeParseResult.NzbUrl);
Uri.PathAndQuery.Should().NotContain("//");
}
parseResults.Should().NotBeEmpty();
parseResults.Should().OnlyContain(s => s.Indexer == mockIndexer.Name);
parseResults.Should().OnlyContain(s => !String.IsNullOrEmpty(s.OriginalString));
parseResults.Should().OnlyContain(s => s.Age >= 0);
Thread.CurrentThread.CurrentCulture = currentCulture;
}
[Test]
public void NzbsRus_NzbInfoUrl_should_contain_information_string()
{
WithConfiguredIndexers();
const string fileName = "nzbsrus.xml";
const string expectedString = "nzbdetails";
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream(It.IsAny<String>(), It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "" + fileName));
var parseResults = Mocker.Resolve<NzbsRUs>().FetchRss();
foreach (var episodeParseResult in parseResults)
{
episodeParseResult.NzbInfoUrl.Should().Contain(expectedString);
}
}
[Test]
public void Newznab_NzbInfoUrl_should_contain_information_string()
{
WithConfiguredIndexers();
const string fileName = "newznab.xml";
const string expectedString = "/details/";
var newznabDefs = Builder<NewznabDefinition>.CreateListOfSize(1)
.All()
.With(n => n.ApiKey = String.Empty)
.Build();
Mocker.GetMock<INewznabService>().Setup(s => s.Enabled()).Returns(newznabDefs.ToList());
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream(It.IsAny<String>(), It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "" + fileName));
var parseResults = Mocker.Resolve<Newznab>().FetchRss();
foreach (var episodeParseResult in parseResults)
{
episodeParseResult.NzbInfoUrl.Should().Contain(expectedString);
}
}
[Test]
public void Wombles_NzbInfoUrl_should_contain_information_string()
{
WithConfiguredIndexers();
const string fileName = "wombles.xml";
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream(It.IsAny<String>(), It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "" + fileName));
var parseResults = Mocker.Resolve<Wombles>().FetchRss();
foreach (var episodeParseResult in parseResults)
{
episodeParseResult.NzbInfoUrl.Should().BeNull();
}
}
[Test]
public void FileSharingTalk_NzbInfoUrl_should_contain_information_string()
{
WithConfiguredIndexers();
const string fileName = "filesharingtalk.xml";
const string expectedString = "/nzbs/tv";
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream(It.IsAny<String>(), It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "" + fileName));
var parseResults = Mocker.Resolve<FileSharingTalk>().FetchRss();
foreach (var episodeParseResult in parseResults)
{
episodeParseResult.NzbInfoUrl.Should().Contain(expectedString);
}
}
[Test]
public void NzbIndex_NzbInfoUrl_should_contain_information_string()
{
WithConfiguredIndexers();
const string expectedString = "release";
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream("http://www.nzbindex.nl/rss/alt.binaries.teevee/?sort=agedesc&minsize=100&complete=1&max=50&more=1&q=%23a.b.teevee", It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "nzbindex.xml"));
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream("http://www.nzbindex.nl/rss/alt.binaries.hdtv/?sort=agedesc&minsize=100&complete=1&max=50&more=1&q=", It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "nzbindex.xml"));
var parseResults = Mocker.Resolve<NzbIndex>().FetchRss();
foreach (var episodeParseResult in parseResults)
{
episodeParseResult.NzbInfoUrl.Should().Contain(expectedString);
}
}
[Test]
public void NzbClub_NzbInfoUrl_should_contain_information_string()
{
WithConfiguredIndexers();
const string fileName = "nzbclub.xml";
const string expectedString = "nzb_view";
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream("http://www.nzbclub.com/nzbfeed.aspx?ig=2&gid=102952&st=1&ns=1&q=%23a.b.teevee", It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "" + fileName));
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream("http://www.nzbclub.com/nzbfeed.aspx?ig=2&gid=5542&st=1&ns=1&q=", It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "" + fileName));
var parseResults = Mocker.Resolve<NzbClub>().FetchRss();
foreach (var episodeParseResult in parseResults)
{
episodeParseResult.NzbInfoUrl.Should().Contain(expectedString);
}
}
[TestCase("30 Rock", "30%20Rock")]
[TestCase("The Office (US)", "Office%20US")]
[TestCase("Revenge", "Revenge")]
[TestCase(" Top Gear ", "Top%20Gear")]
[TestCase("Breaking Bad", "Breaking%20Bad")]
[TestCase("Top Chef (US)", "Top%20Chef%20US")]
[TestCase("Castle (2009)", "Castle%202009")]
public void newznab_GetQueryTitle_should_return_expected_result(string seriesTitle, string expected)
{
Mocker.Resolve<Newznab>().GetQueryTitle(seriesTitle).Should().Be(expected);
}
[Test]
public void should_get_nzbInfoUrl_for_omgwtfnzbs()
{
WithConfiguredIndexers();
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadStream("http://rss.omgwtfnzbs.org/rss-search.php?catid=19,20&user=MockedConfigValue&api=MockedConfigValue&eng=1", It.IsAny<NetworkCredential>()))
.Returns(OpenRead("Files", "Rss", "SizeParsing", "omgwtfnzbs.xml"));
var parseResults = Mocker.Resolve<Omgwtfnzbs>().FetchRss();
parseResults.Should().HaveCount(1);
parseResults[0].NzbInfoUrl.Should().Be("http://omgwtfnzbs.org/details.php?id=OAl4g");
}
}
}

@ -1,120 +0,0 @@
using System;
using System.Net;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.IndexerTests
{
[TestFixture]
public class NzbxFixture : CoreTest
{
[Test]
public void should_get_size_when_parsing_recent_feed()
{
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadString("https://nzbx.co/api/recent?category=tv", It.IsAny<NetworkCredential>()))
.Returns(ReadAllText("Files", "Rss", "SizeParsing", "nzbx_recent.json"));
var parseResults = Mocker.Resolve<Nzbx>().FetchRss();
parseResults.Should().HaveCount(1);
parseResults[0].Size.Should().Be(890190951);
}
[Test]
public void should_get_size_when_parsing_search_results()
{
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadString("https://nzbx.co/api/search?q=30+Rock+S01E01", It.IsAny<NetworkCredential>()))
.Returns(ReadAllText("Files", "Rss", "SizeParsing", "nzbx_search.json"));
var parseResults = Mocker.Resolve<Nzbx>().FetchEpisode("30 Rock", 1, 1);
parseResults.Should().HaveCount(1);
parseResults[0].Size.Should().Be(418067671);
}
[Test]
public void should_be_able_parse_results_from_recent_feed()
{
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadString(It.IsAny<String>(), It.IsAny<NetworkCredential>()))
.Returns(ReadAllText("Files", "Rss", "nzbx_recent.json"));
var parseResults = Mocker.Resolve<Nzbx>().FetchRss();
parseResults.Should().NotBeEmpty();
parseResults.Should().OnlyContain(s => s.Indexer == "nzbx");
parseResults.Should().OnlyContain(s => !String.IsNullOrEmpty(s.OriginalString));
parseResults.Should().OnlyContain(s => s.Age >= 0);
}
[Test]
public void should_be_able_to_parse_results_from_search_results()
{
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadString(It.IsAny<String>(), It.IsAny<NetworkCredential>()))
.Returns(ReadAllText("Files", "Rss", "nzbx_search.json"));
var parseResults = Mocker.Resolve<Nzbx>().FetchEpisode("30 Rock", 1, 1);
parseResults.Should().NotBeEmpty();
parseResults.Should().OnlyContain(s => s.Indexer == "nzbx");
parseResults.Should().OnlyContain(s => !String.IsNullOrEmpty(s.OriginalString));
parseResults.Should().OnlyContain(s => s.Age >= 0);
}
[Test]
public void should_get_postedDate_when_parsing_recent_feed()
{
var expectedAge = DateTime.Today.Subtract(new DateTime(2012, 12, 21)).Days;
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadString("https://nzbx.co/api/recent?category=tv", It.IsAny<NetworkCredential>()))
.Returns(ReadAllText("Files", "Rss", "SizeParsing", "nzbx_recent.json"));
var parseResults = Mocker.Resolve<Nzbx>().FetchRss();
parseResults.Should().HaveCount(1);
parseResults[0].Age.Should().Be(expectedAge);
}
[Test]
public void should_get_postedDate_when_parsing_search_results()
{
var expectedAge = DateTime.Today.Subtract(new DateTime(2012, 2, 11)).Days;
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadString("https://nzbx.co/api/search?q=30+Rock+S01E01", It.IsAny<NetworkCredential>()))
.Returns(ReadAllText("Files", "Rss", "SizeParsing", "nzbx_search.json"));
var parseResults = Mocker.Resolve<Nzbx>().FetchEpisode("30 Rock", 1, 1);
parseResults.Should().HaveCount(1);
parseResults[0].Age.Should().Be(expectedAge);
}
[Test]
public void should_name_nzb_properly()
{
Mocker.GetMock<HttpProvider>()
.Setup(h => h.DownloadString("https://nzbx.co/api/recent?category=tv", It.IsAny<NetworkCredential>()))
.Returns(ReadAllText("Files", "Rss", "SizeParsing", "nzbx_recent.json"));
var parseResults = Mocker.Resolve<Nzbx>().FetchRss();
parseResults.Should().HaveCount(1);
parseResults[0].NzbUrl.Should().EndWith(parseResults[0].OriginalString);
}
}
}

@ -1,362 +0,0 @@

using System;
using System.Collections.Generic;
using System.Net;
using System.ServiceModel.Syndication;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Model;
using NzbDrone.Core.Providers;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common.AutoMoq;
namespace NzbDrone.Core.Test.Indexers
{
[TestFixture]
public class IndexerServiceTest : CoreTest<IndexerService>
{
[Test]
public void should_insert_indexer_in_repository_when_it_doesnt_exist()
{
Mocker.SetConstant<IEnumerable<IndexerBase>>(new List<IndexerBase> { Mocker.Resolve<MockIndexer>() });
Subject.Init();
Mocker.GetMock<IIndexerRepository>()
.Verify(v => v.Insert(It.IsAny<Indexer>()), Times.Once());
}
[Test]
public void getEnabled_should_not_return_any_when_no_indexers_are_enabled()
{
Mocker.SetConstant<IEnumerable<IndexerBase>>(new List<IndexerBase> { Mocker.Resolve<MockIndexer>() });
Mocker.GetMock<IIndexerRepository>()
.Setup(s => s.All())
.Returns(new List<Indexer> {new Indexer {Id = 1, Type = "", Enable = false, Name = "Fake Indexer"}});
Subject.GetEnabledIndexers().Should().BeEmpty();
}
[Test]
public void Init_indexer_should_enable_indexer_that_is_enabled_by_default()
{
Mocker.SetConstant<IEnumerable<IndexerBase>>(new List<IndexerBase> { Mocker.Resolve<DefaultEnabledIndexer>() });
Subject.Init();
Mocker.GetMock<IIndexerRepository>()
.Verify(v => v.Insert(It.Is<Indexer>(indexer => indexer.Enable)), Times.Once());
Mocker.GetMock<IIndexerRepository>()
.Verify(v => v.Insert(It.Is<Indexer>(indexer => !indexer.Enable)), Times.Never());
}
[Test]
public void Init_indexer_should_not_enable_indexer_that_is_not_enabled_by_default()
{
Mocker.SetConstant<IEnumerable<IndexerBase>>(new List<IndexerBase> { Mocker.Resolve<MockIndexer>() });
Subject.Init();
Mocker.GetMock<IIndexerRepository>()
.Verify(v => v.Insert(It.Is<Indexer>(indexer => indexer.Enable)), Times.Never());
Mocker.GetMock<IIndexerRepository>()
.Verify(v => v.Insert(It.Is<Indexer>(indexer => !indexer.Enable)), Times.Once());
}
}
public class MockIndexer : IndexerBase
{
public MockIndexer(HttpProvider httpProvider, IConfigService configService)
: base(httpProvider, configService)
{
}
protected override string[] Urls
{
get { return new[] { "www.google.com" }; }
}
public override bool IsConfigured
{
get { return true; }
}
protected override NetworkCredential Credentials
{
get { return null; }
}
protected override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
{
throw new NotImplementedException();
}
protected override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
{
throw new NotImplementedException();
}
protected override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
{
throw new NotImplementedException();
}
protected override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
{
throw new NotImplementedException();
}
public override string Name
{
get { return "Mocked Indexer"; }
}
protected override string NzbDownloadUrl(SyndicationItem item)
{
return item.Links[0].Uri.ToString();
}
protected override string NzbInfoUrl(SyndicationItem item)
{
return item.Links[0].Uri.ToString();
}
}
public class TestUrlIndexer : IndexerBase
{
public TestUrlIndexer(HttpProvider httpProvider, IConfigService configService)
: base(httpProvider, configService)
{
}
public override string Name
{
get { return "All Urls"; }
}
protected override string[] Urls
{
get { return new[] { "http://rss.nzbs.com/rss.php?cat=TV" }; }
}
public override bool IsConfigured
{
get { return true; }
}
protected override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
{
throw new NotImplementedException();
}
protected override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
{
throw new NotImplementedException();
}
protected override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
{
throw new NotImplementedException();
}
protected override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
{
throw new NotImplementedException();
}
protected override string NzbDownloadUrl(SyndicationItem item)
{
return "http://google.com";
}
protected override string NzbInfoUrl(SyndicationItem item)
{
return "http://google.com";
}
}
public class CustomParserIndexer : IndexerBase
{
public CustomParserIndexer(HttpProvider httpProvider, IConfigService configService)
: base(httpProvider, configService)
{
}
public override string Name
{
get { return "Custom parser"; }
}
protected override string[] Urls
{
get { return new[] { "http://www.google.com" }; }
}
public override bool IsConfigured
{
get { return true; }
}
protected override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
{
throw new NotImplementedException();
}
protected override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
{
throw new NotImplementedException();
}
protected override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
{
throw new NotImplementedException();
}
protected override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
{
throw new NotImplementedException();
}
protected override string NzbDownloadUrl(SyndicationItem item)
{
return "http://www.google.com";
}
protected override string NzbInfoUrl(SyndicationItem item)
{
return "http://www.google.com";
}
protected override EpisodeParseResult CustomParser(SyndicationItem item, EpisodeParseResult currentResult)
{
if (currentResult == null) currentResult = new EpisodeParseResult();
currentResult.Language = LanguageType.Finnish;
return currentResult;
}
}
public class NotConfiguredIndexer : IndexerBase
{
public NotConfiguredIndexer(HttpProvider httpProvider, IConfigService configService)
: base(httpProvider, configService)
{
}
public override string Name
{
get { return "NotConfiguredIndexer"; }
}
protected override string[] Urls
{
get { return new[] { "http://rss.nzbs.com/rss.php?cat=TV" }; }
}
public override bool IsConfigured
{
get { return false; }
}
protected override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
{
throw new NotImplementedException();
}
protected override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
{
throw new NotImplementedException();
}
protected override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
{
throw new NotImplementedException();
}
protected override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
{
throw new NotImplementedException();
}
protected override string NzbDownloadUrl(SyndicationItem item)
{
throw new NotImplementedException();
}
protected override string NzbInfoUrl(SyndicationItem item)
{
throw new NotImplementedException();
}
}
public class DefaultEnabledIndexer : IndexerBase
{
public DefaultEnabledIndexer(HttpProvider httpProvider, IConfigService configService)
: base(httpProvider, configService)
{
}
protected override string[] Urls
{
get { return new[] { "www.google.com" }; }
}
public override bool IsConfigured
{
get { return true; }
}
protected override NetworkCredential Credentials
{
get { return null; }
}
protected override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
{
throw new NotImplementedException();
}
protected override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
{
throw new NotImplementedException();
}
protected override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
{
throw new NotImplementedException();
}
protected override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
{
throw new NotImplementedException();
}
public override string Name
{
get { return "Mocked Indexer"; }
}
protected override string NzbDownloadUrl(SyndicationItem item)
{
return item.Links[0].Uri.ToString();
}
protected override string NzbInfoUrl(SyndicationItem item)
{
return item.Links[1].Uri.ToString();
}
public override bool EnabledByDefault
{
get { return true; }
}
}
}

@ -1,90 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Security.Policy;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Indexers
{
[TestFixture]
public class NewznabProviderTest : CoreTest<NewznabService>
{
private void WithInvalidName()
{
Mocker.GetMock<INewznabRepository>()
.Setup(s => s.All())
.Returns(new List<NewznabDefinition>{new NewznabDefinition { Id = 1, Name = "", Url = "http://www.nzbdrone.com" }});
}
private void WithExisting()
{
Mocker.GetMock<INewznabRepository>()
.Setup(s => s.All())
.Returns(new List<NewznabDefinition> { new NewznabDefinition { Id = 1, Name = "Nzbs.org", Url = "http://nzbs.org" } });
}
[Test]
public void InitializeNewznabIndexers_should_initialize_build_in_indexers()
{
Subject.Init();
Mocker.GetMock<INewznabRepository>()
.Verify(s => s.Insert(It.Is<NewznabDefinition>(n => n.BuiltIn)), Times.Exactly(3));
}
[Test]
public void should_delete_indexers_without_names()
{
WithInvalidName();
Subject.Init();
Mocker.GetMock<INewznabRepository>()
.Verify(s => s.Delete(1), Times.Once());
}
[Test]
public void should_add_new_indexers()
{
WithExisting();
Subject.Init();
Mocker.GetMock<INewznabRepository>()
.Verify(s => s.Insert(It.IsAny<NewznabDefinition>()), Times.Exactly(2));
}
[Test]
public void should_update_existing()
{
WithExisting();
Subject.Init();
Mocker.GetMock<INewznabRepository>()
.Verify(s => s.Update(It.IsAny<NewznabDefinition>()), Times.Once());
}
[Test]
public void CheckHostname_should_do_nothing_if_hostname_is_valid()
{
Subject.CheckHostname("http://www.google.com");
}
[Test]
public void CheckHostname_should_log_error_and_throw_exception_if_dnsHostname_is_invalid()
{
Assert.Throws<SocketException>(() => Subject.CheckHostname("http://BadName"));
ExceptionVerification.ExpectedErrors(1);
}
}
}

@ -1,342 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Jobs.Implementations;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Jobs;
using NzbDrone.Core.Model;
using NzbDrone.Core.Model.Notification;
using NzbDrone.Core.Providers;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.JobTests
{
[TestFixture]
public class BacklogSearchJobTest : CoreTest<BacklogSearchJob>
{
private void WithEnableBacklogSearching()
{
Mocker.GetMock<IConfigService>().SetupGet(s => s.EnableBacklogSearching).Returns(true);
}
[Test]
public void no_missing_epsiodes_should_not_trigger_any_search()
{
var notification = new ProgressNotification("Backlog Search Job Test");
var episodes = new List<Episode>();
WithStrictMocker();
WithEnableBacklogSearching();
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes);
Subject.Start(notification, null);
Mocker.GetMock<SeasonSearchJob>().Verify(c => c.Start(notification, new { SeriesId = It.IsAny<int>(), SeasonNumber = It.IsAny<int>() }),
Times.Never());
Mocker.GetMock<EpisodeSearchJob>().Verify(c => c.Start(notification, new { SeriesId = It.IsAny<int>(), SeasonNumber = 0 }),
Times.Never());
}
[Test]
public void individual_missing_episode()
{
var notification = new ProgressNotification("Backlog Search Job Test");
var series = Builder<Series>.CreateNew()
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Enable)
.Build();
var episodes = Builder<Episode>.CreateListOfSize(1)
.All()
.With(e => e.Series = series)
.Build().ToList();
WithEnableBacklogSearching();
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes);
Mocker.GetMock<EpisodeSearchJob>()
.Setup(s => s.Start(notification, It.Is<object>(d => d.GetPropertyValue<int>("EpisodeId") == 1)));
Subject.Start(notification, null);
Mocker.GetMock<EpisodeSearchJob>().Verify(c => c.Start(notification, It.Is<object>(d => d.GetPropertyValue<int>("EpisodeId") >= 0)),
Times.Once());
}
[Test]
public void individual_missing_episodes_only()
{
var notification = new ProgressNotification("Backlog Search Job Test");
var series = Builder<Series>.CreateNew()
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Enable)
.Build();
var episodes = Builder<Episode>.CreateListOfSize(5)
.All()
.With(e => e.Series = series)
.Build().ToList();
WithEnableBacklogSearching();
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes);
Subject.Start(notification, null);
Mocker.GetMock<EpisodeSearchJob>().Verify(c => c.Start(notification, It.Is<object>(d => d.GetPropertyValue<int>("EpisodeId") >= 0)),
Times.Exactly(episodes.Count));
}
[Test]
public void series_season_missing_episodes_only_mismatch_count()
{
var notification = new ProgressNotification("Backlog Search Job Test");
var series = Builder<Series>.CreateNew()
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Enable)
.Build();
var episodes = Builder<Episode>.CreateListOfSize(5)
.All()
.With(e => e.Series = series)
.With(e => e.SeasonNumber = 1)
.Build().ToList();
WithEnableBacklogSearching();
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes);
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.GetEpisodeNumbersBySeason(1, 1)).Returns(new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
Subject.Start(notification, null);
Mocker.GetMock<EpisodeSearchJob>().Verify(c => c.Start(notification, It.Is<object>(d => d.GetPropertyValue<int>("EpisodeId") >= 0)),
Times.Exactly(episodes.Count));
}
[Test]
public void series_season_missing_episodes_only()
{
var notification = new ProgressNotification("Backlog Search Job Test");
var series = Builder<Series>.CreateNew()
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Enable)
.Build();
var episodes = Builder<Episode>.CreateListOfSize(5)
.All()
.With(e => e.Series = series)
.With(e => e.SeriesId = series.Id)
.With(e => e.SeasonNumber = 1)
.Build().ToList();
WithEnableBacklogSearching();
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes);
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.GetEpisodeNumbersBySeason(1, 1)).Returns(episodes.Select(e => e.EpisodeNumber).ToList());
Subject.Start(notification, null);
Mocker.GetMock<SeasonSearchJob>().Verify(c => c.Start(notification, It.Is<object>(d => d.GetPropertyValue<int>("SeriesId") >= 0 &&
d.GetPropertyValue<int>("SeasonNumber") >= 0)),
Times.Once());
}
[Test]
public void multiple_missing_episodes()
{
var notification = new ProgressNotification("Backlog Search Job Test");
var series = Builder<Series>.CreateNew()
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Enable)
.Build();
var series2 = Builder<Series>.CreateNew()
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Enable)
.Build();
var episodes = Builder<Episode>.CreateListOfSize(10)
.TheFirst(5)
.With(e => e.Series = series)
.With(e => e.SeriesId = series.Id)
.With(e => e.SeasonNumber = 1)
.TheNext(5)
.With(e => e.Series = series2)
.Build().ToList();
WithEnableBacklogSearching();
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes);
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.GetEpisodeNumbersBySeason(1, 1)).Returns(new List<int> { 1, 2, 3, 4, 5 });
Subject.Start(notification, null);
Mocker.GetMock<SeasonSearchJob>().Verify(c => c.Start(notification, It.Is<object>(d => d.GetPropertyValue<int>("SeriesId") >= 0 &&
d.GetPropertyValue<int>("SeasonNumber") >= 0)),
Times.Once());
Mocker.GetMock<EpisodeSearchJob>().Verify(c => c.Start(notification, It.Is<object>(d => d.GetPropertyValue<int>("EpisodeId") >= 0)),
Times.Exactly(5));
}
[Test]
public void GetMissingForEnabledSeries_should_only_return_episodes_for_monitored_series()
{
var series = Builder<Series>.CreateListOfSize(2)
.TheFirst(1)
.With(s => s.Monitored = false)
.With(s => s.BacklogSetting = BacklogSettingType.Enable)
.TheNext(1)
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Enable)
.Build();
var episodes = Builder<Episode>.CreateListOfSize(11)
.TheFirst(5)
.With(e => e.Series = series[0])
.With(e => e.SeasonNumber = 1)
.TheLast(6)
.With(e => e.Series = series[1])
.Build().ToList();
WithEnableBacklogSearching();
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes);
var result = Subject.GetMissingForEnabledSeries();
result.Should().NotBeEmpty();
result.Should().Contain(s => s.Series.Monitored);
result.Should().NotContain(s => !s.Series.Monitored);
}
[Test]
public void GetMissingForEnabledSeries_should_only_return_explicity_enabled_series_when_backlog_searching_is_ignored()
{
var series = Builder<Series>.CreateListOfSize(3)
.TheFirst(1)
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Disable)
.TheNext(1)
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Enable)
.TheNext(1)
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Inherit)
.Build();
var episodes = Builder<Episode>.CreateListOfSize(12)
.TheFirst(3)
.With(e => e.Series = series[0])
.TheNext(4)
.With(e => e.Series = series[1])
.TheNext(5)
.With(e => e.Series = series[2])
.Build().ToList();
//WithEnableBacklogSearching();
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes);
var result = Subject.GetMissingForEnabledSeries();
result.Should().NotBeEmpty();
result.Should().Contain(s => s.Series.BacklogSetting == BacklogSettingType.Enable);
result.Should().NotContain(s => s.Series.BacklogSetting == BacklogSettingType.Disable);
result.Should().NotContain(s => s.Series.BacklogSetting == BacklogSettingType.Inherit);
}
[Test]
public void GetMissingForEnabledSeries_should_return_explicity_enabled_and_inherit_series_when_backlog_searching_is_enabled()
{
var series = Builder<Series>.CreateListOfSize(3)
.TheFirst(1)
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Disable)
.TheNext(1)
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Enable)
.TheNext(1)
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Inherit)
.Build();
var episodes = Builder<Episode>.CreateListOfSize(12)
.TheFirst(3)
.With(e => e.Series = series[0])
.TheNext(4)
.With(e => e.Series = series[1])
.TheNext(5)
.With(e => e.Series = series[2])
.Build().ToList();
WithEnableBacklogSearching();
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes);
var result = Subject.GetMissingForEnabledSeries();
result.Should().NotBeEmpty();
result.Should().Contain(s => s.Series.BacklogSetting == BacklogSettingType.Enable);
result.Should().NotContain(s => s.Series.BacklogSetting == BacklogSettingType.Disable);
result.Should().Contain(s => s.Series.BacklogSetting == BacklogSettingType.Inherit);
}
}
}

@ -1,219 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Jobs.Implementations;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Jobs;
using NzbDrone.Core.Model;
using NzbDrone.Core.Model.Notification;
using NzbDrone.Core.Providers;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
using NzbDrone.Test.Common.AutoMoq;
namespace NzbDrone.Core.Test.JobTests
{
[TestFixture]
public class RecentBacklogSearchJobTest : CoreTest
{
private void WithEnableBacklogSearching()
{
Mocker.GetMock<IConfigService>().SetupGet(s => s.EnableBacklogSearching).Returns(true);
}
[SetUp]
public void Setup()
{
}
[Test]
public void no_missing_epsiodes_should_not_trigger_any_search()
{
var episodes = new List<Episode>();
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes);
Mocker.Resolve<RecentBacklogSearchJob>().Start(MockNotification, null);
Mocker.GetMock<EpisodeSearchJob>().Verify(c => c.Start(MockNotification, new { EpisodeId = It.IsAny<int>() }),
Times.Never());
}
[Test]
public void should_only_process_missing_episodes_from_the_last_30_days()
{
WithEnableBacklogSearching();
var series = Builder<Series>.CreateNew()
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Enable)
.Build();
var episodes = Builder<Episode>.CreateListOfSize(50)
.All()
.With(e => e.Series = series)
.TheFirst(5)
.With(e => e.AirDate = DateTime.Today)
.TheNext(5)
.With(e => e.AirDate = DateTime.Today.AddDays(-1)) //Today
.TheNext(5)
.With(e => e.AirDate = DateTime.Today.AddDays(-5)) //Yeserday
.TheNext(5)
.With(e => e.AirDate = DateTime.Today.AddDays(-10))
.TheNext(5)
.With(e => e.AirDate = DateTime.Today.AddDays(-15))
.TheNext(5)
.With(e => e.AirDate = DateTime.Today.AddDays(-20))
.TheNext(5)
.With(e => e.AirDate = DateTime.Today.AddDays(-25))
.TheNext(5)
.With(e => e.AirDate = DateTime.Today.AddDays(-30))
.TheNext(5)
.With(e => e.AirDate = DateTime.Today.AddDays(-31)) //31 Days
.TheNext(5)
.With(e => e.AirDate = DateTime.Today.AddDays(-35))
.Build().ToList();
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes);
Mocker.GetMock<EpisodeSearchJob>().Setup(c => c.Start(It.IsAny<ProgressNotification>(), It.Is<object>(d => d.GetPropertyValue<int>("EpisodeId") >= 0)));
Mocker.Resolve<RecentBacklogSearchJob>().Start(MockNotification, null);
Mocker.GetMock<EpisodeSearchJob>().Verify(c => c.Start(It.IsAny<ProgressNotification>(), It.Is<object>(d => d.GetPropertyValue<int>("EpisodeId") >= 0)),
Times.Exactly(40));
}
[Test]
public void GetMissingForEnabledSeries_should_only_return_episodes_for_monitored_series()
{
var series = Builder<Series>.CreateListOfSize(2)
.TheFirst(1)
.With(s => s.Monitored = false)
.With(s => s.BacklogSetting = BacklogSettingType.Enable)
.TheNext(1)
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Enable)
.Build();
var episodes = Builder<Episode>.CreateListOfSize(11)
.TheFirst(5)
.With(e => e.Series = series[0])
.With(e => e.SeasonNumber = 1)
.TheLast(6)
.With(e => e.Series = series[1])
.Build().ToList();
WithEnableBacklogSearching();
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes);
var result = Mocker.Resolve<RecentBacklogSearchJob>().GetMissingForEnabledSeries();
result.Should().NotBeEmpty();
result.Should().Contain(s => s.Series.Monitored);
result.Should().NotContain(s => !s.Series.Monitored);
}
[Test]
public void GetMissingForEnabledSeries_should_only_return_explicity_enabled_series_when_backlog_searching_is_ignored()
{
var series = Builder<Series>.CreateListOfSize(3)
.TheFirst(1)
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Disable)
.TheNext(1)
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Enable)
.TheNext(1)
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Inherit)
.Build();
var episodes = Builder<Episode>.CreateListOfSize(12)
.TheFirst(3)
.With(e => e.Series = series[0])
.TheNext(4)
.With(e => e.Series = series[1])
.TheNext(5)
.With(e => e.Series = series[2])
.Build().ToList();
//WithEnableBacklogSearching();
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes);
var result = Mocker.Resolve<RecentBacklogSearchJob>().GetMissingForEnabledSeries();
result.Should().NotBeEmpty();
result.Should().Contain(s => s.Series.BacklogSetting == BacklogSettingType.Enable);
result.Should().NotContain(s => s.Series.BacklogSetting == BacklogSettingType.Disable);
result.Should().NotContain(s => s.Series.BacklogSetting == BacklogSettingType.Inherit);
}
[Test]
public void GetMissingForEnabledSeries_should_return_explicity_enabled_and_inherit_series_when_backlog_searching_is_enabled()
{
var series = Builder<Series>.CreateListOfSize(3)
.TheFirst(1)
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Disable)
.TheNext(1)
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Enable)
.TheNext(1)
.With(s => s.Monitored = true)
.With(s => s.BacklogSetting = BacklogSettingType.Inherit)
.Build();
var episodes = Builder<Episode>.CreateListOfSize(12)
.TheFirst(3)
.With(e => e.Series = series[0])
.TheNext(4)
.With(e => e.Series = series[1])
.TheNext(5)
.With(e => e.Series = series[2])
.Build().ToList();
WithEnableBacklogSearching();
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.EpisodesWithoutFiles(true)).Returns(episodes);
var result = Mocker.Resolve<RecentBacklogSearchJob>().GetMissingForEnabledSeries();
result.Should().NotBeEmpty();
result.Should().Contain(s => s.Series.BacklogSetting == BacklogSettingType.Enable);
result.Should().NotContain(s => s.Series.BacklogSetting == BacklogSettingType.Disable);
result.Should().Contain(s => s.Series.BacklogSetting == BacklogSettingType.Inherit);
}
}
}

@ -1,129 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Jobs.Implementations;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Model.Notification;
using NzbDrone.Core.Providers;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.JobTests
{
[TestFixture]
public class SeasonSearchJobTest : CoreTest
{
private List<Episode> _episodes;
private ProgressNotification notification;
[SetUp]
public void Setup()
{
notification = new ProgressNotification("Search");
_episodes = Builder<Episode>.CreateListOfSize(5)
.All()
.With(e => e.SeriesId = 1)
.With(e => e.SeasonNumber = 1)
.With(e => e.Ignored = false)
.With(e => e.AirDate = DateTime.Today.AddDays(-1))
.Build().ToList();
Mocker.GetMock<IEpisodeService>()
.Setup(c => c.GetEpisodesBySeason(1, 1)).Returns(_episodes);
}
[Test]
public void SeasonSearch_partial_season_success()
{
Mocker.GetMock<SearchProvider>()
.Setup(c => c.PartialSeasonSearch(notification, 1, 1))
.Returns(_episodes.Select(e => e.EpisodeNumber).ToList());
Mocker.Resolve<SeasonSearchJob>().Start(notification, new { SeriesId = 1, SeasonNumber = 1 });
Mocker.VerifyAllMocks();
Mocker.GetMock<SearchProvider>().Verify(c => c.PartialSeasonSearch(notification, 1, 1), Times.Once());
Mocker.GetMock<EpisodeSearchJob>().Verify(c => c.Start(notification, new { EpisodeId = It.IsAny<int>() }), Times.Never());
}
[Test]
public void SeasonSearch_partial_season_failure()
{
Mocker.GetMock<SearchProvider>()
.Setup(c => c.PartialSeasonSearch(notification, 1, 1))
.Returns(new List<int>());
Mocker.Resolve<SeasonSearchJob>().Start(notification, new { SeriesId = 1, SeasonNumber = 1 });
Mocker.GetMock<SearchProvider>().Verify(c => c.PartialSeasonSearch(notification, 1, 1), Times.Once());
}
[Test]
public void SeasonSearch_should_not_search_for_episodes_that_havent_aired_yet_or_air_tomorrow()
{
var episodes = Builder<Episode>.CreateListOfSize(5)
.All()
.With(e => e.SeriesId = 1)
.With(e => e.SeasonNumber = 1)
.With(e => e.Ignored = false)
.With(e => e.AirDate = DateTime.Today.AddDays(-1))
.TheLast(2)
.With(e => e.AirDate = DateTime.Today.AddDays(2))
.Build().ToList();
Mocker.GetMock<IEpisodeService>()
.Setup(c => c.GetEpisodesBySeason(1, 1)).Returns(episodes);
Mocker.GetMock<SearchProvider>()
.Setup(c => c.PartialSeasonSearch(notification, 1, 1))
.Returns(new List<int>{1});
Mocker.Resolve<SeasonSearchJob>().Start(notification, new { SeriesId = 1, SeasonNumber = 1 });
Mocker.VerifyAllMocks();
Mocker.GetMock<SearchProvider>().Verify(c => c.PartialSeasonSearch(notification, 1, 1), Times.Once());
}
[Test]
public void SeasonSearch_should_allow_searching_of_season_zero()
{
Mocker.GetMock<SearchProvider>()
.Setup(c => c.PartialSeasonSearch(notification, 1, 0)).Returns(new List<int>());
Mocker.Resolve<SeasonSearchJob>().Start(notification, new { SeriesId = 1, SeasonNumber = 0 });
Mocker.GetMock<SearchProvider>().Verify(c => c.PartialSeasonSearch(notification, 1, 1), Times.Never());
Mocker.GetMock<EpisodeSearchJob>().Verify(c => c.Start(notification, new { EpisodeId = It.IsAny<int>() }), Times.Never());
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_search_for_individual_episodes_when_no_partial_results_are_returned()
{
Mocker.GetMock<SearchProvider>()
.Setup(c => c.PartialSeasonSearch(notification, 1, 1)).Returns(new List<int>());
Mocker.Resolve<SeasonSearchJob>().Start(notification, new { SeriesId = 1, SeasonNumber = 1 });
Mocker.GetMock<EpisodeSearchJob>().Verify(v => v.Start(notification, It.Is<object>(o => o.GetPropertyValue<Int32>("EpisodeId") > 0)), Times.Exactly(_episodes.Count));
}
}
}

@ -1,82 +0,0 @@
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Jobs.Implementations;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Jobs;
using NzbDrone.Core.Model.Notification;
using NzbDrone.Core.Providers;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
using NzbDrone.Test.Common.AutoMoq;
namespace NzbDrone.Core.Test.JobTests
{
[TestFixture]
public class SeriesSearchJobTest : CoreTest
{
[Test]
public void SeriesSearch_success()
{
var seasons = new List<int> { 1, 2, 3, 4, 5 };
WithStrictMocker();
var notification = new ProgressNotification("Series Search");
Mocker.GetMock<ISeasonRepository>()
.Setup(c => c.GetSeasonNumbers(1)).Returns(seasons);
Mocker.GetMock<ISeasonRepository>()
.Setup(c => c.IsIgnored(It.IsAny<int>(), It.IsAny<int>())).Returns(false);
Mocker.GetMock<SeasonSearchJob>()
.Setup(c => c.Start(notification, It.Is<object>(d => d.GetPropertyValue<int>("SeriesId") == 1 && d.GetPropertyValue<int>("SeasonNumber") >= 0))).Verifiable();
Mocker.Resolve<SeriesSearchJob>().Start(notification, new { SeriesId = 1 });
Mocker.VerifyAllMocks();
Mocker.GetMock<SeasonSearchJob>().Verify(c => c.Start(notification, It.Is<object>(d => d.GetPropertyValue<int>("SeriesId") == 1 && d.GetPropertyValue<int>("SeasonNumber") >= 0)),
Times.Exactly(seasons.Count));
}
[Test]
public void SeriesSearch_no_seasons()
{
var seasons = new List<int>();
WithStrictMocker();
var notification = new ProgressNotification("Series Search");
Mocker.GetMock<ISeasonRepository>()
.Setup(c => c.GetSeasonNumbers(1)).Returns(seasons);
Mocker.Resolve<SeriesSearchJob>().Start(notification, new { SeriesId = 1 });
Mocker.VerifyAllMocks();
Mocker.GetMock<SeasonSearchJob>().Verify(c => c.Start(notification, new { SeriesId = 1, SeasonNumber = It.IsAny<int>() }),
Times.Never());
}
[Test]
public void SeriesSearch_should_not_search_for_season_0()
{
Mocker.GetMock<ISeasonRepository>()
.Setup(c => c.GetSeasonNumbers(It.IsAny<int>()))
.Returns(new List<int> { 0, 1, 2 });
Mocker.Resolve<SeriesSearchJob>().Start(MockNotification, new { SeriesId = 12 });
Mocker.GetMock<SeasonSearchJob>()
.Verify(c => c.Start(It.IsAny<ProgressNotification>(), new { SeriesId = It.IsAny<int>(), SeasonNumber = 0 }), Times.Never());
}
}
}

@ -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.

@ -134,17 +134,7 @@
<Compile Include="Framework\DbTest.cs" />
<Compile Include="Framework\NBuilderExtensions.cs" />
<Compile Include="HelperTests\XElementHelperTests\ConvertToTFixture.cs" />
<Compile Include="IndexerSearchTests\DailyEpisodeSearchTests\IndexerDailyEpisodeSearchFixture.cs" />
<Compile Include="IndexerSearchTests\DailyEpisodeSearchTests\IndexerDailyEpisodeSearch_EpisodeMatch.cs" />
<Compile Include="IndexerSearchTests\EpisodeSearchTests\IndexerEpisodeSearchFixture.cs" />
<Compile Include="IndexerSearchTests\EpisodeSearchTests\IndexerEpisodeSearch_EpisodeMatch.cs" />
<Compile Include="IndexerSearchTests\GetSearchTitleFixture.cs" />
<Compile Include="IndexerSearchTests\IndexerSearchTestBase.cs" />
<Compile Include="IndexerSearchTests\PartialSeasonSearchTests\PartialSeasonSearchFixture.cs" />
<Compile Include="IndexerSearchTests\PartialSeasonSearchTests\PartialSeasonSearch_EpisodeMatch.cs" />
<Compile Include="IndexerSearchTests\ProcessResultsFixture.cs" />
<Compile Include="IndexerSearchTests\TestSearch.cs" />
<Compile Include="IndexerTests\NzbxFixture.cs" />
<Compile Include="IndexerSearchTests\SearchDefinitionFixture.cs" />
<Compile Include="JobTests\JobRepositoryFixture.cs" />
<Compile Include="JobTests\RenameSeasonJobFixture.cs" />
<Compile Include="DecisionEngineTests\LanguageSpecificationFixture.cs" />
@ -157,9 +147,7 @@
<Compile Include="HelperTests\XElementHelperTests\ConvertToDayOfWeekFixture.cs" />
<Compile Include="Qualities\QualityFixture.cs" />
<Compile Include="EpisodeParseResultTest.cs" />
<Compile Include="JobTests\BacklogSearchJobTest.cs" />
<Compile Include="JobTests\PostDownloadScanJobFixture.cs" />
<Compile Include="JobTests\RecentBacklogSearchJobTest.cs" />
<Compile Include="ParserTests\QualityParserFixture.cs" />
<Compile Include="Configuration\ConfigCachingFixture.cs" />
<Compile Include="DecisionEngineTests\AllowedReleaseGroupSpecificationFixture.cs" />
@ -181,7 +169,6 @@
<Compile Include="DecisionEngineTests\QualityUpgradeSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\QualityUpgradableSpecificationFixture.cs" />
<Compile Include="ProviderTests\NotificationProviderTests\NotificationProviderFixture.cs" />
<Compile Include="Indexers\NewznabServiceTest.cs" />
<Compile Include="ProviderTests\DiskProviderTests\FreeDiskSpaceTest.cs" />
<Compile Include="ProviderTests\ProwlProviderTest.cs" />
<Compile Include="ProviderTests\GrowlProviderTest.cs" />
@ -199,8 +186,6 @@
<Compile Include="DecisionEngineTests\AcceptableSizeSpecificationFixture.cs" />
<Compile Include="Qualities\QualitySizeServiceFixture.cs" />
<Compile Include="ProviderTests\MisnamedProviderTest.cs" />
<Compile Include="JobTests\SeasonSearchJobTest.cs" />
<Compile Include="JobTests\SeriesSearchJobTest.cs" />
<Compile Include="ProviderTests\EventClientProviderTest.cs" />
<Compile Include="ProviderTests\XbmcProviderTest.cs" />
<Compile Include="TvTests\EpisodeProviderTests\EpisodeProviderTest_GetEpisodesByParseResult.cs" />
@ -211,12 +196,10 @@
<Compile Include="DecisionEngineTests\MonitoredEpisodeSpecificationFixture.cs" />
<Compile Include="EpisodeStatusTest.cs" />
<Compile Include="JobTests\DiskScanJobTest.cs" />
<Compile Include="IndexerTests\IndexerFixture.cs" />
<Compile Include="DecisionEngineTests\AllowedDownloadSpecificationFixture.cs" />
<Compile Include="JobTests\JobControllerFixture.cs" />
<Compile Include="TvTests\QualityModelFixture.cs" />
<Compile Include="RootFolderTests\RootFolderServiceFixture.cs" />
<Compile Include="Indexers\IndexerServiceTest.cs" />
<Compile Include="HistoryTests\HistoryRepositoryFixture.cs" />
<Compile Include="MediaFileTests\MediaFileServiceTest.cs" />
<Compile Include="Configuration\ConfigServiceFixture.cs" />

@ -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 &lt;filename unknown&gt;:0
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&amp;)
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 &lt;filename unknown&gt;:0
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&amp;)
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 &lt;filename unknown&gt;:0
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&amp;)
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 &lt;filename unknown&gt;:0
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&amp;)
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 &lt;filename unknown&gt;:0
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&amp;)
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 &lt;filename unknown&gt;:0
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&amp;)
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 &lt;filename unknown&gt;:0
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&amp;)
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 &lt;filename unknown&gt;:0
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&amp;)
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 &lt;filename unknown&gt;:0
at (wrapper managed-to-native) System.Reflection.MonoMethod:InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&amp;)
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>

@ -2,13 +2,14 @@ using System;
using System.Linq;
using NLog;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.DataAugmentation.Scene
{
public interface ISceneMappingService
{
void UpdateMappings();
string GetSceneName(int tvdbId, int seasonNumber = -1);
string GetSceneName(int seriesId, int seasonNumber = -1);
Nullable<int> GetTvDbId(string cleanName);
string GetCleanName(int tvdbId);
}
@ -17,12 +18,14 @@ namespace NzbDrone.Core.DataAugmentation.Scene
{
private readonly ISceneMappingRepository _repository;
private readonly ISceneMappingProxy _sceneMappingProxy;
private readonly ISeriesService _seriesService;
private readonly Logger _logger;
public SceneMappingService(ISceneMappingRepository repository, ISceneMappingProxy sceneMappingProxy, Logger logger)
public SceneMappingService(ISceneMappingRepository repository, ISceneMappingProxy sceneMappingProxy, ISeriesService seriesService, Logger logger)
{
_repository = repository;
_sceneMappingProxy = sceneMappingProxy;
_seriesService = seriesService;
_logger = logger;
}
@ -48,16 +51,20 @@ namespace NzbDrone.Core.DataAugmentation.Scene
}
}
public virtual string GetSceneName(int tvdbId, int seasonNumber = -1)
public string GetSceneName(int seriesId, int seasonNumber = -1)
{
var mapping = _repository.FindByTvdbId(tvdbId);
var tvDbId = _seriesService.FindByTvdbId(seriesId).TvDbId;
var mapping = _repository.FindByTvdbId(tvDbId);
if (mapping == null) return null;
return mapping.SceneName;
}
public virtual Nullable<Int32> GetTvDbId(string cleanName)
public Nullable<Int32> GetTvDbId(string cleanName)
{
var mapping = _repository.FindByCleanTitle(cleanName);
@ -68,7 +75,7 @@ namespace NzbDrone.Core.DataAugmentation.Scene
}
public virtual string GetCleanName(int tvdbId)
public string GetCleanName(int tvdbId)
{
var mapping = _repository.FindByTvdbId(tvdbId);

@ -100,8 +100,8 @@ namespace NzbDrone.Core.Datastore.Migration
Create.TableForModel("IndexerDefinitions")
.WithColumn("Enable").AsBoolean()
.WithColumn("Type").AsString().Unique()
.WithColumn("Name").AsString().Unique();
.WithColumn("Name").AsString().Unique()
.WithColumn("Settings").AsString();
Create.TableForModel("NewznabDefinitions")
.WithColumn("Enable").AsBoolean()

@ -9,6 +9,7 @@ using NzbDrone.Core.DataAugmentation.Scene;
using NzbDrone.Core.Datastore.Converters;
using NzbDrone.Core.ExternalNotification;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Indexers.Newznab;
using NzbDrone.Core.Instrumentation;
using NzbDrone.Core.Jobs;
using NzbDrone.Core.MediaFiles;
@ -30,7 +31,7 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<Config>().RegisterModel("Config");
Mapper.Entity<RootFolder>().RegisterModel("RootFolders").Ignore(r => r.FreeSpace);
Mapper.Entity<Indexer>().RegisterModel("IndexerDefinitions");
Mapper.Entity<IndexerDefinition>().RegisterModel("IndexerDefinitions");
Mapper.Entity<NewznabDefinition>().RegisterModel("NewznabDefinitions");
Mapper.Entity<JobDefinition>().RegisterModel("JobDefinitions");
Mapper.Entity<ExternalNotificationDefinition>().RegisterModel("ExternalNotificationDefinitions");

@ -1,11 +1,14 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Model;
namespace NzbDrone.Core.DecisionEngine
{
public class DownloadDecision
{
public EpisodeParseResult ParseResult { get; private set; }
public IEnumerable<string> Rejections { get; private set; }
public bool Approved
{
get
@ -14,9 +17,23 @@ namespace NzbDrone.Core.DecisionEngine
}
}
public DownloadDecision(params string[] rejections)
public DownloadDecision(EpisodeParseResult parseResult, params string[] rejections)
{
ParseResult = parseResult;
Rejections = rejections.ToList();
}
public static EpisodeParseResult PickBestReport(IEnumerable<DownloadDecision> downloadDecisions)
{
var reports = downloadDecisions
.Where(c => c.Approved)
.Select(c => c.ParseResult)
.OrderByDescending(c => c.Quality)
.ThenBy(c => c.EpisodeNumbers.MinOrDefault())
.ThenBy(c => c.Age);
return reports.SingleOrDefault();
}
}
}

@ -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;
}
}
}

@ -2,9 +2,8 @@ using NzbDrone.Core.Model;
namespace NzbDrone.Core.DecisionEngine
{
public interface IFetchableSpecification
public interface IDecisionEngineSpecification : IRejectWithReason
{
string RejectionReason { get; }
bool IsSatisfiedBy(EpisodeParseResult subject);
}
}

@ -0,0 +1,7 @@
namespace NzbDrone.Core.DecisionEngine
{
public interface IRejectWithReason
{
string RejectionReason { get; }
}
}

@ -5,7 +5,7 @@ using NzbDrone.Core.Tv;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class AcceptableSizeSpecification : IFetchableSpecification
public class AcceptableSizeSpecification : IDecisionEngineSpecification
{
private readonly IQualitySizeService _qualityTypeProvider;
private readonly IEpisodeService _episodeService;

@ -5,7 +5,7 @@ using NzbDrone.Core.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class AllowedReleaseGroupSpecification : IFetchableSpecification
public class AllowedReleaseGroupSpecification : IDecisionEngineSpecification
{
private readonly IConfigService _configService;
private readonly Logger _logger;

@ -4,7 +4,7 @@ using NzbDrone.Core.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class CustomStartDateSpecification : IFetchableSpecification
public class CustomStartDateSpecification : IDecisionEngineSpecification
{
private readonly Logger _logger;

@ -3,7 +3,7 @@ using NzbDrone.Core.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class LanguageSpecification : IFetchableSpecification
public class LanguageSpecification : IDecisionEngineSpecification
{
private readonly Logger _logger;

@ -5,7 +5,7 @@ using NzbDrone.Core.Tv;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class MonitoredEpisodeSpecification : IFetchableSpecification
public class MonitoredEpisodeSpecification : IDecisionEngineSpecification
{
private readonly IEpisodeService _episodeService;
private readonly ISeriesRepository _seriesRepository;

@ -4,7 +4,7 @@ using NzbDrone.Core.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class NotInQueueSpecification : IFetchableSpecification
public class NotInQueueSpecification : IDecisionEngineSpecification
{
private readonly IProvideDownloadClient _downloadClientProvider;

@ -3,7 +3,7 @@ using NzbDrone.Core.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class QualityAllowedByProfileSpecification : IFetchableSpecification
public class QualityAllowedByProfileSpecification : IDecisionEngineSpecification
{
private readonly Logger _logger;

@ -4,7 +4,7 @@ using NzbDrone.Core.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class RetentionSpecification : IFetchableSpecification
public class RetentionSpecification : IDecisionEngineSpecification
{
private readonly IConfigService _configService;
private readonly Logger _logger;

@ -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;
}
}
}

@ -6,7 +6,7 @@ using NzbDrone.Core.Tv;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class UpgradeDiskSpecification : IFetchableSpecification
public class UpgradeDiskSpecification : IDecisionEngineSpecification
{
private readonly QualityUpgradableSpecification _qualityUpgradableSpecification;
private readonly Logger _logger;

@ -4,7 +4,7 @@ using NzbDrone.Core.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class UpgradeHistorySpecification : IFetchableSpecification
public class UpgradeHistorySpecification : IDecisionEngineSpecification
{
private readonly IHistoryService _historyService;
private readonly QualityUpgradableSpecification _qualityUpgradableSpecification;

@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Eventing;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Model;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Download
{
@ -29,7 +32,6 @@ namespace NzbDrone.Core.Download
_logger = logger;
}
public bool DownloadReport(EpisodeParseResult parseResult)
{
var downloadTitle = parseResult.OriginalString;
@ -45,7 +47,7 @@ namespace NzbDrone.Core.Download
if (success)
{
_logger.Trace("Download added to Queue: {0}", downloadTitle);
_logger.Info("Report sent to download client. {0}", downloadTitle);
_eventAggregator.Publish(new EpisodeGrabbedEvent(parseResult));
}

@ -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,41 @@
using System;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
namespace NzbDrone.Core.IndexerSearch
{
interface ISearchAndDownload
{
void SearchSingle(int seriesId, int seasonNumber, int episodeNumber);
void SearchDaily(int seriesId, DateTime airDate);
void SearchSeason(int seriesId, int seasonNumber);
}
public class SearchAndDownloadService : ISearchAndDownload
{
private readonly ISearchForNzb _searchService;
private readonly IMakeDownloadDecision _downloadDecisionMaker;
public SearchAndDownloadService(ISearchForNzb searchService, IMakeDownloadDecision downloadDecisionMaker)
{
_searchService = searchService;
_downloadDecisionMaker = downloadDecisionMaker;
}
public void SearchSingle(int seriesId, int seasonNumber, int episodeNumber)
{
var result = _searchService.SearchSingle(seriesId, seasonNumber, episodeNumber);
}
public void SearchDaily(int seriesId, DateTime airDate)
{
throw new NotImplementedException();
}
public void SearchSeason(int seriesId, int seasonNumber)
{
throw new NotImplementedException();
}
}
}

@ -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);
}
}
}

@ -1,85 +0,0 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.ServiceModel.Syndication;
using NzbDrone.Common;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Model;
namespace NzbDrone.Core.Indexers
{
public class FileSharingTalk : IndexerBase
{
public FileSharingTalk(HttpProvider httpProvider, IConfigService configService)
: base(httpProvider, configService)
{
}
protected override string[] Urls
{
get
{
return new[]
{
string.Format("http://filesharingtalk.com/ng_rss.php?uid={0}&ps={1}&category=tv&subcategory=x264sd,x264720,xvid,webdl720,x2641080",
_configService.FileSharingTalkUid, _configService.FileSharingTalkSecret)
};
}
}
public override bool IsConfigured
{
get
{
return !string.IsNullOrWhiteSpace(_configService.FileSharingTalkUid) &&
!string.IsNullOrWhiteSpace(_configService.FileSharingTalkSecret);
}
}
protected override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
{
return new List<string>();
}
protected override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
{
return new List<string>();
}
protected override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
{
return new List<string>();
}
protected override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
{
return new List<string>();
}
public override string Name
{
get { return "FileSharingTalk"; }
}
protected override string NzbDownloadUrl(SyndicationItem item)
{
return item.Links[0].Uri.ToString();
}
protected override string NzbInfoUrl(SyndicationItem item)
{
return item.Id;
}
protected override EpisodeParseResult CustomParser(SyndicationItem item, EpisodeParseResult currentResult)
{
if (currentResult != null)
{
currentResult.Size = 0;
currentResult.Age = 0;
}
return currentResult;
}
}
}

@ -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;
}
}
}

@ -0,0 +1,18 @@
using System;
namespace NzbDrone.Core.Indexers.FileSharingTalk
{
public class FileSharingTalkSetting : IIndexerSetting
{
public String Uid { get; set; }
public String Secret { get; set; }
public bool IsValid
{
get
{
return !string.IsNullOrWhiteSpace(Uid) && !string.IsNullOrWhiteSpace(Secret);
}
}
}
}

@ -0,0 +1,19 @@
namespace NzbDrone.Core.Indexers
{
public interface IIndexerSetting
{
bool IsValid { get; }
}
public class NullSetting : IIndexerSetting
{
public bool IsValid
{
get
{
return true;
}
}
}
}

@ -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;
}
}
}

@ -3,10 +3,10 @@ using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Indexers
{
public class Indexer : ModelBase
public class IndexerDefinition : ModelBase
{
public Boolean Enable { get; set; }
public String Type { get; set; }
public String Name { get; set; }
public String Settings { get; set; }
}
}

@ -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;
}
}
}

@ -1,25 +1,23 @@
using System;
using System.Data;
using System.Linq;
using System.Linq;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Indexers
{
public interface IIndexerRepository : IBasicRepository<Indexer>
public interface IIndexerRepository : IBasicRepository<IndexerDefinition>
{
Indexer Find(Type type);
IndexerDefinition Get(string name);
}
public class IndexerRepository : BasicRepository<Indexer>, IIndexerRepository
public class IndexerRepository : BasicRepository<IndexerDefinition>, IIndexerRepository
{
public IndexerRepository(IDatabase database)
: base(database)
{
}
public Indexer Find(Type type)
public IndexerDefinition Get(string name)
{
return Query.Single(i => i.Type == type.ToString());
return Query.Single(i => i.Name.ToLower() == name.ToLower());
}
}
}

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.Lifecycle;
@ -9,10 +8,10 @@ namespace NzbDrone.Core.Indexers
{
public interface IIndexerService
{
List<Indexer> All();
List<IndexerBase> GetEnabledIndexers();
void SaveSettings(Indexer indexer);
Indexer GetSettings(Type type);
List<IndexerDefinition> All();
List<IIndexerBase> GetAvailableIndexers();
void Save(IndexerDefinition indexer);
IndexerDefinition Get(string name);
}
public class IndexerService : IIndexerService, IInitializable
@ -20,9 +19,9 @@ namespace NzbDrone.Core.Indexers
private readonly IIndexerRepository _indexerRepository;
private readonly Logger _logger;
private IList<IndexerBase> _indexers;
private readonly IList<IIndexerBase> _indexers;
public IndexerService(IIndexerRepository indexerRepository, IEnumerable<IndexerBase> indexers, Logger logger)
public IndexerService(IIndexerRepository indexerRepository, IEnumerable<IIndexerBase> indexers, Logger logger)
{
_indexerRepository = indexerRepository;
_logger = logger;
@ -37,14 +36,13 @@ namespace NzbDrone.Core.Indexers
foreach (var feedProvider in _indexers)
{
IndexerBase indexerLocal = feedProvider;
if (!currentIndexers.Exists(c => c.Type == indexerLocal.GetType().ToString()))
IIndexerBase indexerLocal = feedProvider;
if (!currentIndexers.Exists(c => c.Name == indexerLocal.Name))
{
var settings = new Indexer
var settings = new IndexerDefinition
{
Enable = indexerLocal.EnabledByDefault,
Type = indexerLocal.GetType().ToString(),
Name = indexerLocal.Name
Name = indexerLocal.Name.ToLower()
};
_indexerRepository.Insert(settings);
@ -52,27 +50,27 @@ namespace NzbDrone.Core.Indexers
}
}
public List<Indexer> All()
public List<IndexerDefinition> All()
{
return _indexerRepository.All().ToList();
}
public List<IndexerBase> GetEnabledIndexers()
public List<IIndexerBase> GetAvailableIndexers()
{
var all = All();
return _indexers.Where(i => all.Exists(c => c.Type == i.GetType().ToString() && c.Enable)).ToList();
var enabled = All().Where(c => c.Enable).Select(c => c.Name);
var configureIndexers = _indexers.Where(c => c.Settings.IsValid);
return configureIndexers.Where(c => enabled.Contains(c.Name)).ToList();
}
public void SaveSettings(Indexer indexer)
public void Save(IndexerDefinition indexer)
{
//Todo: This will be used in the API
_logger.Debug("Upserting Indexer definitions for {0}", indexer.Name);
_indexerRepository.Upsert(indexer);
_indexerRepository.Update(indexer);
}
public Indexer GetSettings(Type type)
public IndexerDefinition Get(string name)
{
return _indexerRepository.Find(type);
return _indexerRepository.Get(name);
}
}
}

@ -0,0 +1,25 @@
using Newtonsoft.Json;
namespace NzbDrone.Core.Indexers
{
public interface IProviderIndexerSetting
{
TSetting Get<TSetting>(IIndexerBase indexer) where TSetting : IIndexerSetting, new();
}
public class IndexerSettingProvider : IProviderIndexerSetting
{
private readonly IIndexerRepository _indexerRepository;
public IndexerSettingProvider(IIndexerRepository indexerRepository)
{
_indexerRepository = indexerRepository;
}
public TSetting Get<TSetting>(IIndexerBase indexer) where TSetting : IIndexerSetting, new()
{
var json = _indexerRepository.Get(indexer.Name).Settings;
return JsonConvert.DeserializeObject<TSetting>(json);
}
}
}

@ -1,149 +0,0 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.ServiceModel.Syndication;
using System.Text.RegularExpressions;
using NzbDrone.Common;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Model;
using NzbDrone.Core.Providers;
namespace NzbDrone.Core.Indexers
{
public class Newznab : IndexerBase
{
private readonly INewznabService _newznabProvider;
public Newznab(HttpProvider httpProvider, IConfigService configService, INewznabService newznabProvider)
: base(httpProvider, configService)
{
_newznabProvider = newznabProvider;
}
protected override string[] Urls
{
get { return GetUrls(); }
}
public override bool IsConfigured
{
get { return true; }
}
protected override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
{
var searchUrls = new List<string>();
foreach (var url in Urls)
{
searchUrls.Add(String.Format("{0}&limit=100&q={1}&season={2}&ep={3}", url, seriesTitle, seasonNumber, episodeNumber));
}
return searchUrls;
}
protected override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
{
var searchUrls = new List<string>();
foreach (var url in Urls)
{
searchUrls.Add(String.Format("{0}&limit=100&q={1}&season={2:yyyy}&ep={2:MM/dd}", url, seriesTitle, date));
}
return searchUrls;
}
protected override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
{
var searchUrls = new List<string>();
foreach (var url in Urls)
{
searchUrls.Add(String.Format("{0}&limit=100&q={1}&season={2}", url, seriesTitle, seasonNumber));
}
return searchUrls;
}
protected override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
{
var searchUrls = new List<string>();
foreach (var url in Urls)
{
searchUrls.Add(String.Format("{0}&limit=100&q={1}+S{2:00}E{3}", url, seriesTitle, seasonNumber, episodeWildcard));
}
return searchUrls;
}
public override string Name
{
get { return "Newznab"; }
}
protected override string NzbDownloadUrl(SyndicationItem item)
{
return item.Links[0].Uri.ToString();
}
protected override string NzbInfoUrl(SyndicationItem item)
{
return item.Id;
}
protected override EpisodeParseResult CustomParser(SyndicationItem item, EpisodeParseResult currentResult)
{
if (currentResult != null)
{
if (item.Links.Count > 1)
currentResult.Size = item.Links[1].Length;
currentResult.Indexer = GetName(item);
}
return currentResult;
}
private string[] GetUrls()
{
var urls = new List<string>();
var newznabIndexers = _newznabProvider.Enabled();
foreach (var newznabDefinition in newznabIndexers)
{
if (!String.IsNullOrWhiteSpace(newznabDefinition.ApiKey))
urls.Add(String.Format("{0}/api?t=tvsearch&cat=5030,5040,5070,5090&apikey={1}", newznabDefinition.Url,
newznabDefinition.ApiKey));
else
urls.Add(String.Format("{0}/api?t=tvsearch&cat=5030,5040,5070,5090s", newznabDefinition.Url));
}
return urls.ToArray();
}
private string GetName(SyndicationItem item)
{
var hostname = item.Links[0].Uri.DnsSafeHost.ToLower();
return String.Format("{0}_{1}", Name, hostname);
}
public override string GetQueryTitle(string title)
{
title = RemoveThe.Replace(title, string.Empty);
//remove any repeating whitespace
var cleanTitle = TitleSearchRegex.Replace(title, "%20");
cleanTitle = Regex.Replace(cleanTitle, @"(%20){1,100}", "%20");
//Trim %20 from start then then the end
cleanTitle = Regex.Replace(cleanTitle, "^(%20)", "");
cleanTitle = Regex.Replace(cleanTitle, "(%20)$", "");
return cleanTitle;
}
}
}

@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
namespace NzbDrone.Core.Indexers.Newznab
{
public class Newznab : BaseIndexer
{
private readonly INewznabService _newznabProvider;
public Newznab(INewznabService newznabProvider)
{
_newznabProvider = newznabProvider;
}
public override IEnumerable<string> RecentFeed
{
get { return GetUrls(); }
}
public override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
{
foreach (var url in RecentFeed)
{
yield return String.Format("{0}&limit=100&q={1}&season={2}&ep={3}", url, NewsnabifyTitle(seriesTitle), seasonNumber, episodeNumber);
}
}
public override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
{
var searchUrls = new List<string>();
foreach (var url in RecentFeed)
{
searchUrls.Add(String.Format("{0}&limit=100&q={1}&season={2:yyyy}&ep={2:MM/dd}", url, NewsnabifyTitle(seriesTitle), date));
}
return searchUrls;
}
public override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
{
foreach (var url in RecentFeed)
{
yield return String.Format("{0}&limit=100&q={1}&season={2}", url, NewsnabifyTitle(seriesTitle), seasonNumber);
}
}
public override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
{
foreach (var url in RecentFeed)
{
yield return
String.Format("{0}&limit=100&q={1}+S{2:00}E{3}", url, NewsnabifyTitle(seriesTitle), seasonNumber, episodeWildcard);
}
}
public override string Name
{
get { return "Newznab"; }
}
private IEnumerable<string> GetUrls()
{
var urls = new List<string>();
var newznabIndexers = _newznabProvider.Enabled();
foreach (var newznabDefinition in newznabIndexers)
{
var url = String.Format("{0}/api?t=tvsearch&cat=5030,5040,5070,5090s", newznabDefinition.Url);
if (String.IsNullOrWhiteSpace(newznabDefinition.ApiKey))
{
url += "&apikey=" + newznabDefinition.ApiKey;
}
urls.Add(url);
}
return urls;
}
private static string NewsnabifyTitle(string title)
{
return title.Replace("+", "%20");
}
}
}

@ -1,8 +1,7 @@
using System;
using System;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Indexers
namespace NzbDrone.Core.Indexers.Newznab
{
public class NewznabDefinition : ModelBase
{
@ -10,6 +9,5 @@ namespace NzbDrone.Core.Indexers
public String Name { get; set; }
public String Url { get; set; }
public String ApiKey { get; set; }
public bool BuiltIn { get; set; }
}
}

@ -0,0 +1,41 @@
using System;
using System.ServiceModel.Syndication;
using NzbDrone.Core.Model;
namespace NzbDrone.Core.Indexers.Newznab
{
public class NewznabParser : BasicRssParser
{
private readonly Newznab _newznabIndexer;
public NewznabParser(Newznab newznabIndexer)
{
_newznabIndexer = newznabIndexer;
}
protected override string GetNzbInfoUrl(SyndicationItem item)
{
return item.Id;
}
protected override EpisodeParseResult PostProcessor(SyndicationItem item, EpisodeParseResult currentResult)
{
if (currentResult != null)
{
if (item.Links.Count > 1)
currentResult.Size = item.Links[1].Length;
currentResult.Indexer = GetName(item);
}
return currentResult;
}
private string GetName(SyndicationItem item)
{
var hostname = item.Links[0].Uri.DnsSafeHost.ToLower();
return String.Format("{0}_{1}", _newznabIndexer.Name, hostname);
}
}
}

@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Collections.Generic;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Indexers
namespace NzbDrone.Core.Indexers.Newznab
{
public interface INewznabRepository : IBasicRepository<NewznabDefinition>
{

@ -1,12 +1,11 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using NLog;
using NzbDrone.Core.Lifecycle;
namespace NzbDrone.Core.Indexers
namespace NzbDrone.Core.Indexers.Newznab
{
public interface INewznabService
{
@ -53,7 +52,7 @@ namespace NzbDrone.Core.Indexers
public void Delete(int id)
{
_newznabRepository.Delete(id);
}
@ -79,9 +78,9 @@ namespace NzbDrone.Core.Indexers
{
var newznabIndexers = new List<NewznabDefinition>
{
new NewznabDefinition { Enable = false, Name = "Nzbs.org", Url = "http://nzbs.org", BuiltIn = true },
new NewznabDefinition { Enable = false, Name = "Nzb.su", Url = "https://nzb.su", BuiltIn = true },
new NewznabDefinition { Enable = false, Name = "Dognzb.cr", Url = "https://dognzb.cr", BuiltIn = true }
new NewznabDefinition { Enable = false, Name = "Nzbs.org", Url = "http://nzbs.org" },
new NewznabDefinition { Enable = false, Name = "Nzb.su", Url = "https://nzb.su" },
new NewznabDefinition { Enable = false, Name = "Dognzb.cr", Url = "https://dognzb.cr" }
};
_logger.Debug("Initializing Newznab indexers. Count {0}", newznabIndexers);
@ -112,7 +111,6 @@ namespace NzbDrone.Core.Indexers
Name = indexerLocal.Name,
Url = indexerLocal.Url,
ApiKey = indexerLocal.ApiKey,
BuiltIn = true
};
Insert(definition);
@ -121,7 +119,6 @@ namespace NzbDrone.Core.Indexers
else
{
currentIndexer.Url = indexerLocal.Url;
currentIndexer.BuiltIn = true;
Update(currentIndexer);
}
}

@ -1,128 +0,0 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.ServiceModel.Syndication;
using System.Text.RegularExpressions;
using NzbDrone.Common;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Model;
namespace NzbDrone.Core.Indexers
{
public class NzbClub : IndexerBase
{
public NzbClub(HttpProvider httpProvider, IConfigService configService)
: base(httpProvider, configService)
{
}
protected override string[] Urls
{
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 bool IsConfigured
{
get
{
return true;
}
}
public override string Name
{
get { return "NzbClub"; }
}
protected override string NzbDownloadUrl(SyndicationItem item)
{
return item.Links[0].Uri.ToString();
}
protected override string NzbInfoUrl(SyndicationItem item)
{
return item.Links[1].Uri.ToString();
}
protected override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
{
var searchUrls = new List<String>();
foreach (var url in Urls)
{
searchUrls.Add(String.Format("{0}+{1}+s{2:00}e{3:00}", url, seriesTitle, seasonNumber, episodeNumber));
}
return searchUrls;
}
protected override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
{
var searchUrls = new List<String>();
foreach (var url in Urls)
{
searchUrls.Add(String.Format("{0}+{1}+s{2:00}", url, seriesTitle, seasonNumber));
}
return searchUrls;
}
protected override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
{
var searchUrls = new List<String>();
foreach (var url in Urls)
{
searchUrls.Add(String.Format("{0}+{1}+{2:yyyy MM dd}", url, seriesTitle, date));
}
return searchUrls;
}
protected override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
{
var searchUrls = new List<String>();
foreach (var url in Urls)
{
searchUrls.Add(String.Format("{0}+{1}+S{2:00}E{3}", url, seriesTitle, seasonNumber, episodeWildcard));
}
return searchUrls;
}
protected override EpisodeParseResult CustomParser(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;
}
public override bool EnabledByDefault
{
get { return false; }
}
protected override string TitlePreParser(SyndicationItem item)
{
var title = Parser.ParseHeader(item.Title.Text);
if (String.IsNullOrWhiteSpace(title))
return item.Title.Text;
return title;
}
}
}

@ -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();
}
}
}

@ -1,128 +0,0 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.ServiceModel.Syndication;
using System.Text.RegularExpressions;
using NzbDrone.Common;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Model;
namespace NzbDrone.Core.Indexers
{
public class NzbIndex : IndexerBase
{
public NzbIndex(HttpProvider httpProvider, IConfigService configService)
: base(httpProvider, configService)
{
}
protected override string[] Urls
{
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 bool IsConfigured
{
get
{
return true;
}
}
public override string Name
{
get { return "NzbIndex"; }
}
protected override string NzbDownloadUrl(SyndicationItem item)
{
return item.Links[1].Uri.ToString();
}
protected override string NzbInfoUrl(SyndicationItem item)
{
return item.Links[0].Uri.ToString();
}
protected override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
{
var searchUrls = new List<String>();
foreach (var url in Urls)
{
searchUrls.Add(String.Format("{0}+{1}+s{2:00}e{3:00}", url, seriesTitle, seasonNumber, episodeNumber));
}
return searchUrls;
}
protected override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
{
var searchUrls = new List<String>();
foreach (var url in Urls)
{
searchUrls.Add(String.Format("{0}+{1}+s{2:00}", url, seriesTitle, seasonNumber));
}
return searchUrls;
}
protected override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
{
var searchUrls = new List<String>();
foreach (var url in Urls)
{
searchUrls.Add(String.Format("{0}+{1}+{2:yyyy MM dd}", url, seriesTitle, date));
}
return searchUrls;
}
protected override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
{
var searchUrls = new List<String>();
foreach (var url in Urls)
{
searchUrls.Add(String.Format("{0}+{1}+S{2:00}E{3}", url, seriesTitle, seasonNumber, episodeWildcard));
}
return searchUrls;
}
protected override EpisodeParseResult CustomParser(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;
}
public override bool EnabledByDefault
{
get { return true; }
}
protected override string TitlePreParser(SyndicationItem item)
{
var title = Parser.ParseHeader(item.Title.Text);
if (String.IsNullOrWhiteSpace(title))
return item.Title.Text;
return title;
}
}
}

@ -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;
}
}
}

@ -1,87 +0,0 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.ServiceModel.Syndication;
using System.Text.RegularExpressions;
using NzbDrone.Common;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Model;
namespace NzbDrone.Core.Indexers
{
public class NzbsRUs : IndexerBase
{
public NzbsRUs(HttpProvider httpProvider, IConfigService configService) : base(httpProvider, configService)
{
}
protected override string[] Urls
{
get
{
return new[]
{
string.Format(
"https://www.nzbsrus.com/rssfeed.php?cat=91,75&i={0}&h={1}",
_configService.NzbsrusUId,
_configService.NzbsrusHash)
};
}
}
public override bool IsConfigured
{
get
{
return !string.IsNullOrWhiteSpace(_configService.NzbsrusUId) &&
!string.IsNullOrWhiteSpace(_configService.NzbsrusHash);
}
}
public override string Name
{
get { return "NzbsRUs"; }
}
protected override string NzbDownloadUrl(SyndicationItem item)
{
return item.Links[0].Uri.ToString();
}
protected override string NzbInfoUrl(SyndicationItem item)
{
return item.Links[0].Uri.ToString();
}
protected override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
{
return new List<string>();
}
protected override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
{
return new List<string>();
}
protected override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
{
return new List<string>();
}
protected override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
{
return new List<string>();
}
protected override EpisodeParseResult CustomParser(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,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,18 @@
using System;
namespace NzbDrone.Core.Indexers.NzbsRUs
{
public class NzbsrusSetting : IIndexerSetting
{
public String Uid { get; set; }
public String Hash { get; set; }
public bool IsValid
{
get
{
return !string.IsNullOrWhiteSpace(Uid) && !string.IsNullOrWhiteSpace(Hash);
}
}
}
}

@ -1,230 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.ServiceModel.Syndication;
using Newtonsoft.Json;
using NzbDrone.Common;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Model;
using NzbDrone.Core.Model.Nzbx;
namespace NzbDrone.Core.Indexers
{
class Nzbx : IndexerBase
{
public Nzbx(HttpProvider httpProvider, IConfigService configService)
: base(httpProvider, configService)
{
}
public override string Name
{
get { return "nzbx"; }
}
protected override string[] Urls
{
get
{
return new string[]
{
String.Format("https://nzbx.co/api/recent?category=tv")
};
}
}
public override bool IsConfigured
{
get
{
return true;
//return !string.IsNullOrWhiteSpace(_configProvider.OmgwtfnzbsUsername) &&
// !string.IsNullOrWhiteSpace(_configProvider.OmgwtfnzbsApiKey);
}
}
protected override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
{
var searchUrls = new List<String>();
searchUrls.Add(String.Format("https://nzbx.co/api/search?q={0}+S{1:00}E{2:00}", seriesTitle, seasonNumber, episodeNumber));
return searchUrls;
}
protected override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
{
var searchUrls = new List<String>();
searchUrls.Add(String.Format("https://nzbx.co/api/search?q={0}+{1:yyyy MM dd}", seriesTitle, date));
return searchUrls;
}
protected override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
{
var searchUrls = new List<String>();
searchUrls.Add(String.Format("https://nzbx.co/api/search?q={0}+S{1:00}", seriesTitle, seasonNumber));
return searchUrls;
}
protected override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
{
var searchUrls = new List<String>();
searchUrls.Add(String.Format("https://nzbx.co/api/search?q={0}+S{1:00}E{2}", seriesTitle, seasonNumber, episodeWildcard));
return searchUrls;
}
protected override string NzbDownloadUrl(SyndicationItem item)
{
throw new NotImplementedException();
}
protected override string NzbInfoUrl(SyndicationItem item)
{
throw new NotImplementedException();
}
protected override EpisodeParseResult CustomParser(SyndicationItem item, EpisodeParseResult currentResult)
{
throw new NotImplementedException();
}
public override IList<EpisodeParseResult> FetchRss()
{
_logger.Debug("Fetching feeds from " + Name);
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)
{
var response = Download(url);
if (response != null)
{
var feed = JsonConvert.DeserializeObject<List<NzbxRecentItem>>(response);
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.Indexer = Name;
episodeParseResult.Size = item.Size;
result.Add(episodeParseResult);
}
}
catch (Exception itemEx)
{
itemEx.Data.Add("FeedUrl", url);
itemEx.Data.Add("Item", item.Name);
_logger.ErrorException("An error occurred while processing feed item", itemEx);
}
}
}
}
_logger.Debug("Finished processing feeds from " + Name);
return result;
}
protected 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)
{
var response = Download(url);
if(response != null)
{
var feed = JsonConvert.DeserializeObject<List<NzbxSearchItem>>(response);
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 = item.Nzb;
episodeParseResult.NzbInfoUrl = String.Format("http://nzbx.co/d?{0}", item.Guid);
episodeParseResult.Indexer = Name;
episodeParseResult.Size = item.Size;
result.Add(episodeParseResult);
}
}
catch (Exception itemEx)
{
itemEx.Data.Add("FeedUrl", url);
itemEx.Data.Add("Item", item.Name);
_logger.ErrorException("An error occurred while processing feed item", itemEx);
}
}
}
}
return result;
}
private string Download(string url)
{
try
{
_logger.Trace("Downloading RSS " + url);
return _httpProvider.DownloadString(url, Credentials);
}
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 null;
}
}
}

@ -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;
}
}
}

@ -1,123 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Syndication;
using System.Text.RegularExpressions;
using NzbDrone.Common;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Model;
namespace NzbDrone.Core.Indexers
{
class Omgwtfnzbs : IndexerBase
{
public Omgwtfnzbs(HttpProvider httpProvider, IConfigService configService)
: base(httpProvider, configService)
{
}
public override string Name
{
get { return "omgwtfnzbs"; }
}
protected override string[] Urls
{
get
{
return new string[]
{
String.Format("http://rss.omgwtfnzbs.org/rss-search.php?catid=19,20&user={0}&api={1}&eng=1",
_configService.OmgwtfnzbsUsername, _configService.OmgwtfnzbsApiKey)
};
}
}
public override bool IsConfigured
{
get
{
return !string.IsNullOrWhiteSpace(_configService.OmgwtfnzbsUsername) &&
!string.IsNullOrWhiteSpace(_configService.OmgwtfnzbsApiKey);
}
}
protected override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
{
var searchUrls = new List<String>();
foreach (var url in Urls)
{
searchUrls.Add(String.Format("{0}&search={1}+S{2:00}E{3:00}", url, seriesTitle, seasonNumber, episodeNumber));
}
return searchUrls;
}
protected override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
{
var searchUrls = new List<String>();
foreach (var url in Urls)
{
searchUrls.Add(String.Format("{0}&search={1}+{2:yyyy MM dd}", url, seriesTitle, date));
}
return searchUrls;
}
protected override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
{
var searchUrls = new List<String>();
foreach (var url in Urls)
{
searchUrls.Add(String.Format("{0}&search={1}+S{2:00}", url, seriesTitle, seasonNumber));
}
return searchUrls;
}
protected override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
{
var searchUrls = new List<String>();
foreach (var url in Urls)
{
searchUrls.Add(String.Format("{0}&search={1}+S{2:00}E{3}", url, seriesTitle, seasonNumber, episodeWildcard));
}
return searchUrls;
}
protected override string NzbDownloadUrl(SyndicationItem item)
{
return item.Links[0].Uri.ToString();
}
protected override string NzbInfoUrl(SyndicationItem item)
{
//Todo: Me thinks I need to parse details to get this...
var match = Regex.Match(item.Summary.Text, @"(?:\<b\>View NZB\:\<\/b\>\s\<a\shref\=\"")(?<URL>.+)(?:\""\starget)",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
if(match.Success)
{
return match.Groups["URL"].Value;
}
return String.Empty;
}
protected override EpisodeParseResult CustomParser(SyndicationItem item, EpisodeParseResult currentResult)
{
if (currentResult != null)
{
var sizeString = Regex.Match(item.Summary.Text, @"Size:\<\/b\>\s\d+\.\d{1,2}\s\w{2}\<br \/\>", RegexOptions.IgnoreCase | RegexOptions.Compiled).Value;
currentResult.Size = Parser.GetReportSize(sizeString);
}
return currentResult;
}
}
}

@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
namespace NzbDrone.Core.Indexers.Omgwtfnzbs
{
public class Omgwtfnzbs : BaseIndexer
{
private readonly OmgwtfnzbsSetting _settings;
public Omgwtfnzbs(IProviderIndexerSetting settingProvider)
{
_settings = settingProvider.Get<OmgwtfnzbsSetting>(this);
}
public override string Name
{
get { return "omgwtfnzbs"; }
}
public override IEnumerable<string> RecentFeed
{
get
{
yield return
String.Format("http://rss.omgwtfnzbs.org/rss-search.php?catid=19,20&user={0}&api={1}&eng=1",
_settings.Username, _settings.ApiKey);
}
}
public override IIndexerSetting Settings
{
get { return _settings; }
}
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}&search={1}+S{2:00}E{3:00}", url, seriesTitle, seasonNumber, episodeNumber));
}
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}&search={1}+{2:yyyy MM dd}", url, seriesTitle, date));
}
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}&search={1}+S{2:00}", url, seriesTitle, seasonNumber));
}
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}&search={1}+S{2:00}E{3}", url, seriesTitle, seasonNumber, episodeWildcard));
}
return searchUrls;
}
}
}

@ -0,0 +1,40 @@
using System;
using System.ServiceModel.Syndication;
using System.Text.RegularExpressions;
using NzbDrone.Core.Model;
namespace NzbDrone.Core.Indexers.Omgwtfnzbs
{
public class OmgwtfnzbsParser : BasicRssParser
{
protected override string GetNzbUrl(SyndicationItem item)
{
return item.Links[0].Uri.ToString();
}
protected override string GetNzbInfoUrl(SyndicationItem item)
{
//Todo: Me thinks I need to parse details to get this...
var match = Regex.Match(item.Summary.Text, @"(?:\<b\>View NZB\:\<\/b\>\s\<a\shref\=\"")(?<URL>.+)(?:\""\starget)",
RegexOptions.IgnoreCase | RegexOptions.Compiled);
if (match.Success)
{
return match.Groups["URL"].Value;
}
return String.Empty;
}
protected override EpisodeParseResult PostProcessor(SyndicationItem item, EpisodeParseResult currentResult)
{
if (currentResult != null)
{
var sizeString = Regex.Match(item.Summary.Text, @"Size:\<\/b\>\s\d+\.\d{1,2}\s\w{2}\<br \/\>", RegexOptions.IgnoreCase | RegexOptions.Compiled).Value;
currentResult.Size = Parser.GetReportSize(sizeString);
}
return currentResult;
}
}
}

@ -0,0 +1,18 @@
using System;
namespace NzbDrone.Core.Indexers.Omgwtfnzbs
{
public class OmgwtfnzbsSetting : IIndexerSetting
{
public String Username { get; set; }
public String ApiKey { get; set; }
public bool IsValid
{
get
{
return !string.IsNullOrWhiteSpace(Username) && !string.IsNullOrWhiteSpace(ApiKey);
}
}
}
}

@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NLog;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.Model;
namespace NzbDrone.Core.Indexers
{
public interface ISyncRss
{
void Sync();
}
public class RssSyncService : ISyncRss
{
private readonly IFetchAndParseRss _rssFetcherAndParser;
private readonly IMakeDownloadDecision _downloadDecisionMaker;
private readonly IDownloadService _downloadService;
private readonly Logger _logger;
public RssSyncService(IFetchAndParseRss rssFetcherAndParser, IMakeDownloadDecision downloadDecisionMaker, IDownloadService downloadService, Logger logger)
{
_rssFetcherAndParser = rssFetcherAndParser;
_downloadDecisionMaker = downloadDecisionMaker;
_downloadService = downloadService;
_logger = logger;
}
public void Sync()
{
_logger.Info("Starting RSS Sync");
var parseResults = _rssFetcherAndParser.Fetch();
var decisions = _downloadDecisionMaker.GetRssDecision(parseResults);
var qualifiedReports = decisions
.Where(c => c.Approved)
.Select(c => c.ParseResult)
.OrderByDescending(c => c.Quality)
.ThenBy(c => c.EpisodeNumbers.MinOrDefault())
.ThenBy(c => c.Age);
foreach (var episodeParseResult in qualifiedReports)
{
try
{
_downloadService.DownloadReport(episodeParseResult);
}
catch (Exception e)
{
_logger.WarnException("Couldn't add report to download queue. " + episodeParseResult, e);
}
}
_logger.Info("RSS Sync Completed. Reports found {0}, Fetches attempted {1}", parseResults.Count, qualifiedReports);
}
}
public interface IFetchAndParseRss
{
List<EpisodeParseResult> Fetch();
}
public class FetchAndParseRssService : IFetchAndParseRss
{
private readonly IIndexerService _indexerService;
private readonly IFetchFeedFromIndexers _feedFetcher;
public FetchAndParseRssService(IIndexerService indexerService, IFetchFeedFromIndexers feedFetcher)
{
_indexerService = indexerService;
_feedFetcher = feedFetcher;
}
public List<EpisodeParseResult> Fetch()
{
var result = new List<EpisodeParseResult>();
var indexers = _indexerService.GetAvailableIndexers();
Parallel.ForEach(indexers, indexer =>
{
var indexerFeed = _feedFetcher.FetchRss(indexer);
lock (result)
{
result.AddRange(indexerFeed);
}
});
return result;
}
}
}

@ -1,86 +0,0 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.ServiceModel.Syndication;
using NzbDrone.Common;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Model;
namespace NzbDrone.Core.Indexers
{
public class Wombles : IndexerBase
{
public Wombles(HttpProvider httpProvider, IConfigService configService) : base(httpProvider, configService)
{
}
protected override string[] Urls
{
get
{
return new[]
{
string.Format("http://nzb.isasecret.com/rss")
};
}
}
public override bool IsConfigured
{
get
{
return true;
}
}
public override string Name
{
get { return "WomblesIndex"; }
}
protected override string NzbDownloadUrl(SyndicationItem item)
{
return item.Links[0].Uri.ToString();
}
protected override string NzbInfoUrl(SyndicationItem item)
{
return null;
}
protected override IEnumerable<string> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber)
{
return new List<string>();
}
protected override IEnumerable<string> GetSeasonSearchUrls(string seriesTitle, int seasonNumber)
{
return new List<string>();
}
protected override IEnumerable<string> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date)
{
return new List<string>();
}
protected override IEnumerable<string> GetPartialSeasonSearchUrls(string seriesTitle, int seasonNumber, int episodeWildcard)
{
return new List<string>();
}
protected override EpisodeParseResult CustomParser(SyndicationItem item, EpisodeParseResult currentResult)
{
if (currentResult != null)
{
currentResult.Size = 0;
}
return currentResult;
}
public override bool EnabledByDefault
{
get { return true; }
}
}
}

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
namespace NzbDrone.Core.Indexers.Wombles
{
public class Wombles : BaseIndexer
{
public override IEnumerable<string> RecentFeed
{
get
{
return new[]
{
string.Format("http://nzb.isasecret.com/rss")
};
}
}
public override string Name
{
get { return "WomblesIndex"; }
}
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>();
}
public bool EnabledByDefault
{
get { return true; }
}
}
}

@ -0,0 +1,28 @@
using System.ServiceModel.Syndication;
using NzbDrone.Core.Model;
namespace NzbDrone.Core.Indexers.Wombles
{
public class WomblesParser : BasicRssParser
{
protected override string GetNzbUrl(SyndicationItem item)
{
return item.Links[0].Uri.ToString();
}
protected override string GetNzbInfoUrl(SyndicationItem item)
{
return null;
}
protected override EpisodeParseResult PostProcessor(SyndicationItem item, EpisodeParseResult currentResult)
{
if (currentResult != null)
{
currentResult.Size = 0;
}
return currentResult;
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save