diff --git a/NzbDrone.Core.Test/DiskScanJobTest.cs b/NzbDrone.Core.Test/DiskScanJobTest.cs new file mode 100644 index 000000000..1522f70c2 --- /dev/null +++ b/NzbDrone.Core.Test/DiskScanJobTest.cs @@ -0,0 +1,125 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Threading; +using AutoMoq; +using FizzWare.NBuilder; +using MbUnit.Framework; +using Moq; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Providers.Jobs; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class DiskScanJobTest : TestBase + { + [Test] + public void series_specific_scan_should_scan_series() + { + var series = Builder.CreateNew() + .With(s => s.SeriesId = 12) + .With(s => s.Episodes = Builder.CreateListOfSize(10).Build()) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + mocker.GetMock() + .Setup(p => p.GetSeries(series.SeriesId)) + .Returns(series); + + mocker.GetMock() + .Setup(p => p.Scan(series)) + .Returns(new List()); + + + //Act + mocker.Resolve().Start(new ProgressNotification("Test"), series.SeriesId); + + //Assert + mocker.VerifyAllMocks(); + } + + + [Test] + public void series_with_no_episodes_should_skip_scan() + { + var series = Builder.CreateNew() + .With(s => s.SeriesId = 12) + .With(s => s.Episodes = new List()) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + mocker.GetMock() + .Setup(p => p.GetSeries(series.SeriesId)) + .Returns(series); + + //Act + mocker.Resolve().Start(new ProgressNotification("Test"), series.SeriesId); + + //Assert + mocker.VerifyAllMocks(); + } + + [Test] + public void job_with_no_target_should_scan_all_series() + { + var series = Builder.CreateListOfSize(2) + .WhereTheFirst(1).Has(s => s.SeriesId = 12) + .AndTheNext(1).Has(s => s.SeriesId = 15) + .WhereAll().Have(s => s.Episodes = Builder.CreateListOfSize(10).Build()) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + mocker.GetMock() + .Setup(p => p.GetAllSeries()) + .Returns(series.AsQueryable()); + + mocker.GetMock() + .Setup(s => s.Scan(series[0])) + .Returns(new List()); + + mocker.GetMock() + .Setup(s => s.Scan(series[1])) + .Returns(new List()); + + mocker.Resolve().Start(new ProgressNotification("Test"), 0); + + + mocker.VerifyAllMocks(); + } + + [Test] + public void job_with_no_target_should_scan_series_with_episodes() + { + var series = Builder.CreateListOfSize(2) + .WhereTheFirst(1).Has(s => s.SeriesId = 12) + .And(s => s.Episodes = Builder.CreateListOfSize(10).Build()) + .AndTheNext(1).Has(s => s.SeriesId = 15) + .And(s => s.Episodes = new List()) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + mocker.GetMock() + .Setup(p => p.GetAllSeries()) + .Returns(series.AsQueryable()); + + mocker.GetMock() + .Setup(s => s.Scan(series[0])) + .Returns(new List()); + + mocker.Resolve().Start(new ProgressNotification("Test"), 0); + + + mocker.VerifyAllMocks(); + } + } + +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/ImportNewSeriesJobTest.cs b/NzbDrone.Core.Test/ImportNewSeriesJobTest.cs new file mode 100644 index 000000000..b073cd4c3 --- /dev/null +++ b/NzbDrone.Core.Test/ImportNewSeriesJobTest.cs @@ -0,0 +1,140 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Threading; +using AutoMoq; +using FizzWare.NBuilder; +using MbUnit.Framework; +using Moq; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Providers.Jobs; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class ImportNewSeriesJobTest : TestBase + { + [Test] + public void series_specific_scan_should_scan_series() + { + + + var series = Builder.CreateListOfSize(2) + .WhereAll().Have(s => s.Episodes = Builder.CreateListOfSize(10).Build()) + .WhereAll().Have(s => s.LastInfoSync = null) + .WhereTheFirst(1).Has(s => s.SeriesId = 12) + .AndTheNext(1).Has(s => s.SeriesId = 15) + .Build(); + + var notification = new ProgressNotification("Test"); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + mocker.GetMock() + .Setup(p => p.GetAllSeries()) + .Returns(series.AsQueryable()) + .Callback(() => series.ToList().ForEach(c => c.LastInfoSync = DateTime.Now));//Set the last scan time on the collection + + mocker.GetMock() + .Setup(j => j.Start(notification, series[0].SeriesId)); + + mocker.GetMock() + .Setup(j => j.Start(notification, series[1].SeriesId)); + + mocker.GetMock() + .Setup(j => j.Start(notification, series[0].SeriesId)); + + mocker.GetMock() + .Setup(j => j.Start(notification, series[1].SeriesId)); + + + //Act + mocker.Resolve().Start(notification, 0); + + //Assert + mocker.VerifyAllMocks(); + } + + + [Test] + public void series_with_no_episodes_should_skip_scan() + { + var series = Builder.CreateNew() + .With(s => s.SeriesId = 12) + .With(s => s.Episodes = new List()) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + mocker.GetMock() + .Setup(p => p.GetSeries(series.SeriesId)) + .Returns(series); + + //Act + mocker.Resolve().Start(new ProgressNotification("Test"), series.SeriesId); + + //Assert + mocker.VerifyAllMocks(); + } + + [Test] + public void job_with_no_target_should_scan_all_series() + { + var series = Builder.CreateListOfSize(2) + .WhereTheFirst(1).Has(s => s.SeriesId = 12) + .AndTheNext(1).Has(s => s.SeriesId = 15) + .WhereAll().Have(s => s.Episodes = Builder.CreateListOfSize(10).Build()) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + mocker.GetMock() + .Setup(p => p.GetAllSeries()) + .Returns(series.AsQueryable()); + + mocker.GetMock() + .Setup(s => s.Scan(series[0])) + .Returns(new List()); + + mocker.GetMock() + .Setup(s => s.Scan(series[1])) + .Returns(new List()); + + mocker.Resolve().Start(new ProgressNotification("Test"), 0); + + + mocker.VerifyAllMocks(); + } + + [Test] + public void job_with_no_target_should_scan_series_with_episodes() + { + var series = Builder.CreateListOfSize(2) + .WhereTheFirst(1).Has(s => s.SeriesId = 12) + .And(s => s.Episodes = Builder.CreateListOfSize(10).Build()) + .AndTheNext(1).Has(s => s.SeriesId = 15) + .And(s => s.Episodes = new List()) + .Build(); + + var mocker = new AutoMoqer(MockBehavior.Strict); + + mocker.GetMock() + .Setup(p => p.GetAllSeries()) + .Returns(series.AsQueryable()); + + mocker.GetMock() + .Setup(s => s.Scan(series[0])) + .Returns(new List()); + + mocker.Resolve().Start(new ProgressNotification("Test"), 0); + + + mocker.VerifyAllMocks(); + } + } + +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/MediaFileProviderTests.cs b/NzbDrone.Core.Test/MediaFileProviderTests.cs index 1ed614356..d074790c9 100644 --- a/NzbDrone.Core.Test/MediaFileProviderTests.cs +++ b/NzbDrone.Core.Test/MediaFileProviderTests.cs @@ -334,7 +334,7 @@ namespace NzbDrone.Core.Test mocker.GetMock(MockBehavior.Strict) .Setup(c => c.Scan(It.Is(s => s.LastInfoSync != null))).Returns(new List()).Verifiable(); - mocker.Resolve().Start(new ProgressNotification("test"), 0); + mocker.Resolve().Start(new ProgressNotification("test"), 0); mocker.VerifyAllMocks(); } diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 18c77b923..9431fb56f 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -85,6 +85,8 @@ + + diff --git a/NzbDrone.Core/CentralDispatch.cs b/NzbDrone.Core/CentralDispatch.cs index e7a956578..fb5513448 100644 --- a/NzbDrone.Core/CentralDispatch.cs +++ b/NzbDrone.Core/CentralDispatch.cs @@ -131,9 +131,9 @@ namespace NzbDrone.Core private static void BindJobs() { _kernel.Bind().To().InTransientScope(); - _kernel.Bind().To().InTransientScope(); + _kernel.Bind().To().InTransientScope(); _kernel.Bind().To().InTransientScope(); - _kernel.Bind().To().InTransientScope(); + _kernel.Bind().To().InTransientScope(); _kernel.Bind().To().InTransientScope(); _kernel.Get().Initialize(); diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index f49b192ec..6dfb137ab 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -128,17 +128,11 @@ False Libraries\Exceptioneer.WindowsFormsClient.dll - - False ..\packages\Ninject.2.2.1.0\lib\.NetFramework 4.0\Ninject.dll - - False - Libraries\NLog.Extended.dll - False Libraries\SubSonic.Core.dll @@ -178,8 +172,8 @@ - - + + diff --git a/NzbDrone.Core/Providers/Jobs/MediaFileScanJob.cs b/NzbDrone.Core/Providers/Jobs/DiskScanJob.cs similarity index 78% rename from NzbDrone.Core/Providers/Jobs/MediaFileScanJob.cs rename to NzbDrone.Core/Providers/Jobs/DiskScanJob.cs index 5638638ad..2e9029e8d 100644 --- a/NzbDrone.Core/Providers/Jobs/MediaFileScanJob.cs +++ b/NzbDrone.Core/Providers/Jobs/DiskScanJob.cs @@ -7,17 +7,21 @@ using NzbDrone.Core.Repository; namespace NzbDrone.Core.Providers.Jobs { - public class MediaFileScanJob : IJob + public class DiskScanJob : IJob { private readonly SeriesProvider _seriesProvider; private readonly MediaFileProvider _mediaFileProvider; - public MediaFileScanJob(SeriesProvider seriesProvider, MediaFileProvider mediaFileProvider) + public DiskScanJob(SeriesProvider seriesProvider, MediaFileProvider mediaFileProvider) { _seriesProvider = seriesProvider; _mediaFileProvider = mediaFileProvider; } + public DiskScanJob() + { + } + public string Name { get { return "Media File Scan"; } @@ -28,7 +32,7 @@ namespace NzbDrone.Core.Providers.Jobs get { return 60; } } - public void Start(ProgressNotification notification, int targetId) + public virtual void Start(ProgressNotification notification, int targetId) { IList seriesToScan; if (targetId == 0) @@ -40,7 +44,7 @@ namespace NzbDrone.Core.Providers.Jobs seriesToScan = new List() { _seriesProvider.GetSeries(targetId) }; } - foreach (var series in seriesToScan.Where(c => c.LastInfoSync != null)) + foreach (var series in seriesToScan.Where(c => c.Episodes.Count != 0)) { notification.CurrentMessage = string.Format("Scanning disk for '{0}'", series.Title); _mediaFileProvider.Scan(series); diff --git a/NzbDrone.Core/Providers/Jobs/ImportNewSeriesJob.cs b/NzbDrone.Core/Providers/Jobs/ImportNewSeriesJob.cs new file mode 100644 index 000000000..92ec07f87 --- /dev/null +++ b/NzbDrone.Core/Providers/Jobs/ImportNewSeriesJob.cs @@ -0,0 +1,97 @@ +using System; +using System.IO; +using System.Linq; +using NLog; +using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Repository; + +namespace NzbDrone.Core.Providers.Jobs +{ + /// + /// This job processes newly added jobs by downloading their info + /// from TheTVDB.org and doing a disk scan. this job should only + /// + public class ImportNewSeriesJob : IJob + { + private readonly SeriesProvider _seriesProvider; + private readonly MediaFileProvider _mediaFileProvider; + private readonly SeasonProvider _seasonProvider; + private readonly UpdateInfoJob _updateInfoJob; + private readonly DiskScanJob _diskScanJob; + + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + public ImportNewSeriesJob(SeriesProvider seriesProvider, SeasonProvider seasonProvider, + MediaFileProvider mediaFileProvider, UpdateInfoJob updateInfoJob, DiskScanJob diskScanJob) + { + _seriesProvider = seriesProvider; + _mediaFileProvider = mediaFileProvider; + _seasonProvider = seasonProvider; + _updateInfoJob = updateInfoJob; + _diskScanJob = diskScanJob; + } + + public string Name + { + get { return "New Series Update"; } + } + + public int DefaultInterval + { + get { return 1; } + } + + public void Start(ProgressNotification notification, int targetId) + { + ScanSeries(notification); + } + + private void ScanSeries(ProgressNotification notification) + { + var syncList = _seriesProvider.GetAllSeries().Where(s => s.LastInfoSync == null).ToList(); + if (syncList.Count == 0) + { + return; + } + + foreach (var currentSeries in syncList) + { + try + { + notification.CurrentMessage = String.Format("Searching for '{0}'", new DirectoryInfo(currentSeries.Path).Name); + + _updateInfoJob.Start(notification, currentSeries.SeriesId); + _diskScanJob.Start(notification, currentSeries.SeriesId); + + var updatedSeries = _seriesProvider.GetSeries(currentSeries.SeriesId); + AutoIgnoreSeasons(updatedSeries); + + } + catch (Exception e) + { + Logger.ErrorException(e.Message, e); + } + } + + //Keep scanning until there no more shows left. + ScanSeries(notification); + } + + private void AutoIgnoreSeasons(Series updatedSeries) + { + if (_mediaFileProvider.GetSeriesFiles(updatedSeries.SeriesId).Count() != 0) + { + Logger.Debug("Looking for seasons to ignore"); + foreach (var season in updatedSeries.Seasons) + { + if (season.SeasonNumber != updatedSeries.Seasons.Max(s => s.SeasonNumber) && _mediaFileProvider.GetSeasonFiles(season.SeasonId).Count() == 0) + { + Logger.Info("Season {0} of {1} doesn't have any files on disk. season will not be monitored.", season.SeasonNumber, updatedSeries.Title); + season.Monitored = false; + _seasonProvider.SaveSeason(season); + } + } + } + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Jobs/NewSeriesUpdate.cs b/NzbDrone.Core/Providers/Jobs/NewSeriesUpdate.cs deleted file mode 100644 index d0ad46c07..000000000 --- a/NzbDrone.Core/Providers/Jobs/NewSeriesUpdate.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using NLog; -using NzbDrone.Core.Model.Notification; - -namespace NzbDrone.Core.Providers.Jobs -{ - public class NewSeriesUpdate : IJob - { - private readonly SeriesProvider _seriesProvider; - private readonly EpisodeProvider _episodeProvider; - private readonly MediaFileProvider _mediaFileProvider; - private readonly SeasonProvider _seasonProvider; - - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - - public NewSeriesUpdate(SeriesProvider seriesProvider, EpisodeProvider episodeProvider, MediaFileProvider mediaFileProvider, SeasonProvider seasonProvider) - { - _seriesProvider = seriesProvider; - _episodeProvider = episodeProvider; - _mediaFileProvider = mediaFileProvider; - _seasonProvider = seasonProvider; - } - - public string Name - { - get { return "New Series Update"; } - } - - public int DefaultInterval - { - get { return 1; } - } - - public void Start(ProgressNotification notification, int targetId) - { - ScanSeries(notification); - } - - private void ScanSeries(ProgressNotification notification) - { - var syncList = _seriesProvider.GetAllSeries().Where(s => s.LastInfoSync == null).ToList(); - if (syncList.Count == 0) - { - return; - } - - foreach (var currentSeries in syncList) - { - try - { - notification.CurrentMessage = String.Format("Searching for '{0}'", - new DirectoryInfo(currentSeries.Path).Name); - var updatedSeries = _seriesProvider.UpdateSeriesInfo(currentSeries.SeriesId); - - notification.CurrentMessage = String.Format("Downloading episode info for '{0}'", - updatedSeries.Title); - _episodeProvider.RefreshEpisodeInfo(updatedSeries.SeriesId); - - notification.CurrentMessage = String.Format("Scanning disk for '{0}' files", updatedSeries.Title); - _mediaFileProvider.Scan(_seriesProvider.GetSeries(updatedSeries.SeriesId)); - - if (_mediaFileProvider.GetSeriesFiles(currentSeries.SeriesId).Count() != 0) - { - Logger.Debug("Looking for seasons to ignore"); - foreach (var season in updatedSeries.Seasons) - { - if (season.SeasonNumber != updatedSeries.Seasons.Max(s => s.SeasonNumber) && _mediaFileProvider.GetSeasonFiles(season.SeasonId).Count() == 0) - { - Logger.Info("Season {0} of {1} doesn't have any files on disk. season will not be monitored.", season.SeasonNumber, updatedSeries.Title); - season.Monitored = false; - _seasonProvider.SaveSeason(season); - } - } - } - - } - catch (Exception e) - { - Logger.ErrorException(e.Message, e); - } - } - - //Keep scanning until there no more shows left. - ScanSeries(notification); - } - - } -} \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Jobs/UpdateInfoJob.cs b/NzbDrone.Core/Providers/Jobs/UpdateInfoJob.cs index 42a2f6979..4a6bcc3bb 100644 --- a/NzbDrone.Core/Providers/Jobs/UpdateInfoJob.cs +++ b/NzbDrone.Core/Providers/Jobs/UpdateInfoJob.cs @@ -16,6 +16,11 @@ namespace NzbDrone.Core.Providers.Jobs _episodeProvider = episodeProvider; } + public UpdateInfoJob() + { + + } + public string Name { get { return "Update Info"; } @@ -26,7 +31,7 @@ namespace NzbDrone.Core.Providers.Jobs get { return 1440; } //Daily } - public void Start(ProgressNotification notification, int targetId) + public virtual void Start(ProgressNotification notification, int targetId) { IList seriesToUpdate; if (targetId == 0) diff --git a/NzbDrone.Core/Repository/Episode.cs b/NzbDrone.Core/Repository/Episode.cs index 348d8fabe..197ae6ec1 100644 --- a/NzbDrone.Core/Repository/Episode.cs +++ b/NzbDrone.Core/Repository/Episode.cs @@ -40,7 +40,7 @@ namespace NzbDrone.Core.Repository public virtual EpisodeFile EpisodeFile { get; set; } [SubSonicToManyRelation] - public virtual List Histories { get; protected set; } + public virtual IList Histories { get; protected set; } } } \ No newline at end of file diff --git a/NzbDrone.Core/Repository/EpisodeFile.cs b/NzbDrone.Core/Repository/EpisodeFile.cs index 336b19c7f..f2c23a60c 100644 --- a/NzbDrone.Core/Repository/EpisodeFile.cs +++ b/NzbDrone.Core/Repository/EpisodeFile.cs @@ -18,7 +18,7 @@ namespace NzbDrone.Core.Repository public DateTime DateAdded { get; set; } [SubSonicToManyRelation] - public virtual List Episodes { get; private set; } + public virtual IList Episodes { get; private set; } [SubSonicToOneRelation(ThisClassContainsJoinKey = true)] public virtual Series Series { get; private set; } diff --git a/NzbDrone.Core/Repository/Season.cs b/NzbDrone.Core/Repository/Season.cs index 35baf9bf8..bb1561dfd 100644 --- a/NzbDrone.Core/Repository/Season.cs +++ b/NzbDrone.Core/Repository/Season.cs @@ -18,7 +18,7 @@ namespace NzbDrone.Core.Repository public DayOfWeek? LastDiskSync { get; set; } [SubSonicToManyRelation] - public virtual List Episodes { get; set; } + public virtual IList Episodes { get; set; } [SubSonicToOneRelation(ThisClassContainsJoinKey = true)] public virtual Series Series { get; set; } diff --git a/NzbDrone.Core/Repository/Series.cs b/NzbDrone.Core/Repository/Series.cs index d9dff9e3a..09757c92a 100644 --- a/NzbDrone.Core/Repository/Series.cs +++ b/NzbDrone.Core/Repository/Series.cs @@ -36,6 +36,15 @@ namespace NzbDrone.Core.Repository public bool Monitored { get; set; } + + /// + /// Gets or sets a value indicating whether this is hidden. + /// + /// true if hidden; otherwise, false. + /// This field will be used for shows that are pending delete or + /// new series that haven't been successfully imported + public bool Hidden { get; set; } + public virtual int QualityProfileId { get; set; } public bool SeasonFolder { get; set; } @@ -48,12 +57,12 @@ namespace NzbDrone.Core.Repository public virtual QualityProfile QualityProfile { get; set; } [SubSonicToManyRelation] - public virtual List Seasons { get; protected set; } + public virtual IList Seasons { get; protected set; } [SubSonicToManyRelation] - public virtual List Episodes { get; protected set; } + public virtual IList Episodes { get; set; } [SubSonicToManyRelation] - public virtual List EpisodeFiles { get; protected set; } + public virtual IList EpisodeFiles { get; protected set; } } } \ No newline at end of file diff --git a/NzbDrone.Web/Controllers/AddSeriesController.cs b/NzbDrone.Web/Controllers/AddSeriesController.cs index 1b2bf1723..b957ff317 100644 --- a/NzbDrone.Web/Controllers/AddSeriesController.cs +++ b/NzbDrone.Web/Controllers/AddSeriesController.cs @@ -36,7 +36,7 @@ namespace NzbDrone.Web.Controllers [HttpPost] public JsonResult ScanNewSeries() { - _jobProvider.QueueJob(typeof(NewSeriesUpdate)); + _jobProvider.QueueJob(typeof(ImportNewSeriesJob)); return new JsonResult(); } diff --git a/NzbDrone.Web/Controllers/SeriesController.cs b/NzbDrone.Web/Controllers/SeriesController.cs index 0f716c715..f689cdf4d 100644 --- a/NzbDrone.Web/Controllers/SeriesController.cs +++ b/NzbDrone.Web/Controllers/SeriesController.cs @@ -265,7 +265,7 @@ namespace NzbDrone.Web.Controllers public ActionResult SyncEpisodesOnDisk(int seriesId) { //Syncs the episodes on disk for the specified series - _jobProvider.QueueJob(typeof(MediaFileScanJob), seriesId); + _jobProvider.QueueJob(typeof(DiskScanJob), seriesId); return RedirectToAction("Details", new { seriesId }); }