Fixed: Transmission seeding idle time handling

pull/3141/head
Taloth Saldono 5 years ago
parent 13907d6711
commit af5166e95d

@ -42,8 +42,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
VerifyCompleted(item); VerifyCompleted(item);
item.CanBeRemoved.Should().BeTrue(); item.CanBeRemoved.Should().BeFalse();
item.CanMoveFiles.Should().BeTrue(); item.CanMoveFiles.Should().BeFalse();
} }
[Test] [Test]
@ -175,7 +175,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
item.Status.Should().Be(expectedItemStatus); item.Status.Should().Be(expectedItemStatus);
} }
[TestCase(TransmissionTorrentStatus.Stopped, DownloadItemStatus.Completed, true)] [TestCase(TransmissionTorrentStatus.Stopped, DownloadItemStatus.Completed, false)]
[TestCase(TransmissionTorrentStatus.CheckWait, DownloadItemStatus.Downloading, false)] [TestCase(TransmissionTorrentStatus.CheckWait, DownloadItemStatus.Downloading, false)]
[TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading, false)] [TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading, false)]
[TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Completed, false)] [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Completed, false)]
@ -283,5 +283,139 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
item.RemainingTime.Should().NotHaveValue(); item.RemainingTime.Should().NotHaveValue();
} }
[Test]
public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio_reached_and_not_stopped()
{
GivenGlobalSeedLimits(1.0);
PrepareClientToReturnCompletedItem(false, ratio: 1.0);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse();
item.CanMoveFiles.Should().BeFalse();
}
[Test]
public void should_not_be_removable_and_should_not_allow_move_files_if_max_ratio_is_not_set()
{
GivenGlobalSeedLimits();
PrepareClientToReturnCompletedItem(true, ratio: 1.0);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse();
item.CanMoveFiles.Should().BeFalse();
}
[Test]
public void should_be_removable_and_should_allow_move_files_if_max_ratio_reached_and_paused()
{
GivenGlobalSeedLimits(1.0);
PrepareClientToReturnCompletedItem(true, ratio: 1.0);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
}
[Test]
public void should_be_removable_and_should_allow_move_files_if_overridden_max_ratio_reached_and_paused()
{
GivenGlobalSeedLimits(2.0);
PrepareClientToReturnCompletedItem(true, ratio: 1.0, ratioLimit: 0.8);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
}
[Test]
public void should_not_be_removable_if_overridden_max_ratio_not_reached_and_paused()
{
GivenGlobalSeedLimits(0.2);
PrepareClientToReturnCompletedItem(true, ratio: 0.5, ratioLimit: 0.8);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse();
item.CanMoveFiles.Should().BeFalse();
}
[Test]
public void should_not_be_removable_and_should_not_allow_move_files_if_max_idletime_reached_and_not_paused()
{
GivenGlobalSeedLimits(null, 20);
PrepareClientToReturnCompletedItem(false, ratio: 2.0, seedingTime: 30);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse();
item.CanMoveFiles.Should().BeFalse();
}
[Test]
public void should_be_removable_and_should_allow_move_files_if_max_idletime_reached_and_paused()
{
GivenGlobalSeedLimits(null, 20);
PrepareClientToReturnCompletedItem(true, ratio: 2.0, seedingTime: 20);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
}
[Test]
public void should_be_removable_and_should_allow_move_files_if_overridden_max_idletime_reached_and_paused()
{
GivenGlobalSeedLimits(null, 40);
PrepareClientToReturnCompletedItem(true, ratio: 2.0, seedingTime: 20, idleLimit: 10);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
}
[Test]
public void should_be_removable_and_should_not_allow_move_files_if_overridden_max_idletime_reached_and_not_paused()
{
GivenGlobalSeedLimits(null, 40);
PrepareClientToReturnCompletedItem(false, ratio: 2.0, seedingTime: 20, idleLimit: 10);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeFalse();
}
[Test]
public void should_not_be_removable_if_overridden_max_idletime_not_reached_and_paused()
{
GivenGlobalSeedLimits(null, 20);
PrepareClientToReturnCompletedItem(true, ratio: 2.0, seedingTime: 30, idleLimit: 40);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse();
item.CanMoveFiles.Should().BeFalse();
}
[Test]
public void should_not_be_removable_if_max_idletime_reached_but_ratio_not_and_not_paused()
{
GivenGlobalSeedLimits(2.0, 20);
PrepareClientToReturnCompletedItem(false, ratio: 1.0, seedingTime: 30);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeFalse();
item.CanMoveFiles.Should().BeFalse();
}
[Test]
public void should_be_removable_and_should_allow_move_files_if_max_idletime_configured_and_paused()
{
GivenGlobalSeedLimits(2.0, 20);
PrepareClientToReturnCompletedItem(true, ratio: 1.0, seedingTime: 30);
var item = Subject.GetItems().Single();
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
}
} }
} }

