release endpoint now returns fully parsed rss info with decisions.

pull/3113/head
kay.one 12 years ago
parent 7e473ca78d
commit ca8eba9cf1

@ -6,8 +6,10 @@ using NzbDrone.Api.Indexers;
using NzbDrone.Api.Mapping;
using NzbDrone.Api.RootFolders;
using NzbDrone.Api.Series;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.RootFolders;
using NzbDrone.Test.Common;
@ -21,6 +23,9 @@ namespace NzbDrone.Api.Test.MappingTests
[TestCase(typeof(RootFolder), typeof(RootFolderResource))]
[TestCase(typeof(NamingConfig), typeof(NamingConfigResource))]
[TestCase(typeof(IndexerDefinition), typeof(IndexerResource))]
[TestCase(typeof(ReportInfo), typeof(ReleaseResource))]
[TestCase(typeof(ParsedEpisodeInfo), typeof(ReleaseResource))]
[TestCase(typeof(DownloadDecision), typeof(ReleaseResource))]
public void matching_fields(Type modelType, Type resourceType)
{
MappingValidation.ValidateMapping(modelType, resourceType);

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using NzbDrone.Api.Mapping;
using NzbDrone.Api.REST;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser;
using Omu.ValueInjecter;
using System.Linq;
namespace NzbDrone.Api.Indexers
{
public class ReleaseModule : NzbDroneRestModule<ReleaseResource>
{
private readonly IFetchAndParseRss _rssFetcherAndParser;
private readonly IMakeDownloadDecision _downloadDecisionMaker;
public ReleaseModule(IFetchAndParseRss rssFetcherAndParser, IMakeDownloadDecision downloadDecisionMaker)
{
_rssFetcherAndParser = rssFetcherAndParser;
_downloadDecisionMaker = downloadDecisionMaker;
GetResourceAll = GetRss;
}
private List<ReleaseResource> GetRss()
{
var reports = _rssFetcherAndParser.Fetch();
var decisions = _downloadDecisionMaker.GetRssDecision(reports);
var result = new List<ReleaseResource>();
foreach (var downloadDecision in decisions)
{
var release = new ReleaseResource();
release.InjectFrom(downloadDecision.RemoteEpisode.Report);
release.InjectFrom(downloadDecision.RemoteEpisode.ParsedEpisodeInfo);
release.InjectFrom(downloadDecision);
release.Rejections = downloadDecision.Rejections.ToList();
result.Add(release);
}
return result;
}
}
public class ReleaseResource : RestResource
{
public Int32 Age { get; set; }
public Int64 Size { get; set; }
public String Indexer { get; set; }
public String NzbInfoUrl { get; set; }
public String NzbUrl { get; set; }
public String ReleaseGroup { get; set; }
public String Title { get; set; }
public Boolean FullSeason { get; set; }
public Boolean SceneSource { get; set; }
public Int32 SeasonNumber { get; set; }
public Language Language { get; set; }
public DateTime? AirDate { get; set; }
public String OriginalString { get; set; }
public String SeriesTitle { get; set; }
public int[] EpisodeNumbers { get; set; }
public Boolean Approved { get; set; }
public List<string> Rejections { get; set; }
}
}

@ -0,0 +1,71 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Omu.ValueInjecter;
namespace NzbDrone.Api.Mapping
{
public class CloneInjection : ConventionInjection
{
protected override bool Match(ConventionInfo conventionInfo)
{
return conventionInfo.SourceProp.Name == conventionInfo.TargetProp.Name &&
conventionInfo.SourceProp.Value != null;
}
protected override object SetValue(ConventionInfo conventionInfo)
{
//for value types and string just return the value as is
if (conventionInfo.SourceProp.Type.IsValueType || conventionInfo.SourceProp.Type == typeof(string))
return conventionInfo.SourceProp.Value;
//handle arrays
if (conventionInfo.SourceProp.Type.IsArray)
{
var array = (Array)conventionInfo.SourceProp.Value;
var clone = (Array)array.Clone();
for (var index = 0; index < array.Length; index++)
{
var item = array.GetValue(index);
if (!item.GetType().IsValueType && !(item is string))
{
clone.SetValue(Activator.CreateInstance(item.GetType()).InjectFrom<CloneInjection>(item), index);
}
}
return clone;
}
if (conventionInfo.SourceProp.Type.IsGenericType)
{
//handle IEnumerable<> also ICollection<> IList<> List<>
if (conventionInfo.SourceProp.Type.GetGenericTypeDefinition().GetInterfaces().Any(d => d == typeof(IEnumerable)))
{
var t = conventionInfo.SourceProp.Type.GetGenericArguments()[0];
if (t.IsValueType || t == typeof(string)) return conventionInfo.SourceProp.Value;
var tlist = typeof(List<>).MakeGenericType(t);
var list = Activator.CreateInstance(tlist);
var addMethod = tlist.GetMethod("Add");
foreach (var o in (IEnumerable)conventionInfo.SourceProp.Value)
{
var e = Activator.CreateInstance(t).InjectFrom<CloneInjection>(o);
addMethod.Invoke(list, new[] { e }); // in 4.0 you can use dynamic and just do list.Add(e);
}
return list;
}
//unhandled generic type, you could also return null or throw
return conventionInfo.SourceProp.Value;
}
//for simple object types create a new instace and apply the clone injection on it
return Activator.CreateInstance(conventionInfo.SourceProp.Type)
.InjectFrom<CloneInjection>(conventionInfo.SourceProp.Value);
}
}
}

@ -96,6 +96,8 @@
<Compile Include="Frontend\StaticResourceMapper.cs" />
<Compile Include="Indexers\IndexerModule.cs" />
<Compile Include="Indexers\IndexerResource.cs" />
<Compile Include="Indexers\ReleaseModule.cs" />
<Compile Include="Mapping\CloneInjection.cs" />
<Compile Include="Mapping\MappingValidation.cs" />
<Compile Include="Mapping\ResourceMappingException.cs" />
<Compile Include="Mapping\ValueInjectorExtensions.cs" />

@ -22,11 +22,12 @@ namespace NzbDrone.Common.Reflection
public static bool IsSimpleType(this Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Nullable<>) || type.GetGenericTypeDefinition() == typeof(List<>)))
{
type = type.GetGenericArguments()[0];
}
return type.IsPrimitive
|| type.IsEnum
|| type == typeof(string)

