diff --git a/couchpotato/core/downloaders/deluge.py b/couchpotato/core/downloaders/deluge.py index 788891b..d788bcd 100644 --- a/couchpotato/core/downloaders/deluge.py +++ b/couchpotato/core/downloaders/deluge.py @@ -10,8 +10,7 @@ from couchpotato.core._base.downloader.main import DownloaderBase, ReleaseDownlo from couchpotato.core.helpers.encoding import isInt, sp from couchpotato.core.helpers.variable import tryFloat, cleanHost from couchpotato.core.logger import CPLog -from synchronousdeluge import DelugeClient - +from deluge_client.client import DelugeRPCClient log = CPLog(__name__) @@ -221,8 +220,10 @@ class DelugeRPC(object): self.password = password def connect(self): - self.client = DelugeClient() - self.client.connect(self.host, int(self.port), self.username, self.password) + #self.client = DelugeClient() + #self.client.connect(self.host, int(self.port), self.username, self.password) + self.client = DelugeRPCClient(self.host, int(self.port), self.username, self.password) + self.client.connect() def test(self): try: @@ -235,12 +236,12 @@ class DelugeRPC(object): torrent_id = False try: self.connect() - torrent_id = self.client.core.add_torrent_magnet(torrent, options).get() + torrent_id = self.client.core.add_torrent_magnet(torrent, options) if not torrent_id: torrent_id = self._check_torrent(True, torrent) if torrent_id and options['label']: - self.client.label.set_torrent(torrent_id, options['label']).get() + self.client.label.set_torrent(torrent_id, options['label']) except Exception as err: log.error('Failed to add torrent magnet %s: %s %s', (torrent, err, traceback.format_exc())) finally: @@ -253,12 +254,12 @@ class DelugeRPC(object): torrent_id = False try: self.connect() - torrent_id = self.client.core.add_torrent_file(filename, b64encode(torrent), options).get() + torrent_id = self.client.core.add_torrent_file(filename, b64encode(torrent), options) if not torrent_id: torrent_id = self._check_torrent(False, torrent) if torrent_id and options['label']: - self.client.label.set_torrent(torrent_id, options['label']).get() + self.client.label.set_torrent(torrent_id, options['label']) except Exception as err: log.error('Failed to add torrent file %s: %s %s', (filename, err, traceback.format_exc())) finally: @@ -271,7 +272,7 @@ class DelugeRPC(object): ret = False try: self.connect() - ret = self.client.core.get_torrents_status({'id': ids}, ('name', 'hash', 'save_path', 'move_completed_path', 'progress', 'state', 'eta', 'ratio', 'stop_ratio', 'is_seed', 'is_finished', 'paused', 'move_on_completed', 'files')).get() + ret = self.client.core.get_torrents_status({'id': ids}, ('name', 'hash', 'save_path', 'move_completed_path', 'progress', 'state', 'eta', 'ratio', 'stop_ratio', 'is_seed', 'is_finished', 'paused', 'move_on_completed', 'files')) except Exception as err: log.error('Failed to get all torrents: %s %s', (err, traceback.format_exc())) finally: @@ -282,7 +283,7 @@ class DelugeRPC(object): def pause_torrent(self, torrent_ids): try: self.connect() - self.client.core.pause_torrent(torrent_ids).get() + self.client.core.pause_torrent(torrent_ids) except Exception as err: log.error('Failed to pause torrent: %s %s', (err, traceback.format_exc())) finally: @@ -292,7 +293,7 @@ class DelugeRPC(object): def resume_torrent(self, torrent_ids): try: self.connect() - self.client.core.resume_torrent(torrent_ids).get() + self.client.core.resume_torrent(torrent_ids) except Exception as err: log.error('Failed to resume torrent: %s %s', (err, traceback.format_exc())) finally: @@ -303,7 +304,7 @@ class DelugeRPC(object): ret = False try: self.connect() - ret = self.client.core.remove_torrent(torrent_id, remove_local_data).get() + ret = self.client.core.remove_torrent(torrent_id, remove_local_data) except Exception as err: log.error('Failed to remove torrent: %s %s', (err, traceback.format_exc())) finally: @@ -327,7 +328,7 @@ class DelugeRPC(object): torrent_hash = b16encode(b32decode(torrent_hash)) torrent_hash = torrent_hash.lower() - torrent_check = self.client.core.get_torrent_status(torrent_hash, {}).get() + torrent_check = self.client.core.get_torrent_status(torrent_hash, {}) if torrent_check['hash']: return torrent_hash diff --git a/libs/deluge_client/__init__.py b/libs/deluge_client/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/libs/deluge_client/client.py b/libs/deluge_client/client.py new file mode 100644 index 0000000..827fcd7 --- /dev/null +++ b/libs/deluge_client/client.py @@ -0,0 +1,275 @@ +import logging +import socket +import ssl +import struct +import warnings +import zlib + +from .rencode import dumps, loads + +RPC_RESPONSE = 1 +RPC_ERROR = 2 +RPC_EVENT = 3 + +MESSAGE_HEADER_SIZE = 5 +READ_SIZE = 10 + +logger = logging.getLogger(__name__) + + +class DelugeClientException(Exception): + """Base exception for all deluge client exceptions""" + + +class ConnectionLostException(DelugeClientException): + pass + + +class CallTimeoutException(DelugeClientException): + pass + + +class InvalidHeaderException(DelugeClientException): + pass + + +class FailedToReconnectException(DelugeClientException): + pass + + +class RemoteException(DelugeClientException): + pass + + +class DelugeRPCClient(object): + timeout = 20 + + def __init__(self, host, port, username, password, decode_utf8=False, automatic_reconnect=True): + self.host = host + self.port = port + self.username = username + self.password = password + self.deluge_version = None + # This is only applicable if deluge_version is 2 + self.deluge_protocol_version = None + + self.decode_utf8 = decode_utf8 + if not self.decode_utf8: + warnings.warn('Using `decode_utf8=False` is deprecated, please set it to True.' + 'The argument will be removed in a future release where it will be always True', DeprecationWarning) + + self.automatic_reconnect = automatic_reconnect + + self.request_id = 1 + self.connected = False + self._create_socket() + + def _create_socket(self, ssl_version=None): + if ssl_version is not None: + self._socket = ssl.wrap_socket(socket.socket(socket.AF_INET, socket.SOCK_STREAM), ssl_version=ssl_version) + else: + self._socket = ssl.wrap_socket(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) + self._socket.settimeout(self.timeout) + + def connect(self): + """ + Connects to the Deluge instance + """ + self._connect() + logger.debug('Connected to Deluge, detecting daemon version') + self._detect_deluge_version() + logger.debug('Daemon version {} detected, logging in'.format(self.deluge_version)) + if self.deluge_version == 2: + result = self.call('daemon.login', self.username, self.password, client_version='deluge-client') + else: + result = self.call('daemon.login', self.username, self.password) + logger.debug('Logged in with value %r' % result) + self.connected = True + + def _connect(self): + logger.info('Connecting to %s:%s' % (self.host, self.port)) + try: + self._socket.connect((self.host, self.port)) + except ssl.SSLError as e: + # Note: have not verified that we actually get errno 258 for this error + if (hasattr(ssl, 'PROTOCOL_SSLv3') and + (getattr(e, 'reason', None) == 'UNSUPPORTED_PROTOCOL' or e.errno == 258)): + logger.warning('Was unable to ssl handshake, trying to force SSLv3 (insecure)') + self._create_socket(ssl_version=ssl.PROTOCOL_SSLv3) + self._socket.connect((self.host, self.port)) + else: + raise + + def disconnect(self): + """ + Disconnect from deluge + """ + if self.connected: + self._socket.close() + self._socket = None + self.connected = False + + def _detect_deluge_version(self): + if self.deluge_version is not None: + return + + self._send_call(1, None, 'daemon.info') + self._send_call(2, None, 'daemon.info') + self._send_call(2, 1, 'daemon.info') + result = self._socket.recv(1) + if result[:1] == b'D': + # This is a protocol deluge 2.0 was using before release + self.deluge_version = 2 + self.deluge_protocol_version = None + # If we need the specific version of deluge 2, this is it. + daemon_version = self._receive_response(2, None, partial_data=result) + elif ord(result[:1]) == 1: + self.deluge_version = 2 + self.deluge_protocol_version = 1 + # If we need the specific version of deluge 2, this is it. + daemon_version = self._receive_response(2, 1, partial_data=result) + else: + self.deluge_version = 1 + # Deluge 1 doesn't recover well from the bad request. Re-connect the socket. + self._socket.close() + self._create_socket() + self._connect() + + def _send_call(self, deluge_version, protocol_version, method, *args, **kwargs): + self.request_id += 1 + if method == 'daemon.login': + debug_args = list(args) + if len(debug_args) >= 2: + debug_args[1] = '