Fixed: Transmission seeding idle time handling

pull/6/head
Taloth Saldono 5 years ago committed by Qstick
parent 82f66685b5
commit d41ae7b172

@ -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.MusicCategory.IsNotNullOrWhiteSpace()) if (Settings.MusicCategory.IsNotNullOrWhiteSpace())
{ {
@ -187,7 +227,7 @@ namespace NzbDrone.Core.Download.Clients.Transmission
} }
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.MusicCategory}"; return $"{destDir.TrimEnd('/')}/{Settings.MusicCategory}";
} }

@ -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,21 @@ 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"
}; };
var arguments = new Dictionary<string, object>(); var arguments = new Dictionary<string, object>();

@ -3,31 +3,23 @@ namespace NzbDrone.Core.Download.Clients.Transmission
public class TransmissionTorrent public class TransmissionTorrent
{ {
public int Id { get; set; } public int Id { get; set; }
public string HashString { get; set; } public string HashString { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string DownloadDir { get; set; } public string DownloadDir { get; set; }
public long TotalSize { get; set; } public long TotalSize { get; set; }
public long LeftUntilDone { get; set; } public long LeftUntilDone { get; set; }
public bool IsFinished { get; set; } public bool IsFinished { get; set; }
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 double SeedRatioLimit { get; set; }
public long SeedRatioLimit { get; set; } public int SeedRatioMode { get; set; }
public long SeedIdleLimit { get; set; }
public int SeedIdleMode { get; set; }
public int FileCount { get; set; }
} }
} }

@ -30,7 +30,7 @@ namespace NzbDrone.Core.Download.Clients.Vuze
// - A multi-file torrent is downloaded in a job folder and 'outputPath' points to that directory directly. // - A multi-file torrent is downloaded in a job folder and 'outputPath' points to that directory directly.
// - A single-file torrent is downloaded in the root folder and 'outputPath' poinst to that root folder. // - A single-file torrent is downloaded in the root folder and 'outputPath' poinst to that root folder.
// We have to make sure the return value points to the job folder OR file. // We have to make sure the return value points to the job folder OR file.
if (outputPath == null || outputPath.FileName == torrent.Name) if (outputPath == null || outputPath.FileName == torrent.Name || torrent.FileCount > 1)
{ {
_logger.Trace("Vuze output directory: {0}", outputPath); _logger.Trace("Vuze output directory: {0}", outputPath);
} }

Loading…
Cancel
Save