Add update checker to launcher (#478)

launcher: add release update checker

Co-authored-by: nmzik <Nmzik@mail.ru>
This commit is contained in:
Tarun
2026-09-04 06:08:02 +05:30
committed by GitHub
parent 040b2b9653
commit b9b0b87545
5 changed files with 221 additions and 5 deletions
+1 -1
View File
@@ -20,7 +20,7 @@ add_executable(launcher WIN32
)
target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets)
target_link_libraries(launcher Qt6::Concurrent Qt6::Network Qt6::Widgets kyty_git_version)
if(WIN32)
target_link_libraries(launcher setupapi)
endif()
+36 -2
View File
@@ -72,9 +72,43 @@
</widget>
</item>
<item>
<widget class="QLabel" name="label_Version">
<layout class="QHBoxLayout" name="versionLayout">
<item>
<widget class="QLabel" name="label_Version">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="check_updates_link">
<property name="text">
<string>&lt;a href=&quot;check&quot;&gt;Check for updates&lt;/a&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
<item>
<spacer name="versionSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="check_updates_on_startup">
<property name="text">
<string>TextLabel</string>
<string>Check for updates on startup</string>
</property>
</widget>
</item>
+34
View File
@@ -0,0 +1,34 @@
#ifndef UPDATE_CHECKER_H
#define UPDATE_CHECKER_H
#include <QNetworkAccessManager>
#include <QObject>
class QByteArray;
class QWidget;
class UpdateChecker final: public QObject {
Q_OBJECT
public:
explicit UpdateChecker(QWidget* parent);
[[nodiscard]] static bool IsSupported();
void Check(bool manual);
signals:
void CheckingChanged(bool checking);
private:
struct UpdateInfo;
static UpdateInfo ParseUpdateInfo(const QByteArray& data);
void FetchUpdateInfo(const char* url, bool fallback, bool manual);
void ShowUpdateResult(const UpdateInfo& info, bool manual);
QWidget* m_parent = nullptr;
QNetworkAccessManager m_network;
bool m_checking_updates = false;
};
#endif // UPDATE_CHECKER_H
+25 -2
View File
@@ -5,9 +5,11 @@
#include "configurationItem.h"
#include "configurationListWidget.h"
#include "patchesDialog.h"
#include "updateChecker.h"
#include <QApplication>
#include <QByteArray>
#include <QCheckBox>
#include <QDialog>
#include <QDir>
#include <QFile>
@@ -54,6 +56,7 @@ constexpr DWORD CMD_Y_CHARS = 1000;
#endif
constexpr char SETTINGS_MAIN_DIALOG[] = "MainDialog";
constexpr char SETTINGS_MAIN_LAST_GEOMETRY[] = "geometry";
constexpr char SETTINGS_CHECK_UPDATES[] = "check_updates_on_startup";
class DetachableProcess: public QProcess {
Q_OBJECT;
@@ -89,9 +92,11 @@ public:
private:
static QByteArray g_last_geometry;
static bool g_check_updates_on_startup;
Ui::MainDialog* m_ui = {nullptr};
MainDialog* m_main_dialog = nullptr;
Ui::MainDialog* m_ui = {nullptr};
MainDialog* m_main_dialog = nullptr;
UpdateChecker* m_update_checker = nullptr;
QString m_interpreter;
/*DetachableProcess*/ QProcess m_process;
@@ -100,6 +105,7 @@ private:
};
QByteArray MainDialogPrivate::g_last_geometry;
bool MainDialogPrivate::g_check_updates_on_startup = true;
MainDialog::MainDialog(QWidget* parent): QDialog(parent), m_p(new MainDialogPrivate(this)) {
m_p->Setup(this);
@@ -114,7 +120,11 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) {
m_ui->setupUi(main_dialog);
m_main_dialog = main_dialog;
m_update_checker = new UpdateChecker(main_dialog);
m_ui->widget->SetMainDialog(main_dialog);
m_ui->check_updates_on_startup->setChecked(g_check_updates_on_startup);
m_ui->check_updates_link->setVisible(UpdateChecker::IsSupported());
m_ui->check_updates_on_startup->setVisible(UpdateChecker::IsSupported());
main_dialog->setWindowFlags(Qt::Dialog /*| Qt::MSWindowsFixedSizeDialogHint*/);
@@ -122,6 +132,14 @@ void MainDialogPrivate::Setup(MainDialog* main_dialog) {
Qt::QueuedConnection);
connect(m_ui->widget, &ConfigurationListWidget::Select, this, &MainDialogPrivate::Update);
connect(m_ui->widget, &ConfigurationListWidget::Run, this, &MainDialogPrivate::Run);
connect(m_ui->check_updates_link, &QLabel::linkActivated, this,
[this](const QString&) { m_update_checker->Check(true); });
connect(m_update_checker, &UpdateChecker::CheckingChanged, m_ui->check_updates_link,
&QLabel::setDisabled);
connect(m_ui->check_updates_on_startup, &QCheckBox::toggled, this, [this](bool checked) {
g_check_updates_on_startup = checked;
m_ui->widget->WriteSettings();
});
connect(main_dialog, &MainDialog::Resize, [this]() {
g_last_geometry = m_main_dialog->saveGeometry();
m_ui->widget->WriteSettings();
@@ -189,6 +207,9 @@ void MainDialogPrivate::FindInterpreter() {
m_ui->label_settings_file->setText(tr("Settings file: ") + m_ui->widget->GetSettingsFile());
Update();
if (m_ui->check_updates_on_startup->isChecked()) {
m_update_checker->Check(false);
}
}
static QString BoolArg(bool value) {
@@ -448,6 +469,7 @@ void MainDialogPrivate::WriteSettings(QSettings& s) {
if (!g_last_geometry.isEmpty()) {
s.setValue(SETTINGS_MAIN_LAST_GEOMETRY, g_last_geometry);
}
s.setValue(SETTINGS_CHECK_UPDATES, g_check_updates_on_startup);
s.endGroup();
}
@@ -456,6 +478,7 @@ void MainDialogPrivate::ReadSettings(QSettings& s) {
s.beginGroup(SETTINGS_MAIN_DIALOG);
g_last_geometry = s.value(SETTINGS_MAIN_LAST_GEOMETRY, g_last_geometry).toByteArray();
g_check_updates_on_startup = s.value(SETTINGS_CHECK_UPDATES, true).toBool();
s.endGroup();
}
+125
View File
@@ -0,0 +1,125 @@
#include "updateChecker.h"
#include "kytyGitVersion.h"
#include <QByteArray>
#include <QDebug>
#include <QDesktopServices>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMessageBox>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QString>
#include <QUrl>
#include <QWidget>
namespace {
constexpr char DEFAULT_FEED_URL[] = "https://kytyps5.github.io/data/updates.json";
constexpr char FALLBACK_FEED_URL[] =
"https://api.github.com/repos/KytyPS5/KytyPS5/releases/latest";
} // namespace
struct UpdateChecker::UpdateInfo {
QString tag;
QUrl page_url;
QString error;
};
UpdateChecker::UpdateChecker(QWidget* parent): QObject(parent), m_parent(parent), m_network(this) {}
bool UpdateChecker::IsSupported() {
#if defined(KYTY_OFFICIAL_BUILD) && defined(NDEBUG)
return !QString::fromLatin1(KYTY_GIT_HASH).endsWith(QStringLiteral("-dirty"));
#else
return false;
#endif
}
UpdateChecker::UpdateInfo UpdateChecker::ParseUpdateInfo(const QByteArray& data) {
const auto document = QJsonDocument::fromJson(data);
if (!document.isObject()) {
return {{}, {}, tr("Invalid update feed")};
}
const auto root = document.object();
UpdateInfo info {root.value(QStringLiteral("tag")).toString(),
QUrl(root.value(QStringLiteral("html_url")).toString()), {}};
if (info.tag.isEmpty()) {
info.tag = root.value(QStringLiteral("tag_name")).toString();
}
if (info.tag.isEmpty() || !info.page_url.isValid() ||
info.page_url.scheme() != QStringLiteral("https")) {
info.error = tr("Incomplete update feed");
}
return info;
}
void UpdateChecker::Check(bool manual) {
if (!IsSupported() || m_checking_updates) {
return;
}
m_checking_updates = true;
emit CheckingChanged(true);
FetchUpdateInfo(DEFAULT_FEED_URL, false, manual);
}
void UpdateChecker::FetchUpdateInfo(const char* url, bool fallback, bool manual) {
QNetworkRequest request(QUrl(QString::fromLatin1(url)));
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
QNetworkRequest::NoLessSafeRedirectPolicy);
request.setRawHeader("User-Agent", "Kyty-Launcher");
request.setRawHeader("Accept", "application/vnd.github+json");
request.setTransferTimeout(15000);
auto* reply = m_network.get(request);
connect(reply, &QNetworkReply::finished, this, [this, reply, fallback, manual]() {
UpdateInfo info;
if (reply->error() == QNetworkReply::NoError) {
info = ParseUpdateInfo(reply->readAll());
} else {
info.error = reply->errorString();
}
reply->deleteLater();
if (!info.error.isEmpty() && !fallback) {
FetchUpdateInfo(FALLBACK_FEED_URL, true, manual);
return;
}
m_checking_updates = false;
emit CheckingChanged(false);
ShowUpdateResult(info, manual);
});
}
void UpdateChecker::ShowUpdateResult(const UpdateInfo& info, bool manual) {
if (!info.error.isEmpty()) {
qWarning() << "Could not check for updates:" << info.error;
if (manual) {
QMessageBox::warning(m_parent, tr("Update Check"),
tr("Could not check for updates:\n%1").arg(info.error));
}
return;
}
const bool current = info.tag == QString::fromLatin1(KYTY_RELEASE_TAG);
if (current) {
if (manual) {
QMessageBox::information(m_parent, tr("Update Check"),
tr("You are using the latest version (%1).").arg(info.tag));
}
return;
}
const auto message =
tr("An update is available.\n\nCurrent: %1\nLatest: %2\n\n"
"Open the release page?")
.arg(QString::fromLatin1(KYTY_RELEASE_TAG), info.tag);
if (QMessageBox::question(m_parent, tr("KytyPS5 Update"), message,
QMessageBox::Open | QMessageBox::Cancel,
QMessageBox::Open) == QMessageBox::Open) {
QDesktopServices::openUrl(info.page_url);
}
}