Sfoglia il codice sorgente

Added support for progress reporting via QFuture.

Sascha Zelzer 12 anni fa
parent
commit
7775c377f5

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

@@ -21,6 +21,8 @@ set(KIT_SRCS
   ctkCmdLineModuleDirectoryWatcher_p.h
   ctkCmdLineModule.cpp
   ctkCmdLineModuleFactory.cpp
+  ctkCmdLineModuleFuture.h
+  ctkCmdLineModuleFutureInterface.h
   ctkCmdLineModuleManager.cpp
   ctkCmdLineModuleParameter.cpp
   ctkCmdLineModuleParameterGroup.cpp
@@ -28,6 +30,7 @@ set(KIT_SRCS
   ctkCmdLineModuleParameterParsers_p.h
   ctkCmdLineModulePathBuilder.cpp
   ctkCmdLineModuleProcessTask.cpp
+  ctkCmdLineModuleResult.h
   ctkCmdLineModuleXmlProgressWatcher.cpp
   ctkCmdLineModuleReference.cpp
   ctkCmdLineModuleRunException.cpp

+ 124 - 23
Libs/CommandLineModules/Core/Testing/Cpp/ctkCmdLineModuleFutureTest.cpp

@@ -25,16 +25,14 @@
 #include <ctkCmdLineModuleReference.h>
 #include <ctkCmdLineModuleDescription.h>
 #include <ctkCmdLineModuleParameter.h>
-
-#include <ctkCmdLineModuleXmlProgressWatcher.h>
-#include <QBuffer>
+#include <ctkCmdLineModuleRunException.h>
+#include <ctkCmdLineModuleFuture.h>
 
 #include "ctkCmdLineModuleSignalTester.h"
 
 #include <QVariant>
 #include <QCoreApplication>
 #include <QDebug>
-#include <QFuture>
 #include <QFutureWatcher>
 
 #include <cstdlib>
@@ -54,47 +52,84 @@ public:
 
       virtual QVariant value(const QString& parameter) const
       {
-        return this->moduleReference().description().parameter(parameter).defaultValue();
+        QVariant value = currentValues[parameter];
+        if (!value.isValid())
+          return this->moduleReference().description().parameter(parameter).defaultValue();
+        return value;
       }
 
-      virtual void setValue(const QString& /*parameter*/, const QVariant& /*value*/)
+      virtual void setValue(const QString& parameter, const QVariant& value)
       {
-        // do nothing
+        currentValues[parameter] = value;
       }
+
+    private:
+
+      QHash<QString, QVariant> currentValues;
     };
 
     return new ModuleTestInstance(moduleRef);
   }
 };
 
-
-int ctkCmdLineModuleFutureTest(int argc, char* argv[])
+bool futureTestStartFinish(ctkCmdLineModule* module)
 {
-  QCoreApplication app(argc, argv);
+  qDebug() << "Testing ctkCmdLineModuleFuture start/finish signals.";
 
-  ctkCmdLineModuleTestInstanceFactory factory;
-  ctkCmdLineModuleManager manager(&factory);
+  QList<QString> expectedSignals;
+  expectedSignals.push_back("module.started");
+  expectedSignals.push_back("module.finished");
 
-  QString moduleFilename = app.applicationDirPath() + "/ctkCmdLineModuleTestBed";
-  ctkCmdLineModuleReference moduleRef = manager.registerModule(moduleFilename);
-  if (!moduleRef)
-  {
-    qCritical() << "Module at" << moduleFilename << "could not be registered";
-  }
+  ctkCmdLineModuleSignalTester signalTester;
+
+  QFutureWatcher<ctkCmdLineModuleResult> watcher;
+  QObject::connect(&watcher, SIGNAL(started()), &signalTester, SLOT(moduleStarted()));
+  QObject::connect(&watcher, SIGNAL(finished()), &signalTester, SLOT(moduleFinished()));
+
+  ctkCmdLineModuleFuture future = module->run();
+  watcher.setFuture(future);
+
+  future.waitForFinished();
 
-  ctkCmdLineModule* module = manager.createModule(moduleRef);
+  // process pending events
+  QCoreApplication::processEvents();
+
+  return signalTester.checkSignals(expectedSignals);
+}
+
+bool futureTestProgress(ctkCmdLineModule* module)
+{
+  qDebug() << "Testing ctkCmdLineModuleFuture progress signals.";
 
   QList<QString> expectedSignals;
   expectedSignals.push_back("module.started");
+  // this signal is send when connecting a QFutureWatcher to
+  // an already started QFuture
+  expectedSignals.push_back("module.progressValueChanged");
+
+  // the following two signals are send when the module reports "filter start"
+  expectedSignals.push_back("module.progressValueChanged");
+  expectedSignals.push_back("module.progressTextChanged");
+
+  // this signal is send when the module reports progress for "output1"
+  expectedSignals.push_back("module.progressValueChanged");
+
+  // the following two signal are sent at the end to report
+  // completion and the full standard output text.
+  expectedSignals.push_back("module.progressValueChanged");
+  expectedSignals.push_back("module.progressTextChanged");
   expectedSignals.push_back("module.finished");
 
   ctkCmdLineModuleSignalTester signalTester;
 
-  QFutureWatcher<QString> watcher;
+  QFutureWatcher<ctkCmdLineModuleResult> watcher;
   QObject::connect(&watcher, SIGNAL(started()), &signalTester, SLOT(moduleStarted()));
+  QObject::connect(&watcher, SIGNAL(progressValueChanged(int)), &signalTester, SLOT(moduleProgressValueChanged(int)));
+  QObject::connect(&watcher, SIGNAL(progressTextChanged(QString)), &signalTester, SLOT(moduleProgressTextChanged(QString)));
   QObject::connect(&watcher, SIGNAL(finished()), &signalTester, SLOT(moduleFinished()));
 
-  QFuture<QString> future = module->run();
+  module->setValue("fileVar", "output1");
+  ctkCmdLineModuleFuture future = module->run();
   watcher.setFuture(future);
 
   future.waitForFinished();
@@ -102,12 +137,78 @@ int ctkCmdLineModuleFutureTest(int argc, char* argv[])
   // process pending events
   QCoreApplication::processEvents();
 
-  delete module;
+  return signalTester.checkSignals(expectedSignals);
+}
+
+bool futureTestError(ctkCmdLineModule* module)
+{
+  qDebug() << "Testing ctkCmdLineModuleFuture error reporting.";
+
+  module->setValue("fileVar", "output1");
+  module->setValue("exitCodeVar", 24);
+  module->setValue("errorTextVar", "Some error occured\n");
+
+  QFutureWatcher<ctkCmdLineModuleResult> watcher;
+  ctkCmdLineModuleFuture future = module->run();
+  watcher.setFuture(future);
+
+  try
+  {
+    future.waitForFinished();
+    return EXIT_FAILURE;
+  }
+  catch (const ctkCmdLineModuleRunException& e)
+  {
+    Q_ASSERT_X(e.errorCode() == 24, __FUNCTION__, "Error code mismatch");
+    Q_ASSERT_X(e.errorString() == "Some error occured\n", __FUNCTION__, "Error text mismatch");
+  }
+
+  // process pending events
+  QCoreApplication::processEvents();
+
+  return true;
+}
+
+int ctkCmdLineModuleFutureTest(int argc, char* argv[])
+{
+  QCoreApplication app(argc, argv);
+
+  ctkCmdLineModuleTestInstanceFactory factory;
+  ctkCmdLineModuleManager manager(&factory);
+
+  QString moduleFilename = app.applicationDirPath() + "/ctkCmdLineModuleTestBed";
+  ctkCmdLineModuleReference moduleRef = manager.registerModule(moduleFilename);
+  if (!moduleRef)
+  {
+    qCritical() << "Module at" << moduleFilename << "could not be registered";
+  }
+
+  QScopedPointer<ctkCmdLineModule> module(manager.createModule(moduleRef));
 
-  if (!signalTester.checkSignals(expectedSignals))
+  if (!futureTestStartFinish(module.data()))
   {
     return EXIT_FAILURE;
   }
 
+  if (!futureTestProgress(module.data()))
+  {
+    return EXIT_FAILURE;
+  }
+
+  if (!futureTestError(module.data()))
+  {
+    return EXIT_FAILURE;
+  }
+
+//  if (!futureTestPauseAndCancel(module.data()))
+//  {
+//    return EXIT_FAILURE;
+//  }
+
+  //  if (!futureTestResultReady(module.data()))
+  //  {
+  //    return EXIT_FAILURE;
+  //  }
+
   return EXIT_SUCCESS;
 }