@ -32,7 +32,6 @@ namespace NzbDrone.Core.Test.Datastore
public class TypeWithNoMappableProperties
{
public Series Series { get; set; }
public List<string> ListOfStrings { get; set; }
public int ReadOnly { get; private set; }
public int WriteOnly { private get; set; }

@ -27,14 +27,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
parseResultMulti = new RemoteEpisode
{
Report = new ReportInfo(),
Quality = new QualityModel(Quality.SDTV, true),
ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.SDTV, true) },
Episodes = new List<Episode> { new Episode(), new Episode() }
};
parseResultSingle = new RemoteEpisode
{
Report = new ReportInfo(),
Quality = new QualityModel(Quality.SDTV, true),
ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.SDTV, true) },
Episodes = new List<Episode> { new Episode() }
};
@ -374,7 +374,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
{
var parseResult = new RemoteEpisode
{
Quality = new QualityModel(Quality.RAWHD, false)
ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.RAWHD, false) },
};
Subject.IsSatisfiedBy(parseResult).Should().BeTrue();

@ -7,6 +7,7 @@ using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.DecisionEngineTests
{
@ -43,10 +44,10 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_fail2.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>())).Returns(false);
_fail3.Setup(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>())).Returns(false);
_reports = new List<ReportInfo> { new ReportInfo() };
_remoteEpisode = new RemoteEpisode();
_reports = new List<ReportInfo> { new ReportInfo { Title = "The.Office.S03E115.DVDRip.XviD-OSiTV" } };
_remoteEpisode = new RemoteEpisode { Series = new Series() };
Mocker.GetMock<IParsingService>().Setup(c => c.Map(It.IsAny<ReportInfo>()))
Mocker.GetMock<IParsingService>().Setup(c => c.Map(It.IsAny<ParsedEpisodeInfo>()))
.Returns(_remoteEpisode);
}
@ -102,20 +103,49 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
[Test]
public void should_not_attempt_to_make_decision_if_remote_episode_is_null()
public void should_not_attempt_to_map_episode_if_not_parsable()
{
GivenSpecifications(_pass1, _pass2, _pass3);
Mocker.GetMock<IParsingService>().Setup(c => c.Map(It.IsAny<ReportInfo>()))
.Returns<RemoteEpisode>(null);
_reports[0].Title = "Not parsable";
var results = Subject.GetRssDecision(_reports).ToList();
Mocker.GetMock<IParsingService>().Verify(c => c.Map(It.IsAny<ParsedEpisodeInfo>()), Times.Never());
_pass1.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>()), Times.Never());
_pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>()), Times.Never());
_pass3.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>()), Times.Never());
results.Should().BeEmpty();
}
[Test]
public void should_not_attempt_to_make_decision_if_series_is_unknow()
{
GivenSpecifications(_pass1, _pass2, _pass3);
_remoteEpisode.Series = null;
Subject.GetRssDecision(_reports);
_pass1.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>()), Times.Never());
_pass2.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>()), Times.Never());
_pass3.Verify(c => c.IsSatisfiedBy(It.IsAny<RemoteEpisode>()), Times.Never());
}
[Test]
public void should_return_unknow_series_rejectio_if_series_is_unknow()
{
GivenSpecifications(_pass1, _pass2, _pass3);
_remoteEpisode.Series = null;
var result = Subject.GetRssDecision(_reports);
result.Should().HaveCount(1);
}

@ -1,19 +1,8 @@

using System.Linq;
using System;
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Model;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Providers;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.DecisionEngineTests
@ -26,18 +15,24 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
private void WithEnglishRelease()
{
parseResult = Builder<RemoteEpisode>
.CreateNew()
.With(p => p.Language = Language.English)
.Build();
parseResult = new RemoteEpisode
{
ParsedEpisodeInfo = new ParsedEpisodeInfo
{
Language = Language.English
}
};
}
private void WithGermanRelease()
{
parseResult = Builder<RemoteEpisode>
.CreateNew()
.With(p => p.Language = Language.German)
.Build();
parseResult = new RemoteEpisode
{
ParsedEpisodeInfo = new ParsedEpisodeInfo
{
Language = Language.German
}
};
}
[Test]

@ -1,6 +1,4 @@

using System.Collections.Generic;
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using NUnit.Framework;
@ -16,7 +14,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public class QualityAllowedByProfileSpecificationFixture : CoreTest<QualityAllowedByProfileSpecification>
{
private RemoteEpisode parseResult;
private RemoteEpisode remoteEpisode;
public static object[] AllowedTestCases =
{
@ -39,29 +37,29 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
.With(c => c.QualityProfile = new QualityProfile { Cutoff = Quality.Bluray1080p })
.Build();
parseResult = new RemoteEpisode
remoteEpisode = new RemoteEpisode
{
Series = fakeSeries,
Quality = new QualityModel(Quality.DVD, true),
ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, true) },
};
}
[Test, TestCaseSource("AllowedTestCases")]
public void should_allow_if_quality_is_defined_in_profile(Quality qualityType)
{
parseResult.Quality.Quality = qualityType;
parseResult.Series.QualityProfile.Allowed = new List<Quality> { Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p };
remoteEpisode.ParsedEpisodeInfo.Quality.Quality = qualityType;
remoteEpisode.Series.QualityProfile.Allowed = new List<Quality> { Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p };
Subject.IsSatisfiedBy(parseResult).Should().BeTrue();
Subject.IsSatisfiedBy(remoteEpisode).Should().BeTrue();
}
[Test, TestCaseSource("DeniedTestCases")]
public void should_not_allow_if_quality_is_not_defined_in_profile(Quality qualityType)
{
parseResult.Quality.Quality = qualityType;
parseResult.Series.QualityProfile.Allowed = new List<Quality> { Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p };
remoteEpisode.ParsedEpisodeInfo.Quality.Quality = qualityType;
remoteEpisode.Series.QualityProfile.Allowed = new List<Quality> { Quality.DVD, Quality.HDTV720p, Quality.Bluray1080p };
Subject.IsSatisfiedBy(parseResult).Should().BeFalse();
Subject.IsSatisfiedBy(remoteEpisode).Should().BeFalse();
}
}
}

@ -44,14 +44,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
parseResultMulti = new RemoteEpisode
{
Series = fakeSeries,
Quality = new QualityModel(Quality.DVD, true),
ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, true) },
Episodes = doubleEpisodeList
};
parseResultSingle = new RemoteEpisode
{
Series = fakeSeries,
Quality = new QualityModel(Quality.DVD, true),
ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, true) },
Episodes = singleEpisodeList
};
}
@ -113,7 +113,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_not_be_upgradable_if_qualities_are_the_same()
{
firstFile.Quality = new QualityModel(Quality.WEBDL1080p);
parseResultSingle.Quality = new QualityModel(Quality.WEBDL1080p, false);
parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL1080p, false);
_upgradeDisk.IsSatisfiedBy(parseResultSingle).Should().BeFalse();
}

