Przeglądaj źródła

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 lat temu
rodzic
commit
01d60beb6a
37 zmienionych plików z 2645 dodań i 320 usunięć
  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);
 
 
     /**