]> Nutra Git (v1) - gamesguru/feather.git/commitdiff
MainWindow: Help -> Check for updates
authortobtoht <tob@featherwallet.org>
Wed, 1 Feb 2023 14:40:12 +0000 (15:40 +0100)
committertobtoht <tob@featherwallet.org>
Wed, 1 Feb 2023 14:54:08 +0000 (15:54 +0100)
src/MainWindow.cpp
src/MainWindow.h
src/MainWindow.ui
src/dialog/UpdateDialog.cpp
src/dialog/UpdateDialog.h
src/dialog/UpdateDialog.ui
src/utils/Updater.cpp
src/utils/Updater.h

index 64e63067ded9050882e93d5bfc591174c77276b0..a513b056e0ea7ad351c542a5884cf7d5100449d6 100644 (file)
@@ -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;
index 7b1c7dc62ee18c579d3bbb10c1624b819daa0ada..34a380e92986c292601a20cdfe6bcb8ce3c7739a 100644 (file)
@@ -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
index fa9799d1dc41af9c21483b4ec58844aea6d1fa77..0f913ea1310b117453e6e5599d0f7cae5ebafa99 100644 (file)
      <string>Help</string>
     </property>
     <addaction name="actionAbout"/>
+    <addaction name="actionCheckForUpdates"/>
     <addaction name="actionOfficialWebsite"/>
     <addaction name="separator"/>
     <addaction name="actionDocumentation"/>
     <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>
index 1af5841094a200f45fd95535a7ef8bda5b7306e6..3fd9ecbbc6d7c572da24c8993039de8c71ee09df 100644 (file)
@@ -6,6 +6,7 @@
 
 #include <QFileDialog>
 
+#include "constants.h"
 #include "utils/AsyncTask.h"
 #include "utils/networking.h"
 #include "utils/NetworkManager.h"
 
 #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);
 
index a6641ace70ac50fd77187ebacb3edca23838e3b9..53cb07b7b0dba867128fa820f9d363087980a134 100644 (file)
@@ -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;
 
index fba1689bce4b14628b6e7fe5d969a593352af47e..3c12c1d67e4ddf14bb58569e966568a1bc9dd14a 100644 (file)
@@ -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>
      <property name="text">
       <string>Do you want to download and verify the new version?</string>
      </property>
+     <property name="textInteractionFlags">
+      <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
+     </property>
     </widget>
    </item>
    <item>
-    <widget class="QProgressBar" name="progressBar">
-     <property name="value">
-      <number>0</number>
+    <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>
-   <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>
  <resources/>
index da2e9be2ea9b133919f864b6285381502ec0d98c..4e12c0d303dabe9305e1308f6ba00b4f5b6eb4d4 100644 (file)
 #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,
index 8a42b885c2dc5dd67053dfac177e1f8024c77f49..d280520ff99a1e92b181fd5be5c295ea49b3f5e1 100644 (file)
@@ -8,10 +8,20 @@
 
 #include <openpgp/openpgp.h>
 
-class Updater
+class Updater : public QObject
 {
+Q_OBJECT
+
+public:
+    enum State {
+        NO_UPDATE = 0,
+        UPDATE_AVAILABLE = 1
+    };
+
 public:
-    explicit Updater();
+    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;