From ec0843658789c94acede0edf9e70c3e72bca7f6d Mon Sep 17 00:00:00 2001 From: "kay.one" Date: Sat, 22 Oct 2011 19:31:28 -0700 Subject: [PATCH] Refactored the shit out of PostDownloadProvider --- NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 3 +- .../ProviderTests/HistoryProviderTest.cs | 4 +- .../ProviderTests/JobProviderTest.cs | 8 +- .../ProviderTests/PostDownloadProviderTest.cs | 339 ------------------ .../PostDownloadProviderFixture.cs | 43 +++ .../ProcessDownloadFixture.cs | 203 +++++++++++ NzbDrone.Core/Model/PostDownloadInfoModel.cs | 15 - NzbDrone.Core/Model/PostDownloadStatusType.cs | 9 +- NzbDrone.Core/NzbDrone.Core.csproj | 1 - NzbDrone.Core/Providers/DiskScanProvider.cs | 13 +- .../Providers/Jobs/PostDownloadScanJob.cs | 29 +- .../Providers/PostDownloadProvider.cs | 230 ++---------- 12 files changed, 322 insertions(+), 575 deletions(-) delete mode 100644 NzbDrone.Core.Test/ProviderTests/PostDownloadProviderTest.cs create mode 100644 NzbDrone.Core.Test/ProviderTests/PostDownloadProviderTests/PostDownloadProviderFixture.cs create mode 100644 NzbDrone.Core.Test/ProviderTests/PostDownloadProviderTests/ProcessDownloadFixture.cs delete mode 100644 NzbDrone.Core/Model/PostDownloadInfoModel.cs diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 2d9d28c4c..218141dcd 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -92,8 +92,9 @@ - + + diff --git a/NzbDrone.Core.Test/ProviderTests/HistoryProviderTest.cs b/NzbDrone.Core.Test/ProviderTests/HistoryProviderTest.cs index cf5185e52..47f8272c8 100644 --- a/NzbDrone.Core.Test/ProviderTests/HistoryProviderTest.cs +++ b/NzbDrone.Core.Test/ProviderTests/HistoryProviderTest.cs @@ -1,6 +1,4 @@ -// ReSharper disable RedundantUsingDirective - -using System; +using System; using System.Linq; using AutoMoq; using FizzWare.NBuilder; diff --git a/NzbDrone.Core.Test/ProviderTests/JobProviderTest.cs b/NzbDrone.Core.Test/ProviderTests/JobProviderTest.cs index 5838f2ec3..348de3750 100644 --- a/NzbDrone.Core.Test/ProviderTests/JobProviderTest.cs +++ b/NzbDrone.Core.Test/ProviderTests/JobProviderTest.cs @@ -104,14 +104,12 @@ namespace NzbDrone.Core.Test.ProviderTests timerProvider.QueueJob(typeof(FakeJob)); Thread.Sleep(1000); timerProvider.QueueJob(typeof(FakeJob)); - Thread.Sleep(1000); + Thread.Sleep(2000); JobProvider.Queue.Should().BeEmpty(); fakeJob.ExecutionCount.Should().Be(2); } [Test] - //This test will confirm that the concurrency checks are rest - //after execution so the job can successfully run. public void no_concurent_jobs() { IList fakeJobs = new List { new SlowJob() }; @@ -127,7 +125,7 @@ namespace NzbDrone.Core.Test.ProviderTests timerProvider.QueueJob(typeof(SlowJob), 3); - Thread.Sleep(10000); + Thread.Sleep(5000); JobProvider.Queue.Should().BeEmpty(); //Asserts are done in ExceptionVerification } @@ -538,7 +536,7 @@ namespace NzbDrone.Core.Test.ProviderTests public void Start(ProgressNotification notification, int targetId, int secondaryTargetId) { Console.WriteLine("Starting Job"); - Thread.Sleep(2000); + Thread.Sleep(1000); ExecutionCount++; Console.WriteLine("Finishing Job"); } diff --git a/NzbDrone.Core.Test/ProviderTests/PostDownloadProviderTest.cs b/NzbDrone.Core.Test/ProviderTests/PostDownloadProviderTest.cs deleted file mode 100644 index 419933984..000000000 --- a/NzbDrone.Core.Test/ProviderTests/PostDownloadProviderTest.cs +++ /dev/null @@ -1,339 +0,0 @@ -// ReSharper disable RedundantUsingDirective - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using AutoMoq; -using FizzWare.NBuilder; -using FluentAssertions; -using Moq; -using NUnit.Framework; -using NzbDrone.Core.Model; -using NzbDrone.Core.Providers; -using NzbDrone.Core.Providers.Core; -using NzbDrone.Core.Repository; -using NzbDrone.Core.Test.Framework; - -namespace NzbDrone.Core.Test.ProviderTests -{ - [TestFixture] - // ReSharper disable InconsistentNaming - public class PostDownloadProviderTest : TestBase - { - [TestCase("_UNPACK_The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.Unpacking, 1)] - [TestCase("_FAILED_The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.Failed, 1)] - [TestCase("_UNPACK_The Office (US) - S01E01E02 - Episode Title", PostDownloadStatusType.Unpacking, 2)] - [TestCase("_FAILED_The Office (US) - S01E01E02 - Episode Title", PostDownloadStatusType.Failed, 2)] - [TestCase("_UNPACK_The Office (US) - Season 01 - Episode Title", PostDownloadStatusType.Unpacking, 10)] - [TestCase("_FAILED_The Office (US) - Season 01 - Episode Title", PostDownloadStatusType.Failed, 10)] - public void ProcessFailedOrUnpackingDownload(string folderName, PostDownloadStatusType postDownloadStatus, int episodeCount) - { - var db = MockLib.GetEmptyDatabase(); - var mocker = new AutoMoqer(); - mocker.SetConstant(db); - - var fakeSeries = Builder.CreateNew() - .With(s => s.SeriesId = 12345) - .With(s => s.CleanTitle = "officeus") - .Build(); - - var fakeEpisodes = Builder.CreateListOfSize(episodeCount) - .All() - .With(c => c.SeriesId = 12345) - .With(c => c.SeasonNumber = 1) - .With(c => c.PostDownloadStatus = PostDownloadStatusType.Unknown) - .Build(); - - var expectedEpisodesNumbers = fakeEpisodes.Select(e => e.EpisodeId).ToList(); - - mocker.GetMock().Setup(s => s.FindSeries("officeus")).Returns(fakeSeries); - mocker.GetMock().Setup(s => s.GetEpisodesByParseResult(It.IsAny(), false)).Returns(fakeEpisodes); - mocker.GetMock().Setup(s => s.GetEpisodesBySeason(12345, 1)).Returns(fakeEpisodes); - mocker.GetMock().Setup( - s => s.SetPostDownloadStatus(expectedEpisodesNumbers, postDownloadStatus)).Verifiable(); - - //Act - mocker.Resolve().ProcessFailedOrUnpackingDownload(new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), folderName)), postDownloadStatus); - - //Assert - mocker.GetMock().Verify(c => c.SetPostDownloadStatus(expectedEpisodesNumbers, postDownloadStatus), Times.Once()); - } - - [Test] - public void ProcessFailedOrUnpackingDownload_Already_Existing_Time_Not_Passed() - { - var mocker = new AutoMoqer(MockBehavior.Strict); - - var path = Path.Combine(Directory.GetCurrentDirectory(), - "_FAILED_The Office (US) - S01E01 - Episode Provider"); - - var postDownloadStatus = PostDownloadStatusType.Failed; - - var postDownloadProvider = new PostDownloadProvider(); - - var model = new PostDownloadInfoModel - { - Name = path, - Status = postDownloadStatus, - Added = DateTime.Now.AddMinutes(-5) - }; - - postDownloadProvider.Add(model); - - //Act - mocker.Resolve().ProcessFailedOrUnpackingDownload(new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), path)), postDownloadStatus); - - //Assert - mocker.VerifyAllMocks(); - postDownloadProvider.Remove(model); - } - - [Test] - public void ProcessFailedOrUnpackingDownload_Invalid_Episode() - { - var mocker = new AutoMoqer(MockBehavior.Strict); - - var path = Path.Combine(Directory.GetCurrentDirectory(), - "_FAILED_The Office (US) - S01E01 - Episode Provider"); - - var postDownloadStatus = PostDownloadStatusType.Failed; - - var fakeSeries = Builder.CreateNew() - .With(s => s.SeriesId = 12345) - .With(s => s.CleanTitle = "officeus") - .Build(); - - mocker.GetMock().Setup(s => s.FindSeries("officeus")).Returns(fakeSeries); - mocker.GetMock().Setup(s => s.GetEpisodesByParseResult(It.IsAny(), false)).Returns(new List()); - mocker.GetMock().Setup(s => s.MoveDirectory(It.IsAny(), It.IsAny())); - - //Act - mocker.Resolve().ProcessFailedOrUnpackingDownload(new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), path)), postDownloadStatus); - - //Assert - ExceptionVerification.ExcpectedWarns(1); - mocker.VerifyAllMocks(); - } - - [Test] - public void ProcessDownload_InvalidSeries() - { - //Setup - var mocker = new AutoMoqer(MockBehavior.Strict); - var di = new DirectoryInfo(@"C:\Test\Unsorted TV\The Office - S01E01 - Episode Title"); - - var newFolder = @"C:\Test\Unsorted TV\_NzbDrone_InvalidSeries_The Office - S01E01 - Episode Title"; - Series nullSeries = null; - - //Act - mocker.GetMock().Setup(s => s.FindSeries("office")).Returns(nullSeries); - mocker.GetMock().Setup(s => s.MoveDirectory(di.FullName, newFolder)); - - mocker.Resolve().ProcessDownload(di); - - //Assert - mocker.VerifyAllMocks(); - ExceptionVerification.ExcpectedWarns(1); - } - - [Test] - public void ProcessDownload_ParseError() - { - //Setup - var mocker = new AutoMoqer(MockBehavior.Strict); - var di = new DirectoryInfo(@"C:\Test\Unsorted TV\The Office - S01E01 - Episode Title"); - - var newFolder = @"C:\Test\Unsorted TV\_NzbDrone_ParseError_The Office - S01E01 - Episode Title"; - - var fakeSeries = Builder.CreateNew() - .With(s => s.Title = "The Office") - .Build(); - - //Act - mocker.GetMock().Setup(s => s.FindSeries("office")).Returns(fakeSeries); - mocker.GetMock().Setup(s => s.MoveDirectory(di.FullName, newFolder)); - mocker.GetMock().Setup(s => s.GetDirectorySize(di.FullName)).Returns(100.Megabytes()); - mocker.GetMock().Setup(s => s.Scan(fakeSeries, di.FullName)).Returns( - new List()); - - mocker.Resolve().ProcessDownload(di); - - //Assert - mocker.VerifyAllMocks(); - ExceptionVerification.ExcpectedWarns(1); - } - - [Test] - public void ProcessDownload_Unknown_Error() - { - //Setup - var mocker = new AutoMoqer(MockBehavior.Strict); - var di = new DirectoryInfo(@"C:\Test\Unsorted TV\The Office - Season 01"); - - var newFolder = @"C:\Test\Unsorted TV\_NzbDrone_The Office - Season 01"; - - var fakeSeries = Builder.CreateNew() - .With(s => s.Title = "The Office") - .Build(); - - var fakeEpisodeFiles = Builder.CreateListOfSize(2) - .All() - .With(f => f.SeriesId = fakeSeries.SeriesId) - .Build().ToList(); - - //Act - mocker.GetMock().Setup(s => s.FindSeries("office")).Returns(fakeSeries); - mocker.GetMock().Setup(s => s.MoveDirectory(di.FullName, newFolder)); - mocker.GetMock().Setup(s => s.GetDirectorySize(di.FullName)).Returns(100.Megabytes()); - mocker.GetMock().Setup(s => s.Scan(fakeSeries, di.FullName)).Returns(fakeEpisodeFiles); - mocker.GetMock().Setup(s => s.MoveEpisodeFile(It.IsAny(), true)).Returns(true); - - mocker.Resolve().ProcessDownload(di); - - //Assert - mocker.VerifyAllMocks(); - ExceptionVerification.ExcpectedWarns(1); - } - - [Test] - public void ProcessDownload_Success() - { - //Setup - var mocker = new AutoMoqer(MockBehavior.Strict); - var di = new DirectoryInfo(@"C:\Test\Unsorted TV\The Office - Season 01"); - - var fakeSeries = Builder.CreateNew() - .With(s => s.Title = "The Office") - .Build(); - - var fakeEpisodeFiles = Builder.CreateListOfSize(2) - .All() - .With(f => f.SeriesId = fakeSeries.SeriesId) - .Build().ToList(); - - //Act - mocker.GetMock().Setup(s => s.FindSeries("office")).Returns(fakeSeries); - mocker.GetMock().Setup(s => s.DeleteFolder(di.FullName, true)); - mocker.GetMock().Setup(s => s.GetDirectorySize(di.FullName)).Returns(1.Megabytes()); - mocker.GetMock().Setup(s => s.Scan(fakeSeries, di.FullName)).Returns(fakeEpisodeFiles); - mocker.GetMock().Setup(s => s.MoveEpisodeFile(It.IsAny(), true)).Returns(true); - - mocker.Resolve().ProcessDownload(di); - - //Assert - mocker.VerifyAllMocks(); - } - - [TestCase("_NzbDrone_InvalidEpisode_The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.InvalidEpisode)] - [TestCase("_NzbDrone_InvalidSeries_The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.InvalidSeries)] - [TestCase("_NzbDrone_ParseError_The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.ParseError)] - [TestCase("_UNPACK_The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.Unpacking)] - [TestCase("_FAILED_The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.Failed)] - [TestCase("_NzbDrone_The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.Unknown)] - [TestCase("The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.NoError)] - public void GetPostDownloadStatusForFolder_should_return_a_proper_match(string folderName, PostDownloadStatusType expectedStatus) - { - //Setup - var mocker = new AutoMoqer(MockBehavior.Strict); - - //Act - var result = mocker.Resolve().GetPostDownloadStatusForFolder(folderName); - - //Assert - result.Should().Be(expectedStatus); - } - - [TestCase("_NzbDrone_InvalidEpisode_", "The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.InvalidSeries)] - [TestCase("_NzbDrone_InvalidSeries_", "The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.InvalidEpisode)] - [TestCase("_NzbDrone_ParseError_", "The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.InvalidSeries)] - [TestCase("_UNPACK_", "The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.InvalidEpisode)] - [TestCase("_FAILED_", "The Office (US) - S01E01 - Title", PostDownloadStatusType.ParseError)] - [TestCase("_NzbDrone_", "The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.ParseError)] - public void GetNewFolderNameWithPostDownloadStatus_should_return_a_string_with_the_error_removing_existing_error(string existingErrorString, string folderName, PostDownloadStatusType postDownloadStatus) - { - //Setup - var mocker = new AutoMoqer(MockBehavior.Strict); - - var di = new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), existingErrorString + folderName)); - var expectedFolderName = String.Format("_NzbDrone_{0}_{1}", postDownloadStatus.ToString(), folderName); - - var expectedResult = Path.Combine(Directory.GetCurrentDirectory(), expectedFolderName); - - //Act - var result = mocker.Resolve().GetNewFolderNameWithPostDownloadStatus(di, postDownloadStatus); - - //Assert - result.Should().Be(expectedResult); - } - - [TestCase("The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.InvalidSeries)] - [TestCase("The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.InvalidEpisode)] - [TestCase("The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.ParseError)] - public void GetNewFolderNameWithPostDownloadStatus_should_return_a_string_with_the_error(string folderName, PostDownloadStatusType postDownloadStatus) - { - //Setup - var mocker = new AutoMoqer(MockBehavior.Strict); - - var di = new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), folderName)); - var expectedFolderName = String.Format("_NzbDrone_{0}_{1}", postDownloadStatus.ToString(), folderName); - - var expectedResult = Path.Combine(Directory.GetCurrentDirectory(), expectedFolderName); - - //Act - var result = mocker.Resolve().GetNewFolderNameWithPostDownloadStatus(di, postDownloadStatus); - - //Assert - result.Should().Be(expectedResult); - } - - [TestCase("_NzbDrone_ParseError_", "The Office (US) - S01E01 - Episode Title")] - [TestCase("", "The Office (US) - S01E01 - Episode Title")] - public void GetNewFolderNameWithPostDownloadStatus_should_return_a_path_with_a_unknown_error(string existingError, string folderName) - { - //Setup - var mocker = new AutoMoqer(MockBehavior.Strict); - - var di = new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), folderName)); - var expectedFolderName = String.Format("_NzbDrone_{0}", folderName); - - var expectedResult = Path.Combine(Directory.GetCurrentDirectory(), expectedFolderName); - - //Act - var result = mocker.Resolve().GetNewFolderNameWithPostDownloadStatus(di, PostDownloadStatusType.Unknown); - - //Assert - result.Should().Be(expectedResult); - } - - [TestCase("_NzbDrone_ParseError_", "The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.NoError)] - [TestCase("", "The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.NoError)] - [TestCase("_NzbDrone_ParseError_", "The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.Processed)] - [TestCase("", "The Office (US) - S01E01 - Episode Title", PostDownloadStatusType.Processed)] - public void GetNewFolderNameWithPostDownloadStatus_should_return_a_path_with_no_error(string existingError, string folderName, PostDownloadStatusType postDownloadStatus) - { - //Setup - var mocker = new AutoMoqer(MockBehavior.Strict); - - var di = new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), folderName)); - var expectedFolderName = folderName; - - var expectedResult = Path.Combine(Directory.GetCurrentDirectory(), expectedFolderName); - - //Act - var result = mocker.Resolve().GetNewFolderNameWithPostDownloadStatus(di, postDownloadStatus); - - //Assert - result.Should().Be(expectedResult); - } - - [TestCase("_NzbDrone_ParseError_The Office (US) - S01E01 - Episode Title", "The Office (US) - S01E01 - Episode Title")] - [TestCase("_Status_The Office (US) - S01E01 - Episode Title", "The Office (US) - S01E01 - Episode Title")] - [TestCase("The Office (US) - S01E01 - Episode Title", "The Office (US) - S01E01 - Episode Title")] - [TestCase("_The Office (US) - S01E01 - Episode Title", "_The Office (US) - S01E01 - Episode Title")] - public void RemoveStatus_should_remove_status_string_from_folder_name(string folderName, string cleanFolderName) - { - PostDownloadProvider.RemoveStatusFromFolderName(folderName).Should().Be(cleanFolderName); - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core.Test/ProviderTests/PostDownloadProviderTests/PostDownloadProviderFixture.cs b/NzbDrone.Core.Test/ProviderTests/PostDownloadProviderTests/PostDownloadProviderFixture.cs new file mode 100644 index 000000000..ba71f228e --- /dev/null +++ b/NzbDrone.Core.Test/ProviderTests/PostDownloadProviderTests/PostDownloadProviderFixture.cs @@ -0,0 +1,43 @@ +// ReSharper disable RedundantUsingDirective + +using System; +using System.IO; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Model; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.ProviderTests +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class PostDownloadProviderFixture : TestBase + { + [TestCase(@"c:\_NzbDrone_InvalidEpisode_Title", @"c:\_UnknownSeries_Title", PostDownloadStatusType.UnknownSeries)] + [TestCase(@"c:\Title", @"c:\_Failed_Title", PostDownloadStatusType.Failed)] + [TestCase(@"c:\Root\Test Title", @"c:\Root\_ParseError_Test Title", PostDownloadStatusType.ParseError)] + public void GetFolderNameWithStatus_should_return_a_string_with_the_error_removing_existing_error(string currentName, string excpectedName, PostDownloadStatusType status) + { + PostDownloadProvider.GetFolderNameWithStatus(new DirectoryInfo(currentName), status).Should().Be( + excpectedName); + } + + [TestCase(PostDownloadStatusType.NoError)] + [ExpectedException(typeof(InvalidOperationException))] + public void GetFolderNameWithStatus_should_throw_if_status_is_not_an_error(PostDownloadStatusType status) + { + PostDownloadProvider.GetFolderNameWithStatus(new DirectoryInfo(TempFolder), status); + } + + + [TestCase("_NzbDrone_ParseError_The Office (US) - S01E01 - Episode Title", "The Office (US) - S01E01 - Episode Title")] + [TestCase("_Status_The Office (US) - S01E01 - Episode Title", "The Office (US) - S01E01 - Episode Title")] + [TestCase("The Office (US) - S01E01 - Episode Title", "The Office (US) - S01E01 - Episode Title")] + [TestCase("_The Office (US) - S01E01 - Episode Title", "_The Office (US) - S01E01 - Episode Title")] + public void RemoveStatus_should_remove_status_string_from_folder_name(string folderName, string cleanFolderName) + { + PostDownloadProvider.RemoveStatusFromFolderName(folderName).Should().Be(cleanFolderName); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/ProviderTests/PostDownloadProviderTests/ProcessDownloadFixture.cs b/NzbDrone.Core.Test/ProviderTests/PostDownloadProviderTests/ProcessDownloadFixture.cs new file mode 100644 index 000000000..c1a13da78 --- /dev/null +++ b/NzbDrone.Core.Test/ProviderTests/PostDownloadProviderTests/ProcessDownloadFixture.cs @@ -0,0 +1,203 @@ +// ReSharper disable InconsistentNaming +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using AutoMoq; +using FizzWare.NBuilder; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Model; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Providers.Core; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.ProviderTests.PostDownloadProviderTests +{ + [TestFixture] + public class ProcessDownloadFixture : TestBase + { + [Test] + public void should_skip_if_folder_is_tagged_and_too_fresh() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + + var droppedFolder = new DirectoryInfo(TempFolder + "\\_test\\"); + droppedFolder.Create(); + + mocker.Resolve().ProcessDownload(droppedFolder); + } + + [Test] + public void should_continue_processing_if_folder_is_tagged_and_not_fresh() + { + var mocker = new AutoMoqer(MockBehavior.Loose); + + var droppedFolder = new DirectoryInfo(TempFolder + "\\_test\\"); + droppedFolder.Create(); + + droppedFolder.LastWriteTime = DateTime.Now.AddMinutes(-2); + + //Act + mocker.GetMock().Setup(s => s.FindSeries(It.IsAny())).Returns(null).Verifiable(); + mocker.Resolve().ProcessDownload(droppedFolder); + + //Assert + mocker.VerifyAllMocks(); + ExceptionVerification.ExcpectedWarns(1); + } + + + [Test] + public void should_search_for_series_using_title_without_status() + { + //Setup + var mocker = new AutoMoqer(MockBehavior.Loose); + var droppedFolder = new DirectoryInfo(@"C:\Test\Unsorted TV\_unpack_The Office - S01E01 - Episode Title"); + + mocker.GetMock().Setup(s => s.FindSeries("office")).Returns(null).Verifiable(); + + //Act + mocker.Resolve().ProcessDownload(droppedFolder); + + //Assert + mocker.VerifyAllMocks(); + ExceptionVerification.ExcpectedWarns(1); + } + + [Test] + public void when_series_isnt_found_folder_should_be_tagged_as_unknown_series() + { + //Setup + var mocker = new AutoMoqer(MockBehavior.Strict); + var droppedFolder = new DirectoryInfo(@"C:\Test\Unsorted TV\The Office - S01E01 - Episode Title"); + + var taggedFolder = @"C:\Test\Unsorted TV\_UnknownSeries_The Office - S01E01 - Episode Title"; + + //Act + mocker.GetMock().Setup(s => s.FindSeries("office")).Returns(null); + mocker.GetMock().Setup(s => s.MoveDirectory(droppedFolder.FullName, taggedFolder)); + + mocker.Resolve().ProcessDownload(droppedFolder); + + //Assert + mocker.VerifyAllMocks(); + ExceptionVerification.ExcpectedWarns(1); + } + + [Test] + public void when_no_files_are_imported_folder_should_be_tagged_with_parse_error() + { + //Setup + var mocker = new AutoMoqer(MockBehavior.Strict); + var droppedFolder = new DirectoryInfo(@"C:\Test\Unsorted TV\The Office - S01E01 - Episode Title"); + + var taggedFolder = @"C:\Test\Unsorted TV\_ParseError_The Office - S01E01 - Episode Title"; + + var fakeSeries = Builder.CreateNew() + .With(s => s.Title = "The Office") + .Build(); + + //Act + mocker.GetMock().Setup(s => s.FindSeries("office")).Returns(fakeSeries); + mocker.GetMock().Setup(s => s.Scan(fakeSeries, droppedFolder.FullName)).Returns(new List()); + mocker.GetMock().Setup(s => s.MoveDirectory(droppedFolder.FullName, taggedFolder)); + mocker.GetMock().Setup(s => s.GetDirectorySize(droppedFolder.FullName)).Returns(100.Megabytes()); + + + mocker.Resolve().ProcessDownload(droppedFolder); + + //Assert + mocker.VerifyAllMocks(); + ExceptionVerification.ExcpectedWarns(1); + } + + + [Test] + public void when_no_file_are_imported_and_folder_size_isnt_small_enought_folder_should_be_tagged_unknown() + { + //Setup + var mocker = new AutoMoqer(MockBehavior.Strict); + var droppedFolder = new DirectoryInfo(@"C:\Test\Unsorted TV\The Office - Season 01"); + + var taggedFolder = PostDownloadProvider.GetFolderNameWithStatus(droppedFolder, PostDownloadStatusType.Unknown); + + var fakeSeries = Builder.CreateNew() + .With(s => s.Title = "The Office") + .Build(); + + var fakeEpisodeFiles = Builder.CreateListOfSize(2) + .All() + .With(f => f.SeriesId = fakeSeries.SeriesId) + .Build().ToList(); + + //Act + mocker.GetMock().Setup(s => s.FindSeries("office")).Returns(fakeSeries); + mocker.GetMock().Setup(s => s.MoveDirectory(droppedFolder.FullName, taggedFolder)); + mocker.GetMock().Setup(s => s.GetDirectorySize(droppedFolder.FullName)).Returns(100.Megabytes()); + mocker.GetMock().Setup(s => s.Scan(fakeSeries, droppedFolder.FullName)).Returns(fakeEpisodeFiles); + mocker.GetMock().Setup(s => s.MoveEpisodeFile(It.IsAny(), true)).Returns(true); + + mocker.Resolve().ProcessDownload(droppedFolder); + + //Assert + mocker.VerifyAllMocks(); + ExceptionVerification.ExcpectedWarns(1); + } + + [Test] + public void when_files_are_imported_and_folder_is_small_enought_dir_should_be_deleted() + { + //Setup + var mocker = new AutoMoqer(MockBehavior.Strict); + var droppedFolder = new DirectoryInfo(@"C:\Test\Unsorted TV\The Office - Season 01"); + + var fakeSeries = Builder.CreateNew() + .With(s => s.Title = "The Office") + .Build(); + + var fakeEpisodeFiles = Builder.CreateListOfSize(2) + .All() + .With(f => f.SeriesId = fakeSeries.SeriesId) + .Build().ToList(); + + mocker.GetMock().Setup(s => s.FindSeries("office")).Returns(fakeSeries); + mocker.GetMock().Setup(s => s.Scan(fakeSeries, droppedFolder.FullName)).Returns(fakeEpisodeFiles); + mocker.GetMock().Setup(s => s.MoveEpisodeFile(It.IsAny(), true)).Returns(true); + mocker.GetMock().Setup(s => s.GetDirectorySize(droppedFolder.FullName)).Returns(1.Megabytes()); + mocker.GetMock().Setup(s => s.DeleteFolder(droppedFolder.FullName, true)); + + //Act + mocker.Resolve().ProcessDownload(droppedFolder); + + //Assert + mocker.VerifyAllMocks(); + } + + [Test] + public void all_imported_files_should_be_moved() + { + //Setup + var mocker = new AutoMoqer(MockBehavior.Loose); + var droppedFolder = new DirectoryInfo(TempFolder); + + var fakeSeries = Builder.CreateNew() + .Build(); + + var fakeEpisodeFiles = Builder.CreateListOfSize(2) + .Build().ToList(); + + mocker.GetMock().Setup(s => s.FindSeries(It.IsAny())).Returns(fakeSeries); + mocker.GetMock().Setup(s => s.Scan(fakeSeries, droppedFolder.FullName)).Returns(fakeEpisodeFiles); + + //Act + mocker.Resolve().ProcessDownload(droppedFolder); + + //Assert + mocker.GetMock().Verify(c => c.MoveEpisodeFile(It.IsAny(), true), + Times.Exactly(fakeEpisodeFiles.Count)); + mocker.VerifyAllMocks(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Model/PostDownloadInfoModel.cs b/NzbDrone.Core/Model/PostDownloadInfoModel.cs deleted file mode 100644 index 572ef529e..000000000 --- a/NzbDrone.Core/Model/PostDownloadInfoModel.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; - -namespace NzbDrone.Core.Model -{ - public class PostDownloadInfoModel - { - public string Name { get; set; } - public DateTime Added { get; set; } - public PostDownloadStatusType Status { get; set; } - } -} diff --git a/NzbDrone.Core/Model/PostDownloadStatusType.cs b/NzbDrone.Core/Model/PostDownloadStatusType.cs index 3094b7772..3d170cd4e 100644 --- a/NzbDrone.Core/Model/PostDownloadStatusType.cs +++ b/NzbDrone.Core/Model/PostDownloadStatusType.cs @@ -2,14 +2,11 @@ { public enum PostDownloadStatusType { - Unknown = 0, Unpacking = 1, Failed = 2, - Processed = 3, - InvalidSeries = 4, - ParseError = 5, - InvalidEpisode = 6, - NoError = 7, + UnknownSeries = 3, + ParseError = 4, + NoError = 5, } } \ No newline at end of file diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index dc5c58ea9..52148e7bd 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -195,7 +195,6 @@ - diff --git a/NzbDrone.Core/Providers/DiskScanProvider.cs b/NzbDrone.Core/Providers/DiskScanProvider.cs index 441fa747a..e0988ce34 100644 --- a/NzbDrone.Core/Providers/DiskScanProvider.cs +++ b/NzbDrone.Core/Providers/DiskScanProvider.cs @@ -60,7 +60,7 @@ namespace NzbDrone.Core.Providers if (!_diskProvider.FolderExists(path)) { - Logger. Warn("Series folder doesn't exist: {0}", path); + Logger.Warn("Series folder doesn't exist: {0}", path); return new List(); } @@ -80,7 +80,9 @@ namespace NzbDrone.Core.Providers { var file = ImportFile(series, filePath); if (file != null) + { importedFiles.Add(file); + } } series.LastDiskSync = DateTime.Now; @@ -153,7 +155,7 @@ namespace NzbDrone.Core.Providers foreach (var ep in episodes) { ep.EpisodeFileId = fileId; - ep.PostDownloadStatus = PostDownloadStatusType.Processed; + ep.PostDownloadStatus = PostDownloadStatusType.NoError; _episodeProvider.UpdateEpisode(ep); Logger.Debug("Linking [{0}] > [{1}]", filePath, ep); } @@ -191,12 +193,15 @@ namespace NzbDrone.Core.Providers parseResult.Series = series; var message = _sabProvider.GetSabTitle(parseResult); - + if (newDownload) + { _externalNotificationProvider.OnDownload(message, series); - + } else + { _externalNotificationProvider.OnRename(message, series); + } return true; } diff --git a/NzbDrone.Core/Providers/Jobs/PostDownloadScanJob.cs b/NzbDrone.Core/Providers/Jobs/PostDownloadScanJob.cs index 47d85f9da..8ace8bf40 100644 --- a/NzbDrone.Core/Providers/Jobs/PostDownloadScanJob.cs +++ b/NzbDrone.Core/Providers/Jobs/PostDownloadScanJob.cs @@ -1,16 +1,25 @@ -using Ninject; +using System; +using NLog; +using Ninject; using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers.Core; namespace NzbDrone.Core.Providers.Jobs { public class PostDownloadScanJob : IJob { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private readonly PostDownloadProvider _postDownloadProvider; + private readonly ConfigProvider _configProvider; + private readonly DiskProvider _diskProvider; [Inject] - public PostDownloadScanJob(PostDownloadProvider postDownloadProvider) + public PostDownloadScanJob(PostDownloadProvider postDownloadProvider,ConfigProvider configProvider, DiskProvider diskProvider) { _postDownloadProvider = postDownloadProvider; + _configProvider = configProvider; + _diskProvider = diskProvider; } public PostDownloadScanJob() @@ -29,7 +38,21 @@ namespace NzbDrone.Core.Providers.Jobs public virtual void Start(ProgressNotification notification, int targetId, int secondaryTargetId) { - _postDownloadProvider.ScanDropFolder(notification); + var dropFolder = _configProvider.SabDropDirectory; + + if (String.IsNullOrWhiteSpace(dropFolder)) + { + Logger.Debug("No drop folder is defined. Skipping."); + return; + } + + if (!_diskProvider.FolderExists(dropFolder)) + { + Logger.Warn("Unable to Scan for New Downloads - folder Doesn't exist: [{0}]", dropFolder); + return; + } + + _postDownloadProvider.ProcessDropFolder(dropFolder); } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/PostDownloadProvider.cs b/NzbDrone.Core/Providers/PostDownloadProvider.cs index ef072a89e..456e2a0d2 100644 --- a/NzbDrone.Core/Providers/PostDownloadProvider.cs +++ b/NzbDrone.Core/Providers/PostDownloadProvider.cs @@ -13,53 +13,27 @@ namespace NzbDrone.Core.Providers { public class PostDownloadProvider { - //Used to perform Post Download Processing (Started by PostDownloadScanJob) - private readonly ConfigProvider _configProvider; private readonly DiskProvider _diskProvider; + private readonly EpisodeProvider _episodeProvider; private readonly DiskScanProvider _diskScanProvider; private readonly SeriesProvider _seriesProvider; - private readonly EpisodeProvider _episodeProvider; private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private static readonly Regex StatusRegex = new Regex(@"^_[\w_]*_", RegexOptions.Compiled); - private static readonly List InfoList = new List(); - [Inject] - public PostDownloadProvider(ConfigProvider configProvider, DiskProvider diskProvider, - DiskScanProvider diskScanProvider, SeriesProvider seriesProvider, - EpisodeProvider episodeProvider) + public PostDownloadProvider(DiskProvider diskProvider, EpisodeProvider episodeProvider, + DiskScanProvider diskScanProvider, SeriesProvider seriesProvider) { - _configProvider = configProvider; _diskProvider = diskProvider; + _episodeProvider = episodeProvider; _diskScanProvider = diskScanProvider; _seriesProvider = seriesProvider; - _episodeProvider = episodeProvider; } public PostDownloadProvider() { - - } - - public virtual void ScanDropFolder(ProgressNotification notification) - { - var dropFolder = _configProvider.SabDropDirectory; - - if (String.IsNullOrWhiteSpace(dropFolder)) - { - Logger.Debug("No drop folder is defined. Skipping."); - return; - } - - if (!_diskProvider.FolderExists(dropFolder)) - { - Logger.Warn("Unable to Scan for New Downloads - folder Doesn't exist: [{0}]", dropFolder); - return; - } - - ProcessDropFolder(dropFolder); } public virtual void ProcessDropFolder(string dropFolder) @@ -68,57 +42,33 @@ namespace NzbDrone.Core.Providers { try { - var subfolderInfo = new DirectoryInfo(subfolder); - - var folderStatus = GetPostDownloadStatusForFolder(subfolderInfo.Name); - - - - if (folderStatus == PostDownloadStatusType.Unpacking) - { - ProcessFailedOrUnpackingDownload(subfolderInfo, PostDownloadStatusType.Unpacking); - Logger.Debug("Folder [{0}] is still being unpacked. skipping.", subfolder); - continue; - } - - if (folderStatus == PostDownloadStatusType.Failed) - { - ProcessFailedOrUnpackingDownload(subfolderInfo, PostDownloadStatusType.Failed); - Logger.Debug("Folder [{0}] is marked as failed. skipping.", subfolder); - continue; - } - - if (folderStatus != PostDownloadStatusType.NoError) - { - //Retry processing on the download - ReProcessDownload(new PostDownloadInfoModel { Name = subfolderInfo.FullName, Status = folderStatus }); - continue; - } - - //Process a successful download - ProcessDownload(subfolderInfo); + ProcessDownload(new DirectoryInfo(subfolder)); } - catch (Exception e) { - Logger.ErrorException("An error has occurred while importing " + subfolder, e); + Logger.ErrorException("An error has occurred while importing folder" + subfolder, e); } } } public virtual void ProcessDownload(DirectoryInfo subfolderInfo) { - //Parse the Folder name - var seriesName = Parser.ParseSeriesName(subfolderInfo.Name); + + if (subfolderInfo.Name.StartsWith("_") && subfolderInfo.LastWriteTimeUtc.AddMinutes(1) > DateTime.UtcNow) + { + Logger.Trace("[{0}] is too fresh. skipping", subfolderInfo.Name); + return; + } + + var seriesName = Parser.ParseSeriesName(RemoveStatusFromFolderName(subfolderInfo.Name)); var series = _seriesProvider.FindSeries(seriesName); if (series == null) { - Logger.Warn("Unable to Import new download [{0}], series doesn't exist in database.", subfolderInfo.FullName); + Logger.Warn("Unable to Import new download [{0}], Can't find matching series in database.", subfolderInfo.Name); //Rename the Directory so it's not processed again. - _diskProvider.MoveDirectory(subfolderInfo.FullName, - GetNewFolderNameWithPostDownloadStatus(subfolderInfo, PostDownloadStatusType.InvalidSeries)); + TagFolder(subfolderInfo, PostDownloadStatusType.UnknownSeries); return; } @@ -127,157 +77,41 @@ namespace NzbDrone.Core.Providers //Delete the folder only if folder is small enough if (_diskProvider.GetDirectorySize(subfolderInfo.FullName) < 10.Megabytes()) + { _diskProvider.DeleteFolder(subfolderInfo.FullName, true); - - //Otherwise rename the folder to say it was already processed once by NzbDrone + } else { + //Otherwise rename the folder to say it was already processed once by NzbDrone if (importedFiles.Count == 0) { - Logger.Warn("Unable to Import new download [{0}], unable to parse episode file(s).", subfolderInfo.FullName); - _diskProvider.MoveDirectory(subfolderInfo.FullName, - GetNewFolderNameWithPostDownloadStatus(subfolderInfo, PostDownloadStatusType.ParseError)); + Logger.Warn("Unable to Import new download [{0}], no importable files were found..", subfolderInfo.Name); + TagFolder(subfolderInfo, PostDownloadStatusType.ParseError); } - - //Unknown Error Importing (Possibly a lesser quality than episode currently on disk) + //Unknown Error Importing (Possibly a lesser quality than episode currently on disk) else { - Logger.Warn("Unable to Import new download [{0}].", subfolderInfo.FullName); - - _diskProvider.MoveDirectory(subfolderInfo.FullName, GetNewFolderNameWithPostDownloadStatus(subfolderInfo, PostDownloadStatusType.Unknown)); - } - } - } - - public virtual void ProcessFailedOrUnpackingDownload(DirectoryInfo directoryInfo, PostDownloadStatusType postDownloadStatus) - { - //Check to see if its already in InfoList, if it is, check if enough time has passed to process - var model = CheckForExisting(directoryInfo.FullName); - - if (model != null) - { - //Process if 30 minutes has passed - if (model.Added > DateTime.Now.AddMinutes(30)) - { - ReProcessDownload(model); - - //If everything processed successfully up until now, remove it from InfoList - Remove(model); + Logger.Warn("Unable to Import new download [{0}].", subfolderInfo.Name); + TagFolder(subfolderInfo, PostDownloadStatusType.Unknown); } - - return; - } - - //Remove the error prefix before processing - var parseResult = Parser.ParseTitle(RemoveStatusFromFolderName(directoryInfo.Name)); - - parseResult.Series = _seriesProvider.FindSeries(parseResult.CleanTitle); - - List episodeIds; - - if (parseResult.EpisodeNumbers.Count == 0 && parseResult.FullSeason) - { - episodeIds = - _episodeProvider.GetEpisodesBySeason(parseResult.Series.SeriesId, parseResult.SeasonNumber) - .Select(e => e.EpisodeId).ToList(); - } - else - episodeIds = _episodeProvider.GetEpisodesByParseResult(parseResult).Select(e => e.EpisodeId).ToList(); - - if (episodeIds.Count == 0) - { - //Mark as InvalidEpisode - Logger.Warn("Unable to Import new download [{0}], no episode(s) found in database.", directoryInfo.FullName); - - var newPath = GetNewFolderNameWithPostDownloadStatus(directoryInfo, PostDownloadStatusType.InvalidEpisode); - _diskProvider.MoveDirectory(directoryInfo.FullName, newPath); - - return; } - - //Set the PostDownloadStatus for all found episodes - _episodeProvider.SetPostDownloadStatus(episodeIds, postDownloadStatus); - - //Add to InfoList for possible later processing - Add(new PostDownloadInfoModel - { - Name = directoryInfo.FullName, - Added = DateTime.Now, - Status = postDownloadStatus - }); - } - - public virtual void ReProcessDownload(PostDownloadInfoModel model) - { - var directoryInfo = new DirectoryInfo(model.Name); - var newName = GetNewFolderNameWithPostDownloadStatus(directoryInfo, PostDownloadStatusType.NoError); - - _diskProvider.MoveDirectory(directoryInfo.FullName, newName); - - directoryInfo = new DirectoryInfo(newName); - - ProcessDownload(directoryInfo); - } - - public void Add(PostDownloadInfoModel model) - { - InfoList.Add(model); - } - - public void Remove(PostDownloadInfoModel model) - { - InfoList.Remove(model); - } - - public PostDownloadInfoModel CheckForExisting(string path) - { - return InfoList.SingleOrDefault(i => i.Name == path); } - public PostDownloadStatusType GetPostDownloadStatusForFolder(string folderName) + public void TagFolder(DirectoryInfo directory, PostDownloadStatusType status) { - if (folderName.StartsWith("_UNPACK_")) - return PostDownloadStatusType.Unpacking; - - if (folderName.StartsWith("_FAILED_")) - return PostDownloadStatusType.Failed; - - foreach (PostDownloadStatusType postDownloadStatusType in Enum.GetValues(typeof(PostDownloadStatusType))) - { - var startsWith = String.Format("_NzbDrone_{0}_", postDownloadStatusType.ToString()); - - if (folderName.StartsWith(startsWith)) - return postDownloadStatusType; - } - - if (folderName.StartsWith("_NzbDrone_")) - return PostDownloadStatusType.Unknown; - - return PostDownloadStatusType.NoError; + _diskProvider.MoveDirectory(directory.FullName, GetFolderNameWithStatus(directory, status)); } - public string GetNewFolderNameWithPostDownloadStatus(DirectoryInfo directoryInfo, PostDownloadStatusType postDownloadStatus) + public static string GetFolderNameWithStatus(DirectoryInfo directoryInfo, PostDownloadStatusType status) { - var existingError = GetPostDownloadStatusForFolder(directoryInfo.Name); - var newFolderName = directoryInfo.Name; - var error = String.Format("_NzbDrone_{0}_", postDownloadStatus.ToString()); - - if (existingError != PostDownloadStatusType.NoError) - newFolderName = RemoveStatusFromFolderName(directoryInfo.Name); - - if (postDownloadStatus == PostDownloadStatusType.Unknown) - error = "_NzbDrone_"; - - if (postDownloadStatus == PostDownloadStatusType.NoError) - error = String.Empty; + if (status == PostDownloadStatusType.NoError) + throw new InvalidOperationException("Can't tag a folder with a None-error status. " + status); - if (postDownloadStatus == PostDownloadStatusType.Processed) - error = String.Empty; + var cleanName = RemoveStatusFromFolderName(directoryInfo.Name); - var parent = directoryInfo.Parent.FullName; - var newName = error + newFolderName; + var newName = string.Format("_{0}_{1}", status, cleanName); - return Path.Combine(parent, newName); + return Path.Combine(directoryInfo.Parent.FullName, newName); } public static string RemoveStatusFromFolderName(string folderName)