--- /dev/null
+/**
+ * @file plugins/pluginmanager.cpp
+ * @brief Implementation of the plugin manager
+ *
+ * Copyright (c) 2011 Enar Vaikene
+ *
+ * This file is part of the eVaf C++ cross-platform application development framework.
+ *
+ * This file can be used under the terms of the GNU General Public License
+ * version 3.0 as published by the Free Software Foundation and appearing in
+ * the file LICENSE included in the packaging of this file. Please review the
+ * the following information to ensure the GNU General Public License version
+ * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
+ *
+ * Alternatively, this file may be used in accordance with the Commercial License
+ * Agreement provided with the Software.
+ */
+
+#include "pluginmanager.h"
+#include "pluginmanager_p.h"
+#include "iplugin.h"
+#include "ipluginfactory.h"
+#include "version.h"
+
+#include <Common/Globals>
+#include <Common/Util>
+#include <Common/iLogger>
+#include <Common/iEnv>
+#include <Common/iApp>
+
+#include <QtCore>
+
+
+namespace eVaf {
+namespace Plugins {
+namespace Internal {
+
+ // Plugin manager interface implementation
+ static eVaf::Plugins::PluginManager * mPluginManager = 0;
+
+} // namespace eVaf::Plugins::Internal
+} // namespace eVaf::Plugins
+} // namespace eVaf
+
+
+//-------------------------------------------------------------------
+
+using namespace eVaf;
+using namespace eVaf::Plugins;
+
+PluginManager::PluginManager()
+ : QObject()
+{
+ setObjectName(QString("%1-PluginManager").arg(VER_MODULE_NAME_STR));
+
+ Internal::mPluginManager = this;
+
+ d = new Internal::PluginManagerPrivate;
+
+ EVAF_INFO("%s created", qPrintable(objectName()));
+}
+
+PluginManager::~PluginManager()
+{
+ delete d;
+
+ Internal::mPluginManager = 0;
+
+ EVAF_INFO("%s destroyed", qPrintable(objectName()));
+}
+
+PluginManager * PluginManager::instance()
+{
+ return Internal::mPluginManager;
+}
+
+bool PluginManager::init()
+{
+ // Initialize the internal implementation
+ if (!d->init())
+ return false;
+
+ EVAF_INFO("Loading plugins");
+
+ // Load and initialize plugins
+ if (!d->loadPlugins())
+ return false;
+
+ EVAF_INFO("Plugins loaded");
+
+ emit pluginsLoaded();
+
+ EVAF_INFO("%s initialized", qPrintable(objectName()));
+
+ return true;
+}
+
+void PluginManager::done()
+{
+ EVAF_INFO("Unloading plugins");
+
+ // Finalize and unload plugins
+ d->unloadPlugins();
+
+ emit pluginsUnloaded();
+
+ EVAF_INFO("Plugins unloaded");
+
+ // Finalize the internal implementation
+ d->done();
+
+ EVAF_INFO("%s finalized", qPrintable(objectName()));
+}
+
+
+//-------------------------------------------------------------------
+
+using namespace eVaf::Plugins::Internal;
+
+PluginManagerPrivate::PluginManagerPrivate()
+ : QObject()
+{
+ setObjectName(QString("%1-PluginManagerPrivate").arg(VER_MODULE_NAME_STR));
+
+ EVAF_INFO("%s created", qPrintable(objectName()));
+}
+
+PluginManagerPrivate::~PluginManagerPrivate()
+{
+ EVAF_INFO("%s destroyed", qPrintable(objectName()));
+}
+
+bool PluginManagerPrivate::init()
+{
+ EVAF_INFO("%s initialized", qPrintable(objectName()));
+
+ return true;
+}
+
+void PluginManagerPrivate::done()
+{
+ EVAF_INFO("%s finalized", qPrintable(objectName()));
+}
+
+bool PluginManagerPrivate::loadPlugins()
+{
+ // Get the name of the application's XML file
+ QString xmlFileName = Common::iEnv::instance()->etcDir() + Common::iApp::instance()->xmlFileName();
+
+ // Open the XML file
+ QFile xmlFile(xmlFileName);
+ if (!xmlFile.open(QFile::ReadOnly)) {
+ EVAF_FATAL_ERROR("Failed to open '%s' : %s", qPrintable(xmlFileName), qPrintable(xmlFile.errorString()));
+ return false;
+ }
+
+ // Process the XML file
+ QXmlStreamReader xml(&xmlFile);
+ bool isValid = false;
+ bool isPlugins = false;
+ bool isQtPlugins = false;
+ bool isPlugin = false;
+ QString moduleName;
+ QString pluginName;
+ QString args;
+ QStringList qtPlugins;
+
+ while (!xml.atEnd()) {
+ xml.readNext();
+
+ // Start element?
+ if (xml.isStartElement()) {
+
+ // Not a valid XML file yet?
+ if (!isValid) {
+ if (xml.name() == "eVaf") {
+ isValid = true;
+ }
+ else {
+ EVAF_FATAL_ERROR("'%s' is not a valid XML file for eVaf applications", qPrintable(xmlFileName));
+ return false;
+ }
+ }
+
+ // This is a valid XML file
+ else {
+
+ // No plugins or qtplugins sections yet?
+ if (!isPlugins && !isQtPlugins) {
+ if (xml.name() == "plugins") {
+ // Check for windows and linux only sections
+#ifdef Q_OS_LINUX
+ if (Common::isTrue(xml.attributes().value("windowsonly").toString()))
+ continue;
+#endif
+#ifdef Q_OS_WIN32
+ if (Common::isTrue(xml.attributes().value("linuxonly").toString()))
+ continue;
+#endif
+ isPlugins = true;
+ }
+
+ else if (xml.name() == "qtplugins") {
+ // Check for windows and linux only sections
+#ifdef Q_OS_LINUX
+ if (Common::isTrue(xml.attributes().value("windowsonly").toString()))
+ continue;
+#endif
+#ifdef Q_OS_WIN32
+ if (Common::isTrue(xml.attributes().value("linuxonly").toString()))
+ continue;
+#endif
+ isQtPlugins = true;
+ qtPlugins.clear();
+ }
+ }
+
+ // An individual plugin?
+ else if (isPlugins && xml.name() == "plugin") {
+ // Check for windows and linux only plugins
+#ifdef Q_OS_LINUX
+ if (Common::isTrue(xml.attributes().value("windowsonly").toString())) {
+ EVAF_INFO("Plugin '%s' is for Windows only", qPrintable(xml.attributes().value("name").toString()));
+ continue;
+ }
+#endif
+#ifdef Q_OS_WIN32
+ if (Common::isTrue(xml.attributes().value("linuxonly").toString())) {
+ EVAF_INFO("Plugin '%s' is for Linux only", qPrintable(xml.attributes().value("name").toString()));
+ continue;
+ }
+#endif
+
+ pluginName = xml.attributes().value("name").toString();
+ moduleName = xml.attributes().value("filename").toString();
+
+ /// @TODO: If the file name attribute is empty, loog for the config attribute
+ if (moduleName.isEmpty())
+ continue;
+
+ isPlugin = true;
+ args.clear();
+ }
+
+ // Plugin arguments?
+ else if (isPlugin) {
+ args.append("<" + xml.name().toString());
+ for (int i = 0; i < xml.attributes().size(); ++i)
+ args.append(" " + xml.attributes().at(i).name().toString() + "=\"" + xml.attributes().at(i).value().toString() + "\"");
+ args.append(">");
+ }
+
+ // An individual Qt plugin?
+ else if (isQtPlugins && xml.name() == "plugin") {
+ // Check for windows and linux only plugins
+#ifdef Q_OS_LINUX
+ if (Common::isTrue(xml.attributes().value("windowsonly").toString())) {
+ EVAF_INFO("Qt plugin '%s' is for Windows only", qPrintable(xml.attributes().value("name").toString()));
+ continue;
+ }
+#endif
+#ifdef Q_OS_WIN32
+ if (Common::isTrue(xml.attributes().value("linuxonly").toString())) {
+ EVAF_INFO("Qt plugin '%s' is for Linux only", qPrintable(xml.attributes().value("name").toString()));
+ continue;
+ }
+#endif
+ QString name = xml.attributes().value("filename").toString();
+ if (!name.isEmpty() && !qtPlugins.contains(name))
+ qtPlugins.append(name);
+ }
+ }
+ } // Start element?
+
+ // End element?
+ else if (xml.isEndElement()) {
+ if (isPlugin && xml.name() == "plugin") {
+ isPlugin = false;
+ Module * m = moduleByName(moduleName);
+ if (!m)
+ mModules.append(QExplicitlySharedDataPointer<Module>(m = new Module(moduleName)));
+ mPlugins.append(QExplicitlySharedDataPointer<Plugin>(new Plugin(m, pluginName, args)));
+ }
+ else if (isPlugin)
+ args.append("</" + xml.name().toString() + ">");
+ else if (xml.name() == "plugins")
+ isPlugins = false;
+ else if (xml.name() == "qtplugins")
+ isQtPlugins = false;
+ else if (xml.name() == "eVaf")
+ isValid = false;
+ } // End element?
+ }
+
+ // Load Qt plugins
+ int i;
+ for (i = 0; i < qtPlugins.size(); ++i) {
+ loadQtPlugin(qtPlugins.at(i));
+ }
+
+ // Load eVaf plugins
+ i = 0;
+ while (i < mPlugins.size()) {
+ if (!mPlugins.at(i)->load()) {
+ EVAF_ERROR("Failed to load module '%s'", qPrintable(mPlugins.at(i)->name()));
+ mPlugins.removeAt(i);
+ }
+ else
+ ++i;
+ }
+
+ // Initialize eVaf plugins
+ i = 0;
+ while (i < mPlugins.size()) {
+ if (mPlugins.at(i)->init()) {
+ EVAF_ERROR("Failed to initialize module '%s'", qPrintable(mPlugins.at(i)->name()));
+ mPlugins.removeAt(i);
+ }
+ else
+ ++i;
+ }
+
+ return true;
+}
+
+void PluginManagerPrivate::unloadPlugins()
+{
+ // Finalize all the plugins
+ for (int i = 0; i < mPlugins.size(); ++i)
+ mPlugins.at(i)->done();
+ while (!mPlugins.isEmpty()) {
+ QExplicitlySharedDataPointer<Plugin> p = mPlugins.takeLast();
+ p->unload();
+ }
+
+ // Unload all the modules
+ while (!mModules.isEmpty()) {
+ QExplicitlySharedDataPointer<Module> m = mModules.takeLast();
+ m->unload();
+ }
+}
+
+Module * PluginManagerPrivate::moduleByName(QString const & name) const
+{
+ for (int i = 0; i < mModules.size(); ++i) {
+ if (mModules.at(i)->name() == name)
+ return mModules.at(i).data();
+ }
+ return 0;
+}
+
+bool PluginManagerPrivate::loadQtPlugin(QString const & name) const
+{
+ // Get the Qt plugin file name with the full path
+ QString fileName;
+
+#ifdef Q_OS_LINUX
+ fileName = QString("%1libq%2.so").arg(Common::iEnv::instance()->qtPluginsDir()).arg(name);
+# ifndef QT_NO_DEBUG
+ QString t = QString("%1libq%2.so.debug").arg(Common::iEnv::instance()->qtPluginsDir()).arg(name);
+ if (QFile::exists(t))
+ fileName = t;
+# endif
+#endif
+
+#ifdef Q_OS_WIN32
+ fileName = QString("%2q%2%3").arg(Common::iEnv::instance()->qtPluginsDir()).arg(name).arg("4.dll");
+# ifndef QT_NO_DEBUG
+ QString t = QString(%1q%2%3).arg(Common::iEnv::instance()->qtPluginsDir()).arg(name).arg(d4.dll);
+ if (!QFile::exists(t))
+ fileName = t;
+# endif
+#endif
+
+ if (fileName.isEmpty()) {
+ EVAF_ERROR("Don\'t know how to load Qt plugin '%s'", qPrintable(name));
+ return false;
+ }
+
+ EVAF_INFO("Loading Qt plugin '%s'", qPrintable(fileName));
+
+ QLibrary lib(fileName);
+ void * fn = lib.resolve("qt_plugin_instance");
+ if (fn) {
+ qRegisterStaticPluginInstanceFunction(QtPluginInstanceFunction(fn));
+ return true;
+ }
+ else {
+ EVAF_ERROR("Failed to load Qt plugin '%s' : %s", qPrintable(fileName), qPrintable(lib.errorString()));
+ return false;
+ }
+}
+
+
+//-------------------------------------------------------------------
+
+Module::Module(QString const & name)
+ : QSharedData()
+ , mName(name)
+ , mLoader(0)
+ , mRoot(0)
+ , mPlugin(0)
+ , mPluginFactory(0)
+{}
+
+Module::~Module()
+{
+ if (mPluginFactory)
+ delete mPluginFactory;
+ if (mLoader)
+ delete mLoader;
+}
+
+bool Module::load()
+{
+ // The real file name with path
+ QString fileName = Common::iEnv::instance()->binDir() + expandPluginName(mName);
+
+ // Try to load the module
+ QScopedPointer<QPluginLoader> p(new QPluginLoader(fileName));
+ if (!p->load()) {
+ EVAF_FATAL_ERROR("Failed to load '%s' : %s", qPrintable(mName), qPrintable(p->errorString()));
+ return false;
+ }
+
+ // Get the root component
+ QObject * root = p->instance();
+
+ // Does the module implement the iPluginFactory interface?
+ if ((mPluginFactory = qobject_cast<iPluginFactory *>(root)) == 0) {
+
+ // If not, then it has to implement the iPlugin interface
+ if ((mPlugin = qobject_cast<iPlugin *>(root)) == 0) {
+ EVAF_FATAL_ERROR("Module '%s' is not a valid eVaf module", qPrintable(mName));
+ return false;
+ }
+ }
+
+ mRoot = root;
+ mLoader = p.take();
+ return true;
+}
+
+void Module::unload()
+{
+ mRoot = 0;
+
+ if (mPluginFactory) {
+ delete mPluginFactory;
+ mPluginFactory = 0;
+ }
+ mPlugin = 0;
+ if (mLoader) {
+ delete mLoader;
+ mLoader = 0;
+ }
+}
+
+iPlugin * Module::create(QString const & name)
+{
+ // If the module is not loaded, load it now
+ if (!mLoader) {
+ if (!load())
+ return false;
+ }
+
+ iPlugin * i = 0;
+
+ // Does the module implement the iPluginFactory interface?
+ if (mPluginFactory) {
+ // Use the iPluginFactory interface to create the requested interface
+ i = qobject_cast<iPlugin *>(mPluginFactory->create(name));
+ if (i == 0) {
+ EVAF_FATAL_ERROR("Module '%s' failed to create the iPlugin interface with name '%s'", qPrintable(mName), qPrintable(name));
+ return 0;
+ }
+ }
+
+ // Otherwise use the root component, but only once
+ else {
+ if (mPlugin) {
+ EVAF_FATAL_ERROR("Module '%s' can implement only one iPlugin interface and one with the name '%s' is already created",
+ qPrintable(mName), qPrintable(mPlugin->objectName()));
+ return 0;
+ }
+
+ i = qobject_cast<iPlugin *>(mRoot);
+ if (i == 0) {
+ EVAF_FATAL_ERROR("Module '%s' does not implement the iPlugin interface", qPrintable(mName));
+ return 0;
+ }
+
+ mPlugin = i;
+ }
+
+ return i;
+}
+
+
+//-------------------------------------------------------------------
+
+Plugin::Plugin(Module * module, QString const & name, QString const & args)
+ : QSharedData()
+ , mModule(module)
+ , mName(name)
+ , mArgs(args)
+ , mPlugin(0)
+{}
+
+Plugin::~Plugin()
+{
+ if (mPlugin)
+ delete mPlugin;
+}
+
+bool Plugin::load()
+{
+ mPlugin = mModule->create(mName);
+ if (mPlugin && !mPlugin->objectName().isEmpty())
+ mName = mPlugin->objectName();
+ return mPlugin != 0;
+}
+
+void Plugin::unload()
+{
+ if (mPlugin) {
+ delete mPlugin;
+ mPlugin = 0;
+ }
+}
+
+bool Plugin::init()
+{
+ if (!mPlugin)
+ return false;
+ return mPlugin->init(mArgs);
+}
+
+void Plugin::done()
+{
+ if (mPlugin)
+ mPlugin->done();
+}
--- /dev/null
+/**
+ * @file plugins/pluginmanager_p.h
+ * @brief Private implementation of the plugin manager
+ *
+ * Copyright (c) 2011 Enar Vaikene
+ *
+ * This file is part of the eVaf C++ cross-platform application development framework.
+ *
+ * This file can be used under the terms of the GNU General Public License
+ * version 3.0 as published by the Free Software Foundation and appearing in
+ * the file LICENSE included in the packaging of this file. Please review the
+ * the following information to ensure the GNU General Public License version
+ * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
+ *
+ * Alternatively, this file may be used in accordance with the Commercial License
+ * Agreement provided with the Software.
+ */
+
+#ifndef __PLUGINS_PLUGINMANAGER_P_H
+#define __PLUGINS_PLUGINMANAGER_P_H
+
+#include <QObject>
+#include <QSharedData>
+#include <QExplicitlySharedDataPointer>
+#include <QPluginLoader>
+
+namespace eVaf {
+namespace Plugins {
+
+class iPlugin;
+class iPluginFactory;
+
+namespace Internal {
+
+class Plugin;
+class Module;
+
+/**
+ * Internal implementation of the plugin manager
+ */
+class PluginManagerPrivate : public QObject
+{
+ Q_OBJECT
+
+public:
+
+ /// Ctr.
+ PluginManagerPrivate();
+
+ /// Dtr.
+ virtual ~PluginManagerPrivate();
+
+ /**
+ * Initializes the private plugin manager object
+ * @return True; false if failed
+ */
+ bool init();
+
+ /**
+ * Finalizes the private plugin manager object
+ */
+ void done();
+
+ /**
+ * Loads and initializes plugins
+ * @return True; false if failed
+ */
+ bool loadPlugins();
+
+ /**
+ * Finalizes and unloads plugins
+ */
+ void unloadPlugins();
+
+
+private: // Members
+
+ /// List of all the modules
+ QList<QExplicitlySharedDataPointer<Module> > mModules;
+
+ /// List of all the plugins
+ QList<QExplicitlySharedDataPointer<Plugin> > mPlugins;
+
+
+private: // Methods
+
+ /**
+ * Returns the module object by its name
+ * @param name Name of the module
+ * @return Module object or 0 if not found
+ */
+ Module * moduleByName(QString const & name) const;
+
+ /**
+ * Loads the Qt plugin
+ * @param Name of the Qt plugin
+ * @return True; false if failed
+ */
+ bool loadQtPlugin(QString const & name) const;
+
+};
+
+/**
+ * One external module implementing the iPluginFactory or the iPlugin interfaces.
+ *
+ * This class is a wrapper around the external module.
+ */
+class Module : public QSharedData
+{
+public:
+
+ /**
+ * Ctr.
+ * @param name Name of the module
+ */
+ Module(QString const & name);
+
+ /// Dtr.
+ ~Module();
+
+ /// Returns true if the module is loaded
+ bool isLoaded() const { return mLoader != 0; }
+
+ /// The name of the module
+ QString const & name() const { return mName; }
+
+ /**
+ * Loads the module
+ * @return True; false if failed
+ */
+ bool load();
+
+ /// Unloads the module
+ void unload();
+
+ /**
+ * Creates the requested iPlugin interface object
+ * @param name Name of the interface
+ * @return The iPlugin interface object or 0 if failed
+ */
+ iPlugin * create(QString const & name);
+
+
+private: // Members
+
+ /// Name of the module
+ QString mName;
+
+ /// Plugin loader
+ QPluginLoader * mLoader;
+
+ /// Plugin's root component
+ QObject * mRoot;
+
+ /// The iPlugin interface object if the module implements only one iPlugin interface
+ iPlugin * mPlugin;
+
+ /// The iPluginFactory interface object if the module implements more than one iPluginFactory interface
+ iPluginFactory * mPluginFactory;
+
+};
+
+/**
+ * One iPlugin interface object.
+ *
+ * This class is a wrapper around the plugin.
+ */
+class Plugin : public QSharedData
+{
+public:
+
+ /**
+ * Ctr.
+ * @param module The Module implementing this iPlugin interface
+ * @param name Name of the plugin
+ * @param args Arguments for the plugin initialization
+ */
+ Plugin(Module * module, QString const & name, QString const & args);
+
+ /// Dtr.
+ ~Plugin();
+
+ /// The iPlugin interface
+ iPlugin * plugin() const { return mPlugin; }
+
+ /// The name of the plugin
+ QString const & name() const { return mName; }
+
+ /**
+ * Loads the plugin
+ * @return True; false if failed
+ */
+ bool load();
+
+ /// Unloads the plugin
+ void unload();
+
+ /**
+ * Initializes the plugin
+ * @return True; false if failed
+ */
+ bool init();
+
+ /// Uninitializes the plugin
+ void done();
+
+
+private: // Members
+
+ /// Module implementing this iPlugin interface
+ Module * mModule;
+
+ /// Name of the plugin
+ QString mName;
+
+ /// Arguments for the initialization
+ QString mArgs;
+
+ /// The iPlugin interface object
+ iPlugin * mPlugin;
+
+};
+
+} // namespace eVaf::Plugins::Internal
+} // namespace eVaf::Plugins
+} // namespace eVaf
+
+#endif // pluginmanager_p.h