using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Xml.Linq; using System.Xml.XPath; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Core.Download.Extensions; namespace NzbDrone.Core.Download.Clients.RTorrent { public interface IRTorrentProxy { string GetVersion(RTorrentSettings settings); List GetTorrents(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 SetTorrentLabel(string hash, string label, RTorrentSettings settings); bool HasHashTorrent(string hash, RTorrentSettings settings); void PushTorrentUniqueView(string hash, string view, RTorrentSettings settings); } public class RTorrentProxy : IRTorrentProxy { private readonly IHttpClient _httpClient; public RTorrentProxy(IHttpClient httpClient) { _httpClient = httpClient; } public string GetVersion(RTorrentSettings settings) { var document = ExecuteRequest(settings, "system.client_version"); return document.Descendants("string").FirstOrDefault()?.Value ?? "0.0.0"; } public List GetTorrents(RTorrentSettings settings) { var document = ExecuteRequest(settings, "d.multicall2", "", "", "d.name=", // string "d.hash=", // string "d.base_path=", // string "d.custom1=", // string (label) "d.size_bytes=", // long "d.left_bytes=", // long "d.down.rate=", // long (in bytes / s) "d.ratio=", // long "d.is_open=", // long "d.is_active=", // long "d.complete=", // long "d.timestamp.finished="); // long (unix timestamp) var torrents = document.XPathSelectElement("./methodResponse/params/param/value/array/data") ?.Elements() .Select(x => new RTorrentTorrent(x)) .ToList() ?? new List(); return torrents; } public void AddTorrentFromUrl(string torrentUrl, string label, RTorrentPriority priority, string directory, RTorrentSettings settings) { var args = new List { "", torrentUrl }; args.AddRange(GetCommands(label, priority, directory)); XDocument response; if (settings.AddStopped) { response = ExecuteRequest(settings, "load.normal", args.ToArray()); } else { response = ExecuteRequest(settings, "load.start", args.ToArray()); } if (response.GetIntResponse() != 0) { throw new DownloadClientException("Could not add torrent: {0}.", torrentUrl); } } public void AddTorrentFromFile(string fileName, byte[] fileContent, string label, RTorrentPriority priority, string directory, RTorrentSettings settings) { var args = new List { "", fileContent }; args.AddRange(GetCommands(label, priority, directory)); XDocument response; if (settings.AddStopped) { response = ExecuteRequest(settings, "load.raw", args.ToArray()); } else { response = ExecuteRequest(settings, "load.raw_start", args.ToArray()); } if (response.GetIntResponse() != 0) { throw new DownloadClientException("Could not add torrent: {0}.", fileName); } } public void SetTorrentLabel(string hash, string label, RTorrentSettings settings) { var response = ExecuteRequest(settings, "d.custom1.set", hash, label); if (response.GetStringResponse() != label) { throw new DownloadClientException("Could not set label to {1} for torrent: {0}.", hash, label); } } public void PushTorrentUniqueView(string hash, string view, RTorrentSettings settings) { var response = ExecuteRequest(settings, "d.views.push_back_unique", hash, view); if (response.GetIntResponse() != 0) { throw new DownloadClientException("Could not push unique view {0} for torrent: {1}.", view, hash); } } public void RemoveTorrent(string hash, RTorrentSettings settings) { var response = ExecuteRequest(settings, "d.erase", hash); if (response.GetIntResponse() != 0) { throw new DownloadClientException("Could not remove torrent: {0}.", hash); } } public bool HasHashTorrent(string hash, RTorrentSettings settings) { try { var response = ExecuteRequest(settings, "d.name", hash); var name = response.GetStringResponse(); if (name.IsNullOrWhiteSpace()) { return false; } var metaTorrent = name == (hash + ".meta"); return !metaTorrent; } catch (Exception) { return false; } } private string[] GetCommands(string label, RTorrentPriority priority, string directory) { var result = new List(); if (label.IsNotNullOrWhiteSpace()) { result.Add("d.custom1.set=" + label); } if (priority != RTorrentPriority.Normal) { result.Add("d.priority.set=" + (int)priority); } if (directory.IsNotNullOrWhiteSpace()) { result.Add("d.directory.set=" + directory); } return result.ToArray(); } private XDocument ExecuteRequest(RTorrentSettings settings, string methodName, params object[] args) { var requestBuilder = new XmlRpcRequestBuilder(settings.UseSsl, settings.Host, settings.Port, settings.UrlBase) { LogResponseContent = true, }; if (!settings.Username.IsNullOrWhiteSpace()) { requestBuilder.NetworkCredential = new NetworkCredential(settings.Username, settings.Password); } var request = requestBuilder.Call(methodName, args).Build(); var response = _httpClient.Execute(request); var doc = XDocument.Parse(response.Content); var faultElement = doc.XPathSelectElement("./methodResponse/fault"); if (faultElement != null) { var fault = new RTorrentFault(faultElement); throw new DownloadClientException($"rTorrent returned error code {fault.FaultCode}: {fault.FaultString}"); } return doc; } } }