diff --git a/rtorrent.py b/rtorrent.py index f809721..30c30d3 100644 --- a/rtorrent.py +++ b/rtorrent.py @@ -5,9 +5,10 @@ This module handles the interface with rTorrent via XMLRPC. import re import time import threading -import xmlrpc.client from collections import defaultdict +import rtorrent_xmlrpc + NUM_INST = 10 WATCH_HANDLE = None sp = [] @@ -144,7 +145,7 @@ def init(): global WATCH_HANDLE global sp for n in range(NUM_INST): - s = xmlrpc.client.ServerProxy(f"http://localhost:8000/RPC{n}") + s = rtorrent_xmlrpc.SCGIServerProxy(f"scgi://localhost:500{n}") sp.append(s) WATCH_HANDLE = Watch() WATCH_HANDLE.start() diff --git a/rtorrent_xmlrpc.py b/rtorrent_xmlrpc.py new file mode 100644 index 0000000..b1629ce --- /dev/null +++ b/rtorrent_xmlrpc.py @@ -0,0 +1,204 @@ +#!/usr/bin/python + +# rtorrent_xmlrpc +# (c) 2011 Roger Que +# +# Python module for interacting with rtorrent's XML-RPC interface +# directly over SCGI, instead of through an HTTP server intermediary. +# Inspired by Glenn Washburn's xmlrpc2scgi.py [1], but subclasses the +# built-in xmlrpc.client classes so that it is compatible with features +# such as MultiCall objects. +# +# [1] +# +# Usage: server = SCGIServerProxy('scgi://localhost:7000/') +# server = SCGIServerProxy('scgi:///path/to/scgi.sock') +# print(server.system.listMethods()) +# mc = xmlrpc.client.MultiCall(server) +# mc.get_up_rate() +# mc.get_down_rate() +# print(mc()) +# +# +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# In addition, as a special exception, the copyright holders give +# permission to link the code of portions of this program with the +# OpenSSL library under certain conditions as described in each +# individual source file, and distribute linked combinations +# including the two. +# +# You must obey the GNU General Public License in all respects for +# all of the code used other than OpenSSL. If you modify file(s) +# with this exception, you may extend this exception to your version +# of the file(s), but you are not obligated to do so. If you do not +# wish to do so, delete this exception statement from your version. +# If you delete this exception statement from all source files in the +# program, then also delete it here. +# +# +# +# Portions based on Python's xmlrpc.client: +# +# Copyright (c) 1999-2002 by Secret Labs AB +# Copyright (c) 1999-2002 by Fredrik Lundh +# +# By obtaining, using, and/or copying this software and/or its +# associated documentation, you agree that you have read, understood, +# and will comply with the following terms and conditions: +# +# Permission to use, copy, modify, and distribute this software and +# its associated documentation for any purpose and without fee is +# hereby granted, provided that the above copyright notice appears in +# all copies, and that both that copyright notice and this permission +# notice appear in supporting documentation, and that the name of +# Secret Labs AB or the author not be used in advertising or publicity +# pertaining to distribution of the software without specific, written +# prior permission. +# +# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD +# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- +# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR +# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. + +# Updated to python3 by iou1name + +import re +import socket +import xmlrpc.client +from urllib.parse import urlparse + + +class SCGITransport(xmlrpc.client.Transport): + def single_request(self, host, handler, request_body, verbose=0): + # Add SCGI headers to the request. + headers = {'CONTENT_LENGTH': str(len(request_body)), 'SCGI': '1'} + header = '\x00'.join(('%s\x00%s' % item for item in headers.items())) + '\x00' + header = '%d:%s' % (len(header), header) + request_body = '%s,%s' % (header, request_body) + + sock = None + + try: + if host: + host, _, port = host.partition(':') + addrinfo = socket.getaddrinfo(host, port, socket.AF_INET, + socket.SOCK_STREAM) + sock = socket.socket(*addrinfo[0][:3]) + sock.connect(addrinfo[0][4]) + else: + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(handler) + + self.verbose = verbose + + sock.send(bytes(request_body, 'utf8')) + return self.parse_response(sock.makefile()) + finally: + if sock: + sock.close() + + def parse_response(self, response): + p, u = self.getparser() + + response_body = '' + while True: + data = response.read(1024) + if not data: + break + response_body += data + + # Remove SCGI headers from the response. + response_header, response_body = re.split(r'\n\s*?\n', response_body, + maxsplit=1) + + if self.verbose: + print('body:', repr(response_body)) + + p.feed(response_body) + p.close() + + return u.close() + + +class SCGIServerProxy(xmlrpc.client.ServerProxy): + def __init__(self, uri, transport=None, encoding=None, verbose=False, + allow_none=False, use_datetime=False): + url = urlparse(uri) + if url.scheme not in ('scgi'): + raise IOError('unsupported XML-RPC protocol') + self.__host = url.netloc + self.__handler = url.path + if not self.__handler: + self.__handler = '/' + + if transport is None: + transport = SCGITransport(use_datetime=use_datetime) + self.__transport = transport + + self.__encoding = encoding + self.__verbose = verbose + self.__allow_none = allow_none + + def __close(self): + self.__transport.close() + + def __request(self, methodname, params): + # call a method on the remote server + + request = xmlrpc.client.dumps(params, methodname, encoding=self.__encoding, + allow_none=self.__allow_none) + + response = self.__transport.request( + self.__host, + self.__handler, + request, + verbose=self.__verbose + ) + + if len(response) == 1: + response = response[0] + + return response + + def __repr__(self): + return ( + "" % + (self.__host, self.__handler) + ) + + __str__ = __repr__ + + def __getattr__(self, name): + # magic method dispatcher + return xmlrpc.client._Method(self.__request, name) + + # note: to call a remote object with an non-standard name, use + # result getattr(server, "strange-python-name")(args) + + def __call__(self, attr): + """A workaround to get special attributes on the ServerProxy + without interfering with the magic __getattr__ + """ + if attr == "close": + return self.__close + elif attr == "transport": + return self.__transport + raise AttributeError("Attribute %r not found" % (attr,))