From 560706ae8f6d7f8e4bbdefec1128f880b125f835 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 6 Jun 2011 23:29:07 -0700 Subject: [PATCH] Fixed Renaming of episodes to include extension. PostDownload Importing setup, need to setup job for period scan. --- NzbDrone.Core.Test/MediaFileProviderTests.cs | 120 +++++- ...> MediaFileProvider_GetNewFilenameTest.cs} | 152 +------- ...MediaFileProvider_ImportNewDownloadTest.cs | 346 ++++++++++++++++++ NzbDrone.Core.Test/NzbDrone.Core.Test.csproj | 3 +- NzbDrone.Core/CentralDispatch.cs | 1 - NzbDrone.Core/NzbDrone.Core.csproj | 2 +- .../Providers/Core/ConfigProvider.cs | 7 + NzbDrone.Core/Providers/Core/DiskProvider.cs | 5 + NzbDrone.Core/Providers/EpisodeProvider.cs | Bin 10953 -> 11661 bytes .../Providers/Jobs/RenameEpisodeJob.cs | 16 +- NzbDrone.Core/Providers/MediaFileProvider.cs | 217 ++++++++++- .../Providers/PostProcessingProvider.cs | 86 +++++ NzbDrone.Core/Providers/RenameProvider.cs | 157 -------- NzbDrone.Core/Repository/EpisodeFile.cs | 4 +- NzbDrone.Web/Controllers/SeriesController.cs | 3 - 15 files changed, 807 insertions(+), 312 deletions(-) rename NzbDrone.Core.Test/{RenameProviderTest.cs => MediaFileProvider_GetNewFilenameTest.cs} (64%) create mode 100644 NzbDrone.Core.Test/MediaFileProvider_ImportNewDownloadTest.cs create mode 100644 NzbDrone.Core/Providers/PostProcessingProvider.cs delete mode 100644 NzbDrone.Core/Providers/RenameProvider.cs diff --git a/NzbDrone.Core.Test/MediaFileProviderTests.cs b/NzbDrone.Core.Test/MediaFileProviderTests.cs index eb5417aa7..0e9db08fe 100644 --- a/NzbDrone.Core.Test/MediaFileProviderTests.cs +++ b/NzbDrone.Core.Test/MediaFileProviderTests.cs @@ -1,6 +1,7 @@ // ReSharper disable RedundantUsingDirective using System; using System.Collections.Generic; +using System.IO; using System.Linq.Expressions; using System.Linq; using AutoMoq; @@ -8,6 +9,7 @@ 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.Core; @@ -132,7 +134,6 @@ namespace NzbDrone.Core.Test Assert.AreNotEqual(new DateTime(), result.DateAdded); } - [Test] [Description("Verifies that a new file imported properly")] public void import_existing_season_file() @@ -165,7 +166,6 @@ namespace NzbDrone.Core.Test result.Should().BeNull(); } - [Test] [Description("Verifies that a un-parsable file isn't imported")] public void import_unparsable_file() @@ -251,7 +251,6 @@ namespace NzbDrone.Core.Test var fakeSeries = Builder.CreateNew().Build(); //Mocks - var mocker = new AutoMoqer(); mocker.GetMock(MockBehavior.Strict) .Setup(r => r.Exists(It.IsAny>>())).Returns(true).Verifiable(); @@ -361,5 +360,120 @@ namespace NzbDrone.Core.Test result.Should().HaveSameCount(firstSeriesFiles); } + + [Test] + [Description("Verifies that a new download will import successfully")] + public void import_new_download_success() + { + //Fakes + var fakeSeries = Builder.CreateNew() + .With(s => s.Title = "30 Rock") + .With(s => s.Path = @"C:\Test\TV\30 Rock") + .Build(); + + var fakeEpisode = Builder.CreateNew() + .With(e => e.SeriesId = fakeSeries.SeriesId) + .With(e => e.EpisodeFileId = 0) + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumber = 5) + .Build(); + + //Mocks + var mocker = new AutoMoqer(); + + var diskProvider = mocker.GetMock(); + diskProvider.Setup(d => d.GetFiles(It.IsAny(), "*.*", SearchOption.AllDirectories)).Returns(new string[] { @"C:\Test\30 Rock - 1x05 - Episode Title\30.Rock.S01E05.Gibberish.XviD.avi" }); + diskProvider.Setup(d => d.GetSize(It.IsAny())).Returns(90000000000); + diskProvider.Setup(d => d.CreateDirectory(It.IsAny())).Returns("ok"); + diskProvider.Setup(d => d.RenameFile(It.IsAny(), It.IsAny())); + diskProvider.Setup(d => d.GetExtension(It.IsAny())).Returns(".avi"); + + var episodeProvider = mocker.GetMock(); + episodeProvider.Setup(e => e.GetEpisodes(It.IsAny())).Returns(new List {fakeEpisode}); + episodeProvider.Setup(e => e.GetEpisode(fakeSeries.SeriesId, 1, 5)).Returns(fakeEpisode); + + var configProvider = mocker.GetMock(); + configProvider.SetupGet(c => c.UseSeasonFolder).Returns(true); + configProvider.SetupGet(c => c.SeasonFolderFormat).Returns(@"Season %0s"); + configProvider.SetupGet(c => c.SeriesName).Returns(true); + configProvider.SetupGet(c => c.EpisodeName).Returns(true); + configProvider.SetupGet(c => c.AppendQuality).Returns(true); + configProvider.SetupGet(c => c.SeparatorStyle).Returns(0); + configProvider.SetupGet(c => c.NumberStyle).Returns(2); + configProvider.SetupGet(c => c.ReplaceSpaces).Returns(false); + + var repo = mocker.GetMock(); + repo.Setup(r => r.Exists(It.IsAny>>())).Returns(false).Verifiable(); + repo.Setup(r => r.Add(It.IsAny())).Returns(1); + + //Act + var result = mocker.Resolve().ImportNewFiles(@"C:\Test\30 Rock - 1x05 - Episode Title", fakeSeries); + + //Assert + mocker.VerifyAllMocks(); + Assert.AreEqual(1, result.Count); + } + + [Test] + [Description("Verifies that a new download will import successfully, deletes previous episode")] + public void import_new_download_success_delete_equal_quality() + { + //Fakes + var fakeSeries = Builder.CreateNew() + .With(s => s.Title = "30 Rock") + .With(s => s.Path = @"C:\Test\TV\30 Rock") + .Build(); + + var fakeEpisode = Builder.CreateNew() + .With(e => e.SeriesId = fakeSeries.SeriesId) + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumber = 5) + .With(e => e.EpisodeFileId = 1) + .Build(); + + var fakeEpisodeFile = Builder.CreateNew() + .With(e => e.SeriesId = fakeSeries.SeriesId) + .With(e => e.EpisodeFileId = 1) + .With(e => e.Quality = QualityTypes.SDTV) + .With(e => e.Episodes = new List{ fakeEpisode }) + .Build(); + + fakeEpisode.EpisodeFile = fakeEpisodeFile; + + //Mocks + var mocker = new AutoMoqer(); + + var diskProvider = mocker.GetMock(); + diskProvider.Setup(d => d.GetFiles(It.IsAny(), "*.*", SearchOption.AllDirectories)).Returns(new string[] { @"C:\Test\30 Rock - 1x05 - Episode Title\30.Rock.S01E05.Gibberish.XviD.avi" }); + diskProvider.Setup(d => d.GetSize(It.IsAny())).Returns(90000000000); + diskProvider.Setup(d => d.CreateDirectory(It.IsAny())).Returns("ok"); + diskProvider.Setup(d => d.RenameFile(It.IsAny(), It.IsAny())); + diskProvider.Setup(d => d.GetExtension(It.IsAny())).Returns(".avi"); + + var episodeProvider = mocker.GetMock(); + episodeProvider.Setup(e => e.GetEpisodes(It.IsAny())).Returns(new List { fakeEpisode }); + episodeProvider.Setup(e => e.GetEpisode(fakeSeries.SeriesId, 1, 5)).Returns(fakeEpisode); + + var configProvider = mocker.GetMock(); + configProvider.SetupGet(c => c.UseSeasonFolder).Returns(true); + configProvider.SetupGet(c => c.SeasonFolderFormat).Returns(@"Season %0s"); + configProvider.SetupGet(c => c.SeriesName).Returns(true); + configProvider.SetupGet(c => c.EpisodeName).Returns(true); + configProvider.SetupGet(c => c.AppendQuality).Returns(true); + configProvider.SetupGet(c => c.SeparatorStyle).Returns(0); + configProvider.SetupGet(c => c.NumberStyle).Returns(2); + configProvider.SetupGet(c => c.ReplaceSpaces).Returns(false); + + var repo = mocker.GetMock(); + repo.Setup(r => r.Exists(It.IsAny>>())).Returns(false).Verifiable(); + repo.Setup(r => r.Add(It.IsAny())).Returns(1); + + //Act + var result = mocker.Resolve().ImportNewFiles(@"C:\Test\30 Rock - 1x05 - Episode Title", fakeSeries); + + //Assert + mocker.VerifyAllMocks(); + Assert.AreEqual(1, result.Count); + } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/RenameProviderTest.cs b/NzbDrone.Core.Test/MediaFileProvider_GetNewFilenameTest.cs similarity index 64% rename from NzbDrone.Core.Test/RenameProviderTest.cs rename to NzbDrone.Core.Test/MediaFileProvider_GetNewFilenameTest.cs index 7b8038cb1..9e059e798 100644 --- a/NzbDrone.Core.Test/RenameProviderTest.cs +++ b/NzbDrone.Core.Test/MediaFileProvider_GetNewFilenameTest.cs @@ -18,7 +18,7 @@ namespace NzbDrone.Core.Test { [TestFixture] // ReSharper disable InconsistentNaming - public class RenameProviderTest : TestBase + public class MediaFileProvider_GetNewFilenameTest : TestBase { [Test] public void GetNewFilename_Series_Episode_Quality_S01E05_Dash() @@ -34,23 +34,14 @@ namespace NzbDrone.Core.Test fakeConfig.SetupGet(c => c.NumberStyle).Returns(2); fakeConfig.SetupGet(c => c.ReplaceSpaces).Returns(false); - var episodeFile = Builder.CreateNew() - .With(e => e.EpisodeFileId = 12345) - .With(e => e.Quality = QualityTypes.HDTV) - .Build(); - var episode = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "City Sushi") .With(e => e.SeasonNumber = 15) .With(e => e.EpisodeNumber = 6) .Build(); - var fakeEpisodeProvider = mocker.GetMock(); - fakeEpisodeProvider.Setup(m => m.EpisodesByFileId(12345)).Returns(new List {episode}); - //Act - string result = mocker.Resolve().GetNewFilename(episodeFile, "South Park"); + string result = mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV); //Assert Assert.AreEqual("South Park - S15E06 - City Sushi [HDTV]", result); @@ -70,23 +61,14 @@ namespace NzbDrone.Core.Test fakeConfig.SetupGet(c => c.NumberStyle).Returns(0); fakeConfig.SetupGet(c => c.ReplaceSpaces).Returns(false); - var episodeFile = Builder.CreateNew() - .With(e => e.EpisodeFileId = 12345) - .With(e => e.Quality = QualityTypes.HDTV) - .Build(); - var episode = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "City Sushi") .With(e => e.SeasonNumber = 15) .With(e => e.EpisodeNumber = 6) .Build(); - var fakeEpisodeProvider = mocker.GetMock(); - fakeEpisodeProvider.Setup(m => m.EpisodesByFileId(12345)).Returns(new List { episode }); - //Act - string result = mocker.Resolve().GetNewFilename(episodeFile, "South Park"); + string result = mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV); //Assert Assert.AreEqual("15x06 - City Sushi [HDTV]", result); @@ -106,23 +88,14 @@ namespace NzbDrone.Core.Test fakeConfig.SetupGet(c => c.NumberStyle).Returns(1); fakeConfig.SetupGet(c => c.ReplaceSpaces).Returns(false); - var episodeFile = Builder.CreateNew() - .With(e => e.EpisodeFileId = 12345) - .With(e => e.Quality = QualityTypes.HDTV) - .Build(); - var episode = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "City Sushi") .With(e => e.SeasonNumber = 5) .With(e => e.EpisodeNumber = 6) .Build(); - var fakeEpisodeProvider = mocker.GetMock(); - fakeEpisodeProvider.Setup(m => m.EpisodesByFileId(12345)).Returns(new List { episode }); - //Act - string result = mocker.Resolve().GetNewFilename(episodeFile, "South Park"); + string result = mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV); //Assert Assert.AreEqual("South Park 05x06 [HDTV]", result); @@ -142,23 +115,15 @@ namespace NzbDrone.Core.Test fakeConfig.SetupGet(c => c.NumberStyle).Returns(3); fakeConfig.SetupGet(c => c.ReplaceSpaces).Returns(false); - var episodeFile = Builder.CreateNew() - .With(e => e.EpisodeFileId = 12345) - .With(e => e.Quality = QualityTypes.HDTV) - .Build(); var episode = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "City Sushi") .With(e => e.SeasonNumber = 5) .With(e => e.EpisodeNumber = 6) .Build(); - var fakeEpisodeProvider = mocker.GetMock(); - fakeEpisodeProvider.Setup(m => m.EpisodesByFileId(12345)).Returns(new List { episode }); - //Act - string result = mocker.Resolve().GetNewFilename(episodeFile, "South Park"); + string result = mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV); //Assert Assert.AreEqual("South Park s05e06", result); @@ -178,23 +143,14 @@ namespace NzbDrone.Core.Test fakeConfig.SetupGet(c => c.NumberStyle).Returns(3); fakeConfig.SetupGet(c => c.ReplaceSpaces).Returns(true); - var episodeFile = Builder.CreateNew() - .With(e => e.EpisodeFileId = 12345) - .With(e => e.Quality = QualityTypes.HDTV) - .Build(); - var episode = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "City Sushi") .With(e => e.SeasonNumber = 5) .With(e => e.EpisodeNumber = 6) .Build(); - var fakeEpisodeProvider = mocker.GetMock(); - fakeEpisodeProvider.Setup(m => m.EpisodesByFileId(12345)).Returns(new List { episode }); - //Act - string result = mocker.Resolve().GetNewFilename(episodeFile, "South Park"); + string result = mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV); //Assert Assert.AreEqual("South.Park.s05e06.City.Sushi", result); @@ -214,25 +170,14 @@ namespace NzbDrone.Core.Test fakeConfig.SetupGet(c => c.NumberStyle).Returns(3); fakeConfig.SetupGet(c => c.ReplaceSpaces).Returns(true); - var episodeFile = Builder.CreateNew() - .With(e => e.EpisodeFileId = 12345) - .With(e => e.Quality = QualityTypes.HDTV) - .Build(); - var episode = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "City Sushi") .With(e => e.SeasonNumber = 5) .With(e => e.EpisodeNumber = 6) .Build(); - var fakeEpisodeProvider = mocker.GetMock(); - fakeEpisodeProvider.Setup(m => m.EpisodesByFileId(12345)).Returns(new List { episode }); - - var fakeSeriesProvider = mocker.GetMock(); - //Act - string result = mocker.Resolve().GetNewFilename(episodeFile, "South Park"); + string result = mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV); //Assert Assert.AreEqual("South.Park.-.s05e06.-.City.Sushi.[HDTV]", result); @@ -252,23 +197,15 @@ namespace NzbDrone.Core.Test fakeConfig.SetupGet(c => c.NumberStyle).Returns(2); fakeConfig.SetupGet(c => c.ReplaceSpaces).Returns(false); - var episodeFile = Builder.CreateNew() - .With(e => e.EpisodeFileId = 12345) - .With(e => e.Quality = QualityTypes.HDTV) - .Build(); var episode = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "City Sushi") .With(e => e.SeasonNumber = 15) .With(e => e.EpisodeNumber = 6) .Build(); - var fakeEpisodeProvider = mocker.GetMock(); - fakeEpisodeProvider.Setup(m => m.EpisodesByFileId(12345)).Returns(new List { episode }); - //Act - string result = mocker.Resolve().GetNewFilename(episodeFile, "South Park"); + string result = mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV); //Assert Assert.AreEqual("S15E06", result); @@ -289,30 +226,20 @@ namespace NzbDrone.Core.Test fakeConfig.SetupGet(c => c.ReplaceSpaces).Returns(false); fakeConfig.SetupGet(c => c.MultiEpisodeStyle).Returns(3); - var episodeFile = Builder.CreateNew() - .With(e => e.EpisodeFileId = 12345) - .With(e => e.Quality = QualityTypes.HDTV) - .Build(); - var episodeOne = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "Strawberries and Cream (1)") .With(e => e.SeasonNumber = 3) .With(e => e.EpisodeNumber = 23) .Build(); var episodeTwo = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "Strawberries and Cream (2)") .With(e => e.SeasonNumber = 3) .With(e => e.EpisodeNumber = 24) .Build(); - var fakeEpisodeProvider = mocker.GetMock(); - fakeEpisodeProvider.Setup(m => m.EpisodesByFileId(12345)).Returns(new List { episodeOne, episodeTwo }); - //Act - string result = mocker.Resolve().GetNewFilename(episodeFile, "The Mentalist"); + string result = mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV); //Assert Assert.AreEqual("The Mentalist - S03E23-E24 - Strawberries and Cream (1) + Strawberries and Cream (2) [HDTV]", result); @@ -333,30 +260,20 @@ namespace NzbDrone.Core.Test fakeConfig.SetupGet(c => c.ReplaceSpaces).Returns(false); fakeConfig.SetupGet(c => c.MultiEpisodeStyle).Returns(2); - var episodeFile = Builder.CreateNew() - .With(e => e.EpisodeFileId = 12345) - .With(e => e.Quality = QualityTypes.HDTV) - .Build(); - var episodeOne = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "Strawberries and Cream (1)") .With(e => e.SeasonNumber = 3) .With(e => e.EpisodeNumber = 23) .Build(); var episodeTwo = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "Strawberries and Cream (2)") .With(e => e.SeasonNumber = 3) .With(e => e.EpisodeNumber = 24) .Build(); - var fakeEpisodeProvider = mocker.GetMock(); - fakeEpisodeProvider.Setup(m => m.EpisodesByFileId(12345)).Returns(new List { episodeOne, episodeTwo }); - //Act - string result = mocker.Resolve().GetNewFilename(episodeFile, "The Mentalist"); + string result = mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV); //Assert Assert.AreEqual("3x23x24 - Strawberries and Cream (1) + Strawberries and Cream (2) [HDTV]", result); @@ -377,30 +294,20 @@ namespace NzbDrone.Core.Test fakeConfig.SetupGet(c => c.ReplaceSpaces).Returns(false); fakeConfig.SetupGet(c => c.MultiEpisodeStyle).Returns(2); - var episodeFile = Builder.CreateNew() - .With(e => e.EpisodeFileId = 12345) - .With(e => e.Quality = QualityTypes.HDTV) - .Build(); - var episodeOne = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "Strawberries and Cream (1)") .With(e => e.SeasonNumber = 3) .With(e => e.EpisodeNumber = 23) .Build(); var episodeTwo = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "Strawberries and Cream (2)") .With(e => e.SeasonNumber = 3) .With(e => e.EpisodeNumber = 24) .Build(); - var fakeEpisodeProvider = mocker.GetMock(); - fakeEpisodeProvider.Setup(m => m.EpisodesByFileId(12345)).Returns(new List { episodeOne, episodeTwo }); - //Act - string result = mocker.Resolve().GetNewFilename(episodeFile, "The Mentalist"); + string result = mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV); //Assert Assert.AreEqual("3x23x24 Strawberries and Cream (1) + Strawberries and Cream (2) [HDTV]", result); @@ -421,30 +328,20 @@ namespace NzbDrone.Core.Test fakeConfig.SetupGet(c => c.ReplaceSpaces).Returns(true); fakeConfig.SetupGet(c => c.MultiEpisodeStyle).Returns(1); - var episodeFile = Builder.CreateNew() - .With(e => e.EpisodeFileId = 12345) - .With(e => e.Quality = QualityTypes.HDTV) - .Build(); - var episodeOne = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "Strawberries and Cream (1)") .With(e => e.SeasonNumber = 3) .With(e => e.EpisodeNumber = 23) .Build(); var episodeTwo = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "Strawberries and Cream (2)") .With(e => e.SeasonNumber = 3) .With(e => e.EpisodeNumber = 24) .Build(); - var fakeEpisodeProvider = mocker.GetMock(); - fakeEpisodeProvider.Setup(m => m.EpisodesByFileId(12345)).Returns(new List { episodeOne, episodeTwo }); - //Act - string result = mocker.Resolve().GetNewFilename(episodeFile, "The Mentalist"); + string result = mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV); //Assert Assert.AreEqual("The.Mentalist.s03e23.s03e24.Strawberries.and.Cream.(1).+.Strawberries.and.Cream.(2)", result); @@ -465,31 +362,20 @@ namespace NzbDrone.Core.Test fakeConfig.SetupGet(c => c.ReplaceSpaces).Returns(true); fakeConfig.SetupGet(c => c.MultiEpisodeStyle).Returns(0); - - var episodeFile = Builder.CreateNew() - .With(e => e.EpisodeFileId = 12345) - .With(e => e.Quality = QualityTypes.HDTV) - .Build(); - var episodeOne = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "Strawberries and Cream (1)") .With(e => e.SeasonNumber = 3) .With(e => e.EpisodeNumber = 23) .Build(); var episodeTwo = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "Strawberries and Cream (2)") .With(e => e.SeasonNumber = 3) .With(e => e.EpisodeNumber = 24) .Build(); - var fakeEpisodeProvider = mocker.GetMock(); - fakeEpisodeProvider.Setup(m => m.EpisodesByFileId(12345)).Returns(new List { episodeOne, episodeTwo }); - //Act - string result = mocker.Resolve().GetNewFilename(episodeFile, "The Mentalist"); + string result = mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV); //Assert Assert.AreEqual("The.Mentalist.-.S03E23-24", result); @@ -510,30 +396,20 @@ namespace NzbDrone.Core.Test fakeConfig.SetupGet(c => c.ReplaceSpaces).Returns(true); fakeConfig.SetupGet(c => c.MultiEpisodeStyle).Returns(2); - var episodeFile = Builder.CreateNew() - .With(e => e.EpisodeFileId = 12345) - .With(e => e.Quality = QualityTypes.HDTV) - .Build(); - var episodeOne = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "Strawberries and Cream (1)") .With(e => e.SeasonNumber = 3) .With(e => e.EpisodeNumber = 23) .Build(); var episodeTwo = Builder.CreateNew() - .With(e => e.EpisodeFileId = episodeFile.EpisodeFileId) .With(e => e.Title = "Strawberries and Cream (2)") .With(e => e.SeasonNumber = 3) .With(e => e.EpisodeNumber = 24) .Build(); - var fakeEpisodeProvider = mocker.GetMock(); - fakeEpisodeProvider.Setup(m => m.EpisodesByFileId(12345)).Returns(new List { episodeOne, episodeTwo }); - //Act - string result = mocker.Resolve().GetNewFilename(episodeFile, "The Mentalist"); + string result = mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV); //Assert Assert.AreEqual("3x23x24", result); diff --git a/NzbDrone.Core.Test/MediaFileProvider_ImportNewDownloadTest.cs b/NzbDrone.Core.Test/MediaFileProvider_ImportNewDownloadTest.cs new file mode 100644 index 000000000..1a2f16c38 --- /dev/null +++ b/NzbDrone.Core.Test/MediaFileProvider_ImportNewDownloadTest.cs @@ -0,0 +1,346 @@ +// ReSharper disable RedundantUsingDirective +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq.Expressions; +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.Core; +using NzbDrone.Core.Providers.Jobs; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Test.Framework; +using SubSonic.Repository; + +namespace NzbDrone.Core.Test +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class MediaFileProvider_ImportNewDownloadTest : TestBase + { + private Episode episode; + private Episode episode2; + private EpisodeFile episodeFile; + private EpisodeFile episodeFile2; + private Series series; + + [SetUp] + public new void Setup() + { + series = Builder.CreateNew() + .With(s => s.Title = "30 Rock") + .With(s => s.Path = @"C:\Test\TV\30 Rock") + .Build(); + + episode = Builder.CreateNew() + .With(e => e.SeriesId = series.SeriesId) + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumber = 5) + .With(e => e.EpisodeFileId = 1) + .With(e => e.Title = "Episode One Title") + .Build(); + + episode2 = Builder.CreateNew() + .With(e => e.SeriesId = series.SeriesId) + .With(e => e.SeasonNumber = 1) + .With(e => e.EpisodeNumber = 6) + .With(e => e.EpisodeFileId = 1) + .With(e => e.Title = "Episode Two Title") + .Build(); + + episodeFile = Builder.CreateNew() + .With(e => e.SeriesId = series.SeriesId) + .With(e => e.EpisodeFileId = 1) + .With(e => e.Quality = QualityTypes.SDTV) + .With(e => e.Episodes = new List { episode }) + .Build(); + + episodeFile2 = Builder.CreateNew() + .With(e => e.SeriesId = series.SeriesId) + .With(e => e.EpisodeFileId = 1) + .With(e => e.Quality = QualityTypes.SDTV) + .With(e => e.Episodes = new List { episode }) + .Build(); + + episode.EpisodeFile = episodeFile; + + base.Setup(); + } + + [Test] + [Description("Verifies that a new download will import successfully")] + public void import_new_download_imported() + { + //Mocks + var mocker = new AutoMoqer(); + + var diskProvider = mocker.GetMock(); + diskProvider.Setup(d => d.GetFiles(It.IsAny(), "*.*", SearchOption.AllDirectories)).Returns(new string[] { @"C:\Test\30 Rock - 1x05 - Episode Title\30.Rock.S01E05.Gibberish.XviD.avi" }); + diskProvider.Setup(d => d.GetSize(It.IsAny())).Returns(90000000000); + diskProvider.Setup(d => d.CreateDirectory(It.IsAny())).Returns("ok"); + diskProvider.Setup(d => d.RenameFile(It.IsAny(), It.IsAny())); + diskProvider.Setup(d => d.GetExtension(It.IsAny())).Returns(".avi"); + + var episodeProvider = mocker.GetMock(); + episodeProvider.Setup(e => e.GetEpisodes(It.IsAny())).Returns(new List {episode}); + episodeProvider.Setup(e => e.GetEpisode(series.SeriesId, 1, 5)).Returns(episode); + + var configProvider = mocker.GetMock(); + configProvider.SetupGet(c => c.UseSeasonFolder).Returns(true); + configProvider.SetupGet(c => c.SeasonFolderFormat).Returns(@"Season %0s"); + configProvider.SetupGet(c => c.SeriesName).Returns(true); + configProvider.SetupGet(c => c.EpisodeName).Returns(true); + configProvider.SetupGet(c => c.AppendQuality).Returns(true); + configProvider.SetupGet(c => c.SeparatorStyle).Returns(0); + configProvider.SetupGet(c => c.NumberStyle).Returns(2); + configProvider.SetupGet(c => c.ReplaceSpaces).Returns(false); + + var repo = mocker.GetMock(); + repo.Setup(r => r.Exists(It.IsAny>>())).Returns(false).Verifiable(); + repo.Setup(r => r.Add(It.IsAny())).Returns(1); + + //Act + var result = mocker.Resolve().ImportNewFiles(@"C:\Test\30 Rock - 1x05 - Episode Title", series); + + //Assert + mocker.VerifyAllMocks(); + Assert.AreEqual(1, result.Count); + } + + [Test] + [Description("Verifies that a new download will import successfully, deletes previous episode")] + public void import_new_download_imported_delete_equal_quality() + { + //Mocks + var mocker = new AutoMoqer(); + + var diskProvider = mocker.GetMock(); + diskProvider.Setup(d => d.GetFiles(It.IsAny(), "*.*", SearchOption.AllDirectories)).Returns(new string[] { @"C:\Test\30 Rock - 1x05 - Episode Title\30.Rock.S01E05.Gibberish.XviD.avi" }); + diskProvider.Setup(d => d.GetSize(It.IsAny())).Returns(90000000000); + diskProvider.Setup(d => d.CreateDirectory(It.IsAny())).Returns("ok"); + diskProvider.Setup(d => d.RenameFile(It.IsAny(), It.IsAny())); + diskProvider.Setup(d => d.GetExtension(It.IsAny())).Returns(".avi"); + + var episodeProvider = mocker.GetMock(); + episodeProvider.Setup(e => e.GetEpisodes(It.IsAny())).Returns(new List { episode }); + episodeProvider.Setup(e => e.GetEpisode(series.SeriesId, 1, 5)).Returns(episode); + + var configProvider = mocker.GetMock(); + configProvider.SetupGet(c => c.UseSeasonFolder).Returns(true); + configProvider.SetupGet(c => c.SeasonFolderFormat).Returns(@"Season %0s"); + configProvider.SetupGet(c => c.SeriesName).Returns(true); + configProvider.SetupGet(c => c.EpisodeName).Returns(true); + configProvider.SetupGet(c => c.AppendQuality).Returns(true); + configProvider.SetupGet(c => c.SeparatorStyle).Returns(0); + configProvider.SetupGet(c => c.NumberStyle).Returns(2); + configProvider.SetupGet(c => c.ReplaceSpaces).Returns(false); + + var repo = mocker.GetMock(); + repo.Setup(r => r.Exists(It.IsAny>>())).Returns(false).Verifiable(); + repo.Setup(r => r.Add(It.IsAny())).Returns(1); + + //Act + var result = mocker.Resolve().ImportNewFiles(@"C:\Test\30 Rock - 1x05 - Episode Title", series); + + //Assert + mocker.VerifyAllMocks(); + Assert.AreEqual(1, result.Count); + } + + [Test] + [Description("Verifies that a new download will not import successfully, because existing episode is better")] + public void import_new_download_not_imported_greater_quality() + { + //Alternate Setups + episodeFile.Quality = QualityTypes.DVD; + + //Mocks + var mocker = new AutoMoqer(); + + var diskProvider = mocker.GetMock(); + diskProvider.Setup(d => d.GetFiles(It.IsAny(), "*.*", SearchOption.AllDirectories)).Returns(new string[] { @"C:\Test\30 Rock - 1x05 - Episode Title\30.Rock.S01E05.Gibberish.XviD.avi" }); + diskProvider.Setup(d => d.GetSize(It.IsAny())).Returns(90000000000); + diskProvider.Setup(d => d.CreateDirectory(It.IsAny())).Returns("ok"); + diskProvider.Setup(d => d.GetExtension(It.IsAny())).Returns(".avi"); + + var episodeProvider = mocker.GetMock(); + episodeProvider.Setup(e => e.GetEpisodes(It.IsAny())).Returns(new List { episode }); + + var configProvider = mocker.GetMock(); + configProvider.SetupGet(c => c.UseSeasonFolder).Returns(true); + configProvider.SetupGet(c => c.SeasonFolderFormat).Returns(@"Season %0s"); + configProvider.SetupGet(c => c.SeriesName).Returns(true); + configProvider.SetupGet(c => c.EpisodeName).Returns(true); + configProvider.SetupGet(c => c.AppendQuality).Returns(true); + configProvider.SetupGet(c => c.SeparatorStyle).Returns(0); + configProvider.SetupGet(c => c.NumberStyle).Returns(2); + configProvider.SetupGet(c => c.ReplaceSpaces).Returns(false); + + //Act + var result = mocker.Resolve().ImportNewFiles(@"C:\Test\30 Rock - 1x05 - Episode Title", series); + + //Assert + mocker.VerifyAllMocks(); + Assert.AreEqual(0, result.Count); + } + + [Test] + [Description("Verifies that a new download will not import successfully, because of invalid episode in new file")] + public void import_new_download_not_imported_non_existant_episode() + { + //Mocks + var mocker = new AutoMoqer(); + + var diskProvider = mocker.GetMock(); + diskProvider.Setup(d => d.GetFiles(It.IsAny(), "*.*", SearchOption.AllDirectories)).Returns(new string[] { @"C:\Test\30 Rock - 1x05 - Episode Title\30.Rock.S01E05.Gibberish.XviD.avi" }); + diskProvider.Setup(d => d.GetSize(It.IsAny())).Returns(90000000000); + + var episodeProvider = mocker.GetMock(); + episodeProvider.Setup(e => e.GetEpisodes(It.IsAny())).Returns(new List()); + + //Act + var result = mocker.Resolve().ImportNewFiles(@"C:\Test\30 Rock - 1x05 - Episode Title", series); + + //Assert + mocker.VerifyAllMocks(); + Assert.AreEqual(0, result.Count); + ExceptionVerification.ExcpectedErrors(1); + } + + [Test] + [Description("Verifies that a new download will import successfully, deletes previous episode")] + public void import_new_download_imported_delete_lesser_quality_multi_episodes() + { + //Alternate Setup + episodeFile.Episodes.Add(episode2); + episode2.EpisodeFile = episodeFile; + + //Mocks + var mocker = new AutoMoqer(); + + var diskProvider = mocker.GetMock(); + diskProvider.Setup(d => d.GetFiles(It.IsAny(), "*.*", SearchOption.AllDirectories)).Returns(new string[] { @"C:\Test\30 Rock - 1x05x06 - Episode Title\30.Rock.S01E05E06.Gibberish.x264.avi" }); + diskProvider.Setup(d => d.GetSize(It.IsAny())).Returns(90000000000); + diskProvider.Setup(d => d.CreateDirectory(It.IsAny())).Returns("ok"); + diskProvider.Setup(d => d.RenameFile(It.IsAny(), It.IsAny())); + diskProvider.Setup(d => d.GetExtension(It.IsAny())).Returns(".mkv"); + + var episodeProvider = mocker.GetMock(); + episodeProvider.Setup(e => e.GetEpisodes(It.IsAny())).Returns(new List { episode, episode2 }); + episodeProvider.Setup(e => e.GetEpisode(series.SeriesId, 1, 5)).Returns(episode); + episodeProvider.Setup(e => e.GetEpisode(series.SeriesId, 1, 6)).Returns(episode2); + + var configProvider = mocker.GetMock(); + configProvider.SetupGet(c => c.UseSeasonFolder).Returns(true); + configProvider.SetupGet(c => c.SeasonFolderFormat).Returns(@"Season %0s"); + configProvider.SetupGet(c => c.SeriesName).Returns(true); + configProvider.SetupGet(c => c.EpisodeName).Returns(true); + configProvider.SetupGet(c => c.AppendQuality).Returns(true); + configProvider.SetupGet(c => c.SeparatorStyle).Returns(0); + configProvider.SetupGet(c => c.NumberStyle).Returns(2); + configProvider.SetupGet(c => c.ReplaceSpaces).Returns(false); + + var repo = mocker.GetMock(); + repo.Setup(r => r.Exists(It.IsAny>>())).Returns(false).Verifiable(); + repo.Setup(r => r.Add(It.IsAny())).Returns(1); + + //Act + var result = mocker.Resolve().ImportNewFiles(@"C:\Test\30 Rock - 1x05x06 - Episode Title", series); + + //Assert + mocker.VerifyAllMocks(); + Assert.AreEqual(1, result.Count); + } + + [Test] + [Description("Verifies that a new download will import successfully, deletes previous episode")] + public void import_new_download_imported_delete_lesser_quality_multi_episode_files() + { + //Alternate Setup + episodeFile2.Episodes.Add(episode2); + episode2.EpisodeFile = episodeFile2; + + //Mocks + var mocker = new AutoMoqer(); + + var diskProvider = mocker.GetMock(); + diskProvider.Setup(d => d.GetFiles(It.IsAny(), "*.*", SearchOption.AllDirectories)).Returns(new string[] { @"C:\Test\30 Rock - 1x05x06 - Episode Title\30.Rock.S01E05E06.Gibberish.x264.avi" }); + diskProvider.Setup(d => d.GetSize(It.IsAny())).Returns(90000000000); + diskProvider.Setup(d => d.CreateDirectory(It.IsAny())).Returns("ok"); + diskProvider.Setup(d => d.RenameFile(It.IsAny(), It.IsAny())); + diskProvider.Setup(d => d.GetExtension(It.IsAny())).Returns(".mkv"); + + var episodeProvider = mocker.GetMock(); + episodeProvider.Setup(e => e.GetEpisodes(It.IsAny())).Returns(new List { episode, episode2 }); + episodeProvider.Setup(e => e.GetEpisode(series.SeriesId, 1, 5)).Returns(episode); + episodeProvider.Setup(e => e.GetEpisode(series.SeriesId, 1, 6)).Returns(episode2); + + var configProvider = mocker.GetMock(); + configProvider.SetupGet(c => c.UseSeasonFolder).Returns(true); + configProvider.SetupGet(c => c.SeasonFolderFormat).Returns(@"Season %0s"); + configProvider.SetupGet(c => c.SeriesName).Returns(true); + configProvider.SetupGet(c => c.EpisodeName).Returns(true); + configProvider.SetupGet(c => c.AppendQuality).Returns(true); + configProvider.SetupGet(c => c.SeparatorStyle).Returns(0); + configProvider.SetupGet(c => c.NumberStyle).Returns(2); + configProvider.SetupGet(c => c.ReplaceSpaces).Returns(false); + + var repo = mocker.GetMock(); + repo.Setup(r => r.Exists(It.IsAny>>())).Returns(false).Verifiable(); + repo.Setup(r => r.Add(It.IsAny())).Returns(1); + + //Act + var result = mocker.Resolve().ImportNewFiles(@"C:\Test\30 Rock - 1x05x06 - Episode Title", series); + + //Assert + mocker.VerifyAllMocks(); + Assert.AreEqual(1, result.Count); + } + + [Test] + [Description("Verifies that a new download will not import successfully, previous episode is better quality")] + public void import_new_download_not_imported_multi_episode_files() + { + //Alternate Setup + episodeFile2.Episodes.Add(episode2); + episode2.EpisodeFile = episodeFile2; + episodeFile2.Quality = QualityTypes.Bluray720p; + + //Mocks + var mocker = new AutoMoqer(); + + var diskProvider = mocker.GetMock(); + diskProvider.Setup(d => d.GetFiles(It.IsAny(), "*.*", SearchOption.AllDirectories)).Returns(new string[] { @"C:\Test\30 Rock - 1x05x06 - Episode Title\30.Rock.S01E05E06.Gibberish.x264.avi" }); + diskProvider.Setup(d => d.GetSize(It.IsAny())).Returns(90000000000); + diskProvider.Setup(d => d.CreateDirectory(It.IsAny())).Returns("ok"); + diskProvider.Setup(d => d.GetExtension(It.IsAny())).Returns(".mkv"); + + var episodeProvider = mocker.GetMock(); + episodeProvider.Setup(e => e.GetEpisodes(It.IsAny())).Returns(new List { episode, episode2 }); + + var configProvider = mocker.GetMock(); + configProvider.SetupGet(c => c.UseSeasonFolder).Returns(true); + configProvider.SetupGet(c => c.SeasonFolderFormat).Returns(@"Season %0s"); + configProvider.SetupGet(c => c.SeriesName).Returns(true); + configProvider.SetupGet(c => c.EpisodeName).Returns(true); + configProvider.SetupGet(c => c.AppendQuality).Returns(true); + configProvider.SetupGet(c => c.SeparatorStyle).Returns(0); + configProvider.SetupGet(c => c.NumberStyle).Returns(2); + configProvider.SetupGet(c => c.ReplaceSpaces).Returns(false); + + //Act + var result = mocker.Resolve().ImportNewFiles(@"C:\Test\30 Rock - 1x05x06 - Episode Title", series); + + //Assert + mocker.VerifyAllMocks(); + Assert.AreEqual(0, result.Count); + } + } +} \ 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 c9a790a07..46243f9b6 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -90,7 +90,8 @@ - + + diff --git a/NzbDrone.Core/CentralDispatch.cs b/NzbDrone.Core/CentralDispatch.cs index 2e9346e63..b545a19d1 100644 --- a/NzbDrone.Core/CentralDispatch.cs +++ b/NzbDrone.Core/CentralDispatch.cs @@ -87,7 +87,6 @@ namespace NzbDrone.Core _kernel.Bind().ToSelf().InSingletonScope(); _kernel.Bind().To().InSingletonScope(); _kernel.Bind().ToSelf().InSingletonScope(); - _kernel.Bind().ToSelf().InSingletonScope(); _kernel.Bind().ToSelf().InSingletonScope(); _kernel.Bind().ToSelf().InSingletonScope(); _kernel.Bind().ToSelf().InSingletonScope(); diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 6c3872fce..4a24a4a95 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -195,6 +195,7 @@ + @@ -214,7 +215,6 @@ - diff --git a/NzbDrone.Core/Providers/Core/ConfigProvider.cs b/NzbDrone.Core/Providers/Core/ConfigProvider.cs index 8cb41e6ac..1984b8afc 100644 --- a/NzbDrone.Core/Providers/Core/ConfigProvider.cs +++ b/NzbDrone.Core/Providers/Core/ConfigProvider.cs @@ -174,6 +174,13 @@ namespace NzbDrone.Core.Providers.Core set { SetValue("SabTvPriority", (int)value); } } + public virtual String SabDropDirectory + { + get { return GetValue("SabTvDropDirectory", "", false); } + + set { SetValue("SabTvDropDirectory", value); } + } + public virtual Boolean UseBlackhole { get { return GetValueBoolean("UseBlackhole"); } diff --git a/NzbDrone.Core/Providers/Core/DiskProvider.cs b/NzbDrone.Core/Providers/Core/DiskProvider.cs index b83cd1ee3..48123050d 100644 --- a/NzbDrone.Core/Providers/Core/DiskProvider.cs +++ b/NzbDrone.Core/Providers/Core/DiskProvider.cs @@ -46,5 +46,10 @@ namespace NzbDrone.Core.Providers.Core { File.Move(sourcePath, destinationPath); } + + public virtual string GetExtension(string path) + { + return Path.GetExtension(path); + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index 7dab9bc2d10125f5e5dfbb47c95518737e138124..bab8a744a176aec42ae01b0b3de90bf73b6fdd0c 100644 GIT binary patch delta 339 zcmX>Z+8e!LA^YS5Y?AUCt_7LJ`6;OZiABY!L8-;1IVB1O2=-(~b}6yUGzASLF}>i_ zqRiCd$p!-AlmANzPnH$q(}tR&05(PeYEEXcf@fY?Voqj?jzV#EWRzWJ6(L zwxZOM(xSY{Gr5%~uM-uVoXgKWd4d$n8tqq_o0M8K`Ms1fNVU6y zt%7@M2~-Y?>!6PG1X_*i^u*%)JcwC33aJH?5AusmzQD^3^o%^rQ75@oC-X^jGZs%a qWR{q`N>p@mJS!U~P%${5fTCRLllwWvH}7DV6_~^-zWJT_Z6N?_M|T?l delta 25 hcmeB;J{h`UA^YSB(rlY^x%31k|6`Hdyh-Y|5CDnG3MBvl diff --git a/NzbDrone.Core/Providers/Jobs/RenameEpisodeJob.cs b/NzbDrone.Core/Providers/Jobs/RenameEpisodeJob.cs index bf926531c..f17f44faf 100644 --- a/NzbDrone.Core/Providers/Jobs/RenameEpisodeJob.cs +++ b/NzbDrone.Core/Providers/Jobs/RenameEpisodeJob.cs @@ -3,18 +3,26 @@ using System.IO; using System.Linq; using NLog; using NzbDrone.Core.Model.Notification; +using NzbDrone.Core.Providers.Core; namespace NzbDrone.Core.Providers.Jobs { public class RenameEpisodeJob : IJob { - private readonly RenameProvider _renameProvider; + private readonly DiskProvider _diskProvider; + private readonly EpisodeProvider _episodeProvider; + private readonly MediaFileProvider _mediaFileProvider; + private readonly SeriesProvider _seriesProvider; private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - public RenameEpisodeJob(RenameProvider renameProvider) + public RenameEpisodeJob(DiskProvider diskProvider, EpisodeProvider episodeProvider, + MediaFileProvider mediaFileProvider, SeriesProvider seriesProvider) { - _renameProvider = renameProvider; + _diskProvider = diskProvider; + _episodeProvider = episodeProvider; + _mediaFileProvider = mediaFileProvider; + _seriesProvider = seriesProvider; } public string Name @@ -29,7 +37,7 @@ namespace NzbDrone.Core.Providers.Jobs public void Start(ProgressNotification notification, int targetId) { - _renameProvider.RenameEpisodeFile(targetId, notification); + _mediaFileProvider.RenameEpisodeFile(targetId, notification); } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/MediaFileProvider.cs b/NzbDrone.Core/Providers/MediaFileProvider.cs index 720ae62e3..62acb5532 100644 --- a/NzbDrone.Core/Providers/MediaFileProvider.cs +++ b/NzbDrone.Core/Providers/MediaFileProvider.cs @@ -3,8 +3,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; using NLog; +using NzbDrone.Core.Helpers; +using NzbDrone.Core.Model.Notification; using NzbDrone.Core.Providers.Core; using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; using SubSonic.Repository; namespace NzbDrone.Core.Providers @@ -17,14 +20,17 @@ namespace NzbDrone.Core.Providers private readonly EpisodeProvider _episodeProvider; private readonly SeriesProvider _seriesProvider; private readonly IRepository _repository; + private readonly ConfigProvider _configProvider; public MediaFileProvider(IRepository repository, DiskProvider diskProvider, - EpisodeProvider episodeProvider, SeriesProvider seriesProvider) + EpisodeProvider episodeProvider, SeriesProvider seriesProvider, + ConfigProvider configProvider) { _repository = repository; _diskProvider = diskProvider; _episodeProvider = episodeProvider; _seriesProvider = seriesProvider; + _configProvider = configProvider; } public MediaFileProvider() { } @@ -71,7 +77,6 @@ namespace NzbDrone.Core.Providers { var parseResult = Parser.ParseEpisodeInfo(filePath); - if (parseResult == null) return null; @@ -162,6 +167,15 @@ namespace NzbDrone.Core.Providers if (!_diskProvider.FileExists(episodeFile.Path)) { Logger.Trace("File {0} no longer exists on disk. removing from database.", episodeFile.Path); + + //Set the EpisodeFileId for each episode attached to this file to 0 + foreach (var episode in episodeFile.Episodes) + { + episode.EpisodeFileId = 0; + _episodeProvider.UpdateEpisode(episode); + } + + //Delete it from the DB _repository.Delete(episodeFile.EpisodeFileId); } } @@ -208,5 +222,204 @@ namespace NzbDrone.Core.Providers Logger.Debug("{0} media files were found in {1}", mediaFileList.Count, path); return mediaFileList; } + + public virtual List ImportNewFiles(string path, Series series) + { + var result = new List(); + var files = GetMediaFileList(path); + + foreach (var file in files) + { + //If Size is less than 40MB and contains sample. Check for Size to ensure its not an episode with sample in the title + if (_diskProvider.GetSize(file) < 40000000 && file.ToLower().Contains("sample")) + { + Logger.Trace("[{0}] appears to be a sample. Skipping.", file); + continue; + } + + //Parse the filename + var parseResult = Parser.ParseEpisodeInfo(Path.GetFileName(file)); + parseResult.Series = series; + parseResult.Episodes = _episodeProvider.GetEpisodes(parseResult); + + if (parseResult.Episodes.Count == 0) + { + Logger.Error("File '{0}' contains invalid episode information, skipping import", file); + continue; + } + + var ext = _diskProvider.GetExtension(file); + var filename = GetNewFilename(parseResult.Episodes, series.Title, parseResult.Quality.QualityType) + ext; + var folder = series.Path + Path.DirectorySeparatorChar; + if (_configProvider.UseSeasonFolder) + folder += _configProvider.SeasonFolderFormat + .Replace("%0s", parseResult.SeasonNumber.ToString("00")) + .Replace("%s", parseResult.SeasonNumber.ToString()) + + Path.DirectorySeparatorChar; + + _diskProvider.CreateDirectory(folder); + + //Get a list of episodeFiles that we need to delete and cleanup + var episodeFilesToClean = new List(); + + foreach (var episode in parseResult.Episodes) + { + if (episode.EpisodeFileId > 0) + episodeFilesToClean.Add(episode.EpisodeFile); + } + + if (episodeFilesToClean.Count != episodeFilesToClean.Where(e => parseResult.Quality.QualityType >= e.Quality).Count()) + { + Logger.Debug("Episode isn't an upgrade for all episodes in file: [{0}]. Skipping.", file); + continue; + } + + //Delete the files and then cleanup! + episodeFilesToClean.ForEach(e => _diskProvider.DeleteFile(e.Path)); + CleanUp(episodeFilesToClean); + + //Move the file + _diskProvider.RenameFile(file, folder + filename); + + //Import into DB + result.Add(ImportFile(series, folder + filename)); + } + return result; + } + + public virtual string GetNewFilename(IList episodes, string seriesName, QualityTypes quality) + { + var separatorStyle = EpisodeSortingHelper.GetSeparatorStyle(_configProvider.SeparatorStyle); + var numberStyle = EpisodeSortingHelper.GetNumberStyle(_configProvider.NumberStyle); + var useSeriesName = _configProvider.SeriesName; + var useEpisodeName = _configProvider.EpisodeName; + var replaceSpaces = _configProvider.ReplaceSpaces; + var appendQuality = _configProvider.AppendQuality; + + var title = String.Empty; + + if (episodes.Count == 1) + { + if (useSeriesName) + { + title += seriesName; + title += separatorStyle.Pattern; + } + + title += numberStyle.Pattern.Replace("%s", String.Format("{0}", episodes[0].SeasonNumber)) + .Replace("%0s", String.Format("{0:00}", episodes[0].SeasonNumber)) + .Replace("%0e", String.Format("{0:00}", episodes[0].EpisodeNumber)); + + if (useEpisodeName) + { + title += separatorStyle.Pattern; + title += episodes[0].Title; + } + + if (appendQuality) + title += String.Format(" [{0}]", quality); + + if (replaceSpaces) + title = title.Replace(' ', '.'); + + Logger.Debug("New File Name is: {0}", title); + return title; + } + + var multiEpisodeStyle = EpisodeSortingHelper.GetMultiEpisodeStyle(_configProvider.MultiEpisodeStyle); + + if (useSeriesName) + { + title += seriesName; + title += separatorStyle.Pattern; + } + + title += numberStyle.Pattern.Replace("%s", String.Format("{0}", episodes[0].SeasonNumber)) + .Replace("%0s", String.Format("{0:00}", episodes[0].SeasonNumber)) + .Replace("%0e", String.Format("{0:00}", episodes[0].EpisodeNumber)); + + var numbers = String.Empty; + var episodeNames = episodes[0].Title; + + for (int i = 1; i < episodes.Count; i++) + { + var episode = episodes[i]; + + if (multiEpisodeStyle.Name == "Duplicate") + { + numbers += separatorStyle.Pattern + numberStyle.Pattern.Replace("%s", String.Format("{0}", episode.SeasonNumber)) + .Replace("%0s", String.Format("{0:00}", episode.SeasonNumber)) + .Replace("%0e", String.Format("{0:00}", episode.EpisodeNumber)); + } + else + { + numbers += multiEpisodeStyle.Pattern.Replace("%s", String.Format("{0}", episode.SeasonNumber)) + .Replace("%0s", String.Format("{0:00}", episode.SeasonNumber)) + .Replace("%0e", String.Format("{0:00}", episode.EpisodeNumber)) + .Replace("%x", numberStyle.EpisodeSeparator) + .Replace("%p", separatorStyle.Pattern); + } + + episodeNames += String.Format(" + {0}", episode.Title); + } + + title += numbers; + + if (useEpisodeName) + { + episodeNames = episodeNames.TrimEnd(' ', '+'); + + title += separatorStyle.Pattern; + title += episodeNames; + } + + if (appendQuality) + title += String.Format(" [{0}]", quality); + + if (replaceSpaces) + title = title.Replace(' ', '.'); + + Logger.Debug("New File Name is: {0}", title); + return title; + } + + public virtual bool RenameEpisodeFile(int episodeFileId, ProgressNotification notification) + { + var episodeFile = GetEpisodeFile(episodeFileId); + + if (episodeFile == null) + return false; + + try + { + notification.CurrentMessage = String.Format("Renaming '{0}'", episodeFile.Path); + + var series = _seriesProvider.GetSeries(episodeFile.SeriesId); + var folder = new FileInfo(episodeFile.Path).DirectoryName; + var episodes = _episodeProvider.EpisodesByFileId(episodeFileId); + var ext = _diskProvider.GetExtension(episodeFile.Path); + + var newFileName = GetNewFilename(episodes, series.Title, episodeFile.Quality); + + var newFile = folder + Path.DirectorySeparatorChar + newFileName + ext; + + //Do the rename + _diskProvider.RenameFile(episodeFile.Path, newFile); + + //Update the filename in the DB + episodeFile.Path = newFile; + Update(episodeFile); + + notification.CurrentMessage = String.Format("Finished Renaming '{0}'", newFile); + } + + catch (Exception e) + { + notification.CurrentMessage = String.Format("Failed to Rename '{0}'", episodeFile.Path); + Logger.ErrorException("An error has occurred while renaming episode: " + episodeFile.Path, e); + throw; + } + return true; + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/PostProcessingProvider.cs b/NzbDrone.Core/Providers/PostProcessingProvider.cs new file mode 100644 index 000000000..f644d4052 --- /dev/null +++ b/NzbDrone.Core/Providers/PostProcessingProvider.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using NLog; +using NzbDrone.Core.Providers.Core; +using NzbDrone.Core.Repository; + +namespace NzbDrone.Core.Providers +{ + public class PostProcessingProvider + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + private readonly ConfigProvider _configProvider; + private readonly DiskProvider _diskProvider; + private readonly SeriesProvider _seriesProvider; + private readonly MediaFileProvider _mediaFileProvider; + private readonly EpisodeProvider _episodeProvider; + + public PostProcessingProvider(ConfigProvider configProvider, DiskProvider diskProvider, + SeriesProvider seriesProvider, MediaFileProvider mediaFileProvider, + EpisodeProvider episodeProvider) + { + _configProvider = configProvider; + _diskProvider = diskProvider; + _seriesProvider = seriesProvider; + _mediaFileProvider = mediaFileProvider; + _episodeProvider = episodeProvider; + } + + //Scan folder + //Delete Existing episode(s) + //Move file(s) + //Import file(s) + + public virtual void Scan() + { + var dropFolder = _configProvider.SabDropDirectory; + + if (String.IsNullOrEmpty(dropFolder)) + { + Logger.Warn("Unable to Scan for New Downloads - Folder Name is Empty"); + return; + } + + if (!_diskProvider.FolderExists(dropFolder)) + { + Logger.Warn("Unable to Scan for New Downloads - Folder Doesn't Exist"); + return; + } + + var subfolders = _diskProvider.GetDirectories(dropFolder); + + foreach (var subfolder in subfolders) + { + var di = new DirectoryInfo(subfolder); + + if (di.Name.StartsWith("_UNPACK_")) + { + Logger.Info("Folder [{0}] is still being unpacked", subfolder); + continue; + } + + if (di.Name.StartsWith("_FAILED_")) + { + Logger.Info("Folder [{0}] is marked as failed", subfolder); + continue; + } + + //Parse the Folder name + var seriesName = Parser.ParseSeriesName(di.Name); + var series = _seriesProvider.FindSeries(seriesName); + + if (series == null) + { + Logger.Warn("Unable to Import new download, series is not being watched"); + return; + } + + _mediaFileProvider.ImportNewFiles(subfolder, series); + } + } + } +} diff --git a/NzbDrone.Core/Providers/RenameProvider.cs b/NzbDrone.Core/Providers/RenameProvider.cs deleted file mode 100644 index 22b468823..000000000 --- a/NzbDrone.Core/Providers/RenameProvider.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using NLog; -using NzbDrone.Core.Helpers; -using NzbDrone.Core.Model.Notification; -using NzbDrone.Core.Providers.Core; -using NzbDrone.Core.Repository; - -namespace NzbDrone.Core.Providers -{ - public class RenameProvider - { - //TODO: Remove some of these dependencies. we shouldn't have a single class with dependency on the whole app! - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - private readonly ConfigProvider _configProvider; - private readonly DiskProvider _diskProvider; - private readonly EpisodeProvider _episodeProvider; - private readonly MediaFileProvider _mediaFileProvider; - private readonly SeriesProvider _seriesProvider; - - public RenameProvider(SeriesProvider seriesProvider,EpisodeProvider episodeProvider, - MediaFileProvider mediaFileProvider, DiskProvider diskProvider, - ConfigProvider configProvider) - { - _seriesProvider = seriesProvider; - _episodeProvider = episodeProvider; - _mediaFileProvider = mediaFileProvider; - _diskProvider = diskProvider; - _configProvider = configProvider; - } - - public virtual void RenameEpisodeFile(int episodeFileId, ProgressNotification notification) - { - var episodeFile = _mediaFileProvider.GetEpisodeFile(episodeFileId); - - try - { - notification.CurrentMessage = String.Format("Renaming '{0}'", episodeFile.Path); - - var series = _seriesProvider.GetSeries(episodeFile.SeriesId); - var folder = new FileInfo(episodeFile.Path).DirectoryName; - var newFileName = GetNewFilename(episodeFile, series.Title); - var newFile = folder + Path.DirectorySeparatorChar + newFileName; - _diskProvider.RenameFile(episodeFile.Path, newFile); - - notification.CurrentMessage = String.Format("Finished Renaming '{0}'", newFile); - } - - catch (Exception e) - { - notification.CurrentMessage = String.Format("Failed to Rename '{0}'", episodeFile.Path); - Logger.ErrorException("An error has occurred while renaming episode: " + episodeFile.Path, e); - throw; - } - } - - public string GetNewFilename(EpisodeFile episodeFile, string seriesName) - { - var episodes = _episodeProvider.EpisodesByFileId(episodeFile.EpisodeFileId); - //var series = _seriesProvider.GetSeries(episodeFile.SeriesId); - - var separatorStyle = EpisodeSortingHelper.GetSeparatorStyle(_configProvider.SeparatorStyle); - var numberStyle = EpisodeSortingHelper.GetNumberStyle(_configProvider.NumberStyle); - var useSeriesName = _configProvider.SeriesName; - var useEpisodeName = _configProvider.EpisodeName; - var replaceSpaces = _configProvider.ReplaceSpaces; - var appendQuality = _configProvider.AppendQuality; - - var title = String.Empty; - - if (episodes.Count == 1) - { - if (useSeriesName) - { - title += seriesName; - title += separatorStyle.Pattern; - } - - title += numberStyle.Pattern.Replace("%s", String.Format("{0}", episodes[0].SeasonNumber)) - .Replace("%0s", String.Format("{0:00}", episodes[0].SeasonNumber)) - .Replace("%0e", String.Format("{0:00}", episodes[0].EpisodeNumber)); - - if (useEpisodeName) - { - title += separatorStyle.Pattern; - title += episodes[0].Title; - } - - if (appendQuality) - title += String.Format(" [{0}]", episodeFile.Quality); - - if (replaceSpaces) - title = title.Replace(' ', '.'); - - Logger.Debug("New File Name is: {0}", title); - return title; - } - - var multiEpisodeStyle = EpisodeSortingHelper.GetMultiEpisodeStyle(_configProvider.MultiEpisodeStyle); - - if (useSeriesName) - { - title += seriesName; - title += separatorStyle.Pattern; - } - - title += numberStyle.Pattern.Replace("%s", String.Format("{0}", episodes[0].SeasonNumber)) - .Replace("%0s", String.Format("{0:00}", episodes[0].SeasonNumber)) - .Replace("%0e", String.Format("{0:00}", episodes[0].EpisodeNumber)); - - var numbers = String.Empty; - var episodeNames = episodes[0].Title; - - for (int i = 1; i < episodes.Count; i++) - { - var episode = episodes[i]; - - if (multiEpisodeStyle.Name == "Duplicate") - { - numbers += separatorStyle.Pattern + numberStyle.Pattern.Replace("%s", String.Format("{0}", episode.SeasonNumber)) - .Replace("%0s", String.Format("{0:00}", episode.SeasonNumber)) - .Replace("%0e", String.Format("{0:00}", episode.EpisodeNumber)); - } - else - { - numbers += multiEpisodeStyle.Pattern.Replace("%s", String.Format("{0}", episode.SeasonNumber)) - .Replace("%0s", String.Format("{0:00}", episode.SeasonNumber)) - .Replace("%0e", String.Format("{0:00}", episode.EpisodeNumber)) - .Replace("%x", numberStyle.EpisodeSeparator) - .Replace("%p", separatorStyle.Pattern); - } - - episodeNames += String.Format(" + {0}", episode.Title); - } - - title += numbers; - - if (useEpisodeName) - { - episodeNames = episodeNames.TrimEnd(' ', '+'); - - title += separatorStyle.Pattern; - title += episodeNames; - } - - if (appendQuality) - title += String.Format(" [{0}]", episodeFile.Quality); - - if (replaceSpaces) - title = title.Replace(' ', '.'); - - Logger.Debug("New File Name is: {0}", title); - return title; - } - } -} \ No newline at end of file diff --git a/NzbDrone.Core/Repository/EpisodeFile.cs b/NzbDrone.Core/Repository/EpisodeFile.cs index 8e4a6433f..56bddda87 100644 --- a/NzbDrone.Core/Repository/EpisodeFile.cs +++ b/NzbDrone.Core/Repository/EpisodeFile.cs @@ -19,9 +19,9 @@ namespace NzbDrone.Core.Repository public DateTime DateAdded { get; set; } [SubSonicToManyRelation] - public virtual IList Episodes { get; private set; } + public virtual IList Episodes { get; set; } [SubSonicToOneRelation(ThisClassContainsJoinKey = true)] - public virtual Series Series { get; private set; } + public virtual Series Series { get; set; } } } \ No newline at end of file diff --git a/NzbDrone.Web/Controllers/SeriesController.cs b/NzbDrone.Web/Controllers/SeriesController.cs index b89058e17..a61cc2122 100644 --- a/NzbDrone.Web/Controllers/SeriesController.cs +++ b/NzbDrone.Web/Controllers/SeriesController.cs @@ -16,7 +16,6 @@ namespace NzbDrone.Web.Controllers { private readonly EpisodeProvider _episodeProvider; private readonly QualityProvider _qualityProvider; - private readonly RenameProvider _renameProvider; private readonly SeriesProvider _seriesProvider; private readonly TvDbProvider _tvDbProvider; private readonly JobProvider _jobProvider; @@ -27,7 +26,6 @@ namespace NzbDrone.Web.Controllers public SeriesController(SeriesProvider seriesProvider, EpisodeProvider episodeProvider, QualityProvider qualityProvider, - RenameProvider renameProvider, TvDbProvider tvDbProvider, JobProvider jobProvider, MediaFileProvider mediaFileProvider) @@ -35,7 +33,6 @@ namespace NzbDrone.Web.Controllers _seriesProvider = seriesProvider; _episodeProvider = episodeProvider; _qualityProvider = qualityProvider; - _renameProvider = renameProvider; _tvDbProvider = tvDbProvider; _jobProvider = jobProvider; _mediaFileProvider = mediaFileProvider;