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()) {
|
2024-02-05 16:54:35 +00:00
|
|
|
this->error(reply->errorString(), 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.
|
2024-02-05 16:54:35 +00:00
|
|
|
// Objects in the releases page data are sorted newest to oldest.
|
2024-01-24 16:56:04 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-05 16:54:35 +00:00
|
|
|
void UpdatePromoter::error(const QString &err, const QDateTime retryAfter) {
|
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-02-05 16:54:35 +00:00
|
|
|
|
|
|
|
// If a "retry after" date/time is provided, disable the Retry button until then.
|
|
|
|
// Otherwise users are allowed to retry after an error.
|
|
|
|
auto timeUntil = QDateTime::currentDateTime().msecsTo(retryAfter);
|
|
|
|
if (timeUntil > 0) {
|
|
|
|
this->button_Retry->setEnabled(false);
|
|
|
|
QTimer::singleShot(timeUntil, Qt::VeryCoarseTimer, [this]() {
|
|
|
|
this->button_Retry->setEnabled(true);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
this->button_Retry->setEnabled(true);
|
|
|
|
}
|
2024-01-21 07:00:28 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|