mirror of https://github.com/mgba-emu/mgba.git
All: Add updater stub and hook it into the Qt frontend
This commit is contained in:
parent
4a7feb66f9
commit
2d5f6dd675
|
@ -908,6 +908,19 @@ if(BUILD_OPENEMU)
|
|||
install(TARGETS ${BINARY_NAME}-openemu LIBRARY DESTINATION ${OE_LIBDIR} COMPONENT ${BINARY_NAME}.oecoreplugin NAMELINK_SKIP)
|
||||
endif()
|
||||
|
||||
if(BUILD_QT AND WIN32)
|
||||
set(BUILD_UPDATER ON)
|
||||
endif()
|
||||
|
||||
if(BUILD_UPDATER)
|
||||
add_executable(updater-stub WIN32 ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/updater-main.c)
|
||||
target_link_libraries(updater-stub ${OS_LIB} ${PLATFORM_LIBRARY} ${BINARY_NAME})
|
||||
if(NOT MSVC)
|
||||
set_target_properties(updater-stub PROPERTIES LINK_FLAGS_RELEASE -s)
|
||||
set_target_properties(updater-stub PROPERTIES LINK_FLAGS_RELWITHDEBINFO -s)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(BUILD_SDL)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/platform/sdl ${CMAKE_CURRENT_BINARY_DIR}/sdl)
|
||||
endif()
|
||||
|
|
|
@ -28,6 +28,8 @@ void ConfigurationSetUIntValue(struct Configuration*, const char* section, const
|
|||
void ConfigurationSetFloatValue(struct Configuration*, const char* section, const char* key, float value);
|
||||
|
||||
bool ConfigurationHasSection(const struct Configuration*, const char* section);
|
||||
void ConfigurationDeleteSection(struct Configuration*, const char* section);
|
||||
|
||||
const char* ConfigurationGetValue(const struct Configuration*, const char* section, const char* key);
|
||||
|
||||
void ConfigurationClearValue(struct Configuration*, const char* section, const char* key);
|
||||
|
|
|
@ -38,6 +38,14 @@ const char* mUpdaterGetBucket(const struct mUpdaterContext*);
|
|||
void mUpdateRecord(struct mCoreConfig*, const char* prefix, const struct mUpdate*);
|
||||
bool mUpdateLoad(const struct mCoreConfig*, const char* prefix, struct mUpdate*);
|
||||
|
||||
void mUpdateRegister(struct mCoreConfig*, const char* arg0, const char* updatePath);
|
||||
void mUpdateDeregister(struct mCoreConfig*);
|
||||
|
||||
const char* mUpdateGetRoot(const struct mCoreConfig*);
|
||||
const char* mUpdateGetCommand(const struct mCoreConfig*);
|
||||
const char* mUpdateGetArchiveExtension(const struct mCoreConfig*);
|
||||
bool mUpdateGetArchivePath(const struct mCoreConfig*, char* out, size_t outLength);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
/* Copyright (c) 2013-2021 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 <mgba/core/config.h>
|
||||
#include <mgba/feature/updater.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <direct.h>
|
||||
#include <io.h>
|
||||
#include <process.h>
|
||||
|
||||
#define mkdir(X, Y) _mkdir(X)
|
||||
#elif defined(_POSIX_C_SOURCE)
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
bool extractArchive(struct VDir* archive, const char* root) {
|
||||
char path[PATH_MAX] = {};
|
||||
struct VDirEntry* vde;
|
||||
uint8_t block[8192];
|
||||
ssize_t size;
|
||||
while ((vde = archive->listNext(archive))) {
|
||||
struct VFile* vfIn;
|
||||
struct VFile* vfOut;
|
||||
const char* fname = strchr(vde->name(vde), '/');
|
||||
if (!fname) {
|
||||
continue;
|
||||
}
|
||||
snprintf(path, sizeof(path), "%s/%s", root, &fname[1]);
|
||||
switch (vde->type(vde)) {
|
||||
case VFS_DIRECTORY:
|
||||
printf("mkdir %s\n", fname);
|
||||
if (mkdir(path, 0755) < 0 && errno != EEXIST) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case VFS_FILE:
|
||||
printf("extract %s\n", fname);
|
||||
vfIn = archive->openFile(archive, vde->name(vde), O_RDONLY);
|
||||
errno = 0;
|
||||
vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC);
|
||||
if (!vfOut && errno == EACCES) {
|
||||
sleep(1);
|
||||
vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC);
|
||||
}
|
||||
if (!vfOut) {
|
||||
vfIn->close(vfIn);
|
||||
return false;
|
||||
}
|
||||
while ((size = vfIn->read(vfIn, block, sizeof(block))) > 0) {
|
||||
vfOut->write(vfOut, block, size);
|
||||
}
|
||||
vfOut->close(vfOut);
|
||||
vfIn->close(vfIn);
|
||||
if (size < 0) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case VFS_UNKNOWN:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
UNUSED(argc);
|
||||
UNUSED(argv);
|
||||
struct mCoreConfig config;
|
||||
char updateArchive[PATH_MAX] = {};
|
||||
const char* root;
|
||||
int ok = 1;
|
||||
|
||||
mCoreConfigInit(&config, "updater");
|
||||
if (!mCoreConfigLoad(&config)) {
|
||||
puts("Failed to load config");
|
||||
} else if (!mUpdateGetArchivePath(&config, updateArchive, sizeof(updateArchive)) || !(root = mUpdateGetRoot(&config))) {
|
||||
puts("No pending update found");
|
||||
} else if (access(root, W_OK)) {
|
||||
puts("Cannot write to update path");
|
||||
} else {
|
||||
bool isPortable = mCoreConfigIsPortable();
|
||||
struct VDir* archive = VDirOpenArchive(updateArchive);
|
||||
if (!archive) {
|
||||
puts("Cannot open update archive");
|
||||
} else {
|
||||
puts("Extracting update");
|
||||
if (extractArchive(archive, root)) {
|
||||
puts("Complete");
|
||||
ok = 0;
|
||||
mUpdateDeregister(&config);
|
||||
} else {
|
||||
puts("An error occurred");
|
||||
}
|
||||
archive->close(archive);
|
||||
unlink(updateArchive);
|
||||
}
|
||||
if (!isPortable) {
|
||||
char portableIni[PATH_MAX] = {};
|
||||
snprintf(portableIni, sizeof(portableIni), "%s/portable.ini", root);
|
||||
unlink(portableIni);
|
||||
}
|
||||
}
|
||||
const char* bin = mUpdateGetCommand(&config);
|
||||
mCoreConfigDeinit(&config);
|
||||
if (ok == 0) {
|
||||
const char* argv[] = { bin, NULL };
|
||||
#ifdef _WIN32
|
||||
_execv(bin, argv);
|
||||
#elif defined(_POSIX_C_SOURCE)
|
||||
execv(bin, argv);
|
||||
#endif
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <mgba-util/string.h>
|
||||
#include <mgba-util/vector.h>
|
||||
|
||||
int wmain(int argc, wchar_t* argv[]) {
|
||||
struct StringList argv8;
|
||||
StringListInit(&argv8, argc);
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
*StringListAppend(&argv8) = utf16to8((uint16_t*) argv[i], wcslen(argv[i]) * 2);
|
||||
}
|
||||
int ret = main(argc, StringListGetPointer(&argv8, 0));
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < StringListSize(&argv8); ++i) {
|
||||
free(*StringListGetPointer(&argv8, i));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
|
@ -5,10 +5,13 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include <mgba/feature/updater.h>
|
||||
|
||||
#include <mgba-util/string.h>
|
||||
#include <mgba-util/table.h>
|
||||
#include <mgba-util/vector.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
#define UPDATE_SECTION "update"
|
||||
|
||||
struct mUpdateMatch {
|
||||
const char* channel;
|
||||
struct mUpdate* out;
|
||||
|
@ -168,3 +171,54 @@ bool mUpdateLoad(const struct mCoreConfig* config, const char* prefix, struct mU
|
|||
update->sha256 = mCoreConfigGetValue(config, key);
|
||||
return true;
|
||||
}
|
||||
|
||||
void mUpdateRegister(struct mCoreConfig* config, const char* arg0, const char* updatePath) {
|
||||
struct Configuration* cfg = &config->configTable;
|
||||
char filename[PATH_MAX];
|
||||
|
||||
strlcpy(filename, arg0, sizeof(filename));
|
||||
char* last;
|
||||
#ifdef _WIN32
|
||||
last = strrchr(filename, '\\');
|
||||
#else
|
||||
last = strrchr(filename, '/');
|
||||
#endif
|
||||
if (last) {
|
||||
last[0] = '\0';
|
||||
}
|
||||
ConfigurationSetValue(cfg, UPDATE_SECTION, "bin", arg0);
|
||||
ConfigurationSetValue(cfg, UPDATE_SECTION, "root", filename);
|
||||
|
||||
separatePath(updatePath, NULL, NULL, filename);
|
||||
ConfigurationSetValue(cfg, UPDATE_SECTION, "extension", filename);
|
||||
mCoreConfigSave(config);
|
||||
}
|
||||
|
||||
void mUpdateDeregister(struct mCoreConfig* config) {
|
||||
ConfigurationDeleteSection(&config->configTable, UPDATE_SECTION);
|
||||
mCoreConfigSave(config);
|
||||
}
|
||||
|
||||
const char* mUpdateGetRoot(const struct mCoreConfig* config) {
|
||||
return ConfigurationGetValue(&config->configTable, UPDATE_SECTION, "root");
|
||||
}
|
||||
|
||||
const char* mUpdateGetCommand(const struct mCoreConfig* config) {
|
||||
return ConfigurationGetValue(&config->configTable, UPDATE_SECTION, "bin");
|
||||
}
|
||||
|
||||
const char* mUpdateGetArchiveExtension(const struct mCoreConfig* config) {
|
||||
return ConfigurationGetValue(&config->configTable, UPDATE_SECTION, "extension");
|
||||
}
|
||||
|
||||
bool mUpdateGetArchivePath(const struct mCoreConfig* config, char* out, size_t outLength) {
|
||||
const char* extension = ConfigurationGetValue(&config->configTable, UPDATE_SECTION, "extension");
|
||||
if (!extension) {
|
||||
return false;
|
||||
}
|
||||
mCoreConfigDirectory(out, outLength);
|
||||
size_t start = strlen(out);
|
||||
outLength -= start;
|
||||
snprintf(&out[start], outLength, PATH_SEP "update.%s", extension);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/* Copyright (c) 2013-2021 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 "ApplicationUpdatePrompt.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "ApplicationUpdater.h"
|
||||
#include "GBAApp.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <mgba/core/version.h>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
ApplicationUpdatePrompt::ApplicationUpdatePrompt(QWidget* parent)
|
||||
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
ApplicationUpdater* updater = GBAApp::app()->updater();
|
||||
ApplicationUpdater::UpdateInfo info = updater->updateInfo();
|
||||
m_ui.text->setText(tr("An update to %1 is available.\nDo you want to download and install it now? You will need to restart the emulator when the download is complete.")
|
||||
.arg(QLatin1String(projectName)));
|
||||
m_ui.details->setText(tr("Current version: %1\nNew version: %2\nDownload size: %3")
|
||||
.arg(QLatin1String(projectVersion))
|
||||
.arg(info)
|
||||
.arg(niceSizeFormat(info.size)));
|
||||
m_ui.progressBar->setVisible(false);
|
||||
|
||||
connect(updater, &AbstractUpdater::updateProgress, this, [this](float progress) {
|
||||
m_ui.progressBar->setValue(progress * 100);
|
||||
});
|
||||
m_okDownload = connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &ApplicationUpdatePrompt::startUpdate);
|
||||
connect(updater, &AbstractUpdater::updateDone, this, &ApplicationUpdatePrompt::promptRestart);
|
||||
}
|
||||
|
||||
void ApplicationUpdatePrompt::startUpdate() {
|
||||
ApplicationUpdater* updater = GBAApp::app()->updater();
|
||||
updater->downloadUpdate();
|
||||
|
||||
m_ui.buttonBox->disconnect(m_okDownload);
|
||||
m_ui.progressBar->show();
|
||||
m_ui.text->setText(tr("Downloading update..."));
|
||||
m_ui.details->hide();
|
||||
m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||
}
|
||||
|
||||
void ApplicationUpdatePrompt::promptRestart() {
|
||||
ApplicationUpdater* updater = GBAApp::app()->updater();
|
||||
QString filename = updater->destination();
|
||||
|
||||
QByteArray expectedHash = updater->updateInfo().sha256;
|
||||
QCryptographicHash sha256(QCryptographicHash::Sha256);
|
||||
QFile update(filename);
|
||||
update.open(QIODevice::ReadOnly);
|
||||
if (!sha256.addData(&update) || sha256.result() != expectedHash) {
|
||||
update.close();
|
||||
update.remove();
|
||||
m_ui.text->setText(tr("Downloading failed. Please update manually.")
|
||||
.arg(QLatin1String(projectName)));
|
||||
} else {
|
||||
m_ui.text->setText(tr("Downloading done. Press OK to restart %1 and install the update.")
|
||||
.arg(QLatin1String(projectName)));
|
||||
}
|
||||
m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
|
||||
connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/* Copyright (c) 2013-2021 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 <QDialog>
|
||||
|
||||
#include "ui_ApplicationUpdatePrompt.h"
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class ApplicationUpdatePrompt : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ApplicationUpdatePrompt(QWidget* parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void startUpdate();
|
||||
void promptRestart();
|
||||
|
||||
private:
|
||||
Ui::ApplicationUpdatePrompt m_ui;
|
||||
QMetaObject::Connection m_okDownload;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ApplicationUpdatePrompt</class>
|
||||
<widget class="QDialog" name="ApplicationUpdatePrompt">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>186</width>
|
||||
<height>127</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>An update is available</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetFixedSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="text">
|
||||
<property name="text">
|
||||
<string>{text}</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="details">
|
||||
<property name="text">
|
||||
<string>{details}</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>ApplicationUpdatePrompt</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
|
@ -5,8 +5,10 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "ApplicationUpdater.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "ApplicationUpdatePrompt.h"
|
||||
#include "ConfigController.h"
|
||||
#include "GBAApp.h"
|
||||
|
||||
|
@ -37,9 +39,26 @@ ApplicationUpdater::ApplicationUpdater(ConfigController* config, QObject* parent
|
|||
}
|
||||
}
|
||||
|
||||
connect(this, &AbstractUpdater::updateAvailable, this, [this, config](bool) {
|
||||
connect(this, &AbstractUpdater::updateAvailable, this, [this, config](bool available) {
|
||||
m_lastCheck = QDateTime::currentDateTimeUtc();
|
||||
config->setQtOption("lastUpdateCheck", m_lastCheck);
|
||||
|
||||
if (available && currentVersion() < updateInfo()) {
|
||||
#ifdef Q_OS_WIN
|
||||
// Only works on Windows at the moment
|
||||
ApplicationUpdatePrompt* prompt = new ApplicationUpdatePrompt;
|
||||
connect(prompt, &QDialog::accepted, GBAApp::app(), &GBAApp::restartForUpdate);
|
||||
prompt->setAttribute(Qt::WA_DeleteOnClose);
|
||||
prompt->show();
|
||||
#endif
|
||||
}
|
||||
});
|
||||
|
||||
connect(this, &AbstractUpdater::updateDone, this, [this, config]() {
|
||||
QByteArray arg0 = GBAApp::app()->arguments().at(0).toUtf8();
|
||||
QByteArray path = updateInfo().url.path().toUtf8();
|
||||
mUpdateRegister(config->config(), arg0.constData(), path.constData());
|
||||
config->write();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -79,6 +98,7 @@ QString ApplicationUpdater::readableChannel(const QString& channel) {
|
|||
|
||||
ApplicationUpdater::UpdateInfo ApplicationUpdater::currentVersion() {
|
||||
UpdateInfo info;
|
||||
info.version = QLatin1String(projectVersion);
|
||||
info.rev = gitRevision;
|
||||
info.commit = QLatin1String(gitCommit);
|
||||
return info;
|
||||
|
@ -118,14 +138,20 @@ QUrl ApplicationUpdater::parseManifest(const QByteArray& manifest) {
|
|||
}
|
||||
|
||||
QString ApplicationUpdater::destination() const {
|
||||
return {};
|
||||
QFileInfo path(updateInfo().url.path());
|
||||
QDir dir(ConfigController::configDir());
|
||||
return dir.filePath(QLatin1String("update.") + path.completeSuffix());
|
||||
}
|
||||
|
||||
const char* ApplicationUpdater::platform() {
|
||||
#ifdef Q_OS_WIN
|
||||
QFileInfo exe(GBAApp::app()->arguments().at(0));
|
||||
QFileInfo uninstallInfo(exe.dir().filePath("unins000.dat"));
|
||||
#ifdef Q_OS_WIN64
|
||||
return "win64";
|
||||
return uninstallInfo.exists() ? "win64-installer" : "win64";
|
||||
#elif defined(Q_OS_WIN32)
|
||||
return "win32";
|
||||
return uninstallInfo.exists() ? "win32-installer" : "win32";
|
||||
#endif
|
||||
#elif defined(Q_OS_MACOS)
|
||||
return "osx";
|
||||
#else
|
||||
|
@ -160,8 +186,8 @@ bool ApplicationUpdater::UpdateInfo::operator<(const ApplicationUpdater::UpdateI
|
|||
QStringList components = version.split(QChar('.'));
|
||||
QStringList otherComponents = other.version.split(QChar('.'));
|
||||
for (int i = 0; i < std::max<int>(components.count(), otherComponents.count()); ++i) {
|
||||
int component = 0;
|
||||
int otherComponent = 0;
|
||||
int component = -1;
|
||||
int otherComponent = -1;
|
||||
if (i < components.count()) {
|
||||
bool ok = true;
|
||||
component = components[i].toInt(&ok);
|
||||
|
@ -189,6 +215,9 @@ ApplicationUpdater::UpdateInfo::operator QString() const {
|
|||
if (!version.isNull()) {
|
||||
return version;
|
||||
}
|
||||
if (rev <= 0) {
|
||||
return tr("(None)");
|
||||
}
|
||||
int len = strlen(gitCommitShort);
|
||||
const char* pos = strchr(gitCommitShort, '-');
|
||||
if (pos) {
|
||||
|
|
|
@ -49,10 +49,11 @@ public:
|
|||
|
||||
QDateTime lastCheck() const { return m_lastCheck; }
|
||||
|
||||
virtual QString destination() const override;
|
||||
|
||||
protected:
|
||||
virtual QUrl manifestLocation() const override;
|
||||
virtual QUrl parseManifest(const QByteArray&) override;
|
||||
virtual QString destination() const override;
|
||||
|
||||
private:
|
||||
static const char* platform();
|
||||
|
|
|
@ -61,6 +61,7 @@ set(SOURCE_FILES
|
|||
Action.cpp
|
||||
ActionMapper.cpp
|
||||
ApplicationUpdater.cpp
|
||||
ApplicationUpdatePrompt.cpp
|
||||
AssetInfo.cpp
|
||||
AssetTile.cpp
|
||||
AssetView.cpp
|
||||
|
@ -123,6 +124,7 @@ set(SOURCE_FILES
|
|||
|
||||
set(UI_FILES
|
||||
AboutScreen.ui
|
||||
ApplicationUpdatePrompt.ui
|
||||
ArchiveInspector.ui
|
||||
AssetTile.ui
|
||||
BattleChipView.ui
|
||||
|
@ -228,6 +230,11 @@ if(USE_DISCORD_RPC)
|
|||
endif()
|
||||
|
||||
qt5_add_resources(RESOURCES resources.qrc)
|
||||
if(BUILD_UPDATER)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/updater.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc)
|
||||
qt5_add_resources(UPDATER_RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc)
|
||||
list(APPEND RESOURCES ${UPDATER_RESOURCES})
|
||||
endif()
|
||||
if(APPLE)
|
||||
set(MACOSX_BUNDLE_ICON_FILE mgba.icns)
|
||||
set(MACOSX_BUNDLE_BUNDLE_VERSION ${LIB_VERSION_STRING})
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <QFontDatabase>
|
||||
#include <QIcon>
|
||||
|
||||
#include <mgba/feature/updater.h>
|
||||
#include <mgba-util/socket.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
|
@ -80,6 +81,8 @@ GBAApp::GBAApp(int& argc, char* argv[], ConfigController* config)
|
|||
m_configController->updateOption("useDiscordPresence");
|
||||
#endif
|
||||
|
||||
cleanupAfterUpdate();
|
||||
|
||||
connect(this, &GBAApp::aboutToQuit, this, &GBAApp::cleanup);
|
||||
if (m_configController->getOption("updateAutoCheck", 0).toInt()) {
|
||||
QMetaObject::invokeMethod(&m_updater, "checkUpdate", Qt::QueuedConnection);
|
||||
|
@ -268,7 +271,6 @@ bool GBAApp::removeWorkerJob(qint64 jobId) {
|
|||
return success;
|
||||
}
|
||||
|
||||
|
||||
bool GBAApp::waitOnJob(qint64 jobId, QObject* context, std::function<void ()> callback) {
|
||||
if (!m_workerJobs.contains(jobId)) {
|
||||
return false;
|
||||
|
@ -286,6 +288,52 @@ bool GBAApp::waitOnJob(qint64 jobId, QObject* context, std::function<void ()> ca
|
|||
return true;
|
||||
}
|
||||
|
||||
void GBAApp::cleanupAfterUpdate() {
|
||||
// Remove leftover updater if there's one present
|
||||
QDir configDir(ConfigController::configDir());
|
||||
QString extractedPath = configDir.filePath(QLatin1String("updater"));
|
||||
#ifdef Q_OS_WIN
|
||||
extractedPath += ".exe";
|
||||
#endif
|
||||
QFile updater(extractedPath);
|
||||
if (updater.exists()) {
|
||||
updater.remove();
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// Remove the installer exe if we downloaded that too
|
||||
extractedPath = configDir.filePath(QLatin1String("update.exe"));
|
||||
QFile update(extractedPath);
|
||||
if (update.exists()) {
|
||||
update.remove();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void GBAApp::restartForUpdate() {
|
||||
QFileInfo updaterPath(m_updater.updateInfo().url.path());
|
||||
QDir configDir(ConfigController::configDir());
|
||||
if (updaterPath.completeSuffix() == "exe") {
|
||||
m_invokeOnExit = configDir.filePath(QLatin1String("update.exe"));
|
||||
} else {
|
||||
QFile updater(":/updater");
|
||||
QString extractedPath = configDir.filePath(QLatin1String("updater"));
|
||||
#ifdef Q_OS_WIN
|
||||
extractedPath += ".exe";
|
||||
#endif
|
||||
updater.copy(extractedPath);
|
||||
#ifndef Q_OS_WIN
|
||||
QFile(extractedPath).setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner);
|
||||
#endif
|
||||
m_invokeOnExit = extractedPath;
|
||||
}
|
||||
|
||||
for (auto& window : m_windows) {
|
||||
window->deleteLater();
|
||||
}
|
||||
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void GBAApp::finishJob(qint64 jobId) {
|
||||
m_workerJobs.remove(jobId);
|
||||
emit jobFinished(jobId);
|
||||
|
|
|
@ -77,6 +77,10 @@ public:
|
|||
bool waitOnJob(qint64 jobId, QObject* context, std::function<void ()> callback);
|
||||
|
||||
ApplicationUpdater* updater() { return &m_updater; }
|
||||
QString invokeOnExit() { return m_invokeOnExit; }
|
||||
|
||||
public slots:
|
||||
void restartForUpdate();
|
||||
|
||||
signals:
|
||||
void jobFinished(qint64 jobId);
|
||||
|
@ -104,6 +108,8 @@ private:
|
|||
|
||||
Window* newWindowInternal();
|
||||
|
||||
void cleanupAfterUpdate();
|
||||
|
||||
void pauseAll(QList<Window*>* paused);
|
||||
void continueAll(const QList<Window*>& paused);
|
||||
|
||||
|
@ -112,6 +118,7 @@ private:
|
|||
MultiplayerController m_multiplayer;
|
||||
CoreManager m_manager;
|
||||
ApplicationUpdater m_updater;
|
||||
QString m_invokeOnExit;
|
||||
|
||||
QMap<qint64, WorkerJob*> m_workerJobs;
|
||||
QMultiMap<qint64, QMetaObject::Connection> m_workerJobCallbacks;
|
||||
|
|
|
@ -39,6 +39,12 @@ Q_IMPORT_PLUGIN(AVFServicePlugin);
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <process.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
|
@ -121,7 +127,21 @@ int main(int argc, char* argv[]) {
|
|||
|
||||
w->show();
|
||||
|
||||
return application.exec();
|
||||
int ret = application.exec();
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
QString invoke = application.invokeOnExit();
|
||||
if (!invoke.isNull()) {
|
||||
QByteArray proc = invoke.toUtf8();
|
||||
#ifdef Q_OS_WIN
|
||||
_execl(proc.constData(), proc.constData(), NULL);
|
||||
#else
|
||||
execl(proc.constData(), proc.constData(), NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
|
|
@ -137,6 +137,10 @@ bool ConfigurationHasSection(const struct Configuration* configuration, const ch
|
|||
return HashTableLookup(&configuration->sections, section);
|
||||
}
|
||||
|
||||
void ConfigurationDeleteSection(struct Configuration* configuration, const char* section) {
|
||||
HashTableRemove(&configuration->sections, section);
|
||||
}
|
||||
|
||||
const char* ConfigurationGetValue(const struct Configuration* configuration, const char* section, const char* key) {
|
||||
const struct Table* currentSection = &configuration->root;
|
||||
if (section) {
|
||||
|
|
Loading…
Reference in New Issue