server: rTorrent: use JSON-RPC when available

This commit is contained in:
Jesse Chan
2021-03-03 12:47:13 +08:00
parent 2dd31ac1a4
commit 38605915c6
3 changed files with 90 additions and 9 deletions
@@ -804,14 +804,29 @@ class RTorrentClientGatewayService extends ClientGatewayService {
torrentTracker: string[];
transferSummary: string[];
}> {
const methodList: Array<string> = await this.clientRequestManager
.methodCall('system.listMethods', [])
.then(this.processClientRequestSuccess, this.processRTorrentRequestError)
.catch((e) => {
let methodList: Array<string> = [];
const listMethods = () => {
return this.clientRequestManager
.methodCall('system.listMethods', [])
.then(this.processClientRequestSuccess, this.processRTorrentRequestError);
};
this.clientRequestManager.isJSONCapable = true;
methodList = await listMethods().catch((e: RPCError) => {
if (e.isRPCError || e.name == 'SyntaxError') {
this.clientRequestManager.isJSONCapable = false;
} else if (!fallback) {
throw e;
}
});
if (!this.clientRequestManager.isJSONCapable) {
methodList = await listMethods().catch((e) => {
if (!fallback) {
throw e;
}
});
}
const getAvailableMethodCalls =
methodList?.length > 0
@@ -2,8 +2,8 @@ import type {NetConnectOpts} from 'net';
import type {RTorrentConnectionSettings} from '@shared/schema/ClientConnectionSettings';
import {methodCallJSON, methodCallXML} from './util/scgiUtil';
import {sanitizePath} from '../../util/fileUtil';
import scgiUtil from './util/scgiUtil';
import type {MultiMethodCalls} from './util/rTorrentMethodCallUtil';
@@ -11,6 +11,7 @@ type MethodCallParameters = Array<string | Buffer | MultiMethodCalls>;
class ClientRequestManager {
connectionSettings: RTorrentConnectionSettings;
isJSONCapable = false;
isRequestPending = false;
lastResponseTimestamp = 0;
pendingRequests: Array<{
@@ -70,7 +71,11 @@ class ClientRequestManager {
port: this.connectionSettings.port,
};
return scgiUtil.methodCall(connectionOptions, methodName, parameters).then(
const methodCall = this.isJSONCapable
? methodCallJSON(connectionOptions, methodName, parameters)
: methodCallXML(connectionOptions, methodName, parameters);
return methodCall.then(
(response) => {
this.handleRequestEnd();
return response;
+64 -3
View File
@@ -1,6 +1,11 @@
import net from 'net';
import deserializer from './XMLRPCDeserializer';
import serializer, {XMLRPCValue} from './XMLRPCSerializer';
import serializer from './XMLRPCSerializer';
import {RPCError} from '../types/RPCError';
import type {MultiMethodCalls} from './rTorrentMethodCallUtil';
import type {XMLRPCValue} from './XMLRPCSerializer';
const NULL_CHAR = String.fromCharCode(0);
@@ -13,7 +18,7 @@ const bufferStream = (stream: net.Socket): Promise<string> => {
});
};
const methodCall = (options: net.NetConnectOpts, methodName: string, params: XMLRPCValue[]) =>
export const methodCallXML = (options: net.NetConnectOpts, methodName: string, params: XMLRPCValue[]) =>
// TODO: better typings
// eslint-disable-next-line @typescript-eslint/no-explicit-any
new Promise<any>((resolve, reject) => {
@@ -39,4 +44,60 @@ const methodCall = (options: net.NetConnectOpts, methodName: string, params: XML
.then(resolve, reject);
});
export default {methodCall};
export const methodCallJSON = (options: net.NetConnectOpts, methodName: string, params: unknown[]) =>
// TODO: better typings
// eslint-disable-next-line @typescript-eslint/no-explicit-any
new Promise<any>((resolve, reject) => {
const stream = net.connect(options);
const request =
methodName == 'system.multicall'
? (params[0] as MultiMethodCalls).map((call) => ({
jsonrpc: '2.0',
id: null,
method: call.methodName,
params: call.params,
}))
: {
jsonrpc: '2.0',
id: null,
method: methodName,
params,
};
const json = JSON.stringify(request);
const jsonLength = Buffer.byteLength(json, 'utf8');
stream.on('error', reject);
stream.setEncoding('UTF8');
const headerItems = [
`CONTENT_LENGTH${NULL_CHAR}${jsonLength}${NULL_CHAR}`,
`CONTENT_TYPE${NULL_CHAR}application/json${NULL_CHAR}`,
`SCGI${NULL_CHAR}1${NULL_CHAR}`,
];
const headerLength = headerItems.reduce((accumulator, headerItem) => accumulator + headerItem.length, 0);
stream.end(`${headerLength}:${headerItems.join('')},${json}`);
bufferStream(stream)
.then((data: string) => {
const jsonResponse = JSON.parse(data.slice(data.lastIndexOf('\n')));
if (Array.isArray(jsonResponse)) {
return jsonResponse.map((response) => {
if (response.result == null) {
const {code, message} = response.error || {};
throw RPCError(code, message);
}
return response.result;
});
} else {
if (jsonResponse.result == null) {
const {code, message} = jsonResponse.error || {};
throw RPCError(code, message);
}
return jsonResponse.result;
}
})
.then(resolve, reject);
});