diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs index 967a958d4..f7bcaa528 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs @@ -108,7 +108,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests Subject.Definition.Settings.As().RecentMoviePriority = (int)QBittorrentPriority.First; } - protected void GivenGlobalSeedLimits(float maxRatio, int maxSeedingTime = -1, QBittorrentMaxRatioAction maxRatioAction = QBittorrentMaxRatioAction.Pause) + protected void GivenGlobalSeedLimits(float maxRatio, int maxSeedingTime = -1, int maxInactiveSeedingTime = -1, QBittorrentMaxRatioAction maxRatioAction = QBittorrentMaxRatioAction.Pause) { Mocker.GetMock() .Setup(s => s.GetConfig(It.IsAny())) @@ -118,7 +118,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests MaxRatio = maxRatio, MaxRatioEnabled = maxRatio >= 0, MaxSeedingTime = maxSeedingTime, - MaxSeedingTimeEnabled = maxSeedingTime >= 0 + MaxSeedingTimeEnabled = maxSeedingTime >= 0, + MaxInactiveSeedingTime = maxInactiveSeedingTime, + MaxInactiveSeedingTimeEnabled = maxInactiveSeedingTime >= 0 }); } @@ -609,7 +611,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests float ratio = 0.1f, float ratioLimit = -2, int seedingTime = 1, - int seedingTimeLimit = -2) + int seedingTimeLimit = -2, + int inactiveSeedingTimeLimit = -2, + long lastActivity = -1) { var torrent = new QBittorrentTorrent { @@ -623,7 +627,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests SavePath = "", Ratio = ratio, RatioLimit = ratioLimit, - SeedingTimeLimit = seedingTimeLimit + SeedingTimeLimit = seedingTimeLimit, + InactiveSeedingTimeLimit = inactiveSeedingTimeLimit, + LastActivity = lastActivity == -1 ? DateTimeOffset.UtcNow.ToUnixTimeSeconds() : lastActivity }; GivenTorrents(new List() { torrent }); @@ -738,6 +744,50 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests item.CanMoveFiles.Should().BeFalse(); } + [Test] + public void should_not_be_removable_and_should_not_allow_move_files_if_max_inactive_seedingtime_reached_and_not_paused() + { + GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20); + GivenCompletedTorrent("uploading", ratio: 2.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds()); + + 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_inactive_seedingtime_reached_and_paused() + { + GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20); + GivenCompletedTorrent("pausedUP", ratio: 2.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds()); + + 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_inactive_seedingtime_reached_and_paused() + { + GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 40); + GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20, inactiveSeedingTimeLimit: 10, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(15)).ToUnixTimeSeconds()); + + var item = Subject.GetItems().Single(); + item.CanBeRemoved.Should().BeTrue(); + item.CanMoveFiles.Should().BeTrue(); + } + + [Test] + public void should_not_be_removable_if_overridden_max_inactive_seedingtime_not_reached_and_paused() + { + GivenGlobalSeedLimits(-1, maxInactiveSeedingTime: 20); + GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 30, inactiveSeedingTimeLimit: 40, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(30)).ToUnixTimeSeconds()); + + 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_seedingtime_reached_but_ratio_not_and_paused() { @@ -749,6 +799,17 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.QBittorrentTests item.CanMoveFiles.Should().BeTrue(); } + [Test] + public void should_be_removable_and_should_allow_move_files_if_max_inactive_seedingtime_reached_but_ratio_not_and_paused() + { + GivenGlobalSeedLimits(2.0f, maxInactiveSeedingTime: 20); + GivenCompletedTorrent("pausedUP", ratio: 1.0f, lastActivity: DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMinutes(25)).ToUnixTimeSeconds()); + + var item = Subject.GetItems().Single(); + item.CanBeRemoved.Should().BeTrue(); + item.CanMoveFiles.Should().BeTrue(); + } + [Test] public void should_not_fetch_details_twice() { diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs index 22bc83d5a..63a858c6b 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs @@ -630,7 +630,7 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent } } - if (HasReachedSeedingTimeLimit(torrent, config)) + if (HasReachedSeedingTimeLimit(torrent, config) || HasReachedInactiveSeedingTimeLimit(torrent, config)) { return true; } @@ -702,6 +702,26 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent return false; } + protected bool HasReachedInactiveSeedingTimeLimit(QBittorrentTorrent torrent, QBittorrentPreferences config) + { + long inactiveSeedingTimeLimit; + + if (torrent.InactiveSeedingTimeLimit >= 0) + { + inactiveSeedingTimeLimit = torrent.InactiveSeedingTimeLimit * 60; + } + else if (torrent.InactiveSeedingTimeLimit == -2 && config.MaxInactiveSeedingTimeEnabled) + { + inactiveSeedingTimeLimit = config.MaxInactiveSeedingTime * 60; + } + else + { + return false; + } + + return DateTimeOffset.UtcNow.ToUnixTimeSeconds() - torrent.LastActivity > inactiveSeedingTimeLimit; + } + protected void FetchTorrentDetails(QBittorrentTorrent torrent) { var torrentProperties = Proxy.GetTorrentProperties(torrent.Hash, Settings); diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentPreferences.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentPreferences.cs index e2979bd3a..d33b7bfe8 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentPreferences.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentPreferences.cs @@ -28,6 +28,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent [JsonProperty(PropertyName = "max_seeding_time")] public long MaxSeedingTime { get; set; } // Get the global share time limit in minutes + [JsonProperty(PropertyName = "max_inactive_seeding_time_enabled")] + public bool MaxInactiveSeedingTimeEnabled { get; set; } // True if share inactive time limit is enabled + + [JsonProperty(PropertyName = "max_inactive_seeding_time")] + public long MaxInactiveSeedingTime { get; set; } // Get the global share inactive time limit in minutes + [JsonProperty(PropertyName = "max_ratio_act")] public QBittorrentMaxRatioAction MaxRatioAction { get; set; } // Action performed when a torrent reaches the maximum share ratio. diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentTorrent.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentTorrent.cs index 92e6c7e02..d282e993a 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrentTorrent.cs @@ -37,6 +37,12 @@ namespace NzbDrone.Core.Download.Clients.QBittorrent [JsonProperty(PropertyName = "seeding_time_limit")] // Per torrent seeding time limit (-2 = use global, -1 = unlimited) public long SeedingTimeLimit { get; set; } = -2; + + [JsonProperty(PropertyName = "inactive_seeding_time_limit")] // Per torrent inactive seeding time limit (-2 = use global, -1 = unlimited) + public long InactiveSeedingTimeLimit { get; set; } = -2; + + [JsonProperty(PropertyName = "last_activity")] // Timestamp in unix seconds when a chunk was last downloaded/uploaded + public long LastActivity { get; set; } } public class QBittorrentTorrentProperties