Qt: refactor curl stuff into a downloader

And add 'Background' updater
This commit is contained in:
Megamouse 2020-07-01 20:48:19 +02:00
parent c495ef10b0
commit 14200c1a1f
16 changed files with 517 additions and 363 deletions

View File

@ -393,6 +393,11 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug - LLVM\moc_downloader.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug - LLVM\moc_emu_settings.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
@ -678,6 +683,11 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_downloader.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_emu_settings.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
@ -983,6 +993,11 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release - LLVM\moc_downloader.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release - LLVM\moc_emu_settings.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
@ -1268,6 +1283,11 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_downloader.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_emu_settings.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
@ -1507,6 +1527,7 @@
<ClCompile Include="rpcs3qt\curl_handle.cpp" />
<ClCompile Include="rpcs3qt\custom_dialog.cpp" />
<ClCompile Include="rpcs3qt\debugger_list.cpp" />
<ClCompile Include="rpcs3qt\downloader.cpp" />
<ClCompile Include="rpcs3qt\fatal_error_dialog.cpp" />
<ClCompile Include="rpcs3qt\gui_application.cpp" />
<ClCompile Include="rpcs3qt\input_dialog.cpp" />
@ -2269,6 +2290,24 @@
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">"$(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"</Command>
</CustomBuild>
<CustomBuild Include="rpcs3qt\downloader.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">"$(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"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(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"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(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"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">"$(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"</Command>
</CustomBuild>
<ClInclude Include="rpcs3qt\emu_settings_type.h" />
<CustomBuild Include="rpcs3qt\render_creator.h">
<Message Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">Moc%27ing %(Identity)...</Message>

View File

@ -131,6 +131,9 @@
<Filter Include="Gui\patch manager">
<UniqueIdentifier>{e72a0cbe-fbcd-4a0b-8c17-a2a3b7a42258}</UniqueIdentifier>
</Filter>
<Filter Include="Gui\network">
<UniqueIdentifier>{bad5498c-a915-4a96-b0cc-f754c02d8e65}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="main.cpp">
@ -901,9 +904,6 @@
<ClCompile Include="QTGeneratedFiles\Debug - LLVM\moc_update_manager.cpp">
<Filter>Generated Files\Debug - LLVM</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\curl_handle.cpp">
<Filter>rpcs3</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\screenshot_manager_dialog.cpp">
<Filter>Gui\screenshot manager</Filter>
</ClCompile>
@ -1057,6 +1057,24 @@
<ClCompile Include="QTGeneratedFiles\Debug - LLVM\moc_patch_manager_dialog.cpp">
<Filter>Generated Files\Debug - LLVM</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\curl_handle.cpp">
<Filter>Gui\network</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\downloader.cpp">
<Filter>Gui\network</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release - LLVM\moc_downloader.cpp">
<Filter>Generated Files\Release - LLVM</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_downloader.cpp">
<Filter>Generated Files\Debug</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_downloader.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug - LLVM\moc_downloader.cpp">
<Filter>Generated Files\Debug - LLVM</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Input\ds4_pad_handler.h">
@ -1140,9 +1158,6 @@
<ClInclude Include="rpcs3qt\category.h">
<Filter>Gui\game list</Filter>
</ClInclude>
<ClInclude Include="rpcs3qt\curl_handle.h">
<Filter>rpcs3</Filter>
</ClInclude>
<ClInclude Include="rpcs3qt\emu_settings_type.h">
<Filter>Gui\settings</Filter>
</ClInclude>
@ -1158,6 +1173,9 @@
<ClInclude Include="QTGeneratedFiles\ui_patch_manager_dialog.h">
<Filter>Generated Files</Filter>
</ClInclude>
<ClInclude Include="rpcs3qt\curl_handle.h">
<Filter>Gui\network</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="debug\moc_predefs.h.cbt">
@ -1376,6 +1394,9 @@
<CustomBuild Include="rpcs3qt\patch_manager_dialog.ui">
<Filter>Form Files</Filter>
</CustomBuild>
<CustomBuild Include="rpcs3qt\downloader.h">
<Filter>Gui\network</Filter>
</CustomBuild>
</ItemGroup>
<ItemGroup>
<Image Include="rpcs3.ico" />

View File

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

View File

@ -0,0 +1,164 @@
#include "stdafx.h"
#include <QApplication>
#include <QThread>
#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<downloader*>(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<int>(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<int>(m_actual_download_size);
}
}
Q_EMIT signal_buffer_update(static_cast<int>(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();
}
}

View File

@ -0,0 +1,46 @@
#pragma once
#include <QObject>
#include <atomic>
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<bool> m_curl_abort = false;
std::atomic<bool> m_curl_success = false;
double m_actual_download_size = -1.0;
progress_dialog* m_progress_dialog = nullptr;
std::atomic<bool> m_keep_progress_dialog_open = false;
QString m_progress_dialog_title;
};

View File

