From b638e09f242cff7f0e2910b96e8fb8182fedabca Mon Sep 17 00:00:00 2001
From: Mark McDowall <markus.mcd5@gmail.com>
Date: Wed, 3 Sep 2014 17:57:27 -0700
Subject: [PATCH] Fixed: Don't mark releases as imported unless at least one
 file is imported

---
 .../CompletedDownloadServiceFixture.cs        | 67 ++++++++++++++++++-
 .../ImportDecisionMakerFixture.cs             | 35 ++++++++--
 .../Download/CompletedDownloadService.cs      |  2 +-
 .../EpisodeImport/ImportDecisionMaker.cs      | 15 ++++-
 .../Metadata/ExistingMetadataService.cs       | 31 ++++++---
 src/NzbDrone.Core/NzbDrone.Core.csproj        |  1 +
 .../Parser/EpisodeNotFoundException.cs        | 11 +++
 src/NzbDrone.Core/Parser/ParsingService.cs    |  4 +-
 8 files changed, 143 insertions(+), 23 deletions(-)
 create mode 100644 src/NzbDrone.Core/Parser/EpisodeNotFoundException.cs

diff --git a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs
index bac1c092d..2d841db69 100644
--- a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs
+++ b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs
@@ -449,7 +449,7 @@ namespace NzbDrone.Core.Test.Download
         }
 
         [Test]
-        public void should_not_mark_as_successful_if_no_files_were_imported()
+        public void should_not_mark_as_imported_if_all_files_were_rejected()
         {
             GivenCompletedDownloadClientHistory();
 
@@ -464,6 +464,71 @@ namespace NzbDrone.Core.Test.Download
                   .Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()))
                   .Returns(new List<ImportResult>
                            {
+                               new ImportResult(
+                                   new ImportDecision(new LocalEpisode() {Path = @"C:\TestPath\Droned.S01E01.mkv"}, "Rejected!"),
+                                   "Test Failure")
+                           });
+
+            history.First().Data.Add("downloadClient", "SabnzbdClient");
+            history.First().Data.Add("downloadClientId", _completed.First().DownloadClientId);
+
+            Subject.Execute(new CheckForFinishedDownloadCommand());
+
+            Mocker.GetMock<IDiskProvider>()
+                .Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Never());
+
+            ExceptionVerification.ExpectedErrors(1);
+        }
+
+        [Test]
+        public void should_not_mark_as_imported_if_all_files_were_skipped()
+        {
+            GivenCompletedDownloadClientHistory();
+
+            var history = Builder<History.History>.CreateListOfSize(1)
+                                                  .Build()
+                                                  .ToList();
+
+            GivenGrabbedHistory(history);
+            GivenNoImportedHistory();
+
+            Mocker.GetMock<IDownloadedEpisodesImportService>()
+                  .Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), It.IsAny<DownloadClientItem>()))
+                  .Returns(new List<ImportResult>
+                           {
+                               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<IDiskProvider>()
+                .Verify(c => c.DeleteFolder(It.IsAny<string>(), true), Times.Never());
+
+            ExceptionVerification.ExpectedErrors(1);
+        }
+
+        [Test]
+        public void should_not_mark_as_imported_if_some_files_were_skipped()
+        {
+            GivenCompletedDownloadClientHistory();
+
+            var history = Builder<History.History>.CreateListOfSize(1)
+                                                  .Build()
+                                                  .ToList();
+
+            GivenGrabbedHistory(history);
+            GivenNoImportedHistory();
+
+            Mocker.GetMock<IDownloadedEpisodesImportService>()
+                  .Setup(v => v.ProcessFolder(It.IsAny<DirectoryInfo>(), 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")
diff --git a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs
index 8d4637aa2..6f887ad37 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/EpisodeImport/ImportDecisionMakerFixture.cs
@@ -81,7 +81,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
                   .Setup(c => c.GetLocalEpisode(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<Boolean>()))
                   .Returns(_localEpisode);
 
-
             Mocker.GetMock<IMediaFileService>()
                 .Setup(c => c.FilterExistingFiles(_videoFiles, It.IsAny<Series>()))
                 .Returns(_videoFiles);
@@ -147,12 +146,13 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
         }
 
         [Test]
-        public void failed_parse_shouldnt_blowup_the_process()
+        public void should_not_blowup_the_process_due_to_failed_parse()
         {
             GivenSpecifications(_pass1);
 
-            Mocker.GetMock<IParsingService>().Setup(c => c.GetLocalEpisode(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<Boolean>()))
-                     .Throws<TestException>();
+            Mocker.GetMock<IParsingService>()
+                  .Setup(c => c.GetLocalEpisode(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<Boolean>()))
+                  .Throws<TestException>();
 
             _videoFiles = new List<String>
                 {
@@ -161,7 +161,6 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
                     "The.Office.S03E115.DVDRip.XviD-OSiTV"
                 };
 
-
             Mocker.GetMock<IMediaFileService>()
                 .Setup(c => c.FilterExistingFiles(_videoFiles, It.IsAny<Series>()))
                 .Returns(_videoFiles);
@@ -206,5 +205,31 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
 
             result.Single().LocalEpisode.Quality.Should().Be(expectedQuality);
         }
+
+        [Test]
+        public void should_not_throw_if_episodes_are_not_found()
+        {
+            GivenSpecifications(_pass1);
+
+            Mocker.GetMock<IParsingService>()
+                  .Setup(c => c.GetLocalEpisode(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<Boolean>()))
+                  .Throws(new EpisodeNotFoundException("Episode not found"));
+
+            _videoFiles = new List<String>
+                {
+                    "The.Office.S03E115.DVDRip.XviD-OSiTV",
+                    "The.Office.S03E115.DVDRip.XviD-OSiTV",
+                    "The.Office.S03E115.DVDRip.XviD-OSiTV"
+                };
+
+            Mocker.GetMock<IMediaFileService>()
+                .Setup(c => c.FilterExistingFiles(_videoFiles, It.IsAny<Series>()))
+                .Returns(_videoFiles);
+
+            Subject.GetImportDecisions(_videoFiles, _series, false);
+
+            Mocker.GetMock<IParsingService>()
+                  .Verify(c => c.GetLocalEpisode(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<Boolean>()), Times.Exactly(_videoFiles.Count));
+        }
     }
 }
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Download/CompletedDownloadService.cs b/src/NzbDrone.Core/Download/CompletedDownloadService.cs
index a838c10d7..6c774ea1e 100644
--- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs
+++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs
@@ -187,7 +187,7 @@ namespace NzbDrone.Core.Download
             {
                 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))