@ -44,14 +44,14 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
_parseResultMulti = new RemoteEpisode
{
Series = _fakeSeries,
Quality = new QualityModel(Quality.DVD, true),
ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, true) },
Episodes = doubleEpisodeList
};
_parseResultSingle = new RemoteEpisode
{
Series = _fakeSeries,
Quality = new QualityModel(Quality.DVD, true),
ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = new QualityModel(Quality.DVD, true) },
Episodes = singleEpisodeList
};
@ -115,7 +115,7 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
public void should_not_be_upgradable_if_episode_is_of_same_quality_as_existing()
{
_fakeSeries.QualityProfile = new QualityProfile { Cutoff = Quality.WEBDL1080p };
_parseResultSingle.Quality = new QualityModel(Quality.WEBDL1080p, false);
_parseResultSingle.ParsedEpisodeInfo.Quality = new QualityModel(Quality.WEBDL1080p, false);
_upgradableQuality = new QualityModel(Quality.WEBDL1080p, false);
Mocker.GetMock<IHistoryService>().Setup(c => c.GetBestQualityInHistory(1)).Returns(_upgradableQuality);

@ -13,10 +13,10 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
{
[TestFixture]
public class SabProviderFixture : CoreTest
public class SabProviderFixture : CoreTest<SabnzbdClient>
{
private const string url = "http://www.nzbclub.com/nzb_download.aspx?mid=1950232";
private const string title = "My Series Name - 5x2-5x3 - My title [Bluray720p] [Proper]";
private const string URL = "http://www.nzbclub.com/nzb_download.aspx?mid=1950232";
private const string TITLE = "My Series Name - 5x2-5x3 - My title [Bluray720p] [Proper]";
[SetUp]
public void Setup()
@ -45,17 +45,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
.Returns("{ \"status\": true }");
Mocker.Resolve<SabnzbdClient>().DownloadNzb(url, title, false).Should().BeTrue();
Subject.DownloadNzb(URL, TITLE, false).Should().BeTrue();
}
[Test]
public void add_by_url_should_detect_and_handle_sab_errors()
{
WithFailResponse();
Assert.Throws<ApplicationException>(() => Mocker.Resolve<SabnzbdClient>().DownloadNzb(url, title, false).Should().BeFalse());
//ExceptionVerification.ExpectedErrors(1);
Assert.Throws<ApplicationException>(() => Subject.DownloadNzb(URL, TITLE, false).Should().BeFalse());
}
[Test]
@ -68,14 +65,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
const string username = "admin2";
const string password = "pass2";
Mocker.GetMock<IHttpProvider>(MockBehavior.Strict)
.Setup(s => s.DownloadString("http://192.168.5.22:1111/api?mode=get_cats&output=json&apikey=5c770e3197e4fe763423ee7c392c25d2&ma_username=admin2&ma_password=pass2"))
.Returns(ReadAllText("Files", "Categories_json.txt"));
var result = Mocker.Resolve<SabnzbdClient>().GetCategories(host, port, apikey, username, password);
var result = Subject.GetCategories(host, port, apikey, username, password);
result.Should().NotBeNull();
@ -90,7 +84,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
.Returns(ReadAllText("Files", "Categories_json.txt"));
var result = Mocker.Resolve<SabnzbdClient>().GetCategories();
var result = Subject.GetCategories();
result.Should().NotBeNull();
@ -105,7 +99,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
.Returns(ReadAllText("Files", "History.txt"));
var result = Mocker.Resolve<SabnzbdClient>().GetHistory();
var result = Subject.GetHistory();
result.Should().HaveCount(1);
@ -119,7 +113,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
.Returns(ReadAllText("Files", "HistoryEmpty.txt"));
var result = Mocker.Resolve<SabnzbdClient>().GetHistory();
var result = Subject.GetHistory();
result.Should().BeEmpty();
@ -133,7 +127,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
.Returns(ReadAllText("Files", "JsonError.txt"));
Assert.Throws<ApplicationException>(() => Mocker.Resolve<SabnzbdClient>().GetHistory(), "API Key Incorrect");
Assert.Throws<ApplicationException>(() => Subject.GetHistory(), "API Key Incorrect");
}
[Test]
@ -146,7 +140,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
.Returns(response);
var result = Mocker.Resolve<SabnzbdClient>().GetVersion("192.168.5.55", 2222, "5c770e3197e4fe763423ee7c392c25d1", "admin", "pass");
var result = Subject.GetVersion("192.168.5.55", 2222, "5c770e3197e4fe763423ee7c392c25d1", "admin", "pass");
result.Should().NotBeNull();
@ -163,7 +157,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
.Returns(response);
var result = Mocker.Resolve<SabnzbdClient>().GetVersion();
var result = Subject.GetVersion();
result.Should().NotBeNull();
@ -173,14 +167,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
[Test]
public void Test_should_return_version_as_a_string()
{
var response = "{ \"version\": \"0.6.9\" }";
const string response = "{ \"version\": \"0.6.9\" }";
Mocker.GetMock<IHttpProvider>()
.Setup(s => s.DownloadString("http://192.168.5.55:2222/api?mode=version&output=json&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"))
.Returns(response);
var result = Mocker.Resolve<SabnzbdClient>().Test("192.168.5.55", 2222, "5c770e3197e4fe763423ee7c392c25d1", "admin", "pass");
var result = Subject.Test("192.168.5.55", 2222, "5c770e3197e4fe763423ee7c392c25d1", "admin", "pass");
result.Should().Be("0.6.9");
@ -192,7 +186,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
Mocker.GetMock<IHttpProvider>()
.Setup(s => s.DownloadString(It.IsAny<String>())).Throws(new WebException());
Mocker.Resolve<SabnzbdClient>().DownloadNzb(url, title, false).Should().BeFalse();
Subject.DownloadNzb(URL, TITLE, false).Should().BeFalse();
ExceptionVerification.ExpectedErrors(1);
}
@ -212,7 +206,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
.Returns("{ \"status\": true }");
Mocker.Resolve<SabnzbdClient>().DownloadNzb(url, title, true).Should().BeTrue();
Subject.DownloadNzb(URL, TITLE, true).Should().BeTrue();
Mocker.GetMock<IHttpProvider>()
.Verify(v => v.DownloadString("http://192.168.5.55:2222/api?mode=addurl&name=http://www.nzbclub.com/nzb_download.aspx?mid=1950232&priority=1&pp=3&cat=tv&nzbname=My+Series+Name+-+5x2-5x3+-+My+title+%5bBluray720p%5d+%5bProper%5d&output=json&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"), Times.Once());
@ -234,7 +228,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabProviderTests
.Returns("{ \"status\": true }");
Mocker.Resolve<SabnzbdClient>().DownloadNzb(url, title, false).Should().BeTrue();
Subject.DownloadNzb(URL, TITLE, false).Should().BeTrue();
Mocker.GetMock<IHttpProvider>()
.Verify(v => v.DownloadString("http://192.168.5.55:2222/api?mode=addurl&name=http://www.nzbclub.com/nzb_download.aspx?mid=1950232&priority=-1&pp=3&cat=tv&nzbname=My+Series+Name+-+5x2-5x3+-+My+title+%5bBluray720p%5d+%5bProper%5d&output=json&apikey=5c770e3197e4fe763423ee7c392c25d1&ma_username=admin&ma_password=pass"), Times.Once());

@ -1,13 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Download;
using NzbDrone.Core.Model;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
@ -31,7 +28,6 @@ namespace NzbDrone.Core.Test.Download
.Build().ToList();
_parseResult = Builder<RemoteEpisode>.CreateNew()
.With(c => c.Quality = new QualityModel(Quality.DVD))
.With(c => c.Series = Builder<Series>.CreateNew().Build())
.With(c=>c.Report = Builder<ReportInfo>.CreateNew().Build())
.With(c => c.Episodes = episodes)

@ -88,7 +88,6 @@ namespace NzbDrone.Core.Test.ParserTests
result.SeasonNumber.Should().Be(seasonNumber);
result.EpisodeNumbers.First().Should().Be(episodeNumber);
result.SeriesTitle.Should().Be(Parser.Parser.NormalizeTitle(title));
result.OriginalString.Should().Be(postTitle);
}
[TestCase(@"z:\tv shows\battlestar galactica (2003)\Season 3\S03E05 - Collaborators.mkv", 3, 5)]
@ -107,7 +106,6 @@ namespace NzbDrone.Core.Test.ParserTests
result.EpisodeNumbers.Should().HaveCount(1);
result.SeasonNumber.Should().Be(season);
result.EpisodeNumbers[0].Should().Be(episode);
result.OriginalString.Should().Be(path);
ExceptionVerification.IgnoreWarns();
}
@ -159,7 +157,6 @@ namespace NzbDrone.Core.Test.ParserTests
result.SeasonNumber.Should().Be(season);
result.EpisodeNumbers.Should().BeEquivalentTo(episodes);
result.SeriesTitle.Should().Be(Parser.Parser.NormalizeTitle(title));
result.OriginalString.Should().Be(postTitle);
}
@ -181,7 +178,6 @@ namespace NzbDrone.Core.Test.ParserTests
result.SeriesTitle.Should().Be(Parser.Parser.NormalizeTitle(title));
result.AirDate.Should().Be(airDate);
result.EpisodeNumbers.Should().BeNull();
result.OriginalString.Should().Be(postTitle);
}
[Test]
@ -203,9 +199,8 @@ namespace NzbDrone.Core.Test.ParserTests
var result = Parser.Parser.ParseTitle(postTitle);
result.SeasonNumber.Should().Be(season);
result.SeriesTitle.Should().Be(Parser.Parser.NormalizeTitle(title));
result.EpisodeNumbers.Count.Should().Be(0);
result.EpisodeNumbers.Length.Should().Be(0);
result.FullSeason.Should().BeTrue();
result.OriginalString.Should().Be(postTitle);
}
[TestCase("Conan", "conan")]
@ -346,7 +341,6 @@ namespace NzbDrone.Core.Test.ParserTests
result.SeriesTitle.Should().Be(Parser.Parser.NormalizeTitle(seriesName));
result.SeasonNumber.Should().Be(seasonNumber);
result.FullSeason.Should().BeTrue();
result.OriginalString.Should().Be(postTitle);
}
[TestCase("Acropolis Now S05 EXTRAS DVDRip XviD RUNNER")]

