10 KiB
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::Callback — std::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);
});
При создании этого документа использовался ИИ.