diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs index 5625e954f..e782072ea 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/QBittorrentTests/QBittorrentFixture.cs @@ -556,6 +556,22 @@ public void should_be_removable_and_should_allow_move_files_if_max_seedingtime_r item.CanMoveFiles.Should().BeTrue(); } + [Test] + public void should_not_fetch_details_twice() + { + GivenGlobalSeedLimits(-1, 30); + GivenCompletedTorrent("pausedUP", ratio: 2.0f, seedingTime: 20); + + var item = Subject.GetItems().Single(); + item.CanBeRemoved.Should().BeFalse(); + item.CanMoveFiles.Should().BeFalse(); + + var item2 = Subject.GetItems().Single(); + + Mocker.GetMock() + .Verify(p => p.GetTorrentProperties(It.IsAny(), It.IsAny()), Times.Once()); + } + [Test] public void should_get_category_from_the_category_if_set() { diff --git a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs index 6d0a0b3df..2819fd857 100644 --- a/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/QBittorrent/QBittorrent.cs @@ -13,12 +13,20 @@ using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; using NzbDrone.Core.Organizer; +using NzbDrone.Common.Cache; namespace NzbDrone.Core.Download.Clients.QBittorrent { public class QBittorrent : TorrentClientBase { private readonly IQBittorrentProxySelector _proxySelector; + private readonly ICached _seedingTimeCache; + + private class SeedingTimeCacheEntry + { + public DateTime LastFetched { get; set; } + public long SeedingTime { get; set; } + } public QBittorrent(IQBittorrentProxySelector proxySelector, ITorrentFileInfoReader torrentFileInfoReader, @@ -27,10 +35,13 @@ public QBittorrent(IQBittorrentProxySelector proxySelector, INamingConfigService namingConfigService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, + ICacheManager cacheManager, Logger logger) : base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger) { _proxySelector = proxySelector; + + _seedingTimeCache = cacheManager.GetCache(GetType(), "seedingTime"); } private IQBittorrentProxy Proxy => _proxySelector.GetProxy(Settings); @@ -444,23 +455,71 @@ protected bool HasReachedSeedLimit(QBittorrentTorrent torrent, QBittorrentPrefer if (torrent.Ratio >= config.MaxRatio) return true; } + if (HasReachedSeedingTimeLimit(torrent, config)) return true; + + + return false; + } + + protected bool HasReachedSeedingTimeLimit(QBittorrentTorrent torrent, QBittorrentPreferences config) + { + long seedingTimeLimit; + if (torrent.SeedingTimeLimit >= 0) { - if (!torrent.SeedingTime.HasValue) - { - FetchTorrentDetails(torrent); - } - - if (torrent.SeedingTime >= torrent.SeedingTimeLimit) return true; + seedingTimeLimit = torrent.SeedingTimeLimit; } else if (torrent.SeedingTimeLimit == -2 && config.MaxSeedingTimeEnabled) { - if (!torrent.SeedingTime.HasValue) - { - FetchTorrentDetails(torrent); - } + seedingTimeLimit = config.MaxSeedingTime; + } + else + { + return false; + } - if (torrent.SeedingTime >= config.MaxSeedingTime) return true; + if (torrent.SeedingTime.HasValue) + { + // SeedingTime can't be available here, but use it if the api starts to provide it. + return torrent.SeedingTime.Value >= seedingTimeLimit; + } + + var cacheKey = Settings.Host + Settings.Port + torrent.Hash; + var cacheSeedingTime = _seedingTimeCache.Find(cacheKey); + + if (cacheSeedingTime != null) + { + var togo = seedingTimeLimit - cacheSeedingTime.SeedingTime; + var elapsed = (DateTime.UtcNow - cacheSeedingTime.LastFetched).TotalSeconds; + + if (togo <= 0) + { + // Already reached the limit, keep the cache alive + _seedingTimeCache.Set(cacheKey, cacheSeedingTime, TimeSpan.FromMinutes(5)); + return true; + } + else if (togo > elapsed) + { + // SeedingTime cannot have reached the required value since the last check, preserve the cache + _seedingTimeCache.Set(cacheKey, cacheSeedingTime, TimeSpan.FromMinutes(5)); + return false; + } + } + + FetchTorrentDetails(torrent); + + cacheSeedingTime = new SeedingTimeCacheEntry + { + LastFetched = DateTime.UtcNow, + SeedingTime = torrent.SeedingTime.Value + }; + + _seedingTimeCache.Set(cacheKey, cacheSeedingTime, TimeSpan.FromMinutes(5)); + + if (cacheSeedingTime.SeedingTime >= seedingTimeLimit) + { + // Reached the limit, keep the cache alive + return true; } return false;