1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-11-05 02:22:31 +01:00

New: Removing rtorrent downloads when seeding criteria have been met

(cherry picked from commit 411be4d0116f0739bb9c71235312d0c5a26dd3a2)

Fixes: #6320
Fixes: #5219
This commit is contained in:
leaty 2020-03-19 15:47:25 +01:00 committed by Qstick
parent dd80a64560
commit 1c0621af0a
5 changed files with 184 additions and 30 deletions

View File

@ -5,6 +5,7 @@
using FluentValidation.Results; using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http; using NzbDrone.Common.Http;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
@ -23,6 +24,8 @@ public class RTorrent : TorrentClientBase<RTorrentSettings>
{ {
private readonly IRTorrentProxy _proxy; private readonly IRTorrentProxy _proxy;
private readonly IRTorrentDirectoryValidator _rTorrentDirectoryValidator; private readonly IRTorrentDirectoryValidator _rTorrentDirectoryValidator;
private readonly IDownloadSeedConfigProvider _downloadSeedConfigProvider;
private readonly string _imported_view = string.Concat(BuildInfo.AppName.ToLower(), "_imported");
public RTorrent(IRTorrentProxy proxy, public RTorrent(IRTorrentProxy proxy,
ITorrentFileInfoReader torrentFileInfoReader, ITorrentFileInfoReader torrentFileInfoReader,
@ -31,19 +34,20 @@ public RTorrent(IRTorrentProxy proxy,
INamingConfigService namingConfigService, INamingConfigService namingConfigService,
IDiskProvider diskProvider, IDiskProvider diskProvider,
IRemotePathMappingService remotePathMappingService, IRemotePathMappingService remotePathMappingService,
IDownloadSeedConfigProvider downloadSeedConfigProvider,
IRTorrentDirectoryValidator rTorrentDirectoryValidator, IRTorrentDirectoryValidator rTorrentDirectoryValidator,
Logger logger) Logger logger)
: base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger) : base(torrentFileInfoReader, httpClient, configService, namingConfigService, diskProvider, remotePathMappingService, logger)
{ {
_proxy = proxy; _proxy = proxy;
_rTorrentDirectoryValidator = rTorrentDirectoryValidator; _rTorrentDirectoryValidator = rTorrentDirectoryValidator;
_downloadSeedConfigProvider = downloadSeedConfigProvider;
} }
public override void MarkItemAsImported(DownloadClientItem downloadClientItem) public override void MarkItemAsImported(DownloadClientItem downloadClientItem)
{ {
// set post-import category // Set post-import label
if (Settings.MovieImportedCategory.IsNotNullOrWhiteSpace() && if (Settings.MovieImportedCategory.IsNotNullOrWhiteSpace() && Settings.MovieImportedCategory != Settings.MovieCategory)
Settings.MovieImportedCategory != Settings.MovieCategory)
{ {
try try
{ {
@ -51,12 +55,19 @@ public override void MarkItemAsImported(DownloadClientItem downloadClientItem)
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.Warn(ex, _logger.Warn(ex, "Failed to set torrent post-import label \"{0}\" for {1} in rTorrent. Does the label exist?", Settings.MovieImportedCategory, downloadClientItem.Title);
"Failed to set torrent post-import label \"{0}\" for {1} in rTorrent. Does the label exist?",
Settings.MovieImportedCategory,
downloadClientItem.Title);
} }
} }
// Set post-import view
try
{
_proxy.PushTorrentUniqueView(downloadClientItem.DownloadId.ToLower(), _imported_view, Settings);
}
catch (Exception ex)
{
_logger.Warn(ex, "Failed to set torrent post-import view \"{0}\" for {1} in rTorrent.", _imported_view, downloadClientItem.Title);
}
} }
protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink) protected override string AddFromMagnetLink(RemoteMovie remoteMovie, string hash, string magnetLink)
@ -99,7 +110,7 @@ protected override string AddFromTorrentFile(RemoteMovie remoteMovie, string has
public override string Name => "rTorrent"; public override string Name => "rTorrent";
public override ProviderMessage Message => new ProviderMessage("Radarr is unable to remove torrents that have finished seeding when using rTorrent", ProviderMessageType.Warning); public override ProviderMessage Message => new ProviderMessage($"Radarr will handle automatic removal of torrents based on the current seed criteria in Settings->Indexers. After importing it will also set \"{_imported_view}\" as an rTorrent view, which can be used in rTorrent scripts to customize behavior.", ProviderMessageType.Info);
public override IEnumerable<DownloadClientItem> GetItems() public override IEnumerable<DownloadClientItem> GetItems()
{ {
@ -154,8 +165,15 @@ public override IEnumerable<DownloadClientItem> GetItems()
item.Status = DownloadItemStatus.Paused; item.Status = DownloadItemStatus.Paused;
} }
// No stop ratio data is present, so do not delete // Grab cached seedConfig
item.CanMoveFiles = item.CanBeRemoved = false; var seedConfig = _downloadSeedConfigProvider.GetSeedConfiguration(torrent.Hash);
// Check if torrent is finished and if it exceeds cached seedConfig
item.CanMoveFiles = item.CanBeRemoved =
torrent.IsFinished && seedConfig != null &&
(
(torrent.Ratio / 1000.0) >= seedConfig.Ratio ||
(DateTimeOffset.Now - DateTimeOffset.FromUnixTimeSeconds(torrent.FinishedTime)) >= seedConfig.SeedTime);
items.Add(item); items.Add(item);
} }

