| 1 | #!/usr/bin/env python |
|---|
| 2 | |
|---|
| 3 | # Copyright (C) 2005-2007, Glenn Washburn |
|---|
| 4 | # |
|---|
| 5 | # This program is free software; you can redistribute it and/or modify |
|---|
| 6 | # it under the terms of the GNU General Public License as published by |
|---|
| 7 | # the Free Software Foundation; either version 2 of the License, or |
|---|
| 8 | # (at your option) any later version. |
|---|
| 9 | # |
|---|
| 10 | # This program is distributed in the hope that it will be useful, |
|---|
| 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 13 | # GNU General Public License for more details. |
|---|
| 14 | # |
|---|
| 15 | # You should have received a copy of the GNU General Public License |
|---|
| 16 | # along with this program; if not, write to the Free Software |
|---|
| 17 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|---|
| 18 | # |
|---|
| 19 | # In addition, as a special exception, the copyright holders give |
|---|
| 20 | # permission to link the code of portions of this program with the |
|---|
| 21 | # OpenSSL library under certain conditions as described in each |
|---|
| 22 | # individual source file, and distribute linked combinations |
|---|
| 23 | # including the two. |
|---|
| 24 | # |
|---|
| 25 | # You must obey the GNU General Public License in all respects for |
|---|
| 26 | # all of the code used other than OpenSSL. If you modify file(s) |
|---|
| 27 | # with this exception, you may extend this exception to your version |
|---|
| 28 | # of the file(s), but you are not obligated to do so. If you do not |
|---|
| 29 | # wish to do so, delete this exception statement from your version. |
|---|
| 30 | # If you delete this exception statement from all source files in the |
|---|
| 31 | # program, then also delete it here. |
|---|
| 32 | # |
|---|
| 33 | # Contact: Glenn Washburn <crass@berlios.de> |
|---|
| 34 | |
|---|
| 35 | import sys, cStringIO as StringIO |
|---|
| 36 | import xmlrpclib, urllib, urlparse, socket |
|---|
| 37 | |
|---|
| 38 | # this allows us to parse scgi urls just like http ones |
|---|
| 39 | from urlparse import uses_netloc |
|---|
| 40 | uses_netloc.append('scgi') |
|---|
| 41 | |
|---|
| 42 | def encode_netstring(string): |
|---|
| 43 | "Encode string as netstring" |
|---|
| 44 | return '%d:%s,'%(len(string), string) |
|---|
| 45 | |
|---|
| 46 | def make_headers(headers): |
|---|
| 47 | "Make scgi header list" |
|---|
| 48 | return '\x00'.join(headers)+'\x00' |
|---|
| 49 | |
|---|
| 50 | def convert_xmlrpc2sgi_req(xmlreq): |
|---|
| 51 | "Wrap xmlrpc request in an scgi request,\nsee spec at: http://python.ca/scgi/protocol.txt" |
|---|
| 52 | # See spec at: http://python.ca/scgi/protocol.txt |
|---|
| 53 | headers = make_headers([ |
|---|
| 54 | 'CONTENT_LENGTH', str(len(xmlreq)), |
|---|
| 55 | 'SCGI', '1', |
|---|
| 56 | ]) |
|---|
| 57 | |
|---|
| 58 | enc_headers = encode_netstring(headers) |
|---|
| 59 | |
|---|
| 60 | return enc_headers+xmlreq |
|---|
| 61 | |
|---|
| 62 | def send_scgi(url, scgireq): |
|---|
| 63 | "Send request and get response from url" |
|---|
| 64 | scheme, netloc, path, query, frag = urlparse.urlsplit(url) |
|---|
| 65 | host, port = urllib.splitport(netloc) |
|---|
| 66 | #~ print '>>>', (netloc, host, port) |
|---|
| 67 | |
|---|
| 68 | addrinfo = socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM) |
|---|
| 69 | |
|---|
| 70 | assert len(addrinfo) == 1, "There's more than one? %r"%addrinfo |
|---|
| 71 | #~ print addrinfo |
|---|
| 72 | |
|---|
| 73 | sock = socket.socket(*addrinfo[0][:3]) |
|---|
| 74 | |
|---|
| 75 | sock.connect(addrinfo[0][4]) |
|---|
| 76 | sock.send(scgireq) |
|---|
| 77 | recvdata = resp = sock.recv(1024) |
|---|
| 78 | while recvdata != '': |
|---|
| 79 | recvdata = sock.recv(1024) |
|---|
| 80 | #~ print 'Trying to receive more: %r'%recvdata |
|---|
| 81 | resp += recvdata |
|---|
| 82 | sock.close() |
|---|
| 83 | return resp |
|---|
| 84 | |
|---|
| 85 | def gen_headers(file): |
|---|
| 86 | "Get header lines from scgi response" |
|---|
| 87 | line = file.readline().rstrip() |
|---|
| 88 | while line.strip(): |
|---|
| 89 | yield line |
|---|
| 90 | line = file.readline().rstrip() |
|---|
| 91 | |
|---|
| 92 | def get_scgi_resp(resp): |
|---|
| 93 | "Get xmlrpc response from scgi response" |
|---|
| 94 | fresp = StringIO.StringIO(resp) |
|---|
| 95 | for line in gen_headers(fresp): |
|---|
| 96 | #~ print "Header: %s"%line |
|---|
| 97 | pass |
|---|
| 98 | |
|---|
| 99 | xmlresp = fresp.read() |
|---|
| 100 | return xmlresp |
|---|
| 101 | |
|---|
| 102 | def convert_params_to_native(params): |
|---|
| 103 | "Parse xmlrpc-c command line arg syntax" |
|---|
| 104 | #~ print 'convert_params_to_native', params |
|---|
| 105 | cparams = [] |
|---|
| 106 | # parse parameters |
|---|
| 107 | for param in params: |
|---|
| 108 | if param[1] != '/': |
|---|
| 109 | cparams.append(param) |
|---|
| 110 | continue |
|---|
| 111 | |
|---|
| 112 | if param[0] == 'i': |
|---|
| 113 | ptype = int |
|---|
| 114 | elif param[0] == 'b': |
|---|
| 115 | ptype = bool |
|---|
| 116 | elif param[0] == 's': |
|---|
| 117 | ptype = str |
|---|
| 118 | |
|---|
| 119 | |
|---|
| 120 | cparams.append(ptype(param[2:])) |
|---|
| 121 | |
|---|
| 122 | return tuple(cparams) |
|---|
| 123 | |
|---|
| 124 | def do_scgi_xmlrpc_request(host, methodname, params=()): |
|---|
| 125 | """ |
|---|
| 126 | Send an xmlrpc request over scgi to host. |
|---|
| 127 | host: scgi://host:port/path |
|---|
| 128 | methodname: xmlrpc method name |
|---|
| 129 | params: tuple of simple python objects |
|---|
| 130 | returns xmlrpc response |
|---|
| 131 | """ |
|---|
| 132 | xmlreq = xmlrpclib.dumps(params, methodname) |
|---|
| 133 | |
|---|
| 134 | scgireq = convert_xmlrpc2sgi_req(xmlreq) |
|---|
| 135 | |
|---|
| 136 | #~ print xmlreq, params |
|---|
| 137 | #~ print repr(scgireq) |
|---|
| 138 | #~ sys.stdout.write(scgireq) |
|---|
| 139 | |
|---|
| 140 | resp = send_scgi(host, scgireq) |
|---|
| 141 | #~ print resp |
|---|
| 142 | |
|---|
| 143 | respxml = get_scgi_resp(resp) |
|---|
| 144 | #~ print respxml |
|---|
| 145 | |
|---|
| 146 | return respxml |
|---|
| 147 | |
|---|
| 148 | def do_scgi_xmlrpc_request_py(host, methodname, params=()): |
|---|
| 149 | """ |
|---|
| 150 | Send an xmlrpc request over scgi to host. |
|---|
| 151 | host: scgi://host:port/path |
|---|
| 152 | methodname: xmlrpc method name |
|---|
| 153 | params: tuple of simple python objects |
|---|
| 154 | returns xmlrpc response converted to python |
|---|
| 155 | """ |
|---|
| 156 | xmlresp = do_scgi_xmlrpc_request(host, methodname, params) |
|---|
| 157 | return xmlrpclib.loads(xmlresp)[0][0] |
|---|
| 158 | |
|---|
| 159 | class RTorrentXMLRPCClient(object): |
|---|
| 160 | """ |
|---|
| 161 | The following is an exmple of how to use this class. |
|---|
| 162 | rtorrent_host='http://localhost:33000' |
|---|
| 163 | rtc = RTorrentXMLRPCClient(rtorrent_host) |
|---|
| 164 | for infohash in rtc.download_list('complete'): |
|---|
| 165 | if rtc.d.get_ratio(infohash) > 500: |
|---|
| 166 | print "%s has a ratio of over 0.5"%(rtc.d.get_name(infohash)) |
|---|
| 167 | """ |
|---|
| 168 | |
|---|
| 169 | def __init__(self, url, methodname=''): |
|---|
| 170 | self.url = url |
|---|
| 171 | self.methodname = methodname |
|---|
| 172 | |
|---|
| 173 | def __call__(self, *args): |
|---|
| 174 | #~ print "%s%r"%(self.methodname, args) |
|---|
| 175 | return do_scgi_xmlrpc_request_py(self.url, self.methodname, args) |
|---|
| 176 | |
|---|
| 177 | def __getattr__(self, attr): |
|---|
| 178 | methodname = self.methodname and '.'.join([self.methodname,attr]) or attr |
|---|
| 179 | return RTorrentXMLRPCClient(self.url, methodname) |
|---|
| 180 | |
|---|
| 181 | |
|---|
| 182 | def main(argv): |
|---|
| 183 | output_python=False |
|---|
| 184 | |
|---|
| 185 | if argv[0] == '-p': |
|---|
| 186 | output_python=True |
|---|
| 187 | argv.pop(0) |
|---|
| 188 | |
|---|
| 189 | host, methodname = argv[:2] |
|---|
| 190 | |
|---|
| 191 | respxml = do_scgi_xmlrpc_request(host, methodname, |
|---|
| 192 | convert_params_to_native(argv[2:])) |
|---|
| 193 | |
|---|
| 194 | if not output_python: |
|---|
| 195 | print respxml |
|---|
| 196 | else: |
|---|
| 197 | print xmlrpclib.loads(respxml)[0][0] |
|---|
| 198 | |
|---|
| 199 | if __name__ == "__main__": |
|---|
| 200 | main(sys.argv[1:]) |
|---|