File indexing completed on 2024-12-15 03:45:04

0001 /*
0002     SPDX-FileCopyrightText: 2016 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: MIT
0005 */
0006 
0007 #include "propertyratiosource.h"
0008 #include "abstractdatasource_p.h"
0009 #include "logging_p.h"
0010 
0011 #include <QDebug>
0012 #include <QHash>
0013 #include <QMap>
0014 #include <QMetaProperty>
0015 #include <QObject>
0016 #include <QPointer>
0017 #include <QSettings>
0018 #include <QStringList>
0019 #include <QTime>
0020 #include <QElapsedTimer>
0021 
0022 using namespace KUserFeedback;
0023 
0024 namespace KUserFeedback {
0025 class PropertyRatioSourcePrivate : public AbstractDataSourcePrivate
0026 {
0027 public:
0028     PropertyRatioSourcePrivate();
0029     ~PropertyRatioSourcePrivate() override;
0030 
0031     void propertyChanged();
0032     QString valueToString(const QVariant &value) const;
0033     void trySetup();
0034 
0035     QString name;
0036     QString description;
0037     QPointer<QObject> obj;
0038     QByteArray propertyName;
0039     QObject *signalMonitor;
0040     QMetaProperty property;
0041     QString previousValue;
0042     QElapsedTimer lastChangeTime;
0043     QHash<QString, int> ratioSet; // data we are currently tracking
0044     QHash<QString, int> baseRatioSet; // data loaded from storage
0045     QList<QPair<QVariant, QString>> valueMap;
0046 };
0047 
0048 // inefficient workaround for not being able to connect QMetaMethod to a function directly
0049 class SignalMonitor : public QObject
0050 {
0051     Q_OBJECT
0052 public:
0053     explicit SignalMonitor(PropertyRatioSourcePrivate *r) : m_receiver(r) {}
0054 public Q_SLOTS:
0055     void propertyChanged()
0056     {
0057         m_receiver->propertyChanged();
0058     }
0059 private:
0060     PropertyRatioSourcePrivate *m_receiver;
0061 };
0062 
0063 }
0064 
0065 PropertyRatioSourcePrivate::PropertyRatioSourcePrivate()
0066     : obj(nullptr)
0067     , signalMonitor(nullptr)
0068 {
0069 }
0070 
0071 PropertyRatioSourcePrivate::~PropertyRatioSourcePrivate()
0072 {
0073     delete signalMonitor;
0074 }
0075 
0076 void PropertyRatioSourcePrivate::propertyChanged()
0077 {
0078     if (!previousValue.isEmpty() && lastChangeTime.elapsed() > 1000) {
0079         ratioSet[previousValue] += lastChangeTime.elapsed() / 1000;
0080     }
0081 
0082     lastChangeTime.start();
0083     previousValue = valueToString(property.read(obj));
0084 }
0085 
0086 QString PropertyRatioSourcePrivate::valueToString(const QVariant &value) const
0087 {
0088     const auto it = std::find_if(valueMap.constBegin(), valueMap.constEnd(), [value](QPair<QVariant, QString> p) {
0089             return p.first == value;
0090     });
0091     if (it != valueMap.constEnd()) {
0092         return it->second;
0093     }
0094     return value.toString();
0095 }
0096 
0097 void PropertyRatioSourcePrivate::trySetup()
0098 {
0099     if (!obj || propertyName.isEmpty())
0100         return;
0101 
0102     auto idx = obj->metaObject()->indexOfProperty(propertyName.constData());
0103     Q_ASSERT(idx >= 0);
0104     if (idx < 0) {
0105         qCWarning(Log) << "Property" << propertyName << "not found in" << obj << "!";
0106         return;
0107     }
0108 
0109     property = obj->metaObject()->property(idx);
0110     if (!property.hasNotifySignal()) {
0111         qCWarning(Log) << "Property" << propertyName << "has no notification signal!";
0112         return;
0113     }
0114 
0115     idx = signalMonitor->metaObject()->indexOfMethod("propertyChanged()");
0116     Q_ASSERT(idx >= 0);
0117     const auto propertyChangedMethod = signalMonitor->metaObject()->method(idx);
0118     QObject::connect(obj, property.notifySignal(), signalMonitor, propertyChangedMethod);
0119 
0120     lastChangeTime.start();
0121     propertyChangedMethod.invoke(signalMonitor, Qt::QueuedConnection);
0122 }
0123 
0124 PropertyRatioSource::PropertyRatioSource(QObject *obj, const char *propertyName, const QString &sampleName) :
0125     AbstractDataSource(sampleName, Provider::DetailedUsageStatistics, new PropertyRatioSourcePrivate)
0126 {
0127     Q_D(PropertyRatioSource);
0128 
0129     d->obj = obj;
0130     d->propertyName = propertyName;
0131     d->signalMonitor = new SignalMonitor(d);
0132     d->trySetup();
0133 }
0134 
0135 QObject* PropertyRatioSource::object() const
0136 {
0137     Q_D(const PropertyRatioSource);
0138     return d->obj;
0139 }
0140 
0141 void PropertyRatioSource::setObject(QObject* object)
0142 {
0143     Q_D(PropertyRatioSource);
0144     if (d->obj == object)
0145         return;
0146     d->obj = object;
0147     d->trySetup();
0148 }
0149 
0150 QString PropertyRatioSource::propertyName() const
0151 {
0152     Q_D(const PropertyRatioSource);
0153     return QString::fromUtf8(d->propertyName.constData());
0154 }
0155 
0156 void PropertyRatioSource::setPropertyName(const QString& name)
0157 {
0158     Q_D(PropertyRatioSource);
0159     const auto n = name.toUtf8();
0160     if (d->propertyName == n)
0161         return;
0162     d->propertyName = n;
0163     d->trySetup();
0164 }
0165 
0166 void PropertyRatioSource::addValueMapping(const QVariant &value, const QString &str)
0167 {
0168     Q_D(PropertyRatioSource);
0169     auto it = std::find_if(d->valueMap.begin(), d->valueMap.end(), [value](QPair<QVariant, QString> p) {
0170             return p.first == value;
0171     });
0172     if (it != d->valueMap.end()) {
0173         it->second = str;
0174     } else {
0175         d->valueMap.append(QPair<QVariant, QString>(value, str));
0176     }
0177 }
0178 
0179 QString PropertyRatioSource::name() const
0180 {
0181     Q_D(const PropertyRatioSource);
0182     return d->name;
0183 }
0184 
0185 void PropertyRatioSource::setName(const QString &name)
0186 {
0187     Q_D(PropertyRatioSource);
0188     d->name = name;
0189 }
0190 
0191 QString PropertyRatioSource::description() const
0192 {
0193     Q_D(const PropertyRatioSource);
0194     return d->description;
0195 }
0196 
0197 void PropertyRatioSource::setDescription(const QString& desc)
0198 {
0199     Q_D(PropertyRatioSource);
0200     d->description = desc;
0201 }
0202 
0203 QVariant PropertyRatioSource::data()
0204 {
0205     Q_D(PropertyRatioSource);
0206     d->propertyChanged();
0207 
0208     QVariantMap m;
0209     int total = 0;
0210     for (auto it = d->ratioSet.constBegin(); it != d->ratioSet.constEnd(); ++it)
0211         total += it.value() + d->baseRatioSet.value(it.key());
0212     if (total <= 0)
0213         return m;
0214 
0215     for (auto it = d->ratioSet.constBegin(); it != d->ratioSet.constEnd(); ++it) {
0216         double currentValue = it.value() + d->baseRatioSet.value(it.key());
0217         QVariantMap v;
0218         v.insert(QStringLiteral("property"), currentValue / (double)(total));
0219         m.insert(it.key(), v);
0220     }
0221 
0222     return m;
0223 }
0224 
0225 void PropertyRatioSource::loadImpl(QSettings *settings)
0226 {
0227     Q_D(PropertyRatioSource);
0228     foreach (const auto &value, settings->childKeys()) {
0229         const auto amount = std::max(settings->value(value, 0).toInt(), 0);
0230         d->baseRatioSet.insert(value, amount);
0231         if (!d->ratioSet.contains(value))
0232             d->ratioSet.insert(value, 0);
0233     }
0234 }
0235 
0236 void PropertyRatioSource::storeImpl(QSettings *settings)
0237 {
0238     Q_D(PropertyRatioSource);
0239     d->propertyChanged();
0240 
0241     // note that a second process can have updated the data meanwhile!
0242     for (auto it = d->ratioSet.begin(); it != d->ratioSet.end(); ++it) {
0243         if (it.value() == 0)
0244             continue;
0245         const auto oldValue = std::max(settings->value(it.key(), 0).toInt(), 0);
0246         const auto newValue = oldValue + it.value();
0247         settings->setValue(it.key(), newValue);
0248         *it = 0;
0249         d->baseRatioSet.insert(it.key(), newValue);
0250     }
0251 }
0252 
0253 void PropertyRatioSource::resetImpl(QSettings* settings)
0254 {
0255     Q_D(PropertyRatioSource);
0256     d->baseRatioSet.clear();
0257     d->ratioSet.clear();
0258     settings->remove(QString());
0259 }
0260 
0261 #include "propertyratiosource.moc"