#include "lts_plugin_loader.h" #include #include #include #include #include #include #include #include #include namespace LtsPluginManager { struct LtsPluginLoaderHandler { LtsPluginLoader ltsPluginLoader; static NGL::INGLLogger &logger() { return NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("LtsPluginLoader"); } static void cleanupPluginLoader(); }; Q_GLOBAL_STATIC(LtsPluginLoaderHandler, s_ltsPluginLoader) void LtsPluginLoaderHandler::cleanupPluginLoader() { if (s_ltsPluginLoader.exists()) s_ltsPluginLoader->ltsPluginLoader.unloadAllPlugins(); } LtsPluginLoader *LtsPluginLoader::getInstance() { if (!s_ltsPluginLoader.exists()) qAddPostRoutine(&LtsPluginLoaderHandler::cleanupPluginLoader); return &s_ltsPluginLoader->ltsPluginLoader; } LtsPluginLoader::~LtsPluginLoader() { unloadAllPlugins(); } void LtsPluginLoader::unloadAllPlugins() { for (auto &pi : qAsConst(m_loadedPlugins)) { QPluginLoader *pluginLoader = pi.pluginLoader; delete pluginLoader->instance(); if (!pluginLoader->unload()) qWarning() << "~LtsPluginLoader: could not unload" << pi.pluginName << pluginLoader->errorString(); delete pluginLoader; } m_loadedPlugins.clear(); } void LtsPluginLoader::checkPlugins(const QVector &appVector, QStringList &errors ) { NGL::INGLLogger &logger = LtsPluginLoaderHandler::logger(); if (appVector.isEmpty()) { errors += QStringLiteral("No Apps available"); return; } QVector dataPlugins; QVector guiPlugins; auto addDataPluginFn = [&](const PluginInstance &pi) { dataPlugins.append(pi); return true; }; auto addGuiPluginFn = [&](const PluginInstance &pi) { guiPlugins.append(pi); return true; }; if (!loadPlugins(PluginType::DATA, addDataPluginFn)) { errors += QStringLiteral("failed to load data plugins"); return; } if (!loadPlugins(PluginType::GUI, addGuiPluginFn)) { errors += QStringLiteral("failed to load gui plugins"); return; } auto checkDuplicateIidsFn = [&](const QVector &plugins, const QString &pluginType) { typedef QMap IidMap; IidMap iids; for (auto i3 = plugins.constBegin(); i3 != plugins.constEnd(); ++i3) { const PluginInstance &pin = *i3; auto iidFound = iids.constFind(pin.pluginName); if (iidFound != iids.constEnd()) errors += QStringLiteral("Iid duplicate for %1 plugin '%2'").arg(pluginType, pin.pluginName); else iids.insert(pin.pluginName, pin.fullFileName); } }; checkDuplicateIidsFn(dataPlugins, QStringLiteral("data")); checkDuplicateIidsFn(guiPlugins, QStringLiteral("gui")); QVector plugins(dataPlugins + guiPlugins); for (auto i1 = appVector.constBegin(); i1 != appVector.constEnd(); ++i1) { const LtsAppDescriptor::AppDescription &app = *i1; const QVector &piVector = app.pluginInfos; for (auto i2 = piVector.constBegin(); i2 != piVector.constEnd(); i2++) { const LtsAppDescriptor::PluginInfo &pi = *i2; bool found = false; for (auto i3 = plugins.constBegin(); i3 != plugins.constEnd(); ++i3) { const PluginInstance &pin = *i3; if (pi.iid == pin.pluginName) { found = true; break; } } if (!found) { ERROR_LOG(logger) << "Plugin with iid '" << pi.iid << "' was requested but was not loaded!"; errors += QStringLiteral("App '%1' requires plugin '%2' that could not be found.").arg(app.appName, pi.iid); } } } return; } bool LtsPluginLoader::loadPlugins(PluginType requestedPluginType, const std::function &loadPlugin, const QStringList &requiredPlugins, const QString &specialTopicTypeFilter, const QStringList &optionalPlugins) { NGL::INGLLogger &logger = LtsPluginLoaderHandler::logger(); Q_ASSERT(requestedPluginType != PluginType::UNKNOWN); QStringList requiredPluginsList = requiredPlugins; QStringList optionalPluginsList = optionalPlugins; bool checkPluginsLists = (!requiredPluginsList.isEmpty()) || (!optionalPluginsList.isEmpty()); if (checkPluginsLists && !m_loadedPlugins.isEmpty()) { { QMutableListIterator i1(requiredPluginsList); while (i1.hasNext()) { const QString pluginName = i1.next().toLower(); QMap::iterator i2 = m_loadedPlugins.find(PluginKey(requestedPluginType, pluginName)); if (i2 != m_loadedPlugins.end()) { DEBUG_LOG(logger) << "Plugin " << pluginName << " already loaded, using that instance"; if (loadPlugin) loadPlugin(i2.value()); i1.remove(); } } } { QMutableListIterator i1(optionalPluginsList); while (i1.hasNext()) { const QString pluginName = i1.next().toLower(); QMap::iterator i2 = m_loadedPlugins.find(PluginKey(requestedPluginType, pluginName)); if (i2 != m_loadedPlugins.end()) { DEBUG_LOG(logger) << "Plugin " << pluginName << " already loaded, using that instance"; if (loadPlugin) loadPlugin(i2.value()); i1.remove(); } } } } QFileInfo libPathInfo(QCoreApplication::applicationDirPath() + QStringLiteral("/../lib/")); QString libPath(libPathInfo.absolutePath()); if (!libPathInfo.exists()) { libPathInfo.setFile(QCoreApplication::applicationFilePath()); libPath = libPathInfo.absolutePath(); } #ifdef Q_OS_WIN const QStringList nameFilter = QStringList(QStringLiteral("*Plugin*.dll")); #else const QStringList nameFilter = QStringList(QStringLiteral("*Plugin*.so")); #endif static const QStringList pluginTypes({QStringLiteral("data"), QStringLiteral("gui"), QStringLiteral("topic_type"), QStringLiteral("project"), QStringLiteral("style"), QStringLiteral("special")}); QDirIterator pluginDir(libPath, nameFilter, QDir::Files); while (pluginDir.hasNext()) { pluginDir.next(); const QString filename = pluginDir.fileName(); if (filename.contains(QStringLiteral("PluginInterface"), Qt::CaseSensitive)) continue; const QString absoluteFilePath = pluginDir.filePath(); DEBUG_LOG(logger) << "File Path: " << absoluteFilePath; QScopedPointer pluginLoader(new QPluginLoader); pluginLoader->setFileName(absoluteFilePath); const QJsonObject jObj = pluginLoader->metaData(); QJsonObject::const_iterator iid = jObj.find(QStringLiteral("IID")); if (iid == jObj.end()) { DEBUG_LOG(logger) << "Plugin '" << filename << "' has no IID !"; continue; } const QString jsonIid = iid.value().toString().toLower(); const QVector iidParameters = jsonIid.splitRef(QLatin1Char(':')); DEBUG_LOG(logger) << "Plugin " << absoluteFilePath << " provides iid '" << jsonIid; if (iidParameters.count() != 3) { WARNING_LOG(logger) << "Invalid iid "< i (requiredPluginsList); while (i.hasNext()) { const QString requiredPlugin = i.next().toLower(); if (pluginName == requiredPlugin) { found = true; i.remove(); break; } } } if (!found) { for (auto i = optionalPluginsList.constBegin(); i != optionalPluginsList.constEnd(); i++) { const QString optionalPlugin = (*i).toLower(); if (pluginName == optionalPlugin) { found = true; break; } } } if (!found) { DEBUG_LOG(logger) << "Plugin " << pluginName << " was not requested"; continue; } } if (loadPlugin && !m_loadedPlugins.isEmpty()) { QMap::iterator i = m_loadedPlugins.find(PluginKey(requestedPluginType, pluginName)); if (i != m_loadedPlugins.end()) { DEBUG_LOG(logger) << "Plugin " << pluginName << " already loaded, using that instance ("<metaObject()->className()<<")"; loadPlugin(i.value()); continue; } } QObject *plugin = pluginLoader->instance(); if (!plugin) { ERROR_LOG(logger) << "Failed to load " << filename << " - " << pluginLoader->errorString(); continue; } PluginInstance pi; pi.pluginName = pluginName; pi.fileName = filename; pi.fullFileName = absoluteFilePath; pi.plugin = plugin; pi.pluginLoader = pluginLoader.data(); if (loadPlugin && !loadPlugin(pi)) { DEBUG_LOG(logger) << "Plugin " << pluginName << " unloaded"; delete pluginLoader->instance(); pluginLoader->unload(); continue; } pi.pluginLoader = pluginLoader.take(); m_loadedPlugins.insert(PluginKey(requestedPluginType, pluginName), pi); DEBUG_LOG(logger) << "Loaded " << jsonIid << " - File: " << filename; } if (checkPluginsLists && !requiredPluginsList.isEmpty()) { WARNING_LOG(logger) << "These plugins could not be found for Plugin Type " << (int)requestedPluginType << ": " << requiredPluginsList.join(QStringLiteral(", ")); return false; } return true; } }