diff --git a/couchpotato/core/downloaders/rtorrent/__init__.py b/couchpotato/core/downloaders/rtorrent/__init__.py
index b04e6898..026a56c6 100755
--- a/couchpotato/core/downloaders/rtorrent/__init__.py
+++ b/couchpotato/core/downloaders/rtorrent/__init__.py
@@ -23,6 +23,8 @@ config = [{
{
'name': 'url',
'default': 'http://localhost:80/RPC2',
+ 'description': 'XML-RPC Endpoint URI. Usually scgi://localhost:5000 '
+ 'or http://localhost:80/RPC2'
},
{
'name': 'username',
@@ -38,7 +40,7 @@ config = [{
{
'name': 'directory',
'type': 'directory',
- 'description': 'Directory where rtorrent should download the files too.',
+ 'description': 'Download to this directory. Keep empty for default rTorrent download directory.',
},
{
'name': 'remove_complete',
@@ -48,14 +50,6 @@ config = [{
'type': 'bool',
'description': 'Remove the torrent after it finishes seeding.',
},
- {
- 'name': 'append_label',
- 'label': 'Append Label',
- 'default': False,
- 'advanced': True,
- 'type': 'bool',
- 'description': 'Append label to download location. Requires you to set the download location above.',
- },
{
'name': 'delete_files',
'label': 'Remove files',
@@ -64,6 +58,14 @@ config = [{
'advanced': True,
'description': 'Also remove the leftover files.',
},
+ {
+ 'name': 'append_label',
+ 'label': 'Append Label',
+ 'default': False,
+ 'advanced': True,
+ 'type': 'bool',
+ 'description': 'Append label to download location. Requires you to set the download location above.',
+ },
{
'name': 'paused',
'type': 'bool',
diff --git a/libs/rtorrent/__init__.py b/libs/rtorrent/__init__.py
index b6ff73a0..05add8d4 100755
--- a/libs/rtorrent/__init__.py
+++ b/libs/rtorrent/__init__.py
@@ -17,18 +17,21 @@
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+import urllib
+import os.path
+import time
+import xmlrpclib
from rtorrent.common import find_torrent, \
is_valid_port, convert_version_tuple_to_str
from rtorrent.lib.torrentparser import TorrentParser
from rtorrent.lib.xmlrpc.http import HTTPServerProxy
-from rtorrent.rpc import Method, BasicAuthTransport
+from rtorrent.lib.xmlrpc.scgi import SCGIServerProxy
+from rtorrent.rpc import Method
+from rtorrent.lib.xmlrpc.basic_auth import BasicAuthTransport
from rtorrent.torrent import Torrent
from rtorrent.group import Group
-import os.path
import rtorrent.rpc # @UnresolvedImport
-import time
-import xmlrpclib
__version__ = "0.2.9"
__author__ = "Chris Lucas"
@@ -43,13 +46,25 @@ class RTorrent:
""" Create a new rTorrent connection """
rpc_prefix = None
- def __init__(self, url, username=None, password=None,
- verify=False, sp=HTTPServerProxy, sp_kwargs={}):
- self.url = url # : From X{__init__(self, url)}
+ def __init__(self, uri, username=None, password=None,
+ verify=False, sp=None, sp_kwargs=None):
+ self.uri = uri # : From X{__init__(self, url)}
+
self.username = username
self.password = password
- self.sp = sp
- self.sp_kwargs = sp_kwargs
+
+ self.schema = urllib.splittype(uri)[0]
+
+ if sp:
+ self.sp = sp
+ elif self.schema in ['http', 'https']:
+ self.sp = HTTPServerProxy
+ elif self.schema == 'scgi':
+ self.sp = SCGIServerProxy
+ else:
+ raise NotImplementedError()
+
+ self.sp_kwargs = sp_kwargs or {}
self.torrents = [] # : List of L{Torrent} instances
self._rpc_methods = [] # : List of rTorrent RPC methods
@@ -62,12 +77,16 @@ class RTorrent:
def _get_conn(self):
"""Get ServerProxy instance"""
if self.username is not None and self.password is not None:
+ if self.schema == 'scgi':
+ raise NotImplementedError()
+
return self.sp(
- self.url,
+ self.uri,
transport=BasicAuthTransport(self.username, self.password),
**self.sp_kwargs
)
- return self.sp(self.url, **self.sp_kwargs)
+
+ return self.sp(self.uri, **self.sp_kwargs)
def _verify_conn(self):
# check for rpc methods that should be available
diff --git a/libs/rtorrent/lib/xmlrpc/basic_auth.py b/libs/rtorrent/lib/xmlrpc/basic_auth.py
new file mode 100644
index 00000000..20c02d9a
--- /dev/null
+++ b/libs/rtorrent/lib/xmlrpc/basic_auth.py
@@ -0,0 +1,73 @@
+#
+# Copyright (c) 2013 Dean Gardiner,
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+from base64 import encodestring
+import string
+import xmlrpclib
+
+
+class BasicAuthTransport(xmlrpclib.Transport):
+ def __init__(self, username=None, password=None):
+ xmlrpclib.Transport.__init__(self)
+
+ self.username = username
+ self.password = password
+
+ def send_auth(self, h):
+ if self.username is not None and self.password is not None:
+ h.putheader('AUTHORIZATION', "Basic %s" % string.replace(
+ encodestring("%s:%s" % (self.username, self.password)),
+ "\012", ""
+ ))
+
+ def single_request(self, host, handler, request_body, verbose=0):
+ # issue XML-RPC request
+
+ h = self.make_connection(host)
+ if verbose:
+ h.set_debuglevel(1)
+
+ try:
+ self.send_request(h, handler, request_body)
+ self.send_host(h, host)
+ self.send_user_agent(h)
+ self.send_auth(h)
+ self.send_content(h, request_body)
+
+ response = h.getresponse(buffering=True)
+ if response.status == 200:
+ self.verbose = verbose
+ return self.parse_response(response)
+ except xmlrpclib.Fault:
+ raise
+ except Exception:
+ self.close()
+ raise
+
+ #discard any response data and raise exception
+ if response.getheader("content-length", 0):
+ response.read()
+ raise xmlrpclib.ProtocolError(
+ host + handler,
+ response.status, response.reason,
+ response.msg,
+ )
diff --git a/libs/rtorrent/lib/xmlrpc/scgi.py b/libs/rtorrent/lib/xmlrpc/scgi.py
new file mode 100644
index 00000000..39866971
--- /dev/null
+++ b/libs/rtorrent/lib/xmlrpc/scgi.py
@@ -0,0 +1,201 @@
+#!/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 xmlrpclib 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 = xmlrpclib.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 xmlrpclib:
+#
+# 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.
+
+import re
+import socket
+import urllib
+import xmlrpclib
+
+
+class SCGITransport(xmlrpclib.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.iteritems())) + '\x00'
+ header = '%d:%s' % (len(header), header)
+ request_body = '%s,%s' % (header, request_body)
+
+ sock = None
+
+ try:
+ if host:
+ host, port = urllib.splitport(host)
+ 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(request_body)
+ 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(xmlrpclib.ServerProxy):
+ def __init__(self, uri, transport=None, encoding=None, verbose=False,
+ allow_none=False, use_datetime=False):
+ type, uri = urllib.splittype(uri)
+ if type not in ('scgi'):
+ raise IOError('unsupported XML-RPC protocol')
+ self.__host, self.__handler = urllib.splithost(uri)
+ 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 = xmlrpclib.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 xmlrpclib._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,))
diff --git a/libs/rtorrent/rpc/__init__.py b/libs/rtorrent/rpc/__init__.py
index 034f4eef..116ca1c2 100755
--- a/libs/rtorrent/rpc/__init__.py
+++ b/libs/rtorrent/rpc/__init__.py
@@ -17,66 +17,16 @@
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-from base64 import encodestring
-import httplib
-import inspect
-import string
+import inspect
import rtorrent
import re
from rtorrent.common import bool_to_int, convert_version_tuple_to_str,\
safe_repr
-from rtorrent.err import RTorrentVersionError, MethodError
+from rtorrent.err import MethodError
from rtorrent.compat import xmlrpclib
-class BasicAuthTransport(xmlrpclib.Transport):
- def __init__(self, username=None, password=None):
- xmlrpclib.Transport.__init__(self)
- self.username = username
- self.password = password
-
- def send_auth(self, h):
- if self.username is not None and self.password is not None:
- h.putheader('AUTHORIZATION', "Basic %s" % string.replace(
- encodestring("%s:%s" % (self.username, self.password)),
- "\012", ""
- ))
-
- def single_request(self, host, handler, request_body, verbose=0):
- # issue XML-RPC request
-
- h = self.make_connection(host)
- if verbose:
- h.set_debuglevel(1)
-
- try:
- self.send_request(h, handler, request_body)
- self.send_host(h, host)
- self.send_user_agent(h)
- self.send_auth(h)
- self.send_content(h, request_body)
-
- response = h.getresponse(buffering=True)
- if response.status == 200:
- self.verbose = verbose
- return self.parse_response(response)
- except xmlrpclib.Fault:
- raise
- except Exception:
- self.close()
- raise
-
- #discard any response data and raise exception
- if (response.getheader("content-length", 0)):
- response.read()
- raise xmlrpclib.ProtocolError(
- host + handler,
- response.status, response.reason,
- response.msg,
- )
-
-
def get_varname(rpc_call):
"""Transform rpc method into variable name.
diff --git a/libs/rtorrent/torrent.py b/libs/rtorrent/torrent.py
index c610e368..bd6bb689 100755
--- a/libs/rtorrent/torrent.py
+++ b/libs/rtorrent/torrent.py
@@ -172,6 +172,17 @@ class Torrent:
self.directory = m.call()[-1]
+ def set_directory_base(self, d):
+ """Modify base download directory
+
+ @note: Needs to stop torrent in order to change the directory.
+ Also doesn't restart after directory is set, that must be called
+ separately.
+ """
+ m = rtorrent.rpc.Multicall(self)
+ self.multicall_add(m, "d.try_stop")
+ self.multicall_add(m, "d.set_directory_base", d)
+
def start(self):
"""Start the torrent"""
m = rtorrent.rpc.Multicall(self)