CustusX  15.3.3-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  emit aboutToStop();
326 
327  // stop the framework
328  try
329  {
330  mFramework->stop(); //will start a thread that destructs plugins
331  ctkPluginFrameworkEvent fe = mFramework->waitForStop(5000);
332  if (fe.getType() == ctkPluginFrameworkEvent::FRAMEWORK_WAIT_TIMEDOUT)
333  {
334  CX_LOG_CHANNEL_WARNING("plugin") << "Stopping the plugin framework timed out";
335  return false;
336  }
337  }
338  catch (const ctkRuntimeException& e)
339  {
340  CX_LOG_CHANNEL_WARNING("plugin") << QString("Stopping the plugin framework failed: %1").arg(e.what());
341  return false;
342  }
343  return !this->frameworkStarted();
344 }
345 
346 void PluginFrameworkManager::uninstall(const QString& symbolicName)
347 {
348  QString pluginPath = getPluginPath(symbolicName);
349  if (pluginPath.isEmpty())
350  return;
351 
352  try
353  {
354  ctkPluginContext* pc = this->getPluginContext();
355  pc->installPlugin(QUrl::fromLocalFile(pluginPath))->uninstall();
356  }
357  catch (const ctkPluginException& exc)
358  {
359  CX_LOG_CHANNEL_WARNING("plugin") << QString("Failed to uninstall plugin: %1, %2").arg(symbolicName).arg(exc.what());
360  return;
361  }
362 
363  return;
364 }
365 
366 bool PluginFrameworkManager::start(const QString& symbolicName, ctkPlugin::StartOptions options)
367 {
368  this->startFramework();
369 
370  QString pluginPath = getPluginPath(symbolicName);
371  if (pluginPath.isEmpty())
372  return false;
373 
374  try
375  {
376  ctkPluginContext* pc = this->getPluginContext();
377  pc->installPlugin(QUrl::fromLocalFile(pluginPath))->start(options);
378  }
379  catch (const ctkPluginException& exc)
380  {
381  CX_LOG_CHANNEL_ERROR("plugin") << QString("Failed to start plugin (plugin error): %1, %2").arg(symbolicName).arg(exc.what());
382  return false;
383  }
384  catch (const ctkRuntimeException& exc)
385  {
386  CX_LOG_CHANNEL_ERROR("plugin") << QString("Failed to start plugin (runtime error): %1, %2").arg(symbolicName).arg(exc.what());
387  return false;
388  }
389 
390  return true;
391 }
392 
393 bool PluginFrameworkManager::stop(const QString& symbolicName, ctkPlugin::StopOptions options)
394 {
395  if (!this->frameworkStarted())
396  return false;
397  QString pluginPath = this->getPluginPath(symbolicName);
398  if (pluginPath.isEmpty())
399  return false;
400 
401  QSharedPointer<ctkPlugin> plugin = this->getInstalledPluginFromSymbolicName(symbolicName);
402 
403  if (!plugin)
404  {
405  CX_LOG_CHANNEL_WARNING("plugin") << QString("Plugin: %1 not found").arg(symbolicName);
406  return false;
407  }
408 
409  try
410  {
411  plugin->stop(options);
412  }
413  catch (const ctkPluginException& exc)
414  {
415  CX_LOG_CHANNEL_WARNING("plugin") << QString("Failed to stop plugin %1: ").arg(symbolicName).arg(exc.what());
416  return false;
417  }
418 
419  return true;
420 }
421 
422 QSharedPointer<ctkPlugin> PluginFrameworkManager::getInstalledPluginFromSymbolicName(QString symbolicName)
423 {
424  QSharedPointer<ctkPlugin> empty;
425 
426  if (!this->frameworkInitialized())
427  return empty;
428 
429  QString pluginPath = this->getPluginPath(symbolicName);
430  if (pluginPath.isEmpty())
431  return empty;
432 
433  ctkPluginContext* pc = this->getPluginContext();
434  QList < QSharedPointer<ctkPlugin> > plugins = pc->getPlugins();
435  foreach(QSharedPointer<ctkPlugin> plugin, plugins)
436  {
437  if (plugin->getSymbolicName() == symbolicName)
438  {
439  return plugin;
440  }
441  }
442 
443  return empty;
444 }
445 
446 
447 QString PluginFrameworkManager::getPluginPath(const QString& symbolicName)
448 {
449  QString pluginFileName(symbolicName);
450  pluginFileName.replace(".", "_");
451  foreach(QString searchPath, mPluginSearchPaths)
452  {
453  QDirIterator dirIter(searchPath, mPluginLibFilter, QDir::Files, QDirIterator::Subdirectories);
454  while(dirIter.hasNext())
455  {
456  dirIter.next();
457  QFileInfo fileInfo = dirIter.fileInfo();
458  QString fileBaseName = fileInfo.baseName();
459  if (fileBaseName.startsWith("lib")) fileBaseName = fileBaseName.mid(3);
460 
461  if (fileBaseName == pluginFileName)
462  {
463  return fileInfo.canonicalFilePath();
464  }
465  }
466  }
467 
468  return QString();
469 }
470 
472 {
473  QStringList result;
474  foreach(QString searchPath, mPluginSearchPaths)
475  {
476  result.append(this->getPluginSymbolicNames(searchPath));
477  }
478  result.removeDuplicates();
479  return result;
480 }
481 
482 QStringList PluginFrameworkManager::getPluginSymbolicNames(const QString& searchPath)
483 {
484  QStringList result;
485  QDirIterator dirIter(searchPath, mPluginLibFilter, QDir::Files, QDirIterator::Subdirectories);
486  while (dirIter.hasNext())
487  {
488  dirIter.next();
489  QFileInfo fileInfo = dirIter.fileInfo();
490  QString fileBaseName = fileInfo.baseName();
491  if (fileBaseName.startsWith("lib"))
492  fileBaseName = fileBaseName.mid(3);
493  QString name = fileBaseName.replace("_", ".");
494  if (this->nameIsProbablyPlugin(name))
495  result << name;
496  }
497 
498  return result;
499 }
500 
501 bool PluginFrameworkManager::nameIsProbablyPlugin(QString name) const
502 {
503  // heuristic check for plugin-ish name
504  if (name.count(".")<2) // some libs contain a _, they generate too much spam in installed version
505  return false;
506  if (name.contains("cxtest"))
507  return false;
508  return true;
509 
510 }
511 
512 } /* 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)