From e0d14b8c92b4730cd33836f7febc81ad1eddded9 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Mon, 8 Apr 2019 02:04:37 +0200 Subject: [PATCH] Add support for system upgrades --- .../configuration/nymeaconfiguration.cpp | 3 +- libnymea-app-core/engine.cpp | 10 +- libnymea-app-core/engine.h | 4 + libnymea-app-core/jsonrpc/jsonrpcclient.cpp | 2 +- libnymea-app-core/libnymea-app-core.h | 3 + libnymea-app-core/libnymea-app-core.pro | 11 +- libnymea-app-core/system/systemcontroller.cpp | 129 ++++++++++++++++++ libnymea-app-core/system/systemcontroller.h | 72 ++++++++++ nymea-app/images.qrc | 1 + nymea-app/resources.qrc | 1 + nymea-app/ui/SettingsPage.qml | 16 +++ nymea-app/ui/images/system-update.svg | 21 +++ nymea-app/ui/system/GeneralSettingsPage.qml | 17 +++ nymea-app/ui/system/SystemUpdatePage.qml | 87 ++++++++++++ 14 files changed, 371 insertions(+), 6 deletions(-) create mode 100644 libnymea-app-core/system/systemcontroller.cpp create mode 100644 libnymea-app-core/system/systemcontroller.h create mode 100644 nymea-app/ui/images/system-update.svg create mode 100644 nymea-app/ui/system/SystemUpdatePage.qml diff --git a/libnymea-app-core/configuration/nymeaconfiguration.cpp b/libnymea-app-core/configuration/nymeaconfiguration.cpp index b9d5e096..ad593fd3 100644 --- a/libnymea-app-core/configuration/nymeaconfiguration.cpp +++ b/libnymea-app-core/configuration/nymeaconfiguration.cpp @@ -284,7 +284,6 @@ void NymeaConfiguration::getConfigurationsResponse(const QVariantMap ¶ms) webServerConfigurations()->clear(); foreach (const QVariant &webServerVariant, params.value("params").toMap().value("webServerConfigurations").toList()) { QVariantMap webServerConfigMap = webServerVariant.toMap(); - qDebug() << "**********+ web config" << webServerConfigMap; WebServerConfiguration* config = new WebServerConfiguration(webServerConfigMap.value("id").toString(), QHostAddress(webServerConfigMap.value("address").toString()), webServerConfigMap.value("port").toInt(), webServerConfigMap.value("authenticationEnabled").toBool(), webServerConfigMap.value("sslEnabled").toBool()); config->setPublicFolder(webServerConfigMap.value("publicFolder").toString()); m_webServerConfigurations->addConfiguration(config); @@ -293,7 +292,7 @@ void NymeaConfiguration::getConfigurationsResponse(const QVariantMap ¶ms) void NymeaConfiguration::getAvailableLanguagesResponse(const QVariantMap ¶ms) { - qDebug() << "available languages" << params; +// qDebug() << "available languages" << params; m_availableLanguages = params.value("params").toMap().value("languages").toStringList(); emit availableLanguagesChanged(); } diff --git a/libnymea-app-core/engine.cpp b/libnymea-app-core/engine.cpp index 3f511097..cceb8a1c 100644 --- a/libnymea-app-core/engine.cpp +++ b/libnymea-app-core/engine.cpp @@ -25,6 +25,7 @@ #include "tagsmanager.h" #include "configuration/nymeaconfiguration.h" #include "connection/awsclient.h" +#include "system/systemcontroller.h" #include "connection/tcpsockettransport.h" #include "connection/websockettransport.h" @@ -39,7 +40,8 @@ Engine::Engine(QObject *parent) : m_ruleManager(new RuleManager(m_jsonRpcClient, this)), m_logManager(new LogManager(m_jsonRpcClient, this)), m_tagsManager(new TagsManager(m_jsonRpcClient, this)), - m_nymeaConfiguration(new NymeaConfiguration(m_jsonRpcClient, this)) + m_nymeaConfiguration(new NymeaConfiguration(m_jsonRpcClient, this)), + m_systemController(new SystemController(m_jsonRpcClient, this)) { m_connection->registerTransport(new TcpSocketTransportFactory()); m_connection->registerTransport(new WebsocketTransportFactory()); @@ -97,6 +99,11 @@ NymeaConfiguration *Engine::nymeaConfiguration() const return m_nymeaConfiguration; } +SystemController *Engine::systemController() const +{ + return m_systemController; +} + void Engine::deployCertificate() { if (!m_jsonRpcClient->connected()) { @@ -129,6 +136,7 @@ void Engine::onConnectedChanged() m_deviceManager->init(); m_ruleManager->init(); m_nymeaConfiguration->init(); + m_systemController->init(); } } } diff --git a/libnymea-app-core/engine.h b/libnymea-app-core/engine.h index 28cc4759..8184b3ad 100644 --- a/libnymea-app-core/engine.h +++ b/libnymea-app-core/engine.h @@ -32,6 +32,7 @@ class RuleManager; class LogManager; class TagsManager; class NymeaConfiguration; +class SystemController; class Engine : public QObject { @@ -42,6 +43,7 @@ class Engine : public QObject Q_PROPERTY(TagsManager* tagsManager READ tagsManager CONSTANT) Q_PROPERTY(JsonRpcClient* jsonRpcClient READ jsonRpcClient CONSTANT) Q_PROPERTY(NymeaConfiguration* nymeaConfiguration READ nymeaConfiguration CONSTANT) + Q_PROPERTY(SystemController* systemController READ systemController CONSTANT) public: explicit Engine(QObject *parent = nullptr); @@ -56,6 +58,7 @@ public: JsonRpcClient *jsonRpcClient() const; LogManager *logManager() const; NymeaConfiguration *nymeaConfiguration() const; + SystemController *systemController() const; Q_INVOKABLE void deployCertificate(); @@ -67,6 +70,7 @@ private: LogManager *m_logManager; TagsManager *m_tagsManager; NymeaConfiguration *m_nymeaConfiguration; + SystemController *m_systemController; private slots: void onConnectedChanged(); diff --git a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp index bd8ec6c2..64d67b29 100644 --- a/libnymea-app-core/jsonrpc/jsonrpcclient.cpp +++ b/libnymea-app-core/jsonrpc/jsonrpcclient.cpp @@ -133,7 +133,7 @@ void JsonRpcClient::notificationReceived(const QVariantMap &data) void JsonRpcClient::isCloudConnectedReply(const QVariantMap &data) { - qDebug() << "Cloud is connected" << data; +// qDebug() << "Cloud is connected" << data; QMetaEnum connectionStateEnum = QMetaEnum::fromType(); m_cloudConnectionState = static_cast(connectionStateEnum.keyToValue(data.value("params").toMap().value("connectionState").toByteArray().data())); emit cloudConnectionStateChanged(); diff --git a/libnymea-app-core/libnymea-app-core.h b/libnymea-app-core/libnymea-app-core.h index f11716e8..7f87e7f6 100644 --- a/libnymea-app-core/libnymea-app-core.h +++ b/libnymea-app-core/libnymea-app-core.h @@ -56,6 +56,7 @@ #include "ruletemplates/ruleactionparamtemplate.h" #include "connection/awsclient.h" #include "models/devicemodel.h" +#include "system/systemcontroller.h" #include @@ -199,6 +200,8 @@ void registerQmlTypes() { qmlRegisterUncreatableType(uri, 1, 0, "RuleActionTemplate", "Get it from RuleActionTemplates"); qmlRegisterUncreatableType(uri, 1, 0, "RuleActionParamTemplates", "Get it from RuleActionTemplate"); qmlRegisterUncreatableType(uri, 1, 0, "RuleActionParamTemplate", "Get it from RuleActionParamTemplates"); + + qmlRegisterUncreatableType(uri, 1, 0, "SystemController", "Get it from Engine"); } #endif // LIBNYMEAAPPCORE_H diff --git a/libnymea-app-core/libnymea-app-core.pro b/libnymea-app-core/libnymea-app-core.pro index 6b4127ef..5a3c5bf2 100644 --- a/libnymea-app-core/libnymea-app-core.pro +++ b/libnymea-app-core/libnymea-app-core.pro @@ -81,7 +81,8 @@ SOURCES += \ configuration/nymeaconfiguration.cpp \ configuration/mqttpolicy.cpp \ configuration/mqttpolicies.cpp \ - models/devicemodel.cpp + models/devicemodel.cpp \ + system/systemcontroller.cpp HEADERS += \ engine.h \ @@ -142,4 +143,10 @@ HEADERS += \ configuration/nymeaconfiguration.h \ configuration/mqttpolicy.h \ configuration/mqttpolicies.h \ - models/devicemodel.h + models/devicemodel.h \ + system/systemcontroller.h + +unix { + target.path = /usr/lib + INSTALLS += target +} diff --git a/libnymea-app-core/system/systemcontroller.cpp b/libnymea-app-core/system/systemcontroller.cpp new file mode 100644 index 00000000..3a7e19a5 --- /dev/null +++ b/libnymea-app-core/system/systemcontroller.cpp @@ -0,0 +1,129 @@ +#include "systemcontroller.h" + +SystemController::SystemController(JsonRpcClient *jsonRpcClient, QObject *parent): + JsonHandler(parent), + m_jsonRpcClient(jsonRpcClient) +{ + m_jsonRpcClient->registerNotificationHandler(this, "notificationReceived"); +} + +void SystemController::init() +{ + if (m_jsonRpcClient->ensureServerVersion("2.0")) { + m_jsonRpcClient->sendCommand("System.GetCapabilities", this, "getCapabilitiesResponse"); + } else { + m_powerManagementAvailable = false; + } +} + +QString SystemController::nameSpace() const +{ + return "System"; +} + +bool SystemController::powerManagementAvailable() const +{ + return m_powerManagementAvailable; +} + +bool SystemController::updateManagementAvailable() const +{ + return m_updateManagementAvailable; +} + +bool SystemController::updateAvailable() const +{ + return m_updateAvailable; +} + +QString SystemController::currentVersion() const +{ + return m_currentVersion; +} + +QString SystemController::candidateVersion() const +{ + return m_candidateVersion; +} + +QStringList SystemController::availableChannels() const +{ + return m_availableChannels; +} + +QString SystemController::currentChannel() const +{ + return m_currentChannel; +} + +bool SystemController::updateInProgress() const +{ + return m_updareInProgress; +} + +void SystemController::startUpdate() +{ + m_jsonRpcClient->sendCommand("System.StartUpdate"); +} + +void SystemController::selectChannel(const QString &channel) +{ + QVariantMap params; + params.insert("channel", channel); + m_jsonRpcClient->sendCommand("System.SelectChannel", params, this, "selectChannelResponse"); +} + +void SystemController::reboot() +{ + m_jsonRpcClient->sendCommand("System.Reboot"); +} + +void SystemController::shutdown() +{ + m_jsonRpcClient->sendCommand("System.Shutdown"); +} + +void SystemController::getCapabilitiesResponse(const QVariantMap &data) +{ + qDebug() << "capabilities received" << data; + m_powerManagementAvailable = data.value("params").toMap().value("powerManagement").toBool(); + emit powerManagementAvailableChanged(); + + m_updateManagementAvailable = data.value("params").toMap().value("updateManagement").toBool(); + emit updateManagementAvailableChanged(); + + if (m_updateManagementAvailable) { + m_jsonRpcClient->sendCommand("System.GetUpdateStatus", this, "getUpdateStatusResponse"); + } +} + +void SystemController::getUpdateStatusResponse(const QVariantMap &data) +{ + qDebug() << "Update status:" << data; + m_currentVersion = data.value("params").toMap().value("currentVersion").toString(); + m_candidateVersion = data.value("params").toMap().value("candidateVersion").toString(); + m_availableChannels = data.value("params").toMap().value("availableChannels").toStringList(); + m_currentChannel = data.value("params").toMap().value("currentChannel").toString(); + m_updareInProgress = data.value("params").toMap().value("updateInProgress").toBool(); + m_updateAvailable = data.value("params").toMap().value("updateAvailable").toBool(); + emit updateStatusChanged(); +} + +void SystemController::selectChannelResponse(const QVariantMap &data) +{ + qDebug() << "Select channel response" << data; +} + +void SystemController::notificationReceived(const QVariantMap &data) +{ + if (data.value("notification").toString() == "System.UpdateStatusChanged") { + qDebug() << "Update status changed:" << data; + m_currentVersion = data.value("params").toMap().value("currentVersion").toString(); + m_candidateVersion = data.value("params").toMap().value("candidateVersion").toString(); + m_availableChannels = data.value("params").toMap().value("availableChannels").toStringList(); + m_currentChannel = data.value("params").toMap().value("currentChannel").toString(); + m_updareInProgress = data.value("params").toMap().value("updateInProgress").toBool(); + m_updateAvailable = data.value("params").toMap().value("updateAvailable").toBool(); + emit updateStatusChanged(); + } +} diff --git a/libnymea-app-core/system/systemcontroller.h b/libnymea-app-core/system/systemcontroller.h new file mode 100644 index 00000000..d9c540f7 --- /dev/null +++ b/libnymea-app-core/system/systemcontroller.h @@ -0,0 +1,72 @@ +#ifndef SYSTEMCONTROLLER_H +#define SYSTEMCONTROLLER_H + +#include + +#include "jsonrpc/jsonrpcclient.h" + +class SystemController : public JsonHandler +{ + Q_OBJECT + Q_PROPERTY(bool powerManagementAvailable READ powerManagementAvailable NOTIFY powerManagementAvailableChanged) + // Whether the update mechanism is available in the connected core + Q_PROPERTY(bool updateManagementAvailable READ updateManagementAvailable NOTIFY updateManagementAvailableChanged) + + // Whether there is an update available + Q_PROPERTY(bool updateAvailable READ updateAvailable NOTIFY updateStatusChanged) + Q_PROPERTY(QString currentVersion READ currentVersion NOTIFY updateStatusChanged) + Q_PROPERTY(QString candidateVersion READ candidateVersion NOTIFY updateStatusChanged) + Q_PROPERTY(QStringList availableChannels READ availableChannels NOTIFY updateStatusChanged) + Q_PROPERTY(QString currentChannel READ currentChannel NOTIFY updateStatusChanged) + + Q_PROPERTY(bool updateInProgress READ updateInProgress NOTIFY updateStatusChanged) + +public: + explicit SystemController(JsonRpcClient *jsonRpcClient, QObject *parent = nullptr); + + void init(); + QString nameSpace() const override; + + bool powerManagementAvailable() const; + Q_INVOKABLE void reboot(); + Q_INVOKABLE void shutdown(); + + bool updateManagementAvailable() const; + bool updateAvailable() const; + QString currentVersion() const; + QString candidateVersion() const; + QStringList availableChannels() const; + QString currentChannel() const; + + bool updateInProgress() const; + + Q_INVOKABLE void startUpdate(); + Q_INVOKABLE void selectChannel(const QString &channel); + +signals: + void powerManagementAvailableChanged(); + void updateManagementAvailableChanged(); + void updateStatusChanged(); + +private slots: + void getCapabilitiesResponse(const QVariantMap &data); + void getUpdateStatusResponse(const QVariantMap &data); + void selectChannelResponse(const QVariantMap &data); + + void notificationReceived(const QVariantMap &data); +private: + JsonRpcClient *m_jsonRpcClient = nullptr; + + bool m_powerManagementAvailable = false; + bool m_updateManagementAvailable = false; + + bool m_updateAvailable = false; + QString m_currentVersion; + QString m_candidateVersion; + QStringList m_availableChannels; + QString m_currentChannel; + + bool m_updareInProgress = false; +}; + +#endif // SYSTEMCONTROLLER_H diff --git a/nymea-app/images.qrc b/nymea-app/images.qrc index a1cb016b..2ce38fb7 100644 --- a/nymea-app/images.qrc +++ b/nymea-app/images.qrc @@ -180,5 +180,6 @@ ui/images/sensors/closable.svg ui/images/lock-closed.svg ui/images/lock-open.svg + ui/images/system-update.svg diff --git a/nymea-app/resources.qrc b/nymea-app/resources.qrc index a2a18a9b..a8a372f6 100644 --- a/nymea-app/resources.qrc +++ b/nymea-app/resources.qrc @@ -180,5 +180,6 @@ ui/appsettings/LookAndFeelSettingsPage.qml ui/appsettings/AppLogPage.qml ui/magic/SelectStatePage.qml + ui/system/SystemUpdatePage.qml diff --git a/nymea-app/ui/SettingsPage.qml b/nymea-app/ui/SettingsPage.qml index e779ef65..5d150bb7 100644 --- a/nymea-app/ui/SettingsPage.qml +++ b/nymea-app/ui/SettingsPage.qml @@ -155,6 +155,22 @@ Page { } } + Pane { + Layout.fillWidth: true + Material.elevation: layout.isGrid ? 1 : 0 + + padding: 0 + MeaListItemDelegate { + width: parent.width + iconName: "../images/system-update.svg" + text: qsTr("System update") + subText: qsTr("Update your %1:core system").arg(app.systemName) + prominentSubText: false + wrapTexts: false + onClicked: pageStack.push(Qt.resolvedUrl("system/SystemUpdatePage.qml")) + } + } + Pane { Layout.fillWidth: true Material.elevation: layout.isGrid ? 1 : 0 diff --git a/nymea-app/ui/images/system-update.svg b/nymea-app/ui/images/system-update.svg new file mode 100644 index 00000000..0d98998c --- /dev/null +++ b/nymea-app/ui/images/system-update.svg @@ -0,0 +1,21 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/nymea-app/ui/system/GeneralSettingsPage.qml b/nymea-app/ui/system/GeneralSettingsPage.qml index bc5cc95f..2fa6afd4 100644 --- a/nymea-app/ui/system/GeneralSettingsPage.qml +++ b/nymea-app/ui/system/GeneralSettingsPage.qml @@ -93,5 +93,22 @@ Page { } } } + + Button { + Layout.fillWidth: true + text: qsTr("Reboot %1:core").arg(app.systemName) + visible: engine.systemController.powerManagementAvailable + onClicked: { + engine.systemController.reboot() + } + } + Button { + Layout.fillWidth: true + text: qsTr("Shutdown %1:core").arg(app.systemName) + visible: engine.systemController.powerManagementAvailable + onClicked: { + engine.systemController.shutdown() + } + } } } diff --git a/nymea-app/ui/system/SystemUpdatePage.qml b/nymea-app/ui/system/SystemUpdatePage.qml new file mode 100644 index 00000000..791cb18e --- /dev/null +++ b/nymea-app/ui/system/SystemUpdatePage.qml @@ -0,0 +1,87 @@ +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.3 +import "../components" +import Nymea 1.0 + +Page { + id: root + header: GuhHeader { + text: qsTr("System update") + backButtonVisible: true + onBackPressed: pageStack.pop() + +// HeaderButton { +// imageSource: "../images/configure.svg" +// color: pluginsProxy.showOnlyConfigurable ? app.accentColor : keyColor +// onClicked: { +// pluginsProxy.showOnlyConfigurable = !pluginsProxy.showOnlyConfigurable +// } +// } + } + + ColumnLayout { + anchors { left: parent.left; top: parent.top; right: parent.right } + + Label { + Layout.fillWidth: true + Layout.margins: app.margins + text: qsTr("Your %1 system is up to date.").arg(app.systemName) + visible: !engine.systemController.updateAvailable + } + + MeaListItemDelegate { + Layout.fillWidth: true + progressive: false + text: qsTr("Installed version") + subText: engine.systemController.currentVersion + } + MeaListItemDelegate { + Layout.fillWidth: true + progressive: false + text: qsTr("Candidate version") + subText: engine.systemController.candidateVersion + visible: engine.systemController.updateAvailable + } + + Button { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + text: qsTr("Update system") + visible: engine.systemController.updateAvailable + onClicked: { + engine.systemController.startUpdate() + } + } + + ThinDivider { + visible: settings.showHiddenOptions + } + + RowLayout { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + spacing: app.margins + visible: settings.showHiddenOptions + Label { + Layout.fillWidth: true + text: qsTr("Update channel") + } + ComboBox { + Layout.minimumWidth: 200 + model: engine.systemController.availableChannels + currentIndex: model.indexOf(engine.systemController.currentChannel) + onActivated: { + engine.systemController.selectChannel(model[index]) + } + } + } + } + + BusyOverlay { + visible: engine.systemController.updateInProgress + } +}