Qt updater integration, based on QtAutoUpdater
This commit is contained in:
parent
ee5aecee3f
commit
2e6c80d1aa
@ -23,9 +23,10 @@ set(SRCS
|
|||||||
debugger/profiler.cpp
|
debugger/profiler.cpp
|
||||||
debugger/registers.cpp
|
debugger/registers.cpp
|
||||||
debugger/wait_tree.cpp
|
debugger/wait_tree.cpp
|
||||||
aboutdialog.cpp
|
updater/updater.cpp
|
||||||
util/spinbox.cpp
|
util/spinbox.cpp
|
||||||
util/util.cpp
|
util/util.cpp
|
||||||
|
aboutdialog.cpp
|
||||||
bootmanager.cpp
|
bootmanager.cpp
|
||||||
game_list.cpp
|
game_list.cpp
|
||||||
hotkeys.cpp
|
hotkeys.cpp
|
||||||
@ -56,6 +57,8 @@ set(HEADERS
|
|||||||
debugger/profiler.h
|
debugger/profiler.h
|
||||||
debugger/registers.h
|
debugger/registers.h
|
||||||
debugger/wait_tree.h
|
debugger/wait_tree.h
|
||||||
|
updater/updater.h
|
||||||
|
updater/updater_p.h
|
||||||
util/spinbox.h
|
util/spinbox.h
|
||||||
util/util.h
|
util/util.h
|
||||||
aboutdialog.h
|
aboutdialog.h
|
||||||
|
@ -157,6 +157,12 @@ void Config::ReadValues() {
|
|||||||
qt_config->beginGroup("UI");
|
qt_config->beginGroup("UI");
|
||||||
UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
|
UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
|
||||||
|
|
||||||
|
qt_config->beginGroup("Updater");
|
||||||
|
UISettings::values.check_for_update_on_start =
|
||||||
|
qt_config->value("check_for_update_on_start", true).toBool();
|
||||||
|
UISettings::values.update_on_close = qt_config->value("update_on_close", false).toBool();
|
||||||
|
qt_config->endGroup();
|
||||||
|
|
||||||
qt_config->beginGroup("UILayout");
|
qt_config->beginGroup("UILayout");
|
||||||
UISettings::values.geometry = qt_config->value("geometry").toByteArray();
|
UISettings::values.geometry = qt_config->value("geometry").toByteArray();
|
||||||
UISettings::values.state = qt_config->value("state").toByteArray();
|
UISettings::values.state = qt_config->value("state").toByteArray();
|
||||||
@ -307,6 +313,11 @@ void Config::SaveValues() {
|
|||||||
qt_config->beginGroup("UI");
|
qt_config->beginGroup("UI");
|
||||||
qt_config->setValue("theme", UISettings::values.theme);
|
qt_config->setValue("theme", UISettings::values.theme);
|
||||||
|
|
||||||
|
qt_config->beginGroup("Updater");
|
||||||
|
qt_config->setValue("check_for_update_on_start", UISettings::values.check_for_update_on_start);
|
||||||
|
qt_config->setValue("update_on_close", UISettings::values.update_on_close);
|
||||||
|
qt_config->endGroup();
|
||||||
|
|
||||||
qt_config->beginGroup("UILayout");
|
qt_config->beginGroup("UILayout");
|
||||||
qt_config->setValue("geometry", UISettings::values.geometry);
|
qt_config->setValue("geometry", UISettings::values.geometry);
|
||||||
qt_config->setValue("state", UISettings::values.state);
|
qt_config->setValue("state", UISettings::values.state);
|
||||||
|
@ -20,6 +20,7 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
|
|||||||
this->setConfiguration();
|
this->setConfiguration();
|
||||||
|
|
||||||
ui->toggle_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn());
|
ui->toggle_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn());
|
||||||
|
ui->updateBox->setVisible(UISettings::values.updater_found);
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigureGeneral::~ConfigureGeneral() {}
|
ConfigureGeneral::~ConfigureGeneral() {}
|
||||||
@ -29,6 +30,9 @@ void ConfigureGeneral::setConfiguration() {
|
|||||||
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
|
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
|
||||||
ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit);
|
ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit);
|
||||||
|
|
||||||
|
ui->toggle_update_check->setChecked(UISettings::values.check_for_update_on_start);
|
||||||
|
ui->toggle_auto_update->setChecked(UISettings::values.update_on_close);
|
||||||
|
|
||||||
// The first item is "auto-select" with actual value -1, so plus one here will do the trick
|
// The first item is "auto-select" with actual value -1, so plus one here will do the trick
|
||||||
ui->region_combobox->setCurrentIndex(Settings::values.region_value + 1);
|
ui->region_combobox->setCurrentIndex(Settings::values.region_value + 1);
|
||||||
|
|
||||||
@ -40,6 +44,10 @@ void ConfigureGeneral::applyConfiguration() {
|
|||||||
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
|
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
|
||||||
UISettings::values.theme =
|
UISettings::values.theme =
|
||||||
ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
|
ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
|
||||||
|
|
||||||
|
UISettings::values.check_for_update_on_start = ui->toggle_update_check->isChecked();
|
||||||
|
UISettings::values.update_on_close = ui->toggle_auto_update->isChecked();
|
||||||
|
|
||||||
Settings::values.region_value = ui->region_combobox->currentIndex() - 1;
|
Settings::values.region_value = ui->region_combobox->currentIndex() - 1;
|
||||||
Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
|
Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
|
||||||
Settings::Apply();
|
Settings::Apply();
|
||||||
|
@ -24,6 +24,13 @@
|
|||||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="toggle_check_exit">
|
||||||
|
<property name="text">
|
||||||
|
<string>Confirm exit while emulation is running</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="toggle_deepscan">
|
<widget class="QCheckBox" name="toggle_deepscan">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -31,10 +38,30 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="toggle_check_exit">
|
<widget class="QGroupBox" name="updateBox">
|
||||||
|
<property name="title">
|
||||||
|
<string>Updates</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_update">
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_update">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="toggle_update_check">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Confirm exit while emulation is running</string>
|
<string>Check for updates on start</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="toggle_auto_update">
|
||||||
|
<property name="text">
|
||||||
|
<string>Silently auto update after closing</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -149,8 +176,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QComboBox" name="theme_combobox">
|
<widget class="QComboBox" name="theme_combobox"/>
|
||||||
</widget>
|
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#include "citra_qt/hotkeys.h"
|
#include "citra_qt/hotkeys.h"
|
||||||
#include "citra_qt/main.h"
|
#include "citra_qt/main.h"
|
||||||
#include "citra_qt/ui_settings.h"
|
#include "citra_qt/ui_settings.h"
|
||||||
|
#include "citra_qt/updater/updater.h"
|
||||||
#include "common/logging/backend.h"
|
#include "common/logging/backend.h"
|
||||||
#include "common/logging/filter.h"
|
#include "common/logging/filter.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
@ -100,6 +101,7 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
|
|||||||
InitializeDebugWidgets();
|
InitializeDebugWidgets();
|
||||||
InitializeRecentFileMenuActions();
|
InitializeRecentFileMenuActions();
|
||||||
InitializeHotkeys();
|
InitializeHotkeys();
|
||||||
|
ShowUpdaterWidgets();
|
||||||
|
|
||||||
SetDefaultUIGeometry();
|
SetDefaultUIGeometry();
|
||||||
RestoreUIState();
|
RestoreUIState();
|
||||||
@ -118,6 +120,10 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
|
|||||||
// Show one-time "callout" messages to the user
|
// Show one-time "callout" messages to the user
|
||||||
ShowCallouts();
|
ShowCallouts();
|
||||||
|
|
||||||
|
if (UISettings::values.check_for_update_on_start) {
|
||||||
|
CheckForUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
QStringList args = QApplication::arguments();
|
QStringList args = QApplication::arguments();
|
||||||
if (args.length() >= 2) {
|
if (args.length() >= 2) {
|
||||||
BootGame(args[1]);
|
BootGame(args[1]);
|
||||||
@ -139,6 +145,10 @@ void GMainWindow::InitializeWidgets() {
|
|||||||
game_list = new GameList(this);
|
game_list = new GameList(this);
|
||||||
ui.horizontalLayout->addWidget(game_list);
|
ui.horizontalLayout->addWidget(game_list);
|
||||||
|
|
||||||
|
// Setup updater
|
||||||
|
updater = new Updater(this);
|
||||||
|
UISettings::values.updater_found = updater->HasUpdater();
|
||||||
|
|
||||||
// Create status bar
|
// Create status bar
|
||||||
message_label = new QLabel();
|
message_label = new QLabel();
|
||||||
// Configured separately for left alignment
|
// Configured separately for left alignment
|
||||||
@ -268,6 +278,13 @@ void GMainWindow::InitializeHotkeys() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GMainWindow::ShowUpdaterWidgets() {
|
||||||
|
ui.action_Check_For_Updates->setVisible(UISettings::values.updater_found);
|
||||||
|
ui.action_Open_Maintenance_Tool->setVisible(UISettings::values.updater_found);
|
||||||
|
|
||||||
|
connect(updater, &Updater::CheckUpdatesDone, this, &GMainWindow::OnUpdateFound);
|
||||||
|
}
|
||||||
|
|
||||||
void GMainWindow::SetDefaultUIGeometry() {
|
void GMainWindow::SetDefaultUIGeometry() {
|
||||||
// geometry: 55% of the window contents are in the upper screen half, 45% in the lower half
|
// geometry: 55% of the window contents are in the upper screen half, 45% in the lower half
|
||||||
const QRect screenRect = QApplication::desktop()->screenGeometry(this);
|
const QRect screenRect = QApplication::desktop()->screenGeometry(this);
|
||||||
@ -346,6 +363,10 @@ void GMainWindow::ConnectMenuEvents() {
|
|||||||
connect(ui.action_FAQ, &QAction::triggered,
|
connect(ui.action_FAQ, &QAction::triggered,
|
||||||
[]() { QDesktopServices::openUrl(QUrl("https://citra-emu.org/wiki/faq/")); });
|
[]() { QDesktopServices::openUrl(QUrl("https://citra-emu.org/wiki/faq/")); });
|
||||||
connect(ui.action_About, &QAction::triggered, this, &GMainWindow::OnMenuAboutCitra);
|
connect(ui.action_About, &QAction::triggered, this, &GMainWindow::OnMenuAboutCitra);
|
||||||
|
connect(ui.action_Check_For_Updates, &QAction::triggered, this,
|
||||||
|
&GMainWindow::OnCheckForUpdates);
|
||||||
|
connect(ui.action_Open_Maintenance_Tool, &QAction::triggered, this,
|
||||||
|
&GMainWindow::OnOpenUpdater);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::OnDisplayTitleBars(bool show) {
|
void GMainWindow::OnDisplayTitleBars(bool show) {
|
||||||
@ -368,6 +389,46 @@ void GMainWindow::OnDisplayTitleBars(bool show) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GMainWindow::OnCheckForUpdates() {
|
||||||
|
CheckForUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GMainWindow::CheckForUpdates() {
|
||||||
|
if (updater->CheckForUpdates()) {
|
||||||
|
LOG_INFO(Frontend, "Update check started");
|
||||||
|
} else {
|
||||||
|
LOG_WARNING(Frontend, "Unable to start check for updates");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GMainWindow::OnUpdateFound(bool found, bool error) {
|
||||||
|
if (error) {
|
||||||
|
LOG_WARNING(Frontend, "Update check failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
LOG_INFO(Frontend, "No updates found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO(Frontend, "Update found!");
|
||||||
|
auto result = QMessageBox::question(
|
||||||
|
this, tr("Update available!"),
|
||||||
|
tr("An update for Citra is available. Do you wish to install it now?<br /><br />"
|
||||||
|
"This <b>will</b> terminate emulation, if it is running."),
|
||||||
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||||
|
|
||||||
|
if (result == QMessageBox::Yes) {
|
||||||
|
updater->LaunchUIOnExit();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GMainWindow::OnOpenUpdater() {
|
||||||
|
updater->LaunchUI();
|
||||||
|
}
|
||||||
|
|
||||||
bool GMainWindow::LoadROM(const QString& filename) {
|
bool GMainWindow::LoadROM(const QString& filename) {
|
||||||
// Shutdown previous session if the emu thread is still active...
|
// Shutdown previous session if the emu thread is still active...
|
||||||
if (emu_thread != nullptr)
|
if (emu_thread != nullptr)
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#ifndef _CITRA_QT_MAIN_HXX_
|
#pragma once
|
||||||
#define _CITRA_QT_MAIN_HXX_
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
@ -24,6 +23,7 @@ class GRenderWindow;
|
|||||||
class MicroProfileDialog;
|
class MicroProfileDialog;
|
||||||
class ProfilerWidget;
|
class ProfilerWidget;
|
||||||
class RegistersWidget;
|
class RegistersWidget;
|
||||||
|
class Updater;
|
||||||
class WaitTreeWidget;
|
class WaitTreeWidget;
|
||||||
class AboutDialog;
|
class AboutDialog;
|
||||||
|
|
||||||
@ -82,6 +82,8 @@ private:
|
|||||||
void ShutdownGame();
|
void ShutdownGame();
|
||||||
|
|
||||||
void ShowCallouts();
|
void ShowCallouts();
|
||||||
|
void ShowUpdaterWidgets();
|
||||||
|
void CheckForUpdates();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the filename in the recently loaded files list.
|
* Stores the filename in the recently loaded files list.
|
||||||
@ -134,6 +136,9 @@ private slots:
|
|||||||
void OnCoreError(Core::System::ResultStatus, std::string);
|
void OnCoreError(Core::System::ResultStatus, std::string);
|
||||||
/// Called whenever a user selects Help->About Citra
|
/// Called whenever a user selects Help->About Citra
|
||||||
void OnMenuAboutCitra();
|
void OnMenuAboutCitra();
|
||||||
|
void OnUpdateFound(bool found, bool error);
|
||||||
|
void OnCheckForUpdates();
|
||||||
|
void OnOpenUpdater();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void UpdateStatusBar();
|
void UpdateStatusBar();
|
||||||
@ -166,6 +171,7 @@ private:
|
|||||||
GraphicsVertexShaderWidget* graphicsVertexShaderWidget;
|
GraphicsVertexShaderWidget* graphicsVertexShaderWidget;
|
||||||
GraphicsTracingWidget* graphicsTracingWidget;
|
GraphicsTracingWidget* graphicsTracingWidget;
|
||||||
WaitTreeWidget* waitTreeWidget;
|
WaitTreeWidget* waitTreeWidget;
|
||||||
|
Updater* updater;
|
||||||
|
|
||||||
QAction* actions_recent_files[max_recent_files_item];
|
QAction* actions_recent_files[max_recent_files_item];
|
||||||
|
|
||||||
@ -174,5 +180,3 @@ protected:
|
|||||||
void dragEnterEvent(QDragEnterEvent* event) override;
|
void dragEnterEvent(QDragEnterEvent* event) override;
|
||||||
void dragMoveEvent(QDragMoveEvent* event) override;
|
void dragMoveEvent(QDragMoveEvent* event) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // _CITRA_QT_MAIN_HXX_
|
|
||||||
|
@ -96,6 +96,9 @@
|
|||||||
<property name="title">
|
<property name="title">
|
||||||
<string>&Help</string>
|
<string>&Help</string>
|
||||||
</property>
|
</property>
|
||||||
|
<addaction name="action_Check_For_Updates"/>
|
||||||
|
<addaction name="action_Open_Maintenance_Tool"/>
|
||||||
|
<addaction name="separator"/>
|
||||||
<addaction name="action_FAQ"/>
|
<addaction name="action_FAQ"/>
|
||||||
<addaction name="action_About"/>
|
<addaction name="action_About"/>
|
||||||
</widget>
|
</widget>
|
||||||
@ -211,6 +214,19 @@
|
|||||||
<string>Fullscreen</string>
|
<string>Fullscreen</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="action_Open_Maintenance_Tool">
|
||||||
|
<property name="text">
|
||||||
|
<string>Modify Citra Install</string>
|
||||||
|
</property>
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Opens the maintenance tool to modify your Citra installation</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="action_Check_For_Updates">
|
||||||
|
<property name="text">
|
||||||
|
<string>Check for Updates</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
</ui>
|
</ui>
|
||||||
|
@ -39,6 +39,10 @@ struct Values {
|
|||||||
bool confirm_before_closing;
|
bool confirm_before_closing;
|
||||||
bool first_start;
|
bool first_start;
|
||||||
|
|
||||||
|
bool updater_found;
|
||||||
|
bool update_on_close;
|
||||||
|
bool check_for_update_on_start;
|
||||||
|
|
||||||
QString roms_path;
|
QString roms_path;
|
||||||
QString symbols_path;
|
QString symbols_path;
|
||||||
QString gamedir;
|
QString gamedir;
|
||||||
|
304
src/citra_qt/updater/updater.cpp
Normal file
304
src/citra_qt/updater/updater.cpp
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
//
|
||||||
|
// Based on the original work by Felix Barx
|
||||||
|
// Copyright (c) 2015, Felix Barz
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QXmlStreamReader>
|
||||||
|
#include "citra_qt/ui_settings.h"
|
||||||
|
#include "citra_qt/updater/updater.h"
|
||||||
|
#include "citra_qt/updater/updater_p.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
|
#ifdef Q_OS_OSX
|
||||||
|
#define DEFAULT_TOOL_PATH QStringLiteral("../../../../maintenancetool")
|
||||||
|
#else
|
||||||
|
#define DEFAULT_TOOL_PATH QStringLiteral("../maintenancetool")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Updater::Updater(QObject* parent) : Updater(DEFAULT_TOOL_PATH, parent) {}
|
||||||
|
|
||||||
|
Updater::Updater(const QString& maintenance_tool_path, QObject* parent)
|
||||||
|
: QObject(parent), backend(std::make_unique<UpdaterPrivate>(this)) {
|
||||||
|
backend->tool_path = UpdaterPrivate::ToSystemExe(maintenance_tool_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Updater::~Updater() = default;
|
||||||
|
|
||||||
|
bool Updater::ExitedNormally() const {
|
||||||
|
return backend->normal_exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Updater::ErrorCode() const {
|
||||||
|
return backend->last_error_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray Updater::ErrorLog() const {
|
||||||
|
return backend->last_error_log;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Updater::IsRunning() const {
|
||||||
|
return backend->running;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<Updater::UpdateInfo> Updater::LatestUpdateInfo() const {
|
||||||
|
return backend->update_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Updater::HasUpdater() const {
|
||||||
|
return backend->HasUpdater();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Updater::CheckForUpdates() {
|
||||||
|
return backend->StartUpdateCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::AbortUpdateCheck(int max_delay, bool async) {
|
||||||
|
backend->StopUpdateCheck(max_delay, async);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::LaunchUI() {
|
||||||
|
backend->LaunchUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::SilentlyUpdate() {
|
||||||
|
backend->SilentlyUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Updater::LaunchUIOnExit() {
|
||||||
|
backend->LaunchUIOnExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
Updater::UpdateInfo::UpdateInfo() = default;
|
||||||
|
|
||||||
|
Updater::UpdateInfo::UpdateInfo(const Updater::UpdateInfo&) = default;
|
||||||
|
|
||||||
|
Updater::UpdateInfo::UpdateInfo(QString name, QString version, quint64 size)
|
||||||
|
: name(std::move(name)), version(std::move(version)), size(size) {}
|
||||||
|
|
||||||
|
UpdaterPrivate::UpdaterPrivate(Updater* parent_ptr) : QObject(nullptr), parent(parent_ptr) {
|
||||||
|
connect(qApp, &QCoreApplication::aboutToQuit, this, &UpdaterPrivate::AboutToExit,
|
||||||
|
Qt::DirectConnection);
|
||||||
|
qRegisterMetaType<QProcess::ExitStatus>("QProcess::ExitStatus");
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdaterPrivate::~UpdaterPrivate() {
|
||||||
|
if (main_process && main_process->state() != QProcess::NotRunning) {
|
||||||
|
main_process->kill();
|
||||||
|
main_process->waitForFinished(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString UpdaterPrivate::ToSystemExe(QString base_path) {
|
||||||
|
#if defined(Q_OS_WIN32)
|
||||||
|
if (!base_path.endsWith(QStringLiteral(".exe")))
|
||||||
|
return base_path + QStringLiteral(".exe");
|
||||||
|
else
|
||||||
|
return base_path;
|
||||||
|
#elif defined(Q_OS_OSX)
|
||||||
|
if (base_path.endsWith(QStringLiteral(".app")))
|
||||||
|
base_path.truncate(base_path.lastIndexOf(QStringLiteral(".")));
|
||||||
|
return base_path + QStringLiteral(".app/Contents/MacOS/") + QFileInfo(base_path).fileName();
|
||||||
|
#elif defined(Q_OS_UNIX)
|
||||||
|
return base_path;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UpdaterPrivate::HasUpdater() const {
|
||||||
|
QFileInfo tool_info(QCoreApplication::applicationDirPath(), tool_path);
|
||||||
|
return tool_info.exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UpdaterPrivate::StartUpdateCheck() {
|
||||||
|
if (running || !HasUpdater()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
update_info.clear();
|
||||||
|
normal_exit = true;
|
||||||
|
last_error_code = EXIT_SUCCESS;
|
||||||
|
last_error_log.clear();
|
||||||
|
|
||||||
|
QFileInfo tool_info(QCoreApplication::applicationDirPath(), tool_path);
|
||||||
|
main_process = new QProcess(this);
|
||||||
|
main_process->setProgram(tool_info.absoluteFilePath());
|
||||||
|
main_process->setArguments({QStringLiteral("--checkupdates"), QStringLiteral("-v")});
|
||||||
|
|
||||||
|
connect(main_process,
|
||||||
|
static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
|
||||||
|
&UpdaterPrivate::UpdaterReady, Qt::QueuedConnection);
|
||||||
|
connect(main_process, static_cast<void (QProcess::*)(QProcess::ProcessError)>(&QProcess::error),
|
||||||
|
this, &UpdaterPrivate::UpdaterError, Qt::QueuedConnection);
|
||||||
|
|
||||||
|
main_process->start(QIODevice::ReadOnly);
|
||||||
|
running = true;
|
||||||
|
|
||||||
|
emit parent->UpdateInfoChanged(update_info);
|
||||||
|
emit parent->RunningChanged(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdaterPrivate::StopUpdateCheck(int delay, bool async) {
|
||||||
|
if (main_process == nullptr || main_process->state() == QProcess::NotRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delay > 0) {
|
||||||
|
main_process->terminate();
|
||||||
|
if (async) {
|
||||||
|
QTimer *timer = new QTimer(this);
|
||||||
|
timer->setSingleShot(true);
|
||||||
|
|
||||||
|
connect(timer, &QTimer::timeout, [=]() {
|
||||||
|
StopUpdateCheck(0, false);
|
||||||
|
timer->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
timer->start(delay);
|
||||||
|
} else {
|
||||||
|
if (!main_process->waitForFinished(delay)) {
|
||||||
|
main_process->kill();
|
||||||
|
main_process->waitForFinished(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
main_process->kill();
|
||||||
|
main_process->waitForFinished(100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XMLParseResult UpdaterPrivate::ParseResult(const QByteArray& output,
|
||||||
|
QList<Updater::UpdateInfo>& out) {
|
||||||
|
const auto out_string = QString::fromUtf8(output);
|
||||||
|
const auto xml_begin = out_string.indexOf(QStringLiteral("<updates>"));
|
||||||
|
if (xml_begin < 0)
|
||||||
|
return XMLParseResult::NoUpdate;
|
||||||
|
const auto xml_end = out_string.indexOf(QStringLiteral("</updates>"), xml_begin);
|
||||||
|
if (xml_end < 0)
|
||||||
|
return XMLParseResult::NoUpdate;
|
||||||
|
|
||||||
|
QList<Updater::UpdateInfo> updates;
|
||||||
|
QXmlStreamReader reader(out_string.mid(xml_begin, (xml_end + 10) - xml_begin));
|
||||||
|
|
||||||
|
reader.readNextStartElement();
|
||||||
|
// should always work because it was search for
|
||||||
|
if (reader.name() != QStringLiteral("updates")) {
|
||||||
|
return XMLParseResult::InvalidXML;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (reader.readNextStartElement()) {
|
||||||
|
if (reader.name() != QStringLiteral("update"))
|
||||||
|
return XMLParseResult::InvalidXML;
|
||||||
|
|
||||||
|
auto ok = false;
|
||||||
|
Updater::UpdateInfo info(
|
||||||
|
reader.attributes().value(QStringLiteral("name")).toString(),
|
||||||
|
reader.attributes().value(QStringLiteral("version")).toString(),
|
||||||
|
reader.attributes().value(QStringLiteral("size")).toULongLong(&ok));
|
||||||
|
|
||||||
|
if (info.name.isEmpty() || info.version.isNull() || !ok)
|
||||||
|
return XMLParseResult::InvalidXML;
|
||||||
|
if (reader.readNextStartElement())
|
||||||
|
return XMLParseResult::InvalidXML;
|
||||||
|
|
||||||
|
updates.append(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.hasError()) {
|
||||||
|
LOG_ERROR(Frontend, "Cannot read xml for update: %s",
|
||||||
|
reader.errorString().toStdString().c_str());
|
||||||
|
return XMLParseResult::InvalidXML;
|
||||||
|
}
|
||||||
|
|
||||||
|
out = updates;
|
||||||
|
return XMLParseResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdaterPrivate::UpdaterReady(int exit_code, QProcess::ExitStatus exit_status) {
|
||||||
|
if (main_process == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exit_status != QProcess::NormalExit) {
|
||||||
|
UpdaterError(QProcess::Crashed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
normal_exit = true;
|
||||||
|
last_error_code = exit_code;
|
||||||
|
last_error_log = main_process->readAllStandardError();
|
||||||
|
const auto update_out = main_process->readAllStandardOutput();
|
||||||
|
main_process->deleteLater();
|
||||||
|
main_process = nullptr;
|
||||||
|
|
||||||
|
running = false;
|
||||||
|
emit parent->RunningChanged(false);
|
||||||
|
|
||||||
|
QList<Updater::UpdateInfo> update_info;
|
||||||
|
auto err = ParseResult(update_out, update_info);
|
||||||
|
bool has_error = false;
|
||||||
|
|
||||||
|
if (err == XMLParseResult::Success) {
|
||||||
|
if (!update_info.isEmpty())
|
||||||
|
emit parent->UpdateInfoChanged(update_info);
|
||||||
|
} else if (err == XMLParseResult::InvalidXML) {
|
||||||
|
has_error = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit parent->CheckUpdatesDone(!update_info.isEmpty(), has_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdaterPrivate::UpdaterError(QProcess::ProcessError error) {
|
||||||
|
if (main_process) {
|
||||||
|
normal_exit = false;
|
||||||
|
last_error_code = error;
|
||||||
|
last_error_log = main_process->errorString().toUtf8();
|
||||||
|
main_process->deleteLater();
|
||||||
|
main_process = nullptr;
|
||||||
|
|
||||||
|
running = false;
|
||||||
|
emit parent->RunningChanged(false);
|
||||||
|
emit parent->CheckUpdatesDone(false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdaterPrivate::LaunchWithArguments(const QStringList& args) {
|
||||||
|
if (!HasUpdater()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo tool_info(QCoreApplication::applicationDirPath(), tool_path);
|
||||||
|
|
||||||
|
if (!QProcess::startDetached(tool_info.absoluteFilePath(), args, tool_info.absolutePath())) {
|
||||||
|
LOG_WARNING(Frontend, "Unable to start program %s",
|
||||||
|
tool_info.absoluteFilePath().toStdString().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdaterPrivate::LaunchUI() {
|
||||||
|
LOG_INFO(Frontend, "Launching update UI...");
|
||||||
|
LaunchWithArguments(run_arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdaterPrivate::SilentlyUpdate() {
|
||||||
|
LOG_INFO(Frontend, "Launching silent update...");
|
||||||
|
LaunchWithArguments(silent_arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdaterPrivate::AboutToExit() {
|
||||||
|
if (launch_ui_on_exit) {
|
||||||
|
LaunchUI();
|
||||||
|
} else if (UISettings::values.update_on_close) {
|
||||||
|
SilentlyUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdaterPrivate::LaunchUIOnExit() {
|
||||||
|
launch_ui_on_exit = true;
|
||||||
|
}
|
120
src/citra_qt/updater/updater.h
Normal file
120
src/citra_qt/updater/updater.h
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
//
|
||||||
|
// Based on the original work by Felix Barx
|
||||||
|
// Copyright (c) 2015, Felix Barz
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright notice, this
|
||||||
|
// list of conditions and the following disclaimer.
|
||||||
|
//
|
||||||
|
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
// this list of conditions and the following disclaimer in the documentation
|
||||||
|
// and/or other materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// * Neither the name of QtAutoUpdater nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QList>
|
||||||
|
#include <QScopedPointer>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
class UpdaterPrivate;
|
||||||
|
|
||||||
|
/// The main updater. Can check for updates and run the maintenancetool as updater
|
||||||
|
class Updater : public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
/// Specifies whether the updater is currently checking for updates or not
|
||||||
|
Q_PROPERTY(bool running READ IsRunning NOTIFY RunningChanged);
|
||||||
|
/// Holds extended information about the last update check
|
||||||
|
Q_PROPERTY(QList<UpdateInfo> update_info READ LatestUpdateInfo NOTIFY UpdateInfoChanged);
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Provides information about updates for components
|
||||||
|
struct UpdateInfo {
|
||||||
|
/// The name of the component that has an update
|
||||||
|
QString name;
|
||||||
|
/// The new version for that compontent
|
||||||
|
QString version;
|
||||||
|
/// The update download size (in Bytes)
|
||||||
|
quint64 size = 0;
|
||||||
|
|
||||||
|
/// Default Constructor
|
||||||
|
UpdateInfo();
|
||||||
|
/// Copy Constructor
|
||||||
|
UpdateInfo(const UpdateInfo& other);
|
||||||
|
/// Constructor that takes name, version and size
|
||||||
|
UpdateInfo(QString name, QString version, quint64 size);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Default constructor
|
||||||
|
explicit Updater(QObject* parent = nullptr);
|
||||||
|
/// Constructor with an explicitly set path
|
||||||
|
explicit Updater(const QString& maintenance_tool_path, QObject* parent = nullptr);
|
||||||
|
/// Destroys the updater and kills the update check (if running)
|
||||||
|
~Updater();
|
||||||
|
|
||||||
|
/// Returns `true`, if the updater exited normally
|
||||||
|
bool ExitedNormally() const;
|
||||||
|
/// Returns the mainetancetools error code of the last update
|
||||||
|
int ErrorCode() const;
|
||||||
|
/// returns the error output (stderr) of the last update
|
||||||
|
QByteArray ErrorLog() const;
|
||||||
|
|
||||||
|
/// readAcFn{Updater::running}
|
||||||
|
bool IsRunning() const;
|
||||||
|
/// readAcFn{Updater::updateInfo}
|
||||||
|
QList<UpdateInfo> LatestUpdateInfo() const;
|
||||||
|
|
||||||
|
/// Launches the updater UI formally
|
||||||
|
void LaunchUI();
|
||||||
|
|
||||||
|
/// Silently updates the application in the background
|
||||||
|
void SilentlyUpdate();
|
||||||
|
|
||||||
|
/// Checks to see if a updater application is available
|
||||||
|
bool HasUpdater() const;
|
||||||
|
|
||||||
|
/// Instead of silently updating, explictly open the UI on shutdown
|
||||||
|
void LaunchUIOnExit();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
/// Starts checking for updates
|
||||||
|
bool CheckForUpdates();
|
||||||
|
/// Aborts checking for updates
|
||||||
|
void AbortUpdateCheck(int max_delay = 5000, bool async = false);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
/// Will be emitted as soon as the updater finished checking for updates
|
||||||
|
void CheckUpdatesDone(bool has_updates, bool has_error);
|
||||||
|
|
||||||
|
/// notifyAcFn{Updater::running}
|
||||||
|
void RunningChanged(bool running);
|
||||||
|
/// notifyAcFn{Updater::updateInfo}
|
||||||
|
void UpdateInfoChanged(QList<Updater::UpdateInfo> update_info);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<UpdaterPrivate> backend;
|
||||||
|
};
|
66
src/citra_qt/updater/updater_p.h
Normal file
66
src/citra_qt/updater/updater_p.h
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
//
|
||||||
|
// Based on the original work by Felix Barx
|
||||||
|
// Copyright (c) 2015, Felix Barz
|
||||||
|
// All rights reserved.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QtCore/QProcess>
|
||||||
|
#include "citra_qt/updater/updater.h"
|
||||||
|
|
||||||
|
enum class XMLParseResult {
|
||||||
|
Success,
|
||||||
|
NoUpdate,
|
||||||
|
InvalidXML,
|
||||||
|
};
|
||||||
|
|
||||||
|
class UpdaterPrivate : public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit UpdaterPrivate(Updater* parent_ptr);
|
||||||
|
~UpdaterPrivate();
|
||||||
|
|
||||||
|
static QString ToSystemExe(QString base_path);
|
||||||
|
|
||||||
|
bool HasUpdater() const;
|
||||||
|
|
||||||
|
bool StartUpdateCheck();
|
||||||
|
|
||||||
|
void LaunchWithArguments(const QStringList& args);
|
||||||
|
void LaunchUI();
|
||||||
|
void SilentlyUpdate();
|
||||||
|
|
||||||
|
void LaunchUIOnExit();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void StopUpdateCheck(int delay, bool async);
|
||||||
|
void UpdaterReady(int exit_code, QProcess::ExitStatus exit_status);
|
||||||
|
void UpdaterError(QProcess::ProcessError error);
|
||||||
|
|
||||||
|
void AboutToExit();
|
||||||
|
|
||||||
|
private:
|
||||||
|
XMLParseResult ParseResult(const QByteArray& output, QList<Updater::UpdateInfo>& out);
|
||||||
|
|
||||||
|
Updater* parent;
|
||||||
|
|
||||||
|
QString tool_path{};
|
||||||
|
QList<Updater::UpdateInfo> update_info{};
|
||||||
|
bool normal_exit = true;
|
||||||
|
int last_error_code = 0;
|
||||||
|
QByteArray last_error_log = EXIT_SUCCESS;
|
||||||
|
|
||||||
|
bool running = false;
|
||||||
|
QProcess* main_process = nullptr;
|
||||||
|
|
||||||
|
bool launch_ui_on_exit = false;
|
||||||
|
|
||||||
|
QStringList run_arguments{"--updater"};
|
||||||
|
QStringList silent_arguments{"--silentUpdate"};
|
||||||
|
|
||||||
|
friend class Updater;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user