diff --git a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs index 636f0aaa8..58cc3edfa 100644 --- a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs +++ b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading; -using CookComputing.XmlRpc; using FluentValidation.Results; using NLog; using NzbDrone.Common.Disk; @@ -91,12 +90,7 @@ namespace NzbDrone.Core.Download.Clients.Aria2 var downloadSpeed = long.Parse(torrent.DownloadSpeed); var status = DownloadItemStatus.Failed; - var title = ""; - - if (torrent.Bittorrent?.ContainsKey("info") == true && ((XmlRpcStruct)torrent.Bittorrent["info"]).ContainsKey("name")) - { - title = ((XmlRpcStruct)torrent.Bittorrent["info"])["name"].ToString(); - } + var title = torrent.Bittorrent?.Name ?? ""; switch (torrent.Status) { diff --git a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Containers.cs b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Containers.cs index d4ab5a49c..24122b65c 100644 --- a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Containers.cs +++ b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Containers.cs @@ -1,111 +1,161 @@ -using CookComputing.XmlRpc; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using System.Xml.XPath; +using NzbDrone.Core.Download.Extensions; namespace NzbDrone.Core.Download.Clients.Aria2 { - public class Aria2Version + public class Aria2Fault { - [XmlRpcMember("version")] - public string Version; + public Aria2Fault(XElement element) + { + foreach (var e in element.XPathSelectElements("./value/struct/member")) + { + var name = e.ElementAsString("name"); + if (name == "faultCode") + { + FaultCode = e.Element("value").ElementAsInt("int"); + } + else if (name == "faultString") + { + FaultString = e.Element("value").GetStringValue(); + } + } + } - [XmlRpcMember("enabledFeatures")] - public string[] EnabledFeatures; + public int FaultCode { get; set; } + public string FaultString { get; set; } } - public class Aria2Uri + public class Aria2Version { - [XmlRpcMember("status")] - public string Status; + public Aria2Version(XElement element) + { + foreach (var e in element.XPathSelectElements("./struct/member")) + { + if (e.ElementAsString("name") == "version") + { + Version = e.Element("value").GetStringValue(); + } + } + } - [XmlRpcMember("uri")] - public string Uri; + public string Version { get; set; } } public class Aria2File { - [XmlRpcMember("index")] - public string Index; + public Aria2File(XElement element) + { + foreach (var e in element.XPathSelectElements("./struct/member")) + { + var name = e.ElementAsString("name"); - [XmlRpcMember("length")] - public string Length; + if (name == "path") + { + Path = e.Element("value").GetStringValue(); + } + } + } - [XmlRpcMember("completedLength")] - public string CompletedLength; + public string Path { get; set; } + } - [XmlRpcMember("path")] - public string Path; + public class Aria2Dict + { + public Aria2Dict(XElement element) + { + Dict = new Dictionary(); - [XmlRpcMember("selected")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string Selected; + foreach (var e in element.XPathSelectElements("./struct/member")) + { + Dict.Add(e.ElementAsString("name"), e.Element("value").GetStringValue()); + } + } - [XmlRpcMember("uris")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public Aria2Uri[] Uris; + public Dictionary Dict { get; set; } + } + + public class Aria2Bittorrent + { + public Aria2Bittorrent(XElement element) + { + foreach (var e in element.Descendants("member")) + { + if (e.ElementAsString("name") == "name") + { + Name = e.Element("value").GetStringValue(); + } + } + } + + public string Name; } public class Aria2Status { - [XmlRpcMember("bittorrent")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public XmlRpcStruct Bittorrent; + public Aria2Status(XElement element) + { + foreach (var e in element.XPathSelectElements("./struct/member")) + { + var name = e.ElementAsString("name"); - [XmlRpcMember("bitfield")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string Bitfield; + if (name == "bittorrent") + { + Bittorrent = new Aria2Bittorrent(e.Element("value")); + } + else if (name == "infoHash") + { + InfoHash = e.Element("value").GetStringValue(); + } + else if (name == "completedLength") + { + CompletedLength = e.Element("value").GetStringValue(); + } + else if (name == "downloadSpeed") + { + DownloadSpeed = e.Element("value").GetStringValue(); + } + else if (name == "files") + { + Files = e.XPathSelectElement("./value/array/data") + .Elements() + .Select(x => new Aria2File(x)) + .ToArray(); + } + else if (name == "gid") + { + Gid = e.Element("value").GetStringValue(); + } + else if (name == "status") + { + Status = e.Element("value").GetStringValue(); + } + else if (name == "totalLength") + { + TotalLength = e.Element("value").GetStringValue(); + } + else if (name == "uploadLength") + { + UploadLength = e.Element("value").GetStringValue(); + } + else if (name == "errorMessage") + { + ErrorMessage = e.Element("value").GetStringValue(); + } + } + } - [XmlRpcMember("infoHash")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string InfoHash; - - [XmlRpcMember("completedLength")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string CompletedLength; - - [XmlRpcMember("connections")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string Connections; - - [XmlRpcMember("dir")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string Dir; - - [XmlRpcMember("downloadSpeed")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string DownloadSpeed; - - [XmlRpcMember("files")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public Aria2File[] Files; - - [XmlRpcMember("gid")] - public string Gid; - - [XmlRpcMember("numPieces")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string NumPieces; - - [XmlRpcMember("pieceLength")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string PieceLength; - - [XmlRpcMember("status")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string Status; - - [XmlRpcMember("totalLength")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string TotalLength; - - [XmlRpcMember("uploadLength")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string UploadLength; - - [XmlRpcMember("uploadSpeed")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string UploadSpeed; - - [XmlRpcMember("errorMessage")] - [XmlRpcMissingMapping(MappingAction.Ignore)] - public string ErrorMessage; + public Aria2Bittorrent Bittorrent { get; set; } + public string InfoHash { get; set; } + public string CompletedLength { get; set; } + public string DownloadSpeed { get; set; } + public Aria2File[] Files { get; set; } + public string Gid { get; set; } + public string Status { get; set; } + public string TotalLength { get; set; } + public string UploadLength { get; set; } + public string ErrorMessage { get; set; } } } diff --git a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Proxy.cs b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Proxy.cs index 3e7b5a6be..f141ef7ce 100644 --- a/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Proxy.cs +++ b/src/NzbDrone.Core/Download/Clients/Aria2/Aria2Proxy.cs @@ -1,9 +1,9 @@ -using System; -using System.Collections; using System.Collections.Generic; -using System.Net; -using CookComputing.XmlRpc; -using NLog; +using System.Linq; +using System.Xml.Linq; +using System.Xml.XPath; +using NzbDrone.Common.Http; +using NzbDrone.Core.Download.Extensions; namespace NzbDrone.Core.Download.Clients.Aria2 { @@ -19,103 +19,61 @@ namespace NzbDrone.Core.Download.Clients.Aria2 Aria2Status GetFromGID(Aria2Settings settings, string gid); } - public interface IAria2 : IXmlRpcProxy - { - [XmlRpcMethod("aria2.getVersion")] - Aria2Version GetVersion(string token); - - [XmlRpcMethod("aria2.addUri")] - string AddUri(string token, string[] uri); - - [XmlRpcMethod("aria2.addTorrent")] - string AddTorrent(string token, byte[] torrent); - - [XmlRpcMethod("aria2.forceRemove")] - string Remove(string token, string gid); - - [XmlRpcMethod("aria2.removeDownloadResult")] - string RemoveResult(string token, string gid); - - [XmlRpcMethod("aria2.tellStatus")] - Aria2Status GetFromGid(string token, string gid); - - [XmlRpcMethod("aria2.getGlobalOption")] - XmlRpcStruct GetGlobalOption(string token); - - [XmlRpcMethod("aria2.tellActive")] - Aria2Status[] GetActive(string token); - - [XmlRpcMethod("aria2.tellWaiting")] - Aria2Status[] GetWaiting(string token, int offset, int num); - - [XmlRpcMethod("aria2.tellStopped")] - Aria2Status[] GetStopped(string token, int offset, int num); - } - public class Aria2Proxy : IAria2Proxy { - private readonly Logger _logger; + private readonly IHttpClient _httpClient; - public Aria2Proxy(Logger logger) + public Aria2Proxy(IHttpClient httpClient) { - _logger = logger; - } - - private string GetToken(Aria2Settings settings) - { - return $"token:{settings?.SecretToken}"; - } - - private string GetURL(Aria2Settings settings) - { - return $"http{(settings.UseSsl ? "s" : "")}://{settings.Host}:{settings.Port}{settings.RpcPath}"; + _httpClient = httpClient; } public string GetVersion(Aria2Settings settings) { - _logger.Trace("> aria2.getVersion"); + var response = ExecuteRequest(settings, "aria2.getVersion", GetToken(settings)); - var client = BuildClient(settings); - var version = ExecuteRequest(() => client.GetVersion(GetToken(settings))); + var element = response.XPathSelectElement("./methodResponse/params/param/value"); - _logger.Trace("< aria2.getVersion"); + var version = new Aria2Version(element); return version.Version; } public Aria2Status GetFromGID(Aria2Settings settings, string gid) { - _logger.Trace("> aria2.tellStatus"); + var response = ExecuteRequest(settings, "aria2.tellStatus", GetToken(settings), gid); - var client = BuildClient(settings); - var found = ExecuteRequest(() => client.GetFromGid(GetToken(settings), gid)); + var element = response.XPathSelectElement("./methodResponse/params/param/value"); - _logger.Trace("< aria2.tellStatus"); + return new Aria2Status(element); + } - return found; + private List GetTorrentsMethod(Aria2Settings settings, string method, params object[] args) + { + var allArgs = new List { GetToken(settings) }; + if (args.Any()) + { + allArgs.AddRange(args); + } + + var response = ExecuteRequest(settings, method, allArgs.ToArray()); + + var element = response.XPathSelectElement("./methodResponse/params/param/value/array/data"); + + var torrents = element?.Elements() + .Select(x => new Aria2Status(x)) + .ToList() + ?? new List(); + return torrents; } public List GetTorrents(Aria2Settings settings) { - _logger.Trace("> aria2.tellActive"); + var active = GetTorrentsMethod(settings, "aria2.tellActive"); - var client = BuildClient(settings); + var waiting = GetTorrentsMethod(settings, "aria2.tellWaiting", 0, 10 * 1024); - var active = ExecuteRequest(() => client.GetActive(GetToken(settings))); - - _logger.Trace("< aria2.tellActive"); - - _logger.Trace("> aria2.tellWaiting"); - - var waiting = ExecuteRequest(() => client.GetWaiting(GetToken(settings), 0, 10 * 1024)); - - _logger.Trace("< aria2.tellWaiting"); - - _logger.Trace("> aria2.tellStopped"); - - var stopped = ExecuteRequest(() => client.GetStopped(GetToken(settings), 0, 10 * 1024)); - - _logger.Trace("< aria2.tellStopped"); + var stopped = GetTorrentsMethod(settings, "aria2.tellStopped", 0, 10 * 1024); var items = new List(); @@ -128,98 +86,79 @@ namespace NzbDrone.Core.Download.Clients.Aria2 public Dictionary GetGlobals(Aria2Settings settings) { - _logger.Trace("> aria2.getGlobalOption"); + var response = ExecuteRequest(settings, "aria2.getGlobalOption", GetToken(settings)); - var client = BuildClient(settings); - var options = ExecuteRequest(() => client.GetGlobalOption(GetToken(settings))); + var element = response.XPathSelectElement("./methodResponse/params/param/value"); - _logger.Trace("< aria2.getGlobalOption"); + var result = new Aria2Dict(element); - var ret = new Dictionary(); - - foreach (DictionaryEntry option in options) - { - ret.Add(option.Key.ToString(), option.Value?.ToString()); - } - - return ret; + return result.Dict; } public string AddMagnet(Aria2Settings settings, string magnet) { - _logger.Trace("> aria2.addUri"); + var response = ExecuteRequest(settings, "aria2.addUri", GetToken(settings), new List { magnet }); - var client = BuildClient(settings); - var gid = ExecuteRequest(() => client.AddUri(GetToken(settings), new[] { magnet })); - - _logger.Trace("< aria2.addUri"); + var gid = response.GetStringResponse(); return gid; } public string AddTorrent(Aria2Settings settings, byte[] torrent) { - _logger.Trace("> aria2.addTorrent"); + var response = ExecuteRequest(settings, "aria2.addTorrent", GetToken(settings), torrent); - var client = BuildClient(settings); - var gid = ExecuteRequest(() => client.AddTorrent(GetToken(settings), torrent)); - - _logger.Trace("< aria2.addTorrent"); + var gid = response.GetStringResponse(); return gid; } public bool RemoveTorrent(Aria2Settings settings, string gid) { - _logger.Trace("> aria2.forceRemove"); + var response = ExecuteRequest(settings, "aria2.forceRemove", GetToken(settings), gid); - var client = BuildClient(settings); - var gidres = ExecuteRequest(() => client.Remove(GetToken(settings), gid)); - - _logger.Trace("< aria2.forceRemove"); + var gidres = response.GetStringResponse(); return gid == gidres; } public bool RemoveCompletedTorrent(Aria2Settings settings, string gid) { - _logger.Trace("> aria2.removeDownloadResult"); + var response = ExecuteRequest(settings, "aria2.removeDownloadResult", GetToken(settings), gid); - var client = BuildClient(settings); - var result = ExecuteRequest(() => client.RemoveResult(GetToken(settings), gid)); - - _logger.Trace("< aria2.removeDownloadResult"); + var result = response.GetStringResponse(); return result == "OK"; } - private IAria2 BuildClient(Aria2Settings settings) + private string GetToken(Aria2Settings settings) { - var client = XmlRpcProxyGen.Create(); - client.Url = GetURL(settings); - - return client; + return $"token:{settings?.SecretToken}"; } - private T ExecuteRequest(Func task) + private XDocument ExecuteRequest(Aria2Settings settings, string methodName, params object[] args) { - try + var requestBuilder = new XmlRpcRequestBuilder(settings.UseSsl, settings.Host, settings.Port, settings.RpcPath) { - return task(); - } - catch (XmlRpcServerException ex) - { - throw new DownloadClientException("Unable to connect to aria2, please check your settings", ex); - } - catch (WebException ex) - { - if (ex.Status == WebExceptionStatus.TrustFailure) - { - throw new DownloadClientUnavailableException("Unable to connect to aria2, certificate validation failed.", ex); - } + LogResponseContent = true, + }; - throw new DownloadClientUnavailableException("Unable to connect to aria2, please check your settings", ex); + 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 Aria2Fault(faultElement); + + throw new DownloadClientException($"Aria2 returned error code {fault.FaultCode}: {fault.FaultString}"); } + + return doc; } } } diff --git a/src/NzbDrone.Core/Radarr.Core.csproj b/src/NzbDrone.Core/Radarr.Core.csproj index 483131b19..210583f58 100644 --- a/src/NzbDrone.Core/Radarr.Core.csproj +++ b/src/NzbDrone.Core/Radarr.Core.csproj @@ -16,7 +16,6 @@ -