Browse Source

add plugin loader

xuqiang 5 months ago
parent
commit
999153b757
4 changed files with 401 additions and 0 deletions
  1. 30 0
      include/pluginloader.h
  2. 45 0
      include/pluginmanager.h
  3. 86 0
      src/pluginloader.cpp
  4. 240 0
      src/pluginmanager.cpp

+ 30 - 0
include/pluginloader.h

@@ -0,0 +1,30 @@
+#ifndef __PLUGINLOADER_H__
+#define __PLUGINLOADER_H__
+
+#include <QObject>
+#include <QPluginLoader>
+#include <QDesignerCustomWidgetInterface>
+
+class PluginLoader : public QObject
+{
+    Q_OBJECT
+
+public:
+    explicit PluginLoader(QString fileName, QObject *parent = nullptr);
+    ~PluginLoader(); 
+
+    bool load();
+    bool unload();
+    bool isLoaded() const;
+    const QObject* instance();
+    QString fileName() const;
+    QString errorString() const;
+    QList<QDesignerCustomWidgetInterface *> customWidgets() const;
+
+private:
+    QPluginLoader *m_pLoader;
+    QList<QDesignerCustomWidgetInterface*> m_customWidgets;    // 插件控件列表
+
+};
+
+#endif  // PLUGINLOADER_H

+ 45 - 0
include/pluginmanager.h

@@ -0,0 +1,45 @@
+#ifndef __PLUGINMANAGER_H__
+#define __PLUGINMANAGER_H__
+
+
+#include <QObject>
+#include <QMap>
+#include <QDesignerCustomWidgetInterface>
+#include "pluginloader.h"
+
+typedef struct
+{
+    QString fileName;
+    QString filePath;
+    bool isEnabled;
+}plugin_info_st;
+
+class PluginManager : public QObject
+{
+    Q_OBJECT
+
+public:
+    explicit PluginManager(QObject *parent = nullptr);
+    ~PluginManager();
+
+    void initPluginInfo();
+    void savePluginInfo();
+    void loadPlugins();
+    void loadPlugin(QString fileName);
+    void unloadPlugin(QString fileName);
+    bool delPlugin(QString fileName, bool kepInfo = false);
+    bool upgradePlugin(QString fileName, QString srcFile);
+    bool addPlugin(QString srcFile);
+    bool contains(QString fileName);
+    void clear();
+
+    QList<plugin_info_st> pluginInfoList() const;
+    QList<QDesignerCustomWidgetInterface *> customWidgets() const;
+
+private:
+    QMap<QString, plugin_info_st> m_pluginInfoMap;
+    QMap<QString, PluginLoader *> m_pluginLoaderMap;
+    QList<QDesignerCustomWidgetInterface*> m_customWidgets;     // 插件控件列表
+};
+
+#endif  // PLUGINMANAGER_H

+ 86 - 0
src/pluginloader.cpp

@@ -0,0 +1,86 @@
+#include "pluginloader.h"
+
+PluginLoader::PluginLoader(QString fileName, QObject *parent)
+    : QObject{parent}
+    , m_pLoader(new QPluginLoader(fileName, parent))
+{
+    /**
+     *  QPluginLoader 默认持有 PreventUnloadHint 标志,该标志阻止 DLL 被真正卸载
+     *  使用 setLoadHints 清除 PreventUnloadHint 标志
+     */
+
+    m_pLoader->setLoadHints(QLibrary::ResolveAllSymbolsHint);
+}
+
+PluginLoader::~PluginLoader()
+{
+    if(m_pLoader->isLoaded())
+        m_pLoader->unload();
+    delete m_pLoader;
+    m_customWidgets.clear();
+}
+
+bool PluginLoader::load()
+{
+    if(m_pLoader->load())
+    {
+        QObject *instance = m_pLoader->instance();
+
+        if(auto collection = qobject_cast<QDesignerCustomWidgetCollectionInterface *>(instance))
+        {
+            m_customWidgets << collection->customWidgets();
+        }
+        else if(auto single = qobject_cast<QDesignerCustomWidgetInterface *>(instance))
+        {
+            m_customWidgets << single;
+        }
+        else
+        {
+            return false;
+        }
+        return true;
+    }
+    return false;
+}
+
+bool PluginLoader::unload()
+{
+    m_customWidgets.clear();
+    if(m_pLoader)
+        return m_pLoader->unload();
+    return false;
+}
+
+bool PluginLoader::isLoaded() const
+{
+    if(m_pLoader)
+        return m_pLoader->isLoaded();
+    return false;
+}
+
+const QObject *PluginLoader::instance()
+{
+    if(m_pLoader)
+        return m_pLoader->instance();
+    return nullptr;
+}
+
+QString PluginLoader::fileName() const
+{
+    if(m_pLoader)
+        return m_pLoader->fileName();
+    return QString();
+}
+
+QString PluginLoader::errorString() const
+{
+    if(m_pLoader)
+        return m_pLoader->errorString();
+    else
+        return QString("plugin loader is nullptr.");
+}
+
+QList<QDesignerCustomWidgetInterface *> PluginLoader::customWidgets() const
+{
+    return m_customWidgets;
+}

