8.7 KiB
SeasonalPackDownloader
Обзор класса
SeasonalPackDownloader скачивает один архив сезонной сборки в файл. Задача узкая и отдельная по
двум причинам: пак — это сотни мегабайт, поэтому он пишется потоком, а не держится в памяти; и его
sha256 обязательно сверяется, потому что распаковывать битую загрузку поверх рабочей .minecraft
нельзя.
Распаковкой класс не занимается — это делает BuildSwitcher в отдельном потоке вместе с остальными операциями над содержимым папки игры.
Набор геттеров прогресса повторяет VersionInstaller и JavaInstaller: панель загрузки в интерфейсе читает их одинаково, независимо от того, кто сейчас работает.
Место в проекте и зависимости
Экземпляр создаётся и принадлежит LauncherBackend. Адрес архива, его размер и контрольную сумму даёт запись каталога от SeasonalBuildService.
Требования сборки: Qt6::Core (QCryptographicHash, QSaveFile, QTimer) и Qt6::Network
(QNetworkAccessManager, QNetworkReply).
Иерархия и роль
Наследует QObject: мета-объектная система, пять сигналов и владение по родителю. Объявлен
виртуальный деструктор — класс владеет незавершённой загрузкой и открытым файлом.
Публичные методы
explicit SeasonalPackDownloader(QObject *parent = nullptr)
Создаёт загрузчик, его QNetworkAccessManager и таймер сглаживания прогресса. Конструктор помечен
explicit.
bool isRunning() const
Идёт ли загрузка прямо сейчас.
QString label() const
Подпись загрузки для интерфейса — как правило, название сезонной сборки.
QString stage() const
Текущий этап словами.
QString currentFile() const
Файл, который качается сейчас.
qint64 bytesDone() const
Сколько байт уже получено.
qint64 bytesTotal() const
Ожидаемый размер архива.
double fraction() const
Доля выполнения от 0 до 1 либо -1, пока итог неизвестен — например, когда сервер не сообщил
размер, а в записи каталога он не был указан.
void download(const QUrl &url, const QString &targetPath, const QString &sha256, qint64 expectedSize, const QString &label)
Скачивает архив по адресу url в targetPath, сверяя sha256 с переданным значением.
Файл targetPath перезаписывается: недокачанный пак с прошлой попытки не должен пережить новую.
Запись идёт через QSaveFile, поэтому на месте назначения файл появляется только целиком и только
после успешной проверки контрольной суммы.
Параметр expectedSize берётся из записи каталога и используется для расчёта доли выполнения,
пока сервер не сообщил размер сам.
void cancel()
Отменяет загрузку. Недокачанный файл на месте назначения не остаётся.
Сигналы
started(const QString &label)
Загрузка началась. Обработчик показывает панель прогресса.
progressChanged()
Изменились числа прогресса; испускается не чаще, чем позволяет внутренний таймер, — иначе сигнал на каждый принятый блок обошёлся бы дороже самой загрузки. Обработчик перечитывает геттеры.
finished(const QString &path)
Архив скачан и проверен; в параметре — путь к готовому файлу.
Обработчик передаёт этот путь BuildSwitcher для раскатки поверх содержимого
.minecraft.
failed(const QString &label, const QString &message)
Загрузка не удалась: сеть недоступна, сервер ответил ошибкой или не сошлась контрольная сумма. Последний случай особенно важен — он означает, что архив повреждён и распаковывать его нельзя.
canceled(const QString &label)
Загрузка отменена пользователем.
Владение и время жизни
Класс наследует QObject и принимает parent — родитель его и удалит.
QNetworkAccessManager и таймер создаются в конструкторе с загрузчиком в роли родителя.
Скачиваемый файл хранится как std::unique_ptr<QSaveFile>: незавершённая запись отменяется вместе
с уничтожением объекта, и повреждённый архив не попадает на место назначения. Сетевой ответ
создаётся по ходу работы и закрывается в деструкторе.
Потокобезопасность
Только поток GUI. Данные пишутся на диск блоками по мере поступления, поэтому длительных синхронных операций в потоке нет.
Взаимодействие с другими классами
LauncherBackend вызывает download() при установке или обновлении сезонной сборки, передавая
адрес и контрольную сумму из записи SeasonalBuildService. Сигналы
прогресса он переправляет в те же свойства, что и остальные загрузчики, поэтому панель в главном
окне не различает, кто работает.
По сигналу finished бэкенд передаёт путь к архиву в BuildSwitcher::applyPack() вместе со
списком уходящих файлов из описания предыдущей ревизии.
Внешнее взаимодействие
Сеть, исходящие запросы. Одна загрузка по HTTPS с файлового сервера сборок. Направление одностороннее, тело ответа — двоичный архив.
Данные пишутся потоком через QSaveFile с одновременным подсчётом sha256; несовпадение суммы
приводит к сигналу failed, и файл на месте назначения не появляется. Повторных попыток класс не
делает: решение о повторе принимает пользователь.
Все сигналы приходят в поток GUI.
Пример использования
auto *packLoader = new SeasonalPackDownloader(this);
connect(packLoader, &SeasonalPackDownloader::progressChanged, this, &Backend::downloadChanged);
connect(packLoader, &SeasonalPackDownloader::finished, this, [this](const QString &path) {
m_switcher->applyPack(m_buildId, m_buildName, path,
m_previousEntries, m_note, m_gameDir);
});
connect(packLoader, &SeasonalPackDownloader::failed, this,
[this](const QString &, const QString &message) { emit launchError(message); });
packLoader->download(entry.archiveUrl, targetPath,
entry.archiveSha256, entry.archiveSize, entry.name);
При создании этого документа использовался ИИ.