Show title mismatches, but don't import them automaticallys

Fixed: Show Series title mismatches in the UI
Fixed: Force import from Queue for title mismatches
This commit is contained in:
Mark McDowall 2015-01-19 15:53:31 -08:00
parent 7f27507ef6
commit 2bbce39faa
11 changed files with 154 additions and 68 deletions

View File

@ -72,7 +72,7 @@ namespace NzbDrone.Api.Queue
var resource = Request.Body.FromJson<QueueResource>();
var trackedDownload = GetTrackedDownload(resource.Id);
_completedDownloadService.Process(trackedDownload);
_completedDownloadService.Process(trackedDownload, true);
return resource.AsResponse();
}

View File

@ -11,6 +11,7 @@ using NzbDrone.Core.History;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
@ -66,17 +67,23 @@ namespace NzbDrone.Core.Test.Download
.Returns((History.History)null);
}
private void GivenSuccessfulImport()
{
Mocker.GetMock<IDownloadedEpisodesImportService>()
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<DownloadClientItem>()))
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>
{
new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }))
});
}
private void GivenSeriesMatch()
{
Mocker.GetMock<IParsingService>()
.Setup(s => s.GetSeries(It.IsAny<string>()))
.Returns(_trackedDownload.RemoteEpisode.Series);
}
[TestCase(DownloadItemStatus.Downloading)]
[TestCase(DownloadItemStatus.Failed)]
[TestCase(DownloadItemStatus.Queued)]
@ -107,6 +114,7 @@ namespace NzbDrone.Core.Test.Download
{
_trackedDownload.DownloadItem.Category = "tv";
GivenNoGrabbedHistory();
GivenSeriesMatch();
GivenSuccessfulImport();
Subject.Process(_trackedDownload);
@ -142,7 +150,7 @@ namespace NzbDrone.Core.Test.Download
public void should_not_mark_as_imported_if_all_files_were_rejected()
{
Mocker.GetMock<IDownloadedEpisodesImportService>()
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<DownloadClientItem>()))
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>
{
new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}, "Rejected!"),"Test Failure"),
@ -161,7 +169,7 @@ namespace NzbDrone.Core.Test.Download
public void should_not_mark_as_imported_if_all_files_were_skipped()
{
Mocker.GetMock<IDownloadedEpisodesImportService>()
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<DownloadClientItem>()))
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>
{
new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure"),
@ -177,6 +185,7 @@ namespace NzbDrone.Core.Test.Download
[Test]
public void should_mark_as_imported_if_all_episodes_were_imported_but_extra_files_were_not()
{
GivenSeriesMatch();
_trackedDownload.RemoteEpisode.Episodes = new List<Episode>
{
@ -184,14 +193,13 @@ namespace NzbDrone.Core.Test.Download
};
Mocker.GetMock<IDownloadedEpisodesImportService>()
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<DownloadClientItem>()))
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>
{
new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})),
new ImportResult(new ImportDecision(new LocalEpisode{Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure")
});
Subject.Process(_trackedDownload);
AssertCompletedDownload();
@ -208,7 +216,7 @@ namespace NzbDrone.Core.Test.Download
};
Mocker.GetMock<IDownloadedEpisodesImportService>()
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<DownloadClientItem>()))
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>
{
new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})),
@ -222,11 +230,38 @@ namespace NzbDrone.Core.Test.Download
AssertNoCompletedDownload();
}
[Test]
public void should_not_import_when_there_is_a_title_mismatch()
{
Subject.Process(_trackedDownload);
AssertNoCompletedDownload();
}
[Test]
public void should_mark_as_import_title_mismatch_if_ignore_warnings_is_true()
{
_trackedDownload.RemoteEpisode.Episodes = new List<Episode>
{
new Episode()
};
Mocker.GetMock<IDownloadedEpisodesImportService>()
.Setup(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>
{
new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}))
});
Subject.Process(_trackedDownload, true);
AssertCompletedDownload();
}
private void AssertNoAttemptedImport()
{
Mocker.GetMock<IDownloadedEpisodesImportService>()
.Verify(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<DownloadClientItem>()), Times.Never());
.Verify(v => v.ProcessPath(It.IsAny<string>(), It.IsAny<Series>(), It.IsAny<DownloadClientItem>()), Times.Never());
AssertNoCompletedDownload();
}
@ -242,7 +277,7 @@ namespace NzbDrone.Core.Test.Download
private void AssertCompletedDownload()
{
Mocker.GetMock<IDownloadedEpisodesImportService>()
.Verify(v => v.ProcessPath(_trackedDownload.DownloadItem.OutputPath.FullPath, _trackedDownload.DownloadItem), Times.Once());
.Verify(v => v.ProcessPath(_trackedDownload.DownloadItem.OutputPath.FullPath, _trackedDownload.RemoteEpisode.Series, _trackedDownload.DownloadItem), Times.Once());
_trackedDownload.State.Should().Be(TrackedDownloadStage.Imported);
}