@ -408,13 +408,6 @@ namespace NzbDrone.Core.Configuration
set { SetValue("IgnoreArticlesWhenSortingSeries", value); }
}
public Boolean DownloadClientUseSceneName
{
get { return GetValueBoolean("DownloadClientUseSceneName", false); }
set { SetValue("DownloadClientUseSceneName", value); }
}
public String NzbgetUsername
{
get { return GetValue("NzbgetUsername", "nzbget"); }

@ -68,7 +68,6 @@ namespace NzbDrone.Core.Configuration
string OmgwtfnzbsUsername { get; set; }
string OmgwtfnzbsApiKey { get; set; }
Boolean IgnoreArticlesWhenSortingSeries { get; set; }
Boolean DownloadClientUseSceneName { get; set; }
String NzbgetUsername { get; set; }
String NzbgetPassword { get; set; }
String NzbgetHost { get; set; }

@ -1,14 +1,12 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Model;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.DecisionEngine
{
public class DownloadDecision
{
public RemoteEpisode Episode { get; private set; }
public RemoteEpisode RemoteEpisode { get; private set; }
public IEnumerable<string> Rejections { get; private set; }
public bool Approved
@ -21,7 +19,7 @@ namespace NzbDrone.Core.DecisionEngine
public DownloadDecision(RemoteEpisode episode, params string[] rejections)
{
Episode = episode;
RemoteEpisode = episode;
Rejections = rejections.ToList();
}
}

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.DecisionEngine.Specifications.Search;
@ -9,8 +10,8 @@ namespace NzbDrone.Core.DecisionEngine
{
public interface IMakeDownloadDecision
{
IEnumerable<DownloadDecision> GetRssDecision(IEnumerable<ReportInfo> reports);
IEnumerable<DownloadDecision> GetSearchDecision(IEnumerable<ReportInfo> reports, SearchDefinitionBase searchDefinitionBase);
List<DownloadDecision> GetRssDecision(IEnumerable<ReportInfo> reports);
List<DownloadDecision> GetSearchDecision(IEnumerable<ReportInfo> reports, SearchDefinitionBase searchDefinitionBase);
}
public class DownloadDecisionMaker : IMakeDownloadDecision
@ -24,29 +25,19 @@ namespace NzbDrone.Core.DecisionEngine
_parsingService = parsingService;
}
public IEnumerable<DownloadDecision> GetRssDecision(IEnumerable<ReportInfo> reports)
public List<DownloadDecision> GetRssDecision(IEnumerable<ReportInfo> reports)
{
foreach (var report in reports)
{
var parseResult = _parsingService.Map(report);
if (parseResult != null)
{
yield return new DownloadDecision(parseResult, GetGeneralRejectionReasons(parseResult).ToArray());
}
}
return GetDecisions(reports, GetGeneralRejectionReasons).ToList();
}
public IEnumerable<DownloadDecision> GetSearchDecision(IEnumerable<ReportInfo> reports, SearchDefinitionBase searchDefinitionBase)
public List<DownloadDecision> GetSearchDecision(IEnumerable<ReportInfo> reports, SearchDefinitionBase searchDefinitionBase)
{
foreach (var report in reports)
return GetDecisions(reports, remoteEpisode =>
{
var remoteEpisode = _parsingService.Map(report);
var generalReasons = GetGeneralRejectionReasons(remoteEpisode);
var searchReasons = GetSearchRejectionReasons(remoteEpisode, searchDefinitionBase);
yield return new DownloadDecision(remoteEpisode, generalReasons.Union(searchReasons).ToArray());
}
return generalReasons.Union(searchReasons);
}).ToList();
}
@ -58,6 +49,29 @@ namespace NzbDrone.Core.DecisionEngine
.Select(spec => spec.RejectionReason);
}
private IEnumerable<DownloadDecision> GetDecisions(IEnumerable<ReportInfo> reports, Func<RemoteEpisode, IEnumerable<string>> decisionCallback)
{
foreach (var report in reports)
{
var parsedEpisodeInfo = Parser.Parser.ParseTitle(report.Title);
if (parsedEpisodeInfo != null)
{
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo);
remoteEpisode.Report = report;
if (remoteEpisode.Series != null)
{
yield return new DownloadDecision(remoteEpisode, decisionCallback(remoteEpisode).ToArray());
}
else
{
yield return new DownloadDecision(remoteEpisode, "Unknown Series");
}
}
}
}
private IEnumerable<string> GetSearchRejectionReasons(RemoteEpisode report, SearchDefinitionBase searchDefinitionBase)
{
return _specifications

@ -30,13 +30,13 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
_logger.Trace("Beginning size check for: {0}", subject);
if (subject.Quality.Quality == Quality.RAWHD)
if (subject.ParsedEpisodeInfo.Quality.Quality == Quality.RAWHD)
{
_logger.Trace("Raw-HD release found, skipping size check.");
return true;
}
var qualityType = _qualityTypeProvider.Get((int)subject.Quality.Quality);
var qualityType = _qualityTypeProvider.Get((int)subject.ParsedEpisodeInfo.Quality.Quality);
if (qualityType.MaxSize == 0)
{

@ -1,5 +1,4 @@
using NLog;
using NzbDrone.Core.Model;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
@ -24,10 +23,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public virtual bool IsSatisfiedBy(RemoteEpisode subject)
{
_logger.Trace("Checking if report meets language requirements. {0}", subject.Language);
if (subject.Language != Language.English)
_logger.Trace("Checking if report meets language requirements. {0}", subject.ParsedEpisodeInfo.Language);
if (subject.ParsedEpisodeInfo.Language != Language.English)
{
_logger.Trace("Report Language: {0} rejected because it is not English", subject.Language);
_logger.Trace("Report Language: {0} rejected because it is not English", subject.ParsedEpisodeInfo.Language);
return false;
}

@ -1,10 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.Download;
using NzbDrone.Core.Model;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
@ -27,7 +24,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
}
}
public virtual bool IsSatisfiedBy(RemoteEpisode subject)
public bool IsSatisfiedBy(RemoteEpisode subject)
{
var downloadClient = _downloadClientProvider.GetDownloadClient();
@ -36,25 +33,25 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
return !IsInQueue(subject, queue);
}
public virtual bool IsInQueue(RemoteEpisode newEpisode, IEnumerable<ParsedEpisodeInfo> queue)
private bool IsInQueue(RemoteEpisode newEpisode, IEnumerable<ParsedEpisodeInfo> queue)
{
var matchingTitle = queue.Where(q => String.Equals(q.SeriesTitle, newEpisode.Series.CleanTitle, StringComparison.InvariantCultureIgnoreCase));
var matchingTitleWithQuality = matchingTitle.Where(q => q.Quality >= newEpisode.Quality);
var matchingTitleWithQuality = matchingTitle.Where(q => q.Quality >= newEpisode.ParsedEpisodeInfo.Quality);
if (newEpisode.Series.SeriesType == SeriesTypes.Daily)
{
return matchingTitleWithQuality.Any(q => q.AirDate.Value.Date == newEpisode.AirDate.Value.Date);
return matchingTitleWithQuality.Any(q => q.AirDate.Value.Date == newEpisode.ParsedEpisodeInfo.AirDate.Value.Date);
}
var matchingSeason = matchingTitleWithQuality.Where(q => q.SeasonNumber == newEpisode.SeasonNumber);
var matchingSeason = matchingTitleWithQuality.Where(q => q.SeasonNumber == newEpisode.ParsedEpisodeInfo.SeasonNumber);
if (newEpisode.FullSeason)
if (newEpisode.ParsedEpisodeInfo.FullSeason)
{
return matchingSeason.Any();
}
return matchingSeason.Any(q => q.EpisodeNumbers != null && q.EpisodeNumbers.Any(e => newEpisode.EpisodeNumbers.Contains(e)));
return matchingSeason.Any(q => q.EpisodeNumbers != null && q.EpisodeNumbers.Any(e => newEpisode.ParsedEpisodeInfo.EpisodeNumbers.Contains(e)));
}
}

