From 0c6ca6971ddf070226e3b74100970edc19777a94 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 5 May 2015 07:29:38 -0700 Subject: [PATCH] Fixed: Do not replace a file unless it contains the same episodes --- .../Extensions/IEnumerableExtensions.cs | 5 ++ .../SameEpisodesSpecificationFixture.cs | 76 +++++++++++++++++++ .../NzbDrone.Core.Test.csproj | 1 + .../ParserTests/PathParserFixture.cs | 1 + .../SameEpisodesSpecification.cs | 35 +++++++++ .../SameEpisodesGrabSpecification.cs | 31 ++++++++ .../SameEpisodesImportSpecification.cs | 31 ++++++++ src/NzbDrone.Core/NzbDrone.Core.csproj | 3 + 8 files changed, 183 insertions(+) create mode 100644 src/NzbDrone.Core.Test/DecisionEngineTests/SameEpisodesSpecificationFixture.cs create mode 100644 src/NzbDrone.Core/DecisionEngine/SameEpisodesSpecification.cs create mode 100644 src/NzbDrone.Core/DecisionEngine/Specifications/SameEpisodesGrabSpecification.cs create mode 100644 src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/SameEpisodesImportSpecification.cs diff --git a/src/NzbDrone.Common/Extensions/IEnumerableExtensions.cs b/src/NzbDrone.Common/Extensions/IEnumerableExtensions.cs index 7392a0a1a..1593c69b9 100644 --- a/src/NzbDrone.Common/Extensions/IEnumerableExtensions.cs +++ b/src/NzbDrone.Common/Extensions/IEnumerableExtensions.cs @@ -37,5 +37,10 @@ namespace NzbDrone.Common.Extensions { return !source.All(predicate); } + + public static List SelectList(this IEnumerable source, Func predicate) + { + return source.Select(predicate).ToList(); + } } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/DecisionEngineTests/SameEpisodesSpecificationFixture.cs b/src/NzbDrone.Core.Test/DecisionEngineTests/SameEpisodesSpecificationFixture.cs new file mode 100644 index 000000000..183b6cc77 --- /dev/null +++ b/src/NzbDrone.Core.Test/DecisionEngineTests/SameEpisodesSpecificationFixture.cs @@ -0,0 +1,76 @@ +using System.Collections.Generic; +using System.Linq; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Tv; + +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test.DecisionEngineTests +{ + [TestFixture] + public class SameEpisodesSpecificationFixture : CoreTest + { + private List _episodes; + + [SetUp] + public void Setup() + { + _episodes = Builder.CreateListOfSize(2) + .All() + .With(e => e.EpisodeFileId = 1) + .BuildList(); + } + + private void GivenEpisodesInFile(List episodes) + { + Mocker.GetMock() + .Setup(s => s.GetEpisodesByFileId(It.IsAny())) + .Returns(episodes); + } + + [Test] + public void should_not_upgrade_when_new_release_contains_less_episodes() + { + GivenEpisodesInFile(_episodes); + + Subject.IsSatisfiedBy(new List { _episodes.First() }).Should().BeFalse(); + } + + [Test] + public void should_upgrade_when_new_release_contains_more_episodes() + { + GivenEpisodesInFile(new List { _episodes.First() }); + + Subject.IsSatisfiedBy(_episodes).Should().BeTrue(); + } + + [Test] + public void should_upgrade_when_new_release_contains_the_same_episodes() + { + GivenEpisodesInFile(_episodes); + + Subject.IsSatisfiedBy(_episodes).Should().BeTrue(); + } + + [Test] + public void should_upgrade_when_release_contains_the_same_episodes_as_multiple_files() + { + var episodes = Builder.CreateListOfSize(2) + .BuildList(); + + Mocker.GetMock() + .Setup(s => s.GetEpisodesByFileId(episodes.First().EpisodeFileId)) + .Returns(new List { episodes.First() }); + + Mocker.GetMock() + .Setup(s => s.GetEpisodesByFileId(episodes.Last().EpisodeFileId)) + .Returns(new List { episodes.Last() }); + + Subject.IsSatisfiedBy(episodes).Should().BeTrue(); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 476dcd639..a4d4c1d19 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -147,6 +147,7 @@ + diff --git a/src/NzbDrone.Core.Test/ParserTests/PathParserFixture.cs b/src/NzbDrone.Core.Test/ParserTests/PathParserFixture.cs index f398b7c86..9dfdeb851 100644 --- a/src/NzbDrone.Core.Test/ParserTests/PathParserFixture.cs +++ b/src/NzbDrone.Core.Test/ParserTests/PathParserFixture.cs @@ -30,6 +30,7 @@ namespace NzbDrone.Core.Test.ParserTests [TestCase(@"C:\Test\Series\Season 01\1 Pilot (1080p HD).mkv", 1, 1)] [TestCase(@"C:\Test\Series\Season 1\02 Honor Thy Father (1080p HD).m4v", 1, 2)] [TestCase(@"C:\Test\Series\Season 1\2 Honor Thy Father (1080p HD).m4v", 1, 2)] +// [TestCase(@"C:\CSI.NY.S02E04.720p.WEB-DL.DD5.1.H.264\73696S02-04.mkv", 2, 4)] //Gets treated as S01E04 (because it gets parsed as anime) public void should_parse_from_path(string path, int season, int episode) { var result = Parser.Parser.ParsePath(path.AsOsAgnostic()); diff --git a/src/NzbDrone.Core/DecisionEngine/SameEpisodesSpecification.cs b/src/NzbDrone.Core/DecisionEngine/SameEpisodesSpecification.cs new file mode 100644 index 000000000..65bf4f1ec --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/SameEpisodesSpecification.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.DecisionEngine +{ + public class SameEpisodesSpecification + { + private readonly IEpisodeService _episodeService; + + public SameEpisodesSpecification(IEpisodeService episodeService) + { + _episodeService = episodeService; + } + + public bool IsSatisfiedBy(List episodes) + { + var episodeIds = episodes.SelectList(e => e.Id); + var episodeFileIds = episodes.Where(c => c.EpisodeFileId != 0).Select(c => c.EpisodeFileId).Distinct(); + + foreach (var episodeFileId in episodeFileIds) + { + var episodesInFile = _episodeService.GetEpisodesByFileId(episodeFileId); + + if (episodesInFile.Select(e => e.Id).Except(episodeIds).Any()) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/NzbDrone.Core/DecisionEngine/Specifications/SameEpisodesGrabSpecification.cs b/src/NzbDrone.Core/DecisionEngine/Specifications/SameEpisodesGrabSpecification.cs new file mode 100644 index 000000000..2afea3032 --- /dev/null +++ b/src/NzbDrone.Core/DecisionEngine/Specifications/SameEpisodesGrabSpecification.cs @@ -0,0 +1,31 @@ +using NLog; +using NzbDrone.Core.IndexerSearch.Definitions; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.DecisionEngine.Specifications +{ + public class SameEpisodesGrabSpecification : IDecisionEngineSpecification + { + private readonly SameEpisodesSpecification _sameEpisodesSpecification; + private readonly Logger _logger; + + public SameEpisodesGrabSpecification(SameEpisodesSpecification sameEpisodesSpecification, Logger logger) + { + _sameEpisodesSpecification = sameEpisodesSpecification; + _logger = logger; + } + + public RejectionType Type { get { return RejectionType.Permanent; } } + + public virtual Decision IsSatisfiedBy(RemoteEpisode subject, SearchCriteriaBase searchCriteria) + { + if (_sameEpisodesSpecification.IsSatisfiedBy(subject.Episodes)) + { + return Decision.Accept(); + } + + _logger.Debug("Episode file on disk contains more episodes than this release contains"); + return Decision.Reject("Episode file on disk contains more episodes than this release contains"); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/SameEpisodesImportSpecification.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/SameEpisodesImportSpecification.cs new file mode 100644 index 000000000..da909fb79 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Specifications/SameEpisodesImportSpecification.cs @@ -0,0 +1,31 @@ +using NLog; +using NzbDrone.Core.DecisionEngine; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications +{ + public class SameEpisodesImportSpecification : IImportDecisionEngineSpecification + { + private readonly SameEpisodesSpecification _sameEpisodesSpecification; + private readonly Logger _logger; + + public SameEpisodesImportSpecification(SameEpisodesSpecification sameEpisodesSpecification, Logger logger) + { + _sameEpisodesSpecification = sameEpisodesSpecification; + _logger = logger; + } + + public RejectionType Type { get { return RejectionType.Permanent; } } + + public Decision IsSatisfiedBy(LocalEpisode localEpisode) + { + if (_sameEpisodesSpecification.IsSatisfiedBy(localEpisode.Episodes)) + { + return Decision.Accept(); + } + + _logger.Debug("Episode file on disk contains more episodes than this file contains"); + return Decision.Reject("Episode file on disk contains more episodes than this file contains"); + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index ba7ac3740..e52a5153f 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -281,6 +281,7 @@ + @@ -303,6 +304,7 @@ + @@ -582,6 +584,7 @@ +