mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
Add update checker to launcher (#478)
launcher: add release update checker Co-authored-by: nmzik <Nmzik@mail.ru>
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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><a href="check">Check for updates</a></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>
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user