diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 92813093..4fd8e112 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 12.1.02 +current_version = 12.1.03 commit = True tag = False diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 948047c9..e776ae09 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -26,7 +26,9 @@ jobs: maxParallel: 5 steps: - - script: sudo apt-get install ffmpeg + - script: | + sudo apt-get update + sudo apt-get install ffmpeg displayName: 'Install ffmpeg' - task: UsePythonVersion@0 diff --git a/core/__init__.py b/core/__init__.py index 3dff1f4c..9814c87f 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -83,7 +83,7 @@ from core.utils import ( wake_up, ) -__version__ = '12.1.02' +__version__ = '12.1.03' # Client Agents NZB_CLIENTS = ['sabnzbd', 'nzbget', 'manual'] @@ -983,13 +983,22 @@ def check_python(): # Log warning if within grace period days_left = eol.lifetime() - logger.info( - 'Python v{major}.{minor} will reach end of life in {x} days.'.format( - major=sys.version_info[0], - minor=sys.version_info[1], - x=days_left, - ), - ) + if days_left > 0: + logger.info( + 'Python v{major}.{minor} will reach end of life in {x} days.'.format( + major=sys.version_info[0], + minor=sys.version_info[1], + x=days_left, + ), + ) + else: + logger.info( + 'Python v{major}.{minor} reached end of life {x} days ago.'.format( + major=sys.version_info[0], + minor=sys.version_info[1], + x=-days_left, + ), + ) if days_left <= grace_period: logger.warning('Please upgrade to a more recent Python version.') diff --git a/core/plugins/downloaders/torrent/deluge.py b/core/plugins/downloaders/torrent/deluge.py index 86542a8a..0c4c07c8 100644 --- a/core/plugins/downloaders/torrent/deluge.py +++ b/core/plugins/downloaders/torrent/deluge.py @@ -5,7 +5,7 @@ from __future__ import ( unicode_literals, ) -from synchronousdeluge.client import DelugeClient +from deluge_client.client import DelugeRPCClient import core from core import logger @@ -19,9 +19,9 @@ def configure_client(): password = core.DELUGE_PASSWORD logger.debug('Connecting to {0}: http://{1}:{2}'.format(agent, host, port)) - client = DelugeClient() + client = DelugeRPCClient(host, port, user, password) try: - client.connect(host, port, user, password) + client.connect() except Exception: logger.error('Failed to connect to Deluge') else: diff --git a/core/utils/encoding.py b/core/utils/encoding.py index bcc4994f..bd6183d5 100644 --- a/core/utils/encoding.py +++ b/core/utils/encoding.py @@ -8,12 +8,16 @@ from __future__ import ( import os from six import text_type +from six import PY2 import core from core import logger +if not PY2: + from builtins import bytes -def char_replace(name): + +def char_replace(name_in): # Special character hex range: # CP850: 0x80-0xA5 (fortunately not used in ISO-8859-15) # UTF-8: 1st hex code 0xC2-0xC3 followed by a 2nd hex code 0xA1-0xFF @@ -22,31 +26,36 @@ def char_replace(name): # If there is special character, detects if it is a UTF-8, CP850 or ISO-8859-15 encoding encoded = False encoding = None - if isinstance(name, text_type): - return encoded, name.encode(core.SYS_ENCODING) + if isinstance(name_in, text_type): + return encoded, name_in.encode(core.SYS_ENCODING) + if PY2: + name = name_in + else: + name = bytes(name_in) for Idx in range(len(name)): + print('Trying to intuit the encoding') # /!\ detection is done 2char by 2char for UTF-8 special character if (len(name) != 1) & (Idx < (len(name) - 1)): # Detect UTF-8 - if ((name[Idx] == '\xC2') | (name[Idx] == '\xC3')) & ( - (name[Idx + 1] >= '\xA0') & (name[Idx + 1] <= '\xFF')): + if ((name[Idx] == 0xC2) | (name[Idx] == 0xC3)) & ( + (name[Idx + 1] >= 0xA0) & (name[Idx + 1] <= 0xFF)): encoding = 'utf-8' break # Detect CP850 - elif (name[Idx] >= '\x80') & (name[Idx] <= '\xA5'): + elif (name[Idx] >= 0x80) & (name[Idx] <= 0xA5): encoding = 'cp850' break # Detect ISO-8859-15 - elif (name[Idx] >= '\xA6') & (name[Idx] <= '\xFF'): + elif (name[Idx] >= 0xA6) & (name[Idx] <= 0xFF): encoding = 'iso-8859-15' break else: # Detect CP850 - if (name[Idx] >= '\x80') & (name[Idx] <= '\xA5'): + if (name[Idx] >= 0x80) & (name[Idx] <= 0xA5): encoding = 'cp850' break # Detect ISO-8859-15 - elif (name[Idx] >= '\xA6') & (name[Idx] <= '\xFF'): + elif (name[Idx] >= 0xA6) & (name[Idx] <= 0xFF): encoding = 'iso-8859-15' break if encoding and not encoding == core.SYS_ENCODING: diff --git a/core/utils/parsers.py b/core/utils/parsers.py index fd88ec63..eff4b3e9 100644 --- a/core/utils/parsers.py +++ b/core/utils/parsers.py @@ -65,7 +65,7 @@ def parse_deluge(args): input_hash = args[1] input_id = args[1] try: - input_category = core.TORRENT_CLASS.core.get_torrent_status(input_id, ['label']).get()['label'] + input_category = core.TORRENT_CLASS.core.get_torrent_status(input_id, ['label']).get(b'label').decode() except Exception: input_category = '' return input_directory, input_name, input_category, input_hash, input_id diff --git a/eol.py b/eol.py index fa328fdd..7c46aa4d 100644 --- a/eol.py +++ b/eol.py @@ -96,7 +96,7 @@ def check(version=None, grace_period=0): :return: None """ try: - raise_for_status(version, grace_period) + warn_for_status(version, grace_period) except LifetimeError as error: print('Please use a newer version of Python.') print_statuses() diff --git a/libs/custom/deluge_client/__init__.py b/libs/custom/deluge_client/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/libs/custom/deluge_client/client.py b/libs/custom/deluge_client/client.py new file mode 100644 index 00000000..827fcd7c --- /dev/null +++ b/libs/custom/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] = '