mirror of
https://github.com/feather-wallet/feather.git
synced 2025-01-03 17:39:49 +00:00
MainWindow: Help -> Check for updates
This commit is contained in:
parent
a748eaf494
commit
bc63a9d4f1
8 changed files with 331 additions and 156 deletions
|
@ -54,6 +54,8 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
|
|||
m_splashDialog = new SplashDialog(this);
|
||||
m_accountSwitcherDialog = new AccountSwitcherDialog(m_ctx, this);
|
||||
|
||||
m_updater = QSharedPointer<Updater>(new Updater(this));
|
||||
|
||||
this->restoreGeo();
|
||||
|
||||
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::RedditReceived, ui->redditWidget->model(), &RedditModel::updatePosts);
|
||||
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
|
||||
connect(websocketNotifier(), &WebsocketNotifier::XMRigDownloadsReceived, m_xmrig, &XMRigWidget::onDownloads);
|
||||
#endif
|
||||
|
@ -80,6 +82,8 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
|
|||
connect(torManager(), &TorManager::connectionStateChanged, this, &MainWindow::onTorConnectionStateChanged);
|
||||
this->onTorConnectionStateChanged(torManager()->torConnected);
|
||||
|
||||
connect(m_updater.data(), &Updater::updateAvailable, this, &MainWindow::showUpdateNotification);
|
||||
|
||||
ColorScheme::updateFromWidget(this);
|
||||
QTimer::singleShot(1, [this]{this->updateWidgetIcons();});
|
||||
|
||||
|
@ -350,6 +354,11 @@ void MainWindow::initMenu() {
|
|||
|
||||
// [Help]
|
||||
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->actionDonate_to_Feather, &QAction::triggered, this, &MainWindow::donateButtonClicked);
|
||||
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"));
|
||||
}
|
||||
|
||||
void MainWindow::onCheckUpdatesComplete(const QString &version, const QString &binaryFilename,
|
||||
const QString &hash, const QString &signer) {
|
||||
QString versionDisplay{version};
|
||||
void MainWindow::showUpdateNotification() {
|
||||
QString versionDisplay{m_updater->version};
|
||||
versionDisplay.replace("beta", "Beta");
|
||||
QString updateText = QString("Update to Feather %1 is available").arg(versionDisplay);
|
||||
m_statusUpdateAvailable->setText(updateText);
|
||||
|
@ -1351,82 +1359,15 @@ void MainWindow::onCheckUpdatesComplete(const QString &version, const QString &b
|
|||
m_statusUpdateAvailable->show();
|
||||
|
||||
m_statusUpdateAvailable->disconnect();
|
||||
connect(m_statusUpdateAvailable, &StatusBarButton::clicked, [this, version, binaryFilename, hash, signer] {
|
||||
this->onShowUpdateCheck(version, binaryFilename, hash, signer);
|
||||
});
|
||||
connect(m_statusUpdateAvailable, &StatusBarButton::clicked, this, &MainWindow::showUpdateDialog);
|
||||
}
|
||||
|
||||
void MainWindow::onShowUpdateCheck(const QString &version, const QString &binaryFilename,
|
||||
const QString &hash, const QString &signer) {
|
||||
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};
|
||||
void MainWindow::showUpdateDialog() {
|
||||
UpdateDialog updateDialog{this, m_updater};
|
||||
connect(&updateDialog, &UpdateDialog::restartWallet, m_windowManager, &WindowManager::restartApplication);
|
||||
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() {
|
||||
m_statusDots = 0;
|
||||
m_constructingTransaction = true;
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "utils/networking.h"
|
||||
#include "utils/config.h"
|
||||
#include "utils/EventFilter.h"
|
||||
#include "utils/Updater.h"
|
||||
#include "widgets/CCSWidget.h"
|
||||
#include "widgets/RedditWidget.h"
|
||||
#include "widgets/TickerWidget.h"
|
||||
|
@ -136,9 +137,7 @@ private slots:
|
|||
void loadSignedTxFromText();
|
||||
|
||||
void onTorConnectionStateChanged(bool connected);
|
||||
void onCheckUpdatesComplete(const QString &version, const QString &binaryFilename, const QString &hash, const QString &signer);
|
||||
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 showUpdateDialog();
|
||||
void onInitiateTransaction();
|
||||
void onEndTransaction();
|
||||
void onKeysCorrupted();
|
||||
|
@ -181,10 +180,10 @@ private slots:
|
|||
void onDeviceButtonPressed();
|
||||
void onWalletPassphraseNeeded(bool on_device);
|
||||
void menuHwDeviceClicked();
|
||||
void onUpdatesAvailable(const QJsonObject &updates);
|
||||
void toggleSearchbar(bool enabled);
|
||||
void tryStoreWallet();
|
||||
void onWebsocketStatusChanged(bool enabled);
|
||||
void showUpdateNotification();
|
||||
|
||||
private:
|
||||
friend WindowManager;
|
||||
|
@ -287,6 +286,8 @@ private:
|
|||
|
||||
EventFilter *m_eventFilter = nullptr;
|
||||
qint64 m_userLastActive = QDateTime::currentSecsSinceEpoch();
|
||||
|
||||
QSharedPointer<Updater> m_updater = nullptr;
|
||||
};
|
||||
|
||||
#endif // FEATHER_MAINWINDOW_H
|
||||
|
|
|
@ -591,6 +591,7 @@
|
|||
<string>Help</string>
|
||||
</property>
|
||||
<addaction name="actionAbout"/>
|
||||
<addaction name="actionCheckForUpdates"/>
|
||||
<addaction name="actionOfficialWebsite"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionDocumentation"/>
|
||||
|
@ -942,6 +943,11 @@
|
|||
<string>Lock wallet</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCheckForUpdates">
|
||||
<property name="text">
|
||||
<string>Check for updates</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<customwidgets>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <QFileDialog>
|
||||
|
||||
#include "constants.h"
|
||||
#include "utils/AsyncTask.h"
|
||||
#include "utils/networking.h"
|
||||
#include "utils/NetworkManager.h"
|
||||
|
@ -14,24 +15,23 @@
|
|||
|
||||
#include "zip.h"
|
||||
|
||||
UpdateDialog::UpdateDialog(QWidget *parent, QString version, QString downloadUrl, QString hash, QString signer, QString platformTag)
|
||||
: QDialog(parent)
|
||||
, ui(new Ui::UpdateDialog)
|
||||
, m_version(std::move(version))
|
||||
, m_downloadUrl(std::move(downloadUrl))
|
||||
, m_hash(std::move(hash))
|
||||
, m_signer(std::move(signer))
|
||||
, m_platformTag(std::move(platformTag))
|
||||
UpdateDialog::UpdateDialog(QWidget *parent, QSharedPointer<Updater> updater)
|
||||
: QDialog(parent)
|
||||
, ui(new Ui::UpdateDialog)
|
||||
, m_updater(std::move(updater))
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->btn_installUpdate->hide();
|
||||
ui->btn_restart->hide();
|
||||
ui->progressBar->hide();
|
||||
|
||||
auto bigFont = Utils::relativeFont(4);
|
||||
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]{
|
||||
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_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();
|
||||
}
|
||||
|
||||
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() {
|
||||
ui->label_body->setText("Downloading update..");
|
||||
ui->btn_download->hide();
|
||||
|
@ -53,7 +80,7 @@ void UpdateDialog::onDownloadClicked() {
|
|||
|
||||
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::finished, this, &UpdateDialog::onDownloadFinished);
|
||||
}
|
||||
|
@ -83,7 +110,7 @@ void UpdateDialog::onDownloadFinished() {
|
|||
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) {
|
||||
this->onDownloadError("Error: Hash sum mismatch.");
|
||||
|
@ -180,7 +207,7 @@ void UpdateDialog::onInstallUpdate() {
|
|||
|
||||
QDir applicationDir(Utils::applicationPath());
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -205,7 +232,7 @@ void UpdateDialog::onInstallUpdate() {
|
|||
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.");
|
||||
} else {
|
||||
this->setStatus("Installation successful. Do you want to restart Feather now?");
|
||||
|
@ -219,7 +246,7 @@ void UpdateDialog::installUpdateMac() {
|
|||
if (appPath.endsWith("Contents/MacOS")) {
|
||||
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 fPath = appDir.filePath(zipName);
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#include <QDialog>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include "utils/Updater.h"
|
||||
|
||||
namespace Ui {
|
||||
class UpdateDialog;
|
||||
}
|
||||
|
@ -16,7 +18,7 @@ class UpdateDialog : public QDialog
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit UpdateDialog(QWidget *parent, QString version, QString downloadUrl, QString hash, QString signer, QString platformTag);
|
||||
explicit UpdateDialog(QWidget *parent, QSharedPointer<Updater> updater);
|
||||
~UpdateDialog() override;
|
||||
|
||||
private slots:
|
||||
|
@ -27,21 +29,22 @@ private slots:
|
|||
void onInstallUpdate();
|
||||
void onInstallError(const QString &errMsg);
|
||||
void onRestartClicked();
|
||||
void onUpdateCheckFailed(const QString &onUpdateCheckFailed);
|
||||
|
||||
signals:
|
||||
void restartWallet(const QString &binaryFilename);
|
||||
|
||||
private:
|
||||
void checkForUpdates();
|
||||
void noUpdateAvailable();
|
||||
void updateAvailable();
|
||||
void setStatus(const QString &msg, bool success = false);
|
||||
void installUpdateMac();
|
||||
|
||||
QScopedPointer<Ui::UpdateDialog> ui;
|
||||
QSharedPointer<Updater> m_updater;
|
||||
|
||||
QString m_version;
|
||||
QString m_downloadUrl;
|
||||
QString m_hash;
|
||||
QString m_signer;
|
||||
QString m_platformTag;
|
||||
|
||||
QString m_updatePath;
|
||||
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>569</width>
|
||||
<height>148</height>
|
||||
<width>540</width>
|
||||
<height>144</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Update Available</string>
|
||||
<string>Updater</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
|
@ -26,59 +26,86 @@
|
|||
<property name="text">
|
||||
<string>Do you want to download and verify the new version?</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btn_cancel">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btn_download">
|
||||
<property name="text">
|
||||
<string>Download</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btn_installUpdate">
|
||||
<property name="text">
|
||||
<string>Install Update</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btn_restart">
|
||||
<property name="text">
|
||||
<string>Restart Feather</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<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>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btn_cancel">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btn_download">
|
||||
<property name="text">
|
||||
<string>Download</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btn_installUpdate">
|
||||
<property name="text">
|
||||
<string>Install Update</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btn_restart">
|
||||
<property name="text">
|
||||
<string>Restart Feather</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
|
|
@ -6,13 +6,154 @@
|
|||
#include <common/util.h>
|
||||
#include <openpgp/hash.h>
|
||||
|
||||
#include "config-feather.h"
|
||||
#include "constants.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();
|
||||
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(
|
||||
const QByteArray &armoredSignedHashes,
|
||||
const QString &binaryFilename,
|
||||
|
|
|
@ -8,10 +8,20 @@
|
|||
|
||||
#include <openpgp/openpgp.h>
|
||||
|
||||
class Updater
|
||||
class Updater : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
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;
|
||||
|
||||
|
@ -19,8 +29,27 @@ public:
|
|||
QString verifySignature(const QByteArray &armoredSignedMessage, QString &signer) 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:
|
||||
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:
|
||||
std::vector<openpgp::public_key_block> m_maintainers;
|
||||
|
|
Loading…
Reference in a new issue