// SPDX-License-Identifier: LGPL-3.0-or-later /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2024 - 2025, chargebyte austria GmbH * * This file is part of libnymea-app. * * libnymea-app is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * libnymea-app is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libnymea-app. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "systemcontroller.h" #include "types/package.h" #include "types/packages.h" #include "types/repositories.h" #include "types/repository.h" #include #include #include #include "logging.h" NYMEA_LOGGING_CATEGORY(dcSystemController, "SystemController") SystemController::SystemController(JsonRpcClient *jsonRpcClient, QObject *parent) : QObject(parent) , m_jsonRpcClient(jsonRpcClient) { m_jsonRpcClient->registerNotificationHandler(this, "System", "notificationReceived"); m_packages = new Packages(this); m_repositories = new Repositories(this); startTimer(1000, Qt::VeryCoarseTimer); } void SystemController::init() { m_packages->clear(); m_repositories->clear(); if (m_jsonRpcClient->ensureServerVersion("2.0")) { m_jsonRpcClient->sendCommand("System.GetCapabilities", this, "getCapabilitiesResponse"); } else { m_powerManagementAvailable = false; m_updateManagementAvailable = false; m_timeManagementAvailable = false; } if (m_jsonRpcClient->ensureServerVersion("5.5")) { m_jsonRpcClient->sendCommand("System.GetSystemInfo", this, "getSystemInfoResponse"); } else { m_deviceSerialNumber.clear(); } } bool SystemController::powerManagementAvailable() const { return m_powerManagementAvailable; } bool SystemController::updateManagementAvailable() const { return m_updateManagementAvailable; } SystemController::UpdateType SystemController::updateManagementType() const { return m_updateManagementType; } int SystemController::restart() { return m_jsonRpcClient->sendCommand("System.Restart", this, "restartResponse"); } int SystemController::reboot() { return m_jsonRpcClient->sendCommand("System.Reboot", this, "rebootResponse"); } int SystemController::shutdown() { return m_jsonRpcClient->sendCommand("System.Shutdown", this, "shutdownResponse"); } bool SystemController::updateManagementBusy() const { return m_updateManagementBusy; } bool SystemController::updateRunning() const { return m_updateRunning; } int SystemController::updateProgress() const { return m_updateProgress; } void SystemController::checkForUpdates() { m_jsonRpcClient->sendCommand("System.CheckForUpdates"); } Packages *SystemController::packages() const { return m_packages; } void SystemController::updatePackages(const QString packageId) { QVariantMap params; if (!packageId.isEmpty()) { params.insert("packageIds", QStringList() << packageId); } m_jsonRpcClient->sendCommand("System.UpdatePackages", params); } void SystemController::removePackages(const QString packageId) { QVariantMap params; if (!packageId.isEmpty()) { params.insert("packageIds", QStringList() << packageId); } m_jsonRpcClient->sendCommand("System.RemovePackages", params, this, "removePackageResponse"); } Repositories *SystemController::repositories() const { return m_repositories; } int SystemController::enableRepository(const QString &id, bool enabled) { QVariantMap params; params.insert("repositoryId", id); params.insert("enabled", enabled); return m_jsonRpcClient->sendCommand("System.EnableRepository", params, this, "enableRepositoryResponse"); } bool SystemController::timeManagementAvailable() const { return m_timeManagementAvailable; } QDateTime SystemController::serverTime() const { return m_serverTime; } int SystemController::setServerTime(const QDateTime &serverTime) { QVariantMap params; params.insert("automaticTime", false); params.insert("time", serverTime.toSecsSinceEpoch()); params.insert("timeZone", serverTime.timeZone().id()); return m_jsonRpcClient->sendCommand("System.SetTime", params, this, "setTimeResponse"); } QStringList SystemController::timeZones() const { QStringList ret; foreach (const QByteArray &tzId, QTimeZone::availableTimeZoneIds()) { ret << tzId; } return ret; } QString SystemController::serverTimeZone() const { // NOTE: Ideally we'd just set the TimeZone of our serverTime prooperly, however, there's a bug on Android // Which doesn't allow to create QTimeZone objects by IANA id.... So, let's keep that separated in a string // https://bugreports.qt.io/browse/QTBUG-83438 // return m_serverTime.timeZone().id(); return m_serverTimeZone; } int SystemController::setServerTimeZone(const QString &serverTimeZone) { QVariantMap params; params.insert("timeZone", serverTimeZone); return m_jsonRpcClient->sendCommand("System.SetTime", params, this, "setTimeResponse"); } bool SystemController::automaticTimeAvailable() const { return m_automaticTimeAvailable; } bool SystemController::automaticTime() const { return m_automaticTime; } int SystemController::setAutomaticTime(bool automaticTime) { QVariantMap params; params.insert("automaticTime", automaticTime); return m_jsonRpcClient->sendCommand("System.SetTime", params, this, "setTimeResponse"); } QString SystemController::deviceSerialNumber() const { return m_deviceSerialNumber; } void SystemController::getCapabilitiesResponse(int /*commandId*/, const QVariantMap &data) { qCDebug(dcSystemController()) << data; m_powerManagementAvailable = data.value("powerManagement").toBool(); emit powerManagementAvailableChanged(); m_updateManagementAvailable = data.value("updateManagement").toBool(); emit updateManagementAvailableChanged(); // Since API version 8.5 if (data.contains("updateManagementType")) { QMetaEnum updateTypeEnum = QMetaEnum::fromType(); m_updateManagementType = static_cast(updateTypeEnum.keyToValue(data.value("updateManagementType").toByteArray())); } else { // Property exists since API 8.5, if there is an update management available, default to UpdateTypePackageManager if (m_updateManagementAvailable) { m_updateManagementType = UpdateTypePackageManager; } else { m_updateManagementType = UpdateTypeNone; } } m_timeManagementAvailable = data.value("timeManagement").toBool(); emit timeManagementAvailableChanged(); if (m_updateManagementAvailable) { m_jsonRpcClient->sendCommand("System.GetUpdateStatus", this, "getUpdateStatusResponse"); m_jsonRpcClient->sendCommand("System.GetPackages", this, "getPackagesResponse"); m_jsonRpcClient->sendCommand("System.GetRepositories", this, "getRepositoriesResponse"); } if (m_jsonRpcClient->ensureServerVersion("4.1")) { m_jsonRpcClient->sendCommand("System.GetTime", this, "getServerTimeResponse"); } qCDebug(dcSystemController()) << "nymea:core capabilities: Power management:" << m_powerManagementAvailable << "Update management:" << m_updateManagementAvailable << "Update management type:" << m_updateManagementType << "Time management:" << m_timeManagementAvailable; } void SystemController::getUpdateStatusResponse(int /*commandId*/, const QVariantMap &data) { qCDebug(dcSystemController()) << "Update status:" << qUtf8Printable(QJsonDocument::fromVariant(data).toJson(QJsonDocument::Indented)); m_updateManagementBusy = data.value("busy").toBool(); m_updateRunning = data.value("updateRunning").toBool(); m_updateProgress = data.value("updateProgress", -1).toInt(); // Since API 8.5, optional emit updateRunningChanged(); emit updateManagementBusyChanged(); emit updateProgressChanged(); } void SystemController::getPackagesResponse(int commandId, const QVariantMap &data) { Q_UNUSED(commandId) qCDebug(dcSystemController()) << "Packages:" << qUtf8Printable(QJsonDocument::fromVariant(data).toJson(QJsonDocument::Indented)); foreach (const QVariant &packageVariant, data.value("packages").toList()) { QString id = packageVariant.toMap().value("id").toString(); QString displayName = packageVariant.toMap().value("displayName").toString(); Package *p = new Package(id, displayName); p->setSummary(packageVariant.toMap().value("summary").toString()); p->setInstalledVersion(packageVariant.toMap().value("installedVersion").toString()); p->setCandidateVersion(packageVariant.toMap().value("candidateVersion").toString()); p->setChangelog(packageVariant.toMap().value("changelog").toString()); p->setUpdateAvailable(packageVariant.toMap().value("updateAvailable").toBool()); p->setRollbackAvailable(packageVariant.toMap().value("rollbackAvailable").toBool()); p->setCanRemove(packageVariant.toMap().value("canRemove").toBool()); m_packages->addPackage(p); } } void SystemController::getRepositoriesResponse(int /*commandId*/, const QVariantMap &data) { foreach (const QVariant &repoVariant, data.value("repositories").toList()) { QString id = repoVariant.toMap().value("id").toString(); QString displayName = repoVariant.toMap().value("displayName").toString(); Repository *repo = new Repository(id, displayName); repo->setEnabled(repoVariant.toMap().value("enabled").toBool()); m_repositories->addRepository(repo); } } void SystemController::removePackageResponse(int commandId, const QVariantMap ¶ms) { qCDebug(dcSystemController()) << "Remove result" << commandId << params; } void SystemController::enableRepositoryResponse(int commandId, const QVariantMap ¶ms) { qCDebug(dcSystemController()) << "Enable repo response" << params; emit enableRepositoryFinished(commandId, params.value("success").toBool()); } void SystemController::getServerTimeResponse(int commandId, const QVariantMap ¶ms) { Q_UNUSED(commandId) // NOTE: Ideally we'd just set the TimeZone of our serverTime prooperly, however, there's a bug on Android // Which doesn't allow to create QTimeZone objects by IANA id.... So, let's keep that separated in a string // https://bugreports.qt.io/browse/QTBUG-83438 // m_serverTime.setTimeZone(QTimeZone(params.value("timeZone").toString().toUtf8())); m_serverTimeZone = params.value("timeZone").toString(); emit serverTimeZoneChanged(); // Additionally there is the issue that QML Date().toString() will always show LocalTime but we want to display // the time in the servers time zone. So we'll be setting the time zone to the client one. This is effectively a // wrong/broken DateTime object and should not be used to calculate anything. m_serverTime = QDateTime::fromSecsSinceEpoch(params.value("time").toUInt()); m_serverTime = m_serverTime.toTimeZone(QTimeZone(m_serverTimeZone.toUtf8())); m_serverTime.setTimeZone(QDateTime::currentDateTime().timeZone()); emit serverTimeChanged(); m_automaticTimeAvailable = params.value("automaticTimeAvailable").toBool(); emit automaticTimeAvailableChanged(); m_automaticTime = params.value("automaticTime").toBool(); emit automaticTimeChanged(); qCDebug(dcSystemController()) << "Server time:" << m_serverTime << "Automatic Time available:" << m_automaticTimeAvailable << "Automatic time:" << m_automaticTime; } void SystemController::setTimeResponse(int commandId, const QVariantMap ¶ms) { qCDebug(dcSystemController()) << "set time response" << commandId << params; } void SystemController::restartResponse(int commandId, const QVariantMap ¶ms) { bool success = params.value("success").toBool(); emit restartReply(commandId, success); } void SystemController::rebootResponse(int commandId, const QVariantMap ¶ms) { bool success = params.value("success").toBool(); emit rebootReply(commandId, success); } void SystemController::shutdownResponse(int commandId, const QVariantMap ¶ms) { bool success = params.value("success").toBool(); emit shutdownReply(commandId, success); } void SystemController::getSystemInfoResponse(int commandId, const QVariantMap ¶ms) { Q_UNUSED(commandId) m_deviceSerialNumber = params.value("deviceSerialNumber").toString(); emit deviceSerialNumberChanged(); } void SystemController::notificationReceived(const QVariantMap &data) { QString notification = data.value("notification").toString(); const QVariantMap paramsMap = data.value("params").toMap(); if (notification == "System.UpdateStatusChanged") { qCDebug(dcSystemController()) << "System.UpdateStatusChanged:" << paramsMap; if (m_updateManagementBusy != paramsMap.value("busy").toBool()) { m_updateManagementBusy = paramsMap.value("busy").toBool(); emit updateManagementBusyChanged(); } if (m_updateRunning != paramsMap.value("updateRunning").toBool()) { m_updateRunning = paramsMap.value("updateRunning").toBool(); emit updateRunningChanged(); } // Since API 8.5, optional, not supported or not running = -1 if (m_updateProgress != paramsMap.value("updateProgress", -1).toInt()) { m_updateProgress = paramsMap.value("updateProgress").toInt(); emit updateProgressChanged(); } } else if (notification == "System.PackageAdded") { QVariantMap packageMap = paramsMap.value("package").toMap(); QString id = packageMap.value("id").toString(); QString displayName = packageMap.value("displayName").toString(); Package *p = new Package(id, displayName); p->setSummary(packageMap.value("summary").toString()); p->setInstalledVersion(packageMap.value("installedVersion").toString()); p->setCandidateVersion(packageMap.value("candidateVersion").toString()); p->setChangelog(packageMap.value("changelog").toString()); p->setUpdateAvailable(packageMap.value("updateAvailable").toBool()); p->setRollbackAvailable(packageMap.value("rollbackAvailable").toBool()); p->setCanRemove(packageMap.value("canRemove").toBool()); m_packages->addPackage(p); } else if (notification == "System.PackageChanged") { QVariantMap packageMap = paramsMap.value("package").toMap(); QString id = packageMap.value("id").toString(); Package *p = m_packages->getPackage(id); if (!p) { qWarning() << "Received a package update notification for a package we don't know"; return; } p->setSummary(packageMap.value("summary").toString()); p->setInstalledVersion(packageMap.value("installedVersion").toString()); p->setCandidateVersion(packageMap.value("candidateVersion").toString()); p->setChangelog(packageMap.value("changelog").toString()); p->setUpdateAvailable(packageMap.value("updateAvailable").toBool()); p->setRollbackAvailable(packageMap.value("rollbackAvailable").toBool()); p->setCanRemove(packageMap.value("canRemove").toBool()); } else if (notification == "System.PackageRemoved") { QString packageId = paramsMap.value("packageId").toString(); m_packages->removePackage(packageId); } else if (notification == "System.RepositoryAdded") { QVariantMap repoMap = paramsMap.value("repository").toMap(); QString id = repoMap.value("id").toString(); QString displayName = repoMap.value("displayName").toString(); Repository *repo = new Repository(id, displayName); repo->setEnabled(repoMap.value("enabled").toBool()); m_repositories->addRepository(repo); } else if (notification == "System.RepositoryChanged") { QVariantMap repoMap = paramsMap.value("repository").toMap(); QString id = repoMap.value("id").toString(); Repository *repo = m_repositories->getRepository(id); if (!repo) { qWarning() << "Received a repository update notification for a repository we don't know"; return; } repo->setEnabled(repoMap.value("enabled").toBool()); } else if (notification == "System.RepositoryRemoved") { QString repositoryId = paramsMap.value("repositoryId").toString(); m_repositories->removeRepository(repositoryId); } else if (notification == "System.CapabilitiesChanged") { m_powerManagementAvailable = paramsMap.value("powerManagement").toBool(); m_updateManagementAvailable = paramsMap.value("updateManagement").toBool(); if (paramsMap.contains("updateManagementType")) { QMetaEnum updateTypeEnum = QMetaEnum::fromType(); m_updateManagementType = static_cast(updateTypeEnum.keyToValue(paramsMap.value("updateManagementType").toByteArray())); } else { // Property exists since API 8.5, if there is an update management available, default to UpdateTypePackageManager if (m_updateManagementAvailable) { m_updateManagementType = UpdateTypePackageManager; } else { m_updateManagementType = UpdateTypeNone; } } qCDebug(dcSystemController()) << "System capabilites changed: power management:" << m_powerManagementAvailable << "update management:" << m_updateManagementAvailable << "update management type:" << m_updateManagementType; emit powerManagementAvailableChanged(); emit updateManagementAvailableChanged(); emit updateManagementTypeChanged(); } else if (notification == "System.TimeConfigurationChanged") { qCDebug(dcSystemController()) << "System time configuration changed" << data.value("params").toMap().value("timeZone").toByteArray(); // NOTE: Ideally we'd just set the TimeZone of our serverTime prooperly, however, there's a bug on Android // Which doesn't allow to create QTimeZone objects by IANA id.... So, let's keep that separated in a string // https://bugreports.qt.io/browse/QTBUG-83438 // m_serverTime.setTimeZone(QTimeZone(data.value("params").toMap().value("timeZone").toByteArray())); m_serverTimeZone = data.value("params").toMap().value("timeZone").toString(); emit serverTimeZoneChanged(); // Additionally there is the issue that QML Date().toString() will always show LocalTime but we want to display // the time in the servers time zone. So we'll be setting the time zone to the client one. This is effectively a // wrong/broken DateTime object and should not be used to calculate anything. m_serverTime = QDateTime::fromSecsSinceEpoch(data.value("params").toMap().value("time").toUInt()); m_serverTime = m_serverTime.toTimeZone(QTimeZone(m_serverTimeZone.toUtf8())); m_serverTime.setTimeZone(QDateTime::currentDateTime().timeZone()); emit serverTimeChanged(); m_automaticTimeAvailable = data.value("params").toMap().value("automaticTimeAvailable").toBool(); emit automaticTimeAvailableChanged(); m_automaticTime = data.value("params").toMap().value("automaticTime").toBool(); emit automaticTimeChanged(); } else { qWarning() << "Unhandled System Notification" << data.value("notification"); } } void SystemController::timerEvent(QTimerEvent *event) { Q_UNUSED(event) m_serverTime = m_serverTime.addSecs(1); emit serverTimeChanged(); }