#include "lts_plugin_loader.h" #include #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 : std::as_const(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 QStringList iidParameters = QString(jsonIid).split(QLatin1Char(':')); DEBUG_LOG(logger) << "Plugin " << absoluteFilePath << " provides iid '" << jsonIid; if (iidParameters.count() != 3) { WARNING_LOG(logger) << "Invalid iid " << jsonIid << " for plugin " << filename; continue; } bool ok = false; int pluginLoaderVersion = iidParameters.at(1).toInt(&ok); if (!ok || pluginLoaderVersion != PLUGIN_LOADER_VERSION) { WARNING_LOG(logger) << "Invalid plugin loader version" << pluginLoaderVersion << "for plugin:" << filename; continue; } PluginType pluginType = PluginType::UNKNOWN; const QString pluginName = iidParameters.at(2); const QString pluginTypeString = iidParameters.at(0); switch (pluginTypes.indexOf(pluginTypeString)) { case 0: pluginType = PluginType::DATA; break; case 1: pluginType = PluginType::GUI; break; case 2: pluginType = PluginType::TOPIC_TYPE; break; case 3: pluginType = PluginType::PROJECT; break; case 4: pluginType = PluginType::STYLE; break; case 5: pluginType = PluginType::SPECIAL; break; default: break; } if (pluginType != requestedPluginType) { DEBUG_LOG(logger) << "Plugin provides not the requested type " << (int)requestedPluginType << " is: " << (int)pluginType; continue; } if (pluginType == PluginType::SPECIAL && !specialTopicTypeFilter.isEmpty()) { if (!pluginName.startsWith(specialTopicTypeFilter)) { DEBUG_LOG(logger) << "Special plugin, but filter does not match: " << pluginName << " - " << specialTopicTypeFilter; continue; } } if (checkPluginsLists) { bool found = false; { QMutableListIterator 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 (" << i.value().plugin->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; } } // namespace LtsPluginManager