From 2326db0deabb387a1020466ae66e8cdbe5e6c2c2 Mon Sep 17 00:00:00 2001 From: Taloth Saldono Date: Sat, 11 Mar 2017 11:50:19 +0100 Subject: [PATCH] Fixed: Refactored rtorrent interface to fix reliability issues with adding magnets & torrents. fixes #1745 --- .../RTorrentTests/RTorrentFixture.cs | 4 +- .../Download/Clients/rTorrent/RTorrent.cs | 57 +------ .../Clients/rTorrent/RTorrentProxy.cs | 152 +++--------------- 3 files changed, 29 insertions(+), 184 deletions(-) diff --git a/src/NzbDrone.Core.Test/Download/DownloadClientTests/RTorrentTests/RTorrentFixture.cs b/src/NzbDrone.Core.Test/Download/DownloadClientTests/RTorrentTests/RTorrentFixture.cs index f657a7884..1f5a2713e 100644 --- a/src/NzbDrone.Core.Test/Download/DownloadClientTests/RTorrentTests/RTorrentFixture.cs +++ b/src/NzbDrone.Core.Test/Download/DownloadClientTests/RTorrentTests/RTorrentFixture.cs @@ -54,11 +54,11 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.RTorrentTests protected void GivenSuccessfulDownload() { Mocker.GetMock() - .Setup(s => s.AddTorrentFromUrl(It.IsAny(), It.IsAny())) + .Setup(s => s.AddTorrentFromUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback(PrepareClientToReturnCompletedItem); Mocker.GetMock() - .Setup(s => s.AddTorrentFromFile(It.IsAny(), It.IsAny(), It.IsAny())) + .Setup(s => s.AddTorrentFromFile(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Callback(PrepareClientToReturnCompletedItem); diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs index c9ab52c19..ee384fc92 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs @@ -39,50 +39,29 @@ namespace NzbDrone.Core.Download.Clients.RTorrent protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink) { - _proxy.AddTorrentFromUrl(magnetLink, Settings); + var priority = (RTorrentPriority)(remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority); - // Download the magnet to the appropriate directory. - _proxy.SetTorrentLabel(hash, Settings.TvCategory, Settings); - SetPriority(remoteEpisode, hash); - SetDownloadDirectory(hash); - - _proxy.StartTorrent(hash, Settings); + _proxy.AddTorrentFromUrl(magnetLink, Settings.TvCategory, priority, Settings.TvDirectory, Settings); var tries = 10; var retryDelay = 500; // Wait a bit for the magnet to be resolved. - if (!WaitForTorrent(hash, tries - 2, retryDelay)) + if (!WaitForTorrent(hash, tries, retryDelay)) { - // Once the magnet meta download finishes, rTorrent replaces it with the actual torrent download with default settings. - // Schedule an event to apply the appropriate settings when that happens. - var priority = (RTorrentPriority)(remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority); - _proxy.SetDeferredMagnetProperties(hash, Settings.TvCategory, Settings.TvDirectory, priority, Settings); + _logger.Warn("rTorrent could not resolve magnet within {0} seconds, download may remain stuck: {1}.", tries * retryDelay / 1000, magnetLink); - // Wait a bit longer for the magnet to be resolved. - if (!WaitForTorrent(hash, 2, retryDelay)) - { - // The SetDeferredMagnetProperties will try set the label, priority & directory when rtorrent finishes the magnet, unless rtorrent restarts before that happens. - - _logger.Warn("rTorrent could not resolve magnet within {0} seconds, download may remain stuck: {1}.", tries * retryDelay / 1000, magnetLink); - - return hash; - } + return hash; } - // Make sure the label is set and the torrent started, because we can't rely on SetDeferredMagnetProperties for magnets that get resolved quickly. - _proxy.SetTorrentLabel(hash, Settings.TvCategory, Settings); - SetPriority(remoteEpisode, hash); - SetDownloadDirectory(hash); - - _proxy.StartTorrent(hash, Settings); - return hash; } protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent) { - _proxy.AddTorrentFromFile(filename, fileContent, Settings); + var priority = (RTorrentPriority)(remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority); + + _proxy.AddTorrentFromFile(filename, fileContent, Settings.TvCategory, priority, Settings.TvDirectory, Settings); var tries = 10; var retryDelay = 500; @@ -93,12 +72,6 @@ namespace NzbDrone.Core.Download.Clients.RTorrent throw new ReleaseDownloadException(remoteEpisode.Release, "Downloading torrent failed"); } - _proxy.SetTorrentLabel(hash, Settings.TvCategory, Settings); - SetPriority(remoteEpisode, hash); - SetDownloadDirectory(hash); - - _proxy.StartTorrent(hash, Settings); - return hash; } @@ -238,20 +211,6 @@ namespace NzbDrone.Core.Download.Clients.RTorrent return result.Errors.First(); } - private void SetPriority(RemoteEpisode remoteEpisode, string hash) - { - var priority = (RTorrentPriority)(remoteEpisode.IsRecentEpisode() ? Settings.RecentTvPriority : Settings.OlderTvPriority); - _proxy.SetTorrentPriority(hash, priority, Settings); - } - - private void SetDownloadDirectory(string hash) - { - if (Settings.TvDirectory.IsNotNullOrWhiteSpace()) - { - _proxy.SetTorrentDownloadDirectory(hash, Settings.TvDirectory, Settings); - } - } - private bool WaitForTorrent(string hash, int tries, int retryDelay) { for (var i = 0; i < tries; i++) diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs index a94b6fb1e..e22b9d469 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs @@ -13,15 +13,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent string GetVersion(RTorrentSettings settings); List GetTorrents(RTorrentSettings settings); - void AddTorrentFromUrl(string torrentUrl, RTorrentSettings settings); - void AddTorrentFromFile(string fileName, byte[] fileContent, RTorrentSettings settings); + void AddTorrentFromUrl(string torrentUrl, string label, RTorrentPriority priority, string directory, RTorrentSettings settings); + void AddTorrentFromFile(string fileName, byte[] fileContent, string label, RTorrentPriority priority, string directory, RTorrentSettings settings); void RemoveTorrent(string hash, RTorrentSettings settings); - void SetTorrentPriority(string hash, RTorrentPriority priority, RTorrentSettings settings); - void SetTorrentLabel(string hash, string label, RTorrentSettings settings); - void SetTorrentDownloadDirectory(string hash, string directory, RTorrentSettings settings); bool HasHashTorrent(string hash, RTorrentSettings settings); - void StartTorrent(string hash, RTorrentSettings settings); - void SetDeferredMagnetProperties(string hash, string category, string directory, RTorrentPriority priority, RTorrentSettings settings); } public interface IRTorrent : IXmlRpcProxy @@ -29,35 +24,20 @@ namespace NzbDrone.Core.Download.Clients.RTorrent [XmlRpcMethod("d.multicall2")] object[] TorrentMulticall(params string[] parameters); - [XmlRpcMethod("load.normal")] - int LoadUrl(string target, string data); + [XmlRpcMethod("load.start")] + int LoadStart(string target, string data, params string[] commands); - [XmlRpcMethod("load.raw")] - int LoadBinary(string target, byte[] data); + [XmlRpcMethod("load.raw_start")] + int LoadRawStart(string target, byte[] data, params string[] commands); [XmlRpcMethod("d.erase")] int Remove(string hash); - [XmlRpcMethod("d.custom1.set")] - string SetLabel(string hash, string label); - - [XmlRpcMethod("d.priority.set")] - int SetPriority(string hash, long priority); - - [XmlRpcMethod("d.directory.set")] - int SetDirectory(string hash, string directory); - - [XmlRpcMethod("method.set_key")] - int SetKey(string target, string key, string cmd_key, string value); - [XmlRpcMethod("d.name")] string GetName(string hash); [XmlRpcMethod("system.client_version")] string GetVersion(); - - [XmlRpcMethod("system.multicall")] - object[] SystemMulticall(object[] parameters); } public class RTorrentProxy : IRTorrentProxy @@ -122,26 +102,26 @@ namespace NzbDrone.Core.Download.Clients.RTorrent return items; } - public void AddTorrentFromUrl(string torrentUrl, RTorrentSettings settings) + public void AddTorrentFromUrl(string torrentUrl, string label, RTorrentPriority priority, string directory, RTorrentSettings settings) { _logger.Debug("Executing remote method: load.normal"); var client = BuildClient(settings); - var response = client.LoadUrl("", torrentUrl); + var response = client.LoadStart("", torrentUrl, GetCommands(label, priority, directory)); if (response != 0) { throw new DownloadClientException("Could not add torrent: {0}.", torrentUrl); } } - public void AddTorrentFromFile(string fileName, byte[] fileContent, RTorrentSettings settings) + public void AddTorrentFromFile(string fileName, byte[] fileContent, string label, RTorrentPriority priority, string directory, RTorrentSettings settings) { _logger.Debug("Executing remote method: load.raw"); var client = BuildClient(settings); - var response = client.LoadBinary("", fileContent); + var response = client.LoadRawStart("", fileContent, GetCommands(label, priority, directory)); if (response != 0) { throw new DownloadClientException("Could not add torrent: {0}.", fileName); @@ -161,94 +141,26 @@ namespace NzbDrone.Core.Download.Clients.RTorrent } } - public void SetTorrentPriority(string hash, RTorrentPriority priority, RTorrentSettings settings) + private string[] GetCommands(string label, RTorrentPriority priority, string directory) { - _logger.Debug("Executing remote method: d.priority.set"); + var result = new List(); - var client = BuildClient(settings); - - var response = client.SetPriority(hash, (long) priority); - if (response != 0) + if (label.IsNotNullOrWhiteSpace()) { - throw new DownloadClientException("Could not set priority on torrent: {0}.", hash); - } - } - - public void SetTorrentLabel(string hash, string label, RTorrentSettings settings) - { - _logger.Debug("Executing remote method: d.custom1.set"); - - var labelEncoded = System.Web.HttpUtility.UrlEncode(label); - - var client = BuildClient(settings); - - var setLabel = client.SetLabel(hash, labelEncoded); - if (setLabel != labelEncoded) - { - throw new DownloadClientException("Could set label on torrent: {0}.", hash); - } - } - - public void SetTorrentDownloadDirectory(string hash, string directory, RTorrentSettings settings) - { - _logger.Debug("Executing remote method: d.directory.set"); - - var client = BuildClient(settings); - - var response = client.SetDirectory(hash, directory); - if (response != 0) - { - throw new DownloadClientException("Could not set directory for torrent: {0}.", hash); - } - } - - public void SetDeferredMagnetProperties(string hash, string category, string directory, RTorrentPriority priority, RTorrentSettings settings) - { - var commands = new List(); - - if (category.IsNotNullOrWhiteSpace()) - { - commands.Add("d.custom1.set=" + category); - } - - if (directory.IsNotNullOrWhiteSpace()) - { - commands.Add("d.directory.set=" + directory); + result.Add("d.custom1.set=" + label); } if (priority != RTorrentPriority.Normal) { - commands.Add("d.priority.set=" + (long)priority); + result.Add("d.priority.set=" + (int)priority); } - // Ensure it gets started if the user doesn't have schedule=...,start_tied= - commands.Add("d.open="); - commands.Add("d.start="); - - if (commands.Any()) + if (directory.IsNotNullOrWhiteSpace()) { - var key = "event.download.inserted_new"; - var cmd_key = "sonarr_deferred_" + hash; - - commands.Add(string.Format("print=\"Applying deferred properties to {0}\"", hash)); - - // Remove event handler once triggered. - commands.Add(string.Format("\"method.set_key={0},{1}\"", key, cmd_key)); - - var setKeyValue = string.Format("branch=\"equal=d.hash=,cat={0}\",{{{1}}}", hash, string.Join(",", commands)); - - _logger.Debug("Executing remote method: method.set_key = {0},{1},{2}", key, cmd_key, setKeyValue); - - var client = BuildClient(settings); - - // Commands need a target, in this case the target is an empty string - // See: https://github.com/rakshasa/rtorrent/issues/227 - var response = client.SetKey("", key, cmd_key, setKeyValue); - if (response != 0) - { - throw new DownloadClientException("Could set properties for torrent: {0}.", hash); - } + result.Add("d.directory.set=" + label); } + + return result.ToArray(); } public bool HasHashTorrent(string hash, RTorrentSettings settings) @@ -270,32 +182,6 @@ namespace NzbDrone.Core.Download.Clients.RTorrent } } - public void StartTorrent(string hash, RTorrentSettings settings) - { - _logger.Debug("Executing remote methods: d.open and d.start"); - - var client = BuildClient(settings); - - var multicallResponse = client.SystemMulticall(new[] - { - new - { - methodName = "d.open", - @params = new[] { hash } - }, - new - { - methodName = "d.start", - @params = new[] { hash } - }, - }).SelectMany(c => ((IEnumerable)c)); - - if (multicallResponse.Any(r => r != 0)) - { - throw new DownloadClientException("Could not start torrent: {0}.", hash); - } - } - private IRTorrent BuildClient(RTorrentSettings settings) { var client = XmlRpcProxyGen.Create();