Browse Source

ENH: PluginFramework: Added plugin persistence and a simple PluginBrowser app.

- Plugin meta-data and resources are now persisted in a SQLite database
- Added a simple application which can be used to browse and inspect available plugins
Sascha Zelzer 15 years ago
parent
commit
01d60beb6a
37 changed files with 2645 additions and 320 deletions
  1. 50 0
      Applications/ctkPluginBrowser/CMakeLists.txt
  2. 90 0
      Applications/ctkPluginBrowser/ctkPluginBrowser.cxx
  3. 54 0
      Applications/ctkPluginBrowser/ctkPluginBrowser.h
  4. 55 0
      Applications/ctkPluginBrowser/ctkPluginBrowserMain.cxx
  5. 178 0
      Applications/ctkPluginBrowser/ctkPluginBrowserMainWindow.ui
  6. 265 0
      Applications/ctkPluginBrowser/ctkPluginResourcesTreeModel.cxx
  7. 59 0
      Applications/ctkPluginBrowser/ctkPluginResourcesTreeModel.h
  8. 116 0
      Applications/ctkPluginBrowser/ctkPluginTableModel.cxx
  9. 57 0
      Applications/ctkPluginBrowser/ctkPluginTableModel.h
  10. 258 0
      Applications/ctkPluginBrowser/ctkQtResourcesTreeModel.cxx
  11. 57 0
      Applications/ctkPluginBrowser/ctkQtResourcesTreeModel.h
  12. 9 0
      Applications/ctkPluginBrowser/target_libraries.cmake
  13. 1 0
      CMakeLists.txt
  14. 2 0
      Libs/Core/CMakeLists.txt
  15. 1 1
      Libs/Core/PluginFramework/ctkPlugin.cxx
  16. 4 4
      Libs/Core/PluginFramework/ctkPlugin.h
  17. 18 25
      Libs/Core/PluginFramework/ctkPluginArchive.cxx
  18. 21 17
      Libs/Core/PluginFramework/ctkPluginArchive_p.h
  19. 2 2
      Libs/Core/PluginFramework/ctkPluginConstants.cxx
  20. 1 1
      Libs/Core/PluginFramework/ctkPluginContext.cxx
  21. 2 1
      Libs/Core/PluginFramework/ctkPluginContext.h
  22. 757 0
      Libs/Core/PluginFramework/ctkPluginDatabase.cxx
  23. 85 0
      Libs/Core/PluginFramework/ctkPluginDatabaseException.cxx
  24. 71 0
      Libs/Core/PluginFramework/ctkPluginDatabaseException.h
  25. 209 0
      Libs/Core/PluginFramework/ctkPluginDatabase_p.h
  26. 3 10
      Libs/Core/PluginFramework/ctkPluginException.cxx
  27. 0 2
      Libs/Core/PluginFramework/ctkPluginException.h
  28. 38 0
      Libs/Core/PluginFramework/ctkPluginFramework.cxx
  29. 4 0
      Libs/Core/PluginFramework/ctkPluginFramework.h
  30. 1 1
      Libs/Core/PluginFramework/ctkPluginFrameworkContextPrivate.cxx
  31. 4 10
      Libs/Core/PluginFramework/ctkPluginManifest.cxx
  32. 2 2
      Libs/Core/PluginFramework/ctkPluginManifest_p.h
  33. 1 1
      Libs/Core/PluginFramework/ctkPluginPrivate.cxx
  34. 56 126
      Libs/Core/PluginFramework/ctkPluginStorage.cxx
  35. 91 97
      Libs/Core/PluginFramework/ctkPluginStorage_p.h
  36. 20 18
      Libs/Core/PluginFramework/ctkPlugins.cxx
  37. 3 2
      Libs/Core/PluginFramework/ctkPlugins_p.h

+ 50 - 0
Applications/ctkPluginBrowser/CMakeLists.txt

@@ -0,0 +1,50 @@
+PROJECT(ctkPluginBrowser)
+
+#
+# See CTK/CMake/ctkMacroBuildQtApp.cmake for details
+#
+
+SET(KIT_SRCS
+  ctkPluginBrowser.cxx
+  ctkPluginBrowserMain.cxx
+  ctkPluginTableModel.cxx
+  ctkPluginResourcesTreeModel.cxx
+  ctkQtResourcesTreeModel.cxx
+)
+
+# Headers that should run through moc
+SET(KIT_MOC_SRCS
+  ctkPluginBrowser.h
+  )
+
+# UI files
+SET(KIT_UI_FORMS
+  ctkPluginBrowserMainWindow.ui
+)
+
+# Resources
+SET(KIT_resources
+)
+
+# Target libraries - See CMake/ctkMacroGetTargetLibraries.cmake
+# The following macro will read the target libraries from the file 'target_libraries.cmake'
+ctkMacroGetTargetLibraries(KIT_target_libraries)
+
+# Additional directories to include - Not that CTK_INCLUDE_LIBRARIES is already included
+SET(KIT_include_directories
+  )
+
+ctkMacroBuildQtApp(
+  NAME ${PROJECT_NAME}
+  INCLUDE_DIRECTORIES ${KIT_include_directories}
+  SRCS ${KIT_SRCS}
+  MOC_SRCS ${KIT_MOC_SRCS}
+  UI_FORMS ${KIT_UI_FORMS}
+  TARGET_LIBRARIES ${KIT_target_libraries}
+  RESOURCES ${KIT_resources}
+  )
+
+# Testing
+IF(BUILD_TESTING)
+#   ADD_SUBDIRECTORY(Testing)
+ENDIF(BUILD_TESTING)

+ 90 - 0
Applications/ctkPluginBrowser/ctkPluginBrowser.cxx

@@ -0,0 +1,90 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) 2010 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 "ctkPluginBrowser.h"
+
+#include "ctkPluginTableModel.h"
+#include "ctkPluginResourcesTreeModel.h"
+#include "ctkQtResourcesTreeModel.h"
+
+#include <ui_ctkPluginBrowserMainWindow.h>
+
+#include <PluginFramework/ctkPluginException.h>
+#include <PluginFramework/ctkPluginFramework.h>
+
+#include <QApplication>
+#include <QMainWindow>
+#include <QStringList>
+#include <QDirIterator>
+#include <QUrl>
+
+namespace ctk {
+
+  PluginBrowser::PluginBrowser(PluginFramework* framework)
+    : framework(framework)
+  {
+
+    QStringList pluginDirs;
+    pluginDirs << qApp->applicationDirPath() + "/Plugins";
+
+    QDirIterator dirIter(pluginDirs.at(0), QDir::Files);
+    while(dirIter.hasNext())
+    {
+      try
+      {
+        framework->getPluginContext()->installPlugin(QUrl::fromLocalFile(dirIter.next()).toString());
+      }
+      catch (const PluginException& e)
+      {
+        qCritical() << e.what();
+      }
+    }
+
+    framework->start();
+
+    ui.setupUi(this);
+
+    QAbstractItemModel* pluginTableModel = new PluginTableModel(framework->getPluginContext(), this);
+    ui.pluginsTableView->setModel(pluginTableModel);
+
+    QAbstractItemModel* qtresourcesTreeModel = new QtResourcesTreeModel(this);
+    ui.qtResourcesTreeView->setModel(qtresourcesTreeModel);
+
+    connect(ui.pluginsTableView, SIGNAL(clicked(QModelIndex)), this, SLOT(pluginSelected(QModelIndex)));
+
+  }
+
+  void PluginBrowser::pluginSelected(const QModelIndex &index)
+  {
+    QVariant v = index.data(Qt::UserRole);
+    qDebug() << "Selected plugin:" << v;
+
+    Plugin* plugin = framework->getPluginContext()->getPlugin(v.toInt());
+
+    if (!plugin) return;
+
+    QAbstractItemModel* oldModel = ui.pluginResourcesTreeView->model();
+    ui.pluginResourcesTreeView->setModel(new PluginResourcesTreeModel(plugin, this));
+    if (oldModel) oldModel->deleteLater();;
+  }
+
+
+}

+ 54 - 0
Applications/ctkPluginBrowser/ctkPluginBrowser.h

@@ -0,0 +1,54 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) 2010 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 CTKPLUGINBROWSER_H
+#define CTKPLUGINBROWSER_H
+
+#include <QMainWindow>
+
+#include <ui_ctkPluginBrowserMainWindow.h>
+
+namespace ctk {
+
+  class PluginFramework;
+
+  class PluginBrowser : public QMainWindow
+  {
+    Q_OBJECT
+
+  public:
+
+    PluginBrowser(PluginFramework* framework);
+
+  private slots:
+
+    void pluginSelected(const QModelIndex& index);
+
+  private:
+
+    PluginFramework* framework;
+
+    Ui::PluginBrowserWindow ui;
+  };
+
+}
+
+#endif // CTKPLUGINBROWSER_H

+ 55 - 0
Applications/ctkPluginBrowser/ctkPluginBrowserMain.cxx

@@ -0,0 +1,55 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) 2010 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 <PluginFramework/ctkPluginFrameworkContext.h>
+#include <PluginFramework/ctkPluginFramework.h>
+#include <PluginFramework/ctkPluginException.h>
+
+#include "ctkPluginBrowser.h"
+
+#include <QApplication>
+
+using namespace ctk;
+
+int main(int argv, char** argc)
+{
+  QApplication app(argv, argc);
+
+  PluginFrameworkContext::Properties props;
+
+  PluginFrameworkContext fwContext(props);
+
+  PluginFramework* framework = fwContext.getFramework();
+  try {
+    framework->init();
+  }
+  catch (const PluginException& exc)
+  {
+    qCritical() << "Failed to initialize the plug-in framework:" << exc;
+    exit(1);
+  }
+
+  PluginBrowser browser(framework);
+  browser.show();
+
+  return app.exec();
+
+}

+ 178 - 0
Applications/ctkPluginBrowser/ctkPluginBrowserMainWindow.ui

@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PluginBrowserWindow</class>
+ <widget class="QMainWindow" name="PluginBrowserWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>800</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QVBoxLayout" name="verticalLayout">
+    <property name="spacing">
+     <number>0</number>
+    </property>
+    <property name="margin">
+     <number>0</number>
+    </property>
+    <item>
+     <widget class="QTabWidget" name="detailsTabWidget">
+      <property name="currentIndex">
+       <number>0</number>
+      </property>
+      <widget class="QWidget" name="tab">
+       <attribute name="title">
+        <string>MANIFEST.MF</string>
+       </attribute>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>800</width>
+     <height>23</height>
+    </rect>
+   </property>
+   <widget class="QMenu" name="menuFile">
+    <property name="title">
+     <string>File</string>
+    </property>
+    <addaction name="actionExit"/>
+   </widget>
+   <addaction name="menuFile"/>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+  <widget class="QDockWidget" name="dockWidget">
+   <property name="features">
+    <set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
+   </property>
+   <property name="windowTitle">
+    <string>Plugins</string>
+   </property>
+   <attribute name="dockWidgetArea">
+    <number>1</number>
+   </attribute>
+   <widget class="QWidget" name="dockWidgetContents">
+    <layout class="QVBoxLayout" name="verticalLayout_2">
+     <property name="spacing">
+      <number>0</number>
+     </property>
+     <property name="margin">
+      <number>0</number>
+     </property>
+     <item>
+      <widget class="QTableView" name="pluginsTableView">
+       <property name="selectionMode">
+        <enum>QAbstractItemView::SingleSelection</enum>
+       </property>
+       <property name="selectionBehavior">
+        <enum>QAbstractItemView::SelectRows</enum>
+       </property>
+       <property name="showGrid">
+        <bool>false</bool>
+       </property>
+       <property name="sortingEnabled">
+        <bool>true</bool>
+       </property>
+       <attribute name="horizontalHeaderShowSortIndicator" stdset="0">
+        <bool>true</bool>
+       </attribute>
+       <attribute name="horizontalHeaderStretchLastSection">
+        <bool>true</bool>
+       </attribute>
+       <attribute name="verticalHeaderVisible">
+        <bool>false</bool>
+       </attribute>
+       <attribute name="verticalHeaderVisible">
+        <bool>false</bool>
+       </attribute>
+       <attribute name="horizontalHeaderShowSortIndicator" stdset="0">
+        <bool>true</bool>
+       </attribute>
+       <attribute name="horizontalHeaderStretchLastSection">
+        <bool>true</bool>
+       </attribute>
+      </widget>
+     </item>
+    </layout>
+   </widget>
+  </widget>
+  <widget class="QDockWidget" name="dockWidget_2">
+   <property name="features">
+    <set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
+   </property>
+   <property name="windowTitle">
+    <string>Qt Resources</string>
+   </property>
+   <attribute name="dockWidgetArea">
+    <number>1</number>
+   </attribute>
+   <widget class="QWidget" name="dockWidgetContents_2">
+    <layout class="QVBoxLayout" name="verticalLayout_3">
+     <property name="spacing">
+      <number>0</number>
+     </property>
+     <property name="margin">
+      <number>0</number>
+     </property>
+     <item>
+      <widget class="QTreeView" name="qtResourcesTreeView">
+       <property name="headerHidden">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </widget>
+  </widget>
+  <widget class="QDockWidget" name="dockWidget_3">
+   <property name="features">
+    <set>QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable</set>
+   </property>
+   <property name="windowTitle">
+    <string>Plugin Resources</string>
+   </property>
+   <attribute name="dockWidgetArea">
+    <number>1</number>
+   </attribute>
+   <widget class="QWidget" name="dockWidgetContents_3">
+    <layout class="QVBoxLayout" name="verticalLayout_4">
+     <property name="spacing">
+      <number>0</number>
+     </property>
+     <property name="margin">
+      <number>0</number>
+     </property>
+     <item>
+      <widget class="QTreeView" name="pluginResourcesTreeView">
+       <property name="headerHidden">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </widget>
+  </widget>
+  <action name="actionExit">
+   <property name="text">
+    <string>Exit</string>
+   </property>
+   <property name="menuRole">
+    <enum>QAction::QuitRole</enum>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>

