From 14200c1a1fcdd71724829e54a7471f38ecab9d43 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 1 Jul 2020 20:48:19 +0200 Subject: [PATCH] Qt: refactor curl stuff into a downloader And add 'Background' updater --- rpcs3/rpcs3.vcxproj | 39 ++++ rpcs3/rpcs3.vcxproj.filters | 33 ++- rpcs3/rpcs3qt/CMakeLists.txt | 1 + rpcs3/rpcs3qt/downloader.cpp | 164 +++++++++++++++ rpcs3/rpcs3qt/downloader.h | 46 ++++ rpcs3/rpcs3qt/game_compatibility.cpp | 149 ++++--------- rpcs3/rpcs3qt/game_compatibility.h | 22 +- rpcs3/rpcs3qt/game_list_frame.cpp | 10 +- rpcs3/rpcs3qt/game_list_frame.h | 10 +- rpcs3/rpcs3qt/main_window.cpp | 35 ++- rpcs3/rpcs3qt/qt_utils.cpp | 2 +- rpcs3/rpcs3qt/settings_dialog.cpp | 17 +- rpcs3/rpcs3qt/settings_dialog.ui | 19 +- rpcs3/rpcs3qt/tooltips.h | 2 +- rpcs3/rpcs3qt/update_manager.cpp | 304 +++++++++++---------------- rpcs3/rpcs3qt/update_manager.h | 27 +-- 16 files changed, 517 insertions(+), 363 deletions(-) create mode 100644 rpcs3/rpcs3qt/downloader.cpp create mode 100644 rpcs3/rpcs3qt/downloader.h diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 5ea2777541..8affbb182e 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -393,6 +393,11 @@ true true + + true + true + true + true true @@ -678,6 +683,11 @@ true true + + true + true + true + true true @@ -983,6 +993,11 @@ true true + + true + true + true + true true @@ -1268,6 +1283,11 @@ true true + + true + true + true + true true @@ -1507,6 +1527,7 @@ + @@ -2269,6 +2290,24 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent" + Moc%27ing %(Identity)... diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index fc89752a84..594edda969 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -131,6 +131,9 @@ {e72a0cbe-fbcd-4a0b-8c17-a2a3b7a42258} + + {bad5498c-a915-4a96-b0cc-f754c02d8e65} + @@ -901,9 +904,6 @@ Generated Files\Debug - LLVM - - rpcs3 - Gui\screenshot manager @@ -1057,6 +1057,24 @@ Generated Files\Debug - LLVM + + Gui\network + + + Gui\network + + + Generated Files\Release - LLVM + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\Debug - LLVM + @@ -1140,9 +1158,6 @@ Gui\game list - - rpcs3 - Gui\settings @@ -1158,6 +1173,9 @@ Generated Files + + Gui\network + @@ -1376,6 +1394,9 @@ Form Files + + Gui\network + diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 4be54297cd..151f15d046 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -11,6 +11,7 @@ custom_dialog.cpp debugger_frame.cpp debugger_list.cpp + downloader.cpp _discord_utils.cpp emu_settings.cpp fatal_error_dialog.cpp diff --git a/rpcs3/rpcs3qt/downloader.cpp b/rpcs3/rpcs3qt/downloader.cpp new file mode 100644 index 0000000000..b21f2d4204 --- /dev/null +++ b/rpcs3/rpcs3qt/downloader.cpp @@ -0,0 +1,164 @@ +#include "stdafx.h" + +#include +#include + +#include "downloader.h" +#include "curl_handle.h" +#include "progress_dialog.h" + +LOG_CHANNEL(network_log, "NETWORK"); + +size_t curl_write_cb_compat(char* ptr, size_t /*size*/, size_t nmemb, void* userdata) +{ + downloader* download = reinterpret_cast(userdata); + return download->update_buffer(ptr, nmemb); +} + +downloader::downloader(const std::string& thread_name, QWidget* parent) + : QObject(parent) + , m_parent(parent) + , m_thread_name(thread_name) +{ + m_curl = new curl_handle(this); +} + +void downloader::start(const std::string& url, bool follow_location, bool show_progress_dialog, const QString& progress_dialog_title, bool keep_progress_dialog_open, int exptected_size) +{ + connect(this, &downloader::signal_buffer_update, this, &downloader::handle_buffer_update); + + m_keep_progress_dialog_open = keep_progress_dialog_open; + m_curl_buf.clear(); + m_curl_abort = false; + + curl_easy_setopt(m_curl->get_curl(), CURLOPT_URL, url.c_str()); + curl_easy_setopt(m_curl->get_curl(), CURLOPT_WRITEFUNCTION, curl_write_cb_compat); + curl_easy_setopt(m_curl->get_curl(), CURLOPT_WRITEDATA, this); + curl_easy_setopt(m_curl->get_curl(), CURLOPT_FOLLOWLOCATION, follow_location ? 1 : 0); + + const auto thread = QThread::create([this] + { + const auto result = curl_easy_perform(m_curl->get_curl()); + m_curl_success = result == CURLE_OK; + + if (!m_curl_success && !m_curl_abort) + { + const std::string error = "Curl error: " + std::string{ curl_easy_strerror(result) }; + network_log.error("%s", error); + Q_EMIT signal_download_error(QString::fromStdString(error)); + } + }); + + connect(thread, &QThread::finished, this, [this]() + { + if (m_curl_abort) + { + return; + } + + if (m_progress_dialog && (!m_keep_progress_dialog_open || !m_curl_success)) + { + m_progress_dialog->close(); + m_progress_dialog = nullptr; + } + + if (m_curl_success) + { + Q_EMIT signal_download_finished(m_curl_buf); + } + }); + + if (show_progress_dialog) + { + const int maximum = exptected_size > 0 ? exptected_size : 100; + + if (m_progress_dialog) + { + m_progress_dialog->setWindowTitle(progress_dialog_title); + m_progress_dialog->setAutoClose(!m_keep_progress_dialog_open); + m_progress_dialog->setMaximum(maximum); + } + else + { + m_progress_dialog = new progress_dialog(progress_dialog_title, tr("Please wait..."), tr("Abort"), 0, maximum, true, m_parent); + m_progress_dialog->setAutoReset(false); + m_progress_dialog->setAutoClose(!m_keep_progress_dialog_open); + m_progress_dialog->show(); + + // Handle abort + connect(m_progress_dialog, &QProgressDialog::canceled, this, [this]() + { + m_curl_abort = true; + close_progress_dialog(); + }); + connect(m_progress_dialog, &QProgressDialog::finished, m_progress_dialog, &QProgressDialog::deleteLater); + } + } + + thread->setObjectName("Compat Update"); + thread->start(); +} + +void downloader::update_progress_dialog(const QString& title) +{ + if (m_progress_dialog) + { + m_progress_dialog->setWindowTitle(title); + } +} + +void downloader::close_progress_dialog() +{ + if (m_progress_dialog) + { + m_progress_dialog->close(); + m_progress_dialog = nullptr; + } +} + +progress_dialog* downloader::get_progress_dialog() const +{ + return m_progress_dialog; +} + +size_t downloader::update_buffer(char* data, size_t size) +{ + if (m_curl_abort) + { + return 0; + } + + const auto old_size = m_curl_buf.size(); + const auto new_size = old_size + size; + m_curl_buf.resize(static_cast(new_size)); + memcpy(m_curl_buf.data() + old_size, data, size); + + int max = 0; + + if (m_actual_download_size < 0) + { + if (curl_easy_getinfo(m_curl->get_curl(), CURLINFO_CONTENT_LENGTH_DOWNLOAD, &m_actual_download_size) == CURLE_OK && m_actual_download_size > 0) + { + max = static_cast(m_actual_download_size); + } + } + + Q_EMIT signal_buffer_update(static_cast(new_size), max); + + return size; +} + +void downloader::handle_buffer_update(int size, int max) +{ + if (m_curl_abort) + { + return; + } + + if (m_progress_dialog) + { + m_progress_dialog->setMaximum(max > 0 ? max : m_progress_dialog->maximum()); + m_progress_dialog->setValue(size); + QApplication::processEvents(); + } +} diff --git a/rpcs3/rpcs3qt/downloader.h b/rpcs3/rpcs3qt/downloader.h new file mode 100644 index 0000000000..a0c9a80999 --- /dev/null +++ b/rpcs3/rpcs3qt/downloader.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +#include + +class curl_handle; +class progress_dialog; + +class downloader : public QObject +{ + Q_OBJECT + +public: + downloader(const std::string& thread_name, QWidget* parent = nullptr); + + void start(const std::string& url, bool follow_location, bool show_progress_dialog, const QString& progress_dialog_title = "", bool keep_progress_dialog_open = false, int expected_size = -1); + size_t update_buffer(char* data, size_t size); + + void update_progress_dialog(const QString& title); + void close_progress_dialog(); + + progress_dialog* get_progress_dialog() const; + +private Q_SLOTS: + void handle_buffer_update(int size, int max); + +Q_SIGNALS: + void signal_download_error(const QString& error); + void signal_download_finished(const QByteArray& data); + void signal_buffer_update(int size, int max); + +private: + QWidget* m_parent = nullptr; + std::string m_thread_name; + + curl_handle* m_curl = nullptr; + QByteArray m_curl_buf; + std::atomic m_curl_abort = false; + std::atomic m_curl_success = false; + double m_actual_download_size = -1.0; + + progress_dialog* m_progress_dialog = nullptr; + std::atomic m_keep_progress_dialog_open = false; + QString m_progress_dialog_title; +}; diff --git a/rpcs3/rpcs3qt/game_compatibility.cpp b/rpcs3/rpcs3qt/game_compatibility.cpp index 9f9072f83b..288e481d1c 100644 --- a/rpcs3/rpcs3qt/game_compatibility.cpp +++ b/rpcs3/rpcs3qt/game_compatibility.cpp @@ -1,77 +1,67 @@ #include "game_compatibility.h" #include "gui_settings.h" -#include "progress_dialog.h" -#include "curl_handle.h" +#include "downloader.h" #include #include #include -#include LOG_CHANNEL(compat_log, "Compat"); constexpr auto qstr = QString::fromStdString; inline std::string sstr(const QString& _in) { return _in.toStdString(); } -size_t curl_write_cb_compat(char* ptr, size_t /*size*/, size_t nmemb, void* userdata) +game_compatibility::game_compatibility(std::shared_ptr settings, QWidget* parent) + : QObject(parent) + , m_gui_settings(settings) { - game_compatibility* gm_cmp = reinterpret_cast(userdata); - return gm_cmp->update_buffer(ptr, nmemb); -} - -game_compatibility::game_compatibility(std::shared_ptr settings) : m_xgui_settings(settings) -{ - m_filepath = m_xgui_settings->GetSettingsDir() + "/compat_database.dat"; - m_url = "https://rpcs3.net/compatibility?api=v1&export"; - - m_curl = new curl_handle(this); - + m_filepath = m_gui_settings->GetSettingsDir() + "/compat_database.dat"; + m_downloader = new downloader("Compat Update", parent); RequestCompatibility(); - // We need this signal in order to update the GUI from the main thread - connect(this, &game_compatibility::signal_buffer_update, this, &game_compatibility::handle_buffer_update); + connect(m_downloader, &downloader::signal_download_error, this, &game_compatibility::handle_download_error); + connect(m_downloader, &downloader::signal_download_finished, this, &game_compatibility::handle_download_finished); } -void game_compatibility::handle_buffer_update(int size, int max) +void game_compatibility::handle_download_error(const QString& error) { - if (m_progress_dialog) - { - m_progress_dialog->setMaximum(max); - m_progress_dialog->setValue(size); - QApplication::processEvents(); - } + Q_EMIT DownloadError(error); } -size_t game_compatibility::update_buffer(char* data, size_t size) +void game_compatibility::handle_download_finished(const QByteArray& data) { - if (m_curl_abort) + compat_log.notice("Database download finished"); + + // Create new map from database and write database to file if database was valid + if (ReadJSON(QJsonDocument::fromJson(data).object(), true)) { - return 0; - } + // We have a new database in map, therefore refresh gamelist to new state + Q_EMIT DownloadFinished(); - const auto old_size = m_curl_buf.size(); - const auto new_size = old_size + size; - m_curl_buf.resize(static_cast(new_size)); - memcpy(m_curl_buf.data() + old_size, data, size); + // Write database to file + QFile file(m_filepath); - int max = m_progress_dialog ? m_progress_dialog->maximum() : 0; - - if (m_actual_dwnld_size < 0) - { - if (curl_easy_getinfo(m_curl->get_curl(), CURLINFO_CONTENT_LENGTH_DOWNLOAD, &m_actual_dwnld_size) == CURLE_OK && m_actual_dwnld_size > 0) + if (file.exists()) { - max = static_cast(m_actual_dwnld_size); + compat_log.notice("Database file found: %s", sstr(m_filepath)); } + + if (!file.open(QIODevice::WriteOnly)) + { + compat_log.error("Database Error - Could not write database to file: %s", sstr(m_filepath)); + return; + } + + file.write(data); + file.close(); + + compat_log.success("Wrote database to file: %s", sstr(m_filepath)); } - - Q_EMIT signal_buffer_update(static_cast(new_size), max); - - return size; } bool game_compatibility::ReadJSON(const QJsonObject& json_data, bool after_download) { - int return_code = json_data["return_code"].toInt(); + const int return_code = json_data["return_code"].toInt(); if (return_code < 0) { @@ -167,77 +157,10 @@ void game_compatibility::RequestCompatibility(bool online) return; } - compat_log.notice("Beginning compatibility database download from: %s", m_url); + const std::string url = "https://rpcs3.net/compatibility?api=v1&export"; + compat_log.notice("Beginning compatibility database download from: %s", url); - // Show Progress - m_progress_dialog = new progress_dialog(tr("Downloading Database"), tr("Please wait..."), tr("Abort"), 0, 100, true); - m_progress_dialog->show(); - - curl_easy_setopt(m_curl->get_curl(), CURLOPT_URL, m_url.c_str()); - curl_easy_setopt(m_curl->get_curl(), CURLOPT_WRITEFUNCTION, curl_write_cb_compat); - curl_easy_setopt(m_curl->get_curl(), CURLOPT_WRITEDATA, this); - curl_easy_setopt(m_curl->get_curl(), CURLOPT_FOLLOWLOCATION, 1); - - m_curl_buf.clear(); - m_curl_abort = false; - - // Handle abort - connect(m_progress_dialog, &QProgressDialog::canceled, [this] { m_curl_abort = true; }); - connect(m_progress_dialog, &QProgressDialog::finished, m_progress_dialog, &QProgressDialog::deleteLater); - - auto thread = QThread::create([&] - { - const auto result = curl_easy_perform(m_curl->get_curl()); - m_curl_result = result == CURLE_OK; - - if (!m_curl_result) - { - Q_EMIT DownloadError(qstr("Curl error: ") + qstr(curl_easy_strerror(result))); - } - }); - connect(thread, &QThread::finished, this, [this, online]() - { - if (m_progress_dialog) - { - m_progress_dialog->close(); - m_progress_dialog = nullptr; - } - - if (!m_curl_result) - { - return; - } - - compat_log.notice("Database download finished"); - - // Create new map from database and write database to file if database was valid - if (ReadJSON(QJsonDocument::fromJson(m_curl_buf).object(), online)) - { - // We have a new database in map, therefore refresh gamelist to new state - Q_EMIT DownloadFinished(); - - // Write database to file - QFile file(m_filepath); - - if (file.exists()) - { - compat_log.notice("Database file found: %s", sstr(m_filepath)); - } - - if (!file.open(QIODevice::WriteOnly)) - { - compat_log.error("Database Error - Could not write database to file: %s", sstr(m_filepath)); - return; - } - - file.write(m_curl_buf); - file.close(); - - compat_log.success("Wrote database to file: %s", sstr(m_filepath)); - } - }); - thread->setObjectName("Compat Update"); - thread->start(); + m_downloader->start(url, true, true, tr("Downloading Database")); // We want to retrieve a new database, therefore refresh gamelist and indicate that Q_EMIT DownloadStarted(); diff --git a/rpcs3/rpcs3qt/game_compatibility.h b/rpcs3/rpcs3qt/game_compatibility.h index 6496717861..ffc5f51040 100644 --- a/rpcs3/rpcs3qt/game_compatibility.h +++ b/rpcs3/rpcs3qt/game_compatibility.h @@ -5,9 +5,8 @@ #include #include -class curl_handle; +class downloader; class gui_settings; -class progress_dialog; struct compat_status { @@ -35,16 +34,9 @@ private: { "NoData", { 6, "", "", tr("Database missing"), tr("Right click here and download the current database.\nMake sure you are connected to the internet.") } }, { "Download", { 7, "", "", tr("Retrieving..."), tr("Downloading the compatibility database. Please wait...") } } }; - int m_timer_count = 0; + std::shared_ptr m_gui_settings; QString m_filepath; - std::string m_url; - std::atomic m_curl_result = false; - std::atomic m_curl_abort = false; - double m_actual_dwnld_size = -1.0; - curl_handle* m_curl = nullptr; - QByteArray m_curl_buf; - progress_dialog* m_progress_dialog = nullptr; - std::shared_ptr m_xgui_settings; + downloader* m_downloader = nullptr; std::map m_compat_database; /** Creates new map from the database */ @@ -52,7 +44,7 @@ private: public: /** Handles reads, writes and downloads for the compatibility database */ - game_compatibility(std::shared_ptr settings); + game_compatibility(std::shared_ptr settings, QWidget* parent); /** Reads database. If online set to true: Downloads and writes the database to file */ void RequestCompatibility(bool online = false); @@ -63,16 +55,14 @@ public: /** Returns the data for the requested status */ compat_status GetStatusData(const QString& status); - size_t update_buffer(char* data, size_t size); - Q_SIGNALS: void DownloadStarted(); void DownloadFinished(); void DownloadError(const QString& error); - void signal_buffer_update(int size, int max); private Q_SLOTS: - void handle_buffer_update(int size, int max); + void handle_download_error(const QString& error); + void handle_download_finished(const QByteArray& data); }; class compat_pixmap : public QPixmap diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index fa7d065cf7..79b2e3b7e1 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -95,7 +95,7 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std m_game_list->installEventFilter(this); m_game_list->setColumnCount(gui::column_count); - m_game_compat = std::make_unique(m_gui_settings); + m_game_compat = new game_compatibility(m_gui_settings, this); m_central_widget = new QStackedWidget(this); m_central_widget->addWidget(m_game_list); @@ -143,7 +143,7 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std connect(m_game_grid, &QTableWidget::itemSelectionChanged, this, &game_list_frame::itemSelectionChangedSlot); connect(m_game_grid, &QTableWidget::itemDoubleClicked, this, &game_list_frame::doubleClickedSlot); - connect(m_game_compat.get(), &game_compatibility::DownloadStarted, [this]() + connect(m_game_compat, &game_compatibility::DownloadStarted, [this]() { for (const auto& game : m_game_data) { @@ -151,7 +151,7 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std } Refresh(); }); - connect(m_game_compat.get(), &game_compatibility::DownloadFinished, [this]() + connect(m_game_compat, &game_compatibility::DownloadFinished, [this]() { for (const auto& game : m_game_data) { @@ -159,14 +159,14 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std } Refresh(); }); - connect(m_game_compat.get(), &game_compatibility::DownloadError, [this](const QString& error) + connect(m_game_compat, &game_compatibility::DownloadError, [this](const QString& error) { for (const auto& game : m_game_data) { game->compat = m_game_compat->GetCompatibility(game->info.serial); } Refresh(); - QMessageBox::warning(this, tr("Warning!"), tr("Failed to retrieve the online compatibility database!\nFalling back to local database.\n\n") + tr(qPrintable(error))); + QMessageBox::warning(this, tr("Warning!"), tr("Failed to retrieve the online compatibility database!\nFalling back to local database.\n\n%0").arg(error)); }); for (int col = 0; col < m_columnActs.count(); ++col) diff --git a/rpcs3/rpcs3qt/game_list_frame.h b/rpcs3/rpcs3qt/game_list_frame.h index 3f450d4ab6..c6f0b8e061 100644 --- a/rpcs3/rpcs3qt/game_list_frame.h +++ b/rpcs3/rpcs3qt/game_list_frame.h @@ -128,15 +128,15 @@ private: game_info GetGameInfoFromItem(const QTableWidgetItem* item); // Which widget we are displaying depends on if we are in grid or list mode. - QMainWindow* m_game_dock; - QStackedWidget* m_central_widget; + QMainWindow* m_game_dock = nullptr; + QStackedWidget* m_central_widget = nullptr; // Game Grid - game_list_grid* m_game_grid; + game_list_grid* m_game_grid = nullptr; // Game List - game_list* m_game_list; - std::unique_ptr m_game_compat; + game_list* m_game_list = nullptr; + game_compatibility* m_game_compat = nullptr; QList m_columnActs; Qt::SortOrder m_col_sort_order; int m_sort_column; diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index c2f5b6c768..ea42fe2e73 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -176,10 +176,35 @@ void main_window::Init() // Fix possible hidden game list columns. The game list has to be visible already. Use this after show() m_game_list_frame->FixNarrowColumns(); -#if defined(_WIN32) || defined(__linux__) - if (m_gui_settings->GetValue(gui::m_check_upd_start).toBool()) + // RPCS3 Updater + + QMenuBar *corner_bar = new QMenuBar(ui->menuBar); + + QMenu *download_menu = new QMenu(tr("Update Available!"), corner_bar); + corner_bar->addMenu(download_menu); + + QAction *download_action = new QAction(tr("Download Update"), download_menu); + connect(download_action, &QAction::triggered, this, [this] { - m_updater.check_for_updates(true, this); + m_updater.update(false); + }); + + download_menu->addAction(download_action); + ui->menuBar->setCornerWidget(corner_bar); + ui->menuBar->cornerWidget()->setVisible(false); + + connect(&m_updater, &update_manager::signal_update_available, this, [this](bool update_available) + { + if (ui->menuBar->cornerWidget()) + { + ui->menuBar->cornerWidget()->setVisible(update_available); + } + }); + +#if defined(_WIN32) || defined(__linux__) + if (const auto update_value = m_gui_settings->GetValue(gui::m_check_upd_start).toString(); update_value != "false") + { + m_updater.check_for_updates(true, update_value != "true", this); } #endif } @@ -1538,7 +1563,7 @@ void main_window::CreateConnects() std::unordered_map> games; if (m_game_list_frame) { - for (const auto game : m_game_list_frame->GetGameInfo()) + for (const auto& game : m_game_list_frame->GetGameInfo()) { if (game) { @@ -1675,7 +1700,7 @@ void main_window::CreateConnects() QMessageBox::warning(this, tr("Auto-updater"), tr("Please stop the emulation before trying to update.")); return; } - m_updater.check_for_updates(false, this); + m_updater.check_for_updates(false, false, this); }); connect(ui->aboutAct, &QAction::triggered, [this] diff --git a/rpcs3/rpcs3qt/qt_utils.cpp b/rpcs3/rpcs3qt/qt_utils.cpp index f7b2949a4d..7b44b90197 100644 --- a/rpcs3/rpcs3qt/qt_utils.cpp +++ b/rpcs3/rpcs3qt/qt_utils.cpp @@ -341,7 +341,7 @@ namespace gui { bool match = true; - for (const auto [role, data] : criteria) + for (const auto& [role, data] : criteria) { if (item->data(0, role) != data) { diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 885806fcd6..d1ff368004 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -1537,7 +1537,18 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std ui->cb_show_pkg_install->setChecked(m_gui_settings->GetValue(gui::ib_pkg_success).toBool()); ui->cb_show_pup_install->setChecked(m_gui_settings->GetValue(gui::ib_pup_success).toBool()); - ui->cb_check_update_start->setChecked(m_gui_settings->GetValue(gui::m_check_upd_start).toBool()); + const QString updates_yes = tr("Yes", "Updates"); + const QString updates_background = tr("Background", "Updates"); + const QString updates_no = tr("No", "Updates"); + + ui->combo_updates->addItem(updates_yes, "true"); + ui->combo_updates->addItem(updates_background, "background"); + ui->combo_updates->addItem(updates_no, "false"); + ui->combo_updates->setCurrentIndex(ui->combo_updates->findData(m_gui_settings->GetValue(gui::m_check_upd_start).toString())); + connect(ui->combo_updates, static_cast(&QComboBox::currentIndexChanged), [this](int index) + { + m_gui_settings->SetValue(gui::m_check_upd_start, ui->combo_updates->itemData(index)); + }); const bool enable_ui_colors = m_gui_settings->GetValue(gui::m_enableUIColors).toBool(); ui->cb_custom_colors->setChecked(enable_ui_colors); @@ -1612,10 +1623,6 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std { m_gui_settings->SetValue(gui::ib_pup_success, val); }); - connect(ui->cb_check_update_start, &QCheckBox::clicked, [this](bool val) - { - m_gui_settings->SetValue(gui::m_check_upd_start, val); - }); connect(ui->cb_custom_colors, &QCheckBox::clicked, [this](bool val) { diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index a195cf244b..6958f17a3a 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -3142,13 +3142,6 @@ - - - - Check for updates on startup - - - @@ -3168,6 +3161,18 @@ + + + + Check for updates on startup + + + + + + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index 3e3cdf533c..194b77138c 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -163,7 +163,7 @@ public: const QString show_boot_game = tr("Shows a confirmation dialog when a game was booted while another game is running."); const QString show_pkg_install = tr("Shows a dialog when packages were installed successfully."); const QString show_pup_install = tr("Shows a dialog when firmware was installed successfully."); - const QString check_update_start = tr("Check if an update is available on startup."); + const QString check_update_start = tr("Checks if an update is available on startup and asks if you want to update.\nIf \"Background\" is selected, the check is done silently in the background and a new download option is shown in the top right corner of the menu if a new version was found."); const QString use_rich_presence = tr("Enables use of Discord Rich Presence to show what game you are playing on Discord.\nRequires a restart of RPCS3 to completely close the connection."); const QString discord_state = tr("Tell your friends what you are doing."); const QString custom_colors = tr("Prioritize custom user interface colors over properties set in stylesheet."); diff --git a/rpcs3/rpcs3qt/update_manager.cpp b/rpcs3/rpcs3qt/update_manager.cpp index 5c1e22c901..9e43368495 100644 --- a/rpcs3/rpcs3qt/update_manager.cpp +++ b/rpcs3/rpcs3qt/update_manager.cpp @@ -3,7 +3,7 @@ #include "progress_dialog.h" #include "localized.h" #include "rpcs3_version.h" -#include "curl_handle.h" +#include "downloader.h" #include "Utilities/StrUtil.h" #include "Crypto/sha256.h" #include "Emu/System.h" @@ -35,47 +35,11 @@ LOG_CHANNEL(update_log, "UPDATER"); -size_t curl_write_cb(char* ptr, size_t /*size*/, size_t nmemb, void* userdata) -{ - update_manager* upd_mgr = reinterpret_cast(userdata); - return upd_mgr->update_buffer(ptr, nmemb); -} - update_manager::update_manager() { - m_curl = new curl_handle(this); - - // We need this signal in order to update the GUI from the main thread - connect(this, &update_manager::signal_buffer_update, this, &update_manager::handle_buffer_update); } -void update_manager::handle_buffer_update(int size) -{ - if (m_progress_dialog && m_update_dialog) - { - m_progress_dialog->setValue(size); - QApplication::processEvents(); - } -} - -size_t update_manager::update_buffer(char* data, size_t size) -{ - if (m_curl_abort) - { - return 0; - } - - const auto old_size = m_curl_buf.size(); - const auto new_size = old_size + size; - m_curl_buf.resize(static_cast(new_size)); - memcpy(m_curl_buf.data() + old_size, data, size); - - Q_EMIT signal_buffer_update(static_cast(new_size)); - - return size; -} - -void update_manager::check_for_updates(bool automatic, QWidget* parent) +void update_manager::check_for_updates(bool automatic, bool check_only, QWidget* parent) { #ifdef __linux__ if (automatic && !::getenv("APPIMAGE")) @@ -85,59 +49,42 @@ void update_manager::check_for_updates(bool automatic, QWidget* parent) } #endif - m_parent = parent; - m_curl_abort = false; - m_update_dialog = false; - m_curl_buf.clear(); + m_parent = parent; + m_downloader = new downloader("RPCS3 Updater", parent); - m_progress_dialog = new progress_dialog(tr("Checking For Updates"), tr("Please wait..."), tr("Abort"), 0, 100, true, parent); - m_progress_dialog->setAutoClose(false); - m_progress_dialog->setAutoReset(false); - m_progress_dialog->show(); - - connect(m_progress_dialog, &QProgressDialog::canceled, [this]() { m_curl_abort = true; }); - connect(m_progress_dialog, &QProgressDialog::finished, m_progress_dialog, &QProgressDialog::deleteLater); - - const std::string request_url = "https://update.rpcs3.net/?api=v1&c=" + rpcs3::get_commit_and_hash().second; - curl_easy_setopt(m_curl->get_curl(), CURLOPT_URL, request_url.c_str()); - curl_easy_setopt(m_curl->get_curl(), CURLOPT_WRITEFUNCTION, curl_write_cb); - curl_easy_setopt(m_curl->get_curl(), CURLOPT_WRITEDATA, this); - - auto thread = QThread::create([this] + connect(m_downloader, &downloader::signal_download_error, this, [this, automatic](const QString& /*error*/) { - const auto curl_result = curl_easy_perform(m_curl->get_curl()); - m_curl_result = curl_result == CURLE_OK; - - if (!m_curl_result) + if (!automatic) { - update_log.error("Curl error(query): %s", curl_easy_strerror(curl_result)); + QMessageBox::warning(m_parent, tr("Auto-updater"), tr("An error occurred during the auto-updating process.\nCheck the log for more information.")); } }); - connect(thread, &QThread::finished, this, [this, automatic]() - { - const bool result_json = m_curl_result && handle_json(automatic); - if (!result_json && !m_curl_abort) + connect(m_downloader, &downloader::signal_download_finished, this, [this, automatic, check_only](const QByteArray& data) + { + const bool result_json = handle_json(automatic, check_only, data); + + if (!result_json) { + // The progress dialog is configured to stay open, so we need to close it manually if the download succeeds. + m_downloader->close_progress_dialog(); + if (!automatic) { QMessageBox::warning(m_parent, tr("Auto-updater"), tr("An error occurred during the auto-updating process.\nCheck the log for more information.")); } - - if (m_progress_dialog) - { - m_progress_dialog->close(); - m_progress_dialog = nullptr; - } } + + Q_EMIT signal_update_available(result_json); }); - thread->setObjectName("RPCS3 Update Check"); - thread->start(); + + const std::string url = "https://update.rpcs3.net/?api=v1&c=" + rpcs3::get_commit_and_hash().second; + m_downloader->start(url, true, !automatic, tr("Checking For Updates"), true); } -bool update_manager::handle_json(bool automatic) +bool update_manager::handle_json(bool automatic, bool check_only, const QByteArray& data) { - const QJsonObject json_data = QJsonDocument::fromJson(m_curl_buf).object(); + const QJsonObject json_data = QJsonDocument::fromJson(data).object(); const int return_code = json_data["return_code"].toInt(-255); bool hash_found = true; @@ -202,7 +149,7 @@ bool update_manager::handle_json(bool automatic) if (hash_found && return_code == 0) { update_log.success("RPCS3 is up to date!"); - m_progress_dialog->close(); + m_downloader->close_progress_dialog(); if (!automatic) QMessageBox::information(m_parent, tr("Auto-updater"), tr("Your version is already up to date!")); @@ -225,11 +172,9 @@ bool update_manager::handle_json(bool automatic) Localized localized; - QString message; - if (hash_found) { - message = tr("A new version of RPCS3 is available!\n\nCurrent version: %0 (%1)\nLatest version: %2 (%3)\nYour version is %4 old.\n\nDo you want to update?") + m_update_message = tr("A new version of RPCS3 is available!\n\nCurrent version: %0 (%1)\nLatest version: %2 (%3)\nYour version is %4 old.\n\nDo you want to update?") .arg(current["version"].toString()) .arg(cur_str) .arg(latest["version"].toString()) @@ -238,73 +183,72 @@ bool update_manager::handle_json(bool automatic) } else { - message = tr("You're currently using a custom or PR build.\n\nLatest version: %0 (%1)\nThe latest version is %2 old.\n\nDo you want to update to the latest official RPCS3 version?") + m_update_message = tr("You're currently using a custom or PR build.\n\nLatest version: %0 (%1)\nThe latest version is %2 old.\n\nDo you want to update to the latest official RPCS3 version?") .arg(latest["version"].toString()) .arg(lts_str) .arg(localized.GetVerboseTimeByMs(std::abs(diff_msec), true)); } - if (QMessageBox::question(m_progress_dialog, tr("Update Available"), message, QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) - { - m_progress_dialog->close(); - return true; - } - + m_request_url = latest[os]["download"].toString().toStdString(); m_expected_hash = latest[os]["checksum"].toString().toStdString(); m_expected_size = latest[os]["size"].toInt(); - m_progress_dialog->setWindowTitle(tr("Downloading Update")); - - // Download RPCS3 - m_progress_dialog->setMaximum(m_expected_size); - m_progress_dialog->setValue(0); - m_update_dialog = true; - - const std::string request_url = latest[os]["download"].toString().toStdString(); - curl_easy_setopt(m_curl->get_curl(), CURLOPT_URL, request_url.c_str()); - curl_easy_setopt(m_curl->get_curl(), CURLOPT_FOLLOWLOCATION, 1); - - m_curl_buf.clear(); - - auto thread = QThread::create([this] + if (check_only) { - const auto curl_result = curl_easy_perform(m_curl->get_curl()); - m_curl_result = curl_result == CURLE_OK; + m_downloader->close_progress_dialog(); + return true; + } - if (!m_curl_result) + update(automatic); + return true; +} + +void update_manager::update(bool automatic) +{ + if (QMessageBox::question(m_downloader->get_progress_dialog(), tr("Update Available"), m_update_message, QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) + { + m_downloader->close_progress_dialog(); + return; + } + + m_downloader->disconnect(); + + connect(m_downloader, &downloader::signal_download_error, this, [this, automatic](const QString& /*error*/) + { + if (!automatic) { - update_log.error("Curl error(download): %s", curl_easy_strerror(curl_result)); + QMessageBox::warning(m_parent, tr("Auto-updater"), tr("An error occurred during the auto-updating process.\nCheck the log for more information.")); } }); - connect(thread, &QThread::finished, this, [this, automatic]() - { - const bool result_rpcs3 = m_curl_result && handle_rpcs3(); - if (!result_rpcs3 && !m_curl_abort) + connect(m_downloader, &downloader::signal_download_finished, this, [this, automatic](const QByteArray& data) + { + const bool result_json = handle_rpcs3(data); + + if (!result_json) { + // The progress dialog is configured to stay open, so we need to close it manually if the download succeeds. + m_downloader->close_progress_dialog(); + if (!automatic) { QMessageBox::warning(m_parent, tr("Auto-updater"), tr("An error occurred during the auto-updating process.\nCheck the log for more information.")); } - - if (m_progress_dialog) - { - m_progress_dialog->close(); - m_progress_dialog = nullptr; - } } - }); - thread->setObjectName("RPCS3 Updater"); - thread->start(); - return true; + Q_EMIT signal_update_available(false); + }); + + m_downloader->start(m_request_url, true, true, tr("Downloading Update"), true, m_expected_size); } -bool update_manager::handle_rpcs3() +bool update_manager::handle_rpcs3(const QByteArray& data) { - if (m_expected_size != m_curl_buf.size() + 0u) + m_downloader->update_progress_dialog(tr("Updating RPCS3")); + + if (m_expected_size != data.size() + 0u) { - update_log.error("Download size mismatch: %d expected: %d", m_curl_buf.size(), m_expected_size); + update_log.error("Download size mismatch: %d expected: %d", data.size(), m_expected_size); return false; } @@ -312,7 +256,7 @@ bool update_manager::handle_rpcs3() mbedtls_sha256_context ctx; mbedtls_sha256_init(&ctx); mbedtls_sha256_starts_ret(&ctx, 0); - mbedtls_sha256_update_ret(&ctx, reinterpret_cast(m_curl_buf.data()), m_curl_buf.size()); + mbedtls_sha256_update_ret(&ctx, reinterpret_cast(data.data()), data.size()); mbedtls_sha256_finish_ret(&ctx, res_hash); std::string res_hash_string("0000000000000000000000000000000000000000000000000000000000000000"); @@ -329,9 +273,8 @@ bool update_manager::handle_rpcs3() return false; } - std::string replace_path; - #ifdef _WIN32 + // Get executable path const std::string orig_path = Emulator::GetExeDir() + "rpcs3.exe"; @@ -340,61 +283,6 @@ bool update_manager::handle_rpcs3() wchar_orig_path.resize(tmp_size); MultiByteToWideChar(CP_UTF8, 0, orig_path.c_str(), -1, wchar_orig_path.data(), tmp_size); -#endif - -#ifdef __linux__ - - const char* appimage_path = ::getenv("APPIMAGE"); - if (appimage_path != nullptr) - { - replace_path = appimage_path; - update_log.notice("Found AppImage path: %s", appimage_path); - } - else - { - update_log.warning("Failed to find AppImage path"); - char exe_path[PATH_MAX]; - ssize_t len = ::readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); - - if (len == -1) - { - update_log.error("Failed to find executable path"); - return false; - } - - exe_path[len] = '\0'; - update_log.trace("Found exec path: %s", exe_path); - - replace_path = exe_path; - } - - m_progress_dialog->setWindowTitle(tr("Updating RPCS3")); - - // Move the appimage/exe and replace with new appimage - const std::string move_dest = replace_path + "_old"; - fs::rename(replace_path, move_dest, true); - fs::file new_appimage(replace_path, fs::read + fs::write + fs::create + fs::trunc); - if (!new_appimage) - { - update_log.error("Failed to create new AppImage file: %s", replace_path); - return false; - } - if (new_appimage.write(m_curl_buf.data(), m_curl_buf.size()) != m_curl_buf.size() + 0u) - { - update_log.error("Failed to write new AppImage file: %s", replace_path); - return false; - } - if (fchmod(new_appimage.get_handle(), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1) - { - update_log.error("Failed to chmod rwxrxrx %s", replace_path); - return false; - } - new_appimage.close(); - - update_log.success("Successfully updated %s!", replace_path); - -#elif defined(_WIN32) - char temp_path[PATH_MAX]; GetTempPathA(sizeof(temp_path) - 1, temp_path); @@ -409,15 +297,13 @@ bool update_manager::handle_rpcs3() update_log.error("Failed to create temporary file: %s", tmpfile_path); return false; } - if (tmpfile.write(m_curl_buf.data(), m_curl_buf.size()) != m_curl_buf.size()) + if (tmpfile.write(data.data(), data.size()) != data.size()) { update_log.error("Failed to write temporary file: %s", tmpfile_path); return false; } tmpfile.close(); - m_progress_dialog->setWindowTitle(tr("Updating RPCS3")); - // 7z stuff (most of this stuff is from 7z Util sample and has been reworked to be more stl friendly) ISzAlloc allocImp; @@ -596,8 +482,62 @@ bool update_manager::handle_rpcs3() error_free7z(); if (res) return false; + +#else + + std::string replace_path; + + const char* appimage_path = ::getenv("APPIMAGE"); + if (appimage_path != nullptr) + { + replace_path = appimage_path; + update_log.notice("Found AppImage path: %s", appimage_path); + } + else + { + update_log.warning("Failed to find AppImage path"); + char exe_path[PATH_MAX]; + ssize_t len = ::readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); + + if (len == -1) + { + update_log.error("Failed to find executable path"); + return false; + } + + exe_path[len] = '\0'; + update_log.trace("Found exec path: %s", exe_path); + + replace_path = exe_path; + } + + // Move the appimage/exe and replace with new appimage + const std::string move_dest = replace_path + "_old"; + fs::rename(replace_path, move_dest, true); + fs::file new_appimage(replace_path, fs::read + fs::write + fs::create + fs::trunc); + if (!new_appimage) + { + update_log.error("Failed to create new AppImage file: %s", replace_path); + return false; + } + if (new_appimage.write(data.data(), data.size()) != data.size() + 0u) + { + update_log.error("Failed to write new AppImage file: %s", replace_path); + return false; + } + if (fchmod(new_appimage.get_handle(), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1) + { + update_log.error("Failed to chmod rwxrxrx %s", replace_path); + return false; + } + new_appimage.close(); + + update_log.success("Successfully updated %s!", replace_path); + #endif + m_downloader->close_progress_dialog(); + QMessageBox::information(m_parent, tr("Auto-updater"), tr("Update successful!\nRPCS3 will now restart.")); #ifdef _WIN32 diff --git a/rpcs3/rpcs3qt/update_manager.h b/rpcs3/rpcs3qt/update_manager.h index 5865c50e7d..7cef3799bd 100644 --- a/rpcs3/rpcs3qt/update_manager.h +++ b/rpcs3/rpcs3qt/update_manager.h @@ -4,37 +4,30 @@ #include #include -class curl_handle; -class progress_dialog; +class downloader; class update_manager final : public QObject { Q_OBJECT private: - std::atomic m_update_dialog = false; - progress_dialog* m_progress_dialog = nullptr; - QWidget* m_parent = nullptr; + downloader* m_downloader = nullptr; + QWidget* m_parent = nullptr; - curl_handle* m_curl = nullptr; - QByteArray m_curl_buf; - std::atomic m_curl_abort = false; - std::atomic m_curl_result = false; + QString m_update_message; + std::string m_request_url; std::string m_expected_hash; u64 m_expected_size = 0; - bool handle_json(bool automatic); - bool handle_rpcs3(); + bool handle_json(bool automatic, bool check_only, const QByteArray& data); + bool handle_rpcs3(const QByteArray& data); public: update_manager(); - void check_for_updates(bool automatic, QWidget* parent = nullptr); - size_t update_buffer(char* data, size_t size); + void check_for_updates(bool automatic, bool check_only, QWidget* parent = nullptr); + void update(bool automatic); Q_SIGNALS: - void signal_buffer_update(int size); - -private Q_SLOTS: - void handle_buffer_update(int size); + void signal_update_available(bool update_available); };