11 KiB
JavaInstaller
Обзор класса
JavaInstaller ставит сборку Java в <root>/java/<id>. Как и
ModLoaderInstaller, он прячет за одним фасадом два разных пути.
Temurin отдаёт один архив: лаунчер качает его, сверяет sha256 и распаковывает. Mojang отдаёт манифест с деревом файлов: лаунчер качает файлы по отдельности, как это делает официальный лаунчер.
Набор геттеров прогресса повторяет VersionInstaller: панель загрузки в интерфейсе читает их одинаково, независимо от того, кто сейчас работает.
Место в проекте и зависимости
Подключает javaruntime.h: на вход установщик принимает запись каталога
JavaRuntimeEntry, а результат записывает через JavaRuntimeStore.
Экземпляр создаётся и принадлежит LauncherBackend; записи каталога приходят от JavaRuntimeService.
Требования сборки: Qt6::Core (QCryptographicHash, QSaveFile, QProcess, QTimer),
Qt6::CorePrivate (QZipReader для распаковки zip) и Qt6::Network.
Иерархия и роль
Наследует QObject: мета-объектная система, пять сигналов и владение по родителю. Объявлен
виртуальный деструктор — класс владеет незавершёнными загрузками, открытым архивом и процессом
распаковки.
Публичные структуры
JavaFileTask
Один файл рантайма Mojang.
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
url |
QUrl |
— | Откуда качать |
path |
QString |
— | Абсолютный путь назначения |
sha1 |
QString |
— | Контрольная сумма файла |
size |
qint64 |
0 |
Размер в байтах |
executable |
bool |
false |
Файлу нужно выставить право на исполнение — иначе bin/java не запустится |
attempts |
int |
0 |
Сколько попыток уже сделано |
JavaActiveDownload
Файл в процессе скачивания: задача, сетевой ответ, открытый QSaveFile, накапливаемая
контрольная сумма и число принятых байт. Файлы пишутся потоком — рантайм весит около двухсот
мегабайт.
Структура ZipExtraction, хранящая состояние распаковки, объявлена вперёд и спрятана в
.cpp: так приватный заголовок QZipReader не расходится по проекту вместе с этим заголовком.
Публичные методы
explicit JavaInstaller(QObject *parent = nullptr)
Создаёт установщик, его QNetworkAccessManager и два таймера. Конструктор помечен explicit.
bool isRunning() const
Идёт ли установка прямо сейчас.
QString runtimeId() const
Идентификатор устанавливаемой сборки.
QString label() const
Подпись установки для интерфейса.
QString stage() const
Текущий этап словами: загрузка, проверка, распаковка.
QString currentFile() const
Файл, который обрабатывается сейчас.
qint64 bytesDone() const
Сколько байт уже получено. Байты считаются только на загрузке: на распаковке считать нечего, и панель по нулевому итогу сама прячет мегабайты.
qint64 bytesTotal() const
Ожидаемый общий объём загрузки.
double fraction() const
Доля выполнения от 0 до 1 либо -1, пока итог неизвестен. На этапе распаковки доля считается
по числу обработанных записей архива или файлов дерева, а не по байтам.
void install(const JavaRuntimeEntry &entry)
Ставит сборку по записи каталога. Путь установки выбирается по полю archive записи: значения
zip и tar.gz ведут по пути Temurin, значение mojang — по пути манифеста.
По завершении установщик находит исполняемый файл java в распакованном дереве через
JavaRuntimeStore::locateBinary() и записывает описание сборки рядом с ней.
Одновременно ставится одна сборка; очереди у этого установщика нет.
void cancel()
Отменяет установку. Отмена проверяется между файлами и между кусками распаковки, поэтому срабатывает не мгновенно, но без замораживания интерфейса.
Сигналы
started(const QString &label)
Установка началась. Обработчик показывает панель прогресса.
progressChanged()
Изменились числа прогресса; испускается не чаще, чем позволяет внутренний таймер. Обработчик перечитывает геттеры.
finished(const QString &runtimeId, const QString &javaPath)
Сборка установлена; во втором параметре — абсолютный путь к исполняемому файлу java.
Обработчик обновляет каталог: сборка становится помеченной как скачанная, а диалог настроек перечитывает её описание.
failed(const QString &label, const QString &message)
Установка не удалась; в параметре — текст ошибки для пользователя.
canceled(const QString &label)
Установка отменена пользователем.
Владение и время жизни
Класс наследует QObject и принимает parent — родитель его и удалит.
QNetworkAccessManager и оба таймера создаются в конструкторе с установщиком в роли родителя.
Владение внутренними ресурсами построено на RAII: скачиваемый архив хранится как
std::unique_ptr<QSaveFile>, состояние распаковки — как std::unique_ptr<ZipExtraction>,
активные загрузки — как std::shared_ptr<JavaActiveDownload> с собственным QSaveFile внутри.
Незавершённая запись отменяется вместе с уничтожением объекта, и испорченный файл не попадает на
место назначения.
Процесс tar, используемый для распаковки архивов tar.gz, создаётся по ходу работы и
завершается в деструкторе.
Потокобезопасность
Только поток GUI. Отдельного потока у класса нет намеренно: распаковка zip идёт по кускам по
таймеру — держать поток GUI занятым на всю сотню мегабайт нельзя, а заводить поток ради одной
операции незачем. Распаковка tar.gz отдана внешнему процессу, который работает параллельно сам.
Взаимодействие с другими классами
LauncherBackend вызывает install() с записью, полученной от
JavaRuntimeService, и переправляет сигналы прогресса в те же свойства,
что и остальные установщики. Сигнал finished бэкенд переправляет в QML под собственным именем —
на него подписан диалог настроек, чтобы обновить строку выбранной сборки, когда та докачается.
Раскладку папки <root>/java, поиск исполняемого файла и запись описания обеспечивает
JavaRuntimeStore из javaruntime.h.
Внешнее взаимодействие
Сеть, исходящие запросы. Загрузка по HTTPS с серверов Adoptium или Mojang. Архив Temurin скачивается одним запросом с проверкой sha256; дерево Mojang — множеством параллельных запросов, каждый с проверкой sha1. Неудачная задача повторяется, счётчик попыток хранится в самой задаче.
Дочерний процесс. Архивы tar.gz распаковываются системным tar через QProcess —
собственного распаковщика для этого формата в Qt нет. Обмен односторонний: лаунчер запускает
процесс и ждёт его завершения.
Файловая система. После распаковки дерева Mojang применяются символические ссылки из
манифеста, а файлам с признаком executable выставляется право на исполнение.
Все сигналы приходят в поток GUI.
Пример использования
auto *javaInstaller = new JavaInstaller(this);
connect(javaInstaller, &JavaInstaller::finished, this,
[this](const QString &runtimeId, const QString &javaPath) {
m_settings.javaRuntime = runtimeId;
m_resolvedJavaPath = javaPath;
emit javaRuntimeInstalled(runtimeId);
});
const auto entry = javaCatalog->find(runtimeId);
if (entry)
javaInstaller->install(*entry);
При создании этого документа использовался ИИ.