Browse Source

Merge branch '376-handle-timeout-retrieving-xml'

* 376-handle-timeout-retrieving-xml:
  Added unit tests for CLM timeout and caching handling.
  Added methods to retrieve registered backends from the module manager.
  Added method documentation.
  Added timeOutForXmlRetrieval to the module backend class.
  Stop reloadModules crashing
  Trap exceptions from concurrent registrations of CLI and report error messages and references
  Put messageBoxModuleRegistration back in
  Enable timeout to be specified via MainWindow
  Add ability to replace CLI backend
  Tweak error message format
  Don't cache XML if it was a timeout
  Use try/catch and debug output when extracting xml
  Throw ctkCmdLineModuleTimeoutException when local process times-out retrieving xml
  Add timeout exception type
  Add time-out in ms for XML retrieval for local process
  Added timeout handling for command line modules.
Sascha Zelzer 11 years ago
parent
commit
c15ce9dbfb
36 changed files with 851 additions and 155 deletions
  1. 1 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerConstants.cpp
  2. 1 0
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerConstants.h
  3. 8 1
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerGeneralModuleSettings.cpp
  4. 6 1
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerGeneralModuleSettings.h
  5. 30 10
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerGeneralModuleSettings.ui
  6. 38 10
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerMainWindow.cpp
  7. 9 4
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerModulesSettings.cpp
  8. 0 32
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerUtils.cpp
  9. 0 8
      Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerUtils.h
  10. 1 1
      Libs/CommandLineModules/Backend/FunctionPointer/ctkCmdLineModuleBackendFunctionPointer.cpp
  11. 1 1
      Libs/CommandLineModules/Backend/FunctionPointer/ctkCmdLineModuleBackendFunctionPointer.h
  12. 31 4
      Libs/CommandLineModules/Backend/LocalProcess/ctkCmdLineModuleBackendLocalProcess.cpp
  13. 13 1
      Libs/CommandLineModules/Backend/LocalProcess/ctkCmdLineModuleBackendLocalProcess.h
  14. 1 1
      Libs/CommandLineModules/Backend/XMLChecker/ctkCmdLineModuleBackendXMLChecker.cpp
  15. 1 1
      Libs/CommandLineModules/Backend/XMLChecker/ctkCmdLineModuleBackendXMLChecker.h
  16. 2 0
      Libs/CommandLineModules/Core/CMakeLists.txt
  17. 216 6
      Libs/CommandLineModules/Core/Testing/Cpp/ctkCmdLineModuleManagerTest.cpp
  18. 14 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleBackend.cpp
  19. 38 2
      Libs/CommandLineModules/Core/ctkCmdLineModuleBackend.h
  20. 20 8
      Libs/CommandLineModules/Core/ctkCmdLineModuleConcurrentHelpers.cpp
  21. 4 4
      Libs/CommandLineModules/Core/ctkCmdLineModuleConcurrentHelpers.h
  22. 17 17
      Libs/CommandLineModules/Core/ctkCmdLineModuleDirectoryWatcher.cpp
  23. 0 2
      Libs/CommandLineModules/Core/ctkCmdLineModuleDirectoryWatcher.h
  24. 3 3
      Libs/CommandLineModules/Core/ctkCmdLineModuleDirectoryWatcher_p.h
  25. 1 1
      Libs/CommandLineModules/Core/ctkCmdLineModuleFrontend.h
  26. 70 22
      Libs/CommandLineModules/Core/ctkCmdLineModuleManager.cpp
  27. 37 11
      Libs/CommandLineModules/Core/ctkCmdLineModuleManager.h
  28. 43 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleReferenceResult.h
  29. 11 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleRunException.cpp
  30. 3 3
      Libs/CommandLineModules/Core/ctkCmdLineModuleRunException.h
  31. 70 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleTimeoutException.cpp
  32. 53 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleTimeoutException.h
  33. 67 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleUtils.cpp
  34. 38 0
      Libs/CommandLineModules/Core/ctkCmdLineModuleUtils.h
  35. 1 1
      Libs/CommandLineModules/Frontend/QtGui/Testing/Cpp/ctkCmdLineModuleFrontendQtGuiTest.cpp
  36. 2 0
      Libs/CommandLineModules/Testing/Modules/TestBed/ctkCmdLineModuleTestBed.cpp

+ 1 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerConstants.cpp

@@ -25,3 +25,4 @@ const QString ctkCmdLineModuleExplorerConstants::KEY_SEARCH_PATHS = "ModuleSearc
 const QString ctkCmdLineModuleExplorerConstants::KEY_REGISTERED_MODULES = "RegisteredModules";
 
 const QString ctkCmdLineModuleExplorerConstants::KEY_MAX_PARALLEL_MODULES = "MaxParallelModules";
+const QString ctkCmdLineModuleExplorerConstants::KEY_XML_TIMEOUT_SECONDS = "XmlTimeoutSeconds";

+ 1 - 0
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerConstants.h

@@ -30,6 +30,7 @@ struct ctkCmdLineModuleExplorerConstants
   static const QString KEY_REGISTERED_MODULES;
 
   static const QString KEY_MAX_PARALLEL_MODULES;
+  static const QString KEY_XML_TIMEOUT_SECONDS;
 };
 
 #endif // CTKCMDLINEMODULEEXPLORERCONSTANTS_H

+ 8 - 1
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerGeneralModuleSettings.cpp

@@ -20,21 +20,28 @@
 =============================================================================*/
 
 #include "ctkCmdLineModuleExplorerGeneralModuleSettings.h"
+#include "ctkCmdLineModuleManager.h"
 #include "ctkCmdLineModuleExplorerConstants.h"
 
 #include <QThreadPool>
 #include <QSettings>
 
-ctkCmdLineModuleExplorerGeneralModuleSettings::ctkCmdLineModuleExplorerGeneralModuleSettings()
+ctkCmdLineModuleExplorerGeneralModuleSettings::ctkCmdLineModuleExplorerGeneralModuleSettings(ctkCmdLineModuleManager* cmdLineModuleManager)
+  : CmdLineModuleManager(cmdLineModuleManager)
 {
   this->setupUi(this);
 
   this->registerProperty(ctkCmdLineModuleExplorerConstants::KEY_MAX_PARALLEL_MODULES,
                          this->MaxParallelModules, "value", SIGNAL(valueChanged(int)));
+  this->registerProperty(ctkCmdLineModuleExplorerConstants::KEY_XML_TIMEOUT_SECONDS,
+                         this->XmlTimeout, "value", SIGNAL(valueChanged(int)));
 }
 
 void ctkCmdLineModuleExplorerGeneralModuleSettings::applySettings()
 {
   int maxParallelModules = this->propertyValue(ctkCmdLineModuleExplorerConstants::KEY_MAX_PARALLEL_MODULES).toInt();
   QThreadPool::globalInstance()->setMaxThreadCount(maxParallelModules);
+
+  int timeout = this->propertyValue(ctkCmdLineModuleExplorerConstants::KEY_XML_TIMEOUT_SECONDS).toInt();
+  this->CmdLineModuleManager->setTimeOutForXMLRetrieval(timeout*1000);
 }

+ 6 - 1
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerGeneralModuleSettings.h

@@ -26,6 +26,8 @@
 
 #include "ui_ctkCmdLineModuleExplorerGeneralModuleSettings.h"
 
+class ctkCmdLineModuleManager;
+
 class ctkCmdLineModuleExplorerGeneralModuleSettings : public ctkSettingsPanel,
     public Ui::ctkCmdLineModuleExplorerGeneralModuleSettings
 {
@@ -33,10 +35,13 @@ class ctkCmdLineModuleExplorerGeneralModuleSettings : public ctkSettingsPanel,
 
 public:
 
-  ctkCmdLineModuleExplorerGeneralModuleSettings();
+  ctkCmdLineModuleExplorerGeneralModuleSettings(ctkCmdLineModuleManager* cmdLineModuleManager);
 
   void applySettings();
 
+private:
+
+  ctkCmdLineModuleManager* CmdLineModuleManager;
 };
 
 #endif // CTKCMDLINEMODULEEXPLORERGENERALMODULESETTINGS_H

+ 30 - 10
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerGeneralModuleSettings.ui

@@ -32,15 +32,15 @@
       <string>Run Settings</string>
      </property>
      <layout class="QGridLayout" name="gridLayout">
-      <item row="1" column="0">
-       <spacer name="verticalSpacer">
+      <item row="0" column="2">
+       <spacer name="horizontalSpacer">
         <property name="orientation">
-         <enum>Qt::Vertical</enum>
+         <enum>Qt::Horizontal</enum>
         </property>
         <property name="sizeHint" stdset="0">
          <size>
-          <width>20</width>
-          <height>40</height>
+          <width>40</width>
+          <height>20</height>
          </size>
         </property>
        </spacer>
@@ -74,19 +74,39 @@
         </property>
        </widget>
       </item>
-      <item row="0" column="2">
-       <spacer name="horizontalSpacer">
+      <item row="2" column="0">
+       <spacer name="verticalSpacer">
         <property name="orientation">
