/** * @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 #include #include #include #include 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::iApp::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(m = new Module(moduleName))); mPlugins.append(QExplicitlySharedDataPointer(new Plugin(m, pluginName, args))); } else if (isPlugin) args.append(""); else if (xml.name() == "plugins") isPlugins = false; else if (xml.name() == "qtplugins") isQtPlugins = false; else if (xml.name() == "eVaf") isValid = false; } // End element? } // Load eVaf plugins int 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 = mPlugins.size() - 1; i >= 0; --i) mPlugins.at(i)->done(); while (!mPlugins.isEmpty()) { QExplicitlySharedDataPointer p = mPlugins.takeLast(); p->unload(); } // Unload all the modules while (!mModules.isEmpty()) { QExplicitlySharedDataPointer 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; } //------------------------------------------------------------------- Module::Module(QString const & name) : QSharedData() , mName(name) , mLoader(0) , mRoot(0) , mPlugin(0) , mPluginFactory(0) {} Module::~Module() { if (mLoader) { mLoader->unload(); delete mLoader; } } bool Module::load() { // The real file name with path QString fileName = Common::iApp::instance()->binDir() + expandPluginName(mName); // Try to load the module QScopedPointer 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(root)) == 0) { // If not, then it has to implement the iPlugin interface if (qobject_cast(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; mPluginFactory = 0; mPlugin = 0; if (mLoader) { mLoader->unload(); delete mLoader; mLoader = 0; } } iPlugin * Module::create(QString const & name) { // If the module is not loaded, load it now if (!mLoader) { if (!load()) return 0; } iPlugin * i = 0; // Does the module implement the iPluginFactory interface? if (mPluginFactory) { // Use the iPluginFactory interface to create the requested interface i = qobject_cast(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(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() { } bool Plugin::load() { mPlugin = mModule->create(mName); if (mPlugin && !mPlugin->objectName().isEmpty()) mName = mPlugin->objectName(); return mPlugin != 0; } void Plugin::unload() { mPlugin = 0; } bool Plugin::init() { if (!mPlugin) return false; return mPlugin->init(mArgs); } void Plugin::done() { if (mPlugin) mPlugin->done(); }