Qt: Support update checking without installing

i.e. what I'm going to do with packages, just open the
download page and let the user install it.
This commit is contained in:
Stenzek 2024-09-28 22:01:01 +10:00
parent 7d8ae9aa11
commit b51def8a99
No known key found for this signature in database
4 changed files with 97 additions and 50 deletions

View File

@ -53,6 +53,11 @@ jobs:
shell: cmd shell: cmd
run: | run: |
echo #pragma once > src/scmversion/tag.h echo #pragma once > src/scmversion/tag.h
- name: Set Build Tag Asset
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev'
shell: cmd
run: |
echo #define SCM_RELEASE_ASSET "duckstation-windows-x64-release.zip" >> src/scmversion/tag.h echo #define SCM_RELEASE_ASSET "duckstation-windows-x64-release.zip" >> src/scmversion/tag.h
echo #define SCM_RELEASE_TAGS {"latest", "preview"} >> src/scmversion/tag.h echo #define SCM_RELEASE_TAGS {"latest", "preview"} >> src/scmversion/tag.h
@ -139,6 +144,11 @@ jobs:
shell: cmd shell: cmd
run: | run: |
echo #pragma once > src/scmversion/tag.h echo #pragma once > src/scmversion/tag.h
- name: Set Build Tag Asset
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev'
shell: cmd
run: |
echo #define SCM_RELEASE_ASSET "duckstation-windows-x64-sse2-release.zip" >> src/scmversion/tag.h echo #define SCM_RELEASE_ASSET "duckstation-windows-x64-sse2-release.zip" >> src/scmversion/tag.h
echo #define SCM_RELEASE_TAGS {"latest", "preview"} >> src/scmversion/tag.h echo #define SCM_RELEASE_TAGS {"latest", "preview"} >> src/scmversion/tag.h
@ -226,6 +236,11 @@ jobs:
shell: cmd shell: cmd
run: | run: |
echo #pragma once > src/scmversion/tag.h echo #pragma once > src/scmversion/tag.h
- name: Set Build Tag Asset
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev'
shell: cmd
run: |
echo #define SCM_RELEASE_ASSET "duckstation-windows-arm64-release.zip" >> src/scmversion/tag.h echo #define SCM_RELEASE_ASSET "duckstation-windows-arm64-release.zip" >> src/scmversion/tag.h
echo #define SCM_RELEASE_TAGS {"latest", "preview"} >> src/scmversion/tag.h echo #define SCM_RELEASE_TAGS {"latest", "preview"} >> src/scmversion/tag.h
@ -308,6 +323,10 @@ jobs:
- name: Initialize Build Tag - name: Initialize Build Tag
run: | run: |
echo '#pragma once' > src/scmversion/tag.h echo '#pragma once' > src/scmversion/tag.h
- name: Set Build Tag Asset
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev'
run: |
echo '#define SCM_RELEASE_ASSET "DuckStation-x64.AppImage"' >> src/scmversion/tag.h echo '#define SCM_RELEASE_ASSET "DuckStation-x64.AppImage"' >> src/scmversion/tag.h
echo '#define SCM_RELEASE_TAGS {"latest", "preview"}' >> src/scmversion/tag.h echo '#define SCM_RELEASE_TAGS {"latest", "preview"}' >> src/scmversion/tag.h
@ -368,6 +387,10 @@ jobs:
- name: Initialize Build Tag - name: Initialize Build Tag
run: | run: |
echo '#pragma once' > src/scmversion/tag.h echo '#pragma once' > src/scmversion/tag.h
- name: Set Build Tag Asset
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev'
run: |
echo '#define SCM_RELEASE_ASSET "DuckStation-x64-SSE2.AppImage"' >> src/scmversion/tag.h echo '#define SCM_RELEASE_ASSET "DuckStation-x64-SSE2.AppImage"' >> src/scmversion/tag.h
echo '#define SCM_RELEASE_TAGS {"latest", "preview"}' >> src/scmversion/tag.h echo '#define SCM_RELEASE_TAGS {"latest", "preview"}' >> src/scmversion/tag.h
@ -420,6 +443,21 @@ jobs:
run: | run: |
echo '#pragma once' > src/scmversion/tag.h echo '#pragma once' > src/scmversion/tag.h
- name: Set Build Tags
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev'
run: |
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
- name: Tag as Rolling Release
if: github.ref == 'refs/heads/dev'
run: |
echo '#define SCM_RELEASE_TAG "latest"' >> src/scmversion/tag.h
- name: Generate AppStream XML - name: Generate AppStream XML
run: | run: |
scripts/generate-metainfo.sh scripts/flatpak scripts/generate-metainfo.sh scripts/flatpak
@ -498,18 +536,20 @@ jobs:
run: | run: |
echo '#pragma once' > src/scmversion/tag.h echo '#pragma once' > src/scmversion/tag.h
- name: Tag as Preview Release - name: Set Build Tags
if: github.ref == 'refs/heads/master' if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev'
run: | run: |
echo '#define SCM_RELEASE_ASSET "duckstation-mac-release.zip"' >> 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 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 '#define SCM_RELEASE_TAG "preview"' >> src/scmversion/tag.h
- name: Tag as Rolling Release - name: Tag as Rolling Release
if: github.ref == 'refs/heads/dev' if: github.ref == 'refs/heads/dev'
run: | run: |
echo '#define SCM_RELEASE_ASSET "duckstation-mac-release.zip"' >> src/scmversion/tag.h
echo '#define SCM_RELEASE_TAGS {"latest", "preview"}' >> src/scmversion/tag.h
echo '#define SCM_RELEASE_TAG "latest"' >> src/scmversion/tag.h echo '#define SCM_RELEASE_TAG "latest"' >> src/scmversion/tag.h
- name: Compile and Zip .app - name: Compile and Zip .app
@ -532,6 +572,7 @@ jobs:
create-release: create-release:
name: Create Release
needs: [windows-x64-build, windows-x64-sse2-build, windows-arm64-build, linux-x64-appimage-build, linux-x64-sse2-appimage-build, linux-flatpak-build, macos-build] needs: [windows-x64-build, windows-x64-sse2-build, windows-arm64-build, linux-x64-appimage-build, linux-x64-sse2-appimage-build, linux-flatpak-build, macos-build]
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev' if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/dev'

