feat(client): 迁移Hub更新客户端实现
This commit is contained in:
+258
-214
@@ -1,243 +1,287 @@
|
||||
#include "UpdateLogic.h"
|
||||
#include "ConfigHelper.h"
|
||||
#include <QDebug>
|
||||
#include <QJsonArray>
|
||||
#include <QApplication>
|
||||
#include <QDir>
|
||||
#include <QSaveFile>
|
||||
#include "PolicyHelper.h"
|
||||
#include "LocalStateHelper.h"
|
||||
|
||||
UpdateLogic::UpdateLogic(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
ConfigHelper& cfg = ConfigHelper::instance();
|
||||
m_serverAddr = cfg.getValue("Server", "api_base_url");
|
||||
m_appId = cfg.getValue("App", "app_id");
|
||||
m_curVer = cfg.getValue("App", "current_version");
|
||||
m_channel = cfg.getValue("App", "channel");
|
||||
|
||||
// Debug print config
|
||||
qDebug() << "Read server addr:" << m_serverAddr;
|
||||
qDebug() << "Read app id:" << m_appId;
|
||||
}
|
||||
|
||||
void UpdateLogic::checkUpdate()
|
||||
{
|
||||
if (m_serverAddr.isEmpty() || m_appId.isEmpty() || m_curVer.isEmpty() || m_channel.isEmpty())
|
||||
{
|
||||
qDebug() << "Config incomplete, abort update check";
|
||||
m_needUpdate = false;
|
||||
return;
|
||||
}
|
||||
QString url = m_serverAddr + "/api/v1/update/check";
|
||||
QJsonObject body;
|
||||
body["app_id"] = m_appId;
|
||||
body["current_version"] = m_curVer;
|
||||
body["channel"] = m_channel;
|
||||
const int configuredProtocol = ConfigHelper::instance().getValue("App", "client_protocol").toInt();
|
||||
body["client_protocol"] = qMax(3, configuredProtocol);
|
||||
|
||||
m_http.postRequest(url, body, [this](int code, const QJsonObject& resp)
|
||||
{
|
||||
qDebug() << "Check update HTTP code:" << code;
|
||||
m_lastStatusCode = code;
|
||||
m_checkResp = resp;
|
||||
m_networkOk = (code == 200);
|
||||
if (code == 200)
|
||||
{
|
||||
const QString appDir = QApplication::applicationDirPath();
|
||||
PolicyHelper onlinePolicy(appDir);
|
||||
LocalStateHelper state(appDir);
|
||||
const bool stateLoaded = state.loadState();
|
||||
const bool policyValid = onlinePolicy.loadPolicyObject(
|
||||
resp.value("policy").toObject(), resp.value("policy_text").toString());
|
||||
if (!policyValid || !stateLoaded
|
||||
|| state.isPolicySeqRolledBack(onlinePolicy.policySeq())
|
||||
|| !onlinePolicy.savePolicy())
|
||||
{
|
||||
qDebug() << "Online policy rejected:" << onlinePolicy.errorString();
|
||||
m_networkOk = false;
|
||||
m_needUpdate = false;
|
||||
return;
|
||||
}
|
||||
state.updateOnlineVerified(onlinePolicy.policySeq());
|
||||
if (!state.saveState())
|
||||
{
|
||||
qDebug() << "Cannot persist online policy state";
|
||||
m_networkOk = false;
|
||||
m_needUpdate = false;
|
||||
return;
|
||||
}
|
||||
m_needUpdate = resp["need_update"].toBool();
|
||||
m_latestVer = resp["latest_version"].toString();
|
||||
qDebug() << "Need update:" << m_needUpdate;
|
||||
qDebug() << "Latest version:" << m_latestVer;
|
||||
qDebug() << "Policy seq:" << onlinePolicy.policySeq();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_needUpdate = false;
|
||||
qDebug() << "Check update api failed";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateLogic::getDownloadUrl(const QString& appId, const QString& channel, const QString& ver, int verId)
|
||||
{
|
||||
QString url = m_serverAddr + "/api/v1/update/download-url";
|
||||
QJsonObject body;
|
||||
body["app_id"] = appId;
|
||||
body["channel"] = channel;
|
||||
body["version"] = ver;
|
||||
body["version_id"] = verId;
|
||||
QJsonArray files;
|
||||
body["files"] = files;
|
||||
|
||||
m_http.postRequest(url, body, [](int code, const QJsonObject& resp)
|
||||
{
|
||||
qDebug() << "\n========== File Download Url ==========";
|
||||
qDebug() << resp["files"].toArray();
|
||||
});
|
||||
}
|
||||
|
||||
bool UpdateLogic::cacheManifest(const QString& appId, const QString& channel, const QString& version,
|
||||
int versionId, const QString& cacheDir)
|
||||
{
|
||||
m_error.clear();
|
||||
if (appId.isEmpty() || channel.isEmpty() || version.isEmpty() || versionId <= 0) {
|
||||
m_error = QCoreApplication::translate("UpdateLogic",
|
||||
"Current version manifest identity is incomplete. Stage: cache current version manifest. App: %1, channel: %2, version: %3, version id: %4.")
|
||||
.arg(appId, channel, version, QString::number(versionId));
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject response;
|
||||
int statusCode = 0;
|
||||
QJsonObject body;
|
||||
body["app_id"] = appId;
|
||||
body["channel"] = channel;
|
||||
body["version"] = version;
|
||||
body["version_id"] = versionId;
|
||||
m_http.postRequest(m_serverAddr + "/api/v1/update/manifest", body,
|
||||
[&](int code, const QJsonObject& resp) {
|
||||
statusCode = code;
|
||||
response = resp;
|
||||
});
|
||||
#include "UpdateLogic.h"
|
||||
|
||||
if (statusCode != 200) {
|
||||
const QJsonValue detail = response.value(QStringLiteral("detail"));
|
||||
const QString message = detail.isObject()
|
||||
? detail.toObject().value(QStringLiteral("msg")).toString()
|
||||
: detail.toString();
|
||||
m_error = QCoreApplication::translate("UpdateLogic",
|
||||
"Cannot download signed manifest for the current local version. Stage: cache current version manifest. HTTP status: %1. App: %2, channel: %3, version: %4, version id: %5.%6")
|
||||
.arg(QString::number(statusCode), appId, channel, version, QString::number(versionId),
|
||||
message.isEmpty() ? QString() : QCoreApplication::translate("UpdateLogic", "\nServer message: %1").arg(message));
|
||||
return false;
|
||||
}
|
||||
|
||||
const QJsonObject manifest = response.value("manifest").toObject();
|
||||
const QString manifestText = response.value("manifest_text").toString();
|
||||
if (manifest.isEmpty() || manifestText.isEmpty()) {
|
||||
m_error = QCoreApplication::translate("UpdateLogic",
|
||||
"The signed manifest response for the current local version is incomplete. Stage: cache current version manifest. App: %1, channel: %2, version: %3, version id: %4.")
|
||||
.arg(appId, channel, version, QString::number(versionId));
|
||||
return false;
|
||||
}
|
||||
if (manifest.value("app_id").toString() != appId
|
||||
|| manifest.value("channel").toString() != channel
|
||||
|| manifest.value("version").toString() != version) {
|
||||
m_error = QCoreApplication::translate("UpdateLogic",
|
||||
"The signed manifest identity does not match the current local version. Stage: cache current version manifest. Expected app/channel/version: %1 / %2 / %3. Manifest app/channel/version: %4 / %5 / %6.")
|
||||
.arg(appId, channel, version,
|
||||
manifest.value("app_id").toString(),
|
||||
manifest.value("channel").toString(),
|
||||
manifest.value("version").toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
QDir dir(cacheDir);
|
||||
if (!dir.exists() && !dir.mkpath(".")) {
|
||||
m_error = QCoreApplication::translate("UpdateLogic",
|
||||
"Cannot create manifest cache directory. Stage: cache current version manifest. Directory: %1.")
|
||||
.arg(cacheDir);
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject wrapper;
|
||||
wrapper["manifest"] = manifest;
|
||||
wrapper["manifest_text"] = manifestText;
|
||||
QSaveFile file(dir.filePath("manifest_" + version + ".json"));
|
||||
const QByteArray bytes = QJsonDocument(wrapper).toJson(QJsonDocument::Indented);
|
||||
if (!file.open(QIODevice::WriteOnly) || file.write(bytes) != bytes.size() || !file.commit()) {
|
||||
m_error = QCoreApplication::translate("UpdateLogic",
|
||||
"Cannot save signed manifest cache. Stage: cache current version manifest. File: %1. Error: %2.")
|
||||
.arg(file.fileName(), file.errorString());
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Current version manifest cached to" << file.fileName();
|
||||
return true;
|
||||
#include "ConfigHelper.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QSaveFile>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
|
||||
namespace {
|
||||
|
||||
QString trimBaseUrl(QString value)
|
||||
{
|
||||
value = value.trimmed();
|
||||
while (value.endsWith(QLatin1Char('/')))
|
||||
value.chop(1);
|
||||
return value;
|
||||
}
|
||||
|
||||
bool UpdateLogic::refreshGitTagsFile(const QString& outputPath)
|
||||
void addQueryValue(QUrlQuery& query, const QString& key, const QString& value)
|
||||
{
|
||||
const QString trimmed = value.trimmed();
|
||||
if (!trimmed.isEmpty())
|
||||
query.addQueryItem(key, trimmed);
|
||||
}
|
||||
|
||||
QString serverMessage(const QJsonObject& response)
|
||||
{
|
||||
const QString msg = response.value(QStringLiteral("msg")).toString();
|
||||
if (!msg.isEmpty())
|
||||
return msg;
|
||||
|
||||
const QJsonValue detail = response.value(QStringLiteral("detail"));
|
||||
if (detail.isObject()) {
|
||||
const QJsonObject object = detail.toObject();
|
||||
const QString detailMsg = object.value(QStringLiteral("msg")).toString();
|
||||
if (!detailMsg.isEmpty())
|
||||
return detailMsg;
|
||||
const QString error = object.value(QStringLiteral("error")).toString();
|
||||
if (!error.isEmpty())
|
||||
return error;
|
||||
}
|
||||
return detail.toString();
|
||||
}
|
||||
|
||||
QJsonObject responseDataObject(const QJsonObject& response)
|
||||
{
|
||||
return response.value(QStringLiteral("data")).isObject()
|
||||
? response.value(QStringLiteral("data")).toObject()
|
||||
: response;
|
||||
}
|
||||
|
||||
QString configValue(const QString& key, const QString& fallback = QString())
|
||||
{
|
||||
const QString value = ConfigHelper::instance().getValue(QString(), key).trimmed();
|
||||
return value.isEmpty() ? fallback : value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UpdateLogic::UpdateLogic(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
ConfigHelper& cfg = ConfigHelper::instance();
|
||||
m_serverAddr = trimBaseUrl(cfg.getValue("Server", "api_base_url"));
|
||||
m_appId = cfg.getValue("App", "product_code").trimmed();
|
||||
if (m_appId.isEmpty())
|
||||
m_appId = cfg.getValue("App", "app_id").trimmed();
|
||||
m_curVer = cfg.getValue("App", "current_version").trimmed();
|
||||
m_channel = cfg.getValue("App", "channel").trimmed();
|
||||
if (m_channel.isEmpty())
|
||||
m_channel = QStringLiteral("stable");
|
||||
|
||||
qDebug() << "Read server addr:" << m_serverAddr;
|
||||
qDebug() << "Read product code:" << m_appId;
|
||||
}
|
||||
|
||||
void UpdateLogic::checkUpdate()
|
||||
{
|
||||
m_error.clear();
|
||||
if (m_serverAddr.isEmpty()) {
|
||||
m_error = QCoreApplication::translate("UpdateLogic", "Server address is empty.");
|
||||
m_needUpdate = false;
|
||||
m_networkOk = false;
|
||||
m_lastStatusCode = 0;
|
||||
m_latestVer.clear();
|
||||
m_checkResp = QJsonObject();
|
||||
|
||||
if (m_serverAddr.isEmpty() || m_appId.isEmpty() || m_curVer.isEmpty())
|
||||
{
|
||||
m_error = QCoreApplication::translate(
|
||||
"UpdateLogic",
|
||||
"Update configuration is incomplete. Server, product_code and current_version are required.");
|
||||
qDebug() << "Config incomplete, abort update check:" << m_error;
|
||||
return;
|
||||
}
|
||||
if (configValue(QStringLiteral("client_token")).isEmpty())
|
||||
{
|
||||
m_lastStatusCode = 401;
|
||||
m_error = QCoreApplication::translate(
|
||||
"UpdateLogic",
|
||||
"Update configuration is incomplete. client_token is required.");
|
||||
m_checkResp.insert(QStringLiteral("msg"), m_error);
|
||||
qDebug() << "Client token missing, abort update check:" << m_error;
|
||||
return;
|
||||
}
|
||||
|
||||
QUrl url(m_serverAddr + QStringLiteral("/api/v1/client/update/authorized-check"));
|
||||
QUrlQuery query;
|
||||
addQueryValue(query, QStringLiteral("productCode"), m_appId);
|
||||
addQueryValue(query, QStringLiteral("currentVersion"), m_curVer);
|
||||
addQueryValue(query, QStringLiteral("clientVersion"), configValue(QStringLiteral("client_protocol"), QStringLiteral("3")));
|
||||
addQueryValue(query, QStringLiteral("channel"), m_channel);
|
||||
addQueryValue(query, QStringLiteral("os"), configValue(QStringLiteral("platform")));
|
||||
addQueryValue(query, QStringLiteral("architecture"), configValue(QStringLiteral("arch")));
|
||||
addQueryValue(query, QStringLiteral("abi"), configValue(QStringLiteral("abi")));
|
||||
url.setQuery(query);
|
||||
|
||||
m_http.getRequest(url.toString(QUrl::FullyEncoded),
|
||||
[this](int code, const QJsonObject& resp)
|
||||
{
|
||||
qDebug() << "Check update HTTP code:" << code;
|
||||
m_lastStatusCode = code;
|
||||
m_checkResp = resp;
|
||||
m_networkOk = (code == 200);
|
||||
if (code != 200)
|
||||
{
|
||||
m_needUpdate = false;
|
||||
m_error = serverMessage(resp);
|
||||
qDebug() << "Check update api failed:" << m_error;
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonArray releases = resp.value(QStringLiteral("data")).toArray();
|
||||
QJsonObject selected;
|
||||
for (const QJsonValue& value : releases) {
|
||||
const QJsonObject release = value.toObject();
|
||||
const bool hasPackages = !release.value(QStringLiteral("packages")).toArray().isEmpty();
|
||||
const bool hasManifest = release.value(QStringLiteral("manifest")).isObject()
|
||||
|| !release.value(QStringLiteral("manifestUri")).toString().isEmpty();
|
||||
if (hasPackages && hasManifest) {
|
||||
selected = release;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (selected.isEmpty()) {
|
||||
m_needUpdate = false;
|
||||
if (!releases.isEmpty())
|
||||
m_latestVer = releases.first().toObject().value(QStringLiteral("version")).toString();
|
||||
qDebug() << "No entitled downloadable update for current client.";
|
||||
return;
|
||||
}
|
||||
|
||||
m_checkResp = selected;
|
||||
m_checkResp.insert(QStringLiteral("need_update"), true);
|
||||
m_checkResp.insert(QStringLiteral("latest_version"), selected.value(QStringLiteral("version")).toString());
|
||||
m_checkResp.insert(QStringLiteral("release_id"), selected.value(QStringLiteral("id")).toString());
|
||||
m_needUpdate = true;
|
||||
m_latestVer = selected.value(QStringLiteral("version")).toString();
|
||||
qDebug() << "Need update:" << m_needUpdate;
|
||||
qDebug() << "Latest version:" << m_latestVer;
|
||||
qDebug() << "Release id:" << selected.value(QStringLiteral("id")).toString();
|
||||
});
|
||||
}
|
||||
|
||||
void UpdateLogic::getDownloadUrl(const QString& appId, const QString& channel, const QString& ver, int verId)
|
||||
{
|
||||
Q_UNUSED(appId);
|
||||
Q_UNUSED(channel);
|
||||
Q_UNUSED(ver);
|
||||
Q_UNUSED(verId);
|
||||
qDebug() << "SimCAE Hub manifest already contains authorized package download URLs.";
|
||||
}
|
||||
|
||||
bool UpdateLogic::cacheManifest(const QString& appId, const QString& channel, const QString& version,
|
||||
int versionId, const QString& cacheDir)
|
||||
{
|
||||
Q_UNUSED(versionId);
|
||||
m_error.clear();
|
||||
if (appId.isEmpty() || channel.isEmpty() || version.isEmpty()) {
|
||||
m_error = QCoreApplication::translate(
|
||||
"UpdateLogic",
|
||||
"Current version manifest identity is incomplete. Stage: cache current version manifest. Product: %1, channel: %2, version: %3.")
|
||||
.arg(appId, channel, version);
|
||||
return false;
|
||||
}
|
||||
|
||||
QUrl url(m_serverAddr + QStringLiteral("/api/v1/client/update/manifest"));
|
||||
QUrlQuery query;
|
||||
addQueryValue(query, QStringLiteral("productCode"), appId);
|
||||
addQueryValue(query, QStringLiteral("version"), version);
|
||||
addQueryValue(query, QStringLiteral("clientVersion"), configValue(QStringLiteral("client_protocol"), QStringLiteral("3")));
|
||||
addQueryValue(query, QStringLiteral("channel"), channel);
|
||||
addQueryValue(query, QStringLiteral("os"), configValue(QStringLiteral("platform")));
|
||||
addQueryValue(query, QStringLiteral("architecture"), configValue(QStringLiteral("arch")));
|
||||
addQueryValue(query, QStringLiteral("abi"), configValue(QStringLiteral("abi")));
|
||||
url.setQuery(query);
|
||||
|
||||
QJsonObject response;
|
||||
int statusCode = 0;
|
||||
QJsonObject body;
|
||||
body["app_id"] = m_appId;
|
||||
body["channel"] = m_channel;
|
||||
m_http.postRequest(m_serverAddr + "/api/v1/git/tags", body,
|
||||
m_http.getRequest(url.toString(QUrl::FullyEncoded),
|
||||
[&](int code, const QJsonObject& resp) {
|
||||
statusCode = code;
|
||||
response = resp;
|
||||
});
|
||||
|
||||
if (statusCode != 200) {
|
||||
const QJsonValue detail = response.value("detail");
|
||||
const QString message = detail.isObject()
|
||||
? detail.toObject().value("msg").toString()
|
||||
: detail.toString();
|
||||
m_error = message.isEmpty()
|
||||
? QCoreApplication::translate("UpdateLogic", "Git tags request failed (HTTP %1).").arg(statusCode)
|
||||
: message;
|
||||
const QString message = serverMessage(response);
|
||||
m_error = QCoreApplication::translate(
|
||||
"UpdateLogic",
|
||||
"Cannot download manifest for the current local version. Stage: cache current version manifest. HTTP status: %1. Product: %2, channel: %3, version: %4.%5")
|
||||
.arg(QString::number(statusCode), appId, channel, version,
|
||||
message.isEmpty() ? QString() : QCoreApplication::translate("UpdateLogic", "\nServer message: %1").arg(message));
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString tagsText = response.value("tags_text").toString();
|
||||
if (tagsText.isEmpty()) {
|
||||
m_error = QCoreApplication::translate("UpdateLogic", "Git tags response is empty.");
|
||||
QJsonObject wrapper = responseDataObject(response);
|
||||
const QJsonObject manifest = wrapper.value(QStringLiteral("manifest")).toObject();
|
||||
QString manifestText = wrapper.value(QStringLiteral("manifestText")).toString();
|
||||
if (manifestText.isEmpty())
|
||||
manifestText = wrapper.value(QStringLiteral("manifest_text")).toString();
|
||||
if (manifest.isEmpty() || manifestText.isEmpty()) {
|
||||
m_error = QCoreApplication::translate(
|
||||
"UpdateLogic",
|
||||
"The manifest response for the current local version is incomplete. Stage: cache current version manifest. Product: %1, channel: %2, version: %3.")
|
||||
.arg(appId, channel, version);
|
||||
return false;
|
||||
}
|
||||
const QString manifestProduct = manifest.value(QStringLiteral("productCode")).toString(
|
||||
manifest.value(QStringLiteral("app_id")).toString());
|
||||
if (manifestProduct != appId
|
||||
|| manifest.value(QStringLiteral("channel")).toString() != channel
|
||||
|| manifest.value(QStringLiteral("version")).toString() != version) {
|
||||
m_error = QCoreApplication::translate(
|
||||
"UpdateLogic",
|
||||
"The manifest identity does not match the current local version. Stage: cache current version manifest. Expected product/channel/version: %1 / %2 / %3. Manifest product/channel/version: %4 / %5 / %6.")
|
||||
.arg(appId, channel, version,
|
||||
manifestProduct,
|
||||
manifest.value(QStringLiteral("channel")).toString(),
|
||||
manifest.value(QStringLiteral("version")).toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
QString writeError;
|
||||
if (!ConfigHelper::writeFileWithElevationIfNeeded(outputPath, tagsText.toUtf8(), &writeError)) {
|
||||
m_error = QCoreApplication::translate("UpdateLogic", "Cannot write Git tags file: %1").arg(writeError);
|
||||
QDir dir(cacheDir);
|
||||
if (!dir.exists() && !dir.mkpath(".")) {
|
||||
m_error = QCoreApplication::translate(
|
||||
"UpdateLogic",
|
||||
"Cannot create manifest cache directory. Stage: cache current version manifest. Directory: %1.")
|
||||
.arg(cacheDir);
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Git tags file refreshed:" << outputPath;
|
||||
|
||||
wrapper.insert(QStringLiteral("manifest"), manifest);
|
||||
wrapper.insert(QStringLiteral("manifestText"), manifestText);
|
||||
wrapper.insert(QStringLiteral("manifest_text"), manifestText);
|
||||
QSaveFile file(dir.filePath(QStringLiteral("manifest_") + version + QStringLiteral(".json")));
|
||||
const QByteArray bytes = QJsonDocument(wrapper).toJson(QJsonDocument::Indented);
|
||||
if (!file.open(QIODevice::WriteOnly) || file.write(bytes) != bytes.size() || !file.commit()) {
|
||||
m_error = QCoreApplication::translate(
|
||||
"UpdateLogic",
|
||||
"Cannot save manifest cache. Stage: cache current version manifest. File: %1. Error: %2.")
|
||||
.arg(file.fileName(), file.errorString());
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Current version manifest cached to" << file.fileName();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UpdateLogic::refreshGitTagsFile(const QString& outputPath)
|
||||
{
|
||||
Q_UNUSED(outputPath);
|
||||
m_error.clear();
|
||||
qDebug() << "Git tag export is not part of the current SimCAE Hub admin pages; skipped.";
|
||||
return true;
|
||||
}
|
||||
|
||||
void UpdateLogic::reportUpdateResult(const QString& deviceId, const QString& fromVer, const QString& toVer, bool success)
|
||||
{
|
||||
QString url = m_serverAddr + "/api/v1/update/report";
|
||||
QJsonObject body;
|
||||
body["app_id"] = m_appId;
|
||||
body["device_id"] = deviceId;
|
||||
body["from_version"] = fromVer;
|
||||
body["to_version"] = toVer;
|
||||
body["result"] = success ? "success" : "fail";
|
||||
|
||||
m_http.postRequest(url, body, [](int code, const QJsonObject& resp)
|
||||
{
|
||||
qDebug() << "\n========== Update Report Result ==========";
|
||||
qDebug() << resp;
|
||||
});
|
||||
}
|
||||
Q_UNUSED(deviceId);
|
||||
Q_UNUSED(fromVer);
|
||||
Q_UNUSED(toVer);
|
||||
Q_UNUSED(success);
|
||||
qDebug() << "Update result report is not part of the current SimCAE Hub API; skipped.";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user