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
20#include "dataengine.h"
21#include "private/dataengine_p.h"
22#include "private/datacontainer_p.h"
23
24#include <QQueue>
25#include <QTimer>
26#include <QTime>
27#include <QTimerEvent>
28#include <QVariant>
29
30#include <kdebug.h>
31#include <kplugininfo.h>
32#include <kservice.h>
33#include <kstandarddirs.h>
34
35#include "authorizationmanager.h"
36#include "datacontainer.h"
37#include "package.h"
38#include "service.h"
39#include "scripting/dataenginescript.h"
40
41#include "private/authorizationmanager_p.h"
42#include "private/dataengineservice_p.h"
43#include "private/remotedataengine_p.h"
44#include "private/service_p.h"
45#include "private/storage_p.h"
46
47namespace Plasma
48{
49
50DataEngine::DataEngine(QObject *parent, KService::Ptr service)
51 : QObject(parent),
52 d(new DataEnginePrivate(this, KPluginInfo(service)))
53{
54}
55
56DataEngine::DataEngine(QObject *parent, const QVariantList &args)
57 : QObject(parent),
58 d(new DataEnginePrivate(this, KPluginInfo(KService::serviceByStorageId(args.count() > 0 ? args[0].toString() : QString()))))
59{
60}
61
62DataEngine::~DataEngine()
63{
64 //kDebug() << objectName() << ": bye bye birdy! ";
65 delete d;
66}
67
68QStringList DataEngine::sources() const
69{
70 if (d->script) {
71 return d->script->sources();
72 } else {
73 return d->sources.keys();
74 }
75}
76
77Service *DataEngine::serviceForSource(const QString &source)
78{
79 if (d->script) {
80 Service * s = d->script->serviceForSource(source);
81 if (s) {
82 return s;
83 }
84 }
85
86 return new NullService(source, this);
87}
88
89void DataEngine::connectSource(const QString &source, QObject *visualization,
90 uint pollingInterval,
91 Plasma::IntervalAlignment intervalAlignment) const
92{
93 //kDebug() << "connectSource" << source;
94 bool newSource;
95 DataContainer *s = d->requestSource(source, &newSource);
96
97 if (s) {
98 // we suppress the immediate invocation of dataUpdated here if the
99 // source was prexisting and they don't request delayed updates
100 // (we want to do an immediate update in that case so they don't
101 // have to wait for the first time out)
102 if (newSource && !s->data().isEmpty()) {
103 newSource = false;
104 }
105 d->connectSource(s, visualization, pollingInterval, intervalAlignment,
106 !newSource || pollingInterval > 0);
107 //kDebug() << " ==> source connected";
108 }
109}
110
111void DataEngine::connectAllSources(QObject *visualization, uint pollingInterval,
112 Plasma::IntervalAlignment intervalAlignment) const
113{
114 foreach (DataContainer *s, d->sources) {
115 d->connectSource(s, visualization, pollingInterval, intervalAlignment);
116 }
117}
118
119void DataEngine::disconnectSource(const QString &source, QObject *visualization) const
120{
121 DataContainer *s = d->source(source, false);
122
123 if (s) {
124 s->disconnectVisualization(visualization);
125 }
126}
127
128DataContainer *DataEngine::containerForSource(const QString &source)
129{
130 return d->source(source, false);
131}
132
133DataEngine::Data DataEngine::query(const QString &source) const
134{
135 bool newSource;
136 DataContainer *s = d->requestSource(source, &newSource);
137
138 if (!s) {
139 return DataEngine::Data();
140 } else if (!newSource && d->minPollingInterval >= 0 &&
141 s->timeSinceLastUpdate() >= uint(d->minPollingInterval)) {
142 DataEngine *unconstThis = const_cast<DataEngine*>(this);
143 if (unconstThis->updateSourceEvent(source)) {
144 unconstThis->scheduleSourcesUpdated();
145 }
146 }
147
148 DataEngine::Data data = s->data();
149 s->checkUsage();
150 return data;
151}
152
153void DataEngine::init()
154{
155 if (d->script) {
156 d->setupScriptSupport();
157 d->script->init();
158 } else {
159 // kDebug() << "called";
160 // default implementation does nothing. this is for engines that have to
161 // start things in motion external to themselves before they can work
162 }
163}
164
165bool DataEngine::sourceRequestEvent(const QString &name)
166{
167 if (d->script) {
168 return d->script->sourceRequestEvent(name);
169 } else {
170 return false;
171 }
172}
173
174bool DataEngine::updateSourceEvent(const QString &source)
175{
176 if (d->script) {
177 return d->script->updateSourceEvent(source);
178 } else {
179 //kDebug() << source;
180 return false; //TODO: should this be true to trigger, even needless, updates on every tick?
181 }
182}
183
184void DataEngine::setData(const QString &source, const QVariant &value)
185{
186 setData(source, source, value);
187}
188
189void DataEngine::setData(const QString &source, const QString &key, const QVariant &value)
190{
191 DataContainer *s = d->source(source, false);
192 bool isNew = !s;
193
194 if (isNew) {
195 s = d->source(source);
196 }
197
198 s->setData(key, value);
199
200 if (isNew && source != d->waitingSourceRequest) {
201 emit sourceAdded(source);
202 }
203
204 scheduleSourcesUpdated();
205}
206
207void DataEngine::setData(const QString &source, const Data &data)
208{
209 DataContainer *s = d->source(source, false);
210 bool isNew = !s;
211
212 if (isNew) {
213 s = d->source(source);
214 }
215
216 Data::const_iterator it = data.constBegin();
217 while (it != data.constEnd()) {
218 s->setData(it.key(), it.value());
219 ++it;
220 }
221
222 if (isNew && source != d->waitingSourceRequest) {
223 emit sourceAdded(source);
224 }
225
226 scheduleSourcesUpdated();
227}
228
229void DataEngine::removeAllData(const QString &source)
230{
231 DataContainer *s = d->source(source, false);
232 if (s) {
233 s->removeAllData();
234 scheduleSourcesUpdated();
235 }
236}
237
238void DataEngine::removeData(const QString &source, const QString &key)
239{
240 DataContainer *s = d->source(source, false);
241 if (s) {
242 s->setData(key, QVariant());
243 scheduleSourcesUpdated();
244 }
245}
246
247void DataEngine::addSource(DataContainer *source)
248{
249 if (d->sources.contains(source->objectName())) {
250 kDebug() << "source named \"" << source->objectName() << "\" already exists.";
251 return;
252 }
253
254 QObject::connect(source, SIGNAL(updateRequested(DataContainer*)),
255 this, SLOT(internalUpdateSource(DataContainer*)));
256 QObject::connect(source, SIGNAL(destroyed(QObject*)), this, SLOT(sourceDestroyed(QObject*)));
257 d->sources.insert(source->objectName(), source);
258 emit sourceAdded(source->objectName());
259 scheduleSourcesUpdated();
260}
261
262void DataEngine::setMaxSourceCount(uint limit)
263{
264 if (d->limit == limit) {
265 return;
266 }
267
268 d->limit = limit;
269
270 if (d->limit > 0) {
271 d->trimQueue();
272 } else {
273 d->sourceQueue.clear();
274 }
275}
276
277uint DataEngine::maxSourceCount() const
278{
279 return d->limit;
280}
281
282void DataEngine::setMinimumPollingInterval(int minimumMs)
283{
284 d->minPollingInterval = minimumMs;
285}
286
287int DataEngine::minimumPollingInterval() const
288{
289 return d->minPollingInterval;
290}
291
292void DataEngine::setPollingInterval(uint frequency)
293{
294 killTimer(d->updateTimerId);
295 d->updateTimerId = 0;
296
297 if (frequency > 0) {
298 d->updateTimerId = startTimer(frequency);
299 }
300}
301
302void DataEngine::removeSource(const QString &source)
303{
304 SourceDict::iterator it = d->sources.find(source);
305 if (it != d->sources.end()) {
306 DataContainer *s = it.value();
307
308 // remove it from the limit queue if we're keeping one
309 if (d->limit > 0) {
310 QQueue<DataContainer*>::iterator it = d->sourceQueue.begin();
311 while (it != d->sourceQueue.end()) {
312 if (*it == s) {
313 d->sourceQueue.erase(it);
314 break;
315 }
316 ++it;
317 }
318 }
319
320 s->d->store();
321 s->disconnect(this);
322 s->deleteLater();
323 d->sources.erase(it);
324 emit sourceRemoved(source);
325 }
326}
327
328void DataEngine::removeAllSources()
329{
330 QMutableHashIterator<QString, Plasma::DataContainer*> it(d->sources);
331 while (it.hasNext()) {
332 it.next();
333 Plasma::DataContainer *s = it.value();
334 const QString source = it.key();
335 it.remove();
336 s->disconnect(this);
337 s->deleteLater();
338 emit sourceRemoved(source);
339 }
340}
341
342bool DataEngine::isValid() const
343{
344 return d->valid;
345}
346
347bool DataEngine::isEmpty() const
348{
349 return d->sources.isEmpty();
350}
351
352void DataEngine::setValid(bool valid)
353{
354 d->valid = valid;
355}
356
357DataEngine::SourceDict DataEngine::containerDict() const
358{
359 return d->sources;
360}
361
362void DataEngine::timerEvent(QTimerEvent *event)
363{
364 //kDebug();
365 if (event->timerId() == d->updateTimerId) {
366 // if the freq update is less than 0, don't bother
367 if (d->minPollingInterval < 0) {
368 //kDebug() << "uh oh.. no polling allowed!";
369 return;
370 }
371
372 // minPollingInterval
373 if (d->updateTimer.elapsed() < d->minPollingInterval) {
374 //kDebug() << "hey now.. slow down!";
375 return;
376 }
377
378 d->updateTimer.start();
379 updateAllSources();
380 } else if (event->timerId() == d->checkSourcesTimerId) {
381 killTimer(d->checkSourcesTimerId);
382 d->checkSourcesTimerId = 0;
383
384 QHashIterator<QString, Plasma::DataContainer*> it(d->sources);
385 while (it.hasNext()) {
386 it.next();
387 it.value()->checkForUpdate();
388 }
389 } else {
390 QObject::timerEvent(event);
391 }
392}
393
394void DataEngine::updateAllSources()
395{
396 QHashIterator<QString, Plasma::DataContainer*> it(d->sources);
397 while (it.hasNext()) {
398 it.next();
399 //kDebug() << "updating" << it.key();
400 updateSourceEvent(it.key());
401 }
402
403 scheduleSourcesUpdated();
404}
405
406void DataEngine::forceImmediateUpdateOfAllVisualizations()
407{
408 foreach (DataContainer *source, d->sources) {
409 source->forceImmediateUpdate();
410 }
411}
412
413void DataEngine::setIcon(const QString &icon)
414{
415 d->icon = icon;
416}
417
418QString DataEngine::icon() const
419{
420 return d->icon;
421}
422
423QString DataEngine::pluginName() const
424{
425 if (!d->dataEngineDescription.isValid()) {
426 return QString();
427 }
428
429 return d->dataEngineDescription.pluginName();
430}
431
432void DataEngine::setDefaultService(const QString &serviceName)
433{
434 d->serviceName = serviceName;
435}
436
437Service* DataEngine::createDefaultService(QObject *parent)
438{
439 QVariantList args;
440 args << QVariant::fromValue<DataEngine*>(this);
441 return Service::load(d->serviceName, args, parent);
442}
443
444void DataEnginePrivate::publish(AnnouncementMethods methods, const QString &name)
445{
446 if (!publishedService) {
447 publishedService = new DataEngineService(q);
448 }
449
450 //QString resourceName =
451 //i18nc("%1 is the name of a dataengine, %2 the name of the machine that engine is published
452//on",
453 //"%1 dataengine on %2", name(), AuthorizationManager::self()->d->myCredentials.name());
454 kDebug() << "name: " << name;
455 publishedService->d->publish(methods, name);
456}
457
458void DataEnginePrivate::unpublish(const QString &name)
459{
460 Q_UNUSED(name)
461
462 if (publishedService) {
463 publishedService->d->unpublish();
464 }
465}
466
467bool DataEnginePrivate::isPublished() const
468{
469 if (publishedService) {
470 return publishedService->d->isPublished();
471 } else {
472 return false;
473 }
474}
475
476const Package *DataEngine::package() const
477{
478 return d->package;
479}
480
481void DataEngine::scheduleSourcesUpdated()
482{
483 if (d->checkSourcesTimerId) {
484 return;
485 }
486
487 d->checkSourcesTimerId = startTimer(0);
488}
489
490QString DataEngine::name() const
491{
492 return d->engineName;
493}
494
495void DataEngine::setName(const QString &name)
496{
497 d->engineName = name;
498 setObjectName(name);
499}
500
501void DataEngine::setStorageEnabled(const QString &source, bool store)
502{
503 DataContainer *s = d->source(source, false);
504 if (s) {
505 s->setStorageEnabled(store);
506 }
507}
508
509// Private class implementations
510DataEnginePrivate::DataEnginePrivate(DataEngine *e, const KPluginInfo &info)
511 : q(e),
512 dataEngineDescription(info),
513 refCount(-1), // first ref
514 checkSourcesTimerId(0),
515 updateTimerId(0),
516 minPollingInterval(-1),
517 limit(0),
518 valid(true),
519 script(0),
520 package(0),
521 publishedService(0)
522{
523 updateTimer.start();
524
525 if (!info.isValid()) {
526 engineName = i18n("Unnamed");
527 return;
528 }
529
530 engineName = info.name();
531 if (engineName.isEmpty()) {
532 engineName = i18n("Unnamed");
533 }
534 e->setObjectName(engineName);
535 icon = info.icon();
536
537 if (dataEngineDescription.isValid()) {
538 QString api = dataEngineDescription.property("X-Plasma-API").toString();
539
540 if (!api.isEmpty()) {
541 const QString path =
542 KStandardDirs::locate("data",
543 "plasma/dataengines/" + dataEngineDescription.pluginName() + '/');
544 PackageStructure::Ptr structure = Plasma::packageStructure(api, Plasma::DataEngineComponent);
545 structure->setPath(path);
546 package = new Package(path, structure);
547
548 script = Plasma::loadScriptEngine(api, q);
549 if (!script) {
550 kDebug() << "Could not create a" << api << "ScriptEngine for the"
551 << dataEngineDescription.name() << "DataEngine.";
552 delete package;
553 package = 0;
554 }
555 }
556 }
557}
558
559DataEnginePrivate::~DataEnginePrivate()
560{
561 delete script;
562 script = 0;
563 delete package;
564 package = 0;
565}
566
567void DataEnginePrivate::internalUpdateSource(DataContainer *source)
568{
569 if (minPollingInterval > 0 &&
570 source->timeSinceLastUpdate() < (uint)minPollingInterval) {
571 // skip updating this source; it's been too soon
572 //kDebug() << "internal update source is delaying" << source->timeSinceLastUpdate() << minPollingInterval;
573 //but fake an update so that the signalrelay that triggered this gets the data from the
574 //recent update. this way we don't have to worry about queuing - the relay will send a
575 //signal immediately and everyone else is undisturbed.
576 source->setNeedsUpdate();
577 return;
578 }
579
580 if (q->updateSourceEvent(source->objectName())) {
581 //kDebug() << "queuing an update";
582 q->scheduleSourcesUpdated();
583 }/* else {
584 kDebug() << "no update";
585 }*/
586}
587
588void DataEnginePrivate::ref()
589{
590 --refCount;
591}
592
593void DataEnginePrivate::deref()
594{
595 ++refCount;
596}
597
598bool DataEnginePrivate::isUsed() const
599{
600 return refCount != 0;
601}
602
603DataContainer *DataEnginePrivate::source(const QString &sourceName, bool createWhenMissing)
604{
605 DataEngine::SourceDict::const_iterator it = sources.constFind(sourceName);
606 if (it != sources.constEnd()) {
607 DataContainer *s = it.value();
608 if (limit > 0) {
609 QQueue<DataContainer*>::iterator it = sourceQueue.begin();
610 while (it != sourceQueue.end()) {
611 if (*it == s) {
612 sourceQueue.erase(it);
613 break;
614 }
615 ++it;
616 }
617 sourceQueue.enqueue(s);
618 }
619 return s;
620 }
621
622 if (!createWhenMissing) {
623 return 0;
624 }
625
626 //kDebug() << "DataEngine " << q->objectName() << ": could not find DataContainer " << sourceName << ", creating";
627 DataContainer *s = new DataContainer(q);
628 s->setObjectName(sourceName);
629 sources.insert(sourceName, s);
630 QObject::connect(s, SIGNAL(destroyed(QObject*)), q, SLOT(sourceDestroyed(QObject*)));
631 QObject::connect(s, SIGNAL(updateRequested(DataContainer*)),
632 q, SLOT(internalUpdateSource(DataContainer*)));
633
634 if (limit > 0) {
635 trimQueue();
636 sourceQueue.enqueue(s);
637 }
638 return s;
639}
640
641void DataEnginePrivate::connectSource(DataContainer *s, QObject *visualization,
642 uint pollingInterval,
643 Plasma::IntervalAlignment align,
644 bool immediateCall)
645{
646 //kDebug() << "connect source called" << s->objectName() << "with interval" << pollingInterval;
647
648 //FIXME: at the moment a remote dataengine can only poll, a push mechanism will be needed instead
649 if (pollingInterval == 0 && qobject_cast<RemoteDataEngine *>(q)) {
650 pollingInterval = 5000;
651 }
652 if (pollingInterval > 0) {
653 // never more frequently than allowed, never more than 20 times per second
654 uint min = qMax(50, minPollingInterval); // for qMax below
655 pollingInterval = qMax(min, pollingInterval);
656
657 // align on the 50ms
658 pollingInterval = pollingInterval - (pollingInterval % 50);
659 }
660
661 if (immediateCall) {
662 // we don't want to do an immediate call if we are simply
663 // reconnecting
664 //kDebug() << "immediate call requested, we have:" << s->visualizationIsConnected(visualization);
665 immediateCall = !s->data().isEmpty() &&
666 !s->visualizationIsConnected(visualization);
667 }
668
669 s->connectVisualization(visualization, pollingInterval, align);
670
671 if (immediateCall) {
672 QMetaObject::invokeMethod(visualization, "dataUpdated",
673 Q_ARG(QString, s->objectName()),
674 Q_ARG(Plasma::DataEngine::Data, s->data()));
675 s->d->dirty = false;
676 }
677}
678
679void DataEnginePrivate::sourceDestroyed(QObject *object)
680{
681 DataEngine::SourceDict::iterator it = sources.begin();
682 while (it != sources.end()) {
683 if (it.value() == object) {
684 sources.erase(it);
685 emit q->sourceRemoved(object->objectName());
686 break;
687 }
688 ++it;
689 }
690}
691
692DataContainer *DataEnginePrivate::requestSource(const QString &sourceName, bool *newSource)
693{
694 if (newSource) {
695 *newSource = false;
696 }
697
698 //kDebug() << "requesting source " << sourceName;
699 DataContainer *s = source(sourceName, false);
700
701 if (!s) {
702 // we didn't find a data source, so give the engine an opportunity to make one
703 /*kDebug() << "DataEngine " << q->objectName()
704 << ": could not find DataContainer " << sourceName
705 << " will create on request" << endl;*/
706 waitingSourceRequest = sourceName;
707 if (q->sourceRequestEvent(sourceName)) {
708 s = source(sourceName, false);
709 if (s) {
710 // now we have a source; since it was created on demand, assume
711 // it should be removed when not used
712 if (newSource) {
713 *newSource = true;
714 }
715 QObject::connect(s, SIGNAL(becameUnused(QString)), q, SLOT(removeSource(QString)));
716 emit q->sourceAdded(sourceName);
717 }
718 }
719 waitingSourceRequest.clear();
720 }
721
722 return s;
723}
724
725void DataEnginePrivate::trimQueue()
726{
727 uint queueCount = sourceQueue.count();
728 while (queueCount >= limit && !sourceQueue.isEmpty()) {
729 DataContainer *punted = sourceQueue.dequeue();
730 q->removeSource(punted->objectName());
731 queueCount = sourceQueue.count();
732 }
733}
734
735// put all setup routines for script here. at this point we can assume that
736// package exists and that we have a script engine
737void DataEnginePrivate::setupScriptSupport()
738{
739 if (!package) {
740 return;
741 }
742
743 /*
744 kDebug() << "sletting up script support, package is in" << package->path()
745 << "which is a" << package->structure()->type() << "package"
746 << ", main script is" << package->filePath("mainscript");
747 */
748
749 QString translationsPath = package->filePath("translations");
750 if (!translationsPath.isEmpty()) {
751 //FIXME: we should _probably_ use a KComponentData to segregate the applets
752 // from each other; but I want to get the basics working first :)
753 KGlobal::dirs()->addResourceDir("locale", translationsPath);
754 KGlobal::locale()->insertCatalog(package->metadata().pluginName());
755 }
756}
757
758}
759
760#include "dataengine.moc"
761