507 lines
20 KiB
C++
507 lines
20 KiB
C++
// 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 <https://www.gnu.org/licenses/>.
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include "systemcontroller.h"
|
|
|
|
#include "types/package.h"
|
|
#include "types/packages.h"
|
|
#include "types/repositories.h"
|
|
#include "types/repository.h"
|
|
|
|
#include <QJsonDocument>
|
|
#include <QMetaEnum>
|
|
#include <QTimeZone>
|
|
|
|
#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<SystemController::UpdateType>();
|
|
m_updateManagementType = static_cast<SystemController::UpdateType>(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<SystemController::UpdateType>();
|
|
m_updateManagementType = static_cast<SystemController::UpdateType>(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();
|
|
}
|