Browse Source

Merge branch 'master' into develop

pull/1200/head
JackDandy 6 years ago
parent
commit
6fb9eb0cd7
  1. 6
      CHANGES.md
  2. 1
      HACKS.txt
  3. 2
      gui/slick/interfaces/default/config_search.tmpl
  4. 20
      gui/slick/js/configSearch.js
  5. 2
      lib/cfscrape/__init__.py
  6. 811
      lib/rtorrent/__init__.py
  7. 92
      lib/rtorrent/common.py
  8. 10
      lib/rtorrent/compat.py
  9. 8
      lib/rtorrent/err.py
  10. 79
      lib/rtorrent/file.py
  11. 55
      lib/rtorrent/group.py
  12. 78
      lib/rtorrent/lib/torrentparser.py
  13. 47
      lib/rtorrent/lib/xmlrpc/basic_auth.py
  14. 2
      lib/rtorrent/lib/xmlrpc/http.py
  15. 74
      lib/rtorrent/lib/xmlrpc/scgi.py
  16. 68
      lib/rtorrent/peer.py
  17. 226
      lib/rtorrent/rpc/__init__.py
  18. 517
      lib/rtorrent/torrent.py
  19. 77
      lib/rtorrent/tracker.py
  20. 2
      sickbeard/clients/generic.py
  21. 63
      sickbeard/clients/rtorrent.py

6
CHANGES.md

@ -33,6 +33,12 @@
### 0.19.6 (2019-06-24 00:15:00 UTC)
* Change add rTorrent 0.9.7 compatibility
* Change improve Cloudflare connectivity
### 0.19.5 (2019-06-13 18:25:00 UTC)
* Update Js2Py 0.43 (da310bb) to 0.64 (efbfcca)

1
HACKS.txt

@ -10,6 +10,7 @@ Libs with customisations...
/lib/hachoir_parser/guess.py
/lib/hachoir_parser/misc/torrent.py
/lib/lockfile/mkdirlockfile.py
/lib/rtorrent
/lib/scandir/scandir.py
/lib/tmdb_api/tmdb_api.py
/lib/tornado

2
gui/slick/interfaces/default/config_search.tmpl

@ -516,7 +516,7 @@
</label>
</div>
<div class="field-pair">
<div class="field-pair" id="torrent-password-option">
<label>
<span class="component-title" id="password-title">Client password</span>
<span class="component-desc">

20
gui/slick/js/configSearch.js

