Qt: Use HTTPDownloader instead of QtNetwork for updates

This commit is contained in:
Stenzek 2023-11-24 16:33:03 +10:00
parent 365e3fb965
commit af86e5d058
No known key found for this signature in database
3 changed files with 159 additions and 117 deletions

View file

@ -4,10 +4,13 @@
#include "autoupdaterdialog.h" #include "autoupdaterdialog.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "qthost.h" #include "qthost.h"
#include "qtprogresscallback.h"
#include "qtutils.h" #include "qtutils.h"
#include "scmversion/scmversion.h" #include "scmversion/scmversion.h"
#include "unzip.h" #include "unzip.h"
#include "util/http_downloader.h"
#include "common/file_system.h" #include "common/file_system.h"
#include "common/log.h" #include "common/log.h"
#include "common/minizip_helpers.h" #include "common/minizip_helpers.h"
@ -22,13 +25,13 @@
#include <QtCore/QJsonValue> #include <QtCore/QJsonValue>
#include <QtCore/QProcess> #include <QtCore/QProcess>
#include <QtCore/QString> #include <QtCore/QString>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
#include <QtWidgets/QDialog> #include <QtWidgets/QDialog>
#include <QtWidgets/QMessageBox> #include <QtWidgets/QMessageBox>
#include <QtWidgets/QProgressDialog> #include <QtWidgets/QProgressDialog>
// Interval at which HTTP requests are polled.
static constexpr u32 HTTP_POLL_INTERVAL = 10;
#ifdef __APPLE__ #ifdef __APPLE__
#include "common/cocoa_tools.h" #include "common/cocoa_tools.h"
#endif #endif
@ -45,8 +48,8 @@
#ifdef AUTO_UPDATER_SUPPORTED #ifdef AUTO_UPDATER_SUPPORTED
static const char* LATEST_TAG_URL = "https://api.github.com/repos/stenzek/duckstation/tags"; static const char* LATEST_TAG_URL = "https://api.github.com/repos/stenzek/duckstation/tags";
static const char* LATEST_RELEASE_URL = "https://api.github.com/repos/stenzek/duckstation/releases/tags/%s"; static const char* LATEST_RELEASE_URL = "https://api.github.com/repos/stenzek/duckstation/releases/tags/{}";
static const char* CHANGES_URL = "https://api.github.com/repos/stenzek/duckstation/compare/%s...%s"; static const char* CHANGES_URL = "https://api.github.com/repos/stenzek/duckstation/compare/{}...{}";
static const char* UPDATE_ASSET_FILENAME = SCM_RELEASE_ASSET; static const char* UPDATE_ASSET_FILENAME = SCM_RELEASE_ASSET;
static const char* UPDATE_TAGS[] = SCM_RELEASE_TAGS; static const char* UPDATE_TAGS[] = SCM_RELEASE_TAGS;
static const char* THIS_RELEASE_TAG = SCM_RELEASE_TAG; static const char* THIS_RELEASE_TAG = SCM_RELEASE_TAG;
@ -55,11 +58,8 @@ static const char* THIS_RELEASE_TAG = SCM_RELEASE_TAG;
Log_SetChannel(AutoUpdaterDialog); Log_SetChannel(AutoUpdaterDialog);
AutoUpdaterDialog::AutoUpdaterDialog(EmuThread* host_interface, QWidget* parent /* = nullptr */) AutoUpdaterDialog::AutoUpdaterDialog(QWidget* parent /* = nullptr */) : QDialog(parent)
: QDialog(parent), m_host_interface(host_interface)
{ {
m_network_access_mgr = new QNetworkAccessManager(this);
m_ui.setupUi(this); m_ui.setupUi(this);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
@ -67,6 +67,10 @@ AutoUpdaterDialog::AutoUpdaterDialog(EmuThread* host_interface, QWidget* parent
connect(m_ui.downloadAndInstall, &QPushButton::clicked, this, &AutoUpdaterDialog::downloadUpdateClicked); connect(m_ui.downloadAndInstall, &QPushButton::clicked, this, &AutoUpdaterDialog::downloadUpdateClicked);
connect(m_ui.skipThisUpdate, &QPushButton::clicked, this, &AutoUpdaterDialog::skipThisUpdateClicked); connect(m_ui.skipThisUpdate, &QPushButton::clicked, this, &AutoUpdaterDialog::skipThisUpdateClicked);
connect(m_ui.remindMeLater, &QPushButton::clicked, this, &AutoUpdaterDialog::remindMeLaterClicked); connect(m_ui.remindMeLater, &QPushButton::clicked, this, &AutoUpdaterDialog::remindMeLaterClicked);
m_http = HTTPDownloader::Create(Host::GetHTTPUserAgent());
if (!m_http)
Log_ErrorPrint("Failed to create HTTP downloader, auto updater will not be available.");
} }
AutoUpdaterDialog::~AutoUpdaterDialog() = default; AutoUpdaterDialog::~AutoUpdaterDialog() = default;
@ -129,16 +133,52 @@ void AutoUpdaterDialog::reportError(const char* msg, ...)
QMessageBox::critical(this, tr("Updater Error"), QString::fromStdString(full_msg)); QMessageBox::critical(this, tr("Updater Error"), QString::fromStdString(full_msg));
} }
bool AutoUpdaterDialog::ensureHttpReady()
{
if (!m_http)
return false;
if (!m_http_poll_timer)
{
m_http_poll_timer = new QTimer(this);
m_http_poll_timer->connect(m_http_poll_timer, &QTimer::timeout, this, &AutoUpdaterDialog::httpPollTimerPoll);
}
if (!m_http_poll_timer->isActive())
{
m_http_poll_timer->setSingleShot(false);
m_http_poll_timer->setInterval(HTTP_POLL_INTERVAL);
m_http_poll_timer->start();
}
return true;
}
void AutoUpdaterDialog::httpPollTimerPoll()
{
Assert(m_http);
m_http->PollRequests();
if (!m_http->HasAnyRequests())
{
Log_VerbosePrint("All HTTP requests done.");
m_http_poll_timer->stop();
}
}
void AutoUpdaterDialog::queueUpdateCheck(bool display_message) void AutoUpdaterDialog::queueUpdateCheck(bool display_message)
{ {
m_display_messages = display_message; m_display_messages = display_message;
#ifdef AUTO_UPDATER_SUPPORTED #ifdef AUTO_UPDATER_SUPPORTED
connect(m_network_access_mgr, &QNetworkAccessManager::finished, this, &AutoUpdaterDialog::getLatestTagComplete); if (!ensureHttpReady())
{
emit updateCheckCompleted();
return;
}
QUrl url(QUrl::fromEncoded(QByteArray(LATEST_TAG_URL))); m_http->CreateRequest(LATEST_TAG_URL, std::bind(&AutoUpdaterDialog::getLatestTagComplete, this, std::placeholders::_1,
QNetworkRequest request(url); std::placeholders::_3));
m_network_access_mgr->get(request);
#else #else
emit updateCheckCompleted(); emit updateCheckCompleted();
#endif #endif
@ -147,32 +187,29 @@ void AutoUpdaterDialog::queueUpdateCheck(bool display_message)
void AutoUpdaterDialog::queueGetLatestRelease() void AutoUpdaterDialog::queueGetLatestRelease()
{ {
#ifdef AUTO_UPDATER_SUPPORTED #ifdef AUTO_UPDATER_SUPPORTED
connect(m_network_access_mgr, &QNetworkAccessManager::finished, this, &AutoUpdaterDialog::getLatestReleaseComplete); if (!ensureHttpReady())
{
emit updateCheckCompleted();
return;
}
SmallString url_string; std::string url = fmt::format(fmt::runtime(LATEST_RELEASE_URL), getCurrentUpdateTag());
url_string.format(LATEST_RELEASE_URL, getCurrentUpdateTag().c_str()); m_http->CreateRequest(std::move(url), std::bind(&AutoUpdaterDialog::getLatestReleaseComplete, this,
std::placeholders::_1, std::placeholders::_3));
QUrl url(QUrl::fromEncoded(QByteArray(url_string)));
QNetworkRequest request(url);
m_network_access_mgr->get(request);
#endif #endif
} }
void AutoUpdaterDialog::getLatestTagComplete(QNetworkReply* reply) void AutoUpdaterDialog::getLatestTagComplete(s32 status_code, std::vector<u8> response)
{ {
#ifdef AUTO_UPDATER_SUPPORTED #ifdef AUTO_UPDATER_SUPPORTED
const std::string selected_tag(getCurrentUpdateTag()); const std::string selected_tag(getCurrentUpdateTag());
const QString selected_tag_qstr = QString::fromStdString(selected_tag); const QString selected_tag_qstr = QString::fromStdString(selected_tag);
// this might fail due to a lack of internet connection - in which case, don't spam the user with messages every time. if (status_code == HTTPDownloader::HTTP_STATUS_OK)
m_network_access_mgr->disconnect(this);
reply->deleteLater();
if (reply->error() == QNetworkReply::NoError)
{ {
const QByteArray reply_json(reply->readAll());
QJsonParseError parse_error; QJsonParseError parse_error;
QJsonDocument doc(QJsonDocument::fromJson(reply_json, &parse_error)); const QJsonDocument doc = QJsonDocument::fromJson(
QByteArray(reinterpret_cast<const char*>(response.data()), response.size()), &parse_error);
if (doc.isArray()) if (doc.isArray())
{ {
const QJsonArray doc_array(doc.array()); const QJsonArray doc_array(doc.array());
@ -215,24 +252,21 @@ void AutoUpdaterDialog::getLatestTagComplete(QNetworkReply* reply)
else else
{ {
if (m_display_messages) if (m_display_messages)
reportError("Failed to download latest tag info: %d", static_cast<int>(reply->error())); reportError("Failed to download latest tag info: HTTP %d", status_code);
} }
emit updateCheckCompleted(); emit updateCheckCompleted();
#endif #endif
} }
void AutoUpdaterDialog::getLatestReleaseComplete(QNetworkReply* reply) void AutoUpdaterDialog::getLatestReleaseComplete(s32 status_code, std::vector<u8> response)
{ {
#ifdef AUTO_UPDATER_SUPPORTED #ifdef AUTO_UPDATER_SUPPORTED
m_network_access_mgr->disconnect(this); if (status_code == HTTPDownloader::HTTP_STATUS_OK)
reply->deleteLater();
if (reply->error() == QNetworkReply::NoError)
{ {
const QByteArray reply_json(reply->readAll());
QJsonParseError parse_error; QJsonParseError parse_error;
QJsonDocument doc(QJsonDocument::fromJson(reply_json, &parse_error)); const QJsonDocument doc = QJsonDocument::fromJson(
QByteArray(reinterpret_cast<const char*>(response.data()), response.size()), &parse_error);
if (doc.isObject()) if (doc.isObject())
{ {
const QJsonObject doc_object(doc.object()); const QJsonObject doc_object(doc.object());
@ -253,8 +287,12 @@ void AutoUpdaterDialog::getLatestReleaseComplete(QNetworkReply* reply)
m_ui.newVersion->setText( m_ui.newVersion->setText(
tr("New Version: %1 (%2)").arg(m_latest_sha).arg(doc_object["published_at"].toString())); tr("New Version: %1 (%2)").arg(m_latest_sha).arg(doc_object["published_at"].toString()));
m_ui.updateNotes->setText(tr("Loading...")); m_ui.updateNotes->setText(tr("Loading..."));
m_ui.downloadAndInstall->setEnabled(true);
queueGetChanges(); queueGetChanges();
exec();
// We have to defer this, because it comes back through the timer/HTTP callback...
QMetaObject::invokeMethod(this, "exec", Qt::QueuedConnection);
emit updateCheckCompleted(); emit updateCheckCompleted();
return; return;
} }
@ -272,7 +310,7 @@ void AutoUpdaterDialog::getLatestReleaseComplete(QNetworkReply* reply)
} }
else else
{ {
reportError("Failed to download latest release info: %d", static_cast<int>(reply->error())); reportError("Failed to download latest release info: HTTP %d", status_code);
} }
#endif #endif
} }
@ -280,27 +318,23 @@ void AutoUpdaterDialog::getLatestReleaseComplete(QNetworkReply* reply)
void AutoUpdaterDialog::queueGetChanges() void AutoUpdaterDialog::queueGetChanges()
{ {
#ifdef AUTO_UPDATER_SUPPORTED #ifdef AUTO_UPDATER_SUPPORTED
connect(m_network_access_mgr, &QNetworkAccessManager::finished, this, &AutoUpdaterDialog::getChangesComplete); if (!ensureHttpReady())
return;
const std::string url_string( std::string url = fmt::format(fmt::runtime(CHANGES_URL), g_scm_hash_str, getCurrentUpdateTag());
StringUtil::StdStringFromFormat(CHANGES_URL, g_scm_hash_str, getCurrentUpdateTag().c_str())); m_http->CreateRequest(std::move(url), std::bind(&AutoUpdaterDialog::getChangesComplete, this, std::placeholders::_1,
QUrl url(QUrl::fromEncoded(QByteArray(url_string.c_str(), static_cast<int>(url_string.size())))); std::placeholders::_3));
QNetworkRequest request(url);
m_network_access_mgr->get(request);
#endif #endif
} }
void AutoUpdaterDialog::getChangesComplete(QNetworkReply* reply) void AutoUpdaterDialog::getChangesComplete(s32 status_code, std::vector<u8> response)
{ {
#ifdef AUTO_UPDATER_SUPPORTED #ifdef AUTO_UPDATER_SUPPORTED
m_network_access_mgr->disconnect(this); if (status_code == HTTPDownloader::HTTP_STATUS_OK)
reply->deleteLater();
if (reply->error() == QNetworkReply::NoError)
{ {
const QByteArray reply_json(reply->readAll());
QJsonParseError parse_error; QJsonParseError parse_error;
QJsonDocument doc(QJsonDocument::fromJson(reply_json, &parse_error)); const QJsonDocument doc = QJsonDocument::fromJson(
QByteArray(reinterpret_cast<const char*>(response.data()), response.size()), &parse_error);
if (doc.isObject()) if (doc.isObject())
{ {
const QJsonObject doc_object(doc.object()); const QJsonObject doc_object(doc.object());
@ -362,67 +396,59 @@ void AutoUpdaterDialog::getChangesComplete(QNetworkReply* reply)
} }
else else
{ {
reportError("Failed to download change list: %d", static_cast<int>(reply->error())); reportError("Failed to download change list: HTTP %d", status_code);
} }
#endif #endif
m_ui.downloadAndInstall->setEnabled(true);
} }
void AutoUpdaterDialog::downloadUpdateClicked() void AutoUpdaterDialog::downloadUpdateClicked()
{ {
QUrl url(m_download_url); m_display_messages = true;
QNetworkRequest request(url);
QNetworkReply* reply = m_network_access_mgr->get(request);
QProgressDialog progress(tr("Downloading %1...").arg(m_download_url), tr("Cancel"), 0, 1); std::optional<bool> download_result;
progress.setWindowTitle(tr("Automatic Updater")); QtModalProgressCallback progress(this);
progress.setWindowIcon(windowIcon()); progress.SetTitle(tr("Automatic Updater").toUtf8().constData());
progress.setAutoClose(false); progress.SetStatusText(tr("Downloading %1...").arg(m_latest_sha).toUtf8().constData());
progress.GetDialog().setWindowIcon(windowIcon());
progress.SetCancellable(true);
connect(reply, &QNetworkReply::downloadProgress, [&progress](quint64 received, quint64 total) { m_http->CreateRequest(
progress.setRange(0, static_cast<int>(total)); m_download_url.toStdString(),
progress.setValue(static_cast<int>(received)); [this, &download_result](s32 status_code, const std::string&, std::vector<u8> response) {
}); if (status_code == HTTPDownloader::HTTP_STATUS_CANCELLED)
return;
connect(m_network_access_mgr, &QNetworkAccessManager::finished, this, [this, &progress](QNetworkReply* reply) { if (status_code != HTTPDownloader::HTTP_STATUS_OK)
m_network_access_mgr->disconnect();
if (reply->error() != QNetworkReply::NoError)
{ {
reportError("Download failed: %s", reply->errorString().toUtf8().constData()); reportError("Download failed: %d", status_code);
progress.done(-1); download_result = false;
return; return;
} }
const QByteArray data = reply->readAll(); if (response.empty())
if (data.isEmpty())
{ {
reportError("Download failed: Update is empty"); reportError("Download failed: Update is empty");
progress.done(-1); download_result = false;
return; return;
} }
if (processUpdate(data)) download_result = processUpdate(response);
progress.done(1); },
else &progress);
progress.done(-1);
});
const int result = progress.exec(); // Block until completion.
if (result == 0) while (m_http->HasAnyRequests())
{ {
// cancelled QApplication::processEvents(QEventLoop::AllEvents, HTTP_POLL_INTERVAL);
reply->abort(); m_http->PollRequests();
} }
else if (result == 1)
if (download_result.value_or(false))
{ {
// updater started // updater started. since we're a modal on the main window, we have to queue this.
g_main_window->requestExit(); QMetaObject::invokeMethod(g_main_window, "requestExit", Qt::QueuedConnection, Q_ARG(bool, true));
done(0); done(0);
} }
reply->deleteLater();
} }
bool AutoUpdaterDialog::updateNeeded() const bool AutoUpdaterDialog::updateNeeded() const
@ -456,7 +482,7 @@ void AutoUpdaterDialog::remindMeLaterClicked()
#ifdef _WIN32 #ifdef _WIN32
bool AutoUpdaterDialog::processUpdate(const QByteArray& update_data) bool AutoUpdaterDialog::processUpdate(const std::vector<u8>& update_data)
{ {
const QString update_directory = QCoreApplication::applicationDirPath(); const QString update_directory = QCoreApplication::applicationDirPath();
const QString update_zip_path = update_directory + QStringLiteral("\\update.zip"); const QString update_zip_path = update_directory + QStringLiteral("\\update.zip");
@ -472,7 +498,9 @@ bool AutoUpdaterDialog::processUpdate(const QByteArray& update_data)
{ {
QFile update_zip_file(update_zip_path); QFile update_zip_file(update_zip_path);
if (!update_zip_file.open(QIODevice::WriteOnly) || update_zip_file.write(update_data) != update_data.size()) if (!update_zip_file.open(QIODevice::WriteOnly) ||
update_zip_file.write(reinterpret_cast<const char*>(update_data.data()),
static_cast<qint64>(update_data.size())) != static_cast<qint64>(update_data.size()))
{ {
reportError("Writing update zip to '%s' failed", update_zip_path.toUtf8().constData()); reportError("Writing update zip to '%s' failed", update_zip_path.toUtf8().constData());
return false; return false;
@ -587,7 +615,7 @@ void AutoUpdaterDialog::cleanupAfterUpdate()
#elif defined(__APPLE__) #elif defined(__APPLE__)
bool AutoUpdaterDialog::processUpdate(const QByteArray& update_data) bool AutoUpdaterDialog::processUpdate(const std::vector<u8>& update_data)
{ {
std::optional<std::string> bundle_path = CocoaTools::GetNonTranslocatedBundlePath(); std::optional<std::string> bundle_path = CocoaTools::GetNonTranslocatedBundlePath();
if (!bundle_path.has_value()) if (!bundle_path.has_value())
@ -628,7 +656,9 @@ bool AutoUpdaterDialog::processUpdate(const QByteArray& update_data)
// Save update. // Save update.
{ {
QFile zip_file(QString::fromStdString(zip_path)); QFile zip_file(QString::fromStdString(zip_path));
if (!zip_file.open(QIODevice::WriteOnly) || zip_file.write(update_data) != update_data.size()) if (!zip_file.open(QIODevice::WriteOnly) ||
zip_file.write(reinterpret_cast<const char*>(update_data.data()), static_cast<qint64>(update_data.size())) !=
static_cast<qint64>(update_data.size()))
{ {
reportError("Writing update zip to '%s' failed", zip_path.c_str()); reportError("Writing update zip to '%s' failed", zip_path.c_str());
return false; return false;
@ -659,7 +689,7 @@ void AutoUpdaterDialog::cleanupAfterUpdate()
#elif defined(__linux__) #elif defined(__linux__)
bool AutoUpdaterDialog::processUpdate(const QByteArray& update_data) bool AutoUpdaterDialog::processUpdate(const std::vector<u8>& update_data)
{ {
const char* appimage_path = std::getenv("APPIMAGE"); const char* appimage_path = std::getenv("APPIMAGE");
if (!appimage_path || !FileSystem::FileExists(appimage_path)) if (!appimage_path || !FileSystem::FileExists(appimage_path))
@ -699,7 +729,9 @@ bool AutoUpdaterDialog::processUpdate(const QByteArray& update_data)
QFile old_file(qappimage_path); QFile old_file(qappimage_path);
const QFileDevice::Permissions old_permissions = old_file.permissions(); const QFileDevice::Permissions old_permissions = old_file.permissions();
QFile new_file(new_appimage_path); QFile new_file(new_appimage_path);
if (!new_file.open(QIODevice::WriteOnly) || new_file.write(update_data) != update_data.size() || if (!new_file.open(QIODevice::WriteOnly) ||
new_file.write(reinterpret_cast<const char*>(update_data.data()), static_cast<qint64>(update_data.size())) !=
static_cast<qint64>(update_data.size()) ||
!new_file.setPermissions(old_permissions)) !new_file.setPermissions(old_permissions))
{ {
QFile::remove(new_appimage_path); QFile::remove(new_appimage_path);
@ -759,7 +791,7 @@ void AutoUpdaterDialog::cleanupAfterUpdate()
#else #else
bool AutoUpdaterDialog::processUpdate(const QByteArray& update_data) bool AutoUpdaterDialog::processUpdate(const std::vector<u8>& update_data)
{ {
return false; return false;
} }

View file

@ -3,14 +3,19 @@
#pragma once #pragma once
#include "common/types.h"
#include "ui_autoupdaterdialog.h" #include "ui_autoupdaterdialog.h"
#include <QtCore/QStringList> #include <memory>
#include <QtWidgets/QDialog>
#include <string> #include <string>
class QNetworkAccessManager; #include <QtCore/QDateTime>
class QNetworkReply; #include <QtCore/QStringList>
#include <QtCore/QTimer>
#include <QtWidgets/QDialog>
class HTTPDownloader;
class EmuThread; class EmuThread;
@ -19,7 +24,7 @@ class AutoUpdaterDialog final : public QDialog
Q_OBJECT Q_OBJECT
public: public:
explicit AutoUpdaterDialog(EmuThread* host_interface, QWidget* parent = nullptr); explicit AutoUpdaterDialog(QWidget* parent = nullptr);
~AutoUpdaterDialog(); ~AutoUpdaterDialog();
static bool isSupported(); static bool isSupported();
@ -35,11 +40,7 @@ public Q_SLOTS:
void queueGetLatestRelease(); void queueGetLatestRelease();
private Q_SLOTS: private Q_SLOTS:
void getLatestTagComplete(QNetworkReply* reply); void httpPollTimerPoll();
void getLatestReleaseComplete(QNetworkReply* reply);
void queueGetChanges();
void getChangesComplete(QNetworkReply* reply);
void downloadUpdateClicked(); void downloadUpdateClicked();
void skipThisUpdateClicked(); void skipThisUpdateClicked();
@ -47,21 +48,30 @@ private Q_SLOTS:
private: private:
void reportError(const char* msg, ...); void reportError(const char* msg, ...);
bool ensureHttpReady();
bool updateNeeded() const; bool updateNeeded() const;
std::string getCurrentUpdateTag() const; std::string getCurrentUpdateTag() const;
void getLatestTagComplete(s32 status_code, std::vector<u8> response);
void getLatestReleaseComplete(s32 status_code, std::vector<u8> response);
void queueGetChanges();
void getChangesComplete(s32 status_code, std::vector<u8> response);
#ifdef _WIN32 #ifdef _WIN32
bool processUpdate(const QByteArray& update_data); bool processUpdate(const std::vector<u8>& update_data);
bool extractUpdater(const QString& zip_path, const QString& destination_path); bool extractUpdater(const QString& zip_path, const QString& destination_path);
bool doUpdate(const QString& zip_path, const QString& updater_path, const QString& destination_path); bool doUpdate(const QString& zip_path, const QString& updater_path, const QString& destination_path);
#else #else
bool processUpdate(const QByteArray& update_data); bool processUpdate(const std::vector<u8>& update_data);
#endif #endif
Ui::AutoUpdaterDialog m_ui; Ui::AutoUpdaterDialog m_ui;
EmuThread* m_host_interface; std::unique_ptr<HTTPDownloader> m_http;
QNetworkAccessManager* m_network_access_mgr = nullptr; QTimer* m_http_poll_timer = nullptr;
QString m_latest_sha; QString m_latest_sha;
QString m_download_url; QString m_download_url;
int m_download_size = 0; int m_download_size = 0;

View file

@ -2871,7 +2871,7 @@ void MainWindow::checkForUpdates(bool display_message)
if (m_auto_updater_dialog) if (m_auto_updater_dialog)
return; return;
m_auto_updater_dialog = new AutoUpdaterDialog(g_emu_thread, this); m_auto_updater_dialog = new AutoUpdaterDialog(this);
connect(m_auto_updater_dialog, &AutoUpdaterDialog::updateCheckCompleted, this, &MainWindow::onUpdateCheckComplete); connect(m_auto_updater_dialog, &AutoUpdaterDialog::updateCheckCompleted, this, &MainWindow::onUpdateCheckComplete);
m_auto_updater_dialog->queueUpdateCheck(display_message); m_auto_updater_dialog->queueUpdateCheck(display_message);
} }