aboutsummaryrefslogtreecommitdiff
path: root/src/yuzu/updater/update_dialog.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/yuzu/updater/update_dialog.cpp')
-rw-r--r--src/yuzu/updater/update_dialog.cpp178
1 files changed, 178 insertions, 0 deletions
diff --git a/src/yuzu/updater/update_dialog.cpp b/src/yuzu/updater/update_dialog.cpp
new file mode 100644
index 0000000000..c2c0556e9c
--- /dev/null
+++ b/src/yuzu/updater/update_dialog.cpp
@@ -0,0 +1,178 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <QRadioButton>
+#include <QSaveFile>
+#include <QStandardPaths>
+#include <qdesktopservices.h>
+#include "common/logging.h"
+#include "qt_common/abstract/frontend.h"
+#include "qt_common/abstract/progress.h"
+#include "ui_update_dialog.h"
+#include "update_dialog.h"
+
+#include "common/httplib.h"
+
+#ifdef YUZU_BUNDLED_OPENSSL
+#include <openssl/cert.h>
+#endif
+
+#include <QDesktopServices>
+
+#undef GetSaveFileName
+
+UpdateDialog::UpdateDialog(const Common::Net::Release& release, QWidget* parent)
+ : QDialog(parent), ui(new Ui::UpdateDialog) {
+ ui->setupUi(this);
+
+ ui->version->setText(
+ tr("%1 is available for download.").arg(QString::fromStdString(release.title)));
+ ui->url->setText(
+ tr("<a href=\"%1\">View on Forgejo</a>").arg(QString::fromStdString(release.html_url)));
+
+ std::string text{release.body};
+ if (auto pos = text.find("# Packages"); pos != std::string::npos) {
+ text = text.substr(0, pos);
+ }
+
+ ui->body->setMarkdown(QString::fromStdString(text));
+
+ // TODO(crueter): Find a way to set default
+ const auto assets = release.GetPlatformAssets();
+
+ if (assets.empty()) {
+ ui->groupBox->setHidden(true);
+ connect(this, &QDialog::accepted, this, [release]() {
+ QDesktopServices::openUrl(QUrl{QString::fromStdString(release.html_url)});
+ });
+ } else if (assets.size() == 1) {
+ m_asset = assets[0];
+
+ connect(this, &QDialog::accepted, this, &UpdateDialog::Download);
+ } else {
+ u32 i = 0;
+ for (const Common::Net::Asset& a : assets) {
+ QRadioButton* r = new QRadioButton(tr(a.name.c_str()), this);
+ connect(r, &QRadioButton::toggled, this, [a, this](bool checked) {
+ if (checked)
+ m_asset = a;
+ });
+
+ if (i == 0)
+ r->setChecked(true);
+ ++i;
+
+ ui->radioButtons->addWidget(r);
+ }
+
+ connect(this, &QDialog::accepted, this, &UpdateDialog::Download);
+ }
+}
+
+UpdateDialog::~UpdateDialog() {
+ delete ui;
+}
+
+void UpdateDialog::Download() {
+ const auto filename = QtCommon::Frontend::GetSaveFileName(
+ tr("New Version Location"),
+ qApp->applicationDirPath() % QStringLiteral("/") % QString::fromStdString(m_asset.filename),
+ tr("All Files (*.*)"));
+
+ if (filename.isEmpty())
+ return;
+
+ QSaveFile file(filename);
+ if (!file.open(QIODevice::Truncate | QIODevice::WriteOnly)) {
+ LOG_WARNING(Frontend, "Could not open file {}", filename.toStdString());
+ QtCommon::Frontend::Critical(tr("Failed to save file"),
+ tr("Could not open file %1 for writing.").arg(filename));
+ return;
+ }
+
+ // TODO(crueter): Move to net.cpp
+ constexpr std::size_t timeout_seconds = 15;
+
+ std::unique_ptr<httplib::Client> client = std::make_unique<httplib::Client>(m_asset.url);
+ client->set_connection_timeout(timeout_seconds);
+ client->set_read_timeout(timeout_seconds);
+ client->set_write_timeout(timeout_seconds);
+
+#ifdef YUZU_BUNDLED_OPENSSL
+ client->load_ca_cert_store(kCert, sizeof(kCert));
+#endif
+
+ if (client == nullptr) {
+ LOG_ERROR(Frontend, "Invalid URL {}{}", m_asset.url, m_asset.path);
+ return;
+ }
+
+ auto progress =
+ QtCommon::Frontend::newProgressDialog(tr("Downloading..."), tr("Cancel"), 0, 100);
+ progress->show();
+
+ QGuiApplication::processEvents();
+
+ // Progress dialog.
+ auto progress_callback = [&](size_t processed_size, size_t total_size) {
+ QGuiApplication::processEvents();
+ progress->setValue(static_cast<int>((processed_size * 100) / total_size));
+ return !progress->wasCanceled();
+ };
+
+ // Write file in chunks.
+ auto content_receiver = [&file, filename](const char* t_data, size_t data_length) -> bool {
+ if (file.write(t_data, data_length) == -1) {
+ LOG_WARNING(Frontend, "Could not write {} bytes to file {}", data_length,
+ filename.toStdString());
+ QtCommon::Frontend::Critical(tr("Failed to save file"),
+ tr("Could not write to file %1.").arg(filename));
+ return false;
+ }
+
+ return true;
+ };
+
+ // Now send off request
+ auto result = client->Get(m_asset.path, content_receiver, progress_callback);
+ progress->close();
+
+ // commit to file
+ if (!file.commit()) {
+ LOG_WARNING(Frontend, "Could not commit to file {}", filename.toStdString());
+ QtCommon::Frontend::Critical(tr("Failed to save file"),
+ tr("Could not commit to file %1.").arg(filename));
+ }
+
+ if (!result) {
+ LOG_ERROR(Frontend, "GET to {}{} returned null", m_asset.url, m_asset.path);
+ return;
+ }
+
+ const auto& response = result.value();
+ if (response.status >= 400) {
+ LOG_ERROR(Frontend, "GET to {}{} returned error status code: {}", m_asset.url, m_asset.path,
+ response.status);
+ QtCommon::Frontend::Critical(tr("Failed to download file"),
+ tr("Could not download from %1%2\nError code: %3")
+ .arg(QString::fromStdString(m_asset.url),
+ QString::fromStdString(m_asset.path),
+ QString::number(response.status)));
+ return;
+ }
+ if (!response.headers.contains("content-type")) {
+ LOG_ERROR(Frontend, "GET to {}{} returned no content", m_asset.url, m_asset.path);
+ return;
+ }
+
+ // Download is complete. User may choose to open in the file manager.
+ auto button =
+ QtCommon::Frontend::Question(tr("Download Complete"),
+ tr("Successfully downloaded %1. Would you like to open it?")
+ .arg(QString::fromStdString(m_asset.filename)),
+ QtCommon::Frontend::Yes | QtCommon::Frontend::No);
+
+ if (button == QtCommon::Frontend::Yes) {
+ QDesktopServices::openUrl(QUrl::fromLocalFile(filename));
+ }
+}