#include #include #include #include #include #include #include #include #include #include #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; }