+/**
+ * @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();
+}