+ 265 - 0
Applications/ctkPluginBrowser/ctkPluginResourcesTreeModel.cxx

@@ -0,0 +1,265 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) 2010 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 "ctkPluginResourcesTreeModel.h"
+
+#include <PluginFramework/ctkPlugin.h>
+
+namespace ctk {
+
+  class PluginResourceTreeNode;
+
+  class PluginResourceTreeItem
+  {
+  public:
+
+    PluginResourceTreeItem(const QString& path, PluginResourceTreeNode* parent = 0);
+
+    virtual ~PluginResourceTreeItem();
+
+    virtual PluginResourceTreeItem* child(int row);
+
+    virtual int childCount() const;
+
+    int row();
+
+    QVariant data(int role) const;
+
+    PluginResourceTreeNode* parent() const;
+
+  protected:
+    const QString path;
+    PluginResourceTreeNode* parentNode;
+  };
+
+  class PluginResourceTreeNode : public PluginResourceTreeItem
+  {
+  public:
+
+    PluginResourceTreeNode(const Plugin* plugin, const QString& path, PluginResourceTreeNode* parent = 0);
+
+    ~PluginResourceTreeNode();
+
+    PluginResourceTreeItem* child(int row);
+
+    int childCount() const;
+
+    int indexOf(PluginResourceTreeItem* child) const;
+
+  private:
+
+    const Plugin* plugin;
+    QList<PluginResourceTreeItem*> children;
+
+  };
+
+
+  PluginResourceTreeItem::PluginResourceTreeItem(const QString& path,
+                                                 PluginResourceTreeNode* parent)
+    : path(path), parentNode(parent)
+  {
+
+  }
+
+  PluginResourceTreeItem::~PluginResourceTreeItem()
+  {
+
+  }
+
+  PluginResourceTreeItem* PluginResourceTreeItem::child(int row)
+  {
+    return 0;
+  }
+
+  int PluginResourceTreeItem::childCount() const
+  {
+    return 0;
+  }
+
+  int PluginResourceTreeItem::row()
+  {
+    if (parentNode)
+    {
+      return parentNode->indexOf(this);
+    }
+
+    return 0;
+  }
+
+  QVariant PluginResourceTreeItem::data(int role) const
+  {
+    if (role == Qt::DisplayRole)
+    {
+      QString p = path;
+      if (p.endsWith('/')) p = p.left(p.size()-1);
+
+      int i = p.lastIndexOf('/');
+
+      return p.mid(i+1);
+    }
+
+    return QVariant();
+  }
+
+  PluginResourceTreeNode* PluginResourceTreeItem::parent() const
+  {
+    return parentNode;
+  }
+
+
+  PluginResourceTreeNode::PluginResourceTreeNode(const Plugin* plugin, const QString& path, PluginResourceTreeNode* parent)
+    : PluginResourceTreeItem(path, parent), plugin(plugin)
+  {
+    QStringList pathEntries = plugin->getResourceList(path);
+    QStringListIterator it(pathEntries);
+    while (it.hasNext())
+    {
+      const QString& entry = it.next();
+      if (entry.endsWith('/'))
+      {
+        children.push_back(new PluginResourceTreeNode(plugin, entry, this));
+      }
+      else
+      {
+        children.push_back(new PluginResourceTreeItem(entry, this));
+      }
+    }
+  }
+
+  PluginResourceTreeNode::~PluginResourceTreeNode()
+  {
+    qDeleteAll(children);
+  }
+
+  PluginResourceTreeItem* PluginResourceTreeNode::child(int row)
+  {
+    return children.value(row);
+  }
+
+  int PluginResourceTreeNode::childCount() const
+  {
+    return children.size();
+  }
+
+  int PluginResourceTreeNode::indexOf(PluginResourceTreeItem* child) const
+  {
+    return children.indexOf(child);
+  }
+
+
+  PluginResourcesTreeModel::PluginResourcesTreeModel(const Plugin* plugin, QObject* parent)
+    : QAbstractItemModel(parent), plugin(plugin)
+  {
+    rootItem = new PluginResourceTreeNode(plugin, "/");
+  }
+
+  PluginResourcesTreeModel::~PluginResourcesTreeModel()
+  {
+    delete rootItem;
+  }
+
+  QVariant PluginResourcesTreeModel::data(const QModelIndex &index, int role) const
+  {
+    if (!index.isValid())
+      return QVariant();
+
+    if (role == Qt::DisplayRole)
+    {
+      PluginResourceTreeItem* item = static_cast<PluginResourceTreeItem*>(index.internalPointer());
+      return item->data(role);
+    }
+
+    return QVariant();
+  }
+
+  Qt::ItemFlags PluginResourcesTreeModel::flags(const QModelIndex &index) const
+  {
+    if (!index.isValid())
+      return 0;
+
+    return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+  }
+
+  QVariant PluginResourcesTreeModel::headerData(int section, Qt::Orientation orientation,
+                     int role) const
+  {
+    return QVariant();
+  }
+
+  QModelIndex PluginResourcesTreeModel::index(int row, int column,
+                   const QModelIndex &parent) const
+  {
+    if (!hasIndex(row, column, parent))
+      return QModelIndex();
+
+    PluginResourceTreeItem* parentItem;
+
+    if (!parent.isValid())
+      parentItem = rootItem;
+    else
+      parentItem = static_cast<PluginResourceTreeItem*>(parent.internalPointer());
+
+    PluginResourceTreeItem* childItem = parentItem->child(row);
+
+    if (childItem)
+      return createIndex(row, column, childItem);
+    else
+      return QModelIndex();
+  }
+
+  QModelIndex PluginResourcesTreeModel::parent(const QModelIndex &index) const
+  {
+    if (!index.isValid())
+      return QModelIndex();
+
+    PluginResourceTreeItem* childItem = static_cast<PluginResourceTreeItem*>(index.internalPointer());
+    PluginResourceTreeItem* parentItem = childItem->parent();
+
+    if (parentItem == rootItem)
+      return QModelIndex();
+
+    return createIndex(parentItem->row(), 0, parentItem);
+  }
+
+  int PluginResourcesTreeModel::rowCount(const QModelIndex &parent) const
+  {
+    if (parent.column() > 0) return 0;
+
+    PluginResourceTreeItem* parentItem;
+    if (!parent.isValid())
+    {
+      parentItem = rootItem;
+    }
+    else
+    {
+      parentItem = static_cast<PluginResourceTreeItem*>(parent.internalPointer());
+    }
+
+    return parentItem->childCount();
+  }
+
+  int PluginResourcesTreeModel::columnCount(const QModelIndex &parent) const
+  {
+    return 1;
+  }
+
+}
+

+ 59 - 0
Applications/ctkPluginBrowser/ctkPluginResourcesTreeModel.h

@@ -0,0 +1,59 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) 2010 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 CTKPLUGINRESOURCESTREEMODEL_H
+#define CTKPLUGINRESOURCESTREEMODEL_H
+
+#include <QAbstractItemModel>
+#include <QDir>
+
+namespace ctk {
+
+  class Plugin;
+  class PluginResourceTreeItem;
+
+  class PluginResourcesTreeModel : public QAbstractItemModel
+  {
+  public:
+
+    PluginResourcesTreeModel(const Plugin* plugin, QObject* parent = 0);
+    ~PluginResourcesTreeModel();
+
+    QVariant data(const QModelIndex &index, int role) const;
+    Qt::ItemFlags flags(const QModelIndex &index) const;
+    QVariant headerData(int section, Qt::Orientation orientation,
+                       int role = Qt::DisplayRole) const;
+    QModelIndex index(int row, int column,
+                     const QModelIndex &parent = QModelIndex()) const;
+    QModelIndex parent(const QModelIndex &index) const;
+    int rowCount(const QModelIndex &parent = QModelIndex()) const;
+    int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+  private:
+
+    const Plugin* plugin;
+    PluginResourceTreeItem* rootItem;
+
+  };
+
+}
+
+#endif // CTKPLUGINRESOURCESTREEMODEL_H

+ 116 - 0
Applications/ctkPluginBrowser/ctkPluginTableModel.cxx

@@ -0,0 +1,116 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) 2010 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 "ctkPluginTableModel.h"
+
+#include <PluginFramework/ctkPlugin.h>
+#include <PluginFramework/ctkPluginContext.h>
+
+namespace ctk {
+
+  PluginTableModel::PluginTableModel(PluginContext* pc, QObject* parent)
+    : QAbstractTableModel(parent)
+  {
+    plugins = pc->getPlugins();
+  }
+
+  QVariant PluginTableModel::data(const QModelIndex& index, int role) const
+  {
+    if (!index.isValid()) return QVariant();
+
+    Plugin* plugin = plugins.at(index.row());
+    if (role == Qt::DisplayRole)
+    {
+      int col = index.column();
+      if (col == 0)
+      {
+        return QVariant(plugin->getSymbolicName());
+      }
+      else if (col == 1)
+      {
+        return QVariant(plugin->getVersion().toString());
+      }
+      else if (col == 2)
+      {
+        return QVariant(getStringForState(plugin->getState()));
+      }
+    }
+    else if (role == Qt::UserRole)
+    {
+      return plugin->getPluginId();
+    }
+
+    return QVariant();
+  }
+
+  QVariant PluginTableModel::headerData(int section, Qt::Orientation orientation, int role) const
+  {
+    if (role == Qt::DisplayRole && orientation == Qt::Horizontal)
+    {
+      if (section == 0)
+      {
+        return QVariant("Plugin");
+      }
+      else if (section == 1)
+      {
+        return QVariant("Version");
+      }
+      else if (section == 2)
+      {
+        return QVariant("State");
+      }
+    }
+
+    return QVariant();
+  }
+
+  int PluginTableModel::columnCount(const QModelIndex& parent) const
+  {
+    return 3;
+  }
+
+  int PluginTableModel::rowCount(const QModelIndex& parent) const
+  {
+    return plugins.size();
+  }
+
+  QString PluginTableModel::getStringForState(const Plugin::State state) const
+  {
+    static const QString uninstalled("UNINSTALLED");
+    static const QString installed("INSTALLED");
+    static const QString resolved("RESOLVED");
+    static const QString starting("STARTING");
+    static const QString stopping("STOPPING");
+    static const QString active("ACTIVE");
+
+    switch(state)
+    {
+    case Plugin::UNINSTALLED: return uninstalled;
+    case Plugin::INSTALLED: return installed;
+    case Plugin::RESOLVED: return resolved;
+    case Plugin::STARTING: return starting;
+    case Plugin::STOPPING: return stopping;
+    case Plugin::ACTIVE: return active;
+    default: return QString("unknown");
+    }
+  }
+
+}

+ 57 - 0
Applications/ctkPluginBrowser/ctkPluginTableModel.h

