mirror of https://github.com/evilhero/mylar
Clean checkout of rtorrent
`pip install --upgrade --target=. git+https://github.com/cjlucas/rtorrent-python.git@0763434fd43cb6954178f3b04869e2b73f88b817`
This commit is contained in:
parent
69eeb9b49d
commit
a0a9e4138d
|
@ -22,16 +22,16 @@ import os.path
|
|||
import time
|
||||
import xmlrpclib
|
||||
|
||||
from common import find_torrent, \
|
||||
from rtorrent.common import find_torrent, \
|
||||
is_valid_port, convert_version_tuple_to_str
|
||||
from lib.torrentparser import TorrentParser
|
||||
from lib.xmlrpc.http import HTTPServerProxy
|
||||
from lib.xmlrpc.scgi import SCGIServerProxy
|
||||
from rpc import Method
|
||||
from lib.xmlrpc.basic_auth import BasicAuthTransport
|
||||
from torrent import Torrent
|
||||
from group import Group
|
||||
import rpc # @UnresolvedImport
|
||||
from rtorrent.lib.torrentparser import TorrentParser
|
||||
from rtorrent.lib.xmlrpc.http import HTTPServerProxy
|
||||
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 rtorrent.rpc # @UnresolvedImport
|
||||
|
||||
__version__ = "0.2.9"
|
||||
__author__ = "Chris Lucas"
|
||||
|
@ -139,11 +139,11 @@ class RTorrent:
|
|||
@todo: add validity check for specified view
|
||||
"""
|
||||
self.torrents = []
|
||||
methods = torrent.methods
|
||||
methods = rtorrent.torrent.methods
|
||||
retriever_methods = [m for m in methods
|
||||
if m.is_retriever() and m.is_available(self)]
|
||||
|
||||
m = rpc.Multicall(self)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
m.add("d.multicall", view, "d.get_hash=",
|
||||
*[method.rpc_call + "=" for method in retriever_methods])
|
||||
|
||||
|
@ -153,7 +153,7 @@ class RTorrent:
|
|||
results_dict = {}
|
||||
# build results_dict
|
||||
for m, r in zip(retriever_methods, result[1:]): # result[0] is the info_hash
|
||||
results_dict[m.varname] = rpc.process_result(m, r)
|
||||
results_dict[m.varname] = rtorrent.rpc.process_result(m, r)
|
||||
|
||||
self.torrents.append(
|
||||
Torrent(self, info_hash=result[0], **results_dict)
|
||||
|
@ -165,7 +165,7 @@ class RTorrent:
|
|||
def _manage_torrent_cache(self):
|
||||
"""Carry tracker/peer/file lists over to new torrent list"""
|
||||
for torrent in self._torrent_cache:
|
||||
new_torrent = common.find_torrent(torrent.info_hash,
|
||||
new_torrent = rtorrent.common.find_torrent(torrent.info_hash,
|
||||
self.torrents)
|
||||
if new_torrent is not None:
|
||||
new_torrent.files = torrent.files
|
||||
|
@ -349,7 +349,7 @@ class RTorrent:
|
|||
|
||||
def find_torrent(self, info_hash):
|
||||
"""Frontend for rtorrent.common.find_torrent"""
|
||||
return(common.find_torrent(info_hash, self.get_torrents()))
|
||||
return(rtorrent.common.find_torrent(info_hash, self.get_torrents()))
|
||||
|
||||
def poll(self):
|
||||
""" poll rTorrent to get latest torrent/peer/tracker/file information
|
||||
|
@ -372,7 +372,7 @@ class RTorrent:
|
|||
|
||||
@return: None
|
||||
"""
|
||||
multicall = rpc.Multicall(self)
|
||||
multicall = rtorrent.rpc.Multicall(self)
|
||||
retriever_methods = [m for m in methods
|
||||
if m.is_retriever() and m.is_available(self)]
|
||||
for method in retriever_methods:
|
||||
|
@ -414,10 +414,10 @@ def __check_supported_methods(rt):
|
|||
from pprint import pprint
|
||||
supported_methods = set([m.rpc_call for m in
|
||||
methods +
|
||||
file.methods +
|
||||
torrent.methods +
|
||||
tracker.methods +
|
||||
peer.methods])
|
||||
rtorrent.file.methods +
|
||||
rtorrent.torrent.methods +
|
||||
rtorrent.tracker.methods +
|
||||
rtorrent.peer.methods])
|
||||
all_methods = set(rt._get_rpc_methods())
|
||||
|
||||
print("Methods NOT in supported methods")
|
||||
|
@ -591,19 +591,19 @@ methods = [
|
|||
]
|
||||
|
||||
_all_methods_list = [methods,
|
||||
file.methods,
|
||||
torrent.methods,
|
||||
tracker.methods,
|
||||
peer.methods,
|
||||
rtorrent.file.methods,
|
||||
rtorrent.torrent.methods,
|
||||
rtorrent.tracker.methods,
|
||||
rtorrent.peer.methods,
|
||||
]
|
||||
|
||||
class_methods_pair = {
|
||||
RTorrent: methods,
|
||||
file.File: file.methods,
|
||||
torrent.Torrent: torrent.methods,
|
||||
tracker.Tracker: tracker.methods,
|
||||
peer.Peer: peer.methods,
|
||||
rtorrent.file.File: rtorrent.file.methods,
|
||||
rtorrent.torrent.Torrent: rtorrent.torrent.methods,
|
||||
rtorrent.tracker.Tracker: rtorrent.tracker.methods,
|
||||
rtorrent.peer.Peer: rtorrent.peer.methods,
|
||||
}
|
||||
for c in class_methods_pair.keys():
|
||||
rpc._build_rpc_methods(c, class_methods_pair[c])
|
||||
rtorrent.rpc._build_rpc_methods(c, class_methods_pair[c])
|
||||
_build_class_methods(c)
|
||||
|
|
|
@ -1,609 +0,0 @@
|
|||
# Copyright (c) 2013 Chris Lucas, <chris@chrisjlucas.com>
|
||||
# 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.
|
||||
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.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 rtorrent.rpc # @UnresolvedImport
|
||||
|
||||
__version__ = "0.2.9"
|
||||
__author__ = "Chris Lucas"
|
||||
__contact__ = "chris@chrisjlucas.com"
|
||||
__license__ = "MIT"
|
||||
|
||||
MIN_RTORRENT_VERSION = (0, 8, 1)
|
||||
MIN_RTORRENT_VERSION_STR = convert_version_tuple_to_str(MIN_RTORRENT_VERSION)
|
||||
|
||||
|
||||
class RTorrent:
|
||||
""" Create a new rTorrent connection """
|
||||
rpc_prefix = None
|
||||
|
||||
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.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
|
||||
self._torrent_cache = []
|
||||
self._client_version_tuple = ()
|
||||
|
||||
if verify is True:
|
||||
self._verify_conn()
|
||||
|
||||
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.uri,
|
||||
transport=BasicAuthTransport(self.username, self.password),
|
||||
**self.sp_kwargs
|
||||
)
|
||||
|
||||
return self.sp(self.uri, **self.sp_kwargs)
|
||||
|
||||
def _verify_conn(self):
|
||||
# check for rpc methods that should be available
|
||||
assert "system.client_version" in self._get_rpc_methods(), "Required RPC method not available."
|
||||
assert "system.library_version" in self._get_rpc_methods(), "Required RPC method not available."
|
||||
|
||||
# minimum rTorrent version check
|
||||
assert self._meets_version_requirement() is True,\
|
||||
"Error: Minimum rTorrent version required is {0}".format(
|
||||
MIN_RTORRENT_VERSION_STR)
|
||||
|
||||
def _meets_version_requirement(self):
|
||||
return self._get_client_version_tuple() >= MIN_RTORRENT_VERSION
|
||||
|
||||
def _get_client_version_tuple(self):
|
||||
conn = self._get_conn()
|
||||
|
||||
if not self._client_version_tuple:
|
||||
if not hasattr(self, "client_version"):
|
||||
setattr(self, "client_version",
|
||||
conn.system.client_version())
|
||||
|
||||
rtver = getattr(self, "client_version")
|
||||
self._client_version_tuple = tuple([int(i) for i in
|
||||
rtver.split(".")])
|
||||
|
||||
return self._client_version_tuple
|
||||
|
||||
def _update_rpc_methods(self):
|
||||
self._rpc_methods = self._get_conn().system.listMethods()
|
||||
|
||||
return self._rpc_methods
|
||||
|
||||
def _get_rpc_methods(self):
|
||||
""" Get list of raw RPC commands
|
||||
|
||||
@return: raw RPC commands
|
||||
@rtype: list
|
||||
"""
|
||||
|
||||
return(self._rpc_methods or self._update_rpc_methods())
|
||||
|
||||
def get_torrents(self, view="main"):
|
||||
"""Get list of all torrents in specified view
|
||||
|
||||
@return: list of L{Torrent} instances
|
||||
|
||||
@rtype: list
|
||||
|
||||
@todo: add validity check for specified view
|
||||
"""
|
||||
self.torrents = []
|
||||
methods = rtorrent.torrent.methods
|
||||
retriever_methods = [m for m in methods
|
||||
if m.is_retriever() and m.is_available(self)]
|
||||
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
m.add("d.multicall", view, "d.get_hash=",
|
||||
*[method.rpc_call + "=" for method in retriever_methods])
|
||||
|
||||
results = m.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[1:]): # result[0] is the info_hash
|
||||
results_dict[m.varname] = rtorrent.rpc.process_result(m, r)
|
||||
|
||||
self.torrents.append(
|
||||
Torrent(self, info_hash=result[0], **results_dict)
|
||||
)
|
||||
|
||||
self._manage_torrent_cache()
|
||||
return(self.torrents)
|
||||
|
||||
def _manage_torrent_cache(self):
|
||||
"""Carry tracker/peer/file lists over to new torrent list"""
|
||||
for torrent in self._torrent_cache:
|
||||
new_torrent = rtorrent.common.find_torrent(torrent.info_hash,
|
||||
self.torrents)
|
||||
if new_torrent is not None:
|
||||
new_torrent.files = torrent.files
|
||||
new_torrent.peers = torrent.peers
|
||||
new_torrent.trackers = torrent.trackers
|
||||
|
||||
self._torrent_cache = self.torrents
|
||||
|
||||
def _get_load_function(self, file_type, start, verbose):
|
||||
"""Determine correct "load torrent" RPC method"""
|
||||
func_name = None
|
||||
if file_type == "url":
|
||||
# url strings can be input directly
|
||||
if start and verbose:
|
||||
func_name = "load_start_verbose"
|
||||
elif start:
|
||||
func_name = "load_start"
|
||||
elif verbose:
|
||||
func_name = "load_verbose"
|
||||
else:
|
||||
func_name = "load"
|
||||
elif file_type in ["file", "raw"]:
|
||||
if start and verbose:
|
||||
func_name = "load_raw_start_verbose"
|
||||
elif start:
|
||||
func_name = "load_raw_start"
|
||||
elif verbose:
|
||||
func_name = "load_raw_verbose"
|
||||
else:
|
||||
func_name = "load_raw"
|
||||
|
||||
return(func_name)
|
||||
|
||||
def load_torrent(self, torrent, start=False, verbose=False, verify_load=True):
|
||||
"""
|
||||
Loads torrent into rTorrent (with various enhancements)
|
||||
|
||||
@param torrent: can be a url, a path to a local file, or the raw data
|
||||
of a torrent file
|
||||
@type torrent: str
|
||||
|
||||
@param start: start torrent when loaded
|
||||
@type start: bool
|
||||
|
||||
@param verbose: print error messages to rTorrent log
|
||||
@type verbose: bool
|
||||
|
||||
@param verify_load: verify that torrent was added to rTorrent successfully
|
||||
@type verify_load: bool
|
||||
|
||||
@return: Depends on verify_load:
|
||||
- if verify_load is True, (and the torrent was
|
||||
loaded successfully), it'll return a L{Torrent} instance
|
||||
- if verify_load is False, it'll return None
|
||||
|
||||
@rtype: L{Torrent} instance or None
|
||||
|
||||
@raise AssertionError: If the torrent wasn't successfully added to rTorrent
|
||||
- Check L{TorrentParser} for the AssertionError's
|
||||
it raises
|
||||
|
||||
|
||||
@note: Because this function includes url verification (if a url was input)
|
||||
as well as verification as to whether the torrent was successfully added,
|
||||
this function doesn't execute instantaneously. If that's what you're
|
||||
looking for, use load_torrent_simple() instead.
|
||||
"""
|
||||
p = self._get_conn()
|
||||
tp = TorrentParser(torrent)
|
||||
torrent = xmlrpclib.Binary(tp._raw_torrent)
|
||||
info_hash = tp.info_hash
|
||||
|
||||
func_name = self._get_load_function("raw", start, verbose)
|
||||
|
||||
# load torrent
|
||||
getattr(p, func_name)(torrent)
|
||||
|
||||
if verify_load:
|
||||
MAX_RETRIES = 3
|
||||
i = 0
|
||||
while i < MAX_RETRIES:
|
||||
self.get_torrents()
|
||||
if info_hash in [t.info_hash for t in self.torrents]:
|
||||
break
|
||||
|
||||
# was still getting AssertionErrors, delay should help
|
||||
time.sleep(1)
|
||||
i += 1
|
||||
|
||||
assert info_hash in [t.info_hash for t in self.torrents],\
|
||||
"Adding torrent was unsuccessful."
|
||||
|
||||
return(find_torrent(info_hash, self.torrents))
|
||||
|
||||
def load_torrent_simple(self, torrent, file_type,
|
||||
start=False, verbose=False):
|
||||
"""Loads torrent into rTorrent
|
||||
|
||||
@param torrent: can be a url, a path to a local file, or the raw data
|
||||
of a torrent file
|
||||
@type torrent: str
|
||||
|
||||
@param file_type: valid options: "url", "file", or "raw"
|
||||
@type file_type: str
|
||||
|
||||
@param start: start torrent when loaded
|
||||
@type start: bool
|
||||
|
||||
@param verbose: print error messages to rTorrent log
|
||||
@type verbose: bool
|
||||
|
||||
@return: None
|
||||
|
||||
@raise AssertionError: if incorrect file_type is specified
|
||||
|
||||
@note: This function was written for speed, it includes no enhancements.
|
||||
If you input a url, it won't check if it's valid. You also can't get
|
||||
verification that the torrent was successfully added to rTorrent.
|
||||
Use load_torrent() if you would like these features.
|
||||
"""
|
||||
p = self._get_conn()
|
||||
|
||||
assert file_type in ["raw", "file", "url"], \
|
||||
"Invalid file_type, options are: 'url', 'file', 'raw'."
|
||||
func_name = self._get_load_function(file_type, start, verbose)
|
||||
|
||||
if file_type == "file":
|
||||
# since we have to assume we're connected to a remote rTorrent
|
||||
# client, we have to read the file and send it to rT as raw
|
||||
assert os.path.isfile(torrent), \
|
||||
"Invalid path: \"{0}\"".format(torrent)
|
||||
torrent = open(torrent, "rb").read()
|
||||
|
||||
if file_type in ["raw", "file"]:
|
||||
finput = xmlrpclib.Binary(torrent)
|
||||
elif file_type == "url":
|
||||
finput = torrent
|
||||
|
||||
getattr(p, func_name)(finput)
|
||||
|
||||
def get_views(self):
|
||||
p = self._get_conn()
|
||||
return p.view_list()
|
||||
|
||||
def create_group(self, name, persistent=True, view=None):
|
||||
p = self._get_conn()
|
||||
|
||||
if persistent is True:
|
||||
p.group.insert_persistent_view('', name)
|
||||
else:
|
||||
assert view is not None, "view parameter required on non-persistent groups"
|
||||
p.group.insert('', name, view)
|
||||
|
||||
self._update_rpc_methods()
|
||||
|
||||
def get_group(self, name):
|
||||
assert name is not None, "group name required"
|
||||
|
||||
group = Group(self, name)
|
||||
group.update()
|
||||
return group
|
||||
|
||||
def set_dht_port(self, port):
|
||||
"""Set DHT port
|
||||
|
||||
@param port: port
|
||||
@type port: int
|
||||
|
||||
@raise AssertionError: if invalid port is given
|
||||
"""
|
||||
assert is_valid_port(port), "Valid port range is 0-65535"
|
||||
self.dht_port = self._p.set_dht_port(port)
|
||||
|
||||
def enable_check_hash(self):
|
||||
"""Alias for set_check_hash(True)"""
|
||||
self.set_check_hash(True)
|
||||
|
||||
def disable_check_hash(self):
|
||||
"""Alias for set_check_hash(False)"""
|
||||
self.set_check_hash(False)
|
||||
|
||||
def find_torrent(self, info_hash):
|
||||
"""Frontend for rtorrent.common.find_torrent"""
|
||||
return(rtorrent.common.find_torrent(info_hash, self.get_torrents()))
|
||||
|
||||
def poll(self):
|
||||
""" poll rTorrent to get latest torrent/peer/tracker/file information
|
||||
|
||||
@note: This essentially refreshes every aspect of the rTorrent
|
||||
connection, so it can be very slow if working with a remote
|
||||
connection that has a lot of torrents loaded.
|
||||
|
||||
@return: None
|
||||
"""
|
||||
self.update()
|
||||
torrents = self.get_torrents()
|
||||
for t in torrents:
|
||||
t.poll()
|
||||
|
||||
def update(self):
|
||||
"""Refresh rTorrent client info
|
||||
|
||||
@note: All fields are stored as attributes to self.
|
||||
|
||||
@return: None
|
||||
"""
|
||||
multicall = rtorrent.rpc.Multicall(self)
|
||||
retriever_methods = [m for m in methods
|
||||
if m.is_retriever() and m.is_available(self)]
|
||||
for method in retriever_methods:
|
||||
multicall.add(method)
|
||||
|
||||
multicall.call()
|
||||
|
||||
|
||||
def _build_class_methods(class_obj):
|
||||
# multicall add class
|
||||
caller = lambda self, multicall, method, *args:\
|
||||
multicall.add(method, self.rpc_id, *args)
|
||||
|
||||
caller.__doc__ = """Same as Multicall.add(), but with automatic inclusion
|
||||
of the rpc_id
|
||||
|
||||
@param multicall: A L{Multicall} instance
|
||||
@type: multicall: Multicall
|
||||
|
||||
@param method: L{Method} instance or raw rpc method
|
||||
@type: Method or str
|
||||
|
||||
@param args: optional arguments to pass
|
||||
"""
|
||||
setattr(class_obj, "multicall_add", caller)
|
||||
|
||||
|
||||
def __compare_rpc_methods(rt_new, rt_old):
|
||||
from pprint import pprint
|
||||
rt_new_methods = set(rt_new._get_rpc_methods())
|
||||
rt_old_methods = set(rt_old._get_rpc_methods())
|
||||
print("New Methods:")
|
||||
pprint(rt_new_methods - rt_old_methods)
|
||||
print("Methods not in new rTorrent:")
|
||||
pprint(rt_old_methods - rt_new_methods)
|
||||
|
||||
|
||||
def __check_supported_methods(rt):
|
||||
from pprint import pprint
|
||||
supported_methods = set([m.rpc_call for m in
|
||||
methods +
|
||||
rtorrent.file.methods +
|
||||
rtorrent.torrent.methods +
|
||||
rtorrent.tracker.methods +
|
||||
rtorrent.peer.methods])
|
||||
all_methods = set(rt._get_rpc_methods())
|
||||
|
||||
print("Methods NOT in supported methods")
|
||||
pprint(all_methods - supported_methods)
|
||||
print("Supported methods NOT in all methods")
|
||||
pprint(supported_methods - all_methods)
|
||||
|
||||
methods = [
|
||||
# RETRIEVERS
|
||||
Method(RTorrent, 'get_xmlrpc_size_limit', 'get_xmlrpc_size_limit'),
|
||||
Method(RTorrent, 'get_proxy_address', 'get_proxy_address'),
|
||||
Method(RTorrent, 'get_split_suffix', 'get_split_suffix'),
|
||||
Method(RTorrent, 'get_up_limit', 'get_upload_rate'),
|
||||
Method(RTorrent, 'get_max_memory_usage', 'get_max_memory_usage'),
|
||||
Method(RTorrent, 'get_max_open_files', 'get_max_open_files'),
|
||||
Method(RTorrent, 'get_min_peers_seed', 'get_min_peers_seed'),
|
||||
Method(RTorrent, 'get_use_udp_trackers', 'get_use_udp_trackers'),
|
||||
Method(RTorrent, 'get_preload_min_size', 'get_preload_min_size'),
|
||||
Method(RTorrent, 'get_max_uploads', 'get_max_uploads'),
|
||||
Method(RTorrent, 'get_max_peers', 'get_max_peers'),
|
||||
Method(RTorrent, 'get_timeout_sync', 'get_timeout_sync'),
|
||||
Method(RTorrent, 'get_receive_buffer_size', 'get_receive_buffer_size'),
|
||||
Method(RTorrent, 'get_split_file_size', 'get_split_file_size'),
|
||||
Method(RTorrent, 'get_dht_throttle', 'get_dht_throttle'),
|
||||
Method(RTorrent, 'get_max_peers_seed', 'get_max_peers_seed'),
|
||||
Method(RTorrent, 'get_min_peers', 'get_min_peers'),
|
||||
Method(RTorrent, 'get_tracker_numwant', 'get_tracker_numwant'),
|
||||
Method(RTorrent, 'get_max_open_sockets', 'get_max_open_sockets'),
|
||||
Method(RTorrent, 'get_session', 'get_session'),
|
||||
Method(RTorrent, 'get_ip', 'get_ip'),
|
||||
Method(RTorrent, 'get_scgi_dont_route', 'get_scgi_dont_route'),
|
||||
Method(RTorrent, 'get_hash_read_ahead', 'get_hash_read_ahead'),
|
||||
Method(RTorrent, 'get_http_cacert', 'get_http_cacert'),
|
||||
Method(RTorrent, 'get_dht_port', 'get_dht_port'),
|
||||
Method(RTorrent, 'get_handshake_log', 'get_handshake_log'),
|
||||
Method(RTorrent, 'get_preload_type', 'get_preload_type'),
|
||||
Method(RTorrent, 'get_max_open_http', 'get_max_open_http'),
|
||||
Method(RTorrent, 'get_http_capath', 'get_http_capath'),
|
||||
Method(RTorrent, 'get_max_downloads_global', 'get_max_downloads_global'),
|
||||
Method(RTorrent, 'get_name', 'get_name'),
|
||||
Method(RTorrent, 'get_session_on_completion', 'get_session_on_completion'),
|
||||
Method(RTorrent, 'get_down_limit', 'get_download_rate'),
|
||||
Method(RTorrent, 'get_down_total', 'get_down_total'),
|
||||
Method(RTorrent, 'get_up_rate', 'get_up_rate'),
|
||||
Method(RTorrent, 'get_hash_max_tries', 'get_hash_max_tries'),
|
||||
Method(RTorrent, 'get_peer_exchange', 'get_peer_exchange'),
|
||||
Method(RTorrent, 'get_down_rate', 'get_down_rate'),
|
||||
Method(RTorrent, 'get_connection_seed', 'get_connection_seed'),
|
||||
Method(RTorrent, 'get_http_proxy', 'get_http_proxy'),
|
||||
Method(RTorrent, 'get_stats_preloaded', 'get_stats_preloaded'),
|
||||
Method(RTorrent, 'get_timeout_safe_sync', 'get_timeout_safe_sync'),
|
||||
Method(RTorrent, 'get_hash_interval', 'get_hash_interval'),
|
||||
Method(RTorrent, 'get_port_random', 'get_port_random'),
|
||||
Method(RTorrent, 'get_directory', 'get_directory'),
|
||||
Method(RTorrent, 'get_port_open', 'get_port_open'),
|
||||
Method(RTorrent, 'get_max_file_size', 'get_max_file_size'),
|
||||
Method(RTorrent, 'get_stats_not_preloaded', 'get_stats_not_preloaded'),
|
||||
Method(RTorrent, 'get_memory_usage', 'get_memory_usage'),
|
||||
Method(RTorrent, 'get_connection_leech', 'get_connection_leech'),
|
||||
Method(RTorrent, 'get_check_hash', 'get_check_hash',
|
||||
boolean=True,
|
||||
),
|
||||
Method(RTorrent, 'get_session_lock', 'get_session_lock'),
|
||||
Method(RTorrent, 'get_preload_required_rate', 'get_preload_required_rate'),
|
||||
Method(RTorrent, 'get_max_uploads_global', 'get_max_uploads_global'),
|
||||
Method(RTorrent, 'get_send_buffer_size', 'get_send_buffer_size'),
|
||||
Method(RTorrent, 'get_port_range', 'get_port_range'),
|
||||
Method(RTorrent, 'get_max_downloads_div', 'get_max_downloads_div'),
|
||||
Method(RTorrent, 'get_max_uploads_div', 'get_max_uploads_div'),
|
||||
Method(RTorrent, 'get_safe_sync', 'get_safe_sync'),
|
||||
Method(RTorrent, 'get_bind', 'get_bind'),
|
||||
Method(RTorrent, 'get_up_total', 'get_up_total'),
|
||||
Method(RTorrent, 'get_client_version', 'system.client_version'),
|
||||
Method(RTorrent, 'get_library_version', 'system.library_version'),
|
||||
Method(RTorrent, 'get_api_version', 'system.api_version',
|
||||
min_version=(0, 9, 1)
|
||||
),
|
||||
Method(RTorrent, "get_system_time", "system.time",
|
||||
docstring="""Get the current time of the system rTorrent is running on
|
||||
|
||||
@return: time (posix)
|
||||
@rtype: int""",
|
||||
),
|
||||
|
||||
# MODIFIERS
|
||||
Method(RTorrent, 'set_http_proxy', 'set_http_proxy'),
|
||||
Method(RTorrent, 'set_max_memory_usage', 'set_max_memory_usage'),
|
||||
Method(RTorrent, 'set_max_file_size', 'set_max_file_size'),
|
||||
Method(RTorrent, 'set_bind', 'set_bind',
|
||||
docstring="""Set address bind
|
||||
|
||||
@param arg: ip address
|
||||
@type arg: str
|
||||
""",
|
||||
),
|
||||
Method(RTorrent, 'set_up_limit', 'set_upload_rate',
|
||||
docstring="""Set global upload limit (in bytes)
|
||||
|
||||
@param arg: speed limit
|
||||
@type arg: int
|
||||
""",
|
||||
),
|
||||
Method(RTorrent, 'set_port_random', 'set_port_random'),
|
||||
Method(RTorrent, 'set_connection_leech', 'set_connection_leech'),
|
||||
Method(RTorrent, 'set_tracker_numwant', 'set_tracker_numwant'),
|
||||
Method(RTorrent, 'set_max_peers', 'set_max_peers'),
|
||||
Method(RTorrent, 'set_min_peers', 'set_min_peers'),
|
||||
Method(RTorrent, 'set_max_uploads_div', 'set_max_uploads_div'),
|
||||
Method(RTorrent, 'set_max_open_files', 'set_max_open_files'),
|
||||
Method(RTorrent, 'set_max_downloads_global', 'set_max_downloads_global'),
|
||||
Method(RTorrent, 'set_session_lock', 'set_session_lock'),
|
||||
Method(RTorrent, 'set_session', 'set_session'),
|
||||
Method(RTorrent, 'set_split_suffix', 'set_split_suffix'),
|
||||
Method(RTorrent, 'set_hash_interval', 'set_hash_interval'),
|
||||
Method(RTorrent, 'set_handshake_log', 'set_handshake_log'),
|
||||
Method(RTorrent, 'set_port_range', 'set_port_range'),
|
||||
Method(RTorrent, 'set_min_peers_seed', 'set_min_peers_seed'),
|
||||
Method(RTorrent, 'set_scgi_dont_route', 'set_scgi_dont_route'),
|
||||
Method(RTorrent, 'set_preload_min_size', 'set_preload_min_size'),
|
||||
Method(RTorrent, 'set_log.tracker', 'set_log.tracker'),
|
||||
Method(RTorrent, 'set_max_uploads_global', 'set_max_uploads_global'),
|
||||
Method(RTorrent, 'set_down_limit', 'set_download_rate',
|
||||
docstring="""Set global download limit (in bytes)
|
||||
|
||||
@param arg: speed limit
|
||||
@type arg: int
|
||||
""",
|
||||
),
|
||||
Method(RTorrent, 'set_preload_required_rate', 'set_preload_required_rate'),
|
||||
Method(RTorrent, 'set_hash_read_ahead', 'set_hash_read_ahead'),
|
||||
Method(RTorrent, 'set_max_peers_seed', 'set_max_peers_seed'),
|
||||
Method(RTorrent, 'set_max_uploads', 'set_max_uploads'),
|
||||
Method(RTorrent, 'set_session_on_completion', 'set_session_on_completion'),
|
||||
Method(RTorrent, 'set_max_open_http', 'set_max_open_http'),
|
||||
Method(RTorrent, 'set_directory', 'set_directory'),
|
||||
Method(RTorrent, 'set_http_cacert', 'set_http_cacert'),
|
||||
Method(RTorrent, 'set_dht_throttle', 'set_dht_throttle'),
|
||||
Method(RTorrent, 'set_hash_max_tries', 'set_hash_max_tries'),
|
||||
Method(RTorrent, 'set_proxy_address', 'set_proxy_address'),
|
||||
Method(RTorrent, 'set_split_file_size', 'set_split_file_size'),
|
||||
Method(RTorrent, 'set_receive_buffer_size', 'set_receive_buffer_size'),
|
||||
Method(RTorrent, 'set_use_udp_trackers', 'set_use_udp_trackers'),
|
||||
Method(RTorrent, 'set_connection_seed', 'set_connection_seed'),
|
||||
Method(RTorrent, 'set_xmlrpc_size_limit', 'set_xmlrpc_size_limit'),
|
||||
Method(RTorrent, 'set_xmlrpc_dialect', 'set_xmlrpc_dialect'),
|
||||
Method(RTorrent, 'set_safe_sync', 'set_safe_sync'),
|
||||
Method(RTorrent, 'set_http_capath', 'set_http_capath'),
|
||||
Method(RTorrent, 'set_send_buffer_size', 'set_send_buffer_size'),
|
||||
Method(RTorrent, 'set_max_downloads_div', 'set_max_downloads_div'),
|
||||
Method(RTorrent, 'set_name', 'set_name'),
|
||||
Method(RTorrent, 'set_port_open', 'set_port_open'),
|
||||
Method(RTorrent, 'set_timeout_sync', 'set_timeout_sync'),
|
||||
Method(RTorrent, 'set_peer_exchange', 'set_peer_exchange'),
|
||||
Method(RTorrent, 'set_ip', 'set_ip',
|
||||
docstring="""Set IP
|
||||
|
||||
@param arg: ip address
|
||||
@type arg: str
|
||||
""",
|
||||
),
|
||||
Method(RTorrent, 'set_timeout_safe_sync', 'set_timeout_safe_sync'),
|
||||
Method(RTorrent, 'set_preload_type', 'set_preload_type'),
|
||||
Method(RTorrent, 'set_check_hash', 'set_check_hash',
|
||||
docstring="""Enable/Disable hash checking on finished torrents
|
||||
|
||||
@param arg: True to enable, False to disable
|
||||
@type arg: bool
|
||||
""",
|
||||
boolean=True,
|
||||
),
|
||||
]
|
||||
|
||||
_all_methods_list = [methods,
|
||||
rtorrent.file.methods,
|
||||
rtorrent.torrent.methods,
|
||||
rtorrent.tracker.methods,
|
||||
rtorrent.peer.methods,
|
||||
]
|
||||
|
||||
class_methods_pair = {
|
||||
RTorrent: methods,
|
||||
rtorrent.file.File: rtorrent.file.methods,
|
||||
rtorrent.torrent.Torrent: rtorrent.torrent.methods,
|
||||
rtorrent.tracker.Tracker: rtorrent.tracker.methods,
|
||||
rtorrent.peer.Peer: rtorrent.peer.methods,
|
||||
}
|
||||
for c in class_methods_pair.keys():
|
||||
rtorrent.rpc._build_rpc_methods(c, class_methods_pair[c])
|
||||
_build_class_methods(c)
|
|
@ -1,86 +0,0 @@
|
|||
# Copyright (c) 2013 Chris Lucas, <chris@chrisjlucas.com>
|
||||
# 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 rtorrent.compat import is_py3
|
||||
|
||||
|
||||
def bool_to_int(value):
|
||||
"""Translates python booleans to RPC-safe integers"""
|
||||
if value is True:
|
||||
return("1")
|
||||
elif value is False:
|
||||
return("0")
|
||||
else:
|
||||
return(value)
|
||||
|
||||
|
||||
def cmd_exists(cmds_list, cmd):
|
||||
"""Check if given command is in list of available commands
|
||||
|
||||
@param cmds_list: see L{RTorrent._rpc_methods}
|
||||
@type cmds_list: list
|
||||
|
||||
@param cmd: name of command to be checked
|
||||
@type cmd: str
|
||||
|
||||
@return: bool
|
||||
"""
|
||||
|
||||
return(cmd in cmds_list)
|
||||
|
||||
|
||||
def find_torrent(info_hash, torrent_list):
|
||||
"""Find torrent file in given list of Torrent classes
|
||||
|
||||
@param info_hash: info hash of torrent
|
||||
@type info_hash: str
|
||||
|
||||
@param torrent_list: list of L{Torrent} instances (see L{RTorrent.get_torrents})
|
||||
@type torrent_list: list
|
||||
|
||||
@return: L{Torrent} instance, or -1 if not found
|
||||
"""
|
||||
for t in torrent_list:
|
||||
if t.info_hash == info_hash:
|
||||
return t
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def is_valid_port(port):
|
||||
"""Check if given port is valid"""
|
||||
return(0 <= int(port) <= 65535)
|
||||
|
||||
|
||||
def convert_version_tuple_to_str(t):
|
||||
return(".".join([str(n) for n in t]))
|
||||
|
||||
|
||||
def safe_repr(fmt, *args, **kwargs):
|
||||
""" Formatter that handles unicode arguments """
|
||||
|
||||
if not is_py3():
|
||||
# unicode fmt can take str args, str fmt cannot take unicode args
|
||||
fmt = fmt.decode("utf-8")
|
||||
out = fmt.format(*args, **kwargs)
|
||||
return out.encode("utf-8")
|
||||
else:
|
||||
return fmt.format(*args, **kwargs)
|
|
@ -1,30 +0,0 @@
|
|||
# Copyright (c) 2013 Chris Lucas, <chris@chrisjlucas.com>
|
||||
# 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.
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
def is_py3():
|
||||
return sys.version_info[0] == 3
|
||||
|
||||
if is_py3():
|
||||
import xmlrpc.client as xmlrpclib
|
||||
else:
|
||||
import xmlrpclib
|
|
@ -1,40 +0,0 @@
|
|||
# Copyright (c) 2013 Chris Lucas, <chris@chrisjlucas.com>
|
||||
# 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 rtorrent.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(
|
||||
convert_version_tuple_to_str(min_version))
|
||||
|
||||
def __str__(self):
|
||||
return(self.msg)
|
||||
|
||||
|
||||
class MethodError(Exception):
|
||||
def __init__(self, msg):
|
||||
self.msg = msg
|
||||
|
||||
def __str__(self):
|
||||
return(self.msg)
|
|
@ -1,91 +0,0 @@
|
|||
# Copyright (c) 2013 Chris Lucas, <chris@chrisjlucas.com>
|
||||
# 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 rtorrent.rpc import Method
|
||||
import rtorrent.rpc
|
||||
|
||||
from rtorrent.common import safe_repr
|
||||
|
||||
Method = rtorrent.rpc.Method
|
||||
|
||||
|
||||
class File:
|
||||
"""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():
|
||||
setattr(self, k, kwargs.get(k, None))
|
||||
|
||||
self.rpc_id = "{0}:f{1}".format(
|
||||
self.info_hash, self.index) # : unique id to pass to rTorrent
|
||||
|
||||
def update(self):
|
||||
"""Refresh file data
|
||||
|
||||
@note: All fields are stored as attributes to self.
|
||||
|
||||
@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)
|
||||
|
||||
multicall.call()
|
||||
|
||||
def __repr__(self):
|
||||
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, 'is_resize_queued', 'f.is_resize_queued',
|
||||
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, 'is_created', 'f.is_created',
|
||||
boolean=True,
|
||||
),
|
||||
Method(File, 'is_open', 'f.is_open',
|
||||
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, 'is_create_queued', 'f.is_create_queued',
|
||||
boolean=True,
|
||||
),
|
||||
|
||||
|
||||
# MODIFIERS
|
||||
]
|
|
@ -1,84 +0,0 @@
|
|||
# Copyright (c) 2013 Dean Gardiner, <gardiner91@gmail.com>
|
||||
# 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.
|
||||
|
||||
import rtorrent.rpc
|
||||
|
||||
Method = rtorrent.rpc.Method
|
||||
|
||||
|
||||
class Group:
|
||||
__name__ = 'Group'
|
||||
|
||||
def __init__(self, _rt_obj, name):
|
||||
self._rt_obj = _rt_obj
|
||||
self.name = name
|
||||
|
||||
self.methods = [
|
||||
# RETRIEVERS
|
||||
Method(Group, 'get_max', 'group.' + self.name + '.ratio.max', varname='max'),
|
||||
Method(Group, 'get_min', 'group.' + self.name + '.ratio.min', varname='min'),
|
||||
Method(Group, 'get_upload', 'group.' + self.name + '.ratio.upload', varname='upload'),
|
||||
|
||||
# MODIFIERS
|
||||
Method(Group, 'set_max', 'group.' + self.name + '.ratio.max.set', varname='max'),
|
||||
Method(Group, 'set_min', 'group.' + self.name + '.ratio.min.set', varname='min'),
|
||||
Method(Group, 'set_upload', 'group.' + self.name + '.ratio.upload.set', varname='upload')
|
||||
]
|
||||
|
||||
rtorrent.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)
|
||||
|
||||
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)]
|
||||
|
||||
for method in retriever_methods:
|
||||
multicall.add(method)
|
||||
|
||||
multicall.call()
|
||||
|
||||
def enable(self):
|
||||
p = self._rt_obj._get_conn()
|
||||
return getattr(p, self._get_prefix() + 'enable')()
|
||||
|
||||
def disable(self):
|
||||
p = self._rt_obj._get_conn()
|
||||
return getattr(p, self._get_prefix() + 'disable')()
|
||||
|
||||
def set_command(self, *methods):
|
||||
methods = [m + '=' for m in methods]
|
||||
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(
|
||||
m, 'system.method.set',
|
||||
self._get_prefix() + 'command',
|
||||
*methods
|
||||
)
|
||||
|
||||
return(m.call()[-1])
|
|
@ -1,281 +0,0 @@
|
|||
# Copyright (C) 2011 by clueless <clueless.nospam ! mail.com>
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Version: 20111107
|
||||
#
|
||||
# Changelog
|
||||
# ---------
|
||||
# 2011-11-07 - Added support for Python2 (tested on 2.6)
|
||||
# 2011-10-03 - Fixed: moved check for end of list at the top of the while loop
|
||||
# in _decode_list (in case the list is empty) (Chris Lucas)
|
||||
# - Converted dictionary keys to str
|
||||
# 2011-04-24 - Changed date format to YYYY-MM-DD for versioning, bigger
|
||||
# integer denotes a newer version
|
||||
# - Fixed a bug that would treat False as an integral type but
|
||||
# encode it using the 'False' string, attempting to encode a
|
||||
# boolean now results in an error
|
||||
# - Fixed a bug where an integer value of 0 in a list or
|
||||
# dictionary resulted in a parse error while decoding
|
||||
#
|
||||
# 2011-04-03 - Original release
|
||||
|
||||
import sys
|
||||
|
||||
_py3 = sys.version_info[0] == 3
|
||||
|
||||
if _py3:
|
||||
_VALID_STRING_TYPES = (str,)
|
||||
else:
|
||||
_VALID_STRING_TYPES = (str, unicode) # @UndefinedVariable
|
||||
|
||||
_TYPE_INT = 1
|
||||
_TYPE_STRING = 2
|
||||
_TYPE_LIST = 3
|
||||
_TYPE_DICTIONARY = 4
|
||||
_TYPE_END = 5
|
||||
_TYPE_INVALID = 6
|
||||
|
||||
# Function to determine the type of he next value/item
|
||||
# Arguments:
|
||||
# char First character of the string that is to be decoded
|
||||
# Return value:
|
||||
# Returns an integer that describes what type the next value/item is
|
||||
|
||||
|
||||
def _gettype(char):
|
||||
if not isinstance(char, int):
|
||||
char = ord(char)
|
||||
if char == 0x6C: # 'l'
|
||||
return _TYPE_LIST
|
||||
elif char == 0x64: # 'd'
|
||||
return _TYPE_DICTIONARY
|
||||
elif char == 0x69: # 'i'
|
||||
return _TYPE_INT
|
||||
elif char == 0x65: # 'e'
|
||||
return _TYPE_END
|
||||
elif char >= 0x30 and char <= 0x39: # '0' '9'
|
||||
return _TYPE_STRING
|
||||
else:
|
||||
return _TYPE_INVALID
|
||||
|
||||
# Function to parse a string from the bendcoded data
|
||||
# Arguments:
|
||||
# data bencoded data, must be guaranteed to be a string
|
||||
# Return Value:
|
||||
# Returns a tuple, the first member of the tuple is the parsed string
|
||||
# The second member is whatever remains of the bencoded data so it can
|
||||
# be used to parse the next part of the data
|
||||
|
||||
|
||||
def _decode_string(data):
|
||||
end = 1
|
||||
# if py3, data[end] is going to be an int
|
||||
# if py2, data[end] will be a string
|
||||
if _py3:
|
||||
char = 0x3A
|
||||
else:
|
||||
char = chr(0x3A)
|
||||
|
||||
while data[end] != char: # ':'
|
||||
end = end + 1
|
||||
strlen = int(data[:end])
|
||||
return (data[end + 1:strlen + end + 1], data[strlen + end + 1:])
|
||||
|
||||
# Function to parse an integer from the bencoded data
|
||||
# Arguments:
|
||||
# data bencoded data, must be guaranteed to be an integer
|
||||
# Return Value:
|
||||
# Returns a tuple, the first member of the tuple is the parsed string
|
||||
# The second member is whatever remains of the bencoded data so it can
|
||||
# be used to parse the next part of the data
|
||||
|
||||
|
||||
def _decode_int(data):
|
||||
end = 1
|
||||
# if py3, data[end] is going to be an int
|
||||
# if py2, data[end] will be a string
|
||||
if _py3:
|
||||
char = 0x65
|
||||
else:
|
||||
char = chr(0x65)
|
||||
|
||||
while data[end] != char: # 'e'
|
||||
end = end + 1
|
||||
return (int(data[1:end]), data[end + 1:])
|
||||
|
||||
# Function to parse a bencoded list
|
||||
# Arguments:
|
||||
# data bencoded data, must be guaranted to be the start of a list
|
||||
# Return Value:
|
||||
# Returns a tuple, the first member of the tuple is the parsed list
|
||||
# The second member is whatever remains of the bencoded data so it can
|
||||
# be used to parse the next part of the data
|
||||
|
||||
|
||||
def _decode_list(data):
|
||||
x = []
|
||||
overflow = data[1:]
|
||||
while True: # Loop over the data
|
||||
if _gettype(overflow[0]) == _TYPE_END: # - Break if we reach the end of the list
|
||||
return (x, overflow[1:]) # and return the list and overflow
|
||||
|
||||
value, overflow = _decode(overflow) #
|
||||
if isinstance(value, bool) or overflow == '': # - if we have a parse error
|
||||
return (False, False) # Die with error
|
||||
else: # - Otherwise
|
||||
x.append(value) # add the value to the list
|
||||
|
||||
|
||||
# Function to parse a bencoded list
|
||||
# Arguments:
|
||||
# data bencoded data, must be guaranted to be the start of a list
|
||||
# Return Value:
|
||||
# Returns a tuple, the first member of the tuple is the parsed dictionary
|
||||
# The second member is whatever remains of the bencoded data so it can
|
||||
# be used to parse the next part of the data
|
||||
def _decode_dict(data):
|
||||
x = {}
|
||||
overflow = data[1:]
|
||||
while True: # Loop over the data
|
||||
if _gettype(overflow[0]) != _TYPE_STRING: # - If the key is not a string
|
||||
return (False, False) # Die with error
|
||||
key, overflow = _decode(overflow) #
|
||||
if key == False or overflow == '': # - If parse error
|
||||
return (False, False) # Die with error
|
||||
value, overflow = _decode(overflow) #
|
||||
if isinstance(value, bool) or overflow == '': # - If parse error
|
||||
print("Error parsing value")
|
||||
print(value)
|
||||
print(overflow)
|
||||
return (False, False) # Die with error
|
||||
else:
|
||||
# don't use bytes for the key
|
||||
key = key.decode()
|
||||
x[key] = value
|
||||
if _gettype(overflow[0]) == _TYPE_END:
|
||||
return (x, overflow[1:])
|
||||
|
||||
# Arguments:
|
||||
# data bencoded data in bytes format
|
||||
# Return Values:
|
||||
# Returns a tuple, the first member is the parsed data, could be a string,
|
||||
# an integer, a list or a dictionary, or a combination of those
|
||||
# The second member is the leftover of parsing, if everything parses correctly this
|
||||
# should be an empty byte string
|
||||
|
||||
|
||||
def _decode(data):
|
||||
btype = _gettype(data[0])
|
||||
if btype == _TYPE_INT:
|
||||
return _decode_int(data)
|
||||
elif btype == _TYPE_STRING:
|
||||
return _decode_string(data)
|
||||
elif btype == _TYPE_LIST:
|
||||
return _decode_list(data)
|
||||
elif btype == _TYPE_DICTIONARY:
|
||||
return _decode_dict(data)
|
||||
else:
|
||||
return (False, False)
|
||||
|
||||
# Function to decode bencoded data
|
||||
# Arguments:
|
||||
# data bencoded data, can be str or bytes
|
||||
# Return Values:
|
||||
# Returns the decoded data on success, this coud be bytes, int, dict or list
|
||||
# or a combinatin of those
|
||||
# If an error occurs the return value is False
|
||||
|
||||
|
||||
def decode(data):
|
||||
# if isinstance(data, str):
|
||||
# data = data.encode()
|
||||
decoded, overflow = _decode(data)
|
||||
return decoded
|
||||
|
||||
# Args: data as integer
|
||||
# return: encoded byte string
|
||||
|
||||
|
||||
def _encode_int(data):
|
||||
return b'i' + str(data).encode() + b'e'
|
||||
|
||||
# Args: data as string or bytes
|
||||
# Return: encoded byte string
|
||||
|
||||
|
||||
def _encode_string(data):
|
||||
return str(len(data)).encode() + b':' + data
|
||||
|
||||
# Args: data as list
|
||||
# Return: Encoded byte string, false on error
|
||||
|
||||
|
||||
def _encode_list(data):
|
||||
elist = b'l'
|
||||
for item in data:
|
||||
eitem = encode(item)
|
||||
if eitem == False:
|
||||
return False
|
||||
elist += eitem
|
||||
return elist + b'e'
|
||||
|
||||
# Args: data as dict
|
||||
# Return: encoded byte string, false on error
|
||||
|
||||
|
||||
def _encode_dict(data):
|
||||
edict = b'd'
|
||||
keys = []
|
||||
for key in data:
|
||||
if not isinstance(key, _VALID_STRING_TYPES) and not isinstance(key, bytes):
|
||||
return False
|
||||
keys.append(key)
|
||||
keys.sort()
|
||||
for key in keys:
|
||||
ekey = encode(key)
|
||||
eitem = encode(data[key])
|
||||
if ekey == False or eitem == False:
|
||||
return False
|
||||
edict += ekey + eitem
|
||||
return edict + b'e'
|
||||
|
||||
# Function to encode a variable in bencoding
|
||||
# Arguments:
|
||||
# data Variable to be encoded, can be a list, dict, str, bytes, int or a combination of those
|
||||
# Return Values:
|
||||
# Returns the encoded data as a byte string when successful
|
||||
# If an error occurs the return value is False
|
||||
|
||||
|
||||
def encode(data):
|
||||
if isinstance(data, bool):
|
||||
return False
|
||||
elif isinstance(data, int):
|
||||
return _encode_int(data)
|
||||
elif isinstance(data, bytes):
|
||||
return _encode_string(data)
|
||||
elif isinstance(data, _VALID_STRING_TYPES):
|
||||
return _encode_string(data.encode())
|
||||
elif isinstance(data, list):
|
||||
return _encode_list(data)
|
||||
elif isinstance(data, dict):
|
||||
return _encode_dict(data)
|
||||
else:
|
||||
return False
|
|
@ -1,160 +0,0 @@
|
|||
# Copyright (c) 2013 Chris Lucas, <chris@chrisjlucas.com>
|
||||
# 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 rtorrent.compat import is_py3
|
||||
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():
|
||||
def __init__(self, torrent):
|
||||
"""Decode and parse given torrent
|
||||
|
||||
@param torrent: handles: urls, file paths, string of torrent data
|
||||
@type torrent: str
|
||||
|
||||
@raise AssertionError: Can be raised for a couple reasons:
|
||||
- If _get_raw_torrent() couldn't figure out
|
||||
what X{torrent} is
|
||||
- if X{torrent} isn't a valid bencoded torrent file
|
||||
"""
|
||||
self.torrent = torrent
|
||||
self._raw_torrent = None # : testing yo
|
||||
self._torrent_decoded = None # : what up
|
||||
self.file_type = None
|
||||
|
||||
self._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."
|
||||
self._parse_torrent()
|
||||
|
||||
def _is_raw(self):
|
||||
raw = False
|
||||
if isinstance(self.torrent, (str, bytes)):
|
||||
if isinstance(self._decode_torrent(self.torrent), dict):
|
||||
raw = True
|
||||
else:
|
||||
# reset self._torrent_decoded (currently equals False)
|
||||
self._torrent_decoded = None
|
||||
|
||||
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._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()
|
||||
# url?
|
||||
elif re.search("^(http|ftp):\/\/", self.torrent, re.I):
|
||||
self.file_type = "url"
|
||||
self._raw_torrent = urlopen(self.torrent).read()
|
||||
|
||||
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)
|
||||
|
||||
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_encoded:
|
||||
self.info_hash = hashlib.sha1(info_encoded).hexdigest().upper()
|
||||
|
||||
return(self.info_hash)
|
||||
|
||||
def _parse_torrent(self):
|
||||
for k in self._torrent_decoded:
|
||||
key = k.replace(" ", "_").lower()
|
||||
setattr(self, key, self._torrent_decoded[k])
|
||||
|
||||
self._calc_info_hash()
|
||||
|
||||
|
||||
class NewTorrentParser(object):
|
||||
@staticmethod
|
||||
def _read_file(fp):
|
||||
return fp.read()
|
||||
|
||||
@staticmethod
|
||||
def _write_file(fp):
|
||||
fp.write()
|
||||
return fp
|
||||
|
||||
@staticmethod
|
||||
def _decode_torrent(data):
|
||||
return bencode.decode(data)
|
||||
|
||||
def __init__(self, input):
|
||||
self.input = input
|
||||
self._raw_torrent = None
|
||||
self._decoded_torrent = None
|
||||
self._hash_outdated = False
|
||||
|
||||
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"))
|
||||
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"):
|
||||
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"
|
||||
|
||||
self._decoded_torrent = self._decode_torrent(self._raw_torrent)
|
||||
|
||||
assert isinstance(
|
||||
self._decoded_torrent, dict), "File could not be decoded"
|
||||
|
||||
def _calc_info_hash(self):
|
||||
self.info_hash = None
|
||||
info_dict = self._torrent_decoded["info"]
|
||||
self.info_hash = hashlib.sha1(bencode.encode(
|
||||
info_dict)).hexdigest().upper()
|
||||
|
||||
return(self.info_hash)
|
||||
|
||||
def set_tracker(self, tracker):
|
||||
self._decoded_torrent["announce"] = tracker
|
||||
|
||||
def get_tracker(self):
|
||||
return self._decoded_torrent.get("announce")
|
|
@ -1,73 +0,0 @@
|
|||
#
|
||||
# Copyright (c) 2013 Dean Gardiner, <gardiner91@gmail.com>
|
||||
#
|
||||
# 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,
|
||||
)
|
|
@ -1,23 +0,0 @@
|
|||
# Copyright (c) 2013 Chris Lucas, <chris@chrisjlucas.com>
|
||||
# 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 rtorrent.compat import xmlrpclib
|
||||
|
||||
HTTPServerProxy = xmlrpclib.ServerProxy
|
|
@ -1,219 +0,0 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# rtorrent_xmlrpc
|
||||
# (c) 2011 Roger Que <alerante@bellsouth.net>
|
||||
#
|
||||
# Modified portions:
|
||||
# (c) 2013 Dean Gardiner <gardiner91@gmail.com>
|
||||
#
|
||||
# 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] <http://libtorrent.rakshasa.no/wiki/UtilsXmlrpc2scgi>
|
||||
#
|
||||
# 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 httplib
|
||||
import re
|
||||
import socket
|
||||
import urllib
|
||||
import xmlrpclib
|
||||
import errno
|
||||
|
||||
|
||||
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, 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.
|
||||
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, int(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 (
|
||||
"<SCGIServerProxy for %s%s>" %
|
||||
(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,))
|
|
@ -1,98 +0,0 @@
|
|||
# Copyright (c) 2013 Chris Lucas, <chris@chrisjlucas.com>
|
||||
# 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 rtorrent.rpc import Method
|
||||
import rtorrent.rpc
|
||||
|
||||
from rtorrent.common import safe_repr
|
||||
|
||||
Method = rtorrent.rpc.Method
|
||||
|
||||
|
||||
class Peer:
|
||||
"""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():
|
||||
setattr(self, k, kwargs.get(k, None))
|
||||
|
||||
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)
|
||||
|
||||
def update(self):
|
||||
"""Refresh peer data
|
||||
|
||||
@note: All fields are stored as attributes to self.
|
||||
|
||||
@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)
|
||||
|
||||
multicall.call()
|
||||
|
||||
methods = [
|
||||
# RETRIEVERS
|
||||
Method(Peer, 'is_preferred', 'p.is_preferred',
|
||||
boolean=True,
|
||||
),
|
||||
Method(Peer, 'get_down_rate', 'p.get_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, '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, 'is_banned', 'p.banned',
|
||||
boolean=True,
|
||||
),
|
||||
Method(Peer, 'get_completed_percent', 'p.get_completed_percent'),
|
||||
Method(Peer, 'completed_percent', 'p.completed_percent'),
|
||||
Method(Peer, 'get_id', 'p.get_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, '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_client_version', 'p.client_version'),
|
||||
Method(Peer, 'get_up_total', 'p.get_up_total'),
|
||||
|
||||
# MODIFIERS
|
||||
]
|
|
@ -1,319 +0,0 @@
|
|||
# Copyright (c) 2013 Chris Lucas, <chris@chrisjlucas.com>
|
||||
# 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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
def get_varname(rpc_call):
|
||||
"""Transform rpc method into variable name.
|
||||
|
||||
@newfield example: Example
|
||||
@example: if the name of the rpc method is 'p.get_down_rate', the variable
|
||||
name will be 'down_rate'
|
||||
"""
|
||||
# extract variable name from xmlrpc func name
|
||||
r = re.search(
|
||||
"([ptdf]\.|system\.|get\_|is\_|set\_)+([^=]*)", rpc_call, re.I)
|
||||
if r:
|
||||
return(r.groups()[-1])
|
||||
else:
|
||||
return(None)
|
||||
|
||||
|
||||
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))
|
||||
|
||||
raise MethodError(msg)
|
||||
|
||||
|
||||
class DummyClass:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
class Method:
|
||||
"""Represents an individual RPC method"""
|
||||
|
||||
def __init__(self, _class, method_name,
|
||||
rpc_call, docstring=None, varname=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.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."
|
||||
|
||||
def __repr__(self):
|
||||
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
|
||||
|
||||
def is_modifier(self):
|
||||
if self.method_type == 'm':
|
||||
return(True)
|
||||
else:
|
||||
return(False)
|
||||
|
||||
def is_retriever(self):
|
||||
if self.method_type == 'r':
|
||||
return(True)
|
||||
else:
|
||||
return(False)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class Multicall:
|
||||
def __init__(self, class_obj, **kwargs):
|
||||
self.class_obj = class_obj
|
||||
if class_obj.__class__.__name__ == "RTorrent":
|
||||
self.rt_obj = class_obj
|
||||
else:
|
||||
self.rt_obj = class_obj._rt_obj
|
||||
self.calls = []
|
||||
|
||||
def add(self, method, *args):
|
||||
"""Add call to multicall
|
||||
|
||||
@param method: L{Method} instance or name of raw RPC method
|
||||
@type method: Method or str
|
||||
|
||||
@param args: call arguments
|
||||
"""
|
||||
# if a raw rpc method was given instead of a Method instance,
|
||||
# try and find the instance for it. And if all else fails, create a
|
||||
# dummy Method instance
|
||||
if isinstance(method, str):
|
||||
result = find_method(method)
|
||||
# if result not found
|
||||
if result == -1:
|
||||
method = Method(DummyClass, method, method)
|
||||
else:
|
||||
method = result
|
||||
|
||||
# ensure method is available before adding
|
||||
if not method.is_available(self.rt_obj):
|
||||
_handle_unavailable_rpc_method(method, self.rt_obj)
|
||||
|
||||
self.calls.append((method, args))
|
||||
|
||||
def list_calls(self):
|
||||
for c in self.calls:
|
||||
print(c)
|
||||
|
||||
def call(self):
|
||||
"""Execute added multicall calls
|
||||
|
||||
@return: the results (post-processed), in the order they were added
|
||||
@rtype: tuple
|
||||
"""
|
||||
m = xmlrpclib.MultiCall(self.rt_obj._get_conn())
|
||||
for call in self.calls:
|
||||
method, args = call
|
||||
rpc_call = getattr(method, "rpc_call")
|
||||
getattr(m, rpc_call)(*args)
|
||||
|
||||
results = m()
|
||||
results = tuple(results)
|
||||
results_processed = []
|
||||
|
||||
for r, c in zip(results, self.calls):
|
||||
method = c[0] # Method instance
|
||||
result = process_result(method, r)
|
||||
results_processed.append(result)
|
||||
# assign result to class_obj
|
||||
exists = hasattr(self.class_obj, method.varname)
|
||||
if not exists or not inspect.ismethod(getattr(self.class_obj, method.varname)):
|
||||
setattr(self.class_obj, method.varname, result)
|
||||
|
||||
return(tuple(results_processed))
|
||||
|
||||
|
||||
def call_method(class_obj, method, *args):
|
||||
"""Handles single RPC calls
|
||||
|
||||
@param class_obj: Peer/File/Torrent/Tracker/RTorrent instance
|
||||
@type class_obj: object
|
||||
|
||||
@param method: L{Method} instance or name of raw RPC method
|
||||
@type method: Method or str
|
||||
"""
|
||||
if method.is_retriever():
|
||||
args = args[:-1]
|
||||
else:
|
||||
assert args[-1] is not None, "No argument given."
|
||||
|
||||
if class_obj.__class__.__name__ == "RTorrent":
|
||||
rt_obj = class_obj
|
||||
else:
|
||||
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)
|
||||
# only added one method, only getting one result back
|
||||
ret_value = m.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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def process_result(method, result):
|
||||
"""Process given C{B{result}} based on flags set in C{B{method}}
|
||||
|
||||
@param method: L{Method} instance
|
||||
@type method: Method
|
||||
|
||||
@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
|
||||
convert to python boolean values
|
||||
"""
|
||||
# handle custom post processing function
|
||||
if method.post_process_func is not None:
|
||||
result = method.post_process_func(result)
|
||||
|
||||
# is boolean?
|
||||
if method.boolean:
|
||||
if result in [1, '1']:
|
||||
result = True
|
||||
elif result in [0, '0']:
|
||||
result = False
|
||||
|
||||
return(result)
|
||||
|
||||
|
||||
def _build_rpc_methods(class_, method_list):
|
||||
"""Build glorified aliases to raw RPC methods"""
|
||||
instance = None
|
||||
if not inspect.isclass(class_):
|
||||
instance = class_
|
||||
class_ = instance.__class__
|
||||
|
||||
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)
|
|
@ -1,517 +0,0 @@
|
|||
# Copyright (c) 2013 Chris Lucas, <chris@chrisjlucas.com>
|
||||
# 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.
|
||||
|
||||
import rtorrent.rpc
|
||||
# from rtorrent.rpc import Method
|
||||
import rtorrent.peer
|
||||
import rtorrent.tracker
|
||||
import rtorrent.file
|
||||
import rtorrent.compat
|
||||
|
||||
from rtorrent.common import safe_repr
|
||||
|
||||
Peer = rtorrent.peer.Peer
|
||||
Tracker = rtorrent.tracker.Tracker
|
||||
File = rtorrent.file.File
|
||||
Method = rtorrent.rpc.Method
|
||||
|
||||
|
||||
class Torrent:
|
||||
"""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():
|
||||
setattr(self, k, kwargs.get(k, None))
|
||||
|
||||
self.peers = []
|
||||
self.trackers = []
|
||||
self.files = []
|
||||
|
||||
self._call_custom_methods()
|
||||
|
||||
def __repr__(self):
|
||||
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."""
|
||||
self._is_hash_checking_queued()
|
||||
self._is_started()
|
||||
self._is_paused()
|
||||
|
||||
def get_peers(self):
|
||||
"""Get list of Peer instances for given torrent.
|
||||
|
||||
@return: L{Peer} instances
|
||||
@rtype: list
|
||||
|
||||
@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)]
|
||||
# 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])
|
||||
|
||||
results = m.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)
|
||||
|
||||
self.peers.append(Peer(
|
||||
self._rt_obj, self.info_hash, **results_dict))
|
||||
|
||||
return(self.peers)
|
||||
|
||||
def get_trackers(self):
|
||||
"""Get list of Tracker instances for given torrent.
|
||||
|
||||
@return: L{Tracker} instances
|
||||
@rtype: list
|
||||
|
||||
@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)]
|
||||
|
||||
# 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])
|
||||
|
||||
results = m.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)
|
||||
|
||||
self.trackers.append(Tracker(
|
||||
self._rt_obj, self.info_hash, **results_dict))
|
||||
|
||||
return(self.trackers)
|
||||
|
||||
def get_files(self):
|
||||
"""Get list of File instances for given torrent.
|
||||
|
||||
@return: L{File} instances
|
||||
@rtype: list
|
||||
|
||||
@note: also assigns return value to self.files
|
||||
"""
|
||||
|
||||
self.files = []
|
||||
retriever_methods = [m for m in rtorrent.file.methods
|
||||
if m.is_retriever() and m.is_available(self._rt_obj)]
|
||||
# 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])
|
||||
|
||||
results = m.call()[0] # only sent one call, only need first result
|
||||
|
||||
offset_method_index = retriever_methods.index(
|
||||
rtorrent.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])
|
||||
|
||||
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)
|
||||
|
||||
# get proper index positions for each file (based on the file
|
||||
# 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 set_directory(self, d):
|
||||
"""Modify 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", d)
|
||||
|
||||
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)
|
||||
self.multicall_add(m, "d.try_start")
|
||||
self.multicall_add(m, "d.is_active")
|
||||
|
||||
self.active = m.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")
|
||||
|
||||
self.active = m.call()[-1]
|
||||
return(self.active)
|
||||
|
||||
def pause(self):
|
||||
"""Pause the torrent"""
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(m, "d.pause")
|
||||
|
||||
return(m.call()[-1])
|
||||
|
||||
def resume(self):
|
||||
"""Resume the torrent"""
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(m, "d.resume")
|
||||
|
||||
return(m.call()[-1])
|
||||
|
||||
def close(self):
|
||||
"""Close the torrent and it's files"""
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(m, "d.close")
|
||||
|
||||
return(m.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")
|
||||
|
||||
return(m.call()[-1])
|
||||
|
||||
def check_hash(self):
|
||||
"""(Re)hash check the torrent"""
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(m, "d.check_hash")
|
||||
|
||||
return(m.call()[-1])
|
||||
|
||||
def poll(self):
|
||||
"""poll rTorrent to get latest peer/tracker/file information"""
|
||||
self.get_peers()
|
||||
self.get_trackers()
|
||||
self.get_files()
|
||||
|
||||
def update(self):
|
||||
"""Refresh torrent data
|
||||
|
||||
@note: All fields are stored as attributes to self.
|
||||
|
||||
@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)
|
||||
|
||||
multicall.call()
|
||||
|
||||
# custom functions (only call private methods, since they only check
|
||||
# local variables and are therefore faster)
|
||||
self._call_custom_methods()
|
||||
|
||||
def accept_seeders(self, accept_seeds):
|
||||
"""Enable/disable whether the torrent connects to seeders
|
||||
|
||||
@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"
|
||||
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(m, call)
|
||||
|
||||
return(m.call()[-1])
|
||||
|
||||
def announce(self):
|
||||
"""Announce torrent info to tracker(s)"""
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(m, "d.tracker_announce")
|
||||
|
||||
return(m.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"
|
||||
|
||||
def get_custom(self, key):
|
||||
"""
|
||||
Get custom value
|
||||
|
||||
@param key: the index for the custom field (between 1-5)
|
||||
@type key: int
|
||||
|
||||
@rtype: str
|
||||
"""
|
||||
|
||||
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])
|
||||
|
||||
return (getattr(self, field))
|
||||
|
||||
def set_custom(self, key, value):
|
||||
"""
|
||||
Set custom value
|
||||
|
||||
@param key: the index for the custom field (between 1-5)
|
||||
@type key: int
|
||||
|
||||
@param value: the value to be stored
|
||||
@type value: str
|
||||
|
||||
@return: if successful, value will be returned
|
||||
@rtype: str
|
||||
"""
|
||||
|
||||
self._assert_custom_key_valid(key)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
|
||||
self.multicall_add(m, "d.set_custom{0}".format(key), value)
|
||||
|
||||
return(m.call()[-1])
|
||||
|
||||
def set_visible(self, view, visible=True):
|
||||
p = self._rt_obj._get_conn()
|
||||
|
||||
if visible:
|
||||
return p.view.set_visible(self.info_hash, view)
|
||||
else:
|
||||
return p.view.set_not_visible(self.info_hash, view)
|
||||
|
||||
############################################################################
|
||||
# CUSTOM METHODS (Not part of the official rTorrent API)
|
||||
##########################################################################
|
||||
def _is_hash_checking_queued(self):
|
||||
"""Only checks instance variables, shouldn't be called directly"""
|
||||
# if hashing == 3, then torrent is marked for hash checking
|
||||
# if hash_checking == False, then torrent is waiting to be checked
|
||||
self.hash_checking_queued = (self.hashing == 3 and
|
||||
self.hash_checking is False)
|
||||
|
||||
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])
|
||||
|
||||
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)
|
||||
|
||||
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())
|
||||
|
||||
def _is_started(self):
|
||||
"""Only checks instance variables, shouldn't be called directly"""
|
||||
self.started = (self.state == 1)
|
||||
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())
|
||||
|
||||
|
||||
methods = [
|
||||
# RETRIEVERS
|
||||
Method(Torrent, 'is_hash_checked', 'd.is_hash_checked',
|
||||
boolean=True,
|
||||
),
|
||||
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, '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, 'is_complete', 'd.get_complete',
|
||||
boolean=True,
|
||||
),
|
||||
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, 'is_incomplete', 'd.incomplete',
|
||||
boolean=True,
|
||||
),
|
||||
Method(Torrent, 'get_tracker_size', 'd.get_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_ratio', 'd.get_ratio',
|
||||
post_process_func=lambda x: x / 1000.0,
|
||||
),
|
||||
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, '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, '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, '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",)),
|
||||
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, 'is_accepting_seeders', 'd.accepting_seeders',
|
||||
boolean=True,
|
||||
),
|
||||
Method(Torrent, "get_chunks_seen", "d.chunks_seen",
|
||||
min_version=(0, 9, 1),
|
||||
),
|
||||
Method(Torrent, "is_partially_done", "d.is_partially_done",
|
||||
boolean=True,
|
||||
),
|
||||
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"),
|
||||
|
||||
# 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'),
|
||||
]
|
|
@ -1,138 +0,0 @@
|
|||
# Copyright (c) 2013 Chris Lucas, <chris@chrisjlucas.com>
|
||||
# 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 rtorrent.rpc import Method
|
||||
import rtorrent.rpc
|
||||
|
||||
from rtorrent.common import safe_repr
|
||||
|
||||
Method = rtorrent.rpc.Method
|
||||
|
||||
|
||||
class Tracker:
|
||||
"""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():
|
||||
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.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)
|
||||
|
||||
def enable(self):
|
||||
"""Alias for set_enabled("yes")"""
|
||||
self.set_enabled("yes")
|
||||
|
||||
def disable(self):
|
||||
"""Alias for set_enabled("no")"""
|
||||
self.set_enabled("no")
|
||||
|
||||
def update(self):
|
||||
"""Refresh tracker data
|
||||
|
||||
@note: All fields are stored as attributes to self.
|
||||
|
||||
@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)
|
||||
|
||||
multicall.call()
|
||||
|
||||
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, '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_scrape_complete', 't.get_scrape_complete',
|
||||
min_version=(0, 8, 9),
|
||||
),
|
||||
Method(Tracker, 'get_activity_time_last', 't.activity_time_last',
|
||||
min_version=(0, 8, 9),
|
||||
),
|
||||
Method(Tracker, 'get_activity_time_next', 't.activity_time_next',
|
||||
min_version=(0, 8, 9),
|
||||
),
|
||||
Method(Tracker, 'get_failed_time_last', 't.failed_time_last',
|
||||
min_version=(0, 8, 9),
|
||||
),
|
||||
Method(Tracker, 'get_failed_time_next', 't.failed_time_next',
|
||||
min_version=(0, 8, 9),
|
||||
),
|
||||
Method(Tracker, 'get_success_time_last', 't.success_time_last',
|
||||
min_version=(0, 8, 9),
|
||||
),
|
||||
Method(Tracker, 'get_success_time_next', 't.success_time_next',
|
||||
min_version=(0, 8, 9),
|
||||
),
|
||||
Method(Tracker, 'can_scrape', 't.can_scrape',
|
||||
min_version=(0, 9, 1),
|
||||
boolean=True
|
||||
),
|
||||
Method(Tracker, 'get_failed_counter', 't.failed_counter',
|
||||
min_version=(0, 8, 9)
|
||||
),
|
||||
Method(Tracker, 'get_scrape_counter', 't.scrape_counter',
|
||||
min_version=(0, 8, 9)
|
||||
),
|
||||
Method(Tracker, 'get_success_counter', 't.success_counter',
|
||||
min_version=(0, 8, 9)
|
||||
),
|
||||
Method(Tracker, 'is_usable', 't.is_usable',
|
||||
min_version=(0, 9, 1),
|
||||
boolean=True
|
||||
),
|
||||
Method(Tracker, 'is_busy', 't.is_busy',
|
||||
min_version=(0, 9, 1),
|
||||
boolean=True
|
||||
),
|
||||
Method(Tracker, 'is_extra_tracker', 't.is_extra_tracker',
|
||||
min_version=(0, 9, 1),
|
||||
boolean=True,
|
||||
),
|
||||
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",
|
||||
min_version=(0, 9, 0)
|
||||
),
|
||||
|
||||
# MODIFIERS
|
||||
Method(Tracker, 'set_enabled', 't.set_enabled'),
|
||||
]
|
|
@ -19,7 +19,7 @@
|
|||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
from compat import is_py3
|
||||
from rtorrent.compat import is_py3
|
||||
|
||||
|
||||
def bool_to_int(value):
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
# 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 convert_version_tuple_to_str
|
||||
from rtorrent.common import convert_version_tuple_to_str
|
||||
|
||||
|
||||
class RTorrentVersionError(Exception):
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
# from rtorrent.rpc import Method
|
||||
import rpc
|
||||
import rtorrent.rpc
|
||||
|
||||
from common import safe_repr
|
||||
from rtorrent.common import safe_repr
|
||||
|
||||
Method = rpc.Method
|
||||
Method = rtorrent.rpc.Method
|
||||
|
||||
|
||||
class File:
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
# 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 rpc
|
||||
import rtorrent.rpc
|
||||
|
||||
Method = rpc.Method
|
||||
Method = rtorrent.rpc.Method
|
||||
|
||||
|
||||
class Group:
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
# Copyright (c) 2013 Chris Lucas, <chris@chrisjlucas.com>
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
|
@ -19,10 +18,10 @@
|
|||
# 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 lib.rtorrent.compat import is_py3
|
||||
from rtorrent.compat import is_py3
|
||||
import os.path
|
||||
import re
|
||||
import bencode as bencode
|
||||
import rtorrent.lib.bencode as bencode
|
||||
import hashlib
|
||||
|
||||
if is_py3():
|
||||
|
|
|
@ -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 lib.rtorrent.compat import xmlrpclib
|
||||
from rtorrent.compat import xmlrpclib
|
||||
|
||||
HTTPServerProxy = xmlrpclib.ServerProxy
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
# from rtorrent.rpc import Method
|
||||
import rpc
|
||||
import rtorrent.rpc
|
||||
|
||||
from common import safe_repr
|
||||
from rtorrent.common import safe_repr
|
||||
|
||||
Method = rpc.Method
|
||||
Method = rtorrent.rpc.Method
|
||||
|
||||
|
||||
class Peer:
|
||||
|
|
|
@ -19,12 +19,12 @@
|
|||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import inspect
|
||||
import lib.rtorrent
|
||||
import rtorrent
|
||||
import re
|
||||
from lib.rtorrent.common import bool_to_int, convert_version_tuple_to_str,\
|
||||
from rtorrent.common import bool_to_int, convert_version_tuple_to_str,\
|
||||
safe_repr
|
||||
from lib.rtorrent.err import MethodError
|
||||
from lib.rtorrent.compat import xmlrpclib
|
||||
from rtorrent.err import MethodError
|
||||
from rtorrent.compat import xmlrpclib
|
||||
|
||||
|
||||
def get_varname(rpc_call):
|
||||
|
@ -225,11 +225,11 @@ def call_method(class_obj, method, *args):
|
|||
def find_method(rpc_call):
|
||||
"""Return L{Method} instance associated with given RPC call"""
|
||||
method_lists = [
|
||||
lib.rtorrent.methods,
|
||||
lib.rtorrent.file.methods,
|
||||
lib.rtorrent.tracker.methods,
|
||||
lib.rtorrent.peer.methods,
|
||||
lib.rtorrent.torrent.methods,
|
||||
rtorrent.methods,
|
||||
rtorrent.file.methods,
|
||||
rtorrent.tracker.methods,
|
||||
rtorrent.peer.methods,
|
||||
rtorrent.torrent.methods,
|
||||
]
|
||||
|
||||
for l in method_lists:
|
||||
|
|
|
@ -18,19 +18,19 @@
|
|||
# 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 rpc
|
||||
import rtorrent.rpc
|
||||
# from rtorrent.rpc import Method
|
||||
import peer
|
||||
import tracker
|
||||
import file
|
||||
import compat
|
||||
import rtorrent.peer
|
||||
import rtorrent.tracker
|
||||
import rtorrent.file
|
||||
import rtorrent.compat
|
||||
|
||||
from common import safe_repr
|
||||
from rtorrent.common import safe_repr
|
||||
|
||||
Peer = peer.Peer
|
||||
Tracker = tracker.Tracker
|
||||
File = file.File
|
||||
Method = rpc.Method
|
||||
Peer = rtorrent.peer.Peer
|
||||
Tracker = rtorrent.tracker.Tracker
|
||||
File = rtorrent.file.File
|
||||
Method = rtorrent.rpc.Method
|
||||
|
||||
|
||||
class Torrent:
|
||||
|
@ -68,10 +68,10 @@ class Torrent:
|
|||
@note: also assigns return value to self.peers
|
||||
"""
|
||||
self.peers = []
|
||||
retriever_methods = [m for m in peer.methods
|
||||
retriever_methods = [m for m in rtorrent.peer.methods
|
||||
if m.is_retriever() and m.is_available(self._rt_obj)]
|
||||
# need to leave 2nd arg empty (dunno why)
|
||||
m = rpc.Multicall(self)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
m.add("p.multicall", self.info_hash, "",
|
||||
*[method.rpc_call + "=" for method in retriever_methods])
|
||||
|
||||
|
@ -81,7 +81,7 @@ class Torrent:
|
|||
results_dict = {}
|
||||
# build results_dict
|
||||
for m, r in zip(retriever_methods, result):
|
||||
results_dict[m.varname] = rpc.process_result(m, r)
|
||||
results_dict[m.varname] = rtorrent.rpc.process_result(m, r)
|
||||
|
||||
self.peers.append(Peer(
|
||||
self._rt_obj, self.info_hash, **results_dict))
|
||||
|
@ -97,11 +97,11 @@ class Torrent:
|
|||
@note: also assigns return value to self.trackers
|
||||
"""
|
||||
self.trackers = []
|
||||
retriever_methods = [m for m in tracker.methods
|
||||
retriever_methods = [m for m in rtorrent.tracker.methods
|
||||
if m.is_retriever() and m.is_available(self._rt_obj)]
|
||||
|
||||
# need to leave 2nd arg empty (dunno why)
|
||||
m = rpc.Multicall(self)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
m.add("t.multicall", self.info_hash, "",
|
||||
*[method.rpc_call + "=" for method in retriever_methods])
|
||||
|
||||
|
@ -111,7 +111,7 @@ class Torrent:
|
|||
results_dict = {}
|
||||
# build results_dict
|
||||
for m, r in zip(retriever_methods, result):
|
||||
results_dict[m.varname] = rpc.process_result(m, r)
|
||||
results_dict[m.varname] = rtorrent.rpc.process_result(m, r)
|
||||
|
||||
self.trackers.append(Tracker(
|
||||
self._rt_obj, self.info_hash, **results_dict))
|
||||
|
@ -128,18 +128,18 @@ class Torrent:
|
|||
"""
|
||||
|
||||
self.files = []
|
||||
retriever_methods = [m for m in file.methods
|
||||
retriever_methods = [m for m in rtorrent.file.methods
|
||||
if m.is_retriever() and m.is_available(self._rt_obj)]
|
||||
# 2nd arg can be anything, but it'll return all files in torrent
|
||||
# regardless
|
||||
m = rpc.Multicall(self)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
m.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
|
||||
|
||||
offset_method_index = retriever_methods.index(
|
||||
rpc.find_method("f.get_offset"))
|
||||
rtorrent.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])
|
||||
|
@ -148,7 +148,7 @@ class Torrent:
|
|||
results_dict = {}
|
||||
# build results_dict
|
||||
for m, r in zip(retriever_methods, result):
|
||||
results_dict[m.varname] = rpc.process_result(m, r)
|
||||
results_dict[m.varname] = rtorrent.rpc.process_result(m, r)
|
||||
|
||||
# get proper index positions for each file (based on the file
|
||||
# offset)
|
||||
|
@ -166,7 +166,7 @@ class Torrent:
|
|||
Also doesn't restart after directory is set, that must be called
|
||||
separately.
|
||||
"""
|
||||
m = rpc.Multicall(self)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(m, "d.try_stop")
|
||||
self.multicall_add(m, "d.set_directory", d)
|
||||
|
||||
|
@ -179,13 +179,13 @@ class Torrent:
|
|||
Also doesn't restart after directory is set, that must be called
|
||||
separately.
|
||||
"""
|
||||
m = rpc.Multicall(self)
|
||||
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 = rpc.Multicall(self)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(m, "d.try_start")
|
||||
self.multicall_add(m, "d.is_active")
|
||||
|
||||
|
@ -194,7 +194,7 @@ class Torrent:
|
|||
|
||||
def stop(self):
|
||||
""""Stop the torrent"""
|
||||
m = rpc.Multicall(self)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(m, "d.try_stop")
|
||||
self.multicall_add(m, "d.is_active")
|
||||
|
||||
|
@ -203,21 +203,21 @@ class Torrent:
|
|||
|
||||
def pause(self):
|
||||
"""Pause the torrent"""
|
||||
m = rpc.Multicall(self)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(m, "d.pause")
|
||||
|
||||
return(m.call()[-1])
|
||||
|
||||
def resume(self):
|
||||
"""Resume the torrent"""
|
||||
m = rpc.Multicall(self)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(m, "d.resume")
|
||||
|
||||
return(m.call()[-1])
|
||||
|
||||
def close(self):
|
||||
"""Close the torrent and it's files"""
|
||||
m = rpc.Multicall(self)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(m, "d.close")
|
||||
|
||||
return(m.call()[-1])
|
||||
|
@ -226,14 +226,14 @@ class Torrent:
|
|||
"""Delete the torrent
|
||||
|
||||
@note: doesn't delete the downloaded files"""
|
||||
m = rpc.Multicall(self)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(m, "d.erase")
|
||||
|
||||
return(m.call()[-1])
|
||||
|
||||
def check_hash(self):
|
||||
"""(Re)hash check the torrent"""
|
||||
m = rpc.Multicall(self)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(m, "d.check_hash")
|
||||
|
||||
return(m.call()[-1])
|
||||
|
@ -251,7 +251,7 @@ class Torrent:
|
|||
|
||||
@return: None
|
||||
"""
|
||||
multicall = rpc.Multicall(self)
|
||||
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:
|
||||
|
@ -273,14 +273,14 @@ class Torrent:
|
|||
else:
|
||||
call = "d.accepting_seeders.disable"
|
||||
|
||||
m = rpc.Multicall(self)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(m, call)
|
||||
|
||||
return(m.call()[-1])
|
||||
|
||||
def announce(self):
|
||||
"""Announce torrent info to tracker(s)"""
|
||||
m = rpc.Multicall(self)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(m, "d.tracker_announce")
|
||||
|
||||
return(m.call()[-1])
|
||||
|
@ -301,7 +301,7 @@ class Torrent:
|
|||
"""
|
||||
|
||||
self._assert_custom_key_valid(key)
|
||||
m = rpc.Multicall(self)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
|
||||
field = "custom{0}".format(key)
|
||||
self.multicall_add(m, "d.get_{0}".format(field))
|
||||
|
@ -324,7 +324,7 @@ class Torrent:
|
|||
"""
|
||||
|
||||
self._assert_custom_key_valid(key)
|
||||
m = rpc.Multicall(self)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
|
||||
self.multicall_add(m, "d.set_custom{0}".format(key), value)
|
||||
|
||||
|
@ -354,7 +354,7 @@ class Torrent:
|
|||
"""Check if torrent is waiting to be hash checked
|
||||
|
||||
@note: Variable where the result for this method is stored Torrent.hash_checking_queued"""
|
||||
m = rpc.Multicall(self)
|
||||
m = rtorrent.rpc.Multicall(self)
|
||||
self.multicall_add(m, "d.get_hashing")
|
||||
self.multicall_add(m, "d.is_hash_checking")
|
||||
results = m.call()
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
# from rtorrent.rpc import Method
|
||||
import rpc
|
||||
import rtorrent.rpc
|
||||
|
||||
from common import safe_repr
|
||||
from rtorrent.common import safe_repr
|
||||
|
||||
Method = rpc.Method
|
||||
Method = rtorrent.rpc.Method
|
||||
|
||||
|
||||
class Tracker:
|
||||
|
|
Loading…
Reference in New Issue