@ -24,10 +24,10 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
public virtual bool IsSatisfiedBy(RemoteEpisode subject)
{
_logger.Trace("Checking if report meets quality requirements. {0}", subject.Quality);
if (!subject.Series.QualityProfile.Allowed.Contains(subject.Quality.Quality))
_logger.Trace("Checking if report meets quality requirements. {0}", subject.ParsedEpisodeInfo.Quality);
if (!subject.Series.QualityProfile.Allowed.Contains(subject.ParsedEpisodeInfo.Quality.Quality))
{
_logger.Trace("Quality {0} rejected by Series' quality profile", subject.Quality);
_logger.Trace("Quality {0} rejected by Series' quality profile", subject.ParsedEpisodeInfo.Quality);
return false;
}

@ -31,7 +31,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
var episode = _episodeService.GetEpisode(dailySearchSpec.SeriesId, dailySearchSpec.Airtime);
if (!remoteEpisode.AirDate.HasValue || remoteEpisode.AirDate.Value != episode.AirDate.Value)
if (!remoteEpisode.ParsedEpisodeInfo.AirDate.HasValue || remoteEpisode.ParsedEpisodeInfo.AirDate.Value.Date != episode.AirDate.Value.Date)
{
_logger.Trace("Episode AirDate does not match searched episode number, skipping.");
return false;

@ -1,7 +1,5 @@
using NLog;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Model;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
@ -28,7 +26,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
var singleEpisodeSpec = searchDefinitionBase as SeasonSearchDefinition;
if (singleEpisodeSpec == null) return true;
if (singleEpisodeSpec.SeasonNumber != remoteEpisode.SeasonNumber)
if (singleEpisodeSpec.SeasonNumber != remoteEpisode.ParsedEpisodeInfo.SeasonNumber)
{
_logger.Trace("Season number does not match searched season number, skipping.");
return false;

@ -1,8 +1,6 @@
using System.Linq;
using NLog;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.Model;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications.Search
@ -29,7 +27,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications.Search
var singleEpisodeSpec = searchDefinitionBase as SingleEpisodeSearchDefinition;
if (singleEpisodeSpec == null) return true;
if (singleEpisodeSpec.SeasonNumber != remoteEpisode.SeasonNumber)
if (singleEpisodeSpec.SeasonNumber != remoteEpisode.ParsedEpisodeInfo.SeasonNumber)
{
_logger.Trace("Season number does not match searched season number, skipping.");
return false;

@ -30,12 +30,12 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
{
_logger.Trace("Comparing file quality with report. Existing file is {0}", file.Quality);
if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.QualityProfile, file.Quality, subject.Quality))
if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.QualityProfile, file.Quality, subject.ParsedEpisodeInfo.Quality))
{
return false;
}
if (subject.Quality.Proper && file.DateAdded < DateTime.Today.AddDays(-7))
if (subject.ParsedEpisodeInfo.Quality.Proper && file.DateAdded < DateTime.Today.AddDays(-7))
{
_logger.Trace("Proper for old file, skipping: {0}", subject);
return false;

@ -33,7 +33,7 @@ namespace NzbDrone.Core.DecisionEngine.Specifications
if (bestQualityInHistory != null)
{
_logger.Trace("Comparing history quality with report. History is {0}", bestQualityInHistory);
if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.QualityProfile, bestQualityInHistory, subject.Quality))
if (!_qualityUpgradableSpecification.IsUpgradable(subject.Series.QualityProfile, bestQualityInHistory, subject.ParsedEpisodeInfo.Quality))
return false;
}
}

