mirror of
https://github.com/correl/Transmission-XBMC.git
synced 2025-01-09 19:11:07 +00:00
Replaced transmissionrpc with stock 0.7 release
This commit is contained in:
parent
d5d921f8b2
commit
0df4cb5ace
9 changed files with 1300 additions and 796 deletions
16
resources/lib/transmissionrpc/__init__.py
Normal file → Executable file
16
resources/lib/transmissionrpc/__init__.py
Normal file → Executable file
|
@ -1,10 +1,16 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# 2008-08, Erik Svensson <erik.public@gmail.com>
|
# Copyright (c) 2008-2010 Erik Svensson <erik.public@gmail.com>
|
||||||
|
# Licensed under the MIT license.
|
||||||
|
|
||||||
from constants import *
|
from transmissionrpc.constants import DEFAULT_PORT, DEFAULT_TIMEOUT, STATUS, PRIORITY, RATIO_LIMIT, LOGGER
|
||||||
from transmission import TransmissionError, Torrent, Session, Client
|
from transmissionrpc.error import TransmissionError, HTTPHandlerError
|
||||||
|
from transmissionrpc.httphandler import HTTPHandler, DefaultHTTPHandler
|
||||||
|
from transmissionrpc.torrent import Torrent
|
||||||
|
from transmissionrpc.session import Session
|
||||||
|
from transmissionrpc.client import Client
|
||||||
|
from transmissionrpc.utils import add_stdout_logger
|
||||||
|
|
||||||
__author__ = u'Erik Svensson <erik.public@gmail.com>'
|
__author__ = u'Erik Svensson <erik.public@gmail.com>'
|
||||||
__version__ = u'0.3'
|
__version__ = u'0.7'
|
||||||
__copyright__ = u'Copyright (c) 2008 Erik Svensson'
|
__copyright__ = u'Copyright (c) 2008-2010 Erik Svensson'
|
||||||
__license__ = u'MIT'
|
__license__ = u'MIT'
|
||||||
|
|
643
resources/lib/transmissionrpc/client.py
Executable file
643
resources/lib/transmissionrpc/client.py
Executable file
|
@ -0,0 +1,643 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2008-2010 Erik Svensson <erik.public@gmail.com>
|
||||||
|
# Licensed under the MIT license.
|
||||||
|
|
||||||
|
import os, re, time
|
||||||
|
import warnings
|
||||||
|
import httplib, urllib2, urlparse, base64
|
||||||
|
|
||||||
|
try:
|
||||||
|
import json
|
||||||
|
except ImportError:
|
||||||
|
import simplejson as json
|
||||||
|
|
||||||
|
from transmissionrpc.constants import DEFAULT_PORT, DEFAULT_TIMEOUT
|
||||||
|
from transmissionrpc.error import TransmissionError, HTTPHandlerError
|
||||||
|
from transmissionrpc.utils import LOGGER, get_arguments, make_rpc_name, argument_value_convert, rpc_bool
|
||||||
|
from transmissionrpc.httphandler import DefaultHTTPHandler
|
||||||
|
from transmissionrpc.torrent import Torrent
|
||||||
|
from transmissionrpc.session import Session
|
||||||
|
|
||||||
|
def debug_httperror(error):
|
||||||
|
"""
|
||||||
|
Log the Transmission RPC HTTP error.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
data = json.loads(error.data)
|
||||||
|
except ValueError:
|
||||||
|
data = error.data
|
||||||
|
LOGGER.debug(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
'response': {
|
||||||
|
'url': error.url,
|
||||||
|
'code': error.code,
|
||||||
|
'msg': error.message,
|
||||||
|
'headers': error.headers,
|
||||||
|
'data': data,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
indent=2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
"""
|
||||||
|
Torrent ids
|
||||||
|
|
||||||
|
Many functions in Client takes torrent id. A torrent id can either be id or
|
||||||
|
hashString. When suppling multiple id's it is possible to use a list mixed
|
||||||
|
with both id and hashString.
|
||||||
|
|
||||||
|
Timeouts
|
||||||
|
|
||||||
|
Since most methods results in HTTP requests against Transmission, it is
|
||||||
|
possible to provide a argument called ``timeout``. Timeout is only effective
|
||||||
|
when using Python 2.6 or later and the default timeout is 30 seconds.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Client(object):
|
||||||
|
"""
|
||||||
|
Client is the class handling the Transmission JSON-RPC client protocol.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, address='localhost', port=DEFAULT_PORT, user=None, password=None, http_handler=None, timeout=None):
|
||||||
|
if isinstance(timeout, (int, long, float)):
|
||||||
|
self._query_timeout = float(timeout)
|
||||||
|
else:
|
||||||
|
self._query_timeout = DEFAULT_TIMEOUT
|
||||||
|
urlo = urlparse.urlparse(address)
|
||||||
|
if urlo.scheme == '':
|
||||||
|
base_url = 'http://' + address + ':' + str(port)
|
||||||
|
self.url = base_url + '/transmission/rpc'
|
||||||
|
else:
|
||||||
|
if urlo.port:
|
||||||
|
self.url = urlo.scheme + '://' + urlo.hostname + ':' + str(urlo.port) + urlo.path
|
||||||
|
else:
|
||||||
|
self.url = urlo.scheme + '://' + urlo.hostname + urlo.path
|
||||||
|
LOGGER.info('Using custom URL "' + self.url + '".')
|
||||||
|
if urlo.username and urlo.password:
|
||||||
|
user = urlo.username
|
||||||
|
password = urlo.password
|
||||||
|
elif urlo.username or urlo.password:
|
||||||
|
LOGGER.warning('Either user or password missing, not using authentication.')
|
||||||
|
if http_handler == None:
|
||||||
|
self.http_handler = DefaultHTTPHandler()
|
||||||
|
else:
|
||||||
|
if hasattr(http_handler, 'set_authentication') and hasattr(http_handler, 'request'):
|
||||||
|
self.http_handler = http_handler
|
||||||
|
else:
|
||||||
|
raise ValueError('Invalid HTTP handler.')
|
||||||
|
if user and password:
|
||||||
|
self.http_handler.set_authentication(self.url, user, password)
|
||||||
|
elif user or password:
|
||||||
|
LOGGER.warning('Either user or password missing, not using authentication.')
|
||||||
|
self._sequence = 0
|
||||||
|
self.session = Session()
|
||||||
|
self.session_id = 0
|
||||||
|
self.server_version = None
|
||||||
|
self.protocol_version = None
|
||||||
|
self.get_session()
|
||||||
|
self.torrent_get_arguments = get_arguments('torrent-get'
|
||||||
|
, self.rpc_version)
|
||||||
|
|
||||||
|
def _get_timeout(self):
|
||||||
|
"""
|
||||||
|
Get current timeout for HTTP queries.
|
||||||
|
"""
|
||||||
|
return self._query_timeout
|
||||||
|
|
||||||
|
def _set_timeout(self, value):
|
||||||
|
"""
|
||||||
|
Set timeout for HTTP queries.
|
||||||
|
"""
|
||||||
|
self._query_timeout = float(value)
|
||||||
|
|
||||||
|
def _del_timeout(self):
|
||||||
|
"""
|
||||||
|
Reset the HTTP query timeout to the default.
|
||||||
|
"""
|
||||||
|
self._query_timeout = DEFAULT_TIMEOUT
|
||||||
|
|
||||||
|
timeout = property(_get_timeout, _set_timeout, _del_timeout, doc="HTTP query timeout.")
|
||||||
|
|
||||||
|
def _http_query(self, query, timeout=None):
|
||||||
|
"""
|
||||||
|
Query Transmission through HTTP.
|
||||||
|
"""
|
||||||
|
headers = {'x-transmission-session-id': str(self.session_id)}
|
||||||
|
request_count = 0
|
||||||
|
if timeout == None:
|
||||||
|
timeout = self._query_timeout
|
||||||
|
while True:
|
||||||
|
LOGGER.debug(json.dumps({'url': self.url, 'headers': headers, 'query': query, 'timeout': timeout}, indent=2))
|
||||||
|
try:
|
||||||
|
result = self.http_handler.request(self.url, query, headers, timeout)
|
||||||
|
break
|
||||||
|
except HTTPHandlerError, error:
|
||||||
|
if error.code == 409:
|
||||||
|
LOGGER.info('Server responded with 409, trying to set session-id.')
|
||||||
|
if request_count > 1:
|
||||||
|
raise TransmissionError('Session ID negotiation failed.', error)
|
||||||
|
if 'x-transmission-session-id' in error.headers:
|
||||||
|
self.session_id = error.headers['x-transmission-session-id']
|
||||||
|
headers = {'x-transmission-session-id': str(self.session_id)}
|
||||||
|
else:
|
||||||
|
debug_httperror(error)
|
||||||
|
raise TransmissionError('Unknown conflict.', error)
|
||||||
|
else:
|
||||||
|
debug_httperror(error)
|
||||||
|
raise TransmissionError('Request failed.', error)
|
||||||
|
request_count = request_count + 1
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _request(self, method, arguments=None, ids=None, require_ids=False, timeout=None):
|
||||||
|
"""
|
||||||
|
Send json-rpc request to Transmission using http POST
|
||||||
|
"""
|
||||||
|
if not isinstance(method, (str, unicode)):
|
||||||
|
raise ValueError('request takes method as string')
|
||||||
|
if arguments == None:
|
||||||
|
arguments = {}
|
||||||
|
if not isinstance(arguments, dict):
|
||||||
|
raise ValueError('request takes arguments as dict')
|
||||||
|
ids = self._format_ids(ids)
|
||||||
|
if len(ids) > 0:
|
||||||
|
arguments['ids'] = ids
|
||||||
|
elif require_ids:
|
||||||
|
raise ValueError('request require ids')
|
||||||
|
|
||||||
|
query = json.dumps({'tag': self._sequence, 'method': method
|
||||||
|
, 'arguments': arguments})
|
||||||
|
self._sequence += 1
|
||||||
|
start = time.time()
|
||||||
|
http_data = self._http_query(query, timeout)
|
||||||
|
elapsed = time.time() - start
|
||||||
|
LOGGER.info('http request took %.3f s' % (elapsed))
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(http_data)
|
||||||
|
except ValueError, error:
|
||||||
|
LOGGER.error('Error: ' + str(error))
|
||||||
|
LOGGER.error('Request: \"%s\"' % (query))
|
||||||
|
LOGGER.error('HTTP data: \"%s\"' % (http_data))
|
||||||
|
raise
|
||||||
|
|
||||||
|
LOGGER.debug(json.dumps(data, indent=2))
|
||||||
|
if 'result' in data:
|
||||||
|
if data['result'] != 'success':
|
||||||
|
raise TransmissionError('Query failed with result \"%s\".' % (data['result']))
|
||||||
|
else:
|
||||||
|
raise TransmissionError('Query failed without result.')
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
if method == 'torrent-get':
|
||||||
|
for item in data['arguments']['torrents']:
|
||||||
|
results[item['id']] = Torrent(self, item)
|
||||||
|
if self.protocol_version == 2 and 'peers' not in item:
|
||||||
|
self.protocol_version = 1
|
||||||
|
elif method == 'torrent-add':
|
||||||
|
item = data['arguments']['torrent-added']
|
||||||
|
results[item['id']] = Torrent(self, item)
|
||||||
|
elif method == 'session-get':
|
||||||
|
self._update_session(data['arguments'])
|
||||||
|
elif method == 'session-stats':
|
||||||
|
# older versions of T has the return data in "session-stats"
|
||||||
|
if 'session-stats' in data['arguments']:
|
||||||
|
self._update_session(data['arguments']['session-stats'])
|
||||||
|
else:
|
||||||
|
self._update_session(data['arguments'])
|
||||||
|
elif method in ('port-test', 'blocklist-update'):
|
||||||
|
results = data['arguments']
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def _format_ids(self, args):
|
||||||
|
"""
|
||||||
|
Take things and make them valid torrent identifiers
|
||||||
|
"""
|
||||||
|
ids = []
|
||||||
|
|
||||||
|
if args == None:
|
||||||
|
pass
|
||||||
|
elif isinstance(args, (int, long)):
|
||||||
|
ids.append(args)
|
||||||
|
elif isinstance(args, (str, unicode)):
|
||||||
|
for item in re.split(u'[ ,]+', args):
|
||||||
|
if len(item) == 0:
|
||||||
|
continue
|
||||||
|
addition = None
|
||||||
|
try:
|
||||||
|
# handle index
|
||||||
|
addition = [int(item)]
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if not addition:
|
||||||
|
# handle hashes
|
||||||
|
try:
|
||||||
|
int(item, 16)
|
||||||
|
addition = [item]
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if not addition:
|
||||||
|
# handle index ranges i.e. 5:10
|
||||||
|
match = re.match(u'^(\d+):(\d+)$', item)
|
||||||
|
if match:
|
||||||
|
try:
|
||||||
|
idx_from = int(match.group(1))
|
||||||
|
idx_to = int(match.group(2))
|
||||||
|
addition = range(idx_from, idx_to + 1)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if not addition:
|
||||||
|
raise ValueError(u'Invalid torrent id, \"%s\"' % item)
|
||||||
|
ids.extend(addition)
|
||||||
|
elif isinstance(args, (list)):
|
||||||
|
for item in args:
|
||||||
|
ids.extend(self._format_ids(item))
|
||||||
|
else:
|
||||||
|
raise ValueError(u'Invalid torrent id')
|
||||||
|
return ids
|
||||||
|
|
||||||
|
def _update_session(self, data):
|
||||||
|
"""
|
||||||
|
Update session data.
|
||||||
|
"""
|
||||||
|
self.session.update(data)
|
||||||
|
|
||||||
|
def _update_server_version(self):
|
||||||
|
if self.server_version == None:
|
||||||
|
version_major = 1
|
||||||
|
version_minor = 30
|
||||||
|
version_changeset = 0
|
||||||
|
version_parser = re.compile('(\d).(\d+) \((\d+)\)')
|
||||||
|
if hasattr(self.session, 'version'):
|
||||||
|
match = version_parser.match(self.session.version)
|
||||||
|
if (match):
|
||||||
|
version_major = int(match.group(1))
|
||||||
|
version_minor = int(match.group(2))
|
||||||
|
version_changeset = match.group(3)
|
||||||
|
self.server_version = (version_major, version_minor, version_changeset)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rpc_version(self):
|
||||||
|
"""
|
||||||
|
Get the Transmission RPC version. Trying to deduct if the server don't have a version value.
|
||||||
|
"""
|
||||||
|
if self.protocol_version == None:
|
||||||
|
# Ugly fix for 2.12 reporting rpc-version 10, but having new arguments
|
||||||
|
if (self.server_version and (self.server_version[0] == 2 and self.server_version[1] == 12)):
|
||||||
|
self.protocol_version = 11
|
||||||
|
elif hasattr(self.session, 'rpc_version'):
|
||||||
|
self.protocol_version = self.session.rpc_version
|
||||||
|
elif hasattr(self.session, 'version'):
|
||||||
|
self.protocol_version = 3
|
||||||
|
else:
|
||||||
|
self.protocol_version = 2
|
||||||
|
return self.protocol_version
|
||||||
|
|
||||||
|
def _rpc_version_warning(self, version):
|
||||||
|
"""
|
||||||
|
Add a warning to the log if the Transmission RPC version is lower then the provided version.
|
||||||
|
"""
|
||||||
|
if self.rpc_version < version:
|
||||||
|
LOGGER.warning('Using feature not supported by server. RPC version for server %d, feature introduced in %d.' % (self.rpc_version, version))
|
||||||
|
|
||||||
|
def add(self, data, timeout=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Add torrent to transfers list. Takes a base64 encoded .torrent file in data.
|
||||||
|
Additional arguments are:
|
||||||
|
|
||||||
|
===================== ==== =============================================================
|
||||||
|
Argument RPC Description
|
||||||
|
===================== ==== =============================================================
|
||||||
|
``bandwidthPriority`` 8 - Priority for this transfer.
|
||||||
|
``download_dir`` 1 - The directory where the downloaded contents will be saved in.
|
||||||
|
``filename`` 1 - A filepath or URL to a torrent file or a magnet link.
|
||||||
|
``files_unwanted`` 1 - A list of file id's that shouldn't be downloaded.
|
||||||
|
``files_wanted`` 1 - A list of file id's that should be downloaded.
|
||||||
|
``metainfo`` 1 - The content of a torrent file, base64 encoded.
|
||||||
|
``paused`` 1 - If True, does not start the transfer when added.
|
||||||
|
``peer_limit`` 1 - Maximum number of peers allowed.
|
||||||
|
``priority_high`` 1 - A list of file id's that should have high priority.
|
||||||
|
``priority_low`` 1 - A list of file id's that should have low priority.
|
||||||
|
``priority_normal`` 1 - A list of file id's that should have normal priority.
|
||||||
|
===================== ==== =============================================================
|
||||||
|
"""
|
||||||
|
args = {}
|
||||||
|
if data:
|
||||||
|
args = {'metainfo': data}
|
||||||
|
elif 'metainfo' not in kwargs and 'filename' not in kwargs:
|
||||||
|
raise ValueError('No torrent data or torrent uri.')
|
||||||
|
for key, value in kwargs.iteritems():
|
||||||
|
argument = make_rpc_name(key)
|
||||||
|
(arg, val) = argument_value_convert('torrent-add',
|
||||||
|
argument, value, self.rpc_version)
|
||||||
|
args[arg] = val
|
||||||
|
return self._request('torrent-add', args, timeout=timeout)
|
||||||
|
|
||||||
|
def add_uri(self, uri, **kwargs):
|
||||||
|
"""
|
||||||
|
Add torrent to transfers list. Takes a uri to a torrent, supporting
|
||||||
|
all uri's supported by Transmissions torrent-add 'filename'
|
||||||
|
argument. Additional arguments are:
|
||||||
|
|
||||||
|
===================== ==== =============================================================
|
||||||
|
Argument RPC Description
|
||||||
|
===================== ==== =============================================================
|
||||||
|
``bandwidthPriority`` 8 - Priority for this transfer.
|
||||||
|
``download_dir`` 1 - The directory where the downloaded contents will be saved in.
|
||||||
|
``files_unwanted`` 1 - A list of file id's that shouldn't be downloaded.
|
||||||
|
``files_wanted`` 1 - A list of file id's that should be downloaded.
|
||||||
|
``paused`` 1 - If True, does not start the transfer when added.
|
||||||
|
``peer_limit`` 1 - Maximum number of peers allowed.
|
||||||
|
``priority_high`` 1 - A list of file id's that should have high priority.
|
||||||
|
``priority_low`` 1 - A list of file id's that should have low priority.
|
||||||
|
``priority_normal`` 1 - A list of file id's that should have normal priority.
|
||||||
|
===================== ==== =============================================================
|
||||||
|
"""
|
||||||
|
if uri == None:
|
||||||
|
raise ValueError('add_uri requires a URI.')
|
||||||
|
# there has been some problem with T's built in torrent fetcher,
|
||||||
|
# use a python one instead
|
||||||
|
parseduri = urlparse.urlparse(uri)
|
||||||
|
torrent_data = None
|
||||||
|
if parseduri.scheme in ['file', 'ftp', 'ftps', 'http', 'https']:
|
||||||
|
torrent_file = urllib2.urlopen(uri)
|
||||||
|
torrent_data = base64.b64encode(torrent_file.read())
|
||||||
|
if torrent_data:
|
||||||
|
return self.add(torrent_data, **kwargs)
|
||||||
|
else:
|
||||||
|
return self.add(None, filename=uri, **kwargs)
|
||||||
|
|
||||||
|
def remove(self, ids, delete_data=False, timeout=None):
|
||||||
|
"""
|
||||||
|
remove torrent(s) with provided id(s). Local data is removed if
|
||||||
|
delete_data is True, otherwise not.
|
||||||
|
"""
|
||||||
|
self._rpc_version_warning(3)
|
||||||
|
self._request('torrent-remove',
|
||||||
|
{'delete-local-data':rpc_bool(delete_data)}, ids, True, timeout=timeout)
|
||||||
|
|
||||||
|
def start(self, ids, timeout=None):
|
||||||
|
"""start torrent(s) with provided id(s)"""
|
||||||
|
self._request('torrent-start', {}, ids, True, timeout=timeout)
|
||||||
|
|
||||||
|
def stop(self, ids, timeout=None):
|
||||||
|
"""stop torrent(s) with provided id(s)"""
|
||||||
|
self._request('torrent-stop', {}, ids, True, timeout=timeout)
|
||||||
|
|
||||||
|
def verify(self, ids, timeout=None):
|
||||||
|
"""verify torrent(s) with provided id(s)"""
|
||||||
|
self._request('torrent-verify', {}, ids, True, timeout=timeout)
|
||||||
|
|
||||||
|
def reannounce(self, ids, timeout=None):
|
||||||
|
"""Reannounce torrent(s) with provided id(s)"""
|
||||||
|
self._rpc_version_warning(5)
|
||||||
|
self._request('torrent-reannounce', {}, ids, True, timeout=timeout)
|
||||||
|
|
||||||
|
def info(self, ids=None, arguments=None, timeout=None):
|
||||||
|
"""Get detailed information for torrent(s) with provided id(s)."""
|
||||||
|
if not arguments:
|
||||||
|
arguments = self.torrent_get_arguments
|
||||||
|
return self._request('torrent-get', {'fields': arguments}, ids, timeout=timeout)
|
||||||
|
|
||||||
|
def get_files(self, ids=None, timeout=None):
|
||||||
|
"""
|
||||||
|
Get list of files for provided torrent id(s). If ids is empty,
|
||||||
|
information for all torrents are fetched. This function returns a dictonary
|
||||||
|
for each requested torrent id holding the information about the files.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
{
|
||||||
|
<torrent id>: {
|
||||||
|
<file id>: {
|
||||||
|
'name': <file name>,
|
||||||
|
'size': <file size in bytes>,
|
||||||
|
'completed': <bytes completed>,
|
||||||
|
'priority': <priority ('high'|'normal'|'low')>,
|
||||||
|
'selected': <selected for download (True|False)>
|
||||||
|
}
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
fields = ['id', 'name', 'hashString', 'files', 'priorities', 'wanted']
|
||||||
|
request_result = self._request('torrent-get', {'fields': fields}, ids, timeout=timeout)
|
||||||
|
result = {}
|
||||||
|
for tid, torrent in request_result.iteritems():
|
||||||
|
result[tid] = torrent.files()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def set_files(self, items, timeout=None):
|
||||||
|
"""
|
||||||
|
Set file properties. Takes a dictonary with similar contents as the result
|
||||||
|
of `get_files`.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
{
|
||||||
|
<torrent id>: {
|
||||||
|
<file id>: {
|
||||||
|
'priority': <priority ('high'|'normal'|'low')>,
|
||||||
|
'selected': <selected for download (True|False)>
|
||||||
|
}
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
if not isinstance(items, dict):
|
||||||
|
raise ValueError('Invalid file description')
|
||||||
|
for tid, files in items.iteritems():
|
||||||
|
if not isinstance(files, dict):
|
||||||
|
continue
|
||||||
|
wanted = []
|
||||||
|
unwanted = []
|
||||||
|
priority_high = []
|
||||||
|
priority_normal = []
|
||||||
|
priority_low = []
|
||||||
|
for fid, file_desc in files.iteritems():
|
||||||
|
if not isinstance(file_desc, dict):
|
||||||
|
continue
|
||||||
|
if 'selected' in file_desc and file_desc['selected']:
|
||||||
|
wanted.append(fid)
|
||||||
|
else:
|
||||||
|
unwanted.append(fid)
|
||||||
|
if 'priority' in file_desc:
|
||||||
|
if file_desc['priority'] == 'high':
|
||||||
|
priority_high.append(fid)
|
||||||
|
elif file_desc['priority'] == 'normal':
|
||||||
|
priority_normal.append(fid)
|
||||||
|
elif file_desc['priority'] == 'low':
|
||||||
|
priority_low.append(fid)
|
||||||
|
self.change([tid], files_wanted = wanted
|
||||||
|
, files_unwanted = unwanted
|
||||||
|
, priority_high = priority_high
|
||||||
|
, priority_normal = priority_normal
|
||||||
|
, priority_low = priority_low, timeout=timeout)
|
||||||
|
|
||||||
|
def list(self, timeout=None):
|
||||||
|
"""list all torrents"""
|
||||||
|
fields = ['id', 'hashString', 'name', 'sizeWhenDone', 'leftUntilDone'
|
||||||
|
, 'eta', 'status', 'rateUpload', 'rateDownload', 'uploadedEver'
|
||||||
|
, 'downloadedEver']
|
||||||
|
return self._request('torrent-get', {'fields': fields}, timeout=timeout)
|
||||||
|
|
||||||
|
def change(self, ids, timeout=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Change torrent parameters for the torrent(s) with the supplied id's. The
|
||||||
|
parameters are:
|
||||||
|
|
||||||
|
============================ ===== =============== =======================================================================================
|
||||||
|
Argument RPC Replaced by Description
|
||||||
|
============================ ===== =============== =======================================================================================
|
||||||
|
``bandwidthPriority`` 5 - Priority for this transfer.
|
||||||
|
``downloadLimit`` 5 - Set the speed limit for download in Kib/s.
|
||||||
|
``downloadLimited`` 5 - Enable download speed limiter.
|
||||||
|
``files_unwanted`` 1 - A list of file id's that shouldn't be downloaded.
|
||||||
|
``files_wanted`` 1 - A list of file id's that should be downloaded.
|
||||||
|
``honorsSessionLimits`` 5 - Enables or disables the transfer to honour the upload limit set in the session.
|
||||||
|
``ids`` 1 - Local download location.
|
||||||
|
``peer_limit`` 1 - The peer limit for the torrents.
|
||||||
|
``priority_high`` 1 - A list of file id's that should have high priority.
|
||||||
|
``priority_low`` 1 - A list of file id's that should have normal priority.
|
||||||
|
``priority_normal`` 1 - A list of file id's that should have low priority.
|
||||||
|
``seedIdleLimit`` 10 - Seed inactivity limit in minutes.
|
||||||
|
``seedIdleMode`` 10 - Seed inactivity mode. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.
|
||||||
|
``seedRatioLimit`` 5 - Seeding ratio.
|
||||||
|
``seedRatioMode`` 5 - Which ratio to use. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.
|
||||||
|
``speed_limit_down`` 1 - 5 downloadLimit Set the speed limit for download in Kib/s.
|
||||||
|
``speed_limit_down_enabled`` 1 - 5 downloadLimited Enable download speed limiter.
|
||||||
|
``speed_limit_up`` 1 - 5 uploadLimit Set the speed limit for upload in Kib/s.
|
||||||
|
``speed_limit_up_enabled`` 1 - 5 uploadLimited Enable upload speed limiter.
|
||||||
|
``trackerAdd`` 10 - Array of string with announce URLs to add.
|
||||||
|
``trackerRemove`` 10 - Array of ids of trackers to remove.
|
||||||
|
``trackerReplace`` 10 - Array of (id, url) tuples where the announce URL should be replaced.
|
||||||
|
``uploadLimit`` 5 - Set the speed limit for upload in Kib/s.
|
||||||
|
``uploadLimited`` 5 - Enable upload speed limiter.
|
||||||
|
============================ ===== =============== =======================================================================================
|
||||||
|
|
||||||
|
.. NOTE::
|
||||||
|
transmissionrpc will try to automatically fix argument errors.
|
||||||
|
"""
|
||||||
|
args = {}
|
||||||
|
for key, value in kwargs.iteritems():
|
||||||
|
argument = make_rpc_name(key)
|
||||||
|
(arg, val) = argument_value_convert('torrent-set'
|
||||||
|
, argument, value, self.rpc_version)
|
||||||
|
args[arg] = val
|
||||||
|
|
||||||
|
if len(args) > 0:
|
||||||
|
self._request('torrent-set', args, ids, True, timeout=timeout)
|
||||||
|
else:
|
||||||
|
ValueError("No arguments to set")
|
||||||
|
|
||||||
|
def move(self, ids, location, timeout=None):
|
||||||
|
"""Move torrent data to the new location."""
|
||||||
|
self._rpc_version_warning(6)
|
||||||
|
args = {'location': location, 'move': True}
|
||||||
|
self._request('torrent-set-location', args, ids, True, timeout=timeout)
|
||||||
|
|
||||||
|
def locate(self, ids, location, timeout=None):
|
||||||
|
"""Locate torrent data at the location."""
|
||||||
|
self._rpc_version_warning(6)
|
||||||
|
args = {'location': location, 'move': False}
|
||||||
|
self._request('torrent-set-location', args, ids, True, timeout=timeout)
|
||||||
|
|
||||||
|
def get_session(self, timeout=None):
|
||||||
|
"""Get session parameters"""
|
||||||
|
self._request('session-get', timeout=timeout)
|
||||||
|
self._update_server_version()
|
||||||
|
return self.session
|
||||||
|
|
||||||
|
def set_session(self, timeout=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Set session parameters. The parameters are:
|
||||||
|
|
||||||
|
================================ ===== ================= ==========================================================================================================================
|
||||||
|
Argument RPC Replaced by Description
|
||||||
|
================================ ===== ================= ==========================================================================================================================
|
||||||
|
``alt_speed_down`` 5 - Alternate session download speed limit (in Kib/s).
|
||||||
|
``alt_speed_enabled`` 5 - Enables alternate global download speed limiter.
|
||||||
|
``alt_speed_time_begin`` 5 - Time when alternate speeds should be enabled. Minutes after midnight.
|
||||||
|
``alt_speed_time_day`` 5 - Enables alternate speeds scheduling these days.
|
||||||
|
``alt_speed_time_enabled`` 5 - Enables alternate speeds scheduling.
|
||||||
|
``alt_speed_time_end`` 5 - Time when alternate speeds should be disabled. Minutes after midnight.
|
||||||
|
``alt_speed_up`` 5 - Alternate session upload speed limit (in Kib/s).
|
||||||
|
``blocklist_enabled`` 5 - Enables the block list
|
||||||
|
``cache_size_mb`` 10 - The maximum size of the disk cache in MB
|
||||||
|
``dht_enabled`` 6 - Enables DHT.
|
||||||
|
``download_dir`` 1 - Set the session download directory.
|
||||||
|
``encryption`` 1 - Set the session encryption mode, one of ``required``, ``preferred`` or ``tolerated``.
|
||||||
|
``idle_seeding_limit`` 10 - The default seed inactivity limit in minutes.
|
||||||
|
``idle_seeding_limit_enabled`` 10 - Enables the default seed inactivity limit
|
||||||
|
``incomplete_dir`` 7 - The path to the directory of incomplete transfer data.
|
||||||
|
``incomplete_dir_enabled`` 7 - Enables the incomplete transfer data directory. Otherwise data for incomplete transfers are stored in the download target.
|
||||||
|
``lpd_enabled`` 9 - Enables local peer discovery for public torrents.
|
||||||
|
``peer_limit`` 1 - 5 peer-limit-global Maximum number of peers
|
||||||
|
``peer_limit_global`` 5 - Maximum number of peers
|
||||||
|
``peer_limit_per_torrent`` 5 - Maximum number of peers per transfer
|
||||||
|
``peer_port`` 5 - Peer port.
|
||||||
|
``peer_port_random_on_start`` 5 - Enables randomized peer port on start of Transmission.
|
||||||
|
``pex_allowed`` 1 - 5 pex-enabled Allowing PEX in public torrents.
|
||||||
|
``pex_enabled`` 5 - Allowing PEX in public torrents.
|
||||||
|
``port`` 1 - 5 peer-port Peer port.
|
||||||
|
``port_forwarding_enabled`` 1 - Enables port forwarding.
|
||||||
|
``rename_partial_files`` 8 - Appends ".part" to incomplete files
|
||||||
|
``script_torrent_done_enabled`` 9 - Whether or not to call the "done" script.
|
||||||
|
``script_torrent_done_filename`` 9 - Filename of the script to run when the transfer is done.
|
||||||
|
``seedRatioLimit`` 5 - Seed ratio limit. 1.0 means 1:1 download and upload ratio.
|
||||||
|
``seedRatioLimited`` 5 - Enables seed ration limit.
|
||||||
|
``speed_limit_down`` 1 - Download speed limit (in Kib/s).
|
||||||
|
``speed_limit_down_enabled`` 1 - Enables download speed limiting.
|
||||||
|
``speed_limit_up`` 1 - Upload speed limit (in Kib/s).
|
||||||
|
``speed_limit_up_enabled`` 1 - Enables upload speed limiting.
|
||||||
|
``start_added_torrents`` 9 - Added torrents will be started right away.
|
||||||
|
``trash_original_torrent_files`` 9 - The .torrent file of added torrents will be deleted.
|
||||||
|
================================ ===== ================= ==========================================================================================================================
|
||||||
|
|
||||||
|
.. NOTE::
|
||||||
|
transmissionrpc will try to automatically fix argument errors.
|
||||||
|
"""
|
||||||
|
args = {}
|
||||||
|
for key, value in kwargs.iteritems():
|
||||||
|
if key == 'encryption' and value not in ['required', 'preferred', 'tolerated']:
|
||||||
|
raise ValueError('Invalid encryption value')
|
||||||
|
argument = make_rpc_name(key)
|
||||||
|
(arg, val) = argument_value_convert('session-set'
|
||||||
|
, argument, value, self.rpc_version)
|
||||||
|
args[arg] = val
|
||||||
|
if len(args) > 0:
|
||||||
|
self._request('session-set', args, timeout=timeout)
|
||||||
|
|
||||||
|
def blocklist_update(self, timeout=None):
|
||||||
|
"""Update block list. Returns the size of the block list."""
|
||||||
|
self._rpc_version_warning(5)
|
||||||
|
result = self._request('blocklist-update', timeout=timeout)
|
||||||
|
if 'blocklist-size' in result:
|
||||||
|
return result['blocklist-size']
|
||||||
|
return None
|
||||||
|
|
||||||
|
def port_test(self, timeout=None):
|
||||||
|
"""
|
||||||
|
Tests to see if your incoming peer port is accessible from the
|
||||||
|
outside world.
|
||||||
|
"""
|
||||||
|
self._rpc_version_warning(5)
|
||||||
|
result = self._request('port-test', timeout=timeout)
|
||||||
|
if 'port-is-open' in result:
|
||||||
|
return result['port-is-open']
|
||||||
|
return None
|
||||||
|
|
||||||
|
def session_stats(self, timeout=None):
|
||||||
|
"""Get session statistics"""
|
||||||
|
self._request('session-stats', timeout=timeout)
|
||||||
|
return self.session
|
|
@ -1,17 +1,23 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# 2008-07, Erik Svensson <erik.public@gmail.com>
|
# Copyright (c) 2008-2010 Erik Svensson <erik.public@gmail.com>
|
||||||
|
# Licensed under the MIT license.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger('transmissionrpc')
|
LOGGER = logging.getLogger('transmissionrpc')
|
||||||
logger.setLevel(logging.ERROR)
|
LOGGER.setLevel(logging.ERROR)
|
||||||
|
|
||||||
def mirror_dict(d):
|
def mirror_dict(source):
|
||||||
d.update(dict((v, k) for k, v in d.iteritems()))
|
"""
|
||||||
return d
|
Creates a dictionary with all values as keys and all keys as values.
|
||||||
|
"""
|
||||||
|
source.update(dict((value, key) for key, value in source.iteritems()))
|
||||||
|
return source
|
||||||
|
|
||||||
DEFAULT_PORT = 9091
|
DEFAULT_PORT = 9091
|
||||||
|
|
||||||
|
DEFAULT_TIMEOUT = 30.0
|
||||||
|
|
||||||
TR_STATUS_CHECK_WAIT = (1<<0)
|
TR_STATUS_CHECK_WAIT = (1<<0)
|
||||||
TR_STATUS_CHECK = (1<<1)
|
TR_STATUS_CHECK = (1<<1)
|
||||||
TR_STATUS_DOWNLOAD = (1<<2)
|
TR_STATUS_DOWNLOAD = (1<<2)
|
||||||
|
@ -43,188 +49,238 @@ TR_RATIOLIMIT_UNLIMITED = 2 # override the global settings, seeding regardless o
|
||||||
RATIO_LIMIT = mirror_dict({
|
RATIO_LIMIT = mirror_dict({
|
||||||
'global' : TR_RATIOLIMIT_GLOBAL,
|
'global' : TR_RATIOLIMIT_GLOBAL,
|
||||||
'single' : TR_RATIOLIMIT_SINGLE,
|
'single' : TR_RATIOLIMIT_SINGLE,
|
||||||
'unlimeted' : TR_RATIOLIMIT_UNLIMITED
|
'unlimited' : TR_RATIOLIMIT_UNLIMITED
|
||||||
|
})
|
||||||
|
|
||||||
|
TR_IDLELIMIT_GLOBAL = 0 # follow the global settings
|
||||||
|
TR_IDLELIMIT_SINGLE = 1 # override the global settings, seeding until a certain idle time
|
||||||
|
TR_IDLELIMIT_UNLIMITED = 2 # override the global settings, seeding regardless of activity
|
||||||
|
|
||||||
|
IDLE_LIMIT = mirror_dict({
|
||||||
|
'global' : TR_RATIOLIMIT_GLOBAL,
|
||||||
|
'single' : TR_RATIOLIMIT_SINGLE,
|
||||||
|
'unlimited' : TR_RATIOLIMIT_UNLIMITED
|
||||||
})
|
})
|
||||||
|
|
||||||
# A note on argument maps
|
# A note on argument maps
|
||||||
# These maps are used to verify *-set methods. The information is structured in
|
# These maps are used to verify *-set methods. The information is structured in
|
||||||
# a tree.
|
# a tree.
|
||||||
# set +- <argument1> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>]
|
# set +- <argument1> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>, <description>]
|
||||||
# | +- <argument2> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>]
|
# | +- <argument2> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>, <description>]
|
||||||
# |
|
# |
|
||||||
# get +- <argument1> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>]
|
# get +- <argument1> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>, <description>]
|
||||||
# +- <argument2> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>]
|
# +- <argument2> - [<type>, <added version>, <removed version>, <previous argument name>, <next argument name>, <description>]
|
||||||
|
|
||||||
# Arguments for torrent methods
|
# Arguments for torrent methods
|
||||||
TORRENT_ARGS = {
|
TORRENT_ARGS = {
|
||||||
'get' : {
|
'get' : {
|
||||||
'activityDate': ('number', 1, None, None, None),
|
'activityDate': ('number', 1, None, None, None, ''),
|
||||||
'addedDate': ('number', 1, None, None, None),
|
'addedDate': ('number', 1, None, None, None, ''),
|
||||||
'announceResponse': ('string', 1, None, None, None),
|
'announceResponse': ('string', 1, 7, None, None, ''),
|
||||||
'announceURL': ('string', 1, None, None, None),
|
'announceURL': ('string', 1, 7, None, None, ''),
|
||||||
'bandwidthPriority': ('number', 5, None, None, None),
|
'bandwidthPriority': ('number', 5, None, None, None, ''),
|
||||||
'comment': ('string', 1, None, None, None),
|
'comment': ('string', 1, None, None, None, ''),
|
||||||
'corruptEver': ('number', 1, None, None, None),
|
'corruptEver': ('number', 1, None, None, None, ''),
|
||||||
'creator': ('string', 1, None, None, None),
|
'creator': ('string', 1, None, None, None, ''),
|
||||||
'dateCreated': ('number', 1, None, None, None),
|
'dateCreated': ('number', 1, None, None, None, ''),
|
||||||
'desiredAvailable': ('number', 1, None, None, None),
|
'desiredAvailable': ('number', 1, None, None, None, ''),
|
||||||
'doneDate': ('number', 1, None, None, None),
|
'doneDate': ('number', 1, None, None, None, ''),
|
||||||
'downloadDir': ('string', 4, None, None, None),
|
'downloadDir': ('string', 4, None, None, None, ''),
|
||||||
'downloadedEver': ('number', 1, None, None, None),
|
'downloadedEver': ('number', 1, None, None, None, ''),
|
||||||
'downloaders': ('number', 4, None, None, None),
|
'downloaders': ('number', 4, 7, None, None, ''),
|
||||||
'downloadLimit': ('number', 1, None, None, None),
|
'downloadLimit': ('number', 1, None, None, None, ''),
|
||||||
'downloadLimited': ('boolean', 5, None, None, None),
|
'downloadLimited': ('boolean', 5, None, None, None, ''),
|
||||||
'downloadLimitMode': ('number', 1, 5, None, None),
|
'downloadLimitMode': ('number', 1, 5, None, None, ''),
|
||||||
'error': ('number', 1, None, None, None),
|
'error': ('number', 1, None, None, None, ''),
|
||||||
'errorString': ('number', 1, None, None, None),
|
'errorString': ('number', 1, None, None, None, ''),
|
||||||
'eta': ('number', 1, None, None, None),
|
'eta': ('number', 1, None, None, None, ''),
|
||||||
'files': ('array', 1, None, None, None),
|
'files': ('array', 1, None, None, None, ''),
|
||||||
'fileStats': ('array', 5, None, None, None),
|
'fileStats': ('array', 5, None, None, None, ''),
|
||||||
'hashString': ('string', 1, None, None, None),
|
'hashString': ('string', 1, None, None, None, ''),
|
||||||
'haveUnchecked': ('number', 1, None, None, None),
|
'haveUnchecked': ('number', 1, None, None, None, ''),
|
||||||
'haveValid': ('number', 1, None, None, None),
|
'haveValid': ('number', 1, None, None, None, ''),
|
||||||
'honorsSessionLimits': ('boolean', 5, None, None, None),
|
'honorsSessionLimits': ('boolean', 5, None, None, None, ''),
|
||||||
'id': ('number', 1, None, None, None),
|
'id': ('number', 1, None, None, None, ''),
|
||||||
'isPrivate': ('boolean', 1, None, None, None),
|
'isFinished': ('boolean', 9, None, None, None, ''),
|
||||||
'lastAnnounceTime': ('number', 1, None, None, None),
|
'isPrivate': ('boolean', 1, None, None, None, ''),
|
||||||
'lastScrapeTime': ('number', 1, None, None, None),
|
'lastAnnounceTime': ('number', 1, 7, None, None, ''),
|
||||||
'leechers': ('number', 1, None, None, None),
|
'lastScrapeTime': ('number', 1, 7, None, None, ''),
|
||||||
'leftUntilDone': ('number', 1, None, None, None),
|
'leechers': ('number', 1, 7, None, None, ''),
|
||||||
'manualAnnounceTime': ('number', 1, None, None, None),
|
'leftUntilDone': ('number', 1, None, None, None, ''),
|
||||||
'maxConnectedPeers': ('number', 1, None, None, None),
|
'magnetLink': ('string', 7, None, None, None, ''),
|
||||||
'name': ('string', 1, None, None, None),
|
'manualAnnounceTime': ('number', 1, None, None, None, ''),
|
||||||
'nextAnnounceTime': ('number', 1, None, None, None),
|
'maxConnectedPeers': ('number', 1, None, None, None, ''),
|
||||||
'nextScrapeTime': ('number', 1, None, None, None),
|
'metadataPercentComplete': ('number', 7, None, None, None, ''),
|
||||||
'peer-limit': ('number', 5, None, None, None),
|
'name': ('string', 1, None, None, None, ''),
|
||||||
'peers': ('array', 2, None, None, None),
|
'nextAnnounceTime': ('number', 1, 7, None, None, ''),
|
||||||
'peersConnected': ('number', 1, None, None, None),
|
'nextScrapeTime': ('number', 1, 7, None, None, ''),
|
||||||
'peersFrom': ('object', 1, None, None, None),
|
'peer-limit': ('number', 5, None, None, None, ''),
|
||||||
'peersGettingFromUs': ('number', 1, None, None, None),
|
'peers': ('array', 2, None, None, None, ''),
|
||||||
'peersKnown': ('number', 1, None, None, None),
|
'peersConnected': ('number', 1, None, None, None, ''),
|
||||||
'peersSendingToUs': ('number', 1, None, None, None),
|
'peersFrom': ('object', 1, None, None, None, ''),
|
||||||
'percentDone': ('double', 5, None, None, None),
|
'peersGettingFromUs': ('number', 1, None, None, None, ''),
|
||||||
'pieces': ('string', 5, None, None, None),
|
'peersKnown': ('number', 1, None, None, None, ''),
|
||||||
'pieceCount': ('number', 1, None, None, None),
|
'peersSendingToUs': ('number', 1, None, None, None, ''),
|
||||||
'pieceSize': ('number', 1, None, None, None),
|
'percentDone': ('double', 5, None, None, None, ''),
|
||||||
'priorities': ('array', 1, None, None, None),
|
'pieces': ('string', 5, None, None, None, ''),
|
||||||
'rateDownload': ('number', 1, None, None, None),
|
'pieceCount': ('number', 1, None, None, None, ''),
|
||||||
'rateUpload': ('number', 1, None, None, None),
|
'pieceSize': ('number', 1, None, None, None, ''),
|
||||||
'recheckProgress': ('double', 1, None, None, None),
|
'priorities': ('array', 1, None, None, None, ''),
|
||||||
'scrapeResponse': ('string', 1, None, None, None),
|
'rateDownload': ('number', 1, None, None, None, ''),
|
||||||
'scrapeURL': ('string', 1, None, None, None),
|
'rateUpload': ('number', 1, None, None, None, ''),
|
||||||
'seeders': ('number', 1, None, None, None),
|
'recheckProgress': ('double', 1, None, None, None, ''),
|
||||||
'seedRatioLimit': ('double', 5, None, None, None),
|
'scrapeResponse': ('string', 1, 7, None, None, ''),
|
||||||
'seedRatioMode': ('number', 5, None, None, None),
|
'scrapeURL': ('string', 1, 7, None, None, ''),
|
||||||
'sizeWhenDone': ('number', 1, None, None, None),
|
'seeders': ('number', 1, 7, None, None, ''),
|
||||||
'startDate': ('number', 1, None, None, None),
|
'seedIdleLimit': ('number', 10, None, None, None, ''),
|
||||||
'status': ('number', 1, None, None, None),
|
'seedIdleMode': ('number', 10, None, None, None, ''),
|
||||||
'swarmSpeed': ('number', 1, None, None, None),
|
'seedRatioLimit': ('double', 5, None, None, None, ''),
|
||||||
'timesCompleted': ('number', 1, None, None, None),
|
'seedRatioMode': ('number', 5, None, None, None, ''),
|
||||||
'trackers': ('array', 1, None, None, None),
|
'sizeWhenDone': ('number', 1, None, None, None, ''),
|
||||||
'totalSize': ('number', 1, None, None, None),
|
'startDate': ('number', 1, None, None, None, ''),
|
||||||
'torrentFile': ('string', 5, None, None, None),
|
'status': ('number', 1, None, None, None, ''),
|
||||||
'uploadedEver': ('number', 1, None, None, None),
|
'swarmSpeed': ('number', 1, 7, None, None, ''),
|
||||||
'uploadLimit': ('number', 1, None, None, None),
|
'timesCompleted': ('number', 1, 7, None, None, ''),
|
||||||
'uploadLimitMode': ('number', 1, 5, None, None),
|
'trackers': ('array', 1, None, None, None, ''),
|
||||||
'uploadLimited': ('boolean', 5, None, None, None),
|
'trackerStats': ('object', 7, None, None, None, ''),
|
||||||
'uploadRatio': ('double', 1, None, None, None),
|
'totalSize': ('number', 1, None, None, None, ''),
|
||||||
'wanted': ('array', 1, None, None, None),
|
'torrentFile': ('string', 5, None, None, None, ''),
|
||||||
'webseeds': ('array', 1, None, None, None),
|
'uploadedEver': ('number', 1, None, None, None, ''),
|
||||||
'webseedsSendingToUs': ('number', 1, None, None, None),
|
'uploadLimit': ('number', 1, None, None, None, ''),
|
||||||
|
'uploadLimitMode': ('number', 1, 5, None, None, ''),
|
||||||
|
'uploadLimited': ('boolean', 5, None, None, None, ''),
|
||||||
|
'uploadRatio': ('double', 1, None, None, None, ''),
|
||||||
|
'wanted': ('array', 1, None, None, None, ''),
|
||||||
|
'webseeds': ('array', 1, None, None, None, ''),
|
||||||
|
'webseedsSendingToUs': ('number', 1, None, None, None, ''),
|
||||||
},
|
},
|
||||||
'set': {
|
'set': {
|
||||||
'bandwidthPriority': ('number', 5, None, None, None),
|
'bandwidthPriority': ('number', 5, None, None, None, 'Priority for this transfer.'),
|
||||||
'downloadLimit': ('number', 5, None, 'speed-limit-down', None),
|
'downloadLimit': ('number', 5, None, 'speed-limit-down', None, 'Set the speed limit for download in Kib/s.'),
|
||||||
'downloadLimited': ('boolean', 5, None, 'speed-limit-down-enabled', None),
|
'downloadLimited': ('boolean', 5, None, 'speed-limit-down-enabled', None, 'Enable download speed limiter.'),
|
||||||
'files-wanted': ('array', 1, None, None, None),
|
'files-wanted': ('array', 1, None, None, None, "A list of file id's that should be downloaded."),
|
||||||
'files-unwanted': ('array', 1, None, None, None),
|
'files-unwanted': ('array', 1, None, None, None, "A list of file id's that shouldn't be downloaded."),
|
||||||
'honorsSessionLimits': ('boolean', 5, None, None, None),
|
'honorsSessionLimits': ('boolean', 5, None, None, None, "Enables or disables the transfer to honour the upload limit set in the session."),
|
||||||
'ids': ('array', 1, None, None, None),
|
'ids': ('array', 1, None, None, None, 'Local download location.'),
|
||||||
'peer-limit': ('number', 1, None, None, None),
|
'peer-limit': ('number', 1, None, None, None, 'The peer limit for the torrents.'),
|
||||||
'priority-high': ('array', 1, None, None, None),
|
'priority-high': ('array', 1, None, None, None, "A list of file id's that should have high priority."),
|
||||||
'priority-low': ('array', 1, None, None, None),
|
'priority-low': ('array', 1, None, None, None, "A list of file id's that should have normal priority."),
|
||||||
'priority-normal': ('array', 1, None, None, None),
|
'priority-normal': ('array', 1, None, None, None, "A list of file id's that should have low priority."),
|
||||||
'seedRatioLimit': ('double', 5, None, None, None),
|
'seedIdleLimit': ('number', 10, None, None, None, 'Seed inactivity limit in minutes.'),
|
||||||
'seedRatioMode': ('number', 5, None, None, None),
|
'seedIdleMode': ('number', 10, None, None, None, 'Seed inactivity mode. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.'),
|
||||||
'speed-limit-down': ('number', 1, 5, None, 'downloadLimit'),
|
'seedRatioLimit': ('double', 5, None, None, None, 'Seeding ratio.'),
|
||||||
'speed-limit-down-enabled': ('boolean', 1, 5, None, 'downloadLimited'),
|
'seedRatioMode': ('number', 5, None, None, None, 'Which ratio to use. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.'),
|
||||||
'speed-limit-up': ('number', 1, 5, None, 'uploadLimit'),
|
'speed-limit-down': ('number', 1, 5, None, 'downloadLimit', 'Set the speed limit for download in Kib/s.'),
|
||||||
'speed-limit-up-enabled': ('boolean', 1, 5, None, 'uploadLimited'),
|
'speed-limit-down-enabled': ('boolean', 1, 5, None, 'downloadLimited', 'Enable download speed limiter.'),
|
||||||
'uploadLimit': ('number', 5, None, 'speed-limit-up', None),
|
'speed-limit-up': ('number', 1, 5, None, 'uploadLimit', 'Set the speed limit for upload in Kib/s.'),
|
||||||
'uploadLimited': ('boolean', 5, None, 'speed-limit-up-enabled', None),
|
'speed-limit-up-enabled': ('boolean', 1, 5, None, 'uploadLimited', 'Enable upload speed limiter.'),
|
||||||
|
'trackerAdd': ('array', 10, None, None, None, 'Array of string with announce URLs to add.'),
|
||||||
|
'trackerRemove': ('array', 10, None, None, None, 'Array of ids of trackers to remove.'),
|
||||||
|
'trackerReplace': ('array', 10, None, None, None, 'Array of (id, url) tuples where the announce URL should be replaced.'),
|
||||||
|
'uploadLimit': ('number', 5, None, 'speed-limit-up', None, 'Set the speed limit for upload in Kib/s.'),
|
||||||
|
'uploadLimited': ('boolean', 5, None, 'speed-limit-up-enabled', None, 'Enable upload speed limiter.'),
|
||||||
},
|
},
|
||||||
'add': {
|
'add': {
|
||||||
'download-dir': ('string', 1, None, None, None),
|
'bandwidthPriority': ('number', 8, None, None, None, 'Priority for this transfer.'),
|
||||||
'filename': ('string', 1, None, None, None),
|
'download-dir': ('string', 1, None, None, None, 'The directory where the downloaded contents will be saved in.'),
|
||||||
'files-wanted': ('array', 1, None, None, None),
|
'filename': ('string', 1, None, None, None, "A filepath or URL to a torrent file or a magnet link."),
|
||||||
'files-unwanted': ('array', 1, None, None, None),
|
'files-wanted': ('array', 1, None, None, None, "A list of file id's that should be downloaded."),
|
||||||
'metainfo': ('string', 1, None, None, None),
|
'files-unwanted': ('array', 1, None, None, None, "A list of file id's that shouldn't be downloaded."),
|
||||||
'paused': ('boolean', 1, None, None, None),
|
'metainfo': ('string', 1, None, None, None, 'The content of a torrent file, base64 encoded.'),
|
||||||
'peer-limit': ('number', 1, None, None, None),
|
'paused': ('boolean', 1, None, None, None, 'If True, does not start the transfer when added.'),
|
||||||
'priority-high': ('array', 1, None, None, None),
|
'peer-limit': ('number', 1, None, None, None, 'Maximum number of peers allowed.'),
|
||||||
'priority-low': ('array', 1, None, None, None),
|
'priority-high': ('array', 1, None, None, None, "A list of file id's that should have high priority."),
|
||||||
'priority-normal': ('array', 1, None, None, None),
|
'priority-low': ('array', 1, None, None, None, "A list of file id's that should have low priority."),
|
||||||
|
'priority-normal': ('array', 1, None, None, None, "A list of file id's that should have normal priority."),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Arguments for session methods
|
# Arguments for session methods
|
||||||
SESSION_ARGS = {
|
SESSION_ARGS = {
|
||||||
'get': {
|
'get': {
|
||||||
"alt-speed-down": ('number', 5, None, None, None),
|
"alt-speed-down": ('number', 5, None, None, None, ''),
|
||||||
"alt-speed-enabled": ('boolean', 5, None, None, None),
|
"alt-speed-enabled": ('boolean', 5, None, None, None, ''),
|
||||||
"alt-speed-time-begin": ('number', 5, None, None, None),
|
"alt-speed-time-begin": ('number', 5, None, None, None, ''),
|
||||||
"alt-speed-time-enabled": ('boolean', 5, None, None, None),
|
"alt-speed-time-enabled": ('boolean', 5, None, None, None, ''),
|
||||||
"alt-speed-time-end": ('number', 5, None, None, None),
|
"alt-speed-time-end": ('number', 5, None, None, None, ''),
|
||||||
"alt-speed-time-day": ('number', 5, None, None, None),
|
"alt-speed-time-day": ('number', 5, None, None, None, ''),
|
||||||
"alt-speed-up": ('number', 5, None, None, None),
|
"alt-speed-up": ('number', 5, None, None, None, ''),
|
||||||
"blocklist-enabled": ('boolean', 5, None, None, None),
|
"blocklist-enabled": ('boolean', 5, None, None, None, ''),
|
||||||
"blocklist-size": ('number', 5, None, None, None),
|
"blocklist-size": ('number', 5, None, None, None, ''),
|
||||||
"encryption": ('string', 1, None, None, None),
|
"blocklist-url": ('string', 11, None, None, None, ''),
|
||||||
"download-dir": ('string', 1, None, None, None),
|
"cache-size-mb": ('number', 10, None, None, None, ''),
|
||||||
"peer-limit": ('number', 1, 5, None, None),
|
"config-dir": ('string', 8, None, None, None, ''),
|
||||||
"peer-limit-global": ('number', 5, None, None, None),
|
"dht-enabled": ('boolean', 6, None, None, None, ''),
|
||||||
"peer-limit-per-torrent": ('number', 5, None, None, None),
|
"download-dir": ('string', 1, None, None, None, ''),
|
||||||
"pex-allowed": ('boolean', 1, 5, None, None),
|
"encryption": ('string', 1, None, None, None, ''),
|
||||||
"pex-enabled": ('boolean', 5, None, None, None),
|
"idle-seeding-limit": ('number', 10, None, None, None, ''),
|
||||||
"port": ('number', 1, 5, None, None),
|
"idle-seeding-limit-enabled": ('boolean', 10, None, None, None, ''),
|
||||||
"peer-port": ('number', 5, None, None, None),
|
"incomplete-dir": ('string', 7, None, None, None, ''),
|
||||||
"peer-port-random-on-start": ('boolean', 5, None, None, None),
|
"incomplete-dir-enabled": ('boolean', 7, None, None, None, ''),
|
||||||
"port-forwarding-enabled": ('boolean', 1, None, None, None),
|
"lpd-enabled": ('boolean', 9, None, None, None, ''),
|
||||||
"rpc-version": ('number', 4, None, None, None),
|
"peer-limit": ('number', 1, 5, None, None, ''),
|
||||||
"rpc-version-minimum": ('number', 4, None, None, None),
|
"peer-limit-global": ('number', 5, None, None, None, ''),
|
||||||
"seedRatioLimit": ('double', 5, None, None, None),
|
"peer-limit-per-torrent": ('number', 5, None, None, None, ''),
|
||||||
"seedRatioLimited": ('boolean', 5, None, None, None),
|
"pex-allowed": ('boolean', 1, 5, None, None, ''),
|
||||||
"speed-limit-down": ('number', 1, None, None, None),
|
"pex-enabled": ('boolean', 5, None, None, None, ''),
|
||||||
"speed-limit-down-enabled": ('boolean', 1, None, None, None),
|
"port": ('number', 1, 5, None, None, ''),
|
||||||
"speed-limit-up": ('number', 1, None, None, None),
|
"peer-port": ('number', 5, None, None, None, ''),
|
||||||
"speed-limit-up-enabled": ('boolean', 1, None, None, None),
|
"peer-port-random-on-start": ('boolean', 5, None, None, None, ''),
|
||||||
"version": ('string', 3, None, None, None),
|
"port-forwarding-enabled": ('boolean', 1, None, None, None, ''),
|
||||||
|
"rename-partial-files": ('boolean', 8, None, None, None, ''),
|
||||||
|
"rpc-version": ('number', 4, None, None, None, ''),
|
||||||
|
"rpc-version-minimum": ('number', 4, None, None, None, ''),
|
||||||
|
"script-torrent-done-enabled": ('boolean', 9, None, None, None, ''),
|
||||||
|
"script-torrent-done-filename": ('string', 9, None, None, None, ''),
|
||||||
|
"seedRatioLimit": ('double', 5, None, None, None, ''),
|
||||||
|
"seedRatioLimited": ('boolean', 5, None, None, None, ''),
|
||||||
|
"speed-limit-down": ('number', 1, None, None, None, ''),
|
||||||
|
"speed-limit-down-enabled": ('boolean', 1, None, None, None, ''),
|
||||||
|
"speed-limit-up": ('number', 1, None, None, None, ''),
|
||||||
|
"speed-limit-up-enabled": ('boolean', 1, None, None, None, ''),
|
||||||
|
"start-added-torrents": ('boolean', 9, None, None, None, ''),
|
||||||
|
"trash-original-torrent-files": ('boolean', 9, None, None, None, ''),
|
||||||
|
'units': ('object', 10, None, None, None, ''),
|
||||||
|
"version": ('string', 3, None, None, None, ''),
|
||||||
},
|
},
|
||||||
'set': {
|
'set': {
|
||||||
"alt-speed-down": ('number', 5, None, None, None),
|
"alt-speed-down": ('number', 5, None, None, None, 'Alternate session download speed limit (in Kib/s).'),
|
||||||
"alt-speed-enabled": ('boolean', 5, None, None, None),
|
"alt-speed-enabled": ('boolean', 5, None, None, None, 'Enables alternate global download speed limiter.'),
|
||||||
"alt-speed-time-begin": ('number', 5, None, None, None),
|
"alt-speed-time-begin": ('number', 5, None, None, None, 'Time when alternate speeds should be enabled. Minutes after midnight.'),
|
||||||
"alt-speed-time-enabled": ('boolean', 5, None, None, None),
|
"alt-speed-time-enabled": ('boolean', 5, None, None, None, 'Enables alternate speeds scheduling.'),
|
||||||
"alt-speed-time-end": ('number', 5, None, None, None),
|
"alt-speed-time-end": ('number', 5, None, None, None, 'Time when alternate speeds should be disabled. Minutes after midnight.'),
|
||||||
"alt-speed-time-day": ('number', 5, None, None, None),
|
"alt-speed-time-day": ('number', 5, None, None, None, 'Enables alternate speeds scheduling these days.'),
|
||||||
"alt-speed-up": ('number', 5, None, None, None),
|
"alt-speed-up": ('number', 5, None, None, None, 'Alternate session upload speed limit (in Kib/s).'),
|
||||||
"blocklist-enabled": ('boolean', 5, None, None, None),
|
"blocklist-enabled": ('boolean', 5, None, None, None, 'Enables the block list'),
|
||||||
"encryption": ('string', 1, None, None, None),
|
"blocklist-url": ('string', 11, None, None, None, 'Location of the blocklist. Updated with blocklist-update.'),
|
||||||
"download-dir": ('string', 1, None, None, None),
|
"cache-size-mb": ('number', 10, None, None, None, 'The maximum size of the disk cache in MB'),
|
||||||
"peer-limit": ('number', 1, 5, None, 'peer-limit-global'),
|
"dht-enabled": ('boolean', 6, None, None, None, 'Enables DHT.'),
|
||||||
"peer-limit-global": ('number', 5, None, 'peer-limit', None),
|
"download-dir": ('string', 1, None, None, None, 'Set the session download directory.'),
|
||||||
"peer-limit-per-torrent": ('number', 5, None, None, None),
|
"encryption": ('string', 1, None, None, None, 'Set the session encryption mode, one of ``required``, ``preferred`` or ``tolerated``.'),
|
||||||
"pex-allowed": ('boolean', 1, 5, None, 'pex-enabled'),
|
"idle-seeding-limit": ('number', 10, None, None, None, 'The default seed inactivity limit in minutes.'),
|
||||||
"pex-enabled": ('boolean', 5, None, 'pex-allowed', None),
|
"idle-seeding-limit-enabled": ('boolean', 10, None, None, None, 'Enables the default seed inactivity limit'),
|
||||||
"port": ('number', 1, 5, None, 'peer-port'),
|
"incomplete-dir": ('string', 7, None, None, None, 'The path to the directory of incomplete transfer data.'),
|
||||||
"peer-port": ('number', 5, None, 'port', None),
|
"incomplete-dir-enabled": ('boolean', 7, None, None, None, 'Enables the incomplete transfer data directory. Otherwise data for incomplete transfers are stored in the download target.'),
|
||||||
"peer-port-random-on-start": ('boolean', 5, None, None, None),
|
"lpd-enabled": ('boolean', 9, None, None, None, 'Enables local peer discovery for public torrents.'),
|
||||||
"port-forwarding-enabled": ('boolean', 1, None, None, None),
|
"peer-limit": ('number', 1, 5, None, 'peer-limit-global', 'Maximum number of peers'),
|
||||||
"seedRatioLimit": ('double', 5, None, None, None),
|
"peer-limit-global": ('number', 5, None, 'peer-limit', None, 'Maximum number of peers'),
|
||||||
"seedRatioLimited": ('boolean', 5, None, None, None),
|
"peer-limit-per-torrent": ('number', 5, None, None, None, 'Maximum number of peers per transfer'),
|
||||||
"speed-limit-down": ('number', 1, None, None, None),
|
"pex-allowed": ('boolean', 1, 5, None, 'pex-enabled', 'Allowing PEX in public torrents.'),
|
||||||
"speed-limit-down-enabled": ('boolean', 1, None, None, None),
|
"pex-enabled": ('boolean', 5, None, 'pex-allowed', None, 'Allowing PEX in public torrents.'),
|
||||||
"speed-limit-up": ('number', 1, None, None, None),
|
"port": ('number', 1, 5, None, 'peer-port', 'Peer port.'),
|
||||||
"speed-limit-up-enabled": ('boolean', 1, None, None, None),
|
"peer-port": ('number', 5, None, 'port', None, 'Peer port.'),
|
||||||
|
"peer-port-random-on-start": ('boolean', 5, None, None, None, 'Enables randomized peer port on start of Transmission.'),
|
||||||
|
"port-forwarding-enabled": ('boolean', 1, None, None, None, 'Enables port forwarding.'),
|
||||||
|
"rename-partial-files": ('boolean', 8, None, None, None, 'Appends ".part" to incomplete files'),
|
||||||
|
"script-torrent-done-enabled": ('boolean', 9, None, None, None, 'Whether or not to call the "done" script.'),
|
||||||
|
"script-torrent-done-filename": ('string', 9, None, None, None, 'Filename of the script to run when the transfer is done.'),
|
||||||
|
"seedRatioLimit": ('double', 5, None, None, None, 'Seed ratio limit. 1.0 means 1:1 download and upload ratio.'),
|
||||||
|
"seedRatioLimited": ('boolean', 5, None, None, None, 'Enables seed ration limit.'),
|
||||||
|
"speed-limit-down": ('number', 1, None, None, None, 'Download speed limit (in Kib/s).'),
|
||||||
|
"speed-limit-down-enabled": ('boolean', 1, None, None, None, 'Enables download speed limiting.'),
|
||||||
|
"speed-limit-up": ('number', 1, None, None, None, 'Upload speed limit (in Kib/s).'),
|
||||||
|
"speed-limit-up-enabled": ('boolean', 1, None, None, None, 'Enables upload speed limiting.'),
|
||||||
|
"start-added-torrents": ('boolean', 9, None, None, None, 'Added torrents will be started right away.'),
|
||||||
|
"trash-original-torrent-files": ('boolean', 9, None, None, None, 'The .torrent file of added torrents will be deleted.'),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
52
resources/lib/transmissionrpc/error.py
Executable file
52
resources/lib/transmissionrpc/error.py
Executable file
|
@ -0,0 +1,52 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2008-2010 Erik Svensson <erik.public@gmail.com>
|
||||||
|
# Licensed under the MIT license.
|
||||||
|
|
||||||
|
class TransmissionError(Exception):
|
||||||
|
"""
|
||||||
|
This exception is raised when there has occured an error related to
|
||||||
|
communication with Transmission. It is a subclass of Exception.
|
||||||
|
"""
|
||||||
|
def __init__(self, message='', original=None):
|
||||||
|
Exception.__init__(self)
|
||||||
|
self.message = message
|
||||||
|
self.original = original
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.original:
|
||||||
|
original_name = type(self.original).__name__
|
||||||
|
return '%s Original exception: %s, "%s"' % (self.message, original_name, str(self.original))
|
||||||
|
else:
|
||||||
|
return self.message
|
||||||
|
|
||||||
|
class HTTPHandlerError(Exception):
|
||||||
|
"""
|
||||||
|
This exception is raised when there has occured an error related to
|
||||||
|
the HTTP handler. It is a subclass of Exception.
|
||||||
|
"""
|
||||||
|
def __init__(self, httpurl=None, httpcode=None, httpmsg=None, httpheaders=None, httpdata=None):
|
||||||
|
Exception.__init__(self)
|
||||||
|
self.url = ''
|
||||||
|
self.code = 600
|
||||||
|
self.message = ''
|
||||||
|
self.headers = {}
|
||||||
|
self.data = ''
|
||||||
|
if isinstance(httpurl, (str, unicode)):
|
||||||
|
self.url = httpurl
|
||||||
|
if isinstance(httpcode, (int, long)):
|
||||||
|
self.code = httpcode
|
||||||
|
if isinstance(httpmsg, (str, unicode)):
|
||||||
|
self.message = httpmsg
|
||||||
|
if isinstance(httpheaders, (dict)):
|
||||||
|
self.headers = httpheaders
|
||||||
|
if isinstance(httpdata, (str, unicode)):
|
||||||
|
self.data = httpdata
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<HTTPHandlerError %d, %s>' % (self.code, self.message)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '<HTTPHandlerError %d, %s>' % (self.code, self.message)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return u'<HTTPHandlerError %d, %s>' % (self.code, self.message)
|
72
resources/lib/transmissionrpc/httphandler.py
Executable file
72
resources/lib/transmissionrpc/httphandler.py
Executable file
|
@ -0,0 +1,72 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2010 Erik Svensson <erik.public@gmail.com>
|
||||||
|
# Licensed under the MIT license.
|
||||||
|
|
||||||
|
import sys, httplib, urllib2
|
||||||
|
|
||||||
|
from transmissionrpc.error import HTTPHandlerError
|
||||||
|
|
||||||
|
class HTTPHandler(object):
|
||||||
|
"""
|
||||||
|
Prototype for HTTP handling.
|
||||||
|
"""
|
||||||
|
def set_authentication(self, uri, login, password):
|
||||||
|
"""
|
||||||
|
Transmission use basic authentication in earlier versions and digest
|
||||||
|
authentication in later versions.
|
||||||
|
|
||||||
|
* uri, the authentication realm URI.
|
||||||
|
* login, the authentication login.
|
||||||
|
* password, the authentication password.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("Bad HTTPHandler, failed to implement set_authentication.")
|
||||||
|
|
||||||
|
def request(self, url, query, headers, timeout):
|
||||||
|
"""
|
||||||
|
Implement a HTTP POST request here.
|
||||||
|
|
||||||
|
* url, The URL to request.
|
||||||
|
* query, The query data to send. This is a JSON data string.
|
||||||
|
* headers, a dictionary of headers to send.
|
||||||
|
* timeout, requested request timeout in seconds.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError("Bad HTTPHandler, failed to implement request.")
|
||||||
|
|
||||||
|
class DefaultHTTPHandler(HTTPHandler):
|
||||||
|
"""
|
||||||
|
The default HTTP handler provided with transmissionrpc.
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
HTTPHandler.__init__(self)
|
||||||
|
|
||||||
|
def set_authentication(self, uri, login, password):
|
||||||
|
password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
||||||
|
password_manager.add_password(realm=None, uri=uri, user=login, passwd=password)
|
||||||
|
opener = urllib2.build_opener(
|
||||||
|
urllib2.HTTPBasicAuthHandler(password_manager)
|
||||||
|
, urllib2.HTTPDigestAuthHandler(password_manager)
|
||||||
|
)
|
||||||
|
urllib2.install_opener(opener)
|
||||||
|
|
||||||
|
def request(self, url, query, headers, timeout):
|
||||||
|
request = urllib2.Request(url, query, headers)
|
||||||
|
try:
|
||||||
|
if (sys.version_info[0] == 2 and sys.version_info[1] > 5) or sys.version_info[0] > 2:
|
||||||
|
response = urllib2.urlopen(request, timeout=timeout)
|
||||||
|
else:
|
||||||
|
response = urllib2.urlopen(request)
|
||||||
|
except urllib2.HTTPError, error:
|
||||||
|
if error.fp == None:
|
||||||
|
raise HTTPHandlerError(error.filename, error.code, error.msg, dict(error.hdrs))
|
||||||
|
else:
|
||||||
|
raise HTTPHandlerError(error.filename, error.code, error.msg, dict(error.hdrs), error.read())
|
||||||
|
except urllib2.URLError, error:
|
||||||
|
# urllib2.URLError documentation is absymal!
|
||||||
|
# Try to get the tuple arguments of URLError
|
||||||
|
if hasattr(error.reason, 'args') and isinstance(error.reason.args, tuple) and len(error.reason.args) == 2:
|
||||||
|
raise HTTPHandlerError(httpcode=error.reason.args[0], httpmsg=error.reason.args[1])
|
||||||
|
else:
|
||||||
|
raise HTTPHandlerError(httpmsg='urllib2.URLError: %s' % (error.reason))
|
||||||
|
except httplib.BadStatusLine, error:
|
||||||
|
raise HTTPHandlerError(httpmsg='httplib.BadStatusLine: %s' % (error.line))
|
||||||
|
return response.read()
|
44
resources/lib/transmissionrpc/session.py
Executable file
44
resources/lib/transmissionrpc/session.py
Executable file
|
@ -0,0 +1,44 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2008-2010 Erik Svensson <erik.public@gmail.com>
|
||||||
|
# Licensed under the MIT license.
|
||||||
|
|
||||||
|
class Session(object):
|
||||||
|
"""
|
||||||
|
Session is a class holding the session data for a Transmission daemon.
|
||||||
|
|
||||||
|
Access the session field can be done through attributes.
|
||||||
|
The attributes available are the same as the session arguments in the
|
||||||
|
Transmission RPC specification, but with underscore instead of hypen.
|
||||||
|
``download-dir`` -> ``download_dir``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, fields=None):
|
||||||
|
self.fields = {}
|
||||||
|
if fields != None:
|
||||||
|
self.update(fields)
|
||||||
|
|
||||||
|
def update(self, other):
|
||||||
|
"""Update the session data from a session arguments dictinary"""
|
||||||
|
|
||||||
|
fields = None
|
||||||
|
if isinstance(other, dict):
|
||||||
|
fields = other
|
||||||
|
elif isinstance(other, Session):
|
||||||
|
fields = other.fields
|
||||||
|
else:
|
||||||
|
raise ValueError('Cannot update with supplied data')
|
||||||
|
|
||||||
|
for key, value in fields.iteritems():
|
||||||
|
self.fields[key.replace('-', '_')] = value
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
try:
|
||||||
|
return self.fields[name]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError('No attribute %s' % name)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
text = ''
|
||||||
|
for key in sorted(self.fields.keys()):
|
||||||
|
text += "% 32s: %s\n" % (key[-32:], self.fields[key])
|
||||||
|
return text
|
190
resources/lib/transmissionrpc/torrent.py
Executable file
190
resources/lib/transmissionrpc/torrent.py
Executable file
|
@ -0,0 +1,190 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2008-2010 Erik Svensson <erik.public@gmail.com>
|
||||||
|
# Licensed under the MIT license.
|
||||||
|
|
||||||
|
import sys, datetime
|
||||||
|
|
||||||
|
from transmissionrpc.constants import STATUS, PRIORITY
|
||||||
|
from transmissionrpc.utils import format_timedelta
|
||||||
|
|
||||||
|
class Torrent(object):
|
||||||
|
"""
|
||||||
|
Torrent is a class holding the data raceived from Transmission regarding a bittorrent transfer.
|
||||||
|
All fetched torrent fields are accessable through this class using attributes.
|
||||||
|
This class has a few convenience properties using the torrent data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, client, fields):
|
||||||
|
if 'id' not in fields:
|
||||||
|
raise ValueError('Torrent requires an id')
|
||||||
|
self.fields = {}
|
||||||
|
self.update(fields)
|
||||||
|
self.client = client
|
||||||
|
|
||||||
|
def _getNameString(self, codec=None):
|
||||||
|
if codec == None:
|
||||||
|
codec = sys.getdefaultencoding()
|
||||||
|
name = None
|
||||||
|
# try to find name
|
||||||
|
if 'name' in self.fields:
|
||||||
|
name = self.fields['name']
|
||||||
|
# if name is unicode, try to decode
|
||||||
|
if isinstance(name, unicode):
|
||||||
|
try:
|
||||||
|
name = name.encode(codec)
|
||||||
|
except UnicodeError:
|
||||||
|
name = None
|
||||||
|
return name
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
tid = self.fields['id']
|
||||||
|
name = self._getNameString()
|
||||||
|
if isinstance(name, str):
|
||||||
|
return '<Torrent %d \"%s\">' % (tid, name)
|
||||||
|
else:
|
||||||
|
return '<Torrent %d>' % (tid)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
name = self._getNameString()
|
||||||
|
if isinstance(name, str):
|
||||||
|
return 'Torrent \"%s\"' % (name)
|
||||||
|
else:
|
||||||
|
return 'Torrent'
|
||||||
|
|
||||||
|
def __copy__(self):
|
||||||
|
return Torrent(self.client, self.fields)
|
||||||
|
|
||||||
|
def update(self, other):
|
||||||
|
"""
|
||||||
|
Update the torrent data from a Transmission JSON-RPC arguments dictinary
|
||||||
|
"""
|
||||||
|
fields = None
|
||||||
|
if isinstance(other, dict):
|
||||||
|
fields = other
|
||||||
|
elif isinstance(other, Torrent):
|
||||||
|
fields = other.fields
|
||||||
|
else:
|
||||||
|
raise ValueError('Cannot update with supplied data')
|
||||||
|
for key, value in fields.iteritems():
|
||||||
|
self.fields[key.replace('-', '_')] = value
|
||||||
|
|
||||||
|
def files(self):
|
||||||
|
"""
|
||||||
|
Get list of files for this torrent.
|
||||||
|
|
||||||
|
This function returns a dictionary with file information for each file.
|
||||||
|
The file information is has following fields:
|
||||||
|
::
|
||||||
|
|
||||||
|
{
|
||||||
|
<file id>: {
|
||||||
|
'name': <file name>,
|
||||||
|
'size': <file size in bytes>,
|
||||||
|
'completed': <bytes completed>,
|
||||||
|
'priority': <priority ('high'|'normal'|'low')>,
|
||||||
|
'selected': <selected for download>
|
||||||
|
}
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
result = {}
|
||||||
|
if 'files' in self.fields:
|
||||||
|
indicies = xrange(len(self.fields['files']))
|
||||||
|
files = self.fields['files']
|
||||||
|
priorities = self.fields['priorities']
|
||||||
|
wanted = self.fields['wanted']
|
||||||
|
for item in zip(indicies, files, priorities, wanted):
|
||||||
|
selected = True if item[3] else False
|
||||||
|
priority = PRIORITY[item[2]]
|
||||||
|
result[item[0]] = {
|
||||||
|
'selected': selected,
|
||||||
|
'priority': priority,
|
||||||
|
'size': item[1]['length'],
|
||||||
|
'name': item[1]['name'],
|
||||||
|
'completed': item[1]['bytesCompleted']}
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
try:
|
||||||
|
return self.fields[name]
|
||||||
|
except KeyError:
|
||||||
|
raise AttributeError('No attribute %s' % name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def status(self):
|
||||||
|
"""
|
||||||
|
Returns the torrent status. Is either one of 'check pending', 'checking',
|
||||||
|
'downloading', 'seeding' or 'stopped'. The first two is related to
|
||||||
|
verification.
|
||||||
|
"""
|
||||||
|
return STATUS[self.fields['status']]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def progress(self):
|
||||||
|
"""Get the download progress in percent."""
|
||||||
|
try:
|
||||||
|
return 100.0 * (self.fields['sizeWhenDone'] - self.fields['leftUntilDone']) / float(self.fields['sizeWhenDone'])
|
||||||
|
except ZeroDivisionError:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ratio(self):
|
||||||
|
"""Get the upload/download ratio."""
|
||||||
|
try:
|
||||||
|
return self.fields['uploadedEver'] / float(self.fields['downloadedEver'])
|
||||||
|
except ZeroDivisionError:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def eta(self):
|
||||||
|
"""Get the "eta" as datetime.timedelta."""
|
||||||
|
eta = self.fields['eta']
|
||||||
|
if eta >= 0:
|
||||||
|
return datetime.timedelta(seconds=eta)
|
||||||
|
else:
|
||||||
|
ValueError('eta not valid')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def date_active(self):
|
||||||
|
"""Get the attribute "activityDate" as datetime.datetime."""
|
||||||
|
return datetime.datetime.fromtimestamp(self.fields['activityDate'])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def date_added(self):
|
||||||
|
"""Get the attribute "addedDate" as datetime.datetime."""
|
||||||
|
return datetime.datetime.fromtimestamp(self.fields['addedDate'])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def date_started(self):
|
||||||
|
"""Get the attribute "startDate" as datetime.datetime."""
|
||||||
|
return datetime.datetime.fromtimestamp(self.fields['startDate'])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def date_done(self):
|
||||||
|
"""Get the attribute "doneDate" as datetime.datetime."""
|
||||||
|
return datetime.datetime.fromtimestamp(self.fields['doneDate'])
|
||||||
|
|
||||||
|
def format_eta(self):
|
||||||
|
"""
|
||||||
|
Returns the attribute *eta* formatted as a string.
|
||||||
|
|
||||||
|
* If eta is -1 the result is 'not available'
|
||||||
|
* If eta is -2 the result is 'unknown'
|
||||||
|
* Otherwise eta is formatted as <days> <hours>:<minutes>:<seconds>.
|
||||||
|
"""
|
||||||
|
eta = self.fields['eta']
|
||||||
|
if eta == -1:
|
||||||
|
return 'not available'
|
||||||
|
elif eta == -2:
|
||||||
|
return 'unknown'
|
||||||
|
else:
|
||||||
|
return format_timedelta(self.eta)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def priority(self):
|
||||||
|
"""
|
||||||
|
Get the priority as string.
|
||||||
|
Can be one of 'low', 'normal', 'high'.
|
||||||
|
"""
|
||||||
|
return PRIORITY[self.fields['bandwidthPriority']]
|
|
@ -1,606 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# 2008-07, Erik Svensson <erik.public@gmail.com>
|
|
||||||
|
|
||||||
import sys, os, time, datetime
|
|
||||||
import re
|
|
||||||
import httplib, urllib2, base64, socket
|
|
||||||
|
|
||||||
try:
|
|
||||||
import json
|
|
||||||
except ImportError:
|
|
||||||
import simplejson as json
|
|
||||||
|
|
||||||
from constants import *
|
|
||||||
from utils import *
|
|
||||||
|
|
||||||
class TransmissionError(Exception):
|
|
||||||
def __init__(self, message='', original=None):
|
|
||||||
Exception.__init__(self, message)
|
|
||||||
self.message = message
|
|
||||||
self.original = original
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
if self.original:
|
|
||||||
original_name = type(self.original).__name__
|
|
||||||
return '%s Original exception: %s, "%s"' % (self.message, original_name, self.original.args)
|
|
||||||
else:
|
|
||||||
return self.args
|
|
||||||
|
|
||||||
class Torrent(object):
|
|
||||||
"""
|
|
||||||
Torrent is a class holding the data raceived from Transmission regarding a bittorrent transfer.
|
|
||||||
All fetched torrent fields are accessable through this class using attributes.
|
|
||||||
This class has a few convenience properties using the torrent data.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, fields):
|
|
||||||
if 'id' not in fields:
|
|
||||||
raise ValueError('Torrent requires an id')
|
|
||||||
self.fields = {}
|
|
||||||
self.update(fields)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<Torrent %d \"%s\">' % (self.fields['id'], self.fields['name'])
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return 'torrent %s' % self.fields['name']
|
|
||||||
|
|
||||||
def update(self, other):
|
|
||||||
"""Update the torrent data from a Transmission arguments dictinary"""
|
|
||||||
fields = None
|
|
||||||
if isinstance(other, dict):
|
|
||||||
fields = other
|
|
||||||
elif isinstance(other, Torrent):
|
|
||||||
fields = other.fields
|
|
||||||
else:
|
|
||||||
raise ValueError('Cannot update with supplied data')
|
|
||||||
for k, v in fields.iteritems():
|
|
||||||
self.fields[k.replace('-', '_')] = v
|
|
||||||
|
|
||||||
def files(self):
|
|
||||||
"""
|
|
||||||
Get list of files for this torrent. This function returns a dictionary with file information for each file.
|
|
||||||
"""
|
|
||||||
result = {}
|
|
||||||
if 'files' in self.fields:
|
|
||||||
indicies = xrange(len(self.fields['files']))
|
|
||||||
files = self.fields['files']
|
|
||||||
priorities = self.fields['priorities']
|
|
||||||
wanted = self.fields['wanted']
|
|
||||||
index = 1
|
|
||||||
for item in zip(indicies, files, priorities, wanted):
|
|
||||||
selected = bool(item[3])
|
|
||||||
priority = PRIORITY[item[2]]
|
|
||||||
result[item[0]] = {
|
|
||||||
'selected': selected,
|
|
||||||
'priority': priority,
|
|
||||||
'size': item[1]['length'],
|
|
||||||
'name': item[1]['name'],
|
|
||||||
'completed': item[1]['bytesCompleted']}
|
|
||||||
return result
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
try:
|
|
||||||
return self.fields[name]
|
|
||||||
except KeyError, e:
|
|
||||||
raise AttributeError('No attribute %s' % name)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def status(self):
|
|
||||||
"""Get the status as string."""
|
|
||||||
return STATUS[self.fields['status']]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def progress(self):
|
|
||||||
"""Get the download progress in percent as float."""
|
|
||||||
try:
|
|
||||||
return 100.0 * (self.fields['sizeWhenDone'] - self.fields['leftUntilDone']) / float(self.fields['sizeWhenDone'])
|
|
||||||
except ZeroDivisionError:
|
|
||||||
return 0.0
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ratio(self):
|
|
||||||
"""Get the upload/download ratio."""
|
|
||||||
try:
|
|
||||||
return self.fields['uploadedEver'] / float(self.fields['downloadedEver'])
|
|
||||||
except ZeroDivisionError:
|
|
||||||
return 0.0
|
|
||||||
|
|
||||||
@property
|
|
||||||
def eta(self):
|
|
||||||
"""Get the "eta" as datetime.timedelta."""
|
|
||||||
eta = self.fields['eta']
|
|
||||||
if eta >= 0:
|
|
||||||
return datetime.timedelta(seconds=eta)
|
|
||||||
else:
|
|
||||||
ValueError('eta not valid')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def date_active(self):
|
|
||||||
"""Get the attribute "activityDate" as datetime.datetime."""
|
|
||||||
return datetime.datetime.fromtimestamp(self.fields['activityDate'])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def date_added(self):
|
|
||||||
"""Get the attribute "addedDate" as datetime.datetime."""
|
|
||||||
return datetime.datetime.fromtimestamp(self.fields['addedDate'])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def date_started(self):
|
|
||||||
"""Get the attribute "startDate" as datetime.datetime."""
|
|
||||||
return datetime.datetime.fromtimestamp(self.fields['startDate'])
|
|
||||||
|
|
||||||
@property
|
|
||||||
def date_done(self):
|
|
||||||
"""Get the attribute "doneDate" as datetime.datetime."""
|
|
||||||
return datetime.datetime.fromtimestamp(self.fields['doneDate'])
|
|
||||||
|
|
||||||
def format_eta(self):
|
|
||||||
"""Returns the attribute "eta" formatted as a string."""
|
|
||||||
eta = self.fields['eta']
|
|
||||||
if eta == -1:
|
|
||||||
return 'not available'
|
|
||||||
elif eta == -2:
|
|
||||||
return 'unknown'
|
|
||||||
else:
|
|
||||||
return format_timedelta(self.eta)
|
|
||||||
|
|
||||||
class Session(object):
|
|
||||||
"""
|
|
||||||
Session is a class holding the session data for a Transmission daemon.
|
|
||||||
|
|
||||||
Access the session field can be done through attributes.
|
|
||||||
The attributes available are the same as the session arguments in the
|
|
||||||
Transmission RPC specification, but with underscore instead of hypen.
|
|
||||||
``download-dir`` -> ``download_dir``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, fields={}):
|
|
||||||
self.fields = {}
|
|
||||||
self.update(fields)
|
|
||||||
|
|
||||||
def update(self, other):
|
|
||||||
"""Update the session data from a session arguments dictinary"""
|
|
||||||
|
|
||||||
fields = None
|
|
||||||
if isinstance(other, dict):
|
|
||||||
fields = other
|
|
||||||
elif isinstance(other, Session):
|
|
||||||
fields = other.fields
|
|
||||||
else:
|
|
||||||
raise ValueError('Cannot update with supplied data')
|
|
||||||
|
|
||||||
for k, v in fields.iteritems():
|
|
||||||
self.fields[k.replace('-', '_')] = v
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
try:
|
|
||||||
return self.fields[name]
|
|
||||||
except KeyError, e:
|
|
||||||
raise AttributeError('No attribute %s' % name)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
text = ''
|
|
||||||
for k, v in self.fields.iteritems():
|
|
||||||
text += "% 32s: %s\n" % (k[-32:], v)
|
|
||||||
return text
|
|
||||||
|
|
||||||
class Client(object):
|
|
||||||
"""
|
|
||||||
This is it. This class implements the json-RPC protocol to communicate with Transmission.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, address='localhost', port=DEFAULT_PORT, user=None, password=None):
|
|
||||||
base_url = 'http://' + address + ':' + str(port)
|
|
||||||
self.url = base_url + '/transmission/rpc'
|
|
||||||
if user and password:
|
|
||||||
password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
|
||||||
password_manager.add_password(realm=None, uri=self.url, user=user, passwd=password)
|
|
||||||
opener = urllib2.build_opener(
|
|
||||||
urllib2.HTTPBasicAuthHandler(password_manager)
|
|
||||||
, urllib2.HTTPDigestAuthHandler(password_manager)
|
|
||||||
)
|
|
||||||
urllib2.install_opener(opener)
|
|
||||||
elif user or password:
|
|
||||||
logger.warning('Either user or password missing, not using authentication.')
|
|
||||||
self._sequence = 0
|
|
||||||
self.session = Session()
|
|
||||||
self.sessionid = 0
|
|
||||||
self.protocol_version = None
|
|
||||||
self.get_session()
|
|
||||||
self.torrent_get_arguments = get_arguments('torrent-get'
|
|
||||||
, self.rpc_version)
|
|
||||||
|
|
||||||
def _debug_request(self, request):
|
|
||||||
logger.debug(
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
'request': {
|
|
||||||
'url': request.get_full_url(),
|
|
||||||
'request-headers': dict(request.header_items()),
|
|
||||||
'request-data': json.loads(request.data),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
indent=2
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _debug_response(self, response, response_data):
|
|
||||||
try:
|
|
||||||
response_data = json.loads(response_data)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
logger.debug(
|
|
||||||
json.dumps(
|
|
||||||
{
|
|
||||||
'response': {
|
|
||||||
'url': response.url,
|
|
||||||
'code': response.code,
|
|
||||||
'msg': response.msg,
|
|
||||||
'headers': dict(response.headers),
|
|
||||||
'data': response_data,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
indent=2
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _http_query(self, query):
|
|
||||||
headers = {'X-Transmission-Session-Id': self.sessionid}
|
|
||||||
request = urllib2.Request(self.url, query, headers)
|
|
||||||
request_count = 0
|
|
||||||
while True:
|
|
||||||
error_data = ""
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
self._debug_request(request)
|
|
||||||
socket.setdefaulttimeout(10)
|
|
||||||
if (sys.version_info[0] == 2 and sys.version_info[1] > 5) or sys.version_info[0] > 2:
|
|
||||||
response = urllib2.urlopen(request, timeout=60)
|
|
||||||
else:
|
|
||||||
response = urllib2.urlopen(request)
|
|
||||||
break
|
|
||||||
except urllib2.HTTPError, error:
|
|
||||||
error_data = error.read()
|
|
||||||
if error.code == 409:
|
|
||||||
logger.info('Server responded with 409, trying to set session-id.')
|
|
||||||
if request_count > 1:
|
|
||||||
raise TransmissionError('Session ID negotiation failed.', error)
|
|
||||||
if 'X-Transmission-Session-Id' in error.headers:
|
|
||||||
self.sessionid = error.headers['X-Transmission-Session-Id']
|
|
||||||
request.add_header('X-Transmission-Session-Id', self.sessionid)
|
|
||||||
else:
|
|
||||||
raise TransmissionError('Unknown conflict.', error)
|
|
||||||
except urllib2.URLError, error:
|
|
||||||
raise TransmissionError('Failed to connect to daemon.', error)
|
|
||||||
except httplib.BadStatusLine, error:
|
|
||||||
if (request_count > 1):
|
|
||||||
raise TransmissionError('Failed to request %s "%s".' % (self.url, query), error)
|
|
||||||
finally:
|
|
||||||
if error_data:
|
|
||||||
self._debug_response(error, error_data)
|
|
||||||
request_count = request_count + 1
|
|
||||||
result = response.read()
|
|
||||||
self._debug_response(response, result)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _request(self, method, arguments={}, ids=[], require_ids = False):
|
|
||||||
"""Send json-rpc request to Transmission using http POST"""
|
|
||||||
|
|
||||||
if not isinstance(method, (str, unicode)):
|
|
||||||
raise ValueError('request takes method as string')
|
|
||||||
if not isinstance(arguments, dict):
|
|
||||||
raise ValueError('request takes arguments as dict')
|
|
||||||
ids = self._format_ids(ids)
|
|
||||||
if len(ids) > 0:
|
|
||||||
arguments['ids'] = ids
|
|
||||||
elif require_ids:
|
|
||||||
raise ValueError('request require ids')
|
|
||||||
|
|
||||||
query = json.dumps({'tag': self._sequence, 'method': method
|
|
||||||
, 'arguments': arguments})
|
|
||||||
logger.info(query)
|
|
||||||
self._sequence += 1
|
|
||||||
start = time.time()
|
|
||||||
http_data = self._http_query(query)
|
|
||||||
elapsed = time.time() - start
|
|
||||||
logger.info('http request took %.3f s' % (elapsed))
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = json.loads(http_data)
|
|
||||||
except ValueError, e:
|
|
||||||
logger.error('Error: ' + str(e))
|
|
||||||
logger.error('Request: \"%s\"' % (query))
|
|
||||||
logger.error('HTTP data: \"%s\"' % (http_data))
|
|
||||||
raise
|
|
||||||
|
|
||||||
logger.info(json.dumps(data, indent=2))
|
|
||||||
|
|
||||||
if data['result'] != 'success':
|
|
||||||
raise TransmissionError('Query failed with result \"%s\"'
|
|
||||||
% data['result'])
|
|
||||||
|
|
||||||
results = {}
|
|
||||||
if method == 'torrent-get':
|
|
||||||
for item in data['arguments']['torrents']:
|
|
||||||
results[item['id']] = Torrent(item)
|
|
||||||
if self.protocol_version == 2 and 'peers' not in item:
|
|
||||||
self.protocol_version = 1
|
|
||||||
elif method == 'torrent-add':
|
|
||||||
item = data['arguments']['torrent-added']
|
|
||||||
results[item['id']] = Torrent(item)
|
|
||||||
elif method == 'session-get':
|
|
||||||
self._update_session(data['arguments'])
|
|
||||||
elif method == 'session-stats':
|
|
||||||
# older versions of T has the return data in "session-stats"
|
|
||||||
if 'session-stats' in data['arguments']:
|
|
||||||
self._update_session(data['arguments']['session-stats'])
|
|
||||||
else:
|
|
||||||
self._update_session(data['arguments'])
|
|
||||||
elif method in ('port-test', 'blocklist-update'):
|
|
||||||
results = data['arguments']
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def _format_ids(self, args):
|
|
||||||
"""Take things and make them valid torrent identifiers"""
|
|
||||||
ids = []
|
|
||||||
|
|
||||||
if isinstance(args, (int, long)):
|
|
||||||
ids.append(args)
|
|
||||||
elif isinstance(args, (str, unicode)):
|
|
||||||
for item in re.split(u'[ ,]+', args):
|
|
||||||
if len(item) == 0:
|
|
||||||
continue
|
|
||||||
addition = None
|
|
||||||
try:
|
|
||||||
# handle index
|
|
||||||
addition = [int(item)]
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
if not addition:
|
|
||||||
# handle hashes
|
|
||||||
try:
|
|
||||||
int(item, 16)
|
|
||||||
addition = [item]
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if not addition:
|
|
||||||
# handle index ranges i.e. 5:10
|
|
||||||
match = re.match(u'^(\d+):(\d+)$', item)
|
|
||||||
if match:
|
|
||||||
try:
|
|
||||||
idx_from = int(match.group(1))
|
|
||||||
idx_to = int(match.group(2))
|
|
||||||
addition = range(idx_from, idx_to + 1)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if not addition:
|
|
||||||
raise ValueError(u'Invalid torrent id, \"%s\"' % item)
|
|
||||||
ids.extend(addition)
|
|
||||||
elif isinstance(args, (list)):
|
|
||||||
for item in args:
|
|
||||||
ids.extend(self._format_ids(item))
|
|
||||||
else:
|
|
||||||
raise ValueError(u'Invalid torrent id')
|
|
||||||
return ids
|
|
||||||
|
|
||||||
def _update_session(self, data):
|
|
||||||
self.session.update(data)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def rpc_version(self):
|
|
||||||
if self.protocol_version == None:
|
|
||||||
if hasattr(self.session, 'rpc_version'):
|
|
||||||
self.protocol_version = self.session.rpc_version
|
|
||||||
elif hasattr(self.session, 'version'):
|
|
||||||
self.protocol_version = 3
|
|
||||||
else:
|
|
||||||
self.protocol_version = 2
|
|
||||||
return self.protocol_version
|
|
||||||
|
|
||||||
def _rpc_version_warning(self, version):
|
|
||||||
if self.rpc_version < version:
|
|
||||||
logger.warning('Using feature not supported by server. RPC version for server %d, feature introduced in %d.' % (self.rpc_version, version))
|
|
||||||
|
|
||||||
def add(self, data, **kwargs):
|
|
||||||
"""
|
|
||||||
Add torrent to transfers list. Takes a base64 encoded .torrent file in data.
|
|
||||||
Additional arguments are:
|
|
||||||
|
|
||||||
* `paused`, boolean, Whether to pause the transfer on add.
|
|
||||||
* `download_dir`, path, The directory where the downloaded
|
|
||||||
contents will be saved in.
|
|
||||||
* `peer_limit`, number, Limits the number of peers for this
|
|
||||||
transfer.
|
|
||||||
* `files_unwanted`,
|
|
||||||
* `files_wanted`,
|
|
||||||
* `priority_high`,
|
|
||||||
* `priority_low`,
|
|
||||||
* `priority_normal`,
|
|
||||||
"""
|
|
||||||
args = {'metainfo': data}
|
|
||||||
for key, value in kwargs.iteritems():
|
|
||||||
argument = make_rpc_name(key)
|
|
||||||
(arg, val) = argument_value_convert('torrent-add',
|
|
||||||
argument, value, self.rpc_version)
|
|
||||||
args[arg] = val
|
|
||||||
return self._request('torrent-add', args)
|
|
||||||
|
|
||||||
def add_url(self, torrent_url, **kwargs):
|
|
||||||
"""
|
|
||||||
Add torrent to transfers list. Takes a url to a .torrent file.
|
|
||||||
Additional arguments are:
|
|
||||||
|
|
||||||
* `paused`, boolean, Whether to pause the transfer on add.
|
|
||||||
* `download_dir`, path, The directory where the downloaded
|
|
||||||
contents will be saved in.
|
|
||||||
* `peer_limit`, number, Limits the number of peers for this
|
|
||||||
transfer.
|
|
||||||
* `files_unwanted`,
|
|
||||||
* `files_wanted`,
|
|
||||||
* `priority_high`,
|
|
||||||
* `priority_low`,
|
|
||||||
* `priority_normal`,
|
|
||||||
"""
|
|
||||||
torrent_file = None
|
|
||||||
if os.path.exists(torrent_url):
|
|
||||||
torrent_file = open(torrent_url, 'r')
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
torrent_file = urllib2.urlopen(torrent_url)
|
|
||||||
except:
|
|
||||||
torrent_file = None
|
|
||||||
|
|
||||||
if not torrent_file:
|
|
||||||
raise TransmissionError('File does not exist.')
|
|
||||||
|
|
||||||
torrent_data = base64.b64encode(torrent_file.read())
|
|
||||||
return self.add(torrent_data, **kwargs)
|
|
||||||
|
|
||||||
def remove(self, ids, delete_data=False):
|
|
||||||
"""
|
|
||||||
remove torrent(s) with provided id(s). Local data is removed if
|
|
||||||
delete_data is True, otherwise not.
|
|
||||||
"""
|
|
||||||
self._rpc_version_warning(3)
|
|
||||||
self._request('torrent-remove',
|
|
||||||
{'delete-local-data':rpc_bool(delete_data)}, ids, True)
|
|
||||||
|
|
||||||
def start(self, ids):
|
|
||||||
"""start torrent(s) with provided id(s)"""
|
|
||||||
self._request('torrent-start', {}, ids, True)
|
|
||||||
|
|
||||||
def stop(self, ids):
|
|
||||||
"""stop torrent(s) with provided id(s)"""
|
|
||||||
self._request('torrent-stop', {}, ids, True)
|
|
||||||
|
|
||||||
def verify(self, ids):
|
|
||||||
"""verify torrent(s) with provided id(s)"""
|
|
||||||
self._request('torrent-verify', {}, ids, True)
|
|
||||||
|
|
||||||
def reannounce(self, ids):
|
|
||||||
"""reannounce torrent(s) with provided id(s)"""
|
|
||||||
self._rpc_version_warning(5)
|
|
||||||
self._request('torrent-reannounce', {}, ids, True)
|
|
||||||
|
|
||||||
def info(self, ids=[], arguments={}):
|
|
||||||
"""Get detailed information for torrent(s) with provided id(s)."""
|
|
||||||
if not arguments:
|
|
||||||
arguments = self.torrent_get_arguments
|
|
||||||
return self._request('torrent-get', {'fields': arguments}, ids)
|
|
||||||
|
|
||||||
def get_files(self, ids=[]):
|
|
||||||
"""
|
|
||||||
Get list of files for provided torrent id(s).
|
|
||||||
This function returns a dictonary for each requested torrent id holding
|
|
||||||
the information about the files.
|
|
||||||
"""
|
|
||||||
fields = ['id', 'name', 'hashString', 'files', 'priorities', 'wanted']
|
|
||||||
request_result = self._request('torrent-get', {'fields': fields}, ids)
|
|
||||||
result = {}
|
|
||||||
for id, torrent in request_result.iteritems():
|
|
||||||
result[id] = torrent.files()
|
|
||||||
return result
|
|
||||||
|
|
||||||
def set_files(self, items):
|
|
||||||
"""
|
|
||||||
Set file properties. Takes a dictonary with similar contents as the
|
|
||||||
result of get_files.
|
|
||||||
"""
|
|
||||||
if not isinstance(items, dict):
|
|
||||||
raise ValueError('Invalid file description')
|
|
||||||
for tid, files in items.iteritems():
|
|
||||||
if not isinstance(files, dict):
|
|
||||||
continue
|
|
||||||
wanted = []
|
|
||||||
unwanted = []
|
|
||||||
priority_high = []
|
|
||||||
priority_normal = []
|
|
||||||
priority_low = []
|
|
||||||
for fid, file in files.iteritems():
|
|
||||||
if not isinstance(file, dict):
|
|
||||||
continue
|
|
||||||
if 'selected' in file and file['selected']:
|
|
||||||
wanted.append(fid)
|
|
||||||
else:
|
|
||||||
unwanted.append(fid)
|
|
||||||
if 'priority' in file:
|
|
||||||
if file['priority'] == 'high':
|
|
||||||
priority_high.append(fid)
|
|
||||||
elif file['priority'] == 'normal':
|
|
||||||
priority_normal.append(fid)
|
|
||||||
elif file['priority'] == 'low':
|
|
||||||
priority_low.append(fid)
|
|
||||||
self.change([tid], files_wanted = wanted
|
|
||||||
, files_unwanted = unwanted
|
|
||||||
, priority_high = priority_high
|
|
||||||
, priority_normal = priority_normal
|
|
||||||
, priority_low = priority_low)
|
|
||||||
|
|
||||||
def list(self):
|
|
||||||
"""list all torrents"""
|
|
||||||
fields = ['id', 'hashString', 'name', 'sizeWhenDone', 'leftUntilDone'
|
|
||||||
, 'eta', 'status', 'rateUpload', 'rateDownload', 'uploadedEver'
|
|
||||||
, 'downloadedEver']
|
|
||||||
return self._request('torrent-get', {'fields': fields})
|
|
||||||
|
|
||||||
def change(self, ids, **kwargs):
|
|
||||||
"""
|
|
||||||
Change torrent parameters. This is the list of parameters that.
|
|
||||||
"""
|
|
||||||
args = {}
|
|
||||||
for key, value in kwargs.iteritems():
|
|
||||||
argument = make_rpc_name(key)
|
|
||||||
(arg, val) = argument_value_convert('torrent-set'
|
|
||||||
, argument, value, self.rpc_version)
|
|
||||||
args[arg] = val
|
|
||||||
|
|
||||||
if len(args) > 0:
|
|
||||||
self._request('torrent-set', args, ids, True)
|
|
||||||
else:
|
|
||||||
ValueError("No arguments to set")
|
|
||||||
|
|
||||||
def get_session(self):
|
|
||||||
"""Get session parameters"""
|
|
||||||
self._request('session-get')
|
|
||||||
return self.session
|
|
||||||
|
|
||||||
def set_session(self, **kwargs):
|
|
||||||
"""Set session parameters"""
|
|
||||||
args = {}
|
|
||||||
for key, value in kwargs.iteritems():
|
|
||||||
if key == 'encryption' and value not in ['required', 'preferred', 'tolerated']:
|
|
||||||
raise ValueError('Invalid encryption value')
|
|
||||||
argument = make_rpc_name(key)
|
|
||||||
(arg, val) = argument_value_convert('session-set'
|
|
||||||
, argument, value, self.rpc_version)
|
|
||||||
args[arg] = val
|
|
||||||
if len(args) > 0:
|
|
||||||
self._request('session-set', args)
|
|
||||||
|
|
||||||
def blocklist_update(self):
|
|
||||||
"""Update block list. Returns the size of the block list."""
|
|
||||||
self._rpc_version_warning(5)
|
|
||||||
result = self._request('blocklist-update')
|
|
||||||
if 'blocklist-size' in result:
|
|
||||||
return result['blocklist-size']
|
|
||||||
return None
|
|
||||||
|
|
||||||
def port_test(self):
|
|
||||||
"""
|
|
||||||
Tests to see if your incoming peer port is accessible from the
|
|
||||||
outside world.
|
|
||||||
"""
|
|
||||||
self._rpc_version_warning(5)
|
|
||||||
result = self._request('port-test')
|
|
||||||
if 'port-is-open' in result:
|
|
||||||
return result['port-is-open']
|
|
||||||
return None
|
|
||||||
|
|
||||||
def session_stats(self):
|
|
||||||
"""Get session statistics"""
|
|
||||||
self._request('session-stats')
|
|
||||||
return self.session
|
|
81
resources/lib/transmissionrpc/utils.py
Normal file → Executable file
81
resources/lib/transmissionrpc/utils.py
Normal file → Executable file
|
@ -1,14 +1,18 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# 2008-07, Erik Svensson <erik.public@gmail.com>
|
# Copyright (c) 2008-2010 Erik Svensson <erik.public@gmail.com>
|
||||||
|
# Licensed under the MIT license.
|
||||||
|
|
||||||
import socket, datetime
|
import socket, datetime, logging
|
||||||
import constants
|
import transmissionrpc.constants as constants
|
||||||
from constants import logger
|
from transmissionrpc.constants import LOGGER
|
||||||
|
|
||||||
UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']
|
UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']
|
||||||
|
|
||||||
def format_size(size):
|
def format_size(size):
|
||||||
s = float(size)
|
"""
|
||||||
|
Format byte size into IEC prefixes, B, KiB, MiB ...
|
||||||
|
"""
|
||||||
|
size = float(size)
|
||||||
i = 0
|
i = 0
|
||||||
while size >= 1024.0 and i < len(UNITS):
|
while size >= 1024.0 and i < len(UNITS):
|
||||||
i += 1
|
i += 1
|
||||||
|
@ -16,58 +20,75 @@ def format_size(size):
|
||||||
return (size, UNITS[i])
|
return (size, UNITS[i])
|
||||||
|
|
||||||
def format_speed(size):
|
def format_speed(size):
|
||||||
|
"""
|
||||||
|
Format bytes per second speed into IEC prefixes, B/s, KiB/s, MiB/s ...
|
||||||
|
"""
|
||||||
(size, unit) = format_size(size)
|
(size, unit) = format_size(size)
|
||||||
return (size, unit + '/s')
|
return (size, unit + '/s')
|
||||||
|
|
||||||
def format_timedelta(delta):
|
def format_timedelta(delta):
|
||||||
|
"""
|
||||||
|
Format datetime.timedelta into <days> <hours>:<mminutes>:<seconds>.
|
||||||
|
"""
|
||||||
minutes, seconds = divmod(delta.seconds, 60)
|
minutes, seconds = divmod(delta.seconds, 60)
|
||||||
hours, minutes = divmod(minutes, 60)
|
hours, minutes = divmod(minutes, 60)
|
||||||
return '%d %02d:%02d:%02d' % (delta.days, hours, minutes, seconds)
|
return '%d %02d:%02d:%02d' % (delta.days, hours, minutes, seconds)
|
||||||
|
|
||||||
def format_timestamp(timestamp):
|
def format_timestamp(timestamp):
|
||||||
|
"""
|
||||||
|
Format unix timestamp into ISO date format.
|
||||||
|
"""
|
||||||
if timestamp > 0:
|
if timestamp > 0:
|
||||||
dt = datetime.datetime.fromtimestamp(timestamp)
|
dt_timestamp = datetime.datetime.fromtimestamp(timestamp)
|
||||||
return dt.isoformat(' ')
|
return dt_timestamp.isoformat(' ')
|
||||||
else:
|
else:
|
||||||
return '-'
|
return '-'
|
||||||
|
|
||||||
class INetAddressError(Exception):
|
class INetAddressError(Exception):
|
||||||
|
"""
|
||||||
|
Error parsing / generating a internet address.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def inet_address(address, default_port, default_address='localhost'):
|
def inet_address(address, default_port, default_address='localhost'):
|
||||||
|
"""
|
||||||
|
Parse internet address.
|
||||||
|
"""
|
||||||
addr = address.split(':')
|
addr = address.split(':')
|
||||||
if len(addr) == 1:
|
if len(addr) == 1:
|
||||||
try:
|
try:
|
||||||
port = int(addr[0])
|
port = int(addr[0])
|
||||||
addr = default_address
|
addr = default_address
|
||||||
except:
|
except ValueError:
|
||||||
addr = addr[0]
|
addr = addr[0]
|
||||||
port = default_port
|
port = default_port
|
||||||
elif len(addr) == 2:
|
elif len(addr) == 2:
|
||||||
|
try:
|
||||||
port = int(addr[1])
|
port = int(addr[1])
|
||||||
|
except ValueError:
|
||||||
|
raise INetAddressError('Invalid address "%s".' % address)
|
||||||
if len(addr[0]) == 0:
|
if len(addr[0]) == 0:
|
||||||
addr = default_address
|
addr = default_address
|
||||||
else:
|
else:
|
||||||
addr = addr[0]
|
addr = addr[0]
|
||||||
else:
|
else:
|
||||||
addr = default_address
|
raise INetAddressError('Invalid address "%s".' % address)
|
||||||
port = default_port
|
|
||||||
try:
|
try:
|
||||||
socket.getaddrinfo(addr, port, socket.AF_INET, socket.SOCK_STREAM)
|
socket.getaddrinfo(addr, port, socket.AF_INET, socket.SOCK_STREAM)
|
||||||
except socket.gaierror, e:
|
except socket.gaierror:
|
||||||
raise INetAddressError('Cannot look up address "%s".' % address)
|
raise INetAddressError('Cannot look up address "%s".' % address)
|
||||||
return (addr, port)
|
return (addr, port)
|
||||||
|
|
||||||
def rpc_bool(arg):
|
def rpc_bool(arg):
|
||||||
|
"""
|
||||||
|
Convert between Python boolean and Transmission RPC boolean.
|
||||||
|
"""
|
||||||
if isinstance(arg, (str, unicode)):
|
if isinstance(arg, (str, unicode)):
|
||||||
try:
|
try:
|
||||||
arg = bool(int(arg))
|
arg = bool(int(arg))
|
||||||
except:
|
except ValueError:
|
||||||
arg = arg.lower() in [u'true', u'yes']
|
arg = arg.lower() in [u'true', u'yes']
|
||||||
if bool(arg):
|
return 1 if bool(arg) else 0
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
TR_TYPE_MAP = {
|
TR_TYPE_MAP = {
|
||||||
'number' : int,
|
'number' : int,
|
||||||
|
@ -79,12 +100,21 @@ TR_TYPE_MAP = {
|
||||||
}
|
}
|
||||||
|
|
||||||
def make_python_name(name):
|
def make_python_name(name):
|
||||||
|
"""
|
||||||
|
Convert Transmission RPC name to python compatible name.
|
||||||
|
"""
|
||||||
return name.replace('-', '_')
|
return name.replace('-', '_')
|
||||||
|
|
||||||
def make_rpc_name(name):
|
def make_rpc_name(name):
|
||||||
|
"""
|
||||||
|
Convert python compatible name to Transmission RPC name.
|
||||||
|
"""
|
||||||
return name.replace('_', '-')
|
return name.replace('_', '-')
|
||||||
|
|
||||||
def argument_value_convert(method, argument, value, rpc_version):
|
def argument_value_convert(method, argument, value, rpc_version):
|
||||||
|
"""
|
||||||
|
Check and fix Transmission RPC issues with regards to methods, arguments and values.
|
||||||
|
"""
|
||||||
if method in ('torrent-add', 'torrent-get', 'torrent-set'):
|
if method in ('torrent-add', 'torrent-get', 'torrent-set'):
|
||||||
args = constants.TORRENT_ARGS[method[-3:]]
|
args = constants.TORRENT_ARGS[method[-3:]]
|
||||||
elif method in ('session-get', 'session-set'):
|
elif method in ('session-get', 'session-set'):
|
||||||
|
@ -105,7 +135,7 @@ def argument_value_convert(method, argument, value, rpc_version):
|
||||||
replacement = info[4]
|
replacement = info[4]
|
||||||
if invalid_version:
|
if invalid_version:
|
||||||
if replacement:
|
if replacement:
|
||||||
logger.warning(
|
LOGGER.warning(
|
||||||
'Replacing requested argument "%s" with "%s".'
|
'Replacing requested argument "%s" with "%s".'
|
||||||
% (argument, replacement))
|
% (argument, replacement))
|
||||||
argument = replacement
|
argument = replacement
|
||||||
|
@ -120,6 +150,9 @@ def argument_value_convert(method, argument, value, rpc_version):
|
||||||
(argument, method))
|
(argument, method))
|
||||||
|
|
||||||
def get_arguments(method, rpc_version):
|
def get_arguments(method, rpc_version):
|
||||||
|
"""
|
||||||
|
Get arguments for method in specified Transmission RPC version.
|
||||||
|
"""
|
||||||
if method in ('torrent-add', 'torrent-get', 'torrent-set'):
|
if method in ('torrent-add', 'torrent-get', 'torrent-set'):
|
||||||
args = constants.TORRENT_ARGS[method[-3:]]
|
args = constants.TORRENT_ARGS[method[-3:]]
|
||||||
elif method in ('session-get', 'session-set'):
|
elif method in ('session-get', 'session-set'):
|
||||||
|
@ -136,3 +169,17 @@ def get_arguments(method, rpc_version):
|
||||||
if valid_version:
|
if valid_version:
|
||||||
accessible.append(argument)
|
accessible.append(argument)
|
||||||
return accessible
|
return accessible
|
||||||
|
|
||||||
|
def add_stdout_logger(level='debug'):
|
||||||
|
"""
|
||||||
|
Add a stdout target for the transmissionrpc logging.
|
||||||
|
"""
|
||||||
|
levels = {'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'error': logging.ERROR}
|
||||||
|
|
||||||
|
trpc_logger = logging.getLogger('transmissionrpc')
|
||||||
|
loghandler = logging.StreamHandler()
|
||||||
|
if level in levels.keys():
|
||||||
|
loglevel = levels[level]
|
||||||
|
trpc_logger.setLevel(loglevel)
|
||||||
|
loghandler.setLevel(loglevel)
|
||||||
|
trpc_logger.addHandler(loghandler)
|
||||||
|
|
Loading…
Reference in a new issue