mirror of
https://github.com/stenzek/duckstation.git
synced 2026-02-04 05:04:33 +00:00
Qt: Simplify updater configuration and process
This commit is contained in:
17
.github/workflows/linux-appimage-build.yml
vendored
17
.github/workflows/linux-appimage-build.yml
vendored
@@ -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
|
||||
|
||||
17
.github/workflows/linux-cross-appimage-build.yml
vendored
17
.github/workflows/linux-cross-appimage-build.yml
vendored
@@ -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
|
||||
|
||||
17
.github/workflows/macos-build.yml
vendored
17
.github/workflows/macos-build.yml
vendored
@@ -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
|
||||
|
||||
18
.github/workflows/windows-build.yml
vendored
18
.github/workflows/windows-build.yml
vendored
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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("--"))
|
||||
|
||||
Reference in New Issue
Block a user