237 lines
9.8 KiB
C++
237 lines
9.8 KiB
C++
#include <QApplication>
|
|
#include <QCoreApplication>
|
|
#include <QDebug>
|
|
#include <QDir>
|
|
#include <QFileInfo>
|
|
#include <QMessageBox>
|
|
#include <QProcess>
|
|
#include <QProgressDialog>
|
|
#include <QTranslator>
|
|
#include <QUuid>
|
|
|
|
#include "UpdateLogic.h"
|
|
#include "../Common/ConfigHelper.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")));
|
|
}
|
|
|
|
QString ensureDeviceId()
|
|
{
|
|
ConfigHelper& config = ConfigHelper::instance();
|
|
QString deviceId = config.getValue(QStringLiteral("Update"), QStringLiteral("device_id")).trimmed();
|
|
if (!deviceId.isEmpty())
|
|
return deviceId;
|
|
|
|
deviceId = config.getValue(QStringLiteral("Device"), QStringLiteral("installation_id")).trimmed();
|
|
if (deviceId.isEmpty())
|
|
deviceId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
|
|
|
config.setValue(QStringLiteral("Device"), QStringLiteral("installation_id"), deviceId);
|
|
config.setValue(QStringLiteral("Update"), QStringLiteral("device_id"), deviceId);
|
|
return deviceId;
|
|
}
|
|
|
|
QString authFailureMessage(const QJsonObject& response, const QString& fallback)
|
|
{
|
|
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;
|
|
}
|
|
const QString detailText = detail.toString();
|
|
return detailText.isEmpty() ? fallback : detailText;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
QApplication app(argc, argv);
|
|
QApplication::setApplicationName("SimCAE Launcher");
|
|
QTranslator translator;
|
|
if (translator.load(QStringLiteral(":/i18n/update-client_zh_CN.qm")))
|
|
app.installTranslator(&translator);
|
|
|
|
const int elevatedWriteExitCode = ConfigHelper::runElevatedWriteCommandIfRequested();
|
|
if (elevatedWriteExitCode >= 0)
|
|
return elevatedWriteExitCode;
|
|
|
|
QProgressDialog progress(QCoreApplication::translate("Launcher", "Checking for software updates..."), QString(), 0, 0);
|
|
progress.setWindowTitle(QCoreApplication::translate("Launcher", "SimCAE Launcher"));
|
|
progress.setCancelButton(nullptr);
|
|
progress.setWindowModality(Qt::ApplicationModal);
|
|
progress.setMinimumDuration(0);
|
|
progress.setAutoClose(false);
|
|
progress.show();
|
|
QApplication::processEvents();
|
|
|
|
ConfigHelper& config = ConfigHelper::instance();
|
|
const QString appDir = QApplication::applicationDirPath();
|
|
|
|
progress.setLabelText(QCoreApplication::translate("Launcher", "Checking authorized updates..."));
|
|
QApplication::processEvents();
|
|
UpdateLogic logic;
|
|
logic.checkUpdate();
|
|
|
|
const bool needUpdate = logic.getNeedUpdate();
|
|
const bool networkOk = logic.isNetworkOk();
|
|
const QString latestVer = logic.getLatestVersion();
|
|
const QJsonObject response = logic.getCheckResult();
|
|
const QString releaseId = response.value(QStringLiteral("release_id")).toString(
|
|
response.value(QStringLiteral("id")).toString());
|
|
const QString appId = logic.getProductCode();
|
|
const QString channel = logic.getChannel();
|
|
const QString launchToken = config.getValue(QStringLiteral("App"), QStringLiteral("launch_token"));
|
|
const QString currentVersion = config.getValue(QStringLiteral("App"), QStringLiteral("current_version"));
|
|
const QString deviceId = ensureDeviceId();
|
|
|
|
const auto configuredName = [&](const QString& key, const QString& fallback) {
|
|
return ConfigHelper::executableNameForCurrentPlatform(
|
|
config.getValue(QStringLiteral("Runtime"), key), fallback);
|
|
};
|
|
const QString mainExecutable = configuredName(QStringLiteral("main_executable"), QStringLiteral("MainApp"));
|
|
const QString updaterExecutable = configuredName(QStringLiteral("updater_executable"), QStringLiteral("Updater"));
|
|
const QString mainAppPath = QDir(appDir).filePath(mainExecutable);
|
|
const QString updaterPath = QDir(appDir).filePath(updaterExecutable);
|
|
|
|
QString mainStartupError;
|
|
const auto startMainApp = [&]() {
|
|
mainStartupError.clear();
|
|
if (!QFileInfo::exists(mainAppPath)) {
|
|
mainStartupError = QCoreApplication::translate(
|
|
"Launcher",
|
|
"Cannot start the main application because the executable file does not exist.\nExecutable: %1\nCheck main_executable and install_root in the generated client configuration.")
|
|
.arg(mainAppPath);
|
|
return false;
|
|
}
|
|
|
|
QString ticketPath;
|
|
QString ticketError;
|
|
if (!TicketHelper::createTicket(appId, deviceId, currentVersion,
|
|
launchToken, &ticketPath, &ticketError)) {
|
|
qDebug() << "Cannot create launch ticket:" << ticketError;
|
|
mainStartupError = QCoreApplication::translate(
|
|
"Launcher",
|
|
"Cannot start the main application because the one-time launch ticket could not be created.\nExecutable: %1\nDetails: %2")
|
|
.arg(mainAppPath, ticketError);
|
|
return false;
|
|
}
|
|
|
|
const bool started = QProcess::startDetached(mainAppPath,
|
|
QStringList{QStringLiteral("--ticket-file=%1").arg(ticketPath)});
|
|
if (!started) {
|
|
QFile::remove(ticketPath);
|
|
mainStartupError = QCoreApplication::translate(
|
|
"Launcher",
|
|
"Cannot start the main application process.\nExecutable: %1\nTicket file: %2\nCheck file permissions, dependent DLLs/shared libraries, and whether the executable can run independently.")
|
|
.arg(mainAppPath, ticketPath);
|
|
}
|
|
return started;
|
|
};
|
|
|
|
if (logic.lastStatusCode() == 401 || logic.lastStatusCode() == 403) {
|
|
progress.close();
|
|
QMessageBox::critical(nullptr,
|
|
QCoreApplication::translate("Launcher", "Update Client Rejected"),
|
|
authFailureMessage(response,
|
|
QCoreApplication::translate("Launcher", "The update client token is missing or invalid. Please download a valid package from the customer portal.")));
|
|
return -1;
|
|
} else if (!networkOk) {
|
|
progress.close();
|
|
QMessageBox::warning(nullptr,
|
|
QCoreApplication::translate("Launcher", "Update Server Unavailable"),
|
|
QCoreApplication::translate("Launcher", "Cannot connect to the update server. The installed application will be started without downloading an update."));
|
|
progress.show();
|
|
}
|
|
|
|
if (networkOk && needUpdate)
|
|
{
|
|
progress.close();
|
|
const QString prompt = QCoreApplication::translate(
|
|
"Launcher",
|
|
"Version %1 is available. Update now?").arg(latestVer);
|
|
const bool accepted = QMessageBox::question(nullptr,
|
|
QCoreApplication::translate("Launcher", "New Version Available"),
|
|
prompt,
|
|
QMessageBox::Yes | QMessageBox::No,
|
|
QMessageBox::No) == QMessageBox::Yes;
|
|
if (!accepted) {
|
|
if (!startMainApp()) {
|
|
QMessageBox::critical(nullptr,
|
|
QCoreApplication::translate("Launcher", "Startup Failed"),
|
|
mainStartupError.isEmpty()
|
|
? QCoreApplication::translate("Launcher", "Cannot start the main application: %1").arg(mainAppPath)
|
|
: mainStartupError);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const QStringList updaterArgs{
|
|
appId,
|
|
channel,
|
|
latestVer,
|
|
QStringLiteral("0"),
|
|
QStringLiteral("--release-id=%1").arg(releaseId)
|
|
};
|
|
if (!QFileInfo::exists(updaterPath))
|
|
{
|
|
QMessageBox::critical(nullptr,
|
|
QCoreApplication::translate("Launcher", "Updater Startup Failed"),
|
|
QCoreApplication::translate(
|
|
"Launcher",
|
|
"Cannot start the updater because the executable file does not exist.\nExecutable: %1\nCheck updater_executable and install_root in the generated client configuration.")
|
|
.arg(updaterPath));
|
|
return -1;
|
|
}
|
|
if (!QProcess::startDetached(updaterPath, updaterArgs))
|
|
{
|
|
QMessageBox::critical(nullptr,
|
|
QCoreApplication::translate("Launcher", "Updater Startup Failed"),
|
|
QCoreApplication::translate(
|
|
"Launcher",
|
|
"Cannot start the updater process.\nExecutable: %1\nArguments: %2\nCheck file permissions, dependent DLLs/shared libraries, and whether the updater can run independently.")
|
|
.arg(updaterPath, updaterArgs.join(QStringLiteral(" "))));
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
progress.setLabelText(networkOk
|
|
? QCoreApplication::translate("Launcher", "The application is up to date. Starting...")
|
|
: QCoreApplication::translate("Launcher", "Offline startup. Starting..."));
|
|
QApplication::processEvents();
|
|
if (!startMainApp())
|
|
{
|
|
progress.close();
|
|
QMessageBox::critical(nullptr,
|
|
QCoreApplication::translate("Launcher", "Startup Failed"),
|
|
mainStartupError.isEmpty()
|
|
? QCoreApplication::translate("Launcher", "Cannot start the main application: %1").arg(mainAppPath)
|
|
: mainStartupError);
|
|
return -1;
|
|
}
|
|
progress.close();
|
|
return 0;
|
|
}
|