Revision control

++ // Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB).
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "aspectcommanddebugger_p.h"
++
++ #include <QtNetwork/QTcpSocket>
++ #include <QtCore/QJsonDocument>
++ #include <QtCore/QJsonObject>
++
++ #include <Qt3DCore/private/qabstractaspect_p.h>
++ #include <Qt3DCore/private/qsysteminformationservice_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace Qt3DCore {
++
++ namespace Debug {
++
++ namespace {
++
++ const qint32 MagicNumber = 0x454;
++
++ struct CommandHeader
++ {
++ qint32 magic;
++ qint32 size;
++ };
++
++ } // anonymous
++
++ void AspectCommandDebugger::ReadBuffer::insert(const QByteArray &array)
++ {
++ buffer.insert(endIdx, array);
++ endIdx += array.size();
++ }
++
++ void AspectCommandDebugger::ReadBuffer::trim()
++ {
++ if (startIdx != endIdx && startIdx != 0) {
++ memmove(buffer.data(),
++ buffer.constData() + startIdx,
++ size());
++ endIdx -= startIdx;
++ startIdx = 0;
++ }
++ }
++
++ AspectCommandDebugger::AspectCommandDebugger(QSystemInformationService *parent)
++ : QTcpServer(parent)
++ , m_service(parent)
++ {
++ }
++
++ void AspectCommandDebugger::initialize()
++ {
++ QObject::connect(this, &QTcpServer::newConnection, this, [this] {
++ QTcpSocket *socket = nextPendingConnection();
++ m_connections.push_back(socket);
++
++ QObject::connect(socket, &QTcpSocket::disconnected, this, [this, socket] {
++ m_connections.removeOne(socket);
++ // Destroy object to make sure all QObject connection are removed
++ socket->deleteLater();
++ });
++
++ QObject::connect(socket, &QTcpSocket::readyRead, this, [this, socket] {
++ onCommandReceived(socket);
++ });
++ });
++ const bool listening = listen(QHostAddress::Any, 8883);
++ if (!listening)
++ qWarning() << Q_FUNC_INFO << "failed to listen on port 8883";
++ }
++
++ void AspectCommandDebugger::asynchronousReplyFinished(AsynchronousCommandReply *reply)
++ {
++ Q_ASSERT(reply->isFinished());
++ QTcpSocket *socket = m_asyncCommandToSocketEntries.take(reply);
++ if (m_connections.contains(socket)) {
++ QJsonObject replyObj;
++ replyObj.insert(QLatin1String("command"), QJsonValue(reply->commandName()));
++ replyObj.insert(QLatin1String("data"), QJsonDocument::fromJson(reply->data()).object());
++ sendReply(socket, QJsonDocument(replyObj).toJson());
++ }
++ reply->deleteLater();
++ }
++
++
++ // Expects to receive commands in the form
++ // CommandHeader { MagicNumber; size }
++ // JSON {
++ // command: "commandName"
++ // data: JSON Obj
++ // }
++ void AspectCommandDebugger::onCommandReceived(QTcpSocket *socket)
++ {
++ const QByteArray newData = socket->readAll();
++ m_readBuffer.insert(newData);
++
++ const int commandPacketSize = sizeof(CommandHeader);
++ while (m_readBuffer.size() >= commandPacketSize) {
++ CommandHeader *header = reinterpret_cast<CommandHeader *>(m_readBuffer.buffer.data() + m_readBuffer.startIdx);
++ if (header->magic == MagicNumber) {
++ // Early return, header is valid but we haven't yet received all the data
++ if ((m_readBuffer.size() - commandPacketSize) < header->size)
++ return;
++ // We have a valid command
++ // We expect command to be a CommandHeader + some json text
++ const QJsonDocument doc = QJsonDocument::fromJson(
++ QByteArray(m_readBuffer.buffer.data() + m_readBuffer.startIdx + commandPacketSize,
++ header->size));
++
++ if (!doc.isNull()) {
++ // Send command to the aspectEngine
++ QJsonObject commandObj = doc.object();
++ const QJsonValue commandNameValue = commandObj.value(QLatin1String("command"));
++ executeCommand(commandNameValue.toString(), socket);
++ }
++
++ m_readBuffer.startIdx += commandPacketSize + header->size;
++ }
++ }
++ // Copy remaining length of buffer at begininning if we have read some commands
++ // and some partial one remain
++ m_readBuffer.trim();
++ }
++
++ void AspectCommandDebugger::sendReply(QTcpSocket *socket, const QByteArray &payload)
++ {
++ CommandHeader replyHeader;
++
++ replyHeader.magic = MagicNumber;
++ replyHeader.size = payload.size();
++ // Write header
++ socket->write(reinterpret_cast<const char *>(&replyHeader), sizeof(CommandHeader));
++ // Write payload
++ socket->write(payload.constData(), payload.size());
++ }
++
++ void AspectCommandDebugger::executeCommand(const QString &command,
++ QTcpSocket *socket)
++ {
++ // Only a single aspect is going to reply
++ const QVariant response = m_service->executeCommand(command);
++ if (response.userType() == qMetaTypeId<AsynchronousCommandReply *>()) { // AsynchronousCommand
++ // Store the command | socket in a table
++ AsynchronousCommandReply *reply = response.value<AsynchronousCommandReply *>();
++ // Command has already been completed
++ if (reply->isFinished()) {
++ asynchronousReplyFinished(reply);
++ } else { // Command is not completed yet
++ QObject::connect(reply, &AsynchronousCommandReply::finished,
++ this, &AspectCommandDebugger::asynchronousReplyFinished);
++ m_asyncCommandToSocketEntries.insert(reply, socket);
++ }
++ } else { // Synchronous command
++ // and send response to client
++ QJsonObject reply;
++ reply.insert(QLatin1String("command"), QJsonValue(command));
++ // TO DO: convert QVariant to QJsonDocument/QByteArray
++ sendReply(socket, QJsonDocument(reply).toJson());
++
++ }
++ }
++
++ } // Debug
++
++ } // Qt3DCore
++
++ QT_END_NAMESPACE
++
++ #include "moc_aspectcommanddebugger_p.cpp"