diff --git a/addon.xml b/addon.xml
index 94f6bfb..03af4b9 100644
--- a/addon.xml
+++ b/addon.xml
@@ -7,6 +7,7 @@
+
diff --git a/resources/lib/gui.py b/resources/lib/gui.py
index 6861a03..9b1deb9 100644
--- a/resources/lib/gui.py
+++ b/resources/lib/gui.py
@@ -149,7 +149,7 @@ class TransmissionGUI(xbmcgui.WindowXMLDialog):
if selected < 0:
return
try:
- self.transmission.add_uri(results[selected]['url'])
+ self.transmission.add_torrent(results[selected]['url'])
except:
xbmcgui.Dialog().ok(_(0), _(293))
return
diff --git a/resources/lib/transmissionrpc/__init__.py b/resources/lib/transmissionrpc/__init__.py
old mode 100644
new mode 100755
index 5d3301f..2a174ed
--- a/resources/lib/transmissionrpc/__init__.py
+++ b/resources/lib/transmissionrpc/__init__.py
@@ -1,16 +1,18 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2008-2011 Erik Svensson
-# Licensed under the MIT license.
-
-from transmissionrpc.constants import DEFAULT_PORT, DEFAULT_TIMEOUT, PRIORITY, RATIO_LIMIT, LOGGER
-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 '
-__version__ = u'0.8'
-__copyright__ = u'Copyright (c) 2008-2011 Erik Svensson'
-__license__ = u'MIT'
+# -*- coding: utf-8 -*-
+# Copyright (c) 2008-2013 Erik Svensson
+# Licensed under the MIT license.
+
+from transmissionrpc.constants import DEFAULT_PORT, DEFAULT_TIMEOUT, PRIORITY, RATIO_LIMIT, LOGGER
+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, add_file_logger
+
+__author__ = 'Erik Svensson '
+__version_major__ = 0
+__version_minor__ = 11
+__version__ = '{0}.{1}'.format(__version_major__, __version_minor__)
+__copyright__ = 'Copyright (c) 2008-2013 Erik Svensson'
+__license__ = 'MIT'
diff --git a/resources/lib/transmissionrpc/client.py b/resources/lib/transmissionrpc/client.py
old mode 100644
new mode 100755
index 0fbe8ee..72e9547
--- a/resources/lib/transmissionrpc/client.py
+++ b/resources/lib/transmissionrpc/client.py
@@ -1,736 +1,931 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2008-2011 Erik Svensson
-# Licensed under the MIT license.
-
-import sys
-import re, time
-import 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
- )
- )
-
-if sys.version_info[:2] < (2, 5):
- """
- Mimic python 2.5+ urlparse.urlparse
- """
- _old_urlparse = urlparse.urlparse
- def _urlparse(address):
- class ParsedResult(tuple):
- def __init__(self, address = None):
- self.scheme = ''
- self.netloc = ''
- self.path = ''
- self.params = ''
- self.query = ''
- self.fragment = ''
- self.username = None
- self.password = None
- self.hostname = None
- self.port = None
-
- if address:
- self.parse(address)
- def parse(self, address):
- (
- self.scheme,
- self.netloc,
- self.path,
- self.params,
- self.query,
- self.fragment
- ) = _old_urlparse(address)
- self.hostname = self.netloc
- if '@' in self.netloc:
- (login, self.hostname) = self.netloc.split('@')
- if ':' in login:
- (self.username, self.password) = login.split(':')
- else:
- self.username = login
- if ':' in self.hostname:
- (self.hostname, self.port) = self.hostname.split(':')
- try:
- self.port = int(self.port)
- except:
- self.port = None
-
- result = ParsedResult(address)
- return result
-
- urlparse.urlparse = _urlparse
-
-"""
-Torrent ids
-
-Many functions in Client takes torrent id. A torrent id can either be id or
-hashString. When supplying 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 is 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)}
- result = {}
- request_count = 0
- if timeout is 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 += 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 is 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 is 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 is 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 is None:
- # Ugly fix for 2.20 - 2.22 reporting rpc-version 11, but having new arguments
- if self.server_version and (self.server_version[0] == 2 and self.server_version[1] in [20, 21, 22]):
- self.protocol_version = 12
- # Ugly fix for 2.12 reporting rpc-version 10, but having new arguments
- elif 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 Replaced by Description
- ===================== ===== =========== =============================================================
- ``bandwidthPriority`` 8 - Priority for this transfer.
- ``cookies`` 13 - One or more HTTP cookie(s).
- ``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.
- ===================== ===== =========== =============================================================
-
- """
- 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 Replaced by Description
- ===================== ===== =========== =============================================================
- ``bandwidthPriority`` 8 - Priority for this transfer.
- ``cookies`` 13 - One or more HTTP cookie(s).
- ``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 is 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
- parsed_uri = urlparse.urlparse(uri)
- torrent_data = None
- if parsed_uri.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, bypass_queue=False, timeout=None):
- """start torrent(s) with provided id(s)"""
- method = 'torrent-start'
- if bypass_queue and self.rpc_version >= 14:
- method = 'torrent-start-now'
- self._request(method, {}, 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 dictionary
- for each requested torrent id holding the information about the files.
-
- ::
-
- {
- : {
- : {
- 'name': ,
- 'size': ,
- 'completed': ,
- 'priority': ,
- 'selected':
- }
-
- ...
- }
-
- ...
- }
- """
- 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 dictionary with similar contents as the result
- of `get_files`.
-
- ::
-
- {
- : {
- : {
- 'priority': ,
- 'selected':
- }
-
- ...
- }
-
- ...
- }
- """
- if not isinstance(items, dict):
- raise ValueError('Invalid file description')
- for tid, files in items.iteritems():
- if not isinstance(files, dict):
- continue
- wanted = []
- unwanted = []
- high = []
- normal = []
- 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':
- high.append(fid)
- elif file_desc['priority'] == 'normal':
- normal.append(fid)
- elif file_desc['priority'] == 'low':
- low.append(fid)
- args = {
- 'timeout': timeout,
- 'files_wanted': wanted,
- 'files_unwanted': unwanted,
- }
- if len(high) > 0:
- args['priority_high'] = high
- if len(normal) > 0:
- args['priority_normal'] = normal
- if len(low) > 0:
- args['priority_low'] = low
- self.change([tid], **args)
-
- def list(self, timeout=None):
- """list all torrents"""
- fields = ['id', 'hashString', 'name', 'sizeWhenDone', 'leftUntilDone'
- , 'eta', 'status', 'rateUpload', 'rateDownload', 'uploadedEver'
- , 'downloadedEver', 'uploadRatio']
- 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.
- ``location`` 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.
- ``queuePosition`` 14 - Position of this transfer in its queue.
- ``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 queue_top(self, ids, timeout=None):
- """Move transfer to the top of the queue."""
- self._rpc_version_warning(14)
- self._request('queue-move-top', ids=ids, require_ids=True, timeout=timeout)
-
- def queue_bottom(self, ids, timeout=None):
- """Move transfer to the bottom of the queue."""
- self._rpc_version_warning(14)
- self._request('queue-move-bottom', ids=ids, require_ids=True, timeout=timeout)
-
- def queue_up(self, ids, timeout=None):
- """Move transfer up in the queue."""
- self._rpc_version_warning(14)
- self._request('queue-move-up', ids=ids, require_ids=True, timeout=timeout)
-
- def queue_down(self, ids, timeout=None):
- """Move transfer down in the queue."""
- self._rpc_version_warning(14)
- self._request('queue-move-down', ids=ids, require_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
- ``blocklist_url`` 11 - Location of the block list. Updated with blocklist-update.
- ``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.
- ``download_queue_enabled`` 14 - Enable parallel download restriction.
- ``download_queue_size`` 14 - Number of parallel downloads.
- ``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.
- ``queue_stalled_enabled`` 14 - Enable tracking of stalled transfers.
- ``queue_stalled_minutes`` 14 - Number of minutes of idle that marks a transfer as stalled.
- ``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.
- ``seed_queue_enabled`` 14 - Enable parallel upload restriction.
- ``seed_queue_size`` 14 - Number of parallel uploads.
- ``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.
- ``utp_enabled`` 13 - Enables Micro Transport Protocol (UTP).
- ================================ ===== ================= ==========================================================================================================================
-
- .. 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
+# -*- coding: utf-8 -*-
+# Copyright (c) 2008-2013 Erik Svensson
+# Licensed under the MIT license.
+
+import re, time, operator, warnings, os
+import base64
+import 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
+
+from six import PY3, integer_types, string_types, iteritems
+
+if PY3:
+ from urllib.parse import urlparse
+ from urllib.request import urlopen
+else:
+ from urlparse import urlparse
+ from urllib2 import urlopen
+
+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
+ )
+ )
+
+def parse_torrent_id(arg):
+ """Parse an torrent id or torrent hashString."""
+ torrent_id = None
+ if isinstance(arg, integer_types):
+ # handle index
+ torrent_id = int(arg)
+ elif isinstance(arg, float):
+ torrent_id = int(arg)
+ if torrent_id != arg:
+ torrent_id = None
+ elif isinstance(arg, string_types):
+ try:
+ torrent_id = int(arg)
+ if torrent_id >= 2**31:
+ torrent_id = None
+ except (ValueError, TypeError):
+ pass
+ if torrent_id is None:
+ # handle hashes
+ try:
+ int(arg, 16)
+ torrent_id = arg
+ except (ValueError, TypeError):
+ pass
+ return torrent_id
+
+def parse_torrent_ids(args):
+ """
+ Take things and make them valid torrent identifiers
+ """
+ ids = []
+
+ if args is None:
+ pass
+ elif isinstance(args, string_types):
+ for item in re.split('[ ,]+', args):
+ if len(item) == 0:
+ continue
+ addition = None
+ torrent_id = parse_torrent_id(item)
+ if torrent_id is not None:
+ addition = [torrent_id]
+ if not addition:
+ # handle index ranges i.e. 5:10
+ match = re.match('^(\d+):(\d+)$', item)
+ if match:
+ try:
+ idx_from = int(match.group(1))
+ idx_to = int(match.group(2))
+ addition = list(range(idx_from, idx_to + 1))
+ except ValueError:
+ pass
+ if not addition:
+ raise ValueError('Invalid torrent id, \"%s\"' % item)
+ ids.extend(addition)
+ elif isinstance(args, (list, tuple)):
+ for item in args:
+ ids.extend(parse_torrent_ids(item))
+ else:
+ torrent_id = parse_torrent_id(args)
+ if torrent_id == None:
+ raise ValueError('Invalid torrent id')
+ else:
+ ids = [torrent_id]
+ return ids
+
+"""
+Torrent ids
+
+Many functions in Client takes torrent id. A torrent id can either be id or
+hashString. When supplying 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, (integer_types, float)):
+ self._query_timeout = float(timeout)
+ else:
+ self._query_timeout = DEFAULT_TIMEOUT
+ urlo = 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 is 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 = None
+ 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)}
+ result = {}
+ request_count = 0
+ if timeout is 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 as 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)
+ session_id = None
+ for key in list(error.headers.keys()):
+ if key.lower() == 'x-transmission-session-id':
+ session_id = error.headers[key]
+ self.session_id = session_id
+ headers = {'x-transmission-session-id': str(self.session_id)}
+ if session_id is None:
+ debug_httperror(error)
+ raise TransmissionError('Unknown conflict.', error)
+ else:
+ debug_httperror(error)
+ raise TransmissionError('Request failed.', error)
+ 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, string_types):
+ raise ValueError('request takes method as string')
+ if arguments is None:
+ arguments = {}
+ if not isinstance(arguments, dict):
+ raise ValueError('request takes arguments as dict')
+ ids = parse_torrent_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 as 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 = None
+ if 'torrent-added' in data['arguments']:
+ item = data['arguments']['torrent-added']
+ elif 'torrent-duplicate' in data['arguments']:
+ item = data['arguments']['torrent-duplicate']
+ if item:
+ results[item['id']] = Torrent(self, item)
+ else:
+ raise TransmissionError('Invalid torrent-add response.')
+ 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', 'free-space', 'torrent-rename-path'):
+ results = data['arguments']
+ else:
+ return None
+
+ return results
+
+ def _update_session(self, data):
+ """
+ Update session data.
+ """
+ if self.session:
+ self.session.from_request(data)
+ else:
+ self.session = Session(self, data)
+
+ def _update_server_version(self):
+ """Decode the Transmission version string, if available."""
+ if self.server_version is 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 is None:
+ # Ugly fix for 2.20 - 2.22 reporting rpc-version 11, but having new arguments
+ if self.server_version and (self.server_version[0] == 2 and self.server_version[1] in [20, 21, 22]):
+ self.protocol_version = 12
+ # Ugly fix for 2.12 reporting rpc-version 10, but having new arguments
+ elif 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_torrent(self, torrent, timeout=None, **kwargs):
+ """
+ Add torrent to transfers list. Takes a uri to a torrent or base64 encoded torrent data in ``torrent``.
+ Additional arguments are:
+
+ ===================== ===== =========== =============================================================
+ Argument RPC Replaced by Description
+ ===================== ===== =========== =============================================================
+ ``bandwidthPriority`` 8 - Priority for this transfer.
+ ``cookies`` 13 - One or more HTTP cookie(s).
+ ``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.
+ ===================== ===== =========== =============================================================
+
+ Returns a Torrent object with the fields.
+ """
+ if torrent is None:
+ raise ValueError('add_torrent requires data or a URI.')
+ torrent_data = None
+ parsed_uri = urlparse(torrent)
+ if parsed_uri.scheme in ['ftp', 'ftps', 'http', 'https']:
+ # there has been some problem with T's built in torrent fetcher,
+ # use a python one instead
+ torrent_file = urlopen(torrent)
+ torrent_data = torrent_file.read()
+ torrent_data = base64.b64encode(torrent_data).decode('utf-8')
+ if parsed_uri.scheme in ['file']:
+ filepath = torrent
+ # uri decoded different on linux / windows ?
+ if len(parsed_uri.path) > 0:
+ filepath = parsed_uri.path
+ elif len(parsed_uri.netloc) > 0:
+ filepath = parsed_uri.netloc
+ torrent_file = open(filepath, 'rb')
+ torrent_data = torrent_file.read()
+ torrent_data = base64.b64encode(torrent_data).decode('utf-8')
+ if not torrent_data:
+ if torrent.endswith('.torrent') or torrent.startswith('magnet:'):
+ torrent_data = None
+ else:
+ might_be_base64 = False
+ try:
+ # check if this is base64 data
+ if PY3:
+ base64.b64decode(torrent.encode('utf-8'))
+ else:
+ base64.b64decode(torrent)
+ might_be_base64 = True
+ except Exception:
+ pass
+ if might_be_base64:
+ torrent_data = torrent
+ args = {}
+ if torrent_data:
+ args = {'metainfo': torrent_data}
+ else:
+ args = {'filename': torrent}
+ for key, value in iteritems(kwargs):
+ argument = make_rpc_name(key)
+ (arg, val) = argument_value_convert('torrent-add', argument, value, self.rpc_version)
+ args[arg] = val
+ return list(self._request('torrent-add', args, timeout=timeout).values())[0]
+
+ def add(self, data, timeout=None, **kwargs):
+ """
+
+ .. WARNING::
+ Deprecated, please use add_torrent.
+ """
+ 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 iteritems(kwargs):
+ argument = make_rpc_name(key)
+ (arg, val) = argument_value_convert('torrent-add', argument, value, self.rpc_version)
+ args[arg] = val
+ warnings.warn('add has been deprecated, please use add_torrent instead.', DeprecationWarning)
+ return self._request('torrent-add', args, timeout=timeout)
+
+ def add_uri(self, uri, **kwargs):
+ """
+
+ .. WARNING::
+ Deprecated, please use add_torrent.
+ """
+ if uri is 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
+ parsed_uri = urlparse(uri)
+ torrent_data = None
+ if parsed_uri.scheme in ['ftp', 'ftps', 'http', 'https']:
+ torrent_file = urlopen(uri)
+ torrent_data = torrent_file.read()
+ torrent_data = base64.b64encode(torrent_data).decode('utf-8')
+ if parsed_uri.scheme in ['file']:
+ filepath = uri
+ # uri decoded different on linux / windows ?
+ if len(parsed_uri.path) > 0:
+ filepath = parsed_uri.path
+ elif len(parsed_uri.netloc) > 0:
+ filepath = parsed_uri.netloc
+ torrent_file = open(filepath, 'rb')
+ torrent_data = torrent_file.read()
+ torrent_data = base64.b64encode(torrent_data).decode('utf-8')
+ warnings.warn('add_uri has been deprecated, please use add_torrent instead.', DeprecationWarning)
+ if torrent_data:
+ return self.add(torrent_data, **kwargs)
+ else:
+ return self.add(None, filename=uri, **kwargs)
+
+ def remove_torrent(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 remove(self, ids, delete_data=False, timeout=None):
+ """
+
+ .. WARNING::
+ Deprecated, please use remove_torrent.
+ """
+ warnings.warn('remove has been deprecated, please use remove_torrent instead.', DeprecationWarning)
+ self.remove_torrent(ids, delete_data, timeout)
+
+ def start_torrent(self, ids, bypass_queue=False, timeout=None):
+ """Start torrent(s) with provided id(s)"""
+ method = 'torrent-start'
+ if bypass_queue and self.rpc_version >= 14:
+ method = 'torrent-start-now'
+ self._request(method, {}, ids, True, timeout=timeout)
+
+ def start(self, ids, bypass_queue=False, timeout=None):
+ """
+
+ .. WARNING::
+ Deprecated, please use start_torrent.
+ """
+ warnings.warn('start has been deprecated, please use start_torrent instead.', DeprecationWarning)
+ self.start_torrent(ids, bypass_queue, timeout)
+
+ def start_all(self, bypass_queue=False, timeout=None):
+ """Start all torrents respecting the queue order"""
+ torrent_list = self.get_torrents()
+ method = 'torrent-start'
+ if self.rpc_version >= 14:
+ if bypass_queue:
+ method = 'torrent-start-now'
+ torrent_list = sorted(torrent_list, key=operator.attrgetter('queuePosition'))
+ ids = [x.id for x in torrent_list]
+ self._request(method, {}, ids, True, timeout=timeout)
+
+ def stop_torrent(self, ids, timeout=None):
+ """stop torrent(s) with provided id(s)"""
+ self._request('torrent-stop', {}, ids, True, timeout=timeout)
+
+ def stop(self, ids, timeout=None):
+ """
+
+ .. WARNING::
+ Deprecated, please use stop_torrent.
+ """
+ warnings.warn('stop has been deprecated, please use stop_torrent instead.', DeprecationWarning)
+ self.stop_torrent(ids, timeout)
+
+ def verify_torrent(self, ids, timeout=None):
+ """verify torrent(s) with provided id(s)"""
+ self._request('torrent-verify', {}, ids, True, timeout=timeout)
+
+ def verify(self, ids, timeout=None):
+ """
+
+ .. WARNING::
+ Deprecated, please use verify_torrent.
+ """
+ warnings.warn('verify has been deprecated, please use verify_torrent instead.', DeprecationWarning)
+ self.verify_torrent(ids, timeout)
+
+ def reannounce_torrent(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 reannounce(self, ids, timeout=None):
+ """
+
+ .. WARNING::
+ Deprecated, please use reannounce_torrent.
+ """
+ warnings.warn('reannounce has been deprecated, please use reannounce_torrent instead.', DeprecationWarning)
+ self.reannounce_torrent(ids, timeout)
+
+ def get_torrent(self, torrent_id, arguments=None, timeout=None):
+ """
+ Get information for torrent with provided id.
+ ``arguments`` contains a list of field names to be returned, when None
+ all fields are requested. See the Torrent class for more information.
+
+ Returns a Torrent object with the requested fields.
+ """
+ if not arguments:
+ arguments = self.torrent_get_arguments
+ torrent_id = parse_torrent_id(torrent_id)
+ if torrent_id is None:
+ raise ValueError("Invalid id")
+ result = self._request('torrent-get', {'fields': arguments}, torrent_id, require_ids=True, timeout=timeout)
+ if torrent_id in result:
+ return result[torrent_id]
+ else:
+ for torrent in result.values():
+ if torrent.hashString == torrent_id:
+ return torrent
+ raise KeyError("Torrent not found in result")
+
+ def get_torrents(self, ids=None, arguments=None, timeout=None):
+ """
+ Get information for torrents with provided ids. For more information see get_torrent.
+
+ Returns a list of Torrent object.
+ """
+ if not arguments:
+ arguments = self.torrent_get_arguments
+ return list(self._request('torrent-get', {'fields': arguments}, ids, timeout=timeout).values())
+
+ def info(self, ids=None, arguments=None, timeout=None):
+ """
+
+ .. WARNING::
+ Deprecated, please use get_torrent or get_torrents. Please note that the return argument has changed in
+ the new methods. info returns a dictionary indexed by torrent id.
+ """
+ warnings.warn('info has been deprecated, please use get_torrent or get_torrents instead.', DeprecationWarning)
+ if not arguments:
+ arguments = self.torrent_get_arguments
+ return self._request('torrent-get', {'fields': arguments}, ids, timeout=timeout)
+
+ def list(self, timeout=None):
+ """
+
+ .. WARNING::
+ Deprecated, please use get_torrent or get_torrents. Please note that the return argument has changed in
+ the new methods. list returns a dictionary indexed by torrent id.
+ """
+ warnings.warn('list has been deprecated, please use get_torrent or get_torrents instead.', DeprecationWarning)
+ fields = ['id', 'hashString', 'name', 'sizeWhenDone', 'leftUntilDone'
+ , 'eta', 'status', 'rateUpload', 'rateDownload', 'uploadedEver'
+ , 'downloadedEver', 'uploadRatio', 'queuePosition']
+ return self._request('torrent-get', {'fields': fields}, 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 dictionary
+ for each requested torrent id holding the information about the files.
+
+ ::
+
+ {
+ : {
+ : {
+ 'name': ,
+ 'size': ,
+ 'completed': ,
+ 'priority': ,
+ 'selected':
+ }
+
+ ...
+ }
+
+ ...
+ }
+ """
+ fields = ['id', 'name', 'hashString', 'files', 'priorities', 'wanted']
+ request_result = self._request('torrent-get', {'fields': fields}, ids, timeout=timeout)
+ result = {}
+ for tid, torrent in iteritems(request_result):
+ result[tid] = torrent.files()
+ return result
+
+ def set_files(self, items, timeout=None):
+ """
+ Set file properties. Takes a dictionary with similar contents as the result
+ of `get_files`.
+
+ ::
+
+ {
+ : {
+ : {
+ 'priority': ,
+ 'selected':
+ }
+
+ ...
+ }
+
+ ...
+ }
+ """
+ if not isinstance(items, dict):
+ raise ValueError('Invalid file description')
+ for tid, files in iteritems(items):
+ if not isinstance(files, dict):
+ continue
+ wanted = []
+ unwanted = []
+ high = []
+ normal = []
+ low = []
+ for fid, file_desc in iteritems(files):
+ 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':
+ high.append(fid)
+ elif file_desc['priority'] == 'normal':
+ normal.append(fid)
+ elif file_desc['priority'] == 'low':
+ low.append(fid)
+ args = {
+ 'timeout': timeout
+ }
+ if len(high) > 0:
+ args['priority_high'] = high
+ if len(normal) > 0:
+ args['priority_normal'] = normal
+ if len(low) > 0:
+ args['priority_low'] = low
+ if len(wanted) > 0:
+ args['files_wanted'] = wanted
+ if len(unwanted) > 0:
+ args['files_unwanted'] = unwanted
+ self.change_torrent([tid], **args)
+
+ def change_torrent(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.
+ ``location`` 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.
+ ``queuePosition`` 14 - Position of this transfer in its queue.
+ ``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 iteritems(kwargs):
+ 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 change(self, ids, timeout=None, **kwargs):
+ """
+
+ .. WARNING::
+ Deprecated, please use change_torrent.
+ """
+ warnings.warn('change has been deprecated, please use change_torrent instead.', DeprecationWarning)
+ self.change_torrent(ids, timeout, **kwargs)
+
+ def move_torrent_data(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 move(self, ids, location, timeout=None):
+ """
+
+ .. WARNING::
+ Deprecated, please use move_torrent_data.
+ """
+ warnings.warn('move has been deprecated, please use move_torrent_data instead.', DeprecationWarning)
+ self.move_torrent_data(ids, location, timeout)
+
+ def locate_torrent_data(self, ids, location, timeout=None):
+ """Locate torrent data at the provided location."""
+ self._rpc_version_warning(6)
+ args = {'location': location, 'move': False}
+ self._request('torrent-set-location', args, ids, True, timeout=timeout)
+
+ def locate(self, ids, location, timeout=None):
+ """
+
+ .. WARNING::
+ Deprecated, please use locate_torrent_data.
+ """
+ warnings.warn('locate has been deprecated, please use locate_torrent_data instead.', DeprecationWarning)
+ self.locate_torrent_data(ids, location, timeout)
+
+ def rename_torrent_path(self, torrent_id, location, name, timeout=None):
+ """
+ Rename directory and/or files for torrent.
+ Remember to use get_torrent or get_torrents to update your file information.
+ """
+ self._rpc_version_warning(15)
+ torrent_id = parse_torrent_id(torrent_id)
+ if torrent_id is None:
+ raise ValueError("Invalid id")
+ dirname = os.path.dirname(name)
+ if len(dirname) > 0:
+ raise ValueError("Target name cannot contain a path delimiter")
+ args = {'path': location, 'name': name}
+ result = self._request('torrent-rename-path', args, torrent_id, True, timeout=timeout)
+ return (result['path'], result['name'])
+
+ def queue_top(self, ids, timeout=None):
+ """Move transfer to the top of the queue."""
+ self._rpc_version_warning(14)
+ self._request('queue-move-top', ids=ids, require_ids=True, timeout=timeout)
+
+ def queue_bottom(self, ids, timeout=None):
+ """Move transfer to the bottom of the queue."""
+ self._rpc_version_warning(14)
+ self._request('queue-move-bottom', ids=ids, require_ids=True, timeout=timeout)
+
+ def queue_up(self, ids, timeout=None):
+ """Move transfer up in the queue."""
+ self._rpc_version_warning(14)
+ self._request('queue-move-up', ids=ids, require_ids=True, timeout=timeout)
+
+ def queue_down(self, ids, timeout=None):
+ """Move transfer down in the queue."""
+ self._rpc_version_warning(14)
+ self._request('queue-move-down', ids=ids, require_ids=True, timeout=timeout)
+
+ def get_session(self, timeout=None):
+ """
+ Get session parameters. See the Session class for more information.
+ """
+ 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
+ ``blocklist_url`` 11 - Location of the block list. Updated with blocklist-update.
+ ``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.
+ ``download_queue_enabled`` 14 - Enables download queue.
+ ``download_queue_size`` 14 - Number of slots in the download queue.
+ ``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.
+ ``queue_stalled_enabled`` 14 - Enable tracking of stalled transfers.
+ ``queue_stalled_minutes`` 14 - Number of minutes of idle that marks a transfer as stalled.
+ ``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.
+ ``seed_queue_enabled`` 14 - Enables upload queue.
+ ``seed_queue_size`` 14 - Number of slots in the upload queue.
+ ``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.
+ ``utp_enabled`` 13 - Enables Micro Transport Protocol (UTP).
+ ================================ ===== ================= ==========================================================================================================================
+
+ .. NOTE::
+ transmissionrpc will try to automatically fix argument errors.
+ """
+ args = {}
+ for key, value in iteritems(kwargs):
+ 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 free_space(self, path, timeout=None):
+ """
+ Get the ammount of free space (in bytes) at the provided location.
+ """
+ self._rpc_version_warning(15)
+ result = self._request('free-space', {'path': path}, timeout=timeout)
+ if result['path'] == path:
+ return result['size-bytes']
+ return None
+
+ def session_stats(self, timeout=None):
+ """Get session statistics"""
+ self._request('session-stats', timeout=timeout)
+ return self.session
diff --git a/resources/lib/transmissionrpc/constants.py b/resources/lib/transmissionrpc/constants.py
old mode 100644
new mode 100755
index 8661da3..2ec370d
--- a/resources/lib/transmissionrpc/constants.py
+++ b/resources/lib/transmissionrpc/constants.py
@@ -1,291 +1,295 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2008-2011 Erik Svensson
-# Licensed under the MIT license.
-
-import logging
-
-LOGGER = logging.getLogger('transmissionrpc')
-LOGGER.setLevel(logging.ERROR)
-
-def mirror_dict(source):
- """
- 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_TIMEOUT = 30.0
-
-TR_PRI_LOW = -1
-TR_PRI_NORMAL = 0
-TR_PRI_HIGH = 1
-
-PRIORITY = mirror_dict({
- 'low' : TR_PRI_LOW,
- 'normal' : TR_PRI_NORMAL,
- 'high' : TR_PRI_HIGH
-})
-
-TR_RATIOLIMIT_GLOBAL = 0 # follow the global settings
-TR_RATIOLIMIT_SINGLE = 1 # override the global settings, seeding until a certain ratio
-TR_RATIOLIMIT_UNLIMITED = 2 # override the global settings, seeding regardless of ratio
-
-RATIO_LIMIT = mirror_dict({
- 'global' : TR_RATIOLIMIT_GLOBAL,
- 'single' : TR_RATIOLIMIT_SINGLE,
- '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
-# These maps are used to verify *-set methods. The information is structured in
-# a tree.
-# set +- - [, , , , , ]
-# | +- - [, , , , , ]
-# |
-# get +- - [, , , , , ]
-# +- - [, , , , , ]
-
-# Arguments for torrent methods
-TORRENT_ARGS = {
- 'get' : {
- 'activityDate': ('number', 1, None, None, None, ''),
- 'addedDate': ('number', 1, None, None, None, ''),
- 'announceResponse': ('string', 1, 7, None, None, ''),
- 'announceURL': ('string', 1, 7, None, None, ''),
- 'bandwidthPriority': ('number', 5, None, None, None, ''),
- 'comment': ('string', 1, None, None, None, ''),
- 'corruptEver': ('number', 1, None, None, None, ''),
- 'creator': ('string', 1, None, None, None, ''),
- 'dateCreated': ('number', 1, None, None, None, ''),
- 'desiredAvailable': ('number', 1, None, None, None, ''),
- 'doneDate': ('number', 1, None, None, None, ''),
- 'downloadDir': ('string', 4, None, None, None, ''),
- 'downloadedEver': ('number', 1, None, None, None, ''),
- 'downloaders': ('number', 4, 7, None, None, ''),
- 'downloadLimit': ('number', 1, None, None, None, ''),
- 'downloadLimited': ('boolean', 5, None, None, None, ''),
- 'downloadLimitMode': ('number', 1, 5, None, None, ''),
- 'error': ('number', 1, None, None, None, ''),
- 'errorString': ('number', 1, None, None, None, ''),
- 'eta': ('number', 1, None, None, None, ''),
- 'files': ('array', 1, None, None, None, ''),
- 'fileStats': ('array', 5, None, None, None, ''),
- 'hashString': ('string', 1, None, None, None, ''),
- 'haveUnchecked': ('number', 1, None, None, None, ''),
- 'haveValid': ('number', 1, None, None, None, ''),
- 'honorsSessionLimits': ('boolean', 5, None, None, None, ''),
- 'id': ('number', 1, None, None, None, ''),
- 'isFinished': ('boolean', 9, None, None, None, ''),
- 'isPrivate': ('boolean', 1, None, None, None, ''),
- 'isStalled': ('boolean', 14, None, None, None, ''),
- 'lastAnnounceTime': ('number', 1, 7, None, None, ''),
- 'lastScrapeTime': ('number', 1, 7, None, None, ''),
- 'leechers': ('number', 1, 7, None, None, ''),
- 'leftUntilDone': ('number', 1, None, None, None, ''),
- 'magnetLink': ('string', 7, None, None, None, ''),
- 'manualAnnounceTime': ('number', 1, None, None, None, ''),
- 'maxConnectedPeers': ('number', 1, None, None, None, ''),
- 'metadataPercentComplete': ('number', 7, None, None, None, ''),
- 'name': ('string', 1, None, None, None, ''),
- 'nextAnnounceTime': ('number', 1, 7, None, None, ''),
- 'nextScrapeTime': ('number', 1, 7, None, None, ''),
- 'peer-limit': ('number', 5, None, None, None, ''),
- 'peers': ('array', 2, None, None, None, ''),
- 'peersConnected': ('number', 1, None, None, None, ''),
- 'peersFrom': ('object', 1, None, None, None, ''),
- 'peersGettingFromUs': ('number', 1, None, None, None, ''),
- 'peersKnown': ('number', 1, 13, None, None, ''),
- 'peersSendingToUs': ('number', 1, None, None, None, ''),
- 'percentDone': ('double', 5, None, None, None, ''),
- 'pieces': ('string', 5, None, None, None, ''),
- 'pieceCount': ('number', 1, None, None, None, ''),
- 'pieceSize': ('number', 1, None, None, None, ''),
- 'priorities': ('array', 1, None, None, None, ''),
- 'queuePosition': ('number', 14, None, None, None, ''),
- 'rateDownload': ('number', 1, None, None, None, ''),
- 'rateUpload': ('number', 1, None, None, None, ''),
- 'recheckProgress': ('double', 1, None, None, None, ''),
- 'scrapeResponse': ('string', 1, 7, None, None, ''),
- 'scrapeURL': ('string', 1, 7, None, None, ''),
- 'seeders': ('number', 1, 7, None, None, ''),
- 'seedIdleLimit': ('number', 10, None, None, None, ''),
- 'seedIdleMode': ('number', 10, None, None, None, ''),
- 'seedRatioLimit': ('double', 5, None, None, None, ''),
- 'seedRatioMode': ('number', 5, None, None, None, ''),
- 'sizeWhenDone': ('number', 1, None, None, None, ''),
- 'startDate': ('number', 1, None, None, None, ''),
- 'status': ('number', 1, None, None, None, ''),
- 'swarmSpeed': ('number', 1, 7, None, None, ''),
- 'timesCompleted': ('number', 1, 7, None, None, ''),
- 'trackers': ('array', 1, None, None, None, ''),
- 'trackerStats': ('object', 7, None, None, None, ''),
- 'totalSize': ('number', 1, None, None, None, ''),
- 'torrentFile': ('string', 5, None, None, None, ''),
- 'uploadedEver': ('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': {
- 'bandwidthPriority': ('number', 5, None, None, None, 'Priority for this transfer.'),
- '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, 'Enable download speed limiter.'),
- 'files-wanted': ('array', 1, None, None, None, "A list of file id's that should be downloaded."),
- 'files-unwanted': ('array', 1, None, None, None, "A list of file id's that shouldn't be downloaded."),
- 'honorsSessionLimits': ('boolean', 5, None, None, None, "Enables or disables the transfer to honour the upload limit set in the session."),
- 'location': ('array', 1, None, None, None, 'Local download location.'),
- 'peer-limit': ('number', 1, None, None, None, 'The peer limit for the torrents.'),
- '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, "A list of file id's that should have normal priority."),
- 'priority-normal': ('array', 1, None, None, None, "A list of file id's that should have low priority."),
- 'queuePosition': ('number', 14, None, None, None, 'Position of this transfer in its queue.'),
- 'seedIdleLimit': ('number', 10, None, None, None, 'Seed inactivity limit in minutes.'),
- 'seedIdleMode': ('number', 10, None, None, None, 'Seed inactivity mode. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.'),
- 'seedRatioLimit': ('double', 5, None, None, None, 'Seeding ratio.'),
- 'seedRatioMode': ('number', 5, None, None, None, 'Which ratio to use. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.'),
- 'speed-limit-down': ('number', 1, 5, None, 'downloadLimit', 'Set the speed limit for download in Kib/s.'),
- 'speed-limit-down-enabled': ('boolean', 1, 5, None, 'downloadLimited', 'Enable download speed limiter.'),
- 'speed-limit-up': ('number', 1, 5, None, 'uploadLimit', 'Set the speed limit for upload in Kib/s.'),
- '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': {
- 'bandwidthPriority': ('number', 8, None, None, None, 'Priority for this transfer.'),
- 'download-dir': ('string', 1, None, None, None, 'The directory where the downloaded contents will be saved in.'),
- 'cookies': ('string', 13, None, None, None, 'One or more HTTP cookie(s).'),
- 'filename': ('string', 1, None, None, None, "A file path or URL to a torrent file or a magnet link."),
- 'files-wanted': ('array', 1, None, None, None, "A list of file id's that should be downloaded."),
- 'files-unwanted': ('array', 1, None, None, None, "A list of file id's that shouldn't be downloaded."),
- 'metainfo': ('string', 1, None, None, None, 'The content of a torrent file, base64 encoded.'),
- 'paused': ('boolean', 1, None, None, None, 'If True, does not start the transfer when added.'),
- 'peer-limit': ('number', 1, None, None, None, 'Maximum number of peers allowed.'),
- '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, "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
-SESSION_ARGS = {
- 'get': {
- "alt-speed-down": ('number', 5, None, None, None, ''),
- "alt-speed-enabled": ('boolean', 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-end": ('number', 5, None, None, None, ''),
- "alt-speed-time-day": ('number', 5, None, None, None, ''),
- "alt-speed-up": ('number', 5, None, None, None, ''),
- "blocklist-enabled": ('boolean', 5, None, None, None, ''),
- "blocklist-size": ('number', 5, None, None, None, ''),
- "blocklist-url": ('string', 11, None, None, None, ''),
- "cache-size-mb": ('number', 10, None, None, None, ''),
- "config-dir": ('string', 8, None, None, None, ''),
- "dht-enabled": ('boolean', 6, None, None, None, ''),
- "download-dir": ('string', 1, None, None, None, ''),
- "download-dir-free-space": ('number', 12, None, None, None, ''),
- "download-queue-size": ('number', 14, None, None, None, ''),
- "download-queue-enabled": ('boolean', 14, None, None, None, ''),
- "encryption": ('string', 1, None, None, None, ''),
- "idle-seeding-limit": ('number', 10, None, None, None, ''),
- "idle-seeding-limit-enabled": ('boolean', 10, None, None, None, ''),
- "incomplete-dir": ('string', 7, None, None, None, ''),
- "incomplete-dir-enabled": ('boolean', 7, None, None, None, ''),
- "lpd-enabled": ('boolean', 9, None, None, None, ''),
- "peer-limit": ('number', 1, 5, None, None, ''),
- "peer-limit-global": ('number', 5, None, None, None, ''),
- "peer-limit-per-torrent": ('number', 5, None, None, None, ''),
- "pex-allowed": ('boolean', 1, 5, None, None, ''),
- "pex-enabled": ('boolean', 5, None, None, None, ''),
- "port": ('number', 1, 5, None, None, ''),
- "peer-port": ('number', 5, None, None, None, ''),
- "peer-port-random-on-start": ('boolean', 5, None, None, None, ''),
- "port-forwarding-enabled": ('boolean', 1, None, None, None, ''),
- "queue-stalled-minutes": ('number', 14, None, None, None, ''),
- "queue-stalled-enabled": ('boolean', 14, 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, ''),
- "seed-queue-size": ('number', 14, None, None, None, ''),
- "seed-queue-enabled": ('boolean', 14, 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, ''),
- 'utp-enabled': ('boolean', 13, None, None, None, ''),
- "version": ('string', 3, None, None, None, ''),
- },
- 'set': {
- "alt-speed-down": ('number', 5, None, None, None, 'Alternate session download speed limit (in Kib/s).'),
- "alt-speed-enabled": ('boolean', 5, None, None, None, 'Enables alternate global download speed limiter.'),
- "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, 'Enables alternate speeds scheduling.'),
- "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, 'Enables alternate speeds scheduling these days.'),
- "alt-speed-up": ('number', 5, None, None, None, 'Alternate session upload speed limit (in Kib/s).'),
- "blocklist-enabled": ('boolean', 5, None, None, None, 'Enables the block list'),
- "blocklist-url": ('string', 11, None, None, None, 'Location of the block list. Updated with blocklist-update.'),
- "cache-size-mb": ('number', 10, None, None, None, 'The maximum size of the disk cache in MB'),
- "dht-enabled": ('boolean', 6, None, None, None, 'Enables DHT.'),
- "download-dir": ('string', 1, None, None, None, 'Set the session download directory.'),
- "download-queue-size": ('number', 14, None, None, None, 'Number of parallel downloads.'),
- "download-queue-enabled": ('boolean', 14, None, None, None, 'Enable parallel download restriction.'),
- "encryption": ('string', 1, None, None, None, 'Set the session encryption mode, one of ``required``, ``preferred`` or ``tolerated``.'),
- "idle-seeding-limit": ('number', 10, None, None, None, 'The default seed inactivity limit in minutes.'),
- "idle-seeding-limit-enabled": ('boolean', 10, None, None, None, 'Enables the default seed inactivity limit'),
- "incomplete-dir": ('string', 7, None, None, None, 'The path to the directory of incomplete transfer data.'),
- "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.'),
- "lpd-enabled": ('boolean', 9, None, None, None, 'Enables local peer discovery for public torrents.'),
- "peer-limit": ('number', 1, 5, None, 'peer-limit-global', 'Maximum number of peers'),
- "peer-limit-global": ('number', 5, None, 'peer-limit', None, 'Maximum number of peers'),
- "peer-limit-per-torrent": ('number', 5, None, None, None, 'Maximum number of peers per transfer'),
- "pex-allowed": ('boolean', 1, 5, None, 'pex-enabled', 'Allowing PEX in public torrents.'),
- "pex-enabled": ('boolean', 5, None, 'pex-allowed', None, 'Allowing PEX in public torrents.'),
- "port": ('number', 1, 5, None, 'peer-port', 'Peer port.'),
- "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'),
- "queue-stalled-minutes": ('number', 14, None, None, None, 'Number of minutes of idle that marks a transfer as stalled.'),
- "queue-stalled-enabled": ('boolean', 14, None, None, None, 'Enable tracking of stalled transfers.'),
- "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.'),
- "seed-queue-size": ('number', 14, None, None, None, 'Number of parallel uploads.'),
- "seed-queue-enabled": ('boolean', 14, None, None, None, 'Enable parallel upload restriction.'),
- "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.'),
- 'utp-enabled': ('boolean', 13, None, None, None, 'Enables Micro Transport Protocol (UTP).'),
- },
-}
+# -*- coding: utf-8 -*-
+# Copyright (c) 2008-2013 Erik Svensson
+# Licensed under the MIT license.
+
+import logging
+from six import iteritems
+
+LOGGER = logging.getLogger('transmissionrpc')
+LOGGER.setLevel(logging.ERROR)
+
+def mirror_dict(source):
+ """
+ Creates a dictionary with all values as keys and all keys as values.
+ """
+ source.update(dict((value, key) for key, value in iteritems(source)))
+ return source
+
+DEFAULT_PORT = 9091
+
+DEFAULT_TIMEOUT = 30.0
+
+TR_PRI_LOW = -1
+TR_PRI_NORMAL = 0
+TR_PRI_HIGH = 1
+
+PRIORITY = mirror_dict({
+ 'low' : TR_PRI_LOW,
+ 'normal' : TR_PRI_NORMAL,
+ 'high' : TR_PRI_HIGH
+})
+
+TR_RATIOLIMIT_GLOBAL = 0 # follow the global settings
+TR_RATIOLIMIT_SINGLE = 1 # override the global settings, seeding until a certain ratio
+TR_RATIOLIMIT_UNLIMITED = 2 # override the global settings, seeding regardless of ratio
+
+RATIO_LIMIT = mirror_dict({
+ 'global' : TR_RATIOLIMIT_GLOBAL,
+ 'single' : TR_RATIOLIMIT_SINGLE,
+ '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
+# These maps are used to verify *-set methods. The information is structured in
+# a tree.
+# set +- - [, , , , , ]
+# | +- - [, , , , , ]
+# |
+# get +- - [, , , , , ]
+# +- - [, , , , , ]
+
+# Arguments for torrent methods
+TORRENT_ARGS = {
+ 'get' : {
+ 'activityDate': ('number', 1, None, None, None, 'Last time of upload or download activity.'),
+ 'addedDate': ('number', 1, None, None, None, 'The date when this torrent was first added.'),
+ 'announceResponse': ('string', 1, 7, None, None, 'The announce message from the tracker.'),
+ 'announceURL': ('string', 1, 7, None, None, 'Current announce URL.'),
+ 'bandwidthPriority': ('number', 5, None, None, None, 'Bandwidth priority. Low (-1), Normal (0) or High (1).'),
+ 'comment': ('string', 1, None, None, None, 'Torrent comment.'),
+ 'corruptEver': ('number', 1, None, None, None, 'Number of bytes of corrupt data downloaded.'),
+ 'creator': ('string', 1, None, None, None, 'Torrent creator.'),
+ 'dateCreated': ('number', 1, None, None, None, 'Torrent creation date.'),
+ 'desiredAvailable': ('number', 1, None, None, None, 'Number of bytes avalable and left to be downloaded.'),
+ 'doneDate': ('number', 1, None, None, None, 'The date when the torrent finished downloading.'),
+ 'downloadDir': ('string', 4, None, None, None, 'The directory path where the torrent is downloaded to.'),
+ 'downloadedEver': ('number', 1, None, None, None, 'Number of bytes of good data downloaded.'),
+ 'downloaders': ('number', 4, 7, None, None, 'Number of downloaders.'),
+ 'downloadLimit': ('number', 1, None, None, None, 'Download limit in Kbps.'),
+ 'downloadLimited': ('boolean', 5, None, None, None, 'Download limit is enabled'),
+ 'downloadLimitMode': ('number', 1, 5, None, None, 'Download limit mode. 0 means global, 1 means signle, 2 unlimited.'),
+ 'error': ('number', 1, None, None, None, 'Kind of error. 0 means OK, 1 means tracker warning, 2 means tracker error, 3 means local error.'),
+ 'errorString': ('number', 1, None, None, None, 'Error message.'),
+ 'eta': ('number', 1, None, None, None, 'Estimated number of seconds left when downloading or seeding. -1 means not available and -2 means unknown.'),
+ 'etaIdle': ('number', 15, None, None, None, 'Estimated number of seconds left until the idle time limit is reached. -1 means not available and -2 means unknown.'),
+ 'files': ('array', 1, None, None, None, 'Array of file object containing key, bytesCompleted, length and name.'),
+ 'fileStats': ('array', 5, None, None, None, 'Aray of file statistics containing bytesCompleted, wanted and priority.'),
+ 'hashString': ('string', 1, None, None, None, 'Hashstring unique for the torrent even between sessions.'),
+ 'haveUnchecked': ('number', 1, None, None, None, 'Number of bytes of partial pieces.'),
+ 'haveValid': ('number', 1, None, None, None, 'Number of bytes of checksum verified data.'),
+ 'honorsSessionLimits': ('boolean', 5, None, None, None, 'True if session upload limits are honored'),
+ 'id': ('number', 1, None, None, None, 'Session unique torrent id.'),
+ 'isFinished': ('boolean', 9, None, None, None, 'True if the torrent is finished. Downloaded and seeded.'),
+ 'isPrivate': ('boolean', 1, None, None, None, 'True if the torrent is private.'),
+ 'isStalled': ('boolean', 14, None, None, None, 'True if the torrent has stalled (been idle for a long time).'),
+ 'lastAnnounceTime': ('number', 1, 7, None, None, 'The time of the last announcement.'),
+ 'lastScrapeTime': ('number', 1, 7, None, None, 'The time af the last successful scrape.'),
+ 'leechers': ('number', 1, 7, None, None, 'Number of leechers.'),
+ 'leftUntilDone': ('number', 1, None, None, None, 'Number of bytes left until the download is done.'),
+ 'magnetLink': ('string', 7, None, None, None, 'The magnet link for this torrent.'),
+ 'manualAnnounceTime': ('number', 1, None, None, None, 'The time until you manually ask for more peers.'),
+ 'maxConnectedPeers': ('number', 1, None, None, None, 'Maximum of connected peers.'),
+ 'metadataPercentComplete': ('number', 7, None, None, None, 'Download progress of metadata. 0.0 to 1.0.'),
+ 'name': ('string', 1, None, None, None, 'Torrent name.'),
+ 'nextAnnounceTime': ('number', 1, 7, None, None, 'Next announce time.'),
+ 'nextScrapeTime': ('number', 1, 7, None, None, 'Next scrape time.'),
+ 'peer-limit': ('number', 5, None, None, None, 'Maximum number of peers.'),
+ 'peers': ('array', 2, None, None, None, 'Array of peer objects.'),
+ 'peersConnected': ('number', 1, None, None, None, 'Number of peers we are connected to.'),
+ 'peersFrom': ('object', 1, None, None, None, 'Object containing download peers counts for different peer types.'),
+ 'peersGettingFromUs': ('number', 1, None, None, None, 'Number of peers we are sending data to.'),
+ 'peersKnown': ('number', 1, 13, None, None, 'Number of peers that the tracker knows.'),
+ 'peersSendingToUs': ('number', 1, None, None, None, 'Number of peers sending to us'),
+ 'percentDone': ('double', 5, None, None, None, 'Download progress of selected files. 0.0 to 1.0.'),
+ 'pieces': ('string', 5, None, None, None, 'String with base64 encoded bitfield indicating finished pieces.'),
+ 'pieceCount': ('number', 1, None, None, None, 'Number of pieces.'),
+ 'pieceSize': ('number', 1, None, None, None, 'Number of bytes in a piece.'),
+ 'priorities': ('array', 1, None, None, None, 'Array of file priorities.'),
+ 'queuePosition': ('number', 14, None, None, None, 'The queue position.'),
+ 'rateDownload': ('number', 1, None, None, None, 'Download rate in bps.'),
+ 'rateUpload': ('number', 1, None, None, None, 'Upload rate in bps.'),
+ 'recheckProgress': ('double', 1, None, None, None, 'Progress of recheck. 0.0 to 1.0.'),
+ 'secondsDownloading': ('number', 15, None, None, None, ''),
+ 'secondsSeeding': ('number', 15, None, None, None, ''),
+ 'scrapeResponse': ('string', 1, 7, None, None, 'Scrape response message.'),
+ 'scrapeURL': ('string', 1, 7, None, None, 'Current scrape URL'),
+ 'seeders': ('number', 1, 7, None, None, 'Number of seeders reported by the tracker.'),
+ 'seedIdleLimit': ('number', 10, None, None, None, 'Idle limit in minutes.'),
+ 'seedIdleMode': ('number', 10, None, None, None, 'Use global (0), torrent (1), or unlimited (2) limit.'),
+ 'seedRatioLimit': ('double', 5, None, None, None, 'Seed ratio limit.'),
+ 'seedRatioMode': ('number', 5, None, None, None, 'Use global (0), torrent (1), or unlimited (2) limit.'),
+ 'sizeWhenDone': ('number', 1, None, None, None, 'Size of the torrent download in bytes.'),
+ 'startDate': ('number', 1, None, None, None, 'The date when the torrent was last started.'),
+ 'status': ('number', 1, None, None, None, 'Current status, see source'),
+ 'swarmSpeed': ('number', 1, 7, None, None, 'Estimated speed in Kbps in the swarm.'),
+ 'timesCompleted': ('number', 1, 7, None, None, 'Number of successful downloads reported by the tracker.'),
+ 'trackers': ('array', 1, None, None, None, 'Array of tracker objects.'),
+ 'trackerStats': ('object', 7, None, None, None, 'Array of object containing tracker statistics.'),
+ 'totalSize': ('number', 1, None, None, None, 'Total size of the torrent in bytes'),
+ 'torrentFile': ('string', 5, None, None, None, 'Path to .torrent file.'),
+ 'uploadedEver': ('number', 1, None, None, None, 'Number of bytes uploaded, ever.'),
+ 'uploadLimit': ('number', 1, None, None, None, 'Upload limit in Kbps'),
+ 'uploadLimitMode': ('number', 1, 5, None, None, 'Upload limit mode. 0 means global, 1 means signle, 2 unlimited.'),
+ 'uploadLimited': ('boolean', 5, None, None, None, 'Upload limit enabled.'),
+ 'uploadRatio': ('double', 1, None, None, None, 'Seed ratio.'),
+ 'wanted': ('array', 1, None, None, None, 'Array of booleans indicated wanted files.'),
+ 'webseeds': ('array', 1, None, None, None, 'Array of webseeds objects'),
+ 'webseedsSendingToUs': ('number', 1, None, None, None, 'Number of webseeds seeding to us.'),
+ },
+ 'set': {
+ 'bandwidthPriority': ('number', 5, None, None, None, 'Priority for this transfer.'),
+ '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, 'Enable download speed limiter.'),
+ 'files-wanted': ('array', 1, None, None, None, "A list of file id's that should be downloaded."),
+ 'files-unwanted': ('array', 1, None, None, None, "A list of file id's that shouldn't be downloaded."),
+ 'honorsSessionLimits': ('boolean', 5, None, None, None, "Enables or disables the transfer to honour the upload limit set in the session."),
+ 'location': ('array', 1, None, None, None, 'Local download location.'),
+ 'peer-limit': ('number', 1, None, None, None, 'The peer limit for the torrents.'),
+ '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, "A list of file id's that should have normal priority."),
+ 'priority-normal': ('array', 1, None, None, None, "A list of file id's that should have low priority."),
+ 'queuePosition': ('number', 14, None, None, None, 'Position of this transfer in its queue.'),
+ 'seedIdleLimit': ('number', 10, None, None, None, 'Seed inactivity limit in minutes.'),
+ 'seedIdleMode': ('number', 10, None, None, None, 'Seed inactivity mode. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.'),
+ 'seedRatioLimit': ('double', 5, None, None, None, 'Seeding ratio.'),
+ 'seedRatioMode': ('number', 5, None, None, None, 'Which ratio to use. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit.'),
+ 'speed-limit-down': ('number', 1, 5, None, 'downloadLimit', 'Set the speed limit for download in Kib/s.'),
+ 'speed-limit-down-enabled': ('boolean', 1, 5, None, 'downloadLimited', 'Enable download speed limiter.'),
+ 'speed-limit-up': ('number', 1, 5, None, 'uploadLimit', 'Set the speed limit for upload in Kib/s.'),
+ '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': {
+ 'bandwidthPriority': ('number', 8, None, None, None, 'Priority for this transfer.'),
+ 'download-dir': ('string', 1, None, None, None, 'The directory where the downloaded contents will be saved in.'),
+ 'cookies': ('string', 13, None, None, None, 'One or more HTTP cookie(s).'),
+ 'filename': ('string', 1, None, None, None, "A file path or URL to a torrent file or a magnet link."),
+ 'files-wanted': ('array', 1, None, None, None, "A list of file id's that should be downloaded."),
+ 'files-unwanted': ('array', 1, None, None, None, "A list of file id's that shouldn't be downloaded."),
+ 'metainfo': ('string', 1, None, None, None, 'The content of a torrent file, base64 encoded.'),
+ 'paused': ('boolean', 1, None, None, None, 'If True, does not start the transfer when added.'),
+ 'peer-limit': ('number', 1, None, None, None, 'Maximum number of peers allowed.'),
+ '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, "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
+SESSION_ARGS = {
+ 'get': {
+ "alt-speed-down": ('number', 5, None, None, None, 'Alternate session download speed limit (in Kib/s).'),
+ "alt-speed-enabled": ('boolean', 5, None, None, None, 'True if alternate global download speed limiter is ebabled.'),
+ "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, 'True if alternate speeds scheduling is enabled.'),
+ "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, 'Days alternate speeds scheduling is enabled.'),
+ "alt-speed-up": ('number', 5, None, None, None, 'Alternate session upload speed limit (in Kib/s)'),
+ "blocklist-enabled": ('boolean', 5, None, None, None, 'True when blocklist is enabled.'),
+ "blocklist-size": ('number', 5, None, None, None, 'Number of rules in the blocklist'),
+ "blocklist-url": ('string', 11, None, None, None, 'Location of the block list. Updated with blocklist-update.'),
+ "cache-size-mb": ('number', 10, None, None, None, 'The maximum size of the disk cache in MB'),
+ "config-dir": ('string', 8, None, None, None, 'location of transmissions configuration directory'),
+ "dht-enabled": ('boolean', 6, None, None, None, 'True if DHT enabled.'),
+ "download-dir": ('string', 1, None, None, None, 'The download directory.'),
+ "download-dir-free-space": ('number', 12, None, None, None, 'Free space in the download directory, in bytes'),
+ "download-queue-size": ('number', 14, None, None, None, 'Number of slots in the download queue.'),
+ "download-queue-enabled": ('boolean', 14, None, None, None, 'True if the download queue is enabled.'),
+ "encryption": ('string', 1, None, None, None, 'Encryption mode, one of ``required``, ``preferred`` or ``tolerated``.'),
+ "idle-seeding-limit": ('number', 10, None, None, None, 'Seed inactivity limit in minutes.'),
+ "idle-seeding-limit-enabled": ('boolean', 10, None, None, None, 'True if the seed activity limit is enabled.'),
+ "incomplete-dir": ('string', 7, None, None, None, 'The path to the directory for incomplete torrent transfer data.'),
+ "incomplete-dir-enabled": ('boolean', 7, None, None, None, 'True if the incomplete dir is enabled.'),
+ "lpd-enabled": ('boolean', 9, None, None, None, 'True if local peer discovery is enabled.'),
+ "peer-limit": ('number', 1, 5, None, 'peer-limit-global', 'Maximum number of peers.'),
+ "peer-limit-global": ('number', 5, None, 'peer-limit', None, 'Maximum number of peers.'),
+ "peer-limit-per-torrent": ('number', 5, None, None, None, 'Maximum number of peers per transfer.'),
+ "pex-allowed": ('boolean', 1, 5, None, 'pex-enabled', 'True if PEX is allowed.'),
+ "pex-enabled": ('boolean', 5, None, 'pex-allowed', None, 'True if PEX is enabled.'),
+ "port": ('number', 1, 5, None, 'peer-port', 'Peer port.'),
+ "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, 'True if port forwarding is enabled.'),
+ "queue-stalled-minutes": ('number', 14, None, None, None, 'Number of minutes of idle that marks a transfer as stalled.'),
+ "queue-stalled-enabled": ('boolean', 14, None, None, None, 'True if stalled tracking of transfers is enabled.'),
+ "rename-partial-files": ('boolean', 8, None, None, None, 'True if ".part" is appended to incomplete files'),
+ "rpc-version": ('number', 4, None, None, None, 'Transmission RPC API Version.'),
+ "rpc-version-minimum": ('number', 4, None, None, None, 'Minimum accepted RPC API Version.'),
+ "script-torrent-done-enabled": ('boolean', 9, None, None, None, 'True if the done script is enabled.'),
+ "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, 'True if seed ration limit is enabled.'),
+ "seed-queue-size": ('number', 14, None, None, None, 'Number of slots in the upload queue.'),
+ "seed-queue-enabled": ('boolean', 14, None, None, None, 'True if upload queue is enabled.'),
+ "speed-limit-down": ('number', 1, None, None, None, 'Download speed limit (in Kib/s).'),
+ "speed-limit-down-enabled": ('boolean', 1, None, None, None, 'True if the download speed is limited.'),
+ "speed-limit-up": ('number', 1, None, None, None, 'Upload speed limit (in Kib/s).'),
+ "speed-limit-up-enabled": ('boolean', 1, None, None, None, 'True if the upload speed is limited.'),
+ "start-added-torrents": ('boolean', 9, None, None, None, 'When true uploaded torrents will start right away.'),
+ "trash-original-torrent-files": ('boolean', 9, None, None, None, 'When true added .torrent files will be deleted.'),
+ 'units': ('object', 10, None, None, None, 'An object containing units for size and speed.'),
+ 'utp-enabled': ('boolean', 13, None, None, None, 'True if Micro Transport Protocol (UTP) is enabled.'),
+ "version": ('string', 3, None, None, None, 'Transmission version.'),
+ },
+ 'set': {
+ "alt-speed-down": ('number', 5, None, None, None, 'Alternate session download speed limit (in Kib/s).'),
+ "alt-speed-enabled": ('boolean', 5, None, None, None, 'Enables alternate global download speed limiter.'),
+ "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, 'Enables alternate speeds scheduling.'),
+ "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, 'Enables alternate speeds scheduling these days.'),
+ "alt-speed-up": ('number', 5, None, None, None, 'Alternate session upload speed limit (in Kib/s).'),
+ "blocklist-enabled": ('boolean', 5, None, None, None, 'Enables the block list'),
+ "blocklist-url": ('string', 11, None, None, None, 'Location of the block list. Updated with blocklist-update.'),
+ "cache-size-mb": ('number', 10, None, None, None, 'The maximum size of the disk cache in MB'),
+ "dht-enabled": ('boolean', 6, None, None, None, 'Enables DHT.'),
+ "download-dir": ('string', 1, None, None, None, 'Set the session download directory.'),
+ "download-queue-size": ('number', 14, None, None, None, 'Number of slots in the download queue.'),
+ "download-queue-enabled": ('boolean', 14, None, None, None, 'Enables download queue.'),
+ "encryption": ('string', 1, None, None, None, 'Set the session encryption mode, one of ``required``, ``preferred`` or ``tolerated``.'),
+ "idle-seeding-limit": ('number', 10, None, None, None, 'The default seed inactivity limit in minutes.'),
+ "idle-seeding-limit-enabled": ('boolean', 10, None, None, None, 'Enables the default seed inactivity limit'),
+ "incomplete-dir": ('string', 7, None, None, None, 'The path to the directory of incomplete transfer data.'),
+ "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.'),
+ "lpd-enabled": ('boolean', 9, None, None, None, 'Enables local peer discovery for public torrents.'),
+ "peer-limit": ('number', 1, 5, None, 'peer-limit-global', 'Maximum number of peers.'),
+ "peer-limit-global": ('number', 5, None, 'peer-limit', None, 'Maximum number of peers.'),
+ "peer-limit-per-torrent": ('number', 5, None, None, None, 'Maximum number of peers per transfer.'),
+ "pex-allowed": ('boolean', 1, 5, None, 'pex-enabled', 'Allowing PEX in public torrents.'),
+ "pex-enabled": ('boolean', 5, None, 'pex-allowed', None, 'Allowing PEX in public torrents.'),
+ "port": ('number', 1, 5, None, 'peer-port', 'Peer port.'),
+ "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'),
+ "queue-stalled-minutes": ('number', 14, None, None, None, 'Number of minutes of idle that marks a transfer as stalled.'),
+ "queue-stalled-enabled": ('boolean', 14, None, None, None, 'Enable tracking of stalled transfers.'),
+ "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.'),
+ "seed-queue-size": ('number', 14, None, None, None, 'Number of slots in the upload queue.'),
+ "seed-queue-enabled": ('boolean', 14, None, None, None, 'Enables upload queue.'),
+ "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.'),
+ 'utp-enabled': ('boolean', 13, None, None, None, 'Enables Micro Transport Protocol (UTP).'),
+ },
+}
diff --git a/resources/lib/transmissionrpc/error.py b/resources/lib/transmissionrpc/error.py
index 9b50e00..c003dad 100644
--- a/resources/lib/transmissionrpc/error.py
+++ b/resources/lib/transmissionrpc/error.py
@@ -1,52 +1,54 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2008-2011 Erik Svensson
-# Licensed under the MIT license.
-
-class TransmissionError(Exception):
- """
- This exception is raised when there has occurred 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 occurred 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 '' % (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)
+# -*- coding: utf-8 -*-
+# Copyright (c) 2008-2013 Erik Svensson
+# Licensed under the MIT license.
+
+from six import string_types, integer_types
+
+class TransmissionError(Exception):
+ """
+ This exception is raised when there has occurred 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 occurred 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, string_types):
+ self.url = httpurl
+ if isinstance(httpcode, integer_types):
+ self.code = httpcode
+ if isinstance(httpmsg, string_types):
+ self.message = httpmsg
+ if isinstance(httpheaders, dict):
+ self.headers = httpheaders
+ if isinstance(httpdata, string_types):
+ self.data = httpdata
+
+ def __repr__(self):
+ return '' % (self.code, self.message)
+
+ def __str__(self):
+ return 'HTTPHandlerError %d: %s' % (self.code, self.message)
+
+ def __unicode__(self):
+ return 'HTTPHandlerError %d: %s' % (self.code, self.message)
diff --git a/resources/lib/transmissionrpc/httphandler.py b/resources/lib/transmissionrpc/httphandler.py
index d34b781..a0807a5 100644
--- a/resources/lib/transmissionrpc/httphandler.py
+++ b/resources/lib/transmissionrpc/httphandler.py
@@ -1,72 +1,82 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2011 Erik Svensson
-# 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 is 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 horrendous!
- # 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()
+# -*- coding: utf-8 -*-
+# Copyright (c) 2011-2013 Erik Svensson
+# Licensed under the MIT license.
+
+import sys
+
+from transmissionrpc.error import HTTPHandlerError
+
+from six import PY3
+
+if PY3:
+ from urllib.request import Request, build_opener, \
+ HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, HTTPDigestAuthHandler
+ from urllib.error import HTTPError, URLError
+ from http.client import BadStatusLine
+else:
+ from urllib2 import Request, build_opener, \
+ HTTPPasswordMgrWithDefaultRealm, HTTPBasicAuthHandler, HTTPDigestAuthHandler
+ from urllib2 import HTTPError, URLError
+ from httplib import BadStatusLine
+
+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)
+ self.http_opener = build_opener()
+
+ def set_authentication(self, uri, login, password):
+ password_manager = HTTPPasswordMgrWithDefaultRealm()
+ password_manager.add_password(realm=None, uri=uri, user=login, passwd=password)
+ self.http_opener = build_opener(HTTPBasicAuthHandler(password_manager), HTTPDigestAuthHandler(password_manager))
+
+ def request(self, url, query, headers, timeout):
+ request = Request(url, query.encode('utf-8'), headers)
+ try:
+ if (sys.version_info[0] == 2 and sys.version_info[1] > 5) or sys.version_info[0] > 2:
+ response = self.http_opener.open(request, timeout=timeout)
+ else:
+ response = self.http_opener.open(request)
+ except HTTPError as error:
+ if error.fp is 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 URLError as error:
+ # urllib2.URLError documentation is horrendous!
+ # 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 BadStatusLine as error:
+ raise HTTPHandlerError(httpmsg='httplib.BadStatusLine: %s' % (error.line))
+ return response.read().decode('utf-8')
diff --git a/resources/lib/transmissionrpc/session.py b/resources/lib/transmissionrpc/session.py
index 3f71a2a..62c7cf6 100644
--- a/resources/lib/transmissionrpc/session.py
+++ b/resources/lib/transmissionrpc/session.py
@@ -1,44 +1,111 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2008-2011 Erik Svensson
-# 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 hyphen.
- ``download-dir`` -> ``download_dir``.
- """
-
- def __init__(self, fields=None):
- self.fields = {}
- if fields is not None:
- self.update(fields)
-
- def update(self, other):
- """Update the session data from a session arguments dictionary"""
-
- 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
+# -*- coding: utf-8 -*-
+# Copyright (c) 2008-2013 Erik Svensson
+# Licensed under the MIT license.
+
+from transmissionrpc.utils import Field
+
+from six import iteritems, integer_types
+
+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 hyphen.
+ ``download-dir`` -> ``download_dir``.
+ """
+
+ def __init__(self, client=None, fields=None):
+ self._client = client
+ self._fields = {}
+ if fields is not None:
+ self._update_fields(fields)
+
+ def __getattr__(self, name):
+ try:
+ return self._fields[name].value
+ 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].value)
+ return text
+
+ def _update_fields(self, other):
+ """
+ Update the session data from a Transmission JSON-RPC arguments dictionary
+ """
+ if isinstance(other, dict):
+ for key, value in iteritems(other):
+ self._fields[key.replace('-', '_')] = Field(value, False)
+ elif isinstance(other, Session):
+ for key in list(other._fields.keys()):
+ self._fields[key] = Field(other._fields[key].value, False)
+ else:
+ raise ValueError('Cannot update with supplied data')
+
+ def _dirty_fields(self):
+ """Enumerate changed fields"""
+ outgoing_keys = ['peer_port', 'pex_enabled']
+ fields = []
+ for key in outgoing_keys:
+ if key in self._fields and self._fields[key].dirty:
+ fields.append(key)
+ return fields
+
+ def _push(self):
+ """Push changed fields to the server"""
+ dirty = self._dirty_fields()
+ args = {}
+ for key in dirty:
+ args[key] = self._fields[key].value
+ self._fields[key] = self._fields[key]._replace(dirty=False)
+ if len(args) > 0:
+ self._client.set_session(**args)
+
+ def update(self, timeout=None):
+ """Update the session information."""
+ self._push()
+ session = self._client.get_session(timeout=timeout)
+ self._update_fields(session)
+ session = self._client.session_stats(timeout=timeout)
+ self._update_fields(session)
+
+ def from_request(self, data):
+ """Update the session information."""
+ self._update_fields(data)
+
+ def _get_peer_port(self):
+ """
+ Get the peer port.
+ """
+ return self._fields['peer_port'].value
+
+ def _set_peer_port(self, port):
+ """
+ Set the peer port.
+ """
+ if isinstance(port, integer_types):
+ self._fields['peer_port'] = Field(port, True)
+ self._push()
+ else:
+ raise ValueError("Not a valid limit")
+
+ peer_port = property(_get_peer_port, _set_peer_port, None, "Peer port. This is a mutator.")
+
+ def _get_pex_enabled(self):
+ """Is peer exchange enabled?"""
+ return self._fields['pex_enabled'].value
+
+ def _set_pex_enabled(self, enabled):
+ """Enable/disable peer exchange."""
+ if isinstance(enabled, bool):
+ self._fields['pex_enabled'] = Field(enabled, True)
+ self._push()
+ else:
+ raise TypeError("Not a valid type")
+
+ pex_enabled = property(_get_pex_enabled, _set_pex_enabled, None, "Enable peer exchange. This is a mutator.")
diff --git a/resources/lib/transmissionrpc/torrent.py b/resources/lib/transmissionrpc/torrent.py
old mode 100644
new mode 100755
index 2501555..cfdd072
--- a/resources/lib/transmissionrpc/torrent.py
+++ b/resources/lib/transmissionrpc/torrent.py
@@ -1,222 +1,479 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2008-2011 Erik Svensson
-# Licensed under the MIT license.
-
-import sys, datetime
-
-from transmissionrpc.constants import PRIORITY
-from transmissionrpc.utils import format_timedelta
-
-class Torrent(object):
- """
- Torrent is a class holding the data received from Transmission regarding a bittorrent transfer.
- All fetched torrent fields are accessible 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 is 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 '' % (tid, name)
- else:
- return '' % (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 _rpc_version(self):
- if self.client:
- return self.client.rpc_version
- return 2
-
- def _status_old(self, code):
- mapping = {
- (1<<0): 'check pending',
- (1<<1): 'checking',
- (1<<2): 'downloading',
- (1<<3): 'seeding',
- (1<<4): 'stopped',
- }
- return mapping[code]
-
- def _status_new(self, code):
- mapping = {
- 0: 'stopped',
- 1: 'check pending',
- 2: 'checking',
- 3: 'download pending',
- 4: 'downloading',
- 5: 'seed pending',
- 6: 'seeding',
- }
- return mapping[code]
-
- def _status(self):
- code = self.fields['status']
- if self._rpc_version() >= 14:
- return self._status_new(code)
- else:
- return self._status_old(code)
-
- def update(self, other):
- """
- Update the torrent data from a Transmission JSON-RPC arguments dictionary
- """
- 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:
- ::
-
- {
- : {
- 'name': ,
- 'size': ,
- 'completed': ,
- 'priority': ,
- 'selected':
- }
- ...
- }
- """
- result = {}
- if 'files' in self.fields:
- indices = xrange(len(self.fields['files']))
- files = self.fields['files']
- priorities = self.fields['priorities']
- wanted = self.fields['wanted']
- for item in zip(indices, files, priorities, wanted):
- selected = False
- if item[3]:
- selected = True
- 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 self._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."""
- return float(self.fields['uploadRatio'])
-
- @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 ::.
- """
- 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']]
\ No newline at end of file
+# -*- coding: utf-8 -*-
+# Copyright (c) 2008-2013 Erik Svensson
+# Licensed under the MIT license.
+
+import sys, datetime
+
+from transmissionrpc.constants import PRIORITY, RATIO_LIMIT, IDLE_LIMIT
+from transmissionrpc.utils import Field, format_timedelta
+
+from six import integer_types, string_types, text_type, iteritems
+
+
+def get_status_old(code):
+ """Get the torrent status using old status codes"""
+ mapping = {
+ (1<<0): 'check pending',
+ (1<<1): 'checking',
+ (1<<2): 'downloading',
+ (1<<3): 'seeding',
+ (1<<4): 'stopped',
+ }
+ return mapping[code]
+
+def get_status_new(code):
+ """Get the torrent status using new status codes"""
+ mapping = {
+ 0: 'stopped',
+ 1: 'check pending',
+ 2: 'checking',
+ 3: 'download pending',
+ 4: 'downloading',
+ 5: 'seed pending',
+ 6: 'seeding',
+ }
+ return mapping[code]
+
+class Torrent(object):
+ """
+ Torrent is a class holding the data received from Transmission regarding a bittorrent transfer.
+
+ All fetched torrent fields are accessible 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(fields)
+ self._incoming_pending = False
+ self._outgoing_pending = False
+ self._client = client
+
+ def _get_name_string(self, codec=None):
+ """Get the name"""
+ if codec is None:
+ codec = sys.getdefaultencoding()
+ name = None
+ # try to find name
+ if 'name' in self._fields:
+ name = self._fields['name'].value
+ # if name is unicode, try to decode
+ if isinstance(name, text_type):
+ try:
+ name = name.encode(codec)
+ except UnicodeError:
+ name = None
+ return name
+
+ def __repr__(self):
+ tid = self._fields['id'].value
+ name = self._get_name_string()
+ if isinstance(name, str):
+ return '' % (tid, name)
+ else:
+ return '' % (tid)
+
+ def __str__(self):
+ name = self._get_name_string()
+ if isinstance(name, str):
+ return 'Torrent \"%s\"' % (name)
+ else:
+ return 'Torrent'
+
+ def __copy__(self):
+ return Torrent(self._client, self._fields)
+
+ def __getattr__(self, name):
+ try:
+ return self._fields[name].value
+ except KeyError:
+ raise AttributeError('No attribute %s' % name)
+
+ def _rpc_version(self):
+ """Get the Transmission RPC API version."""
+ if self._client:
+ return self._client.rpc_version
+ return 2
+
+ def _dirty_fields(self):
+ """Enumerate changed fields"""
+ outgoing_keys = ['bandwidthPriority', 'downloadLimit', 'downloadLimited', 'peer_limit', 'queuePosition'
+ , 'seedIdleLimit', 'seedIdleMode', 'seedRatioLimit', 'seedRatioMode', 'uploadLimit', 'uploadLimited']
+ fields = []
+ for key in outgoing_keys:
+ if key in self._fields and self._fields[key].dirty:
+ fields.append(key)
+ return fields
+
+ def _push(self):
+ """Push changed fields to the server"""
+ dirty = self._dirty_fields()
+ args = {}
+ for key in dirty:
+ args[key] = self._fields[key].value
+ self._fields[key] = self._fields[key]._replace(dirty=False)
+ if len(args) > 0:
+ self._client.change_torrent(self.id, **args)
+
+ def _update_fields(self, other):
+ """
+ Update the torrent data from a Transmission JSON-RPC arguments dictionary
+ """
+ fields = None
+ if isinstance(other, dict):
+ for key, value in iteritems(other):
+ self._fields[key.replace('-', '_')] = Field(value, False)
+ elif isinstance(other, Torrent):
+ for key in list(other._fields.keys()):
+ self._fields[key] = Field(other._fields[key].value, False)
+ else:
+ raise ValueError('Cannot update with supplied data')
+ self._incoming_pending = False
+
+ def _status(self):
+ """Get the torrent status"""
+ code = self._fields['status'].value
+ if self._rpc_version() >= 14:
+ return get_status_new(code)
+ else:
+ return get_status_old(code)
+
+ 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:
+ ::
+
+ {
+ : {
+ 'name': ,
+ 'size': ,
+ 'completed': ,
+ 'priority': ,
+ 'selected':
+ }
+ ...
+ }
+ """
+ result = {}
+ if 'files' in self._fields:
+ files = self._fields['files'].value
+ indices = range(len(files))
+ priorities = self._fields['priorities'].value
+ wanted = self._fields['wanted'].value
+ for item in zip(indices, 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
+
+ @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 self._status()
+
+ @property
+ def progress(self):
+ """Get the download progress in percent."""
+ try:
+ size = self._fields['sizeWhenDone'].value
+ left = self._fields['leftUntilDone'].value
+ return 100.0 * (size - left) / float(size)
+ except ZeroDivisionError:
+ return 0.0
+
+ @property
+ def ratio(self):
+ """Get the upload/download ratio."""
+ return float(self._fields['uploadRatio'].value)
+
+ @property
+ def eta(self):
+ """Get the "eta" as datetime.timedelta."""
+ eta = self._fields['eta'].value
+ if eta >= 0:
+ return datetime.timedelta(seconds=eta)
+ else:
+ raise ValueError('eta not valid')
+
+ @property
+ def date_active(self):
+ """Get the attribute "activityDate" as datetime.datetime."""
+ return datetime.datetime.fromtimestamp(self._fields['activityDate'].value)
+
+ @property
+ def date_added(self):
+ """Get the attribute "addedDate" as datetime.datetime."""
+ return datetime.datetime.fromtimestamp(self._fields['addedDate'].value)
+
+ @property
+ def date_started(self):
+ """Get the attribute "startDate" as datetime.datetime."""
+ return datetime.datetime.fromtimestamp(self._fields['startDate'].value)
+
+ @property
+ def date_done(self):
+ """Get the attribute "doneDate" as datetime.datetime."""
+ return datetime.datetime.fromtimestamp(self._fields['doneDate'].value)
+
+ 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 ::.
+ """
+ eta = self._fields['eta'].value
+ if eta == -1:
+ return 'not available'
+ elif eta == -2:
+ return 'unknown'
+ else:
+ return format_timedelta(self.eta)
+
+ def _get_download_limit(self):
+ """
+ Get the download limit.
+ Can be a number or None.
+ """
+ if self._fields['downloadLimited'].value:
+ return self._fields['downloadLimit'].value
+ else:
+ return None
+
+ def _set_download_limit(self, limit):
+ """
+ Get the download limit.
+ Can be a number, 'session' or None.
+ """
+ if isinstance(limit, integer_types):
+ self._fields['downloadLimited'] = Field(True, True)
+ self._fields['downloadLimit'] = Field(limit, True)
+ self._push()
+ elif limit == None:
+ self._fields['downloadLimited'] = Field(False, True)
+ self._push()
+ else:
+ raise ValueError("Not a valid limit")
+
+ download_limit = property(_get_download_limit, _set_download_limit, None, "Download limit in Kbps or None. This is a mutator.")
+
+ def _get_peer_limit(self):
+ """
+ Get the peer limit.
+ """
+ return self._fields['peer_limit'].value
+
+ def _set_peer_limit(self, limit):
+ """
+ Set the peer limit.
+ """
+ if isinstance(limit, integer_types):
+ self._fields['peer_limit'] = Field(limit, True)
+ self._push()
+ else:
+ raise ValueError("Not a valid limit")
+
+ peer_limit = property(_get_peer_limit, _set_peer_limit, None, "Peer limit. This is a mutator.")
+
+ def _get_priority(self):
+ """
+ Get the priority as string.
+ Can be one of 'low', 'normal', 'high'.
+ """
+ return PRIORITY[self._fields['bandwidthPriority'].value]
+
+ def _set_priority(self, priority):
+ """
+ Set the priority as string.
+ Can be one of 'low', 'normal', 'high'.
+ """
+ if isinstance(priority, string_types):
+ self._fields['bandwidthPriority'] = Field(PRIORITY[priority], True)
+ self._push()
+
+ priority = property(_get_priority, _set_priority, None
+ , "Bandwidth priority as string. Can be one of 'low', 'normal', 'high'. This is a mutator.")
+
+ def _get_seed_idle_limit(self):
+ """
+ Get the seed idle limit in minutes.
+ """
+ return self._fields['seedIdleLimit'].value
+
+ def _set_seed_idle_limit(self, limit):
+ """
+ Set the seed idle limit in minutes.
+ """
+ if isinstance(limit, integer_types):
+ self._fields['seedIdleLimit'] = Field(limit, True)
+ self._push()
+ else:
+ raise ValueError("Not a valid limit")
+
+ seed_idle_limit = property(_get_seed_idle_limit, _set_seed_idle_limit, None
+ , "Torrent seed idle limit in minutes. Also see seed_idle_mode. This is a mutator.")
+
+ def _get_seed_idle_mode(self):
+ """
+ Get the seed ratio mode as string. Can be one of 'global', 'single' or 'unlimited'.
+ """
+ return IDLE_LIMIT[self._fields['seedIdleMode'].value]
+
+ def _set_seed_idle_mode(self, mode):
+ """
+ Set the seed ratio mode as string. Can be one of 'global', 'single' or 'unlimited'.
+ """
+ if isinstance(mode, str):
+ self._fields['seedIdleMode'] = Field(IDLE_LIMIT[mode], True)
+ self._push()
+ else:
+ raise ValueError("Not a valid limit")
+
+ seed_idle_mode = property(_get_seed_idle_mode, _set_seed_idle_mode, None,
+ """
+ Seed idle mode as string. Can be one of 'global', 'single' or 'unlimited'.
+
+ * global, use session seed idle limit.
+ * single, use torrent seed idle limit. See seed_idle_limit.
+ * unlimited, no seed idle limit.
+
+ This is a mutator.
+ """
+ )
+
+ def _get_seed_ratio_limit(self):
+ """
+ Get the seed ratio limit as float.
+ """
+ return float(self._fields['seedRatioLimit'].value)
+
+ def _set_seed_ratio_limit(self, limit):
+ """
+ Set the seed ratio limit as float.
+ """
+ if isinstance(limit, (integer_types, float)) and limit >= 0.0:
+ self._fields['seedRatioLimit'] = Field(float(limit), True)
+ self._push()
+ else:
+ raise ValueError("Not a valid limit")
+
+ seed_ratio_limit = property(_get_seed_ratio_limit, _set_seed_ratio_limit, None
+ , "Torrent seed ratio limit as float. Also see seed_ratio_mode. This is a mutator.")
+
+ def _get_seed_ratio_mode(self):
+ """
+ Get the seed ratio mode as string. Can be one of 'global', 'single' or 'unlimited'.
+ """
+ return RATIO_LIMIT[self._fields['seedRatioMode'].value]
+
+ def _set_seed_ratio_mode(self, mode):
+ """
+ Set the seed ratio mode as string. Can be one of 'global', 'single' or 'unlimited'.
+ """
+ if isinstance(mode, str):
+ self._fields['seedRatioMode'] = Field(RATIO_LIMIT[mode], True)
+ self._push()
+ else:
+ raise ValueError("Not a valid limit")
+
+ seed_ratio_mode = property(_get_seed_ratio_mode, _set_seed_ratio_mode, None,
+ """
+ Seed ratio mode as string. Can be one of 'global', 'single' or 'unlimited'.
+
+ * global, use session seed ratio limit.
+ * single, use torrent seed ratio limit. See seed_ratio_limit.
+ * unlimited, no seed ratio limit.
+
+ This is a mutator.
+ """
+ )
+
+ def _get_upload_limit(self):
+ """
+ Get the upload limit.
+ Can be a number or None.
+ """
+ if self._fields['uploadLimited'].value:
+ return self._fields['uploadLimit'].value
+ else:
+ return None
+
+ def _set_upload_limit(self, limit):
+ """
+ Set the upload limit.
+ Can be a number, 'session' or None.
+ """
+ if isinstance(limit, integer_types):
+ self._fields['uploadLimited'] = Field(True, True)
+ self._fields['uploadLimit'] = Field(limit, True)
+ self._push()
+ elif limit == None:
+ self._fields['uploadLimited'] = Field(False, True)
+ self._push()
+ else:
+ raise ValueError("Not a valid limit")
+
+ upload_limit = property(_get_upload_limit, _set_upload_limit, None, "Upload limit in Kbps or None. This is a mutator.")
+
+ def _get_queue_position(self):
+ """Get the queue position for this torrent."""
+ if self._rpc_version() >= 14:
+ return self._fields['queuePosition'].value
+ else:
+ return 0
+
+ def _set_queue_position(self, position):
+ """Set the queue position for this torrent."""
+ if self._rpc_version() >= 14:
+ if isinstance(position, integer_types):
+ self._fields['queuePosition'] = Field(position, True)
+ self._push()
+ else:
+ raise ValueError("Not a valid position")
+ else:
+ pass
+
+ queue_position = property(_get_queue_position, _set_queue_position, None, "Queue position")
+
+ def update(self, timeout=None):
+ """Update the torrent information."""
+ self._push()
+ torrent = self._client.get_torrent(self.id, timeout=timeout)
+ self._update_fields(torrent)
+
+ def start(self, bypass_queue=False, timeout=None):
+ """
+ Start the torrent.
+ """
+ self._incoming_pending = True
+ self._client.start_torrent(self.id, bypass_queue=bypass_queue, timeout=timeout)
+
+ def stop(self, timeout=None):
+ """Stop the torrent."""
+ self._incoming_pending = True
+ self._client.stop_torrent(self.id, timeout=timeout)
+
+ def move_data(self, location, timeout=None):
+ """Move torrent data to location."""
+ self._incoming_pending = True
+ self._client.move_torrent_data(self.id, location, timeout=timeout)
+
+ def locate_data(self, location, timeout=None):
+ """Locate torrent data at location."""
+ self._incoming_pending = True
+ self._client.locate_torrent_data(self.id, location, timeout=timeout)
diff --git a/resources/lib/transmissionrpc/utils.py b/resources/lib/transmissionrpc/utils.py
index 479596f..268aa70 100644
--- a/resources/lib/transmissionrpc/utils.py
+++ b/resources/lib/transmissionrpc/utils.py
@@ -1,191 +1,207 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2008-2011 Erik Svensson
-# Licensed under the MIT license.
-
-import socket, datetime, logging
-import transmissionrpc.constants as constants
-from transmissionrpc.constants import LOGGER
-
-UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']
-
-def format_size(size):
- """
- Format byte size into IEC prefixes, B, KiB, MiB ...
- """
- size = float(size)
- i = 0
- while size >= 1024.0 and i < len(UNITS):
- i += 1
- size /= 1024.0
- return (size, UNITS[i])
-
-def format_speed(size):
- """
- Format bytes per second speed into IEC prefixes, B/s, KiB/s, MiB/s ...
- """
- (size, unit) = format_size(size)
- return (size, unit + '/s')
-
-def format_timedelta(delta):
- """
- Format datetime.timedelta into ::.
- """
- minutes, seconds = divmod(delta.seconds, 60)
- hours, minutes = divmod(minutes, 60)
- return '%d %02d:%02d:%02d' % (delta.days, hours, minutes, seconds)
-
-def format_timestamp(timestamp, utc=False):
- """
- Format unix timestamp into ISO date format.
- """
- if timestamp > 0:
- if utc:
- dt_timestamp = datetime.datetime.utcfromtimestamp(timestamp)
- else:
- dt_timestamp = datetime.datetime.fromtimestamp(timestamp)
- return dt_timestamp.isoformat(' ')
- else:
- return '-'
-
-class INetAddressError(Exception):
- """
- Error parsing / generating a internet address.
- """
- pass
-
-def inet_address(address, default_port, default_address='localhost'):
- """
- Parse internet address.
- """
- addr = address.split(':')
- if len(addr) == 1:
- try:
- port = int(addr[0])
- addr = default_address
- except ValueError:
- addr = addr[0]
- port = default_port
- elif len(addr) == 2:
- try:
- port = int(addr[1])
- except ValueError:
- raise INetAddressError('Invalid address "%s".' % address)
- if len(addr[0]) == 0:
- addr = default_address
- else:
- addr = addr[0]
- else:
- raise INetAddressError('Invalid address "%s".' % address)
- try:
- socket.getaddrinfo(addr, port, socket.AF_INET, socket.SOCK_STREAM)
- except socket.gaierror:
- raise INetAddressError('Cannot look up address "%s".' % address)
- return (addr, port)
-
-def rpc_bool(arg):
- """
- Convert between Python boolean and Transmission RPC boolean.
- """
- if isinstance(arg, (str, unicode)):
- try:
- arg = bool(int(arg))
- except ValueError:
- arg = arg.lower() in [u'true', u'yes']
- if bool(arg):
- return 1
- else:
- return 0
-
-TR_TYPE_MAP = {
- 'number' : int,
- 'string' : str,
- 'double': float,
- 'boolean' : rpc_bool,
- 'array': list,
- 'object': dict
-}
-
-def make_python_name(name):
- """
- Convert Transmission RPC name to python compatible name.
- """
- return name.replace('-', '_')
-
-def make_rpc_name(name):
- """
- Convert python compatible name to Transmission RPC name.
- """
- return name.replace('_', '-')
-
-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'):
- args = constants.TORRENT_ARGS[method[-3:]]
- elif method in ('session-get', 'session-set'):
- args = constants.SESSION_ARGS[method[-3:]]
- else:
- return ValueError('Method "%s" not supported' % (method))
- if argument in args:
- info = args[argument]
- invalid_version = True
- while invalid_version:
- invalid_version = False
- replacement = None
- if rpc_version < info[1]:
- invalid_version = True
- replacement = info[3]
- if info[2] and info[2] <= rpc_version:
- invalid_version = True
- replacement = info[4]
- if invalid_version:
- if replacement:
- LOGGER.warning(
- 'Replacing requested argument "%s" with "%s".'
- % (argument, replacement))
- argument = replacement
- info = args[argument]
- else:
- raise ValueError(
- 'Method "%s" Argument "%s" does not exist in version %d.'
- % (method, argument, rpc_version))
- return (argument, TR_TYPE_MAP[info[0]](value))
- else:
- raise ValueError('Argument "%s" does not exists for method "%s".',
- (argument, method))
-
-def get_arguments(method, rpc_version):
- """
- Get arguments for method in specified Transmission RPC version.
- """
- if method in ('torrent-add', 'torrent-get', 'torrent-set'):
- args = constants.TORRENT_ARGS[method[-3:]]
- elif method in ('session-get', 'session-set'):
- args = constants.SESSION_ARGS[method[-3:]]
- else:
- return ValueError('Method "%s" not supported' % (method))
- accessible = []
- for argument, info in args.iteritems():
- valid_version = True
- if rpc_version < info[1]:
- valid_version = False
- if info[2] and info[2] <= rpc_version:
- valid_version = False
- if valid_version:
- accessible.append(argument)
- 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)
+# -*- coding: utf-8 -*-
+# Copyright (c) 2008-2013 Erik Svensson
+# Licensed under the MIT license.
+
+import socket, datetime, logging
+from collections import namedtuple
+import transmissionrpc.constants as constants
+from transmissionrpc.constants import LOGGER
+
+from six import string_types, iteritems
+
+UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']
+
+def format_size(size):
+ """
+ Format byte size into IEC prefixes, B, KiB, MiB ...
+ """
+ size = float(size)
+ i = 0
+ while size >= 1024.0 and i < len(UNITS):
+ i += 1
+ size /= 1024.0
+ return (size, UNITS[i])
+
+def format_speed(size):
+ """
+ Format bytes per second speed into IEC prefixes, B/s, KiB/s, MiB/s ...
+ """
+ (size, unit) = format_size(size)
+ return (size, unit + '/s')
+
+def format_timedelta(delta):
+ """
+ Format datetime.timedelta into ::.
+ """
+ minutes, seconds = divmod(delta.seconds, 60)
+ hours, minutes = divmod(minutes, 60)
+ return '%d %02d:%02d:%02d' % (delta.days, hours, minutes, seconds)
+
+def format_timestamp(timestamp, utc=False):
+ """
+ Format unix timestamp into ISO date format.
+ """
+ if timestamp > 0:
+ if utc:
+ dt_timestamp = datetime.datetime.utcfromtimestamp(timestamp)
+ else:
+ dt_timestamp = datetime.datetime.fromtimestamp(timestamp)
+ return dt_timestamp.isoformat(' ')
+ else:
+ return '-'
+
+class INetAddressError(Exception):
+ """
+ Error parsing / generating a internet address.
+ """
+ pass
+
+def inet_address(address, default_port, default_address='localhost'):
+ """
+ Parse internet address.
+ """
+ addr = address.split(':')
+ if len(addr) == 1:
+ try:
+ port = int(addr[0])
+ addr = default_address
+ except ValueError:
+ addr = addr[0]
+ port = default_port
+ elif len(addr) == 2:
+ try:
+ port = int(addr[1])
+ except ValueError:
+ raise INetAddressError('Invalid address "%s".' % address)
+ if len(addr[0]) == 0:
+ addr = default_address
+ else:
+ addr = addr[0]
+ else:
+ raise INetAddressError('Invalid address "%s".' % address)
+ try:
+ socket.getaddrinfo(addr, port, socket.AF_INET, socket.SOCK_STREAM)
+ except socket.gaierror:
+ raise INetAddressError('Cannot look up address "%s".' % address)
+ return (addr, port)
+
+def rpc_bool(arg):
+ """
+ Convert between Python boolean and Transmission RPC boolean.
+ """
+ if isinstance(arg, string_types):
+ try:
+ arg = bool(int(arg))
+ except ValueError:
+ arg = arg.lower() in ['true', 'yes']
+ return 1 if bool(arg) else 0
+
+TR_TYPE_MAP = {
+ 'number' : int,
+ 'string' : str,
+ 'double': float,
+ 'boolean' : rpc_bool,
+ 'array': list,
+ 'object': dict
+}
+
+def make_python_name(name):
+ """
+ Convert Transmission RPC name to python compatible name.
+ """
+ return name.replace('-', '_')
+
+def make_rpc_name(name):
+ """
+ Convert python compatible name to Transmission RPC name.
+ """
+ return name.replace('_', '-')
+
+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'):
+ args = constants.TORRENT_ARGS[method[-3:]]
+ elif method in ('session-get', 'session-set'):
+ args = constants.SESSION_ARGS[method[-3:]]
+ else:
+ return ValueError('Method "%s" not supported' % (method))
+ if argument in args:
+ info = args[argument]
+ invalid_version = True
+ while invalid_version:
+ invalid_version = False
+ replacement = None
+ if rpc_version < info[1]:
+ invalid_version = True
+ replacement = info[3]
+ if info[2] and info[2] <= rpc_version:
+ invalid_version = True
+ replacement = info[4]
+ if invalid_version:
+ if replacement:
+ LOGGER.warning(
+ 'Replacing requested argument "%s" with "%s".'
+ % (argument, replacement))
+ argument = replacement
+ info = args[argument]
+ else:
+ raise ValueError(
+ 'Method "%s" Argument "%s" does not exist in version %d.'
+ % (method, argument, rpc_version))
+ return (argument, TR_TYPE_MAP[info[0]](value))
+ else:
+ raise ValueError('Argument "%s" does not exists for method "%s".',
+ (argument, method))
+
+def get_arguments(method, rpc_version):
+ """
+ Get arguments for method in specified Transmission RPC version.
+ """
+ if method in ('torrent-add', 'torrent-get', 'torrent-set'):
+ args = constants.TORRENT_ARGS[method[-3:]]
+ elif method in ('session-get', 'session-set'):
+ args = constants.SESSION_ARGS[method[-3:]]
+ else:
+ return ValueError('Method "%s" not supported' % (method))
+ accessible = []
+ for argument, info in iteritems(args):
+ valid_version = True
+ if rpc_version < info[1]:
+ valid_version = False
+ if info[2] and info[2] <= rpc_version:
+ valid_version = False
+ if valid_version:
+ accessible.append(argument)
+ 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 list(levels.keys()):
+ loglevel = levels[level]
+ trpc_logger.setLevel(loglevel)
+ loghandler.setLevel(loglevel)
+ trpc_logger.addHandler(loghandler)
+
+def add_file_logger(filepath, 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.FileHandler(filepath, encoding='utf-8')
+ if level in list(levels.keys()):
+ loglevel = levels[level]
+ trpc_logger.setLevel(loglevel)
+ loghandler.setLevel(loglevel)
+ trpc_logger.addHandler(loghandler)
+
+Field = namedtuple('Field', ['value', 'dirty'])