Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/linux-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ on:
pull_request:
workflow_dispatch:

permissions:
contents: write

jobs:
linux:
runs-on: ubuntu-22.04
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/windows-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ on:
pull_request:
workflow_dispatch:

permissions:
contents: write

jobs:
windows:
runs-on: windows-2022
Expand Down
2 changes: 2 additions & 0 deletions Source/OpenEPT.pro
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ SOURCES += \
Processing/waveform.cpp \
Utility/log.cpp \
Windows/About/aboutwnd.cpp \
Windows/About/updatechecker.cpp \
Windows/BatteryParams/batterycyclewnd.cpp \
Windows/BatteryParams/batteryfitintervalwnd.cpp \
Windows/BatteryParams/batteryfitqualitywnd.cpp \
Expand Down Expand Up @@ -98,6 +99,7 @@ HEADERS += \
Processing/waveform.h \
Utility/log.h \
Windows/About/aboutwnd.h \
Windows/About/updatechecker.h \
Windows/BatteryParams/batterycyclewnd.h \
Windows/BatteryParams/batteryfitintervalwnd.h \
Windows/BatteryParams/batteryfitqualitywnd.h \
Expand Down
11 changes: 11 additions & 0 deletions Source/Windows/About/aboutwnd.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "aboutwnd.h"
#include "updatechecker.h"

#include <QDialogButtonBox>
#include <QFont>
Expand Down Expand Up @@ -70,9 +71,19 @@ AboutWnd::AboutWnd(QWidget *parent) :

QLabel *copyrightLabel = new QLabel("Copyright © OpenEPT Team", this);

updateChecker = new UpdateChecker(this);

QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);
buttonBox->button(QDialogButtonBox::Close)->setFixedSize(90, 30);

QPushButton *checkUpdatesButton = buttonBox->addButton("Check for Updates", QDialogButtonBox::ActionRole);
checkUpdatesButton->setFixedHeight(30);

connect(checkUpdatesButton, &QPushButton::clicked, this, [this]()
{
updateChecker->checkForUpdates(false);
});

connect(buttonBox, &QDialogButtonBox::rejected, this, &AboutWnd::reject);

mainVL->addLayout(headerHB);
Expand Down
5 changes: 5 additions & 0 deletions Source/Windows/About/aboutwnd.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@
#define APP_VERSION "0.0.0-dev"
#endif

class UpdateChecker;

class AboutWnd : public QDialog
{
Q_OBJECT

public:
explicit AboutWnd(QWidget *parent = nullptr);

private:
UpdateChecker *updateChecker;
};

#endif // ABOUTWND_H
159 changes: 159 additions & 0 deletions Source/Windows/About/updatechecker.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#include "updatechecker.h"

#include <QNetworkReply>
#include <QNetworkRequest>
#include <QUrl>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QMessageBox>
#include <QPushButton>
#include <QDesktopServices>
#include <QStringList>

UpdateChecker::UpdateChecker(QWidget *aParent) :
QObject(aParent)
{
parentWidget = aParent;
silentMode = false;

connect(&networkManager, &QNetworkAccessManager::finished,
this, &UpdateChecker::onManifestFinished);
}

void UpdateChecker::checkForUpdates(bool silent)
{
silentMode = silent;

QNetworkRequest request((QUrl(UPDATE_MANIFEST_URL)));
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute,
QNetworkRequest::NoLessSafeRedirectPolicy);
request.setHeader(QNetworkRequest::UserAgentHeader, QString("OpenEPT/%1").arg(APP_VERSION));

networkManager.get(request);
}

QString UpdateChecker::normalizeVersion(const QString &version)
{
QString value = version.trimmed();

if(value.startsWith('v') || value.startsWith('V')) value.remove(0, 1);

int cut = value.indexOf('-');
if(cut >= 0) value = value.left(cut);

cut = value.indexOf('+');
if(cut >= 0) value = value.left(cut);

return value;
}

