diff --git a/NzbDrone.Core.Test/DiskScanJobTest.cs b/NzbDrone.Core.Test/DiskScanJobTest.cs index 4c66368272..463b8a7eee 100644 --- a/NzbDrone.Core.Test/DiskScanJobTest.cs +++ b/NzbDrone.Core.Test/DiskScanJobTest.cs @@ -36,7 +36,7 @@ public void series_specific_scan_should_scan_series() //Act - mocker.Resolve().Start(new ProgressNotification("Test"), series.SeriesId); + mocker.Resolve().Start(new ProgressNotification("Test"), series.SeriesId, 0); //Assert mocker.VerifyAllMocks(); @@ -66,7 +66,7 @@ public void job_with_no_target_should_scan_all_series() .Setup(s => s.Scan(series[1])) .Returns(new List()); - mocker.Resolve().Start(new ProgressNotification("Test"), 0); + mocker.Resolve().Start(new ProgressNotification("Test"), 0, 0); mocker.VerifyAllMocks(); @@ -94,7 +94,7 @@ public void failed_scan_should_not_terminated_job() .Setup(s => s.Scan(series[1])) .Throws(new InvalidOperationException("Bad Job")); - mocker.Resolve().Start(new ProgressNotification("Test"), 0); + mocker.Resolve().Start(new ProgressNotification("Test"), 0, 0); mocker.VerifyAllMocks(); @@ -123,7 +123,7 @@ public void job_with_no_target_should_scan_series_with_episodes() .Setup(s => s.Scan(series[1])) .Returns(new List()); - mocker.Resolve().Start(new ProgressNotification("Test"), 0); + mocker.Resolve().Start(new ProgressNotification("Test"), 0, 0); diff --git a/NzbDrone.Core.Test/EpisodeSearchJobTest.cs b/NzbDrone.Core.Test/EpisodeSearchJobTest.cs index 0e34f0a64b..6762316886 100644 --- a/NzbDrone.Core.Test/EpisodeSearchJobTest.cs +++ b/NzbDrone.Core.Test/EpisodeSearchJobTest.cs @@ -207,7 +207,7 @@ public void processResults_failed_download_should_not_check_the_rest() public void start_target_id_less_than_0_throws_exception(int target) { var mocker = new AutoMoqer(MockBehavior.Strict); - mocker.Resolve().Start(new ProgressNotification("Test"), target); + mocker.Resolve().Start(new ProgressNotification("Test"), target, 0); } @@ -251,7 +251,7 @@ public void start_should_search_all_providers() .Setup(s => s.GetSceneName(It.IsAny())).Returns(""); //Act - mocker.Resolve().Start(new ProgressNotification("Test"), episode.EpisodeId); + mocker.Resolve().Start(new ProgressNotification("Test"), episode.EpisodeId, 0); //Assert @@ -302,7 +302,7 @@ public void start_should_use_scene_name_to_search() .Setup(s => s.GetSceneName(71256)).Returns("The Daily Show"); //Act - mocker.Resolve().Start(new ProgressNotification("Test"), episode.EpisodeId); + mocker.Resolve().Start(new ProgressNotification("Test"), episode.EpisodeId, 0); //Assert @@ -359,7 +359,7 @@ public void start_failed_indexer_should_not_break_job() .Setup(s => s.GetSceneName(It.IsAny())).Returns(""); //Act - mocker.Resolve().Start(new ProgressNotification("Test"), episode.EpisodeId); + mocker.Resolve().Start(new ProgressNotification("Test"), episode.EpisodeId, 0); //Assert @@ -385,7 +385,7 @@ public void start_no_episode_found_should_return_with_error_logged() .Returns(null); //Act - mocker.Resolve().Start(new ProgressNotification("Test"), 12); + mocker.Resolve().Start(new ProgressNotification("Test"), 12, 0); //Assert diff --git a/NzbDrone.Core.Test/ImportNewSeriesJobTest.cs b/NzbDrone.Core.Test/ImportNewSeriesJobTest.cs index 0a0fe75971..c1432c486d 100644 --- a/NzbDrone.Core.Test/ImportNewSeriesJobTest.cs +++ b/NzbDrone.Core.Test/ImportNewSeriesJobTest.cs @@ -36,21 +36,21 @@ public void import_new_series_succesfull() mocker.GetMock() - .Setup(j => j.Start(notification, series[0].SeriesId)) + .Setup(j => j.Start(notification, series[0].SeriesId, 0)) .Callback(() => series[0].LastDiskSync = DateTime.Now); mocker.GetMock() - .Setup(j => j.Start(notification, series[1].SeriesId)) + .Setup(j => j.Start(notification, series[1].SeriesId, 0)) .Callback(() => series[1].LastDiskSync = DateTime.Now); mocker.GetMock() - .Setup(j => j.Start(notification, series[0].SeriesId)) + .Setup(j => j.Start(notification, series[0].SeriesId, 0)) .Callback(() => series[0].LastInfoSync = DateTime.Now); mocker.GetMock() - .Setup(j => j.Start(notification, series[1].SeriesId)) + .Setup(j => j.Start(notification, series[1].SeriesId, 0)) .Callback(() => series[1].LastInfoSync = DateTime.Now); mocker.GetMock() @@ -63,16 +63,16 @@ public void import_new_series_succesfull() .Setup(s => s.GetSeriesFiles(It.IsAny())).Returns(new List()); //Act - mocker.Resolve().Start(notification, 0); + mocker.Resolve().Start(notification, 0, 0); //Assert mocker.VerifyAllMocks(); - mocker.GetMock().Verify(j => j.Start(notification, series[0].SeriesId), Times.Once()); - mocker.GetMock().Verify(j => j.Start(notification, series[1].SeriesId), Times.Once()); + mocker.GetMock().Verify(j => j.Start(notification, series[0].SeriesId, 0), Times.Once()); + mocker.GetMock().Verify(j => j.Start(notification, series[1].SeriesId, 0), Times.Once()); - mocker.GetMock().Verify(j => j.Start(notification, series[0].SeriesId), Times.Once()); - mocker.GetMock().Verify(j => j.Start(notification, series[1].SeriesId), Times.Once()); + mocker.GetMock().Verify(j => j.Start(notification, series[0].SeriesId, 0), Times.Once()); + mocker.GetMock().Verify(j => j.Start(notification, series[1].SeriesId, 0), Times.Once()); } @@ -98,15 +98,15 @@ public void failed_import_should_not_be_stuck_in_loop() .Returns(series); mocker.GetMock() - .Setup(j => j.Start(notification, series[0].SeriesId)) + .Setup(j => j.Start(notification, series[0].SeriesId, 0)) .Callback(() => series[0].LastInfoSync = DateTime.Now); mocker.GetMock() - .Setup(j => j.Start(notification, series[1].SeriesId)) + .Setup(j => j.Start(notification, series[1].SeriesId, 0)) .Throws(new InvalidOperationException()); mocker.GetMock() - .Setup(j => j.Start(notification, series[0].SeriesId)) + .Setup(j => j.Start(notification, series[0].SeriesId, 0)) .Callback(() => series[0].LastDiskSync = DateTime.Now); @@ -117,15 +117,15 @@ public void failed_import_should_not_be_stuck_in_loop() .Setup(s => s.GetSeriesFiles(It.IsAny())).Returns(new List()); //Act - mocker.Resolve().Start(notification, 0); + mocker.Resolve().Start(notification, 0, 0); //Assert mocker.VerifyAllMocks(); - mocker.GetMock().Verify(j => j.Start(notification, series[0].SeriesId), Times.Once()); - mocker.GetMock().Verify(j => j.Start(notification, series[1].SeriesId), Times.Once()); + mocker.GetMock().Verify(j => j.Start(notification, series[0].SeriesId, 0), Times.Once()); + mocker.GetMock().Verify(j => j.Start(notification, series[1].SeriesId, 0), Times.Once()); - mocker.GetMock().Verify(j => j.Start(notification, series[0].SeriesId), Times.Once()); + mocker.GetMock().Verify(j => j.Start(notification, series[0].SeriesId, 0), Times.Once()); ExceptionVerification.ExcpectedErrors(1); diff --git a/NzbDrone.Core.Test/JobProviderTest.cs b/NzbDrone.Core.Test/JobProviderTest.cs index 81464dfe3e..e09a6dae3d 100644 --- a/NzbDrone.Core.Test/JobProviderTest.cs +++ b/NzbDrone.Core.Test/JobProviderTest.cs @@ -7,6 +7,7 @@ using FluentAssertions; using Moq; using NUnit.Framework; +using NzbDrone.Core.Model; using NzbDrone.Core.Model.Notification; using NzbDrone.Core.Providers.Jobs; using NzbDrone.Core.Test.Framework; @@ -84,7 +85,7 @@ public void scheduler_skips_jobs_that_arent_mature_yet() timerProvider.QueueScheduled(); Thread.Sleep(500); - fakeJob.ExexutionCount.Should().Be(1); + fakeJob.ExecutionCount.Should().Be(1); } [Test] @@ -106,7 +107,7 @@ public void can_run_async_job_again() timerProvider.QueueJob(typeof(FakeJob)); Thread.Sleep(1000); JobProvider.Queue.Should().BeEmpty(); - fakeJob.ExexutionCount.Should().Be(2); + fakeJob.ExecutionCount.Should().Be(2); } [Test] @@ -154,7 +155,7 @@ public void can_run_broken_async_job_again() Thread.Sleep(2000); JobProvider.Queue.Should().BeEmpty(); - brokenJob.ExexutionCount.Should().Be(2); + brokenJob.ExecutionCount.Should().Be(2); ExceptionVerification.ExcpectedErrors(2); } @@ -184,7 +185,7 @@ public void can_run_two_jobs_at_the_same_time() thread2.Join(); - slowJob.ExexutionCount = 2; + slowJob.ExecutionCount = 2; } @@ -216,7 +217,7 @@ public void can_queue_jobs_at_the_same_time() Thread.Sleep(5000); - Assert.AreEqual(1, slowJob.ExexutionCount); + Assert.AreEqual(1, slowJob.ExecutionCount); JobProvider.Queue.Should().BeEmpty(); } @@ -352,7 +353,7 @@ public void Disabled_isnt_run_by_scheduler() //Assert - Assert.AreEqual(0, disabledJob.ExexutionCount); + Assert.AreEqual(0, disabledJob.ExecutionCount); } [Test] @@ -411,7 +412,13 @@ public void existing_queue_should_start_queue_if_not_running() mocker.SetConstant(MockLib.GetEmptyDatabase()); mocker.SetConstant(fakeJobs); - var fakeQueueItem = new Tuple(fakeJob.GetType(), 12); + var fakeQueueItem = new JobQueueItem + { + JobType = fakeJob.GetType(), + TargetId = 12, + SecondaryTargetId = 0 + }; + //Act var jobProvider = mocker.Resolve(); jobProvider.Initialize(); @@ -420,7 +427,7 @@ public void existing_queue_should_start_queue_if_not_running() Thread.Sleep(1000); //Assert - fakeJob.ExexutionCount.Should().Be(1); + fakeJob.ExecutionCount.Should().Be(1); } @@ -449,8 +456,8 @@ public void Item_added_to_queue_while_scheduler_runs_is_executed() //Assert JobProvider.Queue.Should().BeEmpty(); - slowJob.ExexutionCount.Should().Be(1); - disabledJob.ExexutionCount.Should().Be(1); + slowJob.ExecutionCount.Should().Be(1); + disabledJob.ExecutionCount.Should().Be(1); } } @@ -466,11 +473,11 @@ public int DefaultInterval get { return 15; } } - public int ExexutionCount { get; set; } + public int ExecutionCount { get; set; } - public void Start(ProgressNotification notification, int targetId) + public void Start(ProgressNotification notification, int targetId, int secondaryTargetId) { - ExexutionCount++; + ExecutionCount++; } } @@ -486,11 +493,11 @@ public int DefaultInterval get { return 0; } } - public int ExexutionCount { get; set; } + public int ExecutionCount { get; set; } - public void Start(ProgressNotification notification, int targetId) + public void Start(ProgressNotification notification, int targetId, int secondaryTargetId) { - ExexutionCount++; + ExecutionCount++; } } @@ -506,11 +513,11 @@ public int DefaultInterval get { return 15; } } - public int ExexutionCount { get; set; } + public int ExecutionCount { get; set; } - public void Start(ProgressNotification notification, int targetId) + public void Start(ProgressNotification notification, int targetId, int secondaryTargetId) { - ExexutionCount++; + ExecutionCount++; throw new ApplicationException("Broken job is broken"); } } @@ -527,13 +534,13 @@ public int DefaultInterval get { return 15; } } - public int ExexutionCount { get; set; } + public int ExecutionCount { get; set; } - public void Start(ProgressNotification notification, int targetId) + public void Start(ProgressNotification notification, int targetId, int secondaryTargetId) { Console.WriteLine("Starting Job"); Thread.Sleep(2000); - ExexutionCount++; + ExecutionCount++; Console.WriteLine("Finishing Job"); } } diff --git a/NzbDrone.Core.Test/MediaFileProviderTests.cs b/NzbDrone.Core.Test/MediaFileProviderTests.cs index 979e423c02..71170266e9 100644 --- a/NzbDrone.Core.Test/MediaFileProviderTests.cs +++ b/NzbDrone.Core.Test/MediaFileProviderTests.cs @@ -50,6 +50,35 @@ public void get_series_files() result.Should().HaveSameCount(firstSeriesFiles); } + [Test] + public void get_season_files() + { + var firstSeriesFiles = Builder.CreateListOfSize(10) + .WhereAll() + .Have(s => s.SeriesId = 12) + .Have(s => s.SeasonNumber = 1) + .Build(); + + var secondSeriesFiles = Builder.CreateListOfSize(10) + .WhereAll() + .Have(s => s.SeriesId = 12) + .Have(s => s.SeasonNumber = 2) + .Build(); + + var mocker = new AutoMoqer(); + + var database = MockLib.GetEmptyDatabase(true); + + database.InsertMany(firstSeriesFiles); + database.InsertMany(secondSeriesFiles); + + mocker.SetConstant(database); + + var result = mocker.Resolve().GetSeasonFiles(12, 1); + + result.Should().HaveSameCount(firstSeriesFiles); + } + [Test] public void Scan_series_should_skip_series_with_no_episodes() { diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 97485f93ae..bd1d1b24cb 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 new file mode 100644 index 0000000000..499b645b6f --- /dev/null +++ b/NzbDrone.Core.Test/SeasonSearchJobTest.cs @@ -0,0 +1,72 @@ +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 SeasonSearchJobTest : TestBase + { + [Test] + public void SeasonSearch_success() + { + var episodes = Builder.CreateListOfSize(5) + .WhereAll() + .Have(e => e.SeriesId = 1) + .Have(e => e.SeasonNumber = 1) + .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(episodes.Count)); + } + + [Test] + public void SeasonSearch_no_episodes() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + var notification = new ProgressNotification("Season Search"); + List nullList = null; + + mocker.GetMock() + .Setup(c => c.GetEpisodesBySeason(1, 1)).Returns(nullList); + + //Act + mocker.Resolve().Start(notification, 1, 1); + + //Assert + mocker.VerifyAllMocks(); + mocker.GetMock().Verify(c => c.Start(notification, It.IsAny(), 0), + Times.Never()); + ExceptionVerification.ExcpectedWarns(1); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/CentralDispatch.cs b/NzbDrone.Core/CentralDispatch.cs index 993d96d13e..873a25e5f9 100644 --- a/NzbDrone.Core/CentralDispatch.cs +++ b/NzbDrone.Core/CentralDispatch.cs @@ -104,6 +104,8 @@ private static void BindJobs() _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/Model/JobQueueItem.cs b/NzbDrone.Core/Model/JobQueueItem.cs new file mode 100644 index 0000000000..71322190ff --- /dev/null +++ b/NzbDrone.Core/Model/JobQueueItem.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Model +{ + public class JobQueueItem : IEquatable + { + public Type JobType { get; set; } + public int TargetId { get; set; } + public int SecondaryTargetId { get; set; } + + public bool Equals(JobQueueItem other) + { + if (JobType == other.JobType && TargetId == other.TargetId + && SecondaryTargetId == other.SecondaryTargetId) + { + return true; + } + + return false; + } + } +} diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 20f03346fa..3f1f8aec08 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -188,6 +188,7 @@ + @@ -196,6 +197,8 @@ + + diff --git a/NzbDrone.Core/Providers/Jobs/DeleteSeriesJob.cs b/NzbDrone.Core/Providers/Jobs/DeleteSeriesJob.cs index 98c3225789..462e3a246a 100644 --- a/NzbDrone.Core/Providers/Jobs/DeleteSeriesJob.cs +++ b/NzbDrone.Core/Providers/Jobs/DeleteSeriesJob.cs @@ -27,7 +27,7 @@ public int DefaultInterval get { return 0; } } - public void Start(ProgressNotification notification, int targetId) + public void Start(ProgressNotification notification, int targetId, int secondaryTargetId) { DeleteSeries(notification, targetId); } diff --git a/NzbDrone.Core/Providers/Jobs/DiskScanJob.cs b/NzbDrone.Core/Providers/Jobs/DiskScanJob.cs index cc8b1a76df..237567d59d 100644 --- a/NzbDrone.Core/Providers/Jobs/DiskScanJob.cs +++ b/NzbDrone.Core/Providers/Jobs/DiskScanJob.cs @@ -35,7 +35,7 @@ public int DefaultInterval get { return 60; } } - public virtual void Start(ProgressNotification notification, int targetId) + public virtual void Start(ProgressNotification notification, int targetId, int secondaryTargetId) { IList seriesToScan; if (targetId == 0) diff --git a/NzbDrone.Core/Providers/Jobs/EpisodeSearchJob.cs b/NzbDrone.Core/Providers/Jobs/EpisodeSearchJob.cs index d011acc283..dac5a6d9a8 100644 --- a/NzbDrone.Core/Providers/Jobs/EpisodeSearchJob.cs +++ b/NzbDrone.Core/Providers/Jobs/EpisodeSearchJob.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NLog; +using Ninject; using NzbDrone.Core.Model; using NzbDrone.Core.Model.Notification; using NzbDrone.Core.Repository; @@ -18,6 +19,7 @@ public class EpisodeSearchJob : IJob private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + [Inject] public EpisodeSearchJob(InventoryProvider inventoryProvider, DownloadProvider downloadProvider, IndexerProvider indexerProvider, EpisodeProvider episodeProvider, SceneMappingProvider sceneNameMappingProvider) @@ -29,6 +31,11 @@ public EpisodeSearchJob(InventoryProvider inventoryProvider, DownloadProvider do _sceneNameMappingProvider = sceneNameMappingProvider; } + public EpisodeSearchJob() + { + + } + public string Name { get { return "Episode Search"; } @@ -39,7 +46,7 @@ public int DefaultInterval get { return 0; } } - public void Start(ProgressNotification notification, int targetId) + public virtual void Start(ProgressNotification notification, int targetId, int secondaryTargetId) { if (targetId <= 0) throw new ArgumentOutOfRangeException("targetId"); diff --git a/NzbDrone.Core/Providers/Jobs/IJob.cs b/NzbDrone.Core/Providers/Jobs/IJob.cs index ac2f85e769..36bfa47636 100644 --- a/NzbDrone.Core/Providers/Jobs/IJob.cs +++ b/NzbDrone.Core/Providers/Jobs/IJob.cs @@ -7,7 +7,7 @@ public interface IJob /// /// Name of the timer. /// This is the name that will be visible in all UI elements - /// + /// \\\ string Name { get; } @@ -25,6 +25,7 @@ public interface IJob /// Notification object that is passed in by JobProvider. /// this object should be used to update the progress on the UI /// The that should be used to limit the target of this job - void Start(ProgressNotification notification, int targetId); + /// /// The that should be used to limit the target of this job + void Start(ProgressNotification notification, int targetId, int secondaryTargetId); } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Jobs/ImportNewSeriesJob.cs b/NzbDrone.Core/Providers/Jobs/ImportNewSeriesJob.cs index 6da507c410..e9a0f333a8 100644 --- a/NzbDrone.Core/Providers/Jobs/ImportNewSeriesJob.cs +++ b/NzbDrone.Core/Providers/Jobs/ImportNewSeriesJob.cs @@ -46,7 +46,7 @@ public int DefaultInterval get { return 1; } } - public void Start(ProgressNotification notification, int targetId) + public void Start(ProgressNotification notification, int targetId, int secondaryTargetId ) { _attemptedSeries = new List(); ScanSeries(notification); @@ -67,8 +67,8 @@ private void ScanSeries(ProgressNotification notification) _attemptedSeries.Add(currentSeries.SeriesId); notification.CurrentMessage = String.Format("Searching for '{0}'", new DirectoryInfo(currentSeries.Path).Name); - _updateInfoJob.Start(notification, currentSeries.SeriesId); - _diskScanJob.Start(notification, currentSeries.SeriesId); + _updateInfoJob.Start(notification, currentSeries.SeriesId, 0); + _diskScanJob.Start(notification, currentSeries.SeriesId, 0); var updatedSeries = _seriesProvider.GetSeries(currentSeries.SeriesId); AutoIgnoreSeasons(updatedSeries.SeriesId); diff --git a/NzbDrone.Core/Providers/Jobs/JobProvider.cs b/NzbDrone.Core/Providers/Jobs/JobProvider.cs index dc95732859..fd0988fa0a 100644 --- a/NzbDrone.Core/Providers/Jobs/JobProvider.cs +++ b/NzbDrone.Core/Providers/Jobs/JobProvider.cs @@ -7,6 +7,7 @@ using System.Threading; using Ninject; using NLog; +using NzbDrone.Core.Model; using NzbDrone.Core.Model.Notification; using NzbDrone.Core.Repository; using PetaPoco; @@ -28,7 +29,7 @@ public class JobProvider private Thread _jobThread; private static bool _isRunning; - private static readonly List> _queue = new List>(); + private static readonly List _queue = new List(); private ProgressNotification _notification; @@ -51,7 +52,7 @@ public JobProvider() { } /// /// Gets the active queue. /// - public static List> Queue + public static List Queue { get { @@ -122,8 +123,10 @@ public virtual void QueueScheduled() /// Type of the job that should be queued. /// The targetId could be any Id parameter eg. SeriesId. it will be passed to the job implementation /// to allow it to filter it's target of execution. + /// /// The secondaryTargetId could be any Id parameter eg. SeasonNumber. it will be passed to + /// the timer implementation to further allow it to filter it's target of execution /// Job is only added to the queue if same job with the same targetId doesn't already exist in the queue. - public virtual void QueueJob(Type jobType, int targetId = 0) + public virtual void QueueJob(Type jobType, int targetId = 0, int secondaryTargetId = 0) { Logger.Debug("Adding [{0}:{1}] to the queue", jobType.Name, targetId); @@ -131,11 +134,16 @@ public virtual void QueueJob(Type jobType, int targetId = 0) { lock (Queue) { - var queueTuple = new Tuple(jobType, targetId); + var queueItem = new JobQueueItem + { + JobType = jobType, + TargetId = targetId, + SecondaryTargetId = secondaryTargetId + }; - if (!Queue.Contains(queueTuple)) + if (!Queue.Contains(queueItem)) { - Queue.Add(queueTuple); + Queue.Add(queueItem); Logger.Trace("Job [{0}:{1}] added to the queue", jobType.Name, targetId); } @@ -195,7 +203,7 @@ private void ProcessQueue() { try { - Tuple job = null; + JobQueueItem job = null; lock (Queue) { @@ -208,7 +216,7 @@ private void ProcessQueue() if (job != null) { - Execute(job.Item1, job.Item2); + Execute(job.JobType, job.TargetId, job.SecondaryTargetId); } } @@ -231,7 +239,9 @@ private void ProcessQueue() /// Type of the job that should be executed /// The targetId could be any Id parameter eg. SeriesId. it will be passed to the timer implementation /// to allow it to filter it's target of execution - private void Execute(Type jobType, int targetId = 0) + /// /// The secondaryTargetId could be any Id parameter eg. SeasonNumber. it will be passed to + /// the timer implementation to further allow it to filter it's target of execution + private void Execute(Type jobType, int targetId = 0, int secondaryTargetId = 0) { var jobImplementation = _jobs.Where(t => t.GetType() == jobType).Single(); if (jobImplementation == null) @@ -251,7 +261,7 @@ private void Execute(Type jobType, int targetId = 0) var sw = Stopwatch.StartNew(); _notificationProvider.Register(_notification); - jobImplementation.Start(_notification, targetId); + jobImplementation.Start(_notification, targetId, secondaryTargetId); _notification.Status = ProgressNotificationStatus.Completed; settings.LastExecution = DateTime.Now; diff --git a/NzbDrone.Core/Providers/Jobs/PostDownloadScanJob.cs b/NzbDrone.Core/Providers/Jobs/PostDownloadScanJob.cs index 3af0c7557c..f57e7d2b79 100644 --- a/NzbDrone.Core/Providers/Jobs/PostDownloadScanJob.cs +++ b/NzbDrone.Core/Providers/Jobs/PostDownloadScanJob.cs @@ -39,7 +39,7 @@ public int DefaultInterval get { return 1; } } - public virtual void Start(ProgressNotification notification, int targetId) + public virtual void Start(ProgressNotification notification, int targetId, int secondaryTargetId) { var dropFolder = _configProvider.SabDropDirectory; diff --git a/NzbDrone.Core/Providers/Jobs/RenameEpisodeJob.cs b/NzbDrone.Core/Providers/Jobs/RenameEpisodeJob.cs index 9e79207678..4cc3ca1563 100644 --- a/NzbDrone.Core/Providers/Jobs/RenameEpisodeJob.cs +++ b/NzbDrone.Core/Providers/Jobs/RenameEpisodeJob.cs @@ -1,4 +1,5 @@ -using Ninject; +using System; +using Ninject; using NLog; using NzbDrone.Core.Model.Notification; using NzbDrone.Core.Providers.Core; @@ -30,10 +31,14 @@ public int DefaultInterval get { return 0; } } - public void Start(ProgressNotification notification, int targetId) + public void Start(ProgressNotification notification, int targetId, int secondaryTargetId) { + if (targetId <= 0) + throw new ArgumentOutOfRangeException("targetId"); + var episode = _mediaFileProvider.GetEpisodeFile(targetId); _diskScanProvider.MoveEpisodeFile(episode); + notification.CurrentMessage = String.Format("Episode rename completed for: {0} ", targetId); } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Jobs/RenameSeasonJob.cs b/NzbDrone.Core/Providers/Jobs/RenameSeasonJob.cs new file mode 100644 index 0000000000..ba6dfebe07 --- /dev/null +++ b/NzbDrone.Core/Providers/Jobs/RenameSeasonJob.cs @@ -0,0 +1,59 @@ +using System; +using Ninject; +using NLog; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers.Core; + +namespace NzbDrone.Core.Providers.Jobs +{ + public class RenameSeasonJob : IJob + { + private readonly MediaFileProvider _mediaFileProvider; + private readonly DiskScanProvider _diskScanProvider; + + + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + [Inject] + public RenameSeasonJob(MediaFileProvider mediaFileProvider, DiskScanProvider diskScanProvider) + { + _mediaFileProvider = mediaFileProvider; + _diskScanProvider = diskScanProvider; + } + + public string Name + { + get { return "Rename Season"; } + } + + public int DefaultInterval + { + get { return 0; } + } + + public void Start(ProgressNotification notification, int targetId, int secondaryTargetId) + { + if (targetId <= 0) + throw new ArgumentOutOfRangeException("targetId"); + + if (secondaryTargetId <= 0) + throw new ArgumentOutOfRangeException("secondaryTargetId"); + + Logger.Debug("Getting episodes from database for series: {0} and season: {1}", targetId, secondaryTargetId); + var episodeFiles = _mediaFileProvider.GetSeasonFiles(targetId, secondaryTargetId); + + if (episodeFiles == null || episodeFiles.Count == 0) + { + Logger.Warn("No episodes in database found for series: {0} and season: {1}. No", targetId, secondaryTargetId); + return; + } + + foreach (var episodeFile in episodeFiles) + { + _diskScanProvider.MoveEpisodeFile(episodeFile); + } + + notification.CurrentMessage = String.Format("Season rename completed for Series: {0} Season: {1}", targetId, secondaryTargetId); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Jobs/RssSyncJob.cs b/NzbDrone.Core/Providers/Jobs/RssSyncJob.cs index 9fa4bf7f3b..c8ea155224 100644 --- a/NzbDrone.Core/Providers/Jobs/RssSyncJob.cs +++ b/NzbDrone.Core/Providers/Jobs/RssSyncJob.cs @@ -38,7 +38,7 @@ public int DefaultInterval get { return 15; } } - public void Start(ProgressNotification notification, int targetId) + public void Start(ProgressNotification notification, int targetId, int secondaryTargetId) { var reports = new List(); diff --git a/NzbDrone.Core/Providers/Jobs/SeasonSearchJob.cs b/NzbDrone.Core/Providers/Jobs/SeasonSearchJob.cs new file mode 100644 index 0000000000..94c77c942f --- /dev/null +++ b/NzbDrone.Core/Providers/Jobs/SeasonSearchJob.cs @@ -0,0 +1,59 @@ +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 SeasonSearchJob : IJob + { + private readonly EpisodeProvider _episodeProvider; + private readonly EpisodeSearchJob _episodeSearchJob; + + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + public SeasonSearchJob(EpisodeProvider episodeProvider, EpisodeSearchJob episodeSearchJob) + { + _episodeProvider = episodeProvider; + _episodeSearchJob = episodeSearchJob; + } + + public string Name + { + get { return "Season Search"; } + } + + public int DefaultInterval + { + get { return 0; } + } + + public void Start(ProgressNotification notification, int targetId, int secondaryTargetId) + { + if (targetId <= 0) + throw new ArgumentOutOfRangeException("targetId"); + + if (secondaryTargetId <= 0) + throw new ArgumentOutOfRangeException("secondaryTargetId"); + + Logger.Debug("Getting episodes from database for series: {0} and season: {1}", targetId, secondaryTargetId); + var episodes = _episodeProvider.GetEpisodesBySeason(targetId, secondaryTargetId); + + if (episodes == null) + { + Logger.Warn("No episodes in database found for series: {0} and season: {1}. No", targetId, secondaryTargetId); + return; + } + + //Todo: Search for a full season NZB before individual episodes + + foreach (var episode in episodes) + { + _episodeSearchJob.Start(notification, episode.EpisodeId, 0); + } + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Jobs/UpdateInfoJob.cs b/NzbDrone.Core/Providers/Jobs/UpdateInfoJob.cs index ac4bd2fb3f..59a322a62b 100644 --- a/NzbDrone.Core/Providers/Jobs/UpdateInfoJob.cs +++ b/NzbDrone.Core/Providers/Jobs/UpdateInfoJob.cs @@ -33,7 +33,7 @@ public int DefaultInterval get { return 1440; } //Daily } - public virtual void Start(ProgressNotification notification, int targetId) + public virtual void Start(ProgressNotification notification, int targetId, int secondaryTargetId) { IList seriesToUpdate; if (targetId == 0) diff --git a/NzbDrone.Core/Providers/Jobs/UpdateSceneMappingsJob.cs b/NzbDrone.Core/Providers/Jobs/UpdateSceneMappingsJob.cs index 189a814582..b98ab2f2ff 100644 --- a/NzbDrone.Core/Providers/Jobs/UpdateSceneMappingsJob.cs +++ b/NzbDrone.Core/Providers/Jobs/UpdateSceneMappingsJob.cs @@ -26,7 +26,7 @@ public int DefaultInterval get { return 720; } //Every 12 hours } - public virtual void Start(ProgressNotification notification, int targetId) + public virtual void Start(ProgressNotification notification, int targetId, int secondaryTargetId) { _sceneNameMappingProvider.UpdateMappings(); } diff --git a/NzbDrone.Core/Providers/MediaFileProvider.cs b/NzbDrone.Core/Providers/MediaFileProvider.cs index 6d464b29bd..2df284100b 100644 --- a/NzbDrone.Core/Providers/MediaFileProvider.cs +++ b/NzbDrone.Core/Providers/MediaFileProvider.cs @@ -31,8 +31,6 @@ public MediaFileProvider() { } - - public virtual int Add(EpisodeFile episodeFile) { return Convert.ToInt32(_database.Insert(episodeFile)); @@ -65,7 +63,12 @@ public virtual List GetEpisodeFiles() public virtual IList GetSeriesFiles(int seriesId) { - return _database.Fetch("WHERE seriesId= @0", seriesId); + return _database.Fetch("WHERE SeriesId= @0", seriesId); + } + + public virtual IList GetSeasonFiles(int seriesId, int seasonNumber) + { + return _database.Fetch("WHERE SeriesId= @0 AND SeasonNumber = @1", seriesId, seasonNumber); } public virtual Tuple GetEpisodeFilesCount(int seriesId) @@ -132,7 +135,6 @@ LEFT OUTER JOIN Episodes return updated; } - public virtual string GetNewFilename(IList episodes, string seriesTitle, QualityTypes quality) { var separatorStyle = EpisodeSortingHelper.GetSeparatorStyle(_configProvider.SortingSeparatorStyle); diff --git a/NzbDrone.Web/Controllers/EpisodeController.cs b/NzbDrone.Web/Controllers/EpisodeController.cs index 5e1c4972ab..899e270db6 100644 --- a/NzbDrone.Web/Controllers/EpisodeController.cs +++ b/NzbDrone.Web/Controllers/EpisodeController.cs @@ -11,27 +11,40 @@ namespace NzbDrone.Web.Controllers { public class EpisodeController : Controller { - private readonly JobProvider _jobProvider; - public EpisodeController(JobProvider jobProvider) { _jobProvider = jobProvider; } + [HttpPost] public JsonResult Search(int episodeId) { _jobProvider.QueueJob(typeof(EpisodeSearchJob), episodeId); return new JsonResult { Data = "ok" }; } + [HttpPost] + public JsonResult SearchSeason(int seriesId, int seasonNumber) + { + _jobProvider.QueueJob(typeof(SeasonSearchJob), seriesId, seasonNumber); + return new JsonResult { Data = "ok" }; + } + public JsonResult Rename(int episodeFileId) { _jobProvider.QueueJob(typeof(RenameEpisodeJob), episodeFileId); return new JsonResult { Data = "ok" }; } + + public JsonResult RenameSeason(int seriesId, int seasonNumber) + { + _jobProvider.QueueJob(typeof(RenameSeasonJob), seriesId, seasonNumber); + + return new JsonResult { Data = "ok" }; + } } } \ No newline at end of file diff --git a/NzbDrone.Web/Controllers/SystemController.cs b/NzbDrone.Web/Controllers/SystemController.cs index ec1a15d2ec..5fc6723ca2 100644 --- a/NzbDrone.Web/Controllers/SystemController.cs +++ b/NzbDrone.Web/Controllers/SystemController.cs @@ -4,6 +4,7 @@ using System.IO; using System.Web.Mvc; using NzbDrone.Core.Helpers; +using NzbDrone.Core.Model; using NzbDrone.Core.Providers; using NzbDrone.Core.Providers.Core; using NzbDrone.Core.Providers.Jobs; @@ -29,7 +30,11 @@ public SystemController(JobProvider jobProvider, IndexerProvider indexerProvider public ActionResult Jobs() { - ViewData["Queue"] = JobProvider.Queue.Select(c => new Tuple(c.Item1.Name, c.Item2)); + ViewData["Queue"] = JobProvider.Queue.Select(c => new JobQueueItemModel { + Name = c.JobType.Name, + TargetId = c.TargetId, + SecondaryTargetId = c.SecondaryTargetId + }); return View(_jobProvider.All()); } diff --git a/NzbDrone.Web/Models/JobQueueItemModel.cs b/NzbDrone.Web/Models/JobQueueItemModel.cs new file mode 100644 index 0000000000..92f196714d --- /dev/null +++ b/NzbDrone.Web/Models/JobQueueItemModel.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace NzbDrone.Web.Models +{ + public class JobQueueItemModel + { + public string Name { get; set; } + public int TargetId { get; set; } + public int SecondaryTargetId { get; set; } + } +} \ No newline at end of file diff --git a/NzbDrone.Web/NzbDrone.Web.csproj b/NzbDrone.Web/NzbDrone.Web.csproj index fe130accf2..ca5f770030 100644 --- a/NzbDrone.Web/NzbDrone.Web.csproj +++ b/NzbDrone.Web/NzbDrone.Web.csproj @@ -165,6 +165,7 @@ + diff --git a/NzbDrone.Web/Scripts/episodeSearch.js b/NzbDrone.Web/Scripts/episodeSearch.js index 6f091df376..213c43249b 100644 --- a/NzbDrone.Web/Scripts/episodeSearch.js +++ b/NzbDrone.Web/Scripts/episodeSearch.js @@ -9,5 +9,4 @@ function searchForEpisode(id) { alert("Sorry! We could search for " + id + " at this time. " + error); } }); -} - \ No newline at end of file +} \ No newline at end of file diff --git a/NzbDrone.Web/Scripts/seriesDetails.js b/NzbDrone.Web/Scripts/seriesDetails.js index 28a13be6c2..edd0c1e869 100644 --- a/NzbDrone.Web/Scripts/seriesDetails.js +++ b/NzbDrone.Web/Scripts/seriesDetails.js @@ -3,7 +3,11 @@ var ignoredImage = '../../Content/Images/ignored.png'; var seriesId = 0; var saveSeasonIgnoreUrl = '../Series/SaveSeasonIgnore'; var saveEpisodeIgnoreUrl = '../Series/SaveEpisodeIgnore'; +var renameEpisodeUrl = '../Episode/Rename'; +var renameSeasonUrl = '../Episode/RenameSeason'; +var searchSeasonUrl = '../Episode/SearchSeason'; +//Episode Ignore Functions $(".ignoreEpisode").live("click", function () { var toggle = $(this); var ignored = toggle.hasClass('ignored'); @@ -123,6 +127,7 @@ function grid_dataBound(e) { toggleMaster(seasonNumber); } +//Episode Ignore Saving function saveSeasonIgnore(seasonNumber, ignored) { $.ajax({ type: "POST", @@ -143,4 +148,39 @@ function saveEpisodeIgnore(episodeId, ignored) { alert("Sorry! We could save the ignore settings for Episode: " + episodeId + " at this time. " + error); } }); +} + +//Episode Renaming +function renameEpisode(id) { + $.ajax({ + type: "POST", + url: renameEpisodeUrl, + data: jQuery.param({ episodeFileId: id }), + error: function (req, status, error) { + alert("Sorry! We could rename " + id + " at this time. " + error); + } + }); +} + +function renameSeason(seriesId, seasonNumber) { + $.ajax({ + type: "POST", + url: renameSeasonUrl, + data: jQuery.param({ seriesId: seriesId, seasonNumber: seasonNumber }), + error: function (req, status, error) { + alert("Sorry! We could rename series: " + seriesId + " season: " + seasonNumber + " at this time. " + error); + } + }); +} + +//Season Search +function searchSeason(seriesId, seasonNumber) { + $.ajax({ + type: "POST", + url: searchSeasonUrl, + data: jQuery.param({ seriesId: seriesId, seasonNumber: seasonNumber }), + error: function (req, status, error) { + alert("Sorry! We could search for series: " + seriesId + " season: " + seasonNumber + " at this time. " + error); + } + }); } \ No newline at end of file diff --git a/NzbDrone.Web/Views/Series/Details.cshtml b/NzbDrone.Web/Views/Series/Details.cshtml index 81dfa4e0c0..1369ccd8ac 100644 --- a/NzbDrone.Web/Views/Series/Details.cshtml +++ b/NzbDrone.Web/Views/Series/Details.cshtml @@ -84,8 +84,11 @@ } - @foreach (var season in Model.Seasons.Where(s => s > 0).Reverse()) + @foreach (var s in Model.Seasons.Where(s => s > 0).Reverse()) { + var seriesId = @Model.SeriesId; + var season = s; +

