# BuildSwitcher `buildswitcher.h`, `buildswitcher.cpp` Смена активной сборки и раскатка пака сезонной сборки. Файлы двигает [BuildArchiveWorker](BuildArchiveWorker.md) в отдельном потоке, а этот класс решает, в каком порядке это делать, и ведёт журнал, по которому можно продолжить после падения. Экземпляр держит [LauncherBackend](LauncherBackend.md). ## Как устроены сборки на диске В `.minecraft` в каждый момент разложена одна сборка, активная. Содержимое остальных лежит в архивах `/builds//<дата>_<имя>.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//.tmp-.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/` целиком. - `hasArchive(id)` — есть ли у сборки архив. - `isRunning()`, `stage()`, `status()` (текущий файл), `fraction()` — для панели прогресса. Сигнал `progressChanged()` не ограничен по частоте и приходит на каждый обработанный файл. ### index.json `/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 секунд. --- При создании этого документа использовался ИИ.