# VersionManifestService ## Обзор класса Каталог версий Minecraft — это манифест Mojang: около тысячи записей от альф 2010 года до свежайших снапшотов. `VersionManifestService` отвечает за него целиком: скачивает манифест, кэширует в папке лаунчера и отдаёт из кэша, пока тот не устарел. Класс нужен двум потребителям: окну выбора версии, которому нужен весь список, и установщику, которому по идентификатору версии нужна ссылка на её описание. ## Место в проекте и зависимости Экземпляр создаётся и принадлежит [LauncherBackend](LauncherBackend.md). Ссылку на него получает [VersionInstaller](VersionInstaller.md) — установщик берёт из манифеста адрес описания версии. Путь к файлу кэша даёт `LauncherPaths::versionManifestFile()` из [launcherpaths.h](launcherpaths.md). Требования сборки: `Qt6::Core` (`QDateTime`, `QHash`, `QList`, `QUrl`) и `Qt6::Network` (`QNetworkAccessManager`). ## Иерархия и роль Наследует `QObject`: мета-объектная система, два сигнала и владение по родителю. Виртуальных методов базового класса не переопределяет. ## Публичные структуры ### RemoteVersionEntry Одна строка манифеста Mojang. | Поле | Тип | Описание | |------|-----|----------| | `id` | `QString` | Идентификатор версии: `1.21.8`, `25w33a`, `b1.7.3` | | `type` | `QString` | Категория: `release`, `snapshot`, `old_beta` или `old_alpha` | | `url` | `QUrl` | Адрес `.json` с описанием версии | | `sha1` | `QString` | Контрольная сумма самого описания | | `releaseTime` | `QDateTime` | Дата выпуска; по ней список сортируется новыми вперёд | Категории `type` — те же ключи, по которым окно выбора версии делит каталог на вкладки; версии, не попавшие ни в одну из четырёх, интерфейс относит к категории «прочие». ## Псевдонимы типов `VersionManifestService::Callback` — `std::function`. Сочетание `ok == true` с непустым `warning` означает особый случай: данные отдали, но из устаревшего кэша — сеть недоступна, а показать что-то нужно. ## Публичные методы #### explicit VersionManifestService(QObject \*parent = nullptr) Создаёт сервис и его `QNetworkAccessManager`. Манифест при этом не читается — чтение кэша откладывается до первого обращения. Конструктор помечен `explicit`. #### QList<RemoteVersionEntry> versions() const Текущий список версий. Возвращает копию; пустой список означает, что данных ещё нет. #### bool hasData() const Есть ли хоть какие-то данные — из сети или из кэша. #### bool isRefreshing() const Идёт ли сейчас сетевое обновление. Интерфейс показывает по этому признаку строку загрузки вместо пустого списка. #### QDateTime fetchedAt() const Когда данные были получены. По этой отметке решается, устарел ли кэш. #### std::optional<RemoteVersionEntry> find(const QString &id) const Запись по идентификатору версии; `std::nullopt`, если такой версии в манифесте нет. Из неё установщик берёт ссылку на описание версии. Поиск идёт по внутреннему указателю, а не перебором. #### void ensureLoaded(Callback callback, bool forceRefresh = false) Единственная точка входа. Свежий кэш означает отложенный вызов колбэка без обращения к сети; иначе запускается один сетевой запрос на всех, кто успел попросить, — колбэки накапливаются и вызываются все разом по его завершении. Параметр `forceRefresh` обходит проверку свежести кэша: так работает кнопка принудительного обновления. Колбэк вызывается ровно один раз и всегда в потоке GUI, в том числе когда данные уже есть. ## Сигналы #### versionsChanged() Список версий изменился — пришли новые данные из сети или прочитан кэш. Обработчик перечитывает `versions()` и обновляет интерфейс. В лаунчере на этот сигнал завязано свойство каталога версий, которое читает окно выбора. #### refreshingChanged() Изменился признак сетевого обновления. Обработчик показывает или убирает индикатор загрузки. ## Владение и время жизни Класс наследует `QObject` и принимает `parent` — родитель его и удалит. `QNetworkAccessManager` создаётся в конструкторе с сервисом в роли родителя. Отложенные колбэки хранятся в списке до завершения текущего запроса. Уничтожение сервиса с незавершённым запросом обрывает его вместе с менеджером сети, и накопленные колбэки не вызываются. Ссылку на сервис держит установщик версий; уничтожать сервис раньше установщика нельзя. ## Потокобезопасность Только поток GUI — так же, как [AuthService](AuthService.md). Чтение и запись кэша выполняются в вызывающем потоке, поэтому первое обращение к манифесту делает короткую файловую операцию синхронно. ## Взаимодействие с другими классами `LauncherBackend` вызывает `ensureLoaded()` при открытии окна выбора версии и по кнопке обновления, а сигналы `versionsChanged` и `refreshingChanged` переправляет в свойства, которые читает QML. Список из `versions()` он сводит с установленными версиями и отдаёт в интерфейс уже готовыми строками. [VersionInstaller](VersionInstaller.md) обращается к `find()`, чтобы получить адрес описания версии перед началом загрузки. ## Внешнее взаимодействие **Сеть, исходящие запросы.** Класс скачивает манифест версий Mojang через `QNetworkAccessManager`. Формат — JSON поверх HTTPS, запрос инициирует лаунчер. Стратегия при недоступной сети встроена в контракт колбэка: если есть устаревший кэш, он отдаётся с `ok == true` и заполненным `warning`, и интерфейс показывает список вместо ошибки. Полное отсутствие данных даёт `ok == false`. **Файловый кэш.** Манифест сохраняется в файл, путь к которому даёт `LauncherPaths::versionManifestFile()`, вместе с отметкой времени получения. Сигналы и колбэки приходят в поток GUI. ## Пример использования ```cpp auto *manifest = new VersionManifestService(this); connect(manifest, &VersionManifestService::versionsChanged, this, &Backend::rebuildCatalog); manifest->ensureLoaded([this, manifest](bool ok, const QString &warning) { if (!ok) { emit catalogError(warning); return; } if (!warning.isEmpty()) showStatus(warning); // список из устаревшего кэша const auto entry = manifest->find(QStringLiteral("1.21.8")); if (entry) startDownload(entry->url); }); ``` --- При создании этого документа использовался ИИ.