Qt: Add option to download chip data
Vicki Pfau vi@endrift.com
Tue, 26 Feb 2019 20:55:39 -0800
10 files changed,
277 insertions(+),
1 deletions(-)
jump to
A
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 <QNetworkAccessManager> +#include <QNetworkReply> + +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); +}
A
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 <QByteArray> +#include <QFile> +#include <QObject> + +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; +}; + +}
M
src/platform/qt/BattleChipModel.cpp
→
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()) {
M
src/platform/qt/BattleChipModel.h
→
src/platform/qt/BattleChipModel.h
@@ -10,6 +10,8 @@ #include <QPixmap>
namespace QGBA { +class BattleChipUpdater; + class BattleChipModel : public QAbstractListModel { Q_OBJECT@@ -35,6 +37,7 @@ void removeChip(const QModelIndex&);
void setChips(QList<int> ids); void clear(); void setScale(int); + void reloadAssets(); private: struct BattleChip {
A
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 <QFileInfo> +#include <QJsonArray> +#include <QJsonDocument> +#include <QJsonObject> + +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"; +}
A
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; +}; + +}
M
src/platform/qt/BattleChipView.cpp
→
src/platform/qt/BattleChipView.cpp
@@ -5,11 +5,15 @@ * 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 "BattleChipView.h" +#include "BattleChipUpdater.h" +#include "ConfigController.h" #include "CoreController.h" +#include "GBAApp.h" #include "ShortcutController.h" #include "Window.h" #include <QtAlgorithms> +#include <QFileInfo> #include <QFontMetrics> #include <QMessageBox> #include <QMultiMap>@@ -59,6 +63,7 @@ connect(m_ui.remove, &QAbstractButton::clicked, this, &BattleChipView::removeChip);
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) {@@ -100,6 +105,18 @@ } else if (qtitle.startsWith("AGB-BRB") || qtitle.startsWith("AGB-BRK")) {
m_ui.gateProgress->setChecked(Qt::Checked); } 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(); } }@@ -225,4 +242,21 @@ x += index.row();
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(); }
M
src/platform/qt/BattleChipView.h
→
src/platform/qt/BattleChipView.h
@@ -41,6 +41,8 @@
void saveDeck(); void loadDeck(); + void updateData(); + private: static const int UNINSERTED_TIME = 10;@@ -55,6 +57,8 @@ int m_frameCounter = -1;
bool m_next = false; Window* m_window; + + BattleChipUpdater* m_updater = nullptr; }; }
M
src/platform/qt/BattleChipView.ui
→
src/platform/qt/BattleChipView.ui
@@ -6,7 +6,7 @@ <property name="geometry">
<rect> <x>0</x> <y>0</y> - <width>630</width> + <width>658</width> <height>722</height> </rect> </property>@@ -192,6 +192,19 @@ <item row="0" column="0">
<widget class="QLabel" name="label_4"> <property name="text"> <string>Chip ID</string> + </property> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QPushButton" name="updateData"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Update Chip data</string> </property> </widget> </item>
M
src/platform/qt/CMakeLists.txt
→
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 @@ VideoView.ui)
set(GBA_SRC BattleChipModel.cpp + BattleChipUpdater.cpp BattleChipView.cpp GBAOverride.cpp)