From 9fbe06ad683a4556afa55128b8b8a888a6223198 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Tue, 18 Oct 2016 21:17:50 +0200 Subject: [PATCH] New: Added support to override Copy vs Move import logic for DownloadedEpisodesScan API and Manual Import UI. --- .../CompletedDownloadServiceFixture.cs | 38 +++++++++---------- ...DownloadedEpisodesCommandServiceFixture.cs | 24 ++++++++---- .../DownloadedEpisodesImportServiceFixture.cs | 20 +++++----- .../ImportApprovedEpisodesFixture.cs | 24 ++++++++++-- .../Download/CompletedDownloadService.cs | 2 +- .../Commands/DownloadedEpisodesScanCommand.cs | 4 +- .../DownloadedEpisodesCommandService.cs | 6 +-- .../DownloadedEpisodesImportService.cs | 33 ++++++++-------- .../EpisodeImport/ImportApprovedEpisodes.cs | 23 ++++++++--- .../MediaFiles/EpisodeImport/ImportMode.cs | 11 ++++++ .../Manual/ManualImportCommand.cs | 2 + .../Manual/ManualImportService.cs | 7 ++-- src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + src/UI/ManualImport/ManualImportLayout.js | 11 +++++- .../ManualImportLayoutTemplate.hbs | 6 +++ 15 files changed, 140 insertions(+), 72 deletions(-) create mode 100644 src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportMode.cs diff --git a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs index ea7b3dc14..3a1d29ba3 100644 --- a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs +++ b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceFixture.cs @@ -67,7 +67,7 @@ namespace NzbDrone.Core.Test.Download { Series = new Series(), Episodes = new List { new Episode { Id = 1 } } - }; + }; } @@ -77,11 +77,11 @@ namespace NzbDrone.Core.Test.Download .Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId)) .Returns((History.History)null); } - + private void GivenSuccessfulImport() { Mocker.GetMock() - .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult(new ImportDecision(new LocalEpisode() { Path = @"C:\TestPath\Droned.S01E01.mkv" })) @@ -145,7 +145,7 @@ namespace NzbDrone.Core.Test.Download GivenNoGrabbedHistory(); GivenSeriesMatch(); GivenSuccessfulImport(); - + Subject.Process(_trackedDownload); AssertCompletedDownload(); @@ -179,13 +179,13 @@ namespace NzbDrone.Core.Test.Download public void should_mark_as_imported_if_all_episodes_were_imported() { Mocker.GetMock() - .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult( new ImportDecision( new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})), - + new ImportResult( new ImportDecision( new LocalEpisode {Path = @"C:\TestPath\Droned.S01E02.mkv"})) @@ -200,13 +200,13 @@ namespace NzbDrone.Core.Test.Download public void should_not_mark_as_imported_if_all_files_were_rejected() { Mocker.GetMock() - .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult( new ImportDecision( new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}, new Rejection("Rejected!")), "Test Failure"), - + new ImportResult( new ImportDecision( new LocalEpisode {Path = @"C:\TestPath\Droned.S01E02.mkv"},new Rejection("Rejected!")), "Test Failure") @@ -224,13 +224,13 @@ namespace NzbDrone.Core.Test.Download public void should_not_mark_as_imported_if_no_episodes_were_parsed() { Mocker.GetMock() - .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult( new ImportDecision( new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}, new Rejection("Rejected!")), "Test Failure"), - + new ImportResult( new ImportDecision( new LocalEpisode {Path = @"C:\TestPath\Droned.S01E02.mkv"},new Rejection("Rejected!")), "Test Failure") @@ -247,7 +247,7 @@ namespace NzbDrone.Core.Test.Download public void should_not_mark_as_imported_if_all_files_were_skipped() { Mocker.GetMock() - .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"}),"Test Failure"), @@ -271,7 +271,7 @@ namespace NzbDrone.Core.Test.Download }; Mocker.GetMock() - .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})), @@ -294,7 +294,7 @@ namespace NzbDrone.Core.Test.Download }; Mocker.GetMock() - .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})), @@ -314,7 +314,7 @@ namespace NzbDrone.Core.Test.Download GivenABadlyNamedDownload(); Mocker.GetMock() - .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})) @@ -323,7 +323,7 @@ namespace NzbDrone.Core.Test.Download Mocker.GetMock() .Setup(v => v.GetSeries(It.IsAny())) .Returns(BuildRemoteEpisode().Series); - + Subject.Process(_trackedDownload); AssertCompletedDownload(); @@ -335,7 +335,7 @@ namespace NzbDrone.Core.Test.Download GivenABadlyNamedDownload(); Mocker.GetMock() - .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})) @@ -370,7 +370,7 @@ namespace NzbDrone.Core.Test.Download }; Mocker.GetMock() - .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List { new ImportResult(new ImportDecision(new LocalEpisode {Path = @"C:\TestPath\Droned.S01E01.mkv"})) @@ -408,7 +408,7 @@ namespace NzbDrone.Core.Test.Download private void AssertNoAttemptedImport() { Mocker.GetMock() - .Verify(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); + .Verify(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); AssertNoCompletedDownload(); } @@ -424,7 +424,7 @@ namespace NzbDrone.Core.Test.Download private void AssertCompletedDownload() { Mocker.GetMock() - .Verify(v => v.ProcessPath(_trackedDownload.DownloadItem.OutputPath.FullPath, _trackedDownload.RemoteEpisode.Series, _trackedDownload.DownloadItem), Times.Once()); + .Verify(v => v.ProcessPath(_trackedDownload.DownloadItem.OutputPath.FullPath, ImportMode.Auto, _trackedDownload.RemoteEpisode.Series, _trackedDownload.DownloadItem), Times.Once()); Mocker.GetMock() .Verify(v => v.PublishEvent(It.IsAny()), Times.Once()); diff --git a/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesCommandServiceFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesCommandServiceFixture.cs index 9cc71321c..2ea63e183 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesCommandServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesCommandServiceFixture.cs @@ -37,7 +37,7 @@ namespace NzbDrone.Core.Test.MediaFiles .Returns(new List()); Mocker.GetMock() - .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(v => v.ProcessPath(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new List()); var downloadItem = Builder.CreateNew() @@ -113,7 +113,7 @@ namespace NzbDrone.Core.Test.MediaFiles Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder }); - Mocker.GetMock().Verify(c => c.ProcessPath(It.IsAny(), null, null), Times.Once()); + Mocker.GetMock().Verify(c => c.ProcessPath(It.IsAny(), ImportMode.Auto, null, null), Times.Once()); } [Test] @@ -123,7 +123,7 @@ namespace NzbDrone.Core.Test.MediaFiles Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFile }); - Mocker.GetMock().Verify(c => c.ProcessPath(It.IsAny(), null, null), Times.Once()); + Mocker.GetMock().Verify(c => c.ProcessPath(It.IsAny(), ImportMode.Auto, null, null), Times.Once()); } [Test] @@ -134,7 +134,7 @@ namespace NzbDrone.Core.Test.MediaFiles Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder, DownloadClientId = "sab1" }); - Mocker.GetMock().Verify(c => c.ProcessPath(_downloadFolder, _trackedDownload.RemoteEpisode.Series, _trackedDownload.DownloadItem), Times.Once()); + Mocker.GetMock().Verify(c => c.ProcessPath(_downloadFolder, ImportMode.Auto, _trackedDownload.RemoteEpisode.Series, _trackedDownload.DownloadItem), Times.Once()); } [Test] @@ -144,7 +144,7 @@ namespace NzbDrone.Core.Test.MediaFiles Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder, DownloadClientId = "sab1" }); - Mocker.GetMock().Verify(c => c.ProcessPath(_downloadFolder, null, null), Times.Once()); + Mocker.GetMock().Verify(c => c.ProcessPath(_downloadFolder, ImportMode.Auto, null, null), Times.Once()); ExceptionVerification.ExpectedWarns(1); } @@ -154,9 +154,19 @@ namespace NzbDrone.Core.Test.MediaFiles { Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFolder }); - Mocker.GetMock().Verify(c => c.ProcessPath(It.IsAny(), null, null), Times.Never()); + Mocker.GetMock().Verify(c => c.ProcessPath(It.IsAny(), ImportMode.Auto, null, null), Times.Never()); ExceptionVerification.ExpectedWarns(1); } + + [Test] + public void should_override_import_mode() + { + GivenExistingFile(_downloadFile); + + Subject.Execute(new DownloadedEpisodesScanCommand() { Path = _downloadFile, ImportMode = ImportMode.Copy }); + + Mocker.GetMock().Verify(c => c.ProcessPath(It.IsAny(), ImportMode.Copy, null, null), Times.Once()); + } } -} \ 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 da7b3310e..47dcd69c0 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/DownloadedEpisodesImportServiceFixture.cs @@ -38,7 +38,7 @@ namespace NzbDrone.Core.Test.MediaFiles .Returns(true); Mocker.GetMock() - .Setup(s => s.Import(It.IsAny>(), true, null)) + .Setup(s => s.Import(It.IsAny>(), true, null, ImportMode.Auto)) .Returns(new List()); } @@ -66,7 +66,7 @@ namespace NzbDrone.Core.Test.MediaFiles .Returns(true); Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory)); - + VerifyNoImport(); } @@ -109,7 +109,7 @@ namespace NzbDrone.Core.Test.MediaFiles public void should_not_delete_folder_if_no_files_were_imported() { Mocker.GetMock() - .Setup(s => s.Import(It.IsAny>(), false, null)) + .Setup(s => s.Import(It.IsAny>(), false, null, ImportMode.Auto)) .Returns(new List()); Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory)); @@ -133,7 +133,7 @@ namespace NzbDrone.Core.Test.MediaFiles .Returns(imported); Mocker.GetMock() - .Setup(s => s.Import(It.IsAny>(), true, null)) + .Setup(s => s.Import(It.IsAny>(), true, null, ImportMode.Auto)) .Returns(imported.Select(i => new ImportResult(i)).ToList()); Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory)); @@ -159,7 +159,7 @@ namespace NzbDrone.Core.Test.MediaFiles .Returns(imported); Mocker.GetMock() - .Setup(s => s.Import(It.IsAny>(), true, null)) + .Setup(s => s.Import(It.IsAny>(), true, null, ImportMode.Auto)) .Returns(imported.Select(i => new ImportResult(i)).ToList()); Mocker.GetMock() @@ -231,7 +231,7 @@ namespace NzbDrone.Core.Test.MediaFiles .Returns(imported); Mocker.GetMock() - .Setup(s => s.Import(It.IsAny>(), true, null)) + .Setup(s => s.Import(It.IsAny>(), true, null, ImportMode.Auto)) .Returns(imported.Select(i => new ImportResult(i)).ToList()); Mocker.GetMock() @@ -342,7 +342,7 @@ namespace NzbDrone.Core.Test.MediaFiles .Returns(imported); Mocker.GetMock() - .Setup(s => s.Import(It.IsAny>(), true, null)) + .Setup(s => s.Import(It.IsAny>(), true, null, ImportMode.Auto)) .Returns(new List()); Mocker.GetMock() @@ -365,14 +365,14 @@ namespace NzbDrone.Core.Test.MediaFiles private void VerifyNoImport() { - Mocker.GetMock().Verify(c => c.Import(It.IsAny>(), true, null), + Mocker.GetMock().Verify(c => c.Import(It.IsAny>(), true, null, ImportMode.Auto), Times.Never()); } private void VerifyImport() { - Mocker.GetMock().Verify(c => c.Import(It.IsAny>(), true, null), + Mocker.GetMock().Verify(c => c.Import(It.IsAny>(), true, null, ImportMode.Auto), Times.Once()); } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs index 577de5ea0..3516489ea 100644 --- a/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs +++ b/src/NzbDrone.Core.Test/MediaFiles/ImportApprovedEpisodesFixture.cs @@ -67,7 +67,7 @@ namespace NzbDrone.Core.Test.MediaFiles } Mocker.GetMock() - .Setup(s => s.UpgradeEpisodeFile(It.IsAny(), It.IsAny(), false)) + .Setup(s => s.UpgradeEpisodeFile(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new EpisodeFileMoveResult()); _downloadClientItem = Builder.CreateNew().Build(); @@ -95,7 +95,7 @@ namespace NzbDrone.Core.Test.MediaFiles all.AddRange(_approvedDecisions); var result = Subject.Import(all, false); - + result.Should().HaveCount(all.Count); result.Where(i => i.Result == ImportResultType.Imported).Should().HaveCount(_approvedDecisions.Count); } @@ -223,5 +223,23 @@ namespace NzbDrone.Core.Test.MediaFiles results.Should().ContainSingle(d => d.Result == ImportResultType.Imported); results.Should().ContainSingle(d => d.Result == ImportResultType.Imported && d.ImportDecision.LocalEpisode.Size == fileDecision.LocalEpisode.Size); } + + [Test] + public void should_copy_readonly_downloads() + { + Subject.Import(new List { _approvedDecisions.First() }, true, new DownloadClientItem { Title = "30.Rock.S01E01", IsReadOnly = true }); + + Mocker.GetMock() + .Verify(v => v.UpgradeEpisodeFile(It.IsAny(), _approvedDecisions.First().LocalEpisode, true), Times.Once()); + } + + [Test] + public void should_use_override_importmode() + { + Subject.Import(new List { _approvedDecisions.First() }, true, new DownloadClientItem { Title = "30.Rock.S01E01", IsReadOnly = true }, ImportMode.Move); + + Mocker.GetMock() + .Verify(v => v.UpgradeEpisodeFile(It.IsAny(), _approvedDecisions.First().LocalEpisode, false), Times.Once()); + } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/Download/CompletedDownloadService.cs b/src/NzbDrone.Core/Download/CompletedDownloadService.cs index aafada077..caf75f4e4 100644 --- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs +++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs @@ -112,7 +112,7 @@ namespace NzbDrone.Core.Download private void Import(TrackedDownload trackedDownload) { var outputPath = trackedDownload.DownloadItem.OutputPath.FullPath; - var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem); + var importResults = _downloadedEpisodesImportService.ProcessPath(outputPath, ImportMode.Auto, trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem); if (importResults.Empty()) { diff --git a/src/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs b/src/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs index 7d0bb0ec6..a36e6a093 100644 --- a/src/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs +++ b/src/NzbDrone.Core/MediaFiles/Commands/DownloadedEpisodesScanCommand.cs @@ -1,4 +1,5 @@ using System; +using NzbDrone.Core.MediaFiles.EpisodeImport; using NzbDrone.Core.Messaging.Commands; namespace NzbDrone.Core.MediaFiles.Commands @@ -18,5 +19,6 @@ namespace NzbDrone.Core.MediaFiles.Commands // Properties used by third-party apps, do not modify. public string Path { get; set; } public string DownloadClientId { get; set; } + public ImportMode ImportMode { get; set; } } -} \ No newline at end of file +} diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesCommandService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesCommandService.cs index 84f2cf898..f9a2c3d3e 100644 --- a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesCommandService.cs +++ b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesCommandService.cs @@ -70,17 +70,17 @@ namespace NzbDrone.Core.MediaFiles { _logger.Debug("External directory scan request for known download {0}. [{1}]", message.DownloadClientId, message.Path); - return _downloadedEpisodesImportService.ProcessPath(message.Path, trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem); + return _downloadedEpisodesImportService.ProcessPath(message.Path, message.ImportMode, trackedDownload.RemoteEpisode.Series, trackedDownload.DownloadItem); } else { _logger.Warn("External directory scan request for unknown download {0}, attempting normal import. [{1}]", message.DownloadClientId, message.Path); - return _downloadedEpisodesImportService.ProcessPath(message.Path); + return _downloadedEpisodesImportService.ProcessPath(message.Path, message.ImportMode); } } - return _downloadedEpisodesImportService.ProcessPath(message.Path); + return _downloadedEpisodesImportService.ProcessPath(message.Path, message.ImportMode); } public void Execute(DownloadedEpisodesScanCommand message) diff --git a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs index 0478b7888..7cb8f9bee 100644 --- a/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/DownloadedEpisodesImportService.cs @@ -16,7 +16,7 @@ namespace NzbDrone.Core.MediaFiles public interface IDownloadedEpisodesImportService { List ProcessRootFolder(DirectoryInfo directoryInfo); - List ProcessPath(string path, Series series = null, DownloadClientItem downloadClientItem = null); + List ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Series series = null, DownloadClientItem downloadClientItem = null); bool ShouldDeleteFolder(DirectoryInfo directoryInfo, Series series); } @@ -56,20 +56,20 @@ namespace NzbDrone.Core.MediaFiles foreach (var subFolder in _diskProvider.GetDirectories(directoryInfo.FullName)) { - var folderResults = ProcessFolder(new DirectoryInfo(subFolder)); + var folderResults = ProcessFolder(new DirectoryInfo(subFolder), ImportMode.Auto, null); results.AddRange(folderResults); } foreach (var videoFile in _diskScanService.GetVideoFiles(directoryInfo.FullName, false)) { - var fileResults = ProcessFile(new FileInfo(videoFile)); + var fileResults = ProcessFile(new FileInfo(videoFile), ImportMode.Auto, null); results.AddRange(fileResults); } return results; } - public List ProcessPath(string path, Series series = null, DownloadClientItem downloadClientItem = null) + public List ProcessPath(string path, ImportMode importMode = ImportMode.Auto, Series series = null, DownloadClientItem downloadClientItem = null) { if (_diskProvider.FolderExists(path)) { @@ -77,10 +77,10 @@ namespace NzbDrone.Core.MediaFiles if (series == null) { - return ProcessFolder(directoryInfo, downloadClientItem); + return ProcessFolder(directoryInfo, importMode, downloadClientItem); } - return ProcessFolder(directoryInfo, series, downloadClientItem); + return ProcessFolder(directoryInfo, importMode, series, downloadClientItem); } if (_diskProvider.FileExists(path)) @@ -89,10 +89,10 @@ namespace NzbDrone.Core.MediaFiles if (series == null) { - return ProcessFile(fileInfo, downloadClientItem); + return ProcessFile(fileInfo, importMode, downloadClientItem); } - return ProcessFile(fileInfo, series, downloadClientItem); + return ProcessFile(fileInfo, importMode, series, downloadClientItem); } _logger.Error("Import failed, path does not exist or is not accessible by Sonarr: {0}", path); @@ -133,7 +133,7 @@ namespace NzbDrone.Core.MediaFiles return true; } - private List ProcessFolder(DirectoryInfo directoryInfo, DownloadClientItem downloadClientItem = null) + private List ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, DownloadClientItem downloadClientItem) { var cleanedUpName = GetCleanedUpFolderName(directoryInfo.Name); var series = _parsingService.GetSeries(cleanedUpName); @@ -148,11 +148,10 @@ namespace NzbDrone.Core.MediaFiles }; } - return ProcessFolder(directoryInfo, series, downloadClientItem); + return ProcessFolder(directoryInfo, importMode, series, downloadClientItem); } - private List ProcessFolder(DirectoryInfo directoryInfo, Series series, - DownloadClientItem downloadClientItem = null) + private List ProcessFolder(DirectoryInfo directoryInfo, ImportMode importMode, Series series, DownloadClientItem downloadClientItem) { if (_seriesService.SeriesPathExists(directoryInfo.FullName)) { @@ -185,7 +184,7 @@ namespace NzbDrone.Core.MediaFiles } var decisions = _importDecisionMaker.GetImportDecisions(videoFiles.ToList(), series, folderInfo, true); - var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem); + var importResults = _importApprovedEpisodes.Import(decisions, true, downloadClientItem, importMode); if ((downloadClientItem == null || !downloadClientItem.IsReadOnly) && importResults.Any(i => i.Result == ImportResultType.Imported) && @@ -198,7 +197,7 @@ namespace NzbDrone.Core.MediaFiles return importResults; } - private List ProcessFile(FileInfo fileInfo, DownloadClientItem downloadClientItem = null) + private List ProcessFile(FileInfo fileInfo, ImportMode importMode, DownloadClientItem downloadClientItem) { var series = _parsingService.GetSeries(Path.GetFileNameWithoutExtension(fileInfo.Name)); @@ -212,10 +211,10 @@ namespace NzbDrone.Core.MediaFiles }; } - return ProcessFile(fileInfo, series, downloadClientItem); + return ProcessFile(fileInfo, importMode, series, downloadClientItem); } - private List ProcessFile(FileInfo fileInfo, Series series, DownloadClientItem downloadClientItem = null) + private List ProcessFile(FileInfo fileInfo, ImportMode importMode, Series series, DownloadClientItem downloadClientItem) { if (Path.GetFileNameWithoutExtension(fileInfo.Name).StartsWith("._")) { @@ -240,7 +239,7 @@ namespace NzbDrone.Core.MediaFiles var decisions = _importDecisionMaker.GetImportDecisions(new List() { fileInfo.FullName }, series, null, true); - return _importApprovedEpisodes.Import(decisions, true, downloadClientItem); + return _importApprovedEpisodes.Import(decisions, true, downloadClientItem, importMode); } private string GetCleanedUpFolderName(string folder) diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs index dd86ff98e..cdfd289db 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportApprovedEpisodes.cs @@ -18,7 +18,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport { public interface IImportApprovedEpisodes { - List Import(List decisions, bool newDownload, DownloadClientItem downloadClientItem = null); + List Import(List decisions, bool newDownload, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto); } public class ImportApprovedEpisodes : IImportApprovedEpisodes @@ -45,7 +45,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport _logger = logger; } - public List Import(List decisions, bool newDownload, DownloadClientItem downloadClientItem = null) + public List Import(List decisions, bool newDownload, DownloadClientItem downloadClientItem = null, ImportMode importMode = ImportMode.Auto) { var qualifiedImports = decisions.Where(c => c.Approved) .GroupBy(c => c.LocalEpisode.Series.Id, (i, s) => s @@ -85,10 +85,23 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport episodeFile.Episodes = localEpisode.Episodes; episodeFile.ReleaseGroup = localEpisode.ParsedEpisodeInfo.ReleaseGroup; + bool copyOnly; + switch (importMode) + { + default: + case ImportMode.Auto: + copyOnly = downloadClientItem != null && downloadClientItem.IsReadOnly; + break; + case ImportMode.Move: + copyOnly = false; + break; + case ImportMode.Copy: + copyOnly = true; + break; + } + if (newDownload) { - bool copyOnly = downloadClientItem != null && downloadClientItem.IsReadOnly; - episodeFile.SceneName = GetSceneName(downloadClientItem, localEpisode); var moveResult = _episodeFileUpgrader.UpgradeEpisodeFile(episodeFile, localEpisode, copyOnly); @@ -104,7 +117,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport if (newDownload) { - _extraService.ImportExtraFiles(localEpisode, episodeFile, downloadClientItem != null && downloadClientItem.IsReadOnly); + _extraService.ImportExtraFiles(localEpisode, episodeFile, copyOnly); } if (downloadClientItem != null) diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportMode.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportMode.cs new file mode 100644 index 000000000..e28bd88bc --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/ImportMode.cs @@ -0,0 +1,11 @@ +using System; + +namespace NzbDrone.Core.MediaFiles.EpisodeImport +{ + public enum ImportMode + { + Auto = 0, + Move = 1, + Copy = 2 + } +} diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportCommand.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportCommand.cs index 1242577f1..c3c077609 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportCommand.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportCommand.cs @@ -14,5 +14,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual return true; } } + + public ImportMode ImportMode { get; set; } } } diff --git a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs index b5a663c00..d85a2e119 100644 --- a/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs +++ b/src/NzbDrone.Core/MediaFiles/EpisodeImport/Manual/ManualImportService.cs @@ -189,7 +189,7 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual public void Execute(ManualImportCommand message) { - _logger.ProgressTrace("Manually importing {0} files", message.Files.Count); + _logger.ProgressTrace("Manually importing {0} files using mode {1}", message.Files.Count, message.ImportMode); var imported = new List(); var importedTrackedDownload = new List(); @@ -217,20 +217,19 @@ namespace NzbDrone.Core.MediaFiles.EpisodeImport.Manual Size = 0 }; - //TODO: Option to copy instead of import //TODO: Cleanup non-tracked downloads var importDecision = new ImportDecision(localEpisode); if (file.DownloadId.IsNullOrWhiteSpace()) { - imported.AddRange(_importApprovedEpisodes.Import(new List { importDecision }, !existingFile)); + imported.AddRange(_importApprovedEpisodes.Import(new List { importDecision }, !existingFile, null, message.ImportMode)); } else { var trackedDownload = _trackedDownloadService.Find(file.DownloadId); - var importResult = _importApprovedEpisodes.Import(new List { importDecision }, true, trackedDownload.DownloadItem).First(); + var importResult = _importApprovedEpisodes.Import(new List { importDecision }, true, trackedDownload.DownloadItem, message.ImportMode).First(); imported.Add(importResult); diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index c305553da..553e6b41a 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -693,6 +693,7 @@ + diff --git a/src/UI/ManualImport/ManualImportLayout.js b/src/UI/ManualImport/ManualImportLayout.js index 4aac3145d..ba5a139fc 100644 --- a/src/UI/ManualImport/ManualImportLayout.js +++ b/src/UI/ManualImport/ManualImportLayout.js @@ -27,7 +27,8 @@ module.exports = Marionette.Layout.extend({ }, ui : { - importButton : '.x-import' + importButton : '.x-import', + importMode : '.x-importmode' }, events : { @@ -94,6 +95,7 @@ module.exports = Marionette.Layout.extend({ this.folder = options.folder; this.downloadId = options.downloadId; this.title = options.title; + this.importMode = options.importMode || 'Move'; this.templateHelpers = { title : this.title || this.folder @@ -105,11 +107,13 @@ module.exports = Marionette.Layout.extend({ if (this.folder || this.downloadId) { this._showLoading(); this._loadCollection(); + this.ui.importMode.val(this.importMode); } else { this._showSelectFolder(); this.ui.importButton.hide(); + this.ui.importMode.hide(); } }, @@ -196,6 +200,8 @@ module.exports = Marionette.Layout.extend({ return; } + var importMode = this.ui.importMode.val(); + CommandController.Execute('manualImport', { name : 'manualImport', files : _.map(selected, function (file) { @@ -206,7 +212,8 @@ module.exports = Marionette.Layout.extend({ quality : file.get('quality'), downloadId : file.get('downloadId') }; - }) + }), + importMode : importMode }); vent.trigger(vent.Commands.CloseModalCommand); diff --git a/src/UI/ManualImport/ManualImportLayoutTemplate.hbs b/src/UI/ManualImport/ManualImportLayoutTemplate.hbs index 9fefa2eb8..194e094e9 100644 --- a/src/UI/ManualImport/ManualImportLayoutTemplate.hbs +++ b/src/UI/ManualImport/ManualImportLayoutTemplate.hbs @@ -13,6 +13,12 @@