+            else if (importResults.Any(v => v.Result == ImportResultType.Imported) && 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));
 
diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs
index 0806825a8..b6a29ed43 100644
--- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs
+++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportDecisionMaker.cs
@@ -60,10 +60,12 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
                 try
                 {
                     var parsedEpisode = _parsingService.GetLocalEpisode(file, series, sceneSource);
-                    
+
                     if (parsedEpisode != null)
                     {
-                        if (quality != null && new QualityModelComparer(parsedEpisode.Series.Profile).Compare(quality, parsedEpisode.Quality) > 0)
+                        if (quality != null &&
+                            new QualityModelComparer(parsedEpisode.Series.Profile).Compare(quality,
+                                parsedEpisode.Quality) > 0)
                         {
                             _logger.Debug("Using quality from folder: {0}", quality);
                             parsedEpisode.Quality = quality;
@@ -85,9 +87,16 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport
                         decision = new ImportDecision(parsedEpisode, "Unable to parse file");
                     }
                 }
+                catch (EpisodeNotFoundException e)
+                {
+                    var parsedEpisode = new LocalEpisode();
+                    parsedEpisode.Path = file;
+
+                    decision = new ImportDecision(parsedEpisode, e.Message);
+                }
                 catch (Exception e)
                 {
-                    _logger.ErrorException("Couldn't import file." + file, e);
+                    _logger.ErrorException("Couldn't import file. " + file, e);
                 }
 
                 if (decision != null)
diff --git a/src/NzbDrone.Core/Metadata/ExistingMetadataService.cs b/src/NzbDrone.Core/Metadata/ExistingMetadataService.cs
index 00642823d..353f886a7 100644
--- a/src/NzbDrone.Core/Metadata/ExistingMetadataService.cs
+++ b/src/NzbDrone.Core/Metadata/ExistingMetadataService.cs
@@ -59,21 +59,30 @@ namespace NzbDrone.Core.Metadata
                     if (metadata.Type == MetadataType.EpisodeImage ||
                         metadata.Type == MetadataType.EpisodeMetadata)
                     {
-                        var localEpisode = _parsingService.GetLocalEpisode(possibleMetadataFile, message.Series, false);
-
-                        if (localEpisode == null)
+                        try
                         {
-                            _logger.Debug("Cannot find related episodes for: {0}", possibleMetadataFile);
-                            break;
-                        }
+                            var localEpisode = _parsingService.GetLocalEpisode(possibleMetadataFile, message.Series, false);
+
+                            if (localEpisode == null)
+                            {
+                                _logger.Debug("Unable to parse meta data file: {0}", possibleMetadataFile);
+                                break;
+                            }
+
+                            if (localEpisode.Episodes.DistinctBy(e => e.EpisodeFileId).Count() > 1)
+                            {
+                                _logger.Debug("Metadata file: {0} does not match existing files.", possibleMetadataFile);
+                                break;
+                            }
 
-                        if (localEpisode.Episodes.DistinctBy(e => e.EpisodeFileId).Count() > 1)
+                            metadata.EpisodeFileId = localEpisode.Episodes.First().EpisodeFileId;
+
+                        }
+                        catch (EpisodeNotFoundException e)
                         {
-                            _logger.Debug("Metadata file: {0} does not match existing files.", possibleMetadataFile);
-                            break;
+                            _logger.Debug("Cannot find related episodes for: {0}", possibleMetadataFile);
+                            continue;
                         }
-
-                        metadata.EpisodeFileId = localEpisode.Episodes.First().EpisodeFileId;
                     }
 
                     metadataFiles.Add(metadata);
diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj
index c764fd0ac..5bc3166ab 100644
--- a/src/NzbDrone.Core/NzbDrone.Core.csproj
+++ b/src/NzbDrone.Core/NzbDrone.Core.csproj
@@ -652,6 +652,7 @@
     <Compile Include="Organizer\NamingConfig.cs" />
     <Compile Include="Organizer\NamingConfigService.cs" />
     <Compile Include="Organizer\SampleResult.cs" />
+    <Compile Include="Parser\EpisodeNotFoundException.cs" />
     <Compile Include="Parser\InvalidDateException.cs" />
     <Compile Include="Parser\Language.cs" />
     <Compile Include="Parser\Model\LocalEpisode.cs" />
diff --git a/src/NzbDrone.Core/Parser/EpisodeNotFoundException.cs b/src/NzbDrone.Core/Parser/EpisodeNotFoundException.cs
new file mode 100644
index 000000000..8f04af3e1
--- /dev/null
+++ b/src/NzbDrone.Core/Parser/EpisodeNotFoundException.cs
@@ -0,0 +1,11 @@
+using NzbDrone.Common.Exceptions;
+
+namespace NzbDrone.Core.Parser
+{
+    public class EpisodeNotFoundException : NzbDroneException
+    {
+        public EpisodeNotFoundException(string message, params object[] args) : base(message, args)
+        {
+        }
+    }
+}
diff --git a/src/NzbDrone.Core/Parser/ParsingService.cs b/src/NzbDrone.Core/Parser/ParsingService.cs
index ebe85906d..d62e9e02e 100644
--- a/src/NzbDrone.Core/Parser/ParsingService.cs
+++ b/src/NzbDrone.Core/Parser/ParsingService.cs
@@ -62,10 +62,10 @@ namespace NzbDrone.Core.Parser
 
             var episodes = GetEpisodes(parsedEpisodeInfo, series, sceneSource);
 
-            if (!episodes.Any())
+            if (episodes.Empty())
             {
                 _logger.Debug("No matching episodes found for: {0}", parsedEpisodeInfo);
-                return null;
+                throw new EpisodeNotFoundException("Unable to find episodes for file: {0}", parsedEpisodeInfo);
             }
 
             return new LocalEpisode