Fixed: Use our own HttpClient for Aria2 requests

[common]
This commit is contained in:
ta264 2022-01-06 21:12:31 +00:00 committed by Qstick
parent b626c5bbf0
commit 39b99341cd
4 changed files with 204 additions and 222 deletions

View File

@ -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)
{

View File

@ -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<string, string>();
[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<string, string> 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; }
}
}

View File

@ -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<Aria2Status> GetTorrentsMethod(Aria2Settings settings, string method, params object[] args)
{
var allArgs = new List<object> { 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<Aria2Status>();
return torrents;
}
public List<Aria2Status> 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<Aria2Status>();
@ -128,98 +86,79 @@ namespace NzbDrone.Core.Download.Clients.Aria2
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)));
var element = response.XPathSelectElement("./methodResponse/params/param/value");
_logger.Trace("< aria2.getGlobalOption");
var result = new Aria2Dict(element);
var ret = new Dictionary<string, string>();
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<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;
}
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<IAria2>();
client.Url = GetURL(settings);
return client;
return $"token:{settings?.SecretToken}";
}
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();
}
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;
}
}
}

View File

@ -16,7 +16,6 @@
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<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.Text.Json" Version="6.0.0" />
<PackageReference Include="MonoTorrent" Version="2.0.1" />