-         <enum>Qt::Horizontal</enum>
+         <enum>Qt::Vertical</enum>
         </property>
         <property name="sizeHint" stdset="0">
          <size>
-          <width>40</width>
-          <height>20</height>
+          <width>20</width>
+          <height>40</height>
          </size>
         </property>
        </spacer>
       </item>
+      <item row="1" column="0">
+       <widget class="QLabel" name="label_2">
+        <property name="text">
+         <string>XML retrieval timeout (in seconds):</string>
+        </property>
+       </widget>
+      </item>
+      <item row="1" column="1">
+       <widget class="QSpinBox" name="XmlTimeout">
+        <property name="minimum">
+         <number>-1</number>
+        </property>
+        <property name="maximum">
+         <number>999999999</number>
+        </property>
+        <property name="value">
+         <number>30</number>
+        </property>
+       </widget>
+      </item>
      </layout>
     </widget>
    </item>

+ 38 - 10
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerMainWindow.cpp

@@ -28,7 +28,7 @@
 #include "ctkCmdLineModuleExplorerTabList.h"
 #include "ctkCmdLineModuleExplorerProgressWidget.h"
 #include "ctkCmdLineModuleExplorerConstants.h"
-#include "ctkCmdLineModuleExplorerUtils.h"
+#include "ctkCmdLineModuleUtils.h"
 
 #include <ctkCmdLineModuleManager.h>
 #include <ctkCmdLineModuleConcurrentHelpers.h>
@@ -38,6 +38,7 @@
 #include <ctkCmdLineModuleBackendLocalProcess.h>
 #include <ctkCmdLineModuleBackendFunctionPointer.h>
 #include <ctkCmdLineModuleBackendXMLChecker.h>
+#include <ctkCmdLineModuleReferenceResult.h>
 #include <ctkException.h>
 #include <ctkCmdLineModuleXmlException.h>
 
@@ -78,6 +79,13 @@ ctkCLModuleExplorerMainWindow::ctkCLModuleExplorerMainWindow(QWidget *parent) :
   }
   QThreadPool::globalInstance()->setMaxThreadCount(settings.value(ctkCmdLineModuleExplorerConstants::KEY_MAX_PARALLEL_MODULES,
                                                                   QThread::idealThreadCount()).toInt());
+  if (!settings.contains(ctkCmdLineModuleExplorerConstants::KEY_XML_TIMEOUT_SECONDS))
+  {
+    settings.setValue(ctkCmdLineModuleExplorerConstants::KEY_XML_TIMEOUT_SECONDS, QVariant(30));
+  }
+  moduleManager.setTimeOutForXMLRetrieval(
+        settings.value(ctkCmdLineModuleExplorerConstants::KEY_XML_TIMEOUT_SECONDS, QVariant(30)
+                       ).toInt() * 1000);
 
   // Frontends
   moduleFrontendFactories << new ctkCmdLineModuleFrontendFactoryQtGui;
@@ -138,8 +146,12 @@ ctkCLModuleExplorerMainWindow::ctkCLModuleExplorerMainWindow(QWidget *parent) :
   }
 
   // Register persistent modules
-  QFuture<void> future = QtConcurrent::mapped(settings.value(ctkCmdLineModuleExplorerConstants::KEY_REGISTERED_MODULES).toStringList(),
-                                              ctkCmdLineModuleConcurrentRegister(&moduleManager, true));
+  QFuture<ctkCmdLineModuleReferenceResult> future = QtConcurrent::mapped(settings.value(ctkCmdLineModuleExplorerConstants::KEY_REGISTERED_MODULES).toStringList(),
+                                                                         ctkCmdLineModuleConcurrentRegister(&moduleManager, true));
+  future.waitForFinished();
+
+  ctkCmdLineModuleUtils::messageBoxModuleRegistration(future,
+                                                      moduleManager.validationMode());
 
   // Start watching directories
   directoryWatcher.setDebug(true);
@@ -148,8 +160,6 @@ ctkCLModuleExplorerMainWindow::ctkCLModuleExplorerMainWindow(QWidget *parent) :
   moduleTabActivated(NULL);
 
   pollPauseTimer.start();
-
-  future.waitForFinished();
 }
 
 
@@ -264,7 +274,7 @@ void ctkCLModuleExplorerMainWindow::on_actionOptions_triggered()
     settingsDialog = new ctkSettingsDialog(this);
     settings.restoreState(settingsDialog->objectName(), *settingsDialog);
     settingsDialog->setSettings(&settings);
-    ctkSettingsPanel* generalModulePanel = new ctkCmdLineModuleExplorerGeneralModuleSettings();
+    ctkSettingsPanel* generalModulePanel = new ctkCmdLineModuleExplorerGeneralModuleSettings(&moduleManager);
     settingsDialog->addPanel(generalModulePanel);
     settingsDialog->addPanel(new ctkCmdLineModuleExplorerDirectorySettings(&directoryWatcher), generalModulePanel);
     settingsDialog->addPanel(new ctkCmdLineModuleExplorerModulesSettings(&moduleManager), generalModulePanel);
@@ -280,12 +290,14 @@ void ctkCLModuleExplorerMainWindow::on_actionLoad_triggered()
   QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Load modules..."));
 
   this->setCursor(Qt::BusyCursor);
-  QFuture<ctkCmdLineModuleReference> future = QtConcurrent::mapped(fileNames, ctkCmdLineModuleConcurrentRegister(&this->moduleManager));
+
+  QFuture<ctkCmdLineModuleReferenceResult> future = QtConcurrent::mapped(fileNames, ctkCmdLineModuleConcurrentRegister(&this->moduleManager));
   future.waitForFinished();
+
   this->unsetCursor();
 
-  ctkCmdLineModuleExplorerUtils::messageBoxModuleRegistration(fileNames, future.results(),
-                                                              this->moduleManager.validationMode());
+  ctkCmdLineModuleUtils::messageBoxModuleRegistration(future,
+                                                      this->moduleManager.validationMode());
 }
 
 
@@ -313,7 +325,23 @@ void ctkCLModuleExplorerMainWindow::on_actionClear_Cache_triggered()
 //-----------------------------------------------------------------------------
 void ctkCLModuleExplorerMainWindow::on_actionReload_Modules_triggered()
 {
-  moduleManager.reloadModules();
+  this->setCursor(Qt::BusyCursor);
+
+  QList<QUrl> urls;
+
+  QList<ctkCmdLineModuleReference> moduleRefs = this->moduleManager.moduleReferences();
+  foreach (ctkCmdLineModuleReference ref, moduleRefs)
+  {
+    urls.push_back(ref.location());
+  }
+
+  QFuture<ctkCmdLineModuleReferenceResult> future = QtConcurrent::mapped(urls, ctkCmdLineModuleConcurrentRegister(&this->moduleManager));
+  future.waitForFinished();
+
+  this->unsetCursor();
+
+  ctkCmdLineModuleUtils::messageBoxModuleRegistration(future,
+                                                      this->moduleManager.validationMode());
 }
 
 

+ 9 - 4
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerModulesSettings.cpp

@@ -23,7 +23,7 @@
 #include "ctkCmdLineModuleExplorerConstants.h"
 #include "ctkCmdLineModuleExplorerUtils.h"
 #include "ctkCmdLineModuleExplorerShowXmlAction.h"
-#include "ctkCmdLineModuleExplorerUtils.h"
+#include "ctkCmdLineModuleUtils.h"
 
 #include "ui_ctkCmdLineModuleExplorerModulesSettings.h"
 
@@ -83,14 +83,18 @@ void ctkCmdLineModuleExplorerModulesSettings::applySettings()
   this->setCursor(Qt::BusyCursor);
 
   QFuture<void> future1 = QtConcurrent::mapped(removedModules, ctkCmdLineModuleConcurrentUnRegister(this->ModuleManager));
-  QFuture<ctkCmdLineModuleReference> future2 = QtConcurrent::mapped(addedModules, ctkCmdLineModuleConcurrentRegister(this->ModuleManager));
+  QFuture<ctkCmdLineModuleReferenceResult> future2 = QtConcurrent::mapped(addedModules, ctkCmdLineModuleConcurrentRegister(this->ModuleManager, true));
 
   ctkSettingsPanel::applySettings();
 
+  future1.waitForFinished();
+
+  /*
   QFutureSynchronizer<void> sync;
   sync.addFuture(future1);
   sync.addFuture(future2);
   sync.waitForFinished();
+*/
 
   this->ModulesRegistered = true;
   this->pathsAdded(addedModules);
@@ -98,8 +102,9 @@ void ctkCmdLineModuleExplorerModulesSettings::applySettings()
 
   this->unsetCursor();
 
-  ctkCmdLineModuleExplorerUtils::messageBoxModuleRegistration(addedModules, future2.results(),
-                                                              this->ModuleManager->validationMode());
+  future2.waitForFinished();
+  ctkCmdLineModuleUtils::messageBoxModuleRegistration(future2,
+                                                      this->ModuleManager->validationMode());
 
 }
 

+ 0 - 32
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerUtils.cpp

@@ -22,10 +22,6 @@
 #include "ctkCmdLineModuleExplorerUtils.h"
 
 #include <QPainter>
