202 lines
11 KiB
Markdown
202 lines
11 KiB
Markdown
|
|
# JavaInstaller
|
|||
|
|
|
|||
|
|
## Обзор класса
|
|||
|
|
|
|||
|
|
`JavaInstaller` ставит сборку Java в `<root>/java/<id>`. Как и
|
|||
|
|
[ModLoaderInstaller](ModLoaderInstaller.md), он прячет за одним фасадом два разных пути.
|
|||
|
|
|
|||
|
|
**Temurin** отдаёт один архив: лаунчер качает его, сверяет sha256 и распаковывает. **Mojang**
|
|||
|
|
отдаёт манифест с деревом файлов: лаунчер качает файлы по отдельности, как это делает официальный
|
|||
|
|
лаунчер.
|
|||
|
|
|
|||
|
|
Набор геттеров прогресса повторяет [VersionInstaller](VersionInstaller.md): панель загрузки в
|
|||
|
|
интерфейсе читает их одинаково, независимо от того, кто сейчас работает.
|
|||
|
|
|
|||
|
|
## Место в проекте и зависимости
|
|||
|
|
|
|||
|
|
Подключает [javaruntime.h](javaruntime.md): на вход установщик принимает запись каталога
|
|||
|
|
`JavaRuntimeEntry`, а результат записывает через `JavaRuntimeStore`.
|
|||
|
|
|
|||
|
|
Экземпляр создаётся и принадлежит [LauncherBackend](LauncherBackend.md); записи каталога приходят
|
|||
|
|
от [JavaRuntimeService](JavaRuntimeService.md).
|
|||
|
|
|
|||
|
|
Требования сборки: `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](JavaRuntimeService.md), и переправляет сигналы прогресса в те же свойства,
|
|||
|
|
что и остальные установщики. Сигнал `finished` бэкенд переправляет в QML под собственным именем —
|
|||
|
|
на него подписан диалог настроек, чтобы обновить строку выбранной сборки, когда та докачается.
|
|||
|
|
|
|||
|
|
Раскладку папки `<root>/java`, поиск исполняемого файла и запись описания обеспечивает
|
|||
|
|
`JavaRuntimeStore` из [javaruntime.h](javaruntime.md).
|
|||
|
|
|
|||
|
|
## Внешнее взаимодействие
|
|||
|
|
|
|||
|
|
**Сеть, исходящие запросы.** Загрузка по HTTPS с серверов Adoptium или Mojang. Архив Temurin
|
|||
|
|
скачивается одним запросом с проверкой sha256; дерево Mojang — множеством параллельных запросов,
|
|||
|
|
каждый с проверкой sha1. Неудачная задача повторяется, счётчик попыток хранится в самой задаче.
|
|||
|
|
|
|||
|
|
**Дочерний процесс.** Архивы `tar.gz` распаковываются системным `tar` через `QProcess` —
|
|||
|
|
собственного распаковщика для этого формата в Qt нет. Обмен односторонний: лаунчер запускает
|
|||
|
|
процесс и ждёт его завершения.
|
|||
|
|
|
|||
|
|
**Файловая система.** После распаковки дерева Mojang применяются символические ссылки из
|
|||
|
|
манифеста, а файлам с признаком `executable` выставляется право на исполнение.
|
|||
|
|
|
|||
|
|
Все сигналы приходят в поток GUI.
|
|||
|
|
|
|||
|
|
## Пример использования
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
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);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
При создании этого документа использовался ИИ.
|