Files

149 lines
5.5 KiB
C++

#pragma once
#include "javaruntime.h"
#include <QCryptographicHash>
#include <QList>
#include <QObject>
#include <QPair>
#include <QQueue>
#include <QString>
#include <QStringList>
#include <memory>
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<QSaveFile> file;
QCryptographicHash hash{QCryptographicHash::Sha1};
qint64 received = 0;
};
// Установка сборки Java в <root>/java/<id>. Два разных пути под одним фасадом:
//
// * 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<JavaActiveDownload> &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<QSaveFile> 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<ZipExtraction> m_zip;
QQueue<JavaFileTask> m_queue;
QList<std::shared_ptr<JavaActiveDownload>> m_active;
QList<QPair<QString, QString>> m_links; // {абсолютный путь ссылки, цель}
bool m_running = false;
bool m_canceling = false;
};