View File

@ -19,7 +19,6 @@ namespace NzbDrone.Core.Test.MediaFiles
{
private string _droneFactory = "c:\\drop\\".AsOsAgnostic();
[SetUp]
public void Setup()
{
@ -32,11 +31,6 @@ namespace NzbDrone.Core.Test.MediaFiles
Mocker.GetMock<IDownloadedEpisodesImportService>()
.Setup(v => v.ProcessRootFolder(It.IsAny<DirectoryInfo>()))
.Returns(new List<ImportResult>());
Mocker.GetMock<IDownloadedEpisodesImportService>()
.Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()))
.Returns(new List<ImportResult>());
}
[Test]
@ -48,7 +42,7 @@ namespace NzbDrone.Core.Test.MediaFiles
}
[Test]
public void should_skip_import_if_dropfolder_doesnt_exist()
public void should_skip_import_if_dronefactory_doesnt_exist()
{
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists(It.IsAny<string>())).Returns(false);
@ -58,6 +52,5 @@ namespace NzbDrone.Core.Test.MediaFiles
ExceptionVerification.ExpectedWarns(1);
}
}
}

View File

@ -199,9 +199,12 @@ namespace NzbDrone.Core.Test.MediaFiles
[Test]
public void should_return_importresult_on_unknown_series()
{
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists(It.IsAny<string>()))
.Returns(false);
var fileName = @"C:\folder\file.mkv".AsOsAgnostic();
var result = Subject.ProcessFile(new FileInfo(fileName));
var result = Subject.ProcessPath(fileName);
result.Should().HaveCount(1);
result.First().ImportDecision.Should().NotBeNull();

View File

