mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-25 07:05:40 +00:00
Qt: Add automatic updates for AppImage
This commit is contained in:
parent
4ee71eb40f
commit
b6e5b0bd69
17
.github/workflows/rolling-release.yml
vendored
17
.github/workflows/rolling-release.yml
vendored
|
@ -234,6 +234,23 @@ jobs:
|
||||||
if: steps.cache-deps.outputs.cache-hit != 'true'
|
if: steps.cache-deps.outputs.cache-hit != 'true'
|
||||||
run: scripts/build-dependencies.sh
|
run: scripts/build-dependencies.sh
|
||||||
|
|
||||||
|
- name: Tag as preview build
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
run: |
|
||||||
|
echo '#pragma once' > 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_TAG "preview"' >> src/scmversion/tag.h
|
||||||
|
|
||||||
|
- name: Tag as dev build
|
||||||
|
if: github.ref == 'refs/heads/dev'
|
||||||
|
run: |
|
||||||
|
echo '#pragma once' > 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_TAG "latest"' >> src/scmversion/tag.h
|
||||||
|
|
||||||
|
|
||||||
- name: Compile build
|
- name: Compile build
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
|
||||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
#include "autoupdaterdialog.h"
|
#include "autoupdaterdialog.h"
|
||||||
#include "common/file_system.h"
|
|
||||||
#include "common/log.h"
|
|
||||||
#include "common/minizip_helpers.h"
|
|
||||||
#include "common/string_util.h"
|
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "qthost.h"
|
#include "qthost.h"
|
||||||
#include "qtutils.h"
|
#include "qtutils.h"
|
||||||
#include "scmversion/scmversion.h"
|
#include "scmversion/scmversion.h"
|
||||||
#include "unzip.h"
|
#include "unzip.h"
|
||||||
|
|
||||||
|
#include "common/file_system.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include "common/minizip_helpers.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
|
||||||
#include <QtCore/QCoreApplication>
|
#include <QtCore/QCoreApplication>
|
||||||
#include <QtCore/QFile>
|
#include <QtCore/QFile>
|
||||||
#include <QtCore/QJsonArray>
|
#include <QtCore/QJsonArray>
|
||||||
|
@ -25,11 +27,12 @@
|
||||||
#include <QtWidgets/QDialog>
|
#include <QtWidgets/QDialog>
|
||||||
#include <QtWidgets/QMessageBox>
|
#include <QtWidgets/QMessageBox>
|
||||||
#include <QtWidgets/QProgressDialog>
|
#include <QtWidgets/QProgressDialog>
|
||||||
|
|
||||||
Log_SetChannel(AutoUpdaterDialog);
|
Log_SetChannel(AutoUpdaterDialog);
|
||||||
|
|
||||||
// Logic to detect whether we can use the auto updater.
|
// Logic to detect whether we can use the auto updater.
|
||||||
// Currently Windows-only, and requires that the channel be defined by the buildbot.
|
// Currently Windows and Linux-only, and requires that the channel be defined by the buildbot.
|
||||||
#ifdef _WIN32
|
#if defined(_WIN32) || defined(__linux__)
|
||||||
#if defined(__has_include) && __has_include("scmversion/tag.h")
|
#if defined(__has_include) && __has_include("scmversion/tag.h")
|
||||||
#include "scmversion/tag.h"
|
#include "scmversion/tag.h"
|
||||||
#ifdef SCM_RELEASE_TAGS
|
#ifdef SCM_RELEASE_TAGS
|
||||||
|
@ -68,7 +71,19 @@ AutoUpdaterDialog::~AutoUpdaterDialog() = default;
|
||||||
bool AutoUpdaterDialog::isSupported()
|
bool AutoUpdaterDialog::isSupported()
|
||||||
{
|
{
|
||||||
#ifdef AUTO_UPDATER_SUPPORTED
|
#ifdef AUTO_UPDATER_SUPPORTED
|
||||||
|
#ifdef __linux__
|
||||||
|
// For Linux, we need to check whether we're running from the appimage.
|
||||||
|
if (!std::getenv("APPIMAGE"))
|
||||||
|
{
|
||||||
|
Log_InfoPrintf("We're a CI release, but not running from an AppImage. Disabling automatic updater.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
#else
|
||||||
|
// Windows - always supported.
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
#else
|
#else
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
|
@ -120,9 +135,6 @@ void AutoUpdaterDialog::queueUpdateCheck(bool display_message)
|
||||||
|
|
||||||
QUrl url(QUrl::fromEncoded(QByteArray(LATEST_TAG_URL)));
|
QUrl url(QUrl::fromEncoded(QByteArray(LATEST_TAG_URL)));
|
||||||
QNetworkRequest request(url);
|
QNetworkRequest request(url);
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
|
||||||
#endif
|
|
||||||
m_network_access_mgr->get(request);
|
m_network_access_mgr->get(request);
|
||||||
#else
|
#else
|
||||||
emit updateCheckCompleted();
|
emit updateCheckCompleted();
|
||||||
|
@ -139,9 +151,6 @@ void AutoUpdaterDialog::queueGetLatestRelease()
|
||||||
|
|
||||||
QUrl url(QUrl::fromEncoded(QByteArray(url_string)));
|
QUrl url(QUrl::fromEncoded(QByteArray(url_string)));
|
||||||
QNetworkRequest request(url);
|
QNetworkRequest request(url);
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
|
||||||
#endif
|
|
||||||
m_network_access_mgr->get(request);
|
m_network_access_mgr->get(request);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -274,9 +283,6 @@ void AutoUpdaterDialog::queueGetChanges()
|
||||||
StringUtil::StdStringFromFormat(CHANGES_URL, g_scm_hash_str, getCurrentUpdateTag().c_str()));
|
StringUtil::StdStringFromFormat(CHANGES_URL, g_scm_hash_str, getCurrentUpdateTag().c_str()));
|
||||||
QUrl url(QUrl::fromEncoded(QByteArray(url_string.c_str(), static_cast<int>(url_string.size()))));
|
QUrl url(QUrl::fromEncoded(QByteArray(url_string.c_str(), static_cast<int>(url_string.size()))));
|
||||||
QNetworkRequest request(url);
|
QNetworkRequest request(url);
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
|
||||||
#endif
|
|
||||||
m_network_access_mgr->get(request);
|
m_network_access_mgr->get(request);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -364,9 +370,6 @@ void AutoUpdaterDialog::downloadUpdateClicked()
|
||||||
{
|
{
|
||||||
QUrl url(m_download_url);
|
QUrl url(m_download_url);
|
||||||
QNetworkRequest request(url);
|
QNetworkRequest request(url);
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
||||||
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
|
||||||
#endif
|
|
||||||
QNetworkReply* reply = m_network_access_mgr->get(request);
|
QNetworkReply* reply = m_network_access_mgr->get(request);
|
||||||
|
|
||||||
QProgressDialog progress(tr("Downloading %1...").arg(m_download_url), tr("Cancel"), 0, 1);
|
QProgressDialog progress(tr("Downloading %1...").arg(m_download_url), tr("Cancel"), 0, 1);
|
||||||
|
@ -575,6 +578,110 @@ bool AutoUpdaterDialog::doUpdate(const QString& zip_path, const QString& updater
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AutoUpdaterDialog::cleanupAfterUpdate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(__linux__)
|
||||||
|
|
||||||
|
bool AutoUpdaterDialog::processUpdate(const QByteArray& update_data)
|
||||||
|
{
|
||||||
|
const char* appimage_path = std::getenv("APPIMAGE");
|
||||||
|
if (!appimage_path || !FileSystem::FileExists(appimage_path))
|
||||||
|
{
|
||||||
|
reportError("Missing APPIMAGE.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString qappimage_path(QString::fromUtf8(appimage_path));
|
||||||
|
if (!QFile::exists(qappimage_path))
|
||||||
|
{
|
||||||
|
reportError("Current AppImage does not exist: %s", appimage_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString new_appimage_path(qappimage_path + QStringLiteral(".new"));
|
||||||
|
const QString backup_appimage_path(qappimage_path + QStringLiteral(".backup"));
|
||||||
|
Log_InfoPrintf("APPIMAGE = %s", appimage_path);
|
||||||
|
Log_InfoPrintf("Backup AppImage path = %s", backup_appimage_path.toUtf8().constData());
|
||||||
|
Log_InfoPrintf("New AppImage path = %s", new_appimage_path.toUtf8().constData());
|
||||||
|
|
||||||
|
// Remove old "new" appimage and existing backup appimage.
|
||||||
|
if (QFile::exists(new_appimage_path) && !QFile::remove(new_appimage_path))
|
||||||
|
{
|
||||||
|
reportError("Failed to remove old destination AppImage: %s", new_appimage_path.toUtf8().constData());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (QFile::exists(backup_appimage_path) && !QFile::remove(backup_appimage_path))
|
||||||
|
{
|
||||||
|
reportError("Failed to remove old backup AppImage: %s", new_appimage_path.toUtf8().constData());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write "new" appimage.
|
||||||
|
{
|
||||||
|
// We want to copy the permissions from the old appimage to the new one.
|
||||||
|
QFile old_file(qappimage_path);
|
||||||
|
const QFileDevice::Permissions old_permissions = old_file.permissions();
|
||||||
|
QFile new_file(new_appimage_path);
|
||||||
|
if (!new_file.open(QIODevice::WriteOnly) || new_file.write(update_data) != update_data.size() ||
|
||||||
|
!new_file.setPermissions(old_permissions))
|
||||||
|
{
|
||||||
|
QFile::remove(new_appimage_path);
|
||||||
|
reportError("Failed to write new destination AppImage: %s", new_appimage_path.toUtf8().constData());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename "old" appimage.
|
||||||
|
if (!QFile::rename(qappimage_path, backup_appimage_path))
|
||||||
|
{
|
||||||
|
reportError("Failed to rename old AppImage to %s", backup_appimage_path.toUtf8().constData());
|
||||||
|
QFile::remove(new_appimage_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename "new" appimage.
|
||||||
|
if (!QFile::rename(new_appimage_path, qappimage_path))
|
||||||
|
{
|
||||||
|
reportError("Failed to rename new AppImage to %s", qappimage_path.toUtf8().constData());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute new appimage.
|
||||||
|
QProcess* new_process = new QProcess();
|
||||||
|
new_process->setProgram(qappimage_path);
|
||||||
|
new_process->setArguments(QStringList{QStringLiteral("-updatecleanup")});
|
||||||
|
if (!new_process->startDetached())
|
||||||
|
{
|
||||||
|
reportError("Failed to execute new AppImage.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We exit once we return.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AutoUpdaterDialog::cleanupAfterUpdate()
|
||||||
|
{
|
||||||
|
// Remove old/backup AppImage.
|
||||||
|
const char* appimage_path = std::getenv("APPIMAGE");
|
||||||
|
if (!appimage_path)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const QString qappimage_path(QString::fromUtf8(appimage_path));
|
||||||
|
const QString backup_appimage_path(qappimage_path + QStringLiteral(".backup"));
|
||||||
|
if (!QFile::exists(backup_appimage_path))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Log_InfoPrint(QStringLiteral("Removing backup AppImage %1").arg(backup_appimage_path).toStdString().c_str());
|
||||||
|
if (!QFile::remove(backup_appimage_path))
|
||||||
|
{
|
||||||
|
Log_ErrorPrint(
|
||||||
|
QStringLiteral("Failed to remove backup AppImage %1").arg(backup_appimage_path).toStdString().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
bool AutoUpdaterDialog::processUpdate(const QByteArray& update_data)
|
bool AutoUpdaterDialog::processUpdate(const QByteArray& update_data)
|
||||||
|
@ -582,4 +689,8 @@ bool AutoUpdaterDialog::processUpdate(const QByteArray& update_data)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AutoUpdaterDialog::cleanupAfterUpdate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
|
// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com>
|
||||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ui_autoupdaterdialog.h"
|
#include "ui_autoupdaterdialog.h"
|
||||||
#include <string>
|
|
||||||
#include <QtCore/QStringList>
|
#include <QtCore/QStringList>
|
||||||
#include <QtWidgets/QDialog>
|
#include <QtWidgets/QDialog>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
class QNetworkAccessManager;
|
class QNetworkAccessManager;
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
|
@ -23,6 +25,7 @@ public:
|
||||||
static bool isSupported();
|
static bool isSupported();
|
||||||
static QStringList getTagList();
|
static QStringList getTagList();
|
||||||
static std::string getDefaultTag();
|
static std::string getDefaultTag();
|
||||||
|
static void cleanupAfterUpdate();
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void updateCheckCompleted();
|
void updateCheckCompleted();
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
|
||||||
|
|
||||||
#include "qthost.h"
|
#include "qthost.h"
|
||||||
|
#include "autoupdaterdialog.h"
|
||||||
#include "displaywidget.h"
|
#include "displaywidget.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "qtprogresscallback.h"
|
#include "qtprogresscallback.h"
|
||||||
|
@ -1224,10 +1225,9 @@ void Host::OnAchievementsRefreshed()
|
||||||
{
|
{
|
||||||
game_id = Achievements::GetGameID();
|
game_id = Achievements::GetGameID();
|
||||||
|
|
||||||
game_info = qApp
|
game_info = qApp->translate("EmuThread", "Game: %1 (%2)\n")
|
||||||
->translate("EmuThread", "Game: %1 (%2)\n")
|
.arg(QString::fromStdString(Achievements::GetGameTitle()))
|
||||||
.arg(QString::fromStdString(Achievements::GetGameTitle()))
|
.arg(game_id);
|
||||||
.arg(game_id);
|
|
||||||
|
|
||||||
const std::string& rich_presence_string = Achievements::GetRichPresenceString();
|
const std::string& rich_presence_string = Achievements::GetRichPresenceString();
|
||||||
if (Achievements::HasRichPresence() && !rich_presence_string.empty())
|
if (Achievements::HasRichPresence() && !rich_presence_string.empty())
|
||||||
|
@ -1905,6 +1905,13 @@ bool QtHost::ParseCommandLineParametersAndInitializeConfig(QApplication& app,
|
||||||
InitializeEarlyConsole();
|
InitializeEarlyConsole();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
else if (CHECK_ARG("-updatecleanup"))
|
||||||
|
{
|
||||||
|
if (AutoUpdaterDialog::isSupported())
|
||||||
|
AutoUpdaterDialog::cleanupAfterUpdate();
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
#ifdef ENABLE_RAINTEGRATION
|
#ifdef ENABLE_RAINTEGRATION
|
||||||
else if (CHECK_ARG("-raintegration"))
|
else if (CHECK_ARG("-raintegration"))
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in a new issue