From 5098ea32493abc0749e16a9c0292f70a680abae0 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 11 Oct 2011 20:44:19 -0700 Subject: [PATCH] Added two new Episode Statuses - Unpacking and Failed. Tests added to support new Statuses. PostDownloadScanJob will update PostDownloadStatus for failed or unpacking. ImportFile will set the PostDownloadStatus to Processed when added to the database. --- NzbDrone.Core.Test/EpisodeProviderTest.cs | 38 ++++++++++ ...deProviderTest_GetEpisodesByParseResult.cs | 65 +++++++++++++++++- NzbDrone.Core.Test/EpisodeStatusTest.cs | 22 ++++++ NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 2 +- .../Datastore/Migrations/Migration20111011.cs | 21 ++++++ NzbDrone.Core/Model/EpisodeStatusType.cs | 16 ++++- NzbDrone.Core/Model/PostDownloadStatusType.cs | 25 +++++++ NzbDrone.Core/NzbDrone.Core.csproj | 2 + NzbDrone.Core/Providers/DiskScanProvider.cs | 2 + NzbDrone.Core/Providers/EpisodeProvider.cs | 23 +++++++ .../Providers/Jobs/PostDownloadScanJob.cs | 8 ++- NzbDrone.Core/Repository/Episode.cs | 18 +++-- NzbDrone.Web/Content/Images/Failed.png | Bin 0 -> 3931 bytes NzbDrone.Web/Content/Images/Unpacking.png | Bin 0 -> 4178 bytes NzbDrone.Web/NzbDrone.Web.csproj | 2 + 15 files changed, 234 insertions(+), 10 deletions(-) create mode 100644 NzbDrone.Core/Datastore/Migrations/Migration20111011.cs create mode 100644 NzbDrone.Core/Model/PostDownloadStatusType.cs create mode 100644 NzbDrone.Web/Content/Images/Failed.png create mode 100644 NzbDrone.Web/Content/Images/Unpacking.png diff --git a/NzbDrone.Core.Test/EpisodeProviderTest.cs b/NzbDrone.Core.Test/EpisodeProviderTest.cs index e48aa22d7..3d3112305 100644 --- a/NzbDrone.Core.Test/EpisodeProviderTest.cs +++ b/NzbDrone.Core.Test/EpisodeProviderTest.cs @@ -7,6 +7,7 @@ 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; @@ -1464,5 +1465,42 @@ namespace NzbDrone.Core.Test //Assert result.Should().BeFalse(); } + + [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) + { + 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(10) + .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(folderName, postDownloadStatus); + + //Assert + var result = db.Fetch(); + result.Where(e => e.PostDownloadStatus == postDownloadStatus).Count().Should().Be(episodeCount); + } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/EpisodeProviderTest_GetEpisodesByParseResult.cs b/NzbDrone.Core.Test/EpisodeProviderTest_GetEpisodesByParseResult.cs index 70c61f288..1b7e5648f 100644 --- a/NzbDrone.Core.Test/EpisodeProviderTest_GetEpisodesByParseResult.cs +++ b/NzbDrone.Core.Test/EpisodeProviderTest_GetEpisodesByParseResult.cs @@ -101,7 +101,6 @@ namespace NzbDrone.Core.Test db.Fetch().Should().HaveCount(1); } - [Test] public void Multi_GetSeason_Episode_Exists() { @@ -235,5 +234,69 @@ namespace NzbDrone.Core.Test db.Fetch().Should().HaveCount(2); ep.First().Ignored.Should().BeFalse(); } + + [Test] + public void Full_Season_return_all_episodes_for_season() + { + var mocker = new AutoMoqer(); + var db = MockLib.GetEmptyDatabase(); + mocker.SetConstant(db); + + var fakeSeries = Builder.CreateNew().Build(); + + var fakeEpisodes = Builder.CreateListOfSize(10) + .WhereAll() + .Have(e => e.SeriesId = fakeSeries.SeriesId) + .Have(e => e.SeasonNumber = 2) + .Build(); + + db.Insert(fakeSeries); + db.InsertMany(fakeEpisodes); + + var parseResult = new EpisodeParseResult + { + Series = fakeSeries, + SeasonNumber = 2, + EpisodeNumbers = new List(), + FullSeason = true + }; + + var ep = mocker.Resolve().GetEpisodesByParseResult(parseResult); + + ep.Should().HaveCount(10); + db.Fetch().Should().HaveCount(10); + } + + [Test] + public void No_Episodes_Not_a_proper_full_season_release() + { + var mocker = new AutoMoqer(); + var db = MockLib.GetEmptyDatabase(); + mocker.SetConstant(db); + + var fakeSeries = Builder.CreateNew().Build(); + + var fakeEpisodes = Builder.CreateListOfSize(10) + .WhereAll() + .Have(e => e.SeriesId = fakeSeries.SeriesId) + .Have(e => e.SeasonNumber = 2) + .Build(); + + db.Insert(fakeSeries); + db.InsertMany(fakeEpisodes); + + var parseResult = new EpisodeParseResult + { + Series = fakeSeries, + SeasonNumber = 2, + EpisodeNumbers = new List(), + FullSeason = false + }; + + var ep = mocker.Resolve().GetEpisodesByParseResult(parseResult); + + ep.Should().HaveCount(0); + db.Fetch().Should().HaveCount(10); + } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/EpisodeStatusTest.cs b/NzbDrone.Core.Test/EpisodeStatusTest.cs index 8d498a1ac..c0a6adfac 100644 --- a/NzbDrone.Core.Test/EpisodeStatusTest.cs +++ b/NzbDrone.Core.Test/EpisodeStatusTest.cs @@ -108,5 +108,27 @@ namespace NzbDrone.Core.Test episode.Status.Should().Be(EpisodeStatusType.NotAired); } + + [TestCase(false, false, EpisodeStatusType.Failed, PostDownloadStatusType.Failed)] + [TestCase(false, false, EpisodeStatusType.Unpacking, PostDownloadStatusType.Unpacking)] + [TestCase(true, false, EpisodeStatusType.Ready, PostDownloadStatusType.Failed)] + [TestCase(true, true, EpisodeStatusType.Ready, PostDownloadStatusType.Unpacking)] + public void episode_downloaded_post_download_status_is_used(bool hasEpisodes, bool ignored, + EpisodeStatusType status, PostDownloadStatusType postDownloadStatus) + { + Episode episode = Builder.CreateNew() + .With(e => e.Ignored = ignored) + .With(e => e.EpisodeFileId = 0) + .With(e => e.GrabDate = DateTime.Now.AddHours(22)) + .With(e => e.PostDownloadStatus = postDownloadStatus) + .Build(); + + if (hasEpisodes) + { + episode.EpisodeFileId = 12; + } + + Assert.AreEqual(status, episode.Status); + } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index dd19dde3b..dc45e4208 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -21,7 +21,7 @@ DEBUG;TRACE prompt 4 - AnyCPU + x86 pdbonly diff --git a/NzbDrone.Core/Datastore/Migrations/Migration20111011.cs b/NzbDrone.Core/Datastore/Migrations/Migration20111011.cs new file mode 100644 index 000000000..0e8d97a8c --- /dev/null +++ b/NzbDrone.Core/Datastore/Migrations/Migration20111011.cs @@ -0,0 +1,21 @@ +using System; +using System.Data; +using Migrator.Framework; + +namespace NzbDrone.Core.Datastore.Migrations +{ + + [Migration(20111011)] + public class Migration20111011 : Migration + { + public override void Up() + { + Database.AddColumn("Episodes", "PostDownloadStatus", DbType.Int32, ColumnProperty.Null); + } + + public override void Down() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Model/EpisodeStatusType.cs b/NzbDrone.Core/Model/EpisodeStatusType.cs index 345706da6..db98c54bb 100644 --- a/NzbDrone.Core/Model/EpisodeStatusType.cs +++ b/NzbDrone.Core/Model/EpisodeStatusType.cs @@ -5,7 +5,7 @@ /// /// Episode has not aired yet /// - NotAired , + NotAired, /// /// Episode is ignored @@ -16,12 +16,22 @@ /// Episode has aired but no episode /// files have avilable /// - Missing , + Missing, /// /// Episode is being downloaded /// - Downloading , + Downloading, + + /// + /// Episode has been downloaded and is unpacking (_UNPACK_) + /// + Unpacking, + + /// + /// Episode has failed to download properly (_FAILED_) + /// + Failed, /// /// Episode is present in disk diff --git a/NzbDrone.Core/Model/PostDownloadStatusType.cs b/NzbDrone.Core/Model/PostDownloadStatusType.cs new file mode 100644 index 000000000..a330c8954 --- /dev/null +++ b/NzbDrone.Core/Model/PostDownloadStatusType.cs @@ -0,0 +1,25 @@ +namespace NzbDrone.Core.Model +{ + public enum PostDownloadStatusType + { + /// + /// Unknown (Default) + /// + Unknown = 0, + + /// + /// Unpacking + /// + Unpacking = 1, + + /// + /// Failed + /// + Failed = 2, + + /// + /// Processed + /// + Processed = 3 + } +} \ No newline at end of file diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 5a958d2e4..a935d6dfb 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -176,6 +176,7 @@ + @@ -192,6 +193,7 @@ + diff --git a/NzbDrone.Core/Providers/DiskScanProvider.cs b/NzbDrone.Core/Providers/DiskScanProvider.cs index 3348910f3..070d2ad9a 100644 --- a/NzbDrone.Core/Providers/DiskScanProvider.cs +++ b/NzbDrone.Core/Providers/DiskScanProvider.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using Ninject; using NLog; +using NzbDrone.Core.Model; using NzbDrone.Core.Providers.Core; using NzbDrone.Core.Repository; using PetaPoco; @@ -146,6 +147,7 @@ namespace NzbDrone.Core.Providers foreach (var ep in episodes) { ep.EpisodeFileId = fileId; + ep.PostDownloadStatus = PostDownloadStatusType.Processed; _episodeProvider.UpdateEpisode(ep); Logger.Debug("Linking [{0}] > [{1}]", filePath, ep); } diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index eac5ef7fe..676f1c144 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -125,6 +125,14 @@ namespace NzbDrone.Core.Providers { var result = new List(); + if (parseResult.EpisodeNumbers.Count == 0 && parseResult.FullSeason) + { + result.AddRange(GetEpisodesBySeason(parseResult.Series.SeriesId, parseResult.SeasonNumber)); + + //Return now as no further processing is required + return result; + } + foreach (var episodeNumber in parseResult.EpisodeNumbers) { var episodeInfo = GetEpisode(parseResult.Series.SeriesId, parseResult.SeasonNumber, episodeNumber); @@ -396,5 +404,20 @@ namespace NzbDrone.Core.Providers Logger.Trace("Finished deleting invalid episodes for {0}", series.SeriesId); } + + public virtual void SetPostDownloadStatus(string folderName, PostDownloadStatusType postDownloadStatus) + { + var parseResult = Parser.ParseTitle(folderName); + parseResult.Series = _seriesProvider.FindSeries(parseResult.CleanTitle); + + var episodeIds = GetEpisodesByParseResult(parseResult).Select(e => e.EpisodeId); + var episodeIdString = String.Join(", ", episodeIds); + + var episodeIdQuery = String.Format(@"UPDATE Episodes SET PostDownloadStatus = {0} + WHERE EpisodeId IN ({1})", (int)postDownloadStatus, episodeIdString); + + Logger.Trace("Updating PostDownloadStatus for all episodeIds in {0}", episodeIdString); + _database.Execute(episodeIdQuery); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Jobs/PostDownloadScanJob.cs b/NzbDrone.Core/Providers/Jobs/PostDownloadScanJob.cs index 97e5feb6d..3d393058f 100644 --- a/NzbDrone.Core/Providers/Jobs/PostDownloadScanJob.cs +++ b/NzbDrone.Core/Providers/Jobs/PostDownloadScanJob.cs @@ -2,6 +2,7 @@ using System.IO; using Ninject; using NLog; +using NzbDrone.Core.Model; using NzbDrone.Core.Model.Notification; using NzbDrone.Core.Providers.Core; @@ -13,16 +14,19 @@ namespace NzbDrone.Core.Providers.Jobs private readonly DiskProvider _diskProvider; private readonly DiskScanProvider _diskScanProvider; private readonly SeriesProvider _seriesProvider; + private readonly EpisodeProvider _episodeProvider; private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); [Inject] public PostDownloadScanJob(ConfigProvider configProvider, DiskProvider diskProvider, - DiskScanProvider diskScanProvider, SeriesProvider seriesProvider) + DiskScanProvider diskScanProvider, SeriesProvider seriesProvider, + EpisodeProvider episodeProvider) { _configProvider = configProvider; _diskProvider = diskProvider; _diskScanProvider = diskScanProvider; _seriesProvider = seriesProvider; + _episodeProvider = episodeProvider; } public PostDownloadScanJob() @@ -63,12 +67,14 @@ namespace NzbDrone.Core.Providers.Jobs if (subfolderInfo.Name.StartsWith("_UNPACK_", StringComparison.CurrentCultureIgnoreCase)) { + _episodeProvider.SetPostDownloadStatus(subfolderInfo.Name.Substring(8), PostDownloadStatusType.Unpacking); Logger.Debug("Folder [{0}] is still being unpacked. skipping.", subfolder); continue; } if (subfolderInfo.Name.StartsWith("_FAILED_", StringComparison.CurrentCultureIgnoreCase)) { + _episodeProvider.SetPostDownloadStatus(subfolderInfo.Name.Substring(8), PostDownloadStatusType.Failed); Logger.Debug("Folder [{0}] is marked as failed. skipping.", subfolder); continue; } diff --git a/NzbDrone.Core/Repository/Episode.cs b/NzbDrone.Core/Repository/Episode.cs index 12dc398d3..2ad2562ca 100644 --- a/NzbDrone.Core/Repository/Episode.cs +++ b/NzbDrone.Core/Repository/Episode.cs @@ -23,6 +23,8 @@ namespace NzbDrone.Core.Repository public Boolean Ignored { get; set; } + public PostDownloadStatusType PostDownloadStatus { get; set; } + /// /// Gets or sets the grab date. /// @@ -39,15 +41,23 @@ namespace NzbDrone.Core.Repository { if (EpisodeFileId != 0) return EpisodeStatusType.Ready; - if (GrabDate != null && GrabDate.Value.AddDays(1) >= DateTime.Now) + if (GrabDate != null) { - return EpisodeStatusType.Downloading; + if (PostDownloadStatus == PostDownloadStatusType.Unpacking) + return EpisodeStatusType.Unpacking; + + if (PostDownloadStatus == PostDownloadStatusType.Failed) + return EpisodeStatusType.Failed; + + if (GrabDate.Value.AddDays(1) >= DateTime.Now) + return EpisodeStatusType.Downloading; } + if (GrabDate != null && GrabDate.Value.AddDays(1) >= DateTime.Now) + return EpisodeStatusType.Downloading; + if (AirDate != null && AirDate.Value.Date < DateTime.Now) - { return EpisodeStatusType.Missing; - } return EpisodeStatusType.NotAired; } diff --git a/NzbDrone.Web/Content/Images/Failed.png b/NzbDrone.Web/Content/Images/Failed.png new file mode 100644 index 0000000000000000000000000000000000000000..0da8602713314fc239b4baef14a10db58a51ab37 GIT binary patch literal 3931 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 literal 0 HcmV?d00001 diff --git a/NzbDrone.Web/Content/Images/Unpacking.png b/NzbDrone.Web/Content/Images/Unpacking.png new file mode 100644 index 0000000000000000000000000000000000000000..7ed2b590512c024a8aec73a4076fdbc063e4bb26 GIT binary patch literal 4178 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 QsdWRoC_~N0n|2ZZ2S|O3&;S4c literal 0 HcmV?d00001 diff --git a/NzbDrone.Web/NzbDrone.Web.csproj b/NzbDrone.Web/NzbDrone.Web.csproj index 39eb8d0f4..1068453a3 100644 --- a/NzbDrone.Web/NzbDrone.Web.csproj +++ b/NzbDrone.Web/NzbDrone.Web.csproj @@ -333,6 +333,7 @@ + @@ -340,6 +341,7 @@ +