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

0001 /*
0002     SPDX-FileCopyrightText: 2006-2007 Aaron Seigo <aseigo@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 #include "datacontainer.h"
0007 #include "private/datacontainer_p.h"
0008 #include "private/storage_p.h"
0009 
0010 #include <QAbstractItemModel>
0011 #include <QDebug>
0012 #include <QRandomGenerator>
0013 
0014 #include "debug_p.h"
0015 #include "plasma.h"
0016 
0017 namespace Plasma
0018 {
0019 DataContainer::DataContainer(QObject *parent)
0020     : QObject(parent)
0021     , d(new DataContainerPrivate(this))
0022 {
0023 }
0024 
0025 DataContainer::~DataContainer()
0026 {
0027     delete d;
0028 }
0029 
0030 const DataEngine::Data DataContainer::data() const
0031 {
0032     return d->data;
0033 }
0034 
0035 void DataContainer::setData(const QString &key, const QVariant &value)
0036 {
0037     if (!value.isValid()) {
0038         d->data.remove(key);
0039     } else {
0040         d->data.insert(key, value);
0041     }
0042 
0043     d->dirty = true;
0044     d->updateTimer.start();
0045 
0046     // check if storage is enabled and if storage is needed.
0047     // If it is not set to be stored,then this is the first
0048     // setData() since the last time it was stored. This
0049     // gives us only one singleShot timer.
0050     if (isStorageEnabled() || !needsToBeStored()) {
0051         d->storageTimer.start(180000, this);
0052     }
0053 
0054     setNeedsToBeStored(true);
0055 }
0056 
0057 void DataContainer::setModel(QAbstractItemModel *model)
0058 {
0059     if (d->model.data() == model) {
0060         return;
0061     }
0062 
0063     if (d->model) {
0064         d->model.data()->deleteLater();
0065     }
0066 
0067     d->model = model;
0068     model->setParent(this);
0069     Q_EMIT modelChanged(objectName(), model);
0070 }
0071 
0072 QAbstractItemModel *DataContainer::model()
0073 {
0074     return d->model.data();
0075 }
0076 
0077 void DataContainer::removeAllData()
0078 {
0079     if (d->data.isEmpty()) {
0080         // avoid an update if we don't have any data anyways
0081         return;
0082     }
0083 
0084     d->data.clear();
0085     d->dirty = true;
0086     d->updateTimer.start();
0087 }
0088 
0089 bool DataContainer::visualizationIsConnected(QObject *visualization) const
0090 {
0091     return d->relayObjects.contains(visualization);
0092 }
0093 
0094 void DataContainer::connectVisualization(QObject *visualization, uint pollingInterval, Plasma::Types::IntervalAlignment alignment)
0095 {
0096     // qCDebug(LOG_PLASMA) << "connecting visualization" <<this<< visualization << "at interval of"
0097     //         << pollingInterval << "to" << objectName();
0098     QMap<QObject *, SignalRelay *>::iterator objIt = d->relayObjects.find(visualization);
0099     bool connected = objIt != d->relayObjects.end();
0100     if (connected) {
0101         // this visualization is already connected. just adjust the update
0102         // frequency if necessary
0103         SignalRelay *relay = objIt.value();
0104         if (relay) {
0105             // connected to a relay
0106             // qCDebug(LOG_PLASMA) << "     already connected, but to a relay";
0107             if (relay->m_interval == pollingInterval && relay->m_align == alignment) {
0108                 // qCDebug(LOG_PLASMA) << "    already connected to a relay of the same interval of"
0109                 //          << pollingInterval << ", nothing to do";
0110                 return;
0111             }
0112 
0113             if (relay->receiverCount() == 1) {
0114                 // qCDebug(LOG_PLASMA) << "    removing relay, as it is now unused";
0115                 d->relays.remove(relay->m_interval);
0116                 delete relay;
0117             } else {
0118                 if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) {
0119                     disconnect(relay,
0120                                SIGNAL(dataUpdated(QString, Plasma::DataEngine::Data)),
0121                                visualization,
0122                                SLOT(dataUpdated(QString, Plasma::DataEngine::Data)));
0123                 }
0124                 // modelChanged is always emitted by the dataSource since there is no polling there
0125                 if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) {
0126                     disconnect(this, SIGNAL(modelChanged(QString, QAbstractItemModel *)), visualization, SLOT(modelChanged(QString, QAbstractItemModel *)));
0127                 }
0128                 // relay->isUnused();
0129             }
0130         } else if (pollingInterval < 1) {
0131             // the visualization was connected already, but not to a relay
0132             // and it still doesn't want to connect to a relay, so we have
0133             // nothing to do!
0134             // qCDebug(LOG_PLASMA) << "     already connected, nothing to do";
0135             return;
0136         } else {
0137             if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) {
0138                 disconnect(this, SIGNAL(dataUpdated(QString, Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString, Plasma::DataEngine::Data)));
0139             }
0140             if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) {
0141                 disconnect(this, SIGNAL(modelChanged(QString, QAbstractItemModel *)), visualization, SLOT(modelChanged(QString, QAbstractItemModel *)));
0142             }
0143         }
0144     } else {
0145         connect(visualization, &QObject::destroyed, this, &DataContainer::disconnectVisualization); //, Qt::QueuedConnection);
0146     }
0147 
0148     if (pollingInterval < 1) {
0149         // qCDebug(LOG_PLASMA) << "    connecting directly";
0150         d->relayObjects[visualization] = nullptr;
0151         if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) {
0152             connect(this, SIGNAL(dataUpdated(QString, Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString, Plasma::DataEngine::Data)));
0153         }
0154         if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) {
0155             connect(this, SIGNAL(modelChanged(QString, QAbstractItemModel *)), visualization, SLOT(modelChanged(QString, QAbstractItemModel *)));
0156         }
0157     } else {
0158         // qCDebug(LOG_PLASMA) << "    connecting to a relay";
0159         // we only want to do an immediate update if this is not the first object to connect to us
0160         // if it is the first visualization, then the source will already have been populated
0161         // engine's sourceRequested method
0162         bool immediateUpdate = connected || d->relayObjects.count() > 1;
0163         SignalRelay *relay = d->signalRelay(this, visualization, pollingInterval, alignment, immediateUpdate);
0164         if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) {
0165             connect(relay, SIGNAL(dataUpdated(QString, Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString, Plasma::DataEngine::Data)));
0166         }
0167         // modelChanged is always emitted by the dataSource since there is no polling there
0168         if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) {
0169             connect(this, SIGNAL(modelChanged(QString, QAbstractItemModel *)), visualization, SLOT(modelChanged(QString, QAbstractItemModel *)));
0170         }
0171     }
0172 }
0173 
0174 void DataContainer::setStorageEnabled(bool store)
0175 {
0176     d->enableStorage = store;
0177     if (store) {
0178         QTimer::singleShot(QRandomGenerator::global()->bounded(2000 + 1), this, SLOT(retrieve()));
0179     }
0180 }
0181 
0182 bool DataContainer::isStorageEnabled() const
0183 {
0184     return d->enableStorage;
0185 }
0186 
0187 bool DataContainer::needsToBeStored() const
0188 {
0189     return !d->isStored;
0190 }
0191 
0192 void DataContainer::setNeedsToBeStored(bool store)
0193 {
0194     d->isStored = !store;
0195 }
0196 
0197 DataEngine *DataContainer::getDataEngine()
0198 {
0199     QObject *o = this;
0200     DataEngine *de = nullptr;
0201     while (de == nullptr) {
0202         o = dynamic_cast<QObject *>(o->parent());
0203         if (o == nullptr) {
0204             return nullptr;
0205         }
0206         de = dynamic_cast<DataEngine *>(o);
0207     }
0208     return de;
0209 }
0210 
0211 void DataContainerPrivate::store()
0212 {
0213     if (!q->needsToBeStored() || !q->isStorageEnabled()) {
0214         return;
0215     }
0216 
0217     DataEngine *de = q->getDataEngine();
0218     if (!de) {
0219         return;
0220     }
0221 
0222     q->setNeedsToBeStored(false);
0223 
0224     if (!storage) {
0225         storage = new Storage(q);
0226     }
0227 
0228     QVariantMap op = storage->operationDescription(QStringLiteral("save"));
0229     op[QStringLiteral("group")] = q->objectName();
0230     StorageJob *job = static_cast<StorageJob *>(storage->startOperationCall(op));
0231     job->setData(data);
0232     storageCount++;
0233     QObject::connect(job, SIGNAL(finished(KJob *)), q, SLOT(storeJobFinished(KJob *)));
0234 }
0235 
0236 void DataContainerPrivate::storeJobFinished(KJob *)
0237 {
0238     --storageCount;
0239     if (storageCount < 1) {
0240         storage->deleteLater();
0241         storage = nullptr;
0242     }
0243 }
0244 
0245 void DataContainerPrivate::retrieve()
0246 {
0247     DataEngine *de = q->getDataEngine();
0248     if (de == nullptr) {
0249         return;
0250     }
0251 
0252     if (!storage) {
0253         storage = new Storage(q);
0254     }
0255 
0256     QVariantMap retrieveGroup = storage->operationDescription(QStringLiteral("retrieve"));
0257     retrieveGroup[QStringLiteral("group")] = q->objectName();
0258     ServiceJob *retrieveJob = storage->startOperationCall(retrieveGroup);
0259     QObject::connect(retrieveJob, SIGNAL(result(KJob *)), q, SLOT(populateFromStoredData(KJob *)));
0260 }
0261 
0262 void DataContainerPrivate::populateFromStoredData(KJob *job)
0263 {
0264     if (job->error()) {
0265         return;
0266     }
0267 
0268     StorageJob *ret = dynamic_cast<StorageJob *>(job);
0269     if (!ret) {
0270         return;
0271     }
0272 
0273     // Only fill the source with old stored
0274     // data if it is not already populated with new data.
0275     if (data.isEmpty() && !ret->data().isEmpty()) {
0276         data = ret->data();
0277         dirty = true;
0278         q->forceImmediateUpdate();
0279     }
0280 
0281     QVariantMap expireGroup = storage->operationDescription(QStringLiteral("expire"));
0282     // expire things older than 4 days
0283     expireGroup[QStringLiteral("age")] = 345600;
0284     storage->startOperationCall(expireGroup);
0285 }
0286 
0287 void DataContainer::disconnectVisualization(QObject *visualization)
0288 {
0289     QMap<QObject *, SignalRelay *>::iterator objIt = d->relayObjects.find(visualization);
0290     disconnect(visualization, &QObject::destroyed, this, &DataContainer::disconnectVisualization); //, Qt::QueuedConnection);
0291 
0292     if (objIt == d->relayObjects.end() || !objIt.value()) {
0293         // it is connected directly to the DataContainer itself
0294         if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) {
0295             disconnect(this, SIGNAL(dataUpdated(QString, Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString, Plasma::DataEngine::Data)));
0296         }
0297         if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) {
0298             disconnect(this, SIGNAL(modelChanged(QString, QAbstractItemModel *)), visualization, SLOT(modelChanged(QString, QAbstractItemModel *)));
0299         }
0300     } else {
0301         SignalRelay *relay = objIt.value();
0302 
0303         if (relay->receiverCount() == 1) {
0304             d->relays.remove(relay->m_interval);
0305             delete relay;
0306         } else {
0307             if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) {
0308                 disconnect(relay, SIGNAL(dataUpdated(QString, Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString, Plasma::DataEngine::Data)));
0309             }
0310             // modelChanged is always emitted by the dataSource since there is no polling there
0311             if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) {
0312                 disconnect(this, SIGNAL(modelChanged(QString, QAbstractItemModel *)), visualization, SLOT(modelChanged(QString, QAbstractItemModel *)));
0313             }
0314         }
0315     }
0316 
0317     d->relayObjects.erase(objIt);
0318     d->checkUsage();
0319 }
0320 
0321 void DataContainer::checkForUpdate()
0322 {
0323     // qCDebug(LOG_PLASMA) << objectName() << d->dirty;
0324     if (d->dirty) {
0325         Q_EMIT dataUpdated(objectName(), d->data);
0326 
0327         // copy as checkQueueing can result in deletion of the relay
0328         const auto relays = d->relays;
0329         for (SignalRelay *relay : relays) {
0330             relay->checkQueueing();
0331         }
0332 
0333         d->dirty = false;
0334     }
0335 }
0336 
0337 void DataContainer::forceImmediateUpdate()
0338 {
0339     if (d->dirty) {
0340         d->dirty = false;
0341         Q_EMIT dataUpdated(objectName(), d->data);
0342     }
0343 
0344     for (SignalRelay *relay : std::as_const(d->relays)) {
0345         relay->forceImmediateUpdate();
0346     }
0347 }
0348 
0349 uint DataContainer::timeSinceLastUpdate() const
0350 {
0351     return d->updateTimer.elapsed();
0352 }
0353 
0354 void DataContainer::setNeedsUpdate(bool update)
0355 {
0356     d->cached = update;
0357 }
0358 
0359 bool DataContainer::isUsed() const
0360 {
0361     return !d->relays.isEmpty() || receivers(SIGNAL(dataUpdated(QString, Plasma::DataEngine::Data))) > 0;
0362 }
0363 
0364 void DataContainerPrivate::checkUsage()
0365 {
0366     if (!checkUsageTimer.isActive()) {
0367         checkUsageTimer.start(10, q);
0368     }
0369 }
0370 
0371 void DataContainer::timerEvent(QTimerEvent *event)
0372 {
0373     if (event->timerId() == d->checkUsageTimer.timerId()) {
0374         if (!isUsed()) {
0375             // DO NOT CALL ANYTHING AFTER THIS LINE AS IT MAY GET DELETED!
0376             // qCDebug(LOG_PLASMA) << objectName() << "is unused";
0377 
0378             // NOTE: Notifying visualization of the model destruction before actual deletion avoids crashes in some edge cases
0379             if (d->model) {
0380                 d->model.clear();
0381                 Q_EMIT modelChanged(objectName(), nullptr);
0382             }
0383             Q_EMIT becameUnused(objectName());
0384         }
0385         d->checkUsageTimer.stop();
0386     } else if (event->timerId() == d->storageTimer.timerId()) {
0387         d->store();
0388         d->storageTimer.stop();
0389     }
0390 }
0391 
0392 } // Plasma namespace
0393 
0394 #include "moc_datacontainer.cpp"