Season @season

@@ -107,9 +110,10 @@ columns.Bound(c => c.Quality).Width(0); columns.Bound(c => c.Status).Width(0); columns.Bound(o => o.EpisodeId).Title("") - .ClientTemplate("'); return false;\">Search" - + " | " + - "'); return false;\">Rename"); + .ClientTemplate("\" onclick=\"searchForEpisode('<#= EpisodeId #>'); return false;\">Search" + + " | " + + "\" onclick=\"renameEpisode('<#= EpisodeFileId #>'); return false;\">Rename" + ); }) .DetailView(detailView => detailView.ClientTemplate("
<#= Overview #>
<#= Path #>
")) .Sortable(rows => rows.OrderBy(epSort => epSort.Add(c => c.EpisodeNumber).Descending()).Enabled(false)) @@ -118,10 +122,13 @@ d => d.Ajax().Select("_AjaxSeasonGrid", "Series", new RouteValueDictionary { { "seriesId", Model.SeriesId }, { "seasonNumber", season } })) - .ToolBar( - c => - c.Custom().Text("Rename Season").Action("RenameSeason", "Series", new { seasonId = season }) - .ButtonType(GridButtonType.Text)) + .ToolBar(toolbar => toolbar.Template(@ + + )) .ClientEvents(clientEvents => { clientEvents.OnRowDataBound("grid_rowBound"); @@ -171,24 +178,6 @@ } @section Scripts{ } diff --git a/NzbDrone.Web/Views/System/Jobs.cshtml b/NzbDrone.Web/Views/System/Jobs.cshtml index 25d266f33f..61194f6bd3 100644 --- a/NzbDrone.Web/Views/System/Jobs.cshtml +++ b/NzbDrone.Web/Views/System/Jobs.cshtml @@ -1,4 +1,5 @@ @using System.Collections +@using NzbDrone.Web.Models @model IEnumerable @section TitleContent{ Jobs @@ -9,7 +10,9 @@ Jobs Items currently in queue - @{Html.Telerik().Grid((IEnumerable>)ViewData["Queue"]).Name("QueueGrid") - .Columns(c => c.Bound(g => g.Item1).Title("Type").Width(100)).Columns(c => c.Bound(g => g.Item2).Title("Target")) + @{Html.Telerik().Grid((IEnumerable)ViewData["Queue"]).Name("QueueGrid") + .Columns(c => c.Bound(g => g.Name).Title("Type").Width(100)) + .Columns(c => c.Bound(g => g.TargetId).Title("Target")) + .Columns(c => c.Bound(g => g.SecondaryTargetId).Title("Secondary Target")) .Render();} }