Plasma5Support

dataengine.cpp
1/*
2 SPDX-FileCopyrightText: 2006-2007 Aaron Seigo <aseigo@kde.org>
3 SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "dataengine.h"
9#include "private/datacontainer_p.h"
10#include "private/dataengine_p.h"
11
12#include <QAbstractItemModel>
13#include <QQueue>
14#include <QTime>
15#include <QTimer>
16#include <QTimerEvent>
17#include <QVariant>
18
19#include <QDebug>
20#include <QStandardPaths>
21
22#include <KLocalizedString>
23
24#include "datacontainer.h"
25#include "pluginloader.h"
26#include "service.h"
27
28#include "config-plasma5support.h"
29#include "private/service_p.h"
30#include "private/storage_p.h"
31
32namespace Plasma5Support
33{
35 : QObject(parent)
36 , d(new DataEnginePrivate(this, plugin))
37{
38 // default implementation does nothing. this is for engines that have to
39 // start things in motion external to themselves before they can work
40}
41
43 : QObject(parent)
44 , d(new DataEnginePrivate(this, KPluginMetaData()))
45{
46}
47
48DataEngine::~DataEngine()
49{
50 delete d;
51}
52
54{
55 return d->sources.keys();
56}
57
59{
60 return new NullService(source, this);
61}
62
64{
65 return d->dataEngineDescription;
66}
67
69 QObject *visualization,
70 uint pollingInterval,
71 Plasma5Support::Types::IntervalAlignment intervalAlignment) const
72{
73 // qCDebug(LOG_PLASMA) << "connectSource" << source;
74 bool newSource;
75 DataContainer *s = d->requestSource(source, &newSource);
76
77 if (s) {
78 // we suppress the immediate invocation of dataUpdated here if the
79 // source was preexisting and they don't request delayed updates
80 // (we want to do an immediate update in that case so they don't
81 // have to wait for the first time out)
82 if (newSource && !s->data().isEmpty()) {
83 newSource = false;
84 }
85 d->connectSource(s, visualization, pollingInterval, intervalAlignment, !newSource || pollingInterval > 0);
86 // qCDebug(LOG_PLASMA) << " ==> source connected";
87 }
88}
89
90void DataEngine::connectAllSources(QObject *visualization, uint pollingInterval, Plasma5Support::Types::IntervalAlignment intervalAlignment) const
91{
92 for (DataContainer *s : std::as_const(d->sources)) {
93 d->connectSource(s, visualization, pollingInterval, intervalAlignment);
94 }
95}
96
97void DataEngine::disconnectSource(const QString &source, QObject *visualization) const
98{
99 DataContainer *s = d->source(source, false);
100
101 if (s) {
102 s->disconnectVisualization(visualization);
103 }
104}
105
107{
108 return d->source(source, false);
109}
110
112{
113 return false;
114}
115
117{
118 return false; // TODO: should this be true to trigger, even needless, updates on every tick?
119}
120
121void DataEngine::setData(const QString &source, const QVariant &value)
122{
123 setData(source, source, value);
124}
125
126void DataEngine::setData(const QString &source, const QString &key, const QVariant &value)
127{
128 DataContainer *s = d->source(source, false);
129 bool isNew = !s;
130
131 if (isNew) {
132 s = d->source(source);
133 }
134
135 s->setData(key, value);
136
137 if (isNew && source != d->waitingSourceRequest) {
138 Q_EMIT sourceAdded(source);
139 }
140
141 d->scheduleSourcesUpdated();
142}
143
144void DataEngine::setData(const QString &source, const QVariantMap &data)
145{
146 DataContainer *s = d->source(source, false);
147 bool isNew = !s;
148
149 if (isNew) {
150 s = d->source(source);
151 }
152
153 Data::const_iterator it = data.constBegin();
154 while (it != data.constEnd()) {
155 s->setData(it.key(), it.value());
156 ++it;
157 }
158
159 if (isNew && source != d->waitingSourceRequest) {
160 Q_EMIT sourceAdded(source);
161 }
162
163 d->scheduleSourcesUpdated();
164}
165
167{
168 DataContainer *s = d->source(source, false);
169 if (s) {
170 s->removeAllData();
171 d->scheduleSourcesUpdated();
172 }
173}
174
175void DataEngine::removeData(const QString &source, const QString &key)
176{
177 DataContainer *s = d->source(source, false);
178 if (s) {
179 s->setData(key, QVariant());
180 d->scheduleSourcesUpdated();
181 }
182}
183
185{
186 if (model) {
187 setData(source, QStringLiteral("HasModel"), true);
188 } else {
189 removeData(source, QStringLiteral("HasModel"));
190 }
191
193
194 if (s) {
195 s->setModel(model);
196 }
197}
198
200{
202
203 if (s) {
204 return s->model();
205 } else {
206 return nullptr;
207 }
208}
209
211{
212 if (d->sources.contains(source->objectName())) {
213#ifndef NDEBUG
214 // qCDebug(LOG_PLASMA) << "source named \"" << source->objectName() << "\" already exists.";
215#endif
216 return;
217 }
218
219 QObject::connect(source, SIGNAL(updateRequested(DataContainer *)), this, SLOT(internalUpdateSource(DataContainer *)));
220 QObject::connect(source, SIGNAL(destroyed(QObject *)), this, SLOT(sourceDestroyed(QObject *)));
221 d->sources.insert(source->objectName(), source);
222 Q_EMIT sourceAdded(source->objectName());
223 d->scheduleSourcesUpdated();
224}
225
227{
228 d->minPollingInterval = minimumMs;
229}
230
232{
233 return d->minPollingInterval;
234}
235
237{
238 killTimer(d->updateTimerId);
239 d->updateTimerId = 0;
240
241 if (frequency > 0) {
242 d->updateTimerId = startTimer(frequency);
243 }
244}
245
247{
248 // Do not emit signals mid-removal, it may prompt calls into us that cause the iterator to become invalid.
249 // https://bugs.kde.org/show_bug.cgi?id=446531
250 Q_EMIT sourceRemoved(source);
251
252 QHash<QString, DataContainer *>::iterator it = d->sources.find(source);
253 if (it != d->sources.end()) {
254 DataContainer *s = it.value();
255 s->d->store();
256 d->sources.erase(it);
257 s->disconnect(this);
258 s->deleteLater();
259 }
260}
261
263{
264 // Do not emit signals mid-removal, it may prompt calls into us that cause the iterator to become invalid.
265 // https://bugs.kde.org/show_bug.cgi?id=446531
266 const auto sourceNames = d->sources.keys();
267 for (const auto &source : sourceNames) {
268 Q_EMIT sourceRemoved(source);
269 }
270
272 while (it.hasNext()) {
273 it.next();
275 it.remove();
276 s->disconnect(this);
277 s->deleteLater();
278 }
279}
280
282{
283 return d->valid;
284}
285
287{
288 return d->sources.isEmpty();
289}
290
291void DataEngine::setValid(bool valid)
292{
293 d->valid = valid;
294}
295
297{
298 return d->sources;
299}
300
302{
303 // qCDebug(LOG_PLASMA);
304 if (event->timerId() == d->updateTimerId) {
305 // if the freq update is less than 0, don't bother
306 if (d->minPollingInterval < 0) {
307 // qCDebug(LOG_PLASMA) << "uh oh.. no polling allowed!";
308 return;
309 }
310
311 // minPollingInterval
312 if (d->updateTimer.elapsed() < d->minPollingInterval) {
313 // qCDebug(LOG_PLASMA) << "hey now.. slow down!";
314 return;
315 }
316
317 d->updateTimer.start();
319 } else if (event->timerId() == d->checkSourcesTimerId) {
320 killTimer(d->checkSourcesTimerId);
321 d->checkSourcesTimerId = 0;
322
324 while (it.hasNext()) {
325 it.next();
326 it.value()->checkForUpdate();
327 }
328 } else {
330 }
331}
332
334{
336 while (it.hasNext()) {
337 it.next();
338 // qCDebug(LOG_PLASMA) << "updating" << it.key();
339 if (it.value()->isUsed()) {
341 }
342 }
343
344 d->scheduleSourcesUpdated();
345}
346
348{
349 for (DataContainer *source : std::as_const(d->sources)) {
350 if (source->isUsed()) {
351 source->forceImmediateUpdate();
352 }
353 }
354}
355
356void DataEngine::setStorageEnabled(const QString &source, bool store)
357{
358 if (DataContainer *s = d->source(source, false)) {
359 s->setStorageEnabled(store);
360 }
361}
362
363// Private class implementations
364DataEnginePrivate::DataEnginePrivate(DataEngine *e, const KPluginMetaData &md)
365 : q(e)
366 , dataEngineDescription(md)
367 , refCount(-1)
368 , checkSourcesTimerId(0) // first ref
369 , updateTimerId(0)
370 , minPollingInterval(-1)
371 , valid(true)
372{
373 updateTimer.start();
374
375 if (dataEngineDescription.isValid()) {
376 e->setObjectName(dataEngineDescription.name());
377 }
378}
379
380DataEnginePrivate::~DataEnginePrivate()
381{
382}
383
384void DataEnginePrivate::internalUpdateSource(DataContainer *source)
385{
386 if (minPollingInterval > 0 && source->timeSinceLastUpdate() < (uint)minPollingInterval) {
387 // skip updating this source; it's been too soon
388 // qCDebug(LOG_PLASMA) << "internal update source is delaying" << source->timeSinceLastUpdate() << minPollingInterval;
389 // but fake an update so that the signalrelay that triggered this gets the data from the
390 // recent update. this way we don't have to worry about queuing - the relay will send a
391 // signal immediately and everyone else is undisturbed.
392 source->setNeedsUpdate();
393 return;
394 }
395
396 if (q->updateSourceEvent(source->objectName())) {
397 // qCDebug(LOG_PLASMA) << "queuing an update";
398 scheduleSourcesUpdated();
399 }
400}
401
402void DataEnginePrivate::ref()
403{
404 --refCount;
405}
406
407void DataEnginePrivate::deref()
408{
409 ++refCount;
410}
411
412bool DataEnginePrivate::isUsed() const
413{
414 return refCount != 0;
415}
416
417DataContainer *DataEnginePrivate::source(const QString &sourceName, bool createWhenMissing)
418{
419 QHash<QString, DataContainer *>::const_iterator it = sources.constFind(sourceName);
420 if (it != sources.constEnd()) {
421 DataContainer *s = it.value();
422 return s;
423 }
424
425 if (!createWhenMissing) {
426 return nullptr;
427 }
428
429 // qCDebug(LOG_PLASMA) << "DataEngine " << q->objectName() << ": could not find DataContainer " << sourceName << ", creating";
430 DataContainer *s = new DataContainer(q);
431 s->setObjectName(sourceName);
432 sources.insert(sourceName, s);
433 QObject::connect(s, SIGNAL(destroyed(QObject *)), q, SLOT(sourceDestroyed(QObject *)));
434 QObject::connect(s, SIGNAL(updateRequested(DataContainer *)), q, SLOT(internalUpdateSource(DataContainer *)));
435
436 return s;
437}
438
439void DataEnginePrivate::connectSource(DataContainer *s,
440 QObject *visualization,
441 uint pollingInterval,
443 bool immediateCall)
444{
445 // qCDebug(LOG_PLASMA) << "connect source called" << s->objectName() << "with interval" << pollingInterval;
446
447 if (pollingInterval > 0) {
448 // never more frequently than allowed, never more than 20 times per second
449 uint min = qMax(50, minPollingInterval); // for qMax below
450 pollingInterval = qMax(min, pollingInterval);
451
452 // align on the 50ms
453 pollingInterval = pollingInterval - (pollingInterval % 50);
454 }
455
456 if (immediateCall) {
457 // we don't want to do an immediate call if we are simply
458 // reconnecting
459 // qCDebug(LOG_PLASMA) << "immediate call requested, we have:" << s->visualizationIsConnected(visualization);
460 immediateCall = !s->data().isEmpty() && !s->visualizationIsConnected(visualization);
461 }
462
463 s->connectVisualization(visualization, pollingInterval, align);
464
465 if (immediateCall) {
466 QMetaObject::invokeMethod(visualization, "dataUpdated", Q_ARG(QString, s->objectName()), Q_ARG(Plasma5Support::DataEngine::Data, s->data()));
467 if (s->d->model) {
468 QMetaObject::invokeMethod(visualization, "modelChanged", Q_ARG(QString, s->objectName()), Q_ARG(QAbstractItemModel *, s->d->model.data()));
469 }
470 s->d->dirty = false;
471 }
472}
473
474void DataEnginePrivate::sourceDestroyed(QObject *object)
475{
477 while (it != sources.end()) {
478 if (it.value() == object) {
479 sources.erase(it);
480 Q_EMIT q->sourceRemoved(object->objectName());
481 break;
482 }
483 ++it;
484 }
485}
486
487DataContainer *DataEnginePrivate::requestSource(const QString &sourceName, bool *newSource)
488{
489 if (newSource) {
490 *newSource = false;
491 }
492
493 // qCDebug(LOG_PLASMA) << "requesting source " << sourceName;
494 DataContainer *s = source(sourceName, false);
495
496 if (!s) {
497 // we didn't find a data source, so give the engine an opportunity to make one
498 /*// qCDebug(LOG_PLASMA) << "DataEngine " << q->objectName()
499 << ": could not find DataContainer " << sourceName
500 << " will create on request" << endl;*/
501 waitingSourceRequest = sourceName;
502 if (q->sourceRequestEvent(sourceName)) {
503 s = source(sourceName, false);
504 if (s) {
505 // now we have a source; since it was created on demand, assume
506 // it should be removed when not used
507 if (newSource) {
508 *newSource = true;
509 }
511 Q_EMIT q->sourceAdded(sourceName);
512 }
513 }
514 waitingSourceRequest.clear();
515 }
516
517 return s;
518}
519
520void DataEnginePrivate::scheduleSourcesUpdated()
521{
522 if (checkSourcesTimerId) {
523 return;
524 }
525
526 checkSourcesTimerId = q->startTimer(0);
527}
528
529}
530
531#include "moc_dataengine.cpp"
A set of data exported via a DataEngine.
void disconnectVisualization(QObject *visualization)
Disconnects an object from this DataContainer.
void setModel(QAbstractItemModel *model)
Associates a model with this DataContainer.
void setData(const QString &key, const QVariant &value)
Set a value for a key.
QAbstractItemModel * model()
const DataEngine::Data data() const
Returns the data for this DataContainer.
void removeAllData()
Removes all data currently associated with this source.
void becameUnused(const QString &source)
Emitted when the last visualization is disconnected.
Data provider for plasmoids (Plasma plugins)
Definition dataengine.h:45
void removeSource(const QString &source)
Removes a data source.
Q_INVOKABLE void disconnectSource(const QString &source, QObject *visualization) const
Disconnects a source from an object that was receiving data updates.
virtual bool updateSourceEvent(const QString &source)
Called by internal updating mechanisms to trigger the engine to refresh the data contained in a given...
void setModel(const QString &source, QAbstractItemModel *model)
Associates a model to a data source.
virtual QStringList sources() const
virtual Q_INVOKABLE Service * serviceForSource(const QString &source)
QAbstractItemModel * modelForSource(const QString &source)
bool isEmpty() const
Returns true if the data engine is empty, which is to say that it has no data sources currently.
QHash< QString, DataContainer * > containerDict() const
void forceImmediateUpdateOfAllVisualizations()
Forces an immediate update to all connected sources, even those with timeouts that haven't yet expire...
void setValid(bool valid)
Sets whether or not this engine is valid, e.g.
void updateAllSources()
Immediately updates all existing sources when called.
DataEngine(const KPluginMetaData &plugin, QObject *parent=nullptr)
Constructor.
virtual bool sourceRequestEvent(const QString &source)
When a source that does not currently exist is requested by the consumer, this method is called to gi...
void removeData(const QString &source, const QString &key)
Removes a data entry from a source.
void addSource(DataContainer *source)
Adds an already constructed data source.
void timerEvent(QTimerEvent *event) override
Reimplemented from QObject.
Q_INVOKABLE void connectSource(const QString &source, QObject *visualization, uint pollingInterval=0, Plasma5Support::Types::IntervalAlignment intervalAlignment=Types::NoAlignment) const
Connects a source to an object for data updates.
void setData(const QString &source, const QVariant &value)
Sets a value for a data source.
Q_INVOKABLE DataContainer * containerForSource(const QString &source)
Retrieves a pointer to the DataContainer for a given source.
void setMinimumPollingInterval(int minimumMs)
Sets the minimum amount of time, in milliseconds, that must pass between successive updates of data.
Q_INVOKABLE void connectAllSources(QObject *visualization, uint pollingInterval=0, Plasma5Support::Types::IntervalAlignment intervalAlignment=Types::NoAlignment) const
Connects all currently existing sources to an object for data updates.
void removeAllData(const QString &source)
Removes all the data associated with a data source.
void removeAllSources()
Removes all data sources.
void setPollingInterval(uint frequency)
Sets up an internal update tick for all data sources.
void sourceAdded(const QString &source)
Emitted when a new data source is created.
void setStorageEnabled(const QString &source, bool store)
Sets a source to be stored for easy retrieval when the real source of the data (usually a network con...
void sourceRemoved(const QString &source)
Emitted when a data source is removed.
bool isValid() const
Returns true if this engine is valid, otherwise returns false.
KPluginMetaData metadata() const
This class provides a generic API for write access to settings or services.
Definition service.h:78
IntervalAlignment
Possible timing alignments.
Namespace for everything in libplasma.
Definition datamodel.cpp:15
virtual QVariant data(const QModelIndex &index, int role) const const=0
bool hasNext() const const
const Key & key() const const
const T & value() const const
bool isEmpty() const const
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
bool hasNext() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
void destroyed(QObject *obj)
bool disconnect(const QMetaObject::Connection &connection)
virtual bool event(QEvent *e)
void killTimer(int id)
int startTimer(int interval, Qt::TimerType timerType)
virtual void timerEvent(QTimerEvent *event)
void clear()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:08:57 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.