#include "UpdateLogic.h" #include "ConfigHelper.h" #include #include #include #include #include #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; }); 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; } bool UpdateLogic::refreshGitTagsFile(const QString& outputPath) { m_error.clear(); if (m_serverAddr.isEmpty()) { m_error = QCoreApplication::translate("UpdateLogic", "Server address is empty."); return false; } 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, [&](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; return false; } const QString tagsText = response.value("tags_text").toString(); if (tagsText.isEmpty()) { m_error = QCoreApplication::translate("UpdateLogic", "Git tags response is empty."); return false; } QString writeError; if (!ConfigHelper::writeFileWithElevationIfNeeded(outputPath, tagsText.toUtf8(), &writeError)) { m_error = QCoreApplication::translate("UpdateLogic", "Cannot write Git tags file: %1").arg(writeError); return false; } qDebug() << "Git tags file refreshed:" << outputPath; 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; }); }