Qt: Simplify updater configuration and process

This commit is contained in:
Stenzek
2025-11-28 15:35:50 +10:00
parent f6bfb739f4
commit 243a8afe97
9 changed files with 196 additions and 256 deletions

View File

@@ -43,22 +43,19 @@ jobs:
if: steps.cache-deps.outputs.cache-hit != 'true'
run: scripts/deps/build-ffmpeg-linux.sh "$HOME/deps"
- name: Set Build Tag Asset
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev'
run: |
echo '#pragma once' > src/scmversion/tag.h
echo '#define SCM_RELEASE_ASSET "${{ matrix.asset }}"' >> src/scmversion/tag.h
echo '#define SCM_RELEASE_TAGS {"latest", "preview"}' >> src/scmversion/tag.h
- name: Tag as Preview Release
if: github.ref == 'refs/heads/master'
run: |
echo '#define SCM_RELEASE_TAG "preview"' >> src/scmversion/tag.h
echo '#pragma once' > src/scmversion/tag.h
echo '#define UPDATER_RELEASE_CHANNEL "preview"' >> src/scmversion/tag.h
echo '#define UPDATER_RELEASE_IS_OFFICIAL 1' >> src/scmversion/tag.h
- name: Tag as Rolling Release
- name: Tag as Stable Release
if: github.ref == 'refs/heads/dev'
run: |
echo '#define SCM_RELEASE_TAG "latest"' >> src/scmversion/tag.h
echo '#pragma once' > src/scmversion/tag.h
echo '#define UPDATER_RELEASE_CHANNEL "latest"' >> src/scmversion/tag.h
echo '#define UPDATER_RELEASE_IS_OFFICIAL 1' >> src/scmversion/tag.h
- name: Download Patch Archives
shell: bash

View File

@@ -57,22 +57,19 @@ jobs:
echo 'set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld")' >> "$HOME/toolchain.cmake"
echo 'set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld")' >> "$HOME/toolchain.cmake"
- name: Set Build Tag Asset
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev'
run: |
echo '#pragma once' > src/scmversion/tag.h
echo '#define SCM_RELEASE_ASSET "DuckStation-${{ matrix.arch }}.AppImage"' >> src/scmversion/tag.h
echo '#define SCM_RELEASE_TAGS {"latest", "preview"}' >> src/scmversion/tag.h
- name: Tag as Preview Release
if: github.ref == 'refs/heads/master'
run: |
echo '#define SCM_RELEASE_TAG "preview"' >> src/scmversion/tag.h
echo '#pragma once' > src/scmversion/tag.h
echo '#define UPDATER_RELEASE_CHANNEL "preview"' >> src/scmversion/tag.h
echo '#define UPDATER_RELEASE_IS_OFFICIAL 1' >> src/scmversion/tag.h
- name: Tag as Rolling Release
- name: Tag as Stable Release
if: github.ref == 'refs/heads/dev'
run: |
echo '#define SCM_RELEASE_TAG "latest"' >> src/scmversion/tag.h
echo '#pragma once' > src/scmversion/tag.h
echo '#define UPDATER_RELEASE_CHANNEL "latest"' >> src/scmversion/tag.h
echo '#define UPDATER_RELEASE_IS_OFFICIAL 1' >> src/scmversion/tag.h
- name: Download Patch Archives
shell: bash

View File

@@ -31,22 +31,19 @@ jobs:
if: steps.cache-deps-mac.outputs.cache-hit != 'true'
run: scripts/deps/build-dependencies-mac.sh "$HOME/deps"
- name: Set Build Tags
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev'
run: |
echo '#pragma once' > src/scmversion/tag.h
echo '#define SCM_RELEASE_ASSET "duckstation-mac-release.zip"' >> src/scmversion/tag.h
echo '#define SCM_RELEASE_TAGS {"latest", "preview"}' >> src/scmversion/tag.h
- name: Tag as Preview Release
if: github.ref == 'refs/heads/master'
run: |
echo '#define SCM_RELEASE_TAG "preview"' >> src/scmversion/tag.h
echo '#pragma once' > src/scmversion/tag.h
echo '#define UPDATER_RELEASE_CHANNEL "preview"' >> src/scmversion/tag.h
echo '#define UPDATER_RELEASE_IS_OFFICIAL 1' >> src/scmversion/tag.h
- name: Tag as Rolling Release
- name: Tag as Stable Release
if: github.ref == 'refs/heads/dev'
run: |
echo '#define SCM_RELEASE_TAG "latest"' >> src/scmversion/tag.h
echo '#pragma once' > src/scmversion/tag.h
echo '#define UPDATER_RELEASE_CHANNEL "latest"' >> src/scmversion/tag.h
echo '#define UPDATER_RELEASE_IS_OFFICIAL 1' >> src/scmversion/tag.h
- name: Download Patch Archives
shell: bash

