#include "ApplyUpdate.h" #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #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 }