Fraxinus  16.5.0-fx-rc1
An IGT application
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
cxPluginFramework.cpp
Go to the documentation of this file.
1 /*=========================================================================
2 This file is part of CustusX, an Image Guided Therapy Application.
3 
4 Copyright (c) 2008-2014, SINTEF Department of Medical Technology
5 All rights reserved.
6 
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions are met:
9 
10 1. Redistributions of source code must retain the above copyright notice,
11  this list of conditions and the following disclaimer.
12 
13 2. Redistributions in binary form must reproduce the above copyright notice,
14  this list of conditions and the following disclaimer in the documentation
15  and/or other materials provided with the distribution.
16 
17 3. Neither the name of the copyright holder nor the names of its contributors
18  may be used to endorse or promote products derived from this software
19  without specific prior written permission.
20 
21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 =========================================================================*/
32 
33 #include "cxPluginFramework.h"
34 
35 #include <QApplication>
36 #include <QStringList>
37 #include <QDirIterator>
38 #include <QFileInfo>
39 #include <QDebug>
40 
41 #include "ctkPluginFrameworkFactory.h"
42 #include "ctkPluginFramework.h"
43 #include "ctkPluginContext.h"
44 #include "ctkPluginException.h"
45 
46 #include <ctkConfig.h>
47 
48 #include "cxSettings.h"
49 #include "cxDataLocations.h"
51 
52 #include "cxFileHelpers.h"
53 #include "cxLogger.h"
54 #include <iostream>
55 #include "cxTypeConversions.h"
56 #include "cxProfile.h"
57 
58 namespace cx
59 {
60 
62 {
63  mSettingsBase = "pluginFramework";
64  mSettingsSearchPaths = mSettingsBase + "/searchPaths";
65 
66  ctkProperties fwProps;
67  QString storagePath = ProfileManager::getInstance()->getSettingsPath() + "/pluginFramework";
68 
69  fwProps[ctkPluginConstants::FRAMEWORK_STORAGE] = storagePath;
70 
71  // remove settings as stored by CTK, because full paths are stored here, causing
72  // problems when running both debug and release on the same machine (and similar).
73  fwProps[ctkPluginConstants::FRAMEWORK_STORAGE_CLEAN] = ctkPluginConstants::FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT;
74 
75  mFrameworkFactory.reset(new ctkPluginFrameworkFactory(fwProps));
76  mPluginLibFilter << "*.dll" << "*.so" << "*.dylib";
77 }
78 
80 {
81  if(mFramework->getState() == ctkPlugin::ACTIVE)//LogicManager calls stop() before the destructor is called
82  {
83  CX_LOG_CHANNEL_WARNING("plugin") << "This should not happen: PluginFrameworkManager destructor stopping plugin framework";
84  this->stop();
85  }
86 }
87 
88 QString PluginFrameworkManager::convertToRelativePath(QString path) const
89 {
90  QDir base = qApp->applicationDirPath();
91  return base.relativeFilePath(path);
92 }
93 
94 QString PluginFrameworkManager::convertToAbsolutePath(QString path) const
95 {
96  if (QDir(path).isAbsolute())
97  return QDir(path).absolutePath();
98 
99  QDir base = qApp->applicationDirPath();
100  return QDir(base.path() + "/" + path).absolutePath();
101 }
102 
103 
104 
105 std::vector<PluginFrameworkManager::PluginLoadInfo> PluginFrameworkManager::getPluginLoadInfo(QStringList symbolicNames)
106 {
107  std::vector<PluginLoadInfo> retval;
108 
109  for (unsigned i=0; i<symbolicNames.size(); ++i)
110  {
111  PluginLoadInfo info;
112  info.symbolicName = symbolicNames[i];
113  info.storedState = settings()->value(mSettingsBase+"/"+info.symbolicName).toString();
114 
115  if (info.storedState.isEmpty())
116  {
117  info.isNew = true;
118  info.storedState = getStringForctkPluginState(ctkPlugin::ACTIVE);
119  }
120  else
121  {
122  info.isNew = false;
123  }
124 
125  info.targetState = getctkPluginStateForString(info.storedState);
126 
127  retval.push_back(info);
128  }
129 
130  return retval;
131 }
132 
134 {
135  QStringList paths = settings()->value(mSettingsSearchPaths, QStringList()).toStringList();
136  this->setSearchPaths(paths);
137 
138  QStringList names = this->getPluginSymbolicNames();
139  std::vector<PluginLoadInfo> info = this->getPluginLoadInfo(names);
140 
141  // install all plugins, must do this first in order to let FW handle dependencies.
142  CX_LOG_CHANNEL_INFO("plugin") << "Installing all plugins...";
143  for (unsigned i=0; i< info.size(); ++i)
144  {
145  if (info[i].targetState != ctkPlugin::UNINSTALLED)
146  this->install(info[i].symbolicName);
147  }
148 
149 
150  // start all plugins
151  for (unsigned i=0; i< info.size(); ++i)
152  {
153  if (info[i].targetState == ctkPlugin::ACTIVE)
154  {
155  if (info[i].isNew)
156  CX_LOG_CHANNEL_INFO("plugin") << QString("Autostarting plugin %1").arg(info[i].symbolicName);
157  else
158  CX_LOG_CHANNEL_INFO("plugin") << QString("Starting plugin %1").arg(info[i].symbolicName);
159 
160  this->start(info[i].symbolicName, ctkPlugin::START_TRANSIENT);
161  }
162  else
163  {
164  CX_LOG_CHANNEL_INFO("plugin") << QString("Set plugin to state [%2]: %1")
165  .arg(info[i].symbolicName)
166  .arg(info[i].storedState);
167  }
168  }
169 
170 }
171 
172 void PluginFrameworkManager::saveState()
173 {
174  QStringList relativePaths;
175  for (int i=0; i<mPluginSearchPaths.size(); ++i)
176  relativePaths << this->convertToRelativePath(mPluginSearchPaths[i]);
177  settings()->setValue(mSettingsSearchPaths, relativePaths);
178 
179  QStringList names = this->getPluginSymbolicNames();
180  for (unsigned i=0; i<names.size(); ++i)
181  {
182  QString name = names[i];
183  ctkPlugin::State state = this->getStateFromSymbolicName(name);
184  settings()->setValue(mSettingsBase+"/"+name, getStringForctkPluginState(state));
185  }
186 }
187 
189 {
190  ctkPlugin::State state = ctkPlugin::UNINSTALLED;
191  QSharedPointer<ctkPlugin> plugin = this->getInstalledPluginFromSymbolicName(name);
192  if (plugin)
193  state = plugin->getState();
194  return state;
195 }
196 
197 void PluginFrameworkManager::setSearchPaths(const QStringList& searchPath)
198 {
199  mPluginSearchPaths.clear();
200 
201  for (int i=0; i<searchPath.size(); ++i)
202  mPluginSearchPaths << this->convertToAbsolutePath(searchPath[i]);
203 
204  QStringList defPaths = DataLocations::getDefaultPluginsPath();
205  for (unsigned i=0; i<defPaths.size(); ++i)
206  {
207  QString defPath = this->convertToAbsolutePath(defPaths[i]);
208  if (!mPluginSearchPaths.count(defPath))
209  mPluginSearchPaths << defPath;
210  }
211 
212  mPluginSearchPaths.removeDuplicates();
213 
214  for (int i=0; i<searchPath.size(); ++i)
215  {
216  QApplication::addLibraryPath(searchPath[i]);
217  }
218  emit pluginPoolChanged();
219 }
220 
221 
223 {
224  return mPluginSearchPaths;
225 }
226 
228 {
229  return mFramework->getPluginContext();
230 }
231 
232 QSharedPointer<ctkPluginFramework> PluginFrameworkManager::getPluginFramework()
233 {
234  return mFramework;
235 }
236 
237 void PluginFrameworkManager::initializeFramework()
238 {
239  if (this->frameworkInitialized())
240  return;
241 
242  QSharedPointer<ctkPluginFramework> framework = mFrameworkFactory->getFramework();
243 
244  try
245  {
246  framework->init();
247  } catch (const ctkPluginException& exc)
248  {
249  qCritical() << "Failed to initialize the plug-in framework:" << exc;
250  }
251  mFramework = framework;
252 }
253 
254 bool PluginFrameworkManager::frameworkInitialized() const
255 {
256  return mFramework != 0;
257 }
258 
259 bool PluginFrameworkManager::frameworkStarted() const
260 {
261  return mFramework && (mFramework->getState() == ctkPlugin::ACTIVE);
262 }
263 
264 void PluginFrameworkManager::startFramework()
265 {
266  if (!this->frameworkInitialized())
267  this->initializeFramework();
268 
269  if (this->frameworkStarted())
270  return;
271 
272  try
273  {
274  mFramework->start();
275  }
276  catch (const ctkPluginException& exc)
277  {
278  qCritical() << "Failed to start the plug-in framework:" << exc;
279  }
280 }
281 
282 void PluginFrameworkManager::install(const QString& symbolicName)
283 {
284 
285  this->initializeFramework();
286  if (!this->frameworkInitialized())
287  return;
288 
289  QString pluginPath = this->getPluginPath(symbolicName);
290  if (pluginPath.isEmpty())
291  return;
292 
293  try
294  {
295  ctkPluginContext* pc = this->getPluginContext();
296  pc->installPlugin(QUrl::fromLocalFile(pluginPath))->getPluginId();
297  }
298  catch (const ctkPluginException& exc)
299  {
300  CX_LOG_CHANNEL_ERROR("plugin") << "Failed to install plugin:" << symbolicName << ", " << exc.what();
301  }
302  catch (const ctkRuntimeException& exc)
303  {
304  CX_LOG_CHANNEL_ERROR("plugin")
305  << QString("Failed to install plugin (runtime error): %1, %2")
306  .arg(symbolicName)
307  .arg(exc.what());
308  }
309 }
310 
312 {
313  this->startFramework();
314  return this->frameworkStarted();
315 }
316 
318 {
319  this->saveState();
320 
321  // give plugins time to clean up internal resources before different thread deletes them
322  // (obsolete because we have disabled the other-thread shutdown)
323  emit aboutToStop();
324 
325  // Bypass CTK internal 'shutdown in another thread'-mechanism, activated if we
326  // call framework::stop(). It causes too much trouble regarding Qt objects created
327  // in main thread and deleted in another thread. openCV also has trouble.
328  QStringList plugins = getPluginSymbolicNames();
329  for (int i=0; i<plugins.size(); ++i)
330  {
331  this->stop(plugins[i]);
332  }
333 
334  // stop the framework
335  try
336  {
337  mFramework->stop(); //will start a thread that destructs plugins
338  ctkPluginFrameworkEvent fe = mFramework->waitForStop(5000);
339 // int timeout = 5000;
340 // int interval = 50;
341 // ctkPluginFrameworkEvent fe;
342 // for(int i=0; i<timeout/interval; ++i)
343 // {
344 // fe = mFramework->waitForStop(interval);
345 // qApp->processEvents();
346 // }
347  if (fe.getType() == ctkPluginFrameworkEvent::FRAMEWORK_WAIT_TIMEDOUT)
348  {
349  CX_LOG_CHANNEL_WARNING("plugin") << "Stopping the plugin framework timed out";
350  return false;
351  }
352  }
353  catch (const ctkRuntimeException& e)
354  {
355  CX_LOG_CHANNEL_WARNING("plugin") << QString("Stopping the plugin framework failed: %1").arg(e.what());
356  return false;
357  }
358  return !this->frameworkStarted();
359 }
360 
361 void PluginFrameworkManager::uninstall(const QString& symbolicName)
362 {
363  QString pluginPath = getPluginPath(symbolicName);
364  if (pluginPath.isEmpty())
365  return;
366 
367  try
368  {
369  ctkPluginContext* pc = this->getPluginContext();
370  pc->installPlugin(QUrl::fromLocalFile(pluginPath))->uninstall();
371  }
372  catch (const ctkPluginException& exc)
373  {
374  CX_LOG_CHANNEL_WARNING("plugin") << QString("Failed to uninstall plugin: %1, %2").arg(symbolicName).arg(exc.what());
375  return;
376  }
377 
378  return;
379 }
380 
381 bool PluginFrameworkManager::start(const QString& symbolicName, ctkPlugin::StartOptions options)
382 {
383  this->startFramework();
384 
385  QString pluginPath = getPluginPath(symbolicName);
386  if (pluginPath.isEmpty())
387  {
388  CX_LOG_CHANNEL_ERROR("plugin") << QString("Failed to find plugin %1 in search path.").arg(symbolicName);
389  return false;
390  }
391 
392  try
393  {
394  ctkPluginContext* pc = this->getPluginContext();
395  pc->installPlugin(QUrl::fromLocalFile(pluginPath))->start(options);
396  }
397  catch (const ctkPluginException& exc)
398  {
399  CX_LOG_CHANNEL_ERROR("plugin") << QString("Failed to start plugin (plugin error): %1, %2").arg(symbolicName).arg(exc.what());
400  return false;
401  }
402  catch (const ctkRuntimeException& exc)
403  {
404  CX_LOG_CHANNEL_ERROR("plugin") << QString("Failed to start plugin (runtime error): %1, %2").arg(symbolicName).arg(exc.what());
405  return false;
406  }
407 
408  return true;
409 }
410 
411 bool PluginFrameworkManager::stop(const QString& symbolicName, ctkPlugin::StopOptions options)
412 {
413  if (!this->frameworkStarted())
414  return false;
415  QString pluginPath = this->getPluginPath(symbolicName);
416  if (pluginPath.isEmpty())
417  return false;
418 
419  QSharedPointer<ctkPlugin> plugin = this->getInstalledPluginFromSymbolicName(symbolicName);
420 
421  if (!plugin)
422  {
423  CX_LOG_CHANNEL_WARNING("plugin") << QString("Plugin: %1 not found").arg(symbolicName);
424  return false;
425  }
426 
427  try
428  {
429  plugin->stop(options);
430  }
431  catch (const ctkPluginException& exc)
432  {
433  CX_LOG_CHANNEL_WARNING("plugin") << QString("Failed to stop plugin %1: ").arg(symbolicName).arg(exc.what());
434  return false;
435  }
436 
437  return true;
438 }
439 
440 QSharedPointer<ctkPlugin> PluginFrameworkManager::getInstalledPluginFromSymbolicName(QString symbolicName)
441 {
442  QSharedPointer<ctkPlugin> empty;
443 
444  if (!this->frameworkInitialized())
445  return empty;
446 
447  QString pluginPath = this->getPluginPath(symbolicName);
448  if (pluginPath.isEmpty())
449  return empty;
450 
451  ctkPluginContext* pc = this->getPluginContext();
452  QList < QSharedPointer<ctkPlugin> > plugins = pc->getPlugins();
453  foreach(QSharedPointer<ctkPlugin> plugin, plugins)
454  {
455  if (plugin->getSymbolicName() == symbolicName)
456  {
457  return plugin;
458  }
459  }
460 
461  return empty;
462 }
463 
464 
465 QString PluginFrameworkManager::getPluginPath(const QString& symbolicName)
466 {
467  QString pluginFileName(symbolicName);
468  pluginFileName.replace(".", "_");
469  foreach(QString searchPath, mPluginSearchPaths)
470  {
471  QDirIterator dirIter(searchPath, mPluginLibFilter, QDir::Files, QDirIterator::Subdirectories);
472  while(dirIter.hasNext())
473  {
474  dirIter.next();
475  QFileInfo fileInfo = dirIter.fileInfo();
476  QString fileBaseName = fileInfo.baseName();
477  if (fileBaseName.startsWith("lib")) fileBaseName = fileBaseName.mid(3);
478 
479  if (fileBaseName == pluginFileName)
480  {
481  return fileInfo.canonicalFilePath();
482  }
483  }
484  }
485 
486  return QString();
487 }
488 
490 {
491  QStringList result;
492  foreach(QString searchPath, mPluginSearchPaths)
493  {
494  result.append(this->getPluginSymbolicNames(searchPath));
495  }
496  result.removeDuplicates();
497  return result;
498 }
499 
500 QStringList PluginFrameworkManager::getPluginSymbolicNames(const QString& searchPath)
501 {
502  QStringList result;
503  QDirIterator dirIter(searchPath, mPluginLibFilter, QDir::Files, QDirIterator::Subdirectories);
504  while (dirIter.hasNext())
505  {
506  dirIter.next();
507  QFileInfo fileInfo = dirIter.fileInfo();
508  QString fileBaseName = fileInfo.baseName();
509  if (fileBaseName.startsWith("lib"))
510  fileBaseName = fileBaseName.mid(3);
511  QString name = fileBaseName.replace("_", ".");
512  if (this->nameIsProbablyPlugin(name))
513  result << name;
514  }
515 
516  return result;
517 }
518 
519 bool PluginFrameworkManager::nameIsProbablyPlugin(QString name) const
520 {
521  // heuristic check for plugin-ish name
522  if (name.count(".")<2) // some libs contain a _, they generate too much spam in installed version
523  return false;
524  if (name.contains("cxtest"))
525  return false;
526  return true;
527 
528 }
529 
530 } /* namespace cx */
#define CX_LOG_CHANNEL_INFO(channel)
Definition: cxLogger.h:123
ctkPlugin::State getStateFromSymbolicName(QString name)
#define CX_LOG_CHANNEL_WARNING(channel)
Definition: cxLogger.h:125
QStringList getSearchPaths() const
static ProfileManager * getInstance(QString defaultProfile=QString("Laboratory"))
returns the only instance of this class
Definition: cxProfile.cpp:183
QVariant value(const QString &key, const QVariant &defaultValue=QVariant()) const
Definition: cxSettings.cpp:87
QSharedPointer< ctkPluginFramework > getPluginFramework()
void uninstall(const QString &symbolicName)
void install(const QString &symbolicName)
QSharedPointer< ctkPlugin > getInstalledPluginFromSymbolicName(QString symbolicName)
void setValue(const QString &key, const QVariant &value)
Definition: cxSettings.cpp:79
ctkPluginContext * getPluginContext()
Settings * settings()
Shortcut for accessing the settings instance.
Definition: cxSettings.cpp:42
static QStringList getDefaultPluginsPath()
return the folder where plugins should be located, by default.
QString getStringForctkPluginState(const ctkPlugin::State state)
QString getSettingsPath()
Definition: cxProfile.cpp:235
ctkPlugin::State getctkPluginStateForString(QString text)
#define CX_LOG_CHANNEL_ERROR(channel)
Definition: cxLogger.h:126
void setSearchPaths(const QStringList &searchPath)