8#include <QDesktopServices>
10#include <QDialogButtonBox>
15#include <QNetworkAccessManager>
16#include <QNetworkReply>
17#include <QNetworkRequest>
18#include <QProgressDialog>
21#include <QStandardPaths>
43 updateChecker->checkForUpdates();
46void UpdateController::showUpdateDialog(
const QString &latestVersion,
const QUrl &downloadUrl,
const QUrl &releaseUrl)
48 QDialog dialog(m_parent);
49 dialog.setWindowTitle(tr(
"Update Available"));
50 dialog.setWindowModality(Qt::WindowModal);
52 auto *layout =
new QVBoxLayout(&dialog);
54 const bool hasDirectDownload = downloadUrl.isValid() && !downloadUrl.isEmpty();
55 auto *label =
new QLabel(
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),
65 label->setTextFormat(Qt::RichText);
66 label->setWordWrap(
true);
67 layout->addWidget(label);
69 auto *skipCheckBox =
new QCheckBox(tr(
"Don't notify me about this version again"), &dialog);
70 layout->addWidget(skipCheckBox);
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);
78 const bool accepted = dialog.exec() == QDialog::Accepted;
80 if (skipCheckBox->isChecked()) {
87 if (hasDirectDownload) {
88 downloadUpdate(latestVersion, downloadUrl);
90 QDesktopServices::openUrl(releaseUrl);
95void UpdateController::downloadUpdate(
const QString &latestVersion,
const QUrl &url)
97 const QString fileName = url.fileName();
98 const QString savePath = QDir(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation)).filePath(fileName);
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);
106 auto *network =
new QNetworkAccessManager(
this);
112 connect(network, &QNetworkAccessManager::sslErrors,
this, [](QNetworkReply *reply,
const QList<QSslError> &errors) {
113 qWarning() <<
"MainWindow::downloadUpdate: SSL errors, aborting reply:" << errors;
118 QNetworkRequest request(url);
119 request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
120 request.setTransferTimeout(60000);
121 QNetworkReply *reply = network->get(request);
123 connect(reply, &QNetworkReply::downloadProgress, progress, [progress](qint64 received, qint64 total) {
125 progress->setValue(
static_cast<int>(received * 100 / total));
129 connect(progress, &QProgressDialog::canceled, reply, &QNetworkReply::abort);
131 connect(reply, &QNetworkReply::finished,
this, [
this, reply, progress, savePath] {
133 progress->deleteLater();
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()));
139 reply->deleteLater();
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();
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();
156 reply->deleteLater();
158 QMessageBox::information(m_parent, tr(
"Download Complete"),
159 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 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
static void setUpdateCheckSkippedVersion(const QString &version)
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)