MainWindow: Help -> Check for updates

This commit is contained in:
tobtoht 2023-02-01 15:40:12 +01:00
parent a748eaf494
commit bc63a9d4f1
No known key found for this signature in database
GPG key ID: E45B10DD027D2472
8 changed files with 331 additions and 156 deletions

View file

@ -54,6 +54,8 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
m_splashDialog = new SplashDialog(this); m_splashDialog = new SplashDialog(this);
m_accountSwitcherDialog = new AccountSwitcherDialog(m_ctx, this); m_accountSwitcherDialog = new AccountSwitcherDialog(m_ctx, this);
m_updater = QSharedPointer<Updater>(new Updater(this));
this->restoreGeo(); this->restoreGeo();
this->initStatusBar(); this->initStatusBar();
@ -67,7 +69,7 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
connect(websocketNotifier(), &WebsocketNotifier::BountyReceived, ui->bountiesWidget->model(), &BountiesModel::updateBounties); connect(websocketNotifier(), &WebsocketNotifier::BountyReceived, ui->bountiesWidget->model(), &BountiesModel::updateBounties);
connect(websocketNotifier(), &WebsocketNotifier::RedditReceived, ui->redditWidget->model(), &RedditModel::updatePosts); connect(websocketNotifier(), &WebsocketNotifier::RedditReceived, ui->redditWidget->model(), &RedditModel::updatePosts);
connect(websocketNotifier(), &WebsocketNotifier::RevuoReceived, ui->revuoWidget, &RevuoWidget::updateItems); connect(websocketNotifier(), &WebsocketNotifier::RevuoReceived, ui->revuoWidget, &RevuoWidget::updateItems);
connect(websocketNotifier(), &WebsocketNotifier::UpdatesReceived, this, &MainWindow::onUpdatesAvailable); connect(websocketNotifier(), &WebsocketNotifier::UpdatesReceived, m_updater.data(), &Updater::wsUpdatesReceived);
#ifdef HAS_XMRIG #ifdef HAS_XMRIG
connect(websocketNotifier(), &WebsocketNotifier::XMRigDownloadsReceived, m_xmrig, &XMRigWidget::onDownloads); connect(websocketNotifier(), &WebsocketNotifier::XMRigDownloadsReceived, m_xmrig, &XMRigWidget::onDownloads);
#endif #endif
@ -80,6 +82,8 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
connect(torManager(), &TorManager::connectionStateChanged, this, &MainWindow::onTorConnectionStateChanged); connect(torManager(), &TorManager::connectionStateChanged, this, &MainWindow::onTorConnectionStateChanged);
this->onTorConnectionStateChanged(torManager()->torConnected); this->onTorConnectionStateChanged(torManager()->torConnected);
connect(m_updater.data(), &Updater::updateAvailable, this, &MainWindow::showUpdateNotification);
ColorScheme::updateFromWidget(this); ColorScheme::updateFromWidget(this);
QTimer::singleShot(1, [this]{this->updateWidgetIcons();}); QTimer::singleShot(1, [this]{this->updateWidgetIcons();});
@ -350,6 +354,11 @@ void MainWindow::initMenu() {
// [Help] // [Help]
connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::menuAboutClicked); connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::menuAboutClicked);
#if defined(CHECK_UPDATES)
connect(ui->actionCheckForUpdates, &QAction::triggered, this, &MainWindow::showUpdateDialog);
#else
ui->actionCheckForUpdates->setVisible(false);
#endif
connect(ui->actionOfficialWebsite, &QAction::triggered, [this](){Utils::externalLinkWarning(this, "https://featherwallet.org");}); connect(ui->actionOfficialWebsite, &QAction::triggered, [this](){Utils::externalLinkWarning(this, "https://featherwallet.org");});
connect(ui->actionDonate_to_Feather, &QAction::triggered, this, &MainWindow::donateButtonClicked); connect(ui->actionDonate_to_Feather, &QAction::triggered, this, &MainWindow::donateButtonClicked);
connect(ui->actionDocumentation, &QAction::triggered, this, &MainWindow::onShowDocumentation); connect(ui->actionDocumentation, &QAction::triggered, this, &MainWindow::onShowDocumentation);
@ -1341,9 +1350,8 @@ void MainWindow::onTorConnectionStateChanged(bool connected) {
m_statusBtnTor->setIcon(icons()->icon("tor_logo_disabled.png")); m_statusBtnTor->setIcon(icons()->icon("tor_logo_disabled.png"));
} }
void MainWindow::onCheckUpdatesComplete(const QString &version, const QString &binaryFilename, void MainWindow::showUpdateNotification() {
const QString &hash, const QString &signer) { QString versionDisplay{m_updater->version};
QString versionDisplay{version};
versionDisplay.replace("beta", "Beta"); versionDisplay.replace("beta", "Beta");
QString updateText = QString("Update to Feather %1 is available").arg(versionDisplay); QString updateText = QString("Update to Feather %1 is available").arg(versionDisplay);
m_statusUpdateAvailable->setText(updateText); m_statusUpdateAvailable->setText(updateText);
@ -1351,82 +1359,15 @@ void MainWindow::onCheckUpdatesComplete(const QString &version, const QString &b
m_statusUpdateAvailable->show(); m_statusUpdateAvailable->show();
m_statusUpdateAvailable->disconnect(); m_statusUpdateAvailable->disconnect();
connect(m_statusUpdateAvailable, &StatusBarButton::clicked, [this, version, binaryFilename, hash, signer] { connect(m_statusUpdateAvailable, &StatusBarButton::clicked, this, &MainWindow::showUpdateDialog);
this->onShowUpdateCheck(version, binaryFilename, hash, signer);
});
} }
void MainWindow::onShowUpdateCheck(const QString &version, const QString &binaryFilename, void MainWindow::showUpdateDialog() {
const QString &hash, const QString &signer) { UpdateDialog updateDialog{this, m_updater};
QString platformTag = this->getPlatformTag();
QString downloadUrl = QString("https://featherwallet.org/files/releases/%1/%2").arg(platformTag, binaryFilename);
UpdateDialog updateDialog{this, version, downloadUrl, hash, signer, platformTag};
connect(&updateDialog, &UpdateDialog::restartWallet, m_windowManager, &WindowManager::restartApplication); connect(&updateDialog, &UpdateDialog::restartWallet, m_windowManager, &WindowManager::restartApplication);
updateDialog.exec(); updateDialog.exec();
} }
void MainWindow::onUpdatesAvailable(const QJsonObject &updates) {
QString featherVersionStr{FEATHER_VERSION};
auto featherVersion = SemanticVersion::fromString(featherVersionStr);
QString platformTag = getPlatformTag();
if (platformTag.isEmpty()) {
qWarning() << "Unsupported platform, unable to fetch update";
return;
}
QJsonObject platformData = updates["platform"].toObject()[platformTag].toObject();
if (platformData.isEmpty()) {
qWarning() << "Unable to find current platform in updates data";
return;
}
QString newVersion = platformData["version"].toString();
if (SemanticVersion::fromString(newVersion) <= featherVersion) {
return;
}
// Hooray! New update available
QString hashesUrl = QString("%1/files/releases/hashes-%2-plain.txt").arg(constants::websiteUrl, newVersion);
UtilsNetworking network{getNetworkTor()};
QNetworkReply *reply = network.get(hashesUrl);
connect(reply, &QNetworkReply::finished, this, std::bind(&MainWindow::onSignedHashesReceived, this, reply, platformTag, newVersion));
}
void MainWindow::onSignedHashesReceived(QNetworkReply *reply, const QString &platformTag, const QString &version) {
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "Unable to fetch signed hashes: " << reply->errorString();
return;
}
QByteArray armoredSignedHashes = reply->readAll();
reply->deleteLater();
const QString binaryFilename = QString("feather-%1-%2.zip").arg(version, platformTag);
QString signer;
QByteArray signedHash = AsyncTask::runAndWaitForFuture([armoredSignedHashes, binaryFilename, &signer]{
try {
return Updater().verifyParseSignedHashes(armoredSignedHashes, binaryFilename, signer);
}
catch (const std::exception &e) {
qWarning() << "Failed to fetch and verify signed hash: " << e.what();
return QByteArray{};
}
});
if (signedHash.isEmpty()) {
return;
}
QString hash = signedHash.toHex();
qInfo() << "Update found: " << binaryFilename << hash << "signed by:" << signer;
this->onCheckUpdatesComplete(version, binaryFilename, hash, signer);
}
void MainWindow::onInitiateTransaction() { void MainWindow::onInitiateTransaction() {
m_statusDots = 0; m_statusDots = 0;
m_constructingTransaction = true; m_constructingTransaction = true;

View file

@ -31,6 +31,7 @@
#include "utils/networking.h" #include "utils/networking.h"
#include "utils/config.h" #include "utils/config.h"
#include "utils/EventFilter.h" #include "utils/EventFilter.h"
#include "utils/Updater.h"
#include "widgets/CCSWidget.h" #include "widgets/CCSWidget.h"
#include "widgets/RedditWidget.h" #include "widgets/RedditWidget.h"
#include "widgets/TickerWidget.h" #include "widgets/TickerWidget.h"
@ -136,9 +137,7 @@ private slots:
void loadSignedTxFromText(); void loadSignedTxFromText();
void onTorConnectionStateChanged(bool connected); void onTorConnectionStateChanged(bool connected);
void onCheckUpdatesComplete(const QString &version, const QString &binaryFilename, const QString &hash, const QString &signer); void showUpdateDialog();
void onShowUpdateCheck(const QString &version, const QString &binaryFilename, const QString &hash, const QString &signer);
void onSignedHashesReceived(QNetworkReply *reply, const QString &platformTag, const QString &version);
void onInitiateTransaction(); void onInitiateTransaction();
void onEndTransaction(); void onEndTransaction();
void onKeysCorrupted(); void onKeysCorrupted();
@ -181,10 +180,10 @@ private slots:
void onDeviceButtonPressed(); void onDeviceButtonPressed();
void onWalletPassphraseNeeded(bool on_device); void onWalletPassphraseNeeded(bool on_device);
void menuHwDeviceClicked(); void menuHwDeviceClicked();
void onUpdatesAvailable(const QJsonObject &updates);
void toggleSearchbar(bool enabled); void toggleSearchbar(bool enabled);
void tryStoreWallet(); void tryStoreWallet();
void onWebsocketStatusChanged(bool enabled); void onWebsocketStatusChanged(bool enabled);
void showUpdateNotification();
private: private:
friend WindowManager; friend WindowManager;
@ -287,6 +286,8 @@ private:
EventFilter *m_eventFilter = nullptr; EventFilter *m_eventFilter = nullptr;
qint64 m_userLastActive = QDateTime::currentSecsSinceEpoch(); qint64 m_userLastActive = QDateTime::currentSecsSinceEpoch();
QSharedPointer<Updater> m_updater = nullptr;
}; };
#endif // FEATHER_MAINWINDOW_H #endif // FEATHER_MAINWINDOW_H

