#pragma once #include #include #include #include #include #include #include #include #include class QNetworkAccessManager; class QJsonObject; // Одна готовая сборка с сервера сезонных сборок. struct SeasonalBuildEntry { QString id; // season-5 — он же ключ, по которому сборка // узнаётся среди локальных записей QString name; // «Сезон 5: Пустоши» int revision = 0; // растёт при каждой публикации QString minecraftVersion; // 1.20.1 QString loader; // "" | forge | fabric | neoforge | quilt QString loaderVersion; // 47.4.0 int modCount = 0; QDate seasonStart; QDate seasonEnd; // невалидная — сезон ещё не закончен QString serverUrl; // адрес игрового сервера, не файлового int javaMajor = 0; // 0 — определять по версии игры QString description; QUrl archiveUrl; qint64 archiveSize = 0; QString archiveSha256; bool isValid() const { return !id.isEmpty() && revision > 0 && archiveUrl.isValid(); } }; // Каталог сезонных сборок: скачивает index.json с файлового сервера, кэширует // его в папке лаунчера и отдаёт из кэша, пока тот не устарел. Устройство // повторяет VersionManifestService — включая то, что пустой разбор считается // испорченным ответом и хороший кэш им не затирается. class SeasonalBuildService : public QObject { Q_OBJECT public: // ok = true и непустой warning — данные отдали из устаревшего кэша. using Callback = std::function; explicit SeasonalBuildService(QObject *parent = nullptr); QList builds() const { return m_builds; } bool hasData() const { return !m_builds.isEmpty(); } bool isRefreshing() const { return m_refreshing; } // Последняя ошибка обращения к серверу; пусто — всё в порядке. QString lastError() const { return m_lastError; } std::optional find(const QString &id) const; // Адрес сервера сборок. Меняется из настроек, поэтому при смене хоста // накопленные данные и кэш сбрасываются: ссылки в них указывают на старый. void setBaseUrl(const QUrl &baseUrl); QUrl baseUrl() const { return m_baseUrl; } void ensureLoaded(Callback callback, bool forceRefresh = false); signals: void buildsChanged(); void refreshingChanged(); private: bool readCache(); void writeCache(); void startRefresh(); void finishRefresh(bool ok, const QString &warning); void applyBuilds(QList list); QList parseCatalog(const QJsonObject &root, QString *error) const; QNetworkAccessManager *m_network; QUrl m_baseUrl; QList m_builds; QHash m_indexById; QDateTime m_fetchedAt; QString m_lastError; bool m_refreshing = false; bool m_cacheRead = false; QList m_pending; };