#pragma once #include "modloader.h" #include #include #include #include #include #include #include class QNetworkAccessManager; // Списки версий модлоадеров: качает, кэширует в папке лаунчера и отдаёт из // кэша, пока тот не устарел. Устроен как VersionManifestService и работает в // GUI-потоке. // // Совместимость с версией игры не проверяется отдельной логикой, а заложена в // структуру данных: Fabric и Quilt отдают список сразу под нужную версию игры, // а maven-metadata Forge и NeoForge раскладывается по версиям игры при разборе. // Версии игры, под которую сборок нет, соответствует пустой список — выбрать // несовместимый лоадер физически нечем. class ModLoaderVersionService : public QObject { Q_OBJECT public: // ok = true и непустой warning — данные отдали из устаревшего кэша. using Callback = std::function; explicit ModLoaderVersionService(QObject *parent = nullptr); // Единственная точка входа. Свежий кэш — колбэк отложенно, иначе один // сетевой запрос на всех, кто успел попросить. void ensureLoaded(ModLoader loader, const QString &gameVersion, Callback callback, bool forceRefresh = false); // Новые сборки сверху: первая строка — самая свежая для этой версии игры. QList versions(ModLoader loader, const QString &gameVersion) const; bool isRefreshing(ModLoader loader, const QString &gameVersion) const; // Запись по версии лоадера — из неё установщик берёт ссылку на installer.jar. std::optional 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> byGame; QHash 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 parseFabricLike(const QByteArray &payload, ModLoader loader, const QString &gameVersion); static QHash> parseMavenMetadata(const QByteArray &xml, ModLoader loader, bool legacyNeoForge); QNetworkAccessManager *m_network; QHash m_buckets; QHash> m_pending; QSet m_inFlight; };