From c9e1f5d6a62fdea62acc56c55fec310a8b91dcb4 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 26 Feb 2019 20:55:39 -0800 Subject: [PATCH] Qt: Add option to download chip data --- src/platform/qt/AbstractUpdater.cpp | 89 +++++++++++++++++++++++++++ src/platform/qt/AbstractUpdater.h | 47 ++++++++++++++ src/platform/qt/BattleChipModel.cpp | 15 +++++ src/platform/qt/BattleChipModel.h | 3 + src/platform/qt/BattleChipUpdater.cpp | 47 ++++++++++++++ src/platform/qt/BattleChipUpdater.h | 22 +++++++ src/platform/qt/BattleChipView.cpp | 34 ++++++++++ src/platform/qt/BattleChipView.h | 4 ++ src/platform/qt/BattleChipView.ui | 15 ++++- src/platform/qt/CMakeLists.txt | 2 + 10 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 src/platform/qt/AbstractUpdater.cpp create mode 100644 src/platform/qt/AbstractUpdater.h create mode 100644 src/platform/qt/BattleChipUpdater.cpp create mode 100644 src/platform/qt/BattleChipUpdater.h diff --git a/src/platform/qt/AbstractUpdater.cpp b/src/platform/qt/AbstractUpdater.cpp new file mode 100644 index 000000000..c165ae1a0 --- /dev/null +++ b/src/platform/qt/AbstractUpdater.cpp @@ -0,0 +1,89 @@ +/* Copyright (c) 2013-2019 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "AbstractUpdater.h" + +#include +#include + +using namespace QGBA; + +AbstractUpdater::AbstractUpdater(QObject* parent) + : QObject(parent) + , m_netman(new QNetworkAccessManager(this)) +{ +} + +void AbstractUpdater::checkUpdate() { + QNetworkReply* reply = m_netman->get(QNetworkRequest(manifestLocation())); + chaseRedirects(reply, &AbstractUpdater::manifestDownloaded); +} + +void AbstractUpdater::downloadUpdate() { + if (m_isUpdating) { + return; + } + if (m_manifest.isEmpty()) { + m_isUpdating = true; + checkUpdate(); + return; + } + QUrl url = parseManifest(m_manifest); + if (!url.isValid()) { + emit updateDone(false); + return; + } + m_isUpdating = true; + QNetworkReply* reply = m_netman->get(QNetworkRequest(url)); + chaseRedirects(reply, &AbstractUpdater::updateDownloaded); +} + +void AbstractUpdater::chaseRedirects(QNetworkReply* reply, void (AbstractUpdater::*cb)(QNetworkReply*)) { + connect(reply, &QNetworkReply::finished, this, [this, reply, cb]() { + // TODO: check domains, etc + if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() / 100 == 3) { + QNetworkReply* newReply = m_netman->get(QNetworkRequest(reply->header(QNetworkRequest::LocationHeader).toString())); + chaseRedirects(newReply, cb); + } else { + (this->*cb)(reply); + } + }); +} + +void AbstractUpdater::manifestDownloaded(QNetworkReply* reply) { + m_manifest = reply->readAll(); + QUrl url = parseManifest(m_manifest); + if (m_isUpdating) { + if (!url.isValid()) { + emit updateDone(false); + } else { + QNetworkReply* reply = m_netman->get(QNetworkRequest(url)); + chaseRedirects(reply, &AbstractUpdater::updateDownloaded); + } + } else { + emit updateAvailable(url.isValid()); + } +} + +void AbstractUpdater::updateDownloaded(QNetworkReply* reply) { + m_isUpdating = false; + if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() / 100 != 2) { + emit updateDone(false); + return; + } + QFile f(destination()); + if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + emit updateDone(false); + return; + } + while (true) { + QByteArray bytes = reply->read(4096); + if (!bytes.size()) { + break; + } + f.write(bytes); + } + emit updateDone(true); +} diff --git a/src/platform/qt/AbstractUpdater.h b/src/platform/qt/AbstractUpdater.h new file mode 100644 index 000000000..298fe7a82 --- /dev/null +++ b/src/platform/qt/AbstractUpdater.h @@ -0,0 +1,47 @@ +/* Copyright (c) 2013-2019 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#pragma once + +#include +#include +#include + +class QNetworkAccessManager; +class QNetworkReply; + +namespace QGBA { + +class AbstractUpdater : public QObject { +Q_OBJECT + +public: + AbstractUpdater(QObject* parent = nullptr); + virtual ~AbstractUpdater() {} + +public slots: + void checkUpdate(); + void downloadUpdate(); + +signals: + void updateAvailable(bool); + void updateDone(bool); + +protected: + virtual QUrl manifestLocation() const = 0; + virtual QUrl parseManifest(const QByteArray&) const = 0; + virtual QString destination() const = 0; + +private: + void chaseRedirects(QNetworkReply*, void (AbstractUpdater::*cb)(QNetworkReply*)); + void manifestDownloaded(QNetworkReply*); + void updateDownloaded(QNetworkReply*); + + bool m_isUpdating = false; + QNetworkAccessManager* m_netman; + QByteArray m_manifest; +}; + +} \ No newline at end of file diff --git a/src/platform/qt/BattleChipModel.cpp b/src/platform/qt/BattleChipModel.cpp index f7851912d..bd65dfebe 100644 --- a/src/platform/qt/BattleChipModel.cpp +++ b/src/platform/qt/BattleChipModel.cpp @@ -155,6 +155,21 @@ void BattleChipModel::setScale(int scale) { m_scale = scale; } +void BattleChipModel::reloadAssets() { + QResource::unregisterResource(ConfigController::configDir() + "/chips.rcc", "/exe"); + QResource::unregisterResource(GBAApp::dataDir() + "/chips.rcc", "/exe"); + + QResource::registerResource(GBAApp::dataDir() + "/chips.rcc", "/exe"); + QResource::registerResource(ConfigController::configDir() + "/chips.rcc", "/exe"); + + emit layoutAboutToBeChanged(); + setFlavor(m_flavor); + for (int i = 0; i < m_deck.count(); ++i) { + m_deck[i] = createChip(m_deck[i].id); + } + emit layoutChanged(); +} + BattleChipModel::BattleChip BattleChipModel::createChip(int id) const { QString path = QString(":/exe/exe%1/%2.png").arg(m_flavor).arg(id, 3, 10, QLatin1Char('0')); if (!QFile(path).exists()) { diff --git a/src/platform/qt/BattleChipModel.h b/src/platform/qt/BattleChipModel.h index a9de45951..c0f115896 100644 --- a/src/platform/qt/BattleChipModel.h +++ b/src/platform/qt/BattleChipModel.h @@ -10,6 +10,8 @@ namespace QGBA { +class BattleChipUpdater; + class BattleChipModel : public QAbstractListModel { Q_OBJECT @@ -35,6 +37,7 @@ public slots: void setChips(QList ids); void clear(); void setScale(int); + void reloadAssets(); private: struct BattleChip { diff --git a/src/platform/qt/BattleChipUpdater.cpp b/src/platform/qt/BattleChipUpdater.cpp new file mode 100644 index 000000000..d2d919a99 --- /dev/null +++ b/src/platform/qt/BattleChipUpdater.cpp @@ -0,0 +1,47 @@ +/* Copyright (c) 2013-2019 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "BattleChipUpdater.h" + +#include "ConfigController.h" +#include "GBAApp.h" + +#include +#include +#include +#include + +using namespace QGBA; + +BattleChipUpdater::BattleChipUpdater(QObject* parent) + : AbstractUpdater(parent) +{ +} + +QUrl BattleChipUpdater::manifestLocation() const { + return {"https://api.github.com/repos/mgba-emu/chip-assets/releases/latest"}; +} + +QUrl BattleChipUpdater::parseManifest(const QByteArray& manifest) const { + QJsonDocument manifestDoc(QJsonDocument::fromJson(manifest)); + if (manifestDoc.isNull()) { + return QUrl(); + } + for (const auto& assetv : manifestDoc.object()["assets"].toArray()) { + QJsonObject asset = assetv.toObject(); + if (asset["name"].toString() == "chips.rcc") { + return asset["browser_download_url"].toString(); + } + } + return QUrl(); +} + +QString BattleChipUpdater::destination() const { + QFileInfo info(GBAApp::dataDir() + "/chips.rcc"); + if (info.isWritable()) { + return info.filePath(); + } + return ConfigController::configDir() + "/chips.rcc"; +} \ No newline at end of file diff --git a/src/platform/qt/BattleChipUpdater.h b/src/platform/qt/BattleChipUpdater.h new file mode 100644 index 000000000..4ec06e127 --- /dev/null +++ b/src/platform/qt/BattleChipUpdater.h @@ -0,0 +1,22 @@ +/* Copyright (c) 2013-2019 Jeffrey Pfau + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#pragma once + +#include "AbstractUpdater.h" + +namespace QGBA { + +class BattleChipUpdater : public AbstractUpdater { +public: + BattleChipUpdater(QObject* parent = nullptr); + +protected: + virtual QUrl manifestLocation() const override; + virtual QUrl parseManifest(const QByteArray&) const override; + virtual QString destination() const override; +}; + +} \ No newline at end of file diff --git a/src/platform/qt/BattleChipView.cpp b/src/platform/qt/BattleChipView.cpp index a86cad1d1..7baa7deb5 100644 --- a/src/platform/qt/BattleChipView.cpp +++ b/src/platform/qt/BattleChipView.cpp @@ -5,11 +5,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "BattleChipView.h" +#include "BattleChipUpdater.h" +#include "ConfigController.h" #include "CoreController.h" +#include "GBAApp.h" #include "ShortcutController.h" #include "Window.h" #include +#include #include #include #include @@ -59,6 +63,7 @@ BattleChipView::BattleChipView(std::shared_ptr controller, Windo connect(controller.get(), &CoreController::stopping, this, &QWidget::close); connect(m_ui.save, &QAbstractButton::clicked, this, &BattleChipView::saveDeck); connect(m_ui.load, &QAbstractButton::clicked, this, &BattleChipView::loadDeck); + connect(m_ui.updateData, &QAbstractButton::clicked, this, &BattleChipView::updateData); connect(m_ui.buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, &m_model, &BattleChipModel::clear); connect(m_ui.gateBattleChip, &QAbstractButton::toggled, this, [this](bool on) { @@ -101,6 +106,18 @@ BattleChipView::BattleChipView(std::shared_ptr controller, Windo } else if (qtitle.startsWith("AGB-BR5") || qtitle.startsWith("AGB-BR6")) { m_ui.gateBeastLink->setChecked(Qt::Checked); } + + if (!QFileInfo(GBAApp::dataDir() + "/chips.rcc").exists() && !QFileInfo(ConfigController::configDir() + "/chips.rcc").exists()) { + QMessageBox* download = new QMessageBox(this); + download->setIcon(QMessageBox::Information); + download->setStandardButtons(QMessageBox::Yes | QMessageBox::No); + download->setWindowTitle(tr("BattleChip data missing")); + download->setText(tr("BattleChip data is missing. BattleChip Gates will still work, but some graphics will be missing. Would you like to download the data now?")); + download->setAttribute(Qt::WA_DeleteOnClose); + download->setWindowModality(Qt::NonModal); + connect(download, &QDialog::accepted, this, &BattleChipView::updateData); + download->show(); + } } BattleChipView::~BattleChipView() { @@ -225,4 +242,21 @@ void BattleChipView::resort() { chips[x] = m_model.data(index, Qt::UserRole).toInt(); } m_model.setChips(chips.values()); +} + +void BattleChipView::updateData() { + if (m_updater) { + return; + } + m_updater = new BattleChipUpdater(this); + connect(m_updater, &BattleChipUpdater::updateDone, this, [this](bool success) { + if (success) { + m_model.reloadAssets(); + m_ui.chipName->clear(); + m_ui.chipName->addItems(m_model.chipNames().values()); + } + delete m_updater; + m_updater = nullptr; + }); + m_updater->downloadUpdate(); } \ No newline at end of file diff --git a/src/platform/qt/BattleChipView.h b/src/platform/qt/BattleChipView.h index ea96c4284..0617c6f98 100644 --- a/src/platform/qt/BattleChipView.h +++ b/src/platform/qt/BattleChipView.h @@ -41,6 +41,8 @@ private slots: void saveDeck(); void loadDeck(); + void updateData(); + private: static const int UNINSERTED_TIME = 10; @@ -55,6 +57,8 @@ private: bool m_next = false; Window* m_window; + + BattleChipUpdater* m_updater = nullptr; }; } diff --git a/src/platform/qt/BattleChipView.ui b/src/platform/qt/BattleChipView.ui index 8fb9122d3..0b50fe0be 100644 --- a/src/platform/qt/BattleChipView.ui +++ b/src/platform/qt/BattleChipView.ui @@ -6,7 +6,7 @@ 0 0 - 630 + 658 722 @@ -195,6 +195,19 @@ + + + + + 0 + 0 + + + + Update Chip data + + + diff --git a/src/platform/qt/CMakeLists.txt b/src/platform/qt/CMakeLists.txt index e3dc64053..cc719d97a 100644 --- a/src/platform/qt/CMakeLists.txt +++ b/src/platform/qt/CMakeLists.txt @@ -66,6 +66,7 @@ endif() set(SOURCE_FILES AboutScreen.cpp + AbstractUpdater.cpp AssetTile.cpp AssetView.cpp AudioProcessor.cpp @@ -148,6 +149,7 @@ set(UI_FILES set(GBA_SRC BattleChipModel.cpp + BattleChipUpdater.cpp BattleChipView.cpp GBAOverride.cpp)