diff --git a/src/NzbDrone.Core.Test/Framework/CoreTest.cs b/src/NzbDrone.Core.Test/Framework/CoreTest.cs index 4c9b90803..686964552 100644 --- a/src/NzbDrone.Core.Test/Framework/CoreTest.cs +++ b/src/NzbDrone.Core.Test/Framework/CoreTest.cs @@ -1,7 +1,5 @@ using System.IO; using NUnit.Framework; -using NzbDrone.Common; -using NzbDrone.Common.Disk; using NzbDrone.Common.Http; using NzbDrone.Test.Common; @@ -9,11 +7,6 @@ namespace NzbDrone.Core.Test.Framework { public abstract class CoreTest : TestBase { - protected FileStream OpenRead(params string[] path) - { - return File.OpenRead(Path.Combine(path)); - } - protected string ReadAllText(params string[] path) { return File.ReadAllText(Path.Combine(path)); @@ -23,12 +16,6 @@ namespace NzbDrone.Core.Test.Framework { Mocker.SetConstant(new HttpProvider(TestLogger)); } - -// protected void UseRealDisk() -// { -// Mocker.SetConstant(new DiskProvider()); -// WithTempAsAppPath(); -// } } public abstract class CoreTest : CoreTest where TSubject : class diff --git a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs index 91aaf0465..864569d80 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs @@ -5,6 +5,7 @@ using FizzWare.NBuilder; using FluentAssertions; using Moq; using NUnit.Framework; +using NzbDrone.Core.Download; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.MediaFiles.Events; @@ -24,6 +25,8 @@ namespace NzbDrone.Core.Test.MediaFiles private List _rejectedDecisions; private List _approvedDecisions; + private DownloadClientItem _downloadClientItem; + [SetUp] public void Setup() { @@ -38,6 +41,8 @@ namespace NzbDrone.Core.Test.MediaFiles var episodes = Builder.CreateListOfSize(5) .Build(); + + _rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), "Rejected!")); _rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), "Rejected!")); _rejectedDecisions.Add(new ImportDecision(new LocalEpisode(), "Rejected!")); @@ -49,7 +54,7 @@ namespace NzbDrone.Core.Test.MediaFiles new LocalEpisode { Series = series, - Episodes = new List {episode}, + Episodes = new List { episode }, Path = Path.Combine(series.Path, "30 Rock - S01E01 - Pilot.avi"), Quality = new QualityModel(Quality.Bluray720p), ParsedEpisodeInfo = new ParsedEpisodeInfo @@ -62,6 +67,8 @@ namespace NzbDrone.Core.Test.MediaFiles Mocker.GetMock() .Setup(s => s.UpgradeEpisodeFile(It.IsAny(), It.IsAny(), false)) .Returns(new EpisodeFileMoveResult()); + + _downloadClientItem = Builder.CreateNew().Build(); } [Test] @@ -106,7 +113,7 @@ namespace NzbDrone.Core.Test.MediaFiles [Test] public void should_move_new_downloads() { - Subject.Import(new List {_approvedDecisions.First()}, true); + Subject.Import(new List { _approvedDecisions.First() }, true); Mocker.GetMock() .Verify(v => v.UpgradeEpisodeFile(It.IsAny(), _approvedDecisions.First().LocalEpisode, false), @@ -132,6 +139,52 @@ namespace NzbDrone.Core.Test.MediaFiles Times.Never()); } + + [Test] + public void should_use_nzb_title_as_scene_name() + { + _downloadClientItem.Title = "malcolm.in.the.middle.s02e05.dvdrip.xvid-ingot"; + + Subject.Import(new List { _approvedDecisions.First() }, true, _downloadClientItem); + + Mocker.GetMock().Verify(v => v.Add(It.Is(c => c.SceneName == _downloadClientItem.Title))); + } + + + [Test] + public void should_not_use_nzb_title_as_scene_name_if_full_season() + { + _approvedDecisions.First().LocalEpisode.Path = "c:\\tv\\season1\\malcolm.in.the.middle.s02e23.dvdrip.xvid-ingot.mkv".AsOsAgnostic(); + _downloadClientItem.Title = "malcolm.in.the.middle.s02.dvdrip.xvid-ingot"; + + Subject.Import(new List { _approvedDecisions.First() }, true, _downloadClientItem); + + Mocker.GetMock().Verify(v => v.Add(It.Is(c => c.SceneName == "malcolm.in.the.middle.s02e23.dvdrip.xvid-ingot"))); + } + + [Test] + public void should_use_file_name_as_scenename_only_if_it_looks_like_scenename() + { + + _approvedDecisions.First().LocalEpisode.Path = "c:\\tv\\malcolm.in.the.middle.s02e23.dvdrip.xvid-ingot.mkv".AsOsAgnostic(); + + Subject.Import(new List { _approvedDecisions.First() }, true); + + Mocker.GetMock().Verify(v => v.Add(It.Is(c => c.SceneName == "malcolm.in.the.middle.s02e23.dvdrip.xvid-ingot"))); + } + + [Test] + public void should_not_use_file_name_as_scenename_if_it_doesnt_looks_like_scenename() + { + _approvedDecisions.First().LocalEpisode.Path = "c:\\tv\\aaaaa.mkv".AsOsAgnostic(); + + Subject.Import(new List { _approvedDecisions.First() }, true); + + Mocker.GetMock().Verify(v => v.Add(It.Is(c => c.SceneName == null))); + } + + + [Test] public void should_import_larger_files_first() { @@ -142,7 +195,7 @@ namespace NzbDrone.Core.Test.MediaFiles (new LocalEpisode { Series = fileDecision.LocalEpisode.Series, - Episodes = new List {fileDecision.LocalEpisode.Episodes.First()}, + Episodes = new List { fileDecision.LocalEpisode.Episodes.First() }, Path = @"C:\Test\TV\30 Rock\30 Rock - S01E01 - Pilot.avi".AsOsAgnostic(), Quality = new QualityModel(Quality.Bluray720p), Size = 80.Megabytes() diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index cb7e43706..06eefa992 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -244,6 +244,7 @@ + diff --git a/src/NzbDrone.Core.Test/ParserTests/CrapParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/CrapParserFixture.cs index 71699402c..98b74c6ff 100644 --- a/src/NzbDrone.Core.Test/ParserTests/CrapParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/CrapParserFixture.cs @@ -1,11 +1,7 @@ using System; -using System.Linq; using FluentAssertions; using NUnit.Framework; -using NzbDrone.Common.Expansive; -using NzbDrone.Core.Parser; using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Tv; using NzbDrone.Test.Common; using System.Text; diff --git a/src/NzbDrone.Core.Test/ParserTests/ReleaseGroupParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/ReleaseGroupParserFixture.cs index 2b7bd476b..156217e97 100644 --- a/src/NzbDrone.Core.Test/ParserTests/ReleaseGroupParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/ReleaseGroupParserFixture.cs @@ -8,21 +8,21 @@ namespace NzbDrone.Core.Test.ParserTests public class ReleaseGroupParserFixture : CoreTest { [TestCase("Castle.2009.S01E14.English.HDTV.XviD-LOL", "LOL")] - [TestCase("Castle 2009 S01E14 English HDTV XviD LOL", "DRONE")] - [TestCase("Acropolis Now S05 EXTRAS DVDRip XviD RUNNER", "DRONE")] + [TestCase("Castle 2009 S01E14 English HDTV XviD LOL", null)] + [TestCase("Acropolis Now S05 EXTRAS DVDRip XviD RUNNER", null)] [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")] - [TestCase("The Office - S01E01 - Pilot [HTDV-480p]", "DRONE")] - [TestCase("The Office - S01E01 - Pilot [HTDV-720p]", "DRONE")] - [TestCase("The Office - S01E01 - Pilot [HTDV-1080p]", "DRONE")] + [TestCase("The Office - S01E01 - Pilot [HTDV-480p]", null)] + [TestCase("The Office - S01E01 - Pilot [HTDV-720p]", null)] + [TestCase("The Office - S01E01 - Pilot [HTDV-1080p]", null)] [TestCase("The.Walking.Dead.S04E13.720p.WEB-DL.AAC2.0.H.264-Cyphanix", "Cyphanix")] - [TestCase("Arrow.S02E01.720p.WEB-DL.DD5.1.H.264.mkv", "DRONE")] - [TestCase("Series Title S01E01 Episode Title", "DRONE")] - [TestCase("The Colbert Report - 2014-06-02 - Thomas Piketty.mkv", "DRONE")] - [TestCase("Real Time with Bill Maher S12E17 May 23, 2014.mp4", "DRONE")] - [TestCase("Reizen Waes - S01E08 - Transistrië, Zuid-Ossetië en Abchazië SDTV.avi", "DRONE")] - [TestCase("Simpsons 10x11 - Wild Barts Cant Be Broken [rl].avi", "DRONE")] + [TestCase("Arrow.S02E01.720p.WEB-DL.DD5.1.H.264.mkv", null)] + [TestCase("Series Title S01E01 Episode Title", null)] + [TestCase("The Colbert Report - 2014-06-02 - Thomas Piketty.mkv", null)] + [TestCase("Real Time with Bill Maher S12E17 May 23, 2014.mp4", null)] + [TestCase("Reizen Waes - S01E08 - Transistrië, Zuid-Ossetië en Abchazië SDTV.avi", null)] + [TestCase("Simpsons 10x11 - Wild Barts Cant Be Broken [rl].avi", null)] public void should_parse_release_group(string title, string expected) { Parser.Parser.ParseReleaseGroup(title).Should().Be(expected); diff --git a/src/NzbDrone.Core.Test/ParserTests/SceneCheckerFixture.cs b/src/NzbDrone.Core.Test/ParserTests/SceneCheckerFixture.cs new file mode 100644 index 000000000..41f231631 --- /dev/null +++ b/src/NzbDrone.Core.Test/ParserTests/SceneCheckerFixture.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.Parser; + +namespace NzbDrone.Core.Test.ParserTests +{ + [TestFixture] + public class SceneCheckerFixture + { + [TestCase("South.Park.S04E13.Helen.Keller.The.Musical.720p.WEBRip.AAC2.0.H.264-GC")] + [TestCase("Robot.Chicken.S07E02.720p.WEB-DL.DD5.1.H.264-pcsyndicate")] + [TestCase("Archer.2009.S05E06.Baby.Shower.720p.WEB-DL.DD5.1.H.264-iT00NZ")] + [TestCase("30.Rock.S04E17.720p.HDTV.X264-DIMENSION")] + [TestCase("30.Rock.S04.720p.HDTV.X264-DIMENSION")] + public void should_return_true_for_scene_names(string title) + { + SceneChecker.IsSceneTitle(title).Should().BeTrue(); + } + + + [TestCase("S08E05 - Virtual In-Stanity [WEBDL-720p]")] + [TestCase("S08E05 - Virtual In-Stanity.With.Dots [WEBDL-720p]")] + [TestCase("Something")] + [TestCase("86de66b7ef385e2fa56a3e41b98481ea1658bfab")] + [TestCase("30.Rock.S04E17.720p.HDTV.X264", Description = "no group")] + [TestCase("S04E17.720p.HDTV.X264-DIMENSION", Description = "no series title")] + [TestCase("30.Rock.S04E17-DIMENSION", Description = "no quality")] + [TestCase("30.Rock.720p.HDTV.X264-DIMENSION", Description = "no episode")] + public void should_return_false_for_non_scene_names(string title) + { + SceneChecker.IsSceneTitle(title).Should().BeFalse(); + } + + + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core/Datastore/Migration/061_clear_bad_scene_names.cs b/src/NzbDrone.Core/Datastore/Migration/061_clear_bad_scene_names.cs new file mode 100644 index 000000000..4bc2275dc --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/061_clear_bad_scene_names.cs @@ -0,0 +1,22 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(61)] + public class clear_bad_scene_names : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Execute.Sql("UPDATE [EpisodeFiles] " + + "SET ReleaseGroup = NULL , SceneName = NULL " + + "WHERE " + + " ReleaseGroup IS NULL " + + " OR SceneName IS NULL " + + " OR ReleaseGroup =='DRONE' " + + " OR LENGTH(SceneName) <10 " + + " OR LENGTH(ReleaseGroup) > 20 " + + " OR SceneName NOT LIKE '%.%'"); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs b/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs index b36bb6f02..163a1ea80 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeFile.cs @@ -24,10 +24,5 @@ namespace NzbDrone.Core.MediaFiles { return String.Format("[{0}] {1}", Id, RelativePath); } -// -// public String Path(Series series) -// { -// return System.IO.Path.Combine(series.Path, RelativePath); -// } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index 995cc339e..efffd12d2 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -7,6 +7,8 @@ using NzbDrone.Common; using NzbDrone.Common.Disk; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Parser; +using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Qualities; using NzbDrone.Core.Download; @@ -15,7 +17,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport { public interface IImportApprovedEpisodes { - List Import(List decisions, bool newDownload, DownloadClientItem historyItem = null); + List Import(List decisions, bool newDownload, DownloadClientItem downloadClientItem = null); } public class ImportApprovedEpisodes : IImportApprovedEpisodes @@ -39,7 +41,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport _logger = logger; } - public List Import(List decisions, bool newDownload, DownloadClientItem historyItem = null) + public List Import(List decisions, bool newDownload, DownloadClientItem downloadClientItem = null) { var qualifiedImports = decisions.Where(c => c.Approved) .GroupBy(c => c.LocalEpisode.Series.Id, (i, s) => s @@ -80,12 +82,13 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport if (newDownload) { - bool copyOnly = historyItem != null && historyItem.IsReadOnly; - episodeFile.SceneName = Path.GetFileNameWithoutExtension(localEpisode.Path.CleanFilePath()); + bool copyOnly = downloadClientItem != null && downloadClientItem.IsReadOnly; + + episodeFile.SceneName = GetSceneName(downloadClientItem, localEpisode); + var moveResult = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode, copyOnly); oldFiles = moveResult.OldFiles; } - else { episodeFile.RelativePath = localEpisode.Series.Path.GetRelativePath(episodeFile.Path); @@ -94,9 +97,9 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport _mediaFileService.Add(episodeFile); importResults.Add(new ImportResult(importDecision)); - if (historyItem != null) + if (downloadClientItem != null) { - _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload, historyItem.DownloadClient, historyItem.DownloadClientId)); + _eventAggregator.PublishEvent(new EpisodeImportedEvent(localEpisode, episodeFile, newDownload, downloadClientItem.DownloadClient, downloadClientItem.DownloadClientId)); } else { @@ -121,5 +124,27 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport return importResults; } + + private string GetSceneName(DownloadClientItem downloadClientItem, LocalEpisode localEpisode) + { + if (downloadClientItem != null) + { + var parsedTitle = Parser.Parser.ParseTitle(downloadClientItem.Title); + + if (parsedTitle != null && !parsedTitle.FullSeason) + { + return downloadClientItem.Title; + } + } + + var fileName = Path.GetFileNameWithoutExtension(localEpisode.Path.CleanFilePath()); + + if (SceneChecker.IsSceneTitle(fileName)) + { + return fileName; + } + + return null; + } } } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 5bc3166ab..c92a948cb 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -222,6 +222,7 @@ + @@ -663,6 +664,7 @@ + diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 43ec242ca..453ce4c0d 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -368,8 +368,8 @@ namespace NzbDrone.Core.Organizer private void AddEpisodeFileTokens(Dictionary> tokenHandlers, EpisodeFile episodeFile) { - tokenHandlers["{Original Title}"]= m => episodeFile.SceneName; - tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup; + tokenHandlers["{Original Title}"] = m => episodeFile.SceneName; + tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? "DRONE"; tokenHandlers["{Quality Title}"] = m => GetQualityTitle(episodeFile.Quality); } diff --git a/src/NzbDrone.Core/Parser/Parser.cs b/src/NzbDrone.Core/Parser/Parser.cs index 1caf10bf3..4758171c7 100644 --- a/src/NzbDrone.Core/Parser/Parser.cs +++ b/src/NzbDrone.Core/Parser/Parser.cs @@ -322,8 +322,6 @@ namespace NzbDrone.Core.Parser public static string ParseReleaseGroup(string title) { - const string defaultReleaseGroup = "DRONE"; - title = title.Trim(); title = RemoveFileExtension(title); title = title.TrimEnd("-RP"); @@ -337,13 +335,13 @@ namespace NzbDrone.Core.Parser if (Int32.TryParse(group, out groupIsNumeric)) { - return defaultReleaseGroup; + return null; } return group; } - return defaultReleaseGroup; + return null; } public static string RemoveFileExtension(string title) diff --git a/src/NzbDrone.Core/Parser/SceneChecker.cs b/src/NzbDrone.Core/Parser/SceneChecker.cs new file mode 100644 index 000000000..75fa4595c --- /dev/null +++ b/src/NzbDrone.Core/Parser/SceneChecker.cs @@ -0,0 +1,21 @@ +namespace NzbDrone.Core.Parser +{ + public static class SceneChecker + { + //This method should prefer false negatives over false positives. + //It's better not to use a title that might be scene than to use one that isn't scene + public static bool IsSceneTitle(string title) + { + if (!title.Contains(".")) return false; + if (title.Contains(" ")) return false; + + var parsedTitle = Parser.ParseTitle(title); + if (parsedTitle == null + || parsedTitle.ReleaseGroup == null + || parsedTitle.Quality.Quality == Qualities.Quality.Unknown + || string.IsNullOrWhiteSpace(parsedTitle.SeriesTitle)) return false; + + return true; + } + } +} diff --git a/src/NzbDrone.sln.DotSettings b/src/NzbDrone.sln.DotSettings index be0105874..088eea7f9 100644 --- a/src/NzbDrone.sln.DotSettings +++ b/src/NzbDrone.sln.DotSettings @@ -11,8 +11,9 @@ WARNING WARNING HINT + True Fixture - ^(.*)\.Test(\..*)$ + ^(.*)\.Test$ <?xml version="1.0" encoding="utf-16"?><Profile name="NzbDrone"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSUseVar><BehavourStyle>CAN_CHANGE_TO_IMPLICIT</BehavourStyle><LocalVariableStyle>IMPLICIT_EXCEPT_SIMPLE_TYPES</LocalVariableStyle><ForeachVariableStyle>ALWAYS_IMPLICIT</ForeachVariableStyle></CSUseVar><CSUpdateFileHeader>True</CSUpdateFileHeader><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReorderTypeMembers>True</CSReorderTypeMembers><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags></Profile> NzbDrone True