1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-11-04 10:02:40 +01:00

Merge branch 'master' of git://github.com/kayone/NzbDrone

This commit is contained in:
Mark McDowall 2011-04-21 00:17:45 -07:00
commit a977443676
15 changed files with 302 additions and 103 deletions

View File

@ -1,10 +1,13 @@
using System; using System;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq.Expressions;
using AutoMoq; using AutoMoq;
using FizzWare.NBuilder; using FizzWare.NBuilder;
using MbUnit.Framework; using MbUnit.Framework;
using Moq; using Moq;
using NzbDrone.Core.Model;
using NzbDrone.Core.Providers; using NzbDrone.Core.Providers;
using NzbDrone.Core.Repository; using NzbDrone.Core.Repository;
using NzbDrone.Core.Repository.Quality; using NzbDrone.Core.Repository.Quality;
@ -54,42 +57,142 @@ public void RefreshEpisodeInfo()
} }
[Test] [Test]
public void IsNeededTrue()
//Should Download
[Row(QualityTypes.TV, true, QualityTypes.TV, false, true)]
[Row(QualityTypes.DVD, true, QualityTypes.TV, true, true)]
[Row(QualityTypes.DVD, true, QualityTypes.TV, true, true)]
//Should Skip
[Row(QualityTypes.Bluray720, true, QualityTypes.Bluray1080, false, false)]
[Row(QualityTypes.TV, true, QualityTypes.HDTV, true, false)]
public void Is_Needed_Tv_Dvd_BluRay_BluRay720_Is_Cutoff(QualityTypes reportQuality, Boolean isReportProper, QualityTypes fileQuality, Boolean isFileProper, bool excpected)
{ {
//Setup //Setup
var season = new Mock<SeasonProvider>(); var parseResult = new EpisodeParseResult
var series = new Mock<SeriesProvider>();
//var history = new Mock<IHistoryProvider>();
//var quality = new Mock<IQualityProvider>();
var repo = new Mock<IRepository>();
var epInDb = new Episode
{ {
AirDate = DateTime.Today, SeriesId = 12,
EpisodeId = 55555, SeasonNumber = 2,
EpisodeNumber = 5, Episodes = new List<int> { 3 },
Language = "en", Quality = reportQuality,
SeasonId = 4444, Proper = isReportProper
SeasonNumber = 1
}; };
season.Setup(s => s.IsIgnored(12345, 1)).Returns(false); var epFile = new EpisodeFile()
series.Setup(s => s.QualityWanted(12345, QualityTypes.TV)).Returns(true); {
repo.Setup(s => s.Single<Episode>(c => c.SeriesId == 12345 && c.SeasonNumber == 1 && c.EpisodeNumber == 5)). Proper = isFileProper,
Returns(epInDb); Quality = fileQuality
};
//repo.Setup(s => s.All<EpisodeFile>()).Returns(); var episodeInfo = new Episode
//repo.All<EpisodeFile>().Where(c => c.EpisodeId == episode.EpisodeId); {
SeriesId = 12,
SeasonNumber = 2,
EpisodeNumber = 3,
Series = new Series() { QualityProfileId = 1 },
EpisodeFile = epFile
};
var seriesQualityProfile = new QualityProfile()
{
Allowed = new List<QualityTypes> { QualityTypes.TV, QualityTypes.DVD, QualityTypes.Bluray720, QualityTypes.Bluray1080 },
Cutoff = QualityTypes.Bluray720,
Name = "TV/DVD",
};
var mocker = new AutoMoqer();
mocker.GetMock<IRepository>()
.Setup(r => r.Single(It.IsAny<Expression<Func<Episode, Boolean>>>()))
.Returns(episodeInfo);
mocker.GetMock<QualityProvider>()
.Setup(q => q.Find(1))
.Returns(seriesQualityProfile);
var result = mocker.Resolve<EpisodeProvider>().IsNeeded(parseResult);
Assert.AreEqual(excpected, result);
}
[Test]
public void Missing_episode_should_be_added()
{
//Setup
var parseResult1 = new EpisodeParseResult
{
SeriesId = 12,
SeasonNumber = 2,
Episodes = new List<int> { 3 },
Quality = QualityTypes.DVD
};
var parseResult2 = new EpisodeParseResult
{
SeriesId = 12,
SeasonNumber = 3,
Episodes = new List<int> { 3 },
Quality = QualityTypes.DVD
};
var mocker = new AutoMoqer();
mocker.SetConstant(MockLib.GetEmptyRepository());
var episodeProvider = mocker.Resolve<EpisodeProvider>();
var result1 = episodeProvider.IsNeeded(parseResult1);
var result2 = episodeProvider.IsNeeded(parseResult2);
var episodes = episodeProvider.GetEpisodeBySeries(12);
Assert.IsTrue(result1);
Assert.IsTrue(result2);
Assert.IsNotEmpty(episodes);
Assert.Count(2, episodes);
}
[Test]
[Row(1, new[] { 2 }, "My Episode Title", QualityTypes.DVD, false, "My Series Name - 1x2 - My Episode Title DVD")]
[Row(1, new[] { 2 }, "My Episode Title", QualityTypes.DVD, true, "My Series Name - 1x2 - My Episode Title DVD [Proper]")]
[Row(1, new[] { 2 }, "", QualityTypes.DVD, true, "My Series Name - 1x2 - DVD [Proper]")]
[Row(1, new[] { 2, 4 }, "My Episode Title", QualityTypes.HDTV, false, "My Series Name - 1x2-1x4 - My Episode Title HDTV")]
[Row(1, new[] { 2, 4 }, "My Episode Title", QualityTypes.HDTV, true, "My Series Name - 1x2-1x4 - My Episode Title HDTV [Proper]")]
[Row(1, new[] { 2, 4 }, "", QualityTypes.HDTV, true, "My Series Name - 1x2-1x4 - HDTV [Proper]")]
public void sab_title(int seasons, int[] episodes, string title, QualityTypes quality, bool proper, string excpected)
{
//Arrange
var fakeSeries = new Series()
{
SeriesId = 12,
Path = "C:\\TV Shows\\My Series Name"
};
var mocker = new AutoMoqer();
mocker.GetMock<SeriesProvider>(MockBehavior.Strict)
.Setup(c => c.GetSeries(12))
.Returns(fakeSeries);
var parsResult = new EpisodeParseResult()
{
SeriesId = 12,
AirDate = DateTime.Now,
Episodes = episodes.ToList(),
Proper = proper,
Quality = quality,
SeasonNumber = seasons,
EpisodeTitle = title
};
//Act //Act
var actual = mocker.Resolve<EpisodeProvider>().GetSabTitle(parsResult);
//Assert //Assert
Assert.AreEqual(excpected, actual);
} }
[Test] [Test]
[Explicit]
public void Add_daily_show_episodes() public void Add_daily_show_episodes()
{ {
var mocker = new AutoMoqer(); var mocker = new AutoMoqer();

View File

@ -62,7 +62,7 @@ public MockIndexerProvider(SeriesProvider seriesProvider, SeasonProvider seasonP
{ {
} }
protected override string[] Url protected override string[] Urls
{ {
get { return new[] { "www.google.com" }; } get { return new[] { "www.google.com" }; }
} }

View File

@ -50,6 +50,27 @@ public void can_run_job_again()
} }
[Test]
//This test will confirm that the concurrency checks are rest
//after execution so the job can successfully run.
public void can_run_broken_job_again()
{
IEnumerable<IJob> fakeTimers = new List<IJob> { new BrokenJob() };
var mocker = new AutoMoqer();
mocker.SetConstant(MockLib.GetEmptyRepository());
mocker.SetConstant(fakeTimers);
var timerProvider = mocker.Resolve<JobProvider>();
timerProvider.Initialize();
var firstRun = timerProvider.RunScheduled();
var secondRun = timerProvider.RunScheduled();
Assert.IsTrue(firstRun);
Assert.IsTrue(secondRun);
}
[Test] [Test]
//This test will confirm that the concurrency checks are rest //This test will confirm that the concurrency checks are rest
//after execution so the job can successfully run. //after execution so the job can successfully run.
@ -73,6 +94,28 @@ public void can_run_async_job_again()
} }
[Test]
//This test will confirm that the concurrency checks are rest
//after execution so the job can successfully run.
public void can_run_broken_async_job_again()
{
IEnumerable<IJob> fakeTimers = new List<IJob> { new BrokenJob() };
var mocker = new AutoMoqer();
mocker.SetConstant(MockLib.GetEmptyRepository());
mocker.SetConstant(fakeTimers);
var timerProvider = mocker.Resolve<JobProvider>();
timerProvider.Initialize();
var firstRun = timerProvider.BeginExecute(typeof(FakeJob));
Thread.Sleep(2000);
var secondRun = timerProvider.BeginExecute(typeof(FakeJob));
Assert.IsTrue(firstRun);
Assert.IsTrue(secondRun);
}
[Test] [Test]
//This test will confirm that the concurrency checks are rest //This test will confirm that the concurrency checks are rest
//after execution so the job can successfully run. //after execution so the job can successfully run.
@ -183,6 +226,24 @@ public void Start(ProgressNotification notification, int targetId)
} }
} }
public class BrokenJob : IJob
{
public string Name
{
get { return "FakeJob"; }
}
public int DefaultInterval
{
get { return 15; }
}
public void Start(ProgressNotification notification, int targetId)
{
throw new InvalidOperationException();
}
}
public class SlowJob : IJob public class SlowJob : IJob
{ {
public string Name public string Name
@ -197,7 +258,7 @@ public int DefaultInterval
public void Start(ProgressNotification notification, int targetId) public void Start(ProgressNotification notification, int targetId)
{ {
Thread.Sleep(10000); Thread.Sleep(5000);
} }
} }
} }