-#include <QObject>
-#include <QWidget>
-#include <QApplication>
-#include <QMessageBox>
 
 QPixmap ctkCmdLineModuleExplorerUtils::createIconOverlay(const QPixmap &base, const QPixmap &overlay)
 {
@@ -38,31 +34,3 @@ QPixmap ctkCmdLineModuleExplorerUtils::createIconOverlay(const QPixmap &base, co
                      overlay.scaled(base.width()/2, base.height()/2, Qt::KeepAspectRatio));
   return result;
 }
-
-void ctkCmdLineModuleExplorerUtils:: messageBoxModuleRegistration(const QStringList& modulePaths,
-                                                                 const QList<ctkCmdLineModuleReference>& moduleRefs,
-                                                                 ctkCmdLineModuleManager::ValidationMode validationMode)
-{
-  Q_ASSERT(modulePaths.size() == moduleRefs.size());
-
-  QString errorMsg;
-  for(int i = 0; i < modulePaths.size(); ++i)
-  {
-    if (!moduleRefs.at(i))
-    {
-      errorMsg += QObject::tr("Failed to register ") + modulePaths.at(i) + "\n\n";
-    }
-    else if (!moduleRefs.at(i).xmlValidationErrorString().isEmpty() &&
-             validationMode == ctkCmdLineModuleManager::STRICT_VALIDATION)
-    {
-      errorMsg += QObject::tr("Failed to register ") + modulePaths.at(i) + ":\n" + moduleRefs.at(i).xmlValidationErrorString() + "\n\n";
-    }
-  }
-
-  if (!errorMsg.isEmpty())
-  {
-    QWidget* widget = QApplication::activeModalWidget();
-    if (widget == NULL) widget = QApplication::activeWindow();
-    QMessageBox::critical(widget, QObject::tr("Failed to register modules"), errorMsg);
-  }
-}

+ 0 - 8
Applications/ctkCommandLineModuleExplorer/ctkCmdLineModuleExplorerUtils.h

@@ -22,19 +22,11 @@
 #ifndef CTKCOMMANDLINEMODULEEXPLORERUTILS_H
 #define CTKCOMMANDLINEMODULEEXPLORERUTILS_H
 
-#include "ctkCmdLineModuleManager.h"
-
 #include <QPixmap>
 
 struct ctkCmdLineModuleExplorerUtils
 {
-
   static QPixmap createIconOverlay(const QPixmap& base, const QPixmap& overlay);
-
-  static void messageBoxModuleRegistration(const QStringList& modulePaths,
-                                           const QList<ctkCmdLineModuleReference>& moduleRefs,
-                                           ctkCmdLineModuleManager::ValidationMode validationMode);
-
 };
 
 #endif // CTKCOMMANDLINEMODULEEXPLORERUTILS_H

+ 1 - 1
Libs/CommandLineModules/Backend/FunctionPointer/ctkCmdLineModuleBackendFunctionPointer.cpp

@@ -230,7 +230,7 @@ qint64 ctkCmdLineModuleBackendFunctionPointer::timeStamp(const QUrl &location) c
 }
 
 //----------------------------------------------------------------------------
-QByteArray ctkCmdLineModuleBackendFunctionPointer::rawXmlDescription(const QUrl& location)
+QByteArray ctkCmdLineModuleBackendFunctionPointer::rawXmlDescription(const QUrl& location, int /*timeout*/)
 {
   if (!d->UrlToFpDescription.contains(location)) return QByteArray();
   //qDebug() << d->UrlToFpDescription[location].d->xmlDescription();

+ 1 - 1
Libs/CommandLineModules/Backend/FunctionPointer/ctkCmdLineModuleBackendFunctionPointer.h

@@ -150,7 +150,7 @@ public:
 
   virtual qint64 timeStamp(const QUrl &location) const;
 
-  virtual QByteArray rawXmlDescription(const QUrl& location);
+  virtual QByteArray rawXmlDescription(const QUrl& location, int timeout);
 
   QList<QUrl> registeredFunctionPointers() const;
 

+ 31 - 4
Libs/CommandLineModules/Backend/LocalProcess/ctkCmdLineModuleBackendLocalProcess.cpp

@@ -29,6 +29,7 @@
 #include "ctkCmdLineModuleProcessTask.h"
 #include "ctkCmdLineModuleReference.h"
 #include "ctkCmdLineModuleRunException.h"
+#include "ctkCmdLineModuleTimeoutException.h"
 
 #include "ctkUtils.h"
 #include <iostream>
@@ -39,6 +40,13 @@
 struct ctkCmdLineModuleBackendLocalProcessPrivate
 {
 
+  int m_TimeoutForXMLRetrieval;
+
+  ctkCmdLineModuleBackendLocalProcessPrivate()
+    : m_TimeoutForXMLRetrieval(0) // use the value from the module manager
+  {
+  }
+
   QString normalizeFlag(const QString& flag) const
   {
     return flag.trimmed().remove(QRegExp("^-*"));
@@ -165,16 +173,23 @@ qint64 ctkCmdLineModuleBackendLocalProcess::timeStamp(const QUrl &location) cons
 }
 
 //----------------------------------------------------------------------------
-QByteArray ctkCmdLineModuleBackendLocalProcess::rawXmlDescription(const QUrl &location)
+QByteArray ctkCmdLineModuleBackendLocalProcess::rawXmlDescription(const QUrl &location, int timeout)
 {
   QProcess process;
   process.setReadChannel(QProcess::StandardOutput);
   process.start(location.toLocalFile(), QStringList("--xml"));
 
-  if (!process.waitForFinished() || process.exitStatus() == QProcess::CrashExit ||
-      process.error() != QProcess::UnknownError)
+  if (!process.waitForFinished(timeout))
   {
-    throw ctkCmdLineModuleRunException(location, process.exitCode(), process.errorString());
+    if (process.error() == QProcess::Timedout)
+    {
+      throw ctkCmdLineModuleTimeoutException(location, process.errorString());
+    }
+    else if (process.exitStatus() == QProcess::CrashExit ||
+             process.error() != QProcess::UnknownError)
+    {
+      throw ctkCmdLineModuleRunException(location, process.exitCode(), process.errorString());
+    }
   }
 
   process.waitForReadyRead();
@@ -192,3 +207,15 @@ ctkCmdLineModuleFuture ctkCmdLineModuleBackendLocalProcess::run(ctkCmdLineModule
       new ctkCmdLineModuleProcessTask(frontend->location().toLocalFile(), args);
   return moduleProcess->start();
 }
+
+//----------------------------------------------------------------------------
+void ctkCmdLineModuleBackendLocalProcess::setTimeOutForXMLRetrieval(int timeOut)
+{
+  d->m_TimeoutForXMLRetrieval = timeOut;
+}
+
+//----------------------------------------------------------------------------
+int ctkCmdLineModuleBackendLocalProcess::timeOutForXMLRetrieval() const
+{
+  return d->m_TimeoutForXMLRetrieval;
+}

+ 13 - 1
Libs/CommandLineModules/Backend/LocalProcess/ctkCmdLineModuleBackendLocalProcess.h

@@ -81,7 +81,7 @@ public:
    * This method always calls the executable with a \c &ndash;&ndash;xml argument and returns
    * the complete data emitted on the standard output channel.
    */
-  virtual QByteArray rawXmlDescription(const QUrl& location);
+  virtual QByteArray rawXmlDescription(const QUrl& location, int timeout);
 
   /**
    * @brief Run a front-end for this module in a local process.
@@ -90,6 +90,18 @@ public:
    */
   virtual ctkCmdLineModuleFuture run(ctkCmdLineModuleFrontend *frontend);
 
+  /**
+   * @brief Setter for the number of milliseconds to wait when retrieving xml.
+   * @param timeOut in milliseconds.
+   */
+  void setTimeOutForXMLRetrieval(int timeOut);
+
+  /**
+   * @brief Returns the number of milliseconds to wait when retrieving xml.
+   * @return Time-out in milliseconds.
+   */
+  virtual int timeOutForXMLRetrieval() const;
+
 private:
 
   QScopedPointer<ctkCmdLineModuleBackendLocalProcessPrivate> d;

+ 1 - 1
Libs/CommandLineModules/Backend/XMLChecker/ctkCmdLineModuleBackendXMLChecker.cpp

@@ -106,7 +106,7 @@ qint64 ctkCmdLineModuleBackendXMLChecker::timeStamp(const QUrl & /*location*/) c
 
 
 //----------------------------------------------------------------------------
-QByteArray ctkCmdLineModuleBackendXMLChecker::rawXmlDescription(const QUrl& location)
+QByteArray ctkCmdLineModuleBackendXMLChecker::rawXmlDescription(const QUrl& location, int /*timeout*/)
 {
   if (location.isValid())
   {

+ 1 - 1
Libs/CommandLineModules/Backend/XMLChecker/ctkCmdLineModuleBackendXMLChecker.h

@@ -70,7 +70,7 @@ public:
    * This method always calls the executable with a \c &ndash;&ndash;xml argument and returns
    * the complete data emitted on the standard output channel.
    */
-  virtual QByteArray rawXmlDescription(const QUrl& location);
+  virtual QByteArray rawXmlDescription(const QUrl& location, int timeout);
 
   /**
    * @brief Run a front-end for this module in a local process.

+ 2 - 0
Libs/CommandLineModules/Core/CMakeLists.txt

@@ -42,6 +42,8 @@ set(KIT_SRCS
   ctkCmdLineModuleXmlProgressWatcher.cpp
   ctkCmdLineModuleReference.cpp
   ctkCmdLineModuleRunException.cpp
+  ctkCmdLineModuleTimeoutException.cpp
+  ctkCmdLineModuleUtils.cpp
   ctkCmdLineModuleXmlException.cpp
   ctkCmdLineModuleXmlMsgHandler_p.h
   ctkCmdLineModuleXmlMsgHandler.cpp

+ 216 - 6
Libs/CommandLineModules/Core/Testing/Cpp/ctkCmdLineModuleManagerTest.cpp

@@ -24,7 +24,11 @@
 #include "ctkCmdLineModuleBackend.h"
 #include "ctkException.h"
 #include "ctkCmdLineModuleFuture.h"
+#include "ctkCmdLineModuleReferenceResult.h"
+#include <ctkCmdLineModuleConcurrentHelpers.h>
+#include <ctkCmdLineModuleTimeoutException.h>
 
+#include "ctkUtils.h"
 #include "ctkTest.h"
 
 #include <QCoreApplication>
@@ -43,18 +47,47 @@ class BackendMockUp : public ctkCmdLineModuleBackend
 
 public:
 
-  void addModule(const QUrl& location, const QByteArray& xml)
+  void addModule(const QUrl& location, const QByteArray& xml, int msDelay = 0)
   {
-    this->UrlToXml[location] = xml;
+    this->m_UrlToXmlRetrievalCount[location] = 0;
+    this->m_UrlToXml[location] = xml;
+    this->m_UrlToXmlOutputDelay[location] = msDelay;
+  }
+
+  void setTimestamp(const QUrl& location, qint64 timestamp)
+  {
+    this->m_UrlToTimestamp[location] = timestamp;
+  }
+
+  QList<QUrl> moduleLocations() const
+  {
+    return this->m_UrlToXml.keys();
+  }
+
+  int xmlRetrievalCount(const QUrl& location) const
+  {
+    QHash<QUrl,int>::ConstIterator iter = this->m_UrlToXmlRetrievalCount.find(location);
+    return iter == this->m_UrlToXmlRetrievalCount.end() ? 0 : iter.value();
   }
 
   virtual QString name() const { return "Mockup"; }
   virtual QString description() const { return "Test Mock-up"; }
   virtual QList<QString> schemes() const { return QList<QString>() << "test"; }
-  virtual qint64 timeStamp(const QUrl& /*location*/) const { return 0; }
-  virtual QByteArray rawXmlDescription(const QUrl& location)
+
+  virtual qint64 timeStamp(const QUrl& location) const
+  {
+    QHash<QUrl,qint64>::ConstIterator iter = this->m_UrlToTimestamp.find(location);
+    return iter == this->m_UrlToTimestamp.end() ? 0 : iter.value();
+  }
+
+  virtual QByteArray rawXmlDescription(const QUrl& location, int timeout)
   {
-    return UrlToXml[location];
+    ++m_UrlToXmlRetrievalCount[location];
+    if (timeout < m_UrlToXmlOutputDelay[location])
+    {
+      throw ctkCmdLineModuleTimeoutException(location, "Timeout in BackendMockUp occurred");
+    }
+    return m_UrlToXml[location];
   }
 
 protected:
@@ -66,7 +99,10 @@ protected:
 
 private:
 
-  QHash<QUrl, QByteArray> UrlToXml;
+  QHash<QUrl, qint64> m_UrlToTimestamp;
+  QHash<QUrl, int> m_UrlToXmlRetrievalCount;
+  QHash<QUrl, int> m_UrlToXmlOutputDelay;
+  QHash<QUrl, QByteArray> m_UrlToXml;
 };
 
 }
@@ -79,15 +115,19 @@ class ctkCmdLineModuleManagerTester : public QObject
 private Q_SLOTS:
 
   void initTestCase();
+  void cleanup();
 
   void testStrictValidation();
   void testWeakValidation();
   void testSkipValidation();
+  void testTimeoutHandling();
+  void testCaching();
 
 private:
 
   QByteArray validXml;
   QByteArray invalidXml;
+  QString cachePath;
 };
 
 //-----------------------------------------------------------------------------
@@ -122,6 +162,14 @@ void ctkCmdLineModuleManagerTester::initTestCase()
                    "    </integer>\n"
                    "  </parameters>\n"
                    "</executable>\n";
+
+  cachePath = QDir::tempPath() + QDir::separator() + "ctkCmdLineModuleManagerTester_cache";
+}
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleManagerTester::cleanup()
+{
+  ctk::removeDirRecursively(cachePath);
 }
 
 //-----------------------------------------------------------------------------
