130 lines
4.9 KiB
C++
130 lines
4.9 KiB
C++
#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 сам ретраит залоченные файлы; коды возврата 0–7 = успех.
|
||
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
|
||
}
|