CustusX  15.4.0-beta
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  // remove settings as stored by CTK, because full paths are stored here, causing
70  // problems when running both debug and release on the same machine (and similar).
71  //removeNonemptyDirRecursively(storagePath);
72 
73  fwProps[ctkPluginConstants::FRAMEWORK_STORAGE] = storagePath;
74 
75  // clear settings stored by ctk
76  fwProps[ctkPluginConstants::FRAMEWORK_STORAGE_CLEAN] = ctkPluginConstants::FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT;
77 
78  mFrameworkFactory.reset(new ctkPluginFrameworkFactory(fwProps));
79  mPluginLibFilter << "*.dll" << "*.so" << "*.dylib";
80 }
81 
83 {
84  if(mFramework->getState() == ctkPlugin::ACTIVE)//LogicManager calls stop() before the destructor is called
85  {
86  CX_LOG_CHANNEL_WARNING("plugin") << "This should not happen: PluginFrameworkManager destructor stopping plugin framework";
87  this->stop();
88  }
89 }
90 
91 QString PluginFrameworkManager::convertToRelativePath(QString path) const
92 {
93  QDir base = qApp->applicationDirPath();
94  return base.relativeFilePath(path);
95 }
96 
97 QString PluginFrameworkManager::convertToAbsolutePath(QString path) const
98 {
99  if (QDir(path).isAbsolute())
100  return QDir(path).absolutePath();
101 
102  QDir base = qApp->applicationDirPath();
103  return QDir(base.path() + "/" + path).absolutePath();
104 }
105 
106 
107 
108 std::vector<PluginFrameworkManager::PluginLoadInfo> PluginFrameworkManager::getPluginLoadInfo(QStringList symbolicNames)
109 {
110  std::vector<PluginLoadInfo> retval;
111 
112  for (unsigned i=0; i<symbolicNames.size(); ++i)
113  {
114  PluginLoadInfo info;
115  info.symbolicName = symbolicNames[i];
116  info.storedState = settings()->value(mSettingsBase+"/"+info.symbolicName).toString();
117 
118  if (info.storedState.isEmpty())
119  {
120  info.isNew = true;
121  info.storedState = getStringForctkPluginState(ctkPlugin::ACTIVE);
122  }
123  else
124  {
125  info.isNew = false;
126  }
127 
128  info.targetState = getctkPluginStateForString(info.storedState);
129 
130  retval.push_back(info);
131  }
132 
133  return retval;
134 }
135 
137 {
138  QStringList paths = settings()->value(mSettingsSearchPaths, QStringList()).toStringList();
139  this->setSearchPaths(paths);
140 
141  QStringList names = this->getPluginSymbolicNames();
142  std::vector<PluginLoadInfo> info = this->getPluginLoadInfo(names);
143 
144  // install all plugins, must do this first in order to let FW handle dependencies.
145  CX_LOG_CHANNEL_INFO("plugin") << "Installing all plugins...";
146  for (unsigned i=0; i< info.size(); ++i)
147  {
148  if (info[i].targetState != ctkPlugin::UNINSTALLED)
149  this->install(info[i].symbolicName);
150  }
151 
152 
153  // start all plugins
154  for (unsigned i=0; i< info.size(); ++i)
155  {
156  if (info[i].targetState == ctkPlugin::ACTIVE)
157  {
158  if (info[i].isNew)
159  CX_LOG_CHANNEL_INFO("plugin") << QString("Autostarting plugin %1").arg(info[i].symbolicName);
160  else
161  CX_LOG_CHANNEL_INFO("plugin") << QString("Starting plugin %1").arg(info[i].symbolicName);
162 
163  this->start(info[i].symbolicName, ctkPlugin::START_TRANSIENT);
164  }
165  else
166  {
167  CX_LOG_CHANNEL_INFO("plugin") << QString("Set plugin to state [%2]: %1")
168  .arg(info[i].symbolicName)
169  .arg(info[i].storedState);
170  }
171  }
172 
173 }
174 
175 void PluginFrameworkManager::saveState()
176 {
177  QStringList relativePaths;
178  for (int i=0; i<mPluginSearchPaths.size(); ++i)
179  relativePaths << this->convertToRelativePath(mPluginSearchPaths[i]);
180  settings()->setValue(mSettingsSearchPaths, relativePaths);
181 
182  QStringList names = this->getPluginSymbolicNames();
183  for (unsigned i=0; i<names.size(); ++i)
184  {
185  QString name = names[i];
186  ctkPlugin::State state = this->getStateFromSymbolicName(name);
187  settings()->setValue(mSettingsBase+"/"+name, getStringForctkPluginState(state));
188  }
189 }
190 
192 {
193  ctkPlugin::State state = ctkPlugin::UNINSTALLED;
194  QSharedPointer<ctkPlugin> plugin = this->getInstalledPluginFromSymbolicName(name);
195  if (plugin)
196  state = plugin->getState();
197  return state;
198 }
199 
200 void PluginFrameworkManager::setSearchPaths(const QStringList& searchPath)
201 {
202  mPluginSearchPaths.clear();
203 
204  for (int i=0; i<searchPath.size(); ++i)
205  mPluginSearchPaths << this->convertToAbsolutePath(searchPath[i]);
206 
207  QStringList defPaths = DataLocations::getDefaultPluginsPath();
208  for (unsigned i=0; i<defPaths.size(); ++i)
209  {
210  QString defPath = this->convertToAbsolutePath(defPaths[i]);
211  if (!mPluginSearchPaths.count(defPath))
212  mPluginSearchPaths << defPath;
213  }
214 
215  mPluginSearchPaths.removeDuplicates();
216 
217  for (int i=0; i<searchPath.size(); ++i)
218  {
219  QApplication::addLibraryPath(searchPath[i]);
220  }
221  emit pluginPoolChanged();
222 }
223 
224 
226 {
227  return mPluginSearchPaths;
228 }
229 
231 {
232  return mFramework->getPluginContext();
233 }
234 
235 QSharedPointer<ctkPluginFramework> PluginFrameworkManager::getPluginFramework()
236 {
237  return mFramework;
238 }
239 
240 void PluginFrameworkManager::initializeFramework()
241 {
242  if (this->frameworkInitialized())
243  return;
244 
245  QSharedPointer<ctkPluginFramework> framework = mFrameworkFactory->getFramework();
246 
247  try
248  {
249  framework->init();
250  } catch (const ctkPluginException& exc)
251  {
252  qCritical() << "Failed to initialize the plug-in framework:" << exc;
253  }
254  mFramework = framework;
255 }
256 
257 bool PluginFrameworkManager::frameworkInitialized() const
258 {
259  return mFramework != 0;
260 }
261 
262 bool PluginFrameworkManager::frameworkStarted() const
263 {
264  return mFramework && (mFramework->getState() == ctkPlugin::ACTIVE);
265 }
266 
267 void PluginFrameworkManager::startFramework()
268 {
269  if (!this->frameworkInitialized())
270  this->initializeFramework();
271 
272  if (this->frameworkStarted())
273  return;
274 
275  try
276  {
277  mFramework->start();
278  }
279  catch (const ctkPluginException& exc)
280  {
281  qCritical() << "Failed to start the plug-in framework:" << exc;
282  }
283 }
284 
285 void PluginFrameworkManager::install(const QString& symbolicName)
286 {
287 
288  this->initializeFramework();
289  if (!this->frameworkInitialized())
290  return;
291 
292  QString pluginPath = this->getPluginPath(symbolicName);
293  if (pluginPath.isEmpty())
294  return;
295 
296  try
297  {
298  ctkPluginContext* pc = this->getPluginContext();
299  pc->installPlugin(QUrl::fromLocalFile(pluginPath))->getPluginId();
300  }
301  catch (const ctkPluginException& exc)
302  {
303  CX_LOG_CHANNEL_ERROR("plugin") << "Failed to install plugin:" << symbolicName << ", " << exc.what();
304  }
305  catch (const ctkRuntimeException& exc)
306  {
307  CX_LOG_CHANNEL_ERROR("plugin")
308  << QString("Failed to install plugin (runtime error): %1, %2")
309  .arg(symbolicName)
310  .arg(exc.what());
311  }
312 }
313 
315 {
316  this->startFramework();
317  return this->frameworkStarted();
318 }
319 
321 {
322  this->saveState();
323 
324  // give plugins time to clean up internal resources before different thread deletes them
325  // (obsolete because we have disabled the other-thread shutdown)
326  emit aboutToStop();
327 
328  // Bypass CTK internal 'shutdown in another thread'-mechanism, activated if we
329  // call framework::stop(). It causes too much trouble regarding Qt objects created
330  // in main thread and deleted in another thread. openCV also has trouble.
331  QStringList plugins = getPluginSymbolicNames();
332  for (int i=0; i<plugins.size(); ++i)
333  {
334  this->stop(plugins[i]);
335  }
336 
337  // stop the framework
338  try
339  {
340  mFramework->stop(); //will start a thread that destructs plugins
341  ctkPluginFrameworkEvent fe = mFramework->waitForStop(5000);
342 // int timeout = 5000;
343 // int interval = 50;
344 // ctkPluginFrameworkEvent fe;
345 // for(int i=0; i<timeout/interval; ++i)
346 // {
347 // fe = mFramework->waitForStop(interval);
348 // qApp->processEvents();
349 // }
350  if (fe.getType() == ctkPluginFrameworkEvent::FRAMEWORK_WAIT_TIMEDOUT)
351  {
352  CX_LOG_CHANNEL_WARNING("plugin") << "Stopping the plugin framework timed out";
353  return false;
354  }
355  }
356  catch (const ctkRuntimeException& e)
357  {
358  CX_LOG_CHANNEL_WARNING("plugin") << QString("Stopping the plugin framework failed: %1").arg(e.what());
359  return false;
360  }
361  return !this->frameworkStarted();
362 }
363 
364 void PluginFrameworkManager::uninstall(const QString& symbolicName)
365 {
366  QString pluginPath = getPluginPath(symbolicName);
367  if (pluginPath.isEmpty())
368  return;
369 
370  try
371  {
372  ctkPluginContext* pc = this->getPluginContext();
373  pc->installPlugin(QUrl::fromLocalFile(pluginPath))->uninstall();
374  }
375  catch (const ctkPluginException& exc)
376  {
377  CX_LOG_CHANNEL_WARNING("plugin") << QString("Failed to uninstall plugin: %1, %2").arg(symbolicName).arg(exc.what());
378  return;
379  }
380 
381  return;
382 }
383 
384 bool PluginFrameworkManager::start(const QString& symbolicName, ctkPlugin::StartOptions options)
385 {
386  this->startFramework();
387 
388  QString pluginPath = getPluginPath(symbolicName);
389  if (pluginPath.isEmpty())
390  return false;
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:121
ctkPlugin::State getStateFromSymbolicName(QString name)
#define CX_LOG_CHANNEL_WARNING(channel)
Definition: cxLogger.h:123
QStringList getSearchPaths() const
QVariant value(const QString &key, const QVariant &defaultValue=QVariant()) const
Definition: cxSettings.cpp:99
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:91
static ProfileManager * getInstance()
returns the only instance of this class
Definition: cxProfile.cpp:149
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:201
ctkPlugin::State getctkPluginStateForString(QString text)
#define CX_LOG_CHANNEL_ERROR(channel)
Definition: cxLogger.h:124
void setSearchPaths(const QStringList &searchPath)