@@ -186,6 +234,168 @@ void ctkCmdLineModuleManagerTester::testSkipValidation()
   QVERIFY(moduleRef2.xmlValidationErrorString().isEmpty());
 }
 
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleManagerTester::testTimeoutHandling()
+{
+  BackendMockUp backend;
+  backend.addModule(QUrl("test://validXml"), validXml, 1000);
+  backend.addModule(QUrl("test://validXml2"), validXml);
+
+  ctkCmdLineModuleManager manager(ctkCmdLineModuleManager::STRICT_VALIDATION, cachePath);
+  manager.setTimeOutForXMLRetrieval(2000);
+
+  manager.registerBackend(&backend);
+
+  // register modules with a sufficient large timeout value
+  QList<ctkCmdLineModuleReferenceResult> results =
+      QtConcurrent::blockingMapped(backend.moduleLocations(),
+                                   ctkCmdLineModuleConcurrentRegister(&manager, true));
+
+  QVERIFY(results.size() == 2);
+  QVERIFY(results[0].m_Reference && results[0].m_RuntimeError.isEmpty());
+  QVERIFY(results[1].m_Reference && results[1].m_RuntimeError.isEmpty());
+
+  // unregister the modules
+  QList<bool> unregisterResults =
+      QtConcurrent::blockingMapped(backend.moduleLocations(),
+                                   ctkCmdLineModuleConcurrentUnRegister(&manager));
+  QVERIFY(unregisterResults.size() == 2);
+  QVERIFY(unregisterResults[0] && unregisterResults[1]);
+
+  // register modules where one runs into a timeout
+  manager.setTimeOutForXMLRetrieval(500);
+  results = QtConcurrent::blockingMapped(backend.moduleLocations(),
+                                         ctkCmdLineModuleConcurrentRegister(&manager, true));
+
+  QVERIFY(results.size() == 2);
+  QVERIFY(!results[0].m_Reference && !results[0].m_RuntimeError.isEmpty());
+  QVERIFY(results[1].m_Reference && results[1].m_RuntimeError.isEmpty());
+
+}
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleManagerTester::testCaching()
+{
+  QUrl location("test://validXml");
+  QUrl location2("test://validXml2");
+
+  // The code below is inside a local scopes so the manager is destroyed
+  // but the cache is still available (unregistering the module would also
+  // remove the cache entry, but we need to test the entry)
+
+  {
+    BackendMockUp backend;
+    backend.addModule(location, validXml, 1000);
+
+    ctkCmdLineModuleManager manager(ctkCmdLineModuleManager::STRICT_VALIDATION, cachePath);
+    manager.setTimeOutForXMLRetrieval(500);
+
+    manager.registerBackend(&backend);
+
+    QVERIFY(backend.xmlRetrievalCount(location) == 0);
+
+    // module runs into a timeout
+    try
+    {
+      manager.registerModule(location);
+      QFAIL("ctkCmdLineModuleTimeoutException expected");
+    }
+    catch (const ctkCmdLineModuleTimeoutException&)
+    {}
+    QVERIFY(backend.xmlRetrievalCount(location) == 1);
+
+    // Increase the timeout and register the module again. It should
+    // not have been cached and the manager is supposed to try to
+    // retrieve the XML description again
+    manager.setTimeOutForXMLRetrieval(2000);
+    QVERIFY(manager.registerModule(location));
+    QVERIFY(backend.xmlRetrievalCount(location) == 2);
+
+    // Registering the same module again should just return the already
+    // created module reference
+    QVERIFY(manager.registerModule(location));
+    QVERIFY(backend.xmlRetrievalCount(location) == 2);
+  }
+
+  {
+    BackendMockUp backend;
+    backend.addModule(location, validXml);
+    backend.setTimestamp(location, 1);
+    backend.addModule(location2, invalidXml);
+    backend.setTimestamp(location2, 1);
+
+    ctkCmdLineModuleManager manager(ctkCmdLineModuleManager::STRICT_VALIDATION, cachePath);
+    manager.registerBackend(&backend);
+
+    QVERIFY(manager.registerModule(location));
+    QVERIFY(backend.xmlRetrievalCount(location) == 1);
+
+    try
+    {
+      manager.registerModule(location2);
+      QFAIL("ctkInvalidArgumentException (invalid XML) expected");
+    }
+    catch (const ctkInvalidArgumentException&)
+    {}
+    QVERIFY(backend.xmlRetrievalCount(location2) == 1);
+  }
+
+  // Do the same again but now the cache entries should be returned
+  {
+    BackendMockUp backend;
+    backend.addModule(location, validXml);
+    backend.setTimestamp(location, 1);
+    backend.addModule(location2, invalidXml);
+    backend.setTimestamp(location2, 1);
+
+    ctkCmdLineModuleManager manager(ctkCmdLineModuleManager::STRICT_VALIDATION, cachePath);
+    manager.registerBackend(&backend);
+
+    QVERIFY(manager.registerModule(location));
+    QVERIFY(backend.xmlRetrievalCount(location) == 0);
+
+    try
+    {
+      manager.registerModule(location2);
+      QFAIL("ctkInvalidArgumentException (invalid XML) expected");
+    }
+    catch (const ctkInvalidArgumentException&)
+    {}
+    QVERIFY(backend.xmlRetrievalCount(location2) == 0);
+  }
+
+  // Now test updated timestamps
+  {
+    BackendMockUp backend;
+    backend.addModule(location, validXml);
+    backend.setTimestamp(location, 2);
+    // use valid XML now but keep the previous timestamp
+    backend.addModule(location2, validXml);
+    backend.setTimestamp(location2, 1);
+
+    ctkCmdLineModuleManager manager(ctkCmdLineModuleManager::STRICT_VALIDATION, cachePath);
+    manager.registerBackend(&backend);
+
+    QVERIFY(manager.registerModule(location));
+    QVERIFY(backend.xmlRetrievalCount(location) == 1);
+
+    // should still throw an exception due to the cache entry
+    try
+    {
+      manager.registerModule(location2);
+      QFAIL("ctkInvalidArgumentException (invalid XML) expected");
+    }
+    catch (const ctkInvalidArgumentException&)
+    {}
+    QVERIFY(backend.xmlRetrievalCount(location2) == 0);
+
+    // now update the timestamp and check that the valid XML is retrieved
+    backend.setTimestamp(location, 2);
+    QVERIFY(manager.registerModule(location));
+    QVERIFY(backend.xmlRetrievalCount(location) == 1);
+  }
+}
+
 // ----------------------------------------------------------------------------
 CTK_TEST_MAIN(ctkCmdLineModuleManagerTest)
 #include "moc_ctkCmdLineModuleManagerTest.cpp"

