168 lines
9.3 KiB
Markdown
168 lines
9.3 KiB
Markdown
|
|
# 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` | Адрес `<id>.json` с описанием версии |
|
|||
|
|
| `sha1` | `QString` | Контрольная сумма самого описания |
|
|||
|
|
| `releaseTime` | `QDateTime` | Дата выпуска; по ней список сортируется новыми вперёд |
|
|||
|
|
|
|||
|
|
Категории `type` — те же ключи, по которым окно выбора версии делит каталог на вкладки; версии, не
|
|||
|
|
попавшие ни в одну из четырёх, интерфейс относит к категории «прочие».
|
|||
|
|
|
|||
|
|
## Псевдонимы типов
|
|||
|
|
|
|||
|
|
`VersionManifestService::Callback` — `std::function<void(bool ok, const QString &warning)>`.
|
|||
|
|
Сочетание `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);
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
При создании этого документа использовался ИИ.
|