8#include <QDesktopServices>
10#include <QDialogButtonBox>
15#include <QNetworkAccessManager>
16#include <QNetworkReply>
17#include <QNetworkRequest>
19#include <QProgressDialog>
22#include <QStandardPaths>
44 updateChecker->checkForUpdates();
47void UpdateController::showUpdateDialog(
const QString &latestVersion,
const QUrl &downloadUrl,
const QUrl &releaseUrl)
49 QDialog dialog(m_parent);
50 dialog.setWindowTitle(tr(
"Update Available"));
51 dialog.setWindowModality(Qt::WindowModal);
53 auto *layout =
new QVBoxLayout(&dialog);
55 const bool hasDirectDownload = downloadUrl.isValid() && !downloadUrl.isEmpty();
56 auto *label =
new QLabel(
58 ? tr(
"<b>wiRedPanda %1 is available.</b><br><br>"
59 "You are currently running version %2.<br>"
60 "Click <b>Download</b> to save the new version to your computer.")
61 : tr(
"<b>wiRedPanda %1 is available.</b><br><br>"
62 "You are currently running version %2.<br>"
63 "Visit the release page to download the new version."))
64 .arg(latestVersion, APP_VERSION),
66 label->setTextFormat(Qt::RichText);
67 label->setWordWrap(
true);
68 layout->addWidget(label);
70 auto *skipCheckBox =
new QCheckBox(tr(
"Don't notify me about this version again"), &dialog);
71 layout->addWidget(skipCheckBox);
73 auto *buttonBox =
new QDialogButtonBox(QDialogButtonBox::Close, &dialog);
74 auto *downloadButton = buttonBox->addButton(tr(
"Download"), QDialogButtonBox::AcceptRole);
75 connect(downloadButton, &QPushButton::clicked, &dialog, [&dialog] { dialog.accept(); });
76 connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
77 layout->addWidget(buttonBox);
79 const bool accepted = dialog.exec() == QDialog::Accepted;
81 if (skipCheckBox->isChecked()) {
88 if (hasDirectDownload) {
89 downloadUpdate(latestVersion, downloadUrl);
91 QDesktopServices::openUrl(releaseUrl);
96void UpdateController::downloadUpdate(
const QString &latestVersion,
const QUrl &url)
98 const QString fileName = url.fileName();
99 const QString downloadDir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
103 QDir().mkpath(downloadDir);
104 const QString savePath = QDir(downloadDir).filePath(fileName);
109 if (m_progressDialog) {
110 m_progressDialog->raise();
111 m_progressDialog->activateWindow();
115 auto *progress =
new QProgressDialog(tr(
"Downloading wiRedPanda %1…").arg(latestVersion), tr(
"Cancel"), 0, 100, m_parent);
116 progress->setWindowTitle(tr(
"Downloading Update"));
122 progress->setMinimumDuration(0);
123 progress->setValue(0);
125 auto *network =
new QNetworkAccessManager(
this);
131 connect(network, &QNetworkAccessManager::sslErrors,
this, [](QNetworkReply *reply,
const QList<QSslError> &errors) {
132 qWarning() <<
"MainWindow::downloadUpdate: SSL errors, aborting reply:" << errors;
137 QNetworkRequest request(url);
138 request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
139 request.setTransferTimeout(60000);
140 QNetworkReply *reply = network->get(request);
147 const QPointer<QProgressDialog> progressGuard(progress);
148 m_progressDialog = progress;
149 const QMetaObject::Connection progressConnection =
150 connect(reply, &QNetworkReply::downloadProgress, progress, [progressGuard](qint64 received, qint64 total) {
151 if (total > 0 && progressGuard) {
152 progressGuard->setValue(
static_cast<int>(received * 100 / total));
156 connect(progress, &QProgressDialog::canceled, reply, &QNetworkReply::abort);
158 connect(reply, &QNetworkReply::finished,
this, [
this, reply, progressGuard, progressConnection, savePath] {
160 disconnect(progressConnection);
162 progressGuard->close();
163 progressGuard->deleteLater();
166 if (reply->error() != QNetworkReply::NoError) {
167 if (reply->error() != QNetworkReply::OperationCanceledError) {
168 QMessageBox::warning(m_parent, tr(
"Download Failed"), tr(
"Could not download the update:\n%1").arg(reply->errorString()));
170 reply->deleteLater();
174 QFile file(savePath);
175 if (!file.open(QIODevice::WriteOnly)) {
176 QMessageBox::warning(m_parent, tr(
"Download Failed"), tr(
"Could not save the file:\n%1").arg(savePath));
177 reply->deleteLater();
180 const QByteArray payload = reply->readAll();
181 if (file.write(payload) != payload.size()) {
182 QMessageBox::warning(m_parent, tr(
"Download Failed"), tr(
"Could not write the file:\n%1").arg(savePath));
183 reply->deleteLater();
187 reply->deleteLater();
189 QMessageBox::information(m_parent, tr(
"Download Complete"),
190 tr(
"wiRedPanda has been downloaded to:\n%1").arg(savePath));
Custom QApplication subclass with exception handling and main-window access.
Typed wrappers around QSettings for all application preferences.
Checks the wiRedPanda site's published release data for newer versions.
UpdateController: drives the application's check-for-updates workflow.
File-format version constants and application version accessor.
static bool interactiveMode
static void setUpdateCheckSkippedVersion(const QString &version)
Asynchronously queries the site's release data 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)