deluge/deluge/transfer.py
Calum Lind b1cdc32f73 [Lint] Use Black to auto-format code
The move to using auto-formatter makes it easier to read, submit and
speeds up development time. https://github.com/ambv/black/

Although I would prefer 79 chars, the default line length of 88 chars
used by black suffices. The flake8 line length remains at 120 chars
since black does not touch comments or docstrings and this will require
another round of fixes.

The only black setting that is not standard is the use of double-quotes
for strings so disabled any formatting of these. Note however that
flake8 will still flag usage of double-quotes. I may change my mind on
double vs single quotes but for now leave them.

A new pyproject.toml file has been created for black configuration.
2018-10-03 15:21:53 +01:00

158 lines
5.2 KiB
Python

# -*- coding: utf-8 -*-
#
# Copyright (C) 2012 Bro <bro.development@gmail.com>
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
from __future__ import unicode_literals
import logging
import struct
import zlib
from twisted.internet.protocol import Protocol
try:
import rencode # pylint: disable=useless-suppression,relative-import
except ImportError:
import deluge.rencode as rencode
log = logging.getLogger(__name__)
MESSAGE_HEADER_SIZE = 5
class DelugeTransferProtocol(Protocol, object):
"""
Data messages are transfered using very a simple protocol.
Data messages are transfered with a header containing
the length of the data to be transfered (payload).
"""
def __init__(self):
self._buffer = b'' # TODO: Look into using bytearray instead of byte string.
self._message_length = 0
self._bytes_received = 0
self._bytes_sent = 0
def transfer_message(self, data):
"""
Transfer the data.
The data will be serialized and compressed before being sent.
First a header is sent - containing the length of the compressed payload
to come as a signed integer. After the header, the payload is transfered.
:param data: data to be transfered in a data structure serializable by rencode.
"""
compressed = zlib.compress(rencode.dumps(data))
size_data = len(compressed)
# Store length as a signed integer (using 4 bytes). "!" denotes network byte order.
payload_len = struct.pack('!i', size_data)
header = b'D' + payload_len
self._bytes_sent += len(header) + len(compressed)
self.transport.write(header)
self.transport.write(compressed)
def dataReceived(self, data): # NOQA: N802
"""
This method is called whenever data is received.
:param data: a message as transfered by transfer_message, or a part of such
a messsage.
Global variables:
_buffer - contains the data received
_message_length - the length of the payload of the current message.
"""
self._buffer += data
self._bytes_received += len(data)
while len(self._buffer) >= MESSAGE_HEADER_SIZE:
if self._message_length == 0:
self._handle_new_message()
# We have a complete packet
if len(self._buffer) >= self._message_length:
self._handle_complete_message(self._buffer[: self._message_length])
# Remove message data from buffer
self._buffer = self._buffer[self._message_length :]
self._message_length = 0
else:
break
def _handle_new_message(self):
"""
Handle the start of a new message. This method is called only when the
beginning of the buffer contains data from a new message (i.e. the header).
"""
try:
# Read the first bytes of the message (MESSAGE_HEADER_SIZE bytes)
header = self._buffer[:MESSAGE_HEADER_SIZE]
payload_len = header[1:MESSAGE_HEADER_SIZE]
if header[0:1] != b'D':
raise Exception(
'Invalid header format. First byte is %d' % ord(header[0:1])
)
# Extract the length stored as a signed integer (using 4 bytes)
self._message_length = struct.unpack('!i', payload_len)[0]
if self._message_length < 0:
raise Exception('Message length is negative: %d' % self._message_length)
# Remove the header from the buffer
self._buffer = self._buffer[MESSAGE_HEADER_SIZE:]
except Exception as ex:
log.warning('Error occurred when parsing message header: %s.', ex)
log.warning(
'This version of Deluge cannot communicate with the sender of this data.'
)
self._message_length = 0
self._buffer = b''
def _handle_complete_message(self, data):
"""
Handles a complete message as it is transfered on the network.
:param data: a zlib compressed string encoded with rencode.
"""
try:
self.message_received(
rencode.loads(zlib.decompress(data), decode_utf8=True)
)
except Exception as ex:
log.warning(
'Failed to decompress (%d bytes) and load serialized data with rencode: %s',
len(data),
ex,
)
def get_bytes_recv(self):
"""
Returns the number of bytes received.
:returns: the number of bytes received
:rtype: int
"""
return self._bytes_received
def get_bytes_sent(self):
"""
Returns the number of bytes sent.
:returns: the number of bytes sent
:rtype: int
"""
return self._bytes_sent
def message_received(self, message):
"""Override this method to receive the complete message"""
pass