mirror of
https://github.com/feather-wallet/feather.git
synced 2025-01-05 10:29:27 +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_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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue