Compare commits

..

1 Commits

Author SHA1 Message Date
Mark McDowall
17f28a10d5 Fixed: Normalize unicode characters when comparing paths for equality
(cherry picked from commit ceeec091f85d0094e07537b7f62f18292655a710)
2024-11-15 03:02:58 +00:00
13 changed files with 121 additions and 109 deletions

View File

@@ -9,7 +9,7 @@ variables:
testsFolder: './_tests' testsFolder: './_tests'
yarnCacheFolder: $(Pipeline.Workspace)/.yarn yarnCacheFolder: $(Pipeline.Workspace)/.yarn
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
majorVersion: '0.4.5' majorVersion: '0.4.4'
minorVersion: $[counter('minorVersion', 1)] minorVersion: $[counter('minorVersion', 1)]
readarrVersion: '$(majorVersion).$(minorVersion)' readarrVersion: '$(majorVersion).$(minorVersion)'
buildName: '$(Build.SourceBranchName).$(readarrVersion)' buildName: '$(Build.SourceBranchName).$(readarrVersion)'
@@ -1211,7 +1211,7 @@ stages:
- task: SonarCloudAnalyze@2 - task: SonarCloudAnalyze@2
condition: eq(variables['System.PullRequest.IsFork'], 'False') condition: eq(variables['System.PullRequest.IsFork'], 'False')
displayName: Publish SonarCloud Results displayName: Publish SonarCloud Results
- task: reportgenerator@5.3.11 - task: reportgenerator@5
displayName: Generate Coverage Report displayName: Generate Coverage Report
inputs: inputs:
reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml' reports: '$(Build.SourcesDirectory)/CoverageResults/**/coverage.opencover.xml'

View File

@@ -26,7 +26,6 @@ module.exports = (env) => {
const config = { const config = {
mode: isProduction ? 'production' : 'development', mode: isProduction ? 'production' : 'development',
devtool: isProduction ? 'source-map' : 'eval-source-map', devtool: isProduction ? 'source-map' : 'eval-source-map',
target: 'web',
stats: { stats: {
children: false children: false

View File

@@ -54,7 +54,6 @@
} }
.title { .title {
text-wrap: balance;
font-weight: 300; font-weight: 300;
font-size: 50px; font-size: 50px;
line-height: 60px; line-height: 60px;
@@ -128,7 +127,6 @@
flex: 1 1 auto; flex: 1 1 auto;
margin-top: 8px; margin-top: 8px;
min-height: 0; min-height: 0;
text-wrap: balance;
font-size: $intermediateFontSize; font-size: $intermediateFontSize;
} }

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using System.Text;
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
@@ -316,5 +317,14 @@ namespace NzbDrone.Common.Test
result[2].Should().Be(@"Music"); result[2].Should().Be(@"Music");
result[3].Should().Be(@"Author Title"); result[3].Should().Be(@"Author Title");
} }
[Test]
public void should_be_equal_with_different_unicode_representations()
{
var path1 = @"C:\Test\file.mkv".AsOsAgnostic().Normalize(NormalizationForm.FormC);
var path2 = @"C:\Test\file.mkv".AsOsAgnostic().Normalize(NormalizationForm.FormD);
path1.PathEquals(path2);
}
} }
} }

View File

@@ -60,6 +60,10 @@ namespace NzbDrone.Common.Extensions
public static bool PathEquals(this string firstPath, string secondPath, StringComparison? comparison = null) public static bool PathEquals(this string firstPath, string secondPath, StringComparison? comparison = null)
{ {
// Normalize paths to ensure unicode characters are represented the same way
firstPath = firstPath.Normalize();
secondPath = secondPath?.Normalize();
if (!comparison.HasValue) if (!comparison.HasValue)
{ {
comparison = DiskProviderBase.PathStringComparison; comparison = DiskProviderBase.PathStringComparison;

View File

@@ -21,10 +21,10 @@ namespace NzbDrone.Common
{ {
if (OsInfo.IsWindows) if (OsInfo.IsWindows)
{ {
return obj.CleanFilePath().ToLower().GetHashCode(); return obj.CleanFilePath().Normalize().ToLower().GetHashCode();
} }
return obj.CleanFilePath().GetHashCode(); return obj.CleanFilePath().Normalize().GetHashCode();
} }
} }
} }

