diff --git a/NzbDrone.Core.Test/EpisodeProviderTest.cs b/NzbDrone.Core.Test/EpisodeProviderTest.cs index 5a15aa203..d3ba4a556 100644 --- a/NzbDrone.Core.Test/EpisodeProviderTest.cs +++ b/NzbDrone.Core.Test/EpisodeProviderTest.cs @@ -1,6 +1,7 @@ // ReSharper disable RedundantUsingDirective using System; using System.Collections.Generic; +using System.Data.SqlServerCe; using System.Linq; using AutoMoq; using FizzWare.NBuilder; @@ -1502,5 +1503,74 @@ namespace NzbDrone.Core.Test var result = db.Fetch(); result.Where(e => e.PostDownloadStatus == postDownloadStatus).Count().Should().Be(episodeCount); } + + [Test] + public void SetPostDownloadStatus_Invalid_EpisodeId() + { + var db = MockLib.GetEmptyDatabase(); + var mocker = new AutoMoqer(); + mocker.SetConstant(db); + + var postDownloadStatus = PostDownloadStatusType.Failed; + + var fakeSeries = Builder.CreateNew() + .With(s => s.SeriesId = 12345) + .With(s => s.CleanTitle = "officeus") + .Build(); + + var fakeEpisodes = Builder.CreateListOfSize(1) + .WhereAll() + .Have(c => c.SeriesId = 12345) + .Have(c => c.SeasonNumber = 1) + .Have(c => c.PostDownloadStatus = PostDownloadStatusType.Unknown) + .Build(); + + db.Insert(fakeSeries); + db.InsertMany(fakeEpisodes); + + mocker.GetMock().Setup(s => s.FindSeries("officeus")).Returns(fakeSeries); + + //Act + mocker.Resolve().SetPostDownloadStatus(new List{300}, postDownloadStatus); + + //Assert + var result = db.Fetch(); + result.Where(e => e.PostDownloadStatus == postDownloadStatus).Count().Should().Be(0); + } + + [Test] + [ExpectedException(typeof(SqlCeException))] + public void SetPostDownloadStatus_No_EpisodeId_In_Database() + { + var db = MockLib.GetEmptyDatabase(); + var mocker = new AutoMoqer(); + mocker.SetConstant(db); + + var postDownloadStatus = PostDownloadStatusType.Failed; + + var fakeSeries = Builder.CreateNew() + .With(s => s.SeriesId = 12345) + .With(s => s.CleanTitle = "officeus") + .Build(); + + var fakeEpisodes = Builder.CreateListOfSize(1) + .WhereAll() + .Have(c => c.SeriesId = 12345) + .Have(c => c.SeasonNumber = 1) + .Have(c => c.PostDownloadStatus = PostDownloadStatusType.Unknown) + .Build(); + + db.Insert(fakeSeries); + db.InsertMany(fakeEpisodes); + + mocker.GetMock().Setup(s => s.FindSeries("officeus")).Returns(fakeSeries); + + //Act + mocker.Resolve().SetPostDownloadStatus(new List(), postDownloadStatus); + + //Assert + var result = db.Fetch(); + result.Where(e => e.PostDownloadStatus == postDownloadStatus).Count().Should().Be(0); + } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/PostDownloadProviderTest.cs b/NzbDrone.Core.Test/PostDownloadProviderTest.cs index 5ad8fdd66..67e9eb735 100644 --- a/NzbDrone.Core.Test/PostDownloadProviderTest.cs +++ b/NzbDrone.Core.Test/PostDownloadProviderTest.cs @@ -23,13 +23,13 @@ namespace NzbDrone.Core.Test // ReSharper disable InconsistentNaming public class PostDownloadProviderTest : TestBase { - [TestCase("The Office (US) - S01E05 - Episode Title", PostDownloadStatusType.Unpacking, 1)] - [TestCase("The Office (US) - S01E05 - Episode Title", PostDownloadStatusType.Failed, 1)] - [TestCase("The Office (US) - S01E05E06 - Episode Title", PostDownloadStatusType.Unpacking, 2)] - [TestCase("The Office (US) - S01E05E06 - Episode Title", PostDownloadStatusType.Failed, 2)] - [TestCase("The Office (US) - Season 01 - Episode Title", PostDownloadStatusType.Unpacking, 10)] - [TestCase("The Office (US) - Season 01 - Episode Title", PostDownloadStatusType.Failed, 10)] - public void SetPostDownloadStatus(string folderName, PostDownloadStatusType postDownloadStatus, int episodeCount) + [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(); @@ -40,24 +40,78 @@ namespace NzbDrone.Core.Test .With(s => s.CleanTitle = "officeus") .Build(); - var fakeEpisodes = Builder.CreateListOfSize(10) + var fakeEpisodes = Builder.CreateListOfSize(episodeCount) .WhereAll() .Have(c => c.SeriesId = 12345) .Have(c => c.SeasonNumber = 1) .Have(c => c.PostDownloadStatus = PostDownloadStatusType.Unknown) .Build(); - db.Insert(fakeSeries); - db.InsertMany(fakeEpisodes); + var expectedEpisodesNumbers = fakeEpisodes.Select(e => e.EpisodeId); 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().SetPostDownloadStatus(folderName, postDownloadStatus); + mocker.Resolve().ProcessFailedOrUnpackingDownload(new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), folderName)),postDownloadStatus); //Assert - var result = db.Fetch(); - result.Where(e => e.PostDownloadStatus == postDownloadStatus).Count().Should().Be(episodeCount); + 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(); + postDownloadProvider.Add(new PostDownloadInfoModel + { + Name = path, + Status = postDownloadStatus, + Added = DateTime.Now.AddMinutes(-5) + }); + + //Act + mocker.Resolve().ProcessFailedOrUnpackingDownload(new DirectoryInfo(Path.Combine(Directory.GetCurrentDirectory(), path)), postDownloadStatus); + + //Assert + mocker.VerifyAllMocks(); + } + + [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(); } [TestCase(PostDownloadStatusType.Unpacking, 8)] diff --git a/NzbDrone.Core/Model/PostDownloadStatusType.cs b/NzbDrone.Core/Model/PostDownloadStatusType.cs index ce5a5d4ca..6389159d6 100644 --- a/NzbDrone.Core/Model/PostDownloadStatusType.cs +++ b/NzbDrone.Core/Model/PostDownloadStatusType.cs @@ -30,6 +30,11 @@ /// /// ParseError /// - ParseError = 5 + ParseError = 5, + + /// + /// InvalidEpisode + /// + InvalidEpisode = 6, } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/PostDownloadProvider.cs b/NzbDrone.Core/Providers/PostDownloadProvider.cs index 3d964dd6f..b20cff45c 100644 --- a/NzbDrone.Core/Providers/PostDownloadProvider.cs +++ b/NzbDrone.Core/Providers/PostDownloadProvider.cs @@ -36,7 +36,7 @@ namespace NzbDrone.Core.Providers _episodeProvider = episodeProvider; } - PostDownloadProvider() + public PostDownloadProvider() { } @@ -157,27 +157,24 @@ namespace NzbDrone.Core.Providers 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 - if (InfoList.Any(i => i.Name == directoryInfo.FullName)) - { - var model = InfoList.Single(i => i.Name == directoryInfo.FullName); + 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 - InfoList.Remove(model); + //If everything processed successfully up until now, remove it from InfoList + Remove(model); + } + return; } - //Add to InfoList for possible later processing - InfoList.Add(new PostDownloadInfoModel{ Name = directoryInfo.FullName, - Added = DateTime.Now, - Status = postDownloadStatus - }); - - //Remove the first 8 characters of the folder name (removes _UNPACK_ or _FAILED_) before processing - var parseResult = Parser.ParseTitle(directoryInfo.Name.Substring(8)); + //Remove the error prefix before processing + var parseResult = Parser.ParseTitle(directoryInfo.Name.Substring(GetPrefixLength(postDownloadStatus))); parseResult.Series = _seriesProvider.FindSeries(parseResult.CleanTitle); var episodeIds = new List(); @@ -190,7 +187,27 @@ namespace NzbDrone.Core.Providers 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); + _diskProvider.MoveDirectory(directoryInfo.FullName, + Path.Combine(directoryInfo.Parent.FullName, + "_NzbDrone_InvalidEpisode_" + directoryInfo.Name.Substring(GetPrefixLength(postDownloadStatus)))); + + 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) @@ -226,5 +243,20 @@ namespace NzbDrone.Core.Providers //Default to zero return 0; } + + 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); + } } }