diff --git a/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Specifications/NotMultiPartSpecificationFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Specifications/NotMultiPartSpecificationFixture.cs new file mode 100644 index 000000000..10b71b8e6 --- /dev/null +++ b/src/NzbDrone.Core.Test/MediaFiles/MovieImport/Specifications/NotMultiPartSpecificationFixture.cs @@ -0,0 +1,117 @@ +using System.IO; +using System.Linq; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Common.Disk; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.MediaFiles.MovieImport.Specifications; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Specifications +{ + [TestFixture] + public class NotMultiPartSpecificationFixture : CoreTest + { + private LocalMovie _localMovie; + + [SetUp] + public void Setup() + { + _localMovie = new LocalMovie + { + Path = @"C:\Test\Downloaded\somemovie.avi".AsOsAgnostic() + }; + } + + [TestCase(new object[] + { + @"C:\Test\Downloaded\x.men.2018.avi" + })] + [TestCase(new object[] + { + @"C:\Test\Downloaded\Captain.Phillips.2013.MULTi.1080p.BluRay.x264-LOST\lost-captainphillips.2013.1080p.mkv" + })] + [TestCase(new object[] + { + @"C:\Test\Downloaded\Harry.Potter.And.The.Deathly.Hallows.Part.1.2010.1080p.BluRay.x264-EbP\Harry.Potter.And.The.Deathly.Hallows.Part.1.2010.1080p.BluRay.x264-EbP.mkv" + })] + public void should_be_accepted_for_legitimate_files(object[] paths) + { + _localMovie.Path = paths.First().ToString().AsOsAgnostic(); + + string[] filePaths = paths.Cast().Select(x => x.AsOsAgnostic()).ToArray(); + + Mocker.GetMock() + .Setup(s => s.GetFiles(_localMovie.Path.GetParentPath(), SearchOption.TopDirectoryOnly)) + .Returns(filePaths); + + Subject.IsSatisfiedBy(_localMovie, null).Accepted.Should().BeTrue(); + } + + [TestCase(new object[] + { + @"C:\Test\Downloaded\Bad Boys (2006) part1.mkv", + @"C:\Test\Downloaded\Bad Boys (2006) part2.mkv" + })] + [TestCase(new object[] + { + @"C:\Test\Downloaded\blah blah - cd 1.mvk", + @"C:\Test\Downloaded\blah blah - cd 2.mvk" + })] + [TestCase(new object[] + { + @"C:\Test\Downloaded\blah blah - dvd a.mvk", + @"C:\Test\Downloaded\blah blah - dvd b.mvk" + })] + public void should_be_rejected_for_multi_part_files(object[] paths) + { + _localMovie.Path = paths.First().ToString().AsOsAgnostic(); + + string[] filePaths = paths.Cast().Select(x => x.AsOsAgnostic()).ToArray(); + + Mocker.GetMock() + .Setup(s => s.GetFiles(_localMovie.Path.GetParentPath(), SearchOption.TopDirectoryOnly)) + .Returns(filePaths); + + Subject.IsSatisfiedBy(_localMovie, null).Accepted.Should().BeFalse(); + } + + [TestCase(new object[] + { + @"C:\Test\Downloaded\blah blah - cd a.mvk", + @"C:\Test\Downloaded\blah blah - cd 2.mvk" + })] + public void should_not_reject_if_multi_part_schemes_mixed(object[] paths) + { + _localMovie.Path = paths.First().ToString().AsOsAgnostic(); + + string[] filePaths = paths.Cast().Select(x => x.AsOsAgnostic()).ToArray(); + + Mocker.GetMock() + .Setup(s => s.GetFiles(_localMovie.Path.GetParentPath(), SearchOption.TopDirectoryOnly)) + .Returns(filePaths); + + Subject.IsSatisfiedBy(_localMovie, null).Accepted.Should().BeTrue(); + } + + [TestCase(new object[] + { + @"C:\Test\Downloaded\blah blah - cd a.mvk", + @"C:\Test\Downloaded\ping pong - cd b.mvk" + })] + public void should_not_reject_if_file_names_mixed(object[] paths) + { + _localMovie.Path = paths.First().ToString().AsOsAgnostic(); + + string[] filePaths = paths.Cast().Select(x => x.AsOsAgnostic()).ToArray(); + + Mocker.GetMock() + .Setup(s => s.GetFiles(_localMovie.Path.GetParentPath(), SearchOption.TopDirectoryOnly)) + .Returns(filePaths); + + Subject.IsSatisfiedBy(_localMovie, null).Accepted.Should().BeTrue(); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/MovieImport/Specifications/NotMultiPartSpecification.cs b/src/NzbDrone.Core/MediaFiles/MovieImport/Specifications/NotMultiPartSpecification.cs new file mode 100644 index 000000000..b49ac345a --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/MovieImport/Specifications/NotMultiPartSpecification.cs @@ -0,0 +1,53 @@ +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using NLog; +using NzbDrone.Common.Disk; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Download; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.MediaFiles.MovieImport.Specifications +{ + public class NotMultiPartSpecification : IImportDecisionEngineSpecification + { + private readonly Logger _logger; + private readonly IDiskProvider _diskProvider; + + public NotMultiPartSpecification(IDiskProvider diskProvider, Logger logger) + { + _diskProvider = diskProvider; + _logger = logger; + } + + private static readonly Regex[] MovieMultiPartRegex = new Regex[] + { + new Regex(@"(?[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase), + new Regex(@"(?[ _.-]*(?:cd|dvd|p(?:ar)?t|dis[ck])[ _.-]*[a-d]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase), + }; + + public Decision IsSatisfiedBy(LocalMovie localMovie, DownloadClientItem downloadClientItem) + { + var regexReplace = MovieMultiPartRegex.First().Replace(localMovie.Path, ""); + + if (MovieMultiPartRegex.Any(v => v.IsMatch(localMovie.Path))) + { + var parentPath = localMovie.Path.GetParentPath(); + var filesInDirectory = _diskProvider.GetFiles(localMovie.Path.GetParentPath(), SearchOption.TopDirectoryOnly); + + foreach (var regex in MovieMultiPartRegex) + { + if (filesInDirectory.Where(file => regex.Replace(file, "") == regex.Replace(localMovie.Path, "")).Count() > 1) + { + _logger.Debug("Rejected Multi-Part File: {0}", localMovie.Path); + + return Decision.Reject("File is suspected multi-part file, Radarr doesn't support"); + } + } + } + + return Decision.Accept(); + } + } +}