File indexing completed on 2024-04-28 05:31:32

0001 /*
0002     SPDX-FileCopyrightText: 2020 Marco Martin <mart@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "SensorFaceController.h"
0008 #include "SensorFaceController_p.h"
0009 #include "SensorFace_p.h"
0010 #include <Sensor.h>
0011 #include <SensorQuery.h>
0012 
0013 #include <QtQml>
0014 
0015 #include <KConfigLoader>
0016 #include <KConfigPropertyMap>
0017 #include <KDesktopFile>
0018 #include <KLocalizedString>
0019 #include <KPackage/PackageJob>
0020 #include <KPackage/PackageLoader>
0021 #include <KPluginMetaData>
0022 #include <Solid/Block>
0023 #include <Solid/Device>
0024 #include <Solid/Predicate>
0025 #include <Solid/StorageAccess>
0026 #include <Solid/StorageVolume>
0027 
0028 using namespace KSysGuard;
0029 
0030 FacesModel::FacesModel(QObject *parent)
0031     : QStandardItemModel(parent)
0032 {
0033     reload();
0034 }
0035 
0036 void FacesModel::reload()
0037 {
0038     clear();
0039 
0040     const auto list = KPackage::PackageLoader::self()->listPackages(QStringLiteral("KSysguard/SensorFace"));
0041     for (auto plugin : list) {
0042         QStandardItem *item = new QStandardItem(plugin.name());
0043         item->setData(plugin.pluginId(), FacesModel::PluginIdRole);
0044         appendRow(item);
0045     }
0046 }
0047 
0048 QString FacesModel::pluginId(int row)
0049 {
0050     return data(index(row, 0), PluginIdRole).toString();
0051 }
0052 
0053 QHash<int, QByteArray> FacesModel::roleNames() const
0054 {
0055     QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
0056 
0057     roles[PluginIdRole] = "pluginId";
0058     return roles;
0059 }
0060 
0061 PresetsModel::PresetsModel(QObject *parent)
0062     : QStandardItemModel(parent)
0063 {
0064     reload();
0065 }
0066 
0067 void PresetsModel::reload()
0068 {
0069     clear();
0070     QList<KPluginMetaData> plugins =
0071         KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QString(), [](const KPluginMetaData &plugin) {
0072             return plugin.value(QStringLiteral("X-Plasma-RootPath")) == QStringLiteral("org.kde.plasma.systemmonitor");
0073         });
0074 
0075     QSet<QString> usedNames;
0076 
0077     // We iterate backwards because packages under ~/.local are listed first, while we want them last
0078     auto it = plugins.rbegin();
0079     for (; it != plugins.rend(); ++it) {
0080         const auto &plugin = *it;
0081         KPackage::Package p = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"), plugin.pluginId());
0082         auto metadata = p.metadata();
0083 
0084         QString baseName = metadata.name();
0085         QString name = baseName;
0086         int id = 0;
0087 
0088         while (usedNames.contains(name)) {
0089             name = baseName + QStringLiteral(" (") + QString::number(++id) + QStringLiteral(")");
0090         }
0091         usedNames << name;
0092 
0093         QStandardItem *item = new QStandardItem(baseName);
0094 
0095         // TODO config
0096         QVariantMap config;
0097 
0098         KConfigGroup configGroup(KSharedConfig::openConfig(p.filePath("config", QStringLiteral("faceproperties")), KConfig::SimpleConfig),
0099                                  QStringLiteral("Config"));
0100 
0101         const QStringList keys = configGroup.keyList();
0102         for (const QString &key : keys) {
0103             // all strings for now, type conversion happens in QML side when we have the config property map
0104             config.insert(key, configGroup.readEntry(key));
0105         }
0106 
0107         item->setData(plugin.pluginId(), PresetsModel::PluginIdRole);
0108         item->setData(config, PresetsModel::ConfigRole);
0109 
0110         item->setData(QFileInfo(p.metadata().fileName()).isWritable(), PresetsModel::WritableRole);
0111 
0112         appendRow(item);
0113     }
0114 }
0115 
0116 QHash<int, QByteArray> PresetsModel::roleNames() const
0117 {
0118     QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
0119 
0120     roles[PluginIdRole] = "pluginId";
0121     roles[ConfigRole] = "config";
0122     roles[WritableRole] = "writable";
0123     return roles;
0124 }
0125 
0126 SensorResolver::SensorResolver(SensorFaceController *_controller, const QJsonArray &_expected)
0127     : controller(_controller)
0128     , expected(_expected)
0129 {
0130 }
0131 
0132 void SensorResolver::execute()
0133 {
0134     std::transform(expected.begin(), expected.end(), std::back_inserter(queries), [this](const QJsonValue &entry) {
0135         auto query = new SensorQuery{entry.toString()};
0136         query->connect(query, &KSysGuard::SensorQuery::finished, controller, [this](SensorQuery *query) {
0137             query->sortByName();
0138             query->deleteLater();
0139 
0140             const auto ids = query->sensorIds();
0141             if (ids.isEmpty()) {
0142                 missing.append(query->path());
0143             } else {
0144                 std::transform(ids.begin(), ids.end(), std::back_inserter(found), [](const QString &id) {
0145                     return id;
0146                 });
0147             }
0148 
0149             queries.removeOne(query);
0150             if (queries.isEmpty()) {
0151                 callback(this);
0152             }
0153         });
0154         query->execute();
0155         return query;
0156     });
0157 }
0158 
0159 QList<QPair<QRegularExpression, QString>> KSysGuard::SensorFaceControllerPrivate::sensorIdReplacements;
0160 QRegularExpression SensorFaceControllerPrivate::oldDiskSensor = QRegularExpression(QStringLiteral("^disk\\/(.+)_\\(\\d+:\\d+\\)"));
0161 QRegularExpression SensorFaceControllerPrivate::oldPartitionSensor = QRegularExpression(QStringLiteral("^partitions(\\/.+)\\/"));
0162 
0163 SensorFaceControllerPrivate::SensorFaceControllerPrivate()
0164 {
0165     if (SensorFaceControllerPrivate::sensorIdReplacements.isEmpty()) {
0166         // A list of conversion rules to convert old sensor ids to new ones.
0167         // When loading, each regular expression tries to match to the sensor
0168         // id. If it matches, it will be be used to replace the sensor id with
0169         // the second argument.
0170         sensorIdReplacements = {
0171             {QRegularExpression(QStringLiteral("network/interfaces/(.*)")), QStringLiteral("network/\\1")},
0172             {QRegularExpression(QStringLiteral("network/all/receivedDataRate$")), QStringLiteral("network/all/download")},
0173             {QRegularExpression(QStringLiteral("network/all/sentDataRate$")), QStringLiteral("network/all/upload")},
0174             {QRegularExpression(QStringLiteral("network/all/totalReceivedData$")), QStringLiteral("network/all/totalDownload")},
0175             {QRegularExpression(QStringLiteral("network/all/totalSentData$")), QStringLiteral("network/all/totalUpload")},
0176             {QRegularExpression(QStringLiteral("(.*)/receiver/data$")), QStringLiteral("\\1/download")},
0177             {QRegularExpression(QStringLiteral("(.*)/transmitter/data$")), QStringLiteral("\\1/upload")},
0178             {QRegularExpression(QStringLiteral("(.*)/receiver/dataTotal$")), QStringLiteral("\\1/totalDownload")},
0179             {QRegularExpression(QStringLiteral("(.*)/transmitter/dataTotal$")), QStringLiteral("\\1/totalUpload")},
0180             {QRegularExpression(QStringLiteral("(.*)/Rate/rio")), QStringLiteral("\\1/read")},
0181             {QRegularExpression(QStringLiteral("(.*)/Rate/wio$")), QStringLiteral("\\1/write")},
0182             {QRegularExpression(QStringLiteral("(.*)/freespace$")), QStringLiteral("\\1/free")},
0183             {QRegularExpression(QStringLiteral("(.*)/filllevel$")), QStringLiteral("\\1/usedPercent")},
0184             {QRegularExpression(QStringLiteral("(.*)/usedspace$")), QStringLiteral("\\1/used")},
0185             {QRegularExpression(QStringLiteral("cpu/system/(.*)$")), QStringLiteral("cpu/all/\\1")},
0186             {QRegularExpression(QStringLiteral("cpu/(.*)/sys$")), QStringLiteral("cpu/\\1/system")},
0187             {QRegularExpression(QStringLiteral("cpu/(.*)/TotalLoad$")), QStringLiteral("cpu/\\1/usage")},
0188             {QRegularExpression(QStringLiteral("cpu/cpu(\\d+)/clock$")), QStringLiteral("cpu/cpu\\1/frequency")},
0189             {QRegularExpression(QStringLiteral("mem/(.*)level")), QStringLiteral("mem/\\1Percent")},
0190             {QRegularExpression(QStringLiteral("mem/physical/allocated")), QStringLiteral("memory/physical/used")},
0191             {QRegularExpression(QStringLiteral("mem/physical/available")), QStringLiteral("memory/physical/free")},
0192             {QRegularExpression(QStringLiteral("mem/physical/buf")), QStringLiteral("memory/physical/buffer")},
0193             {QRegularExpression(QStringLiteral("mem/physical/cached")), QStringLiteral("memory/physical/cache")},
0194             {QRegularExpression(QStringLiteral("^mem/(.*)")), QStringLiteral("memory/\\1")},
0195             {QRegularExpression(QStringLiteral("nvidia/(.*)/temperature$")), QStringLiteral("gpu/\\1/temperature")},
0196             {QRegularExpression(QStringLiteral("nvidia/(.*)/memoryClock$")), QStringLiteral("gpu/\\1/memoryFrequency")},
0197             {QRegularExpression(QStringLiteral("nvidia/(.*)/processorClock$")), QStringLiteral("gpu/\\1/coreFrequency")},
0198             {QRegularExpression(QStringLiteral("nvidia/(.*)/(memory|sharedMemory)$")), QStringLiteral("gpu/\\1/usedVram")},
0199             {QRegularExpression(QStringLiteral("nvidia/(.*)/(encoderUsage|decoderUsage)$")), QStringLiteral("gpu/\\1/usage")},
0200             {QRegularExpression(QStringLiteral("^(uptime|system/uptime/uptime)$")), QStringLiteral("os/system/uptime")},
0201         };
0202     }
0203 }
0204 
0205 QString SensorFaceControllerPrivate::replaceDiskId(const QString &entryName) const
0206 {
0207     const auto match = oldDiskSensor.match(entryName);
0208     if (!match.hasMatch()) {
0209         return entryName;
0210     }
0211     const QString device = match.captured(1);
0212     Solid::Predicate predicate(Solid::DeviceInterface::StorageAccess);
0213     predicate &= Solid::Predicate(Solid::DeviceInterface::Block, QStringLiteral("device"), QStringLiteral("/dev/%1").arg(device));
0214     const auto devices = Solid::Device::listFromQuery(predicate);
0215     if (devices.empty()) {
0216         return QString();
0217     }
0218     QString sensorId = entryName;
0219     const auto volume = devices[0].as<Solid::StorageVolume>();
0220     const QString id = volume->uuid().isEmpty() ? volume->label() : volume->uuid();
0221     return sensorId.replace(match.captured(0), QStringLiteral("disk/") + id);
0222 }
0223 
0224 QString SensorFaceControllerPrivate::replacePartitionId(const QString &entryName) const
0225 {
0226     const auto match = oldPartitionSensor.match(entryName);
0227     if (!match.hasMatch()) {
0228         return entryName;
0229     }
0230     QString sensorId = entryName;
0231 
0232     if (match.captured(1) == QLatin1String("/all")) {
0233         return sensorId.replace(match.captured(0), QStringLiteral("disk/all/"));
0234     }
0235 
0236     const QString filePath = match.captured(1) == QLatin1String("/__root__") ? QStringLiteral("/") : match.captured(1);
0237     const Solid::Predicate predicate(Solid::DeviceInterface::StorageAccess, QStringLiteral("filePath"), filePath);
0238     const auto devices = Solid::Device::listFromQuery(predicate);
0239     if (devices.empty()) {
0240         return entryName;
0241     }
0242     const auto volume = devices[0].as<Solid::StorageVolume>();
0243     const QString id = volume->uuid().isEmpty() ? volume->label() : volume->uuid();
0244     return sensorId.replace(match.captured(0), QStringLiteral("disk/%1/").arg(id));
0245 }
0246 
0247 QJsonArray SensorFaceControllerPrivate::readSensors(const KConfigGroup &read, const QString &entryName)
0248 {
0249     auto original = QJsonDocument::fromJson(read.readEntry(entryName, QString()).toUtf8()).array();
0250     QJsonArray newSensors;
0251     for (auto entry : original) {
0252         QString sensorId = entry.toString();
0253         for (auto replacement : std::as_const(sensorIdReplacements)) {
0254             auto match = replacement.first.match(sensorId);
0255             if (match.hasMatch()) {
0256                 sensorId.replace(replacement.first, replacement.second);
0257             }
0258         }
0259         sensorId = replaceDiskId(sensorId);
0260         sensorId = replacePartitionId(sensorId);
0261         newSensors.append(sensorId);
0262     }
0263 
0264     return newSensors;
0265 }
0266 
0267 QJsonArray SensorFaceControllerPrivate::readAndUpdateSensors(KConfigGroup &config, const QString &entryName)
0268 {
0269     auto original = QJsonDocument::fromJson(config.readEntry(entryName, QString()).toUtf8()).array();
0270 
0271     const KConfigGroup &group = config;
0272     auto newSensors = readSensors(group, entryName);
0273 
0274     if (newSensors != original) {
0275         config.writeEntry(entryName, QJsonDocument(newSensors).toJson(QJsonDocument::Compact));
0276     }
0277 
0278     return newSensors;
0279 }
0280 
0281 void SensorFaceControllerPrivate::resolveSensors(const QJsonArray &partialEntries, std::function<void(SensorResolver *)> callback)
0282 {
0283     auto resolver = new SensorResolver{q, partialEntries};
0284     resolver->callback = [this, callback](SensorResolver *resolver) {
0285         callback(resolver);
0286 
0287         if (!resolver->missing.isEmpty()) {
0288             for (const auto &entry : std::as_const(resolver->missing)) {
0289                 missingSensors.append(entry);
0290             }
0291             Q_EMIT q->missingSensorsChanged();
0292         }
0293 
0294         delete resolver;
0295     };
0296     resolver->execute();
0297 }
0298 
0299 SensorFace *SensorFaceControllerPrivate::createGui(const QString &qmlPath)
0300 {
0301     QQmlComponent *component = new QQmlComponent(engine, qmlPath, nullptr);
0302     // TODO: eventually support async  components? (only useful for qml files from http, we probably don't want that)
0303     if (component->status() != QQmlComponent::Ready) {
0304         qCritical() << "Error creating component:";
0305         for (auto err : component->errors()) {
0306             qWarning() << err.toString();
0307         }
0308         component->deleteLater();
0309         return nullptr;
0310     }
0311 
0312     QQmlContext *context = new QQmlContext(engine);
0313     context->setContextObject(contextObj);
0314     QObject *guiObject = component->beginCreate(context);
0315     SensorFace *gui = qobject_cast<SensorFace *>(guiObject);
0316     if (!gui) {
0317         qWarning() << "ERROR: QML gui" << guiObject << "not a SensorFace instance";
0318         guiObject->deleteLater();
0319         context->deleteLater();
0320         return nullptr;
0321     }
0322     context->setParent(gui);
0323 
0324     gui->setController(q);
0325     gui->setParent(q);
0326 
0327     component->completeCreate();
0328 
0329     component->deleteLater();
0330     return gui;
0331 }
0332 
0333 QQuickItem *SensorFaceControllerPrivate::createConfigUi(const QString &file, const QVariantMap &initialProperties)
0334 {
0335     QQmlComponent *component = new QQmlComponent(engine, file, nullptr);
0336     // TODO: eventually support async  components? (only useful for qml files from http, we probably don't want that)
0337     if (component->status() != QQmlComponent::Ready) {
0338         qCritical() << "Error creating component:";
0339         for (auto err : component->errors()) {
0340             qWarning() << err.toString();
0341         }
0342         component->deleteLater();
0343         return nullptr;
0344     }
0345 
0346     QQmlContext *context = new QQmlContext(engine);
0347     context->setContextObject(contextObj);
0348     QObject *guiObject = component->createWithInitialProperties(initialProperties, context);
0349     QQuickItem *gui = qobject_cast<QQuickItem *>(guiObject);
0350     Q_ASSERT(gui);
0351     context->setParent(gui);
0352     gui->setParent(q);
0353 
0354     component->deleteLater();
0355 
0356     return gui;
0357 }
0358 
0359 SensorFaceController::SensorFaceController(KConfigGroup &config, QQmlEngine *engine)
0360     : QObject(engine)
0361     , d(std::make_unique<SensorFaceControllerPrivate>())
0362 {
0363     d->q = this;
0364     d->configGroup = config;
0365     d->appearanceGroup = KConfigGroup(&config, "Appearance");
0366     d->sensorsGroup = KConfigGroup(&config, "Sensors");
0367     d->colorsGroup = KConfigGroup(&config, "SensorColors");
0368     d->labelsGroup = KConfigGroup(&config, "SensorLabels");
0369     d->engine = engine;
0370     d->syncTimer = new QTimer(this);
0371     d->syncTimer->setSingleShot(true);
0372     d->syncTimer->setInterval(5000);
0373     connect(d->syncTimer, &QTimer::timeout, this, [this]() {
0374         if (!d->shouldSync) {
0375             return;
0376         }
0377         d->appearanceGroup.sync();
0378         d->sensorsGroup.sync();
0379     });
0380 
0381     d->contextObj = new KLocalizedContext(this);
0382 
0383     d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("totalSensors")), [this](SensorResolver *resolver) {
0384         d->totalSensors = resolver->found;
0385         Q_EMIT totalSensorsChanged();
0386     });
0387     d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("lowPrioritySensorIds")), [this](SensorResolver *resolver) {
0388         d->lowPrioritySensorIds = resolver->found;
0389         Q_EMIT lowPrioritySensorIdsChanged();
0390     });
0391     d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("highPrioritySensorIds")), [this](SensorResolver *resolver) {
0392         d->highPrioritySensorIds = resolver->found;
0393         Q_EMIT highPrioritySensorIdsChanged();
0394     });
0395 
0396     setFaceId(d->appearanceGroup.readEntry("chartFace", QStringLiteral("org.kde.ksysguard.piechart")));
0397 }
0398 
0399 SensorFaceController::~SensorFaceController()
0400 {
0401     if (!d->faceProperties.isValid()) {
0402         return;
0403     }
0404 
0405     auto forceSave = d->faceProperties.readEntry(QStringLiteral("ForceSaveOnDestroy"), false);
0406     if (!forceSave) {
0407         if (!d->shouldSync) {
0408             // If we should not sync automatically, clear all changes before we
0409             // destroy the config objects, otherwise they will be written during
0410             // destruction.
0411             d->appearanceGroup.markAsClean();
0412             d->colorsGroup.markAsClean();
0413             d->labelsGroup.markAsClean();
0414             if (d->faceConfigLoader && d->faceConfigLoader->isSaveNeeded()) {
0415                 d->faceConfigLoader->load();
0416             }
0417         }
0418     } else {
0419         d->faceConfigLoader->save();
0420     }
0421 }
0422 
0423 KConfigGroup KSysGuard::SensorFaceController::configGroup() const
0424 {
0425     return d->configGroup;
0426 }
0427 
0428 QString SensorFaceController::title() const
0429 {
0430     // both Title and title can exist to allow i18n of Title
0431     if (d->appearanceGroup.hasKey("title")) {
0432         return d->appearanceGroup.readEntry("title");
0433     } else {
0434         // if neither exist fall back to name
0435         return d->appearanceGroup.readEntry("Title", i18n("System Monitor Sensor"));
0436     }
0437 }
0438 
0439 void SensorFaceController::setTitle(const QString &title)
0440 {
0441     if (title == SensorFaceController::title()) {
0442         return;
0443     }
0444 
0445     d->appearanceGroup.writeEntry("title", title);
0446     d->syncTimer->start();
0447 
0448     Q_EMIT titleChanged();
0449 }
0450 
0451 bool SensorFaceController::showTitle() const
0452 {
0453     return d->appearanceGroup.readEntry("showTitle", true);
0454 }
0455 
0456 void SensorFaceController::setShowTitle(bool show)
0457 {
0458     if (show == showTitle()) {
0459         return;
0460     }
0461 
0462     d->appearanceGroup.writeEntry("showTitle", show);
0463     d->syncTimer->start();
0464 
0465     Q_EMIT showTitleChanged();
0466 }
0467 
0468 QJsonArray SensorFaceController::totalSensors() const
0469 {
0470     return d->totalSensors;
0471 }
0472 
0473 void SensorFaceController::setTotalSensors(const QJsonArray &totalSensors)
0474 {
0475     if (totalSensors == d->totalSensors) {
0476         return;
0477     }
0478     const auto currentEntry = QJsonDocument::fromJson(d->sensorsGroup.readEntry("totalSensors").toUtf8()).array();
0479     if (totalSensors == currentEntry) {
0480         return;
0481     }
0482     d->sensorsGroup.writeEntry("totalSensors", QJsonDocument(totalSensors).toJson(QJsonDocument::Compact));
0483     // Until we have resolved
0484     d->totalSensors = totalSensors;
0485     d->syncTimer->start();
0486     Q_EMIT totalSensorsChanged();
0487     d->resolveSensors(totalSensors, [this](SensorResolver *resolver) {
0488         if (resolver->found == d->totalSensors) {
0489             return;
0490         }
0491         d->totalSensors = resolver->found;
0492         Q_EMIT totalSensorsChanged();
0493     });
0494 }
0495 
0496 QJsonArray SensorFaceController::highPrioritySensorIds() const
0497 {
0498     return d->highPrioritySensorIds;
0499 }
0500 
0501 void SensorFaceController::setHighPrioritySensorIds(const QJsonArray &highPrioritySensorIds)
0502 {
0503     if (highPrioritySensorIds == d->highPrioritySensorIds) {
0504         return;
0505     }
0506     const auto currentEntry = QJsonDocument::fromJson(d->sensorsGroup.readEntry("highPrioritySensorIds").toUtf8()).array();
0507     if (highPrioritySensorIds == currentEntry) {
0508         return;
0509     }
0510     d->sensorsGroup.writeEntry("highPrioritySensorIds", QJsonDocument(highPrioritySensorIds).toJson(QJsonDocument::Compact));
0511     // Until we have resolved
0512     d->syncTimer->start();
0513     d->highPrioritySensorIds = highPrioritySensorIds;
0514     Q_EMIT highPrioritySensorIdsChanged();
0515     d->resolveSensors(highPrioritySensorIds, [this](SensorResolver *resolver) {
0516         if (resolver->found == d->highPrioritySensorIds) {
0517             return;
0518         }
0519         d->highPrioritySensorIds = resolver->found;
0520         Q_EMIT highPrioritySensorIdsChanged();
0521     });
0522 }
0523 
0524 QJsonArray KSysGuard::SensorFaceController::missingSensors() const
0525 {
0526     return d->missingSensors;
0527 }
0528 
0529 QVariantMap SensorFaceController::sensorColors() const
0530 {
0531     QVariantMap colors;
0532     for (const auto &key : d->colorsGroup.keyList()) {
0533         QColor color = d->colorsGroup.readEntry(key, QColor());
0534 
0535         if (color.isValid()) {
0536             colors[key] = color;
0537         }
0538     }
0539     return colors;
0540 }
0541 
0542 void SensorFaceController::setSensorColors(const QVariantMap &colors)
0543 {
0544     if (colors == this->sensorColors()) {
0545         return;
0546     }
0547 
0548     d->colorsGroup.deleteGroup();
0549     d->colorsGroup = KConfigGroup(&d->configGroup, "SensorColors");
0550 
0551     auto it = colors.constBegin();
0552     for (; it != colors.constEnd(); ++it) {
0553         d->colorsGroup.writeEntry(it.key(), it.value());
0554     }
0555 
0556     d->syncTimer->start();
0557     Q_EMIT sensorColorsChanged();
0558 }
0559 
0560 QVariantMap SensorFaceController::sensorLabels() const
0561 {
0562     QVariantMap labels;
0563     for (const auto &key : d->labelsGroup.keyList()) {
0564         labels[key] = d->labelsGroup.readEntry(key);
0565     }
0566     return labels;
0567 }
0568 
0569 void SensorFaceController::setSensorLabels(const QVariantMap &labels)
0570 {
0571     if (labels == this->sensorLabels()) {
0572         return;
0573     }
0574 
0575     d->labelsGroup.deleteGroup();
0576     d->labelsGroup = KConfigGroup(&d->configGroup, "SensorLabels");
0577 
0578     for (auto it = labels.cbegin(); it != labels.cend(); ++it) {
0579         const auto label = it.value().toString();
0580         if (!label.isEmpty()) {
0581             d->labelsGroup.writeEntry(it.key(), label);
0582         }
0583     }
0584 
0585     d->syncTimer->start();
0586     Q_EMIT sensorLabelsChanged();
0587 }
0588 
0589 QJsonArray SensorFaceController::lowPrioritySensorIds() const
0590 {
0591     return d->lowPrioritySensorIds;
0592 }
0593 
0594 void SensorFaceController::setLowPrioritySensorIds(const QJsonArray &lowPrioritySensorIds)
0595 {
0596     if (lowPrioritySensorIds == d->lowPrioritySensorIds) {
0597         return;
0598     }
0599     const auto currentEntry = QJsonDocument::fromJson(d->sensorsGroup.readEntry("lowPrioritySensorIds").toUtf8()).array();
0600     if (lowPrioritySensorIds == currentEntry) {
0601         return;
0602     }
0603     d->sensorsGroup.writeEntry("lowPrioritySensorIds", QJsonDocument(lowPrioritySensorIds).toJson(QJsonDocument::Compact));
0604     // Until we have resolved
0605     d->lowPrioritySensorIds = lowPrioritySensorIds;
0606     d->syncTimer->start();
0607     Q_EMIT lowPrioritySensorIdsChanged();
0608     d->resolveSensors(lowPrioritySensorIds, [this](SensorResolver *resolver) {
0609         if (resolver->found == d->lowPrioritySensorIds) {
0610             return;
0611         }
0612         d->lowPrioritySensorIds = resolver->found;
0613         Q_EMIT lowPrioritySensorIdsChanged();
0614     });
0615 }
0616 
0617 int SensorFaceController::updateRateLimit() const
0618 {
0619     return d->appearanceGroup.readEntry<int>(QStringLiteral("updateRateLimit"), 0);
0620 }
0621 
0622 void SensorFaceController::setUpdateRateLimit(int limit)
0623 {
0624     if (limit == updateRateLimit()) {
0625         return;
0626     }
0627 
0628     d->appearanceGroup.writeEntry("updateRateLimit", limit);
0629     d->syncTimer->start();
0630 
0631     Q_EMIT updateRateLimitChanged();
0632 }
0633 
0634 // from face config, immutable by the user
0635 QString SensorFaceController::name() const
0636 {
0637     return d->facePackage.metadata().name();
0638 }
0639 
0640 const QString SensorFaceController::icon() const
0641 {
0642     return d->facePackage.metadata().iconName();
0643 }
0644 
0645 bool SensorFaceController::supportsSensorsColors() const
0646 {
0647     if (!d->faceProperties.isValid()) {
0648         return false;
0649     }
0650 
0651     return d->faceProperties.readEntry("SupportsSensorsColors", false);
0652 }
0653 
0654 bool SensorFaceController::supportsTotalSensors() const
0655 {
0656     if (!d->faceProperties.isValid()) {
0657         return false;
0658     }
0659 
0660     return d->faceProperties.readEntry("SupportsTotalSensors", false);
0661 }
0662 
0663 bool SensorFaceController::supportsLowPrioritySensors() const
0664 {
0665     if (!d->faceProperties.isValid()) {
0666         return false;
0667     }
0668 
0669     return d->faceProperties.readEntry("SupportsLowPrioritySensors", false);
0670 }
0671 
0672 int SensorFaceController::maxTotalSensors() const
0673 {
0674     if (!d->faceProperties.isValid()) {
0675         return 1;
0676     }
0677 
0678     return d->faceProperties.readEntry("MaxTotalSensors", 1);
0679 }
0680 
0681 void SensorFaceController::setFaceId(const QString &face)
0682 {
0683     if (d->faceId == face) {
0684         return;
0685     }
0686 
0687     if (d->fullRepresentation) {
0688         d->fullRepresentation->deleteLater();
0689         d->fullRepresentation.clear();
0690     }
0691     if (d->compactRepresentation) {
0692         d->compactRepresentation->deleteLater();
0693         d->compactRepresentation.clear();
0694     }
0695     if (d->faceConfigUi) {
0696         d->faceConfigUi->deleteLater();
0697         d->faceConfigUi.clear();
0698     }
0699 
0700     d->faceId = face;
0701 
0702     d->facePackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("KSysguard/SensorFace"), face);
0703 
0704     if (d->faceConfiguration) {
0705         d->faceConfiguration->deleteLater();
0706         d->faceConfiguration = nullptr;
0707     }
0708     if (d->faceConfigLoader) {
0709         d->faceConfigLoader->deleteLater();
0710         d->faceConfigLoader = nullptr;
0711     }
0712 
0713     if (!d->facePackage.isValid()) {
0714         Q_EMIT faceIdChanged();
0715         return;
0716     }
0717 
0718     d->contextObj->setTranslationDomain(QLatin1String("ksysguard_face_") + face);
0719 
0720     d->faceProperties = KConfigGroup(KSharedConfig::openConfig(d->facePackage.filePath("FaceProperties"), KConfig::SimpleConfig), QStringLiteral("Config"));
0721 
0722     if (!d->faceProperties.isValid()) {
0723         Q_EMIT faceIdChanged();
0724         return;
0725     }
0726 
0727     reloadFaceConfiguration();
0728 
0729     d->appearanceGroup.writeEntry("chartFace", face);
0730     d->syncTimer->start();
0731     Q_EMIT faceIdChanged();
0732     return;
0733 }
0734 
0735 QString SensorFaceController::faceId() const
0736 {
0737     return d->faceId;
0738 }
0739 
0740 KConfigPropertyMap *SensorFaceController::faceConfiguration() const
0741 {
0742     return d->faceConfiguration;
0743 }
0744 
0745 QQuickItem *SensorFaceController::compactRepresentation()
0746 {
0747     if (!d->facePackage.isValid()) {
0748         return nullptr;
0749     } else if (d->compactRepresentation) {
0750         return d->compactRepresentation;
0751     }
0752 
0753     d->compactRepresentation = d->createGui(d->facePackage.filePath("ui", QStringLiteral("CompactRepresentation.qml")));
0754     return d->compactRepresentation;
0755 }
0756 
0757 QQuickItem *SensorFaceController::fullRepresentation()
0758 {
0759     if (!d->facePackage.isValid()) {
0760         return nullptr;
0761     } else if (d->fullRepresentation) {
0762         return d->fullRepresentation;
0763     }
0764 
0765     d->fullRepresentation = d->createGui(d->facePackage.filePath("ui", QStringLiteral("FullRepresentation.qml")));
0766     return d->fullRepresentation;
0767 }
0768 
0769 QQuickItem *SensorFaceController::faceConfigUi()
0770 {
0771     if (!d->facePackage.isValid()) {
0772         return nullptr;
0773     } else if (d->faceConfigUi) {
0774         return d->faceConfigUi;
0775     }
0776 
0777     const QString filePath = d->facePackage.filePath("ui", QStringLiteral("Config.qml"));
0778 
0779     if (filePath.isEmpty()) {
0780         return nullptr;
0781     }
0782 
0783     d->faceConfigUi = d->createConfigUi(QStringLiteral(":/FaceDetailsConfig.qml"),
0784                                         {{QStringLiteral("controller"), QVariant::fromValue(this)}, {QStringLiteral("source"), QUrl::fromLocalFile(filePath)}});
0785 
0786     if (d->faceConfigUi && !d->faceConfigUi->property("item").value<QQuickItem *>()) {
0787         d->faceConfigUi->deleteLater();
0788         d->faceConfigUi.clear();
0789     }
0790     return d->faceConfigUi;
0791 }
0792 
0793 QQuickItem *SensorFaceController::appearanceConfigUi()
0794 {
0795     if (d->appearanceConfigUi) {
0796         return d->appearanceConfigUi;
0797     }
0798 
0799     d->appearanceConfigUi = d->createConfigUi(QStringLiteral(":/ConfigAppearance.qml"), {{QStringLiteral("controller"), QVariant::fromValue(this)}});
0800 
0801     return d->appearanceConfigUi;
0802 }
0803 
0804 QQuickItem *SensorFaceController::sensorsConfigUi()
0805 {
0806     if (d->sensorsConfigUi) {
0807         return d->sensorsConfigUi;
0808     }
0809 
0810     if (d->faceProperties.isValid() && d->faceProperties.readEntry("SupportsSensors", true)) {
0811         d->sensorsConfigUi = d->createConfigUi(QStringLiteral(":/ConfigSensors.qml"), {{QStringLiteral("controller"), QVariant::fromValue(this)}});
0812     } else {
0813         d->sensorsConfigUi = new QQuickItem;
0814     }
0815     return d->sensorsConfigUi;
0816 }
0817 
0818 QAbstractItemModel *SensorFaceController::availableFacesModel()
0819 {
0820     if (d->availableFacesModel) {
0821         return d->availableFacesModel;
0822     }
0823 
0824     d->availableFacesModel = new FacesModel(this);
0825     return d->availableFacesModel;
0826 }
0827 
0828 QAbstractItemModel *SensorFaceController::availablePresetsModel()
0829 {
0830     if (d->availablePresetsModel) {
0831         return d->availablePresetsModel;
0832     }
0833 
0834     d->availablePresetsModel = new PresetsModel(this);
0835 
0836     return d->availablePresetsModel;
0837 }
0838 
0839 void SensorFaceController::reloadConfig()
0840 {
0841     if (d->faceConfigLoader) {
0842         d->faceConfigLoader->load();
0843     }
0844 
0845     d->missingSensors = QJsonArray{};
0846     Q_EMIT missingSensorsChanged();
0847 
0848     d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("totalSensors")), [this](SensorResolver *resolver) {
0849         d->totalSensors = resolver->found;
0850         Q_EMIT totalSensorsChanged();
0851     });
0852     d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("lowPrioritySensorIds")), [this](SensorResolver *resolver) {
0853         d->lowPrioritySensorIds = resolver->found;
0854         Q_EMIT lowPrioritySensorIdsChanged();
0855     });
0856     d->resolveSensors(d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("highPrioritySensorIds")), [this](SensorResolver *resolver) {
0857         d->highPrioritySensorIds = resolver->found;
0858         Q_EMIT highPrioritySensorIdsChanged();
0859     });
0860 
0861     // Force to re-read all the values
0862     setFaceId(d->appearanceGroup.readEntry("chartFace", QStringLiteral("org.kde.ksysguard.textonly")));
0863     Q_EMIT titleChanged();
0864     Q_EMIT sensorColorsChanged();
0865     Q_EMIT sensorLabelsChanged();
0866     Q_EMIT showTitleChanged();
0867     Q_EMIT updateRateLimitChanged();
0868 }
0869 
0870 void SensorFaceController::loadPreset(const QString &preset)
0871 {
0872     if (preset.isEmpty()) {
0873         return;
0874     }
0875 
0876     auto presetPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"));
0877 
0878     presetPackage.setPath(preset);
0879 
0880     if (!presetPackage.isValid()) {
0881         return;
0882     }
0883 
0884     if (presetPackage.metadata().value(QStringLiteral("X-Plasma-RootPath")) != QStringLiteral("org.kde.plasma.systemmonitor")) {
0885         return;
0886     }
0887 
0888     auto c = KSharedConfig::openConfig(presetPackage.filePath("config", QStringLiteral("faceproperties")), KConfig::SimpleConfig);
0889     const KConfigGroup presetGroup(c, QStringLiteral("Config"));
0890     const KConfigGroup colorsGroup(c, QStringLiteral("SensorColors"));
0891 
0892     // Load the title
0893     setTitle(presetPackage.metadata().name());
0894 
0895     // Remove the "custom" value from presets models
0896     if (d->availablePresetsModel && d->availablePresetsModel->data(d->availablePresetsModel->index(0, 0), PresetsModel::PluginIdRole).toString().isEmpty()) {
0897         d->availablePresetsModel->removeRow(0);
0898     }
0899 
0900     setTotalSensors(d->readSensors(presetGroup, QStringLiteral("totalSensors")));
0901     setHighPrioritySensorIds(d->readSensors(presetGroup, QStringLiteral("highPrioritySensorIds")));
0902     setLowPrioritySensorIds(d->readSensors(presetGroup, QStringLiteral("lowPrioritySensorIds")));
0903 
0904     setFaceId(presetGroup.readEntry(QStringLiteral("chartFace"), QStringLiteral("org.kde.ksysguard.piechart")));
0905 
0906     colorsGroup.copyTo(&d->colorsGroup);
0907     Q_EMIT sensorColorsChanged();
0908 
0909     if (d->faceConfigLoader) {
0910         KConfigGroup presetGroup(KSharedConfig::openConfig(presetPackage.filePath("FaceProperties"), KConfig::SimpleConfig), QStringLiteral("FaceConfig"));
0911 
0912         for (const QString &key : presetGroup.keyList()) {
0913             KConfigSkeletonItem *item = d->faceConfigLoader->findItemByName(key);
0914             if (item) {
0915                 if (item->property().type() == QVariant::StringList) {
0916                     item->setProperty(presetGroup.readEntry(key, QStringList()));
0917                 } else {
0918                     item->setProperty(presetGroup.readEntry(key));
0919                 }
0920                 d->faceConfigLoader->save();
0921                 d->faceConfigLoader->read();
0922             }
0923         }
0924     }
0925 }
0926 
0927 void SensorFaceController::savePreset()
0928 {
0929     QString pluginName = QStringLiteral("org.kde.plasma.systemmonitor.") + title().simplified().replace(QLatin1Char(' '), QStringLiteral("")).toLower();
0930     int suffix = 0;
0931 
0932     auto presetPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"));
0933 
0934     presetPackage.setPath(pluginName);
0935     if (presetPackage.isValid()) {
0936         do {
0937             presetPackage.setPath(QString());
0938             presetPackage.setPath(pluginName + QString::number(++suffix));
0939         } while (presetPackage.isValid());
0940 
0941         pluginName += QString::number(suffix);
0942     }
0943 
0944     QTemporaryDir dir;
0945     if (!dir.isValid()) {
0946         return;
0947     }
0948 
0949     // First write "new style" plugin JSON file.
0950     QJsonDocument json;
0951     json.setObject({
0952         {"KPlugin",
0953          QJsonObject{
0954              {"Id", pluginName},
0955              {"Name", title()},
0956              {"Icon", "ksysguardd"},
0957              {"Category", "System Information"},
0958              {"License", "LGPL 2.1+"},
0959              {"EnabledByDefault", true},
0960              {"Version", "0.1"},
0961          }},
0962         {"X-Plasma-API", "declarativeappletscript"},
0963         {"X-Plasma-MainScript", "ui/main.qml"},
0964         {"X-Plasma-Provides", "org.kde.plasma.systemmonitor"},
0965         {"X-Plasma-RootPath", "org.kde.plasma.systemmonitor"},
0966         {"KPackageStructure", "Plasma/Applet"},
0967     });
0968 
0969     if (QFile file{dir.path() % QStringLiteral("/metadata.json")}; file.open(QIODevice::WriteOnly)) {
0970         file.write(json.toJson());
0971     } else {
0972         qWarning() << "Could not write metadata.json file for preset" << title();
0973     }
0974 
0975     QDir subDir(dir.path());
0976     subDir.mkpath(QStringLiteral("contents/config"));
0977     KConfig faceConfig(subDir.path() % QStringLiteral("/contents/config/faceproperties"));
0978 
0979     KConfigGroup configGroup(&faceConfig, "Config");
0980 
0981     auto sensors = d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("totalSensors"));
0982     configGroup.writeEntry(QStringLiteral("totalSensors"), QJsonDocument(sensors).toJson(QJsonDocument::Compact));
0983     sensors = d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("highPrioritySensorIds"));
0984     configGroup.writeEntry(QStringLiteral("highPrioritySensorIds"), QJsonDocument(sensors).toJson(QJsonDocument::Compact));
0985     sensors = d->readAndUpdateSensors(d->sensorsGroup, QStringLiteral("lowPrioritySensorIds"));
0986     configGroup.writeEntry(QStringLiteral("lowPrioritySensorIds"), QJsonDocument(sensors).toJson(QJsonDocument::Compact));
0987     configGroup.writeEntry(QStringLiteral("chartFace"), faceId());
0988 
0989     KConfigGroup colorsGroup(&faceConfig, "SensorColors");
0990     d->colorsGroup.copyTo(&colorsGroup);
0991     colorsGroup.sync();
0992 
0993     configGroup = KConfigGroup(&faceConfig, "FaceConfig");
0994     if (d->faceConfigLoader) {
0995         const auto &items = d->faceConfigLoader->items();
0996         for (KConfigSkeletonItem *item : items) {
0997             configGroup.writeEntry(item->key(), item->property());
0998         }
0999     }
1000     configGroup.sync();
1001 
1002     auto *job = KPackage::PackageJob::install(QStringLiteral("Plasma/Applet"), dir.path());
1003     connect(job, &KJob::finished, this, [this]() {
1004         d->availablePresetsModel->reload();
1005     });
1006 }
1007 
1008 void SensorFaceController::uninstallPreset(const QString &pluginId)
1009 {
1010     auto presetPackage = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet"), pluginId);
1011 
1012     if (presetPackage.metadata().value(QStringLiteral("X-Plasma-RootPath")) != QStringLiteral("org.kde.plasma.systemmonitor")) {
1013         return;
1014     }
1015 
1016     QDir root(presetPackage.path());
1017     root.cdUp();
1018     auto *job = KPackage::PackageJob::uninstall(QStringLiteral("Plasma/Applet"), pluginId, root.path());
1019 
1020     connect(job, &KJob::finished, this, [this]() {
1021         d->availablePresetsModel->reload();
1022     });
1023 }
1024 
1025 bool SensorFaceController::shouldSync() const
1026 {
1027     return d->shouldSync;
1028 }
1029 
1030 void SensorFaceController::setShouldSync(bool sync)
1031 {
1032     d->shouldSync = sync;
1033     if (!d->shouldSync && d->syncTimer->isActive()) {
1034         d->syncTimer->stop();
1035     }
1036 }
1037 
1038 void SensorFaceController::reloadFaceConfiguration()
1039 {
1040     const QString xmlPath = d->facePackage.filePath("mainconfigxml");
1041 
1042     if (!xmlPath.isEmpty()) {
1043         QFile file(xmlPath);
1044         KConfigGroup cg(&d->configGroup, d->faceId);
1045 
1046         if (d->faceConfigLoader) {
1047             delete d->faceConfigLoader;
1048         }
1049 
1050         if (d->faceConfiguration) {
1051             delete d->faceConfiguration;
1052         }
1053 
1054         d->faceConfigLoader = new KConfigLoader(cg, &file, this);
1055         d->faceConfiguration = new KConfigPropertyMap(d->faceConfigLoader, this);
1056         connect(d->faceConfiguration, &KConfigPropertyMap::valueChanged, this, [this](const QString &key) {
1057             auto item = d->faceConfigLoader->findItemByName(key);
1058             if (item) {
1059                 item->writeConfig(d->faceConfigLoader->config());
1060             }
1061         });
1062 
1063         Q_EMIT faceConfigurationChanged();
1064     }
1065 }
1066 
1067 void KSysGuard::SensorFaceController::replaceSensors(const QString &from, const QString &to)
1068 {
1069     auto replaceSensors = [this, from, to](const QString &configEntry) {
1070         auto array = QJsonDocument::fromJson(d->sensorsGroup.readEntry(configEntry, QString()).toUtf8()).array();
1071         for (auto itr = array.begin(); itr != array.end(); ++itr) {
1072             if (itr->toString() == from) {
1073                 *itr = QJsonValue(to);
1074             }
1075         }
1076         return QJsonDocument(array).toJson(QJsonDocument::Compact);
1077     };
1078 
1079     d->sensorsGroup.writeEntry("totalSensors", replaceSensors(QStringLiteral("totalSensors")));
1080     d->sensorsGroup.writeEntry("highPrioritySensorIds", replaceSensors(QStringLiteral("highPrioritySensorIds")));
1081     d->sensorsGroup.writeEntry("lowPrioritySensorIds", replaceSensors(QStringLiteral("lowPrioritySensorIds")));
1082 
1083     if (d->shouldSync) {
1084         d->sensorsGroup.sync();
1085     }
1086 }
1087 
1088 #include "moc_SensorFaceController.cpp"