documentation rewrite

Signed-off-by: galeon <dertiellis@gmail.com>
This commit is contained in:
galeon committed 2026-10-11 22:32:13 +03:00
1 parent 1943996608
commit f10b654953
34 files changed
+2099 -4576

No files matched your search

+40 -180
View File
@@ -1,200 +1,60 @@
# JavaInstaller
## Обзор класса
`javainstaller.h`, `javainstaller.cpp`
`JavaInstaller` ставит сборку Java в `<root>/java/<id>`. Как и
[ModLoaderInstaller](ModLoaderInstaller.md), он прячет за одним фасадом два разных пути.
Ставит одну сборку Java из каталога [JavaRuntimeService](JavaRuntimeService.md) в
`<root>/java/<id>/`. Сборки Temurin приходят архивом, сборки Mojang — деревом отдельных файлов,
поэтому путей установки два.
**Temurin** отдаёт один архив: лаунчер качает его, сверяет sha256 и распаковывает. **Mojang**
отдаёт манифест с деревом файлов: лаунчер качает файлы по отдельности, как это делает официальный
лаунчер.
Экземпляр держит [LauncherBackend](LauncherBackend.md). Геттеры прогресса такие же, как у других
установщиков: `label`, `stage`, `currentFile`, `bytesDone`, `bytesTotal`, `fraction`, плюс
`runtimeId`. Сигналы: `started(label)`, `progressChanged()` (не чаще раза в 100 мс),
`finished(runtimeId, javaPath)`, `failed(label, message)`, `canceled(label)`.
Набор геттеров прогресса повторяет [VersionInstaller](VersionInstaller.md): панель загрузки в
интерфейсе читает их одинаково, независимо от того, кто сейчас работает.
## install(entry)
## Место в проекте и зависимости
Если установка уже идёт, вызов молча игнорируется. Иначе:
Подключает [javaruntime.h](javaruntime.md): на вход установщик принимает запись каталога
`JavaRuntimeEntry`, а результат записывает через `JavaRuntimeStore`.
1. проверяется папка лаунчера;
2. **папка `<root>/java/<id>` удаляется целиком**, если она была, и создаётся заново. Повторная
установка — это всегда установка с нуля;
3. дальше одна из двух веток.
Экземпляр создаётся и принадлежит [LauncherBackend](LauncherBackend.md); записи каталога приходят
от [JavaRuntimeService](JavaRuntimeService.md).
### Temurin (`zip`, `tar.gz`)
Требования сборки: `Qt6::Core` (`QCryptographicHash`, `QSaveFile`, `QProcess`, `QTimer`),
`Qt6::CorePrivate` (`QZipReader` для распаковки zip) и `Qt6::Network`.
1. Архив качается в `<root>/cache/java/<имя файла>` через `QSaveFile`, sha256 считается на ходу и
сверяется с каталогом.
2. `zip` распаковывается через `QZipReader` по 24 записи за такт таймера, чтобы окно не
подвисало. Права из архива переносятся. Записи, которые ведут за пределы папки сборки,
останавливают установку.
3. `tar.gz` распаковывается системным `tar -xzf`. Он есть на Linux, macOS и Windows 10 и новее.
`QZipReader` tar не умеет, а держать ради этого ещё одну библиотеку незачем.
4. Архив удаляется.
## Иерархия и роль
Прогресс при загрузке считается в байтах, при распаковке zip — в записях. Для tar прогресса нет.
Наследует `QObject`: мета-объектная система, пять сигналов и владение по родителю. Объявлен
виртуальный деструктор — класс владеет незавершёнными загрузками, открытым архивом и процессом
распаковки.
### Mojang
## Публичные структуры
1. Качается `manifest.json` компонента. Его sha1 из каталога сейчас **не проверяется**.
2. Из `files` строится дерево: папки создаются сразу, ссылки откладываются, файлы ставятся в
очередь.
3. До 4 файлов качаются параллельно, sha1 каждого сверяется. При временной ошибке (таймаут,
5xx и т. п.) и при несовпадении хеша файл уходит в конец очереди без паузы. После третьей
неудачи установка падает. Файлам с `executable: true` ставится бит исполнения.
4. Ссылки создаются через `QFile::link`. Если не вышло (на Windows без прав разработчика), файл
копируется.
### JavaFileTask
### Конец
Один файл рантайма Mojang.
`JavaRuntimeStore::locateBinary()` находит `bin/java`. На Unix ему ставится бит исполнения.
`runtime.json` записывается последним: пока его нет, `JavaRuntimeStore` сборку не видит. Поэтому
недокачанная сборка в списке не появится, даже если лаунчер упал на середине.
| Поле | Тип | По умолчанию | Описание |
|------|-----|--------------|----------|
| `url` | `QUrl` | — | Откуда качать |
| `path` | `QString` | — | Абсолютный путь назначения |
| `sha1` | `QString` | — | Контрольная сумма файла |
| `size` | `qint64` | `0` | Размер в байтах |
| `executable` | `bool` | `false` | Файлу нужно выставить право на исполнение — иначе `bin/java` не запустится |
| `attempts` | `int` | `0` | Сколько попыток уже сделано |
При ошибке и при отмене папка сборки и скачанный архив удаляются.
### JavaActiveDownload
## cancel()
Файл в процессе скачивания: задача, сетевой ответ, открытый `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);
```
Обрывает загрузки и убивает `tar`. `canceled` приходит, когда вернутся все активные запросы.
---