File indexing completed on 2025-01-19 03:55:36

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2018-07-30
0007  * Description : manager to load external plugins at run-time: private container
0008  *
0009  * SPDX-FileCopyrightText: 2018-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "dpluginloader_p.h"
0016 
0017 // Qt includes
0018 
0019 #include <QStringList>
0020 #include <QElapsedTimer>
0021 #include <QDirIterator>
0022 #include <QStandardPaths>
0023 #include <QMessageBox>
0024 #include <QLibraryInfo>
0025 #include <QMetaProperty>
0026 #include <QFileInfo>
0027 
0028 // KDE includes
0029 
0030 #include <ksharedconfig.h>
0031 #include <kconfiggroup.h>
0032 
0033 // Local includes
0034 
0035 #include "digikam_debug.h"
0036 #include "digikam_config.h"
0037 #include "digikam_version.h"
0038 #include "digikam_globals.h"
0039 
0040 namespace Digikam
0041 {
0042 
0043 DPluginLoader::Private::Private()
0044     : pluginsLoaded(false)
0045 {
0046     // Do not load these plugins as they are not currently working.
0047 
0048     blacklist   << QLatin1String("Generic_FaceBook_Plugin");
0049     blacklist   << QLatin1String("Generic_IpFs_Plugin");
0050     blacklist   << QLatin1String("Generic_Rajce_Plugin");
0051     DKBlacklist << QLatin1String("Generic_DNGConverter_Plugin");
0052     blacklist   << QLatin1String("Generic_YandexFotki_Plugin");
0053     blacklist   << QLatin1String("Generic_VKontakte_Plugin");
0054 }
0055 
0056 DPluginLoader::Private::~Private()
0057 {
0058 }
0059 
0060 QFileInfoList DPluginLoader::Private::pluginEntriesList() const
0061 {
0062     QStringList pathList;
0063 
0064     // First we try to load in first the local plugin if DK_PLUG_PATH variable is declared.
0065     // Else, we will load plugins from the system using the standard Qt plugin path.
0066 
0067     QByteArray  dkenv = qgetenv("DK_PLUGIN_PATH");
0068 
0069     if (dkenv.isEmpty())
0070     {
0071 
0072 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0073 
0074         pathList << QLibraryInfo::path(QLibraryInfo::PluginsPath) + QLatin1String("/digikam/");
0075 
0076 #else
0077 
0078         pathList << QLibraryInfo::location(QLibraryInfo::PluginsPath) + QLatin1String("/digikam/");
0079 
0080 #endif
0081 
0082     }
0083     else
0084     {
0085         qCWarning(DIGIKAM_GENERAL_LOG) << "DK_PLUGIN_PATH env.variable detected. "
0086                                           "We will use it to load plugin...";
0087 
0088         pathList << QString::fromUtf8(dkenv).split(QLatin1Char(';'),
0089                                                    QT_SKIP_EMPTY_PARTS);
0090     }
0091 
0092     qCDebug(DIGIKAM_GENERAL_LOG) << "Parsing plugins from" << pathList;
0093 
0094 #ifdef Q_OS_MACOS
0095 
0096     QString filter(QLatin1String("*.dylib *.so"));
0097 
0098 #elif defined Q_OS_WIN
0099 
0100     QString filter(QLatin1String("*.dll"));
0101 
0102 #else
0103 
0104     QString filter(QLatin1String("*.so"));
0105 
0106 #endif
0107 
0108     QFileInfoList allFiles;
0109     QStringList   dupFiles;
0110 
0111     Q_FOREACH (const QString& path, pathList)
0112     {
0113         QDir dir(path, filter, QDir::Unsorted,
0114                  QDir::Files | QDir::NoDotAndDotDot);
0115 
0116         QDirIterator it(dir, QDirIterator::Subdirectories);
0117 
0118         while (it.hasNext())
0119         {
0120             it.next();
0121 
0122             if (!it.filePath().contains(QLatin1String("dSYM"))   &&  // Ignore debug binary extensions under MacOS
0123                 !it.filePath().contains(QLatin1String("marble")) &&  // Ignore Marble plugins.
0124                 !dupFiles.contains(it.fileInfo().baseName()))
0125             {
0126                 dupFiles << it.fileInfo().baseName();
0127 
0128                 if (it.fileInfo().baseName().startsWith(QLatin1String("DImg_")))
0129                 {
0130                     allFiles.prepend(it.fileInfo());
0131                 }
0132                 else
0133                 {
0134                     allFiles.append(it.fileInfo());
0135                 }
0136             }
0137         }
0138     }
0139 
0140     qCDebug(DIGIKAM_GENERAL_LOG) << "Plugins found:" << allFiles.count();
0141 
0142     return allFiles;
0143 }
0144 
0145 bool DPluginLoader::Private::appendPlugin(QObject* const obj,
0146                                           QPluginLoader* const loader)
0147 {
0148     DPlugin* const plugin = qobject_cast<DPlugin*>(obj);
0149 
0150     if (plugin)
0151     {
0152         Q_ASSERT(obj->metaObject()->superClass()); // all our plugins have a super class
0153 
0154         if (plugin->version() == QLatin1String(digikam_version_short))
0155         {
0156             qCDebug(DIGIKAM_GENERAL_LOG) << "Plugin of type" << obj->metaObject()->superClass()->className()
0157                                          << "loaded from"    << loader->fileName();
0158 
0159             KSharedConfigPtr config = KSharedConfig::openConfig();
0160             KConfigGroup group      = config->group(DPluginLoader::instance()->configGroupName());
0161 
0162             plugin->setShouldLoaded(group.readEntry(plugin->iid(), true));
0163             plugin->setLibraryFileName(loader->fileName());
0164 
0165             allPlugins << plugin;
0166 
0167             return true;
0168         }
0169     }
0170 
0171     return false;
0172 }
0173 
0174 void DPluginLoader::Private::loadPlugins()
0175 {
0176     if (pluginsLoaded)
0177     {
0178         return;
0179     }
0180 
0181     QElapsedTimer t;
0182     t.start();
0183     qCDebug(DIGIKAM_GENERAL_LOG) << "Starting to load external tools.";
0184 
0185     Q_ASSERT(allPlugins.isEmpty());
0186 
0187     Q_FOREACH (const QFileInfo& info, pluginEntriesList())
0188     {
0189         if (!whitelist.isEmpty() && !whitelist.contains(info.baseName()))
0190         {
0191             qCDebug(DIGIKAM_GENERAL_LOG) << "Ignoring non-whitelisted plugin" << info.filePath();
0192             continue;
0193         }
0194 
0195         if (blacklist.contains(info.baseName()))
0196         {
0197             qCDebug(DIGIKAM_GENERAL_LOG) << "Ignoring blacklisted plugin" << info.filePath();
0198             continue;
0199         }
0200 
0201         if ((qApp->applicationName() == QLatin1String("showfoto")) &&
0202             (info.baseName().startsWith(QLatin1String("Bqm_"))))
0203         {
0204             qCDebug(DIGIKAM_GENERAL_LOG) << "Ignoring specific digiKam BQM plugin in Showfoto" << info.filePath();
0205             continue;
0206         }
0207 
0208         if ((qApp->applicationName() == QLatin1String("digikam")) &&
0209             (DKBlacklist.contains(info.baseName())))
0210         {
0211             qCDebug(DIGIKAM_GENERAL_LOG) << "Ignoring specific Showfoto plugin in digiKam" << info.filePath();
0212 
0213             continue;
0214         }
0215 /*
0216         qCDebug(DIGIKAM_GENERAL_LOG) << info.baseName() << "-" << info.canonicalPath();
0217 */
0218         const QString path          = info.canonicalFilePath();
0219         QPluginLoader* const loader = new QPluginLoader(path, DPluginLoader::instance());
0220         QObject* const obj          = loader->instance();
0221 
0222         if (obj)
0223         {
0224             bool isPlugin = appendPlugin(obj, loader);
0225 
0226             if (!isPlugin)
0227             {
0228                 qCWarning(DIGIKAM_GENERAL_LOG) << "Ignoring the following plugin since it couldn't be loaded:"
0229                                                << path;
0230 
0231                 qCDebug(DIGIKAM_GENERAL_LOG) << "External plugin failure:" << path
0232                                              << "is a plugin, but it does not implement the"
0233                                              << "right interface or it was compiled against"
0234                                              << "an old version of digiKam. Ignoring it.";
0235                 delete loader;
0236             }
0237         }
0238         else
0239         {
0240             qCWarning(DIGIKAM_GENERAL_LOG) << "Ignoring to load the following file since it doesn't look like "
0241                                               "a valid digiKam external plugin:" << path << QT_ENDL
0242                                            << "Reason:" << loader->errorString();
0243             delete loader;
0244         }
0245     }
0246 
0247     if (allPlugins.isEmpty())
0248     {
0249         qCWarning(DIGIKAM_GENERAL_LOG) << "No plugins loaded. Please check if the plugins were installed in the correct path,"
0250                                        << "or if any errors occurred while loading plugins.";
0251     }
0252 
0253     pluginsLoaded = true;
0254 
0255     qCDebug(DIGIKAM_GENERAL_LOG) << Q_FUNC_INFO << "Time elapsed:" << t.elapsed() << "ms";
0256 }
0257 
0258 } // namespace Digikam