View File

@ -13,6 +13,8 @@ public class EpisodeParseResult
internal List<int> Episodes { get; set; } internal List<int> Episodes { get; set; }
internal int Year { get; set; } internal int Year { get; set; }
internal string EpisodeTitle { get; set; }
internal DateTime AirDate { get; set; } internal DateTime AirDate { get; set; }
public bool Proper { get; set; } public bool Proper { get; set; }

View File

@ -139,7 +139,6 @@
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>Libraries\NLog.Extended.dll</HintPath> <HintPath>Libraries\NLog.Extended.dll</HintPath>
</Reference> </Reference>
<Reference Include="NzbDrone.Core, Version=0.2.0.35870, Culture=neutral, processorArchitecture=MSIL" />
<Reference Include="SubSonic.Core, Version=3.0.0.3, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="SubSonic.Core, Version=3.0.0.3, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>Libraries\SubSonic.Core.dll</HintPath> <HintPath>Libraries\SubSonic.Core.dll</HintPath>

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Core.Model; using NzbDrone.Core.Model;
@ -58,13 +59,29 @@ public virtual IList<Episode> GetEpisodeBySeason(long seasonId)
return _sonicRepo.Find<Episode>(e => e.SeasonId == seasonId); return _sonicRepo.Find<Episode>(e => e.SeasonId == seasonId);
} }
public virtual String GetSabTitle(Episode episode) public virtual String GetSabTitle(EpisodeParseResult parseResult)
{ {
var series = _series.GetSeries(episode.SeriesId); //Show Name - 1x01-1x02 - Episode Name
if (series == null) throw new ArgumentException("Unknown series. ID: " + episode.SeriesId); //Show Name - 1x01 - Episode Name
var episodeString = new List<String>();
//TODO: This method should return a standard title for the sab episode. foreach (var episode in parseResult.Episodes)
throw new NotImplementedException(); {
episodeString.Add(String.Format("{0}x{1}", parseResult.SeasonNumber, episode));
}
var epNumberString = String.Join("-", episodeString);
var series = _series.GetSeries(parseResult.SeriesId);
var folderName = new DirectoryInfo(series.Path).Name;
var result = String.Format("{0} - {1} - {2} {3}", folderName, epNumberString, parseResult.EpisodeTitle, parseResult.Quality);
if (parseResult.Proper)
{
result += " [Proper]";
}
return result;
} }
/// <summary> /// <summary>
@ -80,10 +97,23 @@ public virtual bool IsNeeded(EpisodeParseResult parsedReport)
if (episodeInfo == null) if (episodeInfo == null)
{ {
//Todo: How do we want to handle this really? Episode could be released before information is on TheTvDB //Todo: How do we want to handle this really? Episode could be released before information is on TheTvDB
//(Parks and Rec did this a lot in the first season, from experience) //(Parks and Rec did this a lot in the first season, from experience)
//Keivan: Should automatically add the episode to db with minimal information. then update the description/title when avilable. //Keivan: Should automatically add the episode to db with minimal information. then update the description/title when available.
throw new NotImplementedException("Episode was not found in the database"); episodeInfo = new Episode()
{
SeriesId = parsedReport.SeriesId,
AirDate = DateTime.Now.Date,
EpisodeNumber = episode,
SeasonNumber = parsedReport.SeasonNumber,
Title = String.Empty,
Overview = String.Empty,
Language = "en"
};
_sonicRepo.Add(episodeInfo);
} }
var file = episodeInfo.EpisodeFile; var file = episodeInfo.EpisodeFile;
@ -221,6 +251,8 @@ public virtual void RefreshEpisodeInfo(Season season)
Title = episode.EpisodeName Title = episode.EpisodeName
}; };
//TODO: Replace this db check with a local check. Should make things even faster
if (_sonicRepo.Exists<Episode>(e => e.EpisodeId == newEpisode.EpisodeId)) if (_sonicRepo.Exists<Episode>(e => e.EpisodeId == newEpisode.EpisodeId))
{ {
updateList.Add(newEpisode); updateList.Add(newEpisode);

View File

@ -1,4 +1,4 @@
using System.ServiceModel.Syndication; using System.ServiceModel.Syndication;
using NLog; using NLog;
using NzbDrone.Core.Model; using NzbDrone.Core.Model;
using NzbDrone.Core.Providers.Core; using NzbDrone.Core.Providers.Core;
@ -32,57 +32,23 @@ public IndexerProviderBase(SeriesProvider seriesProvider, SeasonProvider seasonP
_indexerProvider = indexerProvider; _indexerProvider = indexerProvider;
} }
/// <summary>
/// Gets the source URL for the feed
/// </summary>
protected abstract string[] Url { get; }
/// <summary> /// <summary>
/// Gets the name for the feed /// Gets the name for the feed
/// </summary> /// </summary>
public abstract string Name { get; } public abstract string Name { get; }
/// <summary> /// <summary>
/// Generates direct link to download an NZB /// Gets the source URL for the feed
/// </summary> /// </summary>
/// <param name = "item">RSS Feed item to generate the link for</param> protected abstract string[] Urls { get; }
/// <returns>Download link URL</returns>
protected abstract string NzbDownloadUrl(SyndicationItem item);
/// <summary>
/// Parses the RSS feed item and. protected IndexerSetting Settings
/// </summary>
/// <param name = "item">RSS feed item to parse</param>
/// <returns>Detailed episode info</returns>
protected EpisodeParseResult ParseFeed(SyndicationItem item)
{ {
var episodeParseResult = Parser.ParseEpisodeInfo(item.Title.Text); get
if (episodeParseResult == null) return null;
episodeParseResult = CustomParser(item, episodeParseResult);
var seriesInfo = _seriesProvider.FindSeries(episodeParseResult.SeriesTitle);
if (seriesInfo != null)
{ {
episodeParseResult.SeriesId = seriesInfo.SeriesId; return _indexerProvider.GetSettings(GetType());
episodeParseResult.SeriesTitle = seriesInfo.Title;
return episodeParseResult;
} }
Logger.Debug("Unable to map {0} to any of series in database", episodeParseResult.SeriesTitle);
return null;
}
/// <summary>
/// This method can be overwritten to provide indexer specific info parsing
/// </summary>
/// <param name="item">RSS item that needs to be parsed</param>
/// <param name="currentResult">Result of the built in parse function.</param>
/// <returns></returns>
protected virtual EpisodeParseResult CustomParser(SyndicationItem item, EpisodeParseResult currentResult)
{
return currentResult;
} }
/// <summary> /// <summary>
@ -92,7 +58,7 @@ public void Fetch()
{ {
Logger.Info("Fetching feeds from " + Settings.Name); Logger.Info("Fetching feeds from " + Settings.Name);
foreach (var url in Url) foreach (var url in Urls)
{ {
Logger.Debug("Downloading RSS " + url); Logger.Debug("Downloading RSS " + url);
var feed = SyndicationFeed.Load(_httpProvider.DownloadXml(url)).Items; var feed = SyndicationFeed.Load(_httpProvider.DownloadXml(url)).Items;
@ -142,16 +108,45 @@ private void ProcessItem(SyndicationItem feedItem)
} }
} }
protected IndexerSetting Settings /// <summary>
/// Parses the RSS feed item and.
/// </summary>
/// <param name = "item">RSS feed item to parse</param>
/// <returns>Detailed episode info</returns>
protected EpisodeParseResult ParseFeed(SyndicationItem item)
{ {
get var episodeParseResult = Parser.ParseEpisodeInfo(item.Title.Text);
if (episodeParseResult == null) return CustomParser(item, null);
var seriesInfo = _seriesProvider.FindSeries(episodeParseResult.SeriesTitle);
if (seriesInfo != null)
{ {
return _indexerProvider.GetSettings(GetType()); episodeParseResult.SeriesId = seriesInfo.SeriesId;
} episodeParseResult.SeriesTitle = seriesInfo.Title;
return CustomParser(item, episodeParseResult);
} }
Logger.Debug("Unable to map {0} to any of series in database", episodeParseResult.SeriesTitle);
return CustomParser(item, episodeParseResult);
}
/// <summary>
/// This method can be overwritten to provide indexer specific info parsing
/// </summary>
/// <param name="item">RSS item that needs to be parsed</param>
/// <param name="currentResult">Result of the built in parse function.</param>
/// <returns></returns>
protected virtual EpisodeParseResult CustomParser(SyndicationItem item, EpisodeParseResult currentResult)
{
return currentResult;
}
/// <summary>
/// Generates direct link to download an NZB
/// </summary>
/// <param name = "item">RSS Feed item to generate the link for</param>
/// <returns>Download link URL</returns>
protected abstract string NzbDownloadUrl(SyndicationItem item);
} }
} }