@@ -0,0 +1,57 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) 2010 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 CTKPLUGINTABLEMODEL_H
+#define CTKPLUGINTABLEMODEL_H
+
+#include <QAbstractTableModel>
+
+#include <QList>
+
+#include <PluginFramework/ctkPlugin.h>
+
+namespace ctk {
+
+  class PluginContext;
+
+  class PluginTableModel : public QAbstractTableModel
+  {
+  public:
+
+    PluginTableModel(PluginContext* pc, QObject* parent = 0);
+
+    QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
+
+    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+
+    int columnCount(const QModelIndex& parent = QModelIndex()) const;
+    int rowCount(const QModelIndex& parent = QModelIndex()) const;
+
+  private:
+
+    QString getStringForState(const Plugin::State state) const;
+
+    QList<Plugin*> plugins;
+  };
+
+}
+
+#endif // CTKPLUGINTABLEMODEL_H

+ 258 - 0
Applications/ctkPluginBrowser/ctkQtResourcesTreeModel.cxx

@@ -0,0 +1,258 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) 2010 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 "ctkQtResourcesTreeModel.h"
+
+
+namespace ctk {
+
+  class QtResourceTreeNode;
+
+  class QtResourceTreeItem
+  {
+  public:
+
+    QtResourceTreeItem(const QFileInfo& fileInfo, QtResourceTreeNode* parent = 0);
+
+    virtual ~QtResourceTreeItem();
+
+    virtual QtResourceTreeItem* child(int row);
+
+    virtual int childCount() const;
+
+    int row();
+
+    QVariant data(int role) const;
+
+    QtResourceTreeNode* parent() const;
+
+  protected:
+    QFileInfo entry;
+    QtResourceTreeNode* parentNode;
+  };
+
+  class QtResourceTreeNode : public QtResourceTreeItem
+  {
+  public:
+
+    QtResourceTreeNode(const QFileInfo& dirInfo, QtResourceTreeNode* parent = 0);
+
+    ~QtResourceTreeNode();
+
+    QtResourceTreeItem* child(int row);
+
+    int childCount() const;
+
+    int indexOf(QtResourceTreeItem* child) const;
+
+  private:
+
+    QList<QtResourceTreeItem*> children;
+
+  };
+
+
+  QtResourceTreeItem::QtResourceTreeItem(const QFileInfo& fileInfo, QtResourceTreeNode* parent)
+    : entry(fileInfo), parentNode(parent)
+  {
+
+  }
+
+  QtResourceTreeItem::~QtResourceTreeItem()
+  {
+
+  }
+
+  QtResourceTreeItem* QtResourceTreeItem::child(int row)
+  {
+    return 0;
+  }
+
+  int QtResourceTreeItem::childCount() const
+  {
+    return 0;
+  }
+
+  int QtResourceTreeItem::row()
+  {
+    if (parentNode)
+    {
+      return parentNode->indexOf(this);
+    }
+
+    return 0;
+  }
+
+  QVariant QtResourceTreeItem::data(int role) const
+  {
+    if (role == Qt::DisplayRole)
+    {
+      if (entry.isFile())
+        return entry.fileName();
+
+      QString lastDir = entry.absoluteFilePath();
+      int i = lastDir.lastIndexOf('/');
+
+      if (i == lastDir.size()-1) return lastDir;
+      return lastDir.mid(i+1);
+    }
+
+    return QVariant();
+  }
+
+  QtResourceTreeNode* QtResourceTreeItem::parent() const
+  {
+    return parentNode;
+  }
+
+
+  QtResourceTreeNode::QtResourceTreeNode(const QFileInfo& dirInfo, QtResourceTreeNode* parent)
+    : QtResourceTreeItem(dirInfo, parent)
+  {
+    QFileInfoList infoList = QDir(dirInfo.absoluteFilePath()).entryInfoList();
+    QListIterator<QFileInfo> it(infoList);
+    while (it.hasNext())
+    {
+      const QFileInfo& info = it.next();
+      if (info.isFile())
+      {
+        children.push_back(new QtResourceTreeItem(info, this));
+      }
+      else
+      {
+        children.push_back(new QtResourceTreeNode(info, this));
+      }
+    }
+  }
+
+  QtResourceTreeNode::~QtResourceTreeNode()
+  {
+    qDeleteAll(children);
+  }
+
+  QtResourceTreeItem* QtResourceTreeNode::child(int row)
+  {
+    return children.value(row);
+  }
+
+  int QtResourceTreeNode::childCount() const
+  {
+    return children.size();
+  }
+
+  int QtResourceTreeNode::indexOf(QtResourceTreeItem* child) const
+  {
+    return children.indexOf(child);
+  }
+
+
+  QtResourcesTreeModel::QtResourcesTreeModel(QObject* parent)
+    : QAbstractItemModel(parent)
+  {
+    rootItem = new QtResourceTreeNode(QFileInfo(":/"));
+  }
+
+  QtResourcesTreeModel::~QtResourcesTreeModel()
+  {
+    delete rootItem;
+  }
+
+  QVariant QtResourcesTreeModel::data(const QModelIndex &index, int role) const
+  {
+    if (!index.isValid())
+      return QVariant();
+
+    if (role == Qt::DisplayRole)
+    {
+      QtResourceTreeItem* item = static_cast<QtResourceTreeItem*>(index.internalPointer());
+      return item->data(role);
+    }
+
+    return QVariant();
+  }
+
+  Qt::ItemFlags QtResourcesTreeModel::flags(const QModelIndex &index) const
+  {
+    if (!index.isValid())
+      return 0;
+
+    return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+  }
+
+  QVariant QtResourcesTreeModel::headerData(int section, Qt::Orientation orientation,
+                     int role) const
+  {
+    return QVariant();
+  }
+
+  QModelIndex QtResourcesTreeModel::index(int row, int column,
+                   const QModelIndex &parent) const
+  {
+    if (!hasIndex(row, column, parent))
+      return QModelIndex();
+
+    if (!parent.isValid())
+      return createIndex(row, column, rootItem);
+
+    QtResourceTreeItem* parentItem = static_cast<QtResourceTreeItem*>(parent.internalPointer());
+    QtResourceTreeItem* childItem = parentItem->child(row);
+
+    if (childItem)
+      return createIndex(row, column, childItem);
+    else
+      return QModelIndex();
+  }
+
+  QModelIndex QtResourcesTreeModel::parent(const QModelIndex &index) const
+  {
+    if (!index.isValid())
+      return QModelIndex();
+
+    QtResourceTreeItem* childItem = static_cast<QtResourceTreeItem*>(index.internalPointer());
+    QtResourceTreeItem* parentItem = childItem->parent();
+
+    if (parentItem)
+      return createIndex(parentItem->row(), 0, parentItem);
+
+    return QModelIndex();
+  }
+
+  int QtResourcesTreeModel::rowCount(const QModelIndex &parent) const
+  {
+    if (parent.column() > 0) return 0;
+
+    if (!parent.isValid())
+    {
+      return 1;
+    }
+    else
+    {
+      QtResourceTreeItem* parentItem = static_cast<QtResourceTreeItem*>(parent.internalPointer());
+      return parentItem->childCount();
+    }
+  }
+
+  int QtResourcesTreeModel::columnCount(const QModelIndex &parent) const
+  {
+    return 1;
+  }
+
+}

+ 57 - 0
Applications/ctkPluginBrowser/ctkQtResourcesTreeModel.h

@@ -0,0 +1,57 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) 2010 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 CTKQTRESOURCESTREEMODEL_H
+#define CTKQTRESOURCESTREEMODEL_H
+
+#include <QAbstractItemModel>
+#include <QDir>
+
+namespace ctk {
+
+  class QtResourceTreeItem;
+
+  class QtResourcesTreeModel : public QAbstractItemModel
+  {
+  public:
+
+    QtResourcesTreeModel(QObject* parent = 0);
+    ~QtResourcesTreeModel();
+
+    QVariant data(const QModelIndex &index, int role) const;
+    Qt::ItemFlags flags(const QModelIndex &index) const;
+    QVariant headerData(int section, Qt::Orientation orientation,
+                       int role = Qt::DisplayRole) const;
+    QModelIndex index(int row, int column,
+                     const QModelIndex &parent = QModelIndex()) const;
+    QModelIndex parent(const QModelIndex &index) const;
+    int rowCount(const QModelIndex &parent = QModelIndex()) const;
+    int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+  private:
+
+    QtResourceTreeItem* rootItem;
+
+  };
+
+}
+
+#endif // CTKQTRESOURCESTREEMODEL_H

+ 9 - 0
Applications/ctkPluginBrowser/target_libraries.cmake

@@ -0,0 +1,9 @@
+#
+# See CMake/ctkMacroGetTargetLibraries.cmake
+# 
+# This file should list the libraries required to build the current CTK application.
+# 
+
+SET(target_libraries
+  CTKCore
+  )

+ 1 - 0
CMakeLists.txt