@ -9,12 +9,13 @@ using NzbDrone.Core.History;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.Download
{
public interface ICompletedDownloadService
{
void Process(TrackedDownload trackedDownload);
void Process(TrackedDownload trackedDownload, bool ignoreWarnings = false);
}
public class CompletedDownloadService : ICompletedDownloadService
@ -23,46 +24,64 @@ namespace NzbDrone.Core.Download
private readonly IEventAggregator _eventAggregator;
private readonly IHistoryService _historyService;
private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService;
private readonly IParsingService _parsingService;
private readonly Logger _logger;
public CompletedDownloadService(IConfigService configService,
IEventAggregator eventAggregator,
IHistoryService historyService,
IDownloadedEpisodesImportService downloadedEpisodesImportService)
IDownloadedEpisodesImportService downloadedEpisodesImportService,
IParsingService parsingService,
Logger logger)
{
_configService = configService;
_eventAggregator = eventAggregator;
_historyService = historyService;
_downloadedEpisodesImportService = downloadedEpisodesImportService;
_parsingService = parsingService;
_logger = logger;
}
public void Process(TrackedDownload trackedDownload)
public void Process(TrackedDownload trackedDownload, bool ignoreWarnings = false)
{
if (trackedDownload.DownloadItem.Status != DownloadItemStatus.Completed)
{
return;
}
var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId);
if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
if (!ignoreWarnings)
{
trackedDownload.Warn("Download wasn't grabbed by Sonarr and not in a category, Skipping.");
return;
}
var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId);
var downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath;
if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace())
{
trackedDownload.Warn("Download wasn't grabbed by Sonarr and not in a category, Skipping.");
return;
}
if (downloadItemOutputPath.IsEmpty)
{
trackedDownload.Warn("Download doesn't contain intermediate path, Skipping.");
return;
}
var downloadItemOutputPath = trackedDownload.DownloadItem.OutputPath;
var downloadedEpisodesFolder = new OsPath(_configService.DownloadedEpisodesFolder);
if (downloadedEpisodesFolder.Contains(downloadItemOutputPath))
{
trackedDownload.Warn("Intermediate Download path inside drone factory, Skipping.");
return;
if (downloadItemOutputPath.IsEmpty)
{
trackedDownload.Warn("Download doesn't contain intermediate path, Skipping.");
return;
}
var downloadedEpisodesFolder = new OsPath(_configService.DownloadedEpisodesFolder);
if (downloadedEpisodesFolder.Contains(downloadItemOutputPath))
{
trackedDownload.Warn("Intermediate Download path inside drone factory, Skipping.");
return;
}
var series = _parsingService.GetSeries(trackedDownload.DownloadItem.Title);
if (series == null)
{
trackedDownload.Warn("Series title mismatch, automatic import is not possible.");
return;
}
}
Import(trackedDownload);
@ -71,7 +90,7 @@ namespace NzbDrone.Core.Download
private void Import(TrackedDownload trackedDownload)
{
var outputPath = trackedDownload.DownloadItem.OutputPath.FullPath;
var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, trackedDownload.DownloadItem);
var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem);
if (importResults.Empty())
{

View File

@ -78,7 +78,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
trackedDownloads.AddRange(newItems);
}
if (_configService.RemoveCompletedDownloads)
if (_configService.EnableCompletedDownloadHandling && _configService.RemoveCompletedDownloads)
{
RemoveCompletedDownloads(trackedDownloads);
}

View File