@ -1,77 +1,67 @@
#include "game_compatibility.h"
#include "gui_settings.h"
#include "progress_dialog.h"
#include "curl_handle.h"
#include "downloader.h"
#include <QApplication>
#include <QMessageBox>
#include <QJsonDocument>
#include <QThread>
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<gui_settings> settings, QWidget* parent)
: QObject(parent)
, m_gui_settings(settings)
{
game_compatibility* gm_cmp = reinterpret_cast<game_compatibility*>(userdata);
return gm_cmp->update_buffer(ptr, nmemb);
}
game_compatibility::game_compatibility(std::shared_ptr<gui_settings> 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();
// Write database to file
QFile file(m_filepath);
if (file.exists())
{
compat_log.notice("Database file found: %s", sstr(m_filepath));
}
const auto old_size = m_curl_buf.size();
const auto new_size = old_size + size;
m_curl_buf.resize(static_cast<int>(new_size));
memcpy(m_curl_buf.data() + old_size, data, size);
int max = m_progress_dialog ? m_progress_dialog->maximum() : 0;
if (m_actual_dwnld_size < 0)
if (!file.open(QIODevice::WriteOnly))
{
if (curl_easy_getinfo(m_curl->get_curl(), CURLINFO_CONTENT_LENGTH_DOWNLOAD, &m_actual_dwnld_size) == CURLE_OK && m_actual_dwnld_size > 0)
{
max = static_cast<int>(m_actual_dwnld_size);
}
compat_log.error("Database Error - Could not write database to file: %s", sstr(m_filepath));
return;
}
Q_EMIT signal_buffer_update(static_cast<int>(new_size), max);
file.write(data);
file.close();
return size;
compat_log.success("Wrote database to file: %s", sstr(m_filepath));
}
}
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();

View File

@ -5,9 +5,8 @@
#include <QPainter>
#include <QJsonObject>
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<gui_settings> m_gui_settings;
QString m_filepath;
std::string m_url;
std::atomic<bool> m_curl_result = false;
std::atomic<bool> 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<gui_settings> m_xgui_settings;
downloader* m_downloader = nullptr;
std::map<std::string, compat_status> 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<gui_settings> settings);
game_compatibility(std::shared_ptr<gui_settings> 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

View File

@ -95,7 +95,7 @@ game_list_frame::game_list_frame(std::shared_ptr<gui_settings> gui_settings, std
m_game_list->installEventFilter(this);
m_game_list->setColumnCount(gui::column_count);
m_game_compat = std::make_unique<game_compatibility>(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> 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> 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> 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)

View File

@ -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<game_compatibility> m_game_compat;
game_list* m_game_list = nullptr;
game_compatibility* m_game_compat = nullptr;
QList<QAction*> m_columnActs;
Qt::SortOrder m_col_sort_order;
int m_sort_column;

View File

@ -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<std::string, std::set<std::string>> 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]

View File

@ -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)
{

View File

@ -1537,7 +1537,18 @@ settings_dialog::settings_dialog(std::shared_ptr<gui_settings> 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<void(QComboBox::*)(int)>(&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> 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)
{

View File

@ -3142,13 +3142,6 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cb_check_update_start">
<property name="text">
<string>Check for updates on startup</string>
</property>
</widget>
</item>
<item>
<spacer name="guiTabSpacerRight">
<property name="orientation">
@ -3168,6 +3161,18 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_updates">
<property name="title">
<string>Check for updates on startup</string>
</property>
<layout class="QVBoxLayout" name="layout_gb_updates">
<item>
<widget class="QComboBox" name="combo_updates"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_discord">
<property name="title">

View File

@ -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.");

View File

@ -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<update_manager*>(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<int>(new_size));
memcpy(m_curl_buf.data() + old_size, data, size);
Q_EMIT signal_buffer_update(static_cast<int>(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"))
@ -86,58 +50,41 @@ 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_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]
{
const auto curl_result = curl_easy_perform(m_curl->get_curl());
m_curl_result = curl_result == CURLE_OK;
if (!m_curl_result)
{
update_log.error("Curl error(query): %s", curl_easy_strerror(curl_result));
}
});
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_error, this, [this, automatic](const QString& /*error*/)
{
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 Update Check");
thread->start();
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."));
}
}
bool update_manager::handle_json(bool automatic)
Q_EMIT signal_update_available(result_json);
});
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 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;
if (!m_curl_result)
{
update_log.error("Curl error(download): %s", curl_easy_strerror(curl_result));
m_downloader->close_progress_dialog();
return true;
}
});
connect(thread, &QThread::finished, this, [this, automatic]()
{
const bool result_rpcs3 = m_curl_result && handle_rpcs3();
if (!result_rpcs3 && !m_curl_abort)
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)
{
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;
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."));
}
}
bool update_manager::handle_rpcs3()
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(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<const unsigned char*>(m_curl_buf.data()), m_curl_buf.size());
mbedtls_sha256_update_ret(&ctx, reinterpret_cast<const unsigned char*>(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

View File

@ -4,37 +4,30 @@
#include <QObject>
#include <QByteArray>
class curl_handle;
class progress_dialog;
class downloader;
class update_manager final : public QObject
{
Q_OBJECT
private:
std::atomic<bool> m_update_dialog = false;
progress_dialog* m_progress_dialog = nullptr;
downloader* m_downloader = nullptr;
QWidget* m_parent = nullptr;
curl_handle* m_curl = nullptr;
QByteArray m_curl_buf;
std::atomic<bool> m_curl_abort = false;
std::atomic<bool> 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);
};