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"