From 4e9e0ef2bc8a11398cb40f4099608e3acb31a07c Mon Sep 17 00:00:00 2001 From: Alexis Lopez Zubieta <contact@azubieta.net> Date: Mon, 2 Sep 2019 17:19:13 -0500 Subject: [PATCH] Add updater dialog --- app/app.pri | 10 +- app/src/main.cpp | 17 ++- app/src/updaters/appimageupdatedialog.ui | 96 +++++++++++++++ app/src/updaters/appupdatedialog.cpp | 67 +++++++++++ app/src/updaters/appupdatedialog.h | 44 +++++++ app/src/updaters/appupdater.cpp | 144 +++++++++++++++++++++++ app/src/updaters/appupdater.h | 55 +++++++++ 7 files changed, 429 insertions(+), 4 deletions(-) create mode 100644 app/src/updaters/appimageupdatedialog.ui create mode 100644 app/src/updaters/appupdatedialog.cpp create mode 100644 app/src/updaters/appupdatedialog.h create mode 100644 app/src/updaters/appupdater.cpp create mode 100644 app/src/updaters/appupdater.h diff --git a/app/app.pri b/app/app.pri index 5f750fe..ab4a8ed 100644 --- a/app/app.pri +++ b/app/app.pri @@ -4,18 +4,23 @@ QT += \ core \ gui \ network \ + widgets \ websockets HEADERS += \ + $${PWD}/src/updaters/appupdatedialog.h \ $${PWD}/src/websockets/websocketserver.h \ $${PWD}/src/handlers/confighandler.h \ $${PWD}/src/handlers/systemhandler.h \ $${PWD}/src/handlers/ocsapihandler.h \ $${PWD}/src/handlers/itemhandler.h \ $${PWD}/src/handlers/updatehandler.h \ - $${PWD}/src/handlers/desktopthemehandler.h + $${PWD}/src/handlers/desktopthemehandler.h \ + $${PWD}/src/updaters/appupdater.h SOURCES += \ + $${PWD}/src/updaters/appupdatedialog.cpp \ + $${PWD}/src/updaters/appupdater.cpp \ $${PWD}/src/main.cpp \ $${PWD}/src/websockets/websocketserver.cpp \ $${PWD}/src/handlers/confighandler.cpp \ @@ -48,3 +53,6 @@ contains(DEFINES, APP_DESKTOP) { $${PWD}/src/desktopthemes/cinnamontheme.cpp \ $${PWD}/src/desktopthemes/matetheme.cpp } + +FORMS += \ + $$PWD/src/updaters/appimageupdatedialog.ui diff --git a/app/src/main.cpp b/app/src/main.cpp index 6f03743..34b5851 100644 --- a/app/src/main.cpp +++ b/app/src/main.cpp @@ -4,17 +4,18 @@ #include <QLocale> #include <QCommandLineParser> #include <QCommandLineOption> -#include <QGuiApplication> +#include <QApplication> #include <QIcon> #include <QDebug> #include "handlers/confighandler.h" #include "websockets/websocketserver.h" +#include "updaters/appupdater.h" int main(int argc, char *argv[]) { // Init - QGuiApplication app(argc, argv); // This is backend program, but need GUI module + QApplication app(argc, argv); // This is backend program, but need GUI module auto envPath = QString::fromLocal8Bit(qgetenv("PATH").constData()) + ":" + app.applicationDirPath(); qputenv("PATH", envPath.toUtf8().constData()); @@ -27,6 +28,7 @@ int main(int argc, char *argv[]) app.setOrganizationName(appConfigApplication["organization"].toString()); app.setOrganizationDomain(appConfigApplication["domain"].toString()); app.setWindowIcon(QIcon::fromTheme(appConfigApplication["id"].toString(), QIcon(appConfigApplication["icon"].toString()))); + app.setQuitOnLastWindowClosed(false); // Setup translator QTranslator translator; @@ -45,13 +47,22 @@ int main(int argc, char *argv[]) QCommandLineOption clOptionPort(QStringList() << "p" << "port", "Port for websocket server [default: 49152].", "port", "49152"); clParser.addOption(clOptionPort); + QCommandLineOption clOptionAppPath(QStringList() << "a" << "appFile", "Path to the main AppImage <file>.", "file"); + clParser.addOption(clOptionAppPath); + clParser.process(app); + // Setup AppUpdater + auto appFile = clParser.value(clOptionAppPath); + AppUpdater appUpdater(appFile); + appUpdater.setSilentLookup(true); + appUpdater.doUpdateLookUp(); + auto port = clParser.value(clOptionPort).toUShort(); // Setup websocket server auto *wsServer = new WebSocketServer(configHandler, appConfigApplication["id"].toString(), port, &app); - QObject::connect(wsServer, &WebSocketServer::stopped, &app, &QGuiApplication::quit); + QObject::connect(wsServer, &WebSocketServer::stopped, &app, &QGuiApplication::quit); if (wsServer->start()) { qInfo() << "Websocket server started at:" << wsServer->serverUrl().toString(); diff --git a/app/src/updaters/appimageupdatedialog.ui b/app/src/updaters/appimageupdatedialog.ui new file mode 100644 index 0000000..70fe2ab --- /dev/null +++ b/app/src/updaters/appimageupdatedialog.ui @@ -0,0 +1,96 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AppImageUpdateDialog</class> + <widget class="QDialog" name="AppImageUpdateDialog"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>405</width> + <height>126</height> + </rect> + </property> + <property name="windowTitle"> + <string>Pling Store Updater</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QStackedWidget" name="stackedWidget"> + <widget class="QWidget" name="confirmationPage"> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>12</number> + </property> + <item> + <widget class="QLabel" name="confirmationLabel"> + <property name="text"> + <string>A new version of Pling-Store is available! + +Do you want to download it?</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QPushButton" name="latterButton"> + <property name="text"> + <string>Latter</string> + </property> + </widget> + </item> + <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="doitButton"> + <property name="text"> + <string>Update</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QWidget" name="progressPage"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLabel" name="title"> + <property name="text"> + <string>Downloading update contents</string> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </item> + <item> + <widget class="QProgressBar" name="progressBar"> + <property name="value"> + <number>0</number> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/app/src/updaters/appupdatedialog.cpp b/app/src/updaters/appupdatedialog.cpp new file mode 100644 index 0000000..ae70f3d --- /dev/null +++ b/app/src/updaters/appupdatedialog.cpp @@ -0,0 +1,67 @@ +#include <QDebug> + +#include "appupdatedialog.h" +#include "ui_appimageupdatedialog.h" + +AppUpdateDialog::AppUpdateDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::AppImageUpdateDialog) +{ + ui->setupUi(this); + connect(ui->latterButton, &QPushButton::clicked, this, &AppUpdateDialog::reject); +} + +AppUpdateDialog::~AppUpdateDialog() +{ + delete ui; +} + +void AppUpdateDialog::showUpdateConfirmationMessage() +{ + setWindowTitle(tr("Pling Store Update Available")); + ui->confirmationLabel->setText(tr("Do you want to update now to the new Pling Store version now?")); + ui->doitButton->setText("Yes"); + + disconnect(ui->doitButton, nullptr, this, nullptr); + connect(ui->doitButton, &QPushButton::released, this, &AppUpdateDialog::updateRequested); + + ui->stackedWidget->setCurrentWidget(ui->confirmationPage); + show(); +} + +void AppUpdateDialog::showErrorMessage(const QString &msg) +{ + setWindowTitle(tr("Pling Store Update Failed")); + ui->confirmationLabel->setText(tr("Do you want to try again?")); + ui->doitButton->setText("Yes"); + + disconnect(ui->doitButton, nullptr, this, nullptr); + connect(ui->doitButton, &QPushButton::released, this, &AppUpdateDialog::updateRequested); + + ui->stackedWidget->setCurrentWidget(ui->confirmationPage); + show(); +} + +void AppUpdateDialog::showCompletionMessage() +{ + setWindowTitle(tr("Plign Store Update Completed")); + ui->confirmationLabel->setText(tr("Do you want to open the new version now?")); + ui->doitButton->setText("Yes"); + + disconnect(ui->doitButton, nullptr, this, nullptr); + connect(ui->doitButton, &QPushButton::released, this, &AppUpdateDialog::restartRequested); + + ui->stackedWidget->setCurrentWidget(ui->confirmationPage); + show(); +} + +void AppUpdateDialog::showProgress(int progress) +{ + setWindowTitle(tr("Pling Store Update")); + + ui->progressBar->setValue(progress); + ui->progressPage->show(); + + ui->stackedWidget->setCurrentWidget(ui->progressPage); + show(); +} diff --git a/app/src/updaters/appupdatedialog.h b/app/src/updaters/appupdatedialog.h new file mode 100644 index 0000000..6a39204 --- /dev/null +++ b/app/src/updaters/appupdatedialog.h @@ -0,0 +1,44 @@ +#ifndef APPIMAGEUPDATEDIALOG_H +#define APPIMAGEUPDATEDIALOG_H + +#include <QDialog> + +namespace Ui { +class AppImageUpdateDialog; +} + +namespace appimage { + namespace update { + class Updater; + } +} + +class AppUpdateDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AppUpdateDialog(QWidget *parent = nullptr); + ~AppUpdateDialog(); + +signals: + void restartRequested(); + + void updateRequested(); + +public slots: + void showUpdateConfirmationMessage(); + + void showErrorMessage(const QString &msg); + + void showCompletionMessage(); + + void showProgress(int progress); + +private: + Ui::AppImageUpdateDialog *ui; + QString targetAction; + +}; + +#endif // APPIMAGEUPDATEDIALOG_H diff --git a/app/src/updaters/appupdater.cpp b/app/src/updaters/appupdater.cpp new file mode 100644 index 0000000..b595248 --- /dev/null +++ b/app/src/updaters/appupdater.cpp @@ -0,0 +1,144 @@ +#include <QDebug> +#include <QtConcurrent/QtConcurrent> + +#include "appimage/update.h" +#include "appupdater.h" + +AppUpdater::AppUpdater(const QString &appImagePath, QObject *parent) : QObject(parent), appImagePath(appImagePath), updateHelper(nullptr) +{ + connect(this, &AppUpdater::updateAvailable, &updateDialog, &AppUpdateDialog::showUpdateConfirmationMessage); + connect(&updateDialog, &AppUpdateDialog::updateRequested, this, &AppUpdater::doUpdate); + connect(&updateDialog, &AppUpdateDialog::restartRequested, this, &AppUpdater::doRestart); + + connect(&updateDialog, &AppUpdateDialog::rejected, this, &AppUpdater::stop); +} + +void AppUpdater::setSilentLookup(bool value) +{ + silentLookup = value; +} + +void AppUpdater::doUpdateLookUp() +{ + if (appImagePath.isEmpty()) { + qWarning() << "Self-updates disabled: No app file provided."; + return; + } + + if (!silentLookup) + updateDialog.show(); + + QtConcurrent::run([=]() { + appimage::update::Updater updater(appImagePath.toStdString()); + + bool updateAvailable; // this is an output parameter!!! + updater.checkForChanges(updateAvailable); + + if (updateAvailable) { + qDebug() << "Update available"; + emit this->updateAvailable(); + } + + }); +} + +void AppUpdater::doUpdate() +{ + if (appImagePath.isEmpty()) { + qWarning() << "Self-updates disabled: No app file provided."; + return; + } + + + if (updateHelper !=nullptr) + delete updateHelper; + + updateHelper = new appimage::update::Updater(appImagePath.toStdString()); + + updateHelper->start(); + + progressCheckTimer.setInterval(200); + progressCheckTimer.start(); + + connect(&progressCheckTimer, &QTimer::timeout, this, &AppUpdater::checkUpdateProgress); +} + +void AppUpdater::doRestart() +{ + QProcess::startDetached("pkill", {"pling-store"}); + + if (updateHelper != nullptr) { + std::string pathToNewFile; + updateHelper->pathToNewFile(pathToNewFile); + + if (!pathToNewFile.empty()) { + QString path = QString::fromStdString(pathToNewFile); + QFile::setPermissions(path, QFileDevice::ReadUser | QFileDevice::ExeUser); + + + QProcess proc; + proc.setProgram(path); + proc.setEnvironment(getCleanSystemEnvironment()); + + qDebug() << proc.environment(); + if (proc.startDetached()) { + updateDialog.accept(); + } else + updateDialog.showErrorMessage("Unable to start: " + path); + } + } +} + +void AppUpdater::stop() +{ +} + +void AppUpdater::checkUpdateProgress() +{ + using namespace appimage::update; + auto state = updateHelper->state(); + switch (state) { + case Updater::INITIALIZED: + break; + case Updater::RUNNING: + double progress; + updateHelper->progress(progress); + updateDialog.showProgress(progress*100); + break; + case Updater::STOPPING: + break; + case Updater::SUCCESS: + updateDialog.showCompletionMessage(); + progressCheckTimer.stop(); + break; + case Updater::ERROR: + updateDialog.showErrorMessage(tr("Update failed")); + progressCheckTimer.stop(); + break; + } +} + +QStringList AppUpdater::getCleanSystemEnvironment() +{ + QString appDirPath = qgetenv("APPDIR"); + + QProcessEnvironment systenEnvironemnt = QProcessEnvironment::systemEnvironment(); + QProcessEnvironment processEnvironment; + + for (QString key: systenEnvironemnt.keys()) { + QString value = systenEnvironemnt.value(key); + + QStringList oldValue = value.split(":"); + QStringList newVaule; + for (const QString &valueSection: oldValue) + if (!valueSection.contains(appDirPath)) + newVaule << valueSection; + + if (!newVaule.empty()) + processEnvironment.insert(key, newVaule.join(":")); + } + + return processEnvironment.toStringList(); +// return {"DISPLAY=:0"}; +} + diff --git a/app/src/updaters/appupdater.h b/app/src/updaters/appupdater.h new file mode 100644 index 0000000..b031191 --- /dev/null +++ b/app/src/updaters/appupdater.h @@ -0,0 +1,55 @@ +#pragma once + +#include <QObject> +#include <QTimer> + +#include "appupdatedialog.h" + +namespace appimage { + namespace update { + class Updater; + } +} + +class AppUpdater : public QObject +{ + Q_OBJECT +public: + explicit AppUpdater(const QString &appImagePath, QObject *parent = nullptr); + + + void setSilentLookup(bool value); + +signals: + void updateAvailable(); + void restartApp(QString appPath); + +public slots: + + void doUpdateLookUp(); + + void doUpdate(); + + void doRestart(); + + void stop(); + +protected slots: + void checkUpdateProgress(); + +private: + /** + * @brief Clean reference to APPDIR from the environment + * @return + */ + QStringList getCleanSystemEnvironment(); + + bool silentLookup; + + QString appImagePath; + appimage::update::Updater * updateHelper; + AppUpdateDialog updateDialog; + QTimer progressCheckTimer; + +}; + -- GitLab