Fixed: Transmission seeding idle time handling

This commit is contained in:
Taloth Saldono 2019-05-29 23:56:37 +02:00
parent 13907d6711
commit af5166e95d
8 changed files with 282 additions and 25 deletions

View File

@ -42,8 +42,8 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
var item = Subject.GetItems().Single();
VerifyCompleted(item);
item.CanBeRemoved.Should().BeTrue();
item.CanMoveFiles.Should().BeTrue();
item.CanBeRemoved.Should().BeFalse();
item.CanMoveFiles.Should().BeFalse();
}
[Test]
@ -175,7 +175,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
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.Check, DownloadItemStatus.Downloading, false)]
[TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Completed, false)]
@ -283,5 +283,139 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
var item = Subject.GetItems().Single();
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();
}
}
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.Transmission;
using NzbDrone.Core.MediaFiles.TorrentInfo;
@ -72,11 +73,13 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
{
HashString = "HASH",
IsFinished = true,
Status = TransmissionTorrentStatus.Stopped,
Status = TransmissionTorrentStatus.Seeding,
Name = _title,
TotalSize = 1000,
LeftUntilDone = 0,
DownloadDir = "somepath"
DownloadDir = "somepath",
DownloadedEver = 1000,
UploadedEver = 900
};
_magnet = new TransmissionTorrent
@ -106,7 +109,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
Mocker.GetMock<ITransmissionProxy>()
.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>
{
_completed
@ -193,5 +228,20 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
_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;
}
}
}
}

View File

@ -47,7 +47,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
[Test]
public void completed_download_should_have_required_properties()
{
PrepareClientToReturnCompletedItem();
PrepareClientToReturnCompletedItem(true, ratioLimit: 0.5);
var item = Subject.GetItems().Single();
VerifyCompleted(item);
@ -184,7 +184,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.VuzeTests
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.Check, DownloadItemStatus.Downloading, false)]
[TestCase(TransmissionTorrentStatus.Queued, DownloadItemStatus.Queued, false)]

View File

@ -32,6 +32,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
public override IEnumerable<DownloadClientItem> GetItems()
{
var configFunc = new Lazy<TransmissionConfig>(() => _proxy.GetConfig(Settings));
var torrents = _proxy.GetTorrents(Settings);
var items = new List<DownloadClientItem>();
@ -98,9 +99,8 @@ namespace NzbDrone.Core.Download.Clients.Transmission
item.Status = DownloadItemStatus.Downloading;
}
item.CanMoveFiles = item.CanBeRemoved =
torrent.Status == TransmissionTorrentStatus.Stopped &&
item.SeedRatio >= torrent.SeedRatioLimit;
item.CanBeRemoved = HasReachedSeedLimit(torrent, item.SeedRatio, configFunc);
item.CanMoveFiles = item.CanBeRemoved && torrent.Status == TransmissionTorrentStatus.Stopped;
items.Add(item);
}
@ -108,6 +108,46 @@ namespace NzbDrone.Core.Download.Clients.Transmission
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)
{
_proxy.RemoveTorrent(downloadId.ToLower(), deleteData, Settings);
@ -116,7 +156,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
public override DownloadClientInfo GetStatus()
{
var config = _proxy.GetConfig(Settings);
var destDir = config.GetValueOrDefault("download-dir") as string;
var destDir = config.DownloadDir;
if (Settings.TvCategory.IsNotNullOrWhiteSpace())
{
@ -184,7 +224,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
if (!Settings.TvCategory.IsNotNullOrWhiteSpace()) return null;
var config = _proxy.GetConfig(Settings);
var destDir = (string)config.GetValueOrDefault("download-dir");
var destDir = config.DownloadDir;
return $"{destDir.TrimEnd('/')}/{Settings.TvCategory}";
}

View File

@ -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; }
}
}

View File

@ -16,7 +16,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
void AddTorrentFromUrl(string torrentUrl, string downloadDirectory, TransmissionSettings settings);
void AddTorrentFromData(byte[] torrentData, string downloadDirectory, 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 GetClientVersion(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 version = config["rpc-version"];
return version.ToString();
return config.RpcVersion;
}
public string GetClientVersion(TransmissionSettings settings)
{
var config = GetConfig(settings);
var version = config["version"];
return version.ToString();
return config.Version;
}
public Dictionary<string, object> GetConfig(TransmissionSettings settings)
public TransmissionConfig GetConfig(TransmissionSettings settings)
{
// Gets the transmission version.
var result = GetSessionVariables(settings);
return result.Arguments;
return Json.Deserialize<TransmissionConfig>(result.Arguments.ToJson());
}
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?
"name",
"downloadDir",
"status",
"totalSize",
"leftUntilDone",
"isFinished",
"eta",
"status",
"secondsDownloading",
"secondsSeeding",
"errorString",
"uploadedEver",
"downloadedEver",
"seedRatioLimit",
"seedRatioMode",
"seedIdleLimit",
"seedIdleMode",
"fileCount"
};

View File

@ -12,10 +12,14 @@
public int Eta { get; set; }
public TransmissionTorrentStatus Status { get; set; }
public int SecondsDownloading { get; set; }
public int SecondsSeeding { get; set; }
public string ErrorString { get; set; }
public long DownloadedEver { 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; }
}
}

View File

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