#pragma once #include #include #include #include class QThread; class BuildArchiveWorker; // Смена активной сборки: содержимое .minecraft уезжает в архив своей сборки, // папка чистится, на её место разворачивается архив выбранной. // // Порядок шагов подчинён одному правилу: пока новый архив не записан целиком и // не переименован на место, из .minecraft не удаляется ничего. Отметка о // начатом переключении пишется в builds/index.json до первого разрушающего // действия, поэтому обрыв питания или kill -9 всегда обнаружим на следующем // запуске — см. recoverIfNeeded(). class BuildSwitcher : public QObject { Q_OBJECT public: explicit BuildSwitcher(QObject *parent = nullptr); ~BuildSwitcher() override; bool isRunning() const { return m_state != Idle; } QString stage() const { return m_stage; } QString status() const { return m_status; } double fraction() const; // 0..1, либо -1 пока итог неизвестен // Общие для всех сборок каталоги: они не архивируются и не чистятся. static QStringList sharedTopLevel(); void switchTo(int fromBuildId, const QString &fromName, int toBuildId, const QString &toName, const QString &gameDir); // Докатывает пак сезонной сборки поверх содержимого .minecraft. Вызывается // только для уже активной сборки: пак ложится на то, что сейчас разложено, // а не внутрь чужого архива. // // note — непрозрачные данные вызывающей стороны: они переживают перезапуск // вместе с отметкой о незавершённой операции и возвращаются через // lastPackNote(), когда раскатка доиграна. Сам переключатель в них не // заглядывает. void applyPack(int buildId, const QString &buildName, const QString &packZipPath, const QStringList &removeRelative, const QJsonObject ¬e, const QString &gameDir); // Что принёс последний успешно раскатанный пак и с чем он пришёл. QStringList lastPackEntries() const { return m_lastPackEntries; } QJsonObject lastPackNote() const { return m_lastPackNote; } void cancel(); // Сборку удалили: убираем её архив и запись о нём. Возвращает false, если // папку архива не удалось удалить целиком. bool forgetBuild(int buildId); // Есть ли у сборки сохранённый архив (для текста предупреждения об удалении). bool hasArchive(int buildId) const { return !archivePathOf(buildId).isEmpty(); } // Незавершённое переключение с прошлого запуска. Возвращает пустую строку, // если всё в порядке, иначе — текст для пользователя. QString interruptedSwitchWarning() const; // Доигрывает прерванное переключение: очистка и распаковка целевой сборки. void resumeInterrupted(const QString &gameDir); signals: void progressChanged(); void finished(int toBuildId); void failed(int toBuildId, const QString &message, bool gameDirIntact); private: enum State { Idle, Archiving, Clearing, Restoring, Applying }; // Сигналы к рабочему объекту в другом потоке. void requestArchive(const QString &gameDir, const QString &tempZip, const QStringList &exclude); void requestClear(const QString &gameDir, const QStringList &keep); void requestRestore(const QString &zipPath, const QString &gameDir); void requestApplyPack(const QString &zipPath, const QString &gameDir, const QStringList &remove); void onWorkerFinished(bool ok, const QString &error); void onWorkerProgress(int done, int total, const QString ¤tPath); void commitArchive(); void beginApplying(); void finishApplying(); void resumeApplying(const QJsonObject &index, const QString &gameDir); void beginClearing(); void beginRestoring(); void finish(); void fail(const QString &message, bool gameDirIntact); QString buildDir(int buildId) const; QString archivePathOf(int buildId) const; void setStage(const QString &stage); QJsonObject readIndex() const; void writeIndex(const QJsonObject &index) const; void sweepTempArchives() const; QThread *m_thread; BuildArchiveWorker *m_worker; State m_state = Idle; QString m_stage; QString m_status; int m_done = 0; int m_total = 0; int m_fromId = -1; int m_toId = -1; QString m_fromName; QString m_toName; QString m_gameDir; QString m_tempZip; // Раскатка пака: путь к архиву, список уходящих файлов и данные вызывающей // стороны, которые надо вернуть по завершении. QString m_packPath; QStringList m_packRemove; QJsonObject m_packNote; QStringList m_lastPackEntries; QJsonObject m_lastPackNote; };