-
Burak Hançerli authoredBurak Hançerli authored
ds.cpp 8.43 KiB
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "ds.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QScreen>
namespace PackageFromDesignStudio {
using namespace Qt::Literals;
constexpr auto designStudioReady = "designStudioReady"_L1;
constexpr auto projectData = "projectData"_L1;
constexpr auto stopRunningProject = "stopRunningProject"_L1;
}; // namespace PackageFromDesignStudio
namespace PackageToDesignStudio {
using namespace Qt::Literals;
constexpr auto deviceInfo = "deviceInfo"_L1;
constexpr auto projectReceivingProgress = "projectReceivingProgress"_L1;
constexpr auto projectStarting = "projectStarting"_L1;
constexpr auto projectRunning = "projectRunning"_L1;
constexpr auto projectStopped = "projectStopped"_L1;
constexpr auto projectLogs = "projectLogs"_L1;
}; // namespace PackageToDesignStudio
DesignStudio::DesignStudio(QWebSocket *socket, const QString &deviceID, QObject *parent)
: QObject(parent)
, m_deviceID(deviceID)
, m_socket(socket)
{
initPingPong();
initSocket();
startPingPong();
m_projectStallTimer.setInterval(5000);
m_projectStallTimer.setSingleShot(true);
connect(&m_projectStallTimer, &QTimer::timeout, this, [this]() {
qDebug() << "Project is stalled. Closing the connection.";
m_socket->close();
m_socket->abort();
emit disconnected(m_designStudioID);
});
m_speedCalculator.setInterval(1000);
m_speedCalculator.setSingleShot(false);
connect(&m_speedCalculator, &QTimer::timeout, this, [this]() {
quint64 elapsedTime = m_projectNotificationTime.msecsTo(QTime::currentTime()) / 1000;
int receiveSpeed = elapsedTime > 0 ? m_projectData.size() / elapsedTime / 1024 : 0;
qDebug() << "Receive speed" << receiveSpeed << "KB/s (" << receiveSpeed / 1024 << "MB/s )"
<< "percentage" << m_lastPercentage << "%";
});
}
void DesignStudio::initPingPong()
{
m_pingTimer.setInterval(1000);
m_pongTimer.setInterval(30000);
m_pongTimer.setSingleShot(true);
m_pingTimer.setSingleShot(true);
connect(&m_pingTimer, &QTimer::timeout, this, [this]() {
m_socket->ping();
startPingPong();
});
connect(m_socket.data(),
&QWebSocket::pong,
this,
[this](quint64 elapsedTime, const QByteArray &) {
if (elapsedTime > 1000)
qWarning() << "Design Studio pong is too slow:" << elapsedTime
<< "ms. Newtork issue?";
else if (elapsedTime > 500)
qWarning() << "Design Studio pong is slow:" << elapsedTime << "ms";
m_pongTimer.stop();
m_pingTimer.start();
});
connect(&m_pongTimer, &QTimer::timeout, this, [this]() {
qDebug() << "Design Studio" << m_designStudioID
<< "is not responding. Closing the connection.";
m_socket->close();
m_socket->abort();
emit disconnected(m_designStudioID);
});
}
void DesignStudio::stopPingPong()
{
m_pingTimer.stop();
m_pongTimer.stop();
}
void DesignStudio::startPingPong()
{
m_pingTimer.start();
}
void DesignStudio::initSocket()
{
connect(m_socket.data(), &QWebSocket::disconnected, this, [this]() {
qDebug() << "Design Studio" << m_designStudioID << "disconnected";
m_pingTimer.stop();
m_pongTimer.stop();
emit disconnected(m_designStudioID);
});
connect(m_socket.data(),
&QWebSocket::textMessageReceived,
this,
&DesignStudio::processTextMessage);
connect(m_socket.data(),
&QWebSocket::binaryMessageReceived,
this,
&DesignStudio::processBinaryMessage);
}
void DesignStudio::disconnect()
{
m_socket->close();
m_socket->abort();
}
QString DesignStudio::ipv4Addr() const
{
return m_socket->peerAddress().toString();
}
QString DesignStudio::id() const
{
return m_designStudioID;
}
void DesignStudio::sendDeviceInfo()
{
const QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
QJsonObject deviceInfo;
deviceInfo["screenHeight"] = screenGeometry.height();
deviceInfo["screenWidth"] = screenGeometry.width();
deviceInfo["os"] = QSysInfo::prettyProductName();
deviceInfo["osVersion"] = QSysInfo::productVersion();
deviceInfo["architecture"] = QSysInfo::currentCpuArchitecture();
deviceInfo["deviceId"] = m_deviceID;
deviceInfo["appVersion"] = QString(CMAKE_VAR_GIT_VERSION);
qDebug() << "Sending device info to Design Studio" << deviceInfo;
sendData(PackageToDesignStudio::deviceInfo, deviceInfo);
}
void DesignStudio::sendData(const QLatin1String &dataType, const QJsonValue &data)
{
QJsonObject message;
message["dataType"] = dataType;
message["data"] = data;
m_socket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact));
}
void DesignStudio::processTextMessage(const QString &message)
{
QJsonParseError jsonError;
const QJsonDocument jsonDoc = QJsonDocument::fromJson(message.toLatin1(), &jsonError);
if (jsonError.error != QJsonParseError::NoError) {
qDebug() << "Failed to parse JSON message:" << jsonError.errorString() << message;
return;
}
const QJsonObject jsonObj = jsonDoc.object();
if (!jsonObj.contains("dataType")) {
qDebug() << "Invalid JSON message:" << jsonObj;
return;
}
const QString dataType = jsonObj.value("dataType").toString();
const QJsonObject data = jsonObj.value("data").toObject();
if (dataType == PackageFromDesignStudio::designStudioReady) {
const QString newDesignStudioId = data["designStudioID"].toString();
const int commVersion = data["commVersion"].toInt();
qDebug() << "Design Studio ready with ID" << newDesignStudioId << "and comm version"
<< commVersion;
m_designStudioID = newDesignStudioId;
sendDeviceInfo();
emit idReceived(newDesignStudioId, ipv4Addr());
} else if (dataType == PackageFromDesignStudio::projectData) {
m_incomingProjectSize = data["projectSize"].toInt();
const QString qtVersion = data["qtVersion"].toString();
auto qcompare = QString::compare(qtVersion, QT_VERSION_STR, Qt::CaseInsensitive);
if (qcompare != 0) {
qDebug() << "Qt version mismatch. Expected" << QT_VERSION_STR " or lower, but got"
<< qtVersion << ". Project may not work correctly.";
emit projectVersionMismatch(QT_VERSION_STR, qtVersion);
}
qDebug() << "Project is expected with size" << m_incomingProjectSize << "and Qt version"
<< qtVersion;
m_projectData.clear();
m_projectNotificationTime = QTime::currentTime();
m_speedCalculator.start();
m_projectStallTimer.start();
stopPingPong();
emit projectIncoming(m_designStudioID, m_incomingProjectSize);
} else if (dataType == PackageFromDesignStudio::stopRunningProject) {
qDebug() << "Stop running project requested";
m_projectData.clear();
m_speedCalculator.stop();
m_projectStallTimer.stop();
emit projectStopRequested(m_designStudioID);
} else {
qDebug() << "Unkown JSON message type:" << dataType;
}
}
void DesignStudio::processBinaryMessage(const QByteArray &data)
{
m_projectStallTimer.start();
m_projectData.append(data);
const bool isProjectComplete = m_projectData.size() == m_incomingProjectSize;
int percentage = m_projectData.size() * 100 / m_incomingProjectSize;
if (m_lastPercentage != percentage) {
emit projectIncomingProgress(m_designStudioID, percentage);
m_lastPercentage = percentage;
sendData(PackageToDesignStudio::projectReceivingProgress, percentage);
}
if (isProjectComplete) {
startPingPong();
sendProjectStarting();
emit projectReceived(m_designStudioID, m_projectData);
m_speedCalculator.stop();
m_projectStallTimer.stop();
}
}
void DesignStudio::sendProjectStarting()
{
sendData(PackageToDesignStudio::projectStarting);
}
void DesignStudio::sendProjectRunning()
{
sendData(PackageToDesignStudio::projectRunning);
}
void DesignStudio::sendProjectStopped()
{
sendData(PackageToDesignStudio::projectStopped);
}
void DesignStudio::sendProjectLogs(const QString &logs)
{
sendData(PackageToDesignStudio::projectLogs, logs);
}