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