View file

@ -591,6 +591,7 @@
<string>Help</string> <string>Help</string>
</property> </property>
<addaction name="actionAbout"/> <addaction name="actionAbout"/>
<addaction name="actionCheckForUpdates"/>
<addaction name="actionOfficialWebsite"/> <addaction name="actionOfficialWebsite"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionDocumentation"/> <addaction name="actionDocumentation"/>
@ -942,6 +943,11 @@
<string>Lock wallet</string> <string>Lock wallet</string>
</property> </property>
</action> </action>
<action name="actionCheckForUpdates">
<property name="text">
<string>Check for updates</string>
</property>
</action>
</widget> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>
<customwidgets> <customwidgets>

View file

@ -6,6 +6,7 @@
#include <QFileDialog> #include <QFileDialog>
#include "constants.h"
#include "utils/AsyncTask.h" #include "utils/AsyncTask.h"
#include "utils/networking.h" #include "utils/networking.h"
#include "utils/NetworkManager.h" #include "utils/NetworkManager.h"
@ -14,24 +15,23 @@
#include "zip.h" #include "zip.h"
UpdateDialog::UpdateDialog(QWidget *parent, QString version, QString downloadUrl, QString hash, QString signer, QString platformTag) UpdateDialog::UpdateDialog(QWidget *parent, QSharedPointer<Updater> updater)
: QDialog(parent) : QDialog(parent)
, ui(new Ui::UpdateDialog) , ui(new Ui::UpdateDialog)
, m_version(std::move(version)) , m_updater(std::move(updater))
, m_downloadUrl(std::move(downloadUrl))
, m_hash(std::move(hash))
, m_signer(std::move(signer))
, m_platformTag(std::move(platformTag))
{ {
ui->setupUi(this); ui->setupUi(this);
ui->btn_installUpdate->hide();
ui->btn_restart->hide();
ui->progressBar->hide();
auto bigFont = Utils::relativeFont(4); auto bigFont = Utils::relativeFont(4);
ui->label_header->setFont(bigFont); ui->label_header->setFont(bigFont);
ui->label_header->setText(QString("New Feather version %1 is available").arg(m_version)); ui->frame->hide();
bool updateAvailable = (m_updater->state == Updater::State::UPDATE_AVAILABLE);
if (updateAvailable) {
this->updateAvailable();
} else {
this->checkForUpdates();
}
connect(ui->btn_cancel, &QPushButton::clicked, [this]{ connect(ui->btn_cancel, &QPushButton::clicked, [this]{
if (m_reply) { if (m_reply) {
@ -43,9 +43,36 @@ UpdateDialog::UpdateDialog(QWidget *parent, QString version, QString downloadUrl
connect(ui->btn_installUpdate, &QPushButton::clicked, this, &UpdateDialog::onInstallUpdate); connect(ui->btn_installUpdate, &QPushButton::clicked, this, &UpdateDialog::onInstallUpdate);
connect(ui->btn_restart, &QPushButton::clicked, this, &UpdateDialog::onRestartClicked); connect(ui->btn_restart, &QPushButton::clicked, this, &UpdateDialog::onRestartClicked);
connect(m_updater.data(), &Updater::updateAvailable, this, &UpdateDialog::updateAvailable);
connect(m_updater.data(), &Updater::noUpdateAvailable, this, &UpdateDialog::noUpdateAvailable);
connect(m_updater.data(), &Updater::updateCheckFailed, this, &UpdateDialog::onUpdateCheckFailed);
this->adjustSize(); this->adjustSize();
} }
void UpdateDialog::checkForUpdates() {
ui->label_header->setText("Checking for updates...");
ui->label_body->setText("...");
m_updater->checkForUpdates();
}
void UpdateDialog::noUpdateAvailable() {
this->setStatus("Feather is up-to-date.", true);
}
void UpdateDialog::updateAvailable() {
ui->frame->show();
ui->btn_installUpdate->hide();
ui->btn_restart->hide();
ui->progressBar->hide();
ui->label_header->setText(QString("New Feather version %1 is available").arg(m_updater->version));
ui->label_body->setText("Do you want to download and verify the new version?");
}
void UpdateDialog::onUpdateCheckFailed(const QString &errorMsg) {
this->setStatus(QString("Failed to check for updates: %1").arg(errorMsg), false);
}
void UpdateDialog::onDownloadClicked() { void UpdateDialog::onDownloadClicked() {
ui->label_body->setText("Downloading update.."); ui->label_body->setText("Downloading update..");
ui->btn_download->hide(); ui->btn_download->hide();
@ -53,7 +80,7 @@ void UpdateDialog::onDownloadClicked() {
UtilsNetworking network{getNetworkTor()}; UtilsNetworking network{getNetworkTor()};
m_reply = network.get(m_downloadUrl); m_reply = network.get(m_updater->downloadUrl);
connect(m_reply, &QNetworkReply::downloadProgress, this, &UpdateDialog::onDownloadProgress); connect(m_reply, &QNetworkReply::downloadProgress, this, &UpdateDialog::onDownloadProgress);
connect(m_reply, &QNetworkReply::finished, this, &UpdateDialog::onDownloadFinished); connect(m_reply, &QNetworkReply::finished, this, &UpdateDialog::onDownloadFinished);
} }
@ -83,7 +110,7 @@ void UpdateDialog::onDownloadFinished() {
return Updater().getHash(&responseStr[0], responseStr.size()); return Updater().getHash(&responseStr[0], responseStr.size());
}); });
const QByteArray signedHash = QByteArray::fromHex(m_hash.toUtf8()); const QByteArray signedHash = QByteArray::fromHex(m_updater->hash.toUtf8());
if (signedHash != calculatedHash) { if (signedHash != calculatedHash) {
this->onDownloadError("Error: Hash sum mismatch."); this->onDownloadError("Error: Hash sum mismatch.");
@ -180,7 +207,7 @@ void UpdateDialog::onInstallUpdate() {
QDir applicationDir(Utils::applicationPath()); QDir applicationDir(Utils::applicationPath());
QString filePath = applicationDir.filePath(name); QString filePath = applicationDir.filePath(name);
if (m_platformTag == "win-installer") { if (m_updater->platformTag == "win-installer") {
filePath = QString("%1/%2").arg(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation), name); filePath = QString("%1/%2").arg(QStandardPaths::writableLocation(QStandardPaths::DownloadLocation), name);
} }
@ -205,7 +232,7 @@ void UpdateDialog::onInstallUpdate() {
return; return;
} }
if (m_platformTag == "win-installer") { if (m_updater->platformTag == "win-installer") {
this->setStatus("Installer written. Click 'restart' to close Feather and start the installer."); this->setStatus("Installer written. Click 'restart' to close Feather and start the installer.");
} else { } else {
this->setStatus("Installation successful. Do you want to restart Feather now?"); this->setStatus("Installation successful. Do you want to restart Feather now?");
@ -219,7 +246,7 @@ void UpdateDialog::installUpdateMac() {
if (appPath.endsWith("Contents/MacOS")) { if (appPath.endsWith("Contents/MacOS")) {
appDir.cd("../../.."); appDir.cd("../../..");
} }
QString appName = QString("feather-%1").arg(m_version); QString appName = QString("feather-%1").arg(m_updater->version);
QString zipName = QString("%1.zip").arg(appName); QString zipName = QString("%1.zip").arg(appName);
QString fPath = appDir.filePath(zipName); QString fPath = appDir.filePath(zipName);

View file

@ -7,6 +7,8 @@
#include <QDialog> #include <QDialog>
#include <QNetworkReply> #include <QNetworkReply>
#include "utils/Updater.h"
namespace Ui { namespace Ui {
class UpdateDialog; class UpdateDialog;
} }
@ -16,7 +18,7 @@ class UpdateDialog : public QDialog
Q_OBJECT Q_OBJECT
public: public:
explicit UpdateDialog(QWidget *parent, QString version, QString downloadUrl, QString hash, QString signer, QString platformTag); explicit UpdateDialog(QWidget *parent, QSharedPointer<Updater> updater);
~UpdateDialog() override; ~UpdateDialog() override;
private slots: private slots:
@ -27,21 +29,22 @@ private slots:
void onInstallUpdate(); void onInstallUpdate();
void onInstallError(const QString &errMsg); void onInstallError(const QString &errMsg);
void onRestartClicked(); void onRestartClicked();
void onUpdateCheckFailed(const QString &onUpdateCheckFailed);
signals: signals:
void restartWallet(const QString &binaryFilename); void restartWallet(const QString &binaryFilename);
private: private:
void checkForUpdates();
void noUpdateAvailable();
void updateAvailable();
void setStatus(const QString &msg, bool success = false); void setStatus(const QString &msg, bool success = false);
void installUpdateMac(); void installUpdateMac();
QScopedPointer<Ui::UpdateDialog> ui; QScopedPointer<Ui::UpdateDialog> ui;
QSharedPointer<Updater> m_updater;
QString m_version;
QString m_downloadUrl; QString m_downloadUrl;
QString m_hash;
QString m_signer;
QString m_platformTag;
QString m_updatePath; QString m_updatePath;

View file

@ -6,12 +6,12 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>569</width> <width>540</width>
<height>148</height> <height>144</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Update Available</string> <string>Updater</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
@ -26,8 +26,32 @@
<property name="text"> <property name="text">
<string>Do you want to download and verify the new version?</string> <string>Do you want to download and verify the new version?</string>
</property> </property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget> </widget>
</item> </item>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item> <item>
<widget class="QProgressBar" name="progressBar"> <widget class="QProgressBar" name="progressBar">
<property name="value"> <property name="value">
@ -82,6 +106,9 @@
</item> </item>
</layout> </layout>
</widget> </widget>
</item>
</layout>
</widget>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>

View file

@ -6,13 +6,154 @@
#include <common/util.h> #include <common/util.h>
#include <openpgp/hash.h> #include <openpgp/hash.h>
#include "config-feather.h"
#include "constants.h"
#include "Utils.h" #include "Utils.h"
#include "utils/AsyncTask.h"
#include "utils/networking.h"
#include "utils/NetworkManager.h"
#include "utils/SemanticVersion.h"
Updater::Updater() { Updater::Updater(QObject *parent) :
QObject(parent)
{
std::string featherWallet = Utils::fileOpen(":/assets/gpg_keys/featherwallet.asc").toStdString(); std::string featherWallet = Utils::fileOpen(":/assets/gpg_keys/featherwallet.asc").toStdString();
m_maintainers.emplace_back(featherWallet); m_maintainers.emplace_back(featherWallet);
} }
void Updater::checkForUpdates() {
UtilsNetworking network{getNetworkTor()};
QNetworkReply *reply = network.getJson("https://featherwallet.org/updates.json");
connect(reply, &QNetworkReply::finished, this, std::bind(&Updater::onUpdateCheckResponse, this, reply));
}
void Updater::onUpdateCheckResponse(QNetworkReply *reply) {
const QString err = reply->errorString();
QByteArray data = reply->readAll();
reply->deleteLater();
QJsonObject updates;
if (!data.isEmpty() && Utils::validateJSON(data)) {
auto doc = QJsonDocument::fromJson(data);
updates = doc.object();
}
else {
qWarning() << err;
emit updateCheckFailed(err);
return;
}
this->wsUpdatesReceived(updates);
}
void Updater::wsUpdatesReceived(const QJsonObject &updates) {
QString featherVersionStr{FEATHER_VERSION};
auto featherVersion = SemanticVersion::fromString(featherVersionStr);
QString platformTag = getPlatformTag();
if (platformTag.isEmpty()) {
QString err{"Unsupported platform, unable to fetch update"};
emit updateCheckFailed(err);
qWarning() << err;
return;
}
QJsonObject platformData = updates["platform"].toObject()[platformTag].toObject();
if (platformData.isEmpty()) {
QString err{"Unable to find current platform in updates data"};
emit updateCheckFailed(err);
qWarning() << err;
return;
}
QString newVersion = platformData["version"].toString();
if (SemanticVersion::fromString(newVersion) <= featherVersion) {
emit noUpdateAvailable();
return;
}
// Hooray! New update available
QString hashesUrl = QString("%1/files/releases/hashes-%2-plain.txt").arg(constants::websiteUrl, newVersion);
UtilsNetworking network{getNetworkTor()};
QNetworkReply *reply = network.get(hashesUrl);
connect(reply, &QNetworkReply::finished, this, std::bind(&Updater::onSignedHashesReceived, this, reply, platformTag, newVersion));
}
void Updater::onSignedHashesReceived(QNetworkReply *reply, const QString &platformTag, const QString &version) {
if (reply->error() != QNetworkReply::NoError) {
QString err{QString("Unable to fetch signed hashed: %1").arg(reply->errorString())};
emit updateCheckFailed(err);
qWarning() << err;
return;
}
QByteArray armoredSignedHashes = reply->readAll();
reply->deleteLater();
const QString binaryFilename = QString("feather-%1-%2.zip").arg(version, platformTag);
QByteArray signedHash{};
QString signer;
try {
signedHash = this->verifyParseSignedHashes(armoredSignedHashes, binaryFilename, signer);
}
catch (const std::exception &e) {
QString err{QString("Failed to fetch and verify signed hash: %1").arg(e.what())};
emit updateCheckFailed(err);
qWarning() << err;
return;
}
QString hash = signedHash.toHex();
qInfo() << "Update found: " << binaryFilename << hash << "signed by:" << signer;
this->state = Updater::State::UPDATE_AVAILABLE;
this->version = version;
this->binaryFilename = binaryFilename;
this->downloadUrl = QString("https://featherwallet.org/files/releases/%1/%2").arg(platformTag, binaryFilename);
this->hash = hash;
this->signer = signer;
this->platformTag = platformTag;
emit updateAvailable();
}
QString Updater::getPlatformTag() {
#ifdef Q_OS_MACOS
return "mac";
#endif
#ifdef Q_OS_WIN
#ifdef PLATFORM_INSTALLER
return "win-installer";
#endif
return "win";
#endif
#ifdef Q_OS_LINUX
QString tag = "";
QString arch = QSysInfo::buildCpuArchitecture();
if (arch == "arm64") {
tag += "linux-arm64";
} else if (arch == "arm") {
tag += "linux-arm";
} else {
tag += "linux";
}
if (!qEnvironmentVariableIsEmpty("APPIMAGE")) {
tag += "-appimage";
}
return tag;
#endif
return "";
}
QByteArray Updater::verifyParseSignedHashes( QByteArray Updater::verifyParseSignedHashes(
const QByteArray &armoredSignedHashes, const QByteArray &armoredSignedHashes,
const QString &binaryFilename, const QString &binaryFilename,

View file

@ -8,10 +8,20 @@
#include <openpgp/openpgp.h> #include <openpgp/openpgp.h>
class Updater class Updater : public QObject
{ {
Q_OBJECT
public: public:
explicit Updater(); enum State {
NO_UPDATE = 0,
UPDATE_AVAILABLE = 1
};
public:
explicit Updater(QObject *parent = nullptr);
void checkForUpdates();
QByteArray verifyParseSignedHashes(const QByteArray &armoredSignedHashes, const QString &binaryFilename, QString &signers) const; QByteArray verifyParseSignedHashes(const QByteArray &armoredSignedHashes, const QString &binaryFilename, QString &signers) const;
@ -19,8 +29,27 @@ public:
QString verifySignature(const QByteArray &armoredSignedMessage, QString &signer) const; QString verifySignature(const QByteArray &armoredSignedMessage, QString &signer) const;
QByteArray parseShasumOutput(const QString &message, const QString &filename) const; QByteArray parseShasumOutput(const QString &message, const QString &filename) const;
State state = State::NO_UPDATE;
QString version;
QString binaryFilename;
QString downloadUrl;
QString hash;
QString signer;
QString platformTag;
signals:
void updateCheckFailed(const QString &error);
void noUpdateAvailable();
void updateAvailable();
public slots:
void onUpdateCheckResponse(QNetworkReply *reply);
void wsUpdatesReceived(const QJsonObject &updates);
private: private:
QString verifySignature(const epee::span<const uint8_t> data, const openpgp::signature_rsa &signature) const; QString verifySignature(const epee::span<const uint8_t> data, const openpgp::signature_rsa &signature) const;
void onSignedHashesReceived(QNetworkReply *reply, const QString &platformTag, const QString &version);
QString getPlatformTag();
private: private:
std::vector<openpgp::public_key_block> m_maintainers; std::vector<openpgp::public_key_block> m_maintainers;