Introduce basic (not-yet-compressing) implementation of Compressor
authorManuel Nickschas <sputnick@quassel-irc.org>
Sun, 16 Feb 2014 21:22:52 +0000 (22:22 +0100)
committerManuel Nickschas <sputnick@quassel-irc.org>
Sun, 16 Feb 2014 21:22:52 +0000 (22:22 +0100)
This class is intended to encapsulate the streaming compression between
the socket and the rest of RemotePeer; however, actual compression is
not yet implemented.

The Compressor class sits on top of the socket, providing a rudimentary API
for reading and writing data, and will transparently handle the (de)compression
once it's finished.

src/common/CMakeLists.txt
src/common/compressor.cpp [new file with mode: 0644]
src/common/compressor.h [new file with mode: 0644]

index 9eeb91c..f1dc2f9 100644 (file)
@@ -12,6 +12,7 @@ set(SOURCES
     bufferviewconfig.cpp
     bufferviewmanager.cpp
     cliparser.cpp
     bufferviewconfig.cpp
     bufferviewmanager.cpp
     cliparser.cpp
+    compressor.cpp
     coreinfo.cpp
     ctcpevent.cpp
     event.cpp
     coreinfo.cpp
     ctcpevent.cpp
     event.cpp
@@ -52,6 +53,7 @@ set(MOC_HDRS
     buffersyncer.h
     bufferviewconfig.h
     bufferviewmanager.h
     buffersyncer.h
     bufferviewconfig.h
     bufferviewmanager.h
+    compressor.h
     coreinfo.h
     eventmanager.h
     identity.h
     coreinfo.h
     eventmanager.h
     identity.h
diff --git a/src/common/compressor.cpp b/src/common/compressor.cpp
new file mode 100644 (file)
index 0000000..9ee877c
--- /dev/null
@@ -0,0 +1,118 @@
+/***************************************************************************
+ *   Copyright (C) 2005-2014 by the Quassel Project                        *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program 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 this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
+ ***************************************************************************/
+
+#include "compressor.h"
+
+#include <QTcpSocket>
+#include <QTimer>
+
+const qint64 maxBufferSize = 64 * 1024 * 1024; // protect us from zip bombs
+
+Compressor::Compressor(QTcpSocket *socket, Compressor::CompressionLevel level, QObject *parent)
+    : QObject(parent),
+    _socket(socket),
+    _level(level)
+{
+    _level = NoCompression; // compression not implemented yet
+
+    connect(socket, SIGNAL(readyRead()), SLOT(readData()));
+
+    // It's possible that more data has already arrived during the handshake, so readyRead() wouldn't be triggered.
+    // However, we want to give RemotePeer a chance to connect to our signals, so trigger this asynchronously.
+    if (socket->bytesAvailable())
+        QTimer::singleShot(0, this, SLOT(readData()));
+}
+
+
+qint64 Compressor::bytesAvailable() const
+{
+    return _readBuffer.size();
+}
+
+
+qint64 Compressor::read(char *data, qint64 maxSize)
+{
+    if (maxSize <= 0)
+        maxSize = _readBuffer.size();
+
+    qint64 n = qMin(maxSize, (qint64)_readBuffer.size());
+    memcpy(data, _readBuffer.constData(), n);
+
+    // TODO: don't copy for every read
+    if (n == _readBuffer.size())
+        _readBuffer.clear();
+    else
+        _readBuffer = _readBuffer.mid(n);
+
+    // If there's still data left in the socket buffer, make sure to schedule a read
+    if (_socket->bytesAvailable())
+        QTimer::singleShot(0, this, SLOT(readData()));
+
+    return n;
+}
+
+
+// The usual usage pattern is to write a blocksize first, followed by the actual data.
+// By setting NoFlush, one can indicate that the write buffer should not immediately be
+// written, which should make things a bit more efficient.
+qint64 Compressor::write(const char *data, qint64 count, WriteBufferHint flush)
+{
+    int pos = _writeBuffer.size();
+    _writeBuffer.resize(pos + count);
+    memcpy(_writeBuffer.data() + pos, data, count);
+
+    if (flush != NoFlush)
+        writeData();
+
+    return count;
+}
+
+
+void Compressor::readData()
+{
+    // don't try to read more data if we're already closing
+    if (_socket->state() !=  QAbstractSocket::ConnectedState)
+        return;
+
+    if (!_socket->bytesAvailable() || _readBuffer.size() >= maxBufferSize)
+        return;
+
+    if (compressionLevel() == NoCompression)
+        _readBuffer.append(_socket->read(maxBufferSize - _readBuffer.size()));
+
+    emit readyRead();
+}
+
+
+void Compressor::writeData()
+{
+    if (compressionLevel() == NoCompression) {
+        _socket->write(_writeBuffer);
+        _writeBuffer.clear();
+    }
+}
+
+
+void Compressor::flush()
+{
+    if (compressionLevel() == NoCompression && _socket->state() == QAbstractSocket::ConnectedState)
+        _socket->flush();
+
+}
diff --git a/src/common/compressor.h b/src/common/compressor.h
new file mode 100644 (file)
index 0000000..b8b9e71
--- /dev/null
@@ -0,0 +1,80 @@
+/***************************************************************************
+ *   Copyright (C) 2005-2014 by the Quassel Project                        *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program 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 this program; if not, write to the                         *
+ *   Free Software Foundation, Inc.,                                       *
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
+ ***************************************************************************/
+
+#ifndef COMPRESSOR_H
+#define COMPRESSOR_H
+
+#include <QObject>
+
+class QTcpSocket;
+
+class Compressor : public QObject
+{
+    Q_OBJECT
+
+public:
+    enum CompressionLevel {
+        NoCompression,
+        DefaultCompression,
+        BestCompression,
+        BestSpeed
+    };
+
+    enum Error {
+        NoError,
+        StreamError,
+        DeviceError
+    };
+
+    enum WriteBufferHint {
+        NoFlush,
+        Flush
+    };
+
+    Compressor(QTcpSocket *socket, CompressionLevel level, QObject *parent = 0);
+
+    CompressionLevel compressionLevel() const { return _level; }
+
+    qint64 bytesAvailable() const;
+
+    qint64 read(char *data, qint64 maxSize);
+    qint64 write(const char *data, qint64 count, WriteBufferHint flush = Flush);
+
+    void flush();
+
+signals:
+    void readyRead();
+    void error(Compressor::Error errorCode);
+
+private slots:
+    void readData();
+
+private:
+    void writeData();
+
+private:
+    QTcpSocket *_socket;
+    CompressionLevel _level;
+
+    QByteArray _readBuffer;
+    QByteArray _writeBuffer;
+};
+
+#endif