@ -7,19 +7,61 @@ using Newtonsoft.Json.Linq;
using NLog;
using NzbDrone.Common;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Parser.Model;
using RestSharp;
namespace NzbDrone.Core.Download.Clients.Sabnzbd
{
public class SabRequestBuilder
{
private readonly IConfigService _configService;
public SabRequestBuilder(IConfigService configService)
{
_configService = configService;
}
public IRestRequest AddToQueueRequest(RemoteEpisode remoteEpisode)
{
string cat = _configService.SabTvCategory;
int priority = remoteEpisode.IsRecentEpisode() ? (int)_configService.SabRecentTvPriority : (int)_configService.SabBacklogTvPriority;
string name = remoteEpisode.Report.NzbUrl.Replace("&", "%26");
string nzbName = HttpUtility.UrlEncode(remoteEpisode.Report.Title);
string action = string.Format("mode=addurl&name={0}&priority={1}&pp=3&cat={2}&nzbname={3}&output=json",
name, priority, cat, nzbName);
string request = GetSabRequest(action);
return new RestRequest(request);
}
private string GetSabRequest(string action)
{
return string.Format(@"http://{0}:{1}/api?{2}&apikey={3}&ma_username={4}&ma_password={5}",
_configService.SabHost,
_configService.SabPort,
action,
_configService.SabApiKey,
_configService.SabUsername,
_configService.SabPassword);
}
}
public class SabnzbdClient : IDownloadClient
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
private readonly IConfigService _configService;
private readonly IHttpProvider _httpProvider;
private readonly Logger _logger;
public SabnzbdClient(IConfigService configService, IHttpProvider httpProvider)
public SabnzbdClient(IConfigService configService, IHttpProvider httpProvider, Logger logger)
{
_configService = configService;
_httpProvider = httpProvider;
_logger = logger;
}
public virtual bool DownloadNzb(string url, string title, bool recentlyAired)
@ -36,11 +78,11 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
name, priority, cat, nzbName);
string request = GetSabRequest(action);
logger.Info("Adding report [{0}] to the queue.", title);
_logger.Info("Adding report [{0}] to the queue.", title);
var response = _httpProvider.DownloadString(request);
logger.Debug("Queue Response: [{0}]", response);
_logger.Debug("Queue Response: [{0}]", response);
CheckForError(response);
return true;
@ -48,7 +90,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
catch (WebException ex)
{
logger.Error("Error communicating with SAB: " + ex.Message);
_logger.Error("Error communicating with SAB: " + ex.Message);
}
return false;
@ -62,9 +104,9 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
CheckForError(response);
var sabQeueu = JsonConvert.DeserializeObject<SabQueue>(JObject.Parse(response).SelectToken("queue").ToString()).Items;
var sabQueue = JsonConvert.DeserializeObject<SabQueue>(JObject.Parse(response).SelectToken("queue").ToString()).Items;
foreach (var sabQueueItem in sabQeueu)
foreach (var sabQueueItem in sabQueue)
{
var queueItem = new QueueItem();
queueItem.Id = sabQueueItem.Id;
@ -163,7 +205,7 @@ namespace NzbDrone.Core.Download.Clients.Sabnzbd
}
catch (Exception ex)
{
logger.DebugException("Failed to Test SABnzbd", ex);
_logger.DebugException("Failed to Test SABnzbd", ex);
}
return String.Empty;

