diff --git a/NzbDrone.Core.Test/Files/RSS/SizeParsing/newzbin.xml b/NzbDrone.Core.Test/Files/RSS/SizeParsing/newzbin.xml index 220f672d2..502d1b85f 100644 --- a/NzbDrone.Core.Test/Files/RSS/SizeParsing/newzbin.xml +++ b/NzbDrone.Core.Test/Files/RSS/SizeParsing/newzbin.xml @@ -1,6 +1,6 @@  +xmlns:report="http://www.newzbin2.es/DTD/2007/feeds/report/"> www.newzbin.com (reports) http://www.newzbin.com/browse/category/p/tv/ @@ -63,7 +63,7 @@ xmlns:report="http://www.newzbin.com/DTD/2007/feeds/report/"> http://www.tvrage.com/Rookie_Blue/episodes/1064943067/1x10/ 373966350 - tvp-rookieblue-s01e10-720p.nfo + rookieblue-s01e10-720p-tvp.nfo http://www.newzbin.com/nfo/view/txt/373966350/ http://www.newzbin.com/browse/post/6076287/nzb/ diff --git a/NzbDrone.Core.Test/Files/RSS/newzbin.xml b/NzbDrone.Core.Test/Files/RSS/newzbin.xml index 99b5bb09d..909632fb9 100644 --- a/NzbDrone.Core.Test/Files/RSS/newzbin.xml +++ b/NzbDrone.Core.Test/Files/RSS/newzbin.xml @@ -1,6 +1,6 @@  +xmlns:report="http://www.newzbin2.es/DTD/2007/feeds/report/"> www.newzbin.com (reports) http://www.newzbin.com/browse/category/p/tv/ diff --git a/NzbDrone.Core.Test/IndexerTests.cs b/NzbDrone.Core.Test/IndexerTests.cs index c51defcee..d843dac5c 100644 --- a/NzbDrone.Core.Test/IndexerTests.cs +++ b/NzbDrone.Core.Test/IndexerTests.cs @@ -692,5 +692,21 @@ public void NzbClub_NzbInfoUrl_should_contain_information_string() episodeParseResult.NzbInfoUrl.Should().Contain(expectedString); } } + + [Test] + public void releaseGroup_should_use_nfo_filename_for_newzbin() + { + WithConfiguredIndexers(); + + Mocker.GetMock() + .Setup(h => h.DownloadStream(It.IsAny(), It.IsAny())) + .Returns(File.OpenRead(".\\Files\\Rss\\SizeParsing\\newzbin.xml")); + + //Act + var parseResults = Mocker.Resolve().FetchRss(); + + parseResults.Should().HaveCount(1); + parseResults[0].ReleaseGroup.Should().Be("tvp"); + } } } diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 63dd57d24..78d59655d 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -113,6 +113,7 @@ + diff --git a/NzbDrone.Core.Test/ParserTest.cs b/NzbDrone.Core.Test/ParserTest.cs index 5289e2b10..90898be65 100644 --- a/NzbDrone.Core.Test/ParserTest.cs +++ b/NzbDrone.Core.Test/ParserTest.cs @@ -185,7 +185,6 @@ public void parsing_our_own_quality_enum() { var qualityEnums = Enum.GetValues(typeof(QualityTypes)); - foreach (var qualityEnum in qualityEnums) { var fileName = String.Format("My series S01E01 [{0}]", qualityEnum); @@ -276,8 +275,6 @@ public void series_name_normalize(string parsedSeriesName, string seriesName) result.Should().Be(seriesName); } - - [TestCase("CaPitAl", "capital")] [TestCase("peri.od", "period")] [TestCase("this.^&%^**$%@#$!That", "thisthat")] @@ -290,7 +287,6 @@ public void Normalize_Title(string dirty, string clean) result.Should().Be(clean); } - [TestCase("the")] [TestCase("and")] [TestCase("or")] @@ -360,7 +356,6 @@ public void parse_series_name(string postTitle, string title) result.Should().Be(Parser.NormalizeTitle(title)); } - [TestCase("Castle.2009.S01E14.English.HDTV.XviD-LOL", LanguageType.English)] [TestCase("Castle.2009.S01E14.French.HDTV.XviD-LOL", LanguageType.French)] [TestCase("Castle.2009.S01E14.Spanish.HDTV.XviD-LOL", LanguageType.Spanish)] @@ -445,5 +440,16 @@ public void unparsable_should_log_error_but_not_throw(string title) ExceptionVerification.IgnoreWarns(); ExceptionVerification.ExpectedErrors(1); } + + [TestCase("Castle.2009.S01E14.English.HDTV.XviD-LOL", "LOL")] + [TestCase("Castle 2009 S01E14 English HDTV XviD LOL", "LOL")] + [TestCase("Acropolis Now S05 EXTRAS DVDRip XviD RUNNER", "RUNNER")] + [TestCase("Punky.Brewster.S01.EXTRAS.DVDRip.XviD-RUNNER", "RUNNER")] + [TestCase("2020.NZ.2011.12.02.PDTV.XviD-C4TV", "C4TV")] + [TestCase("The.Office.S03E115.DVDRip.XviD-OSiTV", "OSiTV")] + public void parse_releaseGroup(string title, string expected) + { + Parser.ParseReleaseGroup(title).Should().Be(expected); + } } } diff --git a/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/AllowedReleaseGroupSpecificationFixture.cs b/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/AllowedReleaseGroupSpecificationFixture.cs new file mode 100644 index 000000000..760496dcb --- /dev/null +++ b/NzbDrone.Core.Test/ProviderTests/DecisionEngineTests/AllowedReleaseGroupSpecificationFixture.cs @@ -0,0 +1,69 @@ +// ReSharper disable RedundantUsingDirective + +using System.Linq; +using System; +using System.Collections.Generic; +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.Providers.DecisionEngine; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.ProviderTests.DecisionEngineTests +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class AllowedReleaseGroupSpecificationFixture : CoreTest + { + private EpisodeParseResult parseResult; + + [SetUp] + public void Setup() + { + parseResult = new EpisodeParseResult + { + SeriesTitle = "Title", + Language = LanguageType.English, + Quality = new Quality(QualityTypes.SDTV, true), + EpisodeNumbers = new List { 3 }, + SeasonNumber = 12, + AirDate = DateTime.Now.AddDays(-12).Date, + ReleaseGroup = "2HD" + }; + } + + [Test] + public void should_be_true_when_allowedReleaseGroups_is_empty() + { + Mocker.GetMock().SetupGet(s => s.AllowedReleaseGroups).Returns(String.Empty); + Mocker.Resolve().IsSatisfiedBy(parseResult).Should().BeTrue(); + } + + [Test] + public void should_be_true_when_allowedReleaseGroups_is_nzbs_releaseGroup() + { + Mocker.GetMock().SetupGet(s => s.AllowedReleaseGroups).Returns("2HD"); + Mocker.Resolve().IsSatisfiedBy(parseResult).Should().BeTrue(); + } + + [Test] + public void should_be_true_when_allowedReleaseGroups_contains_nzbs_releaseGroup() + { + Mocker.GetMock().SetupGet(s => s.AllowedReleaseGroups).Returns("2HD, LOL"); + Mocker.Resolve().IsSatisfiedBy(parseResult).Should().BeTrue(); + } + + [Test] + public void should_be_false_when_allowedReleaseGroups_does_not_contain_nzbs_releaseGroup() + { + Mocker.GetMock().SetupGet(s => s.AllowedReleaseGroups).Returns("LOL,DTD"); + Mocker.Resolve().IsSatisfiedBy(parseResult).Should().BeFalse(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTest.cs b/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTest.cs index 596da2640..8973b29c0 100644 --- a/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTest.cs +++ b/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTest.cs @@ -201,7 +201,7 @@ public void move_should_not_move_file_if_source_and_destination_are_the_same_pat .Returns(fakeEpisode); Mocker.GetMock() - .Setup(e => e.GetNewFilename(fakeEpisode, fakeSeries.Title, It.IsAny(), It.IsAny())) + .Setup(e => e.GetNewFilename(fakeEpisode, fakeSeries.Title, It.IsAny(), It.IsAny(), It.IsAny())) .Returns(filename); Mocker.GetMock() @@ -298,7 +298,7 @@ public void CleanUpDropFolder_should_move_file_if_a_conflict_is_found() Mocker.GetMock().Setup(s => s.GetEpisodesByFileId(episodeFile.EpisodeFileId)) .Returns(episode); - Mocker.GetMock().Setup(s => s.GetNewFilename(It.IsAny>(), series.Title, QualityTypes.Unknown, false)) + Mocker.GetMock().Setup(s => s.GetNewFilename(It.IsAny>(), series.Title, QualityTypes.Unknown, false, It.IsAny())) .Returns(newFilename); Mocker.GetMock().Setup(s => s.CalculateFilePath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) @@ -350,7 +350,7 @@ public void MoveEpisodeFile_should_use_EpisodeFiles_quality() .Returns(fakeEpisode); Mocker.GetMock() - .Setup(e => e.GetNewFilename(fakeEpisode, fakeSeries.Title, It.IsAny(), It.IsAny())) + .Setup(e => e.GetNewFilename(fakeEpisode, fakeSeries.Title, It.IsAny(), It.IsAny(), It.IsAny())) .Returns(filename); Mocker.GetMock() diff --git a/NzbDrone.Core.Test/ProviderTests/MediaFileProviderTests/GetNewFilenameFixture.cs b/NzbDrone.Core.Test/ProviderTests/MediaFileProviderTests/GetNewFilenameFixture.cs index 12f333614..21d0de628 100644 --- a/NzbDrone.Core.Test/ProviderTests/MediaFileProviderTests/GetNewFilenameFixture.cs +++ b/NzbDrone.Core.Test/ProviderTests/MediaFileProviderTests/GetNewFilenameFixture.cs @@ -1,7 +1,7 @@ // ReSharper disable RedundantUsingDirective using System.Collections.Generic; - +using System.IO; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; @@ -39,7 +39,7 @@ public void GetNewFilename_Series_Episode_Quality_S01E05_Dash() .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false); + string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false, new EpisodeFile()); //Assert Assert.AreEqual("South Park - S15E06 - City Sushi [HDTV]", result); @@ -66,7 +66,7 @@ public void GetNewFilename_Episode_Quality_1x05_Dash() .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false); + string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false, new EpisodeFile()); //Assert Assert.AreEqual("15x06 - City Sushi [HDTV]", result); @@ -93,7 +93,7 @@ public void GetNewFilename_Series_Quality_01x05_Space() .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false); + string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false, new EpisodeFile()); //Assert Assert.AreEqual("South Park 05x06 [HDTV]", result); @@ -121,7 +121,7 @@ public void GetNewFilename_Series_s01e05_Space() .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false); + string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false, new EpisodeFile()); //Assert Assert.AreEqual("South Park s05e06", result); @@ -148,7 +148,7 @@ public void GetNewFilename_Series_Episode_s01e05_Periods() .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false); + string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false, new EpisodeFile()); //Assert Assert.AreEqual("South.Park.s05e06.City.Sushi", result); @@ -175,7 +175,7 @@ public void GetNewFilename_Series_Episode_s01e05_Dash_Periods_Quality() .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false); + string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false, new EpisodeFile()); //Assert Assert.AreEqual("South.Park.-.s05e06.-.City.Sushi.[HDTV]", result); @@ -203,7 +203,7 @@ public void GetNewFilename_S01E05_Dash() .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false); + string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false, new EpisodeFile()); //Assert Assert.AreEqual("S15E06", result); @@ -237,7 +237,7 @@ public void GetNewFilename_multi_Series_Episode_Quality_S01E05_Scene_Dash() .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV, false); + string result = Mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV, false, new EpisodeFile()); //Assert Assert.AreEqual("The Mentalist - S03E23-E24 - Strawberries and Cream (1) + Strawberries and Cream (2) [HDTV]", result); @@ -271,7 +271,7 @@ public void GetNewFilename_multi_Episode_Quality_1x05_Repeat_Dash() .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV, false); + string result = Mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV, false, new EpisodeFile()); //Assert Assert.AreEqual("3x23x24 - Strawberries and Cream (1) + Strawberries and Cream (2) [HDTV]", result); @@ -305,7 +305,7 @@ public void GetNewFilename_multi_Episode_Quality_01x05_Repeat_Space() .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV, false); + string result = Mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV, false, new EpisodeFile()); //Assert Assert.AreEqual("3x23x24 Strawberries and Cream (1) + Strawberries and Cream (2) [HDTV]", result); @@ -339,7 +339,7 @@ public void GetNewFilename_multi_Series_Episode_s01e05_Duplicate_Period() .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV, false); + string result = Mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV, false, new EpisodeFile()); //Assert Assert.AreEqual("The.Mentalist.s03e23.s03e24.Strawberries.and.Cream.(1).+.Strawberries.and.Cream.(2)", result); @@ -373,7 +373,7 @@ public void GetNewFilename_multi_Series_S01E05_Extend_Dash_Period() .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV, false); + string result = Mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV, false, new EpisodeFile()); //Assert Assert.AreEqual("The.Mentalist.-.S03E23-24", result); @@ -407,7 +407,7 @@ public void GetNewFilename_multi_1x05_Repeat_Dash_Period() .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV, false); + string result = Mocker.Resolve().GetNewFilename(new List { episodeOne, episodeTwo }, "The Mentalist", QualityTypes.HDTV, false, new EpisodeFile()); //Assert Assert.AreEqual("3x23x24", result); @@ -432,7 +432,7 @@ public void GetNewFilename_should_append_proper_when_proper_and_append_quality_i .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, true); + string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, true, new EpisodeFile()); //Assert result.Should().Be("South Park - S15E06 - City Sushi [HDTV] [Proper]"); @@ -457,7 +457,7 @@ public void GetNewFilename_should_not_append_proper_when_not_proper_and_append_q .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false); + string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false, new EpisodeFile()); //Assert result.Should().Be("South Park - S15E06 - City Sushi [HDTV]"); @@ -482,7 +482,7 @@ public void GetNewFilename_should_not_append_proper_when_proper_and_append_quali .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, true); + string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, true, new EpisodeFile()); //Assert result.Should().Be("South Park - S15E06 - City Sushi"); @@ -514,7 +514,7 @@ public void GetNewFilename_should_order_multiple_episode_files_in_numerical_orde .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episode2, episode }, "30 Rock", QualityTypes.HDTV, false); + string result = Mocker.Resolve().GetNewFilename(new List { episode2, episode }, "30 Rock", QualityTypes.HDTV, false, new EpisodeFile()); //Assert result.Should().Be("30 Rock - S06E06-E07 - Hey, Baby, What's Wrong! (1) + Hey, Baby, What's Wrong! (2)"); @@ -541,7 +541,7 @@ public void GetNewFilename_Series_Episode_Quality_S01E05_Period() .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false); + string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false, new EpisodeFile()); //Assert Assert.AreEqual("South Park.S15E06.City Sushi [HDTV]", result); @@ -568,10 +568,72 @@ public void GetNewFilename_Episode_Quality_1x05_Period() .Build(); //Act - string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false); + string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false, new EpisodeFile()); //Assert Assert.AreEqual("15x06.City Sushi [HDTV]", result); } + + [Test] + public void GetNewFilename_UseSceneName_when_sceneName_isNull() + { + //Setup + var fakeConfig = Mocker.GetMock(); + fakeConfig.SetupGet(c => c.SortingIncludeSeriesName).Returns(false); + fakeConfig.SetupGet(c => c.SortingIncludeEpisodeTitle).Returns(true); + fakeConfig.SetupGet(c => c.SortingAppendQuality).Returns(true); + fakeConfig.SetupGet(c => c.SortingSeparatorStyle).Returns(2); + fakeConfig.SetupGet(c => c.SortingNumberStyle).Returns(0); + fakeConfig.SetupGet(c => c.SortingReplaceSpaces).Returns(false); + fakeConfig.SetupGet(c => c.SortingUseSceneName).Returns(true); + + var episode = Builder.CreateNew() + .With(e => e.Title = "City Sushi") + .With(e => e.SeasonNumber = 15) + .With(e => e.EpisodeNumber = 6) + .Build(); + + var episodeFile = Builder.CreateNew() + .With(e => e.SceneName = null) + .With(e => e.Path = @"C:\Test\TV\30 Rock - S01E01 - Test") + .Build(); + + //Act + string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false, episodeFile); + + //Assert + result.Should().Be(Path.GetFileNameWithoutExtension(episodeFile.Path)); + } + + [Test] + public void GetNewFilename_UseSceneName_when_sceneName_isNotNull() + { + //Setup + var fakeConfig = Mocker.GetMock(); + fakeConfig.SetupGet(c => c.SortingIncludeSeriesName).Returns(false); + fakeConfig.SetupGet(c => c.SortingIncludeEpisodeTitle).Returns(true); + fakeConfig.SetupGet(c => c.SortingAppendQuality).Returns(true); + fakeConfig.SetupGet(c => c.SortingSeparatorStyle).Returns(2); + fakeConfig.SetupGet(c => c.SortingNumberStyle).Returns(0); + fakeConfig.SetupGet(c => c.SortingReplaceSpaces).Returns(false); + fakeConfig.SetupGet(c => c.SortingUseSceneName).Returns(true); + + var episode = Builder.CreateNew() + .With(e => e.Title = "City Sushi") + .With(e => e.SeasonNumber = 15) + .With(e => e.EpisodeNumber = 6) + .Build(); + + var episodeFile = Builder.CreateNew() + .With(e => e.SceneName = "30.Rock.S01E01.xvid-LOL") + .With(e => e.Path = @"C:\Test\TV\30 Rock - S01E01 - Test") + .Build(); + + //Act + string result = Mocker.Resolve().GetNewFilename(new List { episode }, "South Park", QualityTypes.HDTV, false, episodeFile); + + //Assert + result.Should().Be(episodeFile.SceneName); + } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/ProviderTests/MisnamedProviderTest.cs b/NzbDrone.Core.Test/ProviderTests/MisnamedProviderTest.cs index 646cfd372..8636ef9ef 100644 --- a/NzbDrone.Core.Test/ProviderTests/MisnamedProviderTest.cs +++ b/NzbDrone.Core.Test/ProviderTests/MisnamedProviderTest.cs @@ -49,11 +49,11 @@ public void no_misnamed_files() .Setup(c => c.EpisodesWithFiles()).Returns(episodes); Mocker.GetMock() - .Setup(c => c.GetNewFilename(new List { episodes[0] }, "SeriesTitle", It.IsAny(), It.IsAny())) + .Setup(c => c.GetNewFilename(new List { episodes[0] }, "SeriesTitle", It.IsAny(), It.IsAny(), episodeFiles[0])) .Returns("Title1"); Mocker.GetMock() - .Setup(c => c.GetNewFilename(new List { episodes[1] }, "SeriesTitle", It.IsAny(), It.IsAny())) + .Setup(c => c.GetNewFilename(new List { episodes[1] }, "SeriesTitle", It.IsAny(), It.IsAny(), episodeFiles[1])) .Returns("Title2"); //Act @@ -98,11 +98,11 @@ public void all_misnamed_files() .Setup(c => c.EpisodesWithFiles()).Returns(episodes); Mocker.GetMock() - .Setup(c => c.GetNewFilename(new List { episodes[0] }, "SeriesTitle", It.IsAny(), It.IsAny())) + .Setup(c => c.GetNewFilename(new List { episodes[0] }, "SeriesTitle", It.IsAny(), It.IsAny(), episodeFiles[0])) .Returns("New Title 1"); Mocker.GetMock() - .Setup(c => c.GetNewFilename(new List { episodes[1] }, "SeriesTitle", It.IsAny(), It.IsAny())) + .Setup(c => c.GetNewFilename(new List { episodes[1] }, "SeriesTitle", It.IsAny(), It.IsAny(), episodeFiles[1])) .Returns("New Title 2"); //Act @@ -147,11 +147,11 @@ public void one_misnamed_file() .Setup(c => c.EpisodesWithFiles()).Returns(episodes); Mocker.GetMock() - .Setup(c => c.GetNewFilename(new List { episodes[0] }, "SeriesTitle", It.IsAny(), It.IsAny())) + .Setup(c => c.GetNewFilename(new List { episodes[0] }, "SeriesTitle", It.IsAny(), It.IsAny(), episodeFiles[0])) .Returns("New Title 1"); Mocker.GetMock() - .Setup(c => c.GetNewFilename(new List { episodes[1] }, "SeriesTitle", It.IsAny(), It.IsAny())) + .Setup(c => c.GetNewFilename(new List { episodes[1] }, "SeriesTitle", It.IsAny(), It.IsAny(), episodeFiles[1])) .Returns("Title2"); //Act @@ -198,11 +198,11 @@ public void misnamed_multi_episode_file() .Setup(c => c.EpisodesWithFiles()).Returns(episodes); Mocker.GetMock() - .Setup(c => c.GetNewFilename(new List { episodes[0], episodes[1] }, "SeriesTitle", It.IsAny(), It.IsAny())) + .Setup(c => c.GetNewFilename(new List { episodes[0], episodes[1] }, "SeriesTitle", It.IsAny(), It.IsAny(), episodeFiles[0])) .Returns("New Title 1"); Mocker.GetMock() - .Setup(c => c.GetNewFilename(new List { episodes[2] }, "SeriesTitle", It.IsAny(), It.IsAny())) + .Setup(c => c.GetNewFilename(new List { episodes[2] }, "SeriesTitle", It.IsAny(), It.IsAny(), episodeFiles[1])) .Returns("Title2"); //Act @@ -249,11 +249,11 @@ public void no_misnamed_multi_episode_file() .Setup(c => c.EpisodesWithFiles()).Returns(episodes); Mocker.GetMock() - .Setup(c => c.GetNewFilename(new List { episodes[0], episodes[1] }, "SeriesTitle", It.IsAny(), It.IsAny())) + .Setup(c => c.GetNewFilename(new List { episodes[0], episodes[1] }, "SeriesTitle", It.IsAny(), It.IsAny(), episodeFiles[0])) .Returns("Title1"); Mocker.GetMock() - .Setup(c => c.GetNewFilename(new List { episodes[2] }, "SeriesTitle", It.IsAny(), It.IsAny())) + .Setup(c => c.GetNewFilename(new List { episodes[2] }, "SeriesTitle", It.IsAny(), It.IsAny(), episodeFiles[1])) .Returns("Title2"); //Act diff --git a/NzbDrone.Core/Datastore/Migrations/Migration20120802.cs b/NzbDrone.Core/Datastore/Migrations/Migration20120802.cs new file mode 100644 index 000000000..fbe04ca84 --- /dev/null +++ b/NzbDrone.Core/Datastore/Migrations/Migration20120802.cs @@ -0,0 +1,19 @@ +using System; +using System.Data; +using Migrator.Framework; +using NzbDrone.Common; + +namespace NzbDrone.Core.Datastore.Migrations +{ + + [Migration(20120802)] + public class Migration20120802 : NzbDroneMigration + { + protected override void MainDbUpgrade() + { + Database.AddColumn("EpisodeFiles", new Column("SceneName", DbType.String, ColumnProperty.Null)); + Database.AddColumn("EpisodeFiles", new Column("ReleaseGroup", DbType.String, ColumnProperty.Null)); + Database.AddColumn("History", new Column("ReleaseGroup", DbType.String, ColumnProperty.Null)); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Model/EpisodeParseResult.cs b/NzbDrone.Core/Model/EpisodeParseResult.cs index 97a4a78a4..e2385d5ff 100644 --- a/NzbDrone.Core/Model/EpisodeParseResult.cs +++ b/NzbDrone.Core/Model/EpisodeParseResult.cs @@ -44,6 +44,8 @@ public string CleanTitle public int Age { get; set; } + public string ReleaseGroup { get; set; } + public override string ToString() { diff --git a/NzbDrone.Core/Model/HistoryQueryModel.cs b/NzbDrone.Core/Model/HistoryQueryModel.cs index 00a018c2b..198178f60 100644 --- a/NzbDrone.Core/Model/HistoryQueryModel.cs +++ b/NzbDrone.Core/Model/HistoryQueryModel.cs @@ -16,6 +16,7 @@ public class HistoryQueryModel public bool IsProper { get; set; } public string Indexer { get; set; } public string NzbInfoUrl { get; set; } + public string ReleaseGroup { get; set; } public string EpisodeTitle { get; set; } public int SeasonNumber { get; set; } diff --git a/NzbDrone.Core/Model/ReportRejectionType.cs b/NzbDrone.Core/Model/ReportRejectionType.cs index 867f37565..b4cf7336f 100644 --- a/NzbDrone.Core/Model/ReportRejectionType.cs +++ b/NzbDrone.Core/Model/ReportRejectionType.cs @@ -17,5 +17,6 @@ public enum ReportRejectionType DownloadClientFailure = 10, Skipped = 11, Failure = 12, + ReleaseGroupNotWanted = 13 } } diff --git a/NzbDrone.Core/NzbDrone.Core.csproj b/NzbDrone.Core/NzbDrone.Core.csproj index 14cb64c3c..dc3c29be6 100644 --- a/NzbDrone.Core/NzbDrone.Core.csproj +++ b/NzbDrone.Core/NzbDrone.Core.csproj @@ -227,6 +227,7 @@ + @@ -287,6 +288,7 @@ + diff --git a/NzbDrone.Core/Parser.cs b/NzbDrone.Core/Parser.cs index da7d6c51f..a151e0ef5 100644 --- a/NzbDrone.Core/Parser.cs +++ b/NzbDrone.Core/Parser.cs @@ -1,468 +1,491 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using NLog; -using NzbDrone.Common; -using NzbDrone.Core.Model; -using NzbDrone.Core.Repository.Quality; - -namespace NzbDrone.Core -{ - public static class Parser - { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - - private static readonly Regex[] ReportTitleRegex = new[] - { - //Episodes with airdate - new Regex(@"^(?.+?)?\W*(?<airyear>\d{4})\W+(?<airmonth>[0-1][0-9])\W+(?<airday>[0-3][0-9])\W?(?!\\)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), - - //Multi-Part episodes without a title (S01E05.S01E06) - new Regex(@"^(?:\W*S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:[ex]){1,2}(?<episode>\d{1,2}(?!\d+)))+){2,}\W?(?!\\)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), - - //Multi-episode Repeated (S01E05 - S01E06, 1x05 - 1x06, etc) - new Regex(@"^(?<title>.+?)(?:\W+S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:[ex]){1,2}(?<episode>\d{1,2}(?!\d+)))+){2,}\W?(?!\\)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), - - //Episodes without a title, Single (S01E05, 1x05) AND Multi (S01E04E05, 1x04x05, etc) - new Regex(@"^(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex])(?<episode>\d{2}(?!\d+)))+\W*)+\W?(?!\\)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), - - //Episodes with a title, Single episodes (S01E05, 1x05, etc) & Multi-episode (S01E05E06, S01E05-06, S01E05 E06, etc) - new Regex(@"^(?<title>.+?)(?:\W+S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+)\W?(?!\\)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), - - //Episodes over 99 (3-digits or more) (S01E105, S01E105E106, etc) - new Regex(@"^(?<title>.*?)(?:\W?S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]){1,2}(?<episode>\d+))+)+\W?(?!\\)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), - - new Regex(@"^(?:S?(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:(?:\-|[ex]|\W[ex])(?<episode>\d{2}(?!\d+)))+\W*)+\W?(?!\\)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), - - //Episodes with a title, Single episodes (S01E05, 1x05, etc) & Multi-episode (S01E05E06, S01E05-06, S01E05 E06, etc) - new Regex(@"^(?<title>.+?)(?:\W+S?(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+)\W?(?!\\)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), - - //Supports 103/113 naming - new Regex(@"^(?<title>.+?)?(?:\W?(?<season>(?<!\d+)\d{1})(?<episode>\d{2}(?!p|i|\d+)))+\W?(?!\\)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), - - //Mini-Series, treated as season 1, episodes are labeled as Part01, Part 01, Part.1 - new Regex(@"^(?<title>.+?)(?:\W+(?:(?:Part\W?|(?<!\d+\W+)e)(?<episode>\d{1,2}(?!\d+)))+)\W?(?!\\)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), - - //Supports 1103/1113 naming - new Regex(@"^(?<title>.+?)?(?:\W?(?<season>(?<!\d+|\(|\[)\d{2})(?<episode>\d{2}(?!p|i|\d+|\)|\])))+\W?(?!\\)", - RegexOptions.IgnoreCase | RegexOptions.Compiled), - - //Supports Season only releases - new Regex(@"^(?<title>.+?)\W(?:S|Season)\W?(?<season>\d{1,2}(?!\d+))\W?(?<extras>EXTRAS|SUBPACK)?(?!\\)", - RegexOptions.IgnoreCase | RegexOptions.Compiled) - }; - - private static readonly Regex NormalizeRegex = new Regex(@"((^|\W)(a|an|the|and|or|of)($|\W))|\W|_|(?:(?<=[^0-9]+)|\b)(?!(?:19\d{2}|20\d{2}))\d+(?=[^0-9ip]+|\b)", - RegexOptions.IgnoreCase | RegexOptions.Compiled); - - private static readonly Regex SimpleTitleRegex = new Regex(@"480[i|p]|720[i|p]|1080[i|p]|[x|h|x\s|h\s]264|DD\W?5\W1|\<|\>|\?|\*|\:|\||""", - RegexOptions.IgnoreCase | RegexOptions.Compiled); - - private static readonly Regex ReportSizeRegex = new Regex(@"(?<value>\d+\.\d{1,2}|\d+\,\d+\.\d{1,2})\W?(?<unit>GB|MB|GiB|MiB)", - RegexOptions.IgnoreCase | RegexOptions.Compiled); - - private static readonly Regex HeaderRegex = new Regex(@"(?:\[.+\]\-\[.+\]\-\[.+\]\-\[)(?<nzbTitle>.+)(?:\]\-.+)", - RegexOptions.IgnoreCase | RegexOptions.Compiled); - - internal static EpisodeParseResult ParsePath(string path) - { - var fileInfo = new FileInfo(path); - - var result = ParseTitle(fileInfo.Name); - - if (result == null) - { - Logger.Trace("Attempting to parse episode info using full path. {0}", fileInfo.FullName); - result = ParseTitle(fileInfo.FullName); - } - - if (result != null) - { - result.OriginalString = path; - } - else - { - Logger.Warn("Unable to parse episode info from path {0}", path); - } - - return result; - } - - internal static EpisodeParseResult ParseTitle(string title) - { - try - { - Logger.Trace("Parsing string '{0}'", title); - var simpleTitle = SimpleTitleRegex.Replace(title, String.Empty); - - foreach (var regex in ReportTitleRegex) - { - var match = regex.Matches(simpleTitle); - - if (match.Count != 0) - { - var result = ParseMatchCollection(match); - if (result != null) - { - //Check if episode is in the future (most likley a parse error) - if (result.AirDate > DateTime.Now.AddDays(1).Date) - break; - - result.Language = ParseLanguage(title); - result.Quality = ParseQuality(title); - result.OriginalString = title; - return result; - } - } - } - } - catch (Exception e) - { - Logger.ErrorException("An error has occurred while trying to parse " + title, e); - } - - Logger.Trace("Unable to parse {0}", title); - ReportingService.ReportParseError(title); - return null; - } - - private static EpisodeParseResult ParseMatchCollection(MatchCollection matchCollection) - { - var seriesName = matchCollection[0].Groups["title"].Value; - - int airyear; - Int32.TryParse(matchCollection[0].Groups["airyear"].Value, out airyear); - - EpisodeParseResult parsedEpisode; - - if (airyear < 1900) - { - var seasons = new List<int>(); - - foreach (Capture seasonCapture in matchCollection[0].Groups["season"].Captures) - { - int parsedSeason; - if (Int32.TryParse(seasonCapture.Value, out parsedSeason)) - seasons.Add(parsedSeason); - } - - //If no season was found it should be treated as a mini series and season 1 - if (seasons.Count == 0) - seasons.Add(1); - - //If more than 1 season was parsed go to the next REGEX (A multi-season release is unlikely) - if (seasons.Distinct().Count() > 1) - return null; - - parsedEpisode = new EpisodeParseResult - { - SeasonNumber = seasons.First(), - EpisodeNumbers = new List<int>() - }; - - foreach (Match matchGroup in matchCollection) - { - var episodeCaptures = matchGroup.Groups["episode"].Captures.Cast<Capture>().ToList(); - - //Allows use to return a list of 0 episodes (We can handle that as a full season release) - if (episodeCaptures.Any()) - { - var first = Convert.ToInt32(episodeCaptures.First().Value); - var last = Convert.ToInt32(episodeCaptures.Last().Value); - parsedEpisode.EpisodeNumbers = Enumerable.Range(first, last - first + 1).ToList(); - } - else - { - //Check to see if this is an "Extras" or "SUBPACK" release, if it is, return NULL - //Todo: Set a "Extras" flag in EpisodeParseResult if we want to download them ever - if (!String.IsNullOrWhiteSpace(matchCollection[0].Groups["extras"].Value)) - return null; - - parsedEpisode.FullSeason = true; - } - } - } - - else - { - //Try to Parse as a daily show - var airmonth = Convert.ToInt32(matchCollection[0].Groups["airmonth"].Value); - var airday = Convert.ToInt32(matchCollection[0].Groups["airday"].Value); - - //Swap day and month if month is bigger than 12 (scene fail) - if (airmonth > 12) - { - var tempDay = airday; - airday = airmonth; - airmonth = tempDay; - } - - parsedEpisode = new EpisodeParseResult - { - - AirDate = new DateTime(airyear, airmonth, airday).Date, - }; - } - - parsedEpisode.SeriesTitle = seriesName; - - Logger.Trace("Episode Parsed. {0}", parsedEpisode); - - return parsedEpisode; - } - - public static string ParseSeriesName(string title) - { - Logger.Trace("Parsing string '{0}'", title); - - foreach (var regex in ReportTitleRegex) - { - var match = regex.Matches(title); - - if (match.Count != 0) - { - var seriesName = NormalizeTitle(match[0].Groups["title"].Value); - - Logger.Trace("Series Parsed. {0}", seriesName); - return seriesName; - } - } - - return NormalizeTitle(title); - } - - internal static Quality ParseQuality(string name) - { - Logger.Trace("Trying to parse quality for {0}", name); - - name = name.Trim(); - var normalizedName = NormalizeTitle(name); - var result = new Quality { QualityType = QualityTypes.Unknown }; - result.Proper = (normalizedName.Contains("proper") || normalizedName.Contains("repack")); - - if (normalizedName.Contains("dvd") || normalizedName.Contains("bdrip") || normalizedName.Contains("brrip")) - { - result.QualityType = QualityTypes.DVD; - return result; - } - - if (normalizedName.Contains("xvid") || normalizedName.Contains("divx") || normalizedName.Contains("dsr")) - { - if (normalizedName.Contains("bluray")) - { - result.QualityType = QualityTypes.DVD; - return result; - } - - result.QualityType = QualityTypes.SDTV; - return result; - } - - if (normalizedName.Contains("bluray")) - { - if (normalizedName.Contains("720p")) - { - result.QualityType = QualityTypes.Bluray720p; - return result; - } - - if (normalizedName.Contains("1080p")) - { - result.QualityType = QualityTypes.Bluray1080p; - return result; - } - - result.QualityType = QualityTypes.Bluray720p; - return result; - } - if (normalizedName.Contains("webdl")) - { - result.QualityType = QualityTypes.WEBDL; - return result; - } - if (normalizedName.Contains("x264") || normalizedName.Contains("h264") || normalizedName.Contains("720p")) - { - result.QualityType = QualityTypes.HDTV; - return result; - } - //Based on extension - - if (result.QualityType == QualityTypes.Unknown) - { - try - { - switch (Path.GetExtension(name).ToLower()) - { - case ".avi": - case ".xvid": - case ".divx": - case ".wmv": - case ".mp4": - case ".mpg": - case ".mpeg": - case ".mov": - case ".rm": - case ".rmvb": - case ".flv": - case ".dvr-ms": - case ".ogm": - case ".strm": - { - result.QualityType = QualityTypes.SDTV; - break; - } - case ".mkv": - case ".ts": - { - result.QualityType = QualityTypes.HDTV; - break; - } - } - } - catch (ArgumentException) - { - //Swallow exception for cases where string contains illegal - //path characters. - } - } - - if (name.Contains("[HDTV]")) - { - result.QualityType = QualityTypes.HDTV; - return result; - } - - if ((normalizedName.Contains("sdtv") || normalizedName.Contains("pdtv") || - (result.QualityType == QualityTypes.Unknown && normalizedName.Contains("hdtv"))) && - !normalizedName.Contains("mpeg")) - { - result.QualityType = QualityTypes.SDTV; - return result; - } - - return result; - } - - internal static LanguageType ParseLanguage(string title) - { - var lowerTitle = title.ToLower(); - - if (lowerTitle.Contains("english")) - return LanguageType.English; - - if (lowerTitle.Contains("french")) - return LanguageType.French; - - if (lowerTitle.Contains("spanish")) - return LanguageType.Spanish; - - if (lowerTitle.Contains("german")) - { - //Make sure it doesn't contain Germany (Since we're not using REGEX for all this) - if (!lowerTitle.Contains("germany")) - return LanguageType.German; - } - - if (lowerTitle.Contains("italian")) - return LanguageType.Italian; - - if (lowerTitle.Contains("danish")) - return LanguageType.Danish; - - if (lowerTitle.Contains("dutch")) - return LanguageType.Dutch; - - if (lowerTitle.Contains("japanese")) - return LanguageType.Japanese; - - if (lowerTitle.Contains("cantonese")) - return LanguageType.Cantonese; - - if (lowerTitle.Contains("mandarin")) - return LanguageType.Mandarin; - - if (lowerTitle.Contains("korean")) - return LanguageType.Korean; - - if (lowerTitle.Contains("russian")) - return LanguageType.Russian; - - if (lowerTitle.Contains("polish")) - return LanguageType.Polish; - - if (lowerTitle.Contains("vietnamese")) - return LanguageType.Vietnamese; - - if (lowerTitle.Contains("swedish")) - return LanguageType.Swedish; - - if (lowerTitle.Contains("norwegian")) - return LanguageType.Norwegian; - - if (lowerTitle.Contains("finnish")) - return LanguageType.Finnish; - - if (lowerTitle.Contains("turkish")) - return LanguageType.Turkish; - - if (lowerTitle.Contains("portuguese")) - return LanguageType.Portuguese; - - return LanguageType.English; - } - - public static string NormalizeTitle(string title) - { - long number = 0; - - //If Title only contains numbers return it as is. - if (Int64.TryParse(title, out number)) - return title; - - return NormalizeRegex.Replace(title, String.Empty).ToLower(); - } - - public static long GetReportSize(string sizeString) - { - var match = ReportSizeRegex.Matches(sizeString); - - if (match.Count != 0) - { - var cultureInfo = new CultureInfo("en-US"); - var value = Decimal.Parse(Regex.Replace(match[0].Groups["value"].Value, "\\,", ""), cultureInfo); - - var unit = match[0].Groups["unit"].Value; - - if (unit.Equals("MB", StringComparison.InvariantCultureIgnoreCase) || unit.Equals("MiB", StringComparison.InvariantCultureIgnoreCase)) - return Convert.ToInt64(value * 1048576L); - - if (unit.Equals("GB", StringComparison.InvariantCultureIgnoreCase) || unit.Equals("GiB", StringComparison.InvariantCultureIgnoreCase)) - return Convert.ToInt64(value * 1073741824L); - } - return 0; - } - - internal static string ParseHeader(string header) - { - var match = HeaderRegex.Matches(header); - - if (match.Count != 0) - return match[0].Groups["nzbTitle"].Value; - - return header; - } - } -}��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using NLog; +using NzbDrone.Common; +using NzbDrone.Core.Model; +using NzbDrone.Core.Repository.Quality; + +namespace NzbDrone.Core +{ + public static class Parser + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + private static readonly Regex[] ReportTitleRegex = new[] + { + //Episodes with airdate + new Regex(@"^(?<title>.+?)?\W*(?<airyear>\d{4})\W+(?<airmonth>[0-1][0-9])\W+(?<airday>[0-3][0-9])\W?(?!\\)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + + //Multi-Part episodes without a title (S01E05.S01E06) + new Regex(@"^(?:\W*S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:[ex]){1,2}(?<episode>\d{1,2}(?!\d+)))+){2,}\W?(?!\\)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + + //Multi-episode Repeated (S01E05 - S01E06, 1x05 - 1x06, etc) + new Regex(@"^(?<title>.+?)(?:\W+S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:[ex]){1,2}(?<episode>\d{1,2}(?!\d+)))+){2,}\W?(?!\\)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + + //Episodes without a title, Single (S01E05, 1x05) AND Multi (S01E04E05, 1x04x05, etc) + new Regex(@"^(?:S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex])(?<episode>\d{2}(?!\d+)))+\W*)+\W?(?!\\)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + + //Episodes with a title, Single episodes (S01E05, 1x05, etc) & Multi-episode (S01E05E06, S01E05-06, S01E05 E06, etc) + new Regex(@"^(?<title>.+?)(?:\W+S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+)\W?(?!\\)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + + //Episodes over 99 (3-digits or more) (S01E105, S01E105E106, etc) + new Regex(@"^(?<title>.*?)(?:\W?S?(?<season>(?<!\d+)\d{1,2}(?!\d+))(?:(?:\-|[ex]){1,2}(?<episode>\d+))+)+\W?(?!\\)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + + new Regex(@"^(?:S?(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:(?:\-|[ex]|\W[ex])(?<episode>\d{2}(?!\d+)))+\W*)+\W?(?!\\)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + + //Episodes with a title, Single episodes (S01E05, 1x05, etc) & Multi-episode (S01E05E06, S01E05-06, S01E05 E06, etc) + new Regex(@"^(?<title>.+?)(?:\W+S?(?<season>(?<!\d+)(?:\d{1,2}|\d{4})(?!\d+))(?:(?:\-|[ex]|\W[ex]){1,2}(?<episode>\d{2}(?!\d+)))+)\W?(?!\\)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + + //Supports 103/113 naming + new Regex(@"^(?<title>.+?)?(?:\W?(?<season>(?<!\d+)\d{1})(?<episode>\d{2}(?!p|i|\d+)))+\W?(?!\\)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + + //Mini-Series, treated as season 1, episodes are labeled as Part01, Part 01, Part.1 + new Regex(@"^(?<title>.+?)(?:\W+(?:(?:Part\W?|(?<!\d+\W+)e)(?<episode>\d{1,2}(?!\d+)))+)\W?(?!\\)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + + //Supports 1103/1113 naming + new Regex(@"^(?<title>.+?)?(?:\W?(?<season>(?<!\d+|\(|\[)\d{2})(?<episode>\d{2}(?!p|i|\d+|\)|\])))+\W?(?!\\)", + RegexOptions.IgnoreCase | RegexOptions.Compiled), + + //Supports Season only releases + new Regex(@"^(?<title>.+?)\W(?:S|Season)\W?(?<season>\d{1,2}(?!\d+))\W?(?<extras>EXTRAS|SUBPACK)?(?!\\)", + RegexOptions.IgnoreCase | RegexOptions.Compiled) + }; + + private static readonly Regex NormalizeRegex = new Regex(@"((^|\W)(a|an|the|and|or|of)($|\W))|\W|_|(?:(?<=[^0-9]+)|\b)(?!(?:19\d{2}|20\d{2}))\d+(?=[^0-9ip]+|\b)", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static readonly Regex SimpleTitleRegex = new Regex(@"480[i|p]|720[i|p]|1080[i|p]|[x|h|x\s|h\s]264|DD\W?5\W1|\<|\>|\?|\*|\:|\||""", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static readonly Regex ReportSizeRegex = new Regex(@"(?<value>\d+\.\d{1,2}|\d+\,\d+\.\d{1,2})\W?(?<unit>GB|MB|GiB|MiB)", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static readonly Regex HeaderRegex = new Regex(@"(?:\[.+\]\-\[.+\]\-\[.+\]\-\[)(?<nzbTitle>.+)(?:\]\-.+)", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + + internal static EpisodeParseResult ParsePath(string path) + { + var fileInfo = new FileInfo(path); + + var result = ParseTitle(fileInfo.Name); + + if (result == null) + { + Logger.Trace("Attempting to parse episode info using full path. {0}", fileInfo.FullName); + result = ParseTitle(fileInfo.FullName); + } + + if (result != null) + { + result.OriginalString = path; + } + else + { + Logger.Warn("Unable to parse episode info from path {0}", path); + } + + return result; + } + + internal static EpisodeParseResult ParseTitle(string title) + { + try + { + Logger.Trace("Parsing string '{0}'", title); + var simpleTitle = SimpleTitleRegex.Replace(title, String.Empty); + + foreach (var regex in ReportTitleRegex) + { + var match = regex.Matches(simpleTitle); + + if (match.Count != 0) + { + var result = ParseMatchCollection(match); + if (result != null) + { + //Check if episode is in the future (most likley a parse error) + if (result.AirDate > DateTime.Now.AddDays(1).Date) + break; + + result.Language = ParseLanguage(title); + result.Quality = ParseQuality(title); + result.OriginalString = title; + result.ReleaseGroup = ParseReleaseGroup(title); + return result; + } + } + } + } + catch (Exception e) + { + Logger.ErrorException("An error has occurred while trying to parse " + title, e); + } + + Logger.Trace("Unable to parse {0}", title); + ReportingService.ReportParseError(title); + return null; + } + + private static EpisodeParseResult ParseMatchCollection(MatchCollection matchCollection) + { + var seriesName = matchCollection[0].Groups["title"].Value; + + int airyear; + Int32.TryParse(matchCollection[0].Groups["airyear"].Value, out airyear); + + EpisodeParseResult parsedEpisode; + + if (airyear < 1900) + { + var seasons = new List<int>(); + + foreach (Capture seasonCapture in matchCollection[0].Groups["season"].Captures) + { + int parsedSeason; + if (Int32.TryParse(seasonCapture.Value, out parsedSeason)) + seasons.Add(parsedSeason); + } + + //If no season was found it should be treated as a mini series and season 1 + if (seasons.Count == 0) + seasons.Add(1); + + //If more than 1 season was parsed go to the next REGEX (A multi-season release is unlikely) + if (seasons.Distinct().Count() > 1) + return null; + + parsedEpisode = new EpisodeParseResult + { + SeasonNumber = seasons.First(), + EpisodeNumbers = new List<int>() + }; + + foreach (Match matchGroup in matchCollection) + { + var episodeCaptures = matchGroup.Groups["episode"].Captures.Cast<Capture>().ToList(); + + //Allows use to return a list of 0 episodes (We can handle that as a full season release) + if (episodeCaptures.Any()) + { + var first = Convert.ToInt32(episodeCaptures.First().Value); + var last = Convert.ToInt32(episodeCaptures.Last().Value); + parsedEpisode.EpisodeNumbers = Enumerable.Range(first, last - first + 1).ToList(); + } + else + { + //Check to see if this is an "Extras" or "SUBPACK" release, if it is, return NULL + //Todo: Set a "Extras" flag in EpisodeParseResult if we want to download them ever + if (!String.IsNullOrWhiteSpace(matchCollection[0].Groups["extras"].Value)) + return null; + + parsedEpisode.FullSeason = true; + } + } + } + + else + { + //Try to Parse as a daily show + var airmonth = Convert.ToInt32(matchCollection[0].Groups["airmonth"].Value); + var airday = Convert.ToInt32(matchCollection[0].Groups["airday"].Value); + + //Swap day and month if month is bigger than 12 (scene fail) + if (airmonth > 12) + { + var tempDay = airday; + airday = airmonth; + airmonth = tempDay; + } + + parsedEpisode = new EpisodeParseResult + { + + AirDate = new DateTime(airyear, airmonth, airday).Date, + }; + } + + parsedEpisode.SeriesTitle = seriesName; + + Logger.Trace("Episode Parsed. {0}", parsedEpisode); + + return parsedEpisode; + } + + public static string ParseSeriesName(string title) + { + Logger.Trace("Parsing string '{0}'", title); + + foreach (var regex in ReportTitleRegex) + { + var match = regex.Matches(title); + + if (match.Count != 0) + { + var seriesName = NormalizeTitle(match[0].Groups["title"].Value); + + Logger.Trace("Series Parsed. {0}", seriesName); + return seriesName; + } + } + + return NormalizeTitle(title); + } + + internal static Quality ParseQuality(string name) + { + Logger.Trace("Trying to parse quality for {0}", name); + + name = name.Trim(); + var normalizedName = NormalizeTitle(name); + var result = new Quality { QualityType = QualityTypes.Unknown }; + result.Proper = (normalizedName.Contains("proper") || normalizedName.Contains("repack")); + + if (normalizedName.Contains("dvd") || normalizedName.Contains("bdrip") || normalizedName.Contains("brrip")) + { + result.QualityType = QualityTypes.DVD; + return result; + } + + if (normalizedName.Contains("xvid") || normalizedName.Contains("divx") || normalizedName.Contains("dsr")) + { + if (normalizedName.Contains("bluray")) + { + result.QualityType = QualityTypes.DVD; + return result; + } + + result.QualityType = QualityTypes.SDTV; + return result; + } + + if (normalizedName.Contains("bluray")) + { + if (normalizedName.Contains("720p")) + { + result.QualityType = QualityTypes.Bluray720p; + return result; + } + + if (normalizedName.Contains("1080p")) + { + result.QualityType = QualityTypes.Bluray1080p; + return result; + } + + result.QualityType = QualityTypes.Bluray720p; + return result; + } + if (normalizedName.Contains("webdl")) + { + result.QualityType = QualityTypes.WEBDL; + return result; + } + if (normalizedName.Contains("x264") || normalizedName.Contains("h264") || normalizedName.Contains("720p")) + { + result.QualityType = QualityTypes.HDTV; + return result; + } + //Based on extension + + if (result.QualityType == QualityTypes.Unknown) + { + try + { + switch (Path.GetExtension(name).ToLower()) + { + case ".avi": + case ".xvid": + case ".divx": + case ".wmv": + case ".mp4": + case ".mpg": + case ".mpeg": + case ".mov": + case ".rm": + case ".rmvb": + case ".flv": + case ".dvr-ms": + case ".ogm": + case ".strm": + { + result.QualityType = QualityTypes.SDTV; + break; + } + case ".mkv": + case ".ts": + { + result.QualityType = QualityTypes.HDTV; + break; + } + } + } + catch (ArgumentException) + { + //Swallow exception for cases where string contains illegal + //path characters. + } + } + + if (name.Contains("[HDTV]")) + { + result.QualityType = QualityTypes.HDTV; + return result; + } + + if ((normalizedName.Contains("sdtv") || normalizedName.Contains("pdtv") || + (result.QualityType == QualityTypes.Unknown && normalizedName.Contains("hdtv"))) && + !normalizedName.Contains("mpeg")) + { + result.QualityType = QualityTypes.SDTV; + return result; + } + + return result; + } + + internal static LanguageType ParseLanguage(string title) + { + var lowerTitle = title.ToLower(); + + if (lowerTitle.Contains("english")) + return LanguageType.English; + + if (lowerTitle.Contains("french")) + return LanguageType.French; + + if (lowerTitle.Contains("spanish")) + return LanguageType.Spanish; + + if (lowerTitle.Contains("german")) + { + //Make sure it doesn't contain Germany (Since we're not using REGEX for all this) + if (!lowerTitle.Contains("germany")) + return LanguageType.German; + } + + if (lowerTitle.Contains("italian")) + return LanguageType.Italian; + + if (lowerTitle.Contains("danish")) + return LanguageType.Danish; + + if (lowerTitle.Contains("dutch")) + return LanguageType.Dutch; + + if (lowerTitle.Contains("japanese")) + return LanguageType.Japanese; + + if (lowerTitle.Contains("cantonese")) + return LanguageType.Cantonese; + + if (lowerTitle.Contains("mandarin")) + return LanguageType.Mandarin; + + if (lowerTitle.Contains("korean")) + return LanguageType.Korean; + + if (lowerTitle.Contains("russian")) + return LanguageType.Russian; + + if (lowerTitle.Contains("polish")) + return LanguageType.Polish; + + if (lowerTitle.Contains("vietnamese")) + return LanguageType.Vietnamese; + + if (lowerTitle.Contains("swedish")) + return LanguageType.Swedish; + + if (lowerTitle.Contains("norwegian")) + return LanguageType.Norwegian; + + if (lowerTitle.Contains("finnish")) + return LanguageType.Finnish; + + if (lowerTitle.Contains("turkish")) + return LanguageType.Turkish; + + if (lowerTitle.Contains("portuguese")) + return LanguageType.Portuguese; + + return LanguageType.English; + } + + internal static string ParseReleaseGroup(string title) + { + Logger.Trace("Trying to parse release group for {0}", title); + + title = title.Trim(); + var index = title.LastIndexOf('-'); + + if (index < 0) + index = title.LastIndexOf(' '); + + if (index < 0) + return String.Empty; + + var group = title.Substring(index + 1); + + if (group.Length == title.Length) + return String.Empty; + + Logger.Trace("Release Group found: {0}", group); + return group; + } + + public static string NormalizeTitle(string title) + { + long number = 0; + + //If Title only contains numbers return it as is. + if (Int64.TryParse(title, out number)) + return title; + + return NormalizeRegex.Replace(title, String.Empty).ToLower(); + } + + public static long GetReportSize(string sizeString) + { + var match = ReportSizeRegex.Matches(sizeString); + + if (match.Count != 0) + { + var cultureInfo = new CultureInfo("en-US"); + var value = Decimal.Parse(Regex.Replace(match[0].Groups["value"].Value, "\\,", ""), cultureInfo); + + var unit = match[0].Groups["unit"].Value; + + if (unit.Equals("MB", StringComparison.InvariantCultureIgnoreCase) || unit.Equals("MiB", StringComparison.InvariantCultureIgnoreCase)) + return Convert.ToInt64(value * 1048576L); + + if (unit.Equals("GB", StringComparison.InvariantCultureIgnoreCase) || unit.Equals("GiB", StringComparison.InvariantCultureIgnoreCase)) + return Convert.ToInt64(value * 1073741824L); + } + return 0; + } + + internal static string ParseHeader(string header) + { + var match = HeaderRegex.Matches(header); + + if (match.Count != 0) + return match[0].Groups["nzbTitle"].Value; + + return header; + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core/Providers/Core/ConfigProvider.cs b/NzbDrone.Core/Providers/Core/ConfigProvider.cs index c2637dc3c..605af206b 100644 --- a/NzbDrone.Core/Providers/Core/ConfigProvider.cs +++ b/NzbDrone.Core/Providers/Core/ConfigProvider.cs @@ -214,6 +214,12 @@ public virtual int SortingMultiEpisodeStyle set { SetValue("Sorting_MultiEpisodeStyle", value); } } + public virtual bool SortingUseSceneName + { + get { return GetValueBoolean("Sorting_UseSceneName", false); } + set { SetValue("Sorting_UseSceneName", value); } + } + public virtual int DefaultQualityProfile { get { return GetValueInt("DefaultQualityProfile", 1); } @@ -508,6 +514,12 @@ public virtual Boolean MetadataUseBanners set { SetValue("MetadataUseBanners", value); } } + public virtual string AllowedReleaseGroups + { + get { return GetValue("AllowedReleaseGroups"); } + set { SetValue("AllowedReleaseGroups", value); } + } + private string GetValue(string key) { return GetValue(key, String.Empty); diff --git a/NzbDrone.Core/Providers/DecisionEngine/AllowedDownloadSpecification.cs b/NzbDrone.Core/Providers/DecisionEngine/AllowedDownloadSpecification.cs index c6e437c5f..df58a271f 100644 --- a/NzbDrone.Core/Providers/DecisionEngine/AllowedDownloadSpecification.cs +++ b/NzbDrone.Core/Providers/DecisionEngine/AllowedDownloadSpecification.cs @@ -13,18 +13,21 @@ public class AllowedDownloadSpecification private readonly AcceptableSizeSpecification _acceptableSizeSpecification; private readonly AlreadyInQueueSpecification _alreadyInQueueSpecification; private readonly RetentionSpecification _retentionSpecification; + private readonly AllowedReleaseGroupSpecification _allowedReleaseGroupSpecification; private static readonly Logger logger = LogManager.GetCurrentClassLogger(); [Inject] public AllowedDownloadSpecification(QualityAllowedByProfileSpecification qualityAllowedByProfileSpecification, UpgradeDiskSpecification upgradeDiskSpecification, AcceptableSizeSpecification acceptableSizeSpecification, - AlreadyInQueueSpecification alreadyInQueueSpecification, RetentionSpecification retentionSpecification) + AlreadyInQueueSpecification alreadyInQueueSpecification, RetentionSpecification retentionSpecification, + AllowedReleaseGroupSpecification allowedReleaseGroupSpecification) { _qualityAllowedByProfileSpecification = qualityAllowedByProfileSpecification; _upgradeDiskSpecification = upgradeDiskSpecification; _acceptableSizeSpecification = acceptableSizeSpecification; _alreadyInQueueSpecification = alreadyInQueueSpecification; _retentionSpecification = retentionSpecification; + _allowedReleaseGroupSpecification = allowedReleaseGroupSpecification; } public AllowedDownloadSpecification() @@ -37,8 +40,9 @@ public virtual ReportRejectionType IsSatisfiedBy(EpisodeParseResult subject) if (!_upgradeDiskSpecification.IsSatisfiedBy(subject)) return ReportRejectionType.ExistingQualityIsEqualOrBetter; if (!_retentionSpecification.IsSatisfiedBy(subject)) return ReportRejectionType.Retention; if (!_acceptableSizeSpecification.IsSatisfiedBy(subject)) return ReportRejectionType.Size; + if (!_allowedReleaseGroupSpecification.IsSatisfiedBy(subject)) return ReportRejectionType.ReleaseGroupNotWanted; if (_alreadyInQueueSpecification.IsSatisfiedBy(subject)) return ReportRejectionType.AlreadyInQueue; - + logger.Debug("Episode {0} is needed", subject); return ReportRejectionType.None; } diff --git a/NzbDrone.Core/Providers/DecisionEngine/AllowedReleaseGroupSpecification.cs b/NzbDrone.Core/Providers/DecisionEngine/AllowedReleaseGroupSpecification.cs new file mode 100644 index 000000000..4d24886ea --- /dev/null +++ b/NzbDrone.Core/Providers/DecisionEngine/AllowedReleaseGroupSpecification.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq; +using NLog; +using Ninject; +using NzbDrone.Core.Model; +using NzbDrone.Core.Providers.Core; + +namespace NzbDrone.Core.Providers.DecisionEngine +{ + public class AllowedReleaseGroupSpecification + { + private readonly ConfigProvider _configProvider; + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + [Inject] + public AllowedReleaseGroupSpecification(ConfigProvider configProvider) + { + _configProvider = configProvider; + } + + public AllowedReleaseGroupSpecification() + { + + } + + public virtual bool IsSatisfiedBy(EpisodeParseResult subject) + { + logger.Trace("Beginning release group check for: {0}", subject); + + var allowed = _configProvider.AllowedReleaseGroups; + + if (string.IsNullOrWhiteSpace(allowed)) + return true; + + foreach(var group in allowed.Trim(',', ' ').Split(',')) + { + if (subject.ReleaseGroup.Equals(group.Trim(' '), StringComparison.CurrentCultureIgnoreCase)) + { + logger.Trace("Item: {0}'s release group is wanted: {1}", subject, subject.ReleaseGroup); + return true; + } + } + + logger.Trace("Item: {0}'s release group is not wanted: {1}", subject, subject.ReleaseGroup); + return false; + } + } +} diff --git a/NzbDrone.Core/Providers/DiskScanProvider.cs b/NzbDrone.Core/Providers/DiskScanProvider.cs index 00705ec35..429a9b2bf 100644 --- a/NzbDrone.Core/Providers/DiskScanProvider.cs +++ b/NzbDrone.Core/Providers/DiskScanProvider.cs @@ -153,6 +153,8 @@ public virtual EpisodeFile ImportFile(Series series, string filePath) episodeFile.Quality = parseResult.Quality.QualityType; episodeFile.Proper = parseResult.Quality.Proper; episodeFile.SeasonNumber = parseResult.SeasonNumber; + episodeFile.SceneName = Path.GetFileNameWithoutExtension(filePath.NormalizePath()); + episodeFile.ReleaseGroup = parseResult.ReleaseGroup; var fileId = _mediaFileProvider.Add(episodeFile); //Link file to all episodes @@ -175,7 +177,7 @@ public virtual EpisodeFile MoveEpisodeFile(EpisodeFile episodeFile, bool newDown var series = _seriesProvider.GetSeries(episodeFile.SeriesId); var episodes = _episodeProvider.GetEpisodesByFileId(episodeFile.EpisodeFileId); - string newFileName = _mediaFileProvider.GetNewFilename(episodes, series.Title, episodeFile.Quality, episodeFile.Proper); + string newFileName = _mediaFileProvider.GetNewFilename(episodes, series.Title, episodeFile.Quality, episodeFile.Proper, episodeFile); var newFile = _mediaFileProvider.CalculateFilePath(series, episodes.First().SeasonNumber, newFileName, Path.GetExtension(episodeFile.Path)); //Only rename if existing and new filenames don't match diff --git a/NzbDrone.Core/Providers/DownloadProvider.cs b/NzbDrone.Core/Providers/DownloadProvider.cs index e70f87e74..d9ab4f3b2 100644 --- a/NzbDrone.Core/Providers/DownloadProvider.cs +++ b/NzbDrone.Core/Providers/DownloadProvider.cs @@ -63,6 +63,7 @@ public virtual bool DownloadReport(EpisodeParseResult parseResult) history.EpisodeId = episode.EpisodeId; history.SeriesId = episode.SeriesId; history.NzbInfoUrl = parseResult.NzbInfoUrl; + history.ReleaseGroup = parseResult.ReleaseGroup; _historyProvider.Add(history); _episodeProvider.MarkEpisodeAsFetched(episode.EpisodeId); diff --git a/NzbDrone.Core/Providers/Indexer/IndexerBase.cs b/NzbDrone.Core/Providers/Indexer/IndexerBase.cs index 626104378..f6089a23c 100644 --- a/NzbDrone.Core/Providers/Indexer/IndexerBase.cs +++ b/NzbDrone.Core/Providers/Indexer/IndexerBase.cs @@ -63,7 +63,6 @@ protected virtual NetworkCredential Credentials get { return null; } } - protected abstract IList<String> GetEpisodeSearchUrls(string seriesTitle, int seasonNumber, int episodeNumber); protected abstract IList<String> GetDailyEpisodeSearchUrls(string seriesTitle, DateTime date); protected abstract IList<String> GetSeasonSearchUrls(string seriesTitle, int seasonNumber); diff --git a/NzbDrone.Core/Providers/Indexer/Newzbin.cs b/NzbDrone.Core/Providers/Indexer/Newzbin.cs index 733f70ce3..db91708cf 100644 --- a/NzbDrone.Core/Providers/Indexer/Newzbin.cs +++ b/NzbDrone.Core/Providers/Indexer/Newzbin.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net; using System.ServiceModel.Syndication; using System.Text.RegularExpressions; +using System.Xml.Linq; using Ninject; using NzbDrone.Common; using NzbDrone.Core.Model; @@ -44,7 +46,6 @@ public override bool IsConfigured } } - protected override NetworkCredential Credentials { get { return new NetworkCredential(_configProvider.NewzbinUsername, _configProvider.NewzbinPassword); } @@ -90,7 +91,6 @@ protected override IList<string> GetPartialSeasonSearchUrls(string seriesTitle, }; } - public override string Name { get { return "Newzbin"; } @@ -118,10 +118,25 @@ protected override EpisodeParseResult CustomParser(SyndicationItem item, Episode var sizeString = Regex.Match(item.Summary.Text, @"\(Size: \d*\,?\d+\.\d{1,2}\w{2}\)", RegexOptions.IgnoreCase).Value; currentResult.Size = Parser.GetReportSize(sizeString); + + try + { + var releaseGroupText = item.ElementExtensions.Single(s => s.OuterName == "nfo") + .GetObject<XElement>() + .Element(XName.Get("filename", "http://www.newzbin2.es/DTD/2007/feeds/report/")) + .Value; + + var releaseGroup = Parser.ParseReleaseGroup(releaseGroupText.Replace(".nfo", "")); + currentResult.ReleaseGroup = releaseGroup; + } + catch(Exception ex) + { + _logger.TraceException("Error getting release group for newzbin release", ex); + currentResult.ReleaseGroup = ""; + } } return currentResult; } - } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/MediaFileProvider.cs b/NzbDrone.Core/Providers/MediaFileProvider.cs index a45b7f9fe..831cb60f3 100644 --- a/NzbDrone.Core/Providers/MediaFileProvider.cs +++ b/NzbDrone.Core/Providers/MediaFileProvider.cs @@ -142,8 +142,22 @@ LEFT OUTER JOIN Episodes } } - public virtual string GetNewFilename(IList<Episode> episodes, string seriesTitle, QualityTypes quality, bool proper) + public virtual string GetNewFilename(IList<Episode> episodes, string seriesTitle, QualityTypes quality, bool proper, EpisodeFile episodeFile) { + if (_configProvider.SortingUseSceneName) + { + Logger.Trace("Attempting to use scene name"); + if (String.IsNullOrWhiteSpace(episodeFile.SceneName)) + { + var name = Path.GetFileNameWithoutExtension(episodeFile.Path); + Logger.Trace("Unable to use scene name, because it is null, sticking with current name: {0}", name); + + return name; + } + + return episodeFile.SceneName; + } + var sortedEpisodes = episodes.OrderBy(e => e.EpisodeNumber); var separatorStyle = EpisodeSortingHelper.GetSeparatorStyle(_configProvider.SortingSeparatorStyle); diff --git a/NzbDrone.Core/Providers/MisnamedProvider.cs b/NzbDrone.Core/Providers/MisnamedProvider.cs index 0b92e3397..f50f2edda 100644 --- a/NzbDrone.Core/Providers/MisnamedProvider.cs +++ b/NzbDrone.Core/Providers/MisnamedProvider.cs @@ -29,7 +29,6 @@ public virtual List<MisnamedEpisodeModel> MisnamedFiles(int pageNumber, int page var episodesWithFiles = _episodeProvider.EpisodesWithFiles().GroupBy(e => e.EpisodeFileId).ToList(); totalItems = episodesWithFiles.Count(); - var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -37,7 +36,7 @@ public virtual List<MisnamedEpisodeModel> MisnamedFiles(int pageNumber, int page w => w.First().EpisodeFile.Path != _mediaFileProvider.GetNewFilename(w.Select(e => e).ToList(), w.First().Series.Title, - w.First().EpisodeFile.Quality, w.First().EpisodeFile.Proper)).Skip(Math.Max(pageSize * (pageNumber - 1), 0)).Take(pageSize); + w.First().EpisodeFile.Quality, w.First().EpisodeFile.Proper, w.First().EpisodeFile)).Skip(Math.Max(pageSize * (pageNumber - 1), 0)).Take(pageSize); //Process the episodes misnamedFilesSelect.AsParallel().ForAll(f => @@ -46,7 +45,7 @@ public virtual List<MisnamedEpisodeModel> MisnamedFiles(int pageNumber, int page var firstEpisode = episodes[0]; var properName = _mediaFileProvider.GetNewFilename(episodes, firstEpisode.Series.Title, - firstEpisode.EpisodeFile.Quality, firstEpisode.EpisodeFile.Proper); + firstEpisode.EpisodeFile.Quality, firstEpisode.EpisodeFile.Proper, firstEpisode.EpisodeFile); var currentName = Path.GetFileNameWithoutExtension(firstEpisode.EpisodeFile.Path); diff --git a/NzbDrone.Core/Repository/EpisodeFile.cs b/NzbDrone.Core/Repository/EpisodeFile.cs index 01b17f3b7..32758b8ce 100644 --- a/NzbDrone.Core/Repository/EpisodeFile.cs +++ b/NzbDrone.Core/Repository/EpisodeFile.cs @@ -34,6 +34,8 @@ public EpisodeFile(EpisodeFile source) public bool Proper { get; set; } public long Size { get; set; } public DateTime DateAdded { get; set; } + public string SceneName { get; set; } + public string ReleaseGroup { get; set; } [Ignore] public Model.Quality QualityWrapper diff --git a/NzbDrone.Core/Repository/History.cs b/NzbDrone.Core/Repository/History.cs index 0c00ff2c9..597aab3da 100644 --- a/NzbDrone.Core/Repository/History.cs +++ b/NzbDrone.Core/Repository/History.cs @@ -17,6 +17,7 @@ public class History public bool IsProper { get; set; } public string Indexer { get; set; } public string NzbInfoUrl { get; set; } + public string ReleaseGroup { get; set; } [ResultColumn] public Episode Episode { get; set; } diff --git a/NzbDrone.Web/Controllers/HistoryController.cs b/NzbDrone.Web/Controllers/HistoryController.cs index be528a0ef..e5d050f6e 100644 --- a/NzbDrone.Web/Controllers/HistoryController.cs +++ b/NzbDrone.Web/Controllers/HistoryController.cs @@ -48,7 +48,8 @@ public ActionResult AjaxBinding(DataTablesPageRequest pageRequest) DateSorter = h.Date.ToString("MM/dd/yyyy h:mm:ss tt"), Indexer = h.Indexer, EpisodeId = h.EpisodeId, - NzbInfoUrl = h.NzbInfoUrl + NzbInfoUrl = h.NzbInfoUrl, + ReleaseGroup = h.ReleaseGroup }); return Json(new diff --git a/NzbDrone.Web/Controllers/SettingsController.cs b/NzbDrone.Web/Controllers/SettingsController.cs index a22fca30a..6d2cfb7f6 100644 --- a/NzbDrone.Web/Controllers/SettingsController.cs +++ b/NzbDrone.Web/Controllers/SettingsController.cs @@ -205,6 +205,7 @@ public ActionResult Naming() model.SeparatorStyle = _configProvider.SortingSeparatorStyle; model.NumberStyle = _configProvider.SortingNumberStyle; model.MultiEpisodeStyle = _configProvider.SortingMultiEpisodeStyle; + model.SceneName = _configProvider.SortingUseSceneName; model.SeparatorStyles = new SelectList(EpisodeSortingHelper.GetSeparatorStyles(), "Id", "Name"); model.NumberStyles = new SelectList(EpisodeSortingHelper.GetNumberStyles(), "Id", "Name"); @@ -243,6 +244,7 @@ public ActionResult Misc() var model = new MiscSettingsModel(); model.EnableBacklogSearching = _configProvider.EnableBacklogSearching; model.AutoIgnorePreviouslyDownloadedEpisodes = _configProvider.AutoIgnorePreviouslyDownloadedEpisodes; + model.AllowedReleaseGroups = _configProvider.AllowedReleaseGroups; return View(model); } @@ -594,6 +596,7 @@ public JsonResult SaveNaming(EpisodeNamingModel data) _configProvider.SortingSeparatorStyle = data.SeparatorStyle; _configProvider.SortingNumberStyle = data.NumberStyle; _configProvider.SortingMultiEpisodeStyle = data.MultiEpisodeStyle; + _configProvider.SortingUseSceneName = data.SceneName; //Metadata _configProvider.MetadataUseBanners = data.MetadataUseBanners; @@ -631,6 +634,7 @@ public JsonResult SaveMisc(MiscSettingsModel data) { _configProvider.EnableBacklogSearching = data.EnableBacklogSearching; _configProvider.AutoIgnorePreviouslyDownloadedEpisodes = data.AutoIgnorePreviouslyDownloadedEpisodes; + _configProvider.AllowedReleaseGroups = data.AllowedReleaseGroups; return GetSuccessResult(); } diff --git a/NzbDrone.Web/Models/EpisodeNamingModel.cs b/NzbDrone.Web/Models/EpisodeNamingModel.cs index 3331f5883..59f58ec88 100644 --- a/NzbDrone.Web/Models/EpisodeNamingModel.cs +++ b/NzbDrone.Web/Models/EpisodeNamingModel.cs @@ -43,6 +43,10 @@ public class EpisodeNamingModel [Description("How will multi-episode files be named?")] public int MultiEpisodeStyle { get; set; } + [DisplayName("Use Scene Name")] + [Description("Use the scene name, ignoring all other naming settings?")] + public bool SceneName { get; set; } + [DisplayName("XBMC")] [Description("Enable creating metadata for XBMC")] public bool MetadataXbmcEnabled { get; set; } diff --git a/NzbDrone.Web/Models/HistoryModel.cs b/NzbDrone.Web/Models/HistoryModel.cs index 2f60a9d6d..dfa46a463 100644 --- a/NzbDrone.Web/Models/HistoryModel.cs +++ b/NzbDrone.Web/Models/HistoryModel.cs @@ -21,5 +21,6 @@ public class HistoryModel public int EpisodeId { get; set; } public string Details { get; set; } public string NzbInfoUrl { get; set; } + public string ReleaseGroup { get; set; } } } \ No newline at end of file diff --git a/NzbDrone.Web/Models/MiscSettingsModel.cs b/NzbDrone.Web/Models/MiscSettingsModel.cs index 82cb57d30..d84550ed9 100644 --- a/NzbDrone.Web/Models/MiscSettingsModel.cs +++ b/NzbDrone.Web/Models/MiscSettingsModel.cs @@ -15,5 +15,9 @@ public class MiscSettingsModel [DisplayName("Automatically Ignore Deleted Episodes")] [Description("Should NzbDrone automatically ignore episodes that were deleted from disk?")] public bool AutoIgnorePreviouslyDownloadedEpisodes { get; set; } + + [DisplayName("Allowed Release Groups")] + [Description("Comma separated list of release groups to download episodes")] + public string AllowedReleaseGroups { get; set; } } } \ No newline at end of file diff --git a/NzbDrone.Web/Views/History/Index.cshtml b/NzbDrone.Web/Views/History/Index.cshtml index f7bad9ef1..2890e31b0 100644 --- a/NzbDrone.Web/Views/History/Index.cshtml +++ b/NzbDrone.Web/Views/History/Index.cshtml @@ -112,6 +112,9 @@ "<b>Proper: </b>" + row.aData["IsProper"] + "<br/>" + "<b>Indexer: </b>" + row.aData["Indexer"].replace('_', ' - '); + if (row.aData["ReleaseGroup"] != null && row.aData["ReleaseGroup"] != "") + result += "<br/><b>Release Group: </b> " + row.aData["ReleaseGroup"]; + if (row.aData["NzbInfoUrl"] != null && row.aData["NzbInfoUrl"] != "") result += "<br/><b>Nzb Details: </b> <a href=\"" + row.aData["NzbInfoUrl"] + "\" target=\"_blank\">Details</a>"; diff --git a/NzbDrone.Web/Views/Settings/EpisodeNamingPartial.cshtml b/NzbDrone.Web/Views/Settings/EpisodeNamingPartial.cshtml index b751a045e..bec5a1c5e 100644 --- a/NzbDrone.Web/Views/Settings/EpisodeNamingPartial.cshtml +++ b/NzbDrone.Web/Views/Settings/EpisodeNamingPartial.cshtml @@ -6,6 +6,10 @@ <div class="settingsContainer"> @Html.ValidationSummary(true, "Unable to save your settings. Please correct the errors and try again.") + <label class="labelClass">@Html.LabelFor(m => m.SceneName) + <span class="small">@Html.DescriptionFor(m => m.SceneName)</span> + </label> + @Html.CheckBoxFor(m => m.SceneName, new { @class = "inputClass checkClass" }) <label class="labelClass">@Html.LabelFor(m => m.SeriesName) <span class="small">@Html.DescriptionFor(m => m.SeriesName)</span> </label> diff --git a/NzbDrone.Web/Views/Settings/Misc.cshtml b/NzbDrone.Web/Views/Settings/Misc.cshtml index 80ca1a904..570c420e3 100644 --- a/NzbDrone.Web/Views/Settings/Misc.cshtml +++ b/NzbDrone.Web/Views/Settings/Misc.cshtml @@ -19,6 +19,11 @@ </label> @Html.CheckBoxFor(m => m.AutoIgnorePreviouslyDownloadedEpisodes, new { @class = "inputClass checkClass" }) + <label class="labelClass">@Html.LabelFor(m => m.AllowedReleaseGroups) + <span class="small">@Html.DescriptionFor(m => m.AllowedReleaseGroups)</span> + </label> + @Html.TextBoxFor(m => m.AllowedReleaseGroups, new { @class = "inputClass" }) + <div style="overflow: hidden; height: 50px;"> </div>