View File

@@ -62,25 +62,21 @@ jobs:
DEBUG: 0
run: scripts/deps/build-dependencies-windows-arm64.bat
- name: Set Build Tag Asset
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev'
shell: cmd
run: |
echo #pragma once > src/scmversion/tag.h
echo #define SCM_RELEASE_ASSET "${{ matrix.assetname }}" >> src/scmversion/tag.h
echo #define SCM_RELEASE_TAGS {"latest", "preview"} >> src/scmversion/tag.h
- name: Tag as Preview Release
if: github.ref == 'refs/heads/master'
shell: cmd
run: |
echo #define SCM_RELEASE_TAG "preview" >> src/scmversion/tag.h
echo #pragma once > src/scmversion/tag.h
echo #define UPDATER_RELEASE_CHANNEL "preview" >> src/scmversion/tag.h
echo #define UPDATER_RELEASE_IS_OFFICIAL 1 >> src/scmversion/tag.h
- name: Tag as Rolling Release Build
- name: Tag as Stable Build
if: github.ref == 'refs/heads/dev'
shell: cmd
run: |
echo #define SCM_RELEASE_TAG "latest" >> src/scmversion/tag.h
echo #pragma once > src/scmversion/tag.h
echo #define UPDATER_RELEASE_CHANNEL "latest" >> src/scmversion/tag.h
echo #define UPDATER_RELEASE_IS_OFFICIAL 1 >> src/scmversion/tag.h
- name: Update RC Version Fields
shell: cmd

View File