@ -37,10 +37,6 @@ namespace NzbDrone.Core.Download
public bool DownloadReport(RemoteEpisode episode)
{
var downloadTitle = episode.Report.Title;
if (!_configService.DownloadClientUseSceneName)
{
downloadTitle = episode.GetDownloadTitle();
}
var provider = _downloadClientProvider.GetDownloadClient();
var recentEpisode = ContainsRecentEpisode(episode);

@ -1,8 +1,4 @@
using System;
using Newtonsoft.Json;
using NzbDrone.Core.Download.Clients.Sabnzbd;
using NzbDrone.Core.Download.Clients.Sabnzbd.JsonConverters;
using NzbDrone.Core.Model;
namespace NzbDrone.Core.Download
{
@ -14,8 +10,6 @@ namespace NzbDrone.Core.Download
public decimal SizeLeft { get; set; }
public int Percentage { get; set; }
public string Id { get; set; }
public TimeSpan Timeleft { get; set; }

@ -83,7 +83,8 @@ namespace NzbDrone.Core.ExternalNotification
try
{
_logger.Trace("Sending grab notification to {0}", Name);
OnGrab(message.Episode.GetDownloadTitle());
//todo: pass all the info to grab event and let the handlers deal with it.
OnGrab(message.Episode.ToString());
}
catch (Exception e)

@ -56,7 +56,7 @@ namespace NzbDrone.Core.History
{
Date = DateTime.Now,
Indexer = message.Episode.Report.Indexer,
Quality = message.Episode.Quality,
Quality = message.Episode.ParsedEpisodeInfo.Quality,
NzbTitle = message.Episode.Report.Title,
EpisodeId = episode.Id,
NzbInfoUrl = message.Episode.Report.NzbInfoUrl,

@ -40,8 +40,8 @@ namespace NzbDrone.Core.Indexers
var qualifiedReports = decisions
.Where(c => c.Approved)
.Select(c => c.Episode)
.OrderByDescending(c => c.Quality)
.Select(c => c.RemoteEpisode)
.OrderByDescending(c => c.ParsedEpisodeInfo.Quality)
.ThenBy(c => c.Episodes.Select(e => e.EpisodeNumber).MinOrDefault())
.ThenBy(c => c.Report.Age);

@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Model;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Parser.Model
@ -9,10 +7,9 @@ namespace NzbDrone.Core.Parser.Model
public class ParsedEpisodeInfo
{
public string SeriesTitle { get; set; }
public string OriginalString { get; set; }
public QualityModel Quality { get; set; }
public int SeasonNumber { get; set; }
public List<int> EpisodeNumbers { get; set; }
public int[] EpisodeNumbers { get; set; }
public DateTime? AirDate { get; set; }
public Language Language { get; set; }

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Parser.Model
@ -10,99 +9,15 @@ namespace NzbDrone.Core.Parser.Model
{
public ReportInfo Report { get; set; }
public bool FullSeason { get; set; }
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
public Series Series { get; set; }
public List<Episode> Episodes { get; set; }
public QualityModel Quality { get; set; }
public Language Language { get; set; }
public int SeasonNumber
{
get { return Episodes.Select(e => e.SeasonNumber).Distinct().SingleOrDefault(); }
}
public DateTime? AirDate
{
get
{
return Episodes.Single().AirDate;
}
}
public IEnumerable<int> EpisodeNumbers
{
get
{
return Episodes.Select(c => c.EpisodeNumber).Distinct();
}
}
public string GetDownloadTitle()
{
var seriesTitle = FileNameBuilder.CleanFilename(Series.Title);
//Handle Full Naming
if (FullSeason)
{
var seasonResult = String.Format("{0} - Season {1} [{2}]", seriesTitle, SeasonNumber, Quality);
if (Quality.Proper)
seasonResult += " [Proper]";
return seasonResult;
}
if (Series.SeriesType == SeriesTypes.Daily)
{
var dailyResult = String.Format("{0} - {1:yyyy-MM-dd} - {2} [{3}]", seriesTitle,
AirDate, Episodes.First().Title, Quality);
if (Quality.Proper)
dailyResult += " [Proper]";
return dailyResult;
}
//Show Name - 1x01-1x02 - Episode Name
//Show Name - 1x01 - Episode Name
var episodeString = new List<string>();
var episodeNames = new List<string>();
foreach (var episode in Episodes)
{
episodeString.Add(String.Format("{0}x{1:00}", episode.SeasonNumber, episode.EpisodeNumber));
episodeNames.Add(Core.Parser.Parser.CleanupEpisodeTitle(episode.Title));
}
var epNumberString = String.Join("-", episodeString);
string episodeName;
if (episodeNames.Distinct().Count() == 1)
episodeName = episodeNames.First();
else
episodeName = String.Join(" + ", episodeNames.Distinct());
var result = String.Format("{0} - {1} - {2} [{3}]", seriesTitle, epNumberString, episodeName, Quality);
if (Quality.Proper)
{
result += " [Proper]";
}
return result;
}
public override string ToString()
public bool IsRecentEpisode()
{
throw new NotImplementedException();
return Episodes.Any(e => e.AirDate >= DateTime.Today.AddDays(-7));
}
}
}

@ -5,7 +5,6 @@ using System.Linq;
using System.Text.RegularExpressions;
using NLog;
using NzbDrone.Common;
using NzbDrone.Core.Model;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
@ -90,11 +89,7 @@ namespace NzbDrone.Core.Parser
result = ParseTitle(fileInfo.FullName);
}
if (result != null)
{
result.OriginalString = path;
}
else
if (result == null)
{
Logger.Warn("Unable to parse episode info from path {0}", path);
}
@ -124,7 +119,6 @@ namespace NzbDrone.Core.Parser
result.Language = ParseLanguage(title);
result.Quality = ParseQuality(title);
result.OriginalString = title;
return result;
}
}
@ -172,7 +166,7 @@ namespace NzbDrone.Core.Parser
result = new ParsedEpisodeInfo
{
SeasonNumber = seasons.First(),
EpisodeNumbers = new List<int>()
EpisodeNumbers = new int[0],
};
foreach (Match matchGroup in matchCollection)
@ -184,7 +178,7 @@ namespace NzbDrone.Core.Parser
{
var first = Convert.ToInt32(episodeCaptures.First().Value);
var last = Convert.ToInt32(episodeCaptures.Last().Value);
result.EpisodeNumbers = Enumerable.Range(first, last - first + 1).ToList();
result.EpisodeNumbers = Enumerable.Range(first, last - first + 1).ToArray();
}
else
{

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Core.Parser.Model;
@ -11,7 +10,7 @@ namespace NzbDrone.Core.Parser
{
LocalEpisode GetEpisodes(string fileName, Series series);
Series GetSeries(string title);
RemoteEpisode Map(ReportInfo reportInfo);
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo);
}
public class ParsingService : IParsingService
@ -65,32 +64,23 @@ namespace NzbDrone.Core.Parser
return _seriesService.FindByTitle(searchTitle);
}
public RemoteEpisode Map(ReportInfo reportInfo)
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo)
{
var parsedInfo = Parser.ParseTitle(reportInfo.Title);
if (parsedInfo == null)
var remoteEpisode = new RemoteEpisode
{
return null;
}
ParsedEpisodeInfo = parsedEpisodeInfo,
};
var series = _seriesService.FindByTitle(parsedInfo.SeriesTitle);
var series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
if (series == null)
{
_logger.Trace("No matching series {0}", parsedInfo.SeriesTitle);
return null;
_logger.Trace("No matching series {0}", parsedEpisodeInfo.SeriesTitle);
return remoteEpisode;
}
var remoteEpisode = new RemoteEpisode
{
Series = series,
Episodes = GetEpisodes(parsedInfo, series),
FullSeason = parsedInfo.FullSeason,
Language = parsedInfo.Language,
Quality = parsedInfo.Quality,
Report = reportInfo
};
remoteEpisode.Series = series;
remoteEpisode.Episodes = GetEpisodes(parsedEpisodeInfo, series);
return remoteEpisode;
}
@ -104,7 +94,7 @@ namespace NzbDrone.Core.Parser
if (series.SeriesType == SeriesTypes.Standard)
{
//Todo: Collect this as a Series we want to treat as a daily series, or possible parsing error
_logger.Warn("Found daily-style episode for non-daily series: {0}. {1}", series.Title, parsedEpisodeInfo.OriginalString);
_logger.Warn("Found daily-style episode for non-daily series: {0}.", series.Title);
return new List<Episode>();
}