@ -3,6 +3,7 @@ using System.Collections.Generic;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.Transmission; using NzbDrone.Core.Download.Clients.Transmission;
using NzbDrone.Core.MediaFiles.TorrentInfo; using NzbDrone.Core.MediaFiles.TorrentInfo;
@ -72,11 +73,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
{ {
HashString = "HASH", HashString = "HASH",
IsFinished = true, IsFinished = true,
Status = TransmissionTorrentStatus.Stopped, Status = TransmissionTorrentStatus.Seeding,
Name = _title, Name = _title,
TotalSize = 1000, TotalSize = 1000,
LeftUntilDone = 0, LeftUntilDone = 0,
DownloadDir = "somepath" DownloadDir = "somepath",
DownloadedEver = 1000,
UploadedEver = 900
}; };
_magnet = new TransmissionTorrent _magnet = new TransmissionTorrent
@ -106,7 +109,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
Mocker.GetMock<ITransmissionProxy>() Mocker.GetMock<ITransmissionProxy>()
.Setup(v => v.GetConfig(It.IsAny<TransmissionSettings>())) .Setup(v => v.GetConfig(It.IsAny<TransmissionSettings>()))
.Returns(_transmissionConfigItems); .Returns(() => Json.Deserialize<TransmissionConfig>(_transmissionConfigItems.ToJson()));
} }
@ -178,8 +181,40 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
}); });
} }
protected void PrepareClientToReturnCompletedItem() protected void PrepareClientToReturnCompletedItem(bool stopped = false, double ratio = 0.9, int seedingTime = 60, double? ratioLimit = null, int? idleLimit = null)
{
if (stopped)
_completed.Status = TransmissionTorrentStatus.Stopped;
_completed.UploadedEver = (int)(_completed.DownloadedEver * ratio);
_completed.SecondsSeeding = seedingTime * 60;
if (ratioLimit.HasValue)
{
if (double.IsPositiveInfinity(ratioLimit.Value))
{
_completed.SeedRatioMode = 2;
}
else
{
_completed.SeedRatioMode = 1;
_completed.SeedRatioLimit = ratioLimit.Value;
}
}
if (idleLimit.HasValue)
{
if (double.IsPositiveInfinity(idleLimit.Value))
{
_completed.SeedIdleMode = 2;
}
else
{ {
_completed.SeedIdleMode = 1;
_completed.SeedIdleLimit = idleLimit.Value;
}
}
GivenTorrents(new List<TransmissionTorrent> GivenTorrents(new List<TransmissionTorrent>
{ {
_completed _completed
@ -193,5 +228,20 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
_magnet _magnet
}); });
} }
protected void GivenGlobalSeedLimits(double? ratioLimit = null, int? idleLimit = null)
{
_transmissionConfigItems["seedRatioLimited"] = ratioLimit.HasValue;
if (ratioLimit.HasValue)
{
_transmissionConfigItems["seedRatioLimit"] = ratioLimit.Value;
}
_transmissionConfigItems["idle-seeding-limit-enabled"] = idleLimit.HasValue;
if (idleLimit.HasValue)
{
_transmissionConfigItems["idle-seeding-limit"] = idleLimit.Value;
}
}
} }
} }

@ -47,7 +47,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
[Test] [Test]
public void completed_download_should_have_required_properties() public void completed_download_should_have_required_properties()
{ {
PrepareClientToReturnCompletedItem(); PrepareClientToReturnCompletedItem(true, ratioLimit: 0.5);
var item = Subject.GetItems().Single(); var item = Subject.GetItems().Single();
VerifyCompleted(item); VerifyCompleted(item);
@ -184,7 +184,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
item.Status.Should().Be(expectedItemStatus); item.Status.Should().Be(expectedItemStatus);
} }
[TestCase(TransmissionTorrentStatus.Stopped, DownloadItemStatus.Completed, true)] [TestCase(TransmissionTorrentStatus.Stopped, DownloadItemStatus.Completed, false)]
[TestCase(TransmissionTorrentStatus.CheckWait, DownloadItemStatus.Downloading, false)] [TestCase(TransmissionTorrentStatus.CheckWait, DownloadItemStatus.Downloading, false)]
[TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading, false)] [TestCase(TransmissionTorrentStatus.Check, DownloadItemStatus.Downloading, false)]
[TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued, false)] [TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued, false)]