+ 18 - 6
Libs/CommandLineModules/Core/Testing/Cpp/ctkCmdLineModuleSignalTester.cpp

@@ -33,24 +33,36 @@ void ctkCmdLineModuleSignalTester::moduleFinished()
   events.push_back("module.finished");
 }
 
-void ctkCmdLineModuleSignalTester::filterStarted(const QString &name, const QString &comment)
+void ctkCmdLineModuleSignalTester::moduleProgressValueChanged(int /*progress*/)
+{
+  events.push_back("module.progressValueChanged");
+}
+
+void ctkCmdLineModuleSignalTester::moduleProgressTextChanged(const QString &/*text*/)
+{
+  events.push_back("module.progressTextChanged");
+}
+
+void ctkCmdLineModuleSignalTester::filterStarted(const QString &/*name*/, const QString &/*comment*/)
 {
-  qDebug() << "Filter started:" << name << "(" << comment << ")";
   events.push_back("filter.started");
 }
 
-void ctkCmdLineModuleSignalTester::filterProgress(float progress)
+void ctkCmdLineModuleSignalTester::filterProgress(float /*progress*/)
 {
-  qDebug() << "progress:" << progress;
   events.push_back("filter.progress");
 }
 
-void ctkCmdLineModuleSignalTester::filterFinished(const QString &name)
+void ctkCmdLineModuleSignalTester::filterFinished(const QString &/*name*/)
 {
-  qDebug() << "Filter finished:" << name;
   events.push_back("filter.finished");
 }
 
