Files
minecraft-launcher/doc/cpp/VersionInstaller.md
T
2026-09-03 09:16:56 +03:00

212 lines
12 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.
# VersionInstaller
## Обзор класса
Установить версию Minecraft — значит положить в `.minecraft` пять групп файлов: описание версии,
клиентский jar, библиотеки, индекс ресурсов и сами ресурсы. Последних — десятки тысяч мелких
файлов.
`VersionInstaller` делает это фоном, не блокируя интерфейс: складывает всё нужное в очередь
загрузок, качает несколько файлов параллельно, пишет их потоком на диск и по ходу сообщает
прогресс. Одна версия ставится за раз, остальные ждут в очереди.
Класс также разворачивает цепочку наследования: если у версии есть `inheritsFrom`, родительская
версия ставится перед ней.
## Место в проекте и зависимости
Подключает [minecraftversion.h](minecraftversion.md) — по разобранной версии он и понимает, что
качать. В конструктор принимает [VersionManifestService](VersionManifestService.md): оттуда
берётся адрес описания версии.
Экземпляр создаётся и принадлежит [LauncherBackend](LauncherBackend.md). На него же опирается
[ModLoaderInstaller](ModLoaderInstaller.md) — профиль модлоадера ставится поверх установленной
версии игры.
Требования сборки: `Qt6::Core` (`QCryptographicHash`, `QSaveFile`, `QQueue`, `QTimer`) и
`Qt6::Network` (`QNetworkAccessManager`, `QNetworkReply`).
## Иерархия и роль
Наследует `QObject`: мета-объектная система, пять сигналов и владение по родителю. Объявлен
виртуальный деструктор — класс владеет незавершёнными загрузками и обязан их закрыть.
## Публичные структуры
### DownloadTask
Один файл, который нужно положить в `.minecraft`.
| Поле | Тип | По умолчанию | Описание |
|------|-----|--------------|----------|
| `kind` | `DownloadTask::Kind` | `ClientJar` | Что это за файл |
| `url` | `QUrl` | — | Откуда качать |
| `path` | `QString` | — | Абсолютный путь назначения |
| `sha1` | `QString` | — | Контрольная сумма; пустая означает «не проверять» |
| `size` | `qint64` | `0` | Ожидаемый размер; `0` — неизвестен |
| `label` | `QString` | — | Что показать в панели прогресса |
| `attempts` | `int` | `0` | Сколько попыток уже сделано |
#### Перечисление DownloadTask::Kind
| Значение | Описание |
|----------|----------|
| `ClientJar` | Клиентский jar версии |
| `Library` | Библиотека из `libraries/` |
| `AssetIndex` | Индекс ресурсов |
| `AssetObject` | Один файл ресурсов |
| `LoggingConfig` | Конфигурация журналирования log4j |
### ActiveDownload
Файл в процессе скачивания: задача, сетевой ответ, открытый `QSaveFile`, накапливаемая
контрольная сумма и число принятых байт. Файлы пишутся потоком — держать десятки мегабайт в
памяти незачем, а нескольких параллельных загрузок хватило бы на сотни.
## Публичные методы
#### explicit VersionInstaller(VersionManifestService \*manifest, QObject \*parent = nullptr)
Создаёт установщик поверх сервиса манифеста. Сервис не переходит во владение установщика и обязан
пережить его. Конструктор помечен `explicit`.
#### bool isRunning() const
Идёт ли установка прямо сейчас.
#### QString versionId() const
Идентификатор версии, которая ставится в данный момент.
#### QString stage() const
Текущий этап установки словами — это же значение показывается в заголовке панели прогресса.
#### QString currentFile() const
Подпись файла, который качается сейчас.
#### qint64 bytesDone() const
Сколько байт уже получено, с учётом идущих загрузок.
#### qint64 bytesTotal() const
Ожидаемый общий объём. Растёт по ходу установки: полный размер ресурсов становится известен
только после разбора их индекса.
#### double fraction() const
Доля выполнения от `0` до `1` либо `-1`, пока итоговый объём неизвестен. Значение `-1` панель
прогресса показывает многоточием вместо процентов.
#### void install(const QString &gameDir, const QString &versionId)
Ставит версию в указанную папку игры. Если установка уже идёт, версия становится в очередь.
Порядок работы: разрешение записи манифеста, загрузка описания версии, подготовка списка задач,
скачивание, разворачивание индекса ресурсов и — для версий до 1.6 — раскладка ресурсов в плоскую
папку, которую те версии умеют читать.
#### bool isQueued(const QString &versionId) const
Стоит ли версия в очереди на установку. Позволяет не ставить одну и ту же версию дважды.
#### void cancel()
Отменяет текущую установку и очищает очередь. Незавершённые файлы не остаются на диске: они
пишутся через `QSaveFile` и фиксируются только целиком.
## Сигналы
#### started(const QString &versionId)
Установка версии началась. Обработчик показывает панель прогресса и выставляет признак занятости.
#### progressChanged()
Изменились числа прогресса. Испускается не чаще десяти раз в секунду: при тысячах мелких файлов
сигнал на каждый принятый блок обошёлся бы дороже самой загрузки.
Обработчик перечитывает `stage()`, `currentFile()`, `bytesDone()`, `bytesTotal()` и `fraction()`.
#### finished(const QString &versionId)
Версия установлена успешно. Обработчик убирает панель прогресса, обновляет список установленных
версий и пересчитывает комплектность сборок.
#### failed(const QString &versionId, const QString &message)
Установка не удалась; в параметре — текст ошибки для пользователя. Часть файлов при этом может
остаться на диске: повторная установка докачает недостающее.
#### canceled(const QString &versionId)
Установка отменена пользователем. В отличие от `failed`, ошибку показывать не нужно.
## Владение и время жизни
Класс наследует `QObject` и принимает `parent` — родитель его и удалит.
Указатель на `VersionManifestService`, переданный в конструктор, **не принадлежит** установщику:
сервис создаётся раньше и живёт дольше. `QNetworkAccessManager` и оба таймера создаются в
конструкторе с установщиком в роли родителя.
Активные загрузки хранятся как `std::shared_ptr<ActiveDownload>`, а файл внутри каждой — как
`std::unique_ptr<QSaveFile>`: незавершённая запись отменяется вместе с уничтожением объекта, и
испорченный файл не попадает на место назначения.
## Потокобезопасность
Только поток GUI, как и остальной сетевой код лаунчера. Параллелизм здесь — не потоки, а
несколько одновременных сетевых запросов в одном цикле событий. Раскладка ресурсов для старых
версий выполняется порциями по таймеру, чтобы не занимать поток надолго.
## Взаимодействие с другими классами
`LauncherBackend` вызывает `install()` при установке сборки и переправляет все пять сигналов в
свойства, которые читает QML: панель прогресса главного окна показывает `stage()`, `fraction()` и
байты, а `finished` обновляет список установленных версий.
[ModLoaderInstaller](ModLoaderInstaller.md) держит ссылку на установщик: профиль модлоадера
требует, чтобы базовая версия игры была уже на месте.
Разбор описания версии идёт через `VersionLoader::load()` из
[minecraftversion.h](minecraftversion.md).
## Внешнее взаимодействие
**Сеть, исходящие запросы.** Класс качает файлы с серверов Mojang через `QNetworkAccessManager`.
Направление одностороннее, протокол — HTTPS; описание версии и индекс ресурсов приходят как JSON,
остальное — двоичными файлами.
Каждый файл пишется потоком через `QSaveFile` с одновременным подсчётом sha1; несовпадение
контрольной суммы считается неудачей загрузки. Неудачная задача повторяется — счётчик попыток
хранится в самой задаче, — и только исчерпав попытки, приводит к сигналу `failed`.
Существующие файлы сверяются только по размеру: перехеширование сотен мегабайт при каждом
добавлении версии дороже, чем риск битого файла.
Все сигналы приходят в поток GUI.
## Пример использования
```cpp
auto *installer = new VersionInstaller(manifestService, this);
connect(installer, &VersionInstaller::progressChanged, this, [this, installer] {
emit downloadProgress(installer->fraction(), installer->currentFile());
});
connect(installer, &VersionInstaller::finished, this, &Backend::onVersionInstalled);
connect(installer, &VersionInstaller::failed, this, [this](const QString &id, const QString &message) {
emit launchError(tr("Не удалось установить %1: %2").arg(id, message));
});
if (!installer->isQueued(versionId))
installer->install(gameDir, versionId);
```
---
При создании этого документа использовался ИИ.