1
0
forked from BRT/arc
arc/services/update/ApplyUpdate.cpp

130 lines
4.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "ApplyUpdate.h"
#include <cstring>
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QProcess>
#include <QString>
#include <QStringList>
#include <QTextStream>
#include <QThread>
#ifdef Q_OS_WIN
#include <windows.h>
#endif
namespace {
void logLine(const QString &logPath, const QString &msg) {
QFile f(logPath);
if (f.open(QIODevice::Append | QIODevice::Text)) {
QTextStream(&f) << msg << '\n';
}
fprintf(stderr, "[apply-update] %s\n", msg.toLocal8Bit().constData());
}
QString argValue(const QStringList &args, const QString &key) {
const int idx = args.indexOf(key);
if (idx >= 0 && idx + 1 < args.size()) return args.at(idx + 1);
return QString();
}
} // namespace
bool isApplyUpdateMode(int argc, char **argv) {
for (int i = 1; i < argc; ++i) {
if (std::strcmp(argv[i], "--apply-update") == 0) return true;
}
return false;
}
int runApplyUpdate(int argc, char **argv) {
// Headless-приложение: даёт QProcess/QFile/QDir, но не поднимает GUI.
QCoreApplication app(argc, argv);
const QStringList args = app.arguments();
const QString src = QDir::cleanPath(argValue(args, QStringLiteral("--src")));
const QString dst = QDir::cleanPath(argValue(args, QStringLiteral("--dst")));
const QString relaunch = argValue(args, QStringLiteral("--relaunch"));
const QString pidStr = argValue(args, QStringLiteral("--pid"));
// Корень стейджинга = родитель src (…/ARC_update/unpacked → …/ARC_update).
const QString root = QFileInfo(src).path();
const QString logPath = root + QStringLiteral("/updater.log");
logLine(logPath, QStringLiteral("start: src=%1 dst=%2 pid=%3").arg(src, dst, pidStr));
if (src.isEmpty() || dst.isEmpty() || relaunch.isEmpty()) {
logLine(logPath, QStringLiteral("ERROR: missing arguments"));
return 2;
}
#ifdef Q_OS_WIN
// 1) Ждём выхода родительского процесса.
const DWORD pid = pidStr.toULong();
if (pid != 0) {
HANDLE h = OpenProcess(SYNCHRONIZE, FALSE, pid);
if (h) {
logLine(logPath, QStringLiteral("waiting for parent to exit..."));
WaitForSingleObject(h, 30000);
CloseHandle(h);
}
}
// 2) Settle-цикл: дожидаемся, пока install/ARC.exe можно открыть на запись
// (переживаем антивирус и подвисшие хэндлы). До ~12 секунд.
const QString dstExe = dst + QStringLiteral("/ARC.exe");
if (QFile::exists(dstExe)) {
const std::wstring wexe = QDir::toNativeSeparators(dstExe).toStdWString();
for (int i = 0; i < 12; ++i) {
HANDLE fh = CreateFileW(wexe.c_str(), GENERIC_WRITE, 0, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (fh != INVALID_HANDLE_VALUE) {
CloseHandle(fh);
break;
}
QThread::msleep(1000);
}
}
// 3) Копируем новые файлы поверх install-папки, сохраняя database/.
// robocopy сам ретраит залоченные файлы; коды возврата 07 = успех.
QProcess robo;
robo.start(QStringLiteral("robocopy"), {
src, dst, QStringLiteral("/E"),
QStringLiteral("/XD"), QStringLiteral("database"),
QStringLiteral("/R:5"), QStringLiteral("/W:1"),
QStringLiteral("/NFL"), QStringLiteral("/NDL"),
QStringLiteral("/NJH"), QStringLiteral("/NJS"),
});
robo.waitForFinished(-1);
const int code = robo.exitCode();
logLine(logPath, QStringLiteral("robocopy exit code: %1").arg(code));
if (code >= 8) {
logLine(logPath, QStringLiteral("ERROR: robocopy failed"));
// Всё равно пробуем перезапустить — часть файлов могла скопироваться.
}
// 4) Перезапускаем установленный (уже обновлённый) ARC.exe.
const bool relaunched = QProcess::startDetached(relaunch, {}, dst);
logLine(logPath, QStringLiteral("relaunch %1: %2")
.arg(relaunch, relaunched ? QStringLiteral("ok") : QStringLiteral("FAILED")));
// 5) Самоудаление временной папки: апдейтер не может удалить свой работающий
// exe, поэтому откладываем через cmd с задержкой.
const QString rmCmd = QStringLiteral("ping 127.0.0.1 -n 4 >nul & rmdir /s /q \"%1\"")
.arg(QDir::toNativeSeparators(root));
QProcess::startDetached(QStringLiteral("cmd.exe"),
{QStringLiteral("/c"), rmCmd});
return 0;
#else
Q_UNUSED(relaunch);
logLine(logPath, QStringLiteral("apply-update is Windows-only; no-op"));
return 0;
#endif
}