# JavaInstaller ## Обзор класса `JavaInstaller` ставит сборку Java в `/java/`. Как и [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`, состояние распаковки — как `std::unique_ptr`, активные загрузки — как `std::shared_ptr` с собственным `QSaveFile` внутри. Незавершённая запись отменяется вместе с уничтожением объекта, и испорченный файл не попадает на место назначения. Процесс `tar`, используемый для распаковки архивов `tar.gz`, создаётся по ходу работы и завершается в деструкторе. ## Потокобезопасность Только поток GUI. Отдельного потока у класса нет намеренно: распаковка zip идёт по кускам по таймеру — держать поток GUI занятым на всю сотню мегабайт нельзя, а заводить поток ради одной операции незачем. Распаковка `tar.gz` отдана внешнему процессу, который работает параллельно сам. ## Взаимодействие с другими классами `LauncherBackend` вызывает `install()` с записью, полученной от [JavaRuntimeService](JavaRuntimeService.md), и переправляет сигналы прогресса в те же свойства, что и остальные установщики. Сигнал `finished` бэкенд переправляет в QML под собственным именем — на него подписан диалог настроек, чтобы обновить строку выбранной сборки, когда та докачается. Раскладку папки `/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); ``` --- При создании этого документа использовался ИИ.