@@ -147,6 +147,7 @@ SET(CTK_PLUGINS
 #
 SET(CTK_APPLICATIONS
   ctkCLIPluginExplorer
+  ctkPluginBrowser
   ctkDICOM
   ctkDICOMIndexer
   )

+ 2 - 0
Libs/Core/CMakeLists.txt

@@ -53,6 +53,8 @@ SET(KIT_SRCS
   PluginFramework/ctkPluginArchive.cxx
   PluginFramework/ctkPluginConstants.cxx
   PluginFramework/ctkPluginContext.cxx
+  PluginFramework/ctkPluginDatabase.cxx
+  PluginFramework/ctkPluginDatabaseException.cxx
   PluginFramework/ctkPluginException.cxx
   PluginFramework/ctkPluginFramework.cxx
   PluginFramework/ctkPluginFrameworkContext.cxx

+ 1 - 1
Libs/Core/PluginFramework/ctkPlugin.cxx

@@ -94,7 +94,7 @@ namespace ctk {
     return d->archive->findResourcesPath(path);
   }
 
-  QString Plugin::getResource(const QString& path) const
+  QByteArray Plugin::getResource(const QString& path) const
   {
     Q_D(const Plugin);
     return d->archive->getPluginResource(path);

+ 4 - 4
Libs/Core/PluginFramework/ctkPlugin.h

@@ -72,7 +72,7 @@ namespace ctk {
     int getPluginId() const;
 
     /**
-     * Returns this bundle's location identifier.
+     * Returns this plugin's location identifier.
      *
      * <p>
      * The location identifier is the location passed to
@@ -111,10 +111,10 @@ namespace ctk {
      * @throws std::logic_error If this plugin has been
      *         uninstalled.
      */
-    QStringList getResourceList(const QString& path) const;
+    virtual QStringList getResourceList(const QString& path) const;
 
     /**
-     * Returns a Qt resource string to the resource at the
+     * Returns a QByteArray containing a Qt resource located at the
      * specified path in this plugin.
      * <p>
      * The specified path is always relative to the root of this plugin
@@ -129,7 +129,7 @@ namespace ctk {
      * @throws std::logic_error If this plugin has been
      *         uninstalled.
      */
-    QString getResource(const QString& path) const;
+    virtual QByteArray getResource(const QString& path) const;
 
     Version getVersion() const;
 

+ 18 - 25
Libs/Core/PluginFramework/ctkPluginArchive.cxx

@@ -33,18 +33,19 @@ namespace ctk {
   const QString PluginArchive::AUTOSTART_SETTING_EAGER("eager");
   const QString PluginArchive::AUTOSTART_SETTING_ACTIVATION_POLICY("activation_policy");
 
-  PluginArchive::PluginArchive(PluginStorage* pluginStorage, QIODevice* is,
-                const QString& pluginLocation, int pluginId, const QString& resourcePrefix)
+  PluginArchive::PluginArchive(PluginStorage* pluginStorage,
+                const QUrl& pluginLocation, const QString& localPluginPath,
+                int pluginId)
                   : autostartSetting(-1), id(pluginId), lastModified(0),
-                  location(pluginLocation), resourcePrefix(resourcePrefix), storage(pluginStorage)
+                  location(pluginLocation), localPluginPath(localPluginPath),
+                  storage(pluginStorage)
   {
-    QString manifestResource = this->getPluginResource("META-INF/MANIFEST.MF");
-    if (manifestResource.isNull())
+    QByteArray manifestResource = this->getPluginResource("META-INF/MANIFEST.MF");
+    if (manifestResource.isEmpty())
     {
-      throw PluginException(QString("Plugin has no MANIFEST.MF resource, location=") + pluginLocation);
+      throw PluginException(QString("Plugin has no MANIFEST.MF resource, location=") + pluginLocation.toString());
     }
-    QFile manifestFile(manifestResource);
-    manifest.read(&manifestFile);
+    manifest.read(manifestResource);
   }
 
   QString PluginArchive::getAttribute(const QString& key) const
@@ -69,27 +70,24 @@ namespace ctk {
     return id;
   }
 
-  QString PluginArchive::getPluginLocation() const
+  QUrl PluginArchive::getPluginLocation() const
   {
     return location;
   }
 
-  QString PluginArchive::getPluginResource(const QString& component) const
+  QString PluginArchive::getLibLocation() const
   {
-    QString resourcePath = QString(":/") + resourcePrefix;
-    if (component.startsWith('/')) resourcePath += component;
-    else resourcePath += QString("/") + component;
+    return localPluginPath;
+  }
 
-    return storage->getPluginResource(resourcePath);
+  QByteArray PluginArchive::getPluginResource(const QString& component) const
+  {
+    return storage->getPluginResource(getPluginId(), component);
   }
 
   QStringList PluginArchive::findResourcesPath(const QString& path) const
   {
-    QString resourcePath = QString(":/") + resourcePrefix;
-    if (path.startsWith('/')) resourcePath += path;
-    else resourcePath += QString("/") + path;
-
-    return storage->findResourcesPath(resourcePath);
+    return storage->findResourcesPath(getPluginId(), path);
   }
 
   int PluginArchive::getStartLevel() const
@@ -137,12 +135,7 @@ namespace ctk {
 
   void PluginArchive::purge()
   {
-    //TODO
-  }
-
-  void PluginArchive::close()
-  {
-    //TODO
+    storage->removeArchive(this);
   }
 
 

+ 21 - 17
Libs/Core/PluginFramework/ctkPluginArchive_p.h

@@ -24,6 +24,7 @@
 
 #include <QString>
 #include <QHash>
+#include <QUrl>
 
 #include "ctkPluginManifest_p.h"
 
@@ -68,8 +69,8 @@ private:
   int autostartSetting;
   int id;
   qtimestamp lastModified;
-  QString location;
-  QString resourcePrefix;
+  QUrl location;
+  QString localPluginPath;
   PluginManifest manifest;
   PluginStorage* storage;
 
@@ -79,9 +80,8 @@ public:
    * Construct new plugin archive.
    *
    */
-  PluginArchive(PluginStorage* pluginStorage, QIODevice* is,
-                const QString& pluginLocation, int pluginId,
-                const QString& resourcePrefix);
+  PluginArchive(PluginStorage* pluginStorage, const QUrl& pluginLocation,
+                const QString& localPluginPath, int pluginId);
 
   /**
    * Get an attribute from the manifest of a plugin.
@@ -118,20 +118,29 @@ public:
 
 
   /**
-   * Get bundle location for this plugin archive.
+   * Get plugin location for this plugin archive.
    *
    * @return Bundle location.
    */
-   QString getPluginLocation() const;
+   QUrl getPluginLocation() const;
+
+   /**
+    * Get the path to the plugin library on the local
+    * file system
+    *
+    * @return Path to the plugin library
+    */
+   QString getLibLocation() const;
 
 
   /**
-   * Get a Qt resource string to a named resource inside a plugin.
+   * Get a Qt resource as a byte array from a plugin. The resource
+   * is cached and may be aquired even if the plugin is not active.
    *
-   * @param component Entry to get reference to.
-   * @return QString to the entry or null if it doesn't exist.
+   * @param component Resource to get the byte array from.
+   * @return QByteArray to the entry (empty if it doesn't exist).
    */
-   QString getPluginResource(const QString& component) const;
+   QByteArray getPluginResource(const QString& component) const;
 
 
   /**
@@ -203,15 +212,10 @@ public:
 
   /**
    * Remove plugin from persistent storage.
+   * This will delete the current PluginArchive instance.
    */
    void purge();
 
-
-  /**
-   * Close archive and all its open files.
-   */
-   void close();
-
 };
 
 }

+ 2 - 2
Libs/Core/PluginFramework/ctkPluginConstants.cxx

@@ -23,8 +23,8 @@
 
 namespace ctk {
 
-  const QString	PluginConstants::SYSTEM_PLUGIN_LOCATION = "System Bundle";
-  const QString	PluginConstants::SYSTEM_PLUGIN_SYMBOLICNAME = "system.bundle";
+  const QString	PluginConstants::SYSTEM_PLUGIN_LOCATION = "System Plugin";
+  const QString	PluginConstants::SYSTEM_PLUGIN_SYMBOLICNAME = "system.plugin";
 
   const QString PluginConstants::FRAMEWORK_STORAGE = "org.commontk.pluginfw.storage";
 

+ 1 - 1
Libs/Core/PluginFramework/ctkPluginContext.cxx

@@ -76,7 +76,7 @@ namespace ctk {
     return d->plugin->fwCtx->plugins->getPlugins();
   }
 
-  Plugin* PluginContext::installPlugin(const QString& location, QIODevice* in)
+  Plugin* PluginContext::installPlugin(const QUrl& location, QIODevice* in)
   {
     Q_D(PluginContext);
     d->isPluginContextValid();

+ 2 - 1
Libs/Core/PluginFramework/ctkPluginContext.h

@@ -25,6 +25,7 @@
 #include <QHash>
 #include <QString>
 #include <QVariant>
+#include <QUrl>
 
 #include "CTKCoreExport.h"
 
@@ -60,7 +61,7 @@ namespace ctk {
 
     QObject* getService(const ServiceReference& reference);
 
-    Plugin* installPlugin(const QString& location, QIODevice* in = 0);
+    Plugin* installPlugin(const QUrl& location, QIODevice* in = 0);
 
   protected:
 

+ 757 - 0
Libs/Core/PluginFramework/ctkPluginDatabase.cxx

@@ -0,0 +1,757 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) 2010 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 "ctkPluginDatabase_p.h"
+#include "ctkPluginDatabaseException.h"
+#include "ctkPlugin.h"
+#include "ctkPluginException.h"
+#include "ctkPluginArchive_p.h"
+#include "ctkPluginStorage_p.h"
+
+#include <QApplication>
+#include <QFileInfo>
+#include <QUrl>
+
+#include <QDebug>
+
+//database name
+#define PLUGINDATABASE "pluginfw.db"
+
+//database table names
+#define PLUGINS_TABLE "Plugins"
+#define PLUGIN_RESOURCES_TABLE "PluginResources"
+
+//separator
+#define PLUGINDATABASE_PATH_SEPARATOR "//"
+
+
+namespace ctk {
+
+  enum TBindIndexes
+  {
+    EBindIndex=0,
+    EBindIndex1,
+    EBindIndex2,
+    EBindIndex3,
+    EBindIndex4,
+    EBindIndex5,
+    EBindIndex6,
+    EBindIndex7
+  };
+
+
+
+PluginDatabase::PluginDatabase(PluginStorage* storage)
+:m_isDatabaseOpen(false), m_inTransaction(false), m_PluginStorage(storage)
+{
+}
+
+PluginDatabase::~PluginDatabase()
+{
+    close();
+}
+
+void PluginDatabase::open()
+{
+  if (m_isDatabaseOpen)
+    return;
+
+  QString path;
+
+  //Create full path to database
+  if(m_databasePath.isEmpty ())
+      m_databasePath = getDatabasePath();
+
+  path = m_databasePath;
+  QFileInfo dbFileInfo(path);
+  if (!dbFileInfo.dir().exists())
+  {
+    if(!QDir::root().mkpath(dbFileInfo.path()))
+    {
+      close();
+      QString errorText("Could not create database directory: %1");
+      throw PluginDatabaseException(errorText.arg(dbFileInfo.path()), PluginDatabaseException::DB_CREATE_DIR_ERROR);
+    }
+  }
+
+  m_connectionName = dbFileInfo.completeBaseName();
+  QSqlDatabase database;
+  if (QSqlDatabase::contains(m_connectionName))
+  {
+    database = QSqlDatabase::database(m_connectionName);
+  }
+  else
+  {
+    database = QSqlDatabase::addDatabase("QSQLITE", m_connectionName);
+    database.setDatabaseName(path);
+  }
+
+  if (!database.isValid())
+  {
+    close();
+    throw PluginDatabaseException(QString("Invalid database connection: %1").arg(m_connectionName),
+                                  PluginDatabaseException::DB_CONNECTION_INVALID);
+  }
+
+  //Create or open database
+  if (!database.isOpen())
+  {
+    if (!database.open())
+    {
+      close();
+      throw PluginDatabaseException(QString("Could not open database. ") + database.lastError().text(),
+                                    PluginDatabaseException::DB_SQL_ERROR);
+    }
+  }
+  m_isDatabaseOpen = true;
+
+  //Check if the sqlite version supports foreign key constraints
+  QSqlQuery query(database);
+  if (!query.exec("PRAGMA foreign_keys"))
+  {
+    close();
+    throw PluginDatabaseException(QString("Check for foreign key support failed."),
+                                  PluginDatabaseException::DB_SQL_ERROR);
+  }
+
+  if (!query.next())
+  {
+    close();
+    throw PluginDatabaseException(QString("SQLite db does not support foreign keys. It is either older than 3.6.19 or was compiled with SQLITE_OMIT_FOREIGN_KEY or SQLITE_OMIT_TRIGGER"),
+                                  PluginDatabaseException::DB_SQL_ERROR);
+  }
+  query.finish();
+  query.clear();
+
+  //Enable foreign key support
+  if (!query.exec("PRAGMA foreign_keys = ON"))
+  {
+    close();
+    throw PluginDatabaseException(QString("Enabling foreign key support failed."),
+                                  PluginDatabaseException::DB_SQL_ERROR);
+  }
+  query.finish();
+
+
+  //Check database structure (tables) and recreate tables if neccessary
+  //If one of the tables is missing remove all tables and recreate them
+  //This operation is required in order to avoid data coruption
+  if (!checkTables())
+  {
+    if (dropTables())
+    {
+      createTables();
+    }
+    else
+    {
+      //dropTables() should've handled error message
+      //and warning
+      close();
+    }
+  }
+
+  //Update database based on the recorded timestamps
+  updateDB();
+}
+
+void PluginDatabase::updateDB()
+{
+  checkConnection();
+
+  QSqlDatabase database = QSqlDatabase::database(m_connectionName);
+  QSqlQuery query(database);
+
+  beginTransaction(&query, Write);
+
+  QString statement = "SELECT ID, Location, LocalPath, Timestamp FROM Plugins WHERE State != ?";
+  QList<QVariant> bindValues;
+  bindValues.append(Plugin::UNINSTALLED);
+
+  QList<qlonglong> outdatedIds;
+  QList<QPair<QString,QString> > outdatedPlugins;
+  try
+  {
+    executeQuery(&query, statement, bindValues);
+
+    while (query.next())
+    {
+      QFileInfo pluginInfo(query.value(EBindIndex2).toString());
+      if (pluginInfo.lastModified() > QDateTime::fromString(query.value(EBindIndex3).toString(), Qt::ISODate))
+      {
+        outdatedIds.append(query.value(EBindIndex).toLongLong());
+        outdatedPlugins.append(qMakePair(query.value(EBindIndex1).toString(), query.value(EBindIndex2).toString()));
+      }
+    }
+  }
+  catch (...)
+  {
+    rollbackTransaction(&query);
+    throw;
+  }
+
+  query.finish();
+  query.clear();
+
+  try
+  {
+    statement = "DELETE FROM Plugins WHERE ID=?";
+    QListIterator<qlonglong> idIter(outdatedIds);
+    while (idIter.hasNext())
+    {
+      bindValues.clear();
+      bindValues.append(idIter.next());
+      executeQuery(&query, statement, bindValues);
+    }
+  }
+  catch (...)
+  {
+    rollbackTransaction(&query);
+    throw;
+  }
+
+  commitTransaction(&query);
+
+  QListIterator<QPair<QString,QString> > locationIter(outdatedPlugins);
+  while (locationIter.hasNext())
+  {
+    const QPair<QString,QString>& locations = locationIter.next();
+    insertPlugin(QUrl(locations.first), locations.second, false);
+  }
+}
+
+
+PluginArchive* PluginDatabase::insertPlugin(const QUrl& location, const QString& localPath, bool createArchive)
+{
+  checkConnection();
+
+  // Assemble the data for the sql record
+  QFileInfo fileInfo(localPath);
+  const QString lastModified = fileInfo.lastModified().toString(Qt::ISODate);
+
+  QString resourcePrefix = fileInfo.baseName();
+  if (resourcePrefix.startsWith("lib"))
+  {
+    resourcePrefix = resourcePrefix.mid(3);
+  }
+  resourcePrefix.replace("_", ".");
+
+  resourcePrefix = QString(":/") + resourcePrefix + "/";
+
+  QSqlDatabase database = QSqlDatabase::database(m_connectionName);
+  QSqlQuery query(database);
+
+  beginTransaction(&query, Write);
+
+  QString statement = "INSERT INTO Plugins(Location,LocalPath,State,Timestamp) VALUES(?,?,?,?)";
+
+  QList<QVariant> bindValues;
+  bindValues.append(location.toString());
+  bindValues.append(localPath);
+  bindValues.append(Plugin::INSTALLED);
+  bindValues.append(lastModified);
+
+  long pluginId = -1;
+  try
+  {
+    executeQuery(&query, statement, bindValues);
+    QVariant lastId = query.lastInsertId();
+    if (lastId.isValid())
+    {
+      pluginId = lastId.toLongLong();
+    }
+  }
+  catch (...)
+  {
+    rollbackTransaction(&query);
+    throw;
+  }
+
+  // Load the plugin and cache the resources
+  QPluginLoader pluginLoader;
+  pluginLoader.setFileName(localPath);
+  if (!pluginLoader.load())
+  {
+    rollbackTransaction(&query);
+    throw PluginException(QString("The plugin could not be loaded: %1").arg(localPath));
+  }
+
+  QDirIterator dirIter(resourcePrefix, QDirIterator::Subdirectories);
+  while (dirIter.hasNext())
+  {
+    QString resourcePath = dirIter.next();
+    if (QFileInfo(resourcePath).isDir()) continue;
+
+    QFile resourceFile(resourcePath);
+    resourceFile.open(QIODevice::ReadOnly);
+    QByteArray resourceData = resourceFile.readAll();
+    resourceFile.close();
+
+    statement = "INSERT INTO PluginResources(PluginID, ResourcePath, Resource) VALUES(?,?,?)";
+    bindValues.clear();
+    bindValues.append(QVariant::fromValue<qlonglong>(pluginId));
+    bindValues.append(resourcePath.mid(resourcePrefix.size()-1));
+    bindValues.append(resourceData);
+
+    try
+    {
+      executeQuery(&query, statement, bindValues);
+    }
+    catch (...)
+    {
+      rollbackTransaction(&query);
+      throw;
+    }
+  }
+
+  pluginLoader.unload();
+
+  try
+  {
+    commitTransaction(&query);
+
+    if (createArchive)
+    {
+      PluginArchive* archive = new PluginArchive(m_PluginStorage, location, localPath,
+                                               pluginId);
+      return archive;
+    }
+    else return 0;
+  }
+  catch (...)
+  {
+      rollbackTransaction(&query);
+      throw;
+  }
+
+}
+
+QStringList PluginDatabase::findResourcesPath(long pluginId, const QString& path) const
+{
+  checkConnection();
+
+  QString statement = "SELECT SUBSTR(ResourcePath,?) FROM PluginResources WHERE PluginID=? AND SUBSTR(ResourcePath,1,?)=?";
+
+  QString resourcePath = path.startsWith('/') ? path : QString("/") + path;
+  if (!resourcePath.endsWith('/'))
+    resourcePath += "/";
+
+  QList<QVariant> bindValues;
+  bindValues.append(resourcePath.size()+1);
+  bindValues.append(qlonglong(pluginId));
+  bindValues.append(resourcePath.size());
+  bindValues.append(resourcePath);
+
+  QSqlDatabase database = QSqlDatabase::database(m_connectionName);
+  QSqlQuery query(database);
+
+  executeQuery(&query, statement, bindValues);
+
+  QStringList paths;
+  while (query.next())
+  {
+    QString currPath = query.value(EBindIndex).toString();
+    int slashIndex = currPath.indexOf('/');
+    if (slashIndex > 0)
+    {
+      currPath = currPath.left(slashIndex+1);
+    }
+
+    paths << currPath;
+  }
+
+  return paths;
+}
+
+void PluginDatabase::removeArchive(const PluginArchive *pa)
+{
+  checkConnection();
+
+  QSqlDatabase database = QSqlDatabase::database(m_connectionName);
+  QSqlQuery query(database);
+
+  QString statement = "DELETE FROM Plugins WHERE ID=?";
+
+  QList<QVariant> bindValues;
+  bindValues.append(pa->getPluginId());
+
+  executeQuery(&query, statement, bindValues);
+}
+
+void PluginDatabase::executeQuery(QSqlQuery *query, const QString &statement, const QList<QVariant> &bindValues) const
+{
+  Q_ASSERT(query != 0);
+
+  bool success = false;
+  enum {Prepare =0 , Execute=1};
+
+  for (int stage=Prepare; stage <= Execute; ++stage)
+  {
+    if ( stage == Prepare)
+      success = query->prepare(statement);
+    else // stage == Execute
+      success = query->exec();
+
+    if (!success)
+    {
+      QString errorText = "Problem: Could not %1 statement: %2\n"
+              "Reason: %3\n"
+              "Parameters: %4\n";
+      QString parameters;
+      if (bindValues.count() > 0)
+      {
+        for (int i = 0; i < bindValues.count(); ++i)
+        {
+          parameters.append(QString("\n\t[") + QString::number(i) + "]: " + bindValues.at(i).toString());
+        }
+      }
+      else
+      {
+        parameters = "None";
+      }
+
+      PluginDatabaseException::Type errorType;
+      int result = query->lastError().number();
+      if (result == 26 || result == 11) //SQLILTE_NOTADB || SQLITE_CORRUPT
+      {
+        qWarning() << "PluginFramework:- Database file is corrupt or invalid:" << getDatabasePath();
+        errorType = PluginDatabaseException::DB_FILE_INVALID;
+      }
+      else if (result == 8) //SQLITE_READONLY
+        errorType = PluginDatabaseException::DB_WRITE_ERROR;
+      else
+        errorType = PluginDatabaseException::DB_SQL_ERROR;
+
+      query->finish();
+      query->clear();
+
+      throw PluginDatabaseException(errorText.arg(stage == Prepare ? "prepare":"execute")
+                  .arg(statement).arg(query->lastError().text()).arg(parameters), errorType);
+    }
+
+    if (stage == Prepare)
+    {
+      foreach(const QVariant &bindValue, bindValues)
+        query->addBindValue(bindValue);
+    }
+  }
+}
+
+
+void PluginDatabase::close()
+{
+  if (m_isDatabaseOpen)
+  {
+    QSqlDatabase database = QSqlDatabase::database(m_connectionName, false);
+    if (database.isValid())
+    {
+      if(database.isOpen())
+      {
+        database.close();
+        m_isDatabaseOpen = false;
+        return;
+      }
+    }
+    else
+    {
+      throw PluginDatabaseException(QString("Problem closing database: Invalid connection %1").arg(m_connectionName));
+    }
+  }
+}
+
+
+void PluginDatabase::setDatabasePath(const QString &databasePath)
+{
+    m_databasePath = QDir::toNativeSeparators(databasePath);
+}
+
+
+QString PluginDatabase::getDatabasePath() const
+{
+    QString path;
+    if(m_databasePath.isEmpty())
+    {
+      QSettings settings(QSettings::UserScope, "commontk", QApplication::applicationName());
+      path = settings.value("PluginDB/Path").toString();
+      if (path.isEmpty())
+      {
+        path = QDir::currentPath();
+        if (path.lastIndexOf(PLUGINDATABASE_PATH_SEPARATOR) != path.length() -1)
+        {
+          path.append(PLUGINDATABASE_PATH_SEPARATOR);
+        }
+        path.append(PLUGINDATABASE);
+      }
+      path = QDir::toNativeSeparators(path);
+    }
+    else
+    {
+      path = m_databasePath;
+    }
+
+    return path;
+}
+
+
+QByteArray PluginDatabase::getPluginResource(long pluginId, const QString& res) const
+{
+  checkConnection();
+
+  QSqlDatabase database = QSqlDatabase::database(m_connectionName);
+  QSqlQuery query(database);
+
+  QString statement = "SELECT Resource FROM PluginResources WHERE PluginID=? AND ResourcePath=?";
+
+  QString resourcePath = res.startsWith('/') ? res : QString("/") + res;
+  QList<QVariant> bindValues;
+  bindValues.append(qlonglong(pluginId));
+  bindValues.append(resourcePath);
+
+  executeQuery(&query, statement, bindValues);
+
+  if (query.next())
+  {
+    return query.value(EBindIndex).toByteArray();
+  }
+
+  return QByteArray();
+}
+
+
+void PluginDatabase::createTables()
+{
+    QSqlDatabase database = QSqlDatabase::database(m_connectionName);
+    QSqlQuery query(database);
+
+    //Begin Transaction
+    beginTransaction(&query, Write);
+
+    QString statement("CREATE TABLE Plugins("
+                      "ID INTEGER PRIMARY KEY,"
+                      "Location TEXT NOT NULL UNIQUE,"
+                      "LocalPath TEXT NOT NULL UNIQUE,"
+                      "State INTEGER NOT NULL,"
+                      "Timestamp TEXT NOT NULL)");
+    try
+    {
+      executeQuery(&query, statement);
+    }
+    catch (...)
+    {
+      rollbackTransaction(&query);
+      throw;
+    }
+
+    statement = "CREATE TABLE PluginResources("
+                "PluginID INTEGER NOT NULL,"
+                "ResourcePath TEXT NOT NULL, "
+                "Resource BLOB NOT NULL,"
+                "FOREIGN KEY(PluginID) REFERENCES Plugins(ID) ON DELETE CASCADE)";
+    try
+    {
+      executeQuery(&query, statement);
+    }
+    catch (...)
+    {
+      rollbackTransaction(&query);
+      throw;
+    }
+
+    try
+    {
+      commitTransaction(&query);
+    }
+    catch (...)
+    {
+      rollbackTransaction(&query);
+      throw;
+    }
+
+}
+
+
+bool PluginDatabase::checkTables() const
+{
+  bool bTables(false);
+  QStringList tables = QSqlDatabase::database(m_connectionName).tables();
+  if (tables.contains(PLUGINS_TABLE)
+      && tables.contains(PLUGIN_RESOURCES_TABLE))
+  {
+    bTables = true;
+  }
+  return bTables;
+}
+
+
+bool PluginDatabase::dropTables()
+{
+  //Execute transaction for deleting the database tables
+  QSqlDatabase database = QSqlDatabase::database(m_connectionName);
+  QSqlQuery query(database);
+  QStringList expectedTables;
+  expectedTables << PLUGINS_TABLE << PLUGIN_RESOURCES_TABLE;
+
+  if (database.tables().count() > 0)
+  {
+    beginTransaction(&query, Write);
+    QStringList actualTables = database.tables();
+
+    foreach(const QString expectedTable, expectedTables)
+    {
+      if (actualTables.contains(expectedTable))
+      {
+        try
+        {
+          executeQuery(&query, QString("DROP TABLE ") + expectedTable);
+        }
+        catch (...)
+        {
+          rollbackTransaction(&query);
+          throw;
+        }
+      }
+      try
+      {
+        commitTransaction(&query);
+      }
+      catch (...)
+      {
+        rollbackTransaction(&query);
+        throw;
+      }
+    }
+  }
+}
+
+
+bool PluginDatabase::isOpen() const
+{
+  return m_isDatabaseOpen;
+}
+
+
+void PluginDatabase::checkConnection() const
+{
+  if(!m_isDatabaseOpen)
+  {
+    throw PluginDatabaseException("Database not open.", PluginDatabaseException::DB_NOT_OPEN_ERROR);
+  }
+
+  if (!QSqlDatabase::database(m_connectionName).isValid())
+  {
+    throw PluginDatabaseException(QString("Database connection invalid: %1").arg(m_connectionName),
+                                  PluginDatabaseException::DB_CONNECTION_INVALID);
+  }
+}
+
+
+void PluginDatabase::beginTransaction(QSqlQuery *query, TransactionType type)
+{
+  bool success;
+  if (type == Read)
+      success = query->exec(QLatin1String("BEGIN"));
+  else
+      success = query->exec(QLatin1String("BEGIN IMMEDIATE"));
+
+  if (!success) {
+      int result = query->lastError().number();
+      if (result == 26 || result == 11) //SQLITE_NOTADB || SQLITE_CORRUPT
+      {
+        throw PluginDatabaseException(QString("PluginFramework: Database file is corrupt or invalid: %1").arg(getDatabasePath()),
+                                      PluginDatabaseException::DB_FILE_INVALID);
+      }
+      else if (result == 8) //SQLITE_READONLY
+      {
+        throw PluginDatabaseException(QString("PluginFramework: Insufficient permissions to write to database: %1").arg(getDatabasePath()),
+                                      PluginDatabaseException::DB_WRITE_ERROR);
+      }
+      else
+        throw PluginDatabaseException(QString("PluginFramework: ") + query->lastError().text(),
+                                      PluginDatabaseException::DB_SQL_ERROR);
+  }
+
+}
+
+
+void PluginDatabase::commitTransaction(QSqlQuery *query)
+{
+  Q_ASSERT(query != 0);
+  query->finish();
+  query->clear();
+  if (!query->exec(QLatin1String("COMMIT")))
+  {
+    throw PluginDatabaseException(QString("PluginFramework: ") + query->lastError().text(),
+                                  PluginDatabaseException::DB_SQL_ERROR);
+  }
+}
+
+
+void PluginDatabase::rollbackTransaction(QSqlQuery *query)
+{
+  Q_ASSERT(query !=0);
+  query->finish();
+  query->clear();
+
+  if (!query->exec(QLatin1String("ROLLBACK")))
+  {
+    throw PluginDatabaseException(QString("PluginFramework: ") + query->lastError().text(),
+                                  PluginDatabaseException::DB_SQL_ERROR);
+  }
+}
+
+QList<PluginArchive*> PluginDatabase::getPluginArchives() const
+{
+  checkConnection();
+
+  QSqlQuery query(QSqlDatabase::database(m_connectionName));
+  QString statement("SELECT ID, Location, LocalPath FROM Plugins WHERE State != ?");
+  QList<QVariant> bindValues;
+  bindValues.append(Plugin::UNINSTALLED);
+
+  executeQuery(&query, statement, bindValues);
+
+  QList<PluginArchive*> archives;
+  while (query.next())
+  {
+    const long id = query.value(EBindIndex).toLongLong();
+    const QUrl location(query.value(EBindIndex1).toString());
+    const QString localPath(query.value(EBindIndex2).toString());
+
+    if (id <= 0 || location.isEmpty() || localPath.isEmpty())
+    {
+      throw PluginDatabaseException(QString("Database integrity corrupted, row %1 contains empty values.").arg(id),
+                                    PluginDatabaseException::DB_FILE_INVALID);
+    }
+
+    try
+    {
+      PluginArchive* pa = new PluginArchive(m_PluginStorage, location, localPath, id);
+      archives.append(pa);
+    }
+    catch (const PluginException& exc)
+    {
+      qWarning() << exc;
+    }
+  }
+
+  return archives;
+}
+
+}

+ 85 - 0
Libs/Core/PluginFramework/ctkPluginDatabaseException.cxx

@@ -0,0 +1,85 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) 2010 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 "ctkPluginDatabaseException.h"
+
+#include <QDebug>
+
+namespace ctk {
+
+  PluginDatabaseException::PluginDatabaseException(const QString& msg, const Type& type, const std::exception& cause)
+    : std::runtime_error(msg.toStdString()),
+      type(type), cause(cause)
+  {
+
+  }
+
+  PluginDatabaseException::PluginDatabaseException(const QString& msg, const std::exception& cause)
+    : std::runtime_error(msg.toStdString()),
+      type(UNSPECIFIED), cause(cause)
+  {
+
+  }
+
+  PluginDatabaseException::PluginDatabaseException(const PluginDatabaseException& o)
+    : std::runtime_error(o.what()),
+      type(o.type), cause(o.cause)
+  {
+
+  }
+
+  PluginDatabaseException& PluginDatabaseException::operator=(const PluginDatabaseException& o)
+  {
+    std::runtime_error::operator=(o);
+    type = o.type;
+    cause = o.cause;
+    return *this;
+  }
+
+  std::exception PluginDatabaseException::getCause() const
+  {
+    return cause;
+  }
+
+  void PluginDatabaseException::setCause(const std::exception& cause) throw(std::logic_error)
+  {
+    if (!cause.what()) throw std::logic_error("The cause for this PluginDatabaseException instance is already set");
+
+    this->cause = cause;
+  }
+
+  PluginDatabaseException::Type PluginDatabaseException::getType() const
+  {
+    return type;
+  }
+
+}
+
+QDebug operator<<(QDebug dbg, const ctk::PluginDatabaseException& exc)
+{
+  dbg << "PluginDatabaseException:" << exc.what();
+
+  const char* causeMsg = exc.getCause().what();
+  if (causeMsg) dbg << "  Caused by:" << causeMsg;
+
+  return dbg.maybeSpace();
+}
+

+ 71 - 0
Libs/Core/PluginFramework/ctkPluginDatabaseException.h

@@ -0,0 +1,71 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) 2010 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 CTKPLUGINDATABASEEXCEPTION_H
+#define CTKPLUGINDATABASEEXCEPTION_H
+
+#include "CTKCoreExport.h"
+
+#include <stdexcept>
+
+#include <QString>
+
+namespace ctk {
+
+  class CTK_CORE_EXPORT PluginDatabaseException : public std::runtime_error
+  {
+  public:
+
+    enum Type {
+      UNSPECIFIED,
+      DB_CONNECTION_INVALID,
+      DB_NOT_OPEN_ERROR,
+      DB_NOT_FOUND_ERROR,
+      DB_CREATE_DIR_ERROR,
+      DB_WRITE_ERROR,
+      DB_FILE_INVALID,
+      DB_SQL_ERROR
+    };
+
+    PluginDatabaseException(const QString& msg, const Type& type = UNSPECIFIED, const std::exception& cause = std::exception());
+    PluginDatabaseException(const QString& msg, const std::exception& cause);
+
+    PluginDatabaseException(const PluginDatabaseException& o);
+    PluginDatabaseException& operator=(const PluginDatabaseException& o);
+
+    ~PluginDatabaseException() throw() {}
+
+    std::exception getCause() const;
+    void setCause(const std::exception&) throw(std::logic_error);
+    Type getType() const;
+
+
+  private:
+
+    Type type;
+    std::exception cause;
+  };
+
+}
+
+CTK_CORE_EXPORT QDebug operator<<(QDebug dbg, const ctk::PluginDatabaseException& exc);
+
+#endif // CTKPLUGINDATABASEEXCEPTION_H

+ 209 - 0
Libs/Core/PluginFramework/ctkPluginDatabase_p.h

@@ -0,0 +1,209 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) 2010 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 CTKPLUGINDATABASE_P_H
+#define CTKPLUGINDATABASE_P_H
+
+#include <QtSql>
+#include <QList>
+
+
+namespace ctk {
+
+// CTK class forward declarations
+class PluginStorage;
+class PluginArchive;
+
+class PluginDatabase {
+
+  public:
+    PluginDatabase(PluginStorage* storage);
+
+    virtual ~PluginDatabase();
+
+    /**
+     * Opens the plugin database. If the database does not
+     * yet exist, it is created using the path from getDatabasePath().
+     *
+     * @see setDatabasePath(const QString&)
+     * @see getDatabasePath()
+     * @see PluginDatabaseException
+     *
+     * @throws PluginDatabaseException
+     */
+    void open();
+
+    /**
+     * Closes the plugin database. Throws a PluginDatabaseException
+     * of type DB_CONNECTION_INVALID if the database is invalid.
+     *
+     * @throws PluginDatabaseException
+     */
+    void close();
+
+    /**
+     * Checks if the database is open
+     */
+    bool isOpen() const;
+
+    /**
+     * Sets the path of the service database to \a databasePath
+     */
+    void setDatabasePath(const QString &databasePath);
+
+    /**
+     * Returns the path of the plugin database
+     */
+    QString getDatabasePath() const;
+
+    /**
+     * Get a Qt resource cached in the database. The resource path \a res
+     * must be relative to the plugin specific resource prefix, but may
+     * start with a '/'.
+     *
+     * @param pluginId The id of the plugin from which to get the resource
+     * @param res The path to the resource in the plugin
+     * @return The byte array of the cached resource
+     *
+     * @throws PluginDatabaseException
+     */
+    QByteArray getPluginResource(long pluginId, const QString& res) const;
+
+    /**
+     * Get a list of resource entries under the given path.
+     *
+     * @param pluginId The id of the plugin from which to get the entries
+     * @param path A resource path relative to the plugin specific resource prefix.
+     * @return A QStringList containing the cached resource entries.
+     *
+     * @throws PluginDatabaseException
+     */
+    QStringList findResourcesPath(long pluginId, const QString& path) const;
+
+    /**
+     * Inserts a new plugin into the database. This method assumes that
+     * the an entry with the same \a location and \a localPath does not
+     * yet exist in the database.
+     *
+     * @param location The URL to the plugin.
+     * @param localPath The path to the plugin library on the local file system.
+     * @param createArchive If \c true (default) a new PluginArchive instance is returned.
+     *
+     * @throws PluginDatabaseException
+     */
+    PluginArchive* insertPlugin(const QUrl& location, const QString& localPath, bool createArchive = true);
+
+    /**
+     * Removes all persisted data related to the given PluginArchive.
+     *
+     * @throws PluginDatabaseException
+     */
+    void removeArchive(const PluginArchive* pa);
+
+    /**
+     * Reads the persisted plugin data and returns a PluginArchive object
+     * for each plugin which is not in state UNINSTALLED.
+     *
+     * @throws PluginDatabaseException
+     */
+    QList<PluginArchive*> getPluginArchives() const;
+
+
+  private:
+
+    enum TransactionType{Read, Write};
+
+    /**
+     *  Helper method that creates the database tables:
+     *
+     * @throws PluginDatabaseException
+     */
+    void createTables();
+    bool dropTables();
+
+    /**
+     * Helper method that checks if all the expected tables exist in the database.
+     *
+     * Returns true if they all exist and false if any of them don't
+     */
+    bool checkTables() const;
+
+    /**
+     * Checks the database connection.
+     *
+     * @throws PluginDatabaseException
+     */
+    void checkConnection() const;
+
+    /**
+     * Compares the persisted plugin modification time with the
+     * file system modification time and updates the database
+     * if the persisted data is outdated.
+     *
+     * This should only be called once when the database is initially opened.
+     */
+    void updateDB();
+
+    /**
+     * Helper function that executes the sql query specified in \a statement.
+     * It is assumed that the \a statement uses positional placeholders and
+     * corresponding parameters are placed in the list of \a bindValues.
+     *
+     * Aside: This function may be safely called standalone or within an explicit
+     * transaction.  If called standalone, it's single query is implicitly
+     * wrapped in it's own transaction.
+     *
+     * @throws PluginDatabaseException
+     */
+    void executeQuery(QSqlQuery* query, const QString &statement, const QList<QVariant> &bindValues = QList<QVariant>()) const;
+
+    /**
+     * Begins a transcaction based on the \a type which can be Read or Write.
+     *
+     * @throws PluginDatabaseException
+     */
+    void beginTransaction(QSqlQuery* query, TransactionType);
+
+    /**
+     * Commits a transaction
+     *
+     * @throws PluginDatabaseException
+     */
+    void commitTransaction(QSqlQuery* query);
+
+    /**
+     * Rolls back a transaction
+     *
+     * @throws PluginDatabaseException
+     */
+    void rollbackTransaction(QSqlQuery* query);
+
+
+    QString m_databasePath;
+    QString m_connectionName;
+    bool m_isDatabaseOpen;
+    bool m_inTransaction;
+    PluginStorage* m_PluginStorage;
+};
+
+}
+
+#endif // CTKPLUGINDATABASE_P_H

+ 3 - 10
Libs/Core/PluginFramework/ctkPluginException.cxx

@@ -25,21 +25,20 @@ namespace ctk {
 
   PluginException::PluginException(const QString& msg, const Type& type, const std::exception& cause)
     : std::runtime_error(msg.toStdString()),
-      msg(msg), type(type), cause(cause)
+      type(type), cause(cause)
   {
 
   }
 
   PluginException::PluginException(const QString& msg, const std::exception& cause)
     : std::runtime_error(msg.toStdString()),
-      msg(msg), type(UNSPECIFIED), cause(cause)
+      type(UNSPECIFIED), cause(cause)
   {
 
   }
 
   PluginException::PluginException(const PluginException& o)
-    : std::runtime_error(msg.toStdString()),
-      msg(o.msg), type(o.type), cause(o.cause)
+    : std::runtime_error(o.what()), type(o.type), cause(o.cause)
   {
 
   }
@@ -47,7 +46,6 @@ namespace ctk {
   PluginException& PluginException::operator=(const PluginException& o)
   {
     std::runtime_error::operator=(o);
-    msg = o.msg;
     type = o.type;
     cause = o.cause;
     return *this;
@@ -70,11 +68,6 @@ namespace ctk {
     return type;
   }
 
-  const char* PluginException::what() const throw()
-  {
-    return qPrintable(msg);
-  }
-
 }
 
 QDebug operator<<(QDebug dbg, const ctk::PluginException& exc)

+ 0 - 2
Libs/Core/PluginFramework/ctkPluginException.h

@@ -87,11 +87,9 @@ namespace ctk {
     void setCause(const std::exception&) throw(std::logic_error);
     Type getType() const;
 
-    const char* what() const throw();
 
   private:
 
-    QString msg;
     Type type;
     std::exception cause;
 

+ 38 - 0
Libs/Core/PluginFramework/ctkPluginFramework.cxx

@@ -24,6 +24,7 @@
 #include "ctkPluginFrameworkPrivate_p.h"
 #include "ctkPluginPrivate_p.h"
 #include "ctkPluginFrameworkContextPrivate_p.h"
+#include "ctkPluginConstants.h"
 
 
 namespace ctk {
@@ -54,6 +55,43 @@ namespace ctk {
     d->init();
   }
 
+  QStringList PluginFramework::getResourceList(const QString& path) const
+  {
+    QString resourcePath = QString(":/") + PluginConstants::SYSTEM_PLUGIN_SYMBOLICNAME;
+    if (path.startsWith('/'))
+      resourcePath += path;
+    else
+      resourcePath += QString("/") + path;
+
+    QStringList paths;
+    QFileInfoList entryInfoList = QDir(resourcePath).entryInfoList();
+    QListIterator<QFileInfo> infoIter(entryInfoList);
+    while (infoIter.hasNext())
+    {
+      const QFileInfo& resInfo = infoIter.next();
+      QString entry = resInfo.canonicalFilePath().mid(resourcePath.size());
+      if (resInfo.isDir())
+        entry += "/";
+
+      paths << entry;
+    }
+
+    return paths;
+  }
+
+  QByteArray PluginFramework::getResource(const QString& path) const
+  {
+    QString resourcePath = QString(":/") + PluginConstants::SYSTEM_PLUGIN_SYMBOLICNAME;
+    if (path.startsWith('/'))
+      resourcePath += path;
+    else
+      resourcePath += QString("/") + path;
+
+    QFile resourceFile(resourcePath);
+    resourceFile.open(QIODevice::ReadOnly);
+    return resourceFile.readAll();
+  }
+
   void waitForStop(int timeout)
   {
     // TODO implement

+ 4 - 0
Libs/Core/PluginFramework/ctkPluginFramework.h

@@ -48,6 +48,10 @@ namespace ctk {
     // method returned
     void waitForStop(int timeout);
 
+    QStringList getResourceList(const QString& path) const;
+
+    QByteArray getResource(const QString& path) const;
+
   protected:
 
     friend class PluginFrameworkContextPrivate;

+ 1 - 1
Libs/Core/PluginFramework/ctkPluginFrameworkContextPrivate.cxx

@@ -72,7 +72,7 @@ namespace ctk {
     for (int i = 0; i < allPAs.size(); ++i)
     {
       PluginArchive* pa = allPAs[i];
-      Plugin* p = plugins->getPlugin(pa->getPluginLocation());
+      Plugin* p = plugins->getPlugin(pa->getPluginLocation().toString());
       log() << " #" << p->getPluginId() << " " << p->getSymbolicName() << ":"
           << p->getVersion() << " location:" << p->getLocation();
     }

+ 4 - 10
Libs/Core/PluginFramework/ctkPluginManifest.cxx

@@ -34,12 +34,12 @@ namespace ctk {
 
   }
 
-  PluginManifest::PluginManifest(QIODevice* in)
+  PluginManifest::PluginManifest(const QByteArray& in)
   {
     read(in);
   }
 
-  void PluginManifest::read(QIODevice* in)
+  void PluginManifest::read(const QByteArray& in)
   {
     mainAttributes.clear();
     sections.clear();
@@ -48,12 +48,10 @@ namespace ctk {
     QString value;
     QString currSection;
 
-    in->open(QIODevice::ReadOnly);
+    QList<QByteArray> lines = in.split('\n');
 
-    while (!in->atEnd())
+    foreach (const QString& line, lines)
     {
-      QString line(in->readLine().data());
-
       // skip empty lines and comments
       if (line.trimmed().isEmpty() | line.startsWith('#')) continue;
 
@@ -99,8 +97,6 @@ namespace ctk {
         // add the line to the value
         value += line;
       }
-
-      in->close();
     }
 
     // save the last key/value pair
@@ -116,8 +112,6 @@ namespace ctk {
       }
     }
 
-    qDebug() << mainAttributes.keys();
-    qDebug() << mainAttributes.values();
   }
 
   PluginManifest::Attributes PluginManifest::getMainAttributes() const

+ 2 - 2
Libs/Core/PluginFramework/ctkPluginManifest_p.h

@@ -36,9 +36,9 @@ namespace ctk {
     typedef QHash<QString,QString> Attributes;
 
     PluginManifest();
-    PluginManifest(QIODevice* in);
+    PluginManifest(const QByteArray& in);
 
-    void read(QIODevice* in);
+    void read(const QByteArray& in);
 
     Attributes getMainAttributes() const;
     QString getAttribute(const QString& key) const;

+ 1 - 1
Libs/Core/PluginFramework/ctkPluginPrivate.cxx

@@ -35,7 +35,7 @@ namespace ctk {
       PluginFrameworkContextPrivate* fw,
       PluginArchive* pa)
     : q_ptr(&qq), fwCtx(fw), id(pa->getPluginId()),
-    location(pa->getPluginLocation()), state(Plugin::INSTALLED),
+    location(pa->getPluginLocation().toString()), state(Plugin::INSTALLED),
     archive(pa), pluginContext(0), pluginActivator(0), lastModified(0),
     lazyActivation(true), activating(false), deactivating(false),
     resolveFailException("")

+ 56 - 126
Libs/Core/PluginFramework/ctkPluginStorage.cxx

@@ -29,94 +29,31 @@
 // CTK includes
 #include "ctkPluginArchive_p.h"
 #include "ctkPluginFrameworkContextPrivate_p.h"
+#include "ctkPluginDatabaseException.h"
 
 namespace ctk {
 
   PluginStorage::PluginStorage(PluginFrameworkContextPrivate* framework)
-    : nextFreeId(1), framework(framework)
+    : framework(framework), pluginDatabase(this)
   {
 //    // See if we have a storage database
 //    bundlesDir = Util.getFileStorage(framework, "bs");
 //    if (bundlesDir == null) {
-//      throw new RuntimeException("No bundle storage area available!");
-//    }
-//    // Restore all saved bundles
-//    String [] list = bundlesDir.list();
-//    for (int i = 0; list != null & i < list.length; i++) {
-//      long id;
-//      try {
-//        id = Long.parseLong(list[i]);
-//      } catch (NumberFormatException e) {
-//        continue;
-//      }
-//      if (id == 0) {
-//        System.err.println("Saved bundle with illegal id 0 is ignored.");
-//      }
-//      int pos = find(id);
-//      if (pos < archives.size() && ((BundleArchive)archives.get(pos)).getBundleId() == id) {
-//        System.err.println("There are two bundle directories with id: " + id);
-//        break;
-//      }
-//      FileTree dir = new FileTree(bundlesDir, list[i]);
-//      if (dir.isDirectory()) {
-//        try {
-//          boolean bUninstalled = BundleArchiveImpl.isUninstalled(dir);
-//          if(bUninstalled) {
-//            // silently remove any bundle marked as uninstalled
-//            dir.delete();
-//          } else {
-//            BundleArchive ba = new BundleArchiveImpl(this, dir, id);
-//            archives.add(pos, ba);
-//          }
-//          if (id >= nextFreeId) {
-//            nextFreeId = id + 1;
-//          }
-//        } catch (Exception e) {
-//          dir.delete();
-//          System.err.println("Removed corrupt bundle dir (" + e.getMessage() + "): " + dir);
-//        }
-//      }
+//      throw RuntimeException("No plugin storage area available!");
 //    }
+
+    pluginDatabase.open();
+    archives << pluginDatabase.getPluginArchives();
   }
 
-  PluginArchive* PluginStorage::insertPlugin(const QString& location, QIODevice* is)
+  PluginArchive* PluginStorage::insertPlugin(const QUrl& location, const QString& localPath)
   {
-    long id = nextFreeId++;
-
-    //TODO check the db if the plugin is already cached
-    //...
-
-    // work-around for now without the cache:
-    // load the plugin to have access to Qt resources
-    QPluginLoader pluginLoader;
-    QString resourcePrefix;
-    if (!is)
-    {
-      QUrl url(location);
-      pluginLoader.setFileName(url.path());
-      QFileInfo fileInfo(url.path());
-      resourcePrefix = fileInfo.baseName();
-      if (resourcePrefix.startsWith("lib"))
-      {
-        resourcePrefix = resourcePrefix.mid(3);
-      }
-      resourcePrefix.replace("_", ".");
-
-      qDebug() << QString("Loading plugin") << pluginLoader.fileName() << ":" << pluginLoader.load();
-    }
-    else
-    {
-      resourcePrefix = location;
-    }
-
-    PluginArchive* pa = new PluginArchive(this, is, location, id, resourcePrefix);
+    PluginArchive* pa = pluginDatabase.insertPlugin(location, localPath);
     archives.push_back(pa);
-
-    pluginLoader.unload();
     return pa;
   }
 
-  PluginArchive* PluginStorage::updatePluginArchive(PluginArchive* old, QIODevice* is)
+  PluginArchive* PluginStorage::updatePluginArchive(PluginArchive* old, const QString& localPath)
   {
     //return new BundleArchiveImpl((BundleArchiveImpl)old, is);
     return 0;
@@ -135,11 +72,13 @@ namespace ctk {
 //    }
   }
 
-  QList<PluginArchive*> PluginStorage::getAllPluginArchives() const {
+  QList<PluginArchive*> PluginStorage::getAllPluginArchives() const
+  {
     return archives;
   }
 
-  QList<QString> PluginStorage::getStartOnLaunchPlugins() {
+  QList<QString> PluginStorage::getStartOnLaunchPlugins()
+  {
     QList<QString> res;
     QListIterator<PluginArchive*> i(archives);
     while(i.hasNext())
@@ -147,76 +86,67 @@ namespace ctk {
       PluginArchive* ba = i.next();
       if (ba->getAutostartSetting() != -1)
       {
-        res.push_back(ba->getPluginLocation());
+        res.push_back(ba->getPluginLocation().toString());
       }
     }
     return res;
   }
 
-  void PluginStorage::close()
+  PluginStorage::~PluginStorage()
   {
-//    for (Iterator i = archives.iterator(); i.hasNext(); ) {
-//      BundleArchive ba = (BundleArchive) i.next();
-//      ba.close();
-//      i.remove();
-//    }
-//    framework = null;
-//    bundlesDir = null;
+    close();
   }
 
-  bool PluginStorage::removeArchive(const PluginArchive* ba) {
-//    synchronized (archives) {
-//      int pos = find(ba.getBundleId());
-//      if (pos < archives.size() && archives.get(pos) == ba) {
-//        archives.remove(pos);
-//        return true;
-//      } else {
-//        return false;
-//      }
-//    }
+  void PluginStorage::close()
+  {
+    pluginDatabase.close();
+    qDeleteAll(archives);
   }
 
-  int PluginStorage::find(long id) {
-    int lb = 0;
-//    int ub = archives.size() - 1;
-//    int x = 0;
-//    while (lb < ub) {
-//      x = (lb + ub) / 2;
-//      long xid = ((BundleArchive)archives.get(x)).getBundleId();
-//      if (id == xid) {
-//        return x;
-//      } else if (id < xid) {
-//        ub = x;
-//      } else {
-//        lb = x+1;
-//      }
-//    }
-//    if (lb < archives.size() && ((BundleArchive)archives.get(lb)).getBundleId() < id) {
-//      return lb + 1;
-//    }
-    return lb;
+  bool PluginStorage::removeArchive(PluginArchive* pa)
+  {
+    QMutexLocker lock(&archivesLock);
+
+    bool removed = false;
+    try
+    {
+      pluginDatabase.removeArchive(pa);
+      removed = archives.removeAll(pa);
+      delete pa;
+    }
+    catch (const PluginDatabaseException& exc)
+    {
+      qDebug() << "Removing plugin archive failed:" << exc;
+      removed = false;
+    }
+
+    return removed;
   }
 
-  QString PluginStorage::getPluginResource(const QString& res) const
+  QByteArray PluginStorage::getPluginResource(long pluginId, const QString& res) const
   {
-    //TODO implement sql cache
-    qDebug() << "Getting resource:" << res;
-    QFileInfo info(res);
-    if (info.exists() && info.isReadable() && info.isFile())
-      return res;
-    return QString();
+    try
+    {
+      return pluginDatabase.getPluginResource(pluginId, res);
+    }
+    catch (const PluginDatabaseException& exc)
+    {
+      qDebug() << QString("Getting plugin resource %1 failed:").arg(res) << exc;
+      return QByteArray();
+    }
   }
 
-  QStringList PluginStorage::findResourcesPath(const QString& path) const
+  QStringList PluginStorage::findResourcesPath(long pluginId, const QString& path) const
   {
-    //TODO implement sql cache
-    QFileInfo info(path);
-    if (info.isDir() && info.isReadable())
+    try
     {
-      return info.absoluteDir().entryList();
+      return pluginDatabase.findResourcesPath(pluginId, path);
+    }
+    catch (const PluginDatabaseException& exc)
+    {
+      qDebug() << QString("Getting plugin resource paths for %1 failed:").arg(path) << exc;
+      return QStringList();
     }
-
-    return QStringList();
   }
 
 }

+ 91 - 97
Libs/Core/PluginFramework/ctkPluginStorage_p.h

@@ -25,6 +25,8 @@
 #include <QList>
 #include <QStringList>
 
+#include "ctkPluginDatabase_p.h"
+
 // Qt class forward declarations
 class QIODevice;
 
@@ -41,112 +43,104 @@ namespace ctk {
 
   private:
 
-    /**
-     * Next available bundle id.
-     */
-    int nextFreeId;
+    QMutex archivesLock;
 
     /**
      * Plugin id sorted list of all active plugin archives.
      */
     QList<PluginArchive*> archives;
 
-     /**
-      * Framework handle.
-      */
-     PluginFrameworkContextPrivate* framework;
+    /**
+     * Framework handle.
+     */
+    PluginFrameworkContextPrivate* framework;
+
+    /**
+     * SQLite db caching plug-in metadata and resources
+     */
+    PluginDatabase pluginDatabase;
 
   public:
 
-     /**
-      * Create a container for all plugin data in this framework.
-      * Try to restore all saved plugin archive state.
-      *
-      */
-     PluginStorage(PluginFrameworkContextPrivate* framework);
-
-
-     /**
-      * Insert a plugin (shared library) into the persistent storage
-      *
-      * @param location Location of the plugin.
-      * @param is QIODevice with plugin content.
-      * @return Plugin archive object.
-      */
-     PluginArchive* insertPlugin(const QString& location, QIODevice* is);
-
-
-     /**
-      * Insert a new plugin (shared library) into the persistent
-      * storagedata as an update
-      * to an existing plugin archive. To commit this data a call to
-      * <code>replacePluginArchive</code> is needed.
-      *
-      * @param old PluginArchive to be replaced.
-      * @param is QIODevice with plugin content.
-      * @return Plugin archive object.
-      */
-     PluginArchive* updatePluginArchive(PluginArchive* old, QIODevice* is);
-
-
-     /**
-      * Replace old plugin archive with a new updated plugin archive, that
-      * was created with updatePluginArchive.
-      *
-      * @param oldPA PluginArchive to be replaced.
-      * @param newPA new PluginArchive.
-      */
-     void replacePluginArchive(PluginArchive* oldPA, PluginArchive* newPA);
-
-
-     /**
-      * Get all plugin archive objects.
-      *
-      * @return QList of all PluginArchives.
-      */
-     QList<PluginArchive*> getAllPluginArchives() const;
-
-
-     /**
-      * Get all plugins to start at next launch of framework.
-      * This list is sorted in increasing plugin id order.
-      *
-      * @return A List with plugin locations.
-      */
-     QList<QString> getStartOnLaunchPlugins();
-
-     QString getPluginResource(const QString& res) const;
-
-     QStringList findResourcesPath(const QString& path) const;
-
-
-     /**
-      * Close plugin storage.
-      *
-      */
-     void close();
-
-
-   private:
-
-     /**
-      * Remove plugin archive from archives list.
-      *
-      * @param id Plugin archive id to find.
-      * @return true if element was removed.
-      */
-     bool removeArchive(const PluginArchive* ba);
-
-     /**
-      * Find position for PluginArchive with specified id
-      *
-      * @param id Plugin archive id to find.
-      * @return Found position for the plugin archive id
-      */
-     int find(long id);
-
-
-  };
+    /**
+     * Create a container for all plugin data in this framework.
+     * Try to restore all saved plugin archive state.
+     *
+     */
+    PluginStorage(PluginFrameworkContextPrivate* framework);
+
+
+    /**
+     * Insert a plugin (shared library) into the persistent storage
+     *
+     * @param location Location of the plugin.
+     * @param localPath Path to the plugin on the local file system
+     * @return Plugin archive object.
+     */
+    PluginArchive* insertPlugin(const QUrl& location, const QString& localPath);
+
+
+    /**
+     * Insert a new plugin (shared library) into the persistent
+     * storagedata as an update
+     * to an existing plugin archive. To commit this data a call to
+     * <code>replacePluginArchive</code> is needed.
+     *
+     * @param old PluginArchive to be replaced.
+     * @param localPath Path to a plugin on the local file system.
+     * @return Plugin archive object.
+     */
+    PluginArchive* updatePluginArchive(PluginArchive* old, const QString& localPath);
+
+
+    /**
+     * Replace old plugin archive with a new updated plugin archive, that
+     * was created with updatePluginArchive.
+     *
+     * @param oldPA PluginArchive to be replaced.
+     * @param newPA new PluginArchive.
+     */
+    void replacePluginArchive(PluginArchive* oldPA, PluginArchive* newPA);
+
+    /**
+     * Remove plugin archive from archives list and persistent storage.
+     * The plugin archive is deleted and must not be used afterwards, if
+     * this method returns \a true.
+     *
+     * @param pa Plugin archive to remove.
+     * @return true if element was removed.
+     */
+    bool removeArchive(PluginArchive* pa);
+
+
+    /**
+     * Get all plugin archive objects.
+     *
+     * @return QList of all PluginArchives.
+     */
+    QList<PluginArchive*> getAllPluginArchives() const;
+
+
+    /**
+     * Get all plugins to start at next launch of framework.
+     * This list is sorted in increasing plugin id order.
+     *
+     * @return A List with plugin locations.
+     */
+    QList<QString> getStartOnLaunchPlugins();
+
+    QByteArray getPluginResource(long pluginId, const QString& res) const;
+
+    QStringList findResourcesPath(long pluginId, const QString& path) const;
+
+    /**
+     * Close this plugin storage and all bundles in it.
+     */
+    void close();
+
+    ~PluginStorage();
+
+};
 
 }
 

+ 20 - 18
Libs/Core/PluginFramework/ctkPlugins.cxx

@@ -46,7 +46,7 @@ namespace ctk {
     fwCtx = 0;
   }
 
-  Plugin* Plugins::install(const QString& location, QIODevice* in)
+  Plugin* Plugins::install(const QUrl& location, QIODevice* in)
   {
     if (!fwCtx)
     { // This Plugins instance has been closed!
@@ -56,19 +56,18 @@ namespace ctk {
     {
       QWriteLocker lock(&pluginsLock);
 
-      QHash<QString, Plugin*>::const_iterator it = plugins.find(location);
+      QHash<QString, Plugin*>::const_iterator it = plugins.find(location.toString());
       if (it != plugins.end()) {
         return it.value();
       }
 
       // install new plugin
-      QIODevice* pin = 0;
       PluginArchive* pa = 0;
+      QString localPluginPath;
       try {
         if (!in) {
-          // extract the input stream from the given location,
-          // which now must be a valid URL
-          QUrl url(location);
+          // extract the input stream from the given location
+          
 
 //          //TODO Support for http proxy authentication
 //          //TODO put in update as well
@@ -92,22 +91,25 @@ namespace ctk {
 //            }
 //          }
 
-          if (url.scheme() != "file")
+          if (location.scheme() != "file")
           {
-            throw std::runtime_error(std::string("Unsupported url scheme: ") + qPrintable(url.scheme()));
+            throw std::runtime_error(std::string("Unsupported url scheme: ") + qPrintable(location.scheme()));
           }
           else
           {
-            qDebug() << QString("Trying to install file:") << url.path();
+            qDebug() << QString("Trying to install file:") << location.path();
+            localPluginPath = location.toLocalFile();
           }
-        } else {
-          pin = in;
+        } 
+        else 
+        {
+          //TODO copy the QIODevice to a local cache
         }
 
-        pa = fwCtx->storage.insertPlugin(location, pin);
+        pa = fwCtx->storage.insertPlugin(location, localPluginPath);
 
         Plugin* res = new Plugin(fwCtx, pa);
-        plugins.insert(location, res);
+        plugins.insert(location.toString(), res);
 
         //TODO send event
         //fwCtx.listeners.bundleChanged(new BundleEvent(BundleEvent.INSTALLED, b));
@@ -124,7 +126,7 @@ namespace ctk {
   //      }
   //      else
   //      {
-        throw PluginException(QString("Failed to install plugin: ") + e.what(),
+        throw PluginException(QString("Failed to install plugin: ") + QString(e.what()),
                                 PluginException::UNSPECIFIED, e);
   //      }
       }
@@ -132,10 +134,10 @@ namespace ctk {
 
   }
 
-  void Plugins::remove(const QString& location)
+  void Plugins::remove(const QUrl& location)
   {
     QWriteLocker lock(&pluginsLock);
-    delete plugins.take(location);
+    delete plugins.take(location.toString());
   }
 
   Plugin* Plugins::getPlugin(int id) const
@@ -291,7 +293,7 @@ namespace ctk {
         try
         {
           Plugin* plugin = new Plugin(fwCtx, pa);
-          plugins.insert(pa->getPluginLocation(), plugin);
+          plugins.insert(pa->getPluginLocation().toString(), plugin);
         }
         catch (const std::exception& e)
         {
@@ -300,7 +302,7 @@ namespace ctk {
 
           std::cerr << "Error: Failed to load bundle "
                     << pa->getPluginId()
-                    << " ("  << qPrintable(pa->getPluginLocation())  << ")"
+                    << " ("  << qPrintable(pa->getPluginLocation().toString())  << ")"
                     << " uninstalled it!\n";
           std::cerr << e.what();
         }

+ 3 - 2
Libs/Core/PluginFramework/ctkPlugins_p.h

@@ -22,6 +22,7 @@
 #ifndef CTKPLUGINS_H
 #define CTKPLUGINS_H
 
+#include <QUrl>
 #include <QHash>
 #include <QReadWriteLock>
 
@@ -76,7 +77,7 @@ namespace ctk {
      *
      * @param location The location to be installed
      */
-    Plugin* install(const QString& location, QIODevice* in);
+    Plugin* install(const QUrl& location, QIODevice* in);
 
 
     /**
@@ -84,7 +85,7 @@ namespace ctk {
      *
      * @param location The location to be removed
      */
-    void remove(const QString& location);
+    void remove(const QUrl& location);
 
 
     /**