163 lines
8.7 KiB
Markdown
163 lines
8.7 KiB
Markdown
# SeasonalPackDownloader
|
|
|
|
## Обзор класса
|
|
|
|
`SeasonalPackDownloader` скачивает один архив сезонной сборки в файл. Задача узкая и отдельная по
|
|
двум причинам: пак — это сотни мегабайт, поэтому он пишется потоком, а не держится в памяти; и его
|
|
sha256 обязательно сверяется, потому что распаковывать битую загрузку поверх рабочей `.minecraft`
|
|
нельзя.
|
|
|
|
Распаковкой класс не занимается — это делает [BuildSwitcher](BuildSwitcher.md) в отдельном потоке
|
|
вместе с остальными операциями над содержимым папки игры.
|
|
|
|
Набор геттеров прогресса повторяет [VersionInstaller](VersionInstaller.md) и
|
|
[JavaInstaller](JavaInstaller.md): панель загрузки в интерфейсе читает их одинаково, независимо от
|
|
того, кто сейчас работает.
|
|
|
|
## Место в проекте и зависимости
|
|
|
|
Экземпляр создаётся и принадлежит [LauncherBackend](LauncherBackend.md). Адрес архива, его размер
|
|
и контрольную сумму даёт запись каталога от [SeasonalBuildService](SeasonalBuildService.md).
|
|
|
|
Требования сборки: `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](BuildSwitcher.md) для раскатки поверх содержимого
|
|
`.minecraft`.
|
|
|
|
#### failed(const QString &label, const QString &message)
|
|
|
|
Загрузка не удалась: сеть недоступна, сервер ответил ошибкой или не сошлась контрольная сумма.
|
|
Последний случай особенно важен — он означает, что архив повреждён и распаковывать его нельзя.
|
|
|
|
#### canceled(const QString &label)
|
|
|
|
Загрузка отменена пользователем.
|
|
|
|
## Владение и время жизни
|
|
|
|
Класс наследует `QObject` и принимает `parent` — родитель его и удалит.
|
|
`QNetworkAccessManager` и таймер создаются в конструкторе с загрузчиком в роли родителя.
|
|
|
|
Скачиваемый файл хранится как `std::unique_ptr<QSaveFile>`: незавершённая запись отменяется вместе
|
|
с уничтожением объекта, и повреждённый архив не попадает на место назначения. Сетевой ответ
|
|
создаётся по ходу работы и закрывается в деструкторе.
|
|
|
|
## Потокобезопасность
|
|
|
|
Только поток GUI. Данные пишутся на диск блоками по мере поступления, поэтому длительных
|
|
синхронных операций в потоке нет.
|
|
|
|
## Взаимодействие с другими классами
|
|
|
|
`LauncherBackend` вызывает `download()` при установке или обновлении сезонной сборки, передавая
|
|
адрес и контрольную сумму из записи [SeasonalBuildService](SeasonalBuildService.md). Сигналы
|
|
прогресса он переправляет в те же свойства, что и остальные загрузчики, поэтому панель в главном
|
|
окне не различает, кто работает.
|
|
|
|
По сигналу `finished` бэкенд передаёт путь к архиву в `BuildSwitcher::applyPack()` вместе со
|
|
списком уходящих файлов из описания предыдущей ревизии.
|
|
|
|
## Внешнее взаимодействие
|
|
|
|
**Сеть, исходящие запросы.** Одна загрузка по HTTPS с файлового сервера сборок. Направление
|
|
одностороннее, тело ответа — двоичный архив.
|
|
|
|
Данные пишутся потоком через `QSaveFile` с одновременным подсчётом sha256; несовпадение суммы
|
|
приводит к сигналу `failed`, и файл на месте назначения не появляется. Повторных попыток класс не
|
|
делает: решение о повторе принимает пользователь.
|
|
|
|
Все сигналы приходят в поток GUI.
|
|
|
|
## Пример использования
|
|
|
|
```cpp
|
|
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);
|
|
```
|
|
|
|
---
|
|
|
|
При создании этого документа использовался ИИ.
|