View File

@ -12,7 +12,7 @@ public NewzbinProvider(SeriesProvider seriesProvider, SeasonProvider seasonProvi
{ {
} }
protected override string[] Url protected override string[] Urls
{ {
get get
{ {

View File

@ -11,7 +11,7 @@ public NzbMatrixProvider(SeriesProvider seriesProvider, SeasonProvider seasonPro
{ {
} }
protected override string[] Url protected override string[] Urls
{ {
get get
{ {

View File

@ -11,7 +11,7 @@ public NzbsOrgProvider(SeriesProvider seriesProvider, SeasonProvider seasonProvi
{ {
} }
protected override string[] Url protected override string[] Urls
{ {
get get
{ {

View File

@ -11,7 +11,7 @@ public NzbsRUsProvider(SeriesProvider seriesProvider, SeasonProvider seasonProvi
{ {
} }
protected override string[] Url protected override string[] Urls
{ {
get get
{ {

View File

@ -127,7 +127,18 @@ public bool BeginExecute(Type jobType, int targetId = 0)
{ {
Logger.Debug("Initializing background thread"); Logger.Debug("Initializing background thread");
ThreadStart starter = () => Execute(jobType, targetId); ThreadStart starter = () =>
{
try
{
Execute(jobType, targetId);
}
finally
{
_isRunning = false;
}
};
_jobThread = new Thread(starter) { Name = "TimerThread", Priority = ThreadPriority.BelowNormal }; _jobThread = new Thread(starter) { Name = "TimerThread", Priority = ThreadPriority.BelowNormal };
_jobThread.Start(); _jobThread.Start();
@ -169,14 +180,7 @@ private void Execute(Type jobType, int targetId = 0)
} }
catch (Exception e) catch (Exception e)
{ {
Logger.ErrorException("An error has occurred while executing timer job" + timerClass.Name, e); Logger.ErrorException("An error has occurred while executing timer job " + timerClass.Name, e);
}
finally
{
if (_jobThread == Thread.CurrentThread)
{
_isRunning = false;
}
} }
} }
@ -194,14 +198,13 @@ public virtual void Initialize()
var timerProviderLocal = timer; var timerProviderLocal = timer;
if (!currentTimer.Exists(c => c.TypeName == timerProviderLocal.GetType().ToString())) if (!currentTimer.Exists(c => c.TypeName == timerProviderLocal.GetType().ToString()))
{ {
var settings = new JobSetting() var settings = new JobSetting
{ {
Enable = true, Enable = true,
TypeName = timer.GetType().ToString(), TypeName = timer.GetType().ToString(),
Name = timerProviderLocal.Name, Name = timerProviderLocal.Name,
Interval = timerProviderLocal.DefaultInterval, Interval = timerProviderLocal.DefaultInterval,
LastExecution = DateTime.MinValue LastExecution = DateTime.MinValue
}; };
SaveSettings(settings); SaveSettings(settings);

View File

@ -32,10 +32,10 @@ public class Episode
public virtual Season Season { get; set; } public virtual Season Season { get; set; }
[SubSonicToOneRelation(ThisClassContainsJoinKey = true)] [SubSonicToOneRelation(ThisClassContainsJoinKey = true)]
public virtual Series Series { get; private set; } public virtual Series Series { get; set; }
[SubSonicToOneRelation(ThisClassContainsJoinKey = true)] [SubSonicToOneRelation(ThisClassContainsJoinKey = true)]
public virtual EpisodeFile EpisodeFile { get; private set; } public virtual EpisodeFile EpisodeFile { get; set; }
[SubSonicToManyRelation] [SubSonicToManyRelation]
public virtual List<History> Histories { get; private set; } public virtual List<History> Histories { get; private set; }

View File

@ -36,7 +36,7 @@
.Columns(columns => .Columns(columns =>
{ {
columns.Bound(c => c.Time).Title("Time").Width(190); columns.Bound(c => c.Time).Title("Time").Width(190);
columns.Bound(c => c.DisplayLevel).Title("Level").Width(0); columns.Bound(c => c.Level).Title("Level").Width(0);
columns.Bound(c => c.Message); columns.Bound(c => c.Message);
}) })
.DetailView(detailView => detailView.ClientTemplate( .DetailView(detailView => detailView.ClientTemplate(

View File

@ -9,18 +9,22 @@
<parameter name="stacktrace" layout="${stacktrace:topFrames=99}" xsi:type="NLogViewerParameterInfo" /> <parameter name="stacktrace" layout="${stacktrace:topFrames=99}" xsi:type="NLogViewerParameterInfo" />
<parameter name="ThreadName" layout="${threadname}" xsi:type="NLogViewerParameterInfo" /> <parameter name="ThreadName" layout="${threadname}" xsi:type="NLogViewerParameterInfo" />
</target> </target>
<target name="file" xsi:type="File"
layout="${longdate} [${level}] ${logger}: ${message} ${exception:ToString}"
fileName="${basedir}/App_Data/logs/${shortdate}.txt" />
</targets> </targets>
<rules> <rules>
<logger name="IIS*" minlevel="Trace" writeTo="consoleTarget"/> <logger name="IIS*" minlevel="Trace" writeTo="consoleTarget"/>
<logger name="Application" minlevel="Trace" writeTo="consoleTarget"/> <logger name="Application" minlevel="Trace" writeTo="consoleTarget"/>
<logger name="*" minlevel="Trace" writeTo="udpTarget"/> <logger name="*" minlevel="Trace" writeTo="udpTarget"/>
<!--<logger name="*" minlevel="Off" writeTo="debugTarget"/>--> <logger name="*" minlevel="Trace" writeTo="file">
<filters>
<when condition="logger == 'NzbDrone.SubSonic'" action="Ignore" />
</filters>
</logger>
<logger name="*" minlevel="Off" writeTo="debugTarget"/>
</rules> </rules>
</nlog> </nlog>