@@ -54,30 +54,58 @@ static constexpr u32 HTTP_POLL_INTERVAL = 10;
// Requires that the channel be defined by the buildbot.
#if __has_include("scmversion/tag.h")
#include "scmversion/tag.h"
#define UPDATE_CHECKER_SUPPORTED
#ifdef SCM_RELEASE_ASSET
#define AUTO_UPDATER_SUPPORTED
#endif
#else
#define UPDATER_RELEASE_CHANNEL "preview"
#endif
#ifdef UPDATE_CHECKER_SUPPORTED
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/{}";
static const char* CHANGES_URL = "https://api.github.com/repos/stenzek/duckstation/compare/{}...{}";
static const char* DOWNLOAD_PAGE_URL = "https://github.com/stenzek/duckstation/releases/tag/{}";
static const char* UPDATE_TAGS[] = SCM_RELEASE_TAGS;
static const char* THIS_RELEASE_TAG = SCM_RELEASE_TAG;
#ifdef AUTO_UPDATER_SUPPORTED
static const char* UPDATE_ASSET_FILENAME = SCM_RELEASE_ASSET;
// Updater asset information.
// clang-format off
#if defined(_WIN32)
#if defined(CPU_ARCH_X64) && defined(CPU_ARCH_SSE41)
#define UPDATER_EXPECTED_EXECUTABLE "duckstation-qt-x64-ReleaseLTCG.exe"
#define UPDATER_ASSET_FILENAME "duckstation-windows-x64-release.zip"
#elif defined(CPU_ARCH_X64)
#define UPDATER_EXPECTED_EXECUTABLE "duckstation-qt-x64-ReleaseLTCG-SSE2.exe"
#define UPDATER_ASSET_FILENAME "duckstation-windows-x64-sse2-release.zip"
#elif defined(CPU_ARCH_ARM64)
#define UPDATER_EXPECTED_EXECUTABLE "duckstation-qt-ARM64-ReleaseLTCG.exe"
#define UPDATER_ASSET_FILENAME "duckstation-windows-arm64-release.zip"
#endif
#elif defined(__APPLE__)
#define UPDATER_ASSET_FILENAME "duckstation-mac-release.zip"
#elif defined(__linux__)
#if defined(CPU_ARCH_X64) && defined(CPU_ARCH_SSE41)
#define UPDATER_ASSET_FILENAME "DuckStation-x64.AppImage"
#elif defined(CPU_ARCH_X64)
#define UPDATER_ASSET_FILENAME "DuckStation-x64-SSE2.AppImage"
#elif defined(CPU_ARCH_ARM64)
#define UPDATER_ASSET_FILENAME "DuckStation-arm64.AppImage"
#elif defined(CPU_ARCH_ARM32)
#define UPDATER_ASSET_FILENAME "DuckStation-armhf.AppImage"
#elif defined(CPU_ARCH_RISCV64)
#define UPDATER_ASSET_FILENAME "DuckStation-riscv64.AppImage"
#endif
#endif
#ifndef UPDATER_ASSET_FILENAME
#error Unsupported platform.
#endif
// clang-format on
// URLs for downloading updates.
#define LATEST_TAG_URL "https://api.github.com/repos/stenzek/duckstation/tags"
#define LATEST_RELEASE_URL "https://api.github.com/repos/stenzek/duckstation/releases/tags/{}"
#define CHANGES_URL "https://api.github.com/repos/stenzek/duckstation/compare/{}...{}"
#define DOWNLOAD_PAGE_URL "https://github.com/stenzek/duckstation/releases/tag/{}"
// Update channels.
static constexpr const std::pair<const char*, const char*> s_update_channels[] = {
{"latest", QT_TRANSLATE_NOOP("AutoUpdaterWindow", "Stable Releases")},
{"preview", QT_TRANSLATE_NOOP("AutoUpdaterWindow", "Preview Releases")},
};
LOG_CHANNEL(Host);
AutoUpdaterWindow::AutoUpdaterWindow() : QWidget()
AutoUpdaterWindow::AutoUpdaterWindow(Error* const error) : QWidget()
{
m_ui.setupUi(this);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
@@ -87,43 +115,24 @@ AutoUpdaterWindow::AutoUpdaterWindow() : QWidget()
connect(m_ui.skipThisUpdate, &QPushButton::clicked, this, &AutoUpdaterWindow::skipThisUpdateClicked);
connect(m_ui.remindMeLater, &QPushButton::clicked, this, &AutoUpdaterWindow::remindMeLaterClicked);
Error error;
m_http = HTTPDownloader::Create(Host::GetHTTPUserAgent(), &error);
if (!m_http)
ERROR_LOG("Failed to create HTTP downloader, auto updater will not be available:\n{}", error.GetDescription());
m_http = HTTPDownloader::Create(Host::GetHTTPUserAgent(), error);
m_http_poll_timer = new QTimer(this);
m_http_poll_timer->connect(m_http_poll_timer, &QTimer::timeout, this, &AutoUpdaterWindow::httpPollTimerPoll);
}
AutoUpdaterWindow::~AutoUpdaterWindow() = default;
bool AutoUpdaterWindow::isSupported()
AutoUpdaterWindow* AutoUpdaterWindow::create(Error* const error)
{
#ifdef UPDATE_CHECKER_SUPPORTED
return true;
#else
return false;
#endif
}
AutoUpdaterWindow* const win = new AutoUpdaterWindow(error);
if (!win->m_http)
{
delete win;
return nullptr;
}
bool AutoUpdaterWindow::canInstallUpdate()
{
#ifndef AUTO_UPDATER_SUPPORTED
return false;
#elif defined(__linux__)
// Linux Flatpak is a wrapper of the AppImage, which will have the AUTO_UPDATER_SUPPORTED flag set.
// Redirect to the download page instead if not running under an AppImage.
return (std::getenv("APPIMAGE") != nullptr);
#else
return true;
#endif
}
bool AutoUpdaterWindow::isOfficialBuild()
{
#if !__has_include("scmversion/tag.h")
return false;
#else
return true;
#endif
return win;
}
void AutoUpdaterWindow::warnAboutUnofficialBuild()
@@ -147,19 +156,19 @@ void AutoUpdaterWindow::warnAboutUnofficialBuild()
// Thanks, and I hope you understand.
//
#if !__has_include("scmversion/tag.h")
#if !__has_include("scmversion/tag.h") || !UPDATER_RELEASE_IS_OFFICIAL
constexpr const char* CONFIG_SECTION = "UI";
constexpr const char* CONFIG_KEY = "UnofficialBuildWarningConfirmed";
if (
#ifndef _WIN32
!StringUtil::StartsWithNoCase(EmuFolders::AppRoot, "/usr") &&
#if defined(_WIN32) && !defined(__APPLE__)
EmuFolders::AppRoot.starts_with("/home") && // Devbuilds should be in home directory.
#endif
Host::GetBaseBoolSettingValue(CONFIG_SECTION, CONFIG_KEY, false))
{
return;
}
constexpr int DELAY_SECONDS = 5;
constexpr int DELAY_SECONDS = 10;
const QString message =
QStringLiteral("<h1>You are not using an official release!</h1><h3>DuckStation is licensed under the terms of "
@@ -218,31 +227,23 @@ void AutoUpdaterWindow::warnAboutUnofficialBuild()
#endif
}
QStringList AutoUpdaterWindow::getTagList()
std::vector<std::pair<QString, QString>> AutoUpdaterWindow::getChannelList()
{
#ifdef UPDATE_CHECKER_SUPPORTED
return QStringList(std::begin(UPDATE_TAGS), std::end(UPDATE_TAGS));
#else
return QStringList();
#endif
std::vector<std::pair<QString, QString>> ret;
ret.reserve(std::size(s_update_channels));
for (const auto& [name, desc] : s_update_channels)
ret.emplace_back(QString::fromUtf8(name), qApp->translate("AutoUpdaterWindow", desc));
return ret;
}
std::string AutoUpdaterWindow::getDefaultTag()
{
#ifdef UPDATE_CHECKER_SUPPORTED
return THIS_RELEASE_TAG;
#else
return {};
#endif
return UPDATER_RELEASE_CHANNEL;
}
std::string AutoUpdaterWindow::getCurrentUpdateTag()
{
#ifdef UPDATE_CHECKER_SUPPORTED
return Host::GetBaseStringSettingValue("AutoUpdater", "UpdateTag", THIS_RELEASE_TAG);
#else
return {};
#endif
return Host::GetBaseStringSettingValue("AutoUpdater", "UpdateTag", UPDATER_RELEASE_CHANNEL);
}
void AutoUpdaterWindow::setDownloadSectionVisibility(bool visible)
@@ -257,33 +258,30 @@ void AutoUpdaterWindow::setDownloadSectionVisibility(bool visible)
void AutoUpdaterWindow::reportError(const std::string_view msg)
{
QtUtils::MessageBoxCritical(this, tr("Updater Error"), QtUtils::StringViewToQString(msg));
// if we're visible, use ourselves.
QWidget* const parent = (isVisible() ? static_cast<QWidget*>(this) : g_main_window);
const QString full_msg = tr("Failed to retrieve or download update:\n\n%1\n\nYou can manually update DuckStation by "
"re-downloading the latest release. Do you want to open the download page now?")
.arg(QtUtils::StringViewToQString(msg));
QMessageBox* const msgbox = QtUtils::NewMessageBox(parent, QMessageBox::Critical, tr("Updater Error"), full_msg,
QMessageBox::Yes | QMessageBox::No);
msgbox->connect(msgbox, &QMessageBox::accepted, parent,
[parent]() { QtUtils::OpenURL(parent, fmt::format(DOWNLOAD_PAGE_URL, UPDATER_RELEASE_CHANNEL)); });
msgbox->open();
}
bool AutoUpdaterWindow::ensureHttpReady()
void AutoUpdaterWindow::ensureHttpPollingActive()
{
if (!m_http)
return false;
if (m_http_poll_timer->isActive())
return;
if (!m_http_poll_timer)
{
m_http_poll_timer = new QTimer(this);
m_http_poll_timer->connect(m_http_poll_timer, &QTimer::timeout, this, &AutoUpdaterWindow::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;
m_http_poll_timer->setSingleShot(false);
m_http_poll_timer->setInterval(HTTP_POLL_INTERVAL);
m_http_poll_timer->start();
}
void AutoUpdaterWindow::httpPollTimerPoll()
{
Assert(m_http);
m_http->PollRequests();
if (!m_http->HasAnyRequests())
@@ -295,42 +293,25 @@ void AutoUpdaterWindow::httpPollTimerPoll()
void AutoUpdaterWindow::queueUpdateCheck(bool display_errors)
{
#ifdef UPDATE_CHECKER_SUPPORTED
if (!ensureHttpReady())
{
emit updateCheckCompleted();
return;
}
ensureHttpPollingActive();
m_http->CreateRequest(LATEST_TAG_URL,
[this, display_errors](s32 status_code, const Error& error, const std::string& content_type,
std::vector<u8> response) {
getLatestTagComplete(status_code, error, std::move(response), display_errors);
});
#else
emit updateCheckCompleted();
#endif
}
void AutoUpdaterWindow::queueGetLatestRelease()
{
#ifdef UPDATE_CHECKER_SUPPORTED
if (!ensureHttpReady())
{
emit updateCheckCompleted();
return;
}
std::string url = fmt::format(fmt::runtime(LATEST_RELEASE_URL), getCurrentUpdateTag());
ensureHttpPollingActive();
std::string url = fmt::format(LATEST_RELEASE_URL, getCurrentUpdateTag());
m_http->CreateRequest(std::move(url), std::bind(&AutoUpdaterWindow::getLatestReleaseComplete, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_4));
#endif
}
void AutoUpdaterWindow::getLatestTagComplete(s32 status_code, const Error& error, std::vector<u8> response,
bool display_errors)
{
#ifdef UPDATE_CHECKER_SUPPORTED
const std::string selected_tag(getCurrentUpdateTag());
const QString selected_tag_qstr = QString::fromStdString(selected_tag);
@@ -363,11 +344,11 @@ void AutoUpdaterWindow::getLatestTagComplete(s32 status_code, const Error& error
{
if (display_errors)
{
QtUtils::MessageBoxInformation(this, tr("Automatic Updater"),
tr("No updates are currently available. Please try again later."));
QtUtils::AsyncMessageBox(g_main_window, QMessageBox::Information, tr("Automatic Updater"),
tr("No updates are currently available. Please try again later."));
}
emit updateCheckCompleted();
emit updateCheckCompleted(false);
return;
}
}
@@ -387,13 +368,11 @@ void AutoUpdaterWindow::getLatestTagComplete(s32 status_code, const Error& error
reportError(fmt::format("Failed to download latest tag info: {}", error.GetDescription()));
}
emit updateCheckCompleted();
#endif
emit updateCheckCompleted(false);
}
void AutoUpdaterWindow::getLatestReleaseComplete(s32 status_code, const Error& error, std::vector<u8> response)
{
#ifdef UPDATE_CHECKER_SUPPORTED
if (status_code == HTTPDownloader::HTTP_STATUS_OK)
{
QJsonParseError parse_error;
@@ -403,10 +382,9 @@ void AutoUpdaterWindow::getLatestReleaseComplete(s32 status_code, const Error& e
{
const QJsonObject doc_object(doc.object());
#ifdef AUTO_UPDATER_SUPPORTED
// search for the correct file
const QJsonArray assets(doc_object["assets"].toArray());
const QString asset_filename(UPDATE_ASSET_FILENAME);
const QString asset_filename = QStringLiteral(UPDATER_ASSET_FILENAME);
bool asset_found = false;
for (const QJsonValue& asset : assets)
{
@@ -426,7 +404,6 @@ void AutoUpdaterWindow::getLatestReleaseComplete(s32 status_code, const Error& e
reportError("Asset not found");
return;
}
#endif
const QString current_date = QtHost::FormatNumber(
Host::NumberFormatType::ShortDateTime,
@@ -437,25 +414,19 @@ void AutoUpdaterWindow::getLatestReleaseComplete(s32 status_code, const Error& e
static_cast<s64>(
QDateTime::fromString(doc_object["published_at"].toString(), Qt::DateFormat::ISODate).toSecsSinceEpoch()));
m_ui.currentVersion->setText(
tr("Current Version: %1 (%2)")
.arg(QtUtils::StringViewToQString(TinyString::from_format("{}/{}", g_scm_version_str, THIS_RELEASE_TAG)))
.arg(current_date));
m_ui.currentVersion->setText(tr("Current Version: %1 (%2)")
.arg(QtUtils::StringViewToQString(
TinyString::from_format("{}/{}", g_scm_version_str, UPDATER_RELEASE_CHANNEL)))
.arg(current_date));
m_ui.newVersion->setText(
tr("New Version: %1 (%2)").arg(QString::fromStdString(getCurrentUpdateTag())).arg(release_date));
m_ui.downloadSize->setText(
tr("Download Size: %1 MB").arg(static_cast<double>(m_download_size) / 1000000.0, 0, 'f', 2));
if (!canInstallUpdate())
{
// Just display the version and a download link.
m_ui.downloadAndInstall->setText(tr("Download..."));
}
m_ui.downloadAndInstall->setEnabled(true);
m_ui.updateNotes->setText(tr("Loading..."));
queueGetChanges();
QtUtils::ShowOrRaiseWindow(this);
emit updateCheckCompleted(true);
return;
}
else
@@ -468,25 +439,19 @@ void AutoUpdaterWindow::getLatestReleaseComplete(s32 status_code, const Error& e
reportError(fmt::format("Failed to download latest release info: {}", error.GetDescription()));
}
emit updateCheckCompleted();
#endif
emit updateCheckCompleted(false);
}
void AutoUpdaterWindow::queueGetChanges()
{
#ifdef UPDATE_CHECKER_SUPPORTED
if (!ensureHttpReady())
return;
std::string url = fmt::format(fmt::runtime(CHANGES_URL), g_scm_hash_str, getCurrentUpdateTag());
ensureHttpPollingActive();
std::string url = fmt::format(CHANGES_URL, g_scm_hash_str, getCurrentUpdateTag());
m_http->CreateRequest(std::move(url), std::bind(&AutoUpdaterWindow::getChangesComplete, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_4));
#endif
}
void AutoUpdaterWindow::getChangesComplete(s32 status_code, const Error& error, std::vector<u8> response)
{
#ifdef UPDATE_CHECKER_SUPPORTED
if (status_code == HTTPDownloader::HTTP_STATUS_OK)
{
QJsonParseError parse_error;
@@ -552,19 +517,10 @@ void AutoUpdaterWindow::getChangesComplete(s32 status_code, const Error& error,
{
reportError(fmt::format("Failed to download change list: {}", error.GetDescription()));
}
#endif
}
void AutoUpdaterWindow::downloadUpdateClicked()
{
#ifdef UPDATE_CHECKER_SUPPORTED
if (!canInstallUpdate())
{
QtUtils::OpenURL(this, fmt::format(fmt::runtime(DOWNLOAD_PAGE_URL), getCurrentUpdateTag()));
return;
}
#endif
#ifdef AUTO_UPDATER_SUPPORTED
// Prevent multiple clicks of the button.
if (m_download_progress_callback)
return;
@@ -576,7 +532,7 @@ void AutoUpdaterWindow::downloadUpdateClicked()
m_ui.downloadButtonBox->button(QDialogButtonBox::Cancel));
m_download_progress_callback->SetStatusText(TRANSLATE_SV("AutoUpdaterWindow", "Downloading Update..."));
ensureHttpReady();
ensureHttpPollingActive();
m_http->CreateRequest(
m_download_url.toStdString(),
[this](s32 status_code, const Error& error, const std::string&, std::vector<u8> response) {
@@ -619,7 +575,6 @@ void AutoUpdaterWindow::downloadUpdateClicked()
}
},
m_download_progress_callback);
#endif
}
bool AutoUpdaterWindow::updateNeeded() const
@@ -653,7 +608,7 @@ void AutoUpdaterWindow::remindMeLaterClicked()
void AutoUpdaterWindow::closeEvent(QCloseEvent* event)
{
emit updateCheckCompleted();
emit closed();
QWidget::closeEvent(event);
}
@@ -726,12 +681,6 @@ bool AutoUpdaterWindow::extractUpdater(const std::string& zip_path, const std::s
// with a partially updated installation
if (unzLocateFile(zf, std::string(check_for_file).c_str(), 0) != UNZ_OK)
{
#ifdef CPU_ARCH_X64
const QString expected_executable = QStringLiteral("duckstation-qt-x64-ReleaseLTCG.exe");
#else
const QString expected_executable = QStringLiteral("duckstation-qt-ARM64-ReleaseLTCG.exe");
#endif
if (QtUtils::MessageBoxIcon(
this, QMessageBox::Warning, tr("Updater Warning"),
tr("<h1>Inconsistent Application State</h1><h3>The update zip is missing the current executable:</h3><div "
@@ -740,7 +689,7 @@ bool AutoUpdaterWindow::extractUpdater(const std::string& zip_path, const std::s
"executable is used. The DuckStation executable should be named:<div "
"align=\"center\"><pre>%2</pre></div><p>Do you want to continue anyway?</p>")
.arg(QString::fromStdString(std::string(check_for_file)))
.arg(expected_executable),
.arg(QStringLiteral(UPDATER_EXPECTED_EXECUTABLE)),
QMessageBox::Yes | QMessageBox::No, QMessageBox::NoButton) == QMessageBox::No)
{
Error::SetStringFmt(error, "Update zip is missing expected file: {}", check_for_file);

View File

@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
@@ -7,9 +7,10 @@
#include "ui_autoupdaterwindow.h"
#include <memory>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#include <QtCore/QDateTime>
#include <QtCore/QStringList>
@@ -26,38 +27,44 @@ class AutoUpdaterWindow final : public QWidget
Q_OBJECT
public:
explicit AutoUpdaterWindow();
~AutoUpdaterWindow();
static AutoUpdaterWindow* create(Error* const error);
void queueUpdateCheck(bool display_errors);
void queueGetLatestRelease();
static bool isSupported();
static bool canInstallUpdate();
static QStringList getTagList();
// (channel name, channel display name)
static std::vector<std::pair<QString, QString>> getChannelList();
static std::string getDefaultTag();
static std::string getCurrentUpdateTag();
static void cleanupAfterUpdate();
static bool isOfficialBuild();
static void warnAboutUnofficialBuild();
Q_SIGNALS:
void updateCheckCompleted();
/// Update check completed, might have an update available.
void updateCheckCompleted(bool update_available);
/// Update was available, but the window was closed.
void closed();
protected:
void closeEvent(QCloseEvent* event) override;
private:
explicit AutoUpdaterWindow(Error* const error);
void setDownloadSectionVisibility(bool visible);
void reportError(const std::string_view msg);
void ensureHttpPollingActive();
void httpPollTimerPoll();
void downloadUpdateClicked();
void skipThisUpdateClicked();
void remindMeLaterClicked();
void reportError(const std::string_view msg);
bool ensureHttpReady();
bool updateNeeded() const;

View File

@@ -99,22 +99,12 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* dialog, QWidget
m_ui.theme->setMinimumContentsLength(m_ui.language->minimumContentsLength());
m_ui.theme->setSizeAdjustPolicy(m_ui.language->sizeAdjustPolicy());
if (AutoUpdaterWindow::isSupported())
{
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.autoUpdateEnabled, "AutoUpdater", "CheckAtStartup", true);
m_ui.autoUpdateTag->addItems(AutoUpdaterWindow::getTagList());
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.autoUpdateTag, "AutoUpdater", "UpdateTag",
AutoUpdaterWindow::getDefaultTag());
connect(m_ui.checkForUpdates, &QPushButton::clicked, this, []() { g_main_window->checkForUpdates(true); });
}
else
{
m_ui.autoUpdateTag->addItem(tr("Unavailable"));
m_ui.autoUpdateEnabled->setEnabled(false);
m_ui.autoUpdateTag->setEnabled(false);
m_ui.checkForUpdates->setEnabled(false);
m_ui.updatesGroup->setEnabled(false);
}
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.autoUpdateEnabled, "AutoUpdater", "CheckAtStartup", true);
for (const auto& [name, desc] : AutoUpdaterWindow::getChannelList())
m_ui.autoUpdateTag->addItem(desc, name);
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.autoUpdateTag, "AutoUpdater", "UpdateTag",
AutoUpdaterWindow::getDefaultTag());
connect(m_ui.checkForUpdates, &QPushButton::clicked, this, []() { g_main_window->checkForUpdates(true); });
m_ui.autoUpdateCurrentVersion->setText(tr("%1 (%2)").arg(g_scm_version_str).arg(g_scm_date_str));
}

View File

@@ -3427,45 +3427,52 @@ void MainWindow::onToolsOpenTextureDirectoryTriggered()
void MainWindow::checkForUpdates(bool display_message)
{
if (!AutoUpdaterWindow::isSupported())
{
if (display_message)
{
QMessageBox mbox(this);
mbox.setWindowTitle(tr("Updater Error"));
mbox.setTextFormat(Qt::RichText);
// The user could click Check for Updates while an update check is in progress.
// Don't show an incomplete dialog in this case.
if (m_auto_updater_dialog)
return;
QString message;
if (!AutoUpdaterWindow::isOfficialBuild())
Error error;
m_auto_updater_dialog = AutoUpdaterWindow::create(&error);
if (!m_auto_updater_dialog)
{
QtUtils::AsyncMessageBox(
this, QMessageBox::Critical, tr("Error"),
tr("Failed to create auto updater: %1").arg(QString::fromStdString(error.GetDescription())));
return;
}
connect(m_auto_updater_dialog, &AutoUpdaterWindow::closed, this, [this]() {
if (!m_auto_updater_dialog)
return;
m_auto_updater_dialog->deleteLater();
m_auto_updater_dialog = nullptr;
});
connect(m_auto_updater_dialog, &AutoUpdaterWindow::updateCheckCompleted, this, [this](bool update_available) {
if (!m_auto_updater_dialog)
return;
if (update_available)
{
if (isRenderingFullscreen())
{
message =
tr("<p>Sorry, you are trying to update a DuckStation version which is not an official GitHub release. To "
"prevent incompatibilities, the auto-updater is only enabled on official builds.</p>"
"<p>Please download an official release from from <a "
"href=\"https://www.duckstation.org/\">duckstation.org</a>.</p>");
// This is truely awful. We have to exit fullscreen to show the dialog, but because it's asynchronous
// the fullscreen exit may not have happened yet. So we have to wait for it.
g_emu_thread->setFullscreen(false);
QTimer::singleShot(500, this, [this]() { QtUtils::ShowOrRaiseWindow(m_auto_updater_dialog, this); });
}
else
{
message = tr("Automatic updating is not supported on the current platform.");
QtUtils::ShowOrRaiseWindow(m_auto_updater_dialog, this);
}
mbox.setText(message);
mbox.setIcon(QMessageBox::Critical);
mbox.exec();
}
return;
}
if (m_auto_updater_dialog)
{
QtUtils::ShowOrRaiseWindow(m_auto_updater_dialog, this);
return;
}
m_auto_updater_dialog = new AutoUpdaterWindow();
connect(m_auto_updater_dialog, &AutoUpdaterWindow::updateCheckCompleted, this,
[this] { QtUtils::CloseAndDeleteWindow(m_auto_updater_dialog); });
else
{
m_auto_updater_dialog->disconnect(m_auto_updater_dialog, &AutoUpdaterWindow::closed, this, nullptr);
QtUtils::CloseAndDeleteWindow(m_auto_updater_dialog);
}
});
m_auto_updater_dialog->queueUpdateCheck(display_message);
}

View File

@@ -3274,7 +3274,7 @@ bool QtHost::ParseCommandLineParametersAndInitializeConfig(QApplication& app,
}
else if (CHECK_ARG("-updatecleanup"))
{
s_state.cleanup_after_update = AutoUpdaterWindow::isSupported();
s_state.cleanup_after_update = true;
continue;
}
else if (CHECK_ARG("--"))