@ -32,6 +32,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
public override IEnumerable<DownloadClientItem> GetItems() public override IEnumerable<DownloadClientItem> GetItems()
{ {
var configFunc = new Lazy<TransmissionConfig>(() => _proxy.GetConfig(Settings));
var torrents = _proxy.GetTorrents(Settings); var torrents = _proxy.GetTorrents(Settings);
var items = new List<DownloadClientItem>(); var items = new List<DownloadClientItem>();
@ -98,9 +99,8 @@ namespace NzbDrone.Core.Download.Clients.Transmission
item.Status = DownloadItemStatus.Downloading; item.Status = DownloadItemStatus.Downloading;
} }
item.CanMoveFiles = item.CanBeRemoved = item.CanBeRemoved = HasReachedSeedLimit(torrent, item.SeedRatio, configFunc);
torrent.Status == TransmissionTorrentStatus.Stopped && item.CanMoveFiles = item.CanBeRemoved && torrent.Status == TransmissionTorrentStatus.Stopped;
item.SeedRatio >= torrent.SeedRatioLimit;
items.Add(item); items.Add(item);
} }
@ -108,6 +108,46 @@ namespace NzbDrone.Core.Download.Clients.Transmission
return items; return items;
} }
protected bool HasReachedSeedLimit(TransmissionTorrent torrent, double? ratio, Lazy<TransmissionConfig> config)
{
var isStopped = torrent.Status == TransmissionTorrentStatus.Stopped;
var isSeeding = torrent.Status == TransmissionTorrentStatus.Seeding;
if (torrent.SeedRatioMode == 1)
{
if (isStopped && ratio.HasValue && ratio >= torrent.SeedRatioLimit)
{
return true;
}
}
else if (torrent.SeedRatioMode == 0)
{
if (isStopped && config.Value.SeedRatioLimited && ratio >= config.Value.SeedRatioLimit)
{
return true;
}
}
// Transmission doesn't support SeedTimeLimit, use/abuse seed idle limit, but only if it was set per-torrent.
if (torrent.SeedIdleMode == 1)
{
if ((isStopped || isSeeding) && torrent.SecondsSeeding > torrent.SeedIdleLimit * 60)
{
return true;
}
}
else if (torrent.SeedIdleMode == 0)
{
// The global idle limit is a real idle limit, if it's configured then 'Stopped' is enough.
if (isStopped && config.Value.IdleSeedingLimitEnabled)
{
return true;
}
}
return false;
}
public override void RemoveItem(string downloadId, bool deleteData) public override void RemoveItem(string downloadId, bool deleteData)
{ {
_proxy.RemoveTorrent(downloadId.ToLower(), deleteData, Settings); _proxy.RemoveTorrent(downloadId.ToLower(), deleteData, Settings);
@ -116,7 +156,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
public override DownloadClientInfo GetStatus() public override DownloadClientInfo GetStatus()
{ {
var config = _proxy.GetConfig(Settings); var config = _proxy.GetConfig(Settings);
var destDir = config.GetValueOrDefault("download-dir") as string; var destDir = config.DownloadDir;
if (Settings.TvCategory.IsNotNullOrWhiteSpace()) if (Settings.TvCategory.IsNotNullOrWhiteSpace())
{ {
@ -184,7 +224,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
if (!Settings.TvCategory.IsNotNullOrWhiteSpace()) return null; if (!Settings.TvCategory.IsNotNullOrWhiteSpace()) return null;
var config = _proxy.GetConfig(Settings); var config = _proxy.GetConfig(Settings);
var destDir = (string)config.GetValueOrDefault("download-dir"); var destDir = config.DownloadDir;
return $"{destDir.TrimEnd('/')}/{Settings.TvCategory}"; return $"{destDir.TrimEnd('/')}/{Settings.TvCategory}";
} }

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace NzbDrone.Core.Download.Clients.Transmission
{
public class TransmissionConfig
{
[JsonProperty("rpc-version")]
public string RpcVersion { get; set; }
public string Version { get; set; }
[JsonProperty("download-dir")]
public string DownloadDir { get; set; }
public double SeedRatioLimit { get; set; }
public bool SeedRatioLimited { get; set; }
[JsonProperty("idle-seeding-limit")]
public long IdleSeedingLimit { get; set; }
[JsonProperty("idle-seeding-limit-enabled")]
public bool IdleSeedingLimitEnabled { get; set; }
}
}

@ -16,7 +16,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
void AddTorrentFromUrl(string torrentUrl, string downloadDirectory, TransmissionSettings settings); void AddTorrentFromUrl(string torrentUrl, string downloadDirectory, TransmissionSettings settings);
void AddTorrentFromData(byte[] torrentData, string downloadDirectory, TransmissionSettings settings); void AddTorrentFromData(byte[] torrentData, string downloadDirectory, TransmissionSettings settings);
void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, TransmissionSettings settings); void SetTorrentSeedingConfiguration(string hash, TorrentSeedConfiguration seedConfiguration, TransmissionSettings settings);
Dictionary<string, object> GetConfig(TransmissionSettings settings); TransmissionConfig GetConfig(TransmissionSettings settings);
string GetProtocolVersion(TransmissionSettings settings); string GetProtocolVersion(TransmissionSettings settings);
string GetClientVersion(TransmissionSettings settings); string GetClientVersion(TransmissionSettings settings);
void RemoveTorrent(string hash, bool removeData, TransmissionSettings settings); void RemoveTorrent(string hash, bool removeData, TransmissionSettings settings);
@ -101,26 +101,22 @@ namespace NzbDrone.Core.Download.Clients.Transmission
{ {
var config = GetConfig(settings); var config = GetConfig(settings);
var version = config["rpc-version"]; return config.RpcVersion;
return version.ToString();
} }
public string GetClientVersion(TransmissionSettings settings) public string GetClientVersion(TransmissionSettings settings)
{ {
var config = GetConfig(settings); var config = GetConfig(settings);
var version = config["version"]; return config.Version;
return version.ToString();
} }
public Dictionary<string, object> GetConfig(TransmissionSettings settings) public TransmissionConfig GetConfig(TransmissionSettings settings)
{ {
// Gets the transmission version. // Gets the transmission version.
var result = GetSessionVariables(settings); var result = GetSessionVariables(settings);
return result.Arguments; return Json.Deserialize<TransmissionConfig>(result.Arguments.ToJson());
} }
public void RemoveTorrent(string hashString, bool removeData, TransmissionSettings settings) public void RemoveTorrent(string hashString, bool removeData, TransmissionSettings settings)
@ -164,15 +160,20 @@ namespace NzbDrone.Core.Download.Clients.Transmission
"hashString", // Unique torrent ID. Use this instead of the client id? "hashString", // Unique torrent ID. Use this instead of the client id?
"name", "name",
"downloadDir", "downloadDir",
"status",
"totalSize", "totalSize",
"leftUntilDone", "leftUntilDone",
"isFinished", "isFinished",
"eta", "eta",
"status",
"secondsDownloading",
"secondsSeeding",
"errorString", "errorString",
"uploadedEver", "uploadedEver",
"downloadedEver", "downloadedEver",
"seedRatioLimit", "seedRatioLimit",
"seedRatioMode",
"seedIdleLimit",
"seedIdleMode",
"fileCount" "fileCount"
}; };

@ -12,10 +12,14 @@
public int Eta { get; set; } public int Eta { get; set; }
public TransmissionTorrentStatus Status { get; set; } public TransmissionTorrentStatus Status { get; set; }
public int SecondsDownloading { get; set; } public int SecondsDownloading { get; set; }
public int SecondsSeeding { get; set; }
public string ErrorString { get; set; } public string ErrorString { get; set; }
public long DownloadedEver { get; set; } public long DownloadedEver { get; set; }
public long UploadedEver { get; set; } public long UploadedEver { get; set; }
public long SeedRatioLimit { get; set; } public double SeedRatioLimit { get; set; }
public int SeedRatioMode { get; set; }
public long SeedIdleLimit { get; set; }
public int SeedIdleMode { get; set; }
public int FileCount { get; set; } public int FileCount { get; set; }
} }
} }

@ -151,6 +151,7 @@
<Compile Include="DecisionEngine\Specifications\RepackSpecification.cs" /> <Compile Include="DecisionEngine\Specifications\RepackSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\MultiSeasonSpecification.cs" /> <Compile Include="DecisionEngine\Specifications\MultiSeasonSpecification.cs" />
<Compile Include="DecisionEngine\Specifications\UpgradeAllowedSpecification.cs" /> <Compile Include="DecisionEngine\Specifications\UpgradeAllowedSpecification.cs" />
<Compile Include="Download\Clients\Transmission\TransmissionConfig.cs" />
<Compile Include="Exceptions\DownloadClientRejectedReleaseException.cs" /> <Compile Include="Exceptions\DownloadClientRejectedReleaseException.cs" />
<Compile Include="Exceptions\SearchFailedException.cs" /> <Compile Include="Exceptions\SearchFailedException.cs" />
<Compile Include="Download\Clients\QBittorrent\QBittorrentLabel.cs" /> <Compile Include="Download\Clients\QBittorrent\QBittorrentLabel.cs" />

Loading…
Cancel
Save