mirror of
https://github.com/correl/Transmission-XBMC.git
synced 2024-11-22 03:00:15 +00:00
Update transmissionrpc to version 0.11
This commit is contained in:
parent
f341a8a125
commit
13dc11f94d
10 changed files with 2179 additions and 1625 deletions
|
@ -7,6 +7,7 @@
|
||||||
<import addon="xbmc.python" version="2.14.0"/>
|
<import addon="xbmc.python" version="2.14.0"/>
|
||||||
<import addon="script.module.simplejson" version="3.3.0"/>
|
<import addon="script.module.simplejson" version="3.3.0"/>
|
||||||
<import addon="script.module.beautifulsoup" version="3.0.8" />
|
<import addon="script.module.beautifulsoup" version="3.0.8" />
|
||||||
|
<import addon="script.module.six" version="1.5.2" />
|
||||||
</requires>
|
</requires>
|
||||||
<extension point="xbmc.python.script"
|
<extension point="xbmc.python.script"
|
||||||
library="default.py" />
|
library="default.py" />
|
||||||
|
|
|
@ -149,7 +149,7 @@ class TransmissionGUI(xbmcgui.WindowXMLDialog):
|
||||||
if selected < 0:
|
if selected < 0:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
self.transmission.add_uri(results[selected]['url'])
|
self.transmission.add_torrent(results[selected]['url'])
|
||||||
except:
|
except:
|
||||||
xbmcgui.Dialog().ok(_(0), _(293))
|
xbmcgui.Dialog().ok(_(0), _(293))
|
||||||
return
|
return
|
||||||
|
|
14
resources/lib/transmissionrpc/__init__.py
Normal file → Executable file
14
resources/lib/transmissionrpc/__init__.py
Normal file → Executable file
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
|
# Copyright (c) 2008-2013 Erik Svensson <erik.public@gmail.com>
|
||||||
# Licensed under the MIT license.
|
# Licensed under the MIT license.
|
||||||
|
|
||||||
from transmissionrpc.constants import DEFAULT_PORT, DEFAULT_TIMEOUT, PRIORITY, RATIO_LIMIT, LOGGER
|
from transmissionrpc.constants import DEFAULT_PORT, DEFAULT_TIMEOUT, PRIORITY, RATIO_LIMIT, LOGGER
|
||||||
|
@ -8,9 +8,11 @@ from transmissionrpc.httphandler import HTTPHandler, DefaultHTTPHandler
|
||||||
from transmissionrpc.torrent import Torrent
|
from transmissionrpc.torrent import Torrent
|
||||||
from transmissionrpc.session import Session
|
from transmissionrpc.session import Session
|
||||||
from transmissionrpc.client import Client
|
from transmissionrpc.client import Client
|
||||||
from transmissionrpc.utils import add_stdout_logger
|
from transmissionrpc.utils import add_stdout_logger, add_file_logger
|
||||||
|
|
||||||
__author__ = u'Erik Svensson <erik.public@gmail.com>'
|
__author__ = 'Erik Svensson <erik.public@gmail.com>'
|
||||||
__version__ = u'0.8'
|
__version_major__ = 0
|
||||||
__copyright__ = u'Copyright (c) 2008-2011 Erik Svensson'
|
__version_minor__ = 11
|
||||||
__license__ = u'MIT'
|
__version__ = '{0}.{1}'.format(__version_major__, __version_minor__)
|
||||||
|
__copyright__ = 'Copyright (c) 2008-2013 Erik Svensson'
|
||||||
|
__license__ = 'MIT'
|
||||||
|
|
553
resources/lib/transmissionrpc/client.py
Normal file → Executable file
553
resources/lib/transmissionrpc/client.py
Normal file → Executable file
|
@ -1,15 +1,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
|
# Copyright (c) 2008-2013 Erik Svensson <erik.public@gmail.com>
|
||||||
# Licensed under the MIT license.
|
# Licensed under the MIT license.
|
||||||
|
|
||||||
import sys
|
import re, time, operator, warnings, os
|
||||||
import re, time
|
import base64
|
||||||
import urllib2, urlparse, base64
|
import json
|
||||||
|
|
||||||
try:
|
|
||||||
import json
|
|
||||||
except ImportError:
|
|
||||||
import simplejson as json
|
|
||||||
|
|
||||||
from transmissionrpc.constants import DEFAULT_PORT, DEFAULT_TIMEOUT
|
from transmissionrpc.constants import DEFAULT_PORT, DEFAULT_TIMEOUT
|
||||||
from transmissionrpc.error import TransmissionError, HTTPHandlerError
|
from transmissionrpc.error import TransmissionError, HTTPHandlerError
|
||||||
|
@ -18,6 +13,15 @@ from transmissionrpc.httphandler import DefaultHTTPHandler
|
||||||
from transmissionrpc.torrent import Torrent
|
from transmissionrpc.torrent import Torrent
|
||||||
from transmissionrpc.session import Session
|
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):
|
def debug_httperror(error):
|
||||||
"""
|
"""
|
||||||
Log the Transmission RPC HTTP error.
|
Log the Transmission RPC HTTP error.
|
||||||
|
@ -41,54 +45,71 @@ def debug_httperror(error):
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if sys.version_info[:2] < (2, 5):
|
def parse_torrent_id(arg):
|
||||||
"""
|
"""Parse an torrent id or torrent hashString."""
|
||||||
Mimic python 2.5+ urlparse.urlparse
|
torrent_id = None
|
||||||
"""
|
if isinstance(arg, integer_types):
|
||||||
_old_urlparse = urlparse.urlparse
|
# handle index
|
||||||
def _urlparse(address):
|
torrent_id = int(arg)
|
||||||
class ParsedResult(tuple):
|
elif isinstance(arg, float):
|
||||||
def __init__(self, address = None):
|
torrent_id = int(arg)
|
||||||
self.scheme = ''
|
if torrent_id != arg:
|
||||||
self.netloc = ''
|
torrent_id = None
|
||||||
self.path = ''
|
elif isinstance(arg, string_types):
|
||||||
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:
|
try:
|
||||||
self.port = int(self.port)
|
torrent_id = int(arg)
|
||||||
except:
|
if torrent_id >= 2**31:
|
||||||
self.port = None
|
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
|
||||||
|
|
||||||
result = ParsedResult(address)
|
def parse_torrent_ids(args):
|
||||||
return result
|
"""
|
||||||
|
Take things and make them valid torrent identifiers
|
||||||
|
"""
|
||||||
|
ids = []
|
||||||
|
|
||||||
urlparse.urlparse = _urlparse
|
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
|
Torrent ids
|
||||||
|
@ -110,11 +131,11 @@ class Client(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, address='localhost', port=DEFAULT_PORT, user=None, password=None, http_handler=None, timeout=None):
|
def __init__(self, address='localhost', port=DEFAULT_PORT, user=None, password=None, http_handler=None, timeout=None):
|
||||||
if isinstance(timeout, (int, long, float)):
|
if isinstance(timeout, (integer_types, float)):
|
||||||
self._query_timeout = float(timeout)
|
self._query_timeout = float(timeout)
|
||||||
else:
|
else:
|
||||||
self._query_timeout = DEFAULT_TIMEOUT
|
self._query_timeout = DEFAULT_TIMEOUT
|
||||||
urlo = urlparse.urlparse(address)
|
urlo = urlparse(address)
|
||||||
if urlo.scheme == '':
|
if urlo.scheme == '':
|
||||||
base_url = 'http://' + address + ':' + str(port)
|
base_url = 'http://' + address + ':' + str(port)
|
||||||
self.url = base_url + '/transmission/rpc'
|
self.url = base_url + '/transmission/rpc'
|
||||||
|
@ -141,7 +162,7 @@ class Client(object):
|
||||||
elif user or password:
|
elif user or password:
|
||||||
LOGGER.warning('Either user or password missing, not using authentication.')
|
LOGGER.warning('Either user or password missing, not using authentication.')
|
||||||
self._sequence = 0
|
self._sequence = 0
|
||||||
self.session = Session()
|
self.session = None
|
||||||
self.session_id = 0
|
self.session_id = 0
|
||||||
self.server_version = None
|
self.server_version = None
|
||||||
self.protocol_version = None
|
self.protocol_version = None
|
||||||
|
@ -183,15 +204,18 @@ class Client(object):
|
||||||
try:
|
try:
|
||||||
result = self.http_handler.request(self.url, query, headers, timeout)
|
result = self.http_handler.request(self.url, query, headers, timeout)
|
||||||
break
|
break
|
||||||
except HTTPHandlerError, error:
|
except HTTPHandlerError as error:
|
||||||
if error.code == 409:
|
if error.code == 409:
|
||||||
LOGGER.info('Server responded with 409, trying to set session-id.')
|
LOGGER.info('Server responded with 409, trying to set session-id.')
|
||||||
if request_count > 1:
|
if request_count > 1:
|
||||||
raise TransmissionError('Session ID negotiation failed.', error)
|
raise TransmissionError('Session ID negotiation failed.', error)
|
||||||
if 'x-transmission-session-id' in error.headers:
|
session_id = None
|
||||||
self.session_id = error.headers['x-transmission-session-id']
|
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)}
|
headers = {'x-transmission-session-id': str(self.session_id)}
|
||||||
else:
|
if session_id is None:
|
||||||
debug_httperror(error)
|
debug_httperror(error)
|
||||||
raise TransmissionError('Unknown conflict.', error)
|
raise TransmissionError('Unknown conflict.', error)
|
||||||
else:
|
else:
|
||||||
|
@ -204,13 +228,13 @@ class Client(object):
|
||||||
"""
|
"""
|
||||||
Send json-rpc request to Transmission using http POST
|
Send json-rpc request to Transmission using http POST
|
||||||
"""
|
"""
|
||||||
if not isinstance(method, (str, unicode)):
|
if not isinstance(method, string_types):
|
||||||
raise ValueError('request takes method as string')
|
raise ValueError('request takes method as string')
|
||||||
if arguments is None:
|
if arguments is None:
|
||||||
arguments = {}
|
arguments = {}
|
||||||
if not isinstance(arguments, dict):
|
if not isinstance(arguments, dict):
|
||||||
raise ValueError('request takes arguments as dict')
|
raise ValueError('request takes arguments as dict')
|
||||||
ids = self._format_ids(ids)
|
ids = parse_torrent_ids(ids)
|
||||||
if len(ids) > 0:
|
if len(ids) > 0:
|
||||||
arguments['ids'] = ids
|
arguments['ids'] = ids
|
||||||
elif require_ids:
|
elif require_ids:
|
||||||
|
@ -226,7 +250,7 @@ class Client(object):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = json.loads(http_data)
|
data = json.loads(http_data)
|
||||||
except ValueError, error:
|
except ValueError as error:
|
||||||
LOGGER.error('Error: ' + str(error))
|
LOGGER.error('Error: ' + str(error))
|
||||||
LOGGER.error('Request: \"%s\"' % (query))
|
LOGGER.error('Request: \"%s\"' % (query))
|
||||||
LOGGER.error('HTTP data: \"%s\"' % (http_data))
|
LOGGER.error('HTTP data: \"%s\"' % (http_data))
|
||||||
|
@ -246,8 +270,15 @@ class Client(object):
|
||||||
if self.protocol_version == 2 and 'peers' not in item:
|
if self.protocol_version == 2 and 'peers' not in item:
|
||||||
self.protocol_version = 1
|
self.protocol_version = 1
|
||||||
elif method == 'torrent-add':
|
elif method == 'torrent-add':
|
||||||
|
item = None
|
||||||
|
if 'torrent-added' in data['arguments']:
|
||||||
item = data['arguments']['torrent-added']
|
item = data['arguments']['torrent-added']
|
||||||
|
elif 'torrent-duplicate' in data['arguments']:
|
||||||
|
item = data['arguments']['torrent-duplicate']
|
||||||
|
if item:
|
||||||
results[item['id']] = Torrent(self, item)
|
results[item['id']] = Torrent(self, item)
|
||||||
|
else:
|
||||||
|
raise TransmissionError('Invalid torrent-add response.')
|
||||||
elif method == 'session-get':
|
elif method == 'session-get':
|
||||||
self._update_session(data['arguments'])
|
self._update_session(data['arguments'])
|
||||||
elif method == 'session-stats':
|
elif method == 'session-stats':
|
||||||
|
@ -256,67 +287,24 @@ class Client(object):
|
||||||
self._update_session(data['arguments']['session-stats'])
|
self._update_session(data['arguments']['session-stats'])
|
||||||
else:
|
else:
|
||||||
self._update_session(data['arguments'])
|
self._update_session(data['arguments'])
|
||||||
elif method in ('port-test', 'blocklist-update'):
|
elif method in ('port-test', 'blocklist-update', 'free-space', 'torrent-rename-path'):
|
||||||
results = data['arguments']
|
results = data['arguments']
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return results
|
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):
|
def _update_session(self, data):
|
||||||
"""
|
"""
|
||||||
Update session data.
|
Update session data.
|
||||||
"""
|
"""
|
||||||
self.session.update(data)
|
if self.session:
|
||||||
|
self.session.from_request(data)
|
||||||
|
else:
|
||||||
|
self.session = Session(self, data)
|
||||||
|
|
||||||
def _update_server_version(self):
|
def _update_server_version(self):
|
||||||
|
"""Decode the Transmission version string, if available."""
|
||||||
if self.server_version is None:
|
if self.server_version is None:
|
||||||
version_major = 1
|
version_major = 1
|
||||||
version_minor = 30
|
version_minor = 30
|
||||||
|
@ -355,11 +343,12 @@ class Client(object):
|
||||||
Add a warning to the log if the Transmission RPC version is lower then the provided version.
|
Add a warning to the log if the Transmission RPC version is lower then the provided version.
|
||||||
"""
|
"""
|
||||||
if self.rpc_version < 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))
|
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):
|
def add_torrent(self, torrent, timeout=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Add torrent to transfers list. Takes a base64 encoded .torrent file in data.
|
Add torrent to transfers list. Takes a uri to a torrent or base64 encoded torrent data in ``torrent``.
|
||||||
Additional arguments are:
|
Additional arguments are:
|
||||||
|
|
||||||
===================== ===== =========== =============================================================
|
===================== ===== =========== =============================================================
|
||||||
|
@ -377,55 +366,106 @@ class Client(object):
|
||||||
``priority_normal`` 1 - A list of file id's that should have normal 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 = {}
|
args = {}
|
||||||
if data:
|
if data:
|
||||||
args = {'metainfo': data}
|
args = {'metainfo': data}
|
||||||
elif 'metainfo' not in kwargs and 'filename' not in kwargs:
|
elif 'metainfo' not in kwargs and 'filename' not in kwargs:
|
||||||
raise ValueError('No torrent data or torrent uri.')
|
raise ValueError('No torrent data or torrent uri.')
|
||||||
for key, value in kwargs.iteritems():
|
for key, value in iteritems(kwargs):
|
||||||
argument = make_rpc_name(key)
|
argument = make_rpc_name(key)
|
||||||
(arg, val) = argument_value_convert('torrent-add',
|
(arg, val) = argument_value_convert('torrent-add', argument, value, self.rpc_version)
|
||||||
argument, value, self.rpc_version)
|
|
||||||
args[arg] = val
|
args[arg] = val
|
||||||
|
warnings.warn('add has been deprecated, please use add_torrent instead.', DeprecationWarning)
|
||||||
return self._request('torrent-add', args, timeout=timeout)
|
return self._request('torrent-add', args, timeout=timeout)
|
||||||
|
|
||||||
def add_uri(self, uri, **kwargs):
|
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:
|
|
||||||
|
|
||||||
===================== ===== =========== =============================================================
|
.. WARNING::
|
||||||
Argument RPC Replaced by Description
|
Deprecated, please use add_torrent.
|
||||||
===================== ===== =========== =============================================================
|
|
||||||
``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:
|
if uri is None:
|
||||||
raise ValueError('add_uri requires a URI.')
|
raise ValueError('add_uri requires a URI.')
|
||||||
# there has been some problem with T's built in torrent fetcher,
|
# there has been some problem with T's built in torrent fetcher,
|
||||||
# use a python one instead
|
# use a python one instead
|
||||||
parsed_uri = urlparse.urlparse(uri)
|
parsed_uri = urlparse(uri)
|
||||||
torrent_data = None
|
torrent_data = None
|
||||||
if parsed_uri.scheme in ['file', 'ftp', 'ftps', 'http', 'https']:
|
if parsed_uri.scheme in ['ftp', 'ftps', 'http', 'https']:
|
||||||
torrent_file = urllib2.urlopen(uri)
|
torrent_file = urlopen(uri)
|
||||||
torrent_data = base64.b64encode(torrent_file.read())
|
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:
|
if torrent_data:
|
||||||
return self.add(torrent_data, **kwargs)
|
return self.add(torrent_data, **kwargs)
|
||||||
else:
|
else:
|
||||||
return self.add(None, filename=uri, **kwargs)
|
return self.add(None, filename=uri, **kwargs)
|
||||||
|
|
||||||
def remove(self, ids, delete_data=False, timeout=None):
|
def remove_torrent(self, ids, delete_data=False, timeout=None):
|
||||||
"""
|
"""
|
||||||
remove torrent(s) with provided id(s). Local data is removed if
|
remove torrent(s) with provided id(s). Local data is removed if
|
||||||
delete_data is True, otherwise not.
|
delete_data is True, otherwise not.
|
||||||
|
@ -434,32 +474,139 @@ class Client(object):
|
||||||
self._request('torrent-remove',
|
self._request('torrent-remove',
|
||||||
{'delete-local-data':rpc_bool(delete_data)}, ids, True, timeout=timeout)
|
{'delete-local-data':rpc_bool(delete_data)}, ids, True, timeout=timeout)
|
||||||
|
|
||||||
def start(self, ids, bypass_queue=False, timeout=None):
|
def remove(self, ids, delete_data=False, timeout=None):
|
||||||
"""start torrent(s) with provided id(s)"""
|
"""
|
||||||
|
|
||||||
|
.. 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'
|
method = 'torrent-start'
|
||||||
if bypass_queue and self.rpc_version >= 14:
|
if bypass_queue and self.rpc_version >= 14:
|
||||||
method = 'torrent-start-now'
|
method = 'torrent-start-now'
|
||||||
self._request(method, {}, ids, True, timeout=timeout)
|
self._request(method, {}, ids, True, timeout=timeout)
|
||||||
|
|
||||||
def stop(self, ids, timeout=None):
|
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)"""
|
"""stop torrent(s) with provided id(s)"""
|
||||||
self._request('torrent-stop', {}, ids, True, timeout=timeout)
|
self._request('torrent-stop', {}, ids, True, timeout=timeout)
|
||||||
|
|
||||||
def verify(self, ids, timeout=None):
|
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)"""
|
"""verify torrent(s) with provided id(s)"""
|
||||||
self._request('torrent-verify', {}, ids, True, timeout=timeout)
|
self._request('torrent-verify', {}, ids, True, timeout=timeout)
|
||||||
|
|
||||||
def reannounce(self, ids, timeout=None):
|
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)"""
|
"""Reannounce torrent(s) with provided id(s)"""
|
||||||
self._rpc_version_warning(5)
|
self._rpc_version_warning(5)
|
||||||
self._request('torrent-reannounce', {}, ids, True, timeout=timeout)
|
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):
|
def info(self, ids=None, arguments=None, timeout=None):
|
||||||
"""Get detailed information for torrent(s) with provided id(s)."""
|
"""
|
||||||
|
|
||||||
|
.. 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:
|
if not arguments:
|
||||||
arguments = self.torrent_get_arguments
|
arguments = self.torrent_get_arguments
|
||||||
return self._request('torrent-get', {'fields': arguments}, ids, timeout=timeout)
|
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):
|
def get_files(self, ids=None, timeout=None):
|
||||||
"""
|
"""
|
||||||
Get list of files for provided torrent id(s). If ids is empty,
|
Get list of files for provided torrent id(s). If ids is empty,
|
||||||
|
@ -487,7 +634,7 @@ class Client(object):
|
||||||
fields = ['id', 'name', 'hashString', 'files', 'priorities', 'wanted']
|
fields = ['id', 'name', 'hashString', 'files', 'priorities', 'wanted']
|
||||||
request_result = self._request('torrent-get', {'fields': fields}, ids, timeout=timeout)
|
request_result = self._request('torrent-get', {'fields': fields}, ids, timeout=timeout)
|
||||||
result = {}
|
result = {}
|
||||||
for tid, torrent in request_result.iteritems():
|
for tid, torrent in iteritems(request_result):
|
||||||
result[tid] = torrent.files()
|
result[tid] = torrent.files()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@ -513,7 +660,7 @@ class Client(object):
|
||||||
"""
|
"""
|
||||||
if not isinstance(items, dict):
|
if not isinstance(items, dict):
|
||||||
raise ValueError('Invalid file description')
|
raise ValueError('Invalid file description')
|
||||||
for tid, files in items.iteritems():
|
for tid, files in iteritems(items):
|
||||||
if not isinstance(files, dict):
|
if not isinstance(files, dict):
|
||||||
continue
|
continue
|
||||||
wanted = []
|
wanted = []
|
||||||
|
@ -521,7 +668,7 @@ class Client(object):
|
||||||
high = []
|
high = []
|
||||||
normal = []
|
normal = []
|
||||||
low = []
|
low = []
|
||||||
for fid, file_desc in files.iteritems():
|
for fid, file_desc in iteritems(files):
|
||||||
if not isinstance(file_desc, dict):
|
if not isinstance(file_desc, dict):
|
||||||
continue
|
continue
|
||||||
if 'selected' in file_desc and file_desc['selected']:
|
if 'selected' in file_desc and file_desc['selected']:
|
||||||
|
@ -536,9 +683,7 @@ class Client(object):
|
||||||
elif file_desc['priority'] == 'low':
|
elif file_desc['priority'] == 'low':
|
||||||
low.append(fid)
|
low.append(fid)
|
||||||
args = {
|
args = {
|
||||||
'timeout': timeout,
|
'timeout': timeout
|
||||||
'files_wanted': wanted,
|
|
||||||
'files_unwanted': unwanted,
|
|
||||||
}
|
}
|
||||||
if len(high) > 0:
|
if len(high) > 0:
|
||||||
args['priority_high'] = high
|
args['priority_high'] = high
|
||||||
|
@ -546,16 +691,13 @@ class Client(object):
|
||||||
args['priority_normal'] = normal
|
args['priority_normal'] = normal
|
||||||
if len(low) > 0:
|
if len(low) > 0:
|
||||||
args['priority_low'] = low
|
args['priority_low'] = low
|
||||||
self.change([tid], **args)
|
if len(wanted) > 0:
|
||||||
|
args['files_wanted'] = wanted
|
||||||
|
if len(unwanted) > 0:
|
||||||
|
args['files_unwanted'] = unwanted
|
||||||
|
self.change_torrent([tid], **args)
|
||||||
|
|
||||||
def list(self, timeout=None):
|
def change_torrent(self, ids, timeout=None, **kwargs):
|
||||||
"""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
|
Change torrent parameters for the torrent(s) with the supplied id's. The
|
||||||
parameters are:
|
parameters are:
|
||||||
|
@ -594,10 +736,9 @@ class Client(object):
|
||||||
transmissionrpc will try to automatically fix argument errors.
|
transmissionrpc will try to automatically fix argument errors.
|
||||||
"""
|
"""
|
||||||
args = {}
|
args = {}
|
||||||
for key, value in kwargs.iteritems():
|
for key, value in iteritems(kwargs):
|
||||||
argument = make_rpc_name(key)
|
argument = make_rpc_name(key)
|
||||||
(arg, val) = argument_value_convert('torrent-set'
|
(arg, val) = argument_value_convert('torrent-set' , argument, value, self.rpc_version)
|
||||||
, argument, value, self.rpc_version)
|
|
||||||
args[arg] = val
|
args[arg] = val
|
||||||
|
|
||||||
if len(args) > 0:
|
if len(args) > 0:
|
||||||
|
@ -605,18 +746,61 @@ class Client(object):
|
||||||
else:
|
else:
|
||||||
ValueError("No arguments to set")
|
ValueError("No arguments to set")
|
||||||
|
|
||||||
def move(self, ids, location, timeout=None):
|
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."""
|
"""Move torrent data to the new location."""
|
||||||
self._rpc_version_warning(6)
|
self._rpc_version_warning(6)
|
||||||
args = {'location': location, 'move': True}
|
args = {'location': location, 'move': True}
|
||||||
self._request('torrent-set-location', args, ids, True, timeout=timeout)
|
self._request('torrent-set-location', args, ids, True, timeout=timeout)
|
||||||
|
|
||||||
def locate(self, ids, location, timeout=None):
|
def move(self, ids, location, timeout=None):
|
||||||
"""Locate torrent data at the location."""
|
"""
|
||||||
|
|
||||||
|
.. 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)
|
self._rpc_version_warning(6)
|
||||||
args = {'location': location, 'move': False}
|
args = {'location': location, 'move': False}
|
||||||
self._request('torrent-set-location', args, ids, True, timeout=timeout)
|
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):
|
def queue_top(self, ids, timeout=None):
|
||||||
"""Move transfer to the top of the queue."""
|
"""Move transfer to the top of the queue."""
|
||||||
self._rpc_version_warning(14)
|
self._rpc_version_warning(14)
|
||||||
|
@ -638,7 +822,9 @@ class Client(object):
|
||||||
self._request('queue-move-down', ids=ids, require_ids=True, timeout=timeout)
|
self._request('queue-move-down', ids=ids, require_ids=True, timeout=timeout)
|
||||||
|
|
||||||
def get_session(self, timeout=None):
|
def get_session(self, timeout=None):
|
||||||
"""Get session parameters"""
|
"""
|
||||||
|
Get session parameters. See the Session class for more information.
|
||||||
|
"""
|
||||||
self._request('session-get', timeout=timeout)
|
self._request('session-get', timeout=timeout)
|
||||||
self._update_server_version()
|
self._update_server_version()
|
||||||
return self.session
|
return self.session
|
||||||
|
@ -662,17 +848,17 @@ class Client(object):
|
||||||
``cache_size_mb`` 10 - The maximum size of the disk cache in MB
|
``cache_size_mb`` 10 - The maximum size of the disk cache in MB
|
||||||
``dht_enabled`` 6 - Enables DHT.
|
``dht_enabled`` 6 - Enables DHT.
|
||||||
``download_dir`` 1 - Set the session download directory.
|
``download_dir`` 1 - Set the session download directory.
|
||||||
``download_queue_enabled`` 14 - Enable parallel download restriction.
|
``download_queue_enabled`` 14 - Enables download queue.
|
||||||
``download_queue_size`` 14 - Number of parallel downloads.
|
``download_queue_size`` 14 - Number of slots in the download queue.
|
||||||
``encryption`` 1 - Set the session encryption mode, one of ``required``, ``preferred`` or ``tolerated``.
|
``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`` 10 - The default seed inactivity limit in minutes.
|
||||||
``idle_seeding_limit_enabled`` 10 - Enables the default seed inactivity limit
|
``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`` 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.
|
``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.
|
``lpd_enabled`` 9 - Enables local peer discovery for public torrents.
|
||||||
``peer_limit`` 1 - 5 peer-limit-global Maximum number of peers
|
``peer_limit`` 1 - 5 peer-limit-global Maximum number of peers.
|
||||||
``peer_limit_global`` 5 - Maximum number of peers
|
``peer_limit_global`` 5 - Maximum number of peers.
|
||||||
``peer_limit_per_torrent`` 5 - Maximum number of peers per transfer
|
``peer_limit_per_torrent`` 5 - Maximum number of peers per transfer.
|
||||||
``peer_port`` 5 - Peer port.
|
``peer_port`` 5 - Peer port.
|
||||||
``peer_port_random_on_start`` 5 - Enables randomized peer port on start of Transmission.
|
``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_allowed`` 1 - 5 pex-enabled Allowing PEX in public torrents.
|
||||||
|
@ -684,8 +870,8 @@ class Client(object):
|
||||||
``rename_partial_files`` 8 - Appends ".part" to incomplete files
|
``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_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.
|
``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_enabled`` 14 - Enables upload queue.
|
||||||
``seed_queue_size`` 14 - Number of parallel uploads.
|
``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.
|
``seedRatioLimit`` 5 - Seed ratio limit. 1.0 means 1:1 download and upload ratio.
|
||||||
``seedRatioLimited`` 5 - Enables seed ration limit.
|
``seedRatioLimited`` 5 - Enables seed ration limit.
|
||||||
``speed_limit_down`` 1 - Download speed limit (in Kib/s).
|
``speed_limit_down`` 1 - Download speed limit (in Kib/s).
|
||||||
|
@ -701,12 +887,11 @@ class Client(object):
|
||||||
transmissionrpc will try to automatically fix argument errors.
|
transmissionrpc will try to automatically fix argument errors.
|
||||||
"""
|
"""
|
||||||
args = {}
|
args = {}
|
||||||
for key, value in kwargs.iteritems():
|
for key, value in iteritems(kwargs):
|
||||||
if key == 'encryption' and value not in ['required', 'preferred', 'tolerated']:
|
if key == 'encryption' and value not in ['required', 'preferred', 'tolerated']:
|
||||||
raise ValueError('Invalid encryption value')
|
raise ValueError('Invalid encryption value')
|
||||||
argument = make_rpc_name(key)
|
argument = make_rpc_name(key)
|
||||||
(arg, val) = argument_value_convert('session-set'
|
(arg, val) = argument_value_convert('session-set' , argument, value, self.rpc_version)
|
||||||
, argument, value, self.rpc_version)
|
|
||||||
args[arg] = val
|
args[arg] = val
|
||||||
if len(args) > 0:
|
if len(args) > 0:
|
||||||
self._request('session-set', args, timeout=timeout)
|
self._request('session-set', args, timeout=timeout)
|
||||||
|
@ -730,6 +915,16 @@ class Client(object):
|
||||||
return result['port-is-open']
|
return result['port-is-open']
|
||||||
return None
|
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):
|
def session_stats(self, timeout=None):
|
||||||
"""Get session statistics"""
|
"""Get session statistics"""
|
||||||
self._request('session-stats', timeout=timeout)
|
self._request('session-stats', timeout=timeout)
|
||||||
|
|
288
resources/lib/transmissionrpc/constants.py
Normal file → Executable file
288
resources/lib/transmissionrpc/constants.py
Normal file → Executable file
|
@ -1,8 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
|
# Copyright (c) 2008-2013 Erik Svensson <erik.public@gmail.com>
|
||||||
# Licensed under the MIT license.
|
# Licensed under the MIT license.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
LOGGER = logging.getLogger('transmissionrpc')
|
LOGGER = logging.getLogger('transmissionrpc')
|
||||||
LOGGER.setLevel(logging.ERROR)
|
LOGGER.setLevel(logging.ERROR)
|
||||||
|
@ -11,7 +12,7 @@ def mirror_dict(source):
|
||||||
"""
|
"""
|
||||||
Creates a dictionary with all values as keys and all keys as values.
|
Creates a dictionary with all values as keys and all keys as values.
|
||||||
"""
|
"""
|
||||||
source.update(dict((value, key) for key, value in source.iteritems()))
|
source.update(dict((value, key) for key, value in iteritems(source)))
|
||||||
return source
|
return source
|
||||||
|
|
||||||
DEFAULT_PORT = 9091
|
DEFAULT_PORT = 9091
|
||||||
|
@ -60,87 +61,90 @@ IDLE_LIMIT = mirror_dict({
|
||||||
# Arguments for torrent methods
|
# Arguments for torrent methods
|
||||||
TORRENT_ARGS = {
|
TORRENT_ARGS = {
|
||||||
'get' : {
|
'get' : {
|
||||||
'activityDate': ('number', 1, None, None, None, ''),
|
'activityDate': ('number', 1, None, None, None, 'Last time of upload or download activity.'),
|
||||||
'addedDate': ('number', 1, None, None, None, ''),
|
'addedDate': ('number', 1, None, None, None, 'The date when this torrent was first added.'),
|
||||||
'announceResponse': ('string', 1, 7, None, None, ''),
|
'announceResponse': ('string', 1, 7, None, None, 'The announce message from the tracker.'),
|
||||||
'announceURL': ('string', 1, 7, None, None, ''),
|
'announceURL': ('string', 1, 7, None, None, 'Current announce URL.'),
|
||||||
'bandwidthPriority': ('number', 5, None, None, None, ''),
|
'bandwidthPriority': ('number', 5, None, None, None, 'Bandwidth priority. Low (-1), Normal (0) or High (1).'),
|
||||||
'comment': ('string', 1, None, None, None, ''),
|
'comment': ('string', 1, None, None, None, 'Torrent comment.'),
|
||||||
'corruptEver': ('number', 1, None, None, None, ''),
|
'corruptEver': ('number', 1, None, None, None, 'Number of bytes of corrupt data downloaded.'),
|
||||||
'creator': ('string', 1, None, None, None, ''),
|
'creator': ('string', 1, None, None, None, 'Torrent creator.'),
|
||||||
'dateCreated': ('number', 1, None, None, None, ''),
|
'dateCreated': ('number', 1, None, None, None, 'Torrent creation date.'),
|
||||||
'desiredAvailable': ('number', 1, None, None, None, ''),
|
'desiredAvailable': ('number', 1, None, None, None, 'Number of bytes avalable and left to be downloaded.'),
|
||||||
'doneDate': ('number', 1, None, None, None, ''),
|
'doneDate': ('number', 1, None, None, None, 'The date when the torrent finished downloading.'),
|
||||||
'downloadDir': ('string', 4, None, None, None, ''),
|
'downloadDir': ('string', 4, None, None, None, 'The directory path where the torrent is downloaded to.'),
|
||||||
'downloadedEver': ('number', 1, None, None, None, ''),
|
'downloadedEver': ('number', 1, None, None, None, 'Number of bytes of good data downloaded.'),
|
||||||
'downloaders': ('number', 4, 7, None, None, ''),
|
'downloaders': ('number', 4, 7, None, None, 'Number of downloaders.'),
|
||||||
'downloadLimit': ('number', 1, None, None, None, ''),
|
'downloadLimit': ('number', 1, None, None, None, 'Download limit in Kbps.'),
|
||||||
'downloadLimited': ('boolean', 5, None, None, None, ''),
|
'downloadLimited': ('boolean', 5, None, None, None, 'Download limit is enabled'),
|
||||||
'downloadLimitMode': ('number', 1, 5, None, None, ''),
|
'downloadLimitMode': ('number', 1, 5, None, None, 'Download limit mode. 0 means global, 1 means signle, 2 unlimited.'),
|
||||||
'error': ('number', 1, None, None, None, ''),
|
'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, ''),
|
'errorString': ('number', 1, None, None, None, 'Error message.'),
|
||||||
'eta': ('number', 1, None, None, None, ''),
|
'eta': ('number', 1, None, None, None, 'Estimated number of seconds left when downloading or seeding. -1 means not available and -2 means unknown.'),
|
||||||
'files': ('array', 1, None, None, None, ''),
|
'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.'),
|
||||||
'fileStats': ('array', 5, None, None, None, ''),
|
'files': ('array', 1, None, None, None, 'Array of file object containing key, bytesCompleted, length and name.'),
|
||||||
'hashString': ('string', 1, None, None, None, ''),
|
'fileStats': ('array', 5, None, None, None, 'Aray of file statistics containing bytesCompleted, wanted and priority.'),
|
||||||
'haveUnchecked': ('number', 1, None, None, None, ''),
|
'hashString': ('string', 1, None, None, None, 'Hashstring unique for the torrent even between sessions.'),
|
||||||
'haveValid': ('number', 1, None, None, None, ''),
|
'haveUnchecked': ('number', 1, None, None, None, 'Number of bytes of partial pieces.'),
|
||||||
'honorsSessionLimits': ('boolean', 5, None, None, None, ''),
|
'haveValid': ('number', 1, None, None, None, 'Number of bytes of checksum verified data.'),
|
||||||
'id': ('number', 1, None, None, None, ''),
|
'honorsSessionLimits': ('boolean', 5, None, None, None, 'True if session upload limits are honored'),
|
||||||
'isFinished': ('boolean', 9, None, None, None, ''),
|
'id': ('number', 1, None, None, None, 'Session unique torrent id.'),
|
||||||
'isPrivate': ('boolean', 1, None, None, None, ''),
|
'isFinished': ('boolean', 9, None, None, None, 'True if the torrent is finished. Downloaded and seeded.'),
|
||||||
'isStalled': ('boolean', 14, None, None, None, ''),
|
'isPrivate': ('boolean', 1, None, None, None, 'True if the torrent is private.'),
|
||||||
'lastAnnounceTime': ('number', 1, 7, None, None, ''),
|
'isStalled': ('boolean', 14, None, None, None, 'True if the torrent has stalled (been idle for a long time).'),
|
||||||
'lastScrapeTime': ('number', 1, 7, None, None, ''),
|
'lastAnnounceTime': ('number', 1, 7, None, None, 'The time of the last announcement.'),
|
||||||
'leechers': ('number', 1, 7, None, None, ''),
|
'lastScrapeTime': ('number', 1, 7, None, None, 'The time af the last successful scrape.'),
|
||||||
'leftUntilDone': ('number', 1, None, None, None, ''),
|
'leechers': ('number', 1, 7, None, None, 'Number of leechers.'),
|
||||||
'magnetLink': ('string', 7, None, None, None, ''),
|
'leftUntilDone': ('number', 1, None, None, None, 'Number of bytes left until the download is done.'),
|
||||||
'manualAnnounceTime': ('number', 1, None, None, None, ''),
|
'magnetLink': ('string', 7, None, None, None, 'The magnet link for this torrent.'),
|
||||||
'maxConnectedPeers': ('number', 1, None, None, None, ''),
|
'manualAnnounceTime': ('number', 1, None, None, None, 'The time until you manually ask for more peers.'),
|
||||||
'metadataPercentComplete': ('number', 7, None, None, None, ''),
|
'maxConnectedPeers': ('number', 1, None, None, None, 'Maximum of connected peers.'),
|
||||||
'name': ('string', 1, None, None, None, ''),
|
'metadataPercentComplete': ('number', 7, None, None, None, 'Download progress of metadata. 0.0 to 1.0.'),
|
||||||
'nextAnnounceTime': ('number', 1, 7, None, None, ''),
|
'name': ('string', 1, None, None, None, 'Torrent name.'),
|
||||||
'nextScrapeTime': ('number', 1, 7, None, None, ''),
|
'nextAnnounceTime': ('number', 1, 7, None, None, 'Next announce time.'),
|
||||||
'peer-limit': ('number', 5, None, None, None, ''),
|
'nextScrapeTime': ('number', 1, 7, None, None, 'Next scrape time.'),
|
||||||
'peers': ('array', 2, None, None, None, ''),
|
'peer-limit': ('number', 5, None, None, None, 'Maximum number of peers.'),
|
||||||
'peersConnected': ('number', 1, None, None, None, ''),
|
'peers': ('array', 2, None, None, None, 'Array of peer objects.'),
|
||||||
'peersFrom': ('object', 1, None, None, None, ''),
|
'peersConnected': ('number', 1, None, None, None, 'Number of peers we are connected to.'),
|
||||||
'peersGettingFromUs': ('number', 1, None, None, None, ''),
|
'peersFrom': ('object', 1, None, None, None, 'Object containing download peers counts for different peer types.'),
|
||||||
'peersKnown': ('number', 1, 13, None, None, ''),
|
'peersGettingFromUs': ('number', 1, None, None, None, 'Number of peers we are sending data to.'),
|
||||||
'peersSendingToUs': ('number', 1, None, None, None, ''),
|
'peersKnown': ('number', 1, 13, None, None, 'Number of peers that the tracker knows.'),
|
||||||
'percentDone': ('double', 5, None, None, None, ''),
|
'peersSendingToUs': ('number', 1, None, None, None, 'Number of peers sending to us'),
|
||||||
'pieces': ('string', 5, None, None, None, ''),
|
'percentDone': ('double', 5, None, None, None, 'Download progress of selected files. 0.0 to 1.0.'),
|
||||||
'pieceCount': ('number', 1, None, None, None, ''),
|
'pieces': ('string', 5, None, None, None, 'String with base64 encoded bitfield indicating finished pieces.'),
|
||||||
'pieceSize': ('number', 1, None, None, None, ''),
|
'pieceCount': ('number', 1, None, None, None, 'Number of pieces.'),
|
||||||
'priorities': ('array', 1, None, None, None, ''),
|
'pieceSize': ('number', 1, None, None, None, 'Number of bytes in a piece.'),
|
||||||
'queuePosition': ('number', 14, None, None, None, ''),
|
'priorities': ('array', 1, None, None, None, 'Array of file priorities.'),
|
||||||
'rateDownload': ('number', 1, None, None, None, ''),
|
'queuePosition': ('number', 14, None, None, None, 'The queue position.'),
|
||||||
'rateUpload': ('number', 1, None, None, None, ''),
|
'rateDownload': ('number', 1, None, None, None, 'Download rate in bps.'),
|
||||||
'recheckProgress': ('double', 1, None, None, None, ''),
|
'rateUpload': ('number', 1, None, None, None, 'Upload rate in bps.'),
|
||||||
'scrapeResponse': ('string', 1, 7, None, None, ''),
|
'recheckProgress': ('double', 1, None, None, None, 'Progress of recheck. 0.0 to 1.0.'),
|
||||||
'scrapeURL': ('string', 1, 7, None, None, ''),
|
'secondsDownloading': ('number', 15, None, None, None, ''),
|
||||||
'seeders': ('number', 1, 7, None, None, ''),
|
'secondsSeeding': ('number', 15, None, None, None, ''),
|
||||||
'seedIdleLimit': ('number', 10, None, None, None, ''),
|
'scrapeResponse': ('string', 1, 7, None, None, 'Scrape response message.'),
|
||||||
'seedIdleMode': ('number', 10, None, None, None, ''),
|
'scrapeURL': ('string', 1, 7, None, None, 'Current scrape URL'),
|
||||||
'seedRatioLimit': ('double', 5, None, None, None, ''),
|
'seeders': ('number', 1, 7, None, None, 'Number of seeders reported by the tracker.'),
|
||||||
'seedRatioMode': ('number', 5, None, None, None, ''),
|
'seedIdleLimit': ('number', 10, None, None, None, 'Idle limit in minutes.'),
|
||||||
'sizeWhenDone': ('number', 1, None, None, None, ''),
|
'seedIdleMode': ('number', 10, None, None, None, 'Use global (0), torrent (1), or unlimited (2) limit.'),
|
||||||
'startDate': ('number', 1, None, None, None, ''),
|
'seedRatioLimit': ('double', 5, None, None, None, 'Seed ratio limit.'),
|
||||||
'status': ('number', 1, None, None, None, ''),
|
'seedRatioMode': ('number', 5, None, None, None, 'Use global (0), torrent (1), or unlimited (2) limit.'),
|
||||||
'swarmSpeed': ('number', 1, 7, None, None, ''),
|
'sizeWhenDone': ('number', 1, None, None, None, 'Size of the torrent download in bytes.'),
|
||||||
'timesCompleted': ('number', 1, 7, None, None, ''),
|
'startDate': ('number', 1, None, None, None, 'The date when the torrent was last started.'),
|
||||||
'trackers': ('array', 1, None, None, None, ''),
|
'status': ('number', 1, None, None, None, 'Current status, see source'),
|
||||||
'trackerStats': ('object', 7, None, None, None, ''),
|
'swarmSpeed': ('number', 1, 7, None, None, 'Estimated speed in Kbps in the swarm.'),
|
||||||
'totalSize': ('number', 1, None, None, None, ''),
|
'timesCompleted': ('number', 1, 7, None, None, 'Number of successful downloads reported by the tracker.'),
|
||||||
'torrentFile': ('string', 5, None, None, None, ''),
|
'trackers': ('array', 1, None, None, None, 'Array of tracker objects.'),
|
||||||
'uploadedEver': ('number', 1, None, None, None, ''),
|
'trackerStats': ('object', 7, None, None, None, 'Array of object containing tracker statistics.'),
|
||||||
'uploadLimit': ('number', 1, None, None, None, ''),
|
'totalSize': ('number', 1, None, None, None, 'Total size of the torrent in bytes'),
|
||||||
'uploadLimitMode': ('number', 1, 5, None, None, ''),
|
'torrentFile': ('string', 5, None, None, None, 'Path to .torrent file.'),
|
||||||
'uploadLimited': ('boolean', 5, None, None, None, ''),
|
'uploadedEver': ('number', 1, None, None, None, 'Number of bytes uploaded, ever.'),
|
||||||
'uploadRatio': ('double', 1, None, None, None, ''),
|
'uploadLimit': ('number', 1, None, None, None, 'Upload limit in Kbps'),
|
||||||
'wanted': ('array', 1, None, None, None, ''),
|
'uploadLimitMode': ('number', 1, 5, None, None, 'Upload limit mode. 0 means global, 1 means signle, 2 unlimited.'),
|
||||||
'webseeds': ('array', 1, None, None, None, ''),
|
'uploadLimited': ('boolean', 5, None, None, None, 'Upload limit enabled.'),
|
||||||
'webseedsSendingToUs': ('number', 1, None, None, None, ''),
|
'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': {
|
'set': {
|
||||||
'bandwidthPriority': ('number', 5, None, None, None, 'Priority for this transfer.'),
|
'bandwidthPriority': ('number', 5, None, None, None, 'Priority for this transfer.'),
|
||||||
|
@ -188,58 +192,58 @@ TORRENT_ARGS = {
|
||||||
# Arguments for session methods
|
# Arguments for session methods
|
||||||
SESSION_ARGS = {
|
SESSION_ARGS = {
|
||||||
'get': {
|
'get': {
|
||||||
"alt-speed-down": ('number', 5, None, None, None, ''),
|
"alt-speed-down": ('number', 5, None, None, None, 'Alternate session download speed limit (in Kib/s).'),
|
||||||
"alt-speed-enabled": ('boolean', 5, None, None, None, ''),
|
"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, ''),
|
"alt-speed-time-begin": ('number', 5, None, None, None, 'Time when alternate speeds should be enabled. Minutes after midnight.'),
|
||||||
"alt-speed-time-enabled": ('boolean', 5, None, None, None, ''),
|
"alt-speed-time-enabled": ('boolean', 5, None, None, None, 'True if alternate speeds scheduling is enabled.'),
|
||||||
"alt-speed-time-end": ('number', 5, None, None, None, ''),
|
"alt-speed-time-end": ('number', 5, None, None, None, 'Time when alternate speeds should be disabled. Minutes after midnight.'),
|
||||||
"alt-speed-time-day": ('number', 5, None, None, None, ''),
|
"alt-speed-time-day": ('number', 5, None, None, None, 'Days alternate speeds scheduling is enabled.'),
|
||||||
"alt-speed-up": ('number', 5, None, None, None, ''),
|
"alt-speed-up": ('number', 5, None, None, None, 'Alternate session upload speed limit (in Kib/s)'),
|
||||||
"blocklist-enabled": ('boolean', 5, None, None, None, ''),
|
"blocklist-enabled": ('boolean', 5, None, None, None, 'True when blocklist is enabled.'),
|
||||||
"blocklist-size": ('number', 5, None, None, None, ''),
|
"blocklist-size": ('number', 5, None, None, None, 'Number of rules in the blocklist'),
|
||||||
"blocklist-url": ('string', 11, None, None, None, ''),
|
"blocklist-url": ('string', 11, None, None, None, 'Location of the block list. Updated with blocklist-update.'),
|
||||||
"cache-size-mb": ('number', 10, None, None, None, ''),
|
"cache-size-mb": ('number', 10, None, None, None, 'The maximum size of the disk cache in MB'),
|
||||||
"config-dir": ('string', 8, None, None, None, ''),
|
"config-dir": ('string', 8, None, None, None, 'location of transmissions configuration directory'),
|
||||||
"dht-enabled": ('boolean', 6, None, None, None, ''),
|
"dht-enabled": ('boolean', 6, None, None, None, 'True if DHT enabled.'),
|
||||||
"download-dir": ('string', 1, None, None, None, ''),
|
"download-dir": ('string', 1, None, None, None, 'The download directory.'),
|
||||||
"download-dir-free-space": ('number', 12, None, None, None, ''),
|
"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, ''),
|
"download-queue-size": ('number', 14, None, None, None, 'Number of slots in the download queue.'),
|
||||||
"download-queue-enabled": ('boolean', 14, None, None, None, ''),
|
"download-queue-enabled": ('boolean', 14, None, None, None, 'True if the download queue is enabled.'),
|
||||||
"encryption": ('string', 1, None, None, None, ''),
|
"encryption": ('string', 1, None, None, None, 'Encryption mode, one of ``required``, ``preferred`` or ``tolerated``.'),
|
||||||
"idle-seeding-limit": ('number', 10, None, None, None, ''),
|
"idle-seeding-limit": ('number', 10, None, None, None, 'Seed inactivity limit in minutes.'),
|
||||||
"idle-seeding-limit-enabled": ('boolean', 10, None, None, None, ''),
|
"idle-seeding-limit-enabled": ('boolean', 10, None, None, None, 'True if the seed activity limit is enabled.'),
|
||||||
"incomplete-dir": ('string', 7, None, None, None, ''),
|
"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, ''),
|
"incomplete-dir-enabled": ('boolean', 7, None, None, None, 'True if the incomplete dir is enabled.'),
|
||||||
"lpd-enabled": ('boolean', 9, None, None, None, ''),
|
"lpd-enabled": ('boolean', 9, None, None, None, 'True if local peer discovery is enabled.'),
|
||||||
"peer-limit": ('number', 1, 5, None, None, ''),
|
"peer-limit": ('number', 1, 5, None, 'peer-limit-global', 'Maximum number of peers.'),
|
||||||
"peer-limit-global": ('number', 5, None, None, None, ''),
|
"peer-limit-global": ('number', 5, None, 'peer-limit', None, 'Maximum number of peers.'),
|
||||||
"peer-limit-per-torrent": ('number', 5, None, None, None, ''),
|
"peer-limit-per-torrent": ('number', 5, None, None, None, 'Maximum number of peers per transfer.'),
|
||||||
"pex-allowed": ('boolean', 1, 5, None, None, ''),
|
"pex-allowed": ('boolean', 1, 5, None, 'pex-enabled', 'True if PEX is allowed.'),
|
||||||
"pex-enabled": ('boolean', 5, None, None, None, ''),
|
"pex-enabled": ('boolean', 5, None, 'pex-allowed', None, 'True if PEX is enabled.'),
|
||||||
"port": ('number', 1, 5, None, None, ''),
|
"port": ('number', 1, 5, None, 'peer-port', 'Peer port.'),
|
||||||
"peer-port": ('number', 5, None, None, None, ''),
|
"peer-port": ('number', 5, None, 'port', None, 'Peer port.'),
|
||||||
"peer-port-random-on-start": ('boolean', 5, None, None, None, ''),
|
"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, ''),
|
"port-forwarding-enabled": ('boolean', 1, None, None, None, 'True if port forwarding is enabled.'),
|
||||||
"queue-stalled-minutes": ('number', 14, None, None, None, ''),
|
"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, ''),
|
"queue-stalled-enabled": ('boolean', 14, None, None, None, 'True if stalled tracking of transfers is enabled.'),
|
||||||
"rename-partial-files": ('boolean', 8, None, None, None, ''),
|
"rename-partial-files": ('boolean', 8, None, None, None, 'True if ".part" is appended to incomplete files'),
|
||||||
"rpc-version": ('number', 4, None, None, None, ''),
|
"rpc-version": ('number', 4, None, None, None, 'Transmission RPC API Version.'),
|
||||||
"rpc-version-minimum": ('number', 4, None, None, None, ''),
|
"rpc-version-minimum": ('number', 4, None, None, None, 'Minimum accepted RPC API Version.'),
|
||||||
"script-torrent-done-enabled": ('boolean', 9, None, None, None, ''),
|
"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, ''),
|
"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, ''),
|
"seedRatioLimit": ('double', 5, None, None, None, 'Seed ratio limit. 1.0 means 1:1 download and upload ratio.'),
|
||||||
"seedRatioLimited": ('boolean', 5, None, None, None, ''),
|
"seedRatioLimited": ('boolean', 5, None, None, None, 'True if seed ration limit is enabled.'),
|
||||||
"seed-queue-size": ('number', 14, None, None, None, ''),
|
"seed-queue-size": ('number', 14, None, None, None, 'Number of slots in the upload queue.'),
|
||||||
"seed-queue-enabled": ('boolean', 14, None, None, None, ''),
|
"seed-queue-enabled": ('boolean', 14, None, None, None, 'True if upload queue is enabled.'),
|
||||||
"speed-limit-down": ('number', 1, None, None, None, ''),
|
"speed-limit-down": ('number', 1, None, None, None, 'Download speed limit (in Kib/s).'),
|
||||||
"speed-limit-down-enabled": ('boolean', 1, None, None, None, ''),
|
"speed-limit-down-enabled": ('boolean', 1, None, None, None, 'True if the download speed is limited.'),
|
||||||
"speed-limit-up": ('number', 1, None, None, None, ''),
|
"speed-limit-up": ('number', 1, None, None, None, 'Upload speed limit (in Kib/s).'),
|
||||||
"speed-limit-up-enabled": ('boolean', 1, None, None, None, ''),
|
"speed-limit-up-enabled": ('boolean', 1, None, None, None, 'True if the upload speed is limited.'),
|
||||||
"start-added-torrents": ('boolean', 9, None, None, None, ''),
|
"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, ''),
|
"trash-original-torrent-files": ('boolean', 9, None, None, None, 'When true added .torrent files will be deleted.'),
|
||||||
'units': ('object', 10, None, None, None, ''),
|
'units': ('object', 10, None, None, None, 'An object containing units for size and speed.'),
|
||||||
'utp-enabled': ('boolean', 13, None, None, None, ''),
|
'utp-enabled': ('boolean', 13, None, None, None, 'True if Micro Transport Protocol (UTP) is enabled.'),
|
||||||
"version": ('string', 3, None, None, None, ''),
|
"version": ('string', 3, None, None, None, 'Transmission version.'),
|
||||||
},
|
},
|
||||||
'set': {
|
'set': {
|
||||||
"alt-speed-down": ('number', 5, None, None, None, 'Alternate session download speed limit (in Kib/s).'),
|
"alt-speed-down": ('number', 5, None, None, None, 'Alternate session download speed limit (in Kib/s).'),
|
||||||
|
@ -254,17 +258,17 @@ SESSION_ARGS = {
|
||||||
"cache-size-mb": ('number', 10, None, None, None, 'The maximum size of the disk cache in MB'),
|
"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.'),
|
"dht-enabled": ('boolean', 6, None, None, None, 'Enables DHT.'),
|
||||||
"download-dir": ('string', 1, None, None, None, 'Set the session download directory.'),
|
"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-size": ('number', 14, None, None, None, 'Number of slots in the download queue.'),
|
||||||
"download-queue-enabled": ('boolean', 14, None, None, None, 'Enable parallel download restriction.'),
|
"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``.'),
|
"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": ('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'),
|
"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": ('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.'),
|
"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.'),
|
"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": ('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-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'),
|
"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-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.'),
|
"pex-enabled": ('boolean', 5, None, 'pex-allowed', None, 'Allowing PEX in public torrents.'),
|
||||||
"port": ('number', 1, 5, None, 'peer-port', 'Peer port.'),
|
"port": ('number', 1, 5, None, 'peer-port', 'Peer port.'),
|
||||||
|
@ -276,8 +280,8 @@ SESSION_ARGS = {
|
||||||
"queue-stalled-enabled": ('boolean', 14, None, None, None, 'Enable tracking of stalled transfers.'),
|
"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-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.'),
|
"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-size": ('number', 14, None, None, None, 'Number of slots in the upload queue.'),
|
||||||
"seed-queue-enabled": ('boolean', 14, None, None, None, 'Enable parallel upload restriction.'),
|
"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.'),
|
"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.'),
|
"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": ('number', 1, None, None, None, 'Download speed limit (in Kib/s).'),
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
|
# Copyright (c) 2008-2013 Erik Svensson <erik.public@gmail.com>
|
||||||
# Licensed under the MIT license.
|
# Licensed under the MIT license.
|
||||||
|
|
||||||
|
from six import string_types, integer_types
|
||||||
|
|
||||||
class TransmissionError(Exception):
|
class TransmissionError(Exception):
|
||||||
"""
|
"""
|
||||||
This exception is raised when there has occurred an error related to
|
This exception is raised when there has occurred an error related to
|
||||||
|
@ -9,7 +11,7 @@ class TransmissionError(Exception):
|
||||||
"""
|
"""
|
||||||
def __init__(self, message='', original=None):
|
def __init__(self, message='', original=None):
|
||||||
Exception.__init__(self)
|
Exception.__init__(self)
|
||||||
self._message = message
|
self.message = message
|
||||||
self.original = original
|
self.original = original
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -28,25 +30,25 @@ class HTTPHandlerError(Exception):
|
||||||
Exception.__init__(self)
|
Exception.__init__(self)
|
||||||
self.url = ''
|
self.url = ''
|
||||||
self.code = 600
|
self.code = 600
|
||||||
self._message = ''
|
self.message = ''
|
||||||
self.headers = {}
|
self.headers = {}
|
||||||
self.data = ''
|
self.data = ''
|
||||||
if isinstance(httpurl, (str, unicode)):
|
if isinstance(httpurl, string_types):
|
||||||
self.url = httpurl
|
self.url = httpurl
|
||||||
if isinstance(httpcode, (int, long)):
|
if isinstance(httpcode, integer_types):
|
||||||
self.code = httpcode
|
self.code = httpcode
|
||||||
if isinstance(httpmsg, (str, unicode)):
|
if isinstance(httpmsg, string_types):
|
||||||
self._message = httpmsg
|
self.message = httpmsg
|
||||||
if isinstance(httpheaders, dict):
|
if isinstance(httpheaders, dict):
|
||||||
self.headers = httpheaders
|
self.headers = httpheaders
|
||||||
if isinstance(httpdata, (str, unicode)):
|
if isinstance(httpdata, string_types):
|
||||||
self.data = httpdata
|
self.data = httpdata
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<HTTPHandlerError %d, %s>' % (self.code, self._message)
|
return '<HTTPHandlerError %d, %s>' % (self.code, self.message)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'HTTPHandlerError %d: %s' % (self.code, self._message)
|
return 'HTTPHandlerError %d: %s' % (self.code, self.message)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u'HTTPHandlerError %d: %s' % (self.code, self._message)
|
return 'HTTPHandlerError %d: %s' % (self.code, self.message)
|
||||||
|
|
|
@ -1,11 +1,24 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2011 Erik Svensson <erik.public@gmail.com>
|
# Copyright (c) 2011-2013 Erik Svensson <erik.public@gmail.com>
|
||||||
# Licensed under the MIT license.
|
# Licensed under the MIT license.
|
||||||
|
|
||||||
import sys, httplib, urllib2
|
import sys
|
||||||
|
|
||||||
from transmissionrpc.error import HTTPHandlerError
|
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):
|
class HTTPHandler(object):
|
||||||
"""
|
"""
|
||||||
Prototype for HTTP handling.
|
Prototype for HTTP handling.
|
||||||
|
@ -38,35 +51,32 @@ class DefaultHTTPHandler(HTTPHandler):
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
HTTPHandler.__init__(self)
|
HTTPHandler.__init__(self)
|
||||||
|
self.http_opener = build_opener()
|
||||||
|
|
||||||
def set_authentication(self, uri, login, password):
|
def set_authentication(self, uri, login, password):
|
||||||
password_manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
|
password_manager = HTTPPasswordMgrWithDefaultRealm()
|
||||||
password_manager.add_password(realm=None, uri=uri, user=login, passwd=password)
|
password_manager.add_password(realm=None, uri=uri, user=login, passwd=password)
|
||||||
opener = urllib2.build_opener(
|
self.http_opener = build_opener(HTTPBasicAuthHandler(password_manager), HTTPDigestAuthHandler(password_manager))
|
||||||
urllib2.HTTPBasicAuthHandler(password_manager)
|
|
||||||
, urllib2.HTTPDigestAuthHandler(password_manager)
|
|
||||||
)
|
|
||||||
urllib2.install_opener(opener)
|
|
||||||
|
|
||||||
def request(self, url, query, headers, timeout):
|
def request(self, url, query, headers, timeout):
|
||||||
request = urllib2.Request(url, query, headers)
|
request = Request(url, query.encode('utf-8'), headers)
|
||||||
try:
|
try:
|
||||||
if (sys.version_info[0] == 2 and sys.version_info[1] > 5) or sys.version_info[0] > 2:
|
if (sys.version_info[0] == 2 and sys.version_info[1] > 5) or sys.version_info[0] > 2:
|
||||||
response = urllib2.urlopen(request, timeout=timeout)
|
response = self.http_opener.open(request, timeout=timeout)
|
||||||
else:
|
else:
|
||||||
response = urllib2.urlopen(request)
|
response = self.http_opener.open(request)
|
||||||
except urllib2.HTTPError, error:
|
except HTTPError as error:
|
||||||
if error.fp is None:
|
if error.fp is None:
|
||||||
raise HTTPHandlerError(error.filename, error.code, error.msg, dict(error.hdrs))
|
raise HTTPHandlerError(error.filename, error.code, error.msg, dict(error.hdrs))
|
||||||
else:
|
else:
|
||||||
raise HTTPHandlerError(error.filename, error.code, error.msg, dict(error.hdrs), error.read())
|
raise HTTPHandlerError(error.filename, error.code, error.msg, dict(error.hdrs), error.read())
|
||||||
except urllib2.URLError, error:
|
except URLError as error:
|
||||||
# urllib2.URLError documentation is horrendous!
|
# urllib2.URLError documentation is horrendous!
|
||||||
# Try to get the tuple arguments of URLError
|
# 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:
|
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])
|
raise HTTPHandlerError(httpcode=error.reason.args[0], httpmsg=error.reason.args[1])
|
||||||
else:
|
else:
|
||||||
raise HTTPHandlerError(httpmsg='urllib2.URLError: %s' % (error.reason))
|
raise HTTPHandlerError(httpmsg='urllib2.URLError: %s' % (error.reason))
|
||||||
except httplib.BadStatusLine, error:
|
except BadStatusLine as error:
|
||||||
raise HTTPHandlerError(httpmsg='httplib.BadStatusLine: %s' % (error.line))
|
raise HTTPHandlerError(httpmsg='httplib.BadStatusLine: %s' % (error.line))
|
||||||
return response.read()
|
return response.read().decode('utf-8')
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
|
# Copyright (c) 2008-2013 Erik Svensson <erik.public@gmail.com>
|
||||||
# Licensed under the MIT license.
|
# Licensed under the MIT license.
|
||||||
|
|
||||||
|
from transmissionrpc.utils import Field
|
||||||
|
|
||||||
|
from six import iteritems, integer_types
|
||||||
|
|
||||||
class Session(object):
|
class Session(object):
|
||||||
"""
|
"""
|
||||||
Session is a class holding the session data for a Transmission daemon.
|
Session is a class holding the session data for a Transmission daemon.
|
||||||
|
@ -12,33 +16,96 @@ class Session(object):
|
||||||
``download-dir`` -> ``download_dir``.
|
``download-dir`` -> ``download_dir``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, fields=None):
|
def __init__(self, client=None, fields=None):
|
||||||
self.fields = {}
|
self._client = client
|
||||||
|
self._fields = {}
|
||||||
if fields is not None:
|
if fields is not None:
|
||||||
self.update(fields)
|
self._update_fields(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):
|
def __getattr__(self, name):
|
||||||
try:
|
try:
|
||||||
return self.fields[name]
|
return self._fields[name].value
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise AttributeError('No attribute %s' % name)
|
raise AttributeError('No attribute %s' % name)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
text = ''
|
text = ''
|
||||||
for key in sorted(self.fields.keys()):
|
for key in sorted(self._fields.keys()):
|
||||||
text += "% 32s: %s\n" % (key[-32:], self.fields[key])
|
text += "% 32s: %s\n" % (key[-32:], self._fields[key].value)
|
||||||
return text
|
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.")
|
||||||
|
|
441
resources/lib/transmissionrpc/torrent.py
Normal file → Executable file
441
resources/lib/transmissionrpc/torrent.py
Normal file → Executable file
|
@ -1,65 +1,17 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
|
# Copyright (c) 2008-2013 Erik Svensson <erik.public@gmail.com>
|
||||||
# Licensed under the MIT license.
|
# Licensed under the MIT license.
|
||||||
|
|
||||||
import sys, datetime
|
import sys, datetime
|
||||||
|
|
||||||
from transmissionrpc.constants import PRIORITY
|
from transmissionrpc.constants import PRIORITY, RATIO_LIMIT, IDLE_LIMIT
|
||||||
from transmissionrpc.utils import format_timedelta
|
from transmissionrpc.utils import Field, format_timedelta
|
||||||
|
|
||||||
class Torrent(object):
|
from six import integer_types, string_types, text_type, iteritems
|
||||||
"""
|
|
||||||
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):
|
def get_status_old(code):
|
||||||
if codec is None:
|
"""Get the torrent status using old status codes"""
|
||||||
codec = sys.getdefaultencoding()
|
|
||||||
name = None
|
|
||||||
# try to find name
|
|
||||||
if 'name' in self.fields:
|
|
||||||
name = self.fields['name']
|
|
||||||
# if name is unicode, try to decode
|
|
||||||
if isinstance(name, unicode):
|
|
||||||
try:
|
|
||||||
name = name.encode(codec)
|
|
||||||
except UnicodeError:
|
|
||||||
name = None
|
|
||||||
return name
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
tid = self.fields['id']
|
|
||||||
name = self._getNameString()
|
|
||||||
if isinstance(name, str):
|
|
||||||
return '<Torrent %d \"%s\">' % (tid, name)
|
|
||||||
else:
|
|
||||||
return '<Torrent %d>' % (tid)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
name = self._getNameString()
|
|
||||||
if isinstance(name, str):
|
|
||||||
return 'Torrent \"%s\"' % (name)
|
|
||||||
else:
|
|
||||||
return 'Torrent'
|
|
||||||
|
|
||||||
def __copy__(self):
|
|
||||||
return Torrent(self.client, self.fields)
|
|
||||||
|
|
||||||
def _rpc_version(self):
|
|
||||||
if self.client:
|
|
||||||
return self.client.rpc_version
|
|
||||||
return 2
|
|
||||||
|
|
||||||
def _status_old(self, code):
|
|
||||||
mapping = {
|
mapping = {
|
||||||
(1<<0): 'check pending',
|
(1<<0): 'check pending',
|
||||||
(1<<1): 'checking',
|
(1<<1): 'checking',
|
||||||
|
@ -69,7 +21,8 @@ class Torrent(object):
|
||||||
}
|
}
|
||||||
return mapping[code]
|
return mapping[code]
|
||||||
|
|
||||||
def _status_new(self, code):
|
def get_status_new(code):
|
||||||
|
"""Get the torrent status using new status codes"""
|
||||||
mapping = {
|
mapping = {
|
||||||
0: 'stopped',
|
0: 'stopped',
|
||||||
1: 'check pending',
|
1: 'check pending',
|
||||||
|
@ -81,26 +34,111 @@ class Torrent(object):
|
||||||
}
|
}
|
||||||
return mapping[code]
|
return mapping[code]
|
||||||
|
|
||||||
def _status(self):
|
class Torrent(object):
|
||||||
code = self.fields['status']
|
"""
|
||||||
if self._rpc_version() >= 14:
|
Torrent is a class holding the data received from Transmission regarding a bittorrent transfer.
|
||||||
return self._status_new(code)
|
|
||||||
else:
|
|
||||||
return self._status_old(code)
|
|
||||||
|
|
||||||
def update(self, other):
|
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 '<Torrent %d \"%s\">' % (tid, name)
|
||||||
|
else:
|
||||||
|
return '<Torrent %d>' % (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
|
Update the torrent data from a Transmission JSON-RPC arguments dictionary
|
||||||
"""
|
"""
|
||||||
fields = None
|
fields = None
|
||||||
if isinstance(other, dict):
|
if isinstance(other, dict):
|
||||||
fields = other
|
for key, value in iteritems(other):
|
||||||
|
self._fields[key.replace('-', '_')] = Field(value, False)
|
||||||
elif isinstance(other, Torrent):
|
elif isinstance(other, Torrent):
|
||||||
fields = other.fields
|
for key in list(other._fields.keys()):
|
||||||
|
self._fields[key] = Field(other._fields[key].value, False)
|
||||||
else:
|
else:
|
||||||
raise ValueError('Cannot update with supplied data')
|
raise ValueError('Cannot update with supplied data')
|
||||||
for key, value in fields.iteritems():
|
self._incoming_pending = False
|
||||||
self.fields[key.replace('-', '_')] = value
|
|
||||||
|
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):
|
def files(self):
|
||||||
"""
|
"""
|
||||||
|
@ -122,15 +160,13 @@ class Torrent(object):
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
result = {}
|
result = {}
|
||||||
if 'files' in self.fields:
|
if 'files' in self._fields:
|
||||||
indices = xrange(len(self.fields['files']))
|
files = self._fields['files'].value
|
||||||
files = self.fields['files']
|
indices = range(len(files))
|
||||||
priorities = self.fields['priorities']
|
priorities = self._fields['priorities'].value
|
||||||
wanted = self.fields['wanted']
|
wanted = self._fields['wanted'].value
|
||||||
for item in zip(indices, files, priorities, wanted):
|
for item in zip(indices, files, priorities, wanted):
|
||||||
selected = False
|
selected = True if item[3] else False
|
||||||
if item[3]:
|
|
||||||
selected = True
|
|
||||||
priority = PRIORITY[item[2]]
|
priority = PRIORITY[item[2]]
|
||||||
result[item[0]] = {
|
result[item[0]] = {
|
||||||
'selected': selected,
|
'selected': selected,
|
||||||
|
@ -140,12 +176,6 @@ class Torrent(object):
|
||||||
'completed': item[1]['bytesCompleted']}
|
'completed': item[1]['bytesCompleted']}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
try:
|
|
||||||
return self.fields[name]
|
|
||||||
except KeyError:
|
|
||||||
raise AttributeError('No attribute %s' % name)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def status(self):
|
def status(self):
|
||||||
"""
|
"""
|
||||||
|
@ -159,43 +189,45 @@ class Torrent(object):
|
||||||
def progress(self):
|
def progress(self):
|
||||||
"""Get the download progress in percent."""
|
"""Get the download progress in percent."""
|
||||||
try:
|
try:
|
||||||
return 100.0 * (self.fields['sizeWhenDone'] - self.fields['leftUntilDone']) / float(self.fields['sizeWhenDone'])
|
size = self._fields['sizeWhenDone'].value
|
||||||
|
left = self._fields['leftUntilDone'].value
|
||||||
|
return 100.0 * (size - left) / float(size)
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ratio(self):
|
def ratio(self):
|
||||||
"""Get the upload/download ratio."""
|
"""Get the upload/download ratio."""
|
||||||
return float(self.fields['uploadRatio'])
|
return float(self._fields['uploadRatio'].value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def eta(self):
|
def eta(self):
|
||||||
"""Get the "eta" as datetime.timedelta."""
|
"""Get the "eta" as datetime.timedelta."""
|
||||||
eta = self.fields['eta']
|
eta = self._fields['eta'].value
|
||||||
if eta >= 0:
|
if eta >= 0:
|
||||||
return datetime.timedelta(seconds=eta)
|
return datetime.timedelta(seconds=eta)
|
||||||
else:
|
else:
|
||||||
ValueError('eta not valid')
|
raise ValueError('eta not valid')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def date_active(self):
|
def date_active(self):
|
||||||
"""Get the attribute "activityDate" as datetime.datetime."""
|
"""Get the attribute "activityDate" as datetime.datetime."""
|
||||||
return datetime.datetime.fromtimestamp(self.fields['activityDate'])
|
return datetime.datetime.fromtimestamp(self._fields['activityDate'].value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def date_added(self):
|
def date_added(self):
|
||||||
"""Get the attribute "addedDate" as datetime.datetime."""
|
"""Get the attribute "addedDate" as datetime.datetime."""
|
||||||
return datetime.datetime.fromtimestamp(self.fields['addedDate'])
|
return datetime.datetime.fromtimestamp(self._fields['addedDate'].value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def date_started(self):
|
def date_started(self):
|
||||||
"""Get the attribute "startDate" as datetime.datetime."""
|
"""Get the attribute "startDate" as datetime.datetime."""
|
||||||
return datetime.datetime.fromtimestamp(self.fields['startDate'])
|
return datetime.datetime.fromtimestamp(self._fields['startDate'].value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def date_done(self):
|
def date_done(self):
|
||||||
"""Get the attribute "doneDate" as datetime.datetime."""
|
"""Get the attribute "doneDate" as datetime.datetime."""
|
||||||
return datetime.datetime.fromtimestamp(self.fields['doneDate'])
|
return datetime.datetime.fromtimestamp(self._fields['doneDate'].value)
|
||||||
|
|
||||||
def format_eta(self):
|
def format_eta(self):
|
||||||
"""
|
"""
|
||||||
|
@ -205,7 +237,7 @@ class Torrent(object):
|
||||||
* If eta is -2 the result is 'unknown'
|
* If eta is -2 the result is 'unknown'
|
||||||
* Otherwise eta is formatted as <days> <hours>:<minutes>:<seconds>.
|
* Otherwise eta is formatted as <days> <hours>:<minutes>:<seconds>.
|
||||||
"""
|
"""
|
||||||
eta = self.fields['eta']
|
eta = self._fields['eta'].value
|
||||||
if eta == -1:
|
if eta == -1:
|
||||||
return 'not available'
|
return 'not available'
|
||||||
elif eta == -2:
|
elif eta == -2:
|
||||||
|
@ -213,10 +245,235 @@ class Torrent(object):
|
||||||
else:
|
else:
|
||||||
return format_timedelta(self.eta)
|
return format_timedelta(self.eta)
|
||||||
|
|
||||||
@property
|
def _get_download_limit(self):
|
||||||
def priority(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.
|
Get the priority as string.
|
||||||
Can be one of 'low', 'normal', 'high'.
|
Can be one of 'low', 'normal', 'high'.
|
||||||
"""
|
"""
|
||||||
return PRIORITY[self.fields['bandwidthPriority']]
|
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)
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2008-2011 Erik Svensson <erik.public@gmail.com>
|
# Copyright (c) 2008-2013 Erik Svensson <erik.public@gmail.com>
|
||||||
# Licensed under the MIT license.
|
# Licensed under the MIT license.
|
||||||
|
|
||||||
import socket, datetime, logging
|
import socket, datetime, logging
|
||||||
|
from collections import namedtuple
|
||||||
import transmissionrpc.constants as constants
|
import transmissionrpc.constants as constants
|
||||||
from transmissionrpc.constants import LOGGER
|
from transmissionrpc.constants import LOGGER
|
||||||
|
|
||||||
|
from six import string_types, iteritems
|
||||||
|
|
||||||
UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']
|
UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']
|
||||||
|
|
||||||
def format_size(size):
|
def format_size(size):
|
||||||
|
@ -86,15 +89,12 @@ def rpc_bool(arg):
|
||||||
"""
|
"""
|
||||||
Convert between Python boolean and Transmission RPC boolean.
|
Convert between Python boolean and Transmission RPC boolean.
|
||||||
"""
|
"""
|
||||||
if isinstance(arg, (str, unicode)):
|
if isinstance(arg, string_types):
|
||||||
try:
|
try:
|
||||||
arg = bool(int(arg))
|
arg = bool(int(arg))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
arg = arg.lower() in [u'true', u'yes']
|
arg = arg.lower() in ['true', 'yes']
|
||||||
if bool(arg):
|
return 1 if bool(arg) else 0
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
TR_TYPE_MAP = {
|
TR_TYPE_MAP = {
|
||||||
'number' : int,
|
'number' : int,
|
||||||
|
@ -166,7 +166,7 @@ def get_arguments(method, rpc_version):
|
||||||
else:
|
else:
|
||||||
return ValueError('Method "%s" not supported' % (method))
|
return ValueError('Method "%s" not supported' % (method))
|
||||||
accessible = []
|
accessible = []
|
||||||
for argument, info in args.iteritems():
|
for argument, info in iteritems(args):
|
||||||
valid_version = True
|
valid_version = True
|
||||||
if rpc_version < info[1]:
|
if rpc_version < info[1]:
|
||||||
valid_version = False
|
valid_version = False
|
||||||
|
@ -184,8 +184,24 @@ def add_stdout_logger(level='debug'):
|
||||||
|
|
||||||
trpc_logger = logging.getLogger('transmissionrpc')
|
trpc_logger = logging.getLogger('transmissionrpc')
|
||||||
loghandler = logging.StreamHandler()
|
loghandler = logging.StreamHandler()
|
||||||
if level in levels.keys():
|
if level in list(levels.keys()):
|
||||||
loglevel = levels[level]
|
loglevel = levels[level]
|
||||||
trpc_logger.setLevel(loglevel)
|
trpc_logger.setLevel(loglevel)
|
||||||
loghandler.setLevel(loglevel)
|
loghandler.setLevel(loglevel)
|
||||||
trpc_logger.addHandler(loghandler)
|
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'])
|
||||||
|
|
Loading…
Reference in a new issue