167 lines
11 KiB
Markdown
167 lines
11 KiB
Markdown
|
|
# 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));
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
При создании этого документа использовался ИИ.
|