mirror of
https://github.com/fergalmoran/Readarr.git
synced 2026-01-03 15:23:59 +00:00
New: Removing rtorrent downloads when seeding criteria have been met
(cherry picked from commit 411be4d0116f0739bb9c71235312d0c5a26dd3a2)
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Download;
|
||||
using NzbDrone.Core.Download.Clients;
|
||||
using NzbDrone.Core.Download.Clients.RTorrent;
|
||||
using NzbDrone.Core.MediaFiles.TorrentInfo;
|
||||
|
||||
@@ -92,6 +94,14 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.RTorrentTests
|
||||
{
|
||||
_completed
|
||||
});
|
||||
|
||||
Mocker.GetMock<IDownloadSeedConfigProvider>()
|
||||
.Setup(x => x.GetSeedConfiguration(It.IsAny<string>()))
|
||||
.Returns(new TorrentSeedConfiguration
|
||||
{
|
||||
Ratio = 1.0,
|
||||
SeedTime = TimeSpan.MaxValue
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Threading;
|
||||
using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
@@ -22,6 +23,8 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
{
|
||||
private readonly IRTorrentProxy _proxy;
|
||||
private readonly IRTorrentDirectoryValidator _rTorrentDirectoryValidator;
|
||||
private readonly IDownloadSeedConfigProvider _downloadSeedConfigProvider;
|
||||
private readonly string _imported_view = string.Concat(BuildInfo.AppName.ToLower(), "_imported");
|
||||
|
||||
public RTorrent(IRTorrentProxy proxy,
|
||||
ITorrentFileInfoReader torrentFileInfoReader,
|
||||
@@ -29,17 +32,19 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
IConfigService configService,
|
||||
IDiskProvider diskProvider,
|
||||
IRemotePathMappingService remotePathMappingService,
|
||||
IDownloadSeedConfigProvider downloadSeedConfigProvider,
|
||||
IRTorrentDirectoryValidator rTorrentDirectoryValidator,
|
||||
Logger logger)
|
||||
: base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger)
|
||||
{
|
||||
_proxy = proxy;
|
||||
_rTorrentDirectoryValidator = rTorrentDirectoryValidator;
|
||||
_downloadSeedConfigProvider = downloadSeedConfigProvider;
|
||||
}
|
||||
|
||||
public override void MarkItemAsImported(DownloadClientItem downloadClientItem)
|
||||
{
|
||||
// set post-import category
|
||||
// Set post-import label
|
||||
if (Settings.MusicImportedCategory.IsNotNullOrWhiteSpace() &&
|
||||
Settings.MusicImportedCategory != Settings.MusicCategory)
|
||||
{
|
||||
@@ -55,6 +60,19 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
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(RemoteBook remoteBook, string hash, string magnetLink)
|
||||
@@ -97,7 +115,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
|
||||
public override string Name => "rTorrent";
|
||||
|
||||
public override ProviderMessage Message => new ProviderMessage("Readarr is unable to remove torrents that have finished seeding when using rTorrent", ProviderMessageType.Warning);
|
||||
public override ProviderMessage Message => new ProviderMessage($"Readarr 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()
|
||||
{
|
||||
@@ -152,8 +170,14 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
item.Status = DownloadItemStatus.Paused;
|
||||
}
|
||||
|
||||
// No stop ratio data is present, so do not delete
|
||||
item.CanMoveFiles = item.CanBeRemoved = false;
|
||||
// Grab cached seedConfig
|
||||
var seedConfig = _downloadSeedConfigProvider.GetSeedConfiguration(torrent.Hash);
|
||||
|
||||
// Check if torrent is finished and if it exceeds cached seedConfig
|
||||
item.CanMoveFiles = item.CanBeRemoved =
|
||||
torrent.IsFinished &&
|
||||
((torrent.Ratio / 1000.0) >= seedConfig.Ratio ||
|
||||
(DateTimeOffset.Now - DateTimeOffset.FromUnixTimeSeconds(torrent.FinishedTime)) >= seedConfig.SeedTime);
|
||||
|
||||
items.Add(item);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
void RemoveTorrent(string hash, RTorrentSettings settings);
|
||||
void SetTorrentLabel(string hash, string label, RTorrentSettings settings);
|
||||
bool HasHashTorrent(string hash, RTorrentSettings settings);
|
||||
void PushTorrentUniqueView(string hash, string view, RTorrentSettings settings);
|
||||
}
|
||||
|
||||
public interface IRTorrent : IXmlRpcProxy
|
||||
@@ -46,6 +47,9 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
[XmlRpcMethod("d.custom1.set")]
|
||||
string SetLabel(string hash, string label);
|
||||
|
||||
[XmlRpcMethod("d.views.push_back_unique")]
|
||||
int PushUniqueView(string hash, string view);
|
||||
|
||||
[XmlRpcMethod("system.client_version")]
|
||||
string GetVersion();
|
||||
}
|
||||
@@ -87,7 +91,8 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
"d.ratio=", // long
|
||||
"d.is_open=", // long
|
||||
"d.is_active=", // long
|
||||
"d.complete=")); //long
|
||||
"d.complete=", // long
|
||||
"d.timestamp.finished=")); // long (unix timestamp)
|
||||
|
||||
var items = new List<RTorrentTorrent>();
|
||||
|
||||
@@ -107,6 +112,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
item.IsOpen = Convert.ToBoolean((long)torrent[8]);
|
||||
item.IsActive = Convert.ToBoolean((long)torrent[9]);
|
||||
item.IsFinished = Convert.ToBoolean((long)torrent[10]);
|
||||
item.FinishedTime = (long)torrent[11];
|
||||
|
||||
items.Add(item);
|
||||
}
|
||||
@@ -173,6 +179,18 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
_logger.Debug("Executing remote method: d.erase");
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
public long RemainingSize { get; set; }
|
||||
public long DownRate { get; set; }
|
||||
public long Ratio { get; set; }
|
||||
public long FinishedTime { get; set; }
|
||||
public bool IsFinished { get; set; }
|
||||
public bool IsOpen { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
92
src/NzbDrone.Core/Download/DownloadSeedConfigProvider.cs
Normal file
92
src/NzbDrone.Core/Download/DownloadSeedConfigProvider.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Download.Clients;
|
||||
using NzbDrone.Core.Download.History;
|
||||
using NzbDrone.Core.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;
|
||||
|
||||
public class CachedSeedConfiguration
|
||||
{
|
||||
public int IndexerId { get; set; }
|
||||
public bool Discography { 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, cachedConfig.Discography);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ParsedBookInfo parsedBookInfo = null;
|
||||
if (historyItem.SourceTitle != null)
|
||||
{
|
||||
parsedBookInfo = Parser.Parser.ParseBookTitle(historyItem.Release.Title);
|
||||
}
|
||||
|
||||
if (parsedBookInfo == 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,
|
||||
Discography = parsedBookInfo.Discography
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Books.Events;
|
||||
@@ -13,6 +14,7 @@ namespace NzbDrone.Core.Download.History
|
||||
{
|
||||
bool DownloadAlreadyImported(string downloadId);
|
||||
DownloadHistory GetLatestDownloadHistoryItem(string downloadId);
|
||||
DownloadHistory GetLatestGrab(string downloadId);
|
||||
}
|
||||
|
||||
public class DownloadHistoryService : IDownloadHistoryService,
|
||||
@@ -91,6 +93,12 @@ namespace NzbDrone.Core.Download.History
|
||||
return null;
|
||||
}
|
||||
|
||||
public DownloadHistory GetLatestGrab(string downloadId)
|
||||
{
|
||||
return _repository.FindByDownloadId(downloadId)
|
||||
.FirstOrDefault(d => d.EventType == DownloadHistoryEventType.DownloadGrabbed);
|
||||
}
|
||||
|
||||
public void Handle(BookGrabbedEvent message)
|
||||
{
|
||||
// Don't store grabbed events for clients that don't download IDs
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Download.Clients;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.Indexers
|
||||
@@ -8,15 +10,18 @@ namespace NzbDrone.Core.Indexers
|
||||
public interface ISeedConfigProvider
|
||||
{
|
||||
TorrentSeedConfiguration GetSeedConfiguration(RemoteBook release);
|
||||
TorrentSeedConfiguration GetSeedConfiguration(int indexerId, bool fullSeason);
|
||||
}
|
||||
|
||||
public class SeedConfigProvider : ISeedConfigProvider
|
||||
public class SeedConfigProvider : ISeedConfigProvider, IHandle<IndexerSettingUpdatedEvent>
|
||||
{
|
||||
private readonly IIndexerFactory _indexerFactory;
|
||||
private readonly ICached<SeedCriteriaSettings> _cache;
|
||||
|
||||
public SeedConfigProvider(IIndexerFactory indexerFactory)
|
||||
public SeedConfigProvider(IIndexerFactory indexerFactory, ICacheManager cacheManager)
|
||||
{
|
||||
_indexerFactory = indexerFactory;
|
||||
_cache = cacheManager.GetRollingCache<SeedCriteriaSettings>(GetType(), "criteriaByIndexer", TimeSpan.FromHours(1));
|
||||
}
|
||||
|
||||
public TorrentSeedConfiguration GetSeedConfiguration(RemoteBook remoteBook)
|
||||
@@ -31,33 +36,55 @@ namespace NzbDrone.Core.Indexers
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetSeedConfiguration(remoteBook.Release.IndexerId, remoteBook.ParsedBookInfo.Discography);
|
||||
}
|
||||
|
||||
public TorrentSeedConfiguration GetSeedConfiguration(int indexerId, bool fullSeason)
|
||||
{
|
||||
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 = fullSeason ? seedCriteria.DiscographySeedTime : seedCriteria.SeedTime;
|
||||
if (seedTime.HasValue)
|
||||
{
|
||||
seedConfig.SeedTime = TimeSpan.FromMinutes(seedTime.Value);
|
||||
}
|
||||
|
||||
return seedConfig;
|
||||
}
|
||||
|
||||
private SeedCriteriaSettings FetchSeedCriteria(int indexerId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var indexer = _indexerFactory.Get(remoteBook.Release.IndexerId);
|
||||
var indexer = _indexerFactory.Get(indexerId);
|
||||
var torrentIndexerSettings = indexer.Settings as ITorrentIndexerSettings;
|
||||
|
||||
if (torrentIndexerSettings != null && torrentIndexerSettings.SeedCriteria != null)
|
||||
{
|
||||
var seedConfig = new TorrentSeedConfiguration
|
||||
{
|
||||
Ratio = torrentIndexerSettings.SeedCriteria.SeedRatio
|
||||
};
|
||||
|
||||
var seedTime = remoteBook.ParsedBookInfo.Discography ? torrentIndexerSettings.SeedCriteria.DiscographySeedTime : torrentIndexerSettings.SeedCriteria.SeedTime;
|
||||
if (seedTime.HasValue)
|
||||
{
|
||||
seedConfig.SeedTime = TimeSpan.FromMinutes(seedTime.Value);
|
||||
}
|
||||
|
||||
return seedConfig;
|
||||
}
|
||||
return torrentIndexerSettings?.SeedCriteria;
|
||||
}
|
||||
catch (ModelNotFoundException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
public void Handle(IndexerSettingUpdatedEvent message)
|
||||
{
|
||||
_cache.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user