New: Already Imported Decision Specification (#661)

pull/6/head
Qstick 6 years ago committed by GitHub
parent d552770da9
commit 61cf1ccb7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -138,7 +138,7 @@ namespace Lidarr.Api.V1.History
{ {
int albumId = Convert.ToInt32(queryAlbumId.Value); int albumId = Convert.ToInt32(queryAlbumId.Value);
return _historyService.GetByAlbum(artistId, albumId, eventType).Select(h => MapToResource(h, includeArtist, includeAlbum, includeTrack)).ToList(); return _historyService.GetByAlbum(albumId, eventType).Select(h => MapToResource(h, includeArtist, includeAlbum, includeTrack)).ToList();
} }
return _historyService.GetByArtist(artistId, eventType).Select(h => MapToResource(h, includeArtist, includeAlbum, includeTrack)).ToList(); return _historyService.GetByArtist(artistId, eventType).Select(h => MapToResource(h, includeArtist, includeAlbum, includeTrack)).ToList();

@ -0,0 +1,172 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.History;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Music;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Languages;
namespace NzbDrone.Core.Test.DecisionEngineTests
{
[TestFixture]
public class AlreadyImportedSpecificationFixture : CoreTest<AlreadyImportedSpecification>
{
private const int FIRST_ALBUM_ID = 1;
private const string TITLE = "Some.Artist-Some.Album-2018-320kbps-CD-Lidarr";
private Artist _artist;
private QualityModel _mp3;
private QualityModel _flac;
private RemoteAlbum _remoteAlbum;
private List<History.History> _history;
private TrackFile _firstFile;
[SetUp]
public void Setup()
{
var singleAlbumList = new List<Album>
{
new Album
{
Id = FIRST_ALBUM_ID,
Title = "Some Album"
}
};
_artist = Builder<Artist>.CreateNew()
.Build();
_firstFile = new TrackFile { Quality = new QualityModel(Quality.FLAC, new Revision(version: 2)), DateAdded = DateTime.Now, Language = Language.English };
_mp3 = new QualityModel(Quality.MP3_320, new Revision(version: 1));
_flac = new QualityModel(Quality.FLAC, new Revision(version: 1));
_remoteAlbum = new RemoteAlbum
{
Artist = _artist,
ParsedAlbumInfo = new ParsedAlbumInfo { Quality = _mp3 },
Albums = singleAlbumList,
Release = Builder<ReleaseInfo>.CreateNew()
.Build()
};
_history = new List<History.History>();
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.EnableCompletedDownloadHandling)
.Returns(true);
Mocker.GetMock<IHistoryService>()
.Setup(s => s.GetByAlbum(It.IsAny<int>(), null))
.Returns(_history);
Mocker.GetMock<IMediaFileService>()
.Setup(c => c.GetFilesByAlbum(It.IsAny<int>()))
.Returns(new List<TrackFile> { _firstFile });
}
private void GivenCdhDisabled()
{
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.EnableCompletedDownloadHandling)
.Returns(false);
}
private void GivenHistoryItem(string downloadId, string sourceTitle, QualityModel quality, HistoryEventType eventType)
{
_history.Add(new History.History
{
DownloadId = downloadId,
SourceTitle = sourceTitle,
Quality = quality,
Date = DateTime.UtcNow,
EventType = eventType
});
}
[Test]
public void should_be_accepted_if_CDH_is_disabled()
{
GivenCdhDisabled();
Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_album_does_not_have_a_file()
{
Mocker.GetMock<IMediaFileService>()
.Setup(c => c.GetFilesByAlbum(It.IsAny<int>()))
.Returns(new List<TrackFile> { });
Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_album_does_not_have_grabbed_event()
{
Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_album_does_not_have_imported_event()
{
GivenHistoryItem(Guid.NewGuid().ToString().ToUpper(), TITLE, _mp3, HistoryEventType.Grabbed);
Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_grabbed_and_imported_quality_is_the_same()
{
var downloadId = Guid.NewGuid().ToString().ToUpper();
GivenHistoryItem(downloadId, TITLE, _mp3, HistoryEventType.Grabbed);
GivenHistoryItem(downloadId, TITLE, _mp3, HistoryEventType.DownloadImported);
Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_rejected_if_grabbed_download_id_matches_release_torrent_hash()
{
var downloadId = Guid.NewGuid().ToString().ToUpper();
GivenHistoryItem(downloadId, TITLE, _mp3, HistoryEventType.Grabbed);
GivenHistoryItem(downloadId, TITLE, _flac, HistoryEventType.DownloadImported);
_remoteAlbum.Release = Builder<TorrentInfo>.CreateNew()
.With(t => t.DownloadProtocol = DownloadProtocol.Torrent)
.With(t => t.InfoHash = downloadId)
.Build();
Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeFalse();
}
[Test]
public void should_be_rejected_if_release_title_matches_grabbed_event_source_title()
{
var downloadId = Guid.NewGuid().ToString().ToUpper();
GivenHistoryItem(downloadId, TITLE, _mp3, HistoryEventType.Grabbed);
GivenHistoryItem(downloadId, TITLE, _flac, HistoryEventType.DownloadImported);
_remoteAlbum.Release = Builder<TorrentInfo>.CreateNew()
.With(t => t.DownloadProtocol = DownloadProtocol.Torrent)
.With(t => t.InfoHash = downloadId)
.Build();
Subject.IsSatisfiedBy(_remoteAlbum, null).Accepted.Should().BeFalse();
}
}
}

@ -133,6 +133,7 @@
<Compile Include="Datastore\ReflectionStrategyFixture\Benchmarks.cs" /> <Compile Include="Datastore\ReflectionStrategyFixture\Benchmarks.cs" />
<Compile Include="Datastore\SqliteSchemaDumperTests\SqliteSchemaDumperFixture.cs" /> <Compile Include="Datastore\SqliteSchemaDumperTests\SqliteSchemaDumperFixture.cs" />
<Compile Include="DecisionEngineTests\AcceptableSizeSpecificationFixture.cs" /> <Compile Include="DecisionEngineTests\AcceptableSizeSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\AlreadyImportedSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\BlockedIndexerSpecificationFixture.cs" /> <Compile Include="DecisionEngineTests\BlockedIndexerSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\DiscographySpecificationFixture.cs" /> <Compile Include="DecisionEngineTests\DiscographySpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\MaximumSizeSpecificationFixture.cs" /> <Compile Include="DecisionEngineTests\MaximumSizeSpecificationFixture.cs" />

@ -0,0 +1,106 @@
using System;
using System.Linq;
using NLog;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.History;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.IndexerSearch.Definitions;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.DecisionEngine.Specifications
{
public class AlreadyImportedSpecification : IDecisionEngineSpecification
{
private readonly IHistoryService _historyService;
private readonly IConfigService _configService;
private readonly IMediaFileService _mediaFileService;
private readonly Logger _logger;
public AlreadyImportedSpecification(IHistoryService historyService,
IConfigService configService,
IMediaFileService mediaFileService,
Logger logger)
{
_historyService = historyService;
_mediaFileService = mediaFileService;
_configService = configService;
_logger = logger;
}
public SpecificationPriority Priority => SpecificationPriority.Database;
public RejectionType Type => RejectionType.Permanent;
public Decision IsSatisfiedBy(RemoteAlbum subject, SearchCriteriaBase searchCriteria)
{
var cdhEnabled = _configService.EnableCompletedDownloadHandling;
if (!cdhEnabled)
{
_logger.Debug("Skipping already imported check because CDH is disabled");
return Decision.Accept();
}
_logger.Debug("Performing already imported check on report");
foreach (var album in subject.Albums)
{
var trackFiles = _mediaFileService.GetFilesByAlbum(album.Id);
if (trackFiles.Count() == 0)
{
_logger.Debug("Skipping already imported check for album without files");
continue;
}
var historyForAlbum = _historyService.GetByAlbum(album.Id, null);
var lastGrabbed = historyForAlbum.FirstOrDefault(h => h.EventType == HistoryEventType.Grabbed);
if (lastGrabbed == null)
{
continue;
}
var imported = historyForAlbum.FirstOrDefault(h =>
h.EventType == HistoryEventType.DownloadImported &&
h.DownloadId == lastGrabbed.DownloadId);
if (imported == null)
{
continue;
}
// This is really only a guard against redownloading the same release over
// and over when the grabbed and imported qualities do not match, if they do
// match skip this check.
if (lastGrabbed.Quality.Equals(imported.Quality))
{
continue;
}
var release = subject.Release;
if (release.DownloadProtocol == DownloadProtocol.Torrent)
{
var torrentInfo = release as TorrentInfo;
if (torrentInfo != null && torrentInfo.InfoHash.ToUpper() == lastGrabbed.DownloadId)
{
_logger.Debug("Has same torrent hash as a grabbed and imported release");
return Decision.Reject("Has same torrent hash as a grabbed and imported release");
}
}
// Only based on title because a release with the same title on another indexer/released at
// a different time very likely has the exact same content and we don't need to also try it.
if (release.Title.Equals(lastGrabbed.SourceTitle, StringComparison.InvariantCultureIgnoreCase))
{
_logger.Debug("Has same release name as a grabbed and imported release");
return Decision.Reject("Has same release name as a grabbed and imported release");
}
}
return Decision.Accept();
}
}
}

@ -15,7 +15,7 @@ namespace NzbDrone.Core.History
History MostRecentForDownloadId(string downloadId); History MostRecentForDownloadId(string downloadId);
List<History> FindByDownloadId(string downloadId); List<History> FindByDownloadId(string downloadId);
List<History> GetByArtist(int artistId, HistoryEventType? eventType); List<History> GetByArtist(int artistId, HistoryEventType? eventType);
List<History> GetByAlbum(int artistId, int albumId, HistoryEventType? eventType); List<History> GetByAlbum(int albumId, HistoryEventType? eventType);
List<History> FindDownloadHistory(int idArtistId, QualityModel quality); List<History> FindDownloadHistory(int idArtistId, QualityModel quality);
void DeleteForArtist(int artistId); void DeleteForArtist(int artistId);
List<History> Since(DateTime date, HistoryEventType? eventType); List<History> Since(DateTime date, HistoryEventType? eventType);
@ -66,11 +66,10 @@ namespace NzbDrone.Core.History
return query; return query;
} }
public List<History> GetByAlbum(int artistId, int albumId, HistoryEventType? eventType) public List<History> GetByAlbum(int albumId, HistoryEventType? eventType)
{ {
var query = Query.Join<History, Album>(JoinType.Inner, h => h.Album, (h, e) => h.AlbumId == e.Id) var query = Query.Join<History, Album>(JoinType.Inner, h => h.Album, (h, e) => h.AlbumId == e.Id)
.Where(h => h.ArtistId == artistId) .Where(h => h.AlbumId == albumId);
.AndWhere(h => h.AlbumId == albumId);
if (eventType.HasValue) if (eventType.HasValue)
{ {

@ -24,7 +24,7 @@ namespace NzbDrone.Core.History
History MostRecentForDownloadId(string downloadId); History MostRecentForDownloadId(string downloadId);
History Get(int historyId); History Get(int historyId);
List<History> GetByArtist(int artistId, HistoryEventType? eventType); List<History> GetByArtist(int artistId, HistoryEventType? eventType);
List<History> GetByAlbum(int artistId, int albumId, HistoryEventType? eventType); List<History> GetByAlbum(int albumId, HistoryEventType? eventType);
List<History> Find(string downloadId, HistoryEventType eventType); List<History> Find(string downloadId, HistoryEventType eventType);
List<History> FindByDownloadId(string downloadId); List<History> FindByDownloadId(string downloadId);
List<History> Since(DateTime date, HistoryEventType? eventType); List<History> Since(DateTime date, HistoryEventType? eventType);
@ -75,9 +75,9 @@ namespace NzbDrone.Core.History
return _historyRepository.GetByArtist(artistId, eventType); return _historyRepository.GetByArtist(artistId, eventType);
} }
public List<History> GetByAlbum(int artistId, int albumId, HistoryEventType? eventType) public List<History> GetByAlbum(int albumId, HistoryEventType? eventType)
{ {
return _historyRepository.GetByAlbum(artistId, albumId, eventType); return _historyRepository.GetByAlbum(albumId, eventType);
} }
public List<History> Find(string downloadId, HistoryEventType eventType) public List<History> Find(string downloadId, HistoryEventType eventType)

@ -225,6 +225,7 @@
<Compile Include="DecisionEngine\DownloadDecisionComparer.cs" /> <Compile Include="DecisionEngine\DownloadDecisionComparer.cs" />
<Compile Include="DecisionEngine\DownloadDecisionMaker.cs" /> <Compile Include="DecisionEngine\DownloadDecisionMaker.cs" />
<Compile Include="DecisionEngine\DownloadDecisionPriorizationService.cs" /> <Compile Include="DecisionEngine\DownloadDecisionPriorizationService.cs" />
<Compile Include="DecisionEngine\Specifications\AlreadyImportedSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\IDecisionEngineSpecification.cs" /> <Compile Include="DecisionEngine\Specifications\IDecisionEngineSpecification.cs" />
<Compile Include="DecisionEngine\IRejectWithReason.cs" /> <Compile Include="DecisionEngine\IRejectWithReason.cs" />
<Compile Include="DecisionEngine\Rejection.cs" /> <Compile Include="DecisionEngine\Rejection.cs" />

Loading…
Cancel
Save