From 1b65ead75ddf0d923369373b868e6166c34150dd Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Wed, 8 Jul 2015 01:11:33 -0700 Subject: [PATCH] New: Choose download folder for rTorrent Closes #626 --- .../Download/Clients/rTorrent/RTorrent.cs | 47 ++++++-- .../rTorrent/RTorrentDirectoryValidator.cs | 29 +++++ .../Clients/rTorrent/RTorrentProxy.cs | 105 +++++++++++++----- .../Clients/rTorrent/RTorrentSettings.cs | 11 +- src/NzbDrone.Core/NzbDrone.Core.csproj | 1 + 5 files changed, 152 insertions(+), 41 deletions(-) create mode 100644 src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentDirectoryValidator.cs diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs index 3abf50d8c..523d2033e 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrent.cs @@ -10,6 +10,7 @@ using NzbDrone.Core.MediaFiles.TorrentInfo; using NLog; using NzbDrone.Core.Validation; using FluentValidation.Results; +using NzbDrone.Core.Download.Clients.rTorrent; using NzbDrone.Core.Parser.Model; using NzbDrone.Core.RemotePathMappings; using NzbDrone.Core.ThingiProvider; @@ -19,6 +20,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent public class RTorrent : TorrentClientBase { private readonly IRTorrentProxy _proxy; + private readonly IRTorrentDirectoryValidator _rTorrentDirectoryValidator; public RTorrent(IRTorrentProxy proxy, ITorrentFileInfoReader torrentFileInfoReader, @@ -26,10 +28,12 @@ namespace NzbDrone.Core.Download.Clients.RTorrent IConfigService configService, IDiskProvider diskProvider, IRemotePathMappingService remotePathMappingService, + IRTorrentDirectoryValidator rTorrentDirectoryValidator, Logger logger) : base(torrentFileInfoReader, httpClient, configService, diskProvider, remotePathMappingService, logger) { _proxy = proxy; + _rTorrentDirectoryValidator = rTorrentDirectoryValidator; } protected override string AddFromMagnetLink(RemoteEpisode remoteEpisode, string hash, string magnetLink) @@ -40,6 +44,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent var TRIES = 5; var RETRY_DELAY = 500; //ms var ready = false; + for (var i = 0; i < TRIES; i++) { ready = _proxy.HasHashTorrent(hash, Settings); @@ -55,9 +60,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent { _proxy.SetTorrentLabel(hash, Settings.TvCategory, Settings); - var priority = (RTorrentPriority)(remoteEpisode.IsRecentEpisode() ? - Settings.RecentTvPriority : Settings.OlderTvPriority); - _proxy.SetTorrentPriority(hash, Settings, priority); + SetPriority(remoteEpisode, hash); + SetDownloadDirectory(hash); + + _proxy.StartTorrent(hash, Settings); return hash; } @@ -74,12 +80,12 @@ namespace NzbDrone.Core.Download.Clients.RTorrent protected override string AddFromTorrentFile(RemoteEpisode remoteEpisode, string hash, string filename, byte[] fileContent) { _proxy.AddTorrentFromFile(filename, fileContent, Settings); - _proxy.SetTorrentLabel(hash, Settings.TvCategory, Settings); - var priority = (RTorrentPriority)(remoteEpisode.IsRecentEpisode() ? - Settings.RecentTvPriority : Settings.OlderTvPriority); - _proxy.SetTorrentPriority(hash, Settings, priority); + SetPriority(remoteEpisode, hash); + SetDownloadDirectory(hash); + + _proxy.StartTorrent(hash, Settings); return hash; } @@ -182,6 +188,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent failures.AddIfNotNull(TestConnection()); if (failures.Any()) return; failures.AddIfNotNull(TestGetTorrents()); + failures.AddIfNotNull(TestDirectory()); } private ValidationFailure TestConnection() @@ -218,5 +225,31 @@ namespace NzbDrone.Core.Download.Clients.RTorrent return null; } + + private ValidationFailure TestDirectory() + { + var result = _rTorrentDirectoryValidator.Validate(Settings); + + if (result.IsValid) + { + return null; + } + + 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); + } + } } } diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentDirectoryValidator.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentDirectoryValidator.cs new file mode 100644 index 000000000..37ff3d71a --- /dev/null +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentDirectoryValidator.cs @@ -0,0 +1,29 @@ +using FluentValidation; +using FluentValidation.Results; +using NzbDrone.Core.Download.Clients.RTorrent; +using NzbDrone.Core.Validation.Paths; + +namespace NzbDrone.Core.Download.Clients.rTorrent +{ + public interface IRTorrentDirectoryValidator + { + ValidationResult Validate(RTorrentSettings instance); + } + + public class RTorrentDirectoryValidator : AbstractValidator, IRTorrentDirectoryValidator + { + public RTorrentDirectoryValidator(RootFolderValidator rootFolderValidator, + PathExistsValidator pathExistsValidator, + DroneFactoryValidator droneFactoryValidator, + MappedNetworkDriveValidator mappedNetworkDriveValidator) + { + RuleFor(c => c.TvDirectory).Cascade(CascadeMode.StopOnFirstFailure) + .IsValidPath() + .SetValidator(rootFolderValidator) + .SetValidator(droneFactoryValidator) + .SetValidator(mappedNetworkDriveValidator) + .SetValidator(pathExistsValidator) + .When(c => c.Host == "localhost" || c.Host == "127.0.0.1"); + } + } +} diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs index 3afb95b24..1ac0032d1 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentProxy.cs @@ -16,9 +16,11 @@ namespace NzbDrone.Core.Download.Clients.RTorrent void AddTorrentFromUrl(string torrentUrl, RTorrentSettings settings); void AddTorrentFromFile(string fileName, byte[] fileContent, RTorrentSettings settings); void RemoveTorrent(string hash, RTorrentSettings settings); - void SetTorrentPriority(string hash, RTorrentSettings settings, RTorrentPriority priority); + 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); } public interface IRTorrent : IXmlRpcProxy @@ -26,10 +28,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent [XmlRpcMethod("d.multicall2")] object[] TorrentMulticall(params string[] parameters); - [XmlRpcMethod("load.start")] - int LoadURL(string target, string data); + [XmlRpcMethod("load.normal")] + int LoadUrl(string target, string data); - [XmlRpcMethod("load.raw_start")] + [XmlRpcMethod("load.raw")] int LoadBinary(string target, byte[] data); [XmlRpcMethod("d.erase")] @@ -41,11 +43,17 @@ namespace NzbDrone.Core.Download.Clients.RTorrent [XmlRpcMethod("d.priority.set")] int SetPriority(string hash, long priority); + [XmlRpcMethod("d.directory.set")] + int SetDirectory(string hash, string directory); + [XmlRpcMethod("d.name")] string GetName(string hash); [XmlRpcMethod("system.client_version")] string GetVersion(); + + [XmlRpcMethod("system.multicall")] + object[] SystemMulticall(object[] parameters); } public class RTorrentProxy : IRTorrentProxy @@ -108,32 +116,13 @@ namespace NzbDrone.Core.Download.Clients.RTorrent return items; } - public bool HasHashTorrent(string hash, RTorrentSettings settings) - { - _logger.Debug("Executing remote method: d.name"); - - var client = BuildClient(settings); - - try - { - var name = client.GetName(hash); - if (name.IsNullOrWhiteSpace()) return false; - bool metaTorrent = name == (hash + ".meta"); - return !metaTorrent; - } - catch (Exception) - { - return false; - } - } - public void AddTorrentFromUrl(string torrentUrl, RTorrentSettings settings) { - _logger.Debug("Executing remote method: load.start"); + _logger.Debug("Executing remote method: load.normal"); var client = BuildClient(settings); - var response = client.LoadURL("", torrentUrl); + var response = client.LoadUrl("", torrentUrl); if (response != 0) { throw new DownloadClientException("Could not add torrent: {0}.", torrentUrl); @@ -142,7 +131,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent public void AddTorrentFromFile(string fileName, Byte[] fileContent, RTorrentSettings settings) { - _logger.Debug("Executing remote method: load.raw_start"); + _logger.Debug("Executing remote method: load.raw"); var client = BuildClient(settings); @@ -166,7 +155,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent } } - public void SetTorrentPriority(string hash, RTorrentSettings settings, RTorrentPriority priority) + public void SetTorrentPriority(string hash, RTorrentPriority priority, RTorrentSettings settings) { _logger.Debug("Executing remote method: d.priority.set"); @@ -185,13 +174,71 @@ namespace NzbDrone.Core.Download.Clients.RTorrent var client = BuildClient(settings); - var satLabel = client.SetLabel(hash, label); - if (satLabel != label) + var setLabel = client.SetLabel(hash, label); + if (setLabel != label) { 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 bool HasHashTorrent(string hash, RTorrentSettings settings) + { + _logger.Debug("Executing remote method: d.name"); + + var client = BuildClient(settings); + + try + { + var name = client.GetName(hash); + if (name.IsNullOrWhiteSpace()) return false; + bool metaTorrent = name == (hash + ".meta"); + return !metaTorrent; + } + catch (Exception) + { + return false; + } + } + + 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(); diff --git a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs index 392c0d042..4b38fd73d 100644 --- a/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs +++ b/src/NzbDrone.Core/Download/Clients/rTorrent/RTorrentSettings.cs @@ -1,6 +1,4 @@ -using System; -using FluentValidation; -using FluentValidation.Results; +using FluentValidation; using NzbDrone.Core.Annotations; using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.Validation; @@ -52,10 +50,13 @@ namespace NzbDrone.Core.Download.Clients.RTorrent [FieldDefinition(6, Label = "Category", Type = FieldType.Textbox, HelpText = "Adding a category specific to Sonarr avoids conflicts with unrelated downloads, but it's optional.")] public string TvCategory { get; set; } - [FieldDefinition(7, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] + [FieldDefinition(7, Label = "Directory", Type = FieldType.Textbox, Advanced = true, HelpText = "Optional location to put downloads in, leave blank to use the default rTorrent location")] + public string TvDirectory { get; set; } + + [FieldDefinition(8, Label = "Recent Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired within the last 14 days")] public int RecentTvPriority { get; set; } - [FieldDefinition(8, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] + [FieldDefinition(9, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing episodes that aired over 14 days ago")] public int OlderTvPriority { get; set; } public NzbDroneValidationResult Validate() diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index cbcf84be4..5aff47579 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -347,6 +347,7 @@ +