#include #include #include #include #include #include #include #include #ifdef Q_OS_UNIX #include #endif namespace httq { DataStream::DataStream(QIODevice *from, QIODevice *to, const QByteArray &bodyPartial, qint64 fileLength, size_t bufferSize, LoggerFactory *loggerFactory, QObject *parent) : QObject(parent) , mBodyPartial(bodyPartial) , mFrom(from) , mTo(to) , mBufferSize(bufferSize) , mFileLength(fileLength) , mLogger(loggerFactory->createLogger(this)) { //#define SAVE_PARTIAL_BODY #ifdef SAVE_PARTIAL_BODY { QTemporaryFile f; f.open(); f.write(mBodyPartial); f.setAutoRemove(false); qWarning() << "partial:" << f.fileName(); } #endif if (mFileLength == 0) // basically, we are done here { mLogger->warning(QStringLiteral("file size = 0 - done")); QTimer::singleShot(1, this, [this]() { emit signalDone(); deleteLater(); } ); // needs to be queued, so the developer has enough time to connect this signal to a slot first return; } mLogger->debug(QStringLiteral("DataStream c_tor")); // TODO: timeout! mBuffer.reserve(bufferSize); // from QFile manpage: "Unlike other QIODevice implementations, such as QTcpSocket, QFile does not emit the aboutToClose(), bytesWritten(), or readyRead() signals." mFromSupportsSignals = (qobject_cast(mFrom) == nullptr); mToSupportsSignals = (qobject_cast(mTo) == nullptr); mLogger->debug(QStringLiteral("signal support - from: %1 - to: %2").arg(mFromSupportsSignals).arg(mToSupportsSignals)); //if (mFromSupportsSignals) //connect(mFrom, &QIODevice::readyRead, auto sock = qobject_cast(mFrom); qWarning() << "######################### SOCK" << sock; connect(sock, &QTcpSocket::readyRead, this, [this]() { mLogger->debug(QStringLiteral(">> DataStream readyRead")); //mReadyRead = true; QTimer::singleShot(1, this, [this]() { readNext(); } ); }); if (mToSupportsSignals) connect(mTo, &QIODevice::bytesWritten, this, [this]() { mLogger->debug(QStringLiteral(">> DataStream write")); mReadyWrite = true; QTimer::singleShot(1, this, [this]() { readNext(); } ); }); QTimer::singleShot(1, this, [this]() { readNext(); } ); } DataStream::~DataStream() { mLogger->debug(QStringLiteral("~DataStream")); } void DataStream::readNext() { #if defined(DEBUG_SLOW) && defined(Q_OS_UNIX) mLogger->debug(QStringLiteral("read: slowing down.....")); usleep(2 * 1000 * 1000); #endif mLogger->debug(QStringLiteral("read: readNext: %1").arg(mBuffer.size())); if (mBuffer.size() > 0) { mLogger->debug(QStringLiteral("read: ignoring read until buffer was written")); return; } if (mToSupportsSignals && !mReadyWrite) { mLogger->debug(QStringLiteral("read: won't read until write is ready!")); return; } if (!mBodyPartial.isEmpty()) // don't read from device, but read from data that was already read when the connection was established { mBuffer = mBodyPartial; // qWarning() << "mBuffer content (partial):")); // for (int i = 0; (i < mBuffer.size() && i < 100); i++) // qWarning() << Qt::hex << mBuffer[i]; alreadyRead += mBuffer.size(); mLogger->debug(QStringLiteral("read: prebody size: %1").arg(mBuffer.size())); mBodyPartial.clear(); //mReadyWrite = true; mLogger->debug(QStringLiteral("read: queueing writeNext after prebody read")); QTimer::singleShot(1, this, [this]() { /*qWarning() << "triggering writeNext and hoping that writeReady is true!!"));*/ writeNext(); } ); return; } if (/*!mFrom->isOpen() || !mFrom->isReadable() || */ (mFrom->bytesAvailable() < 1 /* && !mReadyRead*/)) { // qWarning() << "nothing to read: bytes available" << mFrom->bytesAvailable() << "ready read" << mReadyRead; // qWarning() << "try reading anyways..." << mFrom->read(3000); mLogger->debug(QStringLiteral("read: no bytes available and not ready read")); // XXX: TODO: FIXME: readyRead never arrives.... we need to have this extremely ugly workaround .... :'( QTimer::singleShot(1000, this, [this]() { readNext(); } ); // try again........ TEST return; } // if (mFromSupportsSignals) // mReadyRead = false; mBuffer = mFrom->read(mBufferSize); mLogger->debug(QStringLiteral("read: mPos is now %1").arg(mBuffer.size())); // qWarning() << "read" << mPos << "bytes")); alreadyRead += mBuffer.size(); mLogger->debug(QStringLiteral("read: alreadyRead = %1").arg(alreadyRead)); if (mBuffer.size() < 1) { mLogger->debug(QStringLiteral("read: cannot read anything")); deleteLater(); return; } mLogger->debug(QStringLiteral("read: queueing writeNext")); QTimer::singleShot(1, this, [this]() { writeNext(); } ); } void DataStream::writeNext() { mLogger->debug(QStringLiteral("write: writeNext: %1").arg(mBuffer.size())); if (mBuffer.size() < 1) { mLogger->debug(QStringLiteral("write: nothing to write")); QTimer::singleShot(1, this, [this]() { readNext(); } ); return; } // TODO: check isOpen & isWritable maybe in c_tor? if (!mTo->isOpen() || !mTo->isWritable()) { mLogger->debug(QStringLiteral("write: file error: open? %1 - writable? %2").arg(mTo->isOpen()).arg(mTo->isWritable())); deleteLater(); return; } if (mToSupportsSignals && !mReadyWrite /* wait until bytesWritten signal calls writeNext, but only if bytesWritten is supported, that is, when device is buffered */) { mLogger->debug(QStringLiteral("write: !mReadyWrite")); //readNext(); return; } if (mToSupportsSignals) mReadyWrite = false; const qint64 size = mTo->write(mBuffer); mLogger->debug(QStringLiteral("write: wrote to buffer %1 bytes").arg(size)); //QString::fromUtf8(mBuffer.constData(), mPos); mLogger->debug(QStringLiteral("write: %1 / %2 bytes done (%3 bytes left until DataStream done)").arg(alreadyRead).arg(mFileLength).arg(mFileLength - alreadyRead)); if (size < mBuffer.size()) { mLogger->warning(QStringLiteral("write: cannot write all bytes: %1 / %2 bytes").arg(mBuffer.size()).arg(size)); deleteLater(); return; } mLogger->debug(QStringLiteral("write: mPos is now %1 and was set to zero now!!").arg(mBuffer.size())); mBuffer.clear(); if (alreadyRead == mFileLength) // done { mLogger->debug(QStringLiteral("write: DONE!")); emit signalDone(); deleteLater(); return; } if (alreadyRead > mFileLength) { mLogger->debug(QStringLiteral("write: oops... we read too much!! data may be corrupt!! already read %1 bytes, but content length is only %2 (%1 / %2)").arg(alreadyRead).arg(mFileLength)); emit signalDone(); // TODO: emit error?? deleteLater(); return; } // give the buffer a chance to emit a bytesWritten signal before trying to read again if (mToSupportsSignals) mLogger->debug(QStringLiteral("write: waiting for bytes written to be triggered...")); mLogger->debug(QStringLiteral("write: queueing writeNext again")); QTimer::singleShot(1, this, [this]() { writeNext(); } ); } }