#pragma once #include "javaruntime.h" #include #include #include #include #include #include #include #include class QJsonObject; class QNetworkAccessManager; class QNetworkReply; class QProcess; class QSaveFile; class QTimer; // Состояние распаковки zip. Спрятано в .cpp, чтобы приватный заголовок // QZipReader не расходился по проекту вместе с этим. struct ZipExtraction; // Один файл рантайма Mojang. struct JavaFileTask { QUrl url; QString path; // абсолютный путь назначения QString sha1; qint64 size = 0; bool executable = false; int attempts = 0; }; // Файл в процессе скачивания. Пишем потоком: рантайм — это две сотни мегабайт. struct JavaActiveDownload { JavaFileTask task; QNetworkReply *reply = nullptr; std::unique_ptr file; QCryptographicHash hash{QCryptographicHash::Sha1}; qint64 received = 0; }; // Установка сборки Java в /java/. Два разных пути под одним фасадом: // // * Temurin отдаёт один архив — качаем, сверяем sha256 и распаковываем; // // * Mojang отдаёт манифест с деревом файлов — качаем файлы по отдельности, // как это делает официальный лаунчер. // // Набор геттеров повторяет VersionInstaller: панель загрузки в интерфейсе // читает их одинаково, независимо от того, кто сейчас работает. class JavaInstaller : public QObject { Q_OBJECT public: explicit JavaInstaller(QObject *parent = nullptr); ~JavaInstaller() override; bool isRunning() const { return m_running; } QString runtimeId() const { return m_entry.id; } QString label() const { return m_label; } QString stage() const { return m_stage; } QString currentFile() const { return m_currentFile; } // Байты считаются только на загрузке: на распаковке считать нечего, и панель // по нулевому итогу сама прячет мегабайты. qint64 bytesDone() const; qint64 bytesTotal() const { return m_bytesTotal; } double fraction() const; // 0..1, либо -1 пока итог неизвестен void install(const JavaRuntimeEntry &entry); void cancel(); signals: void started(const QString &label); void progressChanged(); void finished(const QString &runtimeId, const QString &javaPath); void failed(const QString &label, const QString &message); void canceled(const QString &label); private: // ── Общее ─────────────────────────────────────────────────────────── void beginArchive(); void beginMojang(); void complete(); void fail(const QString &message); void reset(); void setStage(const QString &stage); void scheduleProgress(); void flushProgress(); // ── Temurin ───────────────────────────────────────────────────────── void downloadArchive(); void handleArchiveFinished(QNetworkReply *reply); void startExtraction(); void extractZipChunk(); void startTarExtraction(); // ── Mojang ────────────────────────────────────────────────────────── void handleManifest(const QByteArray &payload); bool prepareTree(const QJsonObject &files); void pump(); void startTask(JavaFileTask task); void handleFileFinished(const std::shared_ptr &context); bool retryOrFail(JavaFileTask task, const QString &reason); bool applyLinks(); QNetworkAccessManager *m_network; JavaRuntimeEntry m_entry; QString m_label; QString m_targetDir; QString m_archivePath; QString m_stage; QString m_currentFile; qint64 m_bytesSettled = 0; qint64 m_bytesTotal = 0; // Шаги распаковки: у архива — записи внутри него, у Mojang — файлы дерева. int m_stepsDone = 0; int m_stepsTotal = 0; QNetworkReply *m_reply = nullptr; // архив или манифест std::unique_ptr m_archiveFile; QCryptographicHash m_archiveHash{QCryptographicHash::Sha256}; QProcess *m_tar = nullptr; QTimer *m_extractTimer; QTimer *m_progressTimer; bool m_progressDirty = false; // Распаковка zip по кускам: держать GUI-поток занятым на всю сотню // мегабайт нельзя, а поток ради одной операции заводить незачем. std::unique_ptr m_zip; QQueue m_queue; QList> m_active; QList> m_links; // {абсолютный путь ссылки, цель} bool m_running = false; bool m_canceling = false; };