chore: prepare repository for submodule split
This commit is contained in:
@@ -9,11 +9,11 @@
|
||||
#include <QSet>
|
||||
#include <QUuid>
|
||||
|
||||
UpdateTransaction::UpdateTransaction(const QString& installDir, const QString& fromVersion,
|
||||
UpdateTransaction::UpdateTransaction(const QString& installDir, const QString& updateDir, const QString& fromVersion,
|
||||
const QString& toVersion, int manifestId)
|
||||
: m_installDir(QDir::cleanPath(installDir)),
|
||||
m_updateDir(QDir(installDir).filePath("update")),
|
||||
m_stateFile(QDir(installDir).filePath("update/upgrade_state.json")),
|
||||
m_updateDir(QDir::cleanPath(updateDir.isEmpty() ? QDir(installDir).filePath("update") : updateDir)),
|
||||
m_stateFile(QDir(m_updateDir).filePath("upgrade_state.json")),
|
||||
m_transactionId(QUuid::createUuid().toString(QUuid::WithoutBraces)),
|
||||
m_fromVersion(fromVersion), m_toVersion(toVersion), m_manifestId(manifestId)
|
||||
{
|
||||
@@ -223,9 +223,14 @@ QString UpdateTransaction::backupDir() const { return m_backupDir; }
|
||||
QStringList UpdateTransaction::obsoletePaths() const { return m_obsoletePaths; }
|
||||
QString UpdateTransaction::healthFile() const { return QDir(m_updateDir).filePath("health_" + m_transactionId + ".ok"); }
|
||||
|
||||
bool UpdateTransaction::recoverInterrupted(const QString& installDir, QString* restoredVersion, QString* errorMessage)
|
||||
bool UpdateTransaction::recoverInterrupted(const QString& installDir, const QString& updateDir,
|
||||
QString* restoredVersion, QString* errorMessage)
|
||||
{
|
||||
const QString stateFile = QDir(installDir).filePath("update/upgrade_state.json");
|
||||
QString stateFile = QDir(updateDir.isEmpty() ? QDir(installDir).filePath("update") : updateDir)
|
||||
.filePath("upgrade_state.json");
|
||||
const QString legacyStateFile = QDir(installDir).filePath("update/upgrade_state.json");
|
||||
if (!QFile::exists(stateFile) && QFile::exists(legacyStateFile))
|
||||
stateFile = legacyStateFile;
|
||||
QFile file(stateFile);
|
||||
if (!file.exists()) return true;
|
||||
if (!file.open(QIODevice::ReadOnly)) { if (errorMessage) *errorMessage = "cannot open upgrade_state.json"; return false; }
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
class UpdateTransaction
|
||||
{
|
||||
public:
|
||||
UpdateTransaction(const QString& installDir, const QString& fromVersion,
|
||||
UpdateTransaction(const QString& installDir, const QString& updateDir, const QString& fromVersion,
|
||||
const QString& toVersion, int manifestId);
|
||||
|
||||
bool initialize();
|
||||
@@ -29,7 +29,7 @@ public:
|
||||
QStringList obsoletePaths() const;
|
||||
QString healthFile() const;
|
||||
|
||||
static bool recoverInterrupted(const QString& installDir,
|
||||
static bool recoverInterrupted(const QString& installDir, const QString& updateDir,
|
||||
QString* restoredVersion = nullptr,
|
||||
QString* errorMessage = nullptr);
|
||||
|
||||
|
||||
@@ -263,7 +263,7 @@ QStringList UpdaterLogic::obsoleteFilesComparedTo(const QJsonObject& oldManifest
|
||||
if (isSafeRelativePath(path)) newPaths.insert(path.toCaseFolded());
|
||||
}
|
||||
|
||||
const QSet<QString> protectedPaths{
|
||||
QSet<QString> protectedPaths{
|
||||
QStringLiteral("bootstrap.exe"),
|
||||
QStringLiteral("launcher.exe"),
|
||||
QStringLiteral("updater.exe"),
|
||||
@@ -273,6 +273,17 @@ QStringList UpdaterLogic::obsoleteFilesComparedTo(const QJsonObject& oldManifest
|
||||
QStringLiteral("config/client_identity.dat"),
|
||||
QStringLiteral("config/version_policy.dat")
|
||||
};
|
||||
const QString runtimePrefix = ConfigHelper::instance().runtimeRelativePath().toCaseFolded();
|
||||
if (!runtimePrefix.isEmpty()) {
|
||||
const QStringList runtimeProtected{
|
||||
QStringLiteral("bootstrap.exe"), QStringLiteral("launcher.exe"), QStringLiteral("updater.exe"),
|
||||
QStringLiteral("client.ini"), QStringLiteral("config/app_config.json"),
|
||||
QStringLiteral("config/local_state.json"), QStringLiteral("config/client_identity.dat"),
|
||||
QStringLiteral("config/version_policy.dat")
|
||||
};
|
||||
for (const QString& path : runtimeProtected)
|
||||
protectedPaths.insert(runtimePrefix + "/" + path);
|
||||
}
|
||||
QStringList obsolete;
|
||||
QSet<QString> seen;
|
||||
for (const QJsonValue& value : oldManifest.value("files").toArray()) {
|
||||
@@ -550,7 +561,7 @@ bool UpdaterLogic::downloadSingleFile(const QString& url, const QString& savePat
|
||||
bool UpdaterLogic::isRuntimeProtectedPath(const QString& path) const
|
||||
{
|
||||
const QString normalized = QDir::fromNativeSeparators(path).toCaseFolded();
|
||||
static const QSet<QString> protectedPaths{
|
||||
QSet<QString> protectedPaths{
|
||||
QStringLiteral("bootstrap.exe"),
|
||||
QStringLiteral("client.ini"),
|
||||
QStringLiteral("config/app_config.json"),
|
||||
@@ -558,6 +569,16 @@ bool UpdaterLogic::isRuntimeProtectedPath(const QString& path) const
|
||||
QStringLiteral("config/client_identity.dat"),
|
||||
QStringLiteral("config/version_policy.dat")
|
||||
};
|
||||
const QString runtimePrefix = ConfigHelper::instance().runtimeRelativePath().toCaseFolded();
|
||||
if (!runtimePrefix.isEmpty()) {
|
||||
const QStringList runtimeProtected{
|
||||
QStringLiteral("bootstrap.exe"), QStringLiteral("client.ini"),
|
||||
QStringLiteral("config/app_config.json"), QStringLiteral("config/local_state.json"),
|
||||
QStringLiteral("config/client_identity.dat"), QStringLiteral("config/version_policy.dat")
|
||||
};
|
||||
for (const QString& protectedPath : runtimeProtected)
|
||||
protectedPaths.insert(runtimePrefix + "/" + protectedPath);
|
||||
}
|
||||
return protectedPaths.contains(normalized);
|
||||
}
|
||||
|
||||
@@ -576,7 +597,7 @@ qint64 UpdaterLogic::estimateAdditionalDiskBytes(const QString& targetDir,
|
||||
const QStringList& obsoletePaths) const
|
||||
{
|
||||
qint64 required = 0;
|
||||
const QString cacheDir = QDir(targetDir).filePath("update/download_cache");
|
||||
const QString cacheDir = QDir(ConfigHelper::instance().updateRoot()).filePath("download_cache");
|
||||
for (const auto& item : m_fileItems) {
|
||||
const QString relativePath = QDir::fromNativeSeparators(item.path);
|
||||
if (isRuntimeProtectedPath(relativePath)) continue;
|
||||
@@ -609,7 +630,7 @@ bool UpdaterLogic::downloadAllFiles(const QString& tempDir, const QString& targe
|
||||
QDir stagingDir(tempDir);
|
||||
if (!FileHelper::createDir(tempDir))
|
||||
return false;
|
||||
const QString resumeCacheDir = QDir(targetDir).filePath("update/download_cache");
|
||||
const QString resumeCacheDir = QDir(ConfigHelper::instance().updateRoot()).filePath("download_cache");
|
||||
if (!FileHelper::createDir(resumeCacheDir))
|
||||
return false;
|
||||
QSet<QString> activePartialNames;
|
||||
@@ -727,4 +748,4 @@ void UpdaterLogic::reportResult(const QString& deviceId,
|
||||
QList<FileDownloadItem> UpdaterLogic::getFileList() const
|
||||
{
|
||||
return m_fileItems;
|
||||
}
|
||||
}
|
||||
|
||||
+18
-12
@@ -59,16 +59,19 @@ int main(int argc, char* argv[])
|
||||
bootstrapResult = arg.mid(QString("--bootstrap-resume=").size());
|
||||
}
|
||||
const bool resumingFromBootstrap = !bootstrapResult.isEmpty();
|
||||
const QString targetDir = QApplication::applicationDirPath();
|
||||
const QString runtimeDir = QApplication::applicationDirPath();
|
||||
|
||||
ConfigHelper& config = ConfigHelper::instance();
|
||||
const QString targetDir = config.installRoot();
|
||||
const QString updateDir = config.updateRoot();
|
||||
QDir().mkpath(updateDir);
|
||||
if (appId != config.getValue("App", "app_id") || channel != config.getValue("App", "channel")) {
|
||||
QMessageBox::critical(nullptr, "离线包不适用", "更新包的应用或渠道与本机配置不一致。");
|
||||
return -1;
|
||||
}
|
||||
QString fromVersion = config.getValue("App", "current_version");
|
||||
if (!offlinePackagePath.isEmpty()) {
|
||||
PolicyHelper offlinePolicy(targetDir);
|
||||
PolicyHelper offlinePolicy(runtimeDir);
|
||||
if (!offlinePolicy.loadPolicy() || !offlinePolicy.isValid() || offlinePolicy.isExpired()
|
||||
|| !offlinePolicy.isOfflineAllowed() || !offlinePolicy.isVersionAllowed(targetVersion)) {
|
||||
QMessageBox::critical(nullptr, "离线更新被拒绝", "本地签名策略已过期、禁止离线更新或不允许目标版本。");
|
||||
@@ -87,7 +90,7 @@ int main(int argc, char* argv[])
|
||||
if (!resumingFromBootstrap) {
|
||||
QString restoredVersion;
|
||||
QString recoveryError;
|
||||
if (!UpdateTransaction::recoverInterrupted(targetDir, &restoredVersion, &recoveryError)) {
|
||||
if (!UpdateTransaction::recoverInterrupted(targetDir, updateDir, &restoredVersion, &recoveryError)) {
|
||||
QMessageBox::critical(nullptr, "更新恢复失败",
|
||||
QString("检测到上次更新未完成,但无法恢复旧版本:%1\n请不要继续运行软件,并联系管理员。").arg(recoveryError));
|
||||
return -1;
|
||||
@@ -111,7 +114,7 @@ int main(int argc, char* argv[])
|
||||
progress.show();
|
||||
QApplication::processEvents();
|
||||
|
||||
UpdateTransaction transaction(targetDir, fromVersion, targetVersion, targetVersionId);
|
||||
UpdateTransaction transaction(targetDir, updateDir, fromVersion, targetVersion, targetVersionId);
|
||||
const auto setProgress = [&](int value, const QString& message) {
|
||||
progress.setValue(value);
|
||||
progress.setLabelText(message);
|
||||
@@ -160,9 +163,9 @@ int main(int argc, char* argv[])
|
||||
bool timeoutOk = false;
|
||||
int healthCheckTimeoutMs = config.getValue("Runtime", "health_check_timeout_ms").toInt(&timeoutOk);
|
||||
if (!timeoutOk || healthCheckTimeoutMs < 1000) healthCheckTimeoutMs = 15000;
|
||||
const QString mainAppPath = QDir(targetDir).filePath(mainExecutable);
|
||||
const QString updaterPath = QDir(targetDir).filePath(updaterExecutable);
|
||||
const QString bootstrapPath = QDir(targetDir).filePath(bootstrapExecutable);
|
||||
const QString mainAppPath = QDir(runtimeDir).filePath(mainExecutable);
|
||||
const QString updaterPath = QDir(runtimeDir).filePath(updaterExecutable);
|
||||
const QString bootstrapPath = QDir(runtimeDir).filePath(bootstrapExecutable);
|
||||
const QString launchToken = config.getValue("App", "launch_token");
|
||||
const auto launchMainApp = [&](const QString& healthFile = QString()) {
|
||||
QString ticketPath;
|
||||
@@ -180,7 +183,7 @@ int main(int argc, char* argv[])
|
||||
return started;
|
||||
};
|
||||
const auto bootstrapPlanFile = [&]() {
|
||||
return QDir(targetDir).filePath("update/bootstrap_plan_" + transaction.transactionId() + ".txt");
|
||||
return QDir(updateDir).filePath("bootstrap_plan_" + transaction.transactionId() + ".txt");
|
||||
};
|
||||
const auto launchBootstrap = [&](const QString& mode) {
|
||||
const QStringList args{
|
||||
@@ -245,7 +248,7 @@ int main(int argc, char* argv[])
|
||||
}
|
||||
|
||||
setProgress(resumingFromBootstrap ? 72 : 10, offlinePackagePath.isEmpty() ? "正在获取并验证版本清单..." : "正在验证离线更新包...");
|
||||
const QString manifestCacheDir = QDir(targetDir).filePath("update/manifest_cache");
|
||||
const QString manifestCacheDir = QDir(updateDir).filePath("manifest_cache");
|
||||
if (resumingFromBootstrap) {
|
||||
if (!logic.loadManifestCache(manifestCacheDir, targetVersion))
|
||||
return delegateRollback("清单缓存失败", "Bootstrap 安装后无法读取签名 Manifest 缓存。");
|
||||
@@ -282,7 +285,7 @@ int main(int argc, char* argv[])
|
||||
if (logic.getFileList().isEmpty())
|
||||
return fail("没有可更新文件", "服务器没有返回任何版本文件,更新已停止。", "empty_file_list");
|
||||
|
||||
QStorageInfo storage(targetDir);
|
||||
QStorageInfo storage(updateDir);
|
||||
storage.refresh();
|
||||
const qint64 requiredBytes = logic.estimateAdditionalDiskBytes(targetDir, obsoletePaths);
|
||||
const qint64 availableBytes = storage.bytesAvailable();
|
||||
@@ -316,9 +319,12 @@ int main(int argc, char* argv[])
|
||||
QDirIterator stagingFiles(stagingDir, QDir::Files, QDirIterator::Subdirectories);
|
||||
while (stagingFiles.hasNext())
|
||||
changedPaths.append(QDir::fromNativeSeparators(stagingRoot.relativeFilePath(stagingFiles.next())));
|
||||
const QString runtimePrefix = config.runtimeRelativePath();
|
||||
const QString bootstrapManifestPath = QDir::fromNativeSeparators(
|
||||
runtimePrefix.isEmpty() ? bootstrapExecutable : runtimePrefix + "/" + bootstrapExecutable);
|
||||
for (const QString& path : changedPaths) {
|
||||
if (path.compare(bootstrapExecutable, Qt::CaseInsensitive) == 0)
|
||||
return fail("Bootstrap 无法自更新", QString("本次版本包含新的 %1。请使用安装包升级该组件,再重新发布业务版本。").arg(bootstrapExecutable), "bootstrap_self_update_blocked");
|
||||
if (path.compare(bootstrapManifestPath, Qt::CaseInsensitive) == 0)
|
||||
return fail("Bootstrap 无法自更新", QString("本次版本包含新的 %1。请使用安装包升级该组件,再重新发布业务版本。").arg(bootstrapManifestPath), "bootstrap_self_update_blocked");
|
||||
}
|
||||
if (!transaction.recordVerifiedFiles(changedPaths, obsoletePaths))
|
||||
return fail("事务记录失败", "无法保存已校验或待删除文件列表,更新已停止。", "transaction_record_failed");
|
||||
|
||||
Reference in New Issue
Block a user