feat(client): 迁移Hub更新客户端实现
This commit is contained in:
+69
-72
@@ -1,72 +1,69 @@
|
||||
#include "MainWindow.h"
|
||||
#include <QApplication>
|
||||
#include <QFont>
|
||||
#include <QFile>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
#include <QCryptographicHash>
|
||||
#include "../Common/PolicyHelper.h"
|
||||
#include "../Common/ConfigHelper.h"
|
||||
|
||||
static QString readDllVersion(const QString& dllPath)
|
||||
{
|
||||
QFile file(dllPath);
|
||||
if (!file.exists())
|
||||
return "missing";
|
||||
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
return "unreadable";
|
||||
|
||||
QByteArray data = file.read(1024);
|
||||
file.close();
|
||||
return QString::fromUtf8(QCryptographicHash::hash(data, QCryptographicHash::Sha256).toHex().left(8));
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
this->setWindowTitle("Marsco Demo MainApp");
|
||||
this->resize(600, 400);
|
||||
|
||||
QString appVersion = ConfigHelper::instance().getValue("App", "current_version");
|
||||
if (appVersion.isEmpty())
|
||||
appVersion = "unknown";
|
||||
|
||||
QString dllVersion = readDllVersion(QApplication::applicationDirPath() + "/plugins/demo_plugin.dll");
|
||||
|
||||
PolicyHelper policy(QApplication::applicationDirPath());
|
||||
QString policyResult;
|
||||
if (!policy.loadPolicy("config/version_policy.dat"))
|
||||
{
|
||||
policyResult = "policy missing";
|
||||
}
|
||||
else if (!policy.isValid())
|
||||
{
|
||||
policyResult = "policy invalid";
|
||||
}
|
||||
else if (!policy.isVersionAllowed(appVersion))
|
||||
{
|
||||
policyResult = "version disabled";
|
||||
}
|
||||
else if (policy.isExpired())
|
||||
{
|
||||
policyResult = "policy expired";
|
||||
}
|
||||
else
|
||||
{
|
||||
policyResult = "policy ok";
|
||||
}
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
QLabel* label = new QLabel(QString("Software Running Successfully\nVersion: %1\nDLL Version: %2\nPolicy: %3")
|
||||
.arg(appVersion)
|
||||
.arg(dllVersion)
|
||||
.arg(policyResult));
|
||||
QFont font = label->font();
|
||||
font.setPointSize(14);
|
||||
label->setFont(font);
|
||||
label->setAlignment(Qt::AlignCenter);
|
||||
|
||||
layout->addWidget(label);
|
||||
this->setLayout(layout);
|
||||
}
|
||||
#include "MainWindow.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCryptographicHash>
|
||||
#include <QFile>
|
||||
#include <QFont>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "../Common/ConfigHelper.h"
|
||||
|
||||
namespace {
|
||||
|
||||
QString configValue(const QString& key, const QString& fallback = QString())
|
||||
{
|
||||
const QString value = ConfigHelper::instance().getValue(QString(), key).trimmed();
|
||||
return value.isEmpty() ? fallback : value;
|
||||
}
|
||||
|
||||
QString readDllVersion(const QString& dllPath)
|
||||
{
|
||||
QFile file(dllPath);
|
||||
if (!file.exists())
|
||||
return QStringLiteral("missing");
|
||||
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
return QStringLiteral("unreadable");
|
||||
|
||||
const QByteArray data = file.read(1024);
|
||||
file.close();
|
||||
return QString::fromUtf8(QCryptographicHash::hash(data, QCryptographicHash::Sha256).toHex().left(8));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MainWindow::MainWindow(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
this->setWindowTitle("SimCAE Demo MainApp");
|
||||
this->resize(600, 400);
|
||||
|
||||
const QString appVersion = configValue(QStringLiteral("current_version"), QStringLiteral("unknown"));
|
||||
const QString product = configValue(QStringLiteral("product_code"),
|
||||
configValue(QStringLiteral("app_id"), QStringLiteral("unknown")));
|
||||
const QString channel = configValue(QStringLiteral("channel"), QStringLiteral("stable"));
|
||||
const QString apiBaseUrl = configValue(QStringLiteral("api_base_url"), QStringLiteral("not configured"));
|
||||
const QString tokenState = configValue(QStringLiteral("client_token")).isEmpty()
|
||||
? QStringLiteral("missing")
|
||||
: QStringLiteral("configured");
|
||||
const QString dllVersion = readDllVersion(QApplication::applicationDirPath() + "/plugins/demo_plugin.dll");
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
QLabel* label = new QLabel(QString(
|
||||
"Software Running Successfully\n"
|
||||
"Product: %1\n"
|
||||
"Version: %2\n"
|
||||
"Channel: %3\n"
|
||||
"Server: %4\n"
|
||||
"Update Client Token: %5\n"
|
||||
"DLL Version: %6")
|
||||
.arg(product, appVersion, channel, apiBaseUrl, tokenState, dllVersion));
|
||||
QFont font = label->font();
|
||||
font.setPointSize(13);
|
||||
label->setFont(font);
|
||||
label->setAlignment(Qt::AlignCenter);
|
||||
|
||||
layout->addWidget(label);
|
||||
this->setLayout(layout);
|
||||
}
|
||||
|
||||
+111
-131
@@ -1,135 +1,115 @@
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QMessageBox>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QSaveFile>
|
||||
#include <QTranslator>
|
||||
#include "MainWindow.h"
|
||||
#include "../Common/ConfigHelper.h"
|
||||
#include "../Common/PolicyHelper.h"
|
||||
#include "../Common/LocalStateHelper.h"
|
||||
#include "../Common/TicketHelper.h"
|
||||
#include "../Common/IntegrityHelper.h"
|
||||
#include "../Common/DeviceIdentityHelper.h"
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
QTranslator translator;
|
||||
if (translator.load(":/i18n/update-client_zh_CN.qm"))
|
||||
a.installTranslator(&translator);
|
||||
|
||||
const int elevatedWriteExitCode = ConfigHelper::runElevatedWriteCommandIfRequested();
|
||||
if (elevatedWriteExitCode >= 0)
|
||||
return elevatedWriteExitCode;
|
||||
|
||||
qDebug() << Qt::endl << "entered main app" << Qt::endl;
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QMessageBox>
|
||||
#include <QSaveFile>
|
||||
#include <QTranslator>
|
||||
|
||||
#include "MainWindow.h"
|
||||
#include "../Common/ConfigHelper.h"
|
||||
#include "../Common/IntegrityHelper.h"
|
||||
#include "../Common/TicketHelper.h"
|
||||
|
||||
namespace {
|
||||
|
||||
QString configValue(const QString& key, const QString& fallback = QString())
|
||||
{
|
||||
const QString value = ConfigHelper::instance().getValue(QString(), key).trimmed();
|
||||
return value.isEmpty() ? fallback : value;
|
||||
}
|
||||
|
||||
QString productCode()
|
||||
{
|
||||
return configValue(QStringLiteral("product_code"),
|
||||
configValue(QStringLiteral("app_id")));
|
||||
}
|
||||
|
||||
bool configFlag(const QString& key)
|
||||
{
|
||||
const QString value = configValue(key).toLower();
|
||||
return value == QStringLiteral("true")
|
||||
|| value == QStringLiteral("1")
|
||||
|| value == QStringLiteral("yes")
|
||||
|| value == QStringLiteral("on");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
QTranslator translator;
|
||||
if (translator.load(QStringLiteral(":/i18n/update-client_zh_CN.qm")))
|
||||
a.installTranslator(&translator);
|
||||
|
||||
const int elevatedWriteExitCode = ConfigHelper::runElevatedWriteCommandIfRequested();
|
||||
if (elevatedWriteExitCode >= 0)
|
||||
return elevatedWriteExitCode;
|
||||
|
||||
qDebug() << Qt::endl << "entered main app" << Qt::endl;
|
||||
|
||||
ConfigHelper& config = ConfigHelper::instance();
|
||||
QString launcherExecutable = config.getValue("Runtime", "launcher_executable").trimmed();
|
||||
launcherExecutable = ConfigHelper::executableNameForCurrentPlatform(launcherExecutable, "Launcher");
|
||||
QString ticketFilePath;
|
||||
QString healthFilePath;
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
const QString arg(argv[i]);
|
||||
if (arg.startsWith("--ticket-file="))
|
||||
ticketFilePath = arg.mid(QString("--ticket-file=").size());
|
||||
else if (arg.startsWith("--health-file="))
|
||||
healthFilePath = arg.mid(QString("--health-file=").size());
|
||||
}
|
||||
|
||||
QString ticketError;
|
||||
if (ticketFilePath.isEmpty()
|
||||
|| !TicketHelper::consumeAndVerify(ticketFilePath,
|
||||
config.getValue("App", "app_id"), config.getValue("Update", "device_id"),
|
||||
config.getValue("App", "current_version"), config.getValue("App", "launch_token"),
|
||||
&ticketError))
|
||||
{
|
||||
QMessageBox::critical(nullptr, "Startup Restriction",
|
||||
QString("Invalid or missing one-time launch ticket: %1\nPlease use %2.")
|
||||
.arg(ticketError, launcherExecutable));
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString appDir = QApplication::applicationDirPath();
|
||||
const QString installRoot = config.installRoot();
|
||||
DeviceIdentityHelper identity(appDir);
|
||||
if (!identity.verifyLocal(config.getValue("App", "app_id"), config.getValue("App", "channel")))
|
||||
{
|
||||
QMessageBox::critical(nullptr, "License Error", QString("Local license invalid: %1").arg(identity.errorString()));
|
||||
return -1;
|
||||
}
|
||||
PolicyHelper policy(appDir);
|
||||
if (!policy.loadPolicy("config/version_policy.dat") || !policy.isValid())
|
||||
{
|
||||
QMessageBox::critical(nullptr, "Policy Error", QString("Local policy invalid: %1").arg(policy.errorString()));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (policy.isExpired())
|
||||
{
|
||||
QMessageBox::critical(nullptr, "Policy Error", "Policy expired, cannot start the application.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
LocalStateHelper state(appDir);
|
||||
if (!state.loadState())
|
||||
{
|
||||
QMessageBox::critical(nullptr, "State Error", QString("Cannot load local state: %1").arg(state.errorString()));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (state.isPolicySeqRolledBack(policy.policySeq()))
|
||||
{
|
||||
QMessageBox::critical(nullptr, "Policy Error", "Detected policy sequence rollback, startup blocked.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (state.isSystemTimeRewound())
|
||||
{
|
||||
QMessageBox::critical(nullptr, "Policy Error", "System time appears to be rewound, startup blocked.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
IntegrityHelper integrity(installRoot);
|
||||
if (!integrity.verifyInstalledVersion(
|
||||
config.getValue("App", "app_id"), config.getValue("App", "channel"),
|
||||
config.getValue("App", "current_version")))
|
||||
QString launcherExecutable = config.getValue(QStringLiteral("Runtime"), QStringLiteral("launcher_executable")).trimmed();
|
||||
launcherExecutable = ConfigHelper::executableNameForCurrentPlatform(launcherExecutable, QStringLiteral("Launcher"));
|
||||
|
||||
QString ticketFilePath;
|
||||
QString healthFilePath;
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
QMessageBox::critical(nullptr, "Integrity Check Failed",
|
||||
QString("Startup blocked because the installed files failed local signed Manifest verification.\n"
|
||||
"This is not a download check; it means the current installation directory does not match the signed Manifest cache for this version.\n\n"
|
||||
"Details:\n%1")
|
||||
.arg(integrity.errorString()));
|
||||
const QString arg(argv[i]);
|
||||
if (arg.startsWith(QStringLiteral("--ticket-file=")))
|
||||
ticketFilePath = arg.mid(QStringLiteral("--ticket-file=").size());
|
||||
else if (arg.startsWith(QStringLiteral("--health-file=")))
|
||||
healthFilePath = arg.mid(QStringLiteral("--health-file=").size());
|
||||
}
|
||||
|
||||
QString ticketError;
|
||||
if (ticketFilePath.isEmpty()
|
||||
|| !TicketHelper::consumeAndVerify(ticketFilePath,
|
||||
productCode(), config.getValue(QStringLiteral("Update"), QStringLiteral("device_id")),
|
||||
config.getValue(QStringLiteral("App"), QStringLiteral("current_version")),
|
||||
config.getValue(QStringLiteral("App"), QStringLiteral("launch_token")),
|
||||
&ticketError))
|
||||
{
|
||||
QMessageBox::critical(nullptr, "Startup Restriction",
|
||||
QString("Invalid or missing one-time launch ticket: %1\nPlease use %2.")
|
||||
.arg(ticketError, launcherExecutable));
|
||||
return -1;
|
||||
}
|
||||
|
||||
MainWindow w;
|
||||
w.show();
|
||||
|
||||
state.updateAfterSuccessfulRun(ConfigHelper::instance().getValue("App", "current_version"), policy.policySeq());
|
||||
if (!state.saveState())
|
||||
{
|
||||
QMessageBox::critical(nullptr, "State Error", QString("Cannot save local state: %1").arg(state.errorString()));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!healthFilePath.isEmpty())
|
||||
{
|
||||
QDir().mkpath(QFileInfo(healthFilePath).path());
|
||||
QSaveFile healthFile(healthFilePath);
|
||||
const QByteArray healthy("ok\n");
|
||||
if (!healthFile.open(QIODevice::WriteOnly)
|
||||
|| healthFile.write(healthy) != healthy.size()
|
||||
|| !healthFile.commit())
|
||||
{
|
||||
QMessageBox::critical(nullptr, "Startup Error", "Cannot write update health confirmation file.");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Main program MainApp is running normally";
|
||||
return a.exec();
|
||||
}
|
||||
|
||||
const QString installRoot = config.installRoot();
|
||||
if (configFlag(QStringLiteral("verify_installed_on_start"))) {
|
||||
IntegrityHelper integrity(installRoot);
|
||||
if (!integrity.verifyInstalledVersion(
|
||||
productCode(),
|
||||
config.getValue(QStringLiteral("App"), QStringLiteral("channel")),
|
||||
config.getValue(QStringLiteral("App"), QStringLiteral("current_version"))))
|
||||
{
|
||||
QMessageBox::critical(nullptr, "Integrity Check Failed",
|
||||
QString("Startup blocked because the installed files failed local Manifest verification.\n\nDetails:\n%1")
|
||||
.arg(integrity.errorString()));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!healthFilePath.isEmpty())
|
||||
{
|
||||
QDir().mkpath(QFileInfo(healthFilePath).path());
|
||||
QSaveFile healthFile(healthFilePath);
|
||||
const QByteArray healthy("ok\n");
|
||||
if (!healthFile.open(QIODevice::WriteOnly)
|
||||
|| healthFile.write(healthy) != healthy.size()
|
||||
|| !healthFile.commit())
|
||||
{
|
||||
QMessageBox::critical(nullptr, "Startup Error", "Cannot write update health confirmation file.");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
MainWindow w;
|
||||
w.show();
|
||||
|
||||
qDebug() << "Main program MainApp is running normally";
|
||||
return a.exec();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user