+void ctkCmdLineModuleSignalTester::filterXmlError(const QString &/*error*/)
+{
+  events.push_back("filter.xmlError");
+}
+
 bool ctkCmdLineModuleSignalTester::checkSignals(const QList<QString>& expectedSignals)
 {
   if (events.size() != expectedSignals.size())

+ 9 - 6
Libs/CommandLineModules/Core/Testing/Cpp/ctkCmdLineModuleSignalTester.h

@@ -37,12 +37,15 @@ public:
 
 public Q_SLOTS:
 
-  void moduleStarted();
-  void moduleFinished();
-
-  void filterStarted(const QString& name, const QString& comment);
-  void filterProgress(float progress);
-  void filterFinished(const QString& name);
+  virtual void moduleStarted();
+  virtual void moduleFinished();
+  virtual void moduleProgressValueChanged(int progress);
+  virtual void moduleProgressTextChanged(const QString& text);
+
+  virtual void filterStarted(const QString& name, const QString& comment);
+  virtual void filterProgress(float progress);
+  virtual void filterFinished(const QString& name);
+  virtual void filterXmlError(const QString& error);
 
 private:
 

+ 171 - 14
Libs/CommandLineModules/Core/Testing/Cpp/ctkCmdLineModuleXmlProgressWatcherTest.cpp

@@ -27,36 +27,193 @@
 #include <QCoreApplication>
 #include <QBuffer>
 #include <QDataStream>
+#include <QDebug>
 
 #include <cstdlib>
 
+namespace {
 
-int ctkCmdLineModuleXmlProgressWatcherTest(int argc, char* argv[])
+// Custom signal tester
+class SignalTester : public ctkCmdLineModuleSignalTester
 {
-  QCoreApplication app(argc, argv);
+public:
 
-  QByteArray input;
-  QBuffer buffer(&input);
-  buffer.open(QIODevice::ReadWrite);
+  SignalTester() : accumulatedProgress(0)
+  {}
+
+  void filterStarted(const QString& name, const QString& comment)
+  {
+    ctkCmdLineModuleSignalTester::filterStarted(name, comment);
+    if (name != "My Filter")
+    {
+      error = "Filter name does not match \"My Filter\" (got \"" + name + "\")";
+      return;
+    }
+    if (comment != "Awesome filter")
+    {
+      error = "Filter comment does not match \"Awesome filter\" (got \"" + comment + "\")";
+      return;
+    }
+  }
+
+  void filterProgress(float progress)
+  {
+    ctkCmdLineModuleSignalTester::filterProgress(progress);
+    accumulatedProgress += progress;
+  }
+
+  void filterFinished(const QString& name)
+  {
+    ctkCmdLineModuleSignalTester::filterFinished(name);
+    if (name != "My Filter")
+    {
+      error = "Filter name does not match \"My Filter\" (got \"" + name + "\")";
+      return;
+    }
+  }
 
-  QByteArray ba = "<filter-start><filter-name>My Filter</filter-name><filter-comment>"
-      "Awesome filter</filter-comment></filter-start>"
-      "<filter-progress>0.3</filter-progress>"
-      "<filter-progress>0.6</filter-progress>"
-      "<filter-progress>0.9</filter-progress>"
-      "<filter-end><filter-name>My Filter</filter-name><filter-time>23</filter-time></filter-end>";
+  void filterXmlError(const QString& error)
+  {
+    ctkCmdLineModuleSignalTester::filterXmlError(error);
+    this->error = error;
+  }
 
+  QString error;
+  float accumulatedProgress;
+};
+
+bool xmlProgressWatcherTestSignalsAndValues()
+{
+  // Test data
+  QByteArray filterStart = "<filter-start>\n"
+                             "<filter-name>My Filter</filter-name>\n"
+                             "<filter-comment>Awesome filter</filter-comment>\n"
+                           "</filter-start>\n";
+  QString filterProgress = "<filter-progress>%1</filter-progress>\n";
+  QByteArray filterEnd = "<filter-end>\n"
+                           "<filter-name>My Filter</filter-name>\n"
+                           "<filter-time>23</filter-time>\n"
+                         "</filter-end>";
+
+  QBuffer buffer;
+  buffer.open(QIODevice::ReadWrite);
   ctkCmdLineModuleXmlProgressWatcher progressWatcher(&buffer);
 
-  ctkCmdLineModuleSignalTester signalTester;
+  SignalTester signalTester;
   signalTester.connect(&progressWatcher, SIGNAL(filterStarted(QString,QString)), &signalTester, SLOT(filterStarted(QString,QString)));
   signalTester.connect(&progressWatcher, SIGNAL(filterProgress(float)), &signalTester, SLOT(filterProgress(float)));
   signalTester.connect(&progressWatcher, SIGNAL(filterFinished(QString)), &signalTester, SLOT(filterFinished(QString)));
+  signalTester.connect(&progressWatcher, SIGNAL(filterXmlError(QString)), &signalTester, SLOT(filterXmlError(QString)));
+
+  buffer.write(filterStart);
+  QCoreApplication::processEvents();
+  buffer.write(filterProgress.arg(0.3).toLatin1());
+  QCoreApplication::processEvents();
+  buffer.write(filterProgress.arg(0.6).toLatin1());
+  QCoreApplication::processEvents();
+  buffer.write(filterProgress.arg(0.9).toLatin1());
+  QCoreApplication::processEvents();
+  buffer.write(filterEnd);
+  QCoreApplication::processEvents();
+
+  QList<QString> expectedSignals;
+  expectedSignals << "filter.started";
+  expectedSignals << "filter.progress";
+  expectedSignals << "filter.progress";
+  expectedSignals << "filter.progress";
+  expectedSignals << "filter.finished";
+
+  if (!signalTester.error.isEmpty())
+  {
+    qDebug() << signalTester.error;
+    return false;
+  }
+
+  if (!signalTester.checkSignals(expectedSignals))
+  {
+    return false;
+  }
+
+  if (signalTester.accumulatedProgress != 1.8f)
+  {
+    qDebug() << "Progress information wrong. Expected 1.8, got" << signalTester.accumulatedProgress;
+    return false;
+  }
+
+  return true;
+}
+
+bool xmlProgressWatcherTestMalformedXml()
+{
+  // Test data
+  QByteArray filterOutput = "<filter-start>\n"
+                              "<filter-name>My Filter</filter-name>\n"
+                              "<filter-comment>Awesome filter</filter-comment>\n"
+                              "chunk<some-tag>...</some-tag>\n"
+                              "<filter-progress>0.2</filter-progress>\n"
+                            "</filter-start>\n"
+                            "<filter-progress>0.5</filter-progress>\n"
+                            "<filter-end>\n"
+
+                              "<filter-name>My Filter</filter-name>\n"
+                              "<filter-time>23</filter-time>\n"
+                            "</filter-end>";
+
+  QBuffer buffer;
+  buffer.open(QIODevice::ReadWrite);
+  ctkCmdLineModuleXmlProgressWatcher progressWatcher(&buffer);
 
-  QDataStream xmlOut(&buffer);
-  xmlOut << ba;
+  SignalTester signalTester;
+  signalTester.connect(&progressWatcher, SIGNAL(filterStarted(QString,QString)), &signalTester, SLOT(filterStarted(QString,QString)));
+  signalTester.connect(&progressWatcher, SIGNAL(filterProgress(float)), &signalTester, SLOT(filterProgress(float)));
+  signalTester.connect(&progressWatcher, SIGNAL(filterFinished(QString)), &signalTester, SLOT(filterFinished(QString)));
+  signalTester.connect(&progressWatcher, SIGNAL(filterXmlError(QString)), &signalTester, SLOT(filterXmlError(QString)));
 
+  buffer.write(filterOutput);
   QCoreApplication::processEvents();
 
+
+  QList<QString> expectedSignals;
+  expectedSignals << "filter.xmlError";
+  expectedSignals << "filter.started";
+  expectedSignals << "filter.progress";
+  expectedSignals << "filter.finished";
+
+  if (!signalTester.error.isEmpty())
+  {
+    qDebug() << signalTester.error;
+    //return false;
+  }
+
+  if (!signalTester.checkSignals(expectedSignals))
+  {
+    return false;
+  }
+
+  if (signalTester.accumulatedProgress != 0.5f)
+  {
+    qDebug() << "Progress information wrong. Expected 1.8, got" << signalTester.accumulatedProgress;
+    return false;
+  }
+
+  return true;
+}
+
+}
+
+int ctkCmdLineModuleXmlProgressWatcherTest(int argc, char* argv[])
+{
+  QCoreApplication app(argc, argv);
+
+  if (!xmlProgressWatcherTestSignalsAndValues())
+  {
+    return EXIT_FAILURE;
+  }
+
+  if (!xmlProgressWatcherTestMalformedXml())
+  {
+    return EXIT_FAILURE;
+  }
+
   return EXIT_SUCCESS;
 }

+ 38 - 3
Libs/CommandLineModules/Core/Testing/Modules/TestBed/ctkCmdLineModuleTestBed.cpp

@@ -46,6 +46,7 @@ int main(int argc, char* argv[])
   parser.addArgument("xml", "", QVariant::Bool, "Print a XML description of this modules command line interface");
   parser.addArgument("runtime", "", QVariant::Int, "Runtime in seconds", 1);
   parser.addArgument("exitCode", "", QVariant::Int, "Exit code", 0);
+  parser.addArgument("exitCrash", "", QVariant::Bool, "Force crash", false);
   parser.addArgument("exitTime", "", QVariant::Int, "Exit time", 0);
   parser.addArgument("errorText", "", QVariant::String, "Error text (will not be printed on exit code 0)");
   QTextStream out(stdout, QIODevice::WriteOnly);
@@ -85,6 +86,7 @@ int main(int argc, char* argv[])
   float exitTime = parsedArgs["exitTime"].toFloat();
   int exitTimeMillis = static_cast<long>(exitTime/2.0 * 1000.0);
   int exitCode = parsedArgs["exitCode"].toInt();
+  bool exitCrash = parsedArgs["exitCrash"].toBool();
   QString errorText = parsedArgs["errorText"].toString();
 
   QStringList outputs = parser.unparsedArguments();
@@ -92,6 +94,11 @@ int main(int argc, char* argv[])
   if (outputs.empty())
   {
     // no outputs given, just return
+    if (exitCrash)
+    {
+      int* crash = 0;
+      *crash = 5;
+    }
     if (exitCode != 0)
     {
       err << errorText;
@@ -106,11 +113,24 @@ int main(int argc, char* argv[])
 
   struct timespec nanostep;
 
-  foreach(QString output, outputs)
+  out << "<filter-start>\n";
+  out << "<filter-name>Test Filter</filter-name>\n";
+  out << "<filter-comment>Does nothing useful</filter-comment>\n";
+  out << "</filter-start>\n";
+
+  float progressStep = 1.0f / static_cast<float>(outputs.size());
+  for(int i = 0; i < outputs.size(); ++i)
   {
+    QString output = outputs[i];
+
     if (exitTimeMillis != 0 && exitTimeMillis < time.elapsed())
     {
-      if (exitCode != 0)
+      if (exitCrash)
+      {
+        int* crash = 0;
+        *crash = 5;
+      }
+      if (exitCode != 0 && !errorText.isEmpty())
       {
         err << errorText;
       }
@@ -125,7 +145,22 @@ int main(int argc, char* argv[])
 
     // print the first output
     out << output; endl(out);
+
+    // report progress
+    out << "<filter-progress>" << (i+1)*progressStep << "</filter-progress>\n";
   }
 
-  return EXIT_SUCCESS;
+  // sleep 1 second to avoid squashing the last progress event with the finished event
+  sleep(1);
+
+  if (exitCrash)
+  {
+    int* crash = 0;
+    *crash = 5;
+  }
+  if (exitCode != 0 && !errorText.isEmpty())
+  {
+    err << errorText;
+  }
+  return exitCode;
 }

+ 7 - 0
Libs/CommandLineModules/Core/Testing/Modules/TestBed/ctkCmdLineModuleTestBed.xml

@@ -46,6 +46,13 @@ Configurable behaviour for testing purposes.
       <label>Exit code</label>
       <default>0</default>
     </integer>
+    <boolean>
+      <name>exitCrashVar</name>
+      <longflag>exitCrash</longflag>
+      <description>Exit by crashing.</description>
+      <label>Force a crash</label>
+      <default>false</default>
+    </boolean>
     <string>
       <name>errorTextVar</name>
       <longflag>errorText</longflag>

+ 9 - 3
Libs/CommandLineModules/Core/ctkCmdLineModule.cpp

@@ -25,12 +25,12 @@
 #include "ctkCmdLineModuleParameterGroup.h"
 #include "ctkCmdLineModuleReference.h"
 #include "ctkCmdLineModuleProcessTask.h"
+#include "ctkCmdLineModuleFuture.h"
 
 #include "ctkException.h"
 
 #include <QStringList>
 #include <QDebug>
-#include <QFuture>
 #include <QVariant>
 
 struct ctkCmdLineModulePrivate
@@ -119,12 +119,18 @@ QStringList ctkCmdLineModule::commandLineArguments() const
       {
         argFlag = QString("--") + d->normalizeFlag(parameter.longFlag());
       }
-
       QStringList args;
       if (parameter.multiple())
       {
         args = valuesIter.value().toString().split(',', QString::SkipEmptyParts);
       }
+      else if (parameter.tag() == "boolean")
+      {
+        if (valuesIter.value().toBool())
+        {
+          cmdLineArgs << argFlag;
+        }
+      }
       else
       {
         args.push_back(valuesIter.value().toString());
@@ -147,7 +153,7 @@ QStringList ctkCmdLineModule::commandLineArguments() const
   return cmdLineArgs;
 }
 
-QFuture<QString> ctkCmdLineModule::run() const
+ctkCmdLineModuleFuture ctkCmdLineModule::run() const
 {
   QStringList args = commandLineArguments();
 

+ 3 - 1
Libs/CommandLineModules/Core/ctkCmdLineModule.h

@@ -29,6 +29,8 @@
 template<class K, class V> class QHash;
 template<class T> class QFuture;
 
+class ctkCmdLineModuleResult;
+typedef QFuture<ctkCmdLineModuleResult> ctkCmdLineModuleFuture;
 class ctkCmdLineModuleReference;
 class ctkCmdLineModulePrivate;
 
@@ -59,7 +61,7 @@ public:
 
   QStringList commandLineArguments() const;
 
-  QFuture<QString> run() const;
+  ctkCmdLineModuleFuture run() const;
 
   Q_SIGNAL void valueChanged(const QString& parameter, const QVariant& value);
 

+ 173 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleFuture.h

@@ -0,0 +1,173 @@
+/*=============================================================================
+
+  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 CTKCMDLINEMODULEFUTURE_H
+#define CTKCMDLINEMODULEFUTURE_H
+
+#include "ctkCmdLineModuleFutureInterface.h"
+
+#include <QString>
+#include <QVariant>
+#include <QFuture>
+
+template <typename ctkCmdLineModuleResult>
+class QFutureWatcher;
+template <>
+class QFutureWatcher<void>;
+
+/**
+ * QFuture specialization with two additional methods:
+ *
+ *   - bool canCancel()
+ *   - bool canPause()
+ */
+template<>
+class QFuture<ctkCmdLineModuleResult>
+{
+public:
+
+  QFuture()
+    : d(QFutureInterface<ctkCmdLineModuleResult>::canceledResult())
+  { }
+  explicit QFuture(QFutureInterface<ctkCmdLineModuleResult> *p) // internal
+    : d(*p)
+  { }
+  QFuture(const QFuture &other)
+    : d(other.d)
+  { }
+  ~QFuture()
+  { }
+
+  inline QFuture &operator=(const QFuture &other);
+  bool operator==(const QFuture &other) const { return (d == other.d); }
+  bool operator!=(const QFuture &other) const { return (d != other.d); }
+
+  // additional methods
+  bool canCancel() const { return  d.canCancel(); }
+  bool canPause() const { return d.canPause(); }
+
+  void cancel() { d.cancel(); }
+  bool isCanceled() const { return d.isCanceled(); }
+
+  void setPaused(bool paused) { d.setPaused(paused); }
+  bool isPaused() const { return d.isPaused(); }
+  void pause() { setPaused(true); }
+  void resume() { setPaused(false); }
+  void togglePaused() { d.togglePaused(); }
+
+  bool isStarted() const { return d.isStarted(); }
+  bool isFinished() const { return d.isFinished(); }
+  bool isRunning() const { return d.isRunning(); }
+
+  int resultCount() const { return d.resultCount(); }
+  int progressValue() const { return d.progressValue(); }
+  int progressMinimum() const { return d.progressMinimum(); }
+  int progressMaximum() const { return d.progressMaximum(); }
+  QString progressText() const { return d.progressText(); }
+  void waitForFinished() { d.waitForFinished(); }
+
+  inline ctkCmdLineModuleResult result() const;
+  inline ctkCmdLineModuleResult resultAt(int index) const;
+  bool isResultReadyAt(int resultIndex) const { return d.isResultReadyAt(resultIndex); }
+
+  operator ctkCmdLineModuleResult() const { return result(); }
+  QList<ctkCmdLineModuleResult> results() const { return d.results(); }
+
+  class const_iterator
+  {
+  public:
+    typedef std::bidirectional_iterator_tag iterator_category;
+    typedef qptrdiff difference_type;
+    typedef ctkCmdLineModuleResult value_type;
+    typedef const ctkCmdLineModuleResult *pointer;
+    typedef const ctkCmdLineModuleResult &reference;
+
+    inline const_iterator() {}
+    inline const_iterator(QFuture const * const _future, int _index) : future(_future), index(_index) {}
+    inline const_iterator(const const_iterator &o) : future(o.future), index(o.index)  {}
+    inline const_iterator &operator=(const const_iterator &o)
+    { future = o.future; index = o.index; return *this; }
+    inline const ctkCmdLineModuleResult &operator*() const { return future->d.resultReference(index); }
+    inline const ctkCmdLineModuleResult *operator->() const { return future->d.resultPointer(index); }
+
+    inline bool operator!=(const const_iterator &other) const
+    {
+      if (index == -1 && other.index == -1) // comparing end != end?
+        return false;
+      if (other.index == -1)
+        return (future->isRunning() || (index < future->resultCount()));
+      return (index != other.index);
+    }
+
+    inline bool operator==(const const_iterator &o) const { return !operator!=(o); }
+    inline const_iterator &operator++() { ++index; return *this; }
+    inline const_iterator operator++(int) { const_iterator r = *this; ++index; return r; }
+    inline const_iterator &operator--() { --index; return *this; }
+    inline const_iterator operator--(int) { const_iterator r = *this; --index; return r; }
+    inline const_iterator operator+(int j) const { return const_iterator(future, index + j); }
+    inline const_iterator operator-(int j) const { return const_iterator(future, index - j); }
+    inline const_iterator &operator+=(int j) { index += j; return *this; }
+    inline const_iterator &operator-=(int j) { index -= j; return *this; }
+  private:
+    QFuture const * future;
+    int index;
+  };
+  friend class const_iterator;
+  typedef const_iterator ConstIterator;
+
+  const_iterator begin() const { return  const_iterator(this, 0); }
+  const_iterator constBegin() const { return  const_iterator(this, 0); }
+  const_iterator end() const { return const_iterator(this, -1); }
+  const_iterator constEnd() const { return const_iterator(this, -1); }
+
+private:
+  friend class QFutureWatcher<ctkCmdLineModuleResult>;
+
+public: // Warning: the d pointer is not documented and is considered private.
+  mutable QFutureInterface<ctkCmdLineModuleResult> d;
+};
+
+typedef QFuture<ctkCmdLineModuleResult> ctkCmdLineModuleFuture;
+
+inline ctkCmdLineModuleFuture& ctkCmdLineModuleFuture::operator=(const ctkCmdLineModuleFuture& other)
+{
+  d = other.d;
+  return *this;
+}
+
+inline ctkCmdLineModuleResult ctkCmdLineModuleFuture::result() const
+{
+  d.waitForResult(0);
+  return d.resultReference(0);
+}
+
+inline ctkCmdLineModuleResult ctkCmdLineModuleFuture::resultAt(int index) const
+{
+  d.waitForResult(index);
+  return d.resultReference(index);
+}
+
+inline ctkCmdLineModuleFuture ctkCmdLineModuleFutureInterface::future()
+{
+  return ctkCmdLineModuleFuture(this);
+}
+
+#endif // CTKCMDLINEMODULEFUTURE_H

+ 176 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleFutureInterface.h

@@ -0,0 +1,176 @@
+/*=============================================================================
+
+  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 CTKCMDLINEMODULEFUTUREINTERFACE_H
+#define CTKCMDLINEMODULEFUTUREINTERFACE_H
+
+#include "ctkCmdLineModuleResult.h"
+
+#include <QFutureInterface>
+
+typedef QFuture<ctkCmdLineModuleResult> ctkCmdLineModuleFuture;
+
+template <>
+class QFutureInterface<ctkCmdLineModuleResult> : public QFutureInterfaceBase
+{
+
+public:
+
+  QFutureInterface(State initialState = NoState)
+    : QFutureInterfaceBase(initialState),
+      CanCancel(false), CanPause(false)
+  { }
+
+  QFutureInterface(const QFutureInterface &other)
+    : QFutureInterfaceBase(other),
+      CanCancel(false), CanPause(false)
+  { }
+
+  ~QFutureInterface()
+  {
+    if (referenceCountIsOne())
+      resultStore().clear();
+  }
+
+  static QFutureInterface canceledResult()
+  { return QFutureInterface(State(Started | Finished | Canceled)); }
+
+  QFutureInterface& operator=(const QFutureInterface& other)
+  {
+    if (referenceCountIsOne())
+      resultStore().clear();
+    QFutureInterfaceBase::operator=(other);
+    return *this;
+  }
+
+  inline ctkCmdLineModuleFuture future(); // implemented in ctkCmdLineModuleFuture.h
+
+  inline bool canCancel() const { return CanCancel; }
+  inline void setCanCancel(bool canCancel) { CanCancel = canCancel; }
+  inline bool canPause() const { return CanPause; }
+  inline void setCanPause(bool canPause) { CanPause = canPause; }
+
+  inline void reportResult(const ctkCmdLineModuleResult *result, int index = -1);
+  inline void reportResult(const ctkCmdLineModuleResult &result, int index = -1);
+  inline void reportResults(const QVector<ctkCmdLineModuleResult> &results, int beginIndex = -1, int count = -1);
+  inline void reportFinished(const ctkCmdLineModuleResult *result = 0);
+
+  inline const ctkCmdLineModuleResult &resultReference(int index) const;
+  inline const ctkCmdLineModuleResult *resultPointer(int index) const;
+  inline QList<ctkCmdLineModuleResult> results();
+
+private:
+
+  QtConcurrent::ResultStore<ctkCmdLineModuleResult> &resultStore()
+  { return static_cast<QtConcurrent::ResultStore<ctkCmdLineModuleResult> &>(resultStoreBase()); }
+  const QtConcurrent::ResultStore<ctkCmdLineModuleResult> &resultStore() const
+  { return static_cast<const QtConcurrent::ResultStore<ctkCmdLineModuleResult> &>(resultStoreBase()); }
+
+  bool CanCancel;
+  bool CanPause;
+};
+
+inline void QFutureInterface<ctkCmdLineModuleResult>::reportResult(const ctkCmdLineModuleResult *result, int index)
+{
+    QMutexLocker locker(mutex());
+    if (this->queryState(Canceled) || this->queryState(Finished)) {
+        return;
+    }
+
+    QtConcurrent::ResultStore<ctkCmdLineModuleResult> &store = resultStore();
+
+
+    if (store.filterMode()) {
+        const int resultCountBefore = store.count();
+        store.addResult(index, result);
+        this->reportResultsReady(resultCountBefore, resultCountBefore + store.count());
+    } else {
+        const int insertIndex = store.addResult(index, result);
+        this->reportResultsReady(insertIndex, insertIndex + 1);
+    }
+}
+
+inline void QFutureInterface<ctkCmdLineModuleResult>::reportResult(const ctkCmdLineModuleResult &result, int index)
+{
+    reportResult(&result, index);
+}
+
+inline void QFutureInterface<ctkCmdLineModuleResult>::reportResults(const QVector<ctkCmdLineModuleResult> &_results, int beginIndex, int count)
+{
+    QMutexLocker locker(mutex());
+    if (this->queryState(Canceled) || this->queryState(Finished)) {
+        return;
+    }
+
+    QtConcurrent::ResultStore<ctkCmdLineModuleResult> &store = resultStore();
+
+    if (store.filterMode()) {
+        const int resultCountBefore = store.count();
+        store.addResults(beginIndex, &_results, count);
+        this->reportResultsReady(resultCountBefore, store.count());
+    } else {
+        const int insertIndex = store.addResults(beginIndex, &_results, count);
+        this->reportResultsReady(insertIndex, insertIndex + _results.count());
+    }
+}
+
+inline void QFutureInterface<ctkCmdLineModuleResult>::reportFinished(const ctkCmdLineModuleResult *result)
+{
+    if (result)
+        reportResult(result);
+    QFutureInterfaceBase::reportFinished();
+}
+
+inline const ctkCmdLineModuleResult &QFutureInterface<ctkCmdLineModuleResult>::resultReference(int index) const
+{
+    QMutexLocker lock(mutex());
+    return resultStore().resultAt(index).value();
+}
+
+inline const ctkCmdLineModuleResult *QFutureInterface<ctkCmdLineModuleResult>::resultPointer(int index) const
+{
+    QMutexLocker lock(mutex());
+    return resultStore().resultAt(index).pointer();
+}
+
+inline QList<ctkCmdLineModuleResult> QFutureInterface<ctkCmdLineModuleResult>::results()
+{
+    if (this->isCanceled()) {
+        exceptionStore().throwPossibleException();
+        return QList<ctkCmdLineModuleResult>();
+    }
+    QFutureInterfaceBase::waitForResult(-1);
+
+    QList<ctkCmdLineModuleResult> res;
+    QMutexLocker lock(mutex());
+
+    QtConcurrent::ResultIterator<ctkCmdLineModuleResult> it = resultStore().begin();
+    while (it != resultStore().end()) {
+        res.append(it.value());
+        ++it;
+    }
+
+    return res;
+}
+
+typedef QFutureInterface<ctkCmdLineModuleResult> ctkCmdLineModuleFutureInterface;
+
+#endif // CTKCMDLINEMODULEFUTUREINTERFACE_H

+ 69 - 25
Libs/CommandLineModules/Core/ctkCmdLineModuleProcessTask.cpp

@@ -21,28 +21,80 @@
 
 #include "ctkCmdLineModuleProcessTask.h"
 #include "ctkCmdLineModuleRunException.h"
+#include "ctkCmdLineModuleXmlProgressWatcher.h"
+#include "ctkCmdLineModuleFuture.h"
 
 #include <QDebug>
 #include <QEventLoop>
 #include <QThreadPool>
 #include <QProcess>
-#include <QFuture>
 
+ctkCmdLineModuleProcessProgressWatcher::ctkCmdLineModuleProcessProgressWatcher(QProcess& process, const QString& location,
+                                                                               ctkCmdLineModuleFutureInterface &futureInterface)
+  : process(process), location(location), futureInterface(futureInterface), processXmlWatcher(&process),
+    progressValue(0)
+{
+  futureInterface.setProgressRange(0, 1000);
+
+  connect(&processXmlWatcher, SIGNAL(filterStarted(QString,QString)), SLOT(filterStarted(QString,QString)));
+  connect(&processXmlWatcher, SIGNAL(filterProgress(float)), SLOT(filterProgress(float)));
+  connect(&processXmlWatcher, SIGNAL(filterFinished(QString)), SLOT(filterFinished(QString)));
+  connect(&processXmlWatcher, SIGNAL(filterXmlError(QString)), SLOT(filterXmlError(QString)));
+}
+
+void ctkCmdLineModuleProcessProgressWatcher::filterStarted(const QString& name, const QString& comment)
+{
+  Q_UNUSED(comment)
+  futureInterface.setProgressValueAndText(incrementProgress(), name);
+}
+
+void ctkCmdLineModuleProcessProgressWatcher::filterProgress(float progress)
+{
+  futureInterface.setProgressValue(updateProgress(progress));
+}
+
+void ctkCmdLineModuleProcessProgressWatcher::filterFinished(const QString& name)
+{
+  futureInterface.setProgressValueAndText(incrementProgress(), "Finished: " + name);
+}
+
+void ctkCmdLineModuleProcessProgressWatcher::filterXmlError(const QString &error)
+{
+  qDebug().nospace() << "[Module " << location << "]: " << error;
+}
+
+int ctkCmdLineModuleProcessProgressWatcher::updateProgress(float progress)
+{
+  progressValue = static_cast<int>(progress * 1000.0f);
+  // normalize the value to lie between 0 and 1000.
+  // 0 is reported when the process starts and 1000 is reserved for
+  // reporting completion + standard output text
+  if (progressValue < 1) progressValue = 1;
+  if (progressValue > 999) progressValue = 999;
+  return progressValue;
+}
+
+int ctkCmdLineModuleProcessProgressWatcher::incrementProgress()
+{
+  if (++progressValue > 999) progressValue = 999;
+  return progressValue;
+}
 
 ctkCmdLineModuleProcessTask::ctkCmdLineModuleProcessTask(const QString& location, const QStringList& args)
   : location(location), args(args)
 {
+  this->setCanCancel(true);
 }
 
 ctkCmdLineModuleProcessTask::~ctkCmdLineModuleProcessTask()
 {
 }
 
-QFuture<QString> ctkCmdLineModuleProcessTask::start()
+ctkCmdLineModuleFuture ctkCmdLineModuleProcessTask::start()
 {
   this->setRunnable(this);
   this->reportStarted();
-  QFuture<QString> future = this->future();
+  ctkCmdLineModuleFuture future = this->future();
   QThreadPool::globalInstance()->start(this, /*m_priority*/ 0);
   return future;
 }
@@ -56,39 +108,31 @@ void ctkCmdLineModuleProcessTask::run()
   }
 
   QProcess process;
+  process.setReadChannel(QProcess::StandardOutput);
+
   QEventLoop localLoop;
-  connect(&process, SIGNAL(finished(int)), &localLoop, SLOT(quit()));
-  connect(&process, SIGNAL(error(QProcess::ProcessError)), SLOT(error(QProcess::ProcessError)));
-  connect(&process, SIGNAL(readyReadStandardError()), SLOT(readyReadStandardError()));
-  connect(&process, SIGNAL(readyReadStandardOutput()), SLOT(readyReadStandardOutput()));
+  QObject::connect(&process, SIGNAL(finished(int)), &localLoop, SLOT(quit()));
+  QObject::connect(&process, SIGNAL(error(QProcess::ProcessError)), &localLoop, SLOT(quit()));
 
   process.start(location, args);
 
+  ctkCmdLineModuleProcessProgressWatcher progressWatcher(process, location, *this);
+  Q_UNUSED(progressWatcher)
+
   localLoop.exec();
 
-  if (process.exitCode() != 0 || process.exitStatus() == QProcess::CrashExit)
+  if (process.error() != QProcess::UnknownError)
+  {
+    this->reportException(ctkCmdLineModuleRunException(location, process.exitCode(), process.errorString()));
+  }
+  else if (process.exitCode() != 0)
   {
-    QString msg = "The process running \"%1\" ";
-    msg += process.exitStatus() == QProcess::CrashExit ? QString("crashed: ")
-                                                     : QString("exited with code %1: ").arg(process.exitCode());
-    msg += process.readAllStandardError();
-    this->reportException(ctkCmdLineModuleRunException(msg));
+    this->reportException(ctkCmdLineModuleRunException(location, process.exitCode(), process.readAllStandardError()));
   }
 
-  this->setProgressValueAndText(100, process.readAllStandardOutput());
+  this->setProgressValueAndText(1000, process.readAllStandardError());
 
   //this->reportResult(result);
   this->reportFinished();
-}
-
-void ctkCmdLineModuleProcessTask::error(QProcess::ProcessError error)
-{
-}
 
