new builds menu and modloaders implementation

This commit is contained in:
2026-08-27 09:21:51 +03:00
parent 5bc59f8018
commit 168884127b
23 changed files with 3979 additions and 1081 deletions
+96
View File
@@ -0,0 +1,96 @@
#pragma once
#include "modloader.h"
#include <QDateTime>
#include <QHash>
#include <QList>
#include <QObject>
#include <QSet>
#include <QString>
#include <functional>
class QNetworkAccessManager;
// Списки версий модлоадеров: качает, кэширует в папке лаунчера и отдаёт из
// кэша, пока тот не устарел. Устроен как VersionManifestService и работает в
// GUI-потоке.
//
// Совместимость с версией игры не проверяется отдельной логикой, а заложена в
// структуру данных: Fabric и Quilt отдают список сразу под нужную версию игры,
// а maven-metadata Forge и NeoForge раскладывается по версиям игры при разборе.
// Версии игры, под которую сборок нет, соответствует пустой список — выбрать
// несовместимый лоадер физически нечем.
class ModLoaderVersionService : public QObject
{
Q_OBJECT
public:
// ok = true и непустой warning — данные отдали из устаревшего кэша.
using Callback = std::function<void(bool ok, const QString &warning)>;
explicit ModLoaderVersionService(QObject *parent = nullptr);
// Единственная точка входа. Свежий кэш — колбэк отложенно, иначе один
// сетевой запрос на всех, кто успел попросить.
void ensureLoaded(ModLoader loader,
const QString &gameVersion,
Callback callback,
bool forceRefresh = false);
// Новые сборки сверху: первая строка — самая свежая для этой версии игры.
QList<LoaderVersionEntry> versions(ModLoader loader, const QString &gameVersion) const;
bool isRefreshing(ModLoader loader, const QString &gameVersion) const;
// Запись по версии лоадера — из неё установщик берёт ссылку на installer.jar.
std::optional<LoaderVersionEntry> find(ModLoader loader,
const QString &gameVersion,
const QString &loaderVersion) const;
signals:
void versionsChanged(const QString &loaderKey, const QString &gameVersion);
void refreshingChanged();
private:
// Fabric и Quilt спрашиваются по каждой версии игры отдельно, Forge и
// NeoForge — одним maven-metadata на все версии сразу. Отсюда два вида
// ключа свежести: у первых — на версию игры, у вторых — на весь лоадер.
static bool isPerGameVersion(ModLoader loader);
struct Bucket
{
QHash<QString, QList<LoaderVersionEntry>> byGame;
QHash<QString, QDateTime> fetchedAtByGame; // Fabric, Quilt
QDateTime fetchedAt; // Forge, NeoForge
bool cacheRead = false;
};
Bucket &bucket(ModLoader loader);
const Bucket &bucket(ModLoader loader) const;
static QString requestKey(ModLoader loader, const QString &gameVersion);
bool isFresh(ModLoader loader, const QString &gameVersion) const;
bool readCache(ModLoader loader);
void writeCache(ModLoader loader);
void startRefresh(ModLoader loader, const QString &gameVersion);
void finishRefresh(ModLoader loader,
const QString &gameVersion,
bool ok,
const QString &warning);
// Разбор ответов. Возвращают пустое при испорченном ответе — хороший кэш им
// не затирается.
static QList<LoaderVersionEntry> parseFabricLike(const QByteArray &payload,
ModLoader loader,
const QString &gameVersion);
static QHash<QString, QList<LoaderVersionEntry>> parseMavenMetadata(const QByteArray &xml,
ModLoader loader,
bool legacyNeoForge);
QNetworkAccessManager *m_network;
QHash<int, Bucket> m_buckets;
QHash<QString, QList<Callback>> m_pending;
QSet<QString> m_inFlight;
};