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

pull/6/head
Mark McDowall 14 years ago
commit a977443676

@ -1,10 +1,13 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq.Expressions;
using AutoMoq;
using FizzWare.NBuilder;
using MbUnit.Framework;
using Moq;
using NzbDrone.Core.Model;
using NzbDrone.Core.Providers;
using NzbDrone.Core.Repository;
using NzbDrone.Core.Repository.Quality;
@ -54,42 +57,142 @@ namespace NzbDrone.Core.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
var season = new Mock<SeasonProvider>();
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,
EpisodeId = 55555,
EpisodeNumber = 5,
Language = "en",
SeasonId = 4444,
SeasonNumber = 1
};
season.Setup(s => s.IsIgnored(12345, 1)).Returns(false);
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)).
Returns(epInDb);
//repo.Setup(s => s.All<EpisodeFile>()).Returns();
//repo.All<EpisodeFile>().Where(c => c.EpisodeId == episode.EpisodeId);
var parseResult = new EpisodeParseResult
{
SeriesId = 12,
SeasonNumber = 2,
Episodes = new List<int> { 3 },
Quality = reportQuality,
Proper = isReportProper
};
var epFile = new EpisodeFile()
{
Proper = isFileProper,
Quality = fileQuality
};
var episodeInfo = new Episode
{
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",
};
//Act
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
var actual = mocker.Resolve<EpisodeProvider>().GetSabTitle(parsResult);
//Assert
Assert.AreEqual(excpected, actual);
}
[Test]
[Explicit]
public void Add_daily_show_episodes()
{
var mocker = new AutoMoqer();

@ -62,7 +62,7 @@ namespace NzbDrone.Core.Test
{
}
protected override string[] Url
protected override string[] Urls
{
get { return new[] { "www.google.com" }; }
}

@ -50,6 +50,27 @@ namespace NzbDrone.Core.Test
}
[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]
//This test will confirm that the concurrency checks are rest
//after execution so the job can successfully run.
@ -73,6 +94,28 @@ namespace NzbDrone.Core.Test
}
[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]
//This test will confirm that the concurrency checks are rest
//after execution so the job can successfully run.
@ -183,6 +226,24 @@ namespace NzbDrone.Core.Test
}
}
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 string Name
@ -197,7 +258,7 @@ namespace NzbDrone.Core.Test
public void Start(ProgressNotification notification, int targetId)
{
Thread.Sleep(10000);
Thread.Sleep(5000);
}
}
}

@ -13,6 +13,8 @@ namespace NzbDrone.Core.Model
internal List<int> Episodes { get; set; }
internal int Year { get; set; }
internal string EpisodeTitle { get; set; }
internal DateTime AirDate { get; set; }
public bool Proper { get; set; }

