mirror of
https://github.com/Radarr/Radarr.git
synced 2024-11-04 10:02:40 +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:
parent
dd80a64560
commit
1c0621af0a
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
@ -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; }
|
||||||
|
89
src/NzbDrone.Core/Download/DownloadSeedConfigProvider.cs
Normal file
89
src/NzbDrone.Core/Download/DownloadSeedConfigProvider.cs
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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,33 +36,56 @@ public TorrentSeedConfiguration GetSeedConfiguration(RemoteMovie remoteMovie)
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return GetSeedConfiguration(remoteMovie.Release.IndexerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
Ratio = seedCriteria.SeedRatio
|
||||||
|
};
|
||||||
|
|
||||||
|
var seedTime = seedCriteria.SeedTime;
|
||||||
|
|
||||||
|
if (seedTime.HasValue)
|
||||||
|
{
|
||||||
|
seedConfig.SeedTime = TimeSpan.FromMinutes(seedTime.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return seedConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SeedCriteriaSettings FetchSeedCriteria(int indexerId)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var indexer = _indexerFactory.Get(remoteMovie.Release.IndexerId);
|
var indexer = _indexerFactory.Get(indexerId);
|
||||||
var torrentIndexerSettings = indexer.Settings as ITorrentIndexerSettings;
|
var torrentIndexerSettings = indexer.Settings as ITorrentIndexerSettings;
|
||||||
|
|
||||||
if (torrentIndexerSettings != null && torrentIndexerSettings.SeedCriteria != null)
|
return torrentIndexerSettings?.SeedCriteria;
|
||||||
{
|
|
||||||
var seedConfig = new TorrentSeedConfiguration
|
|
||||||
{
|
|
||||||
Ratio = torrentIndexerSettings.SeedCriteria.SeedRatio
|
|
||||||
};
|
|
||||||
|
|
||||||
var seedTime = torrentIndexerSettings.SeedCriteria.SeedTime;
|
|
||||||
if (seedTime.HasValue)
|
|
||||||
{
|
|
||||||
seedConfig.SeedTime = TimeSpan.FromMinutes(seedTime.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return seedConfig;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (ModelNotFoundException)
|
catch (ModelNotFoundException)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
public void Handle(IndexerSettingUpdatedEvent message)
|
||||||
|
{
|
||||||
|
_cache.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user