mirror of
https://github.com/clinton-hall/nzbToMedia.git
synced 2025-08-14 02:26:53 -07:00
parent
feb4e36c4c
commit
aeb3e0fd6d
13 changed files with 820 additions and 337 deletions
|
@ -5,7 +5,7 @@ from __future__ import (
|
||||||
unicode_literals,
|
unicode_literals,
|
||||||
)
|
)
|
||||||
|
|
||||||
from synchronousdeluge.client import DelugeClient
|
from deluge_client.client import DelugeRPCClient
|
||||||
|
|
||||||
import core
|
import core
|
||||||
from core import logger
|
from core import logger
|
||||||
|
@ -19,9 +19,9 @@ def configure_client():
|
||||||
password = core.DELUGE_PASSWORD
|
password = core.DELUGE_PASSWORD
|
||||||
|
|
||||||
logger.debug('Connecting to {0}: http://{1}:{2}'.format(agent, host, port))
|
logger.debug('Connecting to {0}: http://{1}:{2}'.format(agent, host, port))
|
||||||
client = DelugeClient()
|
client = DelugeRPCClient(host, port, user, password)
|
||||||
try:
|
try:
|
||||||
client.connect(host, port, user, password)
|
client.connect()
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error('Failed to connect to Deluge')
|
logger.error('Failed to connect to Deluge')
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -65,7 +65,7 @@ def parse_deluge(args):
|
||||||
input_hash = args[1]
|
input_hash = args[1]
|
||||||
input_id = args[1]
|
input_id = args[1]
|
||||||
try:
|
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:
|
except Exception:
|
||||||
input_category = ''
|
input_category = ''
|
||||||
return input_directory, input_name, input_category, input_hash, input_id
|
return input_directory, input_name, input_category, input_hash, input_id
|
||||||
|
|
0
libs/custom/deluge_client/__init__.py
Normal file
0
libs/custom/deluge_client/__init__.py
Normal file
275
libs/custom/deluge_client/client.py
Normal file
275
libs/custom/deluge_client/client.py
Normal file
|
@ -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] = '<password hidden>'
|
||||||
|
logger.debug('Calling reqid %s method %r with args:%r kwargs:%r' % (self.request_id, method, debug_args, kwargs))
|
||||||
|
else:
|
||||||
|
logger.debug('Calling reqid %s method %r with args:%r kwargs:%r' % (self.request_id, method, args, kwargs))
|
||||||
|
|
||||||
|
req = ((self.request_id, method, args, kwargs), )
|
||||||
|
req = zlib.compress(dumps(req))
|
||||||
|
|
||||||
|
if deluge_version == 2:
|
||||||
|
if protocol_version is None:
|
||||||
|
# This was a protocol for deluge 2 before they introduced protocol version numbers
|
||||||
|
self._socket.send(b'D' + struct.pack("!i", len(req)))
|
||||||
|
elif protocol_version == 1:
|
||||||
|
self._socket.send(struct.pack('!BI', protocol_version, len(req)))
|
||||||
|
else:
|
||||||
|
raise Exception('Deluge protocol version {} is not (yet) supported.'.format(protocol_version))
|
||||||
|
self._socket.send(req)
|
||||||
|
|
||||||
|
def _receive_response(self, deluge_version, protocol_version, partial_data=b''):
|
||||||
|
expected_bytes = None
|
||||||
|
data = partial_data
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
d = self._socket.recv(READ_SIZE)
|
||||||
|
except ssl.SSLError:
|
||||||
|
raise CallTimeoutException()
|
||||||
|
|
||||||
|
data += d
|
||||||
|
if deluge_version == 2:
|
||||||
|
if expected_bytes is None:
|
||||||
|
if len(data) < 5:
|
||||||
|
continue
|
||||||
|
|
||||||
|
header = data[:MESSAGE_HEADER_SIZE]
|
||||||
|
data = data[MESSAGE_HEADER_SIZE:]
|
||||||
|
|
||||||
|
if protocol_version is None:
|
||||||
|
if header[0] != b'D'[0]:
|
||||||
|
raise InvalidHeaderException('Expected D as first byte in reply')
|
||||||
|
elif ord(header[:1]) != protocol_version:
|
||||||
|
raise InvalidHeaderException(
|
||||||
|
'Expected protocol version ({}) as first byte in reply'.format(protocol_version)
|
||||||
|
)
|
||||||
|
|
||||||
|
if protocol_version is None:
|
||||||
|
expected_bytes = struct.unpack('!i', header[1:])[0]
|
||||||
|
else:
|
||||||
|
expected_bytes = struct.unpack('!I', header[1:])[0]
|
||||||
|
|
||||||
|
if len(data) >= expected_bytes:
|
||||||
|
data = zlib.decompress(data)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
data = zlib.decompress(data)
|
||||||
|
except zlib.error:
|
||||||
|
if not d:
|
||||||
|
raise ConnectionLostException()
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
|
||||||
|
data = list(loads(data, decode_utf8=self.decode_utf8))
|
||||||
|
msg_type = data.pop(0)
|
||||||
|
request_id = data.pop(0)
|
||||||
|
|
||||||
|
if msg_type == RPC_ERROR:
|
||||||
|
if self.deluge_version == 2:
|
||||||
|
exception_type, exception_msg, _, traceback = data
|
||||||
|
# On deluge 2, exception arguments are sent as tuple
|
||||||
|
if self.decode_utf8:
|
||||||
|
exception_msg = ', '.join(exception_msg)
|
||||||
|
else:
|
||||||
|
exception_msg = b', '.join(exception_msg)
|
||||||
|
else:
|
||||||
|
exception_type, exception_msg, traceback = data[0]
|
||||||
|
if self.decode_utf8:
|
||||||
|
exception = type(str(exception_type), (RemoteException, ), {})
|
||||||
|
exception_msg = '%s\n%s' % (exception_msg,
|
||||||
|
traceback)
|
||||||
|
else:
|
||||||
|
exception = type(str(exception_type.decode('utf-8', 'ignore')), (RemoteException, ), {})
|
||||||
|
exception_msg = '%s\n%s' % (exception_msg.decode('utf-8', 'ignore'),
|
||||||
|
traceback.decode('utf-8', 'ignore'))
|
||||||
|
raise exception(exception_msg)
|
||||||
|
elif msg_type == RPC_RESPONSE:
|
||||||
|
retval = data[0]
|
||||||
|
return retval
|
||||||
|
|
||||||
|
def reconnect(self):
|
||||||
|
"""
|
||||||
|
Reconnect
|
||||||
|
"""
|
||||||
|
self.disconnect()
|
||||||
|
self._create_socket()
|
||||||
|
self.connect()
|
||||||
|
|
||||||
|
def call(self, method, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Calls an RPC function
|
||||||
|
"""
|
||||||
|
tried_reconnect = False
|
||||||
|
for _ in range(2):
|
||||||
|
try:
|
||||||
|
self._send_call(self.deluge_version, self.deluge_protocol_version, method, *args, **kwargs)
|
||||||
|
return self._receive_response(self.deluge_version, self.deluge_protocol_version)
|
||||||
|
except (socket.error, ConnectionLostException, CallTimeoutException):
|
||||||
|
if self.automatic_reconnect:
|
||||||
|
if tried_reconnect:
|
||||||
|
raise FailedToReconnectException()
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.reconnect()
|
||||||
|
except (socket.error, ConnectionLostException, CallTimeoutException):
|
||||||
|
raise FailedToReconnectException()
|
||||||
|
|
||||||
|
tried_reconnect = True
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
def __getattr__(self, item):
|
||||||
|
return RPCCaller(self.call, item)
|
||||||
|
|
||||||
|
|
||||||
|
class RPCCaller(object):
|
||||||
|
def __init__(self, caller, method=''):
|
||||||
|
self.caller = caller
|
||||||
|
self.method = method
|
||||||
|
|
||||||
|
def __getattr__(self, item):
|
||||||
|
return RPCCaller(self.caller, self.method+'.'+item)
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
return self.caller(self.method, *args, **kwargs)
|
474
libs/custom/deluge_client/rencode.py
Normal file
474
libs/custom/deluge_client/rencode.py
Normal file
|
@ -0,0 +1,474 @@
|
||||||
|
# Original bencode module by Petru Paler, et al.
|
||||||
|
#
|
||||||
|
# Modifications by Connelly Barnes:
|
||||||
|
#
|
||||||
|
# - Added support for floats (sent as 32-bit or 64-bit in network
|
||||||
|
# order), bools, None.
|
||||||
|
# - Allowed dict keys to be of any serializable type.
|
||||||
|
# - Lists/tuples are always decoded as tuples (thus, tuples can be
|
||||||
|
# used as dict keys).
|
||||||
|
# - Embedded extra information in the 'typecodes' to save some space.
|
||||||
|
# - Added a restriction on integer length, so that malicious hosts
|
||||||
|
# cannot pass us large integers which take a long time to decode.
|
||||||
|
#
|
||||||
|
# Licensed by Bram Cohen under the "MIT license":
|
||||||
|
#
|
||||||
|
# "Copyright (C) 2001-2002 Bram Cohen
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person
|
||||||
|
# obtaining a copy of this software and associated documentation files
|
||||||
|
# (the "Software"), to deal in the Software without restriction,
|
||||||
|
# including without limitation the rights to use, copy, modify, merge,
|
||||||
|
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||||
|
# and to permit persons to whom the Software is furnished to do so,
|
||||||
|
# subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be
|
||||||
|
# included in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# The Software is provided "AS IS", without warranty of any kind,
|
||||||
|
# express or implied, including but not limited to the warranties of
|
||||||
|
# merchantability, fitness for a particular purpose and
|
||||||
|
# noninfringement. In no event shall the authors or copyright holders
|
||||||
|
# be liable for any claim, damages or other liability, whether in an
|
||||||
|
# action of contract, tort or otherwise, arising from, out of or in
|
||||||
|
# connection with the Software or the use or other dealings in the
|
||||||
|
# Software."
|
||||||
|
#
|
||||||
|
# (The rencode module is licensed under the above license as well).
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
rencode -- Web safe object pickling/unpickling.
|
||||||
|
|
||||||
|
Public domain, Connelly Barnes 2006-2007.
|
||||||
|
|
||||||
|
The rencode module is a modified version of bencode from the
|
||||||
|
BitTorrent project. For complex, heterogeneous data structures with
|
||||||
|
many small elements, r-encodings take up significantly less space than
|
||||||
|
b-encodings:
|
||||||
|
|
||||||
|
>>> len(rencode.dumps({'a':0, 'b':[1,2], 'c':99}))
|
||||||
|
13
|
||||||
|
>>> len(bencode.bencode({'a':0, 'b':[1,2], 'c':99}))
|
||||||
|
26
|
||||||
|
|
||||||
|
The rencode format is not standardized, and may change with different
|
||||||
|
rencode module versions, so you should check that you are using the
|
||||||
|
same rencode version throughout your project.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
from threading import Lock
|
||||||
|
|
||||||
|
try:
|
||||||
|
from future_builtins import zip
|
||||||
|
except ImportError:
|
||||||
|
# Ignore on Py3.
|
||||||
|
pass
|
||||||
|
|
||||||
|
__version__ = ('Python', 1, 0, 4)
|
||||||
|
__all__ = ['dumps', 'loads']
|
||||||
|
|
||||||
|
py3 = sys.version_info[0] >= 3
|
||||||
|
if py3:
|
||||||
|
long = int # pylint: disable=redefined-builtin
|
||||||
|
unicode = str # pylint: disable=redefined-builtin
|
||||||
|
|
||||||
|
def int2byte(c):
|
||||||
|
return bytes([c])
|
||||||
|
else:
|
||||||
|
def int2byte(c):
|
||||||
|
return chr(c)
|
||||||
|
|
||||||
|
# Default number of bits for serialized floats, either 32 or 64 (also a parameter for dumps()).
|
||||||
|
DEFAULT_FLOAT_BITS = 32
|
||||||
|
|
||||||
|
# Maximum length of integer when written as base 10 string.
|
||||||
|
MAX_INT_LENGTH = 64
|
||||||
|
|
||||||
|
# The bencode 'typecodes' such as i, d, etc have been extended and
|
||||||
|
# relocated on the base-256 character set.
|
||||||
|
CHR_LIST = int2byte(59)
|
||||||
|
CHR_DICT = int2byte(60)
|
||||||
|
CHR_INT = int2byte(61)
|
||||||
|
CHR_INT1 = int2byte(62)
|
||||||
|
CHR_INT2 = int2byte(63)
|
||||||
|
CHR_INT4 = int2byte(64)
|
||||||
|
CHR_INT8 = int2byte(65)
|
||||||
|
CHR_FLOAT32 = int2byte(66)
|
||||||
|
CHR_FLOAT64 = int2byte(44)
|
||||||
|
CHR_TRUE = int2byte(67)
|
||||||
|
CHR_FALSE = int2byte(68)
|
||||||
|
CHR_NONE = int2byte(69)
|
||||||
|
CHR_TERM = int2byte(127)
|
||||||
|
|
||||||
|
# Positive integers with value embedded in typecode.
|
||||||
|
INT_POS_FIXED_START = 0
|
||||||
|
INT_POS_FIXED_COUNT = 44
|
||||||
|
|
||||||
|
# Dictionaries with length embedded in typecode.
|
||||||
|
DICT_FIXED_START = 102
|
||||||
|
DICT_FIXED_COUNT = 25
|
||||||
|
|
||||||
|
# Negative integers with value embedded in typecode.
|
||||||
|
INT_NEG_FIXED_START = 70
|
||||||
|
INT_NEG_FIXED_COUNT = 32
|
||||||
|
|
||||||
|
# Strings with length embedded in typecode.
|
||||||
|
STR_FIXED_START = 128
|
||||||
|
STR_FIXED_COUNT = 64
|
||||||
|
|
||||||
|
# Lists with length embedded in typecode.
|
||||||
|
LIST_FIXED_START = STR_FIXED_START + STR_FIXED_COUNT
|
||||||
|
LIST_FIXED_COUNT = 64
|
||||||
|
|
||||||
|
# Whether strings should be decoded when loading
|
||||||
|
_decode_utf8 = False
|
||||||
|
|
||||||
|
|
||||||
|
def decode_int(x, f):
|
||||||
|
f += 1
|
||||||
|
newf = x.index(CHR_TERM, f)
|
||||||
|
if newf - f >= MAX_INT_LENGTH:
|
||||||
|
raise ValueError('overflow')
|
||||||
|
try:
|
||||||
|
n = int(x[f:newf])
|
||||||
|
except (OverflowError, ValueError):
|
||||||
|
n = long(x[f:newf])
|
||||||
|
if x[f:f + 1] == '-':
|
||||||
|
if x[f + 1:f + 2] == '0':
|
||||||
|
raise ValueError
|
||||||
|
elif x[f:f + 1] == '0' and newf != f + 1:
|
||||||
|
raise ValueError
|
||||||
|
return (n, newf + 1)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_intb(x, f):
|
||||||
|
f += 1
|
||||||
|
return (struct.unpack('!b', x[f:f + 1])[0], f + 1)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_inth(x, f):
|
||||||
|
f += 1
|
||||||
|
return (struct.unpack('!h', x[f:f + 2])[0], f + 2)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_intl(x, f):
|
||||||
|
f += 1
|
||||||
|
|
||||||
|
return (struct.unpack('!l', x[f:f + 4])[0], f + 4)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_intq(x, f):
|
||||||
|
f += 1
|
||||||
|
return (struct.unpack('!q', x[f:f + 8])[0], f + 8)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_float32(x, f):
|
||||||
|
f += 1
|
||||||
|
n = struct.unpack('!f', x[f:f + 4])[0]
|
||||||
|
return (n, f + 4)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_float64(x, f):
|
||||||
|
f += 1
|
||||||
|
n = struct.unpack('!d', x[f:f + 8])[0]
|
||||||
|
return (n, f + 8)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_string(x, f):
|
||||||
|
colon = x.index(b':', f)
|
||||||
|
try:
|
||||||
|
n = int(x[f:colon])
|
||||||
|
except (OverflowError, ValueError):
|
||||||
|
n = long(x[f:colon])
|
||||||
|
if x[f] == '0' and colon != f + 1:
|
||||||
|
raise ValueError
|
||||||
|
colon += 1
|
||||||
|
s = x[colon:colon + n]
|
||||||
|
if _decode_utf8:
|
||||||
|
s = s.decode('utf8')
|
||||||
|
return (s, colon + n)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_list(x, f):
|
||||||
|
r, f = [], f + 1
|
||||||
|
while x[f:f + 1] != CHR_TERM:
|
||||||
|
v, f = decode_func[x[f:f + 1]](x, f)
|
||||||
|
r.append(v)
|
||||||
|
return (tuple(r), f + 1)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_dict(x, f):
|
||||||
|
r, f = {}, f + 1
|
||||||
|
while x[f:f + 1] != CHR_TERM:
|
||||||
|
k, f = decode_func[x[f:f + 1]](x, f)
|
||||||
|
r[k], f = decode_func[x[f:f + 1]](x, f)
|
||||||
|
return (r, f + 1)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_true(x, f):
|
||||||
|
return (True, f + 1)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_false(x, f):
|
||||||
|
return (False, f + 1)
|
||||||
|
|
||||||
|
|
||||||
|
def decode_none(x, f):
|
||||||
|
return (None, f + 1)
|
||||||
|
|
||||||
|
|
||||||
|
decode_func = {}
|
||||||
|
decode_func[b'0'] = decode_string
|
||||||
|
decode_func[b'1'] = decode_string
|
||||||
|
decode_func[b'2'] = decode_string
|
||||||
|
decode_func[b'3'] = decode_string
|
||||||
|
decode_func[b'4'] = decode_string
|
||||||
|
decode_func[b'5'] = decode_string
|
||||||
|
decode_func[b'6'] = decode_string
|
||||||
|
decode_func[b'7'] = decode_string
|
||||||
|
decode_func[b'8'] = decode_string
|
||||||
|
decode_func[b'9'] = decode_string
|
||||||
|
decode_func[CHR_LIST] = decode_list
|
||||||
|
decode_func[CHR_DICT] = decode_dict
|
||||||
|
decode_func[CHR_INT] = decode_int
|
||||||
|
decode_func[CHR_INT1] = decode_intb
|
||||||
|
decode_func[CHR_INT2] = decode_inth
|
||||||
|
decode_func[CHR_INT4] = decode_intl
|
||||||
|
decode_func[CHR_INT8] = decode_intq
|
||||||
|
decode_func[CHR_FLOAT32] = decode_float32
|
||||||
|
decode_func[CHR_FLOAT64] = decode_float64
|
||||||
|
decode_func[CHR_TRUE] = decode_true
|
||||||
|
decode_func[CHR_FALSE] = decode_false
|
||||||
|
decode_func[CHR_NONE] = decode_none
|
||||||
|
|
||||||
|
|
||||||
|
def make_fixed_length_string_decoders():
|
||||||
|
def make_decoder(slen):
|
||||||
|
def f(x, f):
|
||||||
|
s = x[f + 1:f + 1 + slen]
|
||||||
|
if _decode_utf8:
|
||||||
|
s = s.decode('utf8')
|
||||||
|
return (s, f + 1 + slen)
|
||||||
|
return f
|
||||||
|
for i in range(STR_FIXED_COUNT):
|
||||||
|
decode_func[int2byte(STR_FIXED_START + i)] = make_decoder(i)
|
||||||
|
|
||||||
|
|
||||||
|
make_fixed_length_string_decoders()
|
||||||
|
|
||||||
|
|
||||||
|
def make_fixed_length_list_decoders():
|
||||||
|
def make_decoder(slen):
|
||||||
|
def f(x, f):
|
||||||
|
r, f = [], f + 1
|
||||||
|
for _ in range(slen):
|
||||||
|
v, f = decode_func[x[f:f + 1]](x, f)
|
||||||
|
r.append(v)
|
||||||
|
return (tuple(r), f)
|
||||||
|
return f
|
||||||
|
for i in range(LIST_FIXED_COUNT):
|
||||||
|
decode_func[int2byte(LIST_FIXED_START + i)] = make_decoder(i)
|
||||||
|
|
||||||
|
|
||||||
|
make_fixed_length_list_decoders()
|
||||||
|
|
||||||
|
|
||||||
|
def make_fixed_length_int_decoders():
|
||||||
|
def make_decoder(j):
|
||||||
|
def f(x, f):
|
||||||
|
return (j, f + 1)
|
||||||
|
return f
|
||||||
|
for i in range(INT_POS_FIXED_COUNT):
|
||||||
|
decode_func[int2byte(INT_POS_FIXED_START + i)] = make_decoder(i)
|
||||||
|
for i in range(INT_NEG_FIXED_COUNT):
|
||||||
|
decode_func[int2byte(INT_NEG_FIXED_START + i)] = make_decoder(-1 - i)
|
||||||
|
|
||||||
|
|
||||||
|
make_fixed_length_int_decoders()
|
||||||
|
|
||||||
|
|
||||||
|
def make_fixed_length_dict_decoders():
|
||||||
|
def make_decoder(slen):
|
||||||
|
def f(x, f):
|
||||||
|
r, f = {}, f + 1
|
||||||
|
for _ in range(slen):
|
||||||
|
k, f = decode_func[x[f:f + 1]](x, f)
|
||||||
|
r[k], f = decode_func[x[f:f + 1]](x, f)
|
||||||
|
return (r, f)
|
||||||
|
return f
|
||||||
|
for i in range(DICT_FIXED_COUNT):
|
||||||
|
decode_func[int2byte(DICT_FIXED_START + i)] = make_decoder(i)
|
||||||
|
|
||||||
|
|
||||||
|
make_fixed_length_dict_decoders()
|
||||||
|
|
||||||
|
|
||||||
|
def loads(x, decode_utf8=False):
|
||||||
|
global _decode_utf8
|
||||||
|
_decode_utf8 = decode_utf8
|
||||||
|
try:
|
||||||
|
r, l = decode_func[x[0:1]](x, 0)
|
||||||
|
except (IndexError, KeyError):
|
||||||
|
raise ValueError
|
||||||
|
if l != len(x):
|
||||||
|
raise ValueError
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def encode_int(x, r):
|
||||||
|
if 0 <= x < INT_POS_FIXED_COUNT:
|
||||||
|
r.append(int2byte(INT_POS_FIXED_START + x))
|
||||||
|
elif -INT_NEG_FIXED_COUNT <= x < 0:
|
||||||
|
r.append(int2byte(INT_NEG_FIXED_START - 1 - x))
|
||||||
|
elif -128 <= x < 128:
|
||||||
|
r.extend((CHR_INT1, struct.pack('!b', x)))
|
||||||
|
elif -32768 <= x < 32768:
|
||||||
|
r.extend((CHR_INT2, struct.pack('!h', x)))
|
||||||
|
elif -2147483648 <= x < 2147483648:
|
||||||
|
r.extend((CHR_INT4, struct.pack('!l', x)))
|
||||||
|
elif -9223372036854775808 <= x < 9223372036854775808:
|
||||||
|
r.extend((CHR_INT8, struct.pack('!q', x)))
|
||||||
|
else:
|
||||||
|
s = str(x)
|
||||||
|
if py3:
|
||||||
|
s = bytes(s, 'ascii')
|
||||||
|
|
||||||
|
if len(s) >= MAX_INT_LENGTH:
|
||||||
|
raise ValueError('overflow')
|
||||||
|
r.extend((CHR_INT, s, CHR_TERM))
|
||||||
|
|
||||||
|
|
||||||
|
def encode_float32(x, r):
|
||||||
|
r.extend((CHR_FLOAT32, struct.pack('!f', x)))
|
||||||
|
|
||||||
|
|
||||||
|
def encode_float64(x, r):
|
||||||
|
r.extend((CHR_FLOAT64, struct.pack('!d', x)))
|
||||||
|
|
||||||
|
|
||||||
|
def encode_bool(x, r):
|
||||||
|
r.append({False: CHR_FALSE, True: CHR_TRUE}[bool(x)])
|
||||||
|
|
||||||
|
|
||||||
|
def encode_none(x, r):
|
||||||
|
r.append(CHR_NONE)
|
||||||
|
|
||||||
|
|
||||||
|
def encode_string(x, r):
|
||||||
|
if len(x) < STR_FIXED_COUNT:
|
||||||
|
r.extend((int2byte(STR_FIXED_START + len(x)), x))
|
||||||
|
else:
|
||||||
|
s = str(len(x))
|
||||||
|
if py3:
|
||||||
|
s = bytes(s, 'ascii')
|
||||||
|
r.extend((s, b':', x))
|
||||||
|
|
||||||
|
|
||||||
|
def encode_unicode(x, r):
|
||||||
|
encode_string(x.encode('utf8'), r)
|
||||||
|
|
||||||
|
|
||||||
|
def encode_list(x, r):
|
||||||
|
if len(x) < LIST_FIXED_COUNT:
|
||||||
|
r.append(int2byte(LIST_FIXED_START + len(x)))
|
||||||
|
for i in x:
|
||||||
|
encode_func[type(i)](i, r)
|
||||||
|
else:
|
||||||
|
r.append(CHR_LIST)
|
||||||
|
for i in x:
|
||||||
|
encode_func[type(i)](i, r)
|
||||||
|
r.append(CHR_TERM)
|
||||||
|
|
||||||
|
|
||||||
|
def encode_dict(x, r):
|
||||||
|
if len(x) < DICT_FIXED_COUNT:
|
||||||
|
r.append(int2byte(DICT_FIXED_START + len(x)))
|
||||||
|
for k, v in x.items():
|
||||||
|
encode_func[type(k)](k, r)
|
||||||
|
encode_func[type(v)](v, r)
|
||||||
|
else:
|
||||||
|
r.append(CHR_DICT)
|
||||||
|
for k, v in x.items():
|
||||||
|
encode_func[type(k)](k, r)
|
||||||
|
encode_func[type(v)](v, r)
|
||||||
|
r.append(CHR_TERM)
|
||||||
|
|
||||||
|
|
||||||
|
encode_func = {}
|
||||||
|
encode_func[int] = encode_int
|
||||||
|
encode_func[long] = encode_int
|
||||||
|
encode_func[bytes] = encode_string
|
||||||
|
encode_func[list] = encode_list
|
||||||
|
encode_func[tuple] = encode_list
|
||||||
|
encode_func[dict] = encode_dict
|
||||||
|
encode_func[type(None)] = encode_none
|
||||||
|
encode_func[unicode] = encode_unicode
|
||||||
|
encode_func[bool] = encode_bool
|
||||||
|
|
||||||
|
lock = Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def dumps(x, float_bits=DEFAULT_FLOAT_BITS):
|
||||||
|
"""
|
||||||
|
Dump data structure to str.
|
||||||
|
|
||||||
|
Here float_bits is either 32 or 64.
|
||||||
|
"""
|
||||||
|
with lock:
|
||||||
|
if float_bits == 32:
|
||||||
|
encode_func[float] = encode_float32
|
||||||
|
elif float_bits == 64:
|
||||||
|
encode_func[float] = encode_float64
|
||||||
|
else:
|
||||||
|
raise ValueError('Float bits (%d) is not 32 or 64' % float_bits)
|
||||||
|
r = []
|
||||||
|
encode_func[type(x)](x, r)
|
||||||
|
return b''.join(r)
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
f1 = struct.unpack('!f', struct.pack('!f', 25.5))[0]
|
||||||
|
f2 = struct.unpack('!f', struct.pack('!f', 29.3))[0]
|
||||||
|
f3 = struct.unpack('!f', struct.pack('!f', -0.6))[0]
|
||||||
|
ld = (({b'a': 15, b'bb': f1, b'ccc': f2, b'': (f3, (), False, True, b'')}, (b'a', 10**20),
|
||||||
|
tuple(range(-100000, 100000)), b'b' * 31, b'b' * 62, b'b' * 64, 2**30, 2**33, 2**62,
|
||||||
|
2**64, 2**30, 2**33, 2**62, 2**64, False, False, True, -1, 2, 0),)
|
||||||
|
assert loads(dumps(ld)) == ld
|
||||||
|
d = dict(zip(range(-100000, 100000), range(-100000, 100000)))
|
||||||
|
d.update({b'a': 20, 20: 40, 40: 41, f1: f2, f2: f3, f3: False, False: True, True: False})
|
||||||
|
ld = (d, {}, {5: 6}, {7: 7, True: 8}, {9: 10, 22: 39, 49: 50, 44: b''})
|
||||||
|
assert loads(dumps(ld)) == ld
|
||||||
|
ld = (b'', b'a' * 10, b'a' * 100, b'a' * 1000, b'a' * 10000, b'a' * 100000, b'a' * 1000000, b'a' * 10000000)
|
||||||
|
assert loads(dumps(ld)) == ld
|
||||||
|
ld = tuple([dict(zip(range(n), range(n))) for n in range(100)]) + (b'b',)
|
||||||
|
assert loads(dumps(ld)) == ld
|
||||||
|
ld = tuple([dict(zip(range(n), range(-n, 0))) for n in range(100)]) + (b'b',)
|
||||||
|
assert loads(dumps(ld)) == ld
|
||||||
|
ld = tuple([tuple(range(n)) for n in range(100)]) + (b'b',)
|
||||||
|
assert loads(dumps(ld)) == ld
|
||||||
|
ld = tuple([b'a' * n for n in range(1000)]) + (b'b',)
|
||||||
|
assert loads(dumps(ld)) == ld
|
||||||
|
ld = tuple([b'a' * n for n in range(1000)]) + (None, True, None)
|
||||||
|
assert loads(dumps(ld)) == ld
|
||||||
|
assert loads(dumps(None)) is None
|
||||||
|
assert loads(dumps({None: None})) == {None: None}
|
||||||
|
assert 1e-10 < abs(loads(dumps(1.1)) - 1.1) < 1e-6
|
||||||
|
assert 1e-10 < abs(loads(dumps(1.1, 32)) - 1.1) < 1e-6
|
||||||
|
assert abs(loads(dumps(1.1, 64)) - 1.1) < 1e-12
|
||||||
|
assert loads(dumps('Hello World!!'), decode_utf8=True)
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
import psyco
|
||||||
|
psyco.bind(dumps)
|
||||||
|
psyco.bind(loads)
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test()
|
65
libs/custom/deluge_client/tests.py
Normal file
65
libs/custom/deluge_client/tests.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from .client import DelugeRPCClient, RemoteException
|
||||||
|
|
||||||
|
|
||||||
|
if sys.version_info > (3,):
|
||||||
|
long = int
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client(request):
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
auth_path = os.path.join(os.getenv('APPDATA'), 'deluge', 'auth')
|
||||||
|
else:
|
||||||
|
auth_path = os.path.expanduser("~/.config/deluge/auth")
|
||||||
|
|
||||||
|
with open(auth_path, 'rb') as f:
|
||||||
|
filedata = f.read().decode("utf-8").split('\n')[0].split(':')
|
||||||
|
|
||||||
|
username, password = filedata[:2]
|
||||||
|
ip = '127.0.0.1'
|
||||||
|
port = 58846
|
||||||
|
kwargs = {'decode_utf8': True}
|
||||||
|
if hasattr(request, 'param'):
|
||||||
|
kwargs.update(request.param)
|
||||||
|
client = DelugeRPCClient(ip, port, username, password, **kwargs)
|
||||||
|
client.connect()
|
||||||
|
|
||||||
|
yield client
|
||||||
|
|
||||||
|
try:
|
||||||
|
client.disconnect()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_connect(client):
|
||||||
|
assert client.connected
|
||||||
|
|
||||||
|
|
||||||
|
def test_call_method(client):
|
||||||
|
assert isinstance(client.call('core.get_free_space'), (int, long))
|
||||||
|
|
||||||
|
|
||||||
|
def test_call_method_arguments(client):
|
||||||
|
assert isinstance(client.call('core.get_free_space', '/'), (int, long))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('client',
|
||||||
|
[{'decode_utf8': True}, {'decode_utf8': False}],
|
||||||
|
ids=['decode_utf8_on', 'decode_utf8_off'],
|
||||||
|
indirect=True)
|
||||||
|
def test_call_method_exception(client):
|
||||||
|
with pytest.raises(RemoteException) as ex_info:
|
||||||
|
client.call('core.get_free_space', '1', '2')
|
||||||
|
assert ('takes at most 2 arguments' in str(ex_info.value) or
|
||||||
|
'takes from 1 to 2 positional arguments' in str(ex_info.value)) # deluge 2.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_attr_caller(client):
|
||||||
|
assert isinstance(client.core.get_free_space(), (int, long))
|
||||||
|
assert isinstance(client.core.get_free_space('/'), (int, long))
|
|
@ -1,31 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
"""
|
|
||||||
A synchronous implementation of the Deluge RPC protocol.
|
|
||||||
|
|
||||||
Based on gevent-deluge by Christopher Rosell:
|
|
||||||
https://github.com/chrippa/gevent-deluge
|
|
||||||
|
|
||||||
Example usage:
|
|
||||||
|
|
||||||
from synchronousdeluge import DelgueClient
|
|
||||||
|
|
||||||
client = DelugeClient()
|
|
||||||
client.connect()
|
|
||||||
|
|
||||||
# Wait for value
|
|
||||||
download_location = client.core.get_config_value("download_location").get()
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import (
|
|
||||||
absolute_import,
|
|
||||||
division,
|
|
||||||
print_function,
|
|
||||||
unicode_literals,
|
|
||||||
)
|
|
||||||
|
|
||||||
from .exceptions import DelugeRPCError
|
|
||||||
|
|
||||||
|
|
||||||
__title__ = 'synchronous-deluge'
|
|
||||||
__version__ = '0.1'
|
|
||||||
__author__ = 'Christian Dale'
|
|
|
@ -1,172 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
|
|
||||||
from __future__ import (
|
|
||||||
absolute_import,
|
|
||||||
division,
|
|
||||||
print_function,
|
|
||||||
unicode_literals,
|
|
||||||
)
|
|
||||||
|
|
||||||
import os
|
|
||||||
import platform
|
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
from six.moves import map as imap
|
|
||||||
|
|
||||||
from .exceptions import DelugeRPCError
|
|
||||||
from .protocol import DelugeRPCRequest, DelugeRPCResponse
|
|
||||||
from .transfer import DelugeTransfer
|
|
||||||
|
|
||||||
__all__ = ['DelugeClient']
|
|
||||||
|
|
||||||
RPC_RESPONSE = 1
|
|
||||||
RPC_ERROR = 2
|
|
||||||
RPC_EVENT = 3
|
|
||||||
|
|
||||||
|
|
||||||
class DelugeClient(object):
|
|
||||||
def __init__(self):
|
|
||||||
"""Create a deluge client session."""
|
|
||||||
self.transfer = DelugeTransfer()
|
|
||||||
self.modules = []
|
|
||||||
self._request_counter = 0
|
|
||||||
|
|
||||||
def _get_local_auth(self):
|
|
||||||
username = password = ''
|
|
||||||
if platform.system() in ('Windows', 'Microsoft'):
|
|
||||||
app_data_path = os.environ.get('APPDATA')
|
|
||||||
if not app_data_path:
|
|
||||||
from six.moves import winreg
|
|
||||||
hkey = winreg.OpenKey(
|
|
||||||
winreg.HKEY_CURRENT_USER,
|
|
||||||
'Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders',
|
|
||||||
)
|
|
||||||
app_data_reg = winreg.QueryValueEx(hkey, 'AppData')
|
|
||||||
app_data_path = app_data_reg[0]
|
|
||||||
winreg.CloseKey(hkey)
|
|
||||||
|
|
||||||
auth_file = os.path.join(app_data_path, 'deluge', 'auth')
|
|
||||||
else:
|
|
||||||
from xdg.BaseDirectory import save_config_path
|
|
||||||
try:
|
|
||||||
auth_file = os.path.join(save_config_path('deluge'), 'auth')
|
|
||||||
except OSError:
|
|
||||||
return username, password
|
|
||||||
|
|
||||||
if os.path.exists(auth_file):
|
|
||||||
for line in open(auth_file):
|
|
||||||
if line.startswith('#'):
|
|
||||||
# This is a comment line
|
|
||||||
continue
|
|
||||||
line = line.strip()
|
|
||||||
try:
|
|
||||||
lsplit = line.split(':')
|
|
||||||
except Exception:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if len(lsplit) == 2:
|
|
||||||
username, password = lsplit
|
|
||||||
elif len(lsplit) == 3:
|
|
||||||
username, password, level = lsplit
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if username == 'localclient':
|
|
||||||
return username, password
|
|
||||||
|
|
||||||
return '', ''
|
|
||||||
|
|
||||||
def _create_module_method(self, module, method):
|
|
||||||
fullname = '{0}.{1}'.format(module, method)
|
|
||||||
|
|
||||||
def func(obj, *args, **kwargs):
|
|
||||||
return self.remote_call(fullname, *args, **kwargs)
|
|
||||||
|
|
||||||
func.__name__ = str(method)
|
|
||||||
|
|
||||||
return func
|
|
||||||
|
|
||||||
def _introspect(self):
|
|
||||||
def splitter(value):
|
|
||||||
return value.split('.')
|
|
||||||
|
|
||||||
self.modules = []
|
|
||||||
|
|
||||||
methods = self.remote_call('daemon.get_method_list').get()
|
|
||||||
methods = (x.decode() for x in methods)
|
|
||||||
methodmap = defaultdict(dict)
|
|
||||||
|
|
||||||
for module, method in imap(splitter, methods):
|
|
||||||
methodmap[module][method] = self._create_module_method(module, method)
|
|
||||||
|
|
||||||
for module, methods in methodmap.items():
|
|
||||||
clsname = 'DelugeModule{0}'.format(module.capitalize())
|
|
||||||
cls = type(str(clsname), (), methods)
|
|
||||||
setattr(self, str(module), cls())
|
|
||||||
self.modules.append(module)
|
|
||||||
|
|
||||||
def remote_call(self, method, *args, **kwargs):
|
|
||||||
req = DelugeRPCRequest(self._request_counter, method, *args, **kwargs)
|
|
||||||
message = next(self.transfer.send_request(req))
|
|
||||||
|
|
||||||
response = DelugeRPCResponse()
|
|
||||||
|
|
||||||
if not isinstance(message, tuple):
|
|
||||||
return
|
|
||||||
|
|
||||||
if len(message) < 3:
|
|
||||||
return
|
|
||||||
|
|
||||||
message_type = message[0]
|
|
||||||
|
|
||||||
# if message_type == RPC_EVENT:
|
|
||||||
# event = message[1]
|
|
||||||
# values = message[2]
|
|
||||||
#
|
|
||||||
# if event in self._event_handlers:
|
|
||||||
# for handler in self._event_handlers[event]:
|
|
||||||
# gevent.spawn(handler, *values)
|
|
||||||
#
|
|
||||||
# elif message_type in (RPC_RESPONSE, RPC_ERROR):
|
|
||||||
if message_type in (RPC_RESPONSE, RPC_ERROR):
|
|
||||||
request_id = message[1]
|
|
||||||
value = message[2]
|
|
||||||
|
|
||||||
if request_id == self._request_counter:
|
|
||||||
if message_type == RPC_RESPONSE:
|
|
||||||
response.set(value)
|
|
||||||
elif message_type == RPC_ERROR:
|
|
||||||
err = DelugeRPCError(*value)
|
|
||||||
response.set_exception(err)
|
|
||||||
|
|
||||||
self._request_counter += 1
|
|
||||||
return response
|
|
||||||
|
|
||||||
def connect(self, host='127.0.0.1', port=58846, username='', password=''):
|
|
||||||
"""Connect to a daemon process.
|
|
||||||
|
|
||||||
:param host: str, the hostname of the daemon
|
|
||||||
:param port: int, the port of the daemon
|
|
||||||
:param username: str, the username to login with
|
|
||||||
:param password: str, the password to login with
|
|
||||||
"""
|
|
||||||
# Connect transport
|
|
||||||
self.transfer.connect((host, port))
|
|
||||||
|
|
||||||
# Attempt to fetch local auth info if needed
|
|
||||||
if not username and host in ('127.0.0.1', 'localhost'):
|
|
||||||
username, password = self._get_local_auth()
|
|
||||||
|
|
||||||
# Authenticate
|
|
||||||
self.remote_call('daemon.login', username, password).get()
|
|
||||||
|
|
||||||
# Introspect available methods
|
|
||||||
self._introspect()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def connected(self):
|
|
||||||
return self.transfer.connected
|
|
||||||
|
|
||||||
def disconnect(self):
|
|
||||||
"""Disconnects from the daemon."""
|
|
||||||
self.transfer.disconnect()
|
|
|
@ -1,18 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
|
|
||||||
from __future__ import (
|
|
||||||
absolute_import,
|
|
||||||
division,
|
|
||||||
print_function,
|
|
||||||
unicode_literals,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DelugeRPCError(Exception):
|
|
||||||
def __init__(self, name, msg, traceback):
|
|
||||||
self.name = name
|
|
||||||
self.msg = msg
|
|
||||||
self.traceback = traceback
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '{0}: {1}: {2}'.format(self.__class__.__name__, self.name, self.msg)
|
|
|
@ -1,46 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
|
|
||||||
from __future__ import (
|
|
||||||
absolute_import,
|
|
||||||
division,
|
|
||||||
print_function,
|
|
||||||
unicode_literals,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DelugeRPCRequest(object):
|
|
||||||
def __init__(self, request_id, method, *args, **kwargs):
|
|
||||||
self.request_id = request_id
|
|
||||||
self.method = method
|
|
||||||
self.args = args
|
|
||||||
self.kwargs = kwargs
|
|
||||||
|
|
||||||
def format(self):
|
|
||||||
return self.request_id, self.method, self.args, self.kwargs
|
|
||||||
|
|
||||||
|
|
||||||
class DelugeRPCResponse(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.value = None
|
|
||||||
self._exception = None
|
|
||||||
|
|
||||||
def successful(self):
|
|
||||||
return self._exception is None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def exception(self):
|
|
||||||
if self._exception is not None:
|
|
||||||
return self._exception
|
|
||||||
|
|
||||||
def set(self, value=None):
|
|
||||||
self.value = value
|
|
||||||
self._exception = None
|
|
||||||
|
|
||||||
def set_exception(self, exception):
|
|
||||||
self._exception = exception
|
|
||||||
|
|
||||||
def get(self):
|
|
||||||
if self._exception is None:
|
|
||||||
return self.value
|
|
||||||
else:
|
|
||||||
raise self._exception
|
|
|
@ -1,64 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
|
|
||||||
from __future__ import (
|
|
||||||
absolute_import,
|
|
||||||
division,
|
|
||||||
print_function,
|
|
||||||
unicode_literals,
|
|
||||||
)
|
|
||||||
|
|
||||||
import socket
|
|
||||||
import ssl
|
|
||||||
import struct
|
|
||||||
import zlib
|
|
||||||
|
|
||||||
import rencode
|
|
||||||
|
|
||||||
__all__ = ['DelugeTransfer']
|
|
||||||
|
|
||||||
|
|
||||||
class DelugeTransfer(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.sock = None
|
|
||||||
self.conn = None
|
|
||||||
self.connected = False
|
|
||||||
|
|
||||||
def connect(self, hostport):
|
|
||||||
if self.connected:
|
|
||||||
self.disconnect()
|
|
||||||
|
|
||||||
self.sock = socket.create_connection(hostport)
|
|
||||||
self.conn = ssl.wrap_socket(self.sock, None, None, False, ssl.CERT_NONE, ssl.PROTOCOL_TLSv1)
|
|
||||||
self.connected = True
|
|
||||||
|
|
||||||
def disconnect(self):
|
|
||||||
if self.conn:
|
|
||||||
self.conn.close()
|
|
||||||
self.connected = False
|
|
||||||
|
|
||||||
def send_request(self, request):
|
|
||||||
data = (request.format(),)
|
|
||||||
payload = zlib.compress(rencode.dumps(data))
|
|
||||||
self.conn.sendall(payload)
|
|
||||||
|
|
||||||
buf = b''
|
|
||||||
|
|
||||||
while True:
|
|
||||||
data = self.conn.recv(1024)
|
|
||||||
|
|
||||||
if not data:
|
|
||||||
self.connected = False
|
|
||||||
break
|
|
||||||
|
|
||||||
buf += data
|
|
||||||
dobj = zlib.decompressobj()
|
|
||||||
|
|
||||||
try:
|
|
||||||
message = rencode.loads(dobj.decompress(buf))
|
|
||||||
except (ValueError, zlib.error, struct.error):
|
|
||||||
# Probably incomplete data, read more
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
buf = dobj.unused_data
|
|
||||||
|
|
||||||
yield message
|
|
|
@ -1,2 +1,2 @@
|
||||||
synchronousdeluge
|
deluge_client
|
||||||
utorrent
|
utorrent
|
||||||
|
|
2
tox.ini
2
tox.ini
|
@ -90,7 +90,7 @@ per-file-ignores =
|
||||||
core/utils/__init__.py: F401
|
core/utils/__init__.py: F401
|
||||||
core/plugins/downloaders/configuration.py: F401
|
core/plugins/downloaders/configuration.py: F401
|
||||||
core/plugins/downloaders/utils.py: F401
|
core/plugins/downloaders/utils.py: F401
|
||||||
libs/custom/synchronousdeluge/__init__.py: F401
|
libs/custom/deluge_client/__init__.py: F401
|
||||||
|
|
||||||
[testenv:check]
|
[testenv:check]
|
||||||
deps =
|
deps =
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue