92 lines
3.6 KiB
C++
92 lines
3.6 KiB
C++
|
|
#pragma once
|
||
|
|
|
||
|
|
#include <QDate>
|
||
|
|
#include <QDateTime>
|
||
|
|
#include <QHash>
|
||
|
|
#include <QList>
|
||
|
|
#include <QObject>
|
||
|
|
#include <QString>
|
||
|
|
#include <QUrl>
|
||
|
|
|
||
|
|
#include <functional>
|
||
|
|
#include <optional>
|
||
|
|
|
||
|
|
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<void(bool ok, const QString &warning)>;
|
||
|
|
|
||
|
|
explicit SeasonalBuildService(QObject *parent = nullptr);
|
||
|
|
|
||
|
|
QList<SeasonalBuildEntry> 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<SeasonalBuildEntry> 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<SeasonalBuildEntry> list);
|
||
|
|
QList<SeasonalBuildEntry> parseCatalog(const QJsonObject &root, QString *error) const;
|
||
|
|
|
||
|
|
QNetworkAccessManager *m_network;
|
||
|
|
QUrl m_baseUrl;
|
||
|
|
QList<SeasonalBuildEntry> m_builds;
|
||
|
|
QHash<QString, int> m_indexById;
|
||
|
|
QDateTime m_fetchedAt;
|
||
|
|
QString m_lastError;
|
||
|
|
bool m_refreshing = false;
|
||
|
|
bool m_cacheRead = false;
|
||
|
|
QList<Callback> m_pending;
|
||
|
|
};
|