+ 14 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleBackend.cpp

@@ -21,7 +21,21 @@
 
 #include "ctkCmdLineModuleBackend.h"
 
+#include <qbytearray.h>
+
 //----------------------------------------------------------------------------
 ctkCmdLineModuleBackend::~ctkCmdLineModuleBackend()
 {
 }
+
+//----------------------------------------------------------------------------
+QByteArray ctkCmdLineModuleBackend::rawXmlDescription(const QUrl& location)
+{
+  return this->rawXmlDescription(location, 30000);
+}
+
+//----------------------------------------------------------------------------
+int ctkCmdLineModuleBackend::timeOutForXmlRetrieval() const
+{
+  return 0;
+}

+ 38 - 2
Libs/CommandLineModules/Core/ctkCmdLineModuleBackend.h

@@ -46,7 +46,7 @@ class QUrl;
 struct CTK_CMDLINEMODULECORE_EXPORT ctkCmdLineModuleBackend
 {
 
-  ~ctkCmdLineModuleBackend();
+  virtual ~ctkCmdLineModuleBackend();
 
   /**
    * @brief Returns the name of the type of the backend, not the name
@@ -77,6 +77,7 @@ struct CTK_CMDLINEMODULECORE_EXPORT ctkCmdLineModuleBackend
   /**
    * @brief Get the XML parameter description from the given location.
    * @param location The location URL specifying the module.
+   * @param timeout The time-out for retrieving the XML parameter description
    * @return The raw XML parameter description.
    *
    * This method may be concurrently called by the ctkCmdLineModuleManager and
@@ -84,8 +85,43 @@ struct CTK_CMDLINEMODULECORE_EXPORT ctkCmdLineModuleBackend
    * as caching is done by the ctkCmdLineModuleManager itself, checking the
    * return value of timeStamp().
    *
+   * Implementations should also throw either a ctkCmdLineModuleTimeoutException
+   * object if a time-out occured when retrieving the XML parameter description
+   * or a ctkCmdLineModuleRunException for any other error during invocation
+   * of the module.
+   *
+   * @throws ctkCmdLineModuleTimeoutException if a time-out occurred when
+   *         retrieving the XML parameter description.
+   * @throws ctkCmdLineModuleRunException if a runtime error occurred when
+   *         invoking the module to retrieve the XML parameter description.
+   */
+  virtual QByteArray rawXmlDescription(const QUrl& location, int timeout) = 0;
+
+  /**
+   * @brief Get the XML parameter description from the given location.
+   * @param location The location URL specifying the module.
+   * @return The raw XML parameter description.
+   *
+   * This method calls rawXmlDescription(const QUrl&, int) with a timeout
+   * of 30 seconds.
+   *
+   * @throws ctkCmdLineModuleTimeoutException if a time-out occurred when
+   *         retrieving the XML parameter description.
+   * @throws ctkCmdLineModuleRunException if a runtime error occurred when
+   *         invoking the module to retrieve the XML parameter description.
+   */
+  QByteArray rawXmlDescription(const QUrl& location);
+
+  /**
+   * @brief returns the number of milliseconds to wait when retrieving xml.
+   *
+   * The default implementation returns 0, which signals that the global
+   * timeout value from the ctkCmdLineModuleManager object with which this
+   * backend was registered should be used.
+   *
+   * @return int Time-out in milliseconds.
    */
-  virtual QByteArray rawXmlDescription(const QUrl& location) = 0;
+  virtual int timeOutForXmlRetrieval() const;
 
 protected:
 

+ 20 - 8
Libs/CommandLineModules/Core/ctkCmdLineModuleConcurrentHelpers.cpp

@@ -22,8 +22,9 @@
 #include "ctkCmdLineModuleConcurrentHelpers.h"
 
 #include "ctkCmdLineModuleManager.h"
-#include "ctkException.h"
+#include "ctkCmdLineModuleRunException.h"
 
+#include <qtconcurrentexception.h>
 #include <QUrl>
 #include <QDebug>
 
@@ -34,34 +35,45 @@ ctkCmdLineModuleConcurrentRegister::ctkCmdLineModuleConcurrentRegister(ctkCmdLin
 {}
 
 //----------------------------------------------------------------------------
-ctkCmdLineModuleReference ctkCmdLineModuleConcurrentRegister::operator()(const QString& moduleLocation)
+ctkCmdLineModuleReferenceResult ctkCmdLineModuleConcurrentRegister::operator()(const QString& moduleLocation)
 {
   return this->operator ()(QUrl::fromLocalFile(moduleLocation));
 }
 
 //----------------------------------------------------------------------------
-ctkCmdLineModuleReference ctkCmdLineModuleConcurrentRegister::operator()(const QUrl& moduleUrl)
+ctkCmdLineModuleReferenceResult ctkCmdLineModuleConcurrentRegister::operator()(const QUrl& moduleUrl)
 {
   try
   {
-    return this->ModuleManager->registerModule(moduleUrl);
+    ctkCmdLineModuleReference reference = this->ModuleManager->registerModule(moduleUrl);
+    return ctkCmdLineModuleReferenceResult(reference);
   }
   catch (const ctkException& e)
   {
     if (this->Debug)
     {
-      qDebug() << e;
+      qDebug() << e.message();
     }
-    return ctkCmdLineModuleReference();
+    return ctkCmdLineModuleReferenceResult(moduleUrl, e.message());
+  }
+  catch (const QtConcurrent::Exception& e)
+  {
+    if (this->Debug)
+    {
+      qDebug() << e.what();
+    }
+    return ctkCmdLineModuleReferenceResult(moduleUrl, e.what());
   }
   catch (...)
   {
+    QString errorMessage = QObject::tr("Module %1 failed with an unknown exception.").arg(moduleUrl.toString());
     if (this->Debug)
     {
-      qDebug() << "Registering module" << moduleUrl << "failed with an unknown exception.";
+      qDebug() << errorMessage;
     }
-    return ctkCmdLineModuleReference();
+    return ctkCmdLineModuleReferenceResult(moduleUrl, errorMessage);
   }
+  return ctkCmdLineModuleReferenceResult(moduleUrl);
 }
 
 //----------------------------------------------------------------------------

+ 4 - 4
Libs/CommandLineModules/Core/ctkCmdLineModuleConcurrentHelpers.h

@@ -24,7 +24,7 @@
 
 #include "ctkCommandLineModulesCoreExport.h"
 
-#include "ctkCmdLineModuleReference.h"
+#include "ctkCmdLineModuleReferenceResult.h"
 
 class ctkCmdLineModuleManager;
 
@@ -38,11 +38,11 @@ class CTK_CMDLINEMODULECORE_EXPORT ctkCmdLineModuleConcurrentRegister
 
 public:
 
-  typedef ctkCmdLineModuleReference result_type;
+  typedef ctkCmdLineModuleReferenceResult result_type;
 
   ctkCmdLineModuleConcurrentRegister(ctkCmdLineModuleManager* manager, bool debug = false);
-  ctkCmdLineModuleReference operator()(const QString& moduleLocation);
-  ctkCmdLineModuleReference operator()(const QUrl& moduleUrl);
+  result_type operator()(const QString& moduleLocation);
+  result_type operator()(const QUrl& moduleUrl);
 
 private:
 

+ 17 - 17
Libs/CommandLineModules/Core/ctkCmdLineModuleDirectoryWatcher.cpp

@@ -132,7 +132,7 @@ void ctkCmdLineModuleDirectoryWatcherPrivate::setDirectories(const QStringList&
 {
   QStringList validDirectories = this->filterInvalidDirectories(directories);
   this->setModules(validDirectories);
-  this->updateWatchedPaths(validDirectories, this->MapFileNameToReference.keys());
+  this->updateWatchedPaths(validDirectories, this->MapFileNameToReferenceResult.keys());
 }
 
 
@@ -169,20 +169,20 @@ void ctkCmdLineModuleDirectoryWatcherPrivate::setAdditionalModules(const QString
   QStringList filteredAdditionalModules = this->filterFilesNotInCurrentDirectories(this->AdditionalModules);
 
   this->unloadModules(filteredAdditionalModules);
-  QList<ctkCmdLineModuleReference> refs = this->loadModules(filteredFileNames);
+  QList<ctkCmdLineModuleReferenceResult> refs = this->loadModules(filteredFileNames);
 
   QStringList validFileNames;
 
   for (int i = 0; i < refs.size(); ++i)
   {
-    if (refs[i])
+    if (refs[i].m_Reference)
     {
-      validFileNames << refs[i].location().toLocalFile();
+      validFileNames << refs[i].m_Reference.location().toLocalFile();
     }
   }
 
   this->AdditionalModules = validFileNames;
-  this->updateWatchedPaths(this->directories(), this->MapFileNameToReference.keys());
+  this->updateWatchedPaths(this->directories(), this->MapFileNameToReferenceResult.keys());
 
   if (this->Debug) qDebug() << "ctkCmdLineModuleDirectoryWatcherPrivate::setAdditionalModules watching:" << this->AdditionalModules;
 }
@@ -296,7 +296,7 @@ QStringList ctkCmdLineModuleDirectoryWatcherPrivate::extractCurrentlyWatchedFile
   QDir dir(path);
   if (dir.exists())
   {
-    QList<QString> keys = this->MapFileNameToReference.keys();
+    QList<QString> keys = this->MapFileNameToReferenceResult.keys();
 
     QString fileName;
     foreach(fileName, keys)
@@ -397,24 +397,24 @@ void ctkCmdLineModuleDirectoryWatcherPrivate::updateModules(const QString &direc
     currentlyWatchedDirectories << directory;
   }
   this->setModules(currentlyWatchedDirectories);
-  this->updateWatchedPaths(currentlyWatchedDirectories, this->MapFileNameToReference.keys());
+  this->updateWatchedPaths(currentlyWatchedDirectories, this->MapFileNameToReferenceResult.keys());
 }
 
 
 //-----------------------------------------------------------------------------
-QList<ctkCmdLineModuleReference> ctkCmdLineModuleDirectoryWatcherPrivate::loadModules(const QStringList& executables)
+QList<ctkCmdLineModuleReferenceResult> ctkCmdLineModuleDirectoryWatcherPrivate::loadModules(const QStringList& executables)
 {
-  QList<ctkCmdLineModuleReference> refs = QtConcurrent::blockingMapped(executables,
-                                                                       ctkCmdLineModuleConcurrentRegister(this->ModuleManager, this->Debug));
+  QList<ctkCmdLineModuleReferenceResult> refResults = QtConcurrent::blockingMapped(executables,
+                                                                                   ctkCmdLineModuleConcurrentRegister(this->ModuleManager, this->Debug));
 
   for (int i = 0; i < executables.size(); ++i)
   {
-    if (refs[i])
+    if (refResults[i].m_Reference)
     {
-      this->MapFileNameToReference[executables[i]] = refs[i];
+      this->MapFileNameToReferenceResult[executables[i]] = refResults[i];
     }
   }
-  return refs;
+  return refResults;
 }
 
 
@@ -424,7 +424,7 @@ void ctkCmdLineModuleDirectoryWatcherPrivate::unloadModules(const QStringList& e
   QtConcurrent::blockingMapped(executables, ctkCmdLineModuleConcurrentUnRegister(this->ModuleManager));
   foreach(QString executable, executables)
   {
-    this->MapFileNameToReference.remove(executable);
+    this->MapFileNameToReferenceResult.remove(executable);
   }
 }
 
@@ -432,14 +432,14 @@ void ctkCmdLineModuleDirectoryWatcherPrivate::unloadModules(const QStringList& e
 //-----------------------------------------------------------------------------
 void ctkCmdLineModuleDirectoryWatcherPrivate::onFileChanged(const QString& path)
 {
-  ctkCmdLineModuleReference ref = this->loadModules(QStringList() << path).front();
-  if (ref)
+  ctkCmdLineModuleReferenceResult refResult = this->loadModules(QStringList() << path).front();
+  if (refResult.m_Reference)
   {
     if (this->Debug) qDebug() << "Reloaded " << path;
   }
   else
   {
-    if (this->Debug) qDebug() << "ctkCmdLineModuleDirectoryWatcherPrivate::onFileChanged(" << path << "): failed to load module";
+    if (this->Debug) qDebug() << "ctkCmdLineModuleDirectoryWatcherPrivate::onFileChanged(" << path << "): failed to load module due to " << refResult.m_RuntimeError;
   }
 }
 

+ 0 - 2
Libs/CommandLineModules/Core/ctkCmdLineModuleDirectoryWatcher.h

@@ -23,8 +23,6 @@
 
 #include <ctkCommandLineModulesCoreExport.h>
 
-#include "ctkCmdLineModuleReference.h"
-
 #include <QObject>
 #include <QScopedPointer>
 

+ 3 - 3
Libs/CommandLineModules/Core/ctkCmdLineModuleDirectoryWatcher_p.h

@@ -26,7 +26,7 @@
 #include <QStringList>
 #include <QFileInfoList>
 
-#include "ctkCmdLineModuleReference.h"
+#include "ctkCmdLineModuleReferenceResult.h"
 #include "ctkCmdLineModuleDirectoryWatcher.h"
 
 class QFileSystemWatcher;
@@ -156,7 +156,7 @@ private:
    *
    * \param executables A list of paths to executable files, denoted by an absolute path.
    */
-  QList<ctkCmdLineModuleReference> loadModules(const QStringList& executables);
+  QList<ctkCmdLineModuleReferenceResult> loadModules(const QStringList& executables);
 
   /**
    * \brief Removes the executables from both the ctkCmdLineModuleManager and this->MapFileNameToReference.
@@ -165,7 +165,7 @@ private:
    */
   void unloadModules(const QStringList& executables);
 
-  QHash<QString, ctkCmdLineModuleReference> MapFileNameToReference;
+  QHash<QString, ctkCmdLineModuleReferenceResult> MapFileNameToReferenceResult;
   ctkCmdLineModuleManager* ModuleManager;
   QFileSystemWatcher* FileSystemWatcher;
   QStringList AdditionalModules;

+ 1 - 1
Libs/CommandLineModules/Core/ctkCmdLineModuleFrontend.h

@@ -100,7 +100,7 @@ public:
   };
   Q_DECLARE_FLAGS(ParameterFilters, ParameterFilter)
 
-  ~ctkCmdLineModuleFrontend();
+  virtual ~ctkCmdLineModuleFrontend();
 
   /**
    * @brief Returns the GUI representation.

+ 70 - 22
Libs/CommandLineModules/Core/ctkCmdLineModuleManager.cpp

@@ -1,33 +1,37 @@
 /*=============================================================================
-  
+
   Library: CTK
-  
+
   Copyright (c) German Cancer Research Center,
     Division of Medical and Biological Informatics
-    
+
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
-  
+
     http://www.apache.org/licenses/LICENSE-2.0
-    
+
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
-  
+
 =============================================================================*/
 
 #include "ctkCmdLineModuleManager.h"
 
 #include "ctkCmdLineModuleBackend.h"
 #include "ctkCmdLineModuleFrontend.h"
+#include "ctkCmdLineModuleTimeoutException.h"
 #include "ctkCmdLineModuleCache_p.h"
 #include "ctkCmdLineModuleFuture.h"
 #include "ctkCmdLineModuleXmlValidator.h"
 #include "ctkCmdLineModuleReference.h"
 #include "ctkCmdLineModuleReference_p.h"
+#include "ctkCmdLineModuleRunException.h"
+#include "ctkCmdLineModuleXmlException.h"
+#include "ctkCmdLineModuleTimeoutException.h"
 
 #include <ctkException.h>
 
@@ -39,7 +43,7 @@
 #include <QHash>
 #include <QList>
 #include <QMutex>
-
+#include <QDebug>
 #include <QFuture>
 
 #if (QT_VERSION < QT_VERSION_CHECK(4,7,0))
@@ -50,7 +54,8 @@ extern int qHash(const QUrl& url);
 struct ctkCmdLineModuleManagerPrivate
 {
   ctkCmdLineModuleManagerPrivate(ctkCmdLineModuleManager::ValidationMode mode, const QString& cacheDir)
-    : ValidationMode(mode)
+    : XmlTimeOut(30000)
+    , ValidationMode(mode)
   {
     QFileInfo fileInfo(cacheDir);
     if (!fileInfo.exists())
@@ -84,6 +89,7 @@ struct ctkCmdLineModuleManagerPrivate
   QHash<QString, ctkCmdLineModuleBackend*> SchemeToBackend;
   QHash<QUrl, ctkCmdLineModuleReference> LocationToRef;
   QScopedPointer<ctkCmdLineModuleCache> ModuleCache;
+  int XmlTimeOut;
 
   ctkCmdLineModuleManager::ValidationMode ValidationMode;
 };
@@ -114,6 +120,18 @@ void ctkCmdLineModuleManager::setValidationMode(const ValidationMode& mode)
 
 
 //----------------------------------------------------------------------------
+void ctkCmdLineModuleManager::setTimeOutForXMLRetrieval(int xmlTimeout)
+{
+  d->XmlTimeOut = xmlTimeout;
+}
+
+//----------------------------------------------------------------------------
+int ctkCmdLineModuleManager::timeOutForXMLRetrieval() const
+{
+  return d->XmlTimeOut;
+}
+
+//----------------------------------------------------------------------------
 void ctkCmdLineModuleManager::registerBackend(ctkCmdLineModuleBackend *backend)
 {
   QMutexLocker lock(&d->Mutex);
@@ -138,6 +156,23 @@ void ctkCmdLineModuleManager::registerBackend(ctkCmdLineModuleBackend *backend)
 }
 
 //----------------------------------------------------------------------------
+ctkCmdLineModuleBackend*ctkCmdLineModuleManager::backend(const QString& scheme) const
+{
+  QHash<QString, ctkCmdLineModuleBackend*>::ConstIterator iter =
+      d->SchemeToBackend.find(scheme);
+  return iter == d->SchemeToBackend.end() ? NULL : iter.value();
+}
+
+//----------------------------------------------------------------------------
+QList<ctkCmdLineModuleBackend*> ctkCmdLineModuleManager::backends() const
+{
+  QList<ctkCmdLineModuleBackend*> result = d->SchemeToBackend.values();
+  qSort(result);
+  result.erase(std::unique(result.begin(), result.end()), result.end());
+  return result;
+}
+
+//----------------------------------------------------------------------------
 ctkCmdLineModuleReference
 ctkCmdLineModuleManager::registerModule(const QUrl &location)
 {
@@ -160,6 +195,12 @@ ctkCmdLineModuleManager::registerModule(const QUrl &location)
   bool fromCache = false;
   qint64 newTimeStamp = 0;
   qint64 cacheTimeStamp = 0;
+  int timeout = backend->timeOutForXmlRetrieval();
+  if (timeout == 0)
+  {
+    timeout = d->XmlTimeOut;
+  }
+
   if (d->ModuleCache)
   {
     newTimeStamp = backend->timeStamp(location);
@@ -170,7 +211,13 @@ ctkCmdLineModuleManager::registerModule(const QUrl &location)
       // newly fetch the XML description
       try
       {
-        xml = backend->rawXmlDescription(location);
+        xml = backend->rawXmlDescription(location, timeout);
+      }
+      catch (const ctkCmdLineModuleTimeoutException&)
+      {
+        // in case of a time-out, do not cache it as a failed attempt
+        // by recording an empty QByteArray in the cache
+        throw;
       }
       catch (...)
       {
@@ -188,7 +235,20 @@ ctkCmdLineModuleManager::registerModule(const QUrl &location)
   }
   else
   {
-    xml = backend->rawXmlDescription(location);
+    try
+    {
+      xml = backend->rawXmlDescription(location, timeout);
+    }
+    catch (const ctkCmdLineModuleRunException& e)
+    {
+      qDebug() << "Extracting XML from " << e.location().toString() << " failed with errorCode " << e.errorCode() << " and return string " << e.errorString();
+      throw;
+    }
+    catch (const ctkException& e)
+    {
+      qDebug() << e.what();
+      throw;
+    }
   }
 
   if (xml.isEmpty())
@@ -292,18 +352,6 @@ void ctkCmdLineModuleManager::clearCache()
 
 
 //----------------------------------------------------------------------------
-void ctkCmdLineModuleManager::reloadModules()
-{
-  foreach(const QUrl &location, d->LocationToRef.keys())
-  {
-    ctkCmdLineModuleReference ref = d->LocationToRef[location];
-    this->unregisterModule(ref);
-    this->registerModule(location);
-  }
-}
-
-
-//----------------------------------------------------------------------------
 ctkCmdLineModuleReference ctkCmdLineModuleManager::moduleReference(const QUrl &location) const
 {
   QMutexLocker lock(&d->Mutex);

+ 37 - 11
Libs/CommandLineModules/Core/ctkCmdLineModuleManager.h

@@ -1,22 +1,22 @@
 /*=============================================================================
-  
+
   Library: CTK
-  
+
   Copyright (c) German Cancer Research Center,
     Division of Medical and Biological Informatics
-    
+
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
-  
+
     http://www.apache.org/licenses/LICENSE-2.0
-    
+
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
-  
+
 =============================================================================*/
 
 #ifndef CTKCMDLINEMODULEMANAGER_H
@@ -111,6 +111,21 @@ public:
   void setValidationMode(const ValidationMode& mode);
 
   /**
+   * @brief Set the timeout for retrieving the XML parameter description from a module.
+   *
+   * The default time-out is 30 seconds.
+   *
+   * @param timeout The timeout in milli seconds.
+   */
+  void setTimeOutForXMLRetrieval(int timeout);
+
+  /**
+   * @brief Get the timeout for retrieving the XML parameter description from a module.
+   * @return The timeout in milli seconds.
+   */
+  int timeOutForXMLRetrieval() const;
+
+  /**
    * @brief Registers a new back-end.
    * @param backend The new back-end.
    * @throws ctkInvalidArgumentException if another back-end was already registered handling
@@ -119,11 +134,27 @@ public:
   void registerBackend(ctkCmdLineModuleBackend* backend);
 
   /**
+   * @brief Get the registered backend for a scheme.
+   * @param scheme The scheme the backend was registered with
+   * @return The backend or NULL, if \c scheme is unknown.
+   */
+  ctkCmdLineModuleBackend* backend(const QString& scheme) const;
+
+  /**
+   * @brief Get a list of all registered backends.
+   * @return A list of currently registered backends.
+   */
+  QList<ctkCmdLineModuleBackend*> backends() const;
+
+  /**
    * @brief Registers a module, identified by the given URL.
    * @param location The URL for the new module.
    * @return A module reference.
    * @throws ctkInvalidArgumentException if no back-end for the given URL scheme was registered
    *         or the XML description for the module is invalid.
+   * @throws ctkCmdLineModuleTimeoutException if a time-out occured when retrieving the
+   *         XML description from the module.
+   * @throws ctkCmdLineModuleRunException if a general error occurred when running the module.
    */
   ctkCmdLineModuleReference registerModule(const QUrl& location);
 
@@ -142,11 +173,6 @@ public:
   void clearCache();
 
   /**
-   * @brief Reloads all currently valid modules, forcing the cache to be refreshed.
-   */
-  void reloadModules();
-
-  /**
    * @brief Returns a ctkCmdLineModuleReference object for the given URL.
    * @param location The location URL for which to get a module reference.
    * @return The module reference for the location or an invalid module reference

+ 43 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleReferenceResult.h

@@ -0,0 +1,43 @@
+/*=============================================================================
+  
+  Library: CTK
+  
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+    
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+    http://www.apache.org/licenses/LICENSE-2.0
+    
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+=============================================================================*/
+
+#ifndef CTKCMDLINEMODULEREFERENCERESULT_H
+#define CTKCMDLINEMODULEREFERENCERESULT_H
+
+#include "ctkCommandLineModulesCoreExport.h"
+#include <ctkCmdLineModuleReference.h>
+
+#include <QString>
+#include <QUrl>
+
+struct CTK_CMDLINEMODULECORE_EXPORT ctkCmdLineModuleReferenceResult
+{
+  ctkCmdLineModuleReferenceResult() {}
+  ctkCmdLineModuleReferenceResult(const QUrl& moduleUrl) { m_Url = moduleUrl;}
+  ctkCmdLineModuleReferenceResult(const QUrl& moduleUrl, const QString& errorMessage) { m_Url = moduleUrl; m_RuntimeError = errorMessage; }
+  ctkCmdLineModuleReferenceResult(ctkCmdLineModuleReference& ref) { m_Reference = ref; m_Url = ref.location(); }
+
+  QUrl m_Url;
+  ctkCmdLineModuleReference m_Reference;
+  QString m_RuntimeError;
+};
+
+#endif // CTKCMDLINEMODULEREFERENCERESULT_H

+ 11 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleRunException.cpp

@@ -47,6 +47,17 @@ ctkCmdLineModuleRunException::ctkCmdLineModuleRunException(const ctkCmdLineModul
 }
 
 //----------------------------------------------------------------------------
+ctkCmdLineModuleRunException& ctkCmdLineModuleRunException::operator=(const ctkCmdLineModuleRunException& o)
+{
+  QtConcurrent::Exception::operator=(o);
+  ctkException::operator=(o);
+  this->Location = o.Location;
+  this->ErrorCode = o.ErrorCode;
+  this->ErrorString = o.ErrorString;
+  return *this;
+}
+
+//----------------------------------------------------------------------------
 ctkCmdLineModuleRunException::~ctkCmdLineModuleRunException() throw()
 {
 }

+ 3 - 3
Libs/CommandLineModules/Core/ctkCmdLineModuleRunException.h

@@ -68,9 +68,9 @@ public:
 
 private:
 
-  const QUrl Location;
-  const int ErrorCode;
-  const QString ErrorString;
+  QUrl Location;
+  int ErrorCode;
+  QString ErrorString;
 
 };
 

+ 70 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleTimeoutException.cpp

@@ -0,0 +1,70 @@
+/*=============================================================================
+  
+  Library: CTK
+  
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+    
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+    http://www.apache.org/licenses/LICENSE-2.0
+    
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  
+=============================================================================*/
+
+#include "ctkCmdLineModuleTimeoutException.h"
+
+//----------------------------------------------------------------------------
+ctkCmdLineModuleTimeoutException::ctkCmdLineModuleTimeoutException(const QUrl &location, const QString &errorString)
+  : ctkCmdLineModuleRunException(location, -1, errorString)
+{
+}
+
+//----------------------------------------------------------------------------
+ctkCmdLineModuleTimeoutException::ctkCmdLineModuleTimeoutException(const QUrl &location, const QString &errorString,
+    const ctkCmdLineModuleRunException& cause)
+  : ctkCmdLineModuleRunException(location, -1, errorString, cause)
+{
+}
+
+//----------------------------------------------------------------------------
+ctkCmdLineModuleTimeoutException::~ctkCmdLineModuleTimeoutException() throw()
+{
+}
+
+//----------------------------------------------------------------------------
+const char* ctkCmdLineModuleTimeoutException::name() const throw()
+{
+  return "CTK CommandLineModule Timeout Exception";
+}
+
+//----------------------------------------------------------------------------
+const char* ctkCmdLineModuleTimeoutException::className() const throw()
+{
+  return "ctkCmdLineModuleTimeoutException";
+}
+
+//----------------------------------------------------------------------------
+ctkCmdLineModuleTimeoutException* ctkCmdLineModuleTimeoutException::clone() const
+{
+  return new ctkCmdLineModuleTimeoutException(*this);
+}
+
+//----------------------------------------------------------------------------
+void ctkCmdLineModuleTimeoutException::rethrow() const
+{
+  throw *this;
+}
+
+//----------------------------------------------------------------------------
+void ctkCmdLineModuleTimeoutException::raise() const
+{
+  throw *this;
+}

+ 53 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleTimeoutException.h

@@ -0,0 +1,53 @@
+/*=============================================================================
+  
+  Library: CTK
+  
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+    
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+  
+    http://www.apache.org/licenses/LICENSE-2.0
+    
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+=============================================================================*/
+
+#ifndef CTKCMDLINEMODULETIMEOUTEXCEPTION_H
+#define CTKCMDLINEMODULETIMEOUTEXCEPTION_H
+
+#include <ctkCmdLineModuleRunException.h>
+#include "ctkCommandLineModulesCoreExport.h"
+
+/**
+ * \class ctkCmdLineModuleTimeoutException
+ * \brief Exception class to describe problems with timeouts when running a module.
+ * \ingroup CommandLineModulesCore_API
+ */
+class CTK_CMDLINEMODULECORE_EXPORT ctkCmdLineModuleTimeoutException
+    : public ctkCmdLineModuleRunException
+{
+public:
+
+  explicit ctkCmdLineModuleTimeoutException(const QUrl& location, const QString& errorString);
+
+  ctkCmdLineModuleTimeoutException(const QUrl& location, const QString& errorString,
+                                   const ctkCmdLineModuleRunException& cause);
+
+  ~ctkCmdLineModuleTimeoutException() throw();
+
+  virtual const char* name() const throw();
+  virtual const char* className() const throw();
+  virtual ctkCmdLineModuleTimeoutException* clone() const;
+  virtual void rethrow() const;
+
+  virtual void raise() const;
+
+};
+
+#endif // CTKCMDLINEMODULETIMEOUTEXCEPTION_H

+ 67 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleUtils.cpp

@@ -0,0 +1,67 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=============================================================================*/
+
+#include "ctkCmdLineModuleUtils.h"
+#include "ctkCmdLineModuleRunException.h"
+
+#include <QApplication>
+#include <QMessageBox>
+#include <QUrl>
+#include <QString>
+
+void ctkCmdLineModuleUtils::messageBoxModuleRegistration(const QFuture<ctkCmdLineModuleReferenceResult> &moduleRefsFuture,
+                                                                  ctkCmdLineModuleManager::ValidationMode validationMode)
+{
+  QString errorMsg;
+  QFutureIterator<ctkCmdLineModuleReferenceResult> futureIter(moduleRefsFuture);
+  while(futureIter.hasNext())
+  {
+    try
+    {
+      const ctkCmdLineModuleReferenceResult& moduleRefResult = futureIter.next();
+      if (!moduleRefResult.m_Reference)
+      {
+        errorMsg += (QObject::tr("Failed to register module:\n%1\n\ndue to:\n%2\n\n").arg(moduleRefResult.m_Url.toString()).arg(moduleRefResult.m_RuntimeError));
+      }
+      else if (!moduleRefResult.m_Reference.xmlValidationErrorString().isEmpty() &&
+             validationMode == ctkCmdLineModuleManager::STRICT_VALIDATION)
+      {
+        errorMsg += (QObject::tr("Failed to register module:\n%1\n\ndue to xml validation error:\n%2\n\n").arg(moduleRefResult.m_Url.toString()).arg(moduleRefResult.m_Reference.xmlValidationErrorString()));
+      }
+    }
+    // These exceptions should never happen, as at this point we are iterating over a fixed list of results, and not processing exceptions.
+    catch (const ctkCmdLineModuleRunException& e)
+    {
+      errorMsg += QObject::tr("Failed to register module:\n") + e.location().toString() + "\n\ndue to:\n" + e.message() + "\n\n";
+    }
+    catch (const std::exception& e)
+    {
+      errorMsg += QObject::tr("Failed to register module:\n\n\ndue to:\n") + e.what() + "\n\n";
+    }
+  }
+
+  if (!errorMsg.isEmpty())
+  {
+    QWidget* widget = QApplication::activeModalWidget();
+    if (widget == NULL) widget = QApplication::activeWindow();
+    QMessageBox::critical(widget, QObject::tr("Failed to register modules"), errorMsg);
+  }
+}

+ 38 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleUtils.h

@@ -0,0 +1,38 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) German Cancer Research Center,
+    Division of Medical and Biological Informatics
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=============================================================================*/
+
+#ifndef CTKCOMMANDLINEMODULEUTILS_H
+#define CTKCOMMANDLINEMODULEUTILS_H
+
+#include "ctkCommandLineModulesCoreExport.h"
+
+#include "ctkCmdLineModuleManager.h"
+#include "ctkCmdLineModuleReferenceResult.h"
+
+#include <QFuture>
+
+struct CTK_CMDLINEMODULECORE_EXPORT ctkCmdLineModuleUtils
+{
+  static void messageBoxModuleRegistration(const QFuture<ctkCmdLineModuleReferenceResult>& moduleRefsFuture,
+                                           ctkCmdLineModuleManager::ValidationMode validationMode);
+};
+
+#endif // CTKCOMMANDLINEMODULEUTILS_H

+ 1 - 1
Libs/CommandLineModules/Frontend/QtGui/Testing/Cpp/ctkCmdLineModuleFrontendQtGuiTest.cpp

@@ -58,7 +58,7 @@ public:
   virtual QString description() const { return "Test Mock-up"; }
   virtual QList<QString> schemes() const { return QList<QString>() << "test"; }
   virtual qint64 timeStamp(const QUrl& /*location*/) const { return 0; }
-  virtual QByteArray rawXmlDescription(const QUrl& location)
+  virtual QByteArray rawXmlDescription(const QUrl& location, int /*timeout*/)
   {
     return UrlToXml[location];
   }

+ 2 - 0
Libs/CommandLineModules/Testing/Modules/TestBed/ctkCmdLineModuleTestBed.cpp

@@ -92,6 +92,8 @@ int main(int argc, char* argv[])
 
   if (parsedArgs.contains("xml"))
   {
+    sleep_ms(2000);
+
     QFile xmlDescription(":/ctkCmdLineModuleTestBed.xml");
     xmlDescription.open(QIODevice::ReadOnly);
     out << xmlDescription.readAll();