Files
minecraft-launcher/doc/cpp/SeasonalBuildService.md
2026-09-03 09:16:56 +03:00

10 KiB
Raw Permalink Blame History

SeasonalBuildService

Обзор класса

Кроме сборок, которые пользователь собирает сам, лаунчер умеет ставить готовые сезонные сборки с собственного файлового сервера: набор модов под конкретную версию игры и модлоадер, подготовленный заранее и выдаваемый целиком.

SeasonalBuildService — каталог этих сборок: скачивает index.json с файлового сервера, кэширует в папке лаунчера и отдаёт из кэша, пока тот не устарел. Устройство повторяет VersionManifestService — включая то, что пустой разбор считается испорченным ответом и хороший кэш им не затирается.

Место в проекте и зависимости

Экземпляр создаётся и принадлежит LauncherBackend. Записи каталога используются SeasonalPackDownloader — оттуда берётся адрес архива и его контрольная сумма.

Путь к файлу кэша даёт LauncherPaths::seasonalCatalogFile() из launcherpaths.h.

Требования сборки: Qt6::Core (QDate, QDateTime, QHash, QUrl) и Qt6::Network (QNetworkAccessManager).

Иерархия и роль

Наследует QObject: мета-объектная система, два сигнала и владение по родителю. Виртуальных методов базового класса не переопределяет.

Публичные структуры

SeasonalBuildEntry

Одна готовая сборка с сервера сезонных сборок.

Поле Тип По умолчанию Описание
id QString Идентификатор вида season-5; он же ключ, по которому сборка узнаётся среди локальных записей
name QString Название для интерфейса, например «Сезон 5: Пустоши»
revision int 0 Растёт при каждой публикации; сравнение с установленной ревизией даёт признак доступного обновления
minecraftVersion QString Версия игры, например 1.20.1
loader QString Ключ модлоадера: пустая строка (чистая ваниль), forge, fabric, neoforge или quilt
loaderVersion QString Версия модлоадера
modCount int 0 Число модов в сборке; показывается колонкой в таблице
seasonStart QDate Начало сезона
seasonEnd QDate Конец сезона; невалидная дата означает, что сезон ещё не закончен
serverUrl QString Адрес игрового сервера — не файлового, с которого качается сборка
javaMajor int 0 Требуемая версия Java; 0 означает «определять по версии игры»
description QString Описание сборки; показывается в подвале окна каталога
archiveUrl QUrl Адрес архива сборки
archiveSize qint64 0 Размер архива в байтах
archiveSha256 QString Контрольная сумма архива

Метод isValid() возвращает true, когда заполнен id, revision больше нуля и archiveUrl корректен.

Псевдонимы типов

SeasonalBuildService::Callbackstd::function<void(bool ok, const QString &warning)>. Сочетание ok == true с непустым warning означает данные из устаревшего кэша.

Публичные методы

explicit SeasonalBuildService(QObject *parent = nullptr)

Создаёт сервис и его QNetworkAccessManager. Конструктор помечен explicit.

QList<SeasonalBuildEntry> builds() const

Текущий каталог сборок. Возвращает копию.

bool hasData() const

Есть ли в каталоге хоть что-то — из сети или из кэша.

bool isRefreshing() const

Идёт ли сейчас сетевое обновление.

QString lastError() const

Последняя ошибка обращения к серверу; пустая строка означает, что всё в порядке.

В отличие от остальных каталогов лаунчера, ошибка здесь хранится отдельным полем: пустой список и ошибка выглядят одинаково пустыми, и окно каталога показывает причину прямо на месте строк.

std::optional<SeasonalBuildEntry> find(const QString &id) const

Запись по идентификатору сборки; std::nullopt, если такой нет.

void setBaseUrl(const QUrl &baseUrl)

Задаёт адрес сервера сборок. Адрес меняется из настроек, поэтому при смене хоста накопленные данные и кэш сбрасываются: ссылки в них указывают на старый сервер и после смены недействительны.

QUrl baseUrl() const

Текущий адрес сервера сборок.

void ensureLoaded(Callback callback, bool forceRefresh = false)

Единственная точка входа. Свежий кэш означает отложенный вызов колбэка без сети; иначе запускается один сетевой запрос на всех, кто успел попросить. Параметр forceRefresh обходит проверку свежести — так работает кнопка обновления списка.

Окно каталога вызывает этот метод при каждом открытии без принудительного обновления: свежий кэш отвечает без сети, поэтому вызов ничего не стоит.

Сигналы

buildsChanged()

Каталог изменился — пришли новые данные или прочитан кэш. Обработчик перечитывает builds() и обновляет таблицу.

refreshingChanged()

Изменился признак обновления. Обработчик показывает или убирает индикатор загрузки; в окне каталога по нему же выключается кнопка обновления списка.

Владение и время жизни

Класс наследует QObject и принимает parent — родитель его и удалит. QNetworkAccessManager создаётся в конструкторе с сервисом в роли родителя.

Отложенные колбэки хранятся до завершения текущего запроса; уничтожение сервиса с незавершённым запросом обрывает его, и колбэки не вызываются.

Потокобезопасность

Только поток GUI. Чтение и запись кэша выполняются синхронно в вызывающем потоке.

Взаимодействие с другими классами

LauncherBackend вызывает ensureLoaded() при открытии окна каталога и по кнопке обновления, а сигналы переправляет в свойства для QML. Перед отдачей в интерфейс он сводит записи каталога с локальными: строка таблицы уже содержит готовый статус и признак доступного обновления, поэтому SeasonalBuildsDialog ничего не считает сам.

Установка сезонной сборки начинается с find(): по записи бэкенд получает адрес архива и передаёт его SeasonalPackDownloader, а скачанный пак раскатывает BuildSwitcher.

Внешнее взаимодействие

Сеть, исходящие запросы. Класс скачивает index.json с файлового сервера сборок, адрес которого задаётся через setBaseUrl(). Формат — JSON поверх HTTPS, запрос инициирует лаунчер.

Испорченный или пустой ответ не затирает хороший кэш. При недоступной сети данные отдаются из устаревшего кэша с ok == true и заполненным warning; текст ошибки при этом попадает и в lastError().

Все сигналы и колбэки приходят в поток GUI.

Пример использования

auto *seasonal = new SeasonalBuildService(this);
seasonal->setBaseUrl(QUrl(settings.seasonalServer));
connect(seasonal, &SeasonalBuildService::buildsChanged, this, &Backend::rebuildSeasonalCatalog);

seasonal->ensureLoaded([this, seasonal](bool ok, const QString &warning) {
    if (!ok) {
        emit seasonalCatalogError(seasonal->lastError());
        return;
    }
    if (!warning.isEmpty())
        showStatus(warning);
});

При создании этого документа использовался ИИ.