#pragma once #include "minecraftversion.h" #include #include #include #include #include #include #include #include #include #include class QNetworkAccessManager; class QNetworkReply; class QSaveFile; class QTimer; class VersionManifestService; // Один файл, который нужно положить в .minecraft. struct DownloadTask { enum Kind { ClientJar, Library, AssetIndex, AssetObject, LoggingConfig }; Kind kind = ClientJar; QUrl url; QString path; // абсолютный путь назначения QString sha1; // пусто — не проверяем qint64 size = 0; // 0 — размер неизвестен QString label; // что показать в панели прогресса int attempts = 0; }; // Файл в процессе скачивания. Пишем потоком: держать десятки мегабайт в памяти // незачем, а нескольких параллельных загрузок хватило бы на сотни. struct ActiveDownload { DownloadTask task; QNetworkReply *reply = nullptr; std::unique_ptr file; QCryptographicHash hash{QCryptographicHash::Sha1}; qint64 received = 0; }; // Фоновая установка версии в .minecraft: описание версии, клиентский jar, // библиотеки, индекс ресурсов и сами ресурсы. Одна версия за раз, остальные // ждут в очереди. Всё в GUI-потоке, как и остальной сетевой код лаунчера. class VersionInstaller : public QObject { Q_OBJECT public: explicit VersionInstaller(VersionManifestService *manifest, QObject *parent = nullptr); ~VersionInstaller() override; bool isRunning() const { return m_running; } QString versionId() const { return m_versionId; } QString stage() const { return m_stage; } QString currentFile() const { return m_currentLabel; } qint64 bytesDone() const; qint64 bytesTotal() const { return m_bytesTotal; } double fraction() const; // 0..1, либо -1 пока итог неизвестен void install(const QString &gameDir, const QString &versionId); bool isQueued(const QString &versionId) const; void cancel(); signals: void started(const QString &versionId); void progressChanged(); // не чаще десяти раз в секунду void finished(const QString &versionId); void failed(const QString &versionId, const QString &message); void canceled(const QString &versionId); private: void startNext(); void resolveManifestEntry(); void fetchVersionJson(const QUrl &url, const QString &expectedSha1); bool prepareTasks(); void enqueue(DownloadTask task); void schedulePump(); void pump(); void startTask(DownloadTask task); void handleReplyFinished(const std::shared_ptr &context); bool retryOrFail(DownloadTask task, const QString &reason); void expandAssetIndex(const QString &indexPath); void afterDownloads(); void startLegacyCopy(); void legacyTick(); void complete(); void setStage(const QString &stage); void scheduleProgress(); void flushProgress(); void resetInstallState(); void failInstall(const QString &message); void abandonCurrent(); // версия уступает место родителю, без сигналов void finalizeCancel(); VersionManifestService *m_manifest; QNetworkAccessManager *m_network; QQueue> m_pendingInstalls; // {gameDir, versionId} QString m_gameDir; QString m_versionId; QJsonObject m_versionJson; MinecraftVersion m_version; QQueue m_queue; QList> m_active; QStringList m_warnings; qint64 m_bytesSettled = 0; qint64 m_bytesTotal = 0; qint64 m_assetsDeclaredTotal = 0; bool m_running = false; bool m_canceling = false; bool m_pumpScheduled = false; // Существующие файлы сверяются только по размеру: перехеширование сотен // мегабайт при каждом добавлении версии дороже, чем риск битого файла. // Флаг оставлен под будущую кнопку «проверить файлы». bool m_verifyExisting = false; QString m_stage; QString m_currentLabel; QTimer *m_progressTimer; bool m_progressDirty = false; // Ресурсы до 1.6 игра ищет не в assets/objects, а в плоской папке. QJsonObject m_assetObjects; bool m_assetsVirtual = false; bool m_assetsMapToResources = false; QList> m_legacyCopies; // {источник, назначение} int m_legacyIndex = 0; QTimer *m_legacyTimer; int m_inheritDepth = 0; };