@ -139,7 +139,6 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>Libraries\NLog.Extended.dll</HintPath>
</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">
<SpecificVersion>False</SpecificVersion>
<HintPath>Libraries\SubSonic.Core.dll</HintPath>

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Core.Model;
@ -58,13 +59,29 @@ namespace NzbDrone.Core.Providers
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);
if (series == null) throw new ArgumentException("Unknown series. ID: " + episode.SeriesId);
//Show Name - 1x01-1x02 - Episode Name
//Show Name - 1x01 - Episode Name
var episodeString = new List<String>();
//TODO: This method should return a standard title for the sab episode.
throw new NotImplementedException();
foreach (var episode in parseResult.Episodes)
{
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>
@ -80,10 +97,23 @@ namespace NzbDrone.Core.Providers
if (episodeInfo == null)
{
//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)
//Keivan: Should automatically add the episode to db with minimal information. then update the description/title when avilable.
throw new NotImplementedException("Episode was not found in the database");
//Keivan: Should automatically add the episode to db with minimal information. then update the description/title when available.
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;
@ -221,6 +251,8 @@ namespace NzbDrone.Core.Providers
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))
{
updateList.Add(newEpisode);

@ -1,4 +1,4 @@
using System.ServiceModel.Syndication;
using System.ServiceModel.Syndication;
using NLog;
using NzbDrone.Core.Model;
using NzbDrone.Core.Providers.Core;
@ -32,57 +32,23 @@ namespace NzbDrone.Core.Providers.Indexer
_indexerProvider = indexerProvider;
}
/// <summary>
/// Gets the source URL for the feed
/// </summary>
protected abstract string[] Url { get; }
/// <summary>
/// Gets the name for the feed
/// </summary>
public abstract string Name { get; }
/// <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);
/// <summary>
/// Parses the RSS feed item and.
/// Gets the source URL for the feed
/// </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);
if (episodeParseResult == null) return null;
episodeParseResult = CustomParser(item, episodeParseResult);
protected abstract string[] Urls { get; }
var seriesInfo = _seriesProvider.FindSeries(episodeParseResult.SeriesTitle);
if (seriesInfo != null)
protected IndexerSetting Settings
{
get
{
episodeParseResult.SeriesId = seriesInfo.SeriesId;
episodeParseResult.SeriesTitle = seriesInfo.Title;
return episodeParseResult;
return _indexerProvider.GetSettings(GetType());
}
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>
@ -92,7 +58,7 @@ namespace NzbDrone.Core.Providers.Indexer
{
Logger.Info("Fetching feeds from " + Settings.Name);
foreach (var url in Url)
foreach (var url in Urls)
{
Logger.Debug("Downloading RSS " + url);
var feed = SyndicationFeed.Load(_httpProvider.DownloadXml(url)).Items;
@ -142,16 +108,45 @@ namespace NzbDrone.Core.Providers.Indexer
}
}
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);
}
}

@ -12,7 +12,7 @@ namespace NzbDrone.Core.Providers.Indexer
{
}
protected override string[] Url
protected override string[] Urls
{
get
{

@ -11,7 +11,7 @@ namespace NzbDrone.Core.Providers.Indexer
{
}
protected override string[] Url
protected override string[] Urls
{
get
{

@ -11,7 +11,7 @@ namespace NzbDrone.Core.Providers.Indexer
{
}
protected override string[] Url
protected override string[] Urls
{
get
{

@ -11,7 +11,7 @@ namespace NzbDrone.Core.Providers.Indexer
{
}
protected override string[] Url
protected override string[] Urls
{
get
{

@ -127,7 +127,18 @@ namespace NzbDrone.Core.Providers.Jobs
{
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.Start();
@ -169,14 +180,7 @@ namespace NzbDrone.Core.Providers.Jobs
}
catch (Exception e)
{
Logger.ErrorException("An error has occurred while executing timer job" + timerClass.Name, e);
}
finally
{
if (_jobThread == Thread.CurrentThread)
{
_isRunning = false;
}
Logger.ErrorException("An error has occurred while executing timer job " + timerClass.Name, e);
}
}
@ -194,14 +198,13 @@ namespace NzbDrone.Core.Providers.Jobs
var timerProviderLocal = timer;
if (!currentTimer.Exists(c => c.TypeName == timerProviderLocal.GetType().ToString()))
{
var settings = new JobSetting()
var settings = new JobSetting
{
Enable = true,
TypeName = timer.GetType().ToString(),
Name = timerProviderLocal.Name,
Interval = timerProviderLocal.DefaultInterval,
LastExecution = DateTime.MinValue
};
SaveSettings(settings);

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

@ -36,7 +36,7 @@
.Columns(columns =>
{
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);
})
.DetailView(detailView => detailView.ClientTemplate(

@ -9,18 +9,22 @@
<parameter name="stacktrace" layout="${stacktrace:topFrames=99}" xsi:type="NLogViewerParameterInfo" />
<parameter name="ThreadName" layout="${threadname}" xsi:type="NLogViewerParameterInfo" />
</target>
</targets>
<target name="file" xsi:type="File"
layout="${longdate} [${level}] ${logger}: ${message} ${exception:ToString}"
fileName="${basedir}/App_Data/logs/${shortdate}.txt" />
</targets>
<rules>
<logger name="IIS*" minlevel="Trace" writeTo="consoleTarget"/>
<logger name="Application" minlevel="Trace" writeTo="consoleTarget"/>
<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>
</nlog>
Loading…
Cancel
Save