def __init__(self, address='localhost', port=DEFAULT_PORT, user=None, password=None, path=None, http_handler=None, timeout=None): rpcpath = '/transmission/rpc' if path: rpcpath = path if isinstance(timeout, (integer_types, float)): self._query_timeout = float(timeout) else: self._query_timeout = DEFAULT_TIMEOUT urlo = urlparse(address) if urlo.scheme == '': base_url = 'http://' + address + ':' + str(port) self.url = base_url + rpcpath else: if urlo.port: self.url = urlo.scheme + '://' + urlo.hostname + ':' + str( urlo.port) + urlo.path else: self.url = urlo.scheme + '://' + urlo.hostname + urlo.path LOGGER.info('Using custom URL "' + self.url + '".') if urlo.username and urlo.password: user = urlo.username password = urlo.password elif urlo.username or urlo.password: LOGGER.warning( 'Either user or password missing, not using authentication.' ) if http_handler is None: self.http_handler = DefaultHTTPHandler() else: if hasattr(http_handler, 'set_authentication') and hasattr( http_handler, 'request'): self.http_handler = http_handler else: raise ValueError('Invalid HTTP handler.') if user and password: self.http_handler.set_authentication(self.url, user, password) elif user or password: LOGGER.warning( 'Either user or password missing, not using authentication.') self._sequence = 0 self.session = None self.session_id = 0 self.server_version = None self.protocol_version = None self.get_session() self.torrent_get_arguments = get_arguments('torrent-get', self.rpc_version)
def __init__( self, address="localhost", port=DEFAULT_PORT, user=None, password=None, http_handler=None, timeout=None, ): if isinstance(timeout, (int, float)): self._query_timeout = float(timeout) else: self._query_timeout = DEFAULT_TIMEOUT urlo = urlparse(address) if urlo.scheme == "": base_url = "http://" + address + ":" + str(port) self.url = base_url + "/transmission/rpc" else: if urlo.port: self.url = (urlo.scheme + "://" + urlo.hostname + ":" + str(urlo.port) + urlo.path) else: self.url = urlo.scheme + "://" + urlo.hostname + urlo.path LOGGER.info('Using custom URL "%s".', self.url) if urlo.username and urlo.password: user = urlo.username password = urlo.password elif urlo.username or urlo.password: LOGGER.warning( "Either user or password missing, not using authentication." ) if http_handler is None: self.http_handler = DefaultHTTPHandler() else: if hasattr(http_handler, "set_authentication") and hasattr( http_handler, "request"): self.http_handler = http_handler else: raise ValueError("Invalid HTTP handler.") if user and password: self.http_handler.set_authentication(self.url, user, password) elif user or password: LOGGER.warning( "Either user or password missing, not using authentication.") self._sequence = 0 self.session = None self.session_id = 0 self.server_version = None self.protocol_version = None self.get_session() self.torrent_get_arguments = get_arguments("torrent-get", self.rpc_version)
def __init__( self, address="localhost", port=DEFAULT_PORT, user=None, password=None, http_handler=None, timeout=None ): if isinstance(timeout, (int, long, float)): self._query_timeout = float(timeout) else: self._query_timeout = DEFAULT_TIMEOUT urlo = urlparse.urlparse(address) if urlo.scheme == "": base_url = "http://" + address + ":" + str(port) self.url = base_url + "/transmission/rpc" else: if urlo.port: self.url = urlo.scheme + "://" + urlo.hostname + ":" + str(urlo.port) + urlo.path else: self.url = urlo.scheme + "://" + urlo.hostname + urlo.path LOGGER.info('Using custom URL "' + self.url + '".') if urlo.username and urlo.password: user = urlo.username password = urlo.password elif urlo.username or urlo.password: LOGGER.warning("Either user or password missing, not using authentication.") if http_handler is None: self.http_handler = DefaultHTTPHandler() else: if hasattr(http_handler, "set_authentication") and hasattr(http_handler, "request"): self.http_handler = http_handler else: raise ValueError("Invalid HTTP handler.") if user and password: self.http_handler.set_authentication(self.url, user, password) elif user or password: LOGGER.warning("Either user or password missing, not using authentication.") self._sequence = 0 self.session = Session() self.session_id = 0 self.server_version = None self.protocol_version = None self.get_session() self.torrent_get_arguments = get_arguments("torrent-get", self.rpc_version)
class Client(object): """ Client is the class handling the Transmission JSON-RPC client protocol. """ def __init__(self, address='localhost', port=DEFAULT_PORT, user=None, password=None, http_handler=None, timeout=None): if isinstance(timeout, (integer_types, float)): self._query_timeout = float(timeout) else: self._query_timeout = DEFAULT_TIMEOUT urlo = urlparse(address) if urlo.scheme == '': base_url = 'http://' + address + ':' + str(port) self.url = base_url + '/transmission/rpc' else: if urlo.port: self.url = urlo.scheme + '://' + urlo.hostname + ':' + str( urlo.port) + urlo.path else: self.url = urlo.scheme + '://' + urlo.hostname + urlo.path LOGGER.info('Using custom URL "' + self.url + '".') if urlo.username and urlo.password: user = urlo.username password = urlo.password elif urlo.username or urlo.password: LOGGER.warning( 'Either user or password missing, not using authentication.' ) if http_handler is None: self.http_handler = DefaultHTTPHandler() else: if hasattr(http_handler, 'set_authentication') and hasattr( http_handler, 'request'): self.http_handler = http_handler else: raise ValueError('Invalid HTTP handler.') if user and password: self.http_handler.set_authentication(self.url, user, password) elif user or password: LOGGER.warning( 'Either user or password missing, not using authentication.') self._sequence = 0 self.session = None self.session_id = 0 self.server_version = None self.protocol_version = None self.get_session() self.torrent_get_arguments = get_arguments('torrent-get', self.rpc_version) def _get_timeout(self): """ Get current timeout for HTTP queries. """ return self._query_timeout def _set_timeout(self, value): """ Set timeout for HTTP queries. """ self._query_timeout = float(value) def _del_timeout(self): """ Reset the HTTP query timeout to the default. """ self._query_timeout = DEFAULT_TIMEOUT timeout = property(_get_timeout, _set_timeout, _del_timeout, doc="HTTP query timeout.") def _http_query(self, query, timeout=None): """ Query Transmission through HTTP. """ headers = {'x-transmission-session-id': str(self.session_id)} result = {} request_count = 0 if timeout is None: timeout = self._query_timeout while True: LOGGER.debug( json.dumps( { 'url': self.url, 'headers': headers, 'query': query, 'timeout': timeout }, indent=2)) try: result = self.http_handler.request(self.url, query, headers, timeout) break except HTTPHandlerError as error: if error.code == 409: LOGGER.info( 'Server responded with 409, trying to set session-id.') if request_count > 1: raise TransmissionError( 'Session ID negotiation failed.', error) session_id = None for key in list(error.headers.keys()): if key.lower() == 'x-transmission-session-id': session_id = error.headers[key] self.session_id = session_id headers = { 'x-transmission-session-id': str(self.session_id) } if session_id is None: debug_httperror(error) raise TransmissionError('Unknown conflict.', error) else: debug_httperror(error) raise TransmissionError('Request failed.', error) request_count += 1 return result def _request(self, method, arguments=None, ids=None, require_ids=False, timeout=None): """ Send json-rpc request to Transmission using http POST """ if not isinstance(method, string_types): raise ValueError('request takes method as string') if arguments is None: arguments = {} if not isinstance(arguments, dict): raise ValueError('request takes arguments as dict') ids = parse_torrent_ids(ids) if len(ids) > 0: arguments['ids'] = ids elif require_ids: raise ValueError('request require ids') query = json.dumps({ 'tag': self._sequence, 'method': method, 'arguments': arguments }) self._sequence += 1 start = time.time() http_data = self._http_query(query, timeout) elapsed = time.time() - start LOGGER.info('http request took %.3f s' % (elapsed)) try: data = json.loads(http_data) except ValueError as error: LOGGER.error('Error: ' + str(error)) LOGGER.error('Request: \"%s\"' % (query)) LOGGER.error('HTTP data: \"%s\"' % (http_data)) raise LOGGER.debug(json.dumps(data, indent=2)) if 'result' in data: if data['result'] != 'success': raise TransmissionError('Query failed with result \"%s\".' % (data['result'])) else: raise TransmissionError('Query failed without result.') results = {} if method == 'torrent-get': for item in data['arguments']['torrents']: results[item['id']] = Torrent(self, item) if self.protocol_version == 2 and 'peers' not in item: self.protocol_version = 1 elif method == 'torrent-add': item = None if 'torrent-added' in data['arguments']: item = data['arguments']['torrent-added'] elif 'torrent-duplicate' in data['arguments']: item = data['arguments']['torrent-duplicate'] if item: results[item['id']] = Torrent(self, item) else: raise TransmissionError('Invalid torrent-add response.') elif method == 'session-get': self._update_session(data['arguments']) elif method == 'session-stats': # older versions of T has the return data in "session-stats" if 'session-stats' in data['arguments']: self._update_session(data['arguments']['session-stats']) else: self._update_session(data['arguments']) elif method in ('port-test', 'blocklist-update', 'free-space', 'torrent-rename-path'): results = data['arguments'] else: return None return results def _update_session(self, data): """ Update session data. """ if self.session: self.session.from_request(data) else: self.session = Session(self, data) def _update_server_version(self): """Decode the Transmission version string, if available.""" if self.server_version is None: version_major = 1 version_minor = 30 version_changeset = 0 version_parser = re.compile('(\d).(\d+) \((\d+)\)') if hasattr(self.session, 'version'): match = version_parser.match(self.session.version) if match: version_major = int(match.group(1)) version_minor = int(match.group(2)) version_changeset = match.group(3) self.server_version = (version_major, version_minor, version_changeset) @property def rpc_version(self): """ Get the Transmission RPC version. Trying to deduct if the server don't have a version value. """ if self.protocol_version is None: # Ugly fix for 2.20 - 2.22 reporting rpc-version 11, but having new arguments if self.server_version and (self.server_version[0] == 2 and self.server_version[1] in [20, 21, 22]): self.protocol_version = 12 # Ugly fix for 2.12 reporting rpc-version 10, but having new arguments elif self.server_version and (self.server_version[0] == 2 and self.server_version[1] == 12): self.protocol_version = 11 elif hasattr(self.session, 'rpc_version'): self.protocol_version = self.session.rpc_version elif hasattr(self.session, 'version'): self.protocol_version = 3 else: self.protocol_version = 2 return self.protocol_version def _rpc_version_warning(self, version): """ Add a warning to the log if the Transmission RPC version is lower then the provided version. """ if self.rpc_version < version: LOGGER.warning( 'Using feature not supported by server. RPC version for server %d, feature introduced in %d.' % (self.rpc_version, version)) def add_torrent(self, torrent, timeout=None, **kwargs): """ Add torrent to transfers list. Takes a uri to a torrent or base64 encoded torrent data in ``torrent``. Additional arguments are: ===================== ===== =========== ============================================================= Argument RPC Replaced by Description ===================== ===== =========== ============================================================= ``bandwidthPriority`` 8 - Priority for this transfer. ``cookies`` 13 - One or more HTTP cookie(s). ``download_dir`` 1 - The directory where the downloaded contents will be saved in. ``files_unwanted`` 1 - A list of file id's that shouldn't be downloaded. ``files_wanted`` 1 - A list of file id's that should be downloaded. ``paused`` 1 - If True, does not start the transfer when added. ``peer_limit`` 1 - Maximum number of peers allowed. ``priority_high`` 1 - A list of file id's that should have high priority. ``priority_low`` 1 - A list of file id's that should have low priority. ``priority_normal`` 1 - A list of file id's that should have normal priority. ===================== ===== =========== ============================================================= Returns a Torrent object with the fields. """ if torrent is None: raise ValueError('add_torrent requires data or a URI.') torrent_data = None parsed_uri = urlparse(torrent) if parsed_uri.scheme in ['ftp', 'ftps', 'http', 'https']: # there has been some problem with T's built in torrent fetcher, # use a python one instead req = Request(torrent, headers={'User-Agent': 'TransmissionRPC'}) torrent_file = urlopen(req) torrent_data = torrent_file.read() torrent_data = base64.b64encode(torrent_data).decode('utf-8') if parsed_uri.scheme in ['file']: filepath = torrent # uri decoded different on linux / windows ? if len(parsed_uri.path) > 0: filepath = parsed_uri.path elif len(parsed_uri.netloc) > 0: filepath = parsed_uri.netloc torrent_file = open(filepath, 'rb') torrent_data = torrent_file.read() torrent_data = base64.b64encode(torrent_data).decode('utf-8') if not torrent_data: if torrent.endswith('.torrent') or torrent.startswith('magnet:'): torrent_data = None else: might_be_base64 = False try: # check if this is base64 data if PY3: base64.b64decode(torrent.encode('utf-8')) else: base64.b64decode(torrent) might_be_base64 = True except Exception: pass if might_be_base64: torrent_data = torrent args = {} if torrent_data: args = {'metainfo': torrent_data} else: args = {'filename': torrent} for key, value in iteritems(kwargs): argument = make_rpc_name(key) (arg, val) = argument_value_convert('torrent-add', argument, value, self.rpc_version) args[arg] = val return list( self._request('torrent-add', args, timeout=timeout).values())[0] def add(self, data, timeout=None, **kwargs): """ .. WARNING:: Deprecated, please use add_torrent. """ args = {} if data: args = {'metainfo': data} elif 'metainfo' not in kwargs and 'filename' not in kwargs: raise ValueError('No torrent data or torrent uri.') for key, value in iteritems(kwargs): argument = make_rpc_name(key) (arg, val) = argument_value_convert('torrent-add', argument, value, self.rpc_version) args[arg] = val warnings.warn( 'add has been deprecated, please use add_torrent instead.', DeprecationWarning) return self._request('torrent-add', args, timeout=timeout) def add_uri(self, uri, **kwargs): """ .. WARNING:: Deprecated, please use add_torrent. """ if uri is None: raise ValueError('add_uri requires a URI.') # there has been some problem with T's built in torrent fetcher, # use a python one instead parsed_uri = urlparse(uri) torrent_data = None if parsed_uri.scheme in ['ftp', 'ftps', 'http', 'https']: torrent_file = urlopen(uri) torrent_data = torrent_file.read() torrent_data = base64.b64encode(torrent_data).decode('utf-8') if parsed_uri.scheme in ['file']: filepath = uri # uri decoded different on linux / windows ? if len(parsed_uri.path) > 0: filepath = parsed_uri.path elif len(parsed_uri.netloc) > 0: filepath = parsed_uri.netloc torrent_file = open(filepath, 'rb') torrent_data = torrent_file.read() torrent_data = base64.b64encode(torrent_data).decode('utf-8') warnings.warn( 'add_uri has been deprecated, please use add_torrent instead.', DeprecationWarning) if torrent_data: return self.add(torrent_data, **kwargs) else: return self.add(None, filename=uri, **kwargs) def remove_torrent(self, ids, delete_data=False, timeout=None): """ remove torrent(s) with provided id(s). Local data is removed if delete_data is True, otherwise not. """ self._rpc_version_warning(3) self._request('torrent-remove', {'delete-local-data': rpc_bool(delete_data)}, ids, True, timeout=timeout) def remove(self, ids, delete_data=False, timeout=None): """ .. WARNING:: Deprecated, please use remove_torrent. """ warnings.warn( 'remove has been deprecated, please use remove_torrent instead.', DeprecationWarning) self.remove_torrent(ids, delete_data, timeout) def start_torrent(self, ids, bypass_queue=False, timeout=None): """Start torrent(s) with provided id(s)""" method = 'torrent-start' if bypass_queue and self.rpc_version >= 14: method = 'torrent-start-now' self._request(method, {}, ids, True, timeout=timeout) def start(self, ids, bypass_queue=False, timeout=None): """ .. WARNING:: Deprecated, please use start_torrent. """ warnings.warn( 'start has been deprecated, please use start_torrent instead.', DeprecationWarning) self.start_torrent(ids, bypass_queue, timeout) def start_all(self, bypass_queue=False, timeout=None): """Start all torrents respecting the queue order""" torrent_list = self.get_torrents() method = 'torrent-start' if self.rpc_version >= 14: if bypass_queue: method = 'torrent-start-now' torrent_list = sorted(torrent_list, key=operator.attrgetter('queuePosition')) ids = [x.id for x in torrent_list] self._request(method, {}, ids, True, timeout=timeout) def stop_torrent(self, ids, timeout=None): """stop torrent(s) with provided id(s)""" self._request('torrent-stop', {}, ids, True, timeout=timeout) def stop(self, ids, timeout=None): """ .. WARNING:: Deprecated, please use stop_torrent. """ warnings.warn( 'stop has been deprecated, please use stop_torrent instead.', DeprecationWarning) self.stop_torrent(ids, timeout) def verify_torrent(self, ids, timeout=None): """verify torrent(s) with provided id(s)""" self._request('torrent-verify', {}, ids, True, timeout=timeout) def verify(self, ids, timeout=None): """ .. WARNING:: Deprecated, please use verify_torrent. """ warnings.warn( 'verify has been deprecated, please use verify_torrent instead.', DeprecationWarning) self.verify_torrent(ids, timeout) def reannounce_torrent(self, ids, timeout=None): """Reannounce torrent(s) with provided id(s)""" self._rpc_version_warning(5) self._request('torrent-reannounce', {}, ids, True, timeout=timeout) def reannounce(self, ids, timeout=None): """ .. WARNING:: Deprecated, please use reannounce_torrent. """ warnings.warn( 'reannounce has been deprecated, please use reannounce_torrent instead.', DeprecationWarning) self.reannounce_torrent(ids, timeout) def get_torrent(self, torrent_id, arguments=None, timeout=None): """ Get information for torrent with provided id. ``arguments`` contains a list of field names to be returned, when None all fields are requested. See the Torrent class for more information. Returns a Torrent object with the requested fields. """ if not arguments: arguments = self.torrent_get_arguments torrent_id = parse_torrent_id(torrent_id) if torrent_id is None: raise ValueError("Invalid id") result = self._request('torrent-get', {'fields': arguments}, torrent_id, require_ids=True, timeout=timeout) if torrent_id in result: return result[torrent_id] else: for torrent in result.values(): if torrent.hashString == torrent_id: return torrent raise KeyError("Torrent not found in result") def get_torrents(self, ids=None, arguments=None, timeout=None): """ Get information for torrents with provided ids. For more information see get_torrent. Returns a list of Torrent object. """ if not arguments: arguments = self.torrent_get_arguments return list( self._request('torrent-get', { 'fields': arguments }, ids, timeout=timeout).values()) def info(self, ids=None, arguments=None, timeout=None): """ .. WARNING:: Deprecated, please use get_torrent or get_torrents. Please note that the return argument has changed in the new methods. info returns a dictionary indexed by torrent id. """ warnings.warn( 'info has been deprecated, please use get_torrent or get_torrents instead.', DeprecationWarning) if not arguments: arguments = self.torrent_get_arguments return self._request('torrent-get', {'fields': arguments}, ids, timeout=timeout) def list(self, timeout=None): """ .. WARNING:: Deprecated, please use get_torrent or get_torrents. Please note that the return argument has changed in the new methods. list returns a dictionary indexed by torrent id. """ warnings.warn( 'list has been deprecated, please use get_torrent or get_torrents instead.', DeprecationWarning) fields = [ 'id', 'hashString', 'name', 'sizeWhenDone', 'leftUntilDone', 'eta', 'status', 'rateUpload', 'rateDownload', 'uploadedEver', 'downloadedEver', 'uploadRatio', 'queuePosition' ] return self._request('torrent-get', {'fields': fields}, timeout=timeout) def get_files(self, ids=None, timeout=None): """ Get list of files for provided torrent id(s). If ids is empty, information for all torrents are fetched. This function returns a dictionary for each requested torrent id holding the information about the files. :: { <torrent id>: { <file id>: { 'name': <file name>, 'size': <file size in bytes>, 'completed': <bytes completed>, 'priority': <priority ('high'|'normal'|'low')>, 'selected': <selected for download (True|False)> } ... } ... } """ fields = ['id', 'name', 'hashString', 'files', 'priorities', 'wanted'] request_result = self._request('torrent-get', {'fields': fields}, ids, timeout=timeout) result = {} for tid, torrent in iteritems(request_result): result[tid] = torrent.files() return result def set_files(self, items, timeout=None): """ Set file properties. Takes a dictionary with similar contents as the result of `get_files`. :: { <torrent id>: { <file id>: { 'priority': <priority ('high'|'normal'|'low')>, 'selected': <selected for download (True|False)> } ... } ... } """ if not isinstance(items, dict): raise ValueError('Invalid file description') for tid, files in iteritems(items): if not isinstance(files, dict): continue wanted = [] unwanted = [] high = [] normal = [] low = [] for fid, file_desc in iteritems(files): if not isinstance(file_desc, dict): continue if 'selected' in file_desc and file_desc['selected']: wanted.append(fid) else: unwanted.append(fid) if 'priority' in file_desc: if file_desc['priority'] == 'high': high.append(fid) elif file_desc['priority'] == 'normal': normal.append(fid) elif file_desc['priority'] == 'low': low.append(fid) args = {'timeout': timeout} if len(high) > 0: args['priority_high'] = high if len(normal) > 0: args['priority_normal'] = normal if len(low) > 0: args['priority_low'] = low if len(wanted) > 0: args['files_wanted'] = wanted if len(unwanted) > 0: args['files_unwanted'] = unwanted self.change_torrent([tid], **args) def change_torrent(self, ids, timeout=None, **kwargs): """ Change torrent parameters for the torrent(s) with the supplied id's. The parameters are: ============================ ===== =============== ======================================================================================= Argument RPC Replaced by Description ============================ ===== =============== ======================================================================================= ``bandwidthPriority`` 5 - Priority for this transfer. ``downloadLimit`` 5 - Set the speed limit for download in Kib/s. ``downloadLimited`` 5 - Enable download speed limiter. ``files_unwanted`` 1 - A list of file id's that shouldn't be downloaded. ``files_wanted`` 1 - A list of file id's that should be downloaded. ``honorsSessionLimits`` 5 - Enables or disables the transfer to honour the upload limit set in the session. ``location`` 1 - Local download location. ``peer_limit`` 1 - The peer limit for the torrents. ``priority_high`` 1 - A list of file id's that should have high priority. ``priority_low`` 1 - A list of file id's that should have normal priority. ``priority_normal`` 1 - A list of file id's that should have low priority. ``queuePosition`` 14 - Position of this transfer in its queue. ``seedIdleLimit`` 10 - Seed inactivity limit in minutes. ``seedIdleMode`` 10 - Seed inactivity mode. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit. ``seedRatioLimit`` 5 - Seeding ratio. ``seedRatioMode`` 5 - Which ratio to use. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit. ``speed_limit_down`` 1 - 5 downloadLimit Set the speed limit for download in Kib/s. ``speed_limit_down_enabled`` 1 - 5 downloadLimited Enable download speed limiter. ``speed_limit_up`` 1 - 5 uploadLimit Set the speed limit for upload in Kib/s. ``speed_limit_up_enabled`` 1 - 5 uploadLimited Enable upload speed limiter. ``trackerAdd`` 10 - Array of string with announce URLs to add. ``trackerRemove`` 10 - Array of ids of trackers to remove. ``trackerReplace`` 10 - Array of (id, url) tuples where the announce URL should be replaced. ``uploadLimit`` 5 - Set the speed limit for upload in Kib/s. ``uploadLimited`` 5 - Enable upload speed limiter. ============================ ===== =============== ======================================================================================= .. NOTE:: transmissionrpc will try to automatically fix argument errors. """ args = {} for key, value in iteritems(kwargs): argument = make_rpc_name(key) (arg, val) = argument_value_convert('torrent-set', argument, value, self.rpc_version) args[arg] = val if len(args) > 0: self._request('torrent-set', args, ids, True, timeout=timeout) else: ValueError("No arguments to set") def change(self, ids, timeout=None, **kwargs): """ .. WARNING:: Deprecated, please use change_torrent. """ warnings.warn( 'change has been deprecated, please use change_torrent instead.', DeprecationWarning) self.change_torrent(ids, timeout, **kwargs) def move_torrent_data(self, ids, location, timeout=None): """Move torrent data to the new location.""" self._rpc_version_warning(6) args = {'location': location, 'move': True} self._request('torrent-set-location', args, ids, True, timeout=timeout) def move(self, ids, location, timeout=None): """ .. WARNING:: Deprecated, please use move_torrent_data. """ warnings.warn( 'move has been deprecated, please use move_torrent_data instead.', DeprecationWarning) self.move_torrent_data(ids, location, timeout) def locate_torrent_data(self, ids, location, timeout=None): """Locate torrent data at the provided location.""" self._rpc_version_warning(6) args = {'location': location, 'move': False} self._request('torrent-set-location', args, ids, True, timeout=timeout) def locate(self, ids, location, timeout=None): """ .. WARNING:: Deprecated, please use locate_torrent_data. """ warnings.warn( 'locate has been deprecated, please use locate_torrent_data instead.', DeprecationWarning) self.locate_torrent_data(ids, location, timeout) def rename_torrent_path(self, torrent_id, location, name, timeout=None): """ Rename directory and/or files for torrent. Remember to use get_torrent or get_torrents to update your file information. """ self._rpc_version_warning(15) torrent_id = parse_torrent_id(torrent_id) if torrent_id is None: raise ValueError("Invalid id") dirname = os.path.dirname(name) if len(dirname) > 0: raise ValueError("Target name cannot contain a path delimiter") args = {'path': location, 'name': name} result = self._request('torrent-rename-path', args, torrent_id, True, timeout=timeout) return (result['path'], result['name']) def queue_top(self, ids, timeout=None): """Move transfer to the top of the queue.""" self._rpc_version_warning(14) self._request('queue-move-top', ids=ids, require_ids=True, timeout=timeout) def queue_bottom(self, ids, timeout=None): """Move transfer to the bottom of the queue.""" self._rpc_version_warning(14) self._request('queue-move-bottom', ids=ids, require_ids=True, timeout=timeout) def queue_up(self, ids, timeout=None): """Move transfer up in the queue.""" self._rpc_version_warning(14) self._request('queue-move-up', ids=ids, require_ids=True, timeout=timeout) def queue_down(self, ids, timeout=None): """Move transfer down in the queue.""" self._rpc_version_warning(14) self._request('queue-move-down', ids=ids, require_ids=True, timeout=timeout) def get_session(self, timeout=None): """ Get session parameters. See the Session class for more information. """ self._request('session-get', timeout=timeout) self._update_server_version() return self.session def set_session(self, timeout=None, **kwargs): """ Set session parameters. The parameters are: ================================ ===== ================= ========================================================================================================================== Argument RPC Replaced by Description ================================ ===== ================= ========================================================================================================================== ``alt_speed_down`` 5 - Alternate session download speed limit (in Kib/s). ``alt_speed_enabled`` 5 - Enables alternate global download speed limiter. ``alt_speed_time_begin`` 5 - Time when alternate speeds should be enabled. Minutes after midnight. ``alt_speed_time_day`` 5 - Enables alternate speeds scheduling these days. ``alt_speed_time_enabled`` 5 - Enables alternate speeds scheduling. ``alt_speed_time_end`` 5 - Time when alternate speeds should be disabled. Minutes after midnight. ``alt_speed_up`` 5 - Alternate session upload speed limit (in Kib/s). ``blocklist_enabled`` 5 - Enables the block list ``blocklist_url`` 11 - Location of the block list. Updated with blocklist-update. ``cache_size_mb`` 10 - The maximum size of the disk cache in MB ``dht_enabled`` 6 - Enables DHT. ``download_dir`` 1 - Set the session download directory. ``download_queue_enabled`` 14 - Enables download queue. ``download_queue_size`` 14 - Number of slots in the download queue. ``encryption`` 1 - Set the session encryption mode, one of ``required``, ``preferred`` or ``tolerated``. ``idle_seeding_limit`` 10 - The default seed inactivity limit in minutes. ``idle_seeding_limit_enabled`` 10 - Enables the default seed inactivity limit ``incomplete_dir`` 7 - The path to the directory of incomplete transfer data. ``incomplete_dir_enabled`` 7 - Enables the incomplete transfer data directory. Otherwise data for incomplete transfers are stored in the download target. ``lpd_enabled`` 9 - Enables local peer discovery for public torrents. ``peer_limit`` 1 - 5 peer-limit-global Maximum number of peers. ``peer_limit_global`` 5 - Maximum number of peers. ``peer_limit_per_torrent`` 5 - Maximum number of peers per transfer. ``peer_port`` 5 - Peer port. ``peer_port_random_on_start`` 5 - Enables randomized peer port on start of Transmission. ``pex_allowed`` 1 - 5 pex-enabled Allowing PEX in public torrents. ``pex_enabled`` 5 - Allowing PEX in public torrents. ``port`` 1 - 5 peer-port Peer port. ``port_forwarding_enabled`` 1 - Enables port forwarding. ``queue_stalled_enabled`` 14 - Enable tracking of stalled transfers. ``queue_stalled_minutes`` 14 - Number of minutes of idle that marks a transfer as stalled. ``rename_partial_files`` 8 - Appends ".part" to incomplete files ``script_torrent_done_enabled`` 9 - Whether or not to call the "done" script. ``script_torrent_done_filename`` 9 - Filename of the script to run when the transfer is done. ``seed_queue_enabled`` 14 - Enables upload queue. ``seed_queue_size`` 14 - Number of slots in the upload queue. ``seedRatioLimit`` 5 - Seed ratio limit. 1.0 means 1:1 download and upload ratio. ``seedRatioLimited`` 5 - Enables seed ration limit. ``speed_limit_down`` 1 - Download speed limit (in Kib/s). ``speed_limit_down_enabled`` 1 - Enables download speed limiting. ``speed_limit_up`` 1 - Upload speed limit (in Kib/s). ``speed_limit_up_enabled`` 1 - Enables upload speed limiting. ``start_added_torrents`` 9 - Added torrents will be started right away. ``trash_original_torrent_files`` 9 - The .torrent file of added torrents will be deleted. ``utp_enabled`` 13 - Enables Micro Transport Protocol (UTP). ================================ ===== ================= ========================================================================================================================== .. NOTE:: transmissionrpc will try to automatically fix argument errors. """ args = {} for key, value in iteritems(kwargs): if key == 'encryption' and value not in [ 'required', 'preferred', 'tolerated' ]: raise ValueError('Invalid encryption value') argument = make_rpc_name(key) (arg, val) = argument_value_convert('session-set', argument, value, self.rpc_version) args[arg] = val if len(args) > 0: self._request('session-set', args, timeout=timeout) def blocklist_update(self, timeout=None): """Update block list. Returns the size of the block list.""" self._rpc_version_warning(5) result = self._request('blocklist-update', timeout=timeout) if 'blocklist-size' in result: return result['blocklist-size'] return None def port_test(self, timeout=None): """ Tests to see if your incoming peer port is accessible from the outside world. """ self._rpc_version_warning(5) result = self._request('port-test', timeout=timeout) if 'port-is-open' in result: return result['port-is-open'] return None def free_space(self, path, timeout=None): """ Get the ammount of free space (in bytes) at the provided location. """ self._rpc_version_warning(15) result = self._request('free-space', {'path': path}, timeout=timeout) if result['path'] == path: return result['size-bytes'] return None def session_stats(self, timeout=None): """Get session statistics""" self._request('session-stats', timeout=timeout) return self.session
class Client(object): """ Client is the class handling the Transmission JSON-RPC client protocol. """ def __init__(self, address='localhost', port=DEFAULT_PORT, user=None, password=None, http_handler=None, timeout=None): if isinstance(timeout, (integer_types, float)): self._query_timeout = float(timeout) else: self._query_timeout = DEFAULT_TIMEOUT urlo = urlparse(address) if urlo.scheme == '': base_url = 'http://' + address + ':' + str(port) self.url = base_url + '/transmission/rpc' else: if urlo.port: self.url = urlo.scheme + '://' + urlo.hostname + ':' + str(urlo.port) + urlo.path else: self.url = urlo.scheme + '://' + urlo.hostname + urlo.path LOGGER.info('Using custom URL "' + self.url + '".') if urlo.username and urlo.password: user = urlo.username password = urlo.password elif urlo.username or urlo.password: LOGGER.warning('Either user or password missing, not using authentication.') if http_handler is None: self.http_handler = DefaultHTTPHandler() else: if hasattr(http_handler, 'set_authentication') and hasattr(http_handler, 'request'): self.http_handler = http_handler else: raise ValueError('Invalid HTTP handler.') if user and password: self.http_handler.set_authentication(self.url, user, password) elif user or password: LOGGER.warning('Either user or password missing, not using authentication.') self._sequence = 0 self.session = None self.session_id = 0 self.server_version = None self.protocol_version = None self.get_session() self.torrent_get_arguments = get_arguments('torrent-get' , self.rpc_version) def _get_timeout(self): """ Get current timeout for HTTP queries. """ return self._query_timeout def _set_timeout(self, value): """ Set timeout for HTTP queries. """ self._query_timeout = float(value) def _del_timeout(self): """ Reset the HTTP query timeout to the default. """ self._query_timeout = DEFAULT_TIMEOUT timeout = property(_get_timeout, _set_timeout, _del_timeout, doc="HTTP query timeout.") def _http_query(self, query, timeout=None): """ Query Transmission through HTTP. """ headers = {'x-transmission-session-id': str(self.session_id)} result = {} request_count = 0 if timeout is None: timeout = self._query_timeout while True: LOGGER.debug(json.dumps({'url': self.url, 'headers': headers, 'query': query, 'timeout': timeout}, indent=2)) try: result = self.http_handler.request(self.url, query, headers, timeout) break except HTTPHandlerError as error: if error.code == 409: LOGGER.info('Server responded with 409, trying to set session-id.') if request_count > 1: raise TransmissionError('Session ID negotiation failed.', error) session_id = None for key in list(error.headers.keys()): if key.lower() == 'x-transmission-session-id': session_id = error.headers[key] self.session_id = session_id headers = {'x-transmission-session-id': str(self.session_id)} if session_id is None: debug_httperror(error) raise TransmissionError('Unknown conflict.', error) else: debug_httperror(error) raise TransmissionError('Request failed.', error) request_count += 1 return result def _request(self, method, arguments=None, ids=None, require_ids=False, timeout=None): """ Send json-rpc request to Transmission using http POST """ if not isinstance(method, string_types): raise ValueError('request takes method as string') if arguments is None: arguments = {} if not isinstance(arguments, dict): raise ValueError('request takes arguments as dict') ids = parse_torrent_ids(ids) if len(ids) > 0: arguments['ids'] = ids elif require_ids: raise ValueError('request require ids') query = json.dumps({'tag': self._sequence, 'method': method , 'arguments': arguments}) self._sequence += 1 start = time.time() http_data = self._http_query(query, timeout) elapsed = time.time() - start LOGGER.info('http request took %.3f s' % (elapsed)) try: data = json.loads(http_data) except ValueError as error: LOGGER.error('Error: ' + str(error)) LOGGER.error('Request: \"%s\"' % (query)) LOGGER.error('HTTP data: \"%s\"' % (http_data)) raise LOGGER.debug(json.dumps(data, indent=2)) if 'result' in data: if data['result'] != 'success': raise TransmissionError('Query failed with result \"%s\".' % (data['result'])) else: raise TransmissionError('Query failed without result.') results = {} if method == 'torrent-get': for item in data['arguments']['torrents']: results[item['id']] = Torrent(self, item) if self.protocol_version == 2 and 'peers' not in item: self.protocol_version = 1 elif method == 'torrent-add': item = None if 'torrent-added' in data['arguments']: item = data['arguments']['torrent-added'] elif 'torrent-duplicate' in data['arguments']: item = data['arguments']['torrent-duplicate'] if item: results[item['id']] = Torrent(self, item) else: raise TransmissionError('Invalid torrent-add response.') elif method == 'session-get': self._update_session(data['arguments']) elif method == 'session-stats': # older versions of T has the return data in "session-stats" if 'session-stats' in data['arguments']: self._update_session(data['arguments']['session-stats']) else: self._update_session(data['arguments']) elif method in ('port-test', 'blocklist-update', 'free-space', 'torrent-rename-path'): results = data['arguments'] else: return None return results def _update_session(self, data): """ Update session data. """ if self.session: self.session.from_request(data) else: self.session = Session(self, data) def _update_server_version(self): """Decode the Transmission version string, if available.""" if self.server_version is None: version_major = 1 version_minor = 30 version_changeset = 0 version_parser = re.compile('(\d).(\d+) \((\d+)\)') if hasattr(self.session, 'version'): match = version_parser.match(self.session.version) if match: version_major = int(match.group(1)) version_minor = int(match.group(2)) version_changeset = match.group(3) self.server_version = (version_major, version_minor, version_changeset) @property def rpc_version(self): """ Get the Transmission RPC version. Trying to deduct if the server don't have a version value. """ if self.protocol_version is None: # Ugly fix for 2.20 - 2.22 reporting rpc-version 11, but having new arguments if self.server_version and (self.server_version[0] == 2 and self.server_version[1] in [20, 21, 22]): self.protocol_version = 12 # Ugly fix for 2.12 reporting rpc-version 10, but having new arguments elif self.server_version and (self.server_version[0] == 2 and self.server_version[1] == 12): self.protocol_version = 11 elif hasattr(self.session, 'rpc_version'): self.protocol_version = self.session.rpc_version elif hasattr(self.session, 'version'): self.protocol_version = 3 else: self.protocol_version = 2 return self.protocol_version def _rpc_version_warning(self, version): """ Add a warning to the log if the Transmission RPC version is lower then the provided version. """ if self.rpc_version < version: LOGGER.warning('Using feature not supported by server. RPC version for server %d, feature introduced in %d.' % (self.rpc_version, version)) def add_torrent(self, torrent, timeout=None, **kwargs): """ Add torrent to transfers list. Takes a uri to a torrent or base64 encoded torrent data in ``torrent``. Additional arguments are: ===================== ===== =========== ============================================================= Argument RPC Replaced by Description ===================== ===== =========== ============================================================= ``bandwidthPriority`` 8 - Priority for this transfer. ``cookies`` 13 - One or more HTTP cookie(s). ``download_dir`` 1 - The directory where the downloaded contents will be saved in. ``files_unwanted`` 1 - A list of file id's that shouldn't be downloaded. ``files_wanted`` 1 - A list of file id's that should be downloaded. ``paused`` 1 - If True, does not start the transfer when added. ``peer_limit`` 1 - Maximum number of peers allowed. ``priority_high`` 1 - A list of file id's that should have high priority. ``priority_low`` 1 - A list of file id's that should have low priority. ``priority_normal`` 1 - A list of file id's that should have normal priority. ===================== ===== =========== ============================================================= Returns a Torrent object with the fields. """ if torrent is None: raise ValueError('add_torrent requires data or a URI.') torrent_data = None parsed_uri = urlparse(torrent) if parsed_uri.scheme in ['ftp', 'ftps', 'http', 'https']: # there has been some problem with T's built in torrent fetcher, # use a python one instead user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers = { 'User-Agent' : user_agent } opener = build_opener() opener.addheaders = headers.items() torrent_file = opener.open(torrent) if torrent_file.info().get('Content-Encoding') == 'gzip': buf = BytesIO(torrent_file.read()) gzip_file = GzipFile(fileobj=buf) torrent_data = gzip_file.read() else: torrent_data = torrent_file.read() torrent_data = base64.b64encode(torrent_data).decode('utf-8') if parsed_uri.scheme in ['file']: filepath = torrent # uri decoded different on linux / windows ? if len(parsed_uri.path) > 0: filepath = parsed_uri.path elif len(parsed_uri.netloc) > 0: filepath = parsed_uri.netloc torrent_file = open(filepath, 'rb') torrent_data = torrent_file.read() torrent_data = base64.b64encode(torrent_data).decode('utf-8') if not torrent_data: if torrent.endswith('.torrent') or torrent.startswith('magnet:'): torrent_data = None else: might_be_base64 = False try: # check if this is base64 data if PY3: base64.b64decode(torrent.encode('utf-8')) else: base64.b64decode(torrent) might_be_base64 = True except Exception: pass if might_be_base64: torrent_data = torrent args = {} if torrent_data: args = {'metainfo': torrent_data} else: args = {'filename': torrent} for key, value in iteritems(kwargs): argument = make_rpc_name(key) (arg, val) = argument_value_convert('torrent-add', argument, value, self.rpc_version) args[arg] = val return list(self._request('torrent-add', args, timeout=timeout).values())[0] def add(self, data, timeout=None, **kwargs): """ .. WARNING:: Deprecated, please use add_torrent. """ args = {} if data: args = {'metainfo': data} elif 'metainfo' not in kwargs and 'filename' not in kwargs: raise ValueError('No torrent data or torrent uri.') for key, value in iteritems(kwargs): argument = make_rpc_name(key) (arg, val) = argument_value_convert('torrent-add', argument, value, self.rpc_version) args[arg] = val warnings.warn('add has been deprecated, please use add_torrent instead.', DeprecationWarning) return self._request('torrent-add', args, timeout=timeout) def add_uri(self, uri, **kwargs): """ .. WARNING:: Deprecated, please use add_torrent. """ if uri is None: raise ValueError('add_uri requires a URI.') # there has been some problem with T's built in torrent fetcher, # use a python one instead parsed_uri = urlparse(uri) torrent_data = None if parsed_uri.scheme in ['ftp', 'ftps', 'http', 'https']: torrent_file = urlopen(uri) torrent_data = torrent_file.read() torrent_data = base64.b64encode(torrent_data).decode('utf-8') if parsed_uri.scheme in ['file']: filepath = uri # uri decoded different on linux / windows ? if len(parsed_uri.path) > 0: filepath = parsed_uri.path elif len(parsed_uri.netloc) > 0: filepath = parsed_uri.netloc torrent_file = open(filepath, 'rb') torrent_data = torrent_file.read() torrent_data = base64.b64encode(torrent_data).decode('utf-8') warnings.warn('add_uri has been deprecated, please use add_torrent instead.', DeprecationWarning) if torrent_data: return self.add(torrent_data, **kwargs) else: return self.add(None, filename=uri, **kwargs) def remove_torrent(self, ids, delete_data=False, timeout=None): """ remove torrent(s) with provided id(s). Local data is removed if delete_data is True, otherwise not. """ self._rpc_version_warning(3) self._request('torrent-remove', {'delete-local-data':rpc_bool(delete_data)}, ids, True, timeout=timeout) def remove(self, ids, delete_data=False, timeout=None): """ .. WARNING:: Deprecated, please use remove_torrent. """ warnings.warn('remove has been deprecated, please use remove_torrent instead.', DeprecationWarning) self.remove_torrent(ids, delete_data, timeout) def start_torrent(self, ids, bypass_queue=False, timeout=None): """Start torrent(s) with provided id(s)""" method = 'torrent-start' if bypass_queue and self.rpc_version >= 14: method = 'torrent-start-now' self._request(method, {}, ids, True, timeout=timeout) def start(self, ids, bypass_queue=False, timeout=None): """ .. WARNING:: Deprecated, please use start_torrent. """ warnings.warn('start has been deprecated, please use start_torrent instead.', DeprecationWarning) self.start_torrent(ids, bypass_queue, timeout) def start_all(self, bypass_queue=False, timeout=None): """Start all torrents respecting the queue order""" torrent_list = self.get_torrents() method = 'torrent-start' if self.rpc_version >= 14: if bypass_queue: method = 'torrent-start-now' torrent_list = sorted(torrent_list, key=operator.attrgetter('queuePosition')) ids = [x.id for x in torrent_list] self._request(method, {}, ids, True, timeout=timeout) def stop_torrent(self, ids, timeout=None): """stop torrent(s) with provided id(s)""" self._request('torrent-stop', {}, ids, True, timeout=timeout) def stop(self, ids, timeout=None): """ .. WARNING:: Deprecated, please use stop_torrent. """ warnings.warn('stop has been deprecated, please use stop_torrent instead.', DeprecationWarning) self.stop_torrent(ids, timeout) def verify_torrent(self, ids, timeout=None): """verify torrent(s) with provided id(s)""" self._request('torrent-verify', {}, ids, True, timeout=timeout) def verify(self, ids, timeout=None): """ .. WARNING:: Deprecated, please use verify_torrent. """ warnings.warn('verify has been deprecated, please use verify_torrent instead.', DeprecationWarning) self.verify_torrent(ids, timeout) def reannounce_torrent(self, ids, timeout=None): """Reannounce torrent(s) with provided id(s)""" self._rpc_version_warning(5) self._request('torrent-reannounce', {}, ids, True, timeout=timeout) def reannounce(self, ids, timeout=None): """ .. WARNING:: Deprecated, please use reannounce_torrent. """ warnings.warn('reannounce has been deprecated, please use reannounce_torrent instead.', DeprecationWarning) self.reannounce_torrent(ids, timeout) def get_torrent(self, torrent_id, arguments=None, timeout=None): """ Get information for torrent with provided id. ``arguments`` contains a list of field names to be returned, when None all fields are requested. See the Torrent class for more information. Returns a Torrent object with the requested fields. """ if not arguments: arguments = self.torrent_get_arguments torrent_id = parse_torrent_id(torrent_id) if torrent_id is None: raise ValueError("Invalid id") result = self._request('torrent-get', {'fields': arguments}, torrent_id, require_ids=True, timeout=timeout) if torrent_id in result: return result[torrent_id] else: for torrent in result.values(): if torrent.hashString == torrent_id: return torrent raise KeyError("Torrent not found in result") def get_torrents(self, ids=None, arguments=None, timeout=None): """ Get information for torrents with provided ids. For more information see get_torrent. Returns a list of Torrent object. """ if not arguments: arguments = self.torrent_get_arguments return list(self._request('torrent-get', {'fields': arguments}, ids, timeout=timeout).values()) def info(self, ids=None, arguments=None, timeout=None): """ .. WARNING:: Deprecated, please use get_torrent or get_torrents. Please note that the return argument has changed in the new methods. info returns a dictionary indexed by torrent id. """ warnings.warn('info has been deprecated, please use get_torrent or get_torrents instead.', DeprecationWarning) if not arguments: arguments = self.torrent_get_arguments return self._request('torrent-get', {'fields': arguments}, ids, timeout=timeout) def list(self, timeout=None): """ .. WARNING:: Deprecated, please use get_torrent or get_torrents. Please note that the return argument has changed in the new methods. list returns a dictionary indexed by torrent id. """ warnings.warn('list has been deprecated, please use get_torrent or get_torrents instead.', DeprecationWarning) fields = ['id', 'hashString', 'name', 'sizeWhenDone', 'leftUntilDone' , 'eta', 'status', 'rateUpload', 'rateDownload', 'uploadedEver' , 'downloadedEver', 'uploadRatio', 'queuePosition'] return self._request('torrent-get', {'fields': fields}, timeout=timeout) def get_files(self, ids=None, timeout=None): """ Get list of files for provided torrent id(s). If ids is empty, information for all torrents are fetched. This function returns a dictionary for each requested torrent id holding the information about the files. :: { <torrent id>: { <file id>: { 'name': <file name>, 'size': <file size in bytes>, 'completed': <bytes completed>, 'priority': <priority ('high'|'normal'|'low')>, 'selected': <selected for download (True|False)> } ... } ... } """ fields = ['id', 'name', 'hashString', 'files', 'priorities', 'wanted'] request_result = self._request('torrent-get', {'fields': fields}, ids, timeout=timeout) result = {} for tid, torrent in iteritems(request_result): result[tid] = torrent.files() return result def set_files(self, items, timeout=None): """ Set file properties. Takes a dictionary with similar contents as the result of `get_files`. :: { <torrent id>: { <file id>: { 'priority': <priority ('high'|'normal'|'low')>, 'selected': <selected for download (True|False)> } ... } ... } """ if not isinstance(items, dict): raise ValueError('Invalid file description') for tid, files in iteritems(items): if not isinstance(files, dict): continue wanted = [] unwanted = [] high = [] normal = [] low = [] for fid, file_desc in iteritems(files): if not isinstance(file_desc, dict): continue if 'selected' in file_desc and file_desc['selected']: wanted.append(fid) else: unwanted.append(fid) if 'priority' in file_desc: if file_desc['priority'] == 'high': high.append(fid) elif file_desc['priority'] == 'normal': normal.append(fid) elif file_desc['priority'] == 'low': low.append(fid) args = { 'timeout': timeout } if len(high) > 0: args['priority_high'] = high if len(normal) > 0: args['priority_normal'] = normal if len(low) > 0: args['priority_low'] = low if len(wanted) > 0: args['files_wanted'] = wanted if len(unwanted) > 0: args['files_unwanted'] = unwanted self.change_torrent([tid], **args) def change_torrent(self, ids, timeout=None, **kwargs): """ Change torrent parameters for the torrent(s) with the supplied id's. The parameters are: ============================ ===== =============== ======================================================================================= Argument RPC Replaced by Description ============================ ===== =============== ======================================================================================= ``bandwidthPriority`` 5 - Priority for this transfer. ``downloadLimit`` 5 - Set the speed limit for download in Kib/s. ``downloadLimited`` 5 - Enable download speed limiter. ``files_unwanted`` 1 - A list of file id's that shouldn't be downloaded. ``files_wanted`` 1 - A list of file id's that should be downloaded. ``honorsSessionLimits`` 5 - Enables or disables the transfer to honour the upload limit set in the session. ``location`` 1 - Local download location. ``peer_limit`` 1 - The peer limit for the torrents. ``priority_high`` 1 - A list of file id's that should have high priority. ``priority_low`` 1 - A list of file id's that should have normal priority. ``priority_normal`` 1 - A list of file id's that should have low priority. ``queuePosition`` 14 - Position of this transfer in its queue. ``seedIdleLimit`` 10 - Seed inactivity limit in minutes. ``seedIdleMode`` 10 - Seed inactivity mode. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit. ``seedRatioLimit`` 5 - Seeding ratio. ``seedRatioMode`` 5 - Which ratio to use. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit. ``speed_limit_down`` 1 - 5 downloadLimit Set the speed limit for download in Kib/s. ``speed_limit_down_enabled`` 1 - 5 downloadLimited Enable download speed limiter. ``speed_limit_up`` 1 - 5 uploadLimit Set the speed limit for upload in Kib/s. ``speed_limit_up_enabled`` 1 - 5 uploadLimited Enable upload speed limiter. ``trackerAdd`` 10 - Array of string with announce URLs to add. ``trackerRemove`` 10 - Array of ids of trackers to remove. ``trackerReplace`` 10 - Array of (id, url) tuples where the announce URL should be replaced. ``uploadLimit`` 5 - Set the speed limit for upload in Kib/s. ``uploadLimited`` 5 - Enable upload speed limiter. ============================ ===== =============== ======================================================================================= .. NOTE:: transmissionrpc will try to automatically fix argument errors. """ args = {} for key, value in iteritems(kwargs): argument = make_rpc_name(key) (arg, val) = argument_value_convert('torrent-set' , argument, value, self.rpc_version) args[arg] = val if len(args) > 0: self._request('torrent-set', args, ids, True, timeout=timeout) else: ValueError("No arguments to set") def change(self, ids, timeout=None, **kwargs): """ .. WARNING:: Deprecated, please use change_torrent. """ warnings.warn('change has been deprecated, please use change_torrent instead.', DeprecationWarning) self.change_torrent(ids, timeout, **kwargs) def move_torrent_data(self, ids, location, timeout=None): """Move torrent data to the new location.""" self._rpc_version_warning(6) args = {'location': location, 'move': True} self._request('torrent-set-location', args, ids, True, timeout=timeout) def move(self, ids, location, timeout=None): """ .. WARNING:: Deprecated, please use move_torrent_data. """ warnings.warn('move has been deprecated, please use move_torrent_data instead.', DeprecationWarning) self.move_torrent_data(ids, location, timeout) def locate_torrent_data(self, ids, location, timeout=None): """Locate torrent data at the provided location.""" self._rpc_version_warning(6) args = {'location': location, 'move': False} self._request('torrent-set-location', args, ids, True, timeout=timeout) def locate(self, ids, location, timeout=None): """ .. WARNING:: Deprecated, please use locate_torrent_data. """ warnings.warn('locate has been deprecated, please use locate_torrent_data instead.', DeprecationWarning) self.locate_torrent_data(ids, location, timeout) def rename_torrent_path(self, torrent_id, location, name, timeout=None): """ Rename directory and/or files for torrent. Remember to use get_torrent or get_torrents to update your file information. """ self._rpc_version_warning(15) torrent_id = parse_torrent_id(torrent_id) if torrent_id is None: raise ValueError("Invalid id") dirname = os.path.dirname(name) if len(dirname) > 0: raise ValueError("Target name cannot contain a path delimiter") args = {'path': location, 'name': name} result = self._request('torrent-rename-path', args, torrent_id, True, timeout=timeout) return (result['path'], result['name']) def queue_top(self, ids, timeout=None): """Move transfer to the top of the queue.""" self._rpc_version_warning(14) self._request('queue-move-top', ids=ids, require_ids=True, timeout=timeout) def queue_bottom(self, ids, timeout=None): """Move transfer to the bottom of the queue.""" self._rpc_version_warning(14) self._request('queue-move-bottom', ids=ids, require_ids=True, timeout=timeout) def queue_up(self, ids, timeout=None): """Move transfer up in the queue.""" self._rpc_version_warning(14) self._request('queue-move-up', ids=ids, require_ids=True, timeout=timeout) def queue_down(self, ids, timeout=None): """Move transfer down in the queue.""" self._rpc_version_warning(14) self._request('queue-move-down', ids=ids, require_ids=True, timeout=timeout) def get_session(self, timeout=None): """ Get session parameters. See the Session class for more information. """ self._request('session-get', timeout=timeout) self._update_server_version() return self.session def set_session(self, timeout=None, **kwargs): """ Set session parameters. The parameters are: ================================ ===== ================= ========================================================================================================================== Argument RPC Replaced by Description ================================ ===== ================= ========================================================================================================================== ``alt_speed_down`` 5 - Alternate session download speed limit (in Kib/s). ``alt_speed_enabled`` 5 - Enables alternate global download speed limiter. ``alt_speed_time_begin`` 5 - Time when alternate speeds should be enabled. Minutes after midnight. ``alt_speed_time_day`` 5 - Enables alternate speeds scheduling these days. ``alt_speed_time_enabled`` 5 - Enables alternate speeds scheduling. ``alt_speed_time_end`` 5 - Time when alternate speeds should be disabled. Minutes after midnight. ``alt_speed_up`` 5 - Alternate session upload speed limit (in Kib/s). ``blocklist_enabled`` 5 - Enables the block list ``blocklist_url`` 11 - Location of the block list. Updated with blocklist-update. ``cache_size_mb`` 10 - The maximum size of the disk cache in MB ``dht_enabled`` 6 - Enables DHT. ``download_dir`` 1 - Set the session download directory. ``download_queue_enabled`` 14 - Enables download queue. ``download_queue_size`` 14 - Number of slots in the download queue. ``encryption`` 1 - Set the session encryption mode, one of ``required``, ``preferred`` or ``tolerated``. ``idle_seeding_limit`` 10 - The default seed inactivity limit in minutes. ``idle_seeding_limit_enabled`` 10 - Enables the default seed inactivity limit ``incomplete_dir`` 7 - The path to the directory of incomplete transfer data. ``incomplete_dir_enabled`` 7 - Enables the incomplete transfer data directory. Otherwise data for incomplete transfers are stored in the download target. ``lpd_enabled`` 9 - Enables local peer discovery for public torrents. ``peer_limit`` 1 - 5 peer-limit-global Maximum number of peers. ``peer_limit_global`` 5 - Maximum number of peers. ``peer_limit_per_torrent`` 5 - Maximum number of peers per transfer. ``peer_port`` 5 - Peer port. ``peer_port_random_on_start`` 5 - Enables randomized peer port on start of Transmission. ``pex_allowed`` 1 - 5 pex-enabled Allowing PEX in public torrents. ``pex_enabled`` 5 - Allowing PEX in public torrents. ``port`` 1 - 5 peer-port Peer port. ``port_forwarding_enabled`` 1 - Enables port forwarding. ``queue_stalled_enabled`` 14 - Enable tracking of stalled transfers. ``queue_stalled_minutes`` 14 - Number of minutes of idle that marks a transfer as stalled. ``rename_partial_files`` 8 - Appends ".part" to incomplete files ``script_torrent_done_enabled`` 9 - Whether or not to call the "done" script. ``script_torrent_done_filename`` 9 - Filename of the script to run when the transfer is done. ``seed_queue_enabled`` 14 - Enables upload queue. ``seed_queue_size`` 14 - Number of slots in the upload queue. ``seedRatioLimit`` 5 - Seed ratio limit. 1.0 means 1:1 download and upload ratio. ``seedRatioLimited`` 5 - Enables seed ration limit. ``speed_limit_down`` 1 - Download speed limit (in Kib/s). ``speed_limit_down_enabled`` 1 - Enables download speed limiting. ``speed_limit_up`` 1 - Upload speed limit (in Kib/s). ``speed_limit_up_enabled`` 1 - Enables upload speed limiting. ``start_added_torrents`` 9 - Added torrents will be started right away. ``trash_original_torrent_files`` 9 - The .torrent file of added torrents will be deleted. ``utp_enabled`` 13 - Enables Micro Transport Protocol (UTP). ================================ ===== ================= ========================================================================================================================== .. NOTE:: transmissionrpc will try to automatically fix argument errors. """ args = {} for key, value in iteritems(kwargs): if key == 'encryption' and value not in ['required', 'preferred', 'tolerated']: raise ValueError('Invalid encryption value') argument = make_rpc_name(key) (arg, val) = argument_value_convert('session-set' , argument, value, self.rpc_version) args[arg] = val if len(args) > 0: self._request('session-set', args, timeout=timeout) def blocklist_update(self, timeout=None): """Update block list. Returns the size of the block list.""" self._rpc_version_warning(5) result = self._request('blocklist-update', timeout=timeout) if 'blocklist-size' in result: return result['blocklist-size'] return None def port_test(self, timeout=None): """ Tests to see if your incoming peer port is accessible from the outside world. """ self._rpc_version_warning(5) result = self._request('port-test', timeout=timeout) if 'port-is-open' in result: return result['port-is-open'] return None def free_space(self, path, timeout=None): """ Get the ammount of free space (in bytes) at the provided location. """ self._rpc_version_warning(15) result = self._request('free-space', {'path': path}, timeout=timeout) if result['path'] == path: return result['size-bytes'] return None def session_stats(self, timeout=None): """Get session statistics""" self._request('session-stats', timeout=timeout) return self.session
class Client(object): """ Client is the class handling the Transmission JSON-RPC client protocol. """ def __init__(self, address='localhost', port=DEFAULT_PORT, user=None, password=None, http_handler=None, timeout=None): if isinstance(timeout, (int, long, float)): self._query_timeout = float(timeout) else: self._query_timeout = DEFAULT_TIMEOUT urlo = urlparse.urlparse(address) if urlo.scheme == '': base_url = 'http://' + address + ':' + str(port) self.url = base_url + '/transmission/rpc' else: if urlo.port: self.url = urlo.scheme + '://' + urlo.hostname + ':' + str( urlo.port) + urlo.path else: self.url = urlo.scheme + '://' + urlo.hostname + urlo.path LOGGER.info('Using custom URL "' + self.url + '".') if urlo.username and urlo.password: user = urlo.username password = urlo.password elif urlo.username or urlo.password: LOGGER.warning( 'Either user or password missing, not using authentication.' ) if http_handler == None: self.http_handler = DefaultHTTPHandler() else: if hasattr(http_handler, 'set_authentication') and hasattr( http_handler, 'request'): self.http_handler = http_handler else: raise ValueError('Invalid HTTP handler.') if user and password: self.http_handler.set_authentication(self.url, user, password) elif user or password: LOGGER.warning( 'Either user or password missing, not using authentication.') self._sequence = 0 self.session = Session() self.session_id = 0 self.server_version = None self.protocol_version = None self.get_session() self.torrent_get_arguments = get_arguments('torrent-get', self.rpc_version) def _get_timeout(self): """ Get current timeout for HTTP queries. """ return self._query_timeout def _set_timeout(self, value): """ Set timeout for HTTP queries. """ self._query_timeout = float(value) def _del_timeout(self): """ Reset the HTTP query timeout to the default. """ self._query_timeout = DEFAULT_TIMEOUT timeout = property(_get_timeout, _set_timeout, _del_timeout, doc="HTTP query timeout.") def _http_query(self, query, timeout=None): """ Query Transmission through HTTP. """ headers = {'x-transmission-session-id': str(self.session_id)} request_count = 0 if timeout == None: timeout = self._query_timeout while True: LOGGER.debug( json.dumps( { 'url': self.url, 'headers': headers, 'query': query, 'timeout': timeout }, indent=2)) try: result = self.http_handler.request(self.url, query, headers, timeout) break except HTTPHandlerError, error: if error.code == 409: LOGGER.info( 'Server responded with 409, trying to set session-id.') if request_count > 1: raise TransmissionError( 'Session ID negotiation failed.', error) if 'x-transmission-session-id' in error.headers: self.session_id = error.headers[ 'x-transmission-session-id'] headers = { 'x-transmission-session-id': str(self.session_id) } else: debug_httperror(error) raise TransmissionError('Unknown conflict.', error) else: debug_httperror(error) raise TransmissionError('Request failed.', error) request_count = request_count + 1 return result
class Client(object): """ Client is the class handling the Transmission JSON-RPC client protocol. """ def __init__(self, address='localhost', port=DEFAULT_PORT, user=None, password=None, http_handler=None, timeout=None): if isinstance(timeout, (int, long, float)): self._query_timeout = float(timeout) else: self._query_timeout = DEFAULT_TIMEOUT urlo = _urlparse(address) if urlo.scheme == '': base_url = 'http://' + address + ':' + str(port) self.url = base_url + '/transmission/rpc' else: if urlo.port: self.url = urlo.scheme + '://' + urlo.hostname + ':' + str(urlo.port) + urlo.path else: self.url = urlo.scheme + '://' + urlo.hostname + urlo.path LOGGER.info('Using custom URL "' + self.url + '".') if urlo.username and urlo.password: user = urlo.username password = urlo.password elif urlo.username or urlo.password: LOGGER.warning('Either user or password missing, not using authentication.') if http_handler == None: self.http_handler = DefaultHTTPHandler() else: if hasattr(http_handler, 'set_authentication') and hasattr(http_handler, 'request'): self.http_handler = http_handler else: raise ValueError('Invalid HTTP handler.') if user and password: self.http_handler.set_authentication(self.url, user, password) elif user or password: LOGGER.warning('Either user or password missing, not using authentication.') self._sequence = 0 self.session = Session() self.session_id = 0 self.server_version = None self.protocol_version = None self.get_session() self.torrent_get_arguments = get_arguments('torrent-get' , self.rpc_version) def _get_timeout(self): """ Get current timeout for HTTP queries. """ return self._query_timeout def _set_timeout(self, value): """ Set timeout for HTTP queries. """ self._query_timeout = float(value) def _del_timeout(self): """ Reset the HTTP query timeout to the default. """ self._query_timeout = DEFAULT_TIMEOUT timeout = property(_get_timeout, _set_timeout, _del_timeout, doc="HTTP query timeout.") def _http_query(self, query, timeout=None): """ Query Transmission through HTTP. """ headers = {'x-transmission-session-id': str(self.session_id)} request_count = 0 if timeout == None: timeout = self._query_timeout while True: LOGGER.debug(json.dumps({'url': self.url, 'headers': headers, 'query': query, 'timeout': timeout}, indent=2)) try: result = self.http_handler.request(self.url, query, headers, timeout) break except HTTPHandlerError, error: if error.code == 409: LOGGER.info('Server responded with 409, trying to set session-id.') if request_count > 1: raise TransmissionError('Session ID negotiation failed.', error) if 'x-transmission-session-id' in error.headers: self.session_id = error.headers['x-transmission-session-id'] headers = {'x-transmission-session-id': str(self.session_id)} else: debug_httperror(error) raise TransmissionError('Unknown conflict.', error) else: debug_httperror(error) raise TransmissionError('Request failed.', error) request_count = request_count + 1 return result
class Client: """ Client is the class handling the Transmission JSON-RPC client protocol. """ def __init__( self, address="localhost", port=DEFAULT_PORT, user=None, password=None, http_handler=None, timeout=None, ): if isinstance(timeout, (int, float)): self._query_timeout = float(timeout) else: self._query_timeout = DEFAULT_TIMEOUT urlo = urlparse(address) if urlo.scheme == "": base_url = "http://" + address + ":" + str(port) self.url = base_url + "/transmission/rpc" else: if urlo.port: self.url = (urlo.scheme + "://" + urlo.hostname + ":" + str(urlo.port) + urlo.path) else: self.url = urlo.scheme + "://" + urlo.hostname + urlo.path LOGGER.info('Using custom URL "%s".', self.url) if urlo.username and urlo.password: user = urlo.username password = urlo.password elif urlo.username or urlo.password: LOGGER.warning( "Either user or password missing, not using authentication." ) if http_handler is None: self.http_handler = DefaultHTTPHandler() else: if hasattr(http_handler, "set_authentication") and hasattr( http_handler, "request"): self.http_handler = http_handler else: raise ValueError("Invalid HTTP handler.") if user and password: self.http_handler.set_authentication(self.url, user, password) elif user or password: LOGGER.warning( "Either user or password missing, not using authentication.") self._sequence = 0 self.session = None self.session_id = 0 self.server_version = None self.protocol_version = None self.get_session() self.torrent_get_arguments = get_arguments("torrent-get", self.rpc_version) def _get_timeout(self): """ Get current timeout for HTTP queries. """ return self._query_timeout def _set_timeout(self, value): """ Set timeout for HTTP queries. """ self._query_timeout = float(value) def _del_timeout(self): """ Reset the HTTP query timeout to the default. """ self._query_timeout = DEFAULT_TIMEOUT timeout = property(_get_timeout, _set_timeout, _del_timeout, doc="HTTP query timeout.") def _http_query(self, query, timeout=None): """ Query Transmission through HTTP. """ headers = {"x-transmission-session-id": str(self.session_id)} result = {} request_count = 0 if timeout is None: timeout = self._query_timeout use_logger = is_logger_configured() while True: if use_logger: LOGGER.debug( json.dumps( { "url": self.url, "headers": headers, "query": query, "timeout": timeout, }, indent=2, )) try: result = self.http_handler.request(self.url, query, headers, timeout) break except HTTPHandlerError as error: if error.code == 409: if use_logger: LOGGER.info( "Server responded with 409, trying to set session-id." ) if request_count > 1: raise TransmissionError( "Session ID negotiation failed.", error) session_id = None for key in list(error.headers.keys()): if key.lower() == "x-transmission-session-id": session_id = error.headers[key] self.session_id = session_id headers = { "x-transmission-session-id": str(self.session_id) } if session_id is None: if use_logger: debug_httperror(error) raise TransmissionError("Unknown conflict.", error) else: if use_logger: debug_httperror(error) raise TransmissionError("Request failed.", error) request_count += 1 return result def _request(self, method, arguments=None, ids=None, require_ids=False, timeout=None): """ Send json-rpc request to Transmission using http POST """ if not isinstance(method, str): raise ValueError("request takes method as string") if arguments is None: arguments = {} if not isinstance(arguments, dict): raise ValueError("request takes arguments as dict") ids = parse_torrent_ids(ids) if ids: arguments["ids"] = ids elif require_ids: raise ValueError("request require ids") use_logger = is_logger_configured() query = json.dumps({ "tag": self._sequence, "method": method, "arguments": arguments }) self._sequence += 1 start = time.time() http_data = self._http_query(query, timeout) if isinstance(http_data, bytes): http_data = str(http_data, encoding="utf-8", errors="replace") http_data = "".join( list(filter(lambda c: unicodedata.category(c)[0] != "C", http_data))) elapsed = time.time() - start if use_logger: LOGGER.info("http request took %.3f s", elapsed) try: data = json.loads(http_data) except ValueError as error: if use_logger: LOGGER.error("Error: %s", str(error)) LOGGER.error('Request: "%s"', query) LOGGER.error('HTTP data: "%s"', http_data) raise if use_logger: LOGGER.debug(json.dumps(data, indent=2)) if "result" in data: if data["result"] != "success": raise TransmissionError('Query failed with result "%s".' % (data["result"])) else: raise TransmissionError("Query failed without result.") results = {} if method == "torrent-get": for item in data["arguments"]["torrents"]: results[item["id"]] = Torrent(self, item) if self.protocol_version == 2 and "peers" not in item: self.protocol_version = 1 elif method == "torrent-add": item = None if "torrent-added" in data["arguments"]: item = data["arguments"]["torrent-added"] elif "torrent-duplicate" in data["arguments"]: item = data["arguments"]["torrent-duplicate"] if item: results[item["id"]] = Torrent(self, item) else: raise TransmissionError("Invalid torrent-add response.") elif method == "session-get": self._update_session(data["arguments"]) elif method == "session-stats": # older versions of T has the return data in "session-stats" if "session-stats" in data["arguments"]: self._update_session(data["arguments"]["session-stats"]) else: self._update_session(data["arguments"]) elif method in ( "port-test", "blocklist-update", "free-space", "torrent-rename-path", ): results = data["arguments"] else: return None return results def _update_session(self, data): """ Update session data. """ if self.session: self.session.from_request(data) else: self.session = Session(self, data) def _update_server_version(self): """Decode the Transmission version string, if available.""" if self.server_version is None: version_major = 1 version_minor = 30 version_changeset = 0 version_parser = re.compile(r"(\d).(\d+) \((\d+)\)") if hasattr(self.session, "version"): match = version_parser.match(self.session.version) if match: version_major = int(match.group(1)) version_minor = int(match.group(2)) version_changeset = match.group(3) self.server_version = (version_major, version_minor, version_changeset) @property def rpc_version(self): """ Get the Transmission RPC version. Trying to deduct if the server don't have a version value. """ if self.protocol_version is None: # Ugly fix for 2.20 - 2.22 reporting rpc-version 11, but having new arguments if self.server_version and (self.server_version[0] == 2 and self.server_version[1] in [20, 21, 22]): self.protocol_version = 12 # Ugly fix for 2.12 reporting rpc-version 10, but having new arguments elif self.server_version and (self.server_version[0] == 2 and self.server_version[1] == 12): self.protocol_version = 11 elif hasattr(self.session, "rpc_version"): self.protocol_version = self.session.rpc_version elif hasattr(self.session, "version"): self.protocol_version = 3 else: self.protocol_version = 2 return self.protocol_version def _rpc_version_warning(self, version): """ Add a warning to the log if the Transmission RPC version is lower then the provided version. """ if self.rpc_version < version: LOGGER.warning( "Using feature not supported by server. RPC version for server %d, feature introduced in %d.", self.rpc_version, version, ) def add_torrent(self, torrent, timeout=None, **kwargs): """ Add torrent to transfers list. Takes a uri to a torrent or base64 encoded torrent data in ``torrent``. Additional arguments are: ===================== ===== =========== ============================================================= Argument RPC Replaced by Description ===================== ===== =========== ============================================================= ``bandwidthPriority`` 8 - Priority for this transfer. ``cookies`` 13 - One or more HTTP cookie(s). ``download_dir`` 1 - The directory where the downloaded contents will be saved in. ``files_unwanted`` 1 - A list of file id's that shouldn't be downloaded. ``files_wanted`` 1 - A list of file id's that should be downloaded. ``paused`` 1 - If True, does not start the transfer when added. ``peer_limit`` 1 - Maximum number of peers allowed. ``priority_high`` 1 - A list of file id's that should have high priority. ``priority_low`` 1 - A list of file id's that should have low priority. ``priority_normal`` 1 - A list of file id's that should have normal priority. ===================== ===== =========== ============================================================= Returns a Torrent object with the fields. """ if torrent is None: raise ValueError("add_torrent requires data or a URI.") torrent_data = None parsed_uri = urlparse(torrent) if parsed_uri.scheme in ["ftp", "ftps", "http", "https"]: # there has been some problem with T's built in torrent fetcher, # use a python one instead try: torrent_file = urlopen(torrent) if torrent_file.info().get("Content-Encoding") == "gzip": torrent_data = gzip.decompress(torrent_file.read()) else: torrent_data = torrent_file.read() torrent_data = base64.b64encode(torrent_data).decode("utf-8") except HTTPError as e: # follow links that directly redirect to a magnet (redirects to # non-http/ftp files return a 302 error) if e.code == 302 and e.url.startswith("magnet:"): return self.add_torrent(e.url, timeout=timeout, **kwargs) else: raise e if parsed_uri.scheme in ["file"]: filepath = torrent # uri decoded different on linux / windows ? if parsed_uri.path: filepath = parsed_uri.path elif parsed_uri.netloc: filepath = parsed_uri.netloc if filepath.endswith(".gz"): with gzip.open(filepath, "rb") as torrent_file: torrent_data = torrent_file.read() else: with open(filepath, "rb") as torrent_file: 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: base64.b64decode(torrent.encode("utf-8")) 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 list(kwargs.items()): 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 remove_torrent(self, ids, delete_data=False, timeout=None): """ remove torrent(s) with provided id(s). Local data is removed if delete_data is True, otherwise not. """ self._rpc_version_warning(3) self._request( "torrent-remove", {"delete-local-data": rpc_bool(delete_data)}, ids, True, timeout=timeout, ) def start_torrent(self, ids, bypass_queue=False, timeout=None): """Start torrent(s) with provided id(s)""" method = "torrent-start" if bypass_queue and self.rpc_version >= 14: method = "torrent-start-now" self._request(method, {}, ids, True, timeout=timeout) def start_all(self, bypass_queue=False, timeout=None): """Start all torrents respecting the queue order""" torrent_list = self.get_torrents() method = "torrent-start" if self.rpc_version >= 14: if bypass_queue: method = "torrent-start-now" torrent_list = sorted(torrent_list, key=operator.attrgetter("queuePosition")) ids = [x.id for x in torrent_list] self._request(method, {}, ids, True, timeout=timeout) def stop_torrent(self, ids, timeout=None): """stop torrent(s) with provided id(s)""" self._request("torrent-stop", {}, ids, True, timeout=timeout) def verify_torrent(self, ids, timeout=None): """verify torrent(s) with provided id(s)""" self._request("torrent-verify", {}, ids, True, timeout=timeout) def reannounce_torrent(self, ids, timeout=None): """Reannounce torrent(s) with provided id(s)""" self._rpc_version_warning(5) self._request("torrent-reannounce", {}, ids, True, timeout=timeout) def 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 list(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 get_files(self, ids=None, timeout=None): """ Get list of files for provided torrent id(s). If ids is empty, information for all torrents are fetched. This function returns a dictionary for each requested torrent id holding the information about the files. :: { <torrent id>: { <file id>: { 'name': <file name>, 'size': <file size in bytes>, 'completed': <bytes completed>, 'priority': <priority ('high'|'normal'|'low')>, 'selected': <selected for download (True|False)> } ... } ... } """ fields = ["id", "name", "hashString", "files", "priorities", "wanted"] request_result = self._request("torrent-get", {"fields": fields}, ids, timeout=timeout) result = {} for tid, torrent in list(request_result.items()): result[tid] = torrent.files() return result def set_files(self, items, timeout=None): """ Set file properties. Takes a dictionary with similar contents as the result of `get_files`. :: { <torrent id>: { <file id>: { 'priority': <priority ('high'|'normal'|'low')>, 'selected': <selected for download (True|False)> } ... } ... } """ if not isinstance(items, dict): raise ValueError("Invalid file description") for tid, files in list(items.items()): if not isinstance(files, dict): continue wanted = [] unwanted = [] high = [] normal = [] low = [] for fid, file_desc in list(files.items()): if not isinstance(file_desc, dict): continue if "selected" in file_desc and file_desc["selected"]: wanted.append(fid) else: unwanted.append(fid) if "priority" in file_desc: if file_desc["priority"] == "high": high.append(fid) elif file_desc["priority"] == "normal": normal.append(fid) elif file_desc["priority"] == "low": low.append(fid) args = {"timeout": timeout} if high: args["priority_high"] = high if normal: args["priority_normal"] = normal if low: args["priority_low"] = low if wanted: args["files_wanted"] = wanted if unwanted: args["files_unwanted"] = unwanted self.change_torrent([tid], **args) def change_torrent(self, ids, timeout=None, **kwargs): """ Change torrent parameters for the torrent(s) with the supplied id's. The parameters are: ============================ ===== =============== ======================================================================================= Argument RPC Replaced by Description ============================ ===== =============== ======================================================================================= ``bandwidthPriority`` 5 - Priority for this transfer. ``downloadLimit`` 5 - Set the speed limit for download in Kib/s. ``downloadLimited`` 5 - Enable download speed limiter. ``files_unwanted`` 1 - A list of file id's that shouldn't be downloaded. ``files_wanted`` 1 - A list of file id's that should be downloaded. ``honorsSessionLimits`` 5 - Enables or disables the transfer to honour the upload limit set in the session. ``location`` 1 - Local download location. ``peer_limit`` 1 - The peer limit for the torrents. ``priority_high`` 1 - A list of file id's that should have high priority. ``priority_low`` 1 - A list of file id's that should have normal priority. ``priority_normal`` 1 - A list of file id's that should have low priority. ``queuePosition`` 14 - Position of this transfer in its queue. ``seedIdleLimit`` 10 - Seed inactivity limit in minutes. ``seedIdleMode`` 10 - Seed inactivity mode. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit. ``seedRatioLimit`` 5 - Seeding ratio. ``seedRatioMode`` 5 - Which ratio to use. 0 = Use session limit, 1 = Use transfer limit, 2 = Disable limit. ``speed_limit_down`` 1 - 5 downloadLimit Set the speed limit for download in Kib/s. ``speed_limit_down_enabled`` 1 - 5 downloadLimited Enable download speed limiter. ``speed_limit_up`` 1 - 5 uploadLimit Set the speed limit for upload in Kib/s. ``speed_limit_up_enabled`` 1 - 5 uploadLimited Enable upload speed limiter. ``trackerAdd`` 10 - Array of string with announce URLs to add. ``trackerRemove`` 10 - Array of ids of trackers to remove. ``trackerReplace`` 10 - Array of (id, url) tuples where the announce URL should be replaced. ``uploadLimit`` 5 - Set the speed limit for upload in Kib/s. ``uploadLimited`` 5 - Enable upload speed limiter. ============================ ===== =============== ======================================================================================= .. NOTE:: transmissionrpc will try to automatically fix argument errors. """ args = {} for key, value in list(kwargs.items()): argument = make_rpc_name(key) (arg, val) = argument_value_convert("torrent-set", argument, value, self.rpc_version) args[arg] = val if args: self._request("torrent-set", args, ids, True, timeout=timeout) else: ValueError("No arguments to set") def move_torrent_data(self, ids, location, timeout=None): """Move torrent data to the new location.""" self._rpc_version_warning(6) args = {"location": location, "move": True} self._request("torrent-set-location", args, ids, True, timeout=timeout) def locate_torrent_data(self, ids, location, timeout=None): """Locate torrent data at the provided location.""" self._rpc_version_warning(6) args = {"location": location, "move": False} self._request("torrent-set-location", args, ids, True, timeout=timeout) def 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 dirname: raise ValueError("Target name cannot contain a path delimiter") args = {"path": location, "name": name} result = self._request("torrent-rename-path", args, torrent_id, True, timeout=timeout) return (result["path"], result["name"]) def queue_top(self, ids, timeout=None): """Move transfer to the top of the queue.""" self._rpc_version_warning(14) self._request("queue-move-top", ids=ids, require_ids=True, timeout=timeout) def queue_bottom(self, ids, timeout=None): """Move transfer to the bottom of the queue.""" self._rpc_version_warning(14) self._request("queue-move-bottom", ids=ids, require_ids=True, timeout=timeout) def queue_up(self, ids, timeout=None): """Move transfer up in the queue.""" self._rpc_version_warning(14) self._request("queue-move-up", ids=ids, require_ids=True, timeout=timeout) def queue_down(self, ids, timeout=None): """Move transfer down in the queue.""" self._rpc_version_warning(14) self._request("queue-move-down", ids=ids, require_ids=True, timeout=timeout) def get_session(self, timeout=None): """ Get session parameters. See the Session class for more information. """ if self.session is None: self._request("session-get", timeout=timeout) self._update_server_version() return self.session def set_session(self, timeout=None, **kwargs): """ Set session parameters. The parameters are: ================================ ===== ================= ========================================================================================================================== Argument RPC Replaced by Description ================================ ===== ================= ========================================================================================================================== ``alt_speed_down`` 5 - Alternate session download speed limit (in Kib/s). ``alt_speed_enabled`` 5 - Enables alternate global download speed limiter. ``alt_speed_time_begin`` 5 - Time when alternate speeds should be enabled. Minutes after midnight. ``alt_speed_time_day`` 5 - Enables alternate speeds scheduling these days. ``alt_speed_time_enabled`` 5 - Enables alternate speeds scheduling. ``alt_speed_time_end`` 5 - Time when alternate speeds should be disabled. Minutes after midnight. ``alt_speed_up`` 5 - Alternate session upload speed limit (in Kib/s). ``blocklist_enabled`` 5 - Enables the block list ``blocklist_url`` 11 - Location of the block list. Updated with blocklist-update. ``cache_size_mb`` 10 - The maximum size of the disk cache in MB ``dht_enabled`` 6 - Enables DHT. ``download_dir`` 1 - Set the session download directory. ``download_queue_enabled`` 14 - Enables download queue. ``download_queue_size`` 14 - Number of slots in the download queue. ``encryption`` 1 - Set the session encryption mode, one of ``required``, ``preferred`` or ``tolerated``. ``idle_seeding_limit`` 10 - The default seed inactivity limit in minutes. ``idle_seeding_limit_enabled`` 10 - Enables the default seed inactivity limit ``incomplete_dir`` 7 - The path to the directory of incomplete transfer data. ``incomplete_dir_enabled`` 7 - Enables the incomplete transfer data directory. Otherwise data for incomplete transfers are stored in the download target. ``lpd_enabled`` 9 - Enables local peer discovery for public torrents. ``peer_limit`` 1 - 5 peer-limit-global Maximum number of peers. ``peer_limit_global`` 5 - Maximum number of peers. ``peer_limit_per_torrent`` 5 - Maximum number of peers per transfer. ``peer_port`` 5 - Peer port. ``peer_port_random_on_start`` 5 - Enables randomized peer port on start of Transmission. ``pex_allowed`` 1 - 5 pex-enabled Allowing PEX in public torrents. ``pex_enabled`` 5 - Allowing PEX in public torrents. ``port`` 1 - 5 peer-port Peer port. ``port_forwarding_enabled`` 1 - Enables port forwarding. ``queue_stalled_enabled`` 14 - Enable tracking of stalled transfers. ``queue_stalled_minutes`` 14 - Number of minutes of idle that marks a transfer as stalled. ``rename_partial_files`` 8 - Appends ".part" to incomplete files ``script_torrent_done_enabled`` 9 - Whether or not to call the "done" script. ``script_torrent_done_filename`` 9 - Filename of the script to run when the transfer is done. ``seed_queue_enabled`` 14 - Enables upload queue. ``seed_queue_size`` 14 - Number of slots in the upload queue. ``seedRatioLimit`` 5 - Seed ratio limit. 1.0 means 1:1 download and upload ratio. ``seedRatioLimited`` 5 - Enables seed ration limit. ``speed_limit_down`` 1 - Download speed limit (in Kib/s). ``speed_limit_down_enabled`` 1 - Enables download speed limiting. ``speed_limit_up`` 1 - Upload speed limit (in Kib/s). ``speed_limit_up_enabled`` 1 - Enables upload speed limiting. ``start_added_torrents`` 9 - Added torrents will be started right away. ``trash_original_torrent_files`` 9 - The .torrent file of added torrents will be deleted. ``utp_enabled`` 13 - Enables Micro Transport Protocol (UTP). ================================ ===== ================= ========================================================================================================================== .. NOTE:: transmissionrpc will try to automatically fix argument errors. """ args = {} for key, value in list(kwargs.items()): if key == "encryption" and value not in [ "required", "preferred", "tolerated", ]: raise ValueError("Invalid encryption value") argument = make_rpc_name(key) (arg, val) = argument_value_convert("session-set", argument, value, self.rpc_version) args[arg] = val if args: self._request("session-set", args, timeout=timeout) def blocklist_update(self, timeout=None): """Update block list. Returns the size of the block list.""" self._rpc_version_warning(5) result = self._request("blocklist-update", timeout=timeout) if "blocklist-size" in result: return result["blocklist-size"] return None def port_test(self, timeout=None): """ Tests to see if your incoming peer port is accessible from the outside world. """ self._rpc_version_warning(5) result = self._request("port-test", timeout=timeout) if "port-is-open" in result: return result["port-is-open"] return None def free_space(self, path, timeout=None): """ Get the ammount of free space (in bytes) at the provided location. """ self._rpc_version_warning(15) result = self._request("free-space", {"path": path}, timeout=timeout) if result["path"] == path: return result["size-bytes"] return None def session_stats(self, timeout=None): """Get session statistics""" self._request("session-stats", timeout=timeout) return self.session