File indexing completed on 2025-01-05 03:59:31

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2008 Torsten Rahn <tackat@kde.org>
0004 // SPDX-FileCopyrightText: 2009 Jens-Michael Hoffmann <jensmh@gmx.de>
0005 //
0006 
0007 // Own
0008 #include "PluginManager.h"
0009 
0010 // Qt
0011 #include <QPluginLoader>
0012 #include <QElapsedTimer>
0013 #include <QMessageBox>
0014 #include <QLibraryInfo>
0015 
0016 // Local dir
0017 #include "MarbleDirs.h"
0018 #include "RenderPlugin.h"
0019 #include "ParseRunnerPlugin.h"
0020 #include "ReverseGeocodingRunnerPlugin.h"
0021 #include "SearchRunnerPlugin.h"
0022 
0023 #include "digikam_debug.h"
0024 
0025 namespace Marble
0026 {
0027 
0028 class PluginManagerPrivate
0029 {
0030  public:
0031     PluginManagerPrivate(PluginManager* parent)
0032             : m_pluginsLoaded(false),
0033               m_parent(parent)
0034     {
0035     }
0036 
0037     ~PluginManagerPrivate();
0038 
0039     void loadPlugins();
0040     bool addPlugin(QObject *obj, const QPluginLoader *loader);
0041 
0042     bool m_pluginsLoaded;
0043     QList<const RenderPlugin *> m_renderPluginTemplates;
0044     QList<const SearchRunnerPlugin *> m_searchRunnerPlugins;
0045     QList<const ReverseGeocodingRunnerPlugin *> m_reverseGeocodingRunnerPlugins;
0046     QList<const ParseRunnerPlugin *> m_parsingRunnerPlugins;
0047     PluginManager* m_parent;
0048     static QStringList m_blacklist;
0049     static QStringList m_whitelist;
0050 
0051 #ifdef Q_OS_ANDROID
0052     QStringList m_pluginPaths;
0053 #endif
0054 };
0055 
0056 QStringList PluginManagerPrivate::m_blacklist;
0057 QStringList PluginManagerPrivate::m_whitelist;
0058 
0059 PluginManagerPrivate::~PluginManagerPrivate()
0060 {
0061     // nothing to do
0062 }
0063 
0064 PluginManager::PluginManager( QObject *parent ) : QObject( parent ),
0065     d( new PluginManagerPrivate(this) )
0066 {
0067     //Checking assets:/plugins for uninstalled plugins
0068 #ifdef Q_OS_ANDROID
0069         installPluginsFromAssets();
0070 #endif
0071 }
0072 
0073 PluginManager::~PluginManager()
0074 {
0075     delete d;
0076 }
0077 
0078 QList<const RenderPlugin *> PluginManager::renderPlugins() const
0079 {
0080     d->loadPlugins();
0081     return d->m_renderPluginTemplates;
0082 }
0083 
0084 void PluginManager::addRenderPlugin( const RenderPlugin *plugin )
0085 {
0086     d->loadPlugins();
0087     d->m_renderPluginTemplates << plugin;
0088     Q_EMIT renderPluginsChanged();
0089 }
0090 
0091 QList<const SearchRunnerPlugin *> PluginManager::searchRunnerPlugins() const
0092 {
0093     d->loadPlugins();
0094     return d->m_searchRunnerPlugins;
0095 }
0096 
0097 void PluginManager::addSearchRunnerPlugin( const SearchRunnerPlugin *plugin )
0098 {
0099     d->loadPlugins();
0100     d->m_searchRunnerPlugins << plugin;
0101     Q_EMIT searchRunnerPluginsChanged();
0102 }
0103 
0104 QList<const ReverseGeocodingRunnerPlugin *> PluginManager::reverseGeocodingRunnerPlugins() const
0105 {
0106     d->loadPlugins();
0107     return d->m_reverseGeocodingRunnerPlugins;
0108 }
0109 
0110 void PluginManager::addReverseGeocodingRunnerPlugin( const ReverseGeocodingRunnerPlugin *plugin )
0111 {
0112     d->loadPlugins();
0113     d->m_reverseGeocodingRunnerPlugins << plugin;
0114     Q_EMIT reverseGeocodingRunnerPluginsChanged();
0115 }
0116 
0117 QList<const ParseRunnerPlugin *> PluginManager::parsingRunnerPlugins() const
0118 {
0119     d->loadPlugins();
0120     return d->m_parsingRunnerPlugins;
0121 }
0122 
0123 void PluginManager::addParseRunnerPlugin( const ParseRunnerPlugin *plugin )
0124 {
0125     d->loadPlugins();
0126     d->m_parsingRunnerPlugins << plugin;
0127     Q_EMIT parseRunnerPluginsChanged();
0128 }
0129 
0130 void PluginManager::blacklistPlugin(const QString &filename)
0131 {
0132 
0133 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0134 
0135     PluginManagerPrivate::m_blacklist << QLibraryInfo::path(QLibraryInfo::PluginsPath) + QString::fromUtf8("/marble/") + filename;
0136 
0137 #else
0138 
0139     PluginManagerPrivate::m_blacklist << QLibraryInfo::location(QLibraryInfo::PluginsPath) + QString::fromUtf8("/marble/") + filename;
0140 
0141 #endif
0142 
0143 }
0144 
0145 void PluginManager::whitelistPlugin(const QString &filename)
0146 {
0147 
0148 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0149 
0150     PluginManagerPrivate::m_whitelist << QLibraryInfo::path(QLibraryInfo::PluginsPath) + QString::fromUtf8("/marble/") + filename;
0151 
0152 #else
0153 
0154     PluginManagerPrivate::m_whitelist << QLibraryInfo::location(QLibraryInfo::PluginsPath) + QString::fromUtf8("/marble/") + filename;
0155 
0156 #endif
0157 
0158 }
0159 
0160 /** Append obj to the given plugins list if it inherits both T and U */
0161 template<class Iface, class Plugin>
0162 bool appendPlugin( QObject * obj, const QPluginLoader *loader, QList<Plugin> &plugins )
0163 {
0164     if ( qobject_cast<Iface*>( obj ) && qobject_cast<Plugin>( obj ) ) {
0165         Q_ASSERT( obj->metaObject()->superClass() ); // all our plugins have a super class
0166         qCDebug(DIGIKAM_MARBLE_LOG) <<  obj->metaObject()->superClass()->className()
0167                 << "plugin loaded from" << (loader ? loader->fileName() : QString::fromUtf8( "<static>"));
0168         auto plugin = qobject_cast<Plugin>( obj );
0169         Q_ASSERT( plugin ); // checked above
0170         plugins << plugin;
0171         return true;
0172     }
0173 
0174     return false;
0175 }
0176 
0177 bool PluginManagerPrivate::addPlugin(QObject *obj, const QPluginLoader *loader)
0178 {
0179     bool isPlugin = appendPlugin<RenderPluginInterface>
0180                 ( obj, loader, m_renderPluginTemplates );
0181     isPlugin = isPlugin || appendPlugin<SearchRunnerPlugin>
0182                 ( obj, loader, m_searchRunnerPlugins );
0183     isPlugin = isPlugin || appendPlugin<ReverseGeocodingRunnerPlugin>
0184                 ( obj, loader, m_reverseGeocodingRunnerPlugins );
0185     isPlugin = isPlugin || appendPlugin<ParseRunnerPlugin>
0186                 ( obj, loader, m_parsingRunnerPlugins );
0187     if ( !isPlugin ) {
0188         qCWarning(DIGIKAM_MARBLE_LOG) << "Ignoring the following plugin since it couldn't be loaded:" << (loader ? loader->fileName() : QString::fromUtf8( "<static>"));
0189         qCDebug(DIGIKAM_MARBLE_LOG) << "Plugin failure:" << (loader ? loader->fileName() : QString::fromUtf8( "<static>")) << "is a plugin, but it does not implement the "
0190                 << "right interfaces or it was compiled against an old version of Marble. Ignoring it.";
0191     }
0192     return isPlugin;
0193 }
0194 
0195 void PluginManagerPrivate::loadPlugins()
0196 {
0197     if (m_pluginsLoaded)
0198     {
0199         return;
0200     }
0201 
0202     QElapsedTimer t;
0203     t.start();
0204     qCDebug(DIGIKAM_MARBLE_LOG) << "Starting to load Plugins.";
0205 
0206     QStringList pluginFileNameList = MarbleDirs::pluginEntryList( QString::fromUtf8( "" ), QDir::Files );
0207 
0208     MarbleDirs::debug();
0209 
0210     Q_ASSERT( m_renderPluginTemplates.isEmpty() );
0211     Q_ASSERT( m_searchRunnerPlugins.isEmpty() );
0212     Q_ASSERT( m_reverseGeocodingRunnerPlugins.isEmpty() );
0213     Q_ASSERT( m_parsingRunnerPlugins.isEmpty() );
0214 
0215     bool foundPlugin = false;
0216     for( const QString &fileName: pluginFileNameList ) {
0217         QString const baseName = QFileInfo(fileName).baseName();
0218 
0219 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
0220 
0221         QString const libBaseName = QLibraryInfo::path(QLibraryInfo::PluginsPath) + QString::fromUtf8("/marble/") + QFileInfo(fileName).baseName();
0222 
0223 #else
0224 
0225         QString const libBaseName = QLibraryInfo::location(QLibraryInfo::PluginsPath) + QString::fromUtf8("/marble/") + QFileInfo(fileName).baseName();
0226 
0227 #endif
0228 
0229         if (!m_whitelist.isEmpty() && !m_whitelist.contains(baseName) && !m_whitelist.contains(libBaseName)) {
0230             qCDebug(DIGIKAM_MARBLE_LOG) << "Ignoring non-whitelisted plugin " << fileName;
0231             continue;
0232         }
0233         if (m_blacklist.contains(baseName) || m_blacklist.contains(libBaseName)) {
0234             qCDebug(DIGIKAM_MARBLE_LOG) << "Ignoring blacklisted plugin " << fileName;
0235             continue;
0236         }
0237 
0238         // qCDebug(DIGIKAM_MARBLE_LOG) << fileName << " - " << MarbleDirs::pluginPath( fileName );
0239         QString const path = MarbleDirs::pluginPath( fileName );
0240 #ifdef Q_OS_ANDROID
0241         QFileInfo targetFile( path );
0242         if ( !m_pluginPaths.contains( targetFile.canonicalFilePath() ) ) {
0243             // @todo Delete the file here?
0244             qCDebug(DIGIKAM_MARBLE_LOG) << "Ignoring file " << path << " which is not among the currently installed plugins";
0245             continue;
0246         }
0247 #endif
0248         QPluginLoader* loader = new QPluginLoader( path, m_parent );
0249 
0250         QObject * obj = loader->instance();
0251 
0252         if ( obj ) {
0253             bool isPlugin = addPlugin(obj, loader);
0254             if (!isPlugin) {
0255                 delete loader;
0256             } else {
0257                 foundPlugin = true;
0258             }
0259         } else {
0260             qCWarning(DIGIKAM_MARBLE_LOG) << "Ignoring to load the following file since it doesn't look like a valid Marble plugin:" << path << Qt::endl
0261                        << "Reason:" << loader->errorString();
0262             delete loader;
0263         }
0264     }
0265 
0266     const auto staticPlugins = QPluginLoader::staticInstances();
0267     for (auto obj : staticPlugins) {
0268         if (addPlugin(obj, nullptr)) {
0269             foundPlugin = true;
0270         }
0271     }
0272 
0273     if ( !foundPlugin ) {
0274         qCWarning(DIGIKAM_MARBLE_LOG) << "No plugins loaded. Please check if the plugins were installed in the correct path,"
0275                    << "or if any errors occurred while loading plugins.";
0276     }
0277 
0278     m_pluginsLoaded = true;
0279 
0280     qCDebug(DIGIKAM_MARBLE_LOG) << Q_FUNC_INFO << "Time elapsed:" << t.elapsed() << "ms";
0281 }
0282 
0283 #ifdef Q_OS_ANDROID
0284     void PluginManager::installPluginsFromAssets() const
0285     {
0286         d->m_pluginPaths.clear();
0287         QStringList copyList = MarbleDirs::pluginEntryList(QString());
0288         QDir pluginHome(MarbleDirs::localPath());
0289         pluginHome.mkpath(MarbleDirs::pluginLocalPath());
0290         pluginHome.setCurrent(MarbleDirs::pluginLocalPath());
0291 
0292         QStringList pluginNameFilter = QStringList() << "lib*.so";
0293         QStringList const existingPlugins = QDir(MarbleDirs::pluginLocalPath()).entryList(pluginNameFilter, QDir::Files);
0294         for(const QString &existingPlugin: existingPlugins) {
0295             QFile::remove(existingPlugin);
0296         }
0297 
0298         for (const QString & file: copyList) {
0299             QString const target = MarbleDirs::pluginLocalPath() + QLatin1Char('/') + file;
0300             if (QFileInfo(MarbleDirs::pluginSystemPath() + QLatin1Char('/') + file).isDir()) {
0301                 pluginHome.mkpath(target);
0302             }
0303             else {
0304                 QFile temporaryFile(MarbleDirs::pluginSystemPath() + QLatin1Char('/') + file);
0305                 temporaryFile.copy(target);
0306                 QFileInfo targetFile(target);
0307                 d->m_pluginPaths << targetFile.canonicalFilePath();
0308             }
0309         }
0310     }
0311 #endif
0312 
0313 }
0314 
0315 #include "moc_PluginManager.cpp"