wiRedPanda
Logic Circuit Simulator
Loading...
Searching...
No Matches
UpdateController.cpp
Go to the documentation of this file.
1// Copyright 2015 - 2026, GIBIS-UNIFESP and the wiRedPanda contributors
2// SPDX-License-Identifier: GPL-3.0-or-later
3
5
6#include <QCheckBox>
7#include <QDebug>
8#include <QDesktopServices>
9#include <QDialog>
10#include <QDialogButtonBox>
11#include <QDir>
12#include <QFile>
13#include <QLabel>
14#include <QMessageBox>
15#include <QNetworkAccessManager>
16#include <QNetworkReply>
17#include <QNetworkRequest>
18#include <QProgressDialog>
19#include <QPushButton>
20#include <QSslError>
21#include <QStandardPaths>
22#include <QVBoxLayout>
23
25#include "App/Core/Settings.h"
27#include "App/Versions.h"
28
30 : QObject(parent)
31 , m_parent(parent)
32{
33}
34
36{
38 return;
39 }
40
41 auto *updateChecker = new UpdateChecker(this);
42 connect(updateChecker, &UpdateChecker::updateAvailable, this, &UpdateController::showUpdateDialog);
43 updateChecker->checkForUpdates();
44}
45
46void UpdateController::showUpdateDialog(const QString &latestVersion, const QUrl &downloadUrl, const QUrl &releaseUrl)
47{
48 QDialog dialog(m_parent);
49 dialog.setWindowTitle(tr("Update Available"));
50 dialog.setWindowModality(Qt::WindowModal);
51
52 auto *layout = new QVBoxLayout(&dialog);
53
54 const bool hasDirectDownload = downloadUrl.isValid() && !downloadUrl.isEmpty();
55 auto *label = new QLabel(
56 (hasDirectDownload
57 ? tr("<b>wiRedPanda %1 is available.</b><br><br>"
58 "You are currently running version %2.<br>"
59 "Click <b>Download</b> to save the new version to your computer.")
60 : tr("<b>wiRedPanda %1 is available.</b><br><br>"
61 "You are currently running version %2.<br>"
62 "Visit the release page to download the new version."))
63 .arg(latestVersion, APP_VERSION),
64 &dialog);
65 label->setTextFormat(Qt::RichText);
66 label->setWordWrap(true);
67 layout->addWidget(label);
68
69 auto *skipCheckBox = new QCheckBox(tr("Don't notify me about this version again"), &dialog);
70 layout->addWidget(skipCheckBox);
71
72 auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, &dialog);
73 auto *downloadButton = buttonBox->addButton(tr("Download"), QDialogButtonBox::AcceptRole);
74 connect(downloadButton, &QPushButton::clicked, &dialog, [&dialog] { dialog.accept(); });
75 connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
76 layout->addWidget(buttonBox);
77
78 const bool accepted = dialog.exec() == QDialog::Accepted;
79
80 if (skipCheckBox->isChecked()) {
82 }
83
84 // The check date is recorded by UpdateChecker::onReplyFinished — the
85 // single writer — so no dialog outcome needs to touch it here.
86 if (accepted) {
87 if (hasDirectDownload) {
88 downloadUpdate(latestVersion, downloadUrl);
89 } else {
90 QDesktopServices::openUrl(releaseUrl);
91 }
92 }
93}
94
95void UpdateController::downloadUpdate(const QString &latestVersion, const QUrl &url)
96{
97 const QString fileName = url.fileName();
98 const QString savePath = QDir(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)).filePath(fileName);
99
100 auto *progress = new QProgressDialog(tr("Downloading wiRedPanda %1…").arg(latestVersion), tr("Cancel"), 0, 100, m_parent);
101 progress->setWindowTitle(tr("Downloading Update"));
102 progress->setWindowModality(Qt::WindowModal);
103 progress->setMinimumDuration(0);
104 progress->setValue(0);
105
106 auto *network = new QNetworkAccessManager(this);
107 // QNetworkAccessManager::sslErrors doesn't exist at all when Qt is built with QT_NO_SSL --
108 // the case for every Qt-for-WebAssembly build, since WASM has no native TLS backend and
109 // routes network access through the browser instead (any TLS failure there surfaces as a
110 // generic QNetworkReply error, not through this signal).
111#ifndef QT_NO_SSL
112 connect(network, &QNetworkAccessManager::sslErrors, this, [](QNetworkReply *reply, const QList<QSslError> &errors) {
113 qWarning() << "MainWindow::downloadUpdate: SSL errors, aborting reply:" << errors;
114 reply->abort();
115 });
116#endif
117
118 QNetworkRequest request(url);
119 request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
120 request.setTransferTimeout(60000);
121 QNetworkReply *reply = network->get(request);
122
123 connect(reply, &QNetworkReply::downloadProgress, progress, [progress](qint64 received, qint64 total) {
124 if (total > 0) {
125 progress->setValue(static_cast<int>(received * 100 / total));
126 }
127 });
128
129 connect(progress, &QProgressDialog::canceled, reply, &QNetworkReply::abort);
130
131 connect(reply, &QNetworkReply::finished, this, [this, reply, progress, savePath] {
132 progress->close();
133 progress->deleteLater();
134
135 if (reply->error() != QNetworkReply::NoError) {
136 if (reply->error() != QNetworkReply::OperationCanceledError) {
137 QMessageBox::warning(m_parent, tr("Download Failed"), tr("Could not download the update:\n%1").arg(reply->errorString()));
138 }
139 reply->deleteLater();
140 return;
141 }
142
143 QFile file(savePath);
144 if (!file.open(QIODevice::WriteOnly)) {
145 QMessageBox::warning(m_parent, tr("Download Failed"), tr("Could not save the file:\n%1").arg(savePath));
146 reply->deleteLater();
147 return;
148 }
149 const QByteArray payload = reply->readAll();
150 if (file.write(payload) != payload.size()) {
151 QMessageBox::warning(m_parent, tr("Download Failed"), tr("Could not write the file:\n%1").arg(savePath));
152 reply->deleteLater();
153 return;
154 }
155 file.close();
156 reply->deleteLater();
157
158 QMessageBox::information(m_parent, tr("Download Complete"),
159 tr("wiRedPanda has been downloaded to:\n%1").arg(savePath));
160 });
161}
Custom QApplication subclass with exception handling and main-window access.
Typed wrappers around QSettings for all application preferences.
Checks the GitHub Releases API for newer versions of wiRedPanda.
UpdateController: drives the application's check-for-updates workflow.
File-format version constants and application version accessor.
static bool interactiveMode
Definition Application.h:73
static void setUpdateCheckSkippedVersion(const QString &version)
Definition Settings.cpp:225
Asynchronously queries the GitHub Releases API and emits a signal when a newer version is available.
void updateAvailable(const QString &latestVersion, const QUrl &downloadUrl, const QUrl &releaseUrl)
Emitted when a newer release is available and has not been suppressed.
void checkForUpdates()
Starts an asynchronous version check; shows the update dialog if one is available.
UpdateController(QWidget *parent)