From 10ad7d1ed550dbe9d307df7d2e7cc782e877675a Mon Sep 17 00:00:00 2001 From: "kay.one" Date: Wed, 20 Apr 2011 18:26:13 -0700 Subject: [PATCH] Added sabtitle method Added custom parse option to Indexrbase --- NzbDrone.Core.Test/EpisodeProviderTest.cs | 147 +++++++++++++++--- NzbDrone.Core.Test/IndexerProviderTest.cs | 2 +- NzbDrone.Core.Test/JobProviderTest.cs | 63 +++++++- NzbDrone.Core/Model/EpisodeParseResult.cs | 2 + NzbDrone.Core/NzbDrone.Core.csproj | 1 - NzbDrone.Core/Providers/EpisodeProvider.cs | 46 +++++- .../Providers/Indexer/IndexerProviderBase.cs | 75 ++++----- .../Providers/Indexer/NewzbinProvider.cs | 2 +- .../Providers/Indexer/NzbMatrixProvider.cs | 2 +- .../Providers/Indexer/NzbsOrgProvider.cs | 2 +- .../Providers/Indexer/NzbsRUsProvider.cs | 2 +- NzbDrone.Core/Providers/Jobs/JobProvider.cs | 25 +-- NzbDrone.Core/Repository/Episode.cs | 4 +- 13 files changed, 289 insertions(+), 84 deletions(-) diff --git a/NzbDrone.Core.Test/EpisodeProviderTest.cs b/NzbDrone.Core.Test/EpisodeProviderTest.cs index 4a340f805..d1e14ded1 100644 --- a/NzbDrone.Core.Test/EpisodeProviderTest.cs +++ b/NzbDrone.Core.Test/EpisodeProviderTest.cs @@ -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 @@ public void RefreshEpisodeInfo() } [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(); - var series = new Mock(); - //var history = new Mock(); - //var quality = new Mock(); - var repo = new Mock(); + var parseResult = new EpisodeParseResult + { + SeriesId = 12, + SeasonNumber = 2, + Episodes = new List { 3 }, + Quality = reportQuality, + Proper = isReportProper + }; - var epInDb = new Episode - { - AirDate = DateTime.Today, - EpisodeId = 55555, - EpisodeNumber = 5, - Language = "en", - SeasonId = 4444, - SeasonNumber = 1 - }; + var epFile = new EpisodeFile() + { + Proper = isFileProper, + Quality = fileQuality + }; - season.Setup(s => s.IsIgnored(12345, 1)).Returns(false); - series.Setup(s => s.QualityWanted(12345, QualityTypes.TV)).Returns(true); - repo.Setup(s => s.Single(c => c.SeriesId == 12345 && c.SeasonNumber == 1 && c.EpisodeNumber == 5)). - Returns(epInDb); + var episodeInfo = new Episode + { + SeriesId = 12, + SeasonNumber = 2, + EpisodeNumber = 3, + Series = new Series() { QualityProfileId = 1 }, + EpisodeFile = epFile - //repo.Setup(s => s.All()).Returns(); - //repo.All().Where(c => c.EpisodeId == episode.EpisodeId); + }; + var seriesQualityProfile = new QualityProfile() + { + Allowed = new List { QualityTypes.TV, QualityTypes.DVD, QualityTypes.Bluray720, QualityTypes.Bluray1080 }, + Cutoff = QualityTypes.Bluray720, + Name = "TV/DVD", + }; + + + + var mocker = new AutoMoqer(); + mocker.GetMock() + .Setup(r => r.Single(It.IsAny>>())) + .Returns(episodeInfo); + + mocker.GetMock() + .Setup(q => q.Find(1)) + .Returns(seriesQualityProfile); + + var result = mocker.Resolve().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 { 3 }, + Quality = QualityTypes.DVD + + }; + + var parseResult2 = new EpisodeParseResult + { + SeriesId = 12, + SeasonNumber = 3, + Episodes = new List { 3 }, + Quality = QualityTypes.DVD + + }; + + var mocker = new AutoMoqer(); + mocker.SetConstant(MockLib.GetEmptyRepository()); + + var episodeProvider = mocker.Resolve(); + 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(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().GetSabTitle(parsResult); //Assert + Assert.AreEqual(excpected, actual); } [Test] + [Explicit] public void Add_daily_show_episodes() { var mocker = new AutoMoqer(); diff --git a/NzbDrone.Core.Test/IndexerProviderTest.cs b/NzbDrone.Core.Test/IndexerProviderTest.cs index f1f5dd241..4f2723944 100644 --- a/NzbDrone.Core.Test/IndexerProviderTest.cs +++ b/NzbDrone.Core.Test/IndexerProviderTest.cs @@ -62,7 +62,7 @@ public MockIndexerProvider(SeriesProvider seriesProvider, SeasonProvider seasonP { } - protected override string[] Url + protected override string[] Urls { get { return new[] { "www.google.com" }; } } diff --git a/NzbDrone.Core.Test/JobProviderTest.cs b/NzbDrone.Core.Test/JobProviderTest.cs index aa8b86ea3..f1da4e25e 100644 --- a/NzbDrone.Core.Test/JobProviderTest.cs +++ b/NzbDrone.Core.Test/JobProviderTest.cs @@ -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 fakeTimers = new List { new BrokenJob() }; + var mocker = new AutoMoqer(); + + mocker.SetConstant(MockLib.GetEmptyRepository()); + mocker.SetConstant(fakeTimers); + + var timerProvider = mocker.Resolve(); + 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 @@ 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 fakeTimers = new List { new BrokenJob() }; + var mocker = new AutoMoqer(); + + mocker.SetConstant(MockLib.GetEmptyRepository()); + mocker.SetConstant(fakeTimers); + + var timerProvider = mocker.Resolve(); + 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 @@ 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 string Name @@ -197,7 +258,7 @@ public int DefaultInterval public void Start(ProgressNotification notification, int targetId) { - Thread.Sleep(10000); + Thread.Sleep(5000); } } } \ No newline at end of file diff --git a/NzbDrone.Core/Model/EpisodeParseResult.cs b/NzbDrone.Core/Model/EpisodeParseResult.cs index cf74a7de4..990f87315 100644 --- a/NzbDrone.Core/Model/EpisodeParseResult.cs +++ b/NzbDrone.Core/Model/EpisodeParseResult.cs @@ -13,6 +13,8 @@ public class EpisodeParseResult internal List Episodes { get; set; } internal int Year { get; set; } + internal string EpisodeTitle { get; set; } + internal DateTime AirDate { get; set; } public bool Proper { get; set; } diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 104a8cf1c..ea1498bf1 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -139,7 +139,6 @@ False Libraries\NLog.Extended.dll - False Libraries\SubSonic.Core.dll diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index df39da028..88c32bb96 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -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 @@ public virtual IList GetEpisodeBySeason(long seasonId) return _sonicRepo.Find(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(); - //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; } /// @@ -80,10 +97,23 @@ public virtual bool IsNeeded(EpisodeParseResult parsedReport) 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 @@ public virtual void RefreshEpisodeInfo(Season season) Title = episode.EpisodeName }; + + //TODO: Replace this db check with a local check. Should make things even faster if (_sonicRepo.Exists(e => e.EpisodeId == newEpisode.EpisodeId)) { updateList.Add(newEpisode); diff --git a/NzbDrone.Core/Providers/Indexer/IndexerProviderBase.cs b/NzbDrone.Core/Providers/Indexer/IndexerProviderBase.cs index 93ef5b2b4..82353a54c 100644 --- a/NzbDrone.Core/Providers/Indexer/IndexerProviderBase.cs +++ b/NzbDrone.Core/Providers/Indexer/IndexerProviderBase.cs @@ -32,49 +32,25 @@ public IndexerProviderBase(SeriesProvider seriesProvider, SeasonProvider seasonP _indexerProvider = indexerProvider; } - /// - /// Gets the source URL for the feed - /// - protected abstract string[] Url { get; } - /// /// Gets the name for the feed /// public abstract string Name { get; } - /// - /// Generates direct link to download an NZB + /// Gets the source URL for the feed /// - /// RSS Feed item to generate the link for - /// Download link URL - protected abstract string NzbDownloadUrl(SyndicationItem item); + protected abstract string[] Urls { get; } - /// - /// Parses the RSS feed item and. - /// - /// RSS feed item to parse - /// Detailed episode info - protected EpisodeParseResult ParseFeed(SyndicationItem item) + protected IndexerSetting Settings { - var episodeParseResult = Parser.ParseEpisodeInfo(item.Title.Text); - if (episodeParseResult == null) return null; - - var seriesInfo = _seriesProvider.FindSeries(episodeParseResult.SeriesTitle); - - if (seriesInfo != null) + 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; } - /// /// Fetches RSS feed and process each news item. /// @@ -82,7 +58,7 @@ public void Fetch() { 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; @@ -132,16 +108,45 @@ private void ProcessItem(SyndicationItem feedItem) } } - protected IndexerSetting Settings + /// + /// Parses the RSS feed item and. + /// + /// RSS feed item to parse + /// Detailed episode info + 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); } + /// + /// This method can be overwritten to provide indexer specific info parsing + /// + /// RSS item that needs to be parsed + /// Result of the built in parse function. + /// + protected virtual EpisodeParseResult CustomParser(SyndicationItem item, EpisodeParseResult currentResult) + { + return currentResult; + } - - + /// + /// Generates direct link to download an NZB + /// + /// RSS Feed item to generate the link for + /// Download link URL + protected abstract string NzbDownloadUrl(SyndicationItem item); } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Indexer/NewzbinProvider.cs b/NzbDrone.Core/Providers/Indexer/NewzbinProvider.cs index 87c71cdd6..30fa37a3e 100644 --- a/NzbDrone.Core/Providers/Indexer/NewzbinProvider.cs +++ b/NzbDrone.Core/Providers/Indexer/NewzbinProvider.cs @@ -11,7 +11,7 @@ public NewzbinProvider(SeriesProvider seriesProvider, SeasonProvider seasonProvi { } - protected override string[] Url + protected override string[] Urls { get { diff --git a/NzbDrone.Core/Providers/Indexer/NzbMatrixProvider.cs b/NzbDrone.Core/Providers/Indexer/NzbMatrixProvider.cs index 09d6e9050..c158a982d 100644 --- a/NzbDrone.Core/Providers/Indexer/NzbMatrixProvider.cs +++ b/NzbDrone.Core/Providers/Indexer/NzbMatrixProvider.cs @@ -11,7 +11,7 @@ public NzbMatrixProvider(SeriesProvider seriesProvider, SeasonProvider seasonPro { } - protected override string[] Url + protected override string[] Urls { get { diff --git a/NzbDrone.Core/Providers/Indexer/NzbsOrgProvider.cs b/NzbDrone.Core/Providers/Indexer/NzbsOrgProvider.cs index 46468cff2..e8aa37bc8 100644 --- a/NzbDrone.Core/Providers/Indexer/NzbsOrgProvider.cs +++ b/NzbDrone.Core/Providers/Indexer/NzbsOrgProvider.cs @@ -11,7 +11,7 @@ public NzbsOrgProvider(SeriesProvider seriesProvider, SeasonProvider seasonProvi { } - protected override string[] Url + protected override string[] Urls { get { diff --git a/NzbDrone.Core/Providers/Indexer/NzbsRUsProvider.cs b/NzbDrone.Core/Providers/Indexer/NzbsRUsProvider.cs index d75721147..29eef883e 100644 --- a/NzbDrone.Core/Providers/Indexer/NzbsRUsProvider.cs +++ b/NzbDrone.Core/Providers/Indexer/NzbsRUsProvider.cs @@ -11,7 +11,7 @@ public NzbsRUsProvider(SeriesProvider seriesProvider, SeasonProvider seasonProvi { } - protected override string[] Url + protected override string[] Urls { get { diff --git a/NzbDrone.Core/Providers/Jobs/JobProvider.cs b/NzbDrone.Core/Providers/Jobs/JobProvider.cs index df109d372..facba16d2 100644 --- a/NzbDrone.Core/Providers/Jobs/JobProvider.cs +++ b/NzbDrone.Core/Providers/Jobs/JobProvider.cs @@ -127,7 +127,18 @@ public bool BeginExecute(Type jobType, int targetId = 0) { 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 @@ private void Execute(Type jobType, int targetId = 0) } 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 @@ public virtual void Initialize() 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); diff --git a/NzbDrone.Core/Repository/Episode.cs b/NzbDrone.Core/Repository/Episode.cs index 517cf5f02..e0992d5e4 100644 --- a/NzbDrone.Core/Repository/Episode.cs +++ b/NzbDrone.Core/Repository/Episode.cs @@ -32,10 +32,10 @@ public class Episode 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 Histories { get; private set; }