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

0001 /*
0002     SPDX-FileCopyrightText: 2019 Eike Hein <hein@kde.org>
0003     SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "SensorTreeModel.h"
0009 
0010 #include <optional>
0011 
0012 #include <KLocalizedString>
0013 #include <QCollator>
0014 #include <QDebug>
0015 #include <QMetaEnum>
0016 #include <QMimeData>
0017 #include <QRegularExpression>
0018 
0019 #include "formatter/Formatter.h"
0020 #include "systemstats/SensorInfo.h"
0021 
0022 #include "Sensor.h"
0023 #include "SensorDaemonInterface_p.h"
0024 #include "SensorGroup_p.h"
0025 #include "SensorQuery.h"
0026 
0027 using namespace KSysGuard;
0028 
0029 struct Compare {
0030     bool operator()(const QString &first, const QString &second) const
0031     {
0032         // Place "All" object at the top.
0033         if (first == QLatin1String("all") && first != second) {
0034             return true;
0035         }
0036 
0037         if (second == QLatin1String("all")) {
0038             return false;
0039         }
0040 
0041         if (!collator) {
0042             collator = QCollator();
0043             collator->setNumericMode(true);
0044             collator->setCaseSensitivity(Qt::CaseInsensitive);
0045         }
0046 
0047         return collator->compare(first, second) < 0;
0048     }
0049 
0050     // This uses thread-local storage because QCollator may not be thread safe.
0051     // We store it in an optional to make sure we can initialize it above.
0052     thread_local static std::optional<QCollator> collator;
0053 };
0054 
0055 thread_local std::optional<QCollator> Compare::collator = std::nullopt;
0056 
0057 struct Q_DECL_HIDDEN SensorTreeItem {
0058     SensorTreeItem *parent = nullptr;
0059     QString segment;
0060     std::map<QString, std::unique_ptr<SensorTreeItem>, Compare> children;
0061 
0062     inline int indexOf(const QString &segment) const
0063     {
0064         auto itr = std::find_if(children.cbegin(), children.cend(), [segment](const auto &item) {
0065             return item.second->segment == segment;
0066         });
0067 
0068         if (itr != children.cend()) {
0069             return std::distance(children.cbegin(), itr);
0070         }
0071 
0072         return -1;
0073     }
0074 
0075     inline SensorTreeItem *itemAt(std::size_t index) const
0076     {
0077         if (index >= children.size()) {
0078             return nullptr;
0079         }
0080 
0081         auto itr = children.cbegin();
0082         std::advance(itr, index);
0083         return itr->second.get();
0084     }
0085 };
0086 
0087 class Q_DECL_HIDDEN SensorTreeModel::Private
0088 {
0089 public:
0090     Private(SensorTreeModel *qq)
0091         : rootItem(new SensorTreeItem)
0092         , q(qq)
0093     {
0094         m_sensorGroup = new SensorGroup;
0095     }
0096     ~Private()
0097     {
0098         delete rootItem;
0099         delete m_sensorGroup;
0100     }
0101 
0102     SensorTreeItem *rootItem;
0103     QHash<SensorTreeItem *, SensorInfo> sensorInfos;
0104 
0105     void addSensor(const QString &sensorId, const SensorInfo &info);
0106     void removeSensor(const QString &sensorId);
0107 
0108     QString sensorId(const QModelIndex &index);
0109 
0110     SensorTreeItem *find(const QString &sensorId);
0111 
0112     SensorGroup *m_sensorGroup;
0113 
0114     QHash<QString, int> m_groupMatches;
0115 
0116 private:
0117     SensorTreeModel *q;
0118 };
0119 
0120 void SensorTreeModel::Private::addSensor(const QString &sensorId, const SensorInfo &info)
0121 {
0122     const QStringList &segments = sensorId.split(QLatin1Char('/'));
0123 
0124     if (!segments.count() || segments.at(0).isEmpty()) {
0125         qDebug() << "Rejecting sensor" << sensorId << "- sensor id is not well-formed.";
0126         return;
0127     }
0128 
0129     QString sensorIdExpr = m_sensorGroup->groupRegexForId(sensorId);
0130 
0131     if (!sensorIdExpr.isEmpty()) {
0132         if (m_groupMatches.contains(sensorIdExpr)) {
0133             m_groupMatches[sensorIdExpr]++;
0134         } else {
0135             m_groupMatches[sensorIdExpr] = 1;
0136         }
0137 
0138         if (m_groupMatches[sensorIdExpr] == 2) {
0139             SensorInfo newInfo;
0140             newInfo.name = m_sensorGroup->sensorNameForRegEx(sensorIdExpr);
0141             newInfo.description = info.description;
0142             newInfo.variantType = info.variantType;
0143             newInfo.unit = info.unit;
0144             newInfo.min = info.min;
0145             newInfo.max = info.max;
0146 
0147             addSensor(sensorIdExpr, newInfo);
0148         }
0149     }
0150 
0151     SensorTreeItem *item = rootItem;
0152     for (auto segment : segments) {
0153         if (auto itr = item->children.find(segment); itr != item->children.end() && itr->second) {
0154             item = itr->second.get();
0155         } else {
0156             auto newItem = std::make_unique<SensorTreeItem>();
0157             newItem->parent = item;
0158             newItem->segment = segment;
0159 
0160             const QModelIndex &parentIndex = (item == rootItem) ? QModelIndex() : q->createIndex(item->parent->indexOf(item->segment), 0, item);
0161 
0162             auto index = std::distance(item->children.begin(), item->children.upper_bound(segment));
0163 
0164             q->beginInsertRows(parentIndex, index, index);
0165             item->children[segment] = std::move(newItem);
0166             q->endInsertRows();
0167 
0168             item = item->children[segment].get();
0169         }
0170     }
0171 
0172     sensorInfos[item] = info;
0173 }
0174 
0175 void SensorTreeModel::Private::removeSensor(const QString &sensorId)
0176 {
0177     QString sensorIdExpr = m_sensorGroup->groupRegexForId(sensorId);
0178     if (!sensorIdExpr.isEmpty()) {
0179         if (m_groupMatches[sensorIdExpr] == 1) {
0180             m_groupMatches.remove(sensorIdExpr);
0181             removeSensor(sensorIdExpr);
0182         } else if (m_groupMatches.contains(sensorIdExpr)) {
0183             m_groupMatches[sensorIdExpr]--;
0184         }
0185     }
0186 
0187     SensorTreeItem *item = find(sensorId);
0188     if (!item) {
0189         return;
0190     }
0191 
0192     SensorTreeItem *parent = item->parent;
0193     if (!parent) {
0194         return;
0195     }
0196 
0197     auto remove = [this](SensorTreeItem *item, SensorTreeItem *parent) {
0198         const int index = item->parent->indexOf(item->segment);
0199 
0200         const QModelIndex &parentIndex = (parent == rootItem) ? QModelIndex() : q->createIndex(parent->parent->indexOf(parent->segment), 0, parent);
0201         q->beginRemoveRows(parentIndex, index, index);
0202 
0203         auto itr = item->parent->children.find(item->segment);
0204         item->parent->children.erase(itr);
0205 
0206         q->endRemoveRows();
0207 
0208         sensorInfos.remove(item);
0209     };
0210 
0211     remove(item, parent);
0212 
0213     while (!parent->children.size()) {
0214         item = parent;
0215         parent = parent->parent;
0216 
0217         if (!parent) {
0218             break;
0219         }
0220 
0221         remove(item, parent);
0222     }
0223 }
0224 
0225 QString SensorTreeModel::Private::sensorId(const QModelIndex &index)
0226 {
0227     QStringList segments;
0228 
0229     SensorTreeItem *item = static_cast<SensorTreeItem *>(index.internalPointer());
0230 
0231     segments << item->segment;
0232 
0233     while (item->parent && item->parent != rootItem) {
0234         item = item->parent;
0235         segments.prepend(item->segment);
0236     }
0237 
0238     return segments.join(QLatin1Char('/'));
0239 }
0240 
0241 SensorTreeItem *KSysGuard::SensorTreeModel::Private::find(const QString &sensorId)
0242 {
0243     auto item = rootItem;
0244     const auto segments = sensorId.split(QLatin1Char('/'));
0245     for (const QString &segment : segments) {
0246         if (auto itr = item->children.find(segment); itr != item->children.end() && itr->second) {
0247             item = itr->second.get();
0248         } else {
0249             return nullptr;
0250         }
0251     }
0252     return item;
0253 }
0254 
0255 SensorTreeModel::SensorTreeModel(QObject *parent)
0256     : QAbstractItemModel(parent)
0257     , d(new Private(this))
0258 {
0259     connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::sensorAdded, this, &SensorTreeModel::onSensorAdded);
0260     connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::sensorRemoved, this, &SensorTreeModel::onSensorRemoved);
0261     connect(SensorDaemonInterface::instance(), &SensorDaemonInterface::metaDataChanged, this, &SensorTreeModel::onMetaDataChanged);
0262     init();
0263 }
0264 
0265 SensorTreeModel::~SensorTreeModel()
0266 {
0267 }
0268 
0269 QHash<int, QByteArray> SensorTreeModel::roleNames() const
0270 {
0271     QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
0272 
0273     QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles"));
0274 
0275     for (int i = 0; i < e.keyCount(); ++i) {
0276         roles.insert(e.value(i), e.key(i));
0277     }
0278 
0279     return roles;
0280 }
0281 
0282 QVariant SensorTreeModel::headerData(int section, Qt::Orientation, int role) const
0283 {
0284     if (role != Qt::DisplayRole) {
0285         return QVariant();
0286     }
0287 
0288     if (section == 0) {
0289         return i18n("Sensor Browser");
0290     }
0291 
0292     return QVariant();
0293 }
0294 
0295 QStringList SensorTreeModel::mimeTypes() const
0296 {
0297     return QStringList() << QStringLiteral("application/x-ksysguard");
0298 }
0299 
0300 QVariant SensorTreeModel::data(const QModelIndex &index, int role) const
0301 {
0302     if (!checkIndex(index, CheckIndexOption::IndexIsValid)) {
0303         return QVariant();
0304     }
0305 
0306     if (role == Qt::DisplayRole) {
0307         SensorTreeItem *item = static_cast<SensorTreeItem *>(index.internalPointer());
0308 
0309         if (d->sensorInfos.contains(item)) {
0310             auto info = d->sensorInfos.value(item);
0311             const QString &unit = Formatter::symbol(info.unit);
0312 
0313             if (!unit.isEmpty()) {
0314                 return i18nc("Name (unit)", "%1 (%2)", info.name, unit);
0315             }
0316 
0317             return info.name;
0318         }
0319 
0320         return d->m_sensorGroup->segmentNameForRegEx(item->segment);
0321         // Only leaf nodes are valid sensors
0322     } else if (role == SensorId) {
0323         if (rowCount(index)) {
0324             return QString();
0325         } else {
0326             return d->sensorId(index);
0327         }
0328     }
0329 
0330     return QVariant();
0331 }
0332 
0333 QMimeData *SensorTreeModel::mimeData(const QModelIndexList &indexes) const
0334 {
0335     QMimeData *mimeData = new QMimeData();
0336 
0337     if (indexes.count() != 1) {
0338         return mimeData;
0339     }
0340 
0341     const QModelIndex &index = indexes.at(0);
0342 
0343     if (!checkIndex(index, CheckIndexOption::IndexIsValid)) {
0344         return mimeData;
0345     }
0346 
0347     if (rowCount(index)) {
0348         return mimeData;
0349     }
0350 
0351     mimeData->setData(QStringLiteral("application/x-ksysguard"), d->sensorId(index).toUtf8());
0352 
0353     return mimeData;
0354 }
0355 
0356 Qt::ItemFlags SensorTreeModel::flags(const QModelIndex &index) const
0357 {
0358     if (!checkIndex(index, CheckIndexOption::IndexIsValid)) {
0359         return Qt::NoItemFlags;
0360     }
0361 
0362     if (!rowCount(index)) {
0363         return Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled;
0364     }
0365 
0366     return Qt::ItemIsEnabled;
0367 }
0368 
0369 int SensorTreeModel::rowCount(const QModelIndex &parent) const
0370 {
0371     if (parent.isValid()) {
0372         if (!checkIndex(parent, CheckIndexOption::IndexIsValid)) {
0373             return 0;
0374         }
0375 
0376         const SensorTreeItem *item = static_cast<SensorTreeItem *>(parent.internalPointer());
0377         return item->children.size();
0378     }
0379 
0380     return d->rootItem->children.size();
0381 }
0382 
0383 int SensorTreeModel::columnCount(const QModelIndex &parent) const
0384 {
0385     Q_UNUSED(parent)
0386 
0387     return 1;
0388 }
0389 
0390 QModelIndex SensorTreeModel::index(int row, int column, const QModelIndex &parent) const
0391 {
0392     SensorTreeItem *parentItem = d->rootItem;
0393 
0394     if (parent.isValid()) {
0395         if (parent.model() != this) {
0396             return QModelIndex();
0397         }
0398 
0399         parentItem = static_cast<SensorTreeItem *>(parent.internalPointer());
0400     }
0401 
0402     if (row < 0 || row >= int(parentItem->children.size())) {
0403         return QModelIndex();
0404     }
0405 
0406     if (column < 0) {
0407         return QModelIndex();
0408     }
0409 
0410     return createIndex(row, column, parentItem->itemAt(row));
0411 }
0412 
0413 QModelIndex SensorTreeModel::parent(const QModelIndex &index) const
0414 {
0415     if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::DoNotUseParent)) {
0416         return QModelIndex();
0417     }
0418 
0419     if (index.column() > 0) {
0420         return QModelIndex();
0421     }
0422 
0423     const SensorTreeItem *item = static_cast<SensorTreeItem *>(index.internalPointer());
0424     SensorTreeItem *parentItem = item->parent;
0425 
0426     if (parentItem == d->rootItem) {
0427         return QModelIndex();
0428     }
0429 
0430     return createIndex(parentItem->parent->indexOf(parentItem->segment), 0, parentItem);
0431 }
0432 
0433 void SensorTreeModel::init()
0434 {
0435     auto query = new SensorQuery{QString(), this};
0436     connect(query, &SensorQuery::finished, [query, this]() {
0437         query->deleteLater();
0438         const auto result = query->result();
0439         beginResetModel();
0440         for (auto pair : result) {
0441             d->addSensor(pair.first, pair.second);
0442         }
0443         endResetModel();
0444     });
0445     query->execute();
0446 }
0447 
0448 void KSysGuard::SensorTreeModel::onSensorAdded(const QString &sensor)
0449 {
0450     SensorDaemonInterface::instance()->requestMetaData(sensor);
0451 }
0452 
0453 void KSysGuard::SensorTreeModel::onSensorRemoved(const QString &sensor)
0454 {
0455     d->removeSensor(sensor);
0456 }
0457 
0458 void KSysGuard::SensorTreeModel::onMetaDataChanged(const QString &sensorId, const SensorInfo &info)
0459 {
0460     auto item = d->find(sensorId);
0461     if (!item) {
0462         d->addSensor(sensorId, info);
0463     } else {
0464         d->sensorInfos[item] = info;
0465 
0466         auto parentItem = item->parent;
0467         if (!parentItem) {
0468             return;
0469         }
0470 
0471         auto parentIndex = QModelIndex{};
0472         if (parentItem != d->rootItem) {
0473             parentIndex = createIndex(parentItem->parent->indexOf(parentItem->segment), 0, parentItem);
0474         }
0475 
0476         auto itemIndex = index(parentItem->indexOf(item->segment), 0, parentIndex);
0477         Q_EMIT dataChanged(itemIndex, itemIndex);
0478     }
0479 }