View File

@ -50,20 +50,28 @@ static constexpr u32 HTTP_POLL_INTERVAL = 10;
// Requires that the channel be defined by the buildbot. // Requires that the channel be defined by the buildbot.
#if __has_include("scmversion/tag.h") #if __has_include("scmversion/tag.h")
#include "scmversion/tag.h" #include "scmversion/tag.h"
#if defined(SCM_RELEASE_TAGS) && defined(SCM_RELEASE_TAG) && defined(SCM_RELEASE_ASSET) #if defined(SCM_RELEASE_TAGS) && defined(SCM_RELEASE_TAG)
#define UPDATE_CHECKER_SUPPORTED
#ifdef SCM_RELEASE_ASSET
#define AUTO_UPDATER_SUPPORTED #define AUTO_UPDATER_SUPPORTED
#endif #endif
#endif #endif
#endif
#ifdef AUTO_UPDATER_SUPPORTED #ifdef UPDATE_CHECKER_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/{}"; 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* CHANGES_URL = "https://api.github.com/repos/stenzek/duckstation/compare/{}...{}";
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;
#ifdef AUTO_UPDATER_SUPPORTED
static const char* UPDATE_ASSET_FILENAME = SCM_RELEASE_ASSET;
#else
static const char* DOWNLOAD_PAGE_URL = "https://github.com/stenzek/duckstation/releases/tag/{}";
#endif
#endif #endif
LOG_CHANNEL(AutoUpdaterDialog); LOG_CHANNEL(AutoUpdaterDialog);
@ -87,20 +95,8 @@ AutoUpdaterDialog::~AutoUpdaterDialog() = default;
bool AutoUpdaterDialog::isSupported() bool AutoUpdaterDialog::isSupported()
{ {
#ifdef AUTO_UPDATER_SUPPORTED #ifdef UPDATE_CHECKER_SUPPORTED
#ifdef __linux__
// For Linux, we need to check whether we're running from the appimage.
if (!std::getenv("APPIMAGE"))
{
INFO_LOG("We're a CI release, but not running from an AppImage. Disabling automatic updater.");
return false;
}
return true; return true;
#else
// Windows/Mac - always supported.
return true;
#endif
#else #else
return false; return false;
#endif #endif
@ -204,7 +200,7 @@ bool AutoUpdaterDialog::warnAboutUnofficialBuild()
QStringList AutoUpdaterDialog::getTagList() QStringList AutoUpdaterDialog::getTagList()
{ {
#ifdef AUTO_UPDATER_SUPPORTED #ifdef UPDATE_CHECKER_SUPPORTED
return QStringList(std::begin(UPDATE_TAGS), std::end(UPDATE_TAGS)); return QStringList(std::begin(UPDATE_TAGS), std::end(UPDATE_TAGS));
#else #else
return QStringList(); return QStringList();
@ -213,7 +209,7 @@ QStringList AutoUpdaterDialog::getTagList()
std::string AutoUpdaterDialog::getDefaultTag() std::string AutoUpdaterDialog::getDefaultTag()
{ {
#ifdef AUTO_UPDATER_SUPPORTED #ifdef UPDATE_CHECKER_SUPPORTED
return THIS_RELEASE_TAG; return THIS_RELEASE_TAG;
#else #else
return {}; return {};
@ -222,7 +218,7 @@ std::string AutoUpdaterDialog::getDefaultTag()
std::string AutoUpdaterDialog::getCurrentUpdateTag() const std::string AutoUpdaterDialog::getCurrentUpdateTag() const
{ {
#ifdef AUTO_UPDATER_SUPPORTED #ifdef UPDATE_CHECKER_SUPPORTED
return Host::GetBaseStringSettingValue("AutoUpdater", "UpdateTag", THIS_RELEASE_TAG); return Host::GetBaseStringSettingValue("AutoUpdater", "UpdateTag", THIS_RELEASE_TAG);
#else #else
return {}; return {};
@ -271,7 +267,7 @@ void AutoUpdaterDialog::queueUpdateCheck(bool display_message)
{ {
m_display_messages = display_message; m_display_messages = display_message;
#ifdef AUTO_UPDATER_SUPPORTED #ifdef UPDATE_CHECKER_SUPPORTED
if (!ensureHttpReady()) if (!ensureHttpReady())
{ {
emit updateCheckCompleted(); emit updateCheckCompleted();
@ -287,7 +283,7 @@ void AutoUpdaterDialog::queueUpdateCheck(bool display_message)
void AutoUpdaterDialog::queueGetLatestRelease() void AutoUpdaterDialog::queueGetLatestRelease()
{ {
#ifdef AUTO_UPDATER_SUPPORTED #ifdef UPDATE_CHECKER_SUPPORTED
if (!ensureHttpReady()) if (!ensureHttpReady())
{ {
emit updateCheckCompleted(); emit updateCheckCompleted();
@ -302,7 +298,7 @@ void AutoUpdaterDialog::queueGetLatestRelease()
void AutoUpdaterDialog::getLatestTagComplete(s32 status_code, std::vector<u8> response) void AutoUpdaterDialog::getLatestTagComplete(s32 status_code, std::vector<u8> response)
{ {
#ifdef AUTO_UPDATER_SUPPORTED #ifdef UPDATE_CHECKER_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);
@ -362,7 +358,7 @@ void AutoUpdaterDialog::getLatestTagComplete(s32 status_code, std::vector<u8> re
void AutoUpdaterDialog::getLatestReleaseComplete(s32 status_code, std::vector<u8> response) void AutoUpdaterDialog::getLatestReleaseComplete(s32 status_code, std::vector<u8> response)
{ {
#ifdef AUTO_UPDATER_SUPPORTED #ifdef UPDATE_CHECKER_SUPPORTED
if (status_code == HTTPDownloader::HTTP_STATUS_OK) if (status_code == HTTPDownloader::HTTP_STATUS_OK)
{ {
QJsonParseError parse_error; QJsonParseError parse_error;
@ -372,9 +368,14 @@ void AutoUpdaterDialog::getLatestReleaseComplete(s32 status_code, std::vector<u8
{ {
const QJsonObject doc_object(doc.object()); const QJsonObject doc_object(doc.object());
m_ui.currentVersion->setText(tr("Current Version: %1 (%2)").arg(g_scm_hash_str).arg(g_scm_date_str));
m_ui.newVersion->setText(tr("New Version: %1 (%2)").arg(m_latest_sha).arg(doc_object["published_at"].toString()));
#ifdef AUTO_UPDATER_SUPPORTED
// search for the correct file // search for the correct file
const QJsonArray assets(doc_object["assets"].toArray()); const QJsonArray assets(doc_object["assets"].toArray());
const QString asset_filename(UPDATE_ASSET_FILENAME); const QString asset_filename(UPDATE_ASSET_FILENAME);
bool asset_found = false;
for (const QJsonValue& asset : assets) for (const QJsonValue& asset : assets)
{ {
const QJsonObject asset_obj(asset.toObject()); const QJsonObject asset_obj(asset.toObject());
@ -382,27 +383,28 @@ void AutoUpdaterDialog::getLatestReleaseComplete(s32 status_code, std::vector<u8
{ {
m_download_url = asset_obj["browser_download_url"].toString(); m_download_url = asset_obj["browser_download_url"].toString();
if (!m_download_url.isEmpty()) if (!m_download_url.isEmpty())
{
m_download_size = asset_obj["size"].toInt(); m_download_size = asset_obj["size"].toInt();
m_ui.currentVersion->setText(tr("Current Version: %1 (%2)").arg(g_scm_hash_str).arg(g_scm_date_str)); asset_found = true;
m_ui.newVersion->setText(
tr("New Version: %1 (%2)").arg(m_latest_sha).arg(doc_object["published_at"].toString()));
m_ui.updateNotes->setText(tr("Loading..."));
m_ui.downloadAndInstall->setEnabled(true);
queueGetChanges();
// We have to defer this, because it comes back through the timer/HTTP callback...
QMetaObject::invokeMethod(this, "exec", Qt::QueuedConnection);
emit updateCheckCompleted();
return;
}
break; break;
} }
} }
reportError("Asset/asset download not found"); if (!asset_found)
{
reportError("Asset/asset download not found");
return;
}
#else
// Just display the version and a download link.
m_ui.downloadAndInstall->setText(tr("Download..."));
#endif
m_ui.downloadAndInstall->setEnabled(true);
m_ui.updateNotes->setText(tr("Loading..."));
queueGetChanges();
// We have to defer this, because it comes back through the timer/HTTP callback...
QMetaObject::invokeMethod(this, "exec", Qt::QueuedConnection);
} }
else else
{ {
@ -420,7 +422,7 @@ void AutoUpdaterDialog::getLatestReleaseComplete(s32 status_code, std::vector<u8
void AutoUpdaterDialog::queueGetChanges() void AutoUpdaterDialog::queueGetChanges()
{ {
#ifdef AUTO_UPDATER_SUPPORTED #ifdef UPDATE_CHECKER_SUPPORTED
if (!ensureHttpReady()) if (!ensureHttpReady())
return; return;
@ -432,7 +434,7 @@ void AutoUpdaterDialog::queueGetChanges()
void AutoUpdaterDialog::getChangesComplete(s32 status_code, std::vector<u8> response) void AutoUpdaterDialog::getChangesComplete(s32 status_code, std::vector<u8> response)
{ {
#ifdef AUTO_UPDATER_SUPPORTED #ifdef UPDATE_CHECKER_SUPPORTED
if (status_code == HTTPDownloader::HTTP_STATUS_OK) if (status_code == HTTPDownloader::HTTP_STATUS_OK)
{ {
QJsonParseError parse_error; QJsonParseError parse_error;
@ -506,6 +508,7 @@ void AutoUpdaterDialog::getChangesComplete(s32 status_code, std::vector<u8> resp
void AutoUpdaterDialog::downloadUpdateClicked() void AutoUpdaterDialog::downloadUpdateClicked()
{ {
#ifdef AUTO_UPDATER_SUPPORTED
m_display_messages = true; m_display_messages = true;
std::optional<bool> download_result; std::optional<bool> download_result;
@ -539,8 +542,8 @@ void AutoUpdaterDialog::downloadUpdateClicked()
}, },
&progress); &progress);
// Since we're going to block, don't allow the timer to poll, otherwise the progress callback can cause the timer to // Since we're going to block, don't allow the timer to poll, otherwise the progress callback can cause the timer
// run, and recursively poll again. // to run, and recursively poll again.
m_http_poll_timer->stop(); m_http_poll_timer->stop();
// Block until completion. // Block until completion.
@ -558,6 +561,9 @@ void AutoUpdaterDialog::downloadUpdateClicked()
QMetaObject::invokeMethod(g_main_window, "requestExit", Qt::QueuedConnection, Q_ARG(bool, true)); QMetaObject::invokeMethod(g_main_window, "requestExit", Qt::QueuedConnection, Q_ARG(bool, true));
done(0); done(0);
} }
#elif defined(UPDATE_CHECKER_SUPPORTED)
QtUtils::OpenURL(this, fmt::format(fmt::runtime(DOWNLOAD_PAGE_URL), getCurrentUpdateTag()));
#endif
} }
bool AutoUpdaterDialog::updateNeeded() const bool AutoUpdaterDialog::updateNeeded() const

View File

@ -153,9 +153,9 @@ void QtUtils::OpenURL(QWidget* parent, const QUrl& qurl)
} }
} }
void QtUtils::OpenURL(QWidget* parent, const char* url) void QtUtils::OpenURL(QWidget* parent, const std::string_view url)
{ {
return OpenURL(parent, QUrl::fromEncoded(QByteArray(url, static_cast<int>(std::strlen(url))))); return OpenURL(parent, QUrl::fromEncoded(QByteArray(url.data(), static_cast<int>(url.length()))));
} }
std::optional<unsigned> QtUtils::PromptForAddress(QWidget* parent, const QString& title, const QString& label, std::optional<unsigned> QtUtils::PromptForAddress(QWidget* parent, const QString& title, const QString& label,

View File

@ -84,7 +84,7 @@ u32 KeyEventToCode(const QKeyEvent* ev);
void OpenURL(QWidget* parent, const QUrl& qurl); void OpenURL(QWidget* parent, const QUrl& qurl);
/// Opens a URL string with the default handler. /// Opens a URL string with the default handler.
void OpenURL(QWidget* parent, const char* url); void OpenURL(QWidget* parent, const std::string_view url);
/// Prompts for an address in hex. /// Prompts for an address in hex.
std::optional<unsigned> PromptForAddress(QWidget* parent, const QString& title, const QString& label, bool code); std::optional<unsigned> PromptForAddress(QWidget* parent, const QString& title, const QString& label, bool code);