1/*
2 * Copyright 2006-2007 Aaron Seigo <aseigo@kde.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Library General Public License as
6 * published by the Free Software Foundation; either version 2, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19#include "datacontainer.h"
20#include "private/datacontainer_p.h"
21#include "private/storage_p.h"
22
23#include <kdebug.h>
24
25#include "plasma.h"
26
27namespace Plasma
28{
29
30DataContainer::DataContainer(QObject *parent)
31 : QObject(parent),
32 d(new DataContainerPrivate(this))
33{
34}
35
36DataContainer::~DataContainer()
37{
38 delete d;
39}
40
41const DataEngine::Data DataContainer::data() const
42{
43 return d->data;
44}
45
46void DataContainer::setData(const QString &key, const QVariant &value)
47{
48 if (!value.isValid()) {
49 d->data.remove(key);
50 } else {
51 d->data.insert(key, value);
52 }
53
54 d->dirty = true;
55 d->updateTimer.start();
56
57 //check if storage is enabled and if storage is needed.
58 //If it is not set to be stored,then this is the first
59 //setData() since the last time it was stored. This
60 //gives us only one singleShot timer.
61 if (isStorageEnabled() || !needsToBeStored()) {
62 d->storageTimer.start(180000, this);
63 }
64
65 setNeedsToBeStored(true);
66}
67
68void DataContainer::removeAllData()
69{
70 if (d->data.isEmpty()) {
71 // avoid an update if we don't have any data anyways
72 return;
73 }
74
75 d->data.clear();
76 d->dirty = true;
77 d->updateTimer.start();
78}
79
80bool DataContainer::visualizationIsConnected(QObject *visualization) const
81{
82 return d->relayObjects.contains(visualization);
83}
84
85void DataContainer::connectVisualization(QObject *visualization, uint pollingInterval,
86 Plasma::IntervalAlignment alignment)
87{
88 //kDebug() << "connecting visualization" << visualization << "at interval of"
89 // << pollingInterval << "to" << objectName();
90 QMap<QObject *, SignalRelay *>::iterator objIt = d->relayObjects.find(visualization);
91 bool connected = objIt != d->relayObjects.end();
92 if (connected) {
93 // this visualization is already connected. just adjust the update
94 // frequency if necessary
95 SignalRelay *relay = objIt.value();
96 if (relay) {
97 // connected to a relay
98 //kDebug() << " already connected, but to a relay";
99 if (relay->m_interval == pollingInterval) {
100 //kDebug() << " already connected to a relay of the same interval of"
101 // << pollingInterval << ", nothing to do";
102 return;
103 }
104
105 if (relay->receiverCount() == 1) {
106 //kDebug() << " removing relay, as it is now unused";
107 d->relays.remove(relay->m_interval);
108 delete relay;
109 } else {
110 disconnect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
111 visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data)));
112 //relay->isUnused();
113 }
114 } else if (pollingInterval < 1) {
115 // the visualization was connected already, but not to a relay
116 // and it still doesn't want to connect to a relay, so we have
117 // nothing to do!
118 //kDebug() << " already connected, nothing to do";
119 return;
120 } else {
121 disconnect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
122 visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data)));
123 }
124 } else {
125 connect(visualization, SIGNAL(destroyed(QObject*)),
126 this, SLOT(disconnectVisualization(QObject*)));//, Qt::QueuedConnection);
127 }
128
129 if (pollingInterval < 1) {
130 //kDebug() << " connecting directly";
131 d->relayObjects[visualization] = 0;
132 connect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
133 visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data)));
134 } else {
135 //kDebug() << " connecting to a relay";
136 // we only want to do an imediate update if this is not the first object to connect to us
137 // if it is the first visualization, then the source will already have been populated
138 // engine's sourceRequested method
139 bool immediateUpdate = connected || d->relayObjects.count() > 1;
140 SignalRelay *relay = d->signalRelay(this, visualization, pollingInterval,
141 alignment, immediateUpdate);
142 connect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
143 visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data)));
144 }
145}
146
147void DataContainer::setStorageEnabled(bool store)
148{
149 QTime time = QTime::currentTime();
150 qsrand((uint)time.msec());
151 d->enableStorage = store;
152 if (store) {
153 QTimer::singleShot(qrand() % (2000 + 1) , this, SLOT(retrieve()));
154 }
155}
156
157bool DataContainer::isStorageEnabled() const
158{
159 return d->enableStorage;
160}
161
162bool DataContainer::needsToBeStored() const
163{
164 return !d->isStored;
165}
166
167void DataContainer::setNeedsToBeStored(bool store)
168{
169 d->isStored = !store;
170}
171
172DataEngine* DataContainer::getDataEngine()
173{
174 QObject *o = this;
175 DataEngine *de = NULL;
176 while (de == NULL)
177 {
178 o = dynamic_cast<QObject *> (o->parent());
179 if (o == NULL) {
180 return NULL;
181 }
182 de = dynamic_cast<DataEngine *> (o);
183 }
184 return de;
185}
186
187void DataContainerPrivate::store()
188{
189 if (!q->needsToBeStored() || !q->isStorageEnabled()) {
190 return;
191 }
192
193 DataEngine* de = q->getDataEngine();
194 if (!de) {
195 return;
196 }
197
198 q->setNeedsToBeStored(false);
199
200 if (!storage) {
201 storage = new Storage(q);
202 }
203
204 KConfigGroup op = storage->operationDescription("save");
205 op.writeEntry("group", q->objectName());
206 StorageJob *job = static_cast<StorageJob *>(storage->startOperationCall(op));
207 job->setData(data);
208 storageCount++;
209 QObject::connect(job, SIGNAL(finished(KJob*)), q, SLOT(storeJobFinished(KJob*)));
210}
211
212void DataContainerPrivate::storeJobFinished(KJob* )
213{
214 --storageCount;
215 if (storageCount < 1) {
216 storage->deleteLater();
217 storage = 0;
218 }
219}
220
221void DataContainerPrivate::retrieve()
222{
223 DataEngine* de = q->getDataEngine();
224 if (de == NULL) {
225 return;
226 }
227
228 if (!storage) {
229 storage = new Storage(q);
230 }
231
232 KConfigGroup retrieveGroup = storage->operationDescription("retrieve");
233 retrieveGroup.writeEntry("group", q->objectName());
234 ServiceJob* retrieveJob = storage->startOperationCall(retrieveGroup);
235 QObject::connect(retrieveJob, SIGNAL(result(KJob*)), q,
236 SLOT(populateFromStoredData(KJob*)));
237}
238
239void DataContainerPrivate::populateFromStoredData(KJob *job)
240{
241 if (job->error()) {
242 return;
243 }
244
245 StorageJob *ret = dynamic_cast<StorageJob*>(job);
246 if (!ret) {
247 return;
248 }
249
250 // Only fill the source with old stored
251 // data if it is not already populated with new data.
252 if (data.isEmpty() && !ret->data().isEmpty()) {
253 data = ret->data();
254 dirty = true;
255 q->forceImmediateUpdate();
256 }
257
258 KConfigGroup expireGroup = storage->operationDescription("expire");
259 //expire things older than 4 days
260 expireGroup.writeEntry("age", 345600);
261 storage->startOperationCall(expireGroup);
262}
263
264void DataContainer::disconnectVisualization(QObject *visualization)
265{
266 QMap<QObject *, SignalRelay *>::iterator objIt = d->relayObjects.find(visualization);
267 disconnect(visualization, SIGNAL(destroyed(QObject*)),
268 this, SLOT(disconnectVisualization(QObject*)));//, Qt::QueuedConnection);
269
270 if (objIt == d->relayObjects.end() || !objIt.value()) {
271 // it is connected directly to the DataContainer itself
272 disconnect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
273 visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data)));
274 } else {
275 SignalRelay *relay = objIt.value();
276
277 if (relay->receiverCount() == 1) {
278 d->relays.remove(relay->m_interval);
279 delete relay;
280 } else {
281 disconnect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
282 visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data)));
283 }
284 }
285
286 d->relayObjects.erase(objIt);
287 checkUsage();
288}
289
290void DataContainer::checkForUpdate()
291{
292 //kDebug() << objectName() << d->dirty;
293 if (d->dirty) {
294 emit dataUpdated(objectName(), d->data);
295
296 foreach (SignalRelay *relay, d->relays) {
297 relay->checkQueueing();
298 }
299
300 d->dirty = false;
301 }
302}
303
304void DataContainer::forceImmediateUpdate()
305{
306 if (d->dirty) {
307 d->dirty = false;
308 emit dataUpdated(objectName(), d->data);
309 }
310
311 foreach (SignalRelay *relay, d->relays) {
312 relay->forceImmediateUpdate();
313 }
314}
315
316uint DataContainer::timeSinceLastUpdate() const
317{
318 return d->updateTimer.elapsed();
319}
320
321void DataContainer::setNeedsUpdate(bool update)
322{
323 d->cached = update;
324}
325
326void DataContainer::checkUsage()
327{
328 if (!d->checkUsageTimer.isActive()) {
329 d->checkUsageTimer.start(10, this);
330 }
331}
332
333void DataContainer::timerEvent(QTimerEvent * event)
334{
335 if (event->timerId() == d->checkUsageTimer.timerId()) {
336 if (d->relays.count() < 1 &&
337 receivers(SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data))) < 1) {
338 // DO NOT CALL ANYTHING AFTER THIS LINE AS IT MAY GET DELETED!
339 kDebug() << objectName() << "is unused";
340 emit becameUnused(objectName());
341 }
342 d->checkUsageTimer.stop();
343 } else if (event->timerId() == d->storageTimer.timerId()) {
344 d->store();
345 d->storageTimer.stop();
346 }
347}
348
349} // Plasma namespace
350
351#include "datacontainer.moc"
352
353