int UpdateChecker::compareVersions(const QString &a, const QString &b)
{
QStringList partsA = normalizeVersion(a).split('.');
QStringList partsB = normalizeVersion(b).split('.');
int count = qMax(partsA.size(), partsB.size());

for(int i = 0; i < count; i++)
{
int valueA = i < partsA.size() ? partsA[i].toInt() : 0;
int valueB = i < partsB.size() ? partsB[i].toInt() : 0;

if(valueA != valueB) return valueA < valueB ? -1 : 1;
}

return 0;
}

void UpdateChecker::onManifestFinished(QNetworkReply *reply)
{
reply->deleteLater();

if(reply->error() != QNetworkReply::NoError)
{
if(!silentMode)
QMessageBox::warning(parentWidget, "Check for Updates",
"Could not reach the update server.\n\n" + reply->errorString());
return;
}

QJsonParseError parseError;
QJsonDocument document = QJsonDocument::fromJson(reply->readAll(), &parseError);

if(parseError.error != QJsonParseError::NoError || !document.isObject())
{
if(!silentMode)
QMessageBox::warning(parentWidget, "Check for Updates",
"The update information could not be read.");
return;
}

QJsonArray components = document.object().value("components").toArray();

bool updateAvailable = false;
QString selfUrl;
QString firstUrl;
QString listText;

for(const QJsonValue &value : components)
{
QJsonObject component = value.toObject();
QString name = component.value("name").toString();
QString latest = component.value("latest").toString();
QString url = component.value("url").toString();
bool isSelf = component.value("self").toBool();

if(name.isEmpty() || latest.isEmpty()) continue;

QString status;

if(isSelf)
{
selfUrl = url;

if(compareVersions(APP_VERSION, latest) < 0)
{
updateAvailable = true;
status = " (update available, you have " + QString(APP_VERSION) + ")";
}
else
{
status = " (up to date)";
}
}

listText += " " + name + " " + latest + status + "\n";

if(firstUrl.isEmpty() && !url.isEmpty()) firstUrl = url;
}

if(listText.isEmpty())
{
if(!silentMode)
QMessageBox::information(parentWidget, "Check for Updates",
"No update information is available.");
return;
}

if(silentMode && !updateAvailable) return;

QString downloadUrl = !selfUrl.isEmpty() ? selfUrl : firstUrl;
QString header = updateAvailable
? "A newer OpenEPT version is available."
: "You are running the latest OpenEPT version.";

QMessageBox box(parentWidget);
box.setWindowTitle("Check for Updates");
box.setIcon(QMessageBox::Information);
box.setText(header);
box.setInformativeText("Latest available versions:\n\n" + listText);

QPushButton *downloadButton = box.addButton("Go to Downloads", QMessageBox::AcceptRole);
box.addButton(QMessageBox::Close);
if(updateAvailable) box.setDefaultButton(downloadButton);

box.exec();

if(box.clickedButton() == downloadButton && !downloadUrl.isEmpty())
QDesktopServices::openUrl(QUrl(downloadUrl));
}
38 changes: 38 additions & 0 deletions Source/Windows/About/updatechecker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#ifndef UPDATECHECKER_H
#define UPDATECHECKER_H

#include <QObject>
#include <QNetworkAccessManager>

QT_FORWARD_DECLARE_CLASS(QNetworkReply)

#ifndef APP_VERSION
#define APP_VERSION "0.0.0-dev"
#endif

#ifndef UPDATE_MANIFEST_URL
#define UPDATE_MANIFEST_URL "https://www.openept.net/downloads/manifest.json"
#endif

class UpdateChecker : public QObject
{
Q_OBJECT

public:
explicit UpdateChecker(QWidget *aParent = nullptr);

void checkForUpdates(bool silent);

private slots:
void onManifestFinished(QNetworkReply *reply);

private:
QNetworkAccessManager networkManager;
QWidget *parentWidget;
bool silentMode;

static QString normalizeVersion(const QString &version);
static int compareVersions(const QString &a, const QString &b);
};

#endif // UPDATECHECKER_H
Loading
Loading