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"