mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	Qt updater integration, based on QtAutoUpdater
This commit is contained in:
		
							parent
							
								
									ee5aecee3f
								
							
						
					
					
						commit
						2e6c80d1aa
					
				
					 11 changed files with 651 additions and 28 deletions
				
			
		|  | @ -23,9 +23,10 @@ set(SRCS | |||
|             debugger/profiler.cpp | ||||
|             debugger/registers.cpp | ||||
|             debugger/wait_tree.cpp | ||||
|             aboutdialog.cpp | ||||
|             updater/updater.cpp | ||||
|             util/spinbox.cpp | ||||
|             util/util.cpp | ||||
|             aboutdialog.cpp | ||||
|             bootmanager.cpp | ||||
|             game_list.cpp | ||||
|             hotkeys.cpp | ||||
|  | @ -56,6 +57,8 @@ set(HEADERS | |||
|             debugger/profiler.h | ||||
|             debugger/registers.h | ||||
|             debugger/wait_tree.h | ||||
|             updater/updater.h | ||||
|             updater/updater_p.h | ||||
|             util/spinbox.h | ||||
|             util/util.h | ||||
|             aboutdialog.h | ||||
|  |  | |||
|  | @ -157,6 +157,12 @@ void Config::ReadValues() { | |||
|     qt_config->beginGroup("UI"); | ||||
|     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"); | ||||
|     UISettings::values.geometry = qt_config->value("geometry").toByteArray(); | ||||
|     UISettings::values.state = qt_config->value("state").toByteArray(); | ||||
|  | @ -307,6 +313,11 @@ void Config::SaveValues() { | |||
|     qt_config->beginGroup("UI"); | ||||
|     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->setValue("geometry", UISettings::values.geometry); | ||||
|     qt_config->setValue("state", UISettings::values.state); | ||||
|  |  | |||
|  | @ -20,6 +20,7 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) | |||
|     this->setConfiguration(); | ||||
| 
 | ||||
|     ui->toggle_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); | ||||
|     ui->updateBox->setVisible(UISettings::values.updater_found); | ||||
| } | ||||
| 
 | ||||
| ConfigureGeneral::~ConfigureGeneral() {} | ||||
|  | @ -29,6 +30,9 @@ void ConfigureGeneral::setConfiguration() { | |||
|     ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); | ||||
|     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
 | ||||
|     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.theme = | ||||
|         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.use_cpu_jit = ui->toggle_cpu_jit->isChecked(); | ||||
|     Settings::Apply(); | ||||
|  |  | |||
|  | @ -25,16 +25,16 @@ | |||
|         <item> | ||||
|          <layout class="QVBoxLayout" name="verticalLayout_2"> | ||||
|           <item> | ||||
|            <widget class="QCheckBox" name="toggle_deepscan"> | ||||
|            <widget class="QCheckBox" name="toggle_check_exit"> | ||||
|             <property name="text"> | ||||
|              <string>Search sub-directories for games</string> | ||||
|              <string>Confirm exit while emulation is running</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|           <item> | ||||
|            <widget class="QCheckBox" name="toggle_check_exit"> | ||||
|            <widget class="QCheckBox" name="toggle_deepscan"> | ||||
|             <property name="text"> | ||||
|              <string>Confirm exit while emulation is running</string> | ||||
|              <string>Search sub-directories for games</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|  | @ -44,24 +44,51 @@ | |||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|        <widget class="QGroupBox" name="groupBox_2"> | ||||
|          <property name="title"> | ||||
|            <string>Performance</string> | ||||
|          </property> | ||||
|          <layout class="QHBoxLayout" name="horizontalLayout_7"> | ||||
|            <item> | ||||
|              <layout class="QVBoxLayout" name="verticalLayout_5"> | ||||
|                <item> | ||||
|                  <widget class="QCheckBox" name="toggle_cpu_jit"> | ||||
|                    <property name="text"> | ||||
|                      <string>Enable CPU JIT</string> | ||||
|                    </property> | ||||
|                  </widget> | ||||
|                </item> | ||||
|              </layout> | ||||
|            </item> | ||||
|       <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"> | ||||
|              <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> | ||||
|            </widget> | ||||
|           </item> | ||||
|          </layout> | ||||
|        </widget> | ||||
|         </item> | ||||
|        </layout> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QGroupBox" name="groupBox_2"> | ||||
|        <property name="title"> | ||||
|         <string>Performance</string> | ||||
|        </property> | ||||
|        <layout class="QHBoxLayout" name="horizontalLayout_7"> | ||||
|         <item> | ||||
|          <layout class="QVBoxLayout" name="verticalLayout_5"> | ||||
|           <item> | ||||
|            <widget class="QCheckBox" name="toggle_cpu_jit"> | ||||
|             <property name="text"> | ||||
|              <string>Enable CPU JIT</string> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|          </layout> | ||||
|         </item> | ||||
|        </layout> | ||||
|       </widget> | ||||
|      </item> | ||||
|      <item> | ||||
|       <widget class="QGroupBox" name="groupBox_4"> | ||||
|  | @ -149,8 +176,7 @@ | |||
|              </widget> | ||||
|             </item> | ||||
|             <item> | ||||
|              <widget class="QComboBox" name="theme_combobox"> | ||||
|              </widget> | ||||
|              <widget class="QComboBox" name="theme_combobox"/> | ||||
|             </item> | ||||
|            </layout> | ||||
|           </item> | ||||
|  |  | |||
|  | @ -30,6 +30,7 @@ | |||
| #include "citra_qt/hotkeys.h" | ||||
| #include "citra_qt/main.h" | ||||
| #include "citra_qt/ui_settings.h" | ||||
| #include "citra_qt/updater/updater.h" | ||||
| #include "common/logging/backend.h" | ||||
| #include "common/logging/filter.h" | ||||
| #include "common/logging/log.h" | ||||
|  | @ -100,6 +101,7 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { | |||
|     InitializeDebugWidgets(); | ||||
|     InitializeRecentFileMenuActions(); | ||||
|     InitializeHotkeys(); | ||||
|     ShowUpdaterWidgets(); | ||||
| 
 | ||||
|     SetDefaultUIGeometry(); | ||||
|     RestoreUIState(); | ||||
|  | @ -118,6 +120,10 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { | |||
|     // Show one-time "callout" messages to the user
 | ||||
|     ShowCallouts(); | ||||
| 
 | ||||
|     if (UISettings::values.check_for_update_on_start) { | ||||
|         CheckForUpdates(); | ||||
|     } | ||||
| 
 | ||||
|     QStringList args = QApplication::arguments(); | ||||
|     if (args.length() >= 2) { | ||||
|         BootGame(args[1]); | ||||
|  | @ -139,6 +145,10 @@ void GMainWindow::InitializeWidgets() { | |||
|     game_list = new GameList(this); | ||||
|     ui.horizontalLayout->addWidget(game_list); | ||||
| 
 | ||||
|     // Setup updater
 | ||||
|     updater = new Updater(this); | ||||
|     UISettings::values.updater_found = updater->HasUpdater(); | ||||
| 
 | ||||
|     // Create status bar
 | ||||
|     message_label = new QLabel(); | ||||
|     // 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() { | ||||
|     // geometry: 55% of the window contents are in the upper screen half, 45% in the lower half
 | ||||
|     const QRect screenRect = QApplication::desktop()->screenGeometry(this); | ||||
|  | @ -346,6 +363,10 @@ void GMainWindow::ConnectMenuEvents() { | |||
|     connect(ui.action_FAQ, &QAction::triggered, | ||||
|             []() { QDesktopServices::openUrl(QUrl("https://citra-emu.org/wiki/faq/")); }); | ||||
|     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) { | ||||
|  | @ -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) { | ||||
|     // Shutdown previous session if the emu thread is still active...
 | ||||
|     if (emu_thread != nullptr) | ||||
|  |  | |||
|  | @ -2,8 +2,7 @@ | |||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #ifndef _CITRA_QT_MAIN_HXX_ | ||||
| #define _CITRA_QT_MAIN_HXX_ | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <QMainWindow> | ||||
|  | @ -24,6 +23,7 @@ class GRenderWindow; | |||
| class MicroProfileDialog; | ||||
| class ProfilerWidget; | ||||
| class RegistersWidget; | ||||
| class Updater; | ||||
| class WaitTreeWidget; | ||||
| class AboutDialog; | ||||
| 
 | ||||
|  | @ -82,6 +82,8 @@ private: | |||
|     void ShutdownGame(); | ||||
| 
 | ||||
|     void ShowCallouts(); | ||||
|     void ShowUpdaterWidgets(); | ||||
|     void CheckForUpdates(); | ||||
| 
 | ||||
|     /**
 | ||||
|      * Stores the filename in the recently loaded files list. | ||||
|  | @ -134,6 +136,9 @@ private slots: | |||
|     void OnCoreError(Core::System::ResultStatus, std::string); | ||||
|     /// Called whenever a user selects Help->About Citra
 | ||||
|     void OnMenuAboutCitra(); | ||||
|     void OnUpdateFound(bool found, bool error); | ||||
|     void OnCheckForUpdates(); | ||||
|     void OnOpenUpdater(); | ||||
| 
 | ||||
| private: | ||||
|     void UpdateStatusBar(); | ||||
|  | @ -166,6 +171,7 @@ private: | |||
|     GraphicsVertexShaderWidget* graphicsVertexShaderWidget; | ||||
|     GraphicsTracingWidget* graphicsTracingWidget; | ||||
|     WaitTreeWidget* waitTreeWidget; | ||||
|     Updater* updater; | ||||
| 
 | ||||
|     QAction* actions_recent_files[max_recent_files_item]; | ||||
| 
 | ||||
|  | @ -174,5 +180,3 @@ protected: | |||
|     void dragEnterEvent(QDragEnterEvent* event) override; | ||||
|     void dragMoveEvent(QDragMoveEvent* event) override; | ||||
| }; | ||||
| 
 | ||||
| #endif // _CITRA_QT_MAIN_HXX_
 | ||||
|  |  | |||
|  | @ -96,6 +96,9 @@ | |||
|     <property name="title"> | ||||
|      <string>&Help</string> | ||||
|     </property> | ||||
|     <addaction name="action_Check_For_Updates"/> | ||||
|     <addaction name="action_Open_Maintenance_Tool"/> | ||||
|     <addaction name="separator"/> | ||||
|     <addaction name="action_FAQ"/> | ||||
|     <addaction name="action_About"/> | ||||
|    </widget> | ||||
|  | @ -211,6 +214,19 @@ | |||
|     <string>Fullscreen</string> | ||||
|    </property> | ||||
|   </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> | ||||
|  <resources/> | ||||
| </ui> | ||||
|  |  | |||
|  | @ -39,6 +39,10 @@ struct Values { | |||
|     bool confirm_before_closing; | ||||
|     bool first_start; | ||||
| 
 | ||||
|     bool updater_found; | ||||
|     bool update_on_close; | ||||
|     bool check_for_update_on_start; | ||||
| 
 | ||||
|     QString roms_path; | ||||
|     QString symbols_path; | ||||
|     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…
	
	Add table
		Add a link
		
	
		Reference in a new issue