summaryrefslogtreecommitdiffstats
path: root/deluge/transfer.py
diff options
context:
space:
mode:
authorbendikro <bendikro@gmail.com>2012-06-18 20:28:32 +0200
committerbendikro <bendikro@gmail.com>2012-07-02 02:09:14 +0200
commit8e7432e71c80a47c96bf48c07535ec1991efe1d9 (patch)
tree1acd39b2ac5b90cab88f3ed471ce2b827716efd5 /deluge/transfer.py
parent5dc6dbf216f1e02992761d7ad10cc911b5c416e0 (diff)
downloaddeluge-8e7432e71c80a47c96bf48c07535ec1991efe1d9.tar.gz
deluge-8e7432e71c80a47c96bf48c07535ec1991efe1d9.tar.bz2
deluge-8e7432e71c80a47c96bf48c07535ec1991efe1d9.zip
Added a protocol for the network traffic between client and daemon.
Implemented a protocol layer above twisted.internet.protocol.Protocol which guarantees correct transfer of RPC messages. The network messages are transfered with a header containing the length of the message.
Diffstat (limited to 'deluge/transfer.py')
-rw-r--r--deluge/transfer.py170
1 files changed, 170 insertions, 0 deletions
diff --git a/deluge/transfer.py b/deluge/transfer.py
new file mode 100644
index 000000000..c89f89e4f
--- /dev/null
+++ b/deluge/transfer.py
@@ -0,0 +1,170 @@
+# -*- coding: utf-8 -*-
+#
+# transfer.py
+#
+# Copyright (C) 2012 Bro <bro.development@gmail.com>
+#
+# Deluge is free software.
+#
+# You may redistribute it and/or modify it under the terms of the
+# GNU General Public License, as published by the Free Software
+# Foundation; either version 3 of the License, or (at your option)
+# any later version.
+#
+# deluge is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+# See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with deluge. If not, write to:
+# The Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor
+# Boston, MA 02110-1301, USA.
+#
+# In addition, as a special exception, the copyright holders give
+# permission to link the code of portions of this program with the OpenSSL
+# library.
+# You must obey the GNU General Public License in all respects for all of
+# the code used other than OpenSSL. If you modify file(s) with this
+# exception, you may extend this exception to your version of the file(s),
+# but you are not obligated to do so. If you do not wish to do so, delete
+# this exception statement from your version. If you delete this exception
+# statement from all source files in the program, then also delete it here.
+#
+#
+
+try:
+ import rencode
+except ImportError:
+ import deluge.rencode as rencode
+
+import zlib
+import struct
+
+from twisted.internet.protocol import Protocol
+
+from deluge.log import LOG as log
+
+MESSAGE_HEADER_SIZE = 5
+
+class DelugeTransferProtocol(Protocol):
+ """
+ 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 = ""
+ 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 = "D" + payload_len
+ self._bytes_sent += len(header) + len(compressed)
+ self.transport.write(header)
+ self.transport.write(compressed)
+
+ def dataReceived(self, data):
+ """
+ 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] != 'D':
+ raise Exception("Invalid header format. First byte is %d" % ord(header[0]))
+ # 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, e:
+ log.warn("Error occured when parsing message header: %s." % str(e))
+ log.warn("This version of Deluge cannot communicate with the sender of this data.")
+ self._message_length = 0
+ self._buffer = ""
+
+ 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)))
+ except Exception, e:
+ log.warn("Failed to decompress (%d bytes) and load serialized data "\
+ "with rencode: %s" % (len(data), str(e)))
+
+ 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