dRonin  adbada4
dRonin GCS
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
pluginmanager.cpp
Go to the documentation of this file.
1 
15 /*
16  * This program is free software; you can redistribute it and/or modify
17  * it under the terms of the GNU General Public License as published by
18  * the Free Software Foundation; either version 3 of the License, or
19  * (at your option) any later version.
20  *
21  * This program is distributed in the hope that it will be useful, but
22  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
23  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
24  * for more details.
25  *
26  * You should have received a copy of the GNU General Public License along
27  * with this program; if not, see <http://www.gnu.org/licenses/>
28  */
29 
30 #include "pluginmanager.h"
31 #include "pluginmanager_p.h"
32 #include "pluginspec.h"
33 #include "pluginspec_p.h"
34 #include "optionsparser.h"
35 #include "iplugin.h"
36 
37 #include <QtCore/QMetaProperty>
38 #include <QtCore/QDir>
39 #include <QtCore/QTextStream>
40 #include <QtCore/QWriteLocker>
41 #include <QtDebug>
42 #include <QMetaMethod>
43 
44 #ifdef WITH_TESTS
45 #include <QTest>
46 #endif
47 
49 
50 enum { debugLeaks = 0 };
51 
159 using namespace ExtensionSystem;
160 using namespace ExtensionSystem::Internal;
161 
162 static bool lessThanByPluginName(const PluginSpec *one, const PluginSpec *two)
163 {
164  return one->name() < two->name();
165 }
166 
167 PluginManager *PluginManager::m_instance = nullptr;
168 
173 PluginManager *PluginManager::instance()
174 {
175  return m_instance;
176 }
177 
182 PluginManager::PluginManager()
183  : d(new PluginManagerPrivate(this)),m_allPluginsLoaded(false)
184 {
185  m_instance = this;
186 }
187 
192 PluginManager::~PluginManager()
193 {
194  delete d;
195  d = nullptr;
196 }
197 
210 void PluginManager::addObject(QObject *obj)
211 {
212  d->addObject(obj);
213 }
214 
220 void PluginManager::removeObject(QObject *obj)
221 {
222  d->removeObject(obj);
223 }
224 
232 QList<QObject *> PluginManager::allObjects() const
233 {
234  return d->allObjects;
235 }
236 
246 void PluginManager::loadPlugins()
247 {
248  return d->loadPlugins();
249 }
250 
257 QStringList PluginManager::pluginPaths() const
258 {
259  return d->pluginPaths;
260 }
261 
271 void PluginManager::setPluginPaths(const QStringList &paths)
272 {
273  d->setPluginPaths(paths);
274 }
275 
283 QString PluginManager::fileExtension() const
284 {
285  return d->extension;
286 }
287 
295 void PluginManager::setFileExtension(const QString &extension)
296 {
297  d->extension = extension;
298 }
299 
305 QStringList PluginManager::arguments() const
306 {
307  return d->arguments;
308 }
309 
320 QList<PluginSpec *> PluginManager::plugins() const
321 {
322  return d->pluginSpecs;
323 }
324 
331 QStringList PluginManager::parseOptions(QStringList pluginOptions, QStringList pluginTests, QStringList pluginNoLoad)
332 {
333  OptionsParser options(d);
334  return options.parse(pluginOptions, pluginTests, pluginNoLoad);
335 }
336 
337 static inline void indent(QTextStream &str, int indent)
338 {
339  const QChar blank = QLatin1Char(' ');
340  for (int i = 0 ; i < indent; i++)
341  str << blank;
342 }
343 
344 static inline void formatOption(QTextStream &str,
345  const QString &opt, const QString &parm, const QString &description,
346  int optionIndentation, int descriptionIndentation)
347 {
348  int remainingIndent = descriptionIndentation - optionIndentation - opt.size();
349  indent(str, optionIndentation);
350  str << opt;
351  if (!parm.isEmpty()) {
352  str << " <" << parm << '>';
353  remainingIndent -= 3 + parm.size();
354  }
355  indent(str, qMax(0, remainingIndent));
356  str << description << '\n';
357 }
358 
365 void PluginManager::formatPluginOptions(QTextStream &str, int optionIndentation, int descriptionIndentation) const
366 {
367  typedef PluginSpec::PluginArgumentDescriptions PluginArgumentDescriptions;
368  // Check plugins for options
369  const PluginSpecSet::const_iterator pcend = d->pluginSpecs.constEnd();
370  for (PluginSpecSet::const_iterator pit = d->pluginSpecs.constBegin(); pit != pcend; ++pit) {
371  const PluginArgumentDescriptions pargs = (*pit)->argumentDescriptions();
372  if (!pargs.empty()) {
373  str << "\nPlugin: " << (*pit)->name() << '\n';
374  const PluginArgumentDescriptions::const_iterator acend = pargs.constEnd();
375  for (PluginArgumentDescriptions::const_iterator ait =pargs.constBegin(); ait != acend; ++ait)
376  formatOption(str, ait->name, ait->parameter, ait->description, optionIndentation, descriptionIndentation);
377  }
378  }
379 }
380 
387 void PluginManager::formatPluginVersions(QTextStream &str) const
388 {
389  const PluginSpecSet::const_iterator cend = d->pluginSpecs.constEnd();
390  for (PluginSpecSet::const_iterator it = d->pluginSpecs.constBegin(); it != cend; ++it) {
391  const PluginSpec *ps = *it;
392  str << " " << ps->name() << ' ' << ps->version() << ' ' << ps->description() << '\n';
393  }
394 }
395 
396 void PluginManager::startTests()
397 {
398 #ifdef WITH_TESTS
399  bool failed = false;
400 
401  foreach (PluginSpec *pluginSpec, d->testSpecs) {
402  const QMetaObject *mo = pluginSpec->plugin()->metaObject();
403  QStringList methods;
404  methods.append("arg0");
405  // We only want slots starting with "test"
406  for (int i = mo->methodOffset(); i < mo->methodCount(); ++i) {
407  if (QByteArray(mo->method(i).methodSignature()).startsWith("test")) {
408  QString method = QString::fromLatin1(mo->method(i).methodSignature());
409  methods.append(method.left(method.size()-2));
410  }
411  }
412  if (methods.length() > 1 && QTest::qExec(pluginSpec->plugin(), methods) != 0) {
413  failed = true;
414  qWarning() << "Tests failed for plugin:" << pluginSpec->name();
415  }
416  }
417 
418  if (failed)
419  QCoreApplication::exit(1);
420  else if (runningTests())
421  QCoreApplication::exit(0);
422 #endif
423 }
424 
429 bool PluginManager::runningTests() const
430 {
431  return !d->testSpecs.isEmpty();
432 }
433 
438 QString PluginManager::testDataDirectory() const
439 {
440  QString s = GCS_TEST_DIR;
441  s.append("/tests");
442  s = QDir::cleanPath(s);
443  return s;
444 }
445 
446 //============PluginManagerPrivate===========
447 
452 PluginSpec *PluginManagerPrivate::createSpec()
453 {
454  return new PluginSpec();
455 }
456 
461 PluginSpecPrivate *PluginManagerPrivate::privateSpec(PluginSpec *spec)
462 {
463  return spec->d;
464 }
465 
470 PluginManagerPrivate::PluginManagerPrivate(PluginManager *pluginManager)
471  : extension("xml"), q(pluginManager)
472 {
473 }
474 
479 PluginManagerPrivate::~PluginManagerPrivate()
480 {
481  stopAll();
482  qDeleteAll(pluginSpecs);
483  if (!allObjects.isEmpty()) {
484  qDebug() << "There are" << allObjects.size() << "objects left in the plugin manager pool: " << allObjects;
485  }
486 }
487 
488 void PluginManagerPrivate::stopAll()
489 {
490  QList<PluginSpec *> queue = loadQueue();
491  foreach (PluginSpec *spec, queue) {
492  loadPlugin(spec, PluginSpec::Stopped);
493  }
494  QListIterator<PluginSpec *> it(queue);
495  it.toBack();
496  while (it.hasPrevious()) {
497  loadPlugin(it.previous(), PluginSpec::Deleted);
498  }
499 }
500 
505 void PluginManagerPrivate::addObject(QObject *obj)
506 {
507  {
508  QWriteLocker lock(&(q->m_lock));
509  if (obj == nullptr) {
510  qWarning() << "PluginManagerPrivate::addObject(): trying to add null object";
511  return;
512  }
513  if (allObjects.contains(obj)) {
514  qWarning() << "PluginManagerPrivate::addObject(): trying to add duplicate object";
515  return;
516  }
517 
518  if (debugLeaks)
519  qDebug() << "PluginManagerPrivate::addObject" << obj << obj->objectName();
520 
521  allObjects.append(obj);
522  }
523  emit q->objectAdded(obj);
524 }
525 
530 void PluginManagerPrivate::removeObject(QObject *obj)
531 {
532  if (obj == nullptr) {
533  qWarning() << "PluginManagerPrivate::removeObject(): trying to remove null object";
534  return;
535  }
536 
537  if (!allObjects.contains(obj)) {
538  qWarning() << "PluginManagerPrivate::removeObject(): object not in list:"
539  << obj << obj->objectName();
540  return;
541  }
542  if (debugLeaks)
543  qDebug() << "PluginManagerPrivate::removeObject" << obj << obj->objectName();
544 
545  emit q->aboutToRemoveObject(obj);
546  QWriteLocker lock(&(q->m_lock));
547  allObjects.removeAll(obj);
548 }
549 
554 void PluginManagerPrivate::loadPlugins()
555 {
556  QList<PluginSpec *> queue = loadQueue();
557  foreach (PluginSpec *spec, queue) {
558  emit q->splashMessages(QString(QObject::tr("Loading %1 plugin")).arg(spec->name()));
559  loadPlugin(spec, PluginSpec::Loaded);
560  if(spec->name() == "Core") {
561  QObject::connect(spec->plugin(),SIGNAL(splashMessages(QString)), q, SIGNAL(splashMessages(QString)));
562  QObject::connect(spec->plugin(),SIGNAL(showSplash()), q, SIGNAL(showSplash()));
563  QObject::connect(spec->plugin(),SIGNAL(hideSplash()), q, SIGNAL(hideSplash()));
564  }
565  }
566 
567  foreach (PluginSpec *spec, queue) {
568  emit q->splashMessages(QString(QObject::tr("Initializing %1 plugin")).arg(spec->name()));
569  loadPlugin(spec, PluginSpec::Initialized);
570  }
571  QListIterator<PluginSpec *> it(queue);
572  it.toBack();
573  while (it.hasPrevious()) {
574  loadPlugin(it.previous(), PluginSpec::Running);
575  }
576  emit q->pluginsChanged();
577  q->m_allPluginsLoaded=true;
578  emit q->pluginsLoadEnded();
579 }
580 
585 QList<PluginSpec *> PluginManagerPrivate::loadQueue()
586 {
587  QList<PluginSpec *> queue;
588  foreach (PluginSpec *spec, pluginSpecs) {
589  QList<PluginSpec *> circularityCheckQueue;
590  loadQueue(spec, queue, circularityCheckQueue);
591  }
592  return queue;
593 }
594 
599 bool PluginManagerPrivate::loadQueue(PluginSpec *spec, QList<PluginSpec *> &queue,
600  QList<PluginSpec *> &circularityCheckQueue)
601 {
602  if (queue.contains(spec))
603  return true;
604  // check for circular dependencies
605  if (circularityCheckQueue.contains(spec)) {
606  spec->d->hasError = true;
607  spec->d->errorString = PluginManager::tr("Circular dependency detected:\n");
608  int index = circularityCheckQueue.indexOf(spec);
609  for (int i = index; i < circularityCheckQueue.size(); ++i) {
610  spec->d->errorString.append(PluginManager::tr("%1(%2) depends on\n")
611  .arg(circularityCheckQueue.at(i)->name()).arg(circularityCheckQueue.at(i)->version()));
612  }
613  spec->d->errorString.append(PluginManager::tr("%1(%2)").arg(spec->name()).arg(spec->version()));
614  return false;
615  }
616  circularityCheckQueue.append(spec);
617  // check if we have the dependencies
618  if (spec->state() == PluginSpec::Invalid || spec->state() == PluginSpec::Read) {
619  spec->d->hasError = true;
620  spec->d->errorString += "\n";
621  spec->d->errorString += PluginManager::tr("Cannot load plugin because dependencies are not resolved");
622  return false;
623  }
624  // add dependencies
625  foreach (PluginSpec *depSpec, spec->dependencySpecs()) {
626  if (!loadQueue(depSpec, queue, circularityCheckQueue)) {
627  spec->d->hasError = true;
628  spec->d->errorString =
629  PluginManager::tr("Cannot load plugin because dependency failed to load: %1(%2)\nReason: %3")
630  .arg(depSpec->name()).arg(depSpec->version()).arg(depSpec->errorString());
631  return false;
632  }
633  }
634  // add self
635  queue.append(spec);
636  return true;
637 }
638 
643 void PluginManagerPrivate::loadPlugin(PluginSpec *spec, PluginSpec::State destState)
644 {
645  if (spec->hasError())
646  return;
647  if (destState == PluginSpec::Running) {
648  spec->d->initializeExtensions();
649  return;
650  } else if (destState == PluginSpec::Deleted) {
651  spec->d->kill();
652  return;
653  }
654  foreach (PluginSpec *depSpec, spec->dependencySpecs()) {
655  if (depSpec->state() != destState) {
656  spec->d->hasError = true;
657  spec->d->errorString =
658  PluginManager::tr("Cannot load plugin because dependency failed to load: %1(%2)\nReason: %3")
659  .arg(depSpec->name()).arg(depSpec->version()).arg(depSpec->errorString());
660  return;
661  }
662  }
663  if (destState == PluginSpec::Loaded)
664  spec->d->loadLibrary();
665  else if (destState == PluginSpec::Initialized)
666  spec->d->initializePlugin();
667  else if (destState == PluginSpec::Stopped)
668  spec->d->stop();
669 }
670 
675 void PluginManagerPrivate::setPluginPaths(const QStringList &paths)
676 {
677  pluginPaths = paths;
678  readPluginPaths();
679 }
680 
685 void PluginManagerPrivate::readPluginPaths()
686 {
687  qDeleteAll(pluginSpecs);
688  pluginSpecs.clear();
689 
690  QStringList specFiles;
691  QStringList searchPaths = pluginPaths;
692  while (!searchPaths.isEmpty()) {
693  const QDir dir(searchPaths.takeFirst());
694  const QFileInfoList files = dir.entryInfoList(QStringList() << QString("*.%1").arg(extension), QDir::Files);
695  foreach (const QFileInfo &file, files)
696  specFiles << file.absoluteFilePath();
697  const QFileInfoList dirs = dir.entryInfoList(QDir::Dirs|QDir::NoDotAndDotDot);
698  foreach (const QFileInfo &subdir, dirs)
699  searchPaths << subdir.absoluteFilePath();
700  }
701  foreach (const QString &specFile, specFiles) {
702  PluginSpec *spec = new PluginSpec;
703  spec->d->read(specFile);
704  pluginSpecs.append(spec);
705  }
706  resolveDependencies();
707  // ensure deterministic plugin load order by sorting
708  std::sort(pluginSpecs.begin(), pluginSpecs.end(), lessThanByPluginName);
709  emit q->pluginsChanged();
710 }
711 
712 void PluginManagerPrivate::resolveDependencies()
713 {
714  foreach (PluginSpec *spec, pluginSpecs) {
715  spec->d->resolveDependencies(pluginSpecs);
716  }
717 }
718 
719  // Look in argument descriptions of the specs for the option.
720 PluginSpec *PluginManagerPrivate::pluginForOption(const QString &option, bool *requiresArgument) const
721 {
722  // Look in the plugins for an option
723  typedef PluginSpec::PluginArgumentDescriptions PluginArgumentDescriptions;
724 
725  *requiresArgument = false;
726  const PluginSpecSet::const_iterator pcend = pluginSpecs.constEnd();
727  for (PluginSpecSet::const_iterator pit = pluginSpecs.constBegin(); pit != pcend; ++pit) {
728  PluginSpec *ps = *pit;
729  const PluginArgumentDescriptions pargs = ps->argumentDescriptions();
730  if (!pargs.empty()) {
731  const PluginArgumentDescriptions::const_iterator acend = pargs.constEnd();
732  for (PluginArgumentDescriptions::const_iterator ait = pargs.constBegin(); ait != acend; ++ait) {
733  if (ait->name == option) {
734  *requiresArgument = !ait->parameter.isEmpty();
735  return ps;
736  }
737  }
738  }
739  }
740  return nullptr;
741 }
742 
743 PluginSpec *PluginManagerPrivate::pluginByName(const QString &name) const
744 {
745  foreach (PluginSpec *spec, pluginSpecs)
746  if (spec->name() == name)
747  return spec;
748  return nullptr;
749 }
void setPluginPaths(const QStringList &paths)
Core plugin system that manages the plugins, their life cycle and their registered objects...
Definition: pluginmanager.h:53
for i
Definition: OPPlots.m:140
Eccentricity n
Definition: OPPlots.m:137
QList< ExtensionSystem::PluginSpec * > PluginSpecSet
Contains the information of the plugins xml description file and information about the plugin's curre...
Definition: pluginspec.h:63