450 lines
11 KiB
C++
Executable File
450 lines
11 KiB
C++
Executable File
|
|
|
|
|
|
#include "lts_plugin_loader.h"
|
|
|
|
#include <NGLLogger/NGLLoggerBootstrap.h>
|
|
#include <LtsAppDescriptor/lts_app_descriptor.h>
|
|
|
|
#include <QCoreApplication>
|
|
#include <QDirIterator>
|
|
#include <QFileInfo>
|
|
#include <QJsonObject>
|
|
#include <QPluginLoader>
|
|
#include <QMutableListIterator>
|
|
|
|
#include <QDebug>
|
|
|
|
|
|
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<LtsAppDescriptor::AppDescription> &appVector,
|
|
QStringList &errors )
|
|
{
|
|
NGL::INGLLogger &logger = LtsPluginLoaderHandler::logger();
|
|
|
|
if (appVector.isEmpty())
|
|
{
|
|
errors += QStringLiteral("No Apps available");
|
|
return;
|
|
}
|
|
|
|
|
|
QVector<PluginInstance> dataPlugins;
|
|
QVector<PluginInstance> 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<PluginInstance> &plugins, const QString &pluginType)
|
|
{
|
|
typedef QMap<QString , QString > 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<PluginInstance> plugins(dataPlugins + guiPlugins);
|
|
|
|
|
|
|
|
for (auto i1 = appVector.constBegin(); i1 != appVector.constEnd(); ++i1)
|
|
{
|
|
const LtsAppDescriptor::AppDescription &app = *i1;
|
|
|
|
|
|
|
|
const QVector<LtsAppDescriptor::PluginInfo> &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<bool(const PluginInstance &)> &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<QString> i1(requiredPluginsList);
|
|
|
|
while (i1.hasNext())
|
|
{
|
|
const QString pluginName = i1.next().toLower();
|
|
|
|
QMap<PluginKey, PluginInstance>::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<QString> i1(optionalPluginsList);
|
|
|
|
while (i1.hasNext())
|
|
{
|
|
const QString pluginName = i1.next().toLower();
|
|
|
|
QMap<PluginKey, PluginInstance>::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<QPluginLoader> 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<QStringRef> iidParameters = jsonIid.splitRef(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).toString();
|
|
const QString pluginTypeString = iidParameters.at(0).toString();
|
|
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<QString> 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<PluginKey, PluginInstance>::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;
|
|
}
|
|
|
|
}
|