+ 240 - 0
src/pluginmanager.cpp

@@ -0,0 +1,240 @@
+#include "pluginmanager.h"
+#include <QDir>
+#include <QJsonParseError>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+#include "logger.h"
+
+PluginManager::PluginManager(QObject *parent)
+    : QObject{parent}
+{
+    initPluginInfo();
+    loadPlugins();
+}
+
+PluginManager::~PluginManager()
+{
+    savePluginInfo();
+    clear();
+}
+
+void PluginManager::initPluginInfo()
+{
+    QString fileName = QString("%1/%2").arg(QDir::currentPath(), "plugininfo.json");
+    QFile file(fileName);
+    if(file.exists() && file.open(QIODevice::ReadOnly))
+    {
+        QJsonParseError error;
+        QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error);
+        if(error.error == QJsonParseError::NoError && doc.isObject())
+        {
+            QJsonObject obj = doc.object();
+            QJsonArray array = obj["configure"].toArray();
+            foreach(auto item, array)
+            {
+                LOG_INFO("record plugin name: {}", item["name"].toString().toUtf8().data());
+                LOG_INFO("record plugin path: {}", item["path"].toString().toUtf8().data());
+                LOG_INFO("record plugin enabled: {}", item["enabled"].toBool());
+
+                QFileInfo fileInfo(item["path"].toString());
+                if(fileInfo.exists())
+                {
+                    plugin_info_st info;
+                    info.fileName = item["name"].toString();
+                    info.filePath = item["path"].toString();
+                    info.isEnabled = item["enabled"].toBool();
+                    m_pluginInfoMap.insert(info.fileName, info);
+                }
+            }
+        }
+    }
+
+    QDir dir;
+    QString path;
+    QFileInfoList fileInfoList;
+    path = QString("%1/%2").arg(QDir::currentPath(), "plugins");
+    dir.setPath(path);
+    fileInfoList = dir.entryInfoList(QStringList() << "*.so", QDir::Files | QDir::NoSymLinks);
+
+    foreach(auto fileInfo, fileInfoList)
+    {
+        if(m_pluginInfoMap.contains(fileInfo.fileName()))
+            continue;
+        plugin_info_st info;
+        info.fileName = fileInfo.fileName();
+        info.filePath = fileInfo.filePath();
+        info.isEnabled = true;
+        m_pluginInfoMap.insert(fileInfo.fileName(), info);
+    }
+
+    savePluginInfo();
+}
+
+void PluginManager::savePluginInfo()
+{
+    QJsonObject info;
+    QJsonArray array;
+
+    QList<plugin_info_st> pluginInfoList = m_pluginInfoMap.values();
+    foreach(auto info, pluginInfoList)
+    {
+        QJsonObject obj;
+        obj["name"] = info.fileName;
+        obj["path"] = info.filePath;
+        obj["enabled"] = info.isEnabled;
+        array.push_back(obj);
+    }
+    info["configure"] = array;
+
+    QJsonDocument doc(info);
+    QString fileName = QString("%1/%2").arg(QDir::currentPath(), "plugininfo.json");
+    QFile file(fileName);
+    if(file.open(QIODevice::ReadWrite | QIODevice::Truncate))
+    {
+        file.write(doc.toJson(QJsonDocument::Indented));
+    }
+    else
+    {
+        LOG_ERROR("{}", file.errorString().toUtf8().data());
+    }
+}
+
+void PluginManager::loadPlugins()
+{
+    QList<plugin_info_st> pluginInfoList = m_pluginInfoMap.values();
+    foreach(auto info, pluginInfoList)
+    {
+        PluginLoader *pPluginLoader = new PluginLoader(info.filePath);
+        if(pPluginLoader->load())
+        {
+            if(info.isEnabled)
+                m_customWidgets << pPluginLoader->customWidgets();
+            else
+                pPluginLoader->unload();
+            m_pluginLoaderMap.insert(info.fileName, pPluginLoader);
+        }
+        else
+        {
+            LOG_ERROR("plugin loader load failed: {}", info.filePath.toUtf8().data());
+            delete pPluginLoader;
+            pPluginLoader = nullptr;
+        }
+    }
+}
+
+void PluginManager::loadPlugin(QString fileName)
+{
+    auto it = m_pluginLoaderMap.find(fileName);
+    if(it == m_pluginLoaderMap.end())
+    {
+        plugin_info_st info = m_pluginInfoMap.value(fileName);
+        PluginLoader *pPluginLoader = new PluginLoader(info.filePath);
+        if(pPluginLoader->load())
+        {
+            if(info.isEnabled)
+                m_customWidgets << pPluginLoader->customWidgets();
+            else
+                pPluginLoader->unload();
+            m_pluginLoaderMap.insert(info.fileName, pPluginLoader);
+        }
+    }
+    else
+    {
+        it.value()->load();
+        QList<QDesignerCustomWidgetInterface *> widgets = it.value()->customWidgets();
+        m_customWidgets << widgets;
+    }
+}
+
+void PluginManager::unloadPlugin(QString fileName)
+{
+    auto it = m_pluginLoaderMap.find(fileName);
+    if(it == m_pluginLoaderMap.end())
+        return;
+    QList<QDesignerCustomWidgetInterface *> widgets = it.value()->customWidgets();
+    foreach(auto widget, widgets)
+    {
+        if(m_customWidgets.contains(widget))
+            m_customWidgets.removeOne(widget);
+    }
+    it.value()->unload();
+}
+
+bool PluginManager::delPlugin(QString fileName, bool keepInfo)
+{
+    auto it = m_pluginLoaderMap.find(fileName);
+    if(it == m_pluginLoaderMap.end())
+        return false;
+    QFile file(it.value()->fileName());
+    QList<QDesignerCustomWidgetInterface *> widgets = it.value()->customWidgets();
+    foreach(auto widget, widgets)
+    {
+        if(m_customWidgets.contains(widget))
+            m_customWidgets.removeOne(widget);
+    }
+    delete it.value();
+    it.value() = nullptr;
+    m_pluginLoaderMap.erase(it);
+    if(!keepInfo)
+        m_pluginInfoMap.remove(fileName);
+    return file.remove();
+}
+
+bool PluginManager::upgradePlugin(QString fileName, QString srcFile)
+{
+    bool ret;
+    ret = delPlugin(fileName, true);
+    if(ret)
+    {
+        QString dstFile = QString("%1/%2").arg(QDir::currentPath(), fileName);
+        ret = QFile::copy(srcFile, dstFile);
+        if(ret)
+        {
+            auto it = m_pluginInfoMap.find(fileName);
+            if(it != m_pluginInfoMap.end() && it.value().isEnabled)
+                loadPlugin(fileName);
+        }
+    }
+    return ret;
+}
+
+bool PluginManager::addPlugin(QString srcFile)
+{
+    QFileInfo fileInfo(srcFile);
+    QString dstFile = QString("%1/%2").arg(QDir::currentPath(), fileInfo.fileName());
+    bool ret =  QFile::copy(srcFile, dstFile);
+    if(ret)
+    {
+        plugin_info_st info;
+        info.fileName = fileInfo.fileName();
+        info.filePath = dstFile;
+        info.isEnabled = true;
+        m_pluginInfoMap.insert(info.fileName, info);
+        loadPlugin(info.fileName);
+    }
+    return ret;
+}
+
+bool PluginManager::contains(QString fileName)
+{
+    return m_pluginInfoMap.contains(fileName);
+}
+
+void PluginManager::clear()
+{
+    qDeleteAll(m_pluginLoaderMap);
+    m_pluginLoaderMap.clear();
+    m_customWidgets.clear();
+    m_pluginInfoMap.clear();
+}
+
+QList<plugin_info_st> PluginManager::pluginInfoList() const
+{
+    return m_pluginInfoMap.values();
+}
+
+QList<QDesignerCustomWidgetInterface *> PluginManager::customWidgets() const
+{
+    return m_customWidgets;
+}