Qt: Add option to download chip data

This commit is contained in:
Vicki Pfau 2019-02-26 20:55:39 -08:00
parent 38c8e4c4e1
commit c9e1f5d6a6
10 changed files with 277 additions and 1 deletions

View File

@ -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);
}

View File

@ -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;
};
}

View File

@ -155,6 +155,21 @@ void BattleChipModel::setScale(int scale) {
m_scale = 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 { BattleChipModel::BattleChip BattleChipModel::createChip(int id) const {
QString path = QString(":/exe/exe%1/%2.png").arg(m_flavor).arg(id, 3, 10, QLatin1Char('0')); QString path = QString(":/exe/exe%1/%2.png").arg(m_flavor).arg(id, 3, 10, QLatin1Char('0'));
if (!QFile(path).exists()) { if (!QFile(path).exists()) {

View File

@ -10,6 +10,8 @@
namespace QGBA { namespace QGBA {
class BattleChipUpdater;
class BattleChipModel : public QAbstractListModel { class BattleChipModel : public QAbstractListModel {
Q_OBJECT Q_OBJECT
@ -35,6 +37,7 @@ public slots:
void setChips(QList<int> ids); void setChips(QList<int> ids);
void clear(); void clear();
void setScale(int); void setScale(int);
void reloadAssets();
private: private:
struct BattleChip { struct BattleChip {

View File

@ -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";
}

View File

@ -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;
};
}

View File

@ -5,11 +5,15 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "BattleChipView.h" #include "BattleChipView.h"
#include "BattleChipUpdater.h"
#include "ConfigController.h"
#include "CoreController.h" #include "CoreController.h"
#include "GBAApp.h"
#include "ShortcutController.h" #include "ShortcutController.h"
#include "Window.h" #include "Window.h"
#include <QtAlgorithms> #include <QtAlgorithms>
#include <QFileInfo>
#include <QFontMetrics> #include <QFontMetrics>
#include <QMessageBox> #include <QMessageBox>
#include <QMultiMap> #include <QMultiMap>
@ -59,6 +63,7 @@ BattleChipView::BattleChipView(std::shared_ptr<CoreController> controller, Windo
connect(controller.get(), &CoreController::stopping, this, &QWidget::close); connect(controller.get(), &CoreController::stopping, this, &QWidget::close);
connect(m_ui.save, &QAbstractButton::clicked, this, &BattleChipView::saveDeck); connect(m_ui.save, &QAbstractButton::clicked, this, &BattleChipView::saveDeck);
connect(m_ui.load, &QAbstractButton::clicked, this, &BattleChipView::loadDeck); 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.buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, &m_model, &BattleChipModel::clear);
connect(m_ui.gateBattleChip, &QAbstractButton::toggled, this, [this](bool on) { connect(m_ui.gateBattleChip, &QAbstractButton::toggled, this, [this](bool on) {
@ -101,6 +106,18 @@ BattleChipView::BattleChipView(std::shared_ptr<CoreController> controller, Windo
} else if (qtitle.startsWith("AGB-BR5") || qtitle.startsWith("AGB-BR6")) { } else if (qtitle.startsWith("AGB-BR5") || qtitle.startsWith("AGB-BR6")) {
m_ui.gateBeastLink->setChecked(Qt::Checked); 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() { BattleChipView::~BattleChipView() {
@ -225,4 +242,21 @@ void BattleChipView::resort() {
chips[x] = m_model.data(index, Qt::UserRole).toInt(); chips[x] = m_model.data(index, Qt::UserRole).toInt();
} }
m_model.setChips(chips.values()); 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();
} }

View File

@ -41,6 +41,8 @@ private slots:
void saveDeck(); void saveDeck();
void loadDeck(); void loadDeck();
void updateData();
private: private:
static const int UNINSERTED_TIME = 10; static const int UNINSERTED_TIME = 10;
@ -55,6 +57,8 @@ private:
bool m_next = false; bool m_next = false;
Window* m_window; Window* m_window;
BattleChipUpdater* m_updater = nullptr;
}; };
} }

View File

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>630</width> <width>658</width>
<height>722</height> <height>722</height>
</rect> </rect>
</property> </property>
@ -195,6 +195,19 @@
</property> </property>
</widget> </widget>
</item> </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>
</layout> </layout>
</item> </item>
</layout> </layout>

View File

@ -66,6 +66,7 @@ endif()
set(SOURCE_FILES set(SOURCE_FILES
AboutScreen.cpp AboutScreen.cpp
AbstractUpdater.cpp
AssetTile.cpp AssetTile.cpp
AssetView.cpp AssetView.cpp
AudioProcessor.cpp AudioProcessor.cpp
@ -148,6 +149,7 @@ set(UI_FILES
set(GBA_SRC set(GBA_SRC
BattleChipModel.cpp BattleChipModel.cpp
BattleChipUpdater.cpp
BattleChipView.cpp BattleChipView.cpp
GBAOverride.cpp) GBAOverride.cpp)