File indexing completed on 2023-10-01 04:11:43

0001 /*
0002     SPDX-FileCopyrightText: 2006-2007 Aaron Seigo <aseigo@kde.org>
0003     SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "dataengine.h"
0009 #include "private/datacontainer_p.h"
0010 #include "private/dataengine_p.h"
0011 
0012 #include <QAbstractItemModel>
0013 #include <QQueue>
0014 #include <QTime>
0015 #include <QTimer>
0016 #include <QTimerEvent>
0017 #include <QVariant>
0018 
0019 #include <QDebug>
0020 #include <QStandardPaths>
0021 
0022 #include <KLocalizedString>
0023 
0024 #include "datacontainer.h"
0025 #include "package.h"
0026 #include "pluginloader.h"
0027 #include "scripting/dataenginescript.h"
0028 #include "service.h"
0029 
0030 #include "config-plasma.h"
0031 #include "private/service_p.h"
0032 #include "private/storage_p.h"
0033 
0034 namespace Plasma
0035 {
0036 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 67)
0037 DataEngine::DataEngine(const KPluginInfo &plugin, QObject *parent)
0038     : DataEngine(plugin.toMetaData(), parent)
0039 {
0040 }
0041 #endif
0042 
0043 DataEngine::DataEngine(const KPluginMetaData &plugin, QObject *parent)
0044     : QObject(parent)
0045     , d(new DataEnginePrivate(this, plugin))
0046 {
0047     if (d->script) {
0048         d->setupScriptSupport();
0049         d->script->init();
0050     } else {
0051         // default implementation does nothing. this is for engines that have to
0052         // start things in motion external to themselves before they can work
0053     }
0054 }
0055 
0056 DataEngine::DataEngine(QObject *parent, const QVariantList &args)
0057     : QObject(parent)
0058 {
0059     KPluginMetaData data;
0060     if (!args.isEmpty() && args.first().canConvert<KPluginMetaData>()) {
0061         data = args.first().value<KPluginMetaData>();
0062     }
0063     d = new DataEnginePrivate(this, data, args);
0064     if (d->script) {
0065         d->setupScriptSupport();
0066         d->script->init();
0067     }
0068 }
0069 
0070 DataEngine::~DataEngine()
0071 {
0072     // qCDebug(LOG_PLASMA) << objectName() << ": bye bye birdy! ";
0073     delete d;
0074 }
0075 
0076 QStringList DataEngine::sources() const
0077 {
0078     if (d->script) {
0079         return d->script->sources();
0080     } else {
0081         return d->sources.keys();
0082     }
0083 }
0084 
0085 Service *DataEngine::serviceForSource(const QString &source)
0086 {
0087     if (d->script) {
0088         Service *s = d->script->serviceForSource(source);
0089         if (s) {
0090             return s;
0091         }
0092     }
0093 
0094     return new NullService(source, this);
0095 }
0096 
0097 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 67)
0098 KPluginInfo DataEngine::pluginInfo() const
0099 {
0100     return KPluginInfo(d->dataEngineDescription);
0101 }
0102 #endif
0103 
0104 KPluginMetaData DataEngine::metadata() const
0105 {
0106     return d->dataEngineDescription;
0107 }
0108 
0109 void DataEngine::connectSource(const QString &source, QObject *visualization, uint pollingInterval, Plasma::Types::IntervalAlignment intervalAlignment) const
0110 {
0111     // qCDebug(LOG_PLASMA) << "connectSource" << source;
0112     bool newSource;
0113     DataContainer *s = d->requestSource(source, &newSource);
0114 
0115     if (s) {
0116         // we suppress the immediate invocation of dataUpdated here if the
0117         // source was preexisting and they don't request delayed updates
0118         // (we want to do an immediate update in that case so they don't
0119         // have to wait for the first time out)
0120         if (newSource && !s->data().isEmpty()) {
0121             newSource = false;
0122         }
0123         d->connectSource(s, visualization, pollingInterval, intervalAlignment, !newSource || pollingInterval > 0);
0124         // qCDebug(LOG_PLASMA) << " ==> source connected";
0125     }
0126 }
0127 
0128 void DataEngine::connectAllSources(QObject *visualization, uint pollingInterval, Plasma::Types::IntervalAlignment intervalAlignment) const
0129 {
0130     for (DataContainer *s : std::as_const(d->sources)) {
0131         d->connectSource(s, visualization, pollingInterval, intervalAlignment);
0132     }
0133 }
0134 
0135 void DataEngine::disconnectSource(const QString &source, QObject *visualization) const
0136 {
0137     DataContainer *s = d->source(source, false);
0138 
0139     if (s) {
0140         s->disconnectVisualization(visualization);
0141     }
0142 }
0143 
0144 DataContainer *DataEngine::containerForSource(const QString &source)
0145 {
0146     return d->source(source, false);
0147 }
0148 
0149 bool DataEngine::sourceRequestEvent(const QString &name)
0150 {
0151     if (d->script) {
0152         return d->script->sourceRequestEvent(name);
0153     } else {
0154         return false;
0155     }
0156 }
0157 
0158 bool DataEngine::updateSourceEvent(const QString &source)
0159 {
0160     if (d->script) {
0161         return d->script->updateSourceEvent(source);
0162     } else {
0163         // qCDebug(LOG_PLASMA) << source;
0164         return false; // TODO: should this be true to trigger, even needless, updates on every tick?
0165     }
0166 }
0167 
0168 void DataEngine::setData(const QString &source, const QVariant &value)
0169 {
0170     setData(source, source, value);
0171 }
0172 
0173 void DataEngine::setData(const QString &source, const QString &key, const QVariant &value)
0174 {
0175     DataContainer *s = d->source(source, false);
0176     bool isNew = !s;
0177 
0178     if (isNew) {
0179         s = d->source(source);
0180     }
0181 
0182     s->setData(key, value);
0183 
0184     if (isNew && source != d->waitingSourceRequest) {
0185         Q_EMIT sourceAdded(source);
0186     }
0187 
0188     d->scheduleSourcesUpdated();
0189 }
0190 
0191 void DataEngine::setData(const QString &source, const QVariantMap &data)
0192 {
0193     DataContainer *s = d->source(source, false);
0194     bool isNew = !s;
0195 
0196     if (isNew) {
0197         s = d->source(source);
0198     }
0199 
0200     Data::const_iterator it = data.constBegin();
0201     while (it != data.constEnd()) {
0202         s->setData(it.key(), it.value());
0203         ++it;
0204     }
0205 
0206     if (isNew && source != d->waitingSourceRequest) {
0207         Q_EMIT sourceAdded(source);
0208     }
0209 
0210     d->scheduleSourcesUpdated();
0211 }
0212 
0213 void DataEngine::removeAllData(const QString &source)
0214 {
0215     DataContainer *s = d->source(source, false);
0216     if (s) {
0217         s->removeAllData();
0218         d->scheduleSourcesUpdated();
0219     }
0220 }
0221 
0222 void DataEngine::removeData(const QString &source, const QString &key)
0223 {
0224     DataContainer *s = d->source(source, false);
0225     if (s) {
0226         s->setData(key, QVariant());
0227         d->scheduleSourcesUpdated();
0228     }
0229 }
0230 
0231 void DataEngine::setModel(const QString &source, QAbstractItemModel *model)
0232 {
0233     if (model) {
0234         setData(source, QStringLiteral("HasModel"), true);
0235     } else {
0236         removeData(source, QStringLiteral("HasModel"));
0237     }
0238 
0239     Plasma::DataContainer *s = containerForSource(source);
0240 
0241     if (s) {
0242         s->setModel(model);
0243     }
0244 }
0245 
0246 QAbstractItemModel *DataEngine::modelForSource(const QString &source)
0247 {
0248     Plasma::DataContainer *s = containerForSource(source);
0249 
0250     if (s) {
0251         return s->model();
0252     } else {
0253         return nullptr;
0254     }
0255 }
0256 
0257 void DataEngine::addSource(DataContainer *source)
0258 {
0259     if (d->sources.contains(source->objectName())) {
0260 #ifndef NDEBUG
0261         // qCDebug(LOG_PLASMA) << "source named \"" << source->objectName() << "\" already exists.";
0262 #endif
0263         return;
0264     }
0265 
0266     QObject::connect(source, SIGNAL(updateRequested(DataContainer *)), this, SLOT(internalUpdateSource(DataContainer *)));
0267     QObject::connect(source, SIGNAL(destroyed(QObject *)), this, SLOT(sourceDestroyed(QObject *)));
0268     d->sources.insert(source->objectName(), source);
0269     Q_EMIT sourceAdded(source->objectName());
0270     d->scheduleSourcesUpdated();
0271 }
0272 
0273 void DataEngine::setMinimumPollingInterval(int minimumMs)
0274 {
0275     d->minPollingInterval = minimumMs;
0276 }
0277 
0278 int DataEngine::minimumPollingInterval() const
0279 {
0280     return d->minPollingInterval;
0281 }
0282 
0283 void DataEngine::setPollingInterval(uint frequency)
0284 {
0285     killTimer(d->updateTimerId);
0286     d->updateTimerId = 0;
0287 
0288     if (frequency > 0) {
0289         d->updateTimerId = startTimer(frequency);
0290     }
0291 }
0292 
0293 void DataEngine::removeSource(const QString &source)
0294 {
0295     // Do not emit signals mid-removal, it may prompt calls into us that cause the iterator to become invalid.
0296     // https://bugs.kde.org/show_bug.cgi?id=446531
0297     Q_EMIT sourceRemoved(source);
0298 
0299     QHash<QString, DataContainer *>::iterator it = d->sources.find(source);
0300     if (it != d->sources.end()) {
0301         DataContainer *s = it.value();
0302         s->d->store();
0303         d->sources.erase(it);
0304         s->disconnect(this);
0305         s->deleteLater();
0306     }
0307 }
0308 
0309 void DataEngine::removeAllSources()
0310 {
0311     // Do not emit signals mid-removal, it may prompt calls into us that cause the iterator to become invalid.
0312     // https://bugs.kde.org/show_bug.cgi?id=446531
0313     const auto sourceNames = d->sources.keys();
0314     for (const auto &source : sourceNames) {
0315         Q_EMIT sourceRemoved(source);
0316     }
0317 
0318     QMutableHashIterator<QString, Plasma::DataContainer *> it(d->sources);
0319     while (it.hasNext()) {
0320         it.next();
0321         Plasma::DataContainer *s = it.value();
0322         it.remove();
0323         s->disconnect(this);
0324         s->deleteLater();
0325     }
0326 }
0327 
0328 bool DataEngine::isValid() const
0329 {
0330     return d->valid;
0331 }
0332 
0333 bool DataEngine::isEmpty() const
0334 {
0335     return d->sources.isEmpty();
0336 }
0337 
0338 void DataEngine::setValid(bool valid)
0339 {
0340     d->valid = valid;
0341 }
0342 
0343 QHash<QString, DataContainer *> DataEngine::containerDict() const
0344 {
0345     return d->sources;
0346 }
0347 
0348 void DataEngine::timerEvent(QTimerEvent *event)
0349 {
0350     // qCDebug(LOG_PLASMA);
0351     if (event->timerId() == d->updateTimerId) {
0352         // if the freq update is less than 0, don't bother
0353         if (d->minPollingInterval < 0) {
0354             // qCDebug(LOG_PLASMA) << "uh oh.. no polling allowed!";
0355             return;
0356         }
0357 
0358         // minPollingInterval
0359         if (d->updateTimer.elapsed() < d->minPollingInterval) {
0360             // qCDebug(LOG_PLASMA) << "hey now.. slow down!";
0361             return;
0362         }
0363 
0364         d->updateTimer.start();
0365         updateAllSources();
0366     } else if (event->timerId() == d->checkSourcesTimerId) {
0367         killTimer(d->checkSourcesTimerId);
0368         d->checkSourcesTimerId = 0;
0369 
0370         QHashIterator<QString, Plasma::DataContainer *> it(d->sources);
0371         while (it.hasNext()) {
0372             it.next();
0373             it.value()->checkForUpdate();
0374         }
0375     } else {
0376         QObject::timerEvent(event);
0377     }
0378 }
0379 
0380 void DataEngine::updateAllSources()
0381 {
0382     QHashIterator<QString, Plasma::DataContainer *> it(d->sources);
0383     while (it.hasNext()) {
0384         it.next();
0385         // qCDebug(LOG_PLASMA) << "updating" << it.key();
0386         if (it.value()->isUsed()) {
0387             updateSourceEvent(it.key());
0388         }
0389     }
0390 
0391     d->scheduleSourcesUpdated();
0392 }
0393 
0394 void DataEngine::forceImmediateUpdateOfAllVisualizations()
0395 {
0396     for (DataContainer *source : std::as_const(d->sources)) {
0397         if (source->isUsed()) {
0398             source->forceImmediateUpdate();
0399         }
0400     }
0401 }
0402 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 83)
0403 Package DataEngine::package() const
0404 {
0405     return d->package ? *d->package : Package();
0406 }
0407 #endif
0408 
0409 void DataEngine::setStorageEnabled(const QString &source, bool store)
0410 {
0411     DataContainer *s = d->source(source, false);
0412     if (s) {
0413         s->setStorageEnabled(store);
0414     }
0415 }
0416 
0417 // Private class implementations
0418 DataEnginePrivate::DataEnginePrivate(DataEngine *e, const KPluginMetaData &md, const QVariantList &args)
0419     : q(e)
0420     , dataEngineDescription(md)
0421     , refCount(-1)
0422     , checkSourcesTimerId(0) // first ref
0423     , updateTimerId(0)
0424     , minPollingInterval(-1)
0425     , valid(false)
0426     , script(nullptr)
0427 {
0428     updateTimer.start();
0429 
0430     if (dataEngineDescription.isValid()) {
0431         valid = true;
0432         e->setObjectName(dataEngineDescription.name());
0433     }
0434 
0435     if (dataEngineDescription.isValid()) {
0436 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 83)
0437 
0438         QString api = dataEngineDescription.value(QStringLiteral("X-Plasma-API"));
0439 
0440         if (!api.isEmpty()) {
0441             const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
0442                                                         QStringLiteral(PLASMA_RELATIVE_DATA_INSTALL_DIR "/dataengines/") + dataEngineDescription.pluginId(),
0443                                                         QStandardPaths::LocateDirectory);
0444             package = new Package(PluginLoader::self()->loadPackage(QStringLiteral("Plasma/DataEngine"), api));
0445             package->setPath(path);
0446 
0447             if (package->isValid()) {
0448                 script = Plasma::loadScriptEngine(api, q, args);
0449             }
0450 
0451             if (!script) {
0452                 delete package;
0453                 package = nullptr;
0454             }
0455         }
0456 #endif
0457     }
0458 }
0459 
0460 DataEnginePrivate::~DataEnginePrivate()
0461 {
0462     delete script;
0463     script = nullptr;
0464 #if PLASMA_BUILD_DEPRECATED_SINCE(5, 83)
0465     delete package;
0466     package = nullptr;
0467 #endif
0468 }
0469 
0470 void DataEnginePrivate::internalUpdateSource(DataContainer *source)
0471 {
0472     if (minPollingInterval > 0 && source->timeSinceLastUpdate() < (uint)minPollingInterval) {
0473         // skip updating this source; it's been too soon
0474         // qCDebug(LOG_PLASMA) << "internal update source is delaying" << source->timeSinceLastUpdate() << minPollingInterval;
0475         // but fake an update so that the signalrelay that triggered this gets the data from the
0476         // recent update. this way we don't have to worry about queuing - the relay will send a
0477         // signal immediately and everyone else is undisturbed.
0478         source->setNeedsUpdate();
0479         return;
0480     }
0481 
0482     if (q->updateSourceEvent(source->objectName())) {
0483         // qCDebug(LOG_PLASMA) << "queuing an update";
0484         scheduleSourcesUpdated();
0485     }
0486 }
0487 
0488 void DataEnginePrivate::ref()
0489 {
0490     --refCount;
0491 }
0492 
0493 void DataEnginePrivate::deref()
0494 {
0495     ++refCount;
0496 }
0497 
0498 bool DataEnginePrivate::isUsed() const
0499 {
0500     return refCount != 0;
0501 }
0502 
0503 DataContainer *DataEnginePrivate::source(const QString &sourceName, bool createWhenMissing)
0504 {
0505     QHash<QString, DataContainer *>::const_iterator it = sources.constFind(sourceName);
0506     if (it != sources.constEnd()) {
0507         DataContainer *s = it.value();
0508         return s;
0509     }
0510 
0511     if (!createWhenMissing) {
0512         return nullptr;
0513     }
0514 
0515     // qCDebug(LOG_PLASMA) << "DataEngine " << q->objectName() << ": could not find DataContainer " << sourceName << ", creating";
0516     DataContainer *s = new DataContainer(q);
0517     s->setObjectName(sourceName);
0518     sources.insert(sourceName, s);
0519     QObject::connect(s, SIGNAL(destroyed(QObject *)), q, SLOT(sourceDestroyed(QObject *)));
0520     QObject::connect(s, SIGNAL(updateRequested(DataContainer *)), q, SLOT(internalUpdateSource(DataContainer *)));
0521 
0522     return s;
0523 }
0524 
0525 void DataEnginePrivate::connectSource(DataContainer *s,
0526                                       QObject *visualization,
0527                                       uint pollingInterval,
0528                                       Plasma::Types::IntervalAlignment align,
0529                                       bool immediateCall)
0530 {
0531     // qCDebug(LOG_PLASMA) << "connect source called" << s->objectName() << "with interval" << pollingInterval;
0532 
0533     if (pollingInterval > 0) {
0534         // never more frequently than allowed, never more than 20 times per second
0535         uint min = qMax(50, minPollingInterval); // for qMax below
0536         pollingInterval = qMax(min, pollingInterval);
0537 
0538         // align on the 50ms
0539         pollingInterval = pollingInterval - (pollingInterval % 50);
0540     }
0541 
0542     if (immediateCall) {
0543         // we don't want to do an immediate call if we are simply
0544         // reconnecting
0545         // qCDebug(LOG_PLASMA) << "immediate call requested, we have:" << s->visualizationIsConnected(visualization);
0546         immediateCall = !s->data().isEmpty() && !s->visualizationIsConnected(visualization);
0547     }
0548 
0549     s->connectVisualization(visualization, pollingInterval, align);
0550 
0551     if (immediateCall) {
0552         QMetaObject::invokeMethod(visualization, "dataUpdated", Q_ARG(QString, s->objectName()), Q_ARG(Plasma::DataEngine::Data, s->data()));
0553         if (s->d->model) {
0554             QMetaObject::invokeMethod(visualization, "modelChanged", Q_ARG(QString, s->objectName()), Q_ARG(QAbstractItemModel *, s->d->model.data()));
0555         }
0556         s->d->dirty = false;
0557     }
0558 }
0559 
0560 void DataEnginePrivate::sourceDestroyed(QObject *object)
0561 {
0562     QHash<QString, DataContainer *>::iterator it = sources.begin();
0563     while (it != sources.end()) {
0564         if (it.value() == object) {
0565             sources.erase(it);
0566             Q_EMIT q->sourceRemoved(object->objectName());
0567             break;
0568         }
0569         ++it;
0570     }
0571 }
0572 
0573 DataContainer *DataEnginePrivate::requestSource(const QString &sourceName, bool *newSource)
0574 {
0575     if (newSource) {
0576         *newSource = false;
0577     }
0578 
0579     // qCDebug(LOG_PLASMA) << "requesting source " << sourceName;
0580     DataContainer *s = source(sourceName, false);
0581 
0582     if (!s) {
0583         // we didn't find a data source, so give the engine an opportunity to make one
0584         /*// qCDebug(LOG_PLASMA) << "DataEngine " << q->objectName()
0585             << ": could not find DataContainer " << sourceName
0586             << " will create on request" << endl;*/
0587         waitingSourceRequest = sourceName;
0588         if (q->sourceRequestEvent(sourceName)) {
0589             s = source(sourceName, false);
0590             if (s) {
0591                 // now we have a source; since it was created on demand, assume
0592                 // it should be removed when not used
0593                 if (newSource) {
0594                     *newSource = true;
0595                 }
0596                 QObject::connect(s, &DataContainer::becameUnused, q, &DataEngine::removeSource);
0597                 Q_EMIT q->sourceAdded(sourceName);
0598             }
0599         }
0600         waitingSourceRequest.clear();
0601     }
0602 
0603     return s;
0604 }
0605 
0606 // put all setup routines for script here. at this point we can assume that
0607 // package exists and that we have a script engine
0608 void DataEnginePrivate::setupScriptSupport()
0609 {
0610     /*
0611     #ifndef NDEBUG
0612     // qCDebug(LOG_PLASMA) << "sletting up script support, package is in" << package->path()
0613     #endif
0614              << "which is a" << package->structure()->type() << "package"
0615              << ", main script is" << package->filePath("mainscript");
0616     */
0617 
0618     // FIXME: Replace with ki18n functionality once semantics is clear.
0619     // const QString translationsPath = package->filePath("translations");
0620     // if (!translationsPath.isEmpty()) {
0621     //     KGlobal::dirs()->addResourceDir("locale", translationsPath);
0622     // }
0623 }
0624 
0625 void DataEnginePrivate::scheduleSourcesUpdated()
0626 {
0627     if (checkSourcesTimerId) {
0628         return;
0629     }
0630 
0631     checkSourcesTimerId = q->startTimer(0);
0632 }
0633 
0634 }
0635 
0636 #include "moc_dataengine.cpp"