diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs index 0a4e44f7f5..b128dd0680 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs @@ -994,5 +994,170 @@ public void Test_should_force_api_version_check() Mocker.GetMock() .Verify(v => v.GetProxy(It.IsAny(), true), Times.Once()); } + + [Test] + public async Task Download_should_not_use_savepath_when_preimport_disabled() + { + // Arrange + var remoteMovie = CreateRemoteMovie(); + remoteMovie.Movie.Path = "/movies/My Movie (2024)"; + + // Ensure PreImportToDestination is false (default) + Subject.Definition.Settings.As().PreImportToDestination = false; + + // Act + await Subject.Download(remoteMovie, CreateIndexer()); + + // Assert - savePath parameter should be null + Mocker.GetMock() + .Verify(v => v.AddTorrentFromFile( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + null), Times.Once()); + } + + [Test] + public async Task Download_should_use_savepath_when_preimport_enabled_with_valid_movie_path() + { + // Arrange + var moviePath = "/movies/My Movie (2024)"; + var remoteMovie = CreateRemoteMovie(); + remoteMovie.Movie.Path = moviePath; + + // Enable PreImportToDestination + Subject.Definition.Settings.As().PreImportToDestination = true; + + // Act + await Subject.Download(remoteMovie, CreateIndexer()); + + // Assert - savePath parameter should be the movie path + Mocker.GetMock() + .Verify(v => v.AddTorrentFromFile( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + moviePath), Times.Once()); + } + + [Test] + public async Task Download_should_not_use_savepath_when_preimport_enabled_but_movie_path_is_null() + { + // Arrange + var remoteMovie = CreateRemoteMovie(); + remoteMovie.Movie.Path = null; + + // Enable PreImportToDestination + Subject.Definition.Settings.As().PreImportToDestination = true; + + // Act + await Subject.Download(remoteMovie, CreateIndexer()); + + // Assert - savePath parameter should be null due to null movie path + Mocker.GetMock() + .Verify(v => v.AddTorrentFromFile( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + null), Times.Once()); + } + + [Test] + public async Task Download_should_not_use_savepath_when_preimport_enabled_but_movie_path_is_empty() + { + // Arrange + var remoteMovie = CreateRemoteMovie(); + remoteMovie.Movie.Path = ""; + + // Enable PreImportToDestination + Subject.Definition.Settings.As().PreImportToDestination = true; + + // Act + await Subject.Download(remoteMovie, CreateIndexer()); + + // Assert - savePath parameter should be null due to empty movie path + Mocker.GetMock() + .Verify(v => v.AddTorrentFromFile( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + null), Times.Once()); + } + + [Test] + public async Task Download_from_magnet_should_use_savepath_when_preimport_enabled() + { + // Arrange + var moviePath = "/movies/My Movie (2024)"; + var magnetUrl = "magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp"; + var remoteMovie = CreateRemoteMovie(); + remoteMovie.Movie.Path = moviePath; + remoteMovie.Release.DownloadUrl = magnetUrl; + + // Enable PreImportToDestination + Subject.Definition.Settings.As().PreImportToDestination = true; + + // Act + await Subject.Download(remoteMovie, CreateIndexer()); + + // Assert - savePath parameter should be the movie path for magnet links + Mocker.GetMock() + .Verify(v => v.AddTorrentFromUrl( + magnetUrl, + It.IsAny(), + It.IsAny(), + moviePath), Times.Once()); + } + + [Test] + public async Task Download_from_magnet_should_not_use_savepath_when_preimport_disabled() + { + // Arrange + var magnetUrl = "magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp"; + var remoteMovie = CreateRemoteMovie(); + remoteMovie.Movie.Path = "/movies/My Movie (2024)"; + remoteMovie.Release.DownloadUrl = magnetUrl; + + // Ensure PreImportToDestination is false (default) + Subject.Definition.Settings.As().PreImportToDestination = false; + + // Act + await Subject.Download(remoteMovie, CreateIndexer()); + + // Assert - savePath parameter should be null + Mocker.GetMock() + .Verify(v => v.AddTorrentFromUrl( + magnetUrl, + It.IsAny(), + It.IsAny(), + null), Times.Once()); + } + + [Test] + public async Task Download_should_not_use_savepath_when_movie_is_null() + { + // Arrange + var remoteMovie = CreateRemoteMovie(); + remoteMovie.Movie = null; + + // Enable PreImportToDestination + Subject.Definition.Settings.As().PreImportToDestination = true; + + // Act + await Subject.Download(remoteMovie, CreateIndexer()); + + // Assert - savePath parameter should be null due to null movie + Mocker.GetMock() + .Verify(v => v.AddTorrentFromFile( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + null), Times.Once()); + } } } diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs index d004d067bf..827889667a 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs @@ -81,6 +81,9 @@ protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash var moveToTop = (isRecentMovie && Settings.RecentMoviePriority == (int)QBittorrentPriority.First) || (!isRecentMovie && Settings.OlderMoviePriority == (int)QBittorrentPriority.First); var forceStart = (QBittorrentState)Settings.InitialState == QBittorrentState.ForceStart; + // Pre-Import: Download directly to the movie's final destination folder to avoid + // unnecessary file moves between download directory and media library. + // This is particularly beneficial when they are on different physical drives. string savePath = null; if (Settings.PreImportToDestination && remoteMovie.Movie != null && remoteMovie.Movie.Path.IsNotNullOrWhiteSpace()) { @@ -145,6 +148,9 @@ protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string has var moveToTop = (isRecentMovie && Settings.RecentMoviePriority == (int)QBittorrentPriority.First) || (!isRecentMovie && Settings.OlderMoviePriority == (int)QBittorrentPriority.First); var forceStart = (QBittorrentState)Settings.InitialState == QBittorrentState.ForceStart; + // Pre-Import: Download directly to the movie's final destination folder to avoid + // unnecessary file moves between download directory and media library. + // This is particularly beneficial when they are on different physical drives. string savePath = null; if (Settings.PreImportToDestination && remoteMovie.Movie != null && remoteMovie.Movie.Path.IsNotNullOrWhiteSpace()) { diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV1.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV1.cs index 817db08a08..be511199b9 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV1.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV1.cs @@ -136,6 +136,7 @@ public void AddTorrentFromUrl(string torrentUrl, TorrentSeedConfiguration seedCo .Post() .AddFormParameter("urls", torrentUrl); + // Set custom save path for Pre-Import feature to download directly to destination folder if (savePath.IsNotNullOrWhiteSpace()) { request.AddFormParameter("savepath", savePath); @@ -171,6 +172,7 @@ public void AddTorrentFromFile(string fileName, byte[] fileContent, TorrentSeedC .Post() .AddFormUpload("torrents", fileName, fileContent); + // Set custom save path for Pre-Import feature to download directly to destination folder if (savePath.IsNotNullOrWhiteSpace()) { request.AddFormParameter("savepath", savePath); diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV2.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV2.cs index 139521c6e9..c84a6a7954 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV2.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentProxyV2.cs @@ -148,6 +148,7 @@ public void AddTorrentFromUrl(string torrentUrl, TorrentSeedConfiguration seedCo .Post() .AddFormParameter("urls", torrentUrl); + // Set custom save path for Pre-Import feature to download directly to destination folder if (savePath.IsNotNullOrWhiteSpace()) { request.AddFormParameter("savepath", savePath); @@ -175,6 +176,7 @@ public void AddTorrentFromFile(string fileName, byte[] fileContent, TorrentSeedC .Post() .AddFormUpload("torrents", fileName, fileContent); + // Set custom save path for Pre-Import feature to download directly to destination folder if (savePath.IsNotNullOrWhiteSpace()) { request.AddFormParameter("savepath", savePath);