1 /***************************************************************************
2 * *
3 * copyright : (C) 2010 The University of Toronto *
4 * netterfield@astro.utoronto.ca *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 * *
11 ***************************************************************************/
12
13 #include "datasourcepluginmanager.h"
14
15
16 #include "datasource.h"
17
18 #include <assert.h>
19
20 #include <QApplication>
21 #include <QDebug>
22 #include <QDir>
23 #include <QFile>
24 #include <QFileInfo>
25 #include <QLibraryInfo>
26 #include <QPluginLoader>
27 #include <QTextDocument>
28 #include <QUrl>
29 #include <QXmlStreamWriter>
30 #include <QTimer>
31 #include <QFileSystemWatcher>
32
33 #include "datacollection.h"
34 #include "debug.h"
35 #include "objectstore.h"
36 #include "scalar.h"
37 #include "string.h"
38 #include "updatemanager.h"
39 #include "settings.h"
40 #include "dataplugin.h"
41
42 #define DATASOURCE_UPDATE_TIMER_LENGTH 1000
43
44 using namespace Kst;
45
46
pluginSearchPaths()47 QStringList Kst::pluginSearchPaths()
48 {
49 QStringList pluginPaths;
50
51 QDir rootDir = QApplication::applicationDirPath();
52 rootDir.cdUp();
53 QString path = rootDir.canonicalPath() + '/';
54 pluginPaths << path + QLatin1String("plugins");
55 pluginPaths << path + QLatin1String(KST_INSTALL_PLUGINS);
56 #if defined(__QNX__)
57 pluginPaths << "app/native/plugins";
58 #endif
59
60 rootDir.cdUp();
61 path = rootDir.canonicalPath() + '/';
62 path += QLatin1String(KST_INSTALL_PLUGINS);
63 // Visal Studio paths
64 pluginPaths << path + QLatin1String("/Release");
65 pluginPaths << path + QLatin1String("/Debug");
66 pluginPaths << path + QLatin1String("/RelWithDebInfo");
67
68 Debug::self()->log(DataSource::tr("\nPlugin Search Paths:"));
69 foreach(const QString& p, pluginPaths) {
70 Debug::self()->log(QString("-> %1").arg(p));
71 }
72
73 return pluginPaths;
74 }
75
76
settingsObject()77 QSettings& DataSourcePluginManager::settingsObject()
78 {
79 static QSettings& settingsObject = createSettings("data");
80 return settingsObject;
81 }
82
83 QMap<QString,QString> DataSourcePluginManager::url_map;
84
85
urlMap()86 const QMap<QString,QString> DataSourcePluginManager::urlMap() {
87 return url_map;
88 }
89
init()90 void DataSourcePluginManager::init() {
91 initPlugins();
92 }
93
94
95
96
97 struct FoundPlugin
98 {
FoundPluginFoundPlugin99 FoundPlugin(const SharedPtr<DataSourcePluginInterface>& plug, const QString& path) :
100 plugin(plug),
101 filePath(path)
102 {}
103
104 SharedPtr<DataSourcePluginInterface> plugin;
105 // TODO add filepath to PluginInterface
106 QString filePath;
107 };
108
109 typedef QList<FoundPlugin> PluginList;
110 static PluginList _pluginList;
111
112
cleanupForExit()113 void DataSourcePluginManager::cleanupForExit() {
114 _pluginList.clear();
115 qDebug() << "cleaning up for exit in datasource";
116 // for (QMap<QString,QString>::Iterator i = urlMap.begin(); i != urlMap.end(); ++i) {
117 // KIO::NetAccess::removeTempFile(i.value());
118 // }
119 url_map.clear();
120 }
121
122
obtainFile(const QString & source)123 QString DataSourcePluginManager::obtainFile(const QString& source) {
124 QUrl url;
125
126 if (QFile::exists(source) && QFileInfo(source).isRelative()) {
127 url.setPath(source);
128 } else {
129 url = QUrl(source);
130 }
131
132 // if (url.isLocalFile() || url.protocol().isEmpty() || url.protocol().toLower() == "nad") {
133 return source;
134 // }
135
136 if (url_map.contains(source)) {
137 return url_map[source];
138 }
139
140 // FIXME: come up with a way to indicate the "widget" and fill it in here so
141 // that KIO dialogs are associated with the proper window
142 // if (!KIO::NetAccess::exists(url, true, 0L)) {
143 // return QString();
144 // }
145
146 QString tmpFile;
147 // FIXME: come up with a way to indicate the "widget" and fill it in here so
148 // that KIO dialogs are associated with the proper window
149 // if (!KIO::NetAccess::download(url, tmpFile, 0L)) {
150 // return QString();
151 // }
152
153 url_map[source] = tmpFile;
154
155 return tmpFile;
156 }
157
158
159 // Scans for plugins and stores the information for them in "_pluginList"
scanPlugins()160 static void scanPlugins() {
161 PluginList tmpList;
162
163 Debug::self()->log(DataSource::tr("Scanning for data-source plugins."));
164
165 foreach (QObject *plugin, QPluginLoader::staticInstances()) {
166 //try a cast
167 if (DataSourcePluginInterface *ds = qobject_cast<DataSourcePluginInterface*>(plugin)) {
168 tmpList.append(FoundPlugin(ds, ""));
169 }
170 }
171
172 QStringList pluginPaths = pluginSearchPaths();
173 foreach (const QString& pluginPath, pluginPaths) {
174 QDir d(pluginPath);
175 foreach (const QString &fileName, d.entryList(QDir::Files)) {
176 #ifdef Q_OS_WIN
177 if (!fileName.endsWith(QLatin1String(".dll")))
178 continue;
179 #endif
180 QPluginLoader loader(d.absoluteFilePath(fileName));
181 QObject *plugin = loader.instance();
182 if (plugin) {
183 if (DataSourcePluginInterface *ds = qobject_cast<DataSourcePluginInterface*>(plugin)) {
184
185 tmpList.append(FoundPlugin(ds, d.absoluteFilePath(fileName)));
186 Debug::self()->log(DataSource::tr("Plugin loaded: %1").arg(fileName));
187 }
188 } else {
189 Debug::self()->log(DataSource::tr("instance failed for %1 (%2)").arg(fileName).arg(loader.errorString()));
190 }
191 }
192 }
193
194 // This cleans up plugins that have been uninstalled and adds in new ones.
195 // Since it is a shared pointer it can't dangle anywhere.
196 _pluginList.clear();
197 _pluginList = tmpList;
198 }
199
initPlugins()200 void DataSourcePluginManager::initPlugins() {
201 if (_pluginList.isEmpty()) {
202 scanPlugins();
203 }
204 }
205
206
pluginList()207 QStringList DataSourcePluginManager::pluginList() {
208 // Ensure state. When using kstapp MainWindow calls init.
209 init();
210
211 QStringList plugins;
212 for (PluginList::ConstIterator it = _pluginList.constBegin(); it != _pluginList.constEnd(); ++it) {
213 plugins += (*it).plugin->pluginName();
214 }
215
216 return plugins;
217 }
218
219
pluginFileName(const QString & pluginName)220 QString DataSourcePluginManager::pluginFileName(const QString& pluginName)
221 {
222 for (PluginList::ConstIterator it = _pluginList.constBegin(); it != _pluginList.constEnd(); ++it) {
223 if (it->plugin->pluginName() == pluginName) {
224 return it->filePath;
225 }
226 }
227 return "not available";
228 }
229
230
operator <(const PluginSortContainer & x) const231 int DataSourcePluginManager::PluginSortContainer::operator<(const PluginSortContainer& x) const {
232 return match > x.match; // yes, this is by design. biggest go first
233 }
operator ==(const PluginSortContainer & x) const234 int DataSourcePluginManager::PluginSortContainer::operator==(const PluginSortContainer& x) const {
235 return match == x.match;
236 }
237
238
239
bestPluginsForSource(const QString & filename,const QString & type)240 QList<DataSourcePluginManager::PluginSortContainer> DataSourcePluginManager::bestPluginsForSource(const QString& filename, const QString& type) {
241
242 QList<PluginSortContainer> bestPlugins;
243 DataSourcePluginManager::init();
244
245 PluginList info = _pluginList;
246
247 if (!type.isEmpty()) {
248 for (PluginList::Iterator it = info.begin(); it != info.end(); ++it) {
249 if (DataSourcePluginInterface *p = (*it).plugin.data()) {
250 if (p->provides(type)) {
251 PluginSortContainer psc;
252 psc.match = 100;
253 psc.plugin = p;
254 bestPlugins.append(psc);
255 return bestPlugins;
256 }
257 }
258 }
259 }
260
261 for (PluginList::Iterator it = info.begin(); it != info.end(); ++it) {
262 PluginSortContainer psc;
263 if (DataSourcePluginInterface *p = (*it).plugin.data()) {
264 if ((psc.match = p->understands(&settingsObject(), filename)) > 0) {
265 psc.plugin = p;
266 bestPlugins.append(psc);
267 }
268 }
269 }
270
271 qSort(bestPlugins);
272
273 return bestPlugins;
274 }
275
276
findPluginFor(ObjectStore * store,const QString & filename,const QString & type,const QDomElement & e)277 DataSourcePtr DataSourcePluginManager::findPluginFor(ObjectStore *store, const QString& filename, const QString& type, const QDomElement& e) {
278
279 QList<PluginSortContainer> bestPlugins = bestPluginsForSource(filename, type);
280
281 // we don't actually iterate here, unless the first plugin fails. (Not sure this helps at all.)
282 for (QList<PluginSortContainer>::Iterator i = bestPlugins.begin(); i != bestPlugins.end(); ++i) {
283 DataSourcePtr plugin = (*i).plugin->create(store, &settingsObject(), filename, QString(), e);
284 if (plugin) {
285
286 // add strings
287 const QStringList strings = plugin->string().list();
288 if (!strings.isEmpty()) {
289 foreach(const QString& key, strings) {
290 QString value;
291 DataString::ReadInfo readInfo(&value);
292 plugin->string().read(key, readInfo);
293 StringPtr s = store->createObject<String>();
294 s->setProvider(plugin);
295 s->setSlaveName(key);
296 s->setValue(value);
297 plugin->slavePrimitives.append(s);
298 }
299 }
300
301 // add scalars
302 const QStringList scalars = plugin->scalar().list();
303 if (!scalars.isEmpty()) {
304 foreach(const QString& key, scalars) {
305 double value;
306 DataScalar::ReadInfo readInfo(&value);
307 plugin->scalar().read(key, readInfo);
308 ScalarPtr s = store->createObject<Scalar>();
309 s->setProvider(plugin);
310 s->setSlaveName(key);
311 plugin->slavePrimitives.append(s);
312 s->setValue(value);
313 }
314 }
315
316 return plugin;
317 }
318 }
319 return 0L;
320 }
321
322
loadSource(ObjectStore * store,const QString & filename,const QString & type)323 DataSourcePtr DataSourcePluginManager::loadSource(ObjectStore *store, const QString& filename, const QString& type) {
324
325 #ifndef Q_OS_WIN32
326 //if (filename == "stdin" || filename == "-") {
327 // FIXME: what store do we put this in?
328 // return new StdinSource(0, settingsObject());
329 //}
330 #endif
331 QString fn = obtainFile(filename);
332 if (fn.isEmpty()) {
333 return 0;
334 }
335
336 if (!QFileInfo(fn).exists()) {
337 Debug::self()->log(QObject::tr("File '%1' does not exist.").arg(fn), Debug::Warning);
338 return 0;
339 }
340
341 DataSourcePtr dataSource = findPluginFor(store, fn, type);
342 if (dataSource) {
343 store->addObject<DataSource>(dataSource);
344 }
345
346 return dataSource;
347 }
348
349
findOrLoadSource(ObjectStore * store,const QString & filename,bool updatesDisabled)350 DataSourcePtr DataSourcePluginManager::findOrLoadSource(ObjectStore *store, const QString& filename, bool updatesDisabled) {
351 Q_ASSERT(store);
352
353 DataSourcePtr dataSource = store->dataSourceList().findReusableFileName(filename);
354
355 if (!dataSource) {
356 dataSource = DataSourcePluginManager::loadSource(store, filename);
357 if (!updatesDisabled) {
358 if (dataSource) {
359 dataSource->enableUpdates();
360 }
361 }
362 }
363
364 return dataSource;
365 }
366
367
validSource(const QString & filename)368 bool DataSourcePluginManager::validSource(const QString& filename) {
369 #ifndef Q_OS_WIN32
370 // if (filename == "stdin" || filename == "-") {
371 // return true;
372 // }
373 #endif
374 QString fn = obtainFile(filename);
375 if (fn.isEmpty()) {
376 return false;
377 }
378
379 DataSourcePluginManager::init();
380
381 PluginList info = _pluginList;
382
383 for (PluginList::Iterator it = info.begin(); it != info.end(); ++it) {
384 if (DataSourcePluginInterface *p = (*it).plugin.data()) {
385 if ((p->understands(&settingsObject(), filename)) > 0) {
386 return true;
387 }
388 }
389 }
390
391 return false;
392 }
393
394
395
pluginHasConfigWidget(const QString & plugin)396 bool DataSourcePluginManager::pluginHasConfigWidget(const QString& plugin) {
397 initPlugins();
398
399 PluginList info = _pluginList;
400
401 for (PluginList::ConstIterator it = info.constBegin(); it != info.constEnd(); ++it) {
402 if ((*it).plugin->pluginName() == plugin) {
403 return (*it).plugin->hasConfigWidget();
404 }
405 }
406
407 return false;
408 }
409
410
configWidgetForPlugin(const QString & plugin)411 DataSourceConfigWidget* DataSourcePluginManager::configWidgetForPlugin(const QString& plugin) {
412 initPlugins();
413
414 PluginList info = _pluginList;
415
416 for (PluginList::Iterator it = info.begin(); it != info.end(); ++it) {
417 if (DataSourcePluginInterface *p = (*it).plugin.data()) {
418 if (p->pluginName() == plugin) {
419 return p->configWidget(&settingsObject(), QString());
420 }
421 }
422 }
423
424 return 0L;
425 }
426
427
sourceHasConfigWidget(const QString & filename,const QString & type)428 bool DataSourcePluginManager::sourceHasConfigWidget(const QString& filename, const QString& type) {
429 if (filename == "stdin" || filename == "-") {
430 return 0L;
431 }
432
433 QString fn = obtainFile(filename);
434 if (fn.isEmpty()) {
435 return 0L;
436 }
437
438 QList<PluginSortContainer> bestPlugins = bestPluginsForSource(fn, type);
439 for (QList<PluginSortContainer>::Iterator i = bestPlugins.begin(); i != bestPlugins.end(); ++i) {
440 return (*i).plugin->hasConfigWidget();
441 }
442
443 Debug::self()->log(DataSource::tr("Could not find a datasource for '%1'(%2), but we found one just prior. Something is wrong with Kst.").arg(filename).arg(type), Debug::Error);
444 return false;
445 }
446
447
configWidgetForSource(const QString & filename,const QString & type)448 DataSourceConfigWidget* DataSourcePluginManager::configWidgetForSource(const QString& filename, const QString& type) {
449 if (filename == "stdin" || filename == "-") {
450 return 0L;
451 }
452
453 QString fn = obtainFile(filename);
454 if (fn.isEmpty()) {
455 return 0L;
456 }
457
458 QList<PluginSortContainer> bestPlugins = bestPluginsForSource(fn, type);
459 for (QList<PluginSortContainer>::Iterator i = bestPlugins.begin(); i != bestPlugins.end(); ++i) {
460 DataSourceConfigWidget *w = (*i).plugin->configWidget(&settingsObject(), fn);
461 // Don't iterate.
462 return w;
463 }
464
465 Debug::self()->log(DataSource::tr("Could not find a datasource for '%1'(%2), but we found one just prior. Something is wrong with Kst.").arg(filename).arg(type), Debug::Error);
466 return 0L;
467 }
468
469
470
471
472 /*
473 QStringList DataSourcePluginManager::fieldListForSource(const QString& filename, const QString& type, QString *outType, bool *complete) {
474 if (filename == "stdin" || filename == "-") {
475 return QStringList();
476 }
477
478 QString fn = obtainFile(filename);
479 if (fn.isEmpty()) {
480 return QStringList();
481 }
482
483 QList<PluginSortContainer> bestPlugins = bestPluginsForSource(fn, type);
484 QStringList rc;
485 for (QList<PluginSortContainer>::Iterator i = bestPlugins.begin(); i != bestPlugins.end(); ++i) {
486 QString typeSuggestion;
487 rc = (*i).plugin->fieldList(settingsObject(), fn, QString(), &typeSuggestion, complete);
488 if (!rc.isEmpty()) {
489 if (outType) {
490 if (typeSuggestion.isEmpty()) {
491 *outType = (*i).plugin->provides()[0];
492 } else {
493 *outType = typeSuggestion;
494 }
495 }
496 break;
497 }
498 }
499
500 return rc;
501 }
502 */
503
504 # if 0
505 QStringList DataSourcePluginManager::matrixListForSource(const QString& filename, const QString& type, QString *outType, bool *complete) {
506 if (filename == "stdin" || filename == "-") {
507 return QStringList();
508 }
509
510 QString fn = obtainFile(filename);
511 if (fn.isEmpty()) {
512 return QStringList();
513 }
514
515 QList<PluginSortContainer> bestPlugins = bestPluginsForSource(fn, type);
516 QStringList rc;
517 for (QList<PluginSortContainer>::Iterator i = bestPlugins.begin(); i != bestPlugins.end(); ++i) {
518 QString typeSuggestion;
519 rc = (*i).plugin->matrixList(settingsObject(), fn, QString(), &typeSuggestion, complete);
520 if (!rc.isEmpty()) {
521 if (outType) {
522 if (typeSuggestion.isEmpty()) {
523 *outType = (*i).plugin->provides()[0];
524 } else {
525 *outType = typeSuggestion;
526 }
527 }
528 break;
529 }
530 }
531
532 return rc;
533 }
534 #endif
535
536
scalarListForSource(const QString & filename,const QString & type,QString * outType,bool * complete)537 QStringList DataSourcePluginManager::scalarListForSource(const QString& filename, const QString& type, QString *outType, bool *complete) {
538 if (filename == "stdin" || filename == "-") {
539 return QStringList();
540 }
541
542 QString fn = obtainFile(filename);
543 if (fn.isEmpty()) {
544 return QStringList();
545 }
546
547 QList<PluginSortContainer> bestPlugins = bestPluginsForSource(fn, type);
548 QStringList rc;
549 for (QList<PluginSortContainer>::Iterator i = bestPlugins.begin(); i != bestPlugins.end(); ++i) {
550 QString typeSuggestion;
551 rc = (*i).plugin->scalarList(&settingsObject(), fn, QString(), &typeSuggestion, complete);
552 if (!rc.isEmpty()) {
553 if (outType) {
554 if (typeSuggestion.isEmpty()) {
555 *outType = (*i).plugin->provides()[0];
556 } else {
557 *outType = typeSuggestion;
558 }
559 }
560 break;
561 }
562 }
563
564 return rc;
565 }
566
567
stringListForSource(const QString & filename,const QString & type,QString * outType,bool * complete)568 QStringList DataSourcePluginManager::stringListForSource(const QString& filename, const QString& type, QString *outType, bool *complete) {
569 if (filename == "stdin" || filename == "-") {
570 return QStringList();
571 }
572
573 QString fn = obtainFile(filename);
574 if (fn.isEmpty()) {
575 return QStringList();
576 }
577
578 QList<PluginSortContainer> bestPlugins = bestPluginsForSource(fn, type);
579 QStringList rc;
580 for (QList<PluginSortContainer>::Iterator i = bestPlugins.begin(); i != bestPlugins.end(); ++i) {
581 QString typeSuggestion;
582 rc = (*i).plugin->stringList(&settingsObject(), fn, QString(), &typeSuggestion, complete);
583 if (!rc.isEmpty()) {
584 if (outType) {
585 if (typeSuggestion.isEmpty()) {
586 *outType = (*i).plugin->provides()[0];
587 } else {
588 *outType = typeSuggestion;
589 }
590 }
591 break;
592 }
593 }
594
595 return rc;
596 }
597
598