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

6.5 KiB
Raw Permalink Blame History

BuildSwitcher

buildswitcher.h, buildswitcher.cpp

Смена активной сборки и раскатка пака сезонной сборки. Файлы двигает BuildArchiveWorker в отдельном потоке, а этот класс решает, в каком порядке это делать, и ведёт журнал, по которому можно продолжить после падения.

Экземпляр держит LauncherBackend.

Как устроены сборки на диске

В .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:

{
  "state": "idle",
  "active": 3,
  "builds": {
    "2": { "archive": "2026-10-09_Vanilla.zip", "name": "Vanilla", "savedAt": "..." }
  }
}

Во время работы к нему добавляются from, to, а для раскатки ещё pack, packName, packRemove, packNote. Пишется через QSaveFile.

Деструктор просит работника остановиться и ждёт поток до 5 секунд.


При создании этого документа использовался ИИ.