Fixed: Use our own HttpClient for Aria2 requests

[common]
pull/6912/head
ta264 3 years ago committed by Qstick
parent b626c5bbf0
commit 39b99341cd

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using CookComputing.XmlRpc;
using FluentValidation.Results; using FluentValidation.Results;
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
@ -91,12 +90,7 @@ namespace NzbDrone.Core.Download.Clients.Aria2
var downloadSpeed = long.Parse(torrent.DownloadSpeed); var downloadSpeed = long.Parse(torrent.DownloadSpeed);
var status = DownloadItemStatus.Failed; var status = DownloadItemStatus.Failed;
var title = ""; var title = torrent.Bittorrent?.Name ?? "";
if (torrent.Bittorrent?.ContainsKey("info") == true && ((XmlRpcStruct)torrent.Bittorrent["info"]).ContainsKey("name"))
{
title = ((XmlRpcStruct)torrent.Bittorrent["info"])["name"].ToString();
}
switch (torrent.Status) switch (torrent.Status)
{ {

@ -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 namespace NzbDrone.Core.Download.Clients.Aria2
{ {
public class Aria2Version public class Aria2Fault
{ {
[XmlRpcMember("version")] public Aria2Fault(XElement element)
public string Version; {
foreach (var e in element.XPathSelectElements("./value/struct/member"))
[XmlRpcMember("enabledFeatures")] {
public string[] EnabledFeatures; var name = e.ElementAsString("name");
if (name == "faultCode")
{
FaultCode = e.Element("value").ElementAsInt("int");
}
else if (name == "faultString")
{
FaultString = e.Element("value").GetStringValue();
}
}
}
public int FaultCode { get; set; }
public string FaultString { get; set; }
} }
public class Aria2Uri public class Aria2Version
{ {
[XmlRpcMember("status")] public Aria2Version(XElement element)
public string Status; {
foreach (var e in element.XPathSelectElements("./struct/member"))
[XmlRpcMember("uri")] {
public string Uri; if (e.ElementAsString("name") == "version")
{
Version = e.Element("value").GetStringValue();
}
}
}
public string Version { get; set; }
} }
public class Aria2File public class Aria2File
{ {
[XmlRpcMember("index")] public Aria2File(XElement element)
public string Index; {
foreach (var e in element.XPathSelectElements("./struct/member"))
[XmlRpcMember("length")] {
public string Length; var name = e.ElementAsString("name");
if (name == "path")
{
Path = e.Element("value").GetStringValue();
}
}
}
public string Path { get; set; }
}
[XmlRpcMember("completedLength")] public class Aria2Dict
public string CompletedLength; {
public Aria2Dict(XElement element)
{
Dict = new Dictionary<string, string>();
[XmlRpcMember("path")] foreach (var e in element.XPathSelectElements("./struct/member"))
public string Path; {
Dict.Add(e.ElementAsString("name"), e.Element("value").GetStringValue());
}
}
[XmlRpcMember("selected")] public Dictionary<string, string> Dict { get; set; }
[XmlRpcMissingMapping(MappingAction.Ignore)] }
public string Selected;
[XmlRpcMember("uris")] public class Aria2Bittorrent
[XmlRpcMissingMapping(MappingAction.Ignore)] {
public Aria2Uri[] Uris; 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 public class Aria2Status
{ {
[XmlRpcMember("bittorrent")] public Aria2Status(XElement element)
[XmlRpcMissingMapping(MappingAction.Ignore)] {
public XmlRpcStruct Bittorrent; foreach (var e in element.XPathSelectElements("./struct/member"))
{
[XmlRpcMember("bitfield")] var name = e.ElementAsString("name");
[XmlRpcMissingMapping(MappingAction.Ignore)]
public string Bitfield; if (name == "bittorrent")
{
[XmlRpcMember("infoHash")] Bittorrent = new Aria2Bittorrent(e.Element("value"));
[XmlRpcMissingMapping(MappingAction.Ignore)] }
public string InfoHash; else if (name == "infoHash")
{
[XmlRpcMember("completedLength")] InfoHash = e.Element("value").GetStringValue();
[XmlRpcMissingMapping(MappingAction.Ignore)] }
public string CompletedLength; else if (name == "completedLength")
{
[XmlRpcMember("connections")] CompletedLength = e.Element("value").GetStringValue();
[XmlRpcMissingMapping(MappingAction.Ignore)] }
public string Connections; else if (name == "downloadSpeed")
{
[XmlRpcMember("dir")] DownloadSpeed = e.Element("value").GetStringValue();
[XmlRpcMissingMapping(MappingAction.Ignore)] }
public string Dir; else if (name == "files")
{
[XmlRpcMember("downloadSpeed")] Files = e.XPathSelectElement("./value/array/data")
[XmlRpcMissingMapping(MappingAction.Ignore)] .Elements()
public string DownloadSpeed; .Select(x => new Aria2File(x))
.ToArray();
[XmlRpcMember("files")] }
[XmlRpcMissingMapping(MappingAction.Ignore)] else if (name == "gid")
public Aria2File[] Files; {
Gid = e.Element("value").GetStringValue();
[XmlRpcMember("gid")] }
public string Gid; else if (name == "status")
{
[XmlRpcMember("numPieces")] Status = e.Element("value").GetStringValue();
[XmlRpcMissingMapping(MappingAction.Ignore)] }
public string NumPieces; else if (name == "totalLength")
{
[XmlRpcMember("pieceLength")] TotalLength = e.Element("value").GetStringValue();
[XmlRpcMissingMapping(MappingAction.Ignore)] }
public string PieceLength; else if (name == "uploadLength")
{
[XmlRpcMember("status")] UploadLength = e.Element("value").GetStringValue();
[XmlRpcMissingMapping(MappingAction.Ignore)] }
public string Status; else if (name == "errorMessage")
{
[XmlRpcMember("totalLength")] ErrorMessage = e.Element("value").GetStringValue();
[XmlRpcMissingMapping(MappingAction.Ignore)] }
public string TotalLength; }
}
[XmlRpcMember("uploadLength")]
[XmlRpcMissingMapping(MappingAction.Ignore)] public Aria2Bittorrent Bittorrent { get; set; }
public string UploadLength; public string InfoHash { get; set; }
public string CompletedLength { get; set; }
[XmlRpcMember("uploadSpeed")] public string DownloadSpeed { get; set; }
[XmlRpcMissingMapping(MappingAction.Ignore)] public Aria2File[] Files { get; set; }
public string UploadSpeed; public string Gid { get; set; }
public string Status { get; set; }
[XmlRpcMember("errorMessage")] public string TotalLength { get; set; }
[XmlRpcMissingMapping(MappingAction.Ignore)] public string UploadLength { get; set; }
public string ErrorMessage; public string ErrorMessage { get; set; }
} }
} }

@ -1,9 +1,9 @@
using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Linq;
using CookComputing.XmlRpc; using System.Xml.Linq;
using NLog; using System.Xml.XPath;
using NzbDrone.Common.Http;
using NzbDrone.Core.Download.Extensions;
namespace NzbDrone.Core.Download.Clients.Aria2 namespace NzbDrone.Core.Download.Clients.Aria2
{ {
@ -19,103 +19,61 @@ namespace NzbDrone.Core.Download.Clients.Aria2
Aria2Status GetFromGID(Aria2Settings settings, string gid); 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 public class Aria2Proxy : IAria2Proxy
{ {
private readonly Logger _logger; private readonly IHttpClient _httpClient;
public Aria2Proxy(Logger logger) public Aria2Proxy(IHttpClient httpClient)
{ {
_logger = logger; _httpClient = httpClient;
}
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}";
} }
public string GetVersion(Aria2Settings settings) public string GetVersion(Aria2Settings settings)
{ {
_logger.Trace("> aria2.getVersion"); var response = ExecuteRequest(settings, "aria2.getVersion", GetToken(settings));
var client = BuildClient(settings); var element = response.XPathSelectElement("./methodResponse/params/param/value");
var version = ExecuteRequest(() => client.GetVersion(GetToken(settings)));
_logger.Trace("< aria2.getVersion"); var version = new Aria2Version(element);
return version.Version; return version.Version;
} }
public Aria2Status GetFromGID(Aria2Settings settings, string gid) 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));
_logger.Trace("< aria2.tellStatus"); var element = response.XPathSelectElement("./methodResponse/params/param/value");
return found; return new Aria2Status(element);
} }
public List<Aria2Status> GetTorrents(Aria2Settings settings) private List<Aria2Status> GetTorrentsMethod(Aria2Settings settings, string method, params object[] args)
{ {
_logger.Trace("> aria2.tellActive"); var allArgs = new List<object> { GetToken(settings) };
if (args.Any())
var client = BuildClient(settings); {
allArgs.AddRange(args);
var active = ExecuteRequest(() => client.GetActive(GetToken(settings))); }
_logger.Trace("< aria2.tellActive");
_logger.Trace("> aria2.tellWaiting"); var response = ExecuteRequest(settings, method, allArgs.ToArray());
var waiting = ExecuteRequest(() => client.GetWaiting(GetToken(settings), 0, 10 * 1024)); var element = response.XPathSelectElement("./methodResponse/params/param/value/array/data");
_logger.Trace("< aria2.tellWaiting"); var torrents = element?.Elements()
.Select(x => new Aria2Status(x))
.ToList()
?? new List<Aria2Status>();
return torrents;
}
_logger.Trace("> aria2.tellStopped"); public List<Aria2Status> GetTorrents(Aria2Settings settings)
{
var active = GetTorrentsMethod(settings, "aria2.tellActive");
var stopped = ExecuteRequest(() => client.GetStopped(GetToken(settings), 0, 10 * 1024)); var waiting = GetTorrentsMethod(settings, "aria2.tellWaiting", 0, 10 * 1024);
_logger.Trace("< aria2.tellStopped"); var stopped = GetTorrentsMethod(settings, "aria2.tellStopped", 0, 10 * 1024);
var items = new List<Aria2Status>(); var items = new List<Aria2Status>();
@ -128,98 +86,79 @@ namespace NzbDrone.Core.Download.Clients.Aria2
public Dictionary<string, string> GetGlobals(Aria2Settings settings) public Dictionary<string, string> 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)));
_logger.Trace("< aria2.getGlobalOption"); var element = response.XPathSelectElement("./methodResponse/params/param/value");
var ret = new Dictionary<string, string>(); var result = new Aria2Dict(element);
foreach (DictionaryEntry option in options) return result.Dict;
{
ret.Add(option.Key.ToString(), option.Value?.ToString());
}
return ret;
} }
public string AddMagnet(Aria2Settings settings, string magnet) public string AddMagnet(Aria2Settings settings, string magnet)
{ {
_logger.Trace("> aria2.addUri"); var response = ExecuteRequest(settings, "aria2.addUri", GetToken(settings), new List<string> { magnet });
var client = BuildClient(settings);
var gid = ExecuteRequest(() => client.AddUri(GetToken(settings), new[] { magnet }));
_logger.Trace("< aria2.addUri"); var gid = response.GetStringResponse();
return gid; return gid;
} }
public string AddTorrent(Aria2Settings settings, byte[] torrent) 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 = response.GetStringResponse();
var gid = ExecuteRequest(() => client.AddTorrent(GetToken(settings), torrent));
_logger.Trace("< aria2.addTorrent");
return gid; return gid;
} }
public bool RemoveTorrent(Aria2Settings settings, string 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; return gid == gidres;
} }
public bool RemoveCompletedTorrent(Aria2Settings settings, string gid) 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"; return result == "OK";
} }
private IAria2 BuildClient(Aria2Settings settings) private string GetToken(Aria2Settings settings)
{ {
var client = XmlRpcProxyGen.Create<IAria2>(); return $"token:{settings?.SecretToken}";
client.Url = GetURL(settings);
return client;
} }
private T ExecuteRequest<T>(Func<T> 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(); LogResponseContent = true,
} };
catch (XmlRpcServerException ex)
{ var request = requestBuilder.Call(methodName, args).Build();
throw new DownloadClientException("Unable to connect to aria2, please check your settings", ex);
} var response = _httpClient.Execute(request);
catch (WebException ex)
var doc = XDocument.Parse(response.Content);
var faultElement = doc.XPathSelectElement("./methodResponse/fault");
if (faultElement != null)
{ {
if (ex.Status == WebExceptionStatus.TrustFailure) var fault = new Aria2Fault(faultElement);
{
throw new DownloadClientUnavailableException("Unable to connect to aria2, certificate validation failed.", ex);
}
throw new DownloadClientUnavailableException("Unable to connect to aria2, please check your settings", ex); throw new DownloadClientException($"Aria2 returned error code {fault.FaultCode}: {fault.FaultString}");
} }
return doc;
} }
} }
} }

@ -16,7 +16,6 @@
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" /> <PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NLog" Version="4.7.12" /> <PackageReference Include="NLog" Version="4.7.12" />
<PackageReference Include="Kveer.XmlRPC" Version="1.2.0" />
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" /> <PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.115.5-18" />
<PackageReference Include="System.Text.Json" Version="6.0.0" /> <PackageReference Include="System.Text.Json" Version="6.0.0" />
<PackageReference Include="MonoTorrent" Version="2.0.1" /> <PackageReference Include="MonoTorrent" Version="2.0.1" />

Loading…
Cancel
Save