# 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`: незавершённая запись отменяется вместе с уничтожением объекта, и повреждённый архив не попадает на место назначения. Сетевой ответ создаётся по ходу работы и закрывается в деструкторе. ## Потокобезопасность Только поток 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); ``` --- При создании этого документа использовался ИИ.