File indexing completed on 2024-05-19 05:42:14

0001 // ct_lvtplg_pluginmanager.cpp                                       -*-C++-*-
0002 
0003 /*
0004 // Copyright 2023 Codethink Ltd <codethink@codethink.co.uk>
0005 // SPDX-License-Identifier: Apache-2.0
0006 //
0007 // Licensed under the Apache License, Version 2.0 (the "License");
0008 // you may not use this file except in compliance with the License.
0009 // You may obtain a copy of the License at
0010 //
0011 //     http://www.apache.org/licenses/LICENSE-2.0
0012 //
0013 // Unless required by applicable law or agreed to in writing, software
0014 // distributed under the License is distributed on an "AS IS" BASIS,
0015 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0016 // See the License for the specific language governing permissions and
0017 // limitations under the License.
0018 */
0019 
0020 #include <ct_lvtplg_basicpluginhandlers.h>
0021 #include <ct_lvtplg_basicpluginhooks.h>
0022 #include <ct_lvtplg_pluginmanager.h>
0023 #ifdef ENABLE_PYTHON_PLUGINS
0024 #include <ct_lvtplg_pythonlibrarydispatcher.h>
0025 #endif
0026 #include <ct_lvtplg_sharedlibrarydispatcher.h>
0027 
0028 #include <KPluginMetaData>
0029 #include <QCoreApplication>
0030 
0031 namespace {
0032 template<typename HookFunctionType, typename HandlerType>
0033 void callSingleHook(const std::string& hookName,
0034                     HandlerType&& handler,
0035                     Codethink::lvtplg::AbstractLibraryDispatcher *library)
0036 {
0037     if (!library->isEnabled()) {
0038         return;
0039     }
0040 
0041     auto resolveContext = library->resolve(hookName);
0042     if (resolveContext->hook) {
0043         reinterpret_cast<HookFunctionType>(resolveContext->hook)(&handler);
0044     }
0045 }
0046 
0047 template<typename HookFunctionType, typename HandlerType>
0048 void callAllHooks(
0049     const std::string& hookName,
0050     HandlerType&& handler,
0051     std::unordered_map<std::string, std::unique_ptr<Codethink::lvtplg::AbstractLibraryDispatcher>>& libraries)
0052 {
0053     QDebug dbg(QtDebugMsg);
0054     for (auto const& [_, pluginLib] : libraries) {
0055         if (!pluginLib->isEnabled()) {
0056             continue;
0057         }
0058 
0059         callSingleHook<HookFunctionType, HandlerType>(hookName, std::forward<HandlerType>(handler), pluginLib.get());
0060     }
0061 }
0062 
0063 template<typename DispatcherType>
0064 void tryInstallPlugin(
0065     QString const& pluginDir,
0066     std::unordered_map<std::string, std::unique_ptr<Codethink::lvtplg::AbstractLibraryDispatcher>>& libraries)
0067 {
0068     // TODO: This should check if the plugin is already loaded before attempting
0069     // to load it (because of knewstuff).
0070     QDebug dbg(QtDebugMsg);
0071 
0072     dbg << "(" << DispatcherType::name << "): Trying to install plugin " << pluginDir << "... ";
0073     if (!DispatcherType::isValidPlugin(pluginDir)) {
0074         dbg << "Invalid plugin.";
0075         return;
0076     }
0077 
0078     auto lib = DispatcherType::loadSinglePlugin(pluginDir);
0079     if (!lib) {
0080         dbg << "Could not load *valid* plugin: " << pluginDir;
0081         return;
0082     }
0083 
0084     auto id = lib->pluginId();
0085     libraries[id] = std::move(lib);
0086     dbg << "Plugin successfully loaded!";
0087 }
0088 
0089 template<typename DispatcherType>
0090 void tryReloadPlugin(
0091     QString const& pluginDir,
0092     std::unordered_map<std::string, std::unique_ptr<Codethink::lvtplg::AbstractLibraryDispatcher>>& libraries)
0093 {
0094     QDebug dbg(QtDebugMsg);
0095 
0096     dbg << "(" << DispatcherType::name << "): Trying to reload plugin " << pluginDir << "... ";
0097     const bool res = DispatcherType::reloadSinglePlugin(pluginDir);
0098     if (!res) {
0099         dbg << "Could not load *valid* plugin: " << pluginDir;
0100         return;
0101     }
0102 }
0103 
0104 void loadSinglePlugin(
0105     const QString& pluginDir,
0106     std::unordered_map<std::string, std::unique_ptr<Codethink::lvtplg::AbstractLibraryDispatcher>>& libraries)
0107 {
0108     tryInstallPlugin<Codethink::lvtplg::SharedLibraryDispatcher>(pluginDir, libraries);
0109 #ifdef ENABLE_PYTHON_PLUGINS
0110     tryInstallPlugin<Codethink::lvtplg::PythonLibraryDispatcher>(pluginDir, libraries);
0111 #endif
0112 }
0113 
0114 } // namespace
0115 namespace Codethink::lvtplg {
0116 
0117 void PluginManager::loadPlugins(const QList<QString>& searchPaths)
0118 {
0119     for (const auto& pluginsStr : searchPaths) {
0120         QDir pluginsPath(pluginsStr);
0121 
0122         qDebug() << "Loading plugins from " << pluginsPath.path() << "...";
0123         if (!pluginsPath.exists()) {
0124             qDebug() << pluginsPath.path() << "does not exists, skipping plugin search for it.";
0125             continue;
0126         }
0127         if (!pluginsPath.isReadable()) {
0128             qDebug() << pluginsPath.path() << "exists but it's not readable, skipping plugin search for it.";
0129             continue;
0130         }
0131 
0132         pluginsPath.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
0133         pluginsPath.setSorting(QDir::Name);
0134         const auto entryInfoList = pluginsPath.entryInfoList();
0135         qDebug() << entryInfoList.size() << "is the size of the number of files here";
0136         for (auto const& fileInfo : qAsConst(entryInfoList)) {
0137             qDebug() << "\tTesting fileInfo" << fileInfo;
0138             const auto pluginDir = fileInfo.absoluteFilePath();
0139             loadSinglePlugin(pluginDir, libraries);
0140         }
0141     }
0142 
0143     qDebug() << "Loaded plugins:";
0144     for (auto const& [_, p] : this->libraries) {
0145         qDebug() << "+ " << QString::fromStdString(p->fileName());
0146     }
0147 }
0148 
0149 void PluginManager::reloadPlugin(const QString& pluginFolder)
0150 {
0151     for (auto const& [_, p] : this->libraries) {
0152         const QString loadedLibrary = QString::fromStdString(p->fileName());
0153 
0154         if (loadedLibrary.startsWith(pluginFolder)) {
0155             removePlugin(pluginFolder);
0156             loadSinglePlugin(pluginFolder, libraries);
0157             return;
0158         }
0159     }
0160 
0161     QFileInfo pluginPath(pluginFolder);
0162     if (!pluginPath.exists()) {
0163         return;
0164     }
0165 
0166     tryInstallPlugin<SharedLibraryDispatcher>(pluginPath.absoluteFilePath(), libraries);
0167 #ifdef ENABLE_PYTHON_PLUGINS
0168     tryInstallPlugin<PythonLibraryDispatcher>(pluginPath.absoluteFilePath(), libraries);
0169 #endif
0170 }
0171 
0172 void PluginManager::removePlugin(const QString& pluginFolder)
0173 {
0174     for (auto const& [key, p] : this->libraries) {
0175         const QString loadedLibrary = QString::fromStdString(p->fileName());
0176 
0177         if (loadedLibrary.startsWith(pluginFolder)) {
0178             auto handler = PluginSetupHandler{std::bind_front(&PluginManager::registerPluginData, this),
0179                                               std::bind_front(&PluginManager::getPluginData, this),
0180                                               std::bind_front(&PluginManager::unregisterPluginData, this)};
0181 
0182             callSingleHook<hookTeardownPlugin_f>("hookTeardownPlugin", handler, p.get());
0183             p->unload();
0184             this->libraries.erase(key);
0185             return;
0186         }
0187     }
0188 }
0189 
0190 std::vector<std::string> PluginManager::getPluginsMetadataFilePaths() const
0191 {
0192     auto metadataFilePaths = std::vector<std::string>{};
0193     for (auto const& [_, lib] : this->libraries) {
0194         metadataFilePaths.push_back(lib->metadataFilePath());
0195     }
0196     return metadataFilePaths;
0197 }
0198 
0199 std::optional<std::reference_wrapper<AbstractLibraryDispatcher>>
0200 PluginManager::getPluginById(std::string const& id) const
0201 {
0202     if (!this->libraries.contains(id)) {
0203         return std::nullopt;
0204     }
0205     return *this->libraries.at(id);
0206 }
0207 
0208 void PluginManager::callHooksSetupPlugin()
0209 {
0210     callAllHooks<hookSetupPlugin_f>("hookSetupPlugin",
0211                                     PluginSetupHandler{std::bind_front(&PluginManager::registerPluginData, this),
0212                                                        std::bind_front(&PluginManager::getPluginData, this),
0213                                                        std::bind_front(&PluginManager::unregisterPluginData, this)},
0214                                     libraries);
0215 }
0216 
0217 void PluginManager::callHooksTeardownPlugin()
0218 {
0219     callAllHooks<hookTeardownPlugin_f>("hookTeardownPlugin",
0220                                        PluginSetupHandler{std::bind_front(&PluginManager::registerPluginData, this),
0221                                                           std::bind_front(&PluginManager::getPluginData, this),
0222                                                           std::bind_front(&PluginManager::unregisterPluginData, this)},
0223                                        libraries);
0224 }
0225 
0226 void PluginManager::callHooksContextMenu(getAllEntitiesInCurrentView_f const& getAllEntitiesInCurrentView,
0227                                          getEntityByQualifiedName_f const& getEntityByQualifiedName,
0228                                          getEdgeByQualifiedName_f const& getEdgeByQualifiedName,
0229                                          registerContextMenu_f const& registerContextMenu)
0230 {
0231     callAllHooks<hookGraphicsViewContextMenu_f>(
0232         "hookGraphicsViewContextMenu",
0233         PluginContextMenuHandler{std::bind_front(&PluginManager::getPluginData, this),
0234                                  getAllEntitiesInCurrentView,
0235                                  getEntityByQualifiedName,
0236                                  registerContextMenu,
0237                                  getEdgeByQualifiedName},
0238         libraries);
0239 }
0240 
0241 void PluginManager::callHooksSetupDockWidget(createPluginDock_f const& createPluginDock)
0242 {
0243     callAllHooks<hookSetupDockWidget_f>(
0244         "hookSetupDockWidget",
0245         PluginSetupDockWidgetHandler{std::bind_front(&PluginManager::getPluginData, this), createPluginDock},
0246         libraries);
0247 }
0248 
0249 void PluginManager::callHooksSetupEntityReport(getEntity_f const& getEntity, addReport_f const& addReport)
0250 {
0251     callAllHooks<hookSetupEntityReport_f>(
0252         "hookSetupEntityReport",
0253         PluginEntityReportHandler{std::bind_front(&PluginManager::getPluginData, this), getEntity, addReport},
0254         libraries);
0255 }
0256 
0257 void PluginManager::callHooksPhysicalParserOnHeaderFound(getSourceFile_f const& getSourceFile,
0258                                                          getIncludedFile_f const& getIncludedFile,
0259                                                          getLineNo_f const& getLineNo)
0260 {
0261     callAllHooks<hookPhysicalParserOnHeaderFound_f>(
0262         "hookPhysicalParserOnHeaderFound",
0263         PluginPhysicalParserOnHeaderFoundHandler{std::bind_front(&PluginManager::getPluginData, this),
0264                                                  getSourceFile,
0265                                                  getIncludedFile,
0266                                                  getLineNo},
0267         libraries);
0268 }
0269 
0270 void PluginManager::callHooksPluginLogicalParserOnCppCommentFoundHandler(getFilename_f const& getFilename,
0271                                                                          getBriefText_f const& getBriefText,
0272                                                                          getStartLine_f const& getStartLine,
0273                                                                          getEndLine_f const& getEndLine)
0274 {
0275     callAllHooks<hookLogicalParserOnCppCommentFound_f>(
0276         "hookLogicalParserOnCppCommentFound",
0277         PluginLogicalParserOnCppCommentFoundHandler{std::bind_front(&PluginManager::getPluginData, this),
0278                                                     getFilename,
0279                                                     getBriefText,
0280                                                     getStartLine,
0281                                                     getEndLine},
0282         libraries);
0283 }
0284 
0285 void PluginManager::callHooksOnParseCompleted(runQueryOnDatabase_f const& runQueryOnDatabase)
0286 {
0287     callAllHooks<hookOnParseCompleted_f>(
0288         "hookOnParseCompleted",
0289         PluginParseCompletedHandler{std::bind_front(&PluginManager::getPluginData, this), runQueryOnDatabase},
0290         libraries);
0291 }
0292 
0293 void PluginManager::callHooksActiveSceneChanged(getSceneName_f const& getSceneName)
0294 {
0295     callAllHooks<hookActiveSceneChanged_f>(
0296         "hookActiveSceneChanged",
0297         PluginActiveSceneChangedHandler{std::bind_front(&PluginManager::getPluginData, this), getSceneName},
0298         libraries);
0299 }
0300 
0301 void PluginManager::callHooksGraphChanged(graphChanged_getSceneName_f const& getSceneName,
0302                                           graphChanged_getVisibleEntities_f const& getVisibleEntities,
0303                                           graphChanged_getEdgeByQualifiedName_f const& getEdgeByQualifiedName,
0304                                           graphChanged_getProjectData_f const& getProjectData)
0305 {
0306     callAllHooks<hookGraphChanged_f>("hookGraphChanged",
0307                                      PluginGraphChangedHandler{std::bind_front(&PluginManager::getPluginData, this),
0308                                                                getSceneName,
0309                                                                getVisibleEntities,
0310                                                                getEdgeByQualifiedName,
0311                                                                getProjectData},
0312                                      libraries);
0313 }
0314 
0315 void PluginManager::registerPluginData(std::string const& id, void *data)
0316 {
0317     this->pluginData[id] = data;
0318 }
0319 
0320 void PluginManager::unregisterPluginData(std::string const& id)
0321 {
0322     this->pluginData.erase(id);
0323 }
0324 
0325 void *PluginManager::getPluginData(std::string const& id) const
0326 {
0327     try {
0328         return this->pluginData.at(id);
0329     } catch (std::out_of_range const&) {
0330         return nullptr;
0331     }
0332 }
0333 
0334 void PluginManager::registerPluginQObject(std::string const& id, QObject *object)
0335 {
0336     this->pluginQObjects[id] = object;
0337 }
0338 
0339 QObject *PluginManager::getPluginQObject(std::string const& id) const
0340 {
0341     return this->pluginQObjects.at(id);
0342 }
0343 
0344 } // namespace Codethink::lvtplg