@ -3,6 +3,7 @@ using System.Net;
using FluentAssertions;
using NLog;
using NzbDrone.Api.REST;
using NzbDrone.Common;
using RestSharp;
namespace NzbDrone.Integration.Test.Client
@ -13,6 +14,7 @@ namespace NzbDrone.Integration.Test.Client
private readonly string _resource;
private readonly Logger _logger;
private readonly JsonSerializer _jsonSerializer;
public ClientBase(IRestClient restClient, string resource = null)
{
@ -23,6 +25,11 @@ namespace NzbDrone.Integration.Test.Client
_restClient = restClient;
_resource = resource;
_jsonSerializer = new JsonSerializer();
_logger = LogManager.GetLogger("REST");
}
@ -60,13 +67,13 @@ namespace NzbDrone.Integration.Test.Client
};
}
protected T Get<T>(IRestRequest request, HttpStatusCode statusCode = HttpStatusCode.OK) where T : new()
protected T Get<T>(IRestRequest request, HttpStatusCode statusCode = HttpStatusCode.OK) where T : class, new()
{
request.Method = Method.GET;
return Execute<T>(request, statusCode);
}
public T Post<T>(IRestRequest request, HttpStatusCode statusCode = HttpStatusCode.Created) where T : new()
public T Post<T>(IRestRequest request, HttpStatusCode statusCode = HttpStatusCode.Created) where T : class, new()
{
request.Method = Method.POST;
return Execute<T>(request, statusCode);
@ -78,11 +85,11 @@ namespace NzbDrone.Integration.Test.Client
Execute<object>(request, statusCode);
}
private T Execute<T>(IRestRequest request, HttpStatusCode statusCode) where T : new()
private T Execute<T>(IRestRequest request, HttpStatusCode statusCode) where T : class, new()
{
_logger.Info("{0}: {1}", request.Method, _restClient.BuildUri(request));
var response = _restClient.Execute<T>(request);
var response = _restClient.Execute(request);
_logger.Info("Response: {0}", response.Content);
response.StatusCode.Should().Be(statusCode);
@ -94,7 +101,7 @@ namespace NzbDrone.Integration.Test.Client
response.ErrorMessage.Should().BeBlank();
return response.Data;
return _jsonSerializer.Deserialize<T>(response.Content);
}
}

@ -0,0 +1,16 @@
using NzbDrone.Api.Indexers;
using RestSharp;
namespace NzbDrone.Integration.Test.Client
{
public class ReleaseClient : ClientBase<ReleaseResource>
{
public ReleaseClient(IRestClient restClient)
: base(restClient)
{
}
}
}

@ -31,15 +31,16 @@ namespace NzbDrone.Integration.Test
protected SeriesClient Series;
protected ClientBase<RootFolderResource> RootFolders;
protected ClientBase<CommandResource> Commands;
protected ReleaseClient Releases;
static IntegrationTest()
{
if (LogManager.Configuration == null || LogManager.Configuration is XmlLoggingConfiguration)
{
LogManager.Configuration = new LoggingConfiguration();
var consoleTarget = new ConsoleTarget { Layout = "${logger} - ${message} ${exception}" };
var consoleTarget = new ConsoleTarget { Layout = "${time} - ${logger} - ${message} ${exception}" };
LogManager.Configuration.AddTarget(consoleTarget.GetType().Name, consoleTarget);
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Info, consoleTarget));
LogManager.Configuration.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, consoleTarget));
}
@ -85,6 +86,7 @@ namespace NzbDrone.Integration.Test
RestClient = new RestClient(url + "/api/");
Series = new SeriesClient(RestClient);
Releases = new ReleaseClient(RestClient);
RootFolders = new ClientBase<RootFolderResource>(RestClient);
Commands = new ClientBase<CommandResource>(RestClient);

@ -75,8 +75,10 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Client\ClientBase.cs" />
<Compile Include="Client\SeriesClient - Copy.cs" />
<Compile Include="Client\SeriesClient.cs" />
<Compile Include="CommandIntegerationTests.cs" />
<Compile Include="ReleaseIntegrationTest.cs" />
<Compile Include="IntegrationTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RootFolderIntegrationTest.cs" />

@ -0,0 +1,17 @@
using FluentAssertions;
using NUnit.Framework;
namespace NzbDrone.Integration.Test
{
[TestFixture]
public class ReleaseIntegrationTest : IntegrationTest
{
[Test]
public void should_only_have_unknown_series_releases()
{
Releases.All().Should().OnlyContain(c => c.Rejections.Contains("Unknown Series"));
}
}
}
Loading…
Cancel
Save