Files
BiLauncher/doc/cpp/BuildSwitcher.md
T
2026-10-11 22:32:13 +03:00

106 lines
6.5 KiB
Markdown
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.
# BuildSwitcher
`buildswitcher.h`, `buildswitcher.cpp`
Смена активной сборки и раскатка пака сезонной сборки. Файлы двигает
[BuildArchiveWorker](BuildArchiveWorker.md) в отдельном потоке, а этот класс решает, в каком порядке
это делать, и ведёт журнал, по которому можно продолжить после падения.
Экземпляр держит [LauncherBackend](LauncherBackend.md).
## Как устроены сборки на диске
В `.minecraft` в каждый момент разложена одна сборка, активная. Содержимое остальных лежит в
архивах `<root>/builds/<id>/<дата>_<имя>.zip`, по одному на сборку.
Общие для всех сборок папки и файлы никогда не архивируются и не удаляются (`sharedTopLevel()`):
- `assets`
- `libraries`
- `versions`
- `launcher_profiles.json`
- `runtime`
Всё остальное (моды, конфиги, миры, скриншоты, `options.txt`) принадлежит сборке.
В папке сборки могут лежать и другие zip, но файлы с точкой в начале имени не считаются архивом
сборки. Это временные архивы (`.tmp-*.zip`) и скачанные паки сезонных сборок (`.pack-r*.zip`).
## switchTo(fromId, fromName, toId, toName, gameDir)
1. **Архивация.** Если прежней сборки нет (`fromId < 0`) или папки игры нет, шаг пропускается.
Иначе сначала проверяется свободное место: размер всего, что уйдёт в архив, плюс 256 МБ. Потом
архив пишется во временный `builds/<from>/.tmp-<uuid>.zip`. Если на этом шаге что-то пошло не
так, `.minecraft` не тронута.
2. **Фиксация архива.** Временный файл переименовывается в `<гггг-мм-дд>_<имя>.zip`, остальные
архивы этой сборки удаляются. В `index.json` записывается `state: "switching"` с `from` и `to`.
3. **Очистка.** Из `.minecraft` удаляется всё, кроме общего.
4. **Распаковка.** Если у новой сборки есть архив, он распаковывается. Если нет (новая сборка),
`.minecraft` остаётся пустой, только с общими папками.
5. В `index.json` записываются `state: "idle"` и `active`, испускается `finished(toId)`.
При ошибке приходит `failed(toId, message, gameDirIntact)`. `gameDirIntact` равен `true`, если
ошибка случилась до очистки. Бэкенд по нему выбирает текст: «ничего не изменилось» или «папка
игры разобрана».
`cancel()` работает только на шаге архивации. После него отступать уже некуда.
## applyPack(buildId, buildName, packZip, removeRelative, note, gameDir)
Раскатка пака поверх активной сборки. Сначала в `index.json` пишется `state: "applying"` со всем,
что нужно для повтора: имя файла пака, имя сборки, список на удаление и `note`. Потом работник
удаляет файлы прошлой ревизии и распаковывает пак.
После успеха пак удаляется, `index.json` возвращается в `idle`, а `lastPackEntries()` и
`lastPackNote()` содержат список разложенных файлов и `note`. Бэкенд забирает их в обработчике
`finished` и пишет `season.json`.
`gameDirIntact` при ошибке раскатки равен `true`, только если работник не успел обработать ни одного
файла.
## После падения
При старте бэкенд вызывает `interruptedSwitchWarning()`. Если в `index.json` осталось
`switching` или `applying`, метод возвращает текст для пользователя, а `resumeInterrupted(gameDir)`
продолжает операцию:
- `switching` — архив прежней сборки уже записан целиком, поэтому начинаем с очистки и
распаковываем сборку `to`. Начинать с архивации нельзя: заархивировалась бы полуразобранная
`.minecraft`;
- `applying` — раскатка повторяется с тем же паком и тем же списком на удаление. Если файла пака
уже нет, журнал просто сбрасывается в `idle`.
Если программа упала во время самой архивации, в журнале ещё ничего нет, и продолжать нечего:
`.minecraft` цела, а временный архив удалится при следующем старте (`sweepTempArchives()` в
конструкторе).
## Прочее
- `forgetBuild(id)` — удаляет запись о сборке из `index.json` и папку `builds/<id>` целиком.
- `hasArchive(id)` — есть ли у сборки архив.
- `isRunning()`, `stage()`, `status()` (текущий файл), `fraction()` — для панели прогресса.
Сигнал `progressChanged()` не ограничен по частоте и приходит на каждый обработанный файл.
### index.json
`<root>/builds/index.json`:
```json
{
"state": "idle",
"active": 3,
"builds": {
"2": { "archive": "2026-10-09_Vanilla.zip", "name": "Vanilla", "savedAt": "..." }
}
}
```
Во время работы к нему добавляются `from`, `to`, а для раскатки ещё `pack`, `packName`,
`packRemove`, `packNote`. Пишется через `QSaveFile`.
Деструктор просит работника остановиться и ждёт поток до 5 секунд.
---
При создании этого документа использовался ИИ.