View File

@@ -178,9 +178,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
VerifyWarning(item); VerifyWarning(item);
} }
[TestCase("pausedDL")] [Test]
[TestCase("stoppedDL")] public void paused_item_should_have_required_properties()
public void paused_item_should_have_required_properties(string state)
{ {
var torrent = new QBittorrentTorrent var torrent = new QBittorrentTorrent
{ {
@@ -189,7 +188,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
Size = 1000, Size = 1000,
Progress = 0.7, Progress = 0.7,
Eta = 8640000, Eta = 8640000,
State = state, State = "pausedDL",
Label = "", Label = "",
SavePath = "" SavePath = ""
}; };
@@ -201,7 +200,6 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
} }
[TestCase("pausedUP")] [TestCase("pausedUP")]
[TestCase("stoppedUP")]
[TestCase("queuedUP")] [TestCase("queuedUP")]
[TestCase("uploading")] [TestCase("uploading")]
[TestCase("stalledUP")] [TestCase("stalledUP")]
@@ -399,9 +397,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
result.OutputPath.FullPath.Should().Be(Path.Combine(torrent.SavePath, "Droned.S01.12")); result.OutputPath.FullPath.Should().Be(Path.Combine(torrent.SavePath, "Droned.S01.12"));
} }
[TestCase("pausedUP")] [Test]
[TestCase("stoppedUP")] public void api_261_should_use_content_path()
public void api_261_should_use_content_path(string state)
{ {
var torrent = new QBittorrentTorrent var torrent = new QBittorrentTorrent
{ {
@@ -410,7 +407,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
Size = 1000, Size = 1000,
Progress = 0.7, Progress = 0.7,
Eta = 8640000, Eta = 8640000,
State = state, State = "pausedUP",
Label = "", Label = "",
SavePath = @"C:\Torrents".AsOsAgnostic(), SavePath = @"C:\Torrents".AsOsAgnostic(),
ContentPath = @"C:\Torrents\Droned.S01.12".AsOsAgnostic() ContentPath = @"C:\Torrents\Droned.S01.12".AsOsAgnostic()
@@ -687,48 +684,44 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
item.CanMoveFiles.Should().BeFalse(); item.CanMoveFiles.Should().BeFalse();
} }
[TestCase("pausedUP")] [Test]
[TestCase("stoppedUP")] public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio_is_not_set()
public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio_is_not_set(string state)
{ {
GivenGlobalSeedLimits(-1); GivenGlobalSeedLimits(-1);
GivenCompletedTorrent(state, ratio: 1.0f); GivenCompletedTorrent("pausedUP", ratio: 1.0f);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse(); item.CanBeRemoved.Should().BeFalse();
item.CanMoveFiles.Should().BeFalse(); item.CanMoveFiles.Should().BeFalse();
} }
[TestCase("pausedUP")] [Test]
[TestCase("stoppedUP")] public void should_be_removable_and_should_allow_move_files_if_max_ratio_reached_and_paused()
public void should_be_removable_and_should_allow_move_files_if_max_ratio_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(1.0f); GivenGlobalSeedLimits(1.0f);
GivenCompletedTorrent(state, ratio: 1.0f); GivenCompletedTorrent("pausedUP", ratio: 1.0f);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue(); item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue(); item.CanMoveFiles.Should().BeTrue();
} }
[TestCase("pausedUP")] [Test]
[TestCase("stoppedUP")] public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_and_paused()
public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(2.0f); GivenGlobalSeedLimits(2.0f);
GivenCompletedTorrent(state, ratio: 1.0f, ratioLimit: 0.8f); GivenCompletedTorrent("pausedUP", ratio: 1.0f, ratioLimit: 0.8f);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue(); item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue(); item.CanMoveFiles.Should().BeTrue();
} }
[TestCase("pausedUP")] [Test]
[TestCase("stoppedUP")] public void should_not_be_removable_if_overridden_max_ratio_not_reached_and_paused()
public void should_not_be_removable_if_overridden_max_ratio_not_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(0.2f); GivenGlobalSeedLimits(0.2f);
GivenCompletedTorrent(state, ratio: 0.5f, ratioLimit: 0.8f); GivenCompletedTorrent("pausedUP", ratio: 0.5f, ratioLimit: 0.8f);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse(); item.CanBeRemoved.Should().BeFalse();
@@ -746,36 +739,33 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
item.CanMoveFiles.Should().BeFalse(); item.CanMoveFiles.Should().BeFalse();
} }
[TestCase("pausedUP")] [Test]
[TestCase("stoppedUP")] public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_and_paused()
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(-1, 20); GivenGlobalSeedLimits(-1, 20);
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20); GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue(); item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue(); item.CanMoveFiles.Should().BeTrue();
} }
[TestCase("pausedUP")] [Test]
[TestCase("stoppedUP")] public void should_be_removable_and_should_allow_move_files_if_overridden_max_seedingtime_reached_and_paused()
public void should_be_removable_and_should_allow_move_files_if_overridden_max_seedingtime_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(-1, 40); GivenGlobalSeedLimits(-1, 40);
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20, seedingTimeLimit: 10); GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20, seedingTimeLimit: 10);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue(); item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue(); item.CanMoveFiles.Should().BeTrue();
} }
[TestCase("pausedUP")] [Test]
[TestCase("stoppedUP")] public void should_not_be_removable_if_overridden_max_seedingtime_not_reached_and_paused()
public void should_not_be_removable_if_overridden_max_seedingtime_not_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(-1, 20); GivenGlobalSeedLimits(-1, 20);
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 30, seedingTimeLimit: 40); GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 30, seedingTimeLimit: 40);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse(); item.CanBeRemoved.Should().BeFalse();
@@ -793,72 +783,66 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
item.CanMoveFiles.Should().BeFalse(); item.CanMoveFiles.Should().BeFalse();
} }
[TestCase("pausedUP")] [Test]
[TestCase("stoppedUP")] public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_and_paused()
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20); GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
GivenCompletedTorrent(state, ratio: 2.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds()); GivenCompletedTorrent("pausedUP", ratio: 2.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue(); item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue(); item.CanMoveFiles.Should().BeTrue();
} }
[TestCase("pausedUP")] [Test]
[TestCase("stoppedUP")] public void should_be_removable_and_should_allow_move_files_if_overridden_max_inactive_seedingtime_reached_and_paused()
public void should_be_removable_and_should_allow_move_files_if_overridden_max_inactive_seedingtime_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 40); GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 40);
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20, inactiveSeedingTimeLimit: 10, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(15)).ToUnixTimeSeconds()); GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20, inactiveSeedingTimeLimit: 10, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(15)).ToUnixTimeSeconds());
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue(); item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue(); item.CanMoveFiles.Should().BeTrue();
} }
[TestCase("pausedUP")] [Test]
[TestCase("stoppedUP")] public void should_not_be_removable_if_overridden_max_inactive_seedingtime_not_reached_and_paused()
public void should_not_be_removable_if_overridden_max_inactive_seedingtime_not_reached_and_paused(string state)
{ {
GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20); GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20);
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 30, inactiveSeedingTimeLimit: 40, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(30)).ToUnixTimeSeconds()); GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 30, inactiveSeedingTimeLimit: 40, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(30)).ToUnixTimeSeconds());
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse(); item.CanBeRemoved.Should().BeFalse();
item.CanMoveFiles.Should().BeFalse(); item.CanMoveFiles.Should().BeFalse();
} }
[TestCase("pausedUP")] [Test]
[TestCase("stoppedUP")] public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_but_ratio_not_and_paused()
public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_reached_but_ratio_not_and_paused(string state)
{ {
GivenGlobalSeedLimits(2.0f, 20); GivenGlobalSeedLimits(2.0f, 20);
GivenCompletedTorrent(state, ratio: 1.0f, seedingTime: 30); GivenCompletedTorrent("pausedUP", ratio: 1.0f, seedingTime: 30);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue(); item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue(); item.CanMoveFiles.Should().BeTrue();
} }
[TestCase("pausedUP")] [Test]
[TestCase("stoppedUP")] public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_but_ratio_not_and_paused()
public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_but_ratio_not_and_paused(string state)
{ {
GivenGlobalSeedLimits(2.0f, maxInactiveSeedingTime: 20); GivenGlobalSeedLimits(2.0f, maxInactiveSeedingTime: 20);
GivenCompletedTorrent(state, ratio: 1.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds()); GivenCompletedTorrent("pausedUP", ratio: 1.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds());
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue(); item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue(); item.CanMoveFiles.Should().BeTrue();
} }
[TestCase("pausedUP")] [Test]
[TestCase("stoppedUP")] public void should_not_fetch_details_twice()
public void should_not_fetch_details_twice(string state)
{ {
GivenGlobalSeedLimits(-1, 30); GivenGlobalSeedLimits(-1, 30);
GivenCompletedTorrent(state, ratio: 2.0f, seedingTime: 20); GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse(); item.CanBeRemoved.Should().BeFalse();
@@ -870,9 +854,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
.Verify(p => p.GetTorrentProperties(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()), Times.Once()); .Verify(p => p.GetTorrentProperties(It.IsAny<string>(), It.IsAny<QBittorrentSettings>()), Times.Once());
} }
[TestCase("pausedUP")] [Test]
[TestCase("stoppedUP")] public void should_get_category_from_the_category_if_set()
public void should_get_category_from_the_category_if_set(string state)
{ {
const string category = "music-readarr"; const string category = "music-readarr";
GivenGlobalSeedLimits(1.0f); GivenGlobalSeedLimits(1.0f);
@@ -884,7 +867,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
Size = 1000, Size = 1000,
Progress = 1.0, Progress = 1.0,
Eta = 8640000, Eta = 8640000,
State = state, State = "pausedUP",
Category = category, Category = category,
SavePath = "", SavePath = "",
Ratio = 1.0f Ratio = 1.0f
@@ -896,9 +879,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
item.Category.Should().Be(category); item.Category.Should().Be(category);
} }
[TestCase("pausedUP")] [Test]
[TestCase("stoppedUP")] public void should_get_category_from_the_label_if_the_category_is_not_available()
public void should_get_category_from_the_label_if_the_category_is_not_available(string state)
{ {
const string category = "music-readarr"; const string category = "music-readarr";
GivenGlobalSeedLimits(1.0f); GivenGlobalSeedLimits(1.0f);
@@ -910,7 +892,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests
Size = 1000, Size = 1000,
Progress = 1.0, Progress = 1.0,
Eta = 8640000, Eta = 8640000,
State = state, State = "pausedUP",
Label = category, Label = category,
SavePath = "", SavePath = "",
Ratio = 1.0f Ratio = 1.0f

View File

@@ -40,7 +40,7 @@ namespace NzbDrone.Core.Test.MetadataSource.Goodreads
[TestCase("Harry Potter and the sorcerer's stone a detailed summary", 72245296)] [TestCase("Harry Potter and the sorcerer's stone a detailed summary", 72245296)]
[TestCase("B0192CTMYG", 61209488)] [TestCase("B0192CTMYG", 61209488)]
[TestCase("9780439554930", 3)] [TestCase("9780439554930", 48517161)]
public void successful_book_search(string title, int expected) public void successful_book_search(string title, int expected)
{ {
var result = Subject.Search(title); var result = Subject.Search(title);

View File

@@ -239,7 +239,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
// Avoid removing torrents that haven't reached the global max ratio. // Avoid removing torrents that haven't reached the global max ratio.
// Removal also requires the torrent to be paused, in case a higher max ratio was set on the torrent itself (which is not exposed by the api). // Removal also requires the torrent to be paused, in case a higher max ratio was set on the torrent itself (which is not exposed by the api).
item.CanMoveFiles = item.CanBeRemoved = torrent.State is "pausedUP" or "stoppedUP" && HasReachedSeedLimit(torrent, config); item.CanMoveFiles = item.CanBeRemoved = torrent.State == "pausedUP" && HasReachedSeedLimit(torrent, config);
switch (torrent.State) switch (torrent.State)
{ {
@@ -248,8 +248,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
item.Message = "qBittorrent is reporting an error"; item.Message = "qBittorrent is reporting an error";
break; break;
case "stoppedDL": // torrent is stopped and has NOT finished downloading case "pausedDL": // torrent is paused and has NOT finished downloading
case "pausedDL": // torrent is paused and has NOT finished downloading (qBittorrent < 5)
item.Status = DownloadItemStatus.Paused; item.Status = DownloadItemStatus.Paused;
break; break;
@@ -260,8 +259,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
item.Status = DownloadItemStatus.Queued; item.Status = DownloadItemStatus.Queued;
break; break;
case "pausedUP": // torrent is paused and has finished downloading (qBittorent < 5) case "pausedUP": // torrent is paused and has finished downloading
case "stoppedUP": // torrent is stopped and has finished downloading
case "uploading": // torrent is being seeded and data is being transferred case "uploading": // torrent is being seeded and data is being transferred
case "stalledUP": // torrent is being seeded, but no connection were made case "stalledUP": // torrent is being seeded, but no connection were made
case "queuedUP": // queuing is enabled and torrent is queued for upload case "queuedUP": // queuing is enabled and torrent is queued for upload

View File

@@ -26,6 +26,8 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
Dictionary<string, QBittorrentLabel> GetLabels(QBittorrentSettings settings); Dictionary<string, QBittorrentLabel> GetLabels(QBittorrentSettings settings);
void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings); void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, QBittorrentSettings settings);
void MoveTorrentToTopInQueue(string hash, QBittorrentSettings settings); void MoveTorrentToTopInQueue(string hash, QBittorrentSettings settings);
void PauseTorrent(string hash, QBittorrentSettings settings);
void ResumeTorrent(string hash, QBittorrentSettings settings);
void SetForceStart(string hash, bool enabled, QBittorrentSettings settings); void SetForceStart(string hash, bool enabled, QBittorrentSettings settings);
} }

View File

@@ -148,7 +148,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
{ {
request.AddFormParameter("paused", false); request.AddFormParameter("paused", false);
} }
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Stop) else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
{ {
request.AddFormParameter("paused", true); request.AddFormParameter("paused", true);
} }
@@ -178,7 +178,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
{ {
request.AddFormParameter("paused", false); request.AddFormParameter("paused", false);
} }
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Stop) else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
{ {
request.AddFormParameter("paused", true); request.AddFormParameter("paused", true);
} }
@@ -214,7 +214,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
catch (DownloadClientException ex) catch (DownloadClientException ex)
{ {
// if setCategory fails due to method not being found, then try older setLabel command for qBittorrent < v.3.3.5 // if setCategory fails due to method not being found, then try older setLabel command for qBittorrent < v.3.3.5
if (ex.InnerException is HttpException httpException && httpException.Response.StatusCode == HttpStatusCode.NotFound) if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.NotFound)
{ {
var setLabelRequest = BuildRequest(settings).Resource("/command/setLabel") var setLabelRequest = BuildRequest(settings).Resource("/command/setLabel")
.Post() .Post()
@@ -257,7 +257,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
catch (DownloadClientException ex) catch (DownloadClientException ex)
{ {
// qBittorrent rejects all Prio commands with 403: Forbidden if Options -> BitTorrent -> Torrent Queueing is not enabled // qBittorrent rejects all Prio commands with 403: Forbidden if Options -> BitTorrent -> Torrent Queueing is not enabled
if (ex.InnerException is HttpException httpException && httpException.Response.StatusCode == HttpStatusCode.Forbidden) if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.Forbidden)
{ {
return; return;
} }
@@ -266,6 +266,22 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
} }
} }
public void PauseTorrent(string hash, QBittorrentSettings settings)
{
var request = BuildRequest(settings).Resource("/command/pause")
.Post()
.AddFormParameter("hash", hash);
ProcessRequest(request, settings);
}
public void ResumeTorrent(string hash, QBittorrentSettings settings)
{
var request = BuildRequest(settings).Resource("/command/resume")
.Post()
.AddFormParameter("hash", hash);
ProcessRequest(request, settings);
}
public void SetForceStart(string hash, bool enabled, QBittorrentSettings settings) public void SetForceStart(string hash, bool enabled, QBittorrentSettings settings)
{ {
var request = BuildRequest(settings).Resource("/command/setForceStart") var request = BuildRequest(settings).Resource("/command/setForceStart")

View File

@@ -246,20 +246,14 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
request.AddFormParameter("category", settings.MusicCategory); request.AddFormParameter("category", settings.MusicCategory);
} }
// Avoid extraneous API version check if initial state is ForceStart // Note: ForceStart is handled by separate api call
if ((QBittorrentState)settings.InitialState is QBittorrentState.Start or QBittorrentState.Stop) if ((QBittorrentState)settings.InitialState == QBittorrentState.Start)
{ {
var stoppedParameterName = GetApiVersion(settings) >= new Version(2, 11, 0) ? "stopped" : "paused"; request.AddFormParameter("paused", false);
}
// Note: ForceStart is handled by separate api call else if ((QBittorrentState)settings.InitialState == QBittorrentState.Pause)
if ((QBittorrentState)settings.InitialState == QBittorrentState.Start) {
{ request.AddFormParameter("paused", true);
request.AddFormParameter(stoppedParameterName, false);
}
else if ((QBittorrentState)settings.InitialState == QBittorrentState.Stop)
{
request.AddFormParameter(stoppedParameterName, true);
}
} }
if (settings.SequentialOrder) if (settings.SequentialOrder)
@@ -297,7 +291,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
catch (DownloadClientException ex) catch (DownloadClientException ex)
{ {
// setShareLimits was added in api v2.0.1 so catch it case of the unlikely event that someone has api v2.0 // setShareLimits was added in api v2.0.1 so catch it case of the unlikely event that someone has api v2.0
if (ex.InnerException is HttpException httpException && httpException.Response.StatusCode == HttpStatusCode.NotFound) if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.NotFound)
{ {
return; return;
} }
@@ -319,7 +313,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
catch (DownloadClientException ex) catch (DownloadClientException ex)
{ {
// qBittorrent rejects all Prio commands with 409: Conflict if Options -> BitTorrent -> Torrent Queueing is not enabled // qBittorrent rejects all Prio commands with 409: Conflict if Options -> BitTorrent -> Torrent Queueing is not enabled
if (ex.InnerException is HttpException httpException && httpException.Response.StatusCode == HttpStatusCode.Conflict) if (ex.InnerException is HttpException && (ex.InnerException as HttpException).Response.StatusCode == HttpStatusCode.Conflict)
{ {
return; return;
} }
@@ -328,6 +322,22 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent
} }
} }
public void PauseTorrent(string hash, QBittorrentSettings settings)
{
var request = BuildRequest(settings).Resource("/api/v2/torrents/pause")
.Post()
.AddFormParameter("hashes", hash);
ProcessRequest(request, settings);
}
public void ResumeTorrent(string hash, QBittorrentSettings settings)
{
var request = BuildRequest(settings).Resource("/api/v2/torrents/resume")
.Post()
.AddFormParameter("hashes", hash);
ProcessRequest(request, settings);
}
public void SetForceStart(string hash, bool enabled, QBittorrentSettings settings) public void SetForceStart(string hash, bool enabled, QBittorrentSettings settings)
{ {
var request = BuildRequest(settings).Resource("/api/v2/torrents/setForceStart") var request = BuildRequest(settings).Resource("/api/v2/torrents/setForceStart")

View File

@@ -1,16 +1,9 @@
using NzbDrone.Core.Annotations;
namespace NzbDrone.Core.Download.Clients.QBittorrent namespace NzbDrone.Core.Download.Clients.QBittorrent
{ {
public enum QBittorrentState public enum QBittorrentState
{ {
[FieldOption(Label = "Started")]
Start = 0, Start = 0,
[FieldOption(Label = "Force Started")]
ForceStart = 1, ForceStart = 1,
Pause = 2
[FieldOption(Label = "Stopped")]
Stop = 2
} }
} }