View File

@ -18,6 +18,7 @@ public interface IRTorrentProxy
void RemoveTorrent(string hash, RTorrentSettings settings); void RemoveTorrent(string hash, RTorrentSettings settings);
void SetTorrentLabel(string hash, string label, RTorrentSettings settings); void SetTorrentLabel(string hash, string label, RTorrentSettings settings);
bool HasHashTorrent(string hash, RTorrentSettings settings); bool HasHashTorrent(string hash, RTorrentSettings settings);
void PushTorrentUniqueView(string hash, string view, RTorrentSettings settings);
} }
public interface IRTorrent : IXmlRpcProxy public interface IRTorrent : IXmlRpcProxy
@ -46,6 +47,9 @@ public interface IRTorrent : IXmlRpcProxy
[XmlRpcMethod("d.custom1.set")] [XmlRpcMethod("d.custom1.set")]
string SetLabel(string hash, string label); string SetLabel(string hash, string label);
[XmlRpcMethod("d.views.push_back_unique")]
int PushUniqueView(string hash, string view);
[XmlRpcMethod("system.client_version")] [XmlRpcMethod("system.client_version")]
string GetVersion(); string GetVersion();
} }
@ -87,7 +91,8 @@ public List<RTorrentTorrent> GetTorrents(RTorrentSettings settings)
"d.ratio=", // long "d.ratio=", // long
"d.is_open=", // long "d.is_open=", // long
"d.is_active=", // long "d.is_active=", // long
"d.complete=")); //long "d.complete=", //long
"d.timestamp.finished=")); // long (unix timestamp)
_logger.Trace(ret.ToJson()); _logger.Trace(ret.ToJson());
@ -109,6 +114,7 @@ public List<RTorrentTorrent> GetTorrents(RTorrentSettings settings)
item.IsOpen = Convert.ToBoolean((long)torrent[8]); item.IsOpen = Convert.ToBoolean((long)torrent[8]);
item.IsActive = Convert.ToBoolean((long)torrent[9]); item.IsActive = Convert.ToBoolean((long)torrent[9]);
item.IsFinished = Convert.ToBoolean((long)torrent[10]); item.IsFinished = Convert.ToBoolean((long)torrent[10]);
item.FinishedTime = (long)torrent[11];
items.Add(item); items.Add(item);
} }
@ -175,6 +181,18 @@ public void SetTorrentLabel(string hash, string label, RTorrentSettings settings
} }
} }
public void PushTorrentUniqueView(string hash, string view, RTorrentSettings settings)
{
_logger.Debug("Executing remote method: d.views.push_back_unique");
var client = BuildClient(settings);
var response = ExecuteRequest(() => client.PushUniqueView(hash, view));
if (response != 0)
{
throw new DownloadClientException("Could not push unique view {0} for torrent: {1}.", view, hash);
}
}
public void RemoveTorrent(string hash, RTorrentSettings settings) public void RemoveTorrent(string hash, RTorrentSettings settings)
{ {
_logger.Debug("Executing remote method: d.erase"); _logger.Debug("Executing remote method: d.erase");

View File

@ -10,6 +10,7 @@ public class RTorrentTorrent
public long RemainingSize { get; set; } public long RemainingSize { get; set; }
public long DownRate { get; set; } public long DownRate { get; set; }
public long Ratio { get; set; } public long Ratio { get; set; }
public long FinishedTime { get; set; }
public bool IsFinished { get; set; } public bool IsFinished { get; set; }
public bool IsOpen { get; set; } public bool IsOpen { get; set; }
public bool IsActive { get; set; } public bool IsActive { get; set; }

View File

@ -0,0 +1,89 @@
using System;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Download.History;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download
{
public interface IDownloadSeedConfigProvider
{
TorrentSeedConfiguration GetSeedConfiguration(string infoHash);
}
public class DownloadSeedConfigProvider : IDownloadSeedConfigProvider
{
private readonly Logger _logger;
private readonly ISeedConfigProvider _indexerSeedConfigProvider;
private readonly IDownloadHistoryService _downloadHistoryService;
private class CachedSeedConfiguration
{
public int IndexerId { get; set; }
public bool Movie { get; set; }
}
private readonly ICached<CachedSeedConfiguration> _cacheDownloads;
public DownloadSeedConfigProvider(IDownloadHistoryService downloadHistoryService, ISeedConfigProvider indexerSeedConfigProvider, ICacheManager cacheManager, Logger logger)
{
_logger = logger;
_indexerSeedConfigProvider = indexerSeedConfigProvider;
_downloadHistoryService = downloadHistoryService;
_cacheDownloads = cacheManager.GetRollingCache<CachedSeedConfiguration>(GetType(), "indexerByHash", TimeSpan.FromHours(1));
}
public TorrentSeedConfiguration GetSeedConfiguration(string infoHash)
{
if (infoHash.IsNullOrWhiteSpace())
{
return null;
}
infoHash = infoHash.ToUpper();
var cachedConfig = _cacheDownloads.Get(infoHash, () => FetchIndexer(infoHash));
if (cachedConfig == null)
{
return null;
}
var seedConfig = _indexerSeedConfigProvider.GetSeedConfiguration(cachedConfig.IndexerId);
return seedConfig;
}
private CachedSeedConfiguration FetchIndexer(string infoHash)
{
var historyItem = _downloadHistoryService.GetLatestGrab(infoHash);
if (historyItem == null)
{
_logger.Debug("No download history item for infohash {0}, unable to provide seed configuration", infoHash);
return null;
}
ParsedMovieInfo parsedMovieInfo = null;
if (historyItem.Release != null)
{
parsedMovieInfo = Parser.Parser.ParseMovieTitle(historyItem.Release.Title);
}
if (parsedMovieInfo == null)
{
_logger.Debug("No parsed title in download history item for infohash {0}, unable to provide seed configuration", infoHash);
return null;
}
return new CachedSeedConfiguration
{
IndexerId = historyItem.IndexerId,
};
}
}
}

View File

@ -1,6 +1,8 @@
using System; using System;
using NzbDrone.Common.Cache;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.Download.Clients; using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Indexers namespace NzbDrone.Core.Indexers
@ -8,15 +10,18 @@ namespace NzbDrone.Core.Indexers
public interface ISeedConfigProvider public interface ISeedConfigProvider
{ {
TorrentSeedConfiguration GetSeedConfiguration(RemoteMovie release); TorrentSeedConfiguration GetSeedConfiguration(RemoteMovie release);
TorrentSeedConfiguration GetSeedConfiguration(int indexerId);
} }
public class SeedConfigProvider : ISeedConfigProvider public class SeedConfigProvider : ISeedConfigProvider, IHandle<IndexerSettingUpdatedEvent>
{ {
private readonly IIndexerFactory _indexerFactory; private readonly IIndexerFactory _indexerFactory;
private readonly ICached<SeedCriteriaSettings> _cache;
public SeedConfigProvider(IIndexerFactory indexerFactory) public SeedConfigProvider(IIndexerFactory indexerFactory, ICacheManager cacheManager)
{ {
_indexerFactory = indexerFactory; _indexerFactory = indexerFactory;
_cache = cacheManager.GetRollingCache<SeedCriteriaSettings>(GetType(), "criteriaByIndexer", TimeSpan.FromHours(1));
} }
public TorrentSeedConfiguration GetSeedConfiguration(RemoteMovie remoteMovie) public TorrentSeedConfiguration GetSeedConfiguration(RemoteMovie remoteMovie)
@ -31,19 +36,30 @@ public TorrentSeedConfiguration GetSeedConfiguration(RemoteMovie remoteMovie)
return null; return null;
} }
try return GetSeedConfiguration(remoteMovie.Release.IndexerId);
{ }
var indexer = _indexerFactory.Get(remoteMovie.Release.IndexerId);
var torrentIndexerSettings = indexer.Settings as ITorrentIndexerSettings;
if (torrentIndexerSettings != null && torrentIndexerSettings.SeedCriteria != null) public TorrentSeedConfiguration GetSeedConfiguration(int indexerId)
{ {
if (indexerId == 0)
{
return null;
}
var seedCriteria = _cache.Get(indexerId.ToString(), () => FetchSeedCriteria(indexerId));
if (seedCriteria == null)
{
return null;
}
var seedConfig = new TorrentSeedConfiguration var seedConfig = new TorrentSeedConfiguration
{ {
Ratio = torrentIndexerSettings.SeedCriteria.SeedRatio Ratio = seedCriteria.SeedRatio
}; };
var seedTime = torrentIndexerSettings.SeedCriteria.SeedTime; var seedTime = seedCriteria.SeedTime;
if (seedTime.HasValue) if (seedTime.HasValue)
{ {
seedConfig.SeedTime = TimeSpan.FromMinutes(seedTime.Value); seedConfig.SeedTime = TimeSpan.FromMinutes(seedTime.Value);
@ -51,13 +67,25 @@ public TorrentSeedConfiguration GetSeedConfiguration(RemoteMovie remoteMovie)
return seedConfig; return seedConfig;
} }
private SeedCriteriaSettings FetchSeedCriteria(int indexerId)
{
try
{
var indexer = _indexerFactory.Get(indexerId);
var torrentIndexerSettings = indexer.Settings as ITorrentIndexerSettings;
return torrentIndexerSettings?.SeedCriteria;
} }
catch (ModelNotFoundException) catch (ModelNotFoundException)
{ {
return null; return null;
} }
}
return null; public void Handle(IndexerSettingUpdatedEvent message)
{
_cache.Clear();
} }
} }
} }