New: Don't import episodes that don't match grab history

Closes #5073
This commit is contained in:
Mark McDowall 2023-03-15 23:26:07 -07:00
parent 89e363fd14
commit 978618f041
2 changed files with 189 additions and 0 deletions

View File

@ -0,0 +1,119 @@
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Download;
using NzbDrone.Core.History;
using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications
{
[TestFixture]
public class MatchesGrabSpecificationFixture : CoreTest<MatchesGrabSpecification>
{
private Episode _episode1;
private Episode _episode2;
private Episode _episode3;
private LocalEpisode _localEpisode;
private DownloadClientItem _downloadClientItem;
[SetUp]
public void Setup()
{
_episode1 = Builder<Episode>.CreateNew()
.With(e => e.Id = 1)
.Build();
_episode2 = Builder<Episode>.CreateNew()
.With(e => e.Id = 2)
.Build();
_episode3 = Builder<Episode>.CreateNew()
.With(e => e.Id = 3)
.Build();
_localEpisode = Builder<LocalEpisode>.CreateNew()
.With(l => l.Path = @"C:\Test\Unsorted\Series.Title.S01E01.720p.HDTV-Sonarr\S01E05.mkv".AsOsAgnostic())
.With(l => l.Episodes = new List<Episode> { _episode1 })
.Build();
_downloadClientItem = Builder<DownloadClientItem>.CreateNew().Build();
}
private void GivenHistoryForEpisodes(params Episode[] episodes)
{
var history = new List<EpisodeHistory>();
foreach (var episode in episodes)
{
history.Add(Builder<EpisodeHistory>.CreateNew()
.With(h => h.EventType = EpisodeHistoryEventType.Grabbed)
.With(h => h.EpisodeId = episode.Id)
.Build());
}
Mocker.GetMock<IHistoryService>()
.Setup(s => s.FindByDownloadId(It.IsAny<string>()))
.Returns(history);
}
[Test]
public void should_be_accepted_for_existing_file()
{
_localEpisode.ExistingFile = true;
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_no_download_client_item()
{
Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_no_grab_history()
{
GivenHistoryForEpisodes();
Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_file_episode_matches_single_grab_history()
{
GivenHistoryForEpisodes(_episode1);
Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_file_episode_is_in_multi_episode_grab_history()
{
GivenHistoryForEpisodes(_episode1, _episode2);
Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeTrue();
}
[Test]
public void should_be_rejected_if_file_episode_does_not_match_single_grab_history()
{
GivenHistoryForEpisodes(_episode2);
Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeFalse();
}
[Test]
public void should_be_rejected_if_file_episode_is_not_in_multi_episode_grab_history()
{
GivenHistoryForEpisodes(_episode2, _episode3);
Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeFalse();
}
}
}

View File

@ -0,0 +1,70 @@
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
using NzbDrone.Core.History;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.MediaFiles.EpisodeImport.Specifications
{
public class MatchesGrabSpecification : IImportDecisionEngineSpecification
{
private readonly Logger _logger;
private readonly IParsingService _parsingService;
private readonly IHistoryService _historyService;
public MatchesGrabSpecification(IParsingService parsingService, IHistoryService historyService, Logger logger)
{
_logger = logger;
_parsingService = parsingService;
_historyService = historyService;
}
public Decision IsSatisfiedBy(LocalEpisode localEpisode, DownloadClientItem downloadClientItem)
{
if (localEpisode.ExistingFile)
{
return Decision.Accept();
}
if (downloadClientItem == null)
{
return Decision.Accept();
}
var grabbedHistory = _historyService.FindByDownloadId(downloadClientItem.DownloadId)
.Where(h => h.EventType == EpisodeHistoryEventType.Grabbed)
.ToList();
if (grabbedHistory.Empty())
{
return Decision.Accept();
}
var unexpected = localEpisode.Episodes.Where(e => grabbedHistory.All(o => o.EpisodeId != e.Id)).ToList();
if (unexpected.Any())
{
_logger.Debug("Unexpected episode(s) in file: {0}", FormatEpisode(unexpected));
if (unexpected.Count == 1)
{
return Decision.Reject("Episode {0} was not found in the grabbed release: {1}", FormatEpisode(unexpected), grabbedHistory.First().SourceTitle);
}
return Decision.Reject("Episodes {0} were not found in the grabbed release: {1}", FormatEpisode(unexpected), grabbedHistory.First().SourceTitle);
}
return Decision.Accept();
}
private string FormatEpisode(List<Episode> episodes)
{
return string.Join(", ", episodes.Select(e => $"{e.SeasonNumber}x{e.EpisodeNumber:00}"));
}
}
}