12 KiB
VersionInstaller
Обзор класса
Установить версию Minecraft — значит положить в .minecraft пять групп файлов: описание версии,
клиентский jar, библиотеки, индекс ресурсов и сами ресурсы. Последних — десятки тысяч мелких
файлов.
VersionInstaller делает это фоном, не блокируя интерфейс: складывает всё нужное в очередь
загрузок, качает несколько файлов параллельно, пишет их потоком на диск и по ходу сообщает
прогресс. Одна версия ставится за раз, остальные ждут в очереди.
Класс также разворачивает цепочку наследования: если у версии есть inheritsFrom, родительская
версия ставится перед ней.
Место в проекте и зависимости
Подключает minecraftversion.h — по разобранной версии он и понимает, что качать. В конструктор принимает VersionManifestService: оттуда берётся адрес описания версии.
Экземпляр создаётся и принадлежит LauncherBackend. На него же опирается ModLoaderInstaller — профиль модлоадера ставится поверх установленной версии игры.
Требования сборки: 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 держит ссылку на установщик: профиль модлоадера требует, чтобы базовая версия игры была уже на месте.
Разбор описания версии идёт через VersionLoader::load() из
minecraftversion.h.
Внешнее взаимодействие
Сеть, исходящие запросы. Класс качает файлы с серверов Mojang через QNetworkAccessManager.
Направление одностороннее, протокол — HTTPS; описание версии и индекс ресурсов приходят как JSON,
остальное — двоичными файлами.
Каждый файл пишется потоком через QSaveFile с одновременным подсчётом sha1; несовпадение
контрольной суммы считается неудачей загрузки. Неудачная задача повторяется — счётчик попыток
хранится в самой задаче, — и только исчерпав попытки, приводит к сигналу failed.
Существующие файлы сверяются только по размеру: перехеширование сотен мегабайт при каждом добавлении версии дороже, чем риск битого файла.
Все сигналы приходят в поток GUI.
Пример использования
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);
При создании этого документа использовался ИИ.