plasma-framework/src/plasma/datacontainer.cpp
Kai Uwe Broulik b3f607ec1a Distinguish datacontainer relays by both interval and alignment
This allows being able to change just the alignment, which can happen, since
the order of QML bindings being re-evaluated is non-deterministic

REVIEW: 122593
2015-02-16 18:47:10 +01:00

402 lines
12 KiB
C++

/*
* Copyright 2006-2007 Aaron Seigo <aseigo@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details
*
* You should have received a copy of the GNU Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "datacontainer.h"
#include "private/datacontainer_p.h"
#include "private/storage_p.h"
#include <QDebug>
#include <QAbstractItemModel>
#include "plasma.h"
namespace Plasma
{
DataContainer::DataContainer(QObject *parent)
: QObject(parent),
d(new DataContainerPrivate(this))
{
}
DataContainer::~DataContainer()
{
delete d;
}
const DataEngine::Data DataContainer::data() const
{
return d->data;
}
void DataContainer::setData(const QString &key, const QVariant &value)
{
if (!value.isValid()) {
d->data.remove(key);
} else {
d->data.insert(key, value);
}
d->dirty = true;
d->updateTimer.start();
//check if storage is enabled and if storage is needed.
//If it is not set to be stored,then this is the first
//setData() since the last time it was stored. This
//gives us only one singleShot timer.
if (isStorageEnabled() || !needsToBeStored()) {
d->storageTimer.start(180000, this);
}
setNeedsToBeStored(true);
}
void DataContainer::setModel(QAbstractItemModel *model)
{
if (d->model.data() == model) {
return;
}
if (d->model) {
d->model.data()->deleteLater();
}
d->model = model;
model->setParent(this);
emit modelChanged(objectName(), model);
}
QAbstractItemModel *DataContainer::model()
{
return d->model.data();
}
void DataContainer::removeAllData()
{
if (d->data.isEmpty()) {
// avoid an update if we don't have any data anyways
return;
}
d->data.clear();
d->dirty = true;
d->updateTimer.start();
}
bool DataContainer::visualizationIsConnected(QObject *visualization) const
{
return d->relayObjects.contains(visualization);
}
void DataContainer::connectVisualization(QObject *visualization, uint pollingInterval,
Plasma::Types::IntervalAlignment alignment)
{
//qDebug() << "connecting visualization" <<this<< visualization << "at interval of"
// << pollingInterval << "to" << objectName();
QMap<QObject *, SignalRelay *>::iterator objIt = d->relayObjects.find(visualization);
bool connected = objIt != d->relayObjects.end();
if (connected) {
// this visualization is already connected. just adjust the update
// frequency if necessary
SignalRelay *relay = objIt.value();
if (relay) {
// connected to a relay
//qDebug() << " already connected, but to a relay";
if (relay->m_interval == pollingInterval && relay->m_align == alignment) {
//qDebug() << " already connected to a relay of the same interval of"
// << pollingInterval << ", nothing to do";
return;
}
if (relay->receiverCount() == 1) {
//qDebug() << " removing relay, as it is now unused";
d->relays.remove(relay->m_interval);
delete relay;
} else {
disconnect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data)));
//modelChanged is always emitted by the dataSource since there is no polling there
disconnect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)),
visualization, SLOT(modelChanged(QString,QAbstractItemModel*)));
//relay->isUnused();
}
} else if (pollingInterval < 1) {
// the visualization was connected already, but not to a relay
// and it still doesn't want to connect to a relay, so we have
// nothing to do!
//qDebug() << " already connected, nothing to do";
return;
} else {
disconnect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data)));
disconnect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)),
visualization, SLOT(modelChanged(QString,QAbstractItemModel*)));
}
} else {
connect(visualization, SIGNAL(destroyed(QObject*)),
this, SLOT(disconnectVisualization(QObject*))); //, Qt::QueuedConnection);
}
if (pollingInterval < 1) {
//qDebug() << " connecting directly";
d->relayObjects[visualization] = 0;
if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) {
connect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data)));
}
if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) {
connect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)),
visualization, SLOT(modelChanged(QString,QAbstractItemModel*)));
}
} else {
//qDebug() << " connecting to a relay";
// we only want to do an imediate update if this is not the first object to connect to us
// if it is the first visualization, then the source will already have been populated
// engine's sourceRequested method
bool immediateUpdate = connected || d->relayObjects.count() > 1;
SignalRelay *relay = d->signalRelay(this, visualization, pollingInterval,
alignment, immediateUpdate);
connect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data)));
//modelChanged is always emitted by the dataSource since there is no polling there
connect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)),
visualization, SLOT(modelChanged(QString,QAbstractItemModel*)));
}
}
void DataContainer::setStorageEnabled(bool store)
{
QTime time = QTime::currentTime();
qsrand((uint)time.msec());
d->enableStorage = store;
if (store) {
QTimer::singleShot(qrand() % (2000 + 1), this, SLOT(retrieve()));
}
}
bool DataContainer::isStorageEnabled() const
{
return d->enableStorage;
}
bool DataContainer::needsToBeStored() const
{
return !d->isStored;
}
void DataContainer::setNeedsToBeStored(bool store)
{
d->isStored = !store;
}
DataEngine *DataContainer::getDataEngine()
{
QObject *o = this;
DataEngine *de = NULL;
while (de == NULL) {
o = dynamic_cast<QObject *>(o->parent());
if (o == NULL) {
return NULL;
}
de = dynamic_cast<DataEngine *>(o);
}
return de;
}
void DataContainerPrivate::store()
{
if (!q->needsToBeStored() || !q->isStorageEnabled()) {
return;
}
DataEngine *de = q->getDataEngine();
if (!de) {
return;
}
q->setNeedsToBeStored(false);
if (!storage) {
storage = new Storage(q);
}
QVariantMap op = storage->operationDescription("save");
op["group"] = q->objectName();
StorageJob *job = static_cast<StorageJob *>(storage->startOperationCall(op));
job->setData(data);
storageCount++;
QObject::connect(job, SIGNAL(finished(KJob*)), q, SLOT(storeJobFinished(KJob*)));
}
void DataContainerPrivate::storeJobFinished(KJob *)
{
--storageCount;
if (storageCount < 1) {
storage->deleteLater();
storage = 0;
}
}
void DataContainerPrivate::retrieve()
{
DataEngine *de = q->getDataEngine();
if (de == NULL) {
return;
}
if (!storage) {
storage = new Storage(q);
}
QVariantMap retrieveGroup = storage->operationDescription("retrieve");
retrieveGroup["group"] = q->objectName();
ServiceJob *retrieveJob = storage->startOperationCall(retrieveGroup);
QObject::connect(retrieveJob, SIGNAL(result(KJob*)), q,
SLOT(populateFromStoredData(KJob*)));
}
void DataContainerPrivate::populateFromStoredData(KJob *job)
{
if (job->error()) {
return;
}
StorageJob *ret = dynamic_cast<StorageJob *>(job);
if (!ret) {
return;
}
// Only fill the source with old stored
// data if it is not already populated with new data.
if (data.isEmpty() && !ret->data().isEmpty()) {
data = ret->data();
dirty = true;
q->forceImmediateUpdate();
}
QVariantMap expireGroup = storage->operationDescription("expire");
//expire things older than 4 days
expireGroup["age"] = 345600;
storage->startOperationCall(expireGroup);
}
void DataContainer::disconnectVisualization(QObject *visualization)
{
QMap<QObject *, SignalRelay *>::iterator objIt = d->relayObjects.find(visualization);
disconnect(visualization, SIGNAL(destroyed(QObject*)),
this, SLOT(disconnectVisualization(QObject*))); //, Qt::QueuedConnection);
if (objIt == d->relayObjects.end() || !objIt.value()) {
// it is connected directly to the DataContainer itself
disconnect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data)));
disconnect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)),
visualization, SLOT(modelChanged(QString,QAbstractItemModel*)));
} else {
SignalRelay *relay = objIt.value();
if (relay->receiverCount() == 1) {
d->relays.remove(relay->m_interval);
delete relay;
} else {
disconnect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data)));
//modelChanged is always emitted by the dataSource since there is no polling there
disconnect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)),
visualization, SLOT(modelChanged(QString,QAbstractItemModel*)));
}
}
d->relayObjects.erase(objIt);
d->checkUsage();
}
void DataContainer::checkForUpdate()
{
//qDebug() << objectName() << d->dirty;
if (d->dirty) {
emit dataUpdated(objectName(), d->data);
foreach (SignalRelay *relay, d->relays) {
relay->checkQueueing();
}
d->dirty = false;
}
}
void DataContainer::forceImmediateUpdate()
{
if (d->dirty) {
d->dirty = false;
emit dataUpdated(objectName(), d->data);
}
foreach (SignalRelay *relay, d->relays) {
relay->forceImmediateUpdate();
}
}
uint DataContainer::timeSinceLastUpdate() const
{
return d->updateTimer.elapsed();
}
void DataContainer::setNeedsUpdate(bool update)
{
d->cached = update;
}
bool DataContainer::isUsed() const
{
return !d->relays.isEmpty() ||
receivers(SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data))) > 0;
}
void DataContainerPrivate::checkUsage()
{
if (!checkUsageTimer.isActive()) {
checkUsageTimer.start(10, q);
}
}
void DataContainer::timerEvent(QTimerEvent *event)
{
if (event->timerId() == d->checkUsageTimer.timerId()) {
if (!isUsed()) {
// DO NOT CALL ANYTHING AFTER THIS LINE AS IT MAY GET DELETED!
//qDebug() << objectName() << "is unused";
//NOTE: Notifying visualization of the model destruction before actual deletion avoids crashes in some edge cases
if (d->model) {
d->model.clear();
emit modelChanged(objectName(), 0);
}
emit becameUnused(objectName());
}
d->checkUsageTimer.stop();
} else if (event->timerId() == d->storageTimer.timerId()) {
d->store();
d->storageTimer.stop();
}
}
} // Plasma namespace
#include "moc_datacontainer.cpp"