@ -1,6 +1,8 @@
using System;
using System.Linq;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.History;
using NzbDrone.Core.Parser;
@ -59,9 +61,17 @@ namespace NzbDrone.Core.Download.TrackedDownloads
if (parsedEpisodeInfo == null) return null;
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo);
if (remoteEpisode.Series == null)
{
return null;
var historyItems = _historyService.FindByDownloadId(downloadItem.DownloadId);
if (historyItems.Empty())
{
return null;
}
remoteEpisode = _parsingService.Map(parsedEpisodeInfo, historyItems.First().SeriesId, historyItems.Select(h => h.EpisodeId));
}
trackedDownload.RemoteEpisode = remoteEpisode;
@ -73,6 +83,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads
}
var historyItem = _historyService.MostRecentForDownloadId(downloadItem.DownloadId);
if (historyItem != null)
{
trackedDownload.State = GetStateFromHistory(historyItem.EventType);

View File

@ -22,6 +22,7 @@ namespace NzbDrone.Core.History
History MostRecentForDownloadId(string downloadId);
History Get(int historyId);
List<History> Find(string downloadId, HistoryEventType eventType);
List<History> FindByDownloadId(string downloadId);
}
public class HistoryService : IHistoryService,
@ -64,6 +65,10 @@ namespace NzbDrone.Core.History
return _historyRepository.FindByDownloadId(downloadId).Where(c => c.EventType == eventType).ToList();
}
public List<History> FindByDownloadId(string downloadId)
{
return _historyRepository.FindByDownloadId(downloadId);
}
public QualityModel GetBestQualityInHistory(Profile profile, int episodeId)
{

View File

@ -15,8 +15,7 @@ namespace NzbDrone.Core.MediaFiles
public interface IDownloadedEpisodesImportService
{
List<ImportResult> ProcessRootFolder(DirectoryInfo directoryInfo);
List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem = null);
List<ImportResult> ProcessPath(string path, DownloadClientItem downloadClientItem = null);
List<ImportResult> ProcessPath(string path, Series series = null, DownloadClientItem downloadClientItem = null);
}
public class DownloadedEpisodesImportService : IDownloadedEpisodesImportService
@ -31,13 +30,13 @@ namespace NzbDrone.Core.MediaFiles
private readonly Logger _logger;
public DownloadedEpisodesImportService(IDiskProvider diskProvider,
IDiskScanService diskScanService,
ISeriesService seriesService,
IParsingService parsingService,
IMakeImportDecision importDecisionMaker,
IImportApprovedEpisodes importApprovedEpisodes,
ISampleService sampleService,
Logger logger)
IDiskScanService diskScanService,
ISeriesService seriesService,
IParsingService parsingService,
IMakeImportDecision importDecisionMaker,
IImportApprovedEpisodes importApprovedEpisodes,
ISampleService sampleService,
Logger logger)
{
_diskProvider = diskProvider;
_diskScanService = diskScanService;
@ -68,7 +67,27 @@ namespace NzbDrone.Core.MediaFiles
return results;
}
public List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem = null)
public List<ImportResult> ProcessPath(string path, Series series = null, DownloadClientItem downloadClientItem = null)
{
if (_diskProvider.FolderExists(path))
{
if (series == null)
{
return ProcessFolder(new DirectoryInfo(path), downloadClientItem);
}
return ProcessFolder(new DirectoryInfo(path), series, downloadClientItem);
}
if (series == null)
{
return ProcessFile(new FileInfo(path), downloadClientItem);
}
return ProcessFile(new FileInfo(path), series, downloadClientItem);
}
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem = null)
{
var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name);
var series = _parsingService.GetSeries(cleanedUpName);
@ -87,7 +106,7 @@ namespace NzbDrone.Core.MediaFiles
}
private List<ImportResult> ProcessFolder(DirectoryInfo directoryInfo, Series series,
DownloadClientItem downloadClientItem = null)
DownloadClientItem downloadClientItem = null)
{
if (_seriesService.SeriesPathExists(directoryInfo.FullName))
{
@ -128,7 +147,7 @@ namespace NzbDrone.Core.MediaFiles
return importResults;
}
public List<ImportResult> ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem = null)
private List<ImportResult> ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem = null)
{
var series = _parsingService.GetSeries(Path.GetFileNameWithoutExtension(fileInfo.Name));
@ -162,16 +181,6 @@ namespace NzbDrone.Core.MediaFiles
return _importApprovedEpisodes.Import(decisions, true, downloadClientItem);
}
public List<ImportResult> ProcessPath(string path, DownloadClientItem downloadClientItem = null)
{
if (_diskProvider.FolderExists(path))
{
return ProcessFolder(new DirectoryInfo(path), downloadClientItem);
}
return ProcessFile(new FileInfo(path), downloadClientItem);
}
private string GetCleanedUpFolderName(string folder)
{
folder = folder.Replace("_UNPACK_", "")

View File

@ -16,6 +16,7 @@ namespace NzbDrone.Core.Parser
LocalEpisode GetLocalEpisode(string filename, Series series, bool sceneSource);
Series GetSeries(string title);
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 tvRageId = 0, SearchCriteriaBase searchCriteria = null);
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, Int32 seriesId, IEnumerable<Int32> episodeIds);
List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null);
ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, int tvRageId, SearchCriteriaBase searchCriteria = null);
}
@ -118,6 +119,16 @@ namespace NzbDrone.Core.Parser
return remoteEpisode;
}
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds)
{
return new RemoteEpisode
{
ParsedEpisodeInfo = parsedEpisodeInfo,
Series = _seriesService.GetSeries(seriesId),
Episodes = _episodeService.GetEpisodes(episodeIds)
};
}
public List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null)
{
var result = new List<Episode>();