diff --git a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs index 043aa6f55..bac1c092d 100644 --- a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs @@ -9,7 +9,6 @@ using NzbDrone.Common.Disk; using NzbDrone.Core.Configuration; using NzbDrone.Core.Download; using NzbDrone.Core.History; -using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Test.Framework; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.EpisodeImport; @@ -104,9 +103,9 @@ namespace NzbDrone.Core.Test.Download { Mocker.GetMock() .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny())) - .Returns(new List() + .Returns(new List { - new ImportDecision(null) + new ImportResult(null) }); } @@ -114,9 +113,9 @@ namespace NzbDrone.Core.Test.Download { Mocker.GetMock() .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny())) - .Returns(new List() + .Returns(new List() { - new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }, "Test Failure") + new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" }, "Test Failure")) }); } @@ -448,5 +447,37 @@ namespace NzbDrone.Core.Test.Download Mocker.GetMock() .Verify(c => c.DeleteFolder(It.IsAny(), true), Times.Once()); } + + [Test] + public void should_not_mark_as_successful_if_no_files_were_imported() + { + GivenCompletedDownloadClientHistory(); + + var history = Builder.CreateListOfSize(1) + .Build() + .ToList(); + + GivenGrabbedHistory(history); + GivenNoImportedHistory(); + + Mocker.GetMock() + .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny())) + .Returns(new List + { + new ImportResult( + new ImportDecision(new LocalEpisode() {Path = @"C:\TestPath\Droned.S01E01.mkv"}), + "Test Failure") + }); + + history.First().Data.Add("downloadClient", "SabnzbdClient"); + history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId); + + Subject.Execute(new CheckForFinishedDownloadCommand()); + + Mocker.GetMock() + .Verify(c => c.DeleteFolder(It.IsAny(), true), Times.Never()); + + ExceptionVerification.ExpectedErrors(1); + } } } diff --git a/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs index ad3d85bbb..5ef12266e 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using FizzWare.NBuilder; using Moq; using NUnit.Framework; -using NzbDrone.Common; using NzbDrone.Common.Disk; using NzbDrone.Core.Configuration; using NzbDrone.Core.MediaFiles; @@ -42,7 +42,7 @@ namespace NzbDrone.Core.Test.MediaFiles Mocker.GetMock() .Setup(s => s.Import(It.IsAny>(), true, null)) - .Returns(new List()); + .Returns(new List()); } private void GivenValidSeries() @@ -125,7 +125,7 @@ namespace NzbDrone.Core.Test.MediaFiles { Mocker.GetMock() .Setup(s => s.Import(It.IsAny>(), false, null)) - .Returns(new List()); + .Returns(new List()); Subject.Execute(new DownloadedEpisodesScanCommand()); @@ -149,7 +149,7 @@ namespace NzbDrone.Core.Test.MediaFiles Mocker.GetMock() .Setup(s => s.Import(It.IsAny>(), true, null)) - .Returns(imported); + .Returns(imported.Select(i => new ImportResult(i)).ToList()); Subject.Execute(new DownloadedEpisodesScanCommand()); @@ -175,7 +175,7 @@ namespace NzbDrone.Core.Test.MediaFiles Mocker.GetMock() .Setup(s => s.Import(It.IsAny>(), true, null)) - .Returns(imported); + .Returns(imported.Select(i => new ImportResult(i)).ToList()); Mocker.GetMock() .Setup(s => s.IsSample(It.IsAny(), diff --git a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs index 2e134c74c..91aaf0465 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs @@ -65,9 +65,11 @@ namespace NzbDrone.Core.Test.MediaFiles } [Test] - public void should_return_empty_list_if_there_are_no_approved_decisions() + public void should_not_import_any_if_there_are_no_approved_decisions() { - Subject.Import(_rejectedDecisions, false).Should().BeEmpty(); + Subject.Import(_rejectedDecisions, false).Where(i => i.Result == ImportResultType.Imported).Should().BeEmpty(); + + Mocker.GetMock().Verify(v => v.Add(It.IsAny()), Times.Never()); } [Test] @@ -83,7 +85,10 @@ namespace NzbDrone.Core.Test.MediaFiles all.AddRange(_rejectedDecisions); all.AddRange(_approvedDecisions); - Subject.Import(all, false).Should().HaveCount(5); + var result = Subject.Import(all, false); + + result.Should().HaveCount(all.Count); + result.Where(i => i.Result == ImportResultType.Imported).Should().HaveCount(_approvedDecisions.Count); } [Test] @@ -93,7 +98,9 @@ namespace NzbDrone.Core.Test.MediaFiles all.AddRange(_approvedDecisions); all.Add(new ImportDecision(_approvedDecisions.First().LocalEpisode)); - Subject.Import(all, false).Should().HaveCount(5); + var result = Subject.Import(all, false); + + result.Where(i => i.Result == ImportResultType.Imported).Should().HaveCount(_approvedDecisions.Count); } [Test] @@ -136,7 +143,7 @@ namespace NzbDrone.Core.Test.MediaFiles { Series = fileDecision.LocalEpisode.Series, Episodes = new List {fileDecision.LocalEpisode.Episodes.First()}, - Path = @"C:\Test\TV\30 Rock\30 Rock - S01E01 - Pilit.avi".AsOsAgnostic(), + Path = @"C:\Test\TV\30 Rock\30 Rock - S01E01 - Pilot.avi".AsOsAgnostic(), Quality = new QualityModel(Quality.Bluray720p), Size = 80.Megabytes() }); @@ -148,8 +155,9 @@ namespace NzbDrone.Core.Test.MediaFiles var results = Subject.Import(all, false); - results.Should().HaveCount(1); - results.Should().ContainSingle(d => d.LocalEpisode.Size == fileDecision.LocalEpisode.Size); + results.Should().HaveCount(all.Count); + results.Should().ContainSingle(d => d.Result == ImportResultType.Imported); + results.Should().ContainSingle(d => d.Result == ImportResultType.Imported && d.ImportDecision.LocalEpisode.Size == fileDecision.LocalEpisode.Size); } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/Download/CompletedDownloadService.cs b/src/NzbDrone.Core/Download/CompletedDownloadService.cs index 21c8c8835..a838c10d7 100644 --- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs +++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs @@ -3,19 +3,13 @@ using System.Collections.Generic; using System.Linq; using NLog; using NzbDrone.Common; -using NzbDrone.Common.Cache; using NzbDrone.Common.Disk; using NzbDrone.Core.Configuration; using NzbDrone.Core.History; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.EpisodeImport; -using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using System.IO; -using NzbDrone.Common.EnsureThat; -using NzbDrone.Core.Parser; -using NzbDrone.Core.Tv; -using NzbDrone.Core.Qualities; namespace NzbDrone.Core.Download { @@ -97,51 +91,15 @@ namespace NzbDrone.Core.Download if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath)) { - var decisions = _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem); + var importResults = _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem); - if (!decisions.Any()) - { - UpdateStatusMessage(trackedDownload, LogLevel.Error, "No files found eligible for import in {0}", trackedDownload.DownloadItem.OutputPath); - } - else if (decisions.Any(v => v.Approved)) - { - UpdateStatusMessage(trackedDownload, LogLevel.Info, "Imported {0} files.", decisions.Count(v => v.Approved)); - - trackedDownload.State = TrackedDownloadState.Imported; - } - else - { - var rejections = decisions - .Where(v => !v.Approved) - .Select(v => v.Rejections.Aggregate(Path.GetFileName(v.LocalEpisode.Path), (a, r) => a + "\r\n- " + r)) - .Aggregate("Failed to import:", (a, r) => a + "\r\n" + r); - - UpdateStatusMessage(trackedDownload, LogLevel.Error, rejections); - } + ProcessImportResults(trackedDownload, importResults); } else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath)) { - var decisions = _downloadedEpisodesImportService.ProcessFile(new FileInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem); - - if (!decisions.Any()) - { - UpdateStatusMessage(trackedDownload, LogLevel.Error, "No files found eligible for import in {0}", trackedDownload.DownloadItem.OutputPath); - } - else if (decisions.Any(v => v.Approved)) - { - UpdateStatusMessage(trackedDownload, LogLevel.Info, "Imported {0} files.", decisions.Count(v => v.Approved)); - - trackedDownload.State = TrackedDownloadState.Imported; - } - else - { - var rejections = decisions - .Where(v => !v.Approved) - .Select(v => v.Rejections.Aggregate(Path.GetFileName(v.LocalEpisode.Path), (a, r) => a + "\r\n- " + r)) - .Aggregate("Failed to import:", (a, r) => a + "\r\n" + r); + var importResults = _downloadedEpisodesImportService.ProcessFile(new FileInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem); - UpdateStatusMessage(trackedDownload, LogLevel.Error, rejections); - } + ProcessImportResults(trackedDownload, importResults); } else { @@ -222,5 +180,28 @@ namespace NzbDrone.Core.Download _logger.Debug(logMessage); } } + + private void ProcessImportResults(TrackedDownload trackedDownload, List importResults) + { + if (importResults.Empty()) + { + UpdateStatusMessage(trackedDownload, LogLevel.Error, "No files found are eligible for import in {0}", trackedDownload.DownloadItem.OutputPath); + } + else if (importResults.All(v => v.Result == ImportResultType.Imported || v.Result == ImportResultType.Rejected)) + { + UpdateStatusMessage(trackedDownload, LogLevel.Info, "Imported {0} files.", importResults.Count(v => v.Result == ImportResultType.Imported)); + + trackedDownload.State = TrackedDownloadState.Imported; + } + else + { + var errors = importResults + .Where(v => v.Result == ImportResultType.Skipped || v.Result == ImportResultType.Rejected) + .Select(v => v.Errors.Aggregate(Path.GetFileName(v.ImportDecision.LocalEpisode.Path), (a, r) => a + "\r\n- " + r)) + .Aggregate("Failed to import:", (a, r) => a + "\r\n" + r); + + UpdateStatusMessage(trackedDownload, LogLevel.Error, errors); + } + } } } diff --git a/src/NzbDrone.Core/Download/TrackedDownload.cs b/src/NzbDrone.Core/Download/TrackedDownload.cs index 841e803a5..215e0d036 100644 --- a/src/NzbDrone.Core/Download/TrackedDownload.cs +++ b/src/NzbDrone.Core/Download/TrackedDownload.cs @@ -14,6 +14,7 @@ namespace NzbDrone.Core.Download public Int32 RetryCount { get; set; } public Boolean HasError { get; set; } public String StatusMessage { get; set; } + public List StatusMessages { get; set; } } public enum TrackedDownloadState diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs index b7d0637b3..3bf824315 100644 --- a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using NLog; @@ -12,7 +11,6 @@ using NzbDrone.Core.MediaFiles.Commands; using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Parser; -using NzbDrone.Core.Qualities; using NzbDrone.Core.Tv; using NzbDrone.Core.Download; @@ -20,8 +18,8 @@ namespace NzbDrone.Core.MediaFiles { public interface IDownloadedEpisodesImportService { - List ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem); - List ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem); + List ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem); + List ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem); } public class DownloadedEpisodesImportService : IDownloadedEpisodesImportService, IExecute @@ -57,7 +55,7 @@ namespace NzbDrone.Core.MediaFiles _logger = logger; } - public List ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem) + public List ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem) { var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name); var series = _parsingService.GetSeries(cleanedUpName); @@ -67,39 +65,35 @@ namespace NzbDrone.Core.MediaFiles if (series == null) { _logger.Debug("Unknown Series {0}", cleanedUpName); - return new List(); + return new List(); } var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName); - var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, true, quality); - var importedDecisions = _importApprovedEpisodes.Import(decisions, true, downloadClientItem); + var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem); - if (!downloadClientItem.IsReadOnly && importedDecisions.Any() && ShouldDeleteFolder(directoryInfo)) + if (!downloadClientItem.IsReadOnly && importResults.Any() && ShouldDeleteFolder(directoryInfo)) { _logger.Debug("Deleting folder after importing valid files"); _diskProvider.DeleteFolder(directoryInfo.FullName, true); } - return importedDecisions.Union(decisions).ToList(); + return importResults; } - public List ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem) + public List ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem) { var series = _parsingService.GetSeries(Path.GetFileNameWithoutExtension(fileInfo.Name)); if (series == null) { _logger.Debug("Unknown Series for file: {0}", fileInfo.Name); - return new List(); + return new List(); } - var decisions = _importDecisionMaker.GetImportDecisions(new List() { fileInfo.FullName }, series, true, null); - - var importedDecisions = _importApprovedEpisodes.Import(decisions, true, downloadClientItem); - - return importedDecisions.Union(decisions).ToList(); + var decisions = _importDecisionMaker.GetImportDecisions(new List() { fileInfo.FullName }, series, true); + return _importApprovedEpisodes.Import(decisions, true, downloadClientItem); } private void ProcessDownloadedEpisodesFolder() @@ -136,7 +130,7 @@ namespace NzbDrone.Core.MediaFiles } } - private List ProcessFolder(DirectoryInfo directoryInfo) + private List ProcessFolder(DirectoryInfo directoryInfo) { var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name); var series = _parsingService.GetSeries(cleanedUpName); @@ -146,7 +140,7 @@ namespace NzbDrone.Core.MediaFiles if (series == null) { _logger.Debug("Unknown Series {0}", cleanedUpName); - return new List(); + return new List(); } var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName); @@ -156,7 +150,7 @@ namespace NzbDrone.Core.MediaFiles if (_diskProvider.IsFileLocked(videoFile)) { _logger.Debug("[{0}] is currently locked by another process, skipping", videoFile); - return new List(); + return new List(); } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index a9cba1359..995cc339e 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -8,7 +8,6 @@ using NzbDrone.Common.Disk; using NzbDrone.Core.MediaFiles.Events; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Qualities; -using NzbDrone.Core.Tv; using NzbDrone.Core.Download; @@ -16,7 +15,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport { public interface IImportApprovedEpisodes { - List Import(List decisions, bool newDownload, DownloadClientItem historyItem = null); + List Import(List decisions, bool newDownload, DownloadClientItem historyItem = null); } public class ImportApprovedEpisodes : IImportApprovedEpisodes @@ -40,7 +39,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport _logger = logger; } - public List Import(List decisions, bool newDownload, DownloadClientItem historyItem = null) + public List Import(List decisions, bool newDownload, DownloadClientItem historyItem = null) { var qualifiedImports = decisions.Where(c => c.Approved) .GroupBy(c => c.LocalEpisode.Series.Id, (i, s) => s @@ -49,7 +48,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport .SelectMany(c => c) .ToList(); - var imported = new List(); + var importResults = new List(); foreach (var importDecision in qualifiedImports.OrderByDescending(e => e.LocalEpisode.Size)) { @@ -59,11 +58,12 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport try { //check if already imported - if (imported.SelectMany(r => r.LocalEpisode.Episodes) + if (importResults.SelectMany(r => r.ImportDecision.LocalEpisode.Episodes) .Select(e => e.Id) .Intersect(localEpisode.Episodes.Select(e => e.Id)) .Any()) { + importResults.Add(new ImportResult(importDecision, "Episode has already been imported")); continue; } @@ -92,7 +92,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport } _mediaFileService.Add(episodeFile); - imported.Add(importDecision); + importResults.Add(new ImportResult(importDecision)); if (historyItem != null) { @@ -111,10 +111,15 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport catch (Exception e) { _logger.WarnException("Couldn't import episode " + localEpisode, e); + importResults.Add(new ImportResult(importDecision, "Failed to import episode")); } } - return imported; + //Adding all the rejected decisions + importResults.AddRange(decisions.Where(c => !c.Approved) + .Select(d => new ImportResult(d, d.Rejections.ToArray()))); + + return importResults; } } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportResult.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportResult.cs new file mode 100644 index 000000000..ef146ed3f --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportResult.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common; + +namespace NzbDrone.Core.MediaFiles.EpisodeImport +{ + public class ImportResult + { + public ImportDecision ImportDecision { get; private set; } + public List Errors { get; private set; } + + public ImportResultType Result + { + get + { + //Approved and imported + if (Errors.Empty()) return ImportResultType.Imported; + + //Decision was approved, but it was not imported + if (ImportDecision.Approved) return ImportResultType.Skipped; + + //Decision was rejected + return ImportResultType.Rejected; + } + } + + public ImportResult(ImportDecision importDecision, params String[] errors) + { + ImportDecision = importDecision; + Errors = errors.ToList(); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportResultType.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportResultType.cs new file mode 100644 index 000000000..7c43332de --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportResultType.cs @@ -0,0 +1,9 @@ +namespace NzbDrone.Core.MediaFiles.EpisodeImport +{ + public enum ImportResultType + { + Imported, + Rejected, + Skipped + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 990e8d655..4e112cbb6 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -468,10 +468,12 @@ + + diff --git a/src/NzbDrone.Core/Queue/QueueService.cs b/src/NzbDrone.Core/Queue/QueueService.cs index 0f1983ad6..3289bb210 100644 --- a/src/NzbDrone.Core/Queue/QueueService.cs +++ b/src/NzbDrone.Core/Queue/QueueService.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using System.Collections.Generic; -using NLog; using NzbDrone.Core.Download; namespace NzbDrone.Core.Queue