mirror of
https://github.com/logos-storage/deluge.git
synced 2026-01-10 00:53:07 +00:00
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.
158 lines
5.2 KiB
Python
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
|