porymap/src/ui/updatepromoter.cpp

198 lines
7.5 KiB
C++
Raw Normal View History

2024-01-21 07:00:28 +00:00
#include "updatepromoter.h"
#include "ui_updatepromoter.h"
#include "log.h"
#include "config.h"
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QDesktopServices>
2024-01-24 16:56:04 +00:00
#include <QTimer>
2024-01-21 07:00:28 +00:00
2024-01-24 16:56:04 +00:00
UpdatePromoter::UpdatePromoter(QWidget *parent, NetworkAccessManager *manager)
2024-01-21 07:00:28 +00:00
: QDialog(parent),
ui(new Ui::UpdatePromoter),
manager(manager)
{
ui->setupUi(this);
// Set up "Do not alert me" check box
this->updatePreferences();
2024-01-24 16:56:04 +00:00
ui->checkBox_StopAlerts->setVisible(false);
2024-01-21 07:00:28 +00:00
connect(ui->checkBox_StopAlerts, &QCheckBox::stateChanged, [this](int state) {
bool enable = (state != Qt::Checked);
porymapConfig.setCheckForUpdates(enable);
emit this->changedPreferences();
});
// Set up button box
2024-01-24 16:56:04 +00:00
this->button_Retry = ui->buttonBox->button(QDialogButtonBox::Retry);
2024-01-21 07:00:28 +00:00
this->button_Downloads = ui->buttonBox->addButton("Go to Downloads...", QDialogButtonBox::ActionRole);
2024-01-24 16:56:04 +00:00
ui->buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
2024-01-21 07:00:28 +00:00
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &UpdatePromoter::dialogButtonClicked);
this->resetDialog();
}
void UpdatePromoter::resetDialog() {
this->button_Downloads->setEnabled(false);
2024-01-24 16:56:04 +00:00
2024-01-21 07:00:28 +00:00
ui->text_Changelog->setVisible(false);
ui->label_Warning->setVisible(false);
2024-01-24 16:56:04 +00:00
ui->label_Status->setText("");
2024-01-21 07:00:28 +00:00
this->changelog = QString();
2024-01-24 16:56:04 +00:00
this->downloadUrl = QString();
this->breakingChanges = false;
this->foundReleases = false;
this->visitedUrls.clear();
2024-01-21 07:00:28 +00:00
}
void UpdatePromoter::checkForUpdates() {
2024-01-24 16:56:04 +00:00
// If the Retry button is disabled, making requests is disabled
if (!this->button_Retry->isEnabled())
2024-01-21 07:00:28 +00:00
return;
2024-01-24 16:56:04 +00:00
2024-01-21 07:00:28 +00:00
this->resetDialog();
2024-01-24 16:56:04 +00:00
this->button_Retry->setEnabled(false);
ui->label_Status->setText("Checking for updates...");
2024-01-21 07:00:28 +00:00
2024-01-24 16:56:04 +00:00
// We could use the URL ".../releases/latest" to retrieve less data, but this would run into problems if the
2024-01-21 07:00:28 +00:00
// most recent item on the releases page is not actually a new release (like the static windows build).
// By getting all releases we can also present a multi-version changelog of all changes since the host release.
2024-01-24 16:56:04 +00:00
static const QUrl url("https://api.github.com/repos/huderlem/porymap/releases");
this->get(url);
}
void UpdatePromoter::get(const QUrl &url) {
this->visitedUrls.insert(url);
auto reply = this->manager->get(url);
connect(reply, &NetworkReplyData::finished, [this, reply] () {
if (!reply->errorString().isEmpty()) {
this->error(reply->errorString());
this->disableRequestsUntil(reply->retryAfter());
2024-01-21 07:00:28 +00:00
} else {
2024-01-24 16:56:04 +00:00
this->processWebpage(QJsonDocument::fromJson(reply->body()), reply->nextUrl());
2024-01-21 07:00:28 +00:00
}
2024-01-24 16:56:04 +00:00
reply->deleteLater();
2024-01-21 07:00:28 +00:00
});
}
// Read all the items on the releases page, ignoring entries without a version identifier tag.
// Objects in the releases page data are sorted newest to oldest.
2024-01-24 16:56:04 +00:00
// Returns true when finished, returns false to request processing for the next page.
void UpdatePromoter::processWebpage(const QJsonDocument &data, const QUrl &nextUrl) {
2024-01-21 07:00:28 +00:00
const QJsonArray releases = data.array();
2024-01-24 16:56:04 +00:00
int i;
for (i = 0; i < releases.size(); i++) {
2024-01-21 07:00:28 +00:00
auto release = releases.at(i).toObject();
// Convert tag string to version numbers
const QString tagName = release.value("tag_name").toString();
const QStringList tag = tagName.split(".");
if (tag.length() != 3) continue;
bool ok;
int major = tag.at(0).toInt(&ok);
if (!ok) continue;
int minor = tag.at(1).toInt(&ok);
if (!ok) continue;
int patch = tag.at(2).toInt(&ok);
if (!ok) continue;
// We've found a valid release tag. If the version number is not newer than the host version then we can stop looking at releases.
2024-01-24 16:56:04 +00:00
this->foundReleases = true;
2024-01-21 23:41:23 +00:00
if (!this->isNewerVersion(major, minor, patch))
2024-01-21 07:00:28 +00:00
break;
const QString description = release.value("body").toString();
2024-01-24 16:56:04 +00:00
if (description.isEmpty()) {
2024-01-21 07:00:28 +00:00
// If the release was published very recently it won't have a description yet, in which case don't tell the user about it yet.
continue;
}
2024-01-24 16:56:04 +00:00
if (this->downloadUrl.isEmpty()) {
2024-01-21 07:00:28 +00:00
// This is the first (newest) release we've found. Record its URL for download.
2024-01-24 16:56:04 +00:00
const QUrl url = QUrl(release.value("html_url").toString());
if (url.isEmpty()) {
// If there's no URL, something has gone wrong and we should skip this release.
continue;
}
this->downloadUrl = url;
this->breakingChanges = (major > PORYMAP_VERSION_MAJOR);
2024-01-21 07:00:28 +00:00
}
// Record the changelog of this release so we can show all changes since the host release.
this->changelog.append(QString("## %1\n%2\n\n").arg(tagName).arg(description));
}
2024-01-24 16:56:04 +00:00
// If we read the entire page then we didn't find a release as old as the host version.
// Keep looking on the second page, there might still be new releases there.
if (i == releases.size() && !nextUrl.isEmpty() && !this->visitedUrls.contains(nextUrl)) {
this->get(nextUrl);
return;
}
if (!this->foundReleases) {
2024-01-21 07:00:28 +00:00
// We retrieved the webpage but didn't successfully parse any releases.
2024-01-24 16:56:04 +00:00
this->error("Error parsing releases webpage");
2024-01-21 07:00:28 +00:00
return;
}
2024-01-24 16:56:04 +00:00
// Populate dialog with result
bool updateAvailable = !this->changelog.isEmpty();
ui->label_Status->setText(updateAvailable ? "A new version of Porymap is available!"
: "Your version of Porymap is up to date!");
ui->label_Warning->setVisible(this->breakingChanges);
ui->text_Changelog->setMarkdown(this->changelog);
ui->text_Changelog->setVisible(updateAvailable);
this->button_Downloads->setEnabled(!this->downloadUrl.isEmpty());
this->button_Retry->setEnabled(true);
// Alert the user if there's a new update available and the dialog wasn't already open.
// Show the window, but also show the option to turn off automatic alerts in the future.
if (updateAvailable && !this->isVisible()) {
ui->checkBox_StopAlerts->setVisible(true);
2024-01-21 07:00:28 +00:00
this->show();
2024-01-24 16:56:04 +00:00
}
}
void UpdatePromoter::disableRequestsUntil(const QDateTime time) {
this->button_Retry->setEnabled(false);
auto timeUntil = QDateTime::currentDateTime().msecsTo(time);
if (timeUntil < 0) timeUntil = 0;
QTimer::singleShot(timeUntil, Qt::VeryCoarseTimer, [this]() {
this->button_Retry->setEnabled(true);
});
2024-01-21 07:00:28 +00:00
}
2024-01-24 16:56:04 +00:00
void UpdatePromoter::error(const QString &err) {
2024-01-21 07:00:28 +00:00
const QString message = QString("Failed to check for version update: %1").arg(err);
2024-01-24 16:56:04 +00:00
ui->label_Status->setText(message);
if (!this->isVisible())
2024-01-21 07:00:28 +00:00
logWarn(message);
}
2024-01-21 23:41:23 +00:00
bool UpdatePromoter::isNewerVersion(int major, int minor, int patch) {
if (major != PORYMAP_VERSION_MAJOR)
return major > PORYMAP_VERSION_MAJOR;
if (minor != PORYMAP_VERSION_MINOR)
return minor > PORYMAP_VERSION_MINOR;
return patch > PORYMAP_VERSION_PATCH;
}
2024-01-21 07:00:28 +00:00
void UpdatePromoter::updatePreferences() {
const QSignalBlocker blocker(ui->checkBox_StopAlerts);
2024-01-24 16:56:04 +00:00
ui->checkBox_StopAlerts->setChecked(!porymapConfig.getCheckForUpdates());
2024-01-21 07:00:28 +00:00
}
void UpdatePromoter::dialogButtonClicked(QAbstractButton *button) {
2024-01-24 16:56:04 +00:00
if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::RejectRole) {
2024-01-21 07:00:28 +00:00
this->close();
2024-01-24 16:56:04 +00:00
} else if (button == this->button_Retry) {
2024-01-21 07:00:28 +00:00
this->checkForUpdates();
2024-01-24 16:56:04 +00:00
} else if (button == this->button_Downloads) {
QDesktopServices::openUrl(this->downloadUrl);
2024-01-21 07:00:28 +00:00
}
}