# BuildArchiveWorker ## Обзор класса Смена сборки в лаунчере — это перекладывание содержимого `.minecraft`: текущее упаковывается в архив, папка вычищается, на её место распаковывается архив другой сборки. Речь о гигабайтах модов, конфигов и миров, и гонять их в потоке GUI нельзя — окно замерзало бы на всё время смены. `BuildArchiveWorker` — исполнитель этих операций в отдельном потоке. Он умеет четыре вещи: упаковать, вычистить, распаковать и докатить пак сезонной сборки поверх уже разложенного содержимого. Класс намеренно ничего не знает ни о сборках, ни о путях лаунчера: он принимает готовые пути и списки. Порядок шагов и восстановление после сбоя — дело [BuildSwitcher](BuildSwitcher.md). ## Место в проекте и зависимости Создаётся и целиком управляется [BuildSwitcher](BuildSwitcher.md), который же и переносит его в собственный `QThread`. Больше к классу никто не обращается. Требования сборки: `Qt6::Core` и `Qt6::CorePrivate` — последний нужен ради `QZipReader` и `QZipWriter`, которыми читаются и пишутся архивы сборок. ## Иерархия и роль Наследует `QObject`: мета-объектная система, слоты, сигналы и возможность жить в отдельном потоке через `moveToThread()`. Виртуальных методов базового класса не переопределяет. Объект живёт в своём `QThread` и вызывается только через очередь сигналов — прямых вызовов его слотов из потока GUI быть не должно. ## Публичные методы #### explicit BuildArchiveWorker(QObject \*parent = nullptr) Создаёт исполнителя. Конструктор помечен `explicit`. #### void requestCancel() Просит прервать текущую операцию. Вызывается из потока GUI прямо во время работы — это единственный метод класса, предназначенный для вызова снаружи рабочего потока. Отмена не прерывает операцию мгновенно: признак проверяется между файлами. Хранится он в `QAtomicInt`, поэтому запись из одного потока и чтение из другого безопасны без блокировок. #### void clearCancel() Сбрасывает признак отмены перед началом новой операции. ## Публичные слоты Все четыре слота вызываются через очередь сигналов и выполняются в рабочем потоке. Каждый завершается сигналом `finished`. #### void archive(const QString &gameDir, const QString &tempZipPath, const QStringList &excludeTop) Упаковывает всё содержимое `gameDir` во временный файл `tempZipPath`. Элементы верхнего уровня, перечисленные в `excludeTop`, в архив не попадают — так из архива сборки исключаются общие каталоги лаунчера, которые не принадлежат ни одной сборке. Запись идёт во временный файл, чтобы прерванная упаковка не оставила повреждённый архив на месте настоящего. #### void clear(const QString &gameDir, const QStringList &keepTop) Удаляет из `gameDir` всё, кроме элементов верхнего уровня, перечисленных в `keepTop`. Выполняется после упаковки, перед распаковкой другой сборки. #### void restore(const QString &zipPath, const QString &gameDir) Распаковывает архив сборки в `gameDir`. #### void applyPack(const QString &zipPath, const QString &gameDir, const QStringList &removeRelative, const QStringList &forbiddenTop) Докатывает пак сезонной сборки поверх уже разложенного содержимого. Сначала удаляет файлы из `removeRelative` — те, что ушли из сборки в новой ревизии, — затем распаковывает архив с перезаписью. Того, чего нет ни в списке, ни в архиве, операция не касается: миры и скриншоты игрока остаются на месте. Именно это отличает обновление сезонной сборки от её переустановки. Параметр `forbiddenTop` — элементы верхнего уровня, которые паку трогать нельзя: общие каталоги лаунчера. Проверяется здесь, а не только у издателя пака, потому что архив приезжает из сети. ## Сигналы #### progress(int done, int total, const QString ¤tPath) Ход операции: сколько файлов обработано из скольких и какой обрабатывается сейчас. Испускается по ходу всех четырёх операций. Обработчик — `BuildSwitcher` — пересчитывает долю выполнения и обновляет панель прогресса. Сигнал приходит в поток GUI через очередь, поэтому прямого доступа к состоянию рабочего потока у обработчика нет. #### finished(bool ok, const QString &error) Операция завершена. При `ok == false` во втором параметре — текст ошибки. Обработчик решает, что делать дальше: перейти к следующему шагу смены сборки или откатить уже сделанное. #### packEntries(const QStringList &entries) Что именно принёс пак — относительными путями. Испускается только из `applyPack()`. Список сохраняется в описании сезонной сборки: следующему обновлению он нужен, чтобы вычислить, какие файлы из сборки ушли, и передать их в `removeRelative`. ## Владение и время жизни Класс наследует `QObject` и принимает `parent`, но на практике родителя не имеет: объект, перенесённый в другой поток через `moveToThread()`, не может иметь родителя в потоке GUI. Ответственность за его удаление лежит на `BuildSwitcher`, который создаёт и поток, и исполнителя. Удалять объект следует безопасным для потоков способом — не напрямую из потока GUI во время работы. Уничтожение потока раньше исполнителя приведёт к обрыву незавершённой операции. ## Потокобезопасность Класс рассчитан на жизнь в отдельном потоке. Все четыре слота выполняются в рабочем потоке; вызывать их напрямую нельзя — только через очередь сигналов. Единственная точка межпотокового взаимодействия — признак отмены в `QAtomicInt`: `requestCancel()` и `clearCancel()` пишут его из потока GUI, а рабочий поток читает между файлами. Другого разделяемого состояния у класса нет, поэтому иных блокировок не требуется. ## Взаимодействие с другими классами Единственный собеседник — [BuildSwitcher](BuildSwitcher.md). Он создаёт поток и исполнителя, переносит второй в первый, вызывает слоты через очередь сигналов и принимает `progress`, `finished` и `packEntries`. Пути и списки исключений `BuildSwitcher` берёт из [launcherpaths.h](launcherpaths.md) и из описаний сборок; сам исполнитель к ним не обращается. ## Внешнее взаимодействие **Файловая система.** Класс читает, пишет и удаляет файлы в папке игры и в каталоге архивов сборок. Сетевых обращений и дочерних процессов у него нет: архив сезонной сборки к моменту вызова `applyPack()` уже скачан [SeasonalPackDownloader](SeasonalPackDownloader.md). Все операции выполняются в рабочем потоке, все сигналы приходят в поток GUI через очередь. ## Пример использования ```cpp auto *thread = new QThread(this); auto *worker = new BuildArchiveWorker; // без родителя: уедет в другой поток worker->moveToThread(thread); connect(thread, &QThread::finished, worker, &QObject::deleteLater); thread->start(); connect(worker, &BuildArchiveWorker::progress, this, &Switcher::onProgress); connect(worker, &BuildArchiveWorker::finished, this, &Switcher::onStepFinished); worker->clearCancel(); QMetaObject::invokeMethod(worker, "archive", Qt::QueuedConnection, Q_ARG(QString, gameDir), Q_ARG(QString, tempZipPath), Q_ARG(QStringList, excludeTop)); ``` --- При создании этого документа использовался ИИ.