#!/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,))