diff --git a/NzbDrone.Core.Test/EpisodeProviderTest.cs b/NzbDrone.Core.Test/EpisodeProviderTest.cs index 9ca954590e..75c99977d6 100644 --- a/NzbDrone.Core.Test/EpisodeProviderTest.cs +++ b/NzbDrone.Core.Test/EpisodeProviderTest.cs @@ -1355,5 +1355,71 @@ public void GetEpisodesByFileId_single_episode() episodes.First().ShouldHave().AllPropertiesBut(e => e.Series).EqualTo(fakeEpisode); mocker.VerifyAllMocks(); } + + [Test] + public void IsFirstOrLastEpisodeInSeason_false() + { + var db = MockLib.GetEmptyDatabase(); + var mocker = new AutoMoqer(); + mocker.SetConstant(db); + + var fakeEpisodes = Builder.CreateListOfSize(10) + .WhereAll() + .Have(c => c.SeriesId = 10) + .Have(c => c.SeasonNumber = 1) + .Build(); + + db.InsertMany(fakeEpisodes); + + //Act + var result = mocker.Resolve().IsFirstOrLastEpisodeOfSeason(10, 1, 5); + + //Assert + result.Should().BeFalse(); + } + + [Test] + public void IsFirstOrLastEpisodeInSeason_true_first() + { + var db = MockLib.GetEmptyDatabase(); + var mocker = new AutoMoqer(); + mocker.SetConstant(db); + + var fakeEpisodes = Builder.CreateListOfSize(10) + .WhereAll() + .Have(c => c.SeriesId = 10) + .Have(c => c.SeasonNumber = 1) + .Build(); + + db.InsertMany(fakeEpisodes); + + //Act + var result = mocker.Resolve().IsFirstOrLastEpisodeOfSeason(10, 1, 1); + + //Assert + result.Should().BeFalse(); + } + + [Test] + public void IsFirstOrLastEpisodeInSeason_true_last() + { + var db = MockLib.GetEmptyDatabase(); + var mocker = new AutoMoqer(); + mocker.SetConstant(db); + + var fakeEpisodes = Builder.CreateListOfSize(10) + .WhereAll() + .Have(c => c.SeriesId = 10) + .Have(c => c.SeasonNumber = 1) + .Build(); + + db.InsertMany(fakeEpisodes); + + //Act + var result = mocker.Resolve().IsFirstOrLastEpisodeOfSeason(10, 1, 10); + + //Assert + result.Should().BeFalse(); + } } } \ No newline at end of file diff --git a/NzbDrone.Core.Test/InventoryProvider_IsAcceptableSizeTest.cs b/NzbDrone.Core.Test/InventoryProvider_IsAcceptableSizeTest.cs new file mode 100644 index 0000000000..75a3b5272f --- /dev/null +++ b/NzbDrone.Core.Test/InventoryProvider_IsAcceptableSizeTest.cs @@ -0,0 +1,323 @@ +// ReSharper disable RedundantUsingDirective +using System; +using System.Collections.Generic; +using AutoMoq; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Core.Model; +using NzbDrone.Core.Providers; +using NzbDrone.Core.Repository; +using NzbDrone.Core.Repository.Quality; +using NzbDrone.Core.Test.Framework; + +namespace NzbDrone.Core.Test +{ + [TestFixture] + // ReSharper disable InconsistentNaming + public class InventoryProvider_IsAcceptableSizeTest : TestBase + { + private EpisodeParseResult parseResultMulti; + private EpisodeParseResult parseResultSingle; + private Series series30minutes; + private Series series60minutes; + private QualityType qualityType; + + [SetUp] + public new void Setup() + { + parseResultMulti = new EpisodeParseResult + { + CleanTitle = "Title", + Language = LanguageType.English, + Quality = new Quality(QualityTypes.SDTV, true), + EpisodeNumbers = new List { 3, 4 }, + SeasonNumber = 12, + AirDate = DateTime.Now.AddDays(-12).Date + }; + + parseResultSingle = new EpisodeParseResult + { + CleanTitle = "Title", + Language = LanguageType.English, + Quality = new Quality(QualityTypes.SDTV, true), + EpisodeNumbers = new List { 3 }, + SeasonNumber = 12, + AirDate = DateTime.Now.AddDays(-12).Date + }; + + series30minutes = Builder.CreateNew() + .With(c => c.Monitored = true) + .With(d => d.CleanTitle = parseResultMulti.CleanTitle) + .With(c => c.Runtime = 30) + .Build(); + + series60minutes = Builder.CreateNew() + .With(c => c.Monitored = true) + .With(d => d.CleanTitle = parseResultMulti.CleanTitle) + .With(c => c.Runtime = 60) + .Build(); + + qualityType = Builder.CreateNew() + .With(q => q.MinSize = 0) + .With(q => q.MaxSize = 314572800) + .With(q => q.QualityTypeId = 1) + .Build(); + + base.Setup(); + } + + [Test] + public void IsAcceptableSize_true_single_episode_not_first_or_last_30_minute() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + + parseResultSingle.Series = series30minutes; + parseResultSingle.Size = 184572800; + + mocker.GetMock().Setup(s => s.Get(1)).Returns(qualityType); + + mocker.GetMock().Setup( + s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(false); + + //Act + bool result = mocker.Resolve().IsAcceptableSize(parseResultSingle); + + //Assert + result.Should().BeTrue(); + } + + [Test] + public void IsAcceptableSize_true_single_episode_not_first_or_last_60_minute() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + + parseResultSingle.Series = series60minutes; + parseResultSingle.Size = 368572800; + + mocker.GetMock().Setup(s => s.Get(1)).Returns(qualityType); + + mocker.GetMock().Setup( + s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(false); + + //Act + bool result = mocker.Resolve().IsAcceptableSize(parseResultSingle); + + //Assert + result.Should().BeTrue(); + } + + [Test] + public void IsAcceptableSize_false_single_episode_not_first_or_last_30_minute() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + + parseResultSingle.Series = series30minutes; + parseResultSingle.Size = 1184572800; + + mocker.GetMock().Setup(s => s.Get(1)).Returns(qualityType); + + mocker.GetMock().Setup( + s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(false); + + //Act + bool result = mocker.Resolve().IsAcceptableSize(parseResultSingle); + + //Assert + result.Should().BeFalse(); + } + + [Test] + public void IsAcceptableSize_false_single_episode_not_first_or_last_60_minute() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + + parseResultSingle.Series = series60minutes; + parseResultSingle.Size = 1368572800; + + mocker.GetMock().Setup(s => s.Get(1)).Returns(qualityType); + + mocker.GetMock().Setup( + s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(false); + + //Act + bool result = mocker.Resolve().IsAcceptableSize(parseResultSingle); + + //Assert + result.Should().BeFalse(); + } + + [Test] + public void IsAcceptableSize_true_multi_episode_not_first_or_last_30_minute() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + + parseResultMulti.Series = series30minutes; + parseResultMulti.Size = 184572800; + + mocker.GetMock().Setup(s => s.Get(1)).Returns(qualityType); + + mocker.GetMock().Setup( + s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(false); + + //Act + bool result = mocker.Resolve().IsAcceptableSize(parseResultMulti); + + //Assert + result.Should().BeTrue(); + } + + [Test] + public void IsAcceptableSize_true_multi_episode_not_first_or_last_60_minute() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + + parseResultMulti.Series = series60minutes; + parseResultMulti.Size = 368572800; + + mocker.GetMock().Setup(s => s.Get(1)).Returns(qualityType); + + mocker.GetMock().Setup( + s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(false); + + //Act + bool result = mocker.Resolve().IsAcceptableSize(parseResultMulti); + + //Assert + result.Should().BeTrue(); + } + + [Test] + public void IsAcceptableSize_false_multi_episode_not_first_or_last_30_minute() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + + parseResultMulti.Series = series30minutes; + parseResultMulti.Size = 1184572800; + + mocker.GetMock().Setup(s => s.Get(1)).Returns(qualityType); + + mocker.GetMock().Setup( + s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(false); + + //Act + bool result = mocker.Resolve().IsAcceptableSize(parseResultMulti); + + //Assert + result.Should().BeFalse(); + } + + [Test] + public void IsAcceptableSize_false_multi_episode_not_first_or_last_60_minute() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + + parseResultMulti.Series = series60minutes; + parseResultMulti.Size = 1368572800; + + mocker.GetMock().Setup(s => s.Get(1)).Returns(qualityType); + + mocker.GetMock().Setup( + s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(false); + + //Act + bool result = mocker.Resolve().IsAcceptableSize(parseResultMulti); + + //Assert + result.Should().BeFalse(); + } + + [Test] + public void IsAcceptableSize_true_single_episode_first_30_minute() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + + parseResultSingle.Series = series30minutes; + parseResultSingle.Size = 184572800; + + mocker.GetMock().Setup(s => s.Get(1)).Returns(qualityType); + + mocker.GetMock().Setup( + s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(true); + + //Act + bool result = mocker.Resolve().IsAcceptableSize(parseResultSingle); + + //Assert + result.Should().BeTrue(); + } + + [Test] + public void IsAcceptableSize_true_single_episode_first_60_minute() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + + parseResultSingle.Series = series60minutes; + parseResultSingle.Size = 368572800; + + mocker.GetMock().Setup(s => s.Get(1)).Returns(qualityType); + + mocker.GetMock().Setup( + s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(true); + + //Act + bool result = mocker.Resolve().IsAcceptableSize(parseResultSingle); + + //Assert + result.Should().BeTrue(); + } + + [Test] + public void IsAcceptableSize_false_single_episode_first_30_minute() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + + parseResultSingle.Series = series30minutes; + parseResultSingle.Size = 1184572800; + + mocker.GetMock().Setup(s => s.Get(1)).Returns(qualityType); + + mocker.GetMock().Setup( + s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(true); + + //Act + bool result = mocker.Resolve().IsAcceptableSize(parseResultSingle); + + //Assert + result.Should().BeFalse(); + } + + [Test] + public void IsAcceptableSize_false_single_episode_first_60_minute() + { + var mocker = new AutoMoqer(MockBehavior.Strict); + + parseResultSingle.Series = series60minutes; + parseResultSingle.Size = 1368572800; + + mocker.GetMock().Setup(s => s.Get(1)).Returns(qualityType); + + mocker.GetMock().Setup( + s => s.IsFirstOrLastEpisodeOfSeason(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(true); + + //Act + bool result = mocker.Resolve().IsAcceptableSize(parseResultSingle); + + //Assert + result.Should().BeFalse(); + } + } +} \ No newline at end of file diff --git a/NzbDrone.Core.Test/InventoryProvider_QualityNeededTest.cs b/NzbDrone.Core.Test/InventoryProvider_QualityNeededTest.cs index 455c71e33a..d2202bc2ad 100644 --- a/NzbDrone.Core.Test/InventoryProvider_QualityNeededTest.cs +++ b/NzbDrone.Core.Test/InventoryProvider_QualityNeededTest.cs @@ -135,6 +135,10 @@ public void IsQualityNeeded_file_already_at_cut_off_should_be_skipped() .Setup(p => p.GetEpisodesByParseResult(parseResultMulti, true)) .Returns(new List { episode, episode2 }); + mocker.GetMock() + .Setup(s => s.Get(It.IsAny())) + .Returns(new QualityType { MaxSize = 10.Gigabytes(), MinSize = 0 }); + episode.EpisodeFile.Quality = QualityTypes.Bluray720p; //Act @@ -145,7 +149,6 @@ public void IsQualityNeeded_file_already_at_cut_off_should_be_skipped() mocker.VerifyAllMocks(); } - [Test] public void IsQualityNeeded_file_in_history_should_be_skipped() { @@ -162,6 +165,14 @@ public void IsQualityNeeded_file_in_history_should_be_skipped() .Setup(p => p.GetEpisodesByParseResult(parseResultSingle, true)) .Returns(new List { episode }); + mocker.GetMock() + .Setup(p => p.IsFirstOrLastEpisodeOfSeason(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(false); + + mocker.GetMock() + .Setup(s => s.Get(It.IsAny())) + .Returns(new QualityType { MaxSize = 10.Gigabytes(), MinSize = 0 }); + episode.EpisodeFile.Quality = QualityTypes.SDTV; //Act @@ -188,6 +199,14 @@ public void IsQualityNeeded_lesser_file_in_history_should_be_downloaded() .Setup(p => p.GetEpisodesByParseResult(parseResultSingle, true)) .Returns(new List { episode }); + mocker.GetMock() + .Setup(p => p.IsFirstOrLastEpisodeOfSeason(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(false); + + mocker.GetMock() + .Setup(s => s.Get(It.IsAny())) + .Returns(new QualityType { MaxSize = 10.Gigabytes(), MinSize = 0 }); + episode.EpisodeFile.Quality = QualityTypes.SDTV; //Act @@ -210,11 +229,18 @@ public void IsQualityNeeded_file_not_in_history_should_be_downloaded() .Setup(p => p.GetBestQualityInHistory(episode.EpisodeId)) .Returns(null); - mocker.GetMock() .Setup(p => p.GetEpisodesByParseResult(parseResultSingle, true)) .Returns(new List { episode }); + mocker.GetMock() + .Setup(p => p.IsFirstOrLastEpisodeOfSeason(It.IsAny(), It.IsAny(), It.IsAny())) + .Returns(false); + + mocker.GetMock() + .Setup(s => s.Get(It.IsAny())) + .Returns(new QualityType { MaxSize = 10.Gigabytes(), MinSize = 0 }); + episode.EpisodeFile.Quality = QualityTypes.SDTV; //Act bool result = mocker.Resolve().IsQualityNeeded(parseResultSingle); diff --git a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index 5fab039927..859d80822a 100644 --- a/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -90,6 +90,7 @@ + diff --git a/NzbDrone.Core/Providers/EpisodeProvider.cs b/NzbDrone.Core/Providers/EpisodeProvider.cs index af2cdda2a3..aac9944419 100644 --- a/NzbDrone.Core/Providers/EpisodeProvider.cs +++ b/NzbDrone.Core/Providers/EpisodeProvider.cs @@ -330,6 +330,21 @@ public virtual void SetEpisodeIgnore(int episodeId, bool isIgnored) Logger.Info("Ignore flag for Episode:{0} successfully set to {1}", episodeId, isIgnored); } + public virtual bool IsFirstOrLastEpisodeOfSeason(int seriesId, int seasonNumber, int episodeNumber) + { + var episodes = GetEpisodesBySeason(seriesId, seasonNumber).OrderBy(e => e.EpisodeNumber); + + if (episodes.Count() == 0) + return false; + + //Ensure that this is either the first episode + //or is the last episode in a season that has 10 or more episodes + if (episodes.First().EpisodeNumber == episodeNumber || (episodes.Count() >= 10 && episodes.Last().EpisodeNumber == episodeNumber)) + return true; + + return false; + } + public IList AttachSeries(IList episodes) { if (episodes.Count == 0) return episodes; diff --git a/NzbDrone.Core/Providers/InventoryProvider.cs b/NzbDrone.Core/Providers/InventoryProvider.cs index fd9ada11be..6544831f21 100644 --- a/NzbDrone.Core/Providers/InventoryProvider.cs +++ b/NzbDrone.Core/Providers/InventoryProvider.cs @@ -14,15 +14,18 @@ public class InventoryProvider private readonly SeriesProvider _seriesProvider; private readonly EpisodeProvider _episodeProvider; private readonly HistoryProvider _historyProvider; + private readonly QualityTypeProvider _qualityTypeProvider; private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); [Inject] - public InventoryProvider(SeriesProvider seriesProvider, EpisodeProvider episodeProvider, HistoryProvider historyProvider) + public InventoryProvider(SeriesProvider seriesProvider, EpisodeProvider episodeProvider, + HistoryProvider historyProvider, QualityTypeProvider qualityTypeProvider) { _seriesProvider = seriesProvider; _episodeProvider = episodeProvider; _historyProvider = historyProvider; + _qualityTypeProvider = qualityTypeProvider; } public InventoryProvider() @@ -79,6 +82,9 @@ public virtual bool IsQualityNeeded(EpisodeParseResult parsedReport) var cutoff = parsedReport.Series.QualityProfile.Cutoff; + if (!IsAcceptableSize(parsedReport)) + return false; + foreach (var episode in _episodeProvider.GetEpisodesByParseResult(parsedReport, true)) { //Checking File @@ -132,5 +138,39 @@ public static bool IsUpgrade(Quality currentQuality, Quality newQuality, Quality Logger.Debug("New item has better quality than existing item"); return true; } + + public virtual bool IsAcceptableSize(EpisodeParseResult parseResult) + { + var qualityType = _qualityTypeProvider.Get((int) parseResult.Quality.QualityType); + + //Need to determine if this is a 30 or 60 minute episode + //Is it a multi-episode release? + //Is it the first or last series of a season? + + var maxSize = qualityType.MaxSize; + var series = parseResult.Series; + + //If this is an hour long episode (between 50 and 65 minutes) then multiply by 2 + if (series.Runtime >= 50 && series.Runtime <= 65) + maxSize = maxSize * 2; + + //Multiply maxSize by the number of episodes parsed + maxSize = maxSize * parseResult.EpisodeNumbers.Count; + + //Check if there was only one episode parsed + //and it is the first or last episode of the season + if (parseResult.EpisodeNumbers.Count == 1 && + _episodeProvider.IsFirstOrLastEpisodeOfSeason(series.SeriesId, + parseResult.SeasonNumber, parseResult.EpisodeNumbers[0])) + { + maxSize = maxSize * 2; + } + + //If the parsed size is greater than maxSize we don't want it + if (parseResult.Size > maxSize) + return false; + + return true; + } } } \ No newline at end of file diff --git a/NzbDrone.Core/Providers/QualityTypeProvider.cs b/NzbDrone.Core/Providers/QualityTypeProvider.cs index ae746723dc..79c28354d7 100644 --- a/NzbDrone.Core/Providers/QualityTypeProvider.cs +++ b/NzbDrone.Core/Providers/QualityTypeProvider.cs @@ -19,6 +19,11 @@ public QualityTypeProvider(IDatabase database) _database = database; } + public QualityTypeProvider() + { + + } + public virtual void Update(QualityType qualityType) { _database.Update(qualityType);