diff --git a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs index 6b6c73dcb..51f81076e 100644 --- a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs @@ -118,7 +118,7 @@ namespace NzbDrone.Core.Test.Download .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny())) .Returns(new List { - new ImportResult(null) + new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" })) }); } diff --git a/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesCommandServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesCommandServiceFixture.cs new file mode 100644 index 000000000..6768e33cf --- /dev/null +++ b/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesCommandServiceFixture.cs @@ -0,0 +1,173 @@ +using System.Collections.Generic; +using System.IO; +using FizzWare.NBuilder; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using NzbDrone.Common.Disk; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Download; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.MediaFiles.Commands; +using NzbDrone.Core.MediaFiles.EpisodeImport; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.MediaFiles +{ + [TestFixture] + public class DownloadedEpisodesCommandServiceFixture : CoreTest + { + private string _droneFactory = "c:\\drop\\".AsOsAgnostic(); + private string _downloadFolder = "c:\\drop_other\\Show.S01E01\\".AsOsAgnostic(); + + private TrackedDownload _trackedDownload; + + [SetUp] + public void Setup() + { + Mocker.GetMock().Setup(c => c.FolderExists(It.IsAny())) + .Returns(true); + + Mocker.GetMock().SetupGet(c => c.DownloadedEpisodesFolder) + .Returns(_droneFactory); + + Mocker.GetMock() + .Setup(v => v.ProcessRootFolder(It.IsAny())) + .Returns(new List()); + + Mocker.GetMock() + .Setup(v => v.ProcessFolder(It.IsAny(), It.IsAny())) + .Returns(new List()); + + var downloadItem = Builder.CreateNew() + .With(v => v.DownloadClientId = "sab1") + .With(v => v.Status = DownloadItemStatus.Downloading) + .Build(); + + _trackedDownload = new TrackedDownload + { + DownloadItem = downloadItem, + State = TrackedDownloadState.Downloading + }; + } + + private void GivenValidQueueItem() + { + var downloadItem = Builder.CreateNew() + .With(v => v.DownloadClientId = "sab1") + .With(v => v.Status = DownloadItemStatus.Downloading) + .Build(); + + Mocker.GetMock() + .Setup(s => s.GetQueuedDownloads()) + .Returns(new [] { _trackedDownload }); + } + + private void GivenSuccessfulImport() + { + 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" })) + }); + } + + private void GivenRejectedImport() + { + 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" }, "Some Rejection"), "I was rejected") + }); + } + + private void GivenSkippedImport() + { + 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" }), "I was skipped") + }); + } + + [Test] + public void should_process_dronefactory_if_path_is_not_specified() + { + Subject.Execute(new DownloadedEpisodesScanCommand()); + + Mocker.GetMock().Verify(c => c.ProcessRootFolder(It.IsAny()), Times.Once()); + } + + [Test] + public void should_skip_import_if_dropfolder_doesnt_exist() + { + Mocker.GetMock().Setup(c => c.FolderExists(It.IsAny())).Returns(false); + + Subject.Execute(new DownloadedEpisodesScanCommand()); + + Mocker.GetMock().Verify(c => c.ProcessRootFolder(It.IsAny()), Times.Never()); + + ExceptionVerification.ExpectedWarns(1); + } + + [Test] + public void should_ignore_downloadclientid_if_path_is_not_specified() + { + Subject.Execute(new DownloadedEpisodesScanCommand() { DownloadClientId = "sab1" }); + + Mocker.GetMock().Verify(c => c.ProcessRootFolder(It.IsAny()), Times.Once()); + } + + [Test] + public void should_process_folder_if_downloadclientid_is_not_specified() + { + Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder }); + + Mocker.GetMock().Verify(c => c.ProcessFolder(It.IsAny(), null), Times.Once()); + } + + [Test] + public void should_process_folder_with_downloadclientitem_if_available() + { + GivenValidQueueItem(); + + Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder, DownloadClientId = "sab1" }); + + Mocker.GetMock().Verify(c => c.Import(It.Is(v => v != null), _downloadFolder), Times.Once()); + } + + [Test] + public void should_process_folder_without_downloadclientitem_if_not_available() + { + Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder, DownloadClientId = "sab1" }); + + Mocker.GetMock().Verify(c => c.ProcessFolder(It.IsAny(), null), Times.Once()); + + ExceptionVerification.ExpectedWarns(1); + } + + [Test] + public void should_not_mark_trackeddownload_as_completed_if_import_rejected() + { + GivenValidQueueItem(); + GivenRejectedImport(); + + Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder, DownloadClientId = "sab1" }); + + _trackedDownload.State.Should().Be(TrackedDownloadState.Downloading); + } + + [Test] + public void should_mark_trackeddownload_as_completed_if_import_skipped() + { + GivenValidQueueItem(); + GivenRejectedImport(); + + Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder, DownloadClientId = "sab1" }); + + _trackedDownload.State.Should().Be(TrackedDownloadState.Imported); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs index 5ef12266e..3b4cb2994 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs @@ -22,6 +22,7 @@ namespace NzbDrone.Core.Test.MediaFiles [TestFixture] public class DownloadedEpisodesImportServiceFixture : CoreTest { + private string _droneFactory = "c:\\drop\\".AsOsAgnostic(); private string[] _subFolders = new[] { "c:\\root\\foldername".AsOsAgnostic() }; private string[] _videoFiles = new[] { "c:\\root\\foldername\\30.rock.s01e01.ext".AsOsAgnostic() }; @@ -37,9 +38,6 @@ namespace NzbDrone.Core.Test.MediaFiles Mocker.GetMock().Setup(c => c.FolderExists(It.IsAny())) .Returns(true); - Mocker.GetMock().SetupGet(c => c.DownloadedEpisodesFolder) - .Returns("c:\\drop\\".AsOsAgnostic()); - Mocker.GetMock() .Setup(s => s.Import(It.IsAny>(), true, null)) .Returns(new List()); @@ -55,25 +53,11 @@ namespace NzbDrone.Core.Test.MediaFiles [Test] public void should_search_for_series_using_folder_name() { - Subject.Execute(new DownloadedEpisodesScanCommand()); - + Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory)); Mocker.GetMock().Verify(c => c.GetSeries("foldername"), Times.Once()); } - [Test] - public void should_skip_import_if_dropfolder_doesnt_exist() - { - Mocker.GetMock().Setup(c => c.FolderExists(It.IsAny())).Returns(false); - - Subject.Execute(new DownloadedEpisodesScanCommand()); - - Mocker.GetMock().Verify(c => c.GetDirectories(It.IsAny()), Times.Never()); - Mocker.GetMock().Verify(c => c.GetFiles(It.IsAny(), It.IsAny()), Times.Never()); - - ExceptionVerification.ExpectedWarns(1); - } - [Test] public void should_skip_if_file_is_in_use_by_another_process() { @@ -82,7 +66,7 @@ namespace NzbDrone.Core.Test.MediaFiles Mocker.GetMock().Setup(c => c.IsFileLocked(It.IsAny())) .Returns(true); - Subject.Execute(new DownloadedEpisodesScanCommand()); + Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory)); VerifyNoImport(); } @@ -92,7 +76,7 @@ namespace NzbDrone.Core.Test.MediaFiles { Mocker.GetMock().Setup(c => c.GetSeries("foldername")).Returns((Series)null); - Subject.Execute(new DownloadedEpisodesScanCommand()); + Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory)); Mocker.GetMock() .Verify(c => c.GetImportDecisions(It.IsAny>(), It.IsAny(), It.IsAny(), It.IsAny()), @@ -112,7 +96,7 @@ namespace NzbDrone.Core.Test.MediaFiles .Setup(c => c.GetVideoFiles(It.IsAny(), It.IsAny())) .Returns(new string[0]); - Subject.Execute(new DownloadedEpisodesScanCommand()); + Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory)); Mocker.GetMock() .Verify(v => v.GetSeries(It.IsAny()), Times.Never()); @@ -127,7 +111,7 @@ namespace NzbDrone.Core.Test.MediaFiles .Setup(s => s.Import(It.IsAny>(), false, null)) .Returns(new List()); - Subject.Execute(new DownloadedEpisodesScanCommand()); + Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory)); Mocker.GetMock() .Verify(v => v.GetFolderSize(It.IsAny()), Times.Never()); @@ -151,7 +135,7 @@ namespace NzbDrone.Core.Test.MediaFiles .Setup(s => s.Import(It.IsAny>(), true, null)) .Returns(imported.Select(i => new ImportResult(i)).ToList()); - Subject.Execute(new DownloadedEpisodesScanCommand()); + Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory)); Mocker.GetMock() .Verify(v => v.DeleteFolder(It.IsAny(), true), Times.Never()); @@ -185,7 +169,7 @@ namespace NzbDrone.Core.Test.MediaFiles It.IsAny())) .Returns(true); - Subject.Execute(new DownloadedEpisodesScanCommand()); + Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory)); Mocker.GetMock() .Verify(v => v.DeleteFolder(It.IsAny(), true), Times.Once()); @@ -202,7 +186,7 @@ namespace NzbDrone.Core.Test.MediaFiles .Setup(c => c.GetDirectories(It.IsAny())) .Returns(folders); - Subject.Execute(new DownloadedEpisodesScanCommand()); + Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory)); Mocker.GetMock() .Verify(v => v.GetSeries(folderName), Times.Once()); diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj index b254ec1f5..65f3eba77 100644 --- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj +++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj @@ -197,6 +197,7 @@ + diff --git a/src/NzbDrone.Core/Download/CompletedDownloadService.cs b/src/NzbDrone.Core/Download/CompletedDownloadService.cs index 3d49b0281..54cd15cd6 100644 --- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs +++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs @@ -15,7 +15,7 @@ namespace NzbDrone.Core.Download public interface ICompletedDownloadService { void CheckForCompletedItem(IDownloadClient downloadClient, TrackedDownload trackedDownload, List grabbedHistory, List importedHistory); - List Import(TrackedDownload trackedDownload); + List Import(TrackedDownload trackedDownload, String overrideOutputPath = null); } public class CompletedDownloadService : ICompletedDownloadService @@ -110,21 +110,22 @@ namespace NzbDrone.Core.Download } } - public List Import(TrackedDownload trackedDownload) + public List Import(TrackedDownload trackedDownload, String overrideOutputPath = null) { var importResults = new List(); + var outputPath = overrideOutputPath ?? trackedDownload.DownloadItem.OutputPath; - if (_diskProvider.FolderExists(trackedDownload.DownloadItem.OutputPath)) + if (_diskProvider.FolderExists(outputPath)) { - importResults = _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem); + importResults = _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(outputPath), trackedDownload.DownloadItem); - ProcessImportResults(trackedDownload, importResults); + ProcessImportResults(trackedDownload, outputPath, importResults); } - else if (_diskProvider.FileExists(trackedDownload.DownloadItem.OutputPath)) + else if (_diskProvider.FileExists(outputPath)) { - importResults = _downloadedEpisodesImportService.ProcessFile(new FileInfo(trackedDownload.DownloadItem.OutputPath), trackedDownload.DownloadItem); + importResults = _downloadedEpisodesImportService.ProcessFile(new FileInfo(outputPath), trackedDownload.DownloadItem); - ProcessImportResults(trackedDownload, importResults); + ProcessImportResults(trackedDownload, outputPath, importResults); } return importResults; @@ -147,11 +148,11 @@ namespace NzbDrone.Core.Download } } - private void ProcessImportResults(TrackedDownload trackedDownload, List importResults) + private void ProcessImportResults(TrackedDownload trackedDownload, String outputPath, List importResults) { if (importResults.Empty()) { - UpdateStatusMessage(trackedDownload, LogLevel.Error, "No files found are eligible for import in {0}", trackedDownload.DownloadItem.OutputPath); + UpdateStatusMessage(trackedDownload, LogLevel.Error, "No files found are eligible for import in {0}", outputPath); } else if (importResults.Any(v => v.Result == ImportResultType.Imported) && importResults.All(v => v.Result == ImportResultType.Imported || v.Result == ImportResultType.Rejected)) { diff --git a/src/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs index 40066c875..c5424d608 100644 --- a/src/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs +++ b/src/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs @@ -15,5 +15,6 @@ namespace NzbDrone.Core.MediaFiles.Commands public Boolean SendUpdates { get; set; } public String Path { get; set; } + public String DownloadClientId { get; set; } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesCommandService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesCommandService.cs new file mode 100644 index 000000000..c133b3405 --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesCommandService.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using NLog; +using NzbDrone.Common; +using NzbDrone.Common.Disk; +using NzbDrone.Core.Configuration; +using NzbDrone.Core.Download; +using NzbDrone.Core.MediaFiles.Commands; +using NzbDrone.Core.MediaFiles.EpisodeImport; +using NzbDrone.Core.Messaging.Commands; + +namespace NzbDrone.Core.MediaFiles +{ + public class DownloadedEpisodesCommandService : IExecute + { + private readonly IDownloadedEpisodesImportService _downloadedEpisodesImportService; + private readonly IDownloadTrackingService _downloadTrackingService; + private readonly ICompletedDownloadService _completedDownloadService; + private readonly IDiskProvider _diskProvider; + private readonly IConfigService _configService; + private readonly Logger _logger; + + public DownloadedEpisodesCommandService(IDownloadedEpisodesImportService downloadedEpisodesImportService, + IDownloadTrackingService downloadTrackingService, + ICompletedDownloadService completedDownloadService, + IDiskProvider diskProvider, + IConfigService configService, + Logger logger) + { + _downloadedEpisodesImportService = downloadedEpisodesImportService; + _downloadTrackingService = downloadTrackingService; + _completedDownloadService = completedDownloadService; + _diskProvider = diskProvider; + _configService = configService; + _logger = logger; + } + + private List ProcessDroneFactoryFolder() + { + var downloadedEpisodesFolder = _configService.DownloadedEpisodesFolder; + + if (String.IsNullOrEmpty(downloadedEpisodesFolder)) + { + _logger.Trace("Drone Factory folder is not configured"); + return new List(); + } + + if (!_diskProvider.FolderExists(downloadedEpisodesFolder)) + { + _logger.Warn("Drone Factory folder [{0}] doesn't exist.", downloadedEpisodesFolder); + return new List(); + } + + return _downloadedEpisodesImportService.ProcessRootFolder(new DirectoryInfo(downloadedEpisodesFolder)); + } + + private List ProcessFolder(DownloadedEpisodesScanCommand message) + { + if (!_diskProvider.FolderExists(message.Path)) + { + _logger.Warn("Folder specified for import scan [{0}] doesn't exist.", message.Path); + return new List(); + } + + if (message.DownloadClientId.IsNotNullOrWhiteSpace()) + { + var trackedDownload = _downloadTrackingService.GetQueuedDownloads().Where(v => v.DownloadItem.DownloadClientId == message.DownloadClientId).FirstOrDefault(); + + if (trackedDownload == null) + { + _logger.Warn("External directory scan request for unknown download {0}, attempting normal import. [{1}]", message.DownloadClientId, message.Path); + + return _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(message.Path)); + } + else + { + return _completedDownloadService.Import(trackedDownload, message.Path); + } + } + else + { + return _downloadedEpisodesImportService.ProcessFolder(new DirectoryInfo(message.Path)); + } + } + + public void Execute(DownloadedEpisodesScanCommand message) + { + List importResults; + + if (message.Path.IsNotNullOrWhiteSpace()) + { + importResults = ProcessFolder(message); + } + else + { + importResults = ProcessDroneFactoryFolder(); + } + + if (importResults == null || !importResults.Any(v => v.Result == ImportResultType.Imported)) + { + // Atm we don't report it as a command failure, coz that would cause the download to be failed. + // Changing the message won't do a thing either, coz it will get set to 'Completed' a msec later. + //message.SetMessage("Failed to import"); + } + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs index 3bf824315..1d12c02ca 100644 --- a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs @@ -13,16 +13,18 @@ using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Parser; using NzbDrone.Core.Tv; using NzbDrone.Core.Download; +using NzbDrone.Core.Parser.Model; namespace NzbDrone.Core.MediaFiles { public interface IDownloadedEpisodesImportService { - List ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem); - List ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem); + List ProcessRootFolder(DirectoryInfo directoryInfo); + List ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem = null); + List ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem = null); } - public class DownloadedEpisodesImportService : IDownloadedEpisodesImportService, IExecute + public class DownloadedEpisodesImportService : IDownloadedEpisodesImportService { private readonly IDiskProvider _diskProvider; private readonly IDiskScanService _diskScanService; @@ -55,8 +57,33 @@ namespace NzbDrone.Core.MediaFiles _logger = logger; } - public List ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem) + public List ProcessRootFolder(DirectoryInfo directoryInfo) { + var results = new List(); + + foreach (var subFolder in _diskProvider.GetDirectories(directoryInfo.FullName)) + { + var folderResults = ProcessFolder(new DirectoryInfo(subFolder)); + results.AddRange(folderResults); + } + + foreach (var videoFile in _diskScanService.GetVideoFiles(directoryInfo.FullName, false)) + { + var fileResults = ProcessFile(new FileInfo(videoFile)); + results.AddRange(fileResults); + } + + return results; + } + + public List ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem = null) + { + if (_seriesService.SeriesPathExists(directoryInfo.FullName)) + { + _logger.Warn("Unable to process folder that contains sorted TV Shows"); + return new List(); + } + var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name); var series = _parsingService.GetSeries(cleanedUpName); var quality = QualityParser.ParseQuality(cleanedUpName); @@ -65,82 +92,10 @@ namespace NzbDrone.Core.MediaFiles if (series == null) { _logger.Debug("Unknown Series {0}", cleanedUpName); - return new List(); - } - - var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName); - var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, true, quality); - - var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem); - - if (!downloadClientItem.IsReadOnly && importResults.Any() && ShouldDeleteFolder(directoryInfo)) - { - _logger.Debug("Deleting folder after importing valid files"); - _diskProvider.DeleteFolder(directoryInfo.FullName, true); - } - - return importResults; - } - - 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(); - } - - var decisions = _importDecisionMaker.GetImportDecisions(new List() { fileInfo.FullName }, series, true); - return _importApprovedEpisodes.Import(decisions, true, downloadClientItem); - } - - private void ProcessDownloadedEpisodesFolder() - { - var downloadedEpisodesFolder = _configService.DownloadedEpisodesFolder; - - if (String.IsNullOrEmpty(downloadedEpisodesFolder)) - { - _logger.Trace("Drone Factory folder is not configured"); - return; - } - - if (!_diskProvider.FolderExists(downloadedEpisodesFolder)) - { - _logger.Warn("Drone Factory folder [{0}] doesn't exist.", downloadedEpisodesFolder); - return; - } - - foreach (var subFolder in _diskProvider.GetDirectories(downloadedEpisodesFolder)) - { - ProcessFolder(subFolder); - } - - foreach (var videoFile in _diskScanService.GetVideoFiles(downloadedEpisodesFolder, false)) - { - try - { - ProcessVideoFile(videoFile); - } - catch (Exception ex) - { - _logger.ErrorException("An error has occurred while importing video file" + videoFile, ex); - } - } - } - - private List ProcessFolder(DirectoryInfo directoryInfo) - { - var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name); - var series = _parsingService.GetSeries(cleanedUpName); - var quality = QualityParser.ParseQuality(cleanedUpName); - _logger.Debug("{0} folder quality: {1}", cleanedUpName, quality); - - if (series == null) - { - _logger.Debug("Unknown Series {0}", cleanedUpName); - return new List(); + return new List + { + new ImportResult(new ImportDecision(null, "Unknown Series"), "Unknown Series") + }; } var videoFiles = _diskScanService.GetVideoFiles(directoryInfo.FullName); @@ -150,59 +105,44 @@ 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 + { + new ImportResult(new ImportDecision(new LocalEpisode { Path = videoFile }, "Locked file, try again later"), "Locked file, try again later") + }; } } var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, true, quality); - return _importApprovedEpisodes.Import(decisions, true); + + var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem); + + if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) && importResults.Any() && ShouldDeleteFolder(directoryInfo)) + { + _logger.Debug("Deleting folder after importing valid files"); + _diskProvider.DeleteFolder(directoryInfo.FullName, true); + } + + return importResults; } - private void ProcessVideoFile(string videoFile) + public List ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem = null) { - var series = _parsingService.GetSeries(Path.GetFileNameWithoutExtension(videoFile)); + var series = _parsingService.GetSeries(Path.GetFileNameWithoutExtension(fileInfo.Name)); if (series == null) { - _logger.Debug("Unknown Series for file: {0}", videoFile); - return; + _logger.Debug("Unknown Series for file: {0}", fileInfo.Name); + return new List() { new ImportResult(null, String.Format("Unknown Series for file: {0}", fileInfo.Name)) }; } - if (_diskProvider.IsFileLocked(videoFile)) + if (_diskProvider.IsFileLocked(fileInfo.FullName)) { - _logger.Debug("[{0}] is currently locked by another process, skipping", videoFile); - return; + _logger.Debug("[{0}] is currently locked by another process, skipping", fileInfo.FullName); + return new List(); } - var decisions = _importDecisionMaker.GetImportDecisions(new [] { videoFile }.ToList(), series, true, null); - _importApprovedEpisodes.Import(decisions, true); - } - - private void ProcessFolder(string path) - { - Ensure.That(path, () => path).IsValidPath(); - - try - { - if (_seriesService.SeriesPathExists(path)) - { - _logger.Warn("Unable to process folder that contains sorted TV Shows"); - return; - } - - var directoryFolderInfo = new DirectoryInfo(path); - var importedFiles = ProcessFolder(directoryFolderInfo); - - if (importedFiles.Any() && ShouldDeleteFolder(directoryFolderInfo)) - { - _logger.Debug("Deleting folder after importing valid files"); - _diskProvider.DeleteFolder(path, true); - } - } - catch (Exception e) - { - _logger.ErrorException("An error has occurred while importing folder: " + path, e); - } + var decisions = _importDecisionMaker.GetImportDecisions(new List() { fileInfo.FullName }, series, true); + return _importApprovedEpisodes.Import(decisions, true, downloadClientItem); } private string GetCleanedUpFolderName(string folder) @@ -242,18 +182,5 @@ namespace NzbDrone.Core.MediaFiles return true; } - - public void Execute(DownloadedEpisodesScanCommand message) - { - if (message.Path.IsNullOrWhiteSpace()) - { - ProcessDownloadedEpisodesFolder(); - } - - else - { - ProcessFolder(message.Path); - } - } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 74ad39c70..64f055d7b 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -481,6 +481,7 @@ Code +