From f6c9fa4f95ec5e2f95940bd42b39ea9b1c05f70d Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 22 Aug 2011 22:29:12 -0700 Subject: [PATCH] Added SeriesSearch and RenameSeries jobs. Add UI controls for new jobs. Skip ignored episodes when doing series/season searches. --- NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 1 + NzbDrone.Core.Test/SeasonSearchJobTest.cs | 33 ++++++ NzbDrone.Core.Test/SeriesSearchJobTest.cs | 103 ++++++++++++++++++ NzbDrone.Core/CentralDispatch.cs | 2 + NzbDrone.Core/NzbDrone.Core.csproj | 2 + .../Providers/Jobs/RenameSeasonJob.cs | 2 +- .../Providers/Jobs/RenameSeriesJob.cs | 56 ++++++++++ .../Providers/Jobs/SeasonSearchJob.cs | 4 +- .../Providers/Jobs/SeriesSearchJob.cs | 56 ++++++++++ NzbDrone.Web/Controllers/CommandController.cs | 8 -- NzbDrone.Web/Controllers/EpisodeController.cs | 16 +++ NzbDrone.Web/Views/Series/Details.cshtml | 5 +- 12 files changed, 275 insertions(+), 13 deletions(-) create mode 100644 NzbDrone.Core.Test/SeriesSearchJobTest.cs create mode 100644 NzbDrone.Core/Providers/Jobs/RenameSeriesJob.cs create mode 100644 NzbDrone.Core/Providers/Jobs/SeriesSearchJob.cs diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index bd1d1b24c..4ea3d9c4e 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -88,6 +88,7 @@ + diff --git a/NzbDrone.Core.Test/SeasonSearchJobTest.cs b/NzbDrone.Core.Test/SeasonSearchJobTest.cs index 499b645b6..76e51255b 100644 --- a/NzbDrone.Core.Test/SeasonSearchJobTest.cs +++ b/NzbDrone.Core.Test/SeasonSearchJobTest.cs @@ -28,6 +28,7 @@ namespace NzbDrone.Core.Test .WhereAll() .Have(e => e.SeriesId = 1) .Have(e => e.SeasonNumber = 1) + .Have(e => e.Ignored = false) .Build(); var mocker = new AutoMoqer(MockBehavior.Strict); @@ -68,5 +69,37 @@ namespace NzbDrone.Core.Test Times.Never()); ExceptionVerification.ExcpectedWarns(1); } + + [Test] + public void SeasonSearch_skip_ignored() + { + var episodes = Builder.CreateListOfSize(10) + .WhereAll() + .Have(e => e.SeriesId = 1) + .Have(e => e.SeasonNumber = 1) + .WhereTheFirst(5) + .Have(e => e.Ignored = false) + .AndTheRemaining() + .Have(e => e.Ignored = true) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + var notification = new ProgressNotification("Season Search"); + + mocker.GetMock() + .Setup(c => c.GetEpisodesBySeason(1, 1)).Returns(episodes); + + mocker.GetMock() + .Setup(c => c.Start(notification, It.IsAny(), 0)).Verifiable(); + + //Act + mocker.Resolve().Start(notification, 1, 1); + + //Assert + mocker.VerifyAllMocks(); + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), + Times.Exactly(5)); + } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/SeriesSearchJobTest.cs b/NzbDrone.Core.Test/SeriesSearchJobTest.cs new file mode 100644 index 000000000..81b442854 --- /dev/null +++ b/NzbDrone.Core.Test/SeriesSearchJobTest.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using AutoMoq; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Model; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Providers.Indexer; +using NzbDrone.Core.Providers.Jobs; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class SeriesSearchJobTest : TestBase + { + [Test] + public void SeriesSearch_success() + { + var episodes = Builder.CreateListOfSize(5) + .WhereAll() + .Have(e => e.SeriesId = 1) + .Have(e => e.Ignored = false) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + var notification = new ProgressNotification("Series Search"); + + mocker.GetMock() + .Setup(c => c.GetEpisodeBySeries(1)).Returns(episodes); + + mocker.GetMock() + .Setup(c => c.Start(notification, It.IsAny(), 0)).Verifiable(); + + //Act + mocker.Resolve().Start(notification, 1, 0); + + //Assert + mocker.VerifyAllMocks(); + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), + Times.Exactly(episodes.Count)); + } + + [Test] + public void SeriesSearch_no_episodes() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + var notification = new ProgressNotification("Series Search"); + List nullList = null; + + mocker.GetMock() + .Setup(c => c.GetEpisodeBySeries(1)).Returns(nullList); + + //Act + mocker.Resolve().Start(notification, 1, 0); + + //Assert + mocker.VerifyAllMocks(); + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), + Times.Never()); + ExceptionVerification.ExcpectedWarns(1); + } + + [Test] + public void SeriesSearch_skip_ignored() + { + var episodes = Builder.CreateListOfSize(10) + .WhereAll() + .Have(e => e.SeriesId = 1) + .WhereTheFirst(5) + .Have(e => e.Ignored = false) + .AndTheRemaining() + .Have(e => e.Ignored = true) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + var notification = new ProgressNotification("Series Search"); + + mocker.GetMock() + .Setup(c => c.GetEpisodeBySeries(1)).Returns(episodes); + + mocker.GetMock() + .Setup(c => c.Start(notification, It.IsAny(), 0)).Verifiable(); + + //Act + mocker.Resolve().Start(notification, 1, 0); + + //Assert + mocker.VerifyAllMocks(); + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), + Times.Exactly(5)); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/CentralDispatch.cs b/NzbDrone.Core/CentralDispatch.cs index 873a25e5f..20c0cbee8 100644 --- a/NzbDrone.Core/CentralDispatch.cs +++ b/NzbDrone.Core/CentralDispatch.cs @@ -106,6 +106,8 @@ namespace NzbDrone.Core _kernel.Bind().To().InSingletonScope(); _kernel.Bind().To().InSingletonScope(); _kernel.Bind().To().InSingletonScope(); + _kernel.Bind().To().InSingletonScope(); + _kernel.Bind().To().InSingletonScope(); _kernel.Get().Initialize(); _kernel.Get().StartTimer(30); diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 6e6dee154..d027b7d2d 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -197,6 +197,8 @@ + + diff --git a/NzbDrone.Core/Providers/Jobs/RenameSeasonJob.cs b/NzbDrone.Core/Providers/Jobs/RenameSeasonJob.cs index ba6dfebe0..df8dd0c30 100644 --- a/NzbDrone.Core/Providers/Jobs/RenameSeasonJob.cs +++ b/NzbDrone.Core/Providers/Jobs/RenameSeasonJob.cs @@ -44,7 +44,7 @@ namespace NzbDrone.Core.Providers.Jobs if (episodeFiles == null || episodeFiles.Count == 0) { - Logger.Warn("No episodes in database found for series: {0} and season: {1}. No", targetId, secondaryTargetId); + Logger.Warn("No episodes in database found for series: {0} and season: {1}.", targetId, secondaryTargetId); return; } diff --git a/NzbDrone.Core/Providers/Jobs/RenameSeriesJob.cs b/NzbDrone.Core/Providers/Jobs/RenameSeriesJob.cs new file mode 100644 index 000000000..c67b98e4c --- /dev/null +++ b/NzbDrone.Core/Providers/Jobs/RenameSeriesJob.cs @@ -0,0 +1,56 @@ +using System; +using Ninject; +using NLog; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers.Core; + +namespace NzbDrone.Core.Providers.Jobs +{ + public class RenameSeriesJob : IJob + { + private readonly MediaFileProvider _mediaFileProvider; + private readonly DiskScanProvider _diskScanProvider; + + + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + [Inject] + public RenameSeriesJob(MediaFileProvider mediaFileProvider, DiskScanProvider diskScanProvider) + { + _mediaFileProvider = mediaFileProvider; + _diskScanProvider = diskScanProvider; + } + + public string Name + { + get { return "Rename Series"; } + } + + public int DefaultInterval + { + get { return 0; } + } + + public void Start(ProgressNotification notification, int targetId, int secondaryTargetId) + { + if (targetId <= 0) + throw new ArgumentOutOfRangeException("targetId"); + + Logger.Debug("Getting episodes from database for series: {0}", targetId); + var episodeFiles = _mediaFileProvider.GetSeriesFiles(targetId); + + if (episodeFiles == null || episodeFiles.Count == 0) + { + Logger.Warn("No episodes in database found for series: {0}", targetId); + return; + } + + foreach (var episodeFile in episodeFiles) + { + _diskScanProvider.MoveEpisodeFile(episodeFile); + } + + notification.CurrentMessage = String.Format("Series rename completed for Series: {0}", targetId); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Jobs/SeasonSearchJob.cs b/NzbDrone.Core/Providers/Jobs/SeasonSearchJob.cs index 94c77c942..21456377c 100644 --- a/NzbDrone.Core/Providers/Jobs/SeasonSearchJob.cs +++ b/NzbDrone.Core/Providers/Jobs/SeasonSearchJob.cs @@ -44,13 +44,13 @@ namespace NzbDrone.Core.Providers.Jobs if (episodes == null) { - Logger.Warn("No episodes in database found for series: {0} and season: {1}. No", targetId, secondaryTargetId); + Logger.Warn("No episodes in database found for series: {0} and season: {1}.", targetId, secondaryTargetId); return; } //Todo: Search for a full season NZB before individual episodes - foreach (var episode in episodes) + foreach (var episode in episodes.Where(e => !e.Ignored)) { _episodeSearchJob.Start(notification, episode.EpisodeId, 0); } diff --git a/NzbDrone.Core/Providers/Jobs/SeriesSearchJob.cs b/NzbDrone.Core/Providers/Jobs/SeriesSearchJob.cs new file mode 100644 index 000000000..e5d7acd34 --- /dev/null +++ b/NzbDrone.Core/Providers/Jobs/SeriesSearchJob.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NLog; +using NzbDrone.Core.Model; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Repository; + +namespace NzbDrone.Core.Providers.Jobs +{ + public class SeriesSearchJob : IJob + { + private readonly EpisodeProvider _episodeProvider; + private readonly EpisodeSearchJob _episodeSearchJob; + + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + public SeriesSearchJob(EpisodeProvider episodeProvider, EpisodeSearchJob episodeSearchJob) + { + _episodeProvider = episodeProvider; + _episodeSearchJob = episodeSearchJob; + } + + public string Name + { + get { return "Series Search"; } + } + + public int DefaultInterval + { + get { return 0; } + } + + public void Start(ProgressNotification notification, int targetId, int secondaryTargetId) + { + if (targetId <= 0) + throw new ArgumentOutOfRangeException("targetId"); + + Logger.Debug("Getting episodes from database for series: {0}.", targetId); + var episodes = _episodeProvider.GetEpisodeBySeries(targetId); + + if (episodes == null) + { + Logger.Warn("No episodes in database found for series: {0}.", targetId); + return; + } + + //Todo: Search for a full season NZB before individual episodes + + foreach (var episode in episodes.Where(e => !e.Ignored)) + { + _episodeSearchJob.Start(notification, episode.EpisodeId, 0); + } + } + } +} \ No newline at end of file diff --git a/NzbDrone.Web/Controllers/CommandController.cs b/NzbDrone.Web/Controllers/CommandController.cs index 7d8ae9164..6a42c1acf 100644 --- a/NzbDrone.Web/Controllers/CommandController.cs +++ b/NzbDrone.Web/Controllers/CommandController.cs @@ -37,13 +37,5 @@ namespace NzbDrone.Web.Controllers return new JsonResult { Data = "ok", JsonRequestBehavior = JsonRequestBehavior.AllowGet }; } - - public JsonResult RenameSeries(int seriesId) - { - //Syncs the episodes on disk for the specified series - //_jobProvider.QueueJob(typeof(UpdateInfoJob), seriesId); - - return new JsonResult { Data = "ok", JsonRequestBehavior = JsonRequestBehavior.AllowGet }; - } } } diff --git a/NzbDrone.Web/Controllers/EpisodeController.cs b/NzbDrone.Web/Controllers/EpisodeController.cs index 899e270db..99e23e34a 100644 --- a/NzbDrone.Web/Controllers/EpisodeController.cs +++ b/NzbDrone.Web/Controllers/EpisodeController.cs @@ -33,6 +33,14 @@ namespace NzbDrone.Web.Controllers return new JsonResult { Data = "ok" }; } + public JsonResult SearchSeries(int seriesId) + { + //Syncs the episodes on disk for the specified series + _jobProvider.QueueJob(typeof(SeriesSearchJob), seriesId); + + return new JsonResult { Data = "ok", JsonRequestBehavior = JsonRequestBehavior.AllowGet }; + } + public JsonResult Rename(int episodeFileId) { _jobProvider.QueueJob(typeof(RenameEpisodeJob), episodeFileId); @@ -46,5 +54,13 @@ namespace NzbDrone.Web.Controllers return new JsonResult { Data = "ok" }; } + + public JsonResult RenameSeries(int seriesId) + { + //Syncs the episodes on disk for the specified series + _jobProvider.QueueJob(typeof(RenameSeriesJob), seriesId); + + return new JsonResult { Data = "ok", JsonRequestBehavior = JsonRequestBehavior.AllowGet }; + } } } \ No newline at end of file diff --git a/NzbDrone.Web/Views/Series/Details.cshtml b/NzbDrone.Web/Views/Series/Details.cshtml index 067c9d6c6..0cc76374b 100644 --- a/NzbDrone.Web/Views/Series/Details.cshtml +++ b/NzbDrone.Web/Views/Series/Details.cshtml @@ -86,7 +86,8 @@
  • @Html.ActionLink("Back to Series List", "Index", "Series")
  • @Ajax.ActionLink("Scan For Episodes on Disk", "SyncEpisodesOnDisk", "Command", new { seriesId = Model.SeriesId }, null)
  • @Ajax.ActionLink("Update Info", "UpdateInfo", "Command", new { seriesId = Model.SeriesId }, null)
  • -
  • @Ajax.ActionLink("Rename Series", "RenameSeries", "Command", new { seriesId = Model.SeriesId }, null)
  • +
  • @Ajax.ActionLink("Search for Series", "SearchSeries", "Episode", new { seriesId = Model.SeriesId }, null)
  • +
  • @Ajax.ActionLink("Rename Series", "RenameSeries", "Episode", new { seriesId = Model.SeriesId }, null)
  • } @section MainContent{ @@ -120,7 +121,7 @@ //columns.Bound(o => o.Ignored) // .Title("") // .ClientTemplate( - // "") + // "") // .Width(20) // .HtmlAttributes(new { style = "text-align:center" });