@ -1,7 +1,13 @@
/** @namespace $.SickGear.Root */
/** @namespace config.defaultHost */
$(document).ready(function(){
var loading = '<img src="' + sbRoot + '/images/loading16' + themeSpinner + '.gif" height="16" width="16" />';
var loading = '<img src="' + $.SickGear.Root + '/images/loading16' + themeSpinner + '.gif" height="16" width="16" />',
handleSCGI = function() {
var opts$ = $('#torrent-username-option,#torrent-password-option');
$('#torrent_username,#torrent_password').each(function() {$(this).val(
/^\s*scgi:\/\//.test($('#torrent_host').val()) ? opts$.hide() && $('#torrent_label').select() && ''
: opts$.show() && $('#torrent_username').select() && $(this).prop('defaultValue'));});};
function toggleTorrentTitle() {
var noTorrent$ = $('#no_torrents');
@ -65,7 +71,8 @@ $(document).ready(function(){
pathBlank = '#path-blank',
seedTimeOption = '#torrent-seed-time-option',
pausedOption = '#torrent-paused-option',
highBandwidthOption = '#torrent-high-bandwidth-option';
highBandwidthOption = '#torrent-high-bandwidth-option',
torrentHost$ = $('#torrent_host');
$([labelWarningDeluge, hostDescDeluge, hostDescRtorrent, verifyCertOption, seedTimeOption,
highBandwidthOption, qBitTorrent, synology, transmission].join(',')).hide();
@ -73,6 +80,8 @@ $(document).ready(function(){
$([hostDesc, usernameOption, pathOption, labelOption, pathBlank, pausedOption].join(',')).show();
$(pathOption).find('.fileBrowser').show();
torrentHost$.off('blur');
switch (selectedProvider) {
case 'utorrent':
client = 'uTorrent';
@ -99,8 +108,9 @@ $(document).ready(function(){
$(synology).show();
break;
case 'rtorrent':
client = 'rTorrent'; hideHostDesc = !0; hidePausedOption = !0;
client = 'rTorrent'; hideHostDesc = !0;
$(hostDescRtorrent).show();
torrentHost$.on('blur', handleSCGI);
break;
}
hideHostDesc && $(hostDesc).hide();
@ -129,8 +139,10 @@ $(document).ready(function(){
$('#test_torrent').click(function() {
$('#test-torrent-result').html(loading);
var method = $('#torrent_method').find(':selected').val();
(('rtorrent' === method) && handleSCGI());
$.get(sbRoot + '/home/test_torrent',
{'torrent_method': $('#torrent_method').find(':selected').val(), 'host': $('#torrent_host').val(),
{'torrent_method': method, 'host': $('#torrent_host').val(),
'username': $('#torrent_username').val(), 'password': $('#torrent_password').val()},
function(data) { $(this).testResult(data, '#test-torrent-result'); });
});

2
lib/cfscrape/__init__.py

@ -199,7 +199,7 @@ class CloudflareScraper(Session):
([], ['GREASE_3A', 'GREASE_6A', 'AES128-GCM-SHA256', 'AES256-GCM-SHA256', 'AES256-GCM-SHA384',
'CHACHA20-POLY1305-SHA256'])[hasattr(ssl, 'PROTOCOL_TLSv1_3')] +
['ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES128-GCM-SHA256',
'ECDHE-ECDSA-AES256-GCM-SHA384', 'ECDHE-RSA-AES256-GCM-SHA384',
'ECDHE-ECDSA-AES256-GCM-SHA384',
'ECDHE-ECDSA-CHACHA20-POLY1305-SHA256', 'ECDHE-RSA-CHACHA20-POLY1305-SHA256',
'ECDHE-RSA-AES128-CBC-SHA', 'ECDHE-RSA-AES256-CBC-SHA', 'RSA-AES128-GCM-SHA256',
'RSA-AES256-GCM-SHA384', 'ECDHE-RSA-AES128-GCM-SHA256', 'RSA-AES256-SHA', '3DES-EDE-CBC']):

811
lib/rtorrent/__init__.py

File diff suppressed because it is too large

92
lib/rtorrent/common.py

@ -10,26 +10,25 @@
# 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,
# 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 compat import is_py3, urlparse, urlunparse, ParseResult
from rtorrent.compat import is_py3
import os
def bool_to_int(value):
"""Translates python booleans to RPC-safe integers"""
if value is True:
return("1")
return '1'
elif value is False:
return("0")
else:
return(value)
return '0'
return value
def cmd_exists(cmds_list, cmd):
@ -44,7 +43,7 @@ def cmd_exists(cmds_list, cmd):
@return: bool
"""
return(cmd in cmds_list)
return cmd in cmds_list
def find_torrent(info_hash, torrent_list):
@ -67,11 +66,11 @@ def find_torrent(info_hash, torrent_list):
def is_valid_port(port):
"""Check if given port is valid"""
return(0 <= int(port) <= 65535)
return 0 <= int(port) <= 65535
def convert_version_tuple_to_str(t):
return(".".join([str(n) for n in t]))
return '.'.join([str(n) for n in t])
def safe_repr(fmt, *args, **kwargs):
@ -79,8 +78,73 @@ def safe_repr(fmt, *args, **kwargs):
if not is_py3():
# unicode fmt can take str args, str fmt cannot take unicode args
fmt = fmt.decode("utf-8")
fmt = fmt.decode('utf-8')
out = fmt.format(*args, **kwargs)
return out.encode("utf-8")
else:
return fmt.format(*args, **kwargs)
return out.encode('utf-8')
return fmt.format(*args, **kwargs)
def split_path(path):
fragments = path.split('/')
if 1 == len(fragments):
return fragments
if not fragments[-1]:
return fragments[:-1]
return fragments
def join_path(base, path):
# Return if we have a new absolute path
if os.path.isabs(path):
return path
# non-absolute base encountered
if base and not os.path.isabs(base):
raise NotImplementedError()
return '/'.join(split_path(base) + split_path(path))
def join_uri(base, uri, construct=True):
p_uri = urlparse(uri)
# Return if there is nothing to join
if not p_uri.path:
return base
scheme, netloc, path, params, query, fragment = urlparse(base)
# Switch to 'uri' parts
_, _, _, params, query, fragment = p_uri
path = join_path(path, p_uri.path)
result = ParseResult(scheme, netloc, path, params, query, fragment)
if not construct:
return result
# Construct from parts
return urlunparse(result)
def update_uri(uri, construct=True, **kwargs):
if isinstance(uri, ParseResult):
# noinspection PyProtectedMember
uri = dict(uri._asdict())
if type(uri) is not dict:
raise ValueError('Unknown URI type')
uri.update(kwargs)
result = ParseResult(**uri)
if not construct:
return result
return urlunparse(result)

10
lib/rtorrent/compat.py

@ -24,7 +24,17 @@ import sys
def is_py3():
return sys.version_info[0] == 3
if is_py3():
# noinspection PyCompatibility
from urllib.parse import parse_qs, ParseResult, urlparse, urlunparse
# noinspection PyCompatibility
from urllib.request import urlopen
import xmlrpc.client as xmlrpclib
else:
from urllib2 import urlopen
# noinspection PyProtectedMember
from urlparse import parse_qs, ParseResult, urlparse, urlunparse
import xmlrpclib

8
lib/rtorrent/err.py

@ -18,18 +18,18 @@
# 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 rtorrent.common import convert_version_tuple_to_str
from common import convert_version_tuple_to_str
class RTorrentVersionError(Exception):
def __init__(self, min_version, cur_version):
self.min_version = min_version
self.cur_version = cur_version
self.msg = "Minimum version required: {0}".format(
self.msg = 'Minimum version required: {0}'.format(
convert_version_tuple_to_str(min_version))
def __str__(self):
return(self.msg)
return self.msg
class MethodError(Exception):
@ -37,4 +37,4 @@ class MethodError(Exception):
self.msg = msg
def __str__(self):
return(self.msg)
return self.msg

79
lib/rtorrent/file.py

@ -18,25 +18,22 @@
# 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 rtorrent.rpc import Method
import rtorrent.rpc
from .common import safe_repr
from .rpc import Method
import rpc
from rtorrent.common import safe_repr
Method = rtorrent.rpc.Method
class File:
class File(object):
"""Represents an individual file within a L{Torrent} instance."""
def __init__(self, _rt_obj, info_hash, index, **kwargs):
self._rt_obj = _rt_obj
self.info_hash = info_hash # : info hash for the torrent the file is associated with
self.index = index # : The position of the file within the file list
for k in kwargs.keys():
for k in kwargs:
setattr(self, k, kwargs.get(k, None))
self.rpc_id = "{0}:f{1}".format(
self.rpc_id = '{0}:f{1}'.format(
self.info_hash, self.index) # : unique id to pass to rTorrent
def update(self):
@ -46,44 +43,58 @@ class File:
@return: None
"""
multicall = rtorrent.rpc.Multicall(self)
retriever_methods = [m for m in methods
if m.is_retriever() and m.is_available(self._rt_obj)]
for method in retriever_methods:
multicall.add(method, self.rpc_id)
mc = rpc.Multicall(self)
multicall.call()
for method in filter(lambda m: m.is_retriever() and m.is_available(self._rt_obj), methods):
mc.add(method, self.rpc_id)
mc.call()
def __repr__(self):
return safe_repr("File(index={0} path=\"{1}\")", self.index, self.path)
return safe_repr('File(index={0} path="{1}")', self.index, self.path)
methods = [
# RETRIEVERS
Method(File, 'get_last_touched', 'f.get_last_touched'),
Method(File, 'get_range_second', 'f.get_range_second'),
Method(File, 'get_size_bytes', 'f.get_size_bytes'),
Method(File, 'get_priority', 'f.get_priority'),
Method(File, 'get_match_depth_next', 'f.get_match_depth_next'),
Method(File, 'get_last_touched', 'f.get_last_touched',
aliases=('f.last_touched',)),
Method(File, 'get_range_second', 'f.get_range_second',
aliases=('f.range_second',)),
Method(File, 'get_size_bytes', 'f.get_size_bytes',
aliases=('f.size_bytes',)),
Method(File, 'get_priority', 'f.get_priority',
aliases=('f.priority',)),
Method(File, 'get_match_depth_next', 'f.get_match_depth_next',
aliases=('f.match_depth_next',)),
Method(File, 'is_resize_queued', 'f.is_resize_queued',
boolean=True,
boolean=True
),
Method(File, 'get_range_first', 'f.get_range_first'),
Method(File, 'get_match_depth_prev', 'f.get_match_depth_prev'),
Method(File, 'get_path', 'f.get_path'),
Method(File, 'get_completed_chunks', 'f.get_completed_chunks'),
Method(File, 'get_path_components', 'f.get_path_components'),
Method(File, 'get_range_first', 'f.get_range_first',
aliases=('f.range_first',)),
Method(File, 'get_match_depth_prev', 'f.get_match_depth_prev',
aliases=('f.match_depth_prev',)),
Method(File, 'get_path', 'f.get_path',
aliases=('f.path',)),
Method(File, 'get_completed_chunks', 'f.get_completed_chunks',
aliases=('f.completed_chunks',)),
Method(File, 'get_path_components', 'f.get_path_components',
aliases=('f.path_components',)),
Method(File, 'is_created', 'f.is_created',
boolean=True,
boolean=True
),
Method(File, 'is_open', 'f.is_open',
boolean=True,
boolean=True
),
Method(File, 'get_size_chunks', 'f.get_size_chunks'),
Method(File, 'get_offset', 'f.get_offset'),
Method(File, 'get_frozen_path', 'f.get_frozen_path'),
Method(File, 'get_path_depth', 'f.get_path_depth'),
Method(File, 'get_size_chunks', 'f.get_size_chunks',
aliases=('f.size_chunks',)),
Method(File, 'get_offset', 'f.get_offset',
aliases=('f.offset',)),
Method(File, 'get_frozen_path', 'f.get_frozen_path',
aliases=('f.frozen_path',)),
Method(File, 'get_path_depth', 'f.get_path_depth',
aliases=('f.path_depth',)),
Method(File, 'is_create_queued', 'f.is_create_queued',
boolean=True,
boolean=True
),

55
lib/rtorrent/group.py

@ -18,12 +18,11 @@
# 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 rtorrent.rpc
from .rpc import Method
import rpc
Method = rtorrent.rpc.Method
class Group:
class Group(object):
__name__ = 'Group'
def __init__(self, _rt_obj, name):
@ -42,43 +41,49 @@ class Group:
Method(Group, 'set_upload', 'group.' + self.name + '.ratio.upload.set', varname='upload')
]
rtorrent.rpc._build_rpc_methods(self, self.methods)
# noinspection PyProtectedMember
rpc._build_rpc_methods(self, self.methods)
# Setup multicall_add method
caller = lambda multicall, method, *args: \
multicall.add(method, *args)
setattr(self, "multicall_add", caller)
caller = (lambda mc, method, *args: mc.add(method, *args))
setattr(self, 'multicall_add', caller)
def _get_prefix(self):
return 'group.' + self.name + '.ratio.'
def update(self):
multicall = rtorrent.rpc.Multicall(self)
retriever_methods = [m for m in self.methods
if m.is_retriever() and m.is_available(self._rt_obj)]
mc = rpc.Multicall(self)
for method in retriever_methods:
multicall.add(method)
for method in filter(lambda m: m.is_retriever() and m.is_available(self._rt_obj), self.methods):
mc.add(method)
multicall.call()
mc.call()
def enable(self):
p = self._rt_obj._get_conn()
p = self._rt_obj.get_connection()
return getattr(p, self._get_prefix() + 'enable')()
def disable(self):
p = self._rt_obj._get_conn()
p = self._rt_obj.get_connection()
return getattr(p, self._get_prefix() + 'disable')()
def _get_method(self, *choices):
try:
return filter(lambda method: self._rt_obj.method_exists(method), choices)[0]
except(Exception, BaseException):
pass
def set_command(self, *methods):
methods = [m + '=' for m in methods]
method = self._get_method(*('system.method.set', 'method.set'))
if method:
methods = [m + '=' for m in methods]
mc = rpc.Multicall(self)
m = rtorrent.rpc.Multicall(self)
self.multicall_add(
m, 'system.method.set',
self._get_prefix() + 'command',
*methods
)
self.multicall_add(
mc, method,
self._get_prefix() + 'command',
*methods
)
return(m.call()[-1])
return mc.call()[-1]

78
lib/rtorrent/lib/torrentparser.py

@ -18,19 +18,15 @@
# 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 rtorrent.compat import is_py3
from ..compat import parse_qs, urlopen, urlparse
import bencode
import hashlib
import os.path
import re
import rtorrent.lib.bencode as bencode
import hashlib
if is_py3():
from urllib.request import urlopen # @UnresolvedImport @UnusedImport
else:
from urllib2 import urlopen # @UnresolvedImport @Reimport
class TorrentParser():
class TorrentParser(object):
def __init__(self, torrent):
"""Decode and parse given torrent
@ -48,12 +44,16 @@ class TorrentParser():
self.file_type = None
self._get_raw_torrent()
assert self._raw_torrent is not None, "Couldn't get raw_torrent."
assert self._raw_torrent is not None, 'Couldn\'t get raw_torrent.'
if self._torrent_decoded is None:
self._decode_torrent()
assert isinstance(self._torrent_decoded, dict), "Invalid torrent file."
assert isinstance(self._torrent_decoded, dict), 'Invalid torrent file.'
self._parse_torrent()
@property
def raw_torrent(self):
return self._raw_torrent
def _is_raw(self):
raw = False
if isinstance(self.torrent, (str, bytes)):
@ -63,43 +63,57 @@ class TorrentParser():
# reset self._torrent_decoded (currently equals False)
self._torrent_decoded = None
return(raw)
return raw
def _get_raw_torrent(self):
"""Get raw torrent data by determining what self.torrent is"""
# already raw?
if self._is_raw():
self.file_type = "raw"
self.file_type = 'raw'
self._raw_torrent = self.torrent
return
# local file?
if os.path.isfile(self.torrent):
self.file_type = "file"
self._raw_torrent = open(self.torrent, "rb").read()
self.file_type = 'file'
self._raw_torrent = open(self.torrent, 'rb').read()
# url?
elif re.search("^(http|ftp)s?:\/\/", self.torrent, re.I):
self.file_type = "url"
elif re.search(r'^(http|ftp)s?://', self.torrent, re.I):
self.file_type = 'url'
self._raw_torrent = urlopen(self.torrent).read()
# magnet link?
elif re.match(r'magnet:', self.torrent, re.I):
self.file_type = 'url'
query = {}
try:
parsed = urlparse(self.torrent)
query = parse_qs(parsed.query)
except(Exception, BaseException):
pass
self.title = query.get('dn', [''])[0]
self.info_hash = query.get('xt', [''])[0].split(':')[-1]
self.trackers = query.get('tr', [])
self._torrent_decoded = query
self._raw_torrent = self.torrent
def _decode_torrent(self, raw_torrent=None):
if raw_torrent is None:
raw_torrent = self._raw_torrent
self._torrent_decoded = bencode.decode(raw_torrent)
return(self._torrent_decoded)
return self._torrent_decoded
def _calc_info_hash(self):
self.info_hash = None
if "info" in self._torrent_decoded.keys():
info_encoded = bencode.encode(self._torrent_decoded["info"])
if 'info' in self._torrent_decoded:
info_encoded = bencode.encode(self._torrent_decoded['info'])
if info_encoded:
self.info_hash = hashlib.sha1(info_encoded).hexdigest().upper()
return(self.info_hash)
return self.info_hash
def _parse_torrent(self):
for k in self._torrent_decoded:
key = k.replace(" ", "_").lower()
key = k.replace(' ', '_').lower()
setattr(self, key, self._torrent_decoded[k])
self._calc_info_hash()
@ -119,8 +133,8 @@ class NewTorrentParser(object):
def _decode_torrent(data):
return bencode.decode(data)
def __init__(self, input):
self.input = input
def __init__(self, infile):
self.input = infile
self._raw_torrent = None
self._decoded_torrent = None
self._hash_outdated = False
@ -128,33 +142,33 @@ class NewTorrentParser(object):
if isinstance(self.input, (str, bytes)):
# path to file?
if os.path.isfile(self.input):
self._raw_torrent = self._read_file(open(self.input, "rb"))
self._raw_torrent = self._read_file(open(self.input, 'rb'))
else:
# assume input was the raw torrent data (do we really want
# this?)
self._raw_torrent = self.input
# file-like object?
elif self.input.hasattr("read"):
elif self.input.hasattr('read'):
self._raw_torrent = self._read_file(self.input)
assert self._raw_torrent is not None, "Invalid input: input must be a path or a file-like object"
assert self._raw_torrent is not None, 'Invalid input: input must be a path or a file-like object'
self._decoded_torrent = self._decode_torrent(self._raw_torrent)
assert isinstance(
self._decoded_torrent, dict), "File could not be decoded"
self._decoded_torrent, dict), 'File could not be decoded'
def _calc_info_hash(self):
self.info_hash = None
info_dict = self._torrent_decoded["info"]
info_dict = self._torrent_decoded['info']
self.info_hash = hashlib.sha1(bencode.encode(
info_dict)).hexdigest().upper()
return(self.info_hash)
return self.info_hash
def set_tracker(self, tracker):
self._decoded_torrent["announce"] = tracker
self._decoded_torrent['announce'] = tracker
def get_tracker(self):
return self._decoded_torrent.get("announce")
return self._decoded_torrent.get('announce')

47
lib/rtorrent/lib/xmlrpc/basic_auth.py

@ -20,27 +20,44 @@
# 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
try:
import xmlrpc.client as xmlrpclib
except:
import xmlrpclib
from ...compat import xmlrpclib
from base64 import b64encode
import httplib
class BasicAuthTransport(xmlrpclib.Transport):
def __init__(self, username=None, password=None):
def __init__(self, secure=False, username=None, password=None):
xmlrpclib.Transport.__init__(self)
self.secure = secure
self.username = username
self.password = password
self.verbose = None
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", ""
))
if self.username and self.password:
h.putheader('Authorization', 'Basic %s' % b64encode('%s:%s' % (self.username, self.password)))
def make_connection(self, host):
if self._connection and host == self._connection[0]:
return self._connection[1]
chost, self._extra_headers, x509 = self.get_host_info(host)
if self.secure:
try:
self._connection = host, httplib.HTTPSConnection(chost, None, **(x509 or {}))
except AttributeError:
raise NotImplementedError(
'In use version of httplib doesn\'t support HTTPS'
)
else:
self._connection = host, httplib.HTTPConnection(chost)
return self._connection[1]
def single_request(self, host, handler, request_body, verbose=0):
# issue XML-RPC request
@ -57,7 +74,7 @@ class BasicAuthTransport(xmlrpclib.Transport):
self.send_content(h, request_body)
response = h.getresponse(buffering=True)
if response.status == 200:
if 200 == response.status:
self.verbose = verbose
return self.parse_response(response)
except xmlrpclib.Fault:
@ -66,8 +83,8 @@ class BasicAuthTransport(xmlrpclib.Transport):
self.close()
raise
#discard any response data and raise exception
if response.getheader("content-length", 0):
# discard any response data and raise exception
if response.getheader('content-length', 0):
response.read()
raise xmlrpclib.ProtocolError(
host + handler,

2
lib/rtorrent/lib/xmlrpc/http.py

@ -18,6 +18,6 @@
# 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 rtorrent.compat import xmlrpclib
from ...compat import xmlrpclib
HTTPServerProxy = xmlrpclib.ServerProxy

74
lib/rtorrent/lib/xmlrpc/scgi.py

@ -82,39 +82,14 @@
# OF THIS SOFTWARE.
from __future__ import print_function
try:
import http.client as httplib
except ImportError:
import httplib
import re
import socket
import sys
try:
import urllib.parse as urlparser
except ImportError:
import urllib as urlparser
try:
import xmlrpc.client as xmlrpclib
except:
import xmlrpclib
import errno
from ...compat import urlparse, xmlrpclib
class SCGITransport(xmlrpclib.Transport):
# Added request() from Python 2.7 xmlrpclib here to backport to Python 2.6
def request(self, host, handler, request_body, verbose=0):
#retry request once if cached connection has gone cold
for i in (0, 1):
try:
return self.single_request(host, handler, request_body, verbose)
except socket.error as e:
if i or e.errno not in (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE):
raise
except httplib.BadStatusLine: #close after we sent request
if i:
raise
def single_request(self, host, handler, request_body, verbose=0):
# Add SCGI headers to the request.
@ -127,7 +102,9 @@ class SCGITransport(xmlrpclib.Transport):
try:
if host:
host, port = urlparser.splitport(host)
parsed = urlparse('//' + host)
host, port = parsed.hostname, parsed.port
addrinfo = socket.getaddrinfo(host, int(port), socket.AF_INET,
socket.SOCK_STREAM)
sock = socket.socket(*addrinfo[0][:3])
@ -136,10 +113,12 @@ class SCGITransport(xmlrpclib.Transport):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(handler)
# noinspection PyAttributeOutsideInit
self.verbose = verbose
if sys.version_info[0] > 2:
sock.send(bytes(request_body, "utf-8"))
# noinspection PyArgumentList
sock.send(bytes(request_body, 'utf-8')) # py3
else:
sock.send(request_body)
return self.parse_response(sock.makefile())
@ -163,10 +142,9 @@ class SCGITransport(xmlrpclib.Transport):
print('body:', repr(response_body))
try:
response_header, response_body = re.split(r'\n\s*?\n', response_body,
maxsplit=1)
response_header, response_body = re.split(r'\n\s*?\n', response_body, maxsplit=1)
except ValueError:
print("error in response: %s", response_body)
print('error in response: %s', response_body)
p.close()
u.close()
@ -177,12 +155,14 @@ class SCGITransport(xmlrpclib.Transport):
class SCGIServerProxy(xmlrpclib.ServerProxy):
def __init__(self, uri, transport=None, encoding=None, verbose=False,
allow_none=False, use_datetime=False):
type, uri = urlparser.splittype(uri)
if type not in ('scgi'):
# noinspection PyMissingConstructor
def __init__(self, uri, transport=None, encoding=None, verbose=False, allow_none=False, use_datetime=False):
parsed = urlparse(uri)
if 'scgi' != parsed.scheme:
raise IOError('unsupported XML-RPC protocol')
self.__host, self.__handler = urlparser.splithost(uri)
self.__host, self.__handler = parsed.netloc, parsed.path
if not self.__handler:
self.__handler = '/'
@ -194,14 +174,10 @@ class SCGIServerProxy(xmlrpclib.ServerProxy):
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)
request = xmlrpclib.dumps(params, methodname, encoding=self.__encoding, allow_none=self.__allow_none)
response = self.__transport.request(
self.__host,
@ -210,22 +186,22 @@ class SCGIServerProxy(xmlrpclib.ServerProxy):
verbose=self.__verbose
)
if len(response) == 1:
if 1 == len(response):
response = response[0]
return response
def __repr__(self):
return (
"<SCGIServerProxy for %s%s>" %
(self.__host, self.__handler)
)
return ('<SCGIServerProxy for %s%s>' %
(self.__host, self.__handler))
__str__ = __repr__
def __getattr__(self, name):
# magic method dispatcher
# noinspection PyProtectedMember
return xmlrpclib._Method(self.__request, name)
# return _Method(self.__request, name)
# note: to call a remote object with an non-standard name, use
# result getattr(server, "strange-python-name")(args)
@ -234,8 +210,8 @@ class SCGIServerProxy(xmlrpclib.ServerProxy):
"""A workaround to get special attributes on the ServerProxy
without interfering with the magic __getattr__
"""
if attr == "close":
if 'close' == attr:
return self.__close
elif attr == "transport":
elif 'transport' == attr:
return self.__transport
raise AttributeError("Attribute %r not found" % (attr,))
raise AttributeError('Attribute %r not found' % (attr,))

68
lib/rtorrent/peer.py

@ -18,27 +18,24 @@
# 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 rtorrent.rpc import Method
import rtorrent.rpc
from .common import safe_repr
from .rpc import Method
import rpc
from rtorrent.common import safe_repr
Method = rtorrent.rpc.Method
class Peer:
class Peer(object):
"""Represents an individual peer within a L{Torrent} instance."""
def __init__(self, _rt_obj, info_hash, **kwargs):
self._rt_obj = _rt_obj
self.info_hash = info_hash # : info hash for the torrent the peer is associated with
for k in kwargs.keys():
for k in kwargs:
setattr(self, k, kwargs.get(k, None))
self.rpc_id = "{0}:p{1}".format(
self.rpc_id = '{0}:p{1}'.format(
self.info_hash, self.id) # : unique id to pass to rTorrent
def __repr__(self):
return safe_repr("Peer(id={0})", self.id)
return safe_repr('Peer(id={0})', self.id)
def update(self):
"""Refresh peer data
@ -47,52 +44,65 @@ class Peer:
@return: None
"""
multicall = rtorrent.rpc.Multicall(self)
retriever_methods = [m for m in methods
if m.is_retriever() and m.is_available(self._rt_obj)]
for method in retriever_methods:
multicall.add(method, self.rpc_id)
mc = rpc.Multicall(self)
for method in filter(lambda fx: fx.is_retriever() and fx.is_available(self._rt_obj), methods):
mc.add(method, self.rpc_id)
mc.call()
multicall.call()
methods = [
# RETRIEVERS
Method(Peer, 'is_preferred', 'p.is_preferred',
boolean=True,
),
Method(Peer, 'get_down_rate', 'p.get_down_rate'),
Method(Peer, 'get_down_rate', 'p.get_down_rate',
aliases=('p.down_rate',)),
Method(Peer, 'is_unwanted', 'p.is_unwanted',
boolean=True,
),
Method(Peer, 'get_peer_total', 'p.get_peer_total'),
Method(Peer, 'get_peer_rate', 'p.get_peer_rate'),
Method(Peer, 'get_port', 'p.get_port'),
Method(Peer, 'get_peer_total', 'p.get_peer_total',
aliases=('p.peer_total',)),
Method(Peer, 'get_peer_rate', 'p.get_peer_rate',
aliases=('p.peer_rate',)),
Method(Peer, 'get_port', 'p.get_port',
aliases=('p.port',)),
Method(Peer, 'is_snubbed', 'p.is_snubbed',
boolean=True,
),
Method(Peer, 'get_id_html', 'p.get_id_html'),
Method(Peer, 'get_up_rate', 'p.get_up_rate'),
Method(Peer, 'get_id_html', 'p.get_id_html',
aliases=('p.id_html',)),
Method(Peer, 'get_up_rate', 'p.get_up_rate',
aliases=('p.up_rate',)),
Method(Peer, 'is_banned', 'p.banned',
boolean=True,
),
Method(Peer, 'get_completed_percent', 'p.get_completed_percent'),
Method(Peer, 'get_completed_percent', 'p.get_completed_percent',
aliases=('p.completed_percent',)),
Method(Peer, 'completed_percent', 'p.completed_percent'),
Method(Peer, 'get_id', 'p.get_id'),
Method(Peer, 'get_id', 'p.get_id',
aliases=('p.id',)),
Method(Peer, 'is_obfuscated', 'p.is_obfuscated',
boolean=True,
),
Method(Peer, 'get_down_total', 'p.get_down_total'),
Method(Peer, 'get_client_version', 'p.get_client_version'),
Method(Peer, 'get_address', 'p.get_address'),
Method(Peer, 'get_down_total', 'p.get_down_total',
aliases=('p.down_total',)),
Method(Peer, 'get_client_version', 'p.get_client_version',
aliases=('p.client_version',)),
Method(Peer, 'get_address', 'p.get_address',
aliases=('p.address',)),
Method(Peer, 'is_incoming', 'p.is_incoming',
boolean=True,
),
Method(Peer, 'is_encrypted', 'p.is_encrypted',
boolean=True,
),
Method(Peer, 'get_options_str', 'p.get_options_str'),
Method(Peer, 'get_options_str', 'p.get_options_str',
aliases=('p.options_str',)),
Method(Peer, 'get_client_version', 'p.client_version'),
Method(Peer, 'get_up_total', 'p.get_up_total'),
Method(Peer, 'get_up_total', 'p.get_up_total',
aliases=('p.up_total',)),
# MODIFIERS
]

226
lib/rtorrent/rpc/__init__.py

@ -10,7 +10,7 @@
# 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,
# 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
@ -18,13 +18,14 @@
# 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 ..common import bool_to_int, convert_version_tuple_to_str, safe_repr
from ..compat import xmlrpclib
from ..err import MethodError
import inspect
import rtorrent
import re
from rtorrent.common import bool_to_int, convert_version_tuple_to_str,\
safe_repr
from rtorrent.err import MethodError
from rtorrent.compat import xmlrpclib
import rtorrent
def get_varname(rpc_call):
@ -36,91 +37,78 @@ def get_varname(rpc_call):
"""
# extract variable name from xmlrpc func name
r = re.search(
"([ptdf]\.|system\.|get\_|is\_|set\_)+([^=]*)", rpc_call, re.I)
r'(^([dtfp]|system)\.|(is|[sg]et)_)+([^=]*?)(?:=|.[sg]et)?$', rpc_call, re.I)
if r:
return(r.groups()[-1])
else:
return(None)
return r.groups()[-1]
def _handle_unavailable_rpc_method(method, rt_obj):
msg = "Method isn't available."
if rt_obj._get_client_version_tuple() < method.min_version:
msg = "This method is only available in " \
"RTorrent version v{0} or later".format(
convert_version_tuple_to_str(method.min_version))
msg = 'Method isn\'t available.'
if rt_obj.get_client_version_tuple() < method.min_version:
msg = 'This method is only available in ' \
'RTorrent version v{0} or later'.format(convert_version_tuple_to_str(method.min_version))
raise MethodError(msg)
class DummyClass:
class DummyClass(object):
def __init__(self):
pass
class Method:
class Method(object):
"""Represents an individual RPC method"""
def __init__(self, _class, method_name,
rpc_call, docstring=None, varname=None, **kwargs):
def __init__(self, _class, method_name, rpc_call, docstring=None, **kwargs):
self._class = _class # : Class this method is associated with
self.class_name = _class.__name__
self.method_name = method_name # : name of public-facing method
self.rpc_call = rpc_call # : name of rpc method
self.docstring = docstring # : docstring for rpc method (optional)
self.varname = varname # : variable for the result of the method call, usually set to self.varname
self.min_version = kwargs.get("min_version", (
0, 0, 0)) # : Minimum version of rTorrent required
self.boolean = kwargs.get("boolean", False) # : returns boolean value?
self.post_process_func = kwargs.get(
"post_process_func", None) # : custom post process function
self.aliases = kwargs.get(
"aliases", []) # : aliases for method (optional)
self.required_args = []
#: Arguments required when calling the method (not utilized)
self.min_version = kwargs.get('min_version', (0, 0, 0)) # : Minimum version of rTorrent required
self.boolean = kwargs.get('boolean', False) # : returns boolean value?
self.post_process_func = kwargs.get('post_process_func', None) # : custom post process function
self.aliases = kwargs.get('aliases', []) # : aliases for method (optional)
self.required_args = [] # : Arguments required when calling the method (not utilized)
self.method_type = self._get_method_type()
if self.varname is None:
self.varname = get_varname(self.rpc_call)
assert self.varname is not None, "Couldn't get variable name."
self.varname = None # : variable for the result of the method call, usually set to self.varname
def __repr__(self):
return safe_repr("Method(method_name='{0}', rpc_call='{1}')",
self.method_name, self.rpc_call)
return safe_repr('Method(method_name="{0}", rpc_call="{1}")', self.method_name, self.rpc_call)
def _get_method_type(self):
"""Determine whether method is a modifier or a retriever"""
if self.method_name[:4] == "set_": return('m') # modifier
else:
return('r') # retriever
if 'set_' == self.method_name[:4]:
return 'm' # modifier
return 'r' # retriever
def is_modifier(self):
if self.method_type == 'm':
return(True)
else:
return(False)
return 'm' == self.method_type
def is_retriever(self):
if self.method_type == 'r':
return(True)
else:
return(False)
return 'r' == self.method_type
def is_available(self, rt_obj):
if rt_obj._get_client_version_tuple() < self.min_version or \
self.rpc_call not in rt_obj._get_rpc_methods():
return(False)
else:
return(True)
if rt_obj.get_client_version_tuple() >= self.min_version:
try:
self.varname = get_varname(filter(lambda f: rt_obj.method_exists(f),
(self.rpc_call,) + tuple(getattr(self, 'aliases', '')))[0])
return True
except IndexError:
pass
return False
class Multicall:
class Multicall(object):
# noinspection PyUnusedLocal
def __init__(self, class_obj, **kwargs):
self.class_obj = class_obj
if class_obj.__class__.__name__ == "RTorrent":
if 'RTorrent' == class_obj.__class__.__name__:
self.rt_obj = class_obj
else:
# noinspection PyProtectedMember
self.rt_obj = class_obj._rt_obj
self.calls = []
@ -138,7 +126,8 @@ class Multicall:
if isinstance(method, str):
result = find_method(method)
# if result not found
if result == -1:
if -1 == result:
# noinspection PyTypeChecker
method = Method(DummyClass, method, method)
else:
method = result
@ -159,17 +148,25 @@ class Multicall:
@return: the results (post-processed), in the order they were added
@rtype: tuple
"""
m = xmlrpclib.MultiCall(self.rt_obj._get_conn())
xmc = xmlrpclib.MultiCall(self.rt_obj.get_connection())
for call in self.calls:
method, args = call
rpc_call = getattr(method, "rpc_call")
getattr(m, rpc_call)(*args)
rpc_call = method.rpc_call
if not self.rt_obj.method_exists(rpc_call):
for alias in getattr(method, 'aliases', None) or []:
if self.rt_obj.method_exists(alias):
rpc_call = alias
break
getattr(xmc, rpc_call)(*args)
try:
results = tuple(filter(lambda x: isinstance(x, list), xmc().results)[0])
except IndexError:
return [[]]
results = m()
results = tuple(results)
results_processed = []
for r, c in zip(results, self.calls):
for r, c in list(zip(results, self.calls)):
method = c[0] # Method instance
result = process_result(method, r)
results_processed.append(result)
@ -178,7 +175,7 @@ class Multicall:
if not exists or not inspect.ismethod(getattr(self.class_obj, method.varname)):
setattr(self.class_obj, method.varname, result)
return(tuple(results_processed))
return tuple(results_processed)
def call_method(class_obj, method, *args):
@ -193,51 +190,35 @@ def call_method(class_obj, method, *args):
if method.is_retriever():
args = args[:-1]
else:
assert args[-1] is not None, "No argument given."
assert args[-1] is not None, 'No argument given.'
if class_obj.__class__.__name__ == "RTorrent":
if 'RTorrent' == class_obj.__class__.__name__:
rt_obj = class_obj
else:
# noinspection PyProtectedMember
rt_obj = class_obj._rt_obj
# check if rpc method is even available
if not method.is_available(rt_obj):
_handle_unavailable_rpc_method(method, rt_obj)
m = Multicall(class_obj)
m.add(method, *args)
mc = Multicall(class_obj)
mc.add(method, *args)
# only added one method, only getting one result back
ret_value = m.call()[0]
ret_value = mc.call()[0]
####### OBSOLETE ##########################################################
# if method.is_retriever():
# #value = process_result(method, ret_value)
# value = ret_value #MultiCall already processed the result
# else:
# # we're setting the user's input to method.varname
# # but we'll return the value that xmlrpc gives us
# value = process_result(method, args[-1])
##########################################################################
return(ret_value)
return ret_value
def find_method(rpc_call):
"""Return L{Method} instance associated with given RPC call"""
method_lists = [
rtorrent.methods,
rtorrent.file.methods,
rtorrent.tracker.methods,
rtorrent.peer.methods,
rtorrent.torrent.methods,
]
for l in method_lists:
for m in l:
if m.rpc_call.lower() == rpc_call.lower():
return(m)
return(-1)
try:
rpc_call = rpc_call.lower()
return filter(lambda m: rpc_call in map(lambda n: n.lower(), [m.rpc_call] + list(getattr(m, 'aliases', []))),
rtorrent.methods + rtorrent.torrent.methods +
rtorrent.file.methods + rtorrent.tracker.methods + rtorrent.peer.methods)[0]
except IndexError:
return -1
def process_result(method, result):
@ -249,7 +230,7 @@ def process_result(method, result):
@param result: result to be processed (the result of given L{Method} instance)
@note: Supported Processing:
- boolean - convert ones and zeros returned by rTorrent and
- bololean - convert ones and zeros returned by rTorrent and
convert to python boolean values
"""
# handle custom post processing function
@ -263,7 +244,7 @@ def process_result(method, result):
elif result in [0, '0']:
result = False
return(result)
return result
def _build_rpc_methods(class_, method_list):
@ -273,47 +254,26 @@ def _build_rpc_methods(class_, method_list):
instance = class_
class_ = instance.__class__
method_store = (instance, class_)[None is instance]
for m in method_list:
class_name = m.class_name
if class_name != class_.__name__:
continue
if class_name == "RTorrent":
caller = lambda self, arg = None, method = m:\
call_method(self, method, bool_to_int(arg))
elif class_name == "Torrent":
caller = lambda self, arg = None, method = m:\
call_method(self, method, self.rpc_id,
bool_to_int(arg))
elif class_name in ["Tracker", "File"]:
caller = lambda self, arg = None, method = m:\
call_method(self, method, self.rpc_id,
bool_to_int(arg))
elif class_name == "Peer":
caller = lambda self, arg = None, method = m:\
call_method(self, method, self.rpc_id,
bool_to_int(arg))
elif class_name == "Group":
caller = lambda arg = None, method = m: \
call_method(instance, method, bool_to_int(arg))
if m.docstring is None:
m.docstring = ""
# print(m)
docstring = """{0}
@note: Variable where the result for this method is stored: {1}.{2}""".format(
m.docstring,
class_name,
m.varname)
caller.__doc__ = docstring
for method_name in [m.method_name] + list(m.aliases):
if instance is None:
setattr(class_, method_name, caller)
else:
setattr(instance, method_name, caller)
caller = None
if 'RTorrent' == class_name:
caller = (lambda self, arg=None, method=m: call_method(self, method, bool_to_int(arg)))
elif class_name in ['Torrent', 'Tracker', 'File', 'Peer']:
caller = (lambda self, arg=None, method=m: call_method(self, method, self.rpc_id, bool_to_int(arg)))
elif 'Group' == class_name:
caller = (lambda arg=None, method=m: call_method(instance, method, bool_to_int(arg)))
if None is not caller:
for method_name in [m.method_name] + list(getattr(m, 'aliases', [])):
setattr(method_store, method_name, caller)
m.docstring = m.docstring or ''
caller.__doc__ = """{0}
@note: Variable where the result for this method is stored: {1}.{2}""".format(
m.docstring, class_name, m.varname) # print(m)

517
lib/rtorrent/torrent.py

@ -18,40 +18,38 @@
# 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 rtorrent.rpc
# from rtorrent.rpc import Method
import rtorrent.peer
import rtorrent.tracker
import rtorrent.file
import rtorrent.compat
from .common import safe_repr
from rtorrent.common import safe_repr
from .file import File, methods as file_methods
from .peer import Peer, methods as peer_methods
from .tracker import Tracker, methods as tracker_methods
from .rpc import Method
import rpc
Peer = rtorrent.peer.Peer
Tracker = rtorrent.tracker.Tracker
File = rtorrent.file.File
Method = rtorrent.rpc.Method
class Torrent:
class Torrent(object):
"""Represents an individual torrent within a L{RTorrent} instance."""
def __init__(self, _rt_obj, info_hash, **kwargs):
self._rt_obj = _rt_obj
self.info_hash = info_hash # : info hash for the torrent
self.rpc_id = self.info_hash # : unique id to pass to rTorrent
for k in kwargs.keys():
for k in kwargs:
setattr(self, k, kwargs.get(k, None))
self.peers = []
self.trackers = []
self.files = []
self.hashing = None
self.state = None
self.directory = None
self.active = None
self._call_custom_methods()
def __repr__(self):
return safe_repr("Torrent(info_hash=\"{0}\" name=\"{1}\")",
self.info_hash, self.name)
return safe_repr('Torrent(info_hash="{0}" name="{1}")',
self.info_hash, self.name)
def _call_custom_methods(self):
"""only calls methods that check instance variables."""
@ -68,25 +66,23 @@ class Torrent:
@note: also assigns return value to self.peers
"""
self.peers = []
retriever_methods = [m for m in rtorrent.peer.methods
if m.is_retriever() and m.is_available(self._rt_obj)]
retriever_methods = filter(lambda m: m.is_retriever() and m.is_available(self._rt_obj), peer_methods)
mc = rpc.Multicall(self)
# need to leave 2nd arg empty (dunno why)
m = rtorrent.rpc.Multicall(self)
m.add("p.multicall", self.info_hash, "",
*[method.rpc_call + "=" for method in retriever_methods])
mc.add('p.multicall', self.info_hash, '', *[method.rpc_call + '=' for method in retriever_methods])
results = m.call()[0] # only sent one call, only need first result
results = mc.call()[0] # only sent one call, only need first result
for result in results:
results_dict = {}
# build results_dict
for m, r in zip(retriever_methods, result):
results_dict[m.varname] = rtorrent.rpc.process_result(m, r)
for mc, r in zip(retriever_methods, result):
results_dict[mc.varname] = rpc.process_result(mc, r)
self.peers.append(Peer(
self._rt_obj, self.info_hash, **results_dict))
self.peers.append(Peer(self._rt_obj, self.info_hash, **results_dict))
return(self.peers)
return self.peers
def get_trackers(self):
"""Get list of Tracker instances for given torrent.
@ -97,26 +93,23 @@ class Torrent:
@note: also assigns return value to self.trackers
"""
self.trackers = []
retriever_methods = [m for m in rtorrent.tracker.methods
if m.is_retriever() and m.is_available(self._rt_obj)]
retriever_methods = filter(lambda m: m.is_retriever() and m.is_available(self._rt_obj), tracker_methods)
mc = rpc.Multicall(self)
# need to leave 2nd arg empty (dunno why)
m = rtorrent.rpc.Multicall(self)
m.add("t.multicall", self.info_hash, "",
*[method.rpc_call + "=" for method in retriever_methods])
mc.add('t.multicall', self.info_hash, '', *[method.rpc_call + '=' for method in retriever_methods])
results = m.call()[0] # only sent one call, only need first result
results = mc.call()[0] # only sent one call, only need first result
for result in results:
results_dict = {}
# build results_dict
for m, r in zip(retriever_methods, result):
results_dict[m.varname] = rtorrent.rpc.process_result(m, r)
for mc, r in zip(retriever_methods, result):
results_dict[mc.varname] = rpc.process_result(mc, r)
self.trackers.append(Tracker(
self._rt_obj, self.info_hash, **results_dict))
self.trackers.append(Tracker(self._rt_obj, self.info_hash, **results_dict))
return(self.trackers)
return self.trackers
def get_files(self):
"""Get list of File instances for given torrent.
@ -128,18 +121,15 @@ class Torrent:
"""
self.files = []
retriever_methods = [m for m in rtorrent.file.methods
if m.is_retriever() and m.is_available(self._rt_obj)]
retriever_methods = filter(lambda m: m.is_retriever() and m.is_available(self._rt_obj), file_methods)
mc = rpc.Multicall(self)
# 2nd arg can be anything, but it'll return all files in torrent
# regardless
m = rtorrent.rpc.Multicall(self)
m.add("f.multicall", self.info_hash, "",
*[method.rpc_call + "=" for method in retriever_methods])
mc.add('f.multicall', self.info_hash, '', *[method.rpc_call + '=' for method in retriever_methods])
results = m.call()[0] # only sent one call, only need first result
results = mc.call()[0] # only sent one call, only need first result
offset_method_index = retriever_methods.index(
rtorrent.rpc.find_method("f.get_offset"))
offset_method_index = retriever_methods.index(rpc.find_method('f.get_offset'))
# make a list of the offsets of all the files, sort appropriately
offset_list = sorted([r[offset_method_index] for r in results])
@ -147,17 +137,33 @@ class Torrent:
for result in results:
results_dict = {}
# build results_dict
for m, r in zip(retriever_methods, result):
results_dict[m.varname] = rtorrent.rpc.process_result(m, r)
for mc, r in zip(retriever_methods, result):
results_dict[mc.varname] = rpc.process_result(mc, r)
# get proper index positions for each file (based on the file
# offset)
f_index = offset_list.index(results_dict["offset"])
f_index = offset_list.index(results_dict['offset'])
self.files.append(File(self._rt_obj, self.info_hash, f_index, **results_dict))
return self.files
def _get_method(self, *choices):
try:
return filter(lambda method: self._rt_obj.method_exists(method), choices)[0]
except(Exception, BaseException):
pass
self.files.append(File(self._rt_obj, self.info_hash,
f_index, **results_dict))
def get_state(self):
return(self.files)
method = self._get_method(*('d.get_state', 'd.state'))
if method:
mc = rpc.Multicall(self)
self.multicall_add(mc, method)
return mc.call()[-1]
def set_directory(self, d):
"""Modify download directory
@ -166,11 +172,14 @@ class Torrent:
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", d)
method = self._get_method(*('d.set_directory', 'd.directory.set'))
if method:
mc = rpc.Multicall(self)
self.multicall_add(mc, 'd.try_stop')
self.multicall_add(mc, method, d)
self.directory = m.call()[-1]
self.directory = mc.call()[-1]
def set_directory_base(self, d):
"""Modify base download directory
@ -179,64 +188,74 @@ class Torrent:
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)
method = self._get_method(*('d.set_directory_base', 'd.directory_base.set'))
if method:
mc = rpc.Multicall(self)
self.multicall_add(mc, 'd.try_stop')
self.multicall_add(mc, method, d)
def start(self):
"""Start the torrent"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.try_start")
self.multicall_add(m, "d.is_active")
mc = rpc.Multicall(self)
self.multicall_add(mc, 'd.try_start')
self.multicall_add(mc, 'd.is_active')
self.active = m.call()[-1]
return(self.active)
self.active = mc.call()[-1]
return self.active
def stop(self):
""""Stop the torrent"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.try_stop")
self.multicall_add(m, "d.is_active")
mc = rpc.Multicall(self)
self.multicall_add(mc, 'd.try_stop')
self.multicall_add(mc, 'd.is_active')
self.active = m.call()[-1]
return(self.active)
self.active = mc.call()[-1]
return self.active
def pause(self):
"""Pause the torrent"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.pause")
mc = rpc.Multicall(self)
return(m.call()[-1])
self.multicall_add(mc, 'd.pause')
return mc.call()[-1]
def resume(self):
"""Resume the torrent"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.resume")
mc = rpc.Multicall(self)
self.multicall_add(mc, 'd.resume')
return(m.call()[-1])
return mc.call()[-1]
def close(self):
"""Close the torrent and it's files"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.close")
mc = rpc.Multicall(self)
self.multicall_add(mc, 'd.close')
return(m.call()[-1])
return mc.call()[-1]
def erase(self):
"""Delete the torrent
@note: doesn't delete the downloaded files"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.erase")
mc = rpc.Multicall(self)
return(m.call()[-1])
self.multicall_add(mc, 'd.erase')
return mc.call()[-1]
def check_hash(self):
"""(Re)hash check the torrent"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.check_hash")
mc = rpc.Multicall(self)
self.multicall_add(mc, 'd.check_hash')
return(m.call()[-1])
return mc.call()[-1]
def poll(self):
"""poll rTorrent to get latest peer/tracker/file information"""
@ -251,13 +270,12 @@ class Torrent:
@return: None
"""
multicall = rtorrent.rpc.Multicall(self)
retriever_methods = [m for m in methods
if m.is_retriever() and m.is_available(self._rt_obj)]
for method in retriever_methods:
multicall.add(method, self.rpc_id)
mc = rpc.Multicall(self)
for method in filter(lambda m: m.is_retriever() and m.is_available(self._rt_obj), methods):
mc.add(method, self.rpc_id)
multicall.call()
mc.call()
# custom functions (only call private methods, since they only check
# local variables and are therefore faster)
@ -268,27 +286,23 @@ class Torrent:
@param accept_seeds: enable/disable accepting seeders
@type accept_seeds: bool"""
if accept_seeds:
call = "d.accepting_seeders.enable"
else:
call = "d.accepting_seeders.disable"
mc = rpc.Multicall(self)
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, call)
self.multicall_add(mc, ('d.accepting_seeders.disable', 'd.accepting_seeders.enable')[accept_seeds])
return(m.call()[-1])
return mc.call()[-1]
def announce(self):
"""Announce torrent info to tracker(s)"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.tracker_announce")
mc = rpc.Multicall(self)
return(m.call()[-1])
self.multicall_add(mc, 'd.tracker_announce')
return mc.call()[-1]
@staticmethod
def _assert_custom_key_valid(key):
assert type(key) == int and key > 0 and key < 6, \
"key must be an integer between 1-5"
assert type(key) == int and 0 < key < 6, 'key must be an integer between 1-5'
def get_custom(self, key):
"""
@ -301,13 +315,16 @@ class Torrent:
"""
self._assert_custom_key_valid(key)
m = rtorrent.rpc.Multicall(self)
field = "custom{0}".format(key)
self.multicall_add(m, "d.get_{0}".format(field))
setattr(self, field, m.call()[-1])
field = 'custom%s' % key
method = self._get_method(*('d.get_%s' % field, 'd.%s' % field))
if method:
mc = rpc.Multicall(self)
self.multicall_add(mc, method)
return (getattr(self, field))
setattr(self, field, mc.call()[-1])
return getattr(self, field)
def set_custom(self, key, value):
"""
@ -324,19 +341,23 @@ class Torrent:
"""
self._assert_custom_key_valid(key)
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.set_custom{0}".format(key), value)
field = 'custom%s' % key
method = self._get_method(*('d.set_%s' % field, 'd.%s.set' % field))
if method:
mc = rpc.Multicall(self)
self.multicall_add(mc, method, value)
return(m.call()[-1])
return mc.call()[-1]
def set_visible(self, view, visible=True):
p = self._rt_obj._get_conn()
p = self._rt_obj.get_connection()
if visible:
return p.view.set_visible(self.info_hash, view)
else:
return p.view.set_not_visible(self.info_hash, view)
return p.view.set_not_visible(self.info_hash, view)
def add_tracker(self, group, tracker):
"""
@ -351,10 +372,11 @@ class Torrent:
@return: if successful, 0
@rtype: int
"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.tracker.insert", group, tracker)
mc = rpc.Multicall(self)
self.multicall_add(mc, 'd.tracker.insert', group, tracker)
return (m.call()[-1])
return mc.call()[-1]
############################################################################
# CUSTOM METHODS (Not part of the official rTorrent API)
@ -366,45 +388,48 @@ class Torrent:
self.hash_checking_queued = (self.hashing == 3 and
self.hash_checking is False)
return(self.hash_checking_queued)
return self.hash_checking_queued
def is_hash_checking_queued(self):
"""Check if torrent is waiting to be hash checked
@note: Variable where the result for this method is stored Torrent.hash_checking_queued"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.get_hashing")
self.multicall_add(m, "d.is_hash_checking")
results = m.call()
setattr(self, "hashing", results[0])
setattr(self, "hash_checking", results[1])
method = self._get_method(*('d.get_hashing', 'd.hashing'))
if method:
mc = rpc.Multicall(self)
self.multicall_add(mc, method)
self.multicall_add(mc, 'd.is_hash_checking')
results = mc.call()
return(self._is_hash_checking_queued())
setattr(self, 'hashing', results[0])
setattr(self, 'hash_checking', results[1])
return self._is_hash_checking_queued()
def _is_paused(self):
"""Only checks instance variables, shouldn't be called directly"""
self.paused = (self.state == 0)
return(self.paused)
return self.paused
def is_paused(self):
"""Check if torrent is paused
@note: Variable where the result for this method is stored: Torrent.paused"""
self.get_state()
return(self._is_paused())
return self._is_paused()
def _is_started(self):
"""Only checks instance variables, shouldn't be called directly"""
self.started = (self.state == 1)
return(self.started)
return self.started
def is_started(self):
"""Check if torrent is started
@note: Variable where the result for this method is stored: Torrent.started"""
self.get_state()
return(self._is_started())
return self._is_started()
methods = [
@ -415,121 +440,197 @@ methods = [
Method(Torrent, 'is_hash_checking', 'd.is_hash_checking',
boolean=True,
),
Method(Torrent, 'get_peers_max', 'd.get_peers_max'),
Method(Torrent, 'get_tracker_focus', 'd.get_tracker_focus'),
Method(Torrent, 'get_skip_total', 'd.get_skip_total'),
Method(Torrent, 'get_state', 'd.get_state'),
Method(Torrent, 'get_peer_exchange', 'd.get_peer_exchange'),
Method(Torrent, 'get_down_rate', 'd.get_down_rate'),
Method(Torrent, 'get_connection_seed', 'd.get_connection_seed'),
Method(Torrent, 'get_uploads_max', 'd.get_uploads_max'),
Method(Torrent, 'get_priority_str', 'd.get_priority_str'),
Method(Torrent, 'get_peers_max', 'd.get_peers_max',
aliases=('d.peers_max',)),
Method(Torrent, 'get_tracker_focus', 'd.get_tracker_focus',
aliases=('d.tracker_focus',)),
Method(Torrent, 'get_skip_total', 'd.get_skip_total',
aliases=('d.skip.total',)),
Method(Torrent, 'get_state', 'd.get_state',
aliases=('d.state',)),
Method(Torrent, 'get_peer_exchange', 'd.get_peer_exchange',
aliases=('d.peer_exchange',)),
Method(Torrent, 'get_down_rate', 'd.get_down_rate',
aliases=('d.down.rate',)),
Method(Torrent, 'get_connection_seed', 'd.get_connection_seed',
aliases=('d.connection_seed',)),
Method(Torrent, 'get_uploads_max', 'd.get_uploads_max',
aliases=('d.uploads_max',)),
Method(Torrent, 'get_priority_str', 'd.get_priority_str',
aliases=('d.priority_str',)),
Method(Torrent, 'is_open', 'd.is_open',
boolean=True,
),
Method(Torrent, 'get_peers_min', 'd.get_peers_min'),
Method(Torrent, 'get_peers_complete', 'd.get_peers_complete'),
Method(Torrent, 'get_tracker_numwant', 'd.get_tracker_numwant'),
Method(Torrent, 'get_connection_current', 'd.get_connection_current'),
Method(Torrent, 'get_peers_min', 'd.get_peers_min',
aliases=('d.peers_min',)),
Method(Torrent, 'get_peers_complete', 'd.get_peers_complete',
aliases=('d.peers_complete',)),
Method(Torrent, 'get_tracker_numwant', 'd.get_tracker_numwant',
aliases=('d.tracker_numwant',)),
Method(Torrent, 'get_connection_current', 'd.get_connection_current',
aliases=('d.connection_current',)),
Method(Torrent, 'is_complete', 'd.get_complete',
boolean=True,
aliases=('d.complete',)
),
Method(Torrent, 'get_peers_connected', 'd.get_peers_connected'),
Method(Torrent, 'get_chunk_size', 'd.get_chunk_size'),
Method(Torrent, 'get_state_counter', 'd.get_state_counter'),
Method(Torrent, 'get_base_filename', 'd.get_base_filename'),
Method(Torrent, 'get_state_changed', 'd.get_state_changed'),
Method(Torrent, 'get_peers_not_connected', 'd.get_peers_not_connected'),
Method(Torrent, 'get_directory', 'd.get_directory'),
Method(Torrent, 'get_peers_connected', 'd.get_peers_connected',
aliases=('d.peers_connected',)),
Method(Torrent, 'get_chunk_size', 'd.get_chunk_size',
aliases=('d.chunk_size',)),
Method(Torrent, 'get_state_counter', 'd.get_state_counter',
aliases=('d.state_counter',)),
Method(Torrent, 'get_base_filename', 'd.get_base_filename',
aliases=('d.base_filename',)),
Method(Torrent, 'get_state_changed', 'd.get_state_changed',
aliases=('d.state_changed',)),
Method(Torrent, 'get_peers_not_connected', 'd.get_peers_not_connected',
aliases=('d.peers_not_connected',)),
Method(Torrent, 'get_directory', 'd.get_directory',
aliases=('d.directory',)),
Method(Torrent, 'is_incomplete', 'd.incomplete',
boolean=True,
),
Method(Torrent, 'get_tracker_size', 'd.get_tracker_size'),
Method(Torrent, 'get_tracker_size', 'd.get_tracker_size',
aliases=('d.tracker_size',)),
Method(Torrent, 'is_multi_file', 'd.is_multi_file',
boolean=True,
),
Method(Torrent, 'get_local_id', 'd.get_local_id'),
Method(Torrent, 'get_local_id', 'd.get_local_id',
aliases=('d.local_id',)),
Method(Torrent, 'get_ratio', 'd.get_ratio',
post_process_func=lambda x: x / 1000.0,
aliases=('d.ratio',)
),
Method(Torrent, 'get_loaded_file', 'd.get_loaded_file'),
Method(Torrent, 'get_max_file_size', 'd.get_max_file_size'),
Method(Torrent, 'get_size_chunks', 'd.get_size_chunks'),
Method(Torrent, 'get_loaded_file', 'd.get_loaded_file',
aliases=('d.loaded_file',)),
Method(Torrent, 'get_max_file_size', 'd.get_max_file_size',
aliases=('d.max_file_size',)),
Method(Torrent, 'get_size_chunks', 'd.get_size_chunks',
aliases=('d.size_chunks',)),
Method(Torrent, 'is_pex_active', 'd.is_pex_active',
boolean=True,
),
Method(Torrent, 'get_hashing', 'd.get_hashing'),
Method(Torrent, 'get_bitfield', 'd.get_bitfield'),
Method(Torrent, 'get_local_id_html', 'd.get_local_id_html'),
Method(Torrent, 'get_connection_leech', 'd.get_connection_leech'),
Method(Torrent, 'get_peers_accounted', 'd.get_peers_accounted'),
Method(Torrent, 'get_message', 'd.get_message'),
Method(Torrent, 'get_hashing', 'd.get_hashing',
aliases=('d.hashing',)),
Method(Torrent, 'get_bitfield', 'd.get_bitfield',
aliases=('d.bitfield',)),
Method(Torrent, 'get_local_id_html', 'd.get_local_id_html',
aliases=('d.bitfield',)),
Method(Torrent, 'get_connection_leech', 'd.get_connection_leech',
aliases=('d.connection_leech',)),
Method(Torrent, 'get_peers_accounted', 'd.get_peers_accounted',
aliases=('d.peers_accounted',)),
Method(Torrent, 'get_message', 'd.get_message',
aliases=('d.message',)),
Method(Torrent, 'is_active', 'd.is_active',
boolean=True,
),
Method(Torrent, 'get_size_bytes', 'd.get_size_bytes'),
Method(Torrent, 'get_ignore_commands', 'd.get_ignore_commands'),
Method(Torrent, 'get_creation_date', 'd.get_creation_date'),
Method(Torrent, 'get_base_path', 'd.get_base_path'),
Method(Torrent, 'get_left_bytes', 'd.get_left_bytes'),
Method(Torrent, 'get_size_files', 'd.get_size_files'),
Method(Torrent, 'get_size_pex', 'd.get_size_pex'),
Method(Torrent, 'get_size_bytes', 'd.get_size_bytes',
aliases=('d.size_bytes',)),
Method(Torrent, 'get_ignore_commands', 'd.get_ignore_commands',
aliases=('d.ignore_commands',)),
Method(Torrent, 'get_creation_date', 'd.get_creation_date',
aliases=('d.creation_date',)),
Method(Torrent, 'get_base_path', 'd.get_base_path',
aliases=('d.base_path',)),
Method(Torrent, 'get_left_bytes', 'd.get_left_bytes',
aliases=('d.left_bytes',)),
Method(Torrent, 'get_size_files', 'd.get_size_files',
aliases=('d.size_files',)),
Method(Torrent, 'get_size_pex', 'd.get_size_pex',
aliases=('d.size_pex',)),
Method(Torrent, 'is_private', 'd.is_private',
boolean=True,
),
Method(Torrent, 'get_max_size_pex', 'd.get_max_size_pex'),
Method(Torrent, 'get_num_chunks_hashed', 'd.get_chunks_hashed',
aliases=("get_chunks_hashed",)),
aliases=('get_chunks_hashed', 'd.chunks_hashed')),
Method(Torrent, 'get_num_chunks_wanted', 'd.wanted_chunks'),
Method(Torrent, 'get_priority', 'd.get_priority'),
Method(Torrent, 'get_skip_rate', 'd.get_skip_rate'),
Method(Torrent, 'get_completed_bytes', 'd.get_completed_bytes'),
Method(Torrent, 'get_name', 'd.get_name'),
Method(Torrent, 'get_completed_chunks', 'd.get_completed_chunks'),
Method(Torrent, 'get_throttle_name', 'd.get_throttle_name'),
Method(Torrent, 'get_free_diskspace', 'd.get_free_diskspace'),
Method(Torrent, 'get_directory_base', 'd.get_directory_base'),
Method(Torrent, 'get_hashing_failed', 'd.get_hashing_failed'),
Method(Torrent, 'get_tied_to_file', 'd.get_tied_to_file'),
Method(Torrent, 'get_down_total', 'd.get_down_total'),
Method(Torrent, 'get_bytes_done', 'd.get_bytes_done'),
Method(Torrent, 'get_up_rate', 'd.get_up_rate'),
Method(Torrent, 'get_up_total', 'd.get_up_total'),
Method(Torrent, 'get_priority', 'd.get_priority',
aliases=('d.priority',)),
Method(Torrent, 'get_skip_rate', 'd.get_skip_rate',
aliases=('d.skip.rate',)),
Method(Torrent, 'get_completed_bytes', 'd.get_completed_bytes',
aliases=('d.completed_bytes',)),
Method(Torrent, 'get_name', 'd.get_name',
aliases=('d.name',)),
Method(Torrent, 'get_completed_chunks', 'd.get_completed_chunks',
aliases=('d.completed_chunks',)),
Method(Torrent, 'get_throttle_name', 'd.get_throttle_name',
aliases=('d.throttle_name',)),
Method(Torrent, 'get_free_diskspace', 'd.get_free_diskspace',
aliases=('d.free_diskspace',)),
Method(Torrent, 'get_directory_base', 'd.get_directory_base',
aliases=('d.directory_base',)),
Method(Torrent, 'get_hashing_failed', 'd.get_hashing_failed',
aliases=('d.hashing_failed',)),
Method(Torrent, 'get_tied_to_file', 'd.get_tied_to_file',
aliases=('d.tied_to_file',)),
Method(Torrent, 'get_down_total', 'd.get_down_total',
aliases=('d.down.total',)),
Method(Torrent, 'get_bytes_done', 'd.get_bytes_done',
aliases=('d.bytes_done',)),
Method(Torrent, 'get_up_rate', 'd.get_up_rate',
aliases=('d.up.rate',)),
Method(Torrent, 'get_up_total', 'd.get_up_total',
aliases=('d.up.total',)),
Method(Torrent, 'is_accepting_seeders', 'd.accepting_seeders',
boolean=True,
),
Method(Torrent, "get_chunks_seen", "d.chunks_seen",
Method(Torrent, 'get_chunks_seen', 'd.chunks_seen',
min_version=(0, 9, 1),
),
Method(Torrent, "is_partially_done", "d.is_partially_done",
Method(Torrent, 'is_partially_done', 'd.is_partially_done',
boolean=True,
),
Method(Torrent, "is_not_partially_done", "d.is_not_partially_done",
Method(Torrent, 'is_not_partially_done', 'd.is_not_partially_done',
boolean=True,
),
Method(Torrent, "get_time_started", "d.timestamp.started"),
Method(Torrent, "get_custom1", "d.get_custom1"),
Method(Torrent, "get_custom2", "d.get_custom2"),
Method(Torrent, "get_custom3", "d.get_custom3"),
Method(Torrent, "get_custom4", "d.get_custom4"),
Method(Torrent, "get_custom5", "d.get_custom5"),
Method(Torrent, 'get_time_started', 'd.timestamp.started'),
Method(Torrent, 'get_custom1', 'd.get_custom1',
aliases=('d.custom1',)),
Method(Torrent, 'get_custom2', 'd.get_custom2',
aliases=('d.custom2',)),
Method(Torrent, 'get_custom3', 'd.get_custom3',
aliases=('d.custom3',)),
Method(Torrent, 'get_custom4', 'd.get_custom4',
aliases=('d.custom4',)),
Method(Torrent, 'get_custom5', 'd.get_custom5',
aliases=('d.custom5',)),
# MODIFIERS
Method(Torrent, 'set_uploads_max', 'd.set_uploads_max'),
Method(Torrent, 'set_tied_to_file', 'd.set_tied_to_file'),
Method(Torrent, 'set_tracker_numwant', 'd.set_tracker_numwant'),
Method(Torrent, 'set_priority', 'd.set_priority'),
Method(Torrent, 'set_peers_max', 'd.set_peers_max'),
Method(Torrent, 'set_hashing_failed', 'd.set_hashing_failed'),
Method(Torrent, 'set_message', 'd.set_message'),
Method(Torrent, 'set_throttle_name', 'd.set_throttle_name'),
Method(Torrent, 'set_peers_min', 'd.set_peers_min'),
Method(Torrent, 'set_ignore_commands', 'd.set_ignore_commands'),
Method(Torrent, 'set_max_file_size', 'd.set_max_file_size'),
Method(Torrent, 'set_custom5', 'd.set_custom5'),
Method(Torrent, 'set_custom4', 'd.set_custom4'),
Method(Torrent, 'set_custom2', 'd.set_custom2'),
Method(Torrent, 'set_custom1', 'd.set_custom1'),
Method(Torrent, 'set_custom3', 'd.set_custom3'),
Method(Torrent, 'set_connection_current', 'd.set_connection_current'),
Method(Torrent, 'set_uploads_max', 'd.set_uploads_max',
aliases=('d.uploads_max.set',)),
Method(Torrent, 'set_tied_to_file', 'd.set_tied_to_file',
aliases=('d.tied_to_file.set',)),
Method(Torrent, 'set_tracker_numwant', 'd.set_tracker_numwant',
aliases=('d.tracker_numwant.set',)),
Method(Torrent, 'set_priority', 'd.set_priority',
aliases=('d.priority.set',)),
Method(Torrent, 'set_peers_max', 'd.set_peers_max',
aliases=('d.peers_max.set',)),
Method(Torrent, 'set_hashing_failed', 'd.set_hashing_failed',
aliases=('d.hashing_failed.set',)),
Method(Torrent, 'set_message', 'd.set_message',
aliases=('d.message.set',)),
Method(Torrent, 'set_throttle_name', 'd.set_throttle_name',
aliases=('d.throttle_name.set',)),
Method(Torrent, 'set_peers_min', 'd.set_peers_min',
aliases=('d.peers_min.set',)),
Method(Torrent, 'set_ignore_commands', 'd.set_ignore_commands',
aliases=('d.ignore_commands.set',)),
Method(Torrent, 'set_max_file_size', 'd.set_max_file_size',
aliases=('d.max_file_size.set',)),
Method(Torrent, 'set_custom5', 'd.set_custom5',
aliases=('d.custom5.set',)),
Method(Torrent, 'set_custom4', 'd.set_custom4',
aliases=('d.custom4.set',)),
Method(Torrent, 'set_custom2', 'd.set_custom2',
aliases=('d.custom2.set',)),
Method(Torrent, 'set_custom1', 'd.set_custom1',
aliases=('d.custom1.set',)),
Method(Torrent, 'set_custom3', 'd.set_custom3',
aliases=('d.custom3.set',)),
Method(Torrent, 'set_connection_current', 'd.set_connection_current',
aliases=('d.connection_current.set',)),
]

77
lib/rtorrent/tracker.py

@ -18,39 +18,36 @@
# 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 rtorrent.rpc import Method
import rtorrent.rpc
from .common import safe_repr
from .rpc import Method
import rpc
from rtorrent.common import safe_repr
Method = rtorrent.rpc.Method
class Tracker:
class Tracker(object):
"""Represents an individual tracker within a L{Torrent} instance."""
def __init__(self, _rt_obj, info_hash, **kwargs):
self._rt_obj = _rt_obj
self.info_hash = info_hash # : info hash for the torrent using this tracker
for k in kwargs.keys():
for k in kwargs:
setattr(self, k, kwargs.get(k, None))
# for clarity's sake...
self.index = self.group # : position of tracker within the torrent's tracker list
self.rpc_id = "{0}:t{1}".format(
self.rpc_id = '{0}:t{1}'.format(
self.info_hash, self.index) # : unique id to pass to rTorrent
def __repr__(self):
return safe_repr("Tracker(index={0}, url=\"{1}\")",
self.index, self.url)
return safe_repr('Tracker(index={0}, url="{1}")',
self.index, self.url)
def enable(self):
"""Alias for set_enabled("yes")"""
self.set_enabled("yes")
self.set_enabled('yes')
def disable(self):
"""Alias for set_enabled("no")"""
self.set_enabled("no")
self.set_enabled('no')
def update(self):
"""Refresh tracker data
@ -59,13 +56,12 @@ class Tracker:
@return: None
"""
multicall = rtorrent.rpc.Multicall(self)
retriever_methods = [m for m in methods
if m.is_retriever() and m.is_available(self._rt_obj)]
for method in retriever_methods:
multicall.add(method, self.rpc_id)
mc = rpc.Multicall(self)
for method in filter(lambda fx: fx.is_retriever() and fx.is_available(self._rt_obj), methods):
mc.add(method, self.rpc_id)
multicall.call()
mc.call()
def append_tracker(self, tracker):
"""
@ -77,26 +73,38 @@ class Tracker:
@return: if successful, 0
@rtype: int
"""
m = rtorrent.rpc.Multicall(self)
self.multicall_add(m, "d.tracker.insert", self.index, tracker)
mc = rpc.Multicall(self)
self.multicall_add(mc, 'd.tracker.insert', self.index, tracker)
return mc.call()[-1]
return (m.call()[-1])
methods = [
# RETRIEVERS
Method(Tracker, 'is_enabled', 't.is_enabled', boolean=True),
Method(Tracker, 'get_id', 't.get_id'),
Method(Tracker, 'get_scrape_incomplete', 't.get_scrape_incomplete'),
Method(Tracker, 'get_id', 't.get_id',
aliases=('t.id',)),
Method(Tracker, 'get_scrape_incomplete', 't.get_scrape_incomplete',
aliases=('t.scrape_incomplete',)),
Method(Tracker, 'is_open', 't.is_open', boolean=True),
Method(Tracker, 'get_min_interval', 't.get_min_interval'),
Method(Tracker, 'get_scrape_downloaded', 't.get_scrape_downloaded'),
Method(Tracker, 'get_group', 't.get_group'),
Method(Tracker, 'get_scrape_time_last', 't.get_scrape_time_last'),
Method(Tracker, 'get_type', 't.get_type'),
Method(Tracker, 'get_normal_interval', 't.get_normal_interval'),
Method(Tracker, 'get_url', 't.get_url'),
Method(Tracker, 'get_min_interval', 't.get_min_interval',
aliases=('t.min_interval',)),
Method(Tracker, 'get_scrape_downloaded', 't.get_scrape_downloaded',
aliases=('t.scrape_downloaded',)),
Method(Tracker, 'get_group', 't.get_group',
aliases=('t.group',)),
Method(Tracker, 'get_scrape_time_last', 't.get_scrape_time_last',
aliases=('t.scrape_time_last',)),
Method(Tracker, 'get_type', 't.get_type',
aliases=('t.type',)),
Method(Tracker, 'get_normal_interval', 't.get_normal_interval',
aliases=('t.normal_interval',)),
Method(Tracker, 'get_url', 't.get_url',
aliases=('t.url',)),
Method(Tracker, 'get_scrape_complete', 't.get_scrape_complete',
min_version=(0, 8, 9),
aliases=('t.scrape_complete',)
),
Method(Tracker, 'get_activity_time_last', 't.activity_time_last',
min_version=(0, 8, 9),
@ -141,13 +149,14 @@ methods = [
min_version=(0, 9, 1),
boolean=True,
),
Method(Tracker, "get_latest_sum_peers", "t.latest_sum_peers",
Method(Tracker, 'get_latest_sum_peers', 't.latest_sum_peers',
min_version=(0, 9, 0)
),
Method(Tracker, "get_latest_new_peers", "t.latest_new_peers",
Method(Tracker, 'get_latest_new_peers', 't.latest_new_peers',
min_version=(0, 9, 0)
),
# MODIFIERS
Method(Tracker, 'set_enabled', 't.set_enabled'),
Method(Tracker, 'set_enabled', 't.set_enabled',
aliases=('t.is_enabled.set',)),
]

2
sickbeard/clients/generic.py

@ -152,7 +152,7 @@ class GenericClient(object):
def _get_torrent_hash(result):
if result.url.startswith('magnet'):
result.hash = re.findall('urn:btih:([\w]{32,40})', result.url)[0]
result.hash = re.findall(r'urn:btih:([\w]{32,40})', result.url)[0]
if 32 == len(result.hash):
result.hash = b16encode(b32decode(result.hash)).lower()
else:

63
sickbeard/clients/rtorrent.py

@ -1,5 +1,3 @@
# Author: jkaberg <joel.kaberg@gmail.com>, based on fuzemans work (https://github.com/RuudBurger/CouchPotatoServer/blob/develop/couchpotato/core/downloaders/rtorrent/main.py)
# URL: http://code.google.com/p/sickbeard/
#
# This file is part of SickGear.
#
@ -16,11 +14,11 @@
# You should have received a copy of the GNU General Public License
# along with SickGear. If not, see <http://www.gnu.org/licenses/>.
import xmlrpclib
import sickbeard
from sickbeard import helpers
from sickbeard.clients.generic import GenericClient
from lib.rtorrent.compat import xmlrpclib
from lib.rtorrent import RTorrent
from sickbeard import helpers, logger
from sickbeard.clients.generic import GenericClient
import sickbeard
class RtorrentAPI(GenericClient):
@ -31,60 +29,59 @@ class RtorrentAPI(GenericClient):
super(RtorrentAPI, self).__init__('rTorrent', host, username, password)
# self.url = self.host
def _get_auth(self):
self.auth = None
if self.host:
try:
if self.host and self.host.startswith('scgi:'):
if self.host.startswith('scgi:'):
self.username = self.password = None
self.auth = RTorrent(self.host, self.username, self.password, True)
except (AssertionError, xmlrpclib.ProtocolError) as e:
self.auth = RTorrent(self.host, self.username, self.password)
except (AssertionError, xmlrpclib.ProtocolError):
pass
return self.auth
def _add_torrent(self, cmd, **kwargs):
def _add_torrent(self, cmd, data):
torrent = None
if self.auth:
try:
if self.auth.has_local_id(data.hash):
logger.log('%s: Item already exists %s' % (self.name, data.name), logger.WARNING)
raise
params = {
'start': not sickbeard.TORRENT_PAUSED,
'extra': ([], ['d.set_custom1=%s' % sickbeard.TORRENT_LABEL])[any([sickbeard.TORRENT_LABEL])] +
([], ['d.set_directory=%s' % sickbeard.TORRENT_PATH])[any([sickbeard.TORRENT_PATH])]
or None}
# Send magnet to rTorrent
if 'file' == cmd:
torrent = self.auth.load_torrent(kwargs['file'])
torrent = self.auth.load_torrent(data.content, **params)
elif 'magnet' == cmd:
torrent = self.auth.load_magnet(kwargs['uri'], kwargs['btih'])
torrent = self.auth.load_magnet(data.url, data.hash, **params)
if torrent:
if torrent and sickbeard.TORRENT_LABEL:
label = torrent.get_custom(1)
if sickbeard.TORRENT_LABEL != label:
logger.log('%s: could not change custom1 category \'%s\' to \'%s\' for %s' % (
self.name, label, sickbeard.TORRENT_LABEL, torrent.name), logger.WARNING)
if sickbeard.TORRENT_LABEL:
torrent.set_custom(1, sickbeard.TORRENT_LABEL)
if sickbeard.TORRENT_PATH:
torrent.set_directory(sickbeard.TORRENT_PATH)
torrent.start()
except (StandardError, Exception) as e:
except(Exception, BaseException):
pass
return any([torrent])
def _add_torrent_file(self, result):
if result:
return self._add_torrent('file', file=result.content)
return False
return result and self._add_torrent('file', result) or False
def _add_torrent_uri(self, result):
if result:
return self._add_torrent('magnet', uri=result.url, btih=result.hash)
return False
return result and self._add_torrent('magnet', result) or False
#def _set_torrent_ratio(self, name):
# def _set_torrent_ratio(self, name):
# if not name:
# return False
@ -122,9 +119,7 @@ class RtorrentAPI(GenericClient):
def test_authentication(self):
try:
self._get_auth()
if None is self.auth:
if not self._get_auth():
return False, 'Error: Unable to get %s authentication, check your config!' % self.name
return True, 'Success: Connected and Authenticated'

Loading…
Cancel
Save