-void ctkCmdLineModuleProcessTask::readyReadStandardError()
-{
-}
-
-void ctkCmdLineModuleProcessTask::readyReadStandardOutput()
-{
 }

+ 36 - 11
Libs/CommandLineModules/Core/ctkCmdLineModuleProcessTask.h

@@ -22,37 +22,62 @@
 #ifndef CTKCMDLINEMODULEPROCESSTASK_H
 #define CTKCMDLINEMODULEPROCESSTASK_H
 
-#include <QFutureInterface>
+#include "ctkCmdLineModuleXmlProgressWatcher.h"
+#include "ctkCmdLineModuleFutureInterface.h"
 
 #include <QObject>
 #include <QRunnable>
 #include <QStringList>
-#include <QProcess>
+#include <QBuffer>
 
-class ctkCmdLineModuleProcessTask : public QObject, public QFutureInterface<QString>, public QRunnable
+class QProcess;
+
+class ctkCmdLineModuleProcessTask : public ctkCmdLineModuleFutureInterface, public QRunnable
 {
-  Q_OBJECT
 
 public:
 
   ctkCmdLineModuleProcessTask(const QString& location, const QStringList& args);
   ~ctkCmdLineModuleProcessTask();
 
-  QFuture<QString> start();
+  ctkCmdLineModuleFuture start();
 
   void run();
 
+private:
+
+  const QString location;
+  const QStringList args;
+
+};
+
+class ctkCmdLineModuleProcessProgressWatcher : public QObject
+{
+  Q_OBJECT
+
+public:
+
+  ctkCmdLineModuleProcessProgressWatcher(QProcess& process, const QString& location,
+                                         ctkCmdLineModuleFutureInterface& futureInterface);
+
 protected Q_SLOTS:
 
-  void error(QProcess::ProcessError error);
-  void readyReadStandardError();
-  void readyReadStandardOutput();
+  void filterStarted(const QString& name, const QString& comment);
+  void filterProgress(float progress);
+  void filterFinished(const QString& name);
+
+  void filterXmlError(const QString& error);
 
 private:
 
-  const QString location;
-  const QStringList args;
-  QString result;
+  int updateProgress(float progress);
+  int incrementProgress();
+
+  QProcess& process;
+  QString location;
+  ctkCmdLineModuleFutureInterface& futureInterface;
+  ctkCmdLineModuleXmlProgressWatcher processXmlWatcher;
+  int progressValue;
 };
 
 #endif // CTKCMDLINEMODULEPROCESSTASK_H

+ 47 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleResult.h

@@ -0,0 +1,47 @@
+/*=============================================================================
+
+  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 CTKCMDLINEMODULERESULT_H
+#define CTKCMDLINEMODULERESULT_H
+
+#include <QString>
+#include <QVariant>
+
+class ctkCmdLineModuleResult
+{
+public:
+
+  ctkCmdLineModuleResult() {}
+
+  ctkCmdLineModuleResult(const QString& parameter, const QVariant& value)
+    : Parameter(parameter), Value(value)
+  {}
+
+  inline QString parameter() const { return Parameter; }
+  inline QVariant value() const { return Value; }
+
+private:
+
+  QString Parameter;
+  QVariant Value;
+};
+
+#endif // CTKCMDLINEMODULERESULT_H

+ 27 - 5
Libs/CommandLineModules/Core/ctkCmdLineModuleRunException.cpp

@@ -16,18 +16,25 @@ See LICENSE.txt or http://www.mitk.org for details.
 
 #include "ctkCmdLineModuleRunException.h"
 
-ctkCmdLineModuleRunException::ctkCmdLineModuleRunException(const QString& msg)
-  : QtConcurrent::Exception(), ctkException(msg)
+ctkCmdLineModuleRunException::ctkCmdLineModuleRunException(
+    const QString& location, int errorCode, const QString &errorString)
+  : QtConcurrent::Exception(),
+    ctkException(QString("Running module \"%1\" failed with code %2: %3").arg(location).arg(errorCode).arg(errorString)),
+    Location(location), ErrorCode(errorCode), ErrorString(errorString)
 {
 }
 
-ctkCmdLineModuleRunException::ctkCmdLineModuleRunException(const QString& msg, const ctkCmdLineModuleRunException& cause)
-  : QtConcurrent::Exception(), ctkException(msg, cause)
+ctkCmdLineModuleRunException::ctkCmdLineModuleRunException(
+    const QString& location, int errorCode, const QString &errorString,
+    const ctkCmdLineModuleRunException& cause)
+  : QtConcurrent::Exception(), ctkException(location, cause),
+    Location(location), ErrorCode(errorCode), ErrorString(errorString)
 {
 }
 
 ctkCmdLineModuleRunException::ctkCmdLineModuleRunException(const ctkCmdLineModuleRunException& o)
-  : QtConcurrent::Exception(o), ctkException(o)
+  : QtConcurrent::Exception(o), ctkException(o),
+    Location(o.Location), ErrorCode(o.ErrorCode), ErrorString(o.ErrorString)
 {
 }
 
@@ -35,6 +42,21 @@ ctkCmdLineModuleRunException::~ctkCmdLineModuleRunException() throw()
 {
 }
 
+QString ctkCmdLineModuleRunException::location() const throw()
+{
+  return Location;
+}
+
+int ctkCmdLineModuleRunException::errorCode() const throw()
+{
+  return ErrorCode;
+}
+
+QString ctkCmdLineModuleRunException::errorString() const throw()
+{
+  return ErrorString;
+}
+
 const char* ctkCmdLineModuleRunException::name() const throw()
 {
   return "CTK CommandLineModule Run Exception";

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

@@ -17,23 +17,32 @@ See LICENSE.txt or http://www.mitk.org for details.
 #ifndef CTKCMDLINEMODULERUNEXCEPTION_H
 #define CTKCMDLINEMODULERUNEXCEPTION_H
 
+#include "ctkCommandLineModulesCoreExport.h"
+
 #include <ctkException.h>
 
 #include <QtCore>
 
-class ctkCmdLineModuleRunException : public QtConcurrent::Exception, public ctkException
+class CTK_CMDLINEMODULECORE_EXPORT ctkCmdLineModuleRunException
+    : public QtConcurrent::Exception, public ctkException
 {
 public:
 
-  explicit ctkCmdLineModuleRunException(const QString& msg);
+  explicit ctkCmdLineModuleRunException(const QString& location, int errorCode,
+                                        const QString& errorString);
 
-  ctkCmdLineModuleRunException(const QString& msg, const ctkCmdLineModuleRunException& cause);
+  ctkCmdLineModuleRunException(const QString& location, int errorCode,
+                               const QString& errorString, const ctkCmdLineModuleRunException& cause);
   ctkCmdLineModuleRunException(const ctkCmdLineModuleRunException& o);
 
   ctkCmdLineModuleRunException& operator=(const ctkCmdLineModuleRunException& o);
 
   ~ctkCmdLineModuleRunException() throw();
 
+  QString location() const throw();
+  int errorCode() const throw();
+  QString errorString() const throw();
+
   virtual const char* name() const throw();
   virtual const char* className() const throw();
   virtual ctkCmdLineModuleRunException* clone() const;
@@ -41,6 +50,12 @@ public:
 
   virtual void raise() const;
 
+private:
+
+  QString Location;
+  int ErrorCode;
+  QString ErrorString;
+
 };
 
 #endif // CTKCMDLINEMODULERUNEXCEPTION_H

+ 89 - 58
Libs/CommandLineModules/Core/ctkCmdLineModuleXmlProgressWatcher.cpp

@@ -36,15 +36,18 @@ class ctkCmdLineModuleXmlProgressWatcherPrivate
 public:
 
   ctkCmdLineModuleXmlProgressWatcherPrivate(QIODevice* input, ctkCmdLineModuleXmlProgressWatcher* qq)
-    : input(input), q(qq), error(false)
-  {}
+    : input(input), readPos(0), q(qq), error(false), currentProgress(0)
+  {
+    // wrap the content in an artifical root element
+    reader.addData("<module-root>");
+  }
 
   void _q_readyRead()
   {
-    QByteArray ba = input->readAll();
-    qDebug() << input->pos() << " [" << input->bytesAvailable() << "]:" << ba;
-    //reader.addData(ba);
-    //parseProgressXml();
+    input->seek(readPos);
+    reader.addData(input->readAll());
+    readPos = input->pos();
+    parseProgressXml();
   }
 
   void parseProgressXml()
@@ -55,92 +58,123 @@ public:
       switch(type)
       {
       case QXmlStreamReader::NoToken: break;
-      case QXmlStreamReader::StartElement:
+      case QXmlStreamReader::Characters:
       {
-        QStringRef name = reader.name();
-        if (name.compare(FILTER_START, Qt::CaseInsensitive) == 0)
-        {
-          stack.push_back(FILTER_START);
-          currentName.clear();
-          currentComment.clear();
-        }
-        else if (name.compare(FILTER_NAME, Qt::CaseInsensitive) == 0)
+        if (stack.empty()) break;
+
+        if (stack.size() == 2 && stack.front() == FILTER_START)
         {
-          if (stack.back() == FILTER_START || stack.back() == FILTER_END)
+          if (stack.back() == FILTER_NAME)
           {
-            currentName = reader.name().toString().trimmed();
+            currentName = reader.text().toString().trimmed();
           }
-        }
-        else if (name.compare(FILTER_COMMENT, Qt::CaseInsensitive) == 0)
-        {
-          if (stack.back() == FILTER_START)
+          else if (stack.back() == FILTER_COMMENT)
           {
-            currentComment = reader.name().toString().trimmed();
+            currentComment = reader.text().toString().trimmed();
           }
         }
-        else if (name.compare(FILTER_PROGRESS, Qt::CaseInsensitive) == 0)
+        else if (stack.size() == 1 && stack.back() == FILTER_PROGRESS)
+        {
+          currentProgress = reader.text().toString().toFloat();
+        }
+        break;
+      }
+      case QXmlStreamReader::StartElement:
+      {
+        QStringRef name = reader.name();
+        QString parent;
+        if (!stack.empty()) parent = stack.back();
+
+        if (name.compare("module-root") != 0)
         {
-          if (!stack.empty())
+          stack.push_back(name.toString());
+        }
+
+        if (name.compare(FILTER_START, Qt::CaseInsensitive) == 0 ||
+            name.compare(FILTER_PROGRESS, Qt::CaseInsensitive) == 0 ||
+            name.compare(FILTER_END, Qt::CaseInsensitive) == 0)
+        {
+          if (!parent.isEmpty())
+          {
+            unexpectedNestedElement(name.toString());
+            break;
+          }
+
+          if (name.compare(FILTER_START, Qt::CaseInsensitive) == 0)
           {
-            if (!error)
-            {
-              emit q->filterXmlError(QString("\"%1\" must be a top-level element, found at line %2.")
-                                     .arg(FILTER_PROGRESS).arg(reader.lineNumber()));
-            }
-            continue;
+            currentName.clear();
+            currentComment.clear();
+            currentProgress = 0;
           }
-          emit q->filterProgress(reader.text().toString().toFloat());
         }
-        type = reader.readNext();
         break;
       }
       case QXmlStreamReader::EndElement:
       {
         QStringRef name = reader.name();
-        if (name.compare(FILTER_START, Qt::CaseInsensitive) == 0)
+
+        QString curr;
+        QString parent;
+        if (!stack.empty())
         {
-          if (stack.back() != FILTER_START)
-          {
-            if (!error)
-            {
-              emit q->filterXmlError(QString("Unexpected end tag \"%1\" found at line %2.")
-                                     .arg(FILTER_END).arg(reader.lineNumber()));
-            }
-            continue;
-          }
+          curr = stack.back();
           stack.pop_back();
-          emit q->filterStarted(currentName, currentComment);
+          if (!stack.empty()) parent = stack.back();
         }
-        else if (name.compare(FILTER_END, Qt::CaseInsensitive) == 0)
+
+        if (parent.isEmpty())
         {
-          if (!stack.empty())
+          if (name.compare(FILTER_START, Qt::CaseInsensitive) == 0)
           {
-            if (!error)
-            {
-              emit q->filterXmlError(QString("\"%1\" must be a top-level element, found at line %2.")
-                                     .arg(FILTER_PROGRESS).arg(reader.lineNumber()));
-            }
-            continue;
+            emit q->filterStarted(currentName, currentComment);
+          }
+          else if (name.compare(FILTER_PROGRESS, Qt::CaseInsensitive) == 0)
+          {
+            emit q->filterProgress(currentProgress);
+          }
+          else if (name.compare(FILTER_END, Qt::CaseInsensitive) == 0)
+          {
+            emit q->filterFinished(currentName);
           }
-          stack.pop_back();
-          emit q->filterFinished(currentName);
         }
-        type = reader.readNext();
         break;
       }
       default:
-        type = reader.readNext();
+        break;
+      }
+      type = reader.readNext();
+    }
+
+    if (type == QXmlStreamReader::Invalid && reader.error() != QXmlStreamReader::PrematureEndOfDocumentError)
+    {
+      if (!error)
+      {
+        error = true;
+        emit q->filterXmlError(QString("Error parsing XML at line %1, column %2: ")
+                               .arg(reader.lineNumber()).arg(reader.columnNumber()) + reader.errorString());
       }
     }
   }
 
+  void unexpectedNestedElement(const QString& element)
+  {
+    if (!error)
+    {
+      error = true;
+      emit q->filterXmlError(QString("\"%1\" must be a top-level element, found at line %2.")
+                             .arg(element).arg(reader.lineNumber()));
+    }
+  }
+
   QIODevice* input;
+  qint64 readPos;
   ctkCmdLineModuleXmlProgressWatcher* q;
   bool error;
   QXmlStreamReader reader;
   QList<QString> stack;
   QString currentName;
   QString currentComment;
+  float currentProgress;
 };
 
 ctkCmdLineModuleXmlProgressWatcher::ctkCmdLineModuleXmlProgressWatcher(QIODevice* input)
@@ -153,9 +187,6 @@ ctkCmdLineModuleXmlProgressWatcher::ctkCmdLineModuleXmlProgressWatcher(QIODevice
     input->open(QIODevice::ReadOnly);
   }
   connect(d->input, SIGNAL(readyRead()), SLOT(_q_readyRead()));
-
-  // start parsing
-  d->_q_readyRead();
 }
 
 ctkCmdLineModuleXmlProgressWatcher::~ctkCmdLineModuleXmlProgressWatcher()