File indexing completed on 2024-05-19 04:26:52

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org>
0003    SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
0004 
0005    SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "KoJsonTrader.h"
0009 
0010 #include "kis_debug.h"
0011 
0012 #include <QCoreApplication>
0013 #include <QPluginLoader>
0014 #include <QJsonObject>
0015 #include <QJsonArray>
0016 #include <QDirIterator>
0017 #include <QDir>
0018 #include <QProcessEnvironment>
0019 #include <QGlobalStatic>
0020 #include <QMutexLocker>
0021 
0022 struct KoJsonTrader::PluginCacheEntry
0023 {
0024     QString filePath;
0025     QJsonArray serviceTypes;
0026     QStringList mimeTypes;
0027     QSharedPointer<QPluginLoader> loader;
0028 };
0029 
0030 
0031 KoJsonTrader::KoJsonTrader()
0032 {
0033     // Allow a command line variable KRITA_PLUGIN_PATH to override the automatic search
0034     m_pluginPath = QProcessEnvironment::systemEnvironment().value("KRITA_PLUGIN_PATH");
0035 
0036     if (m_pluginPath.isEmpty() ||
0037         !(QFileInfo(m_pluginPath).exists() && QFileInfo(m_pluginPath).isDir())) {
0038 
0039         QList<QDir> searchDirs;
0040 
0041         QDir appDir(qApp->applicationDirPath());
0042         appDir.cdUp();
0043 #ifdef Q_OS_MACOS
0044         // Help Krita run without deployment
0045         QDir d(appDir);
0046         d.cd("../../../");
0047         searchDirs << d;
0048 #endif
0049 
0050 #ifdef Q_OS_ANDROID
0051         appDir.cdUp();
0052 #endif
0053         searchDirs << appDir;
0054 
0055         Q_FOREACH (const QDir& dir, searchDirs) {
0056             const QStringList nameFilters = {
0057 #ifdef Q_OS_MACOS
0058                 "*PlugIns*",
0059 #endif
0060                 "lib*",
0061             };
0062             Q_FOREACH (const QFileInfo &info, dir.entryInfoList(nameFilters, QDir::Dirs | QDir::NoDotAndDotDot)) {
0063 #ifdef Q_OS_MACOS
0064                 if (info.fileName().contains("PlugIns")) {
0065                     m_pluginPath = info.absoluteFilePath();
0066                     break;
0067                 }
0068                 else if (info.fileName().contains("lib")) {
0069 #else
0070                 if (info.fileName().contains("lib")) {
0071 #endif
0072                     QDir libDir(info.absoluteFilePath());
0073 
0074 #ifdef Q_OS_ANDROID
0075 #if defined(Q_PROCESSOR_ARM_64)
0076                     libDir.cd("arm64-v8a");
0077 #elif defined(Q_PROCESSOR_ARM)
0078                     libDir.cd("armeabi-v7a");
0079 #elif defined(Q_PROCESSOR_X86_64)
0080                     libDir.cd("x86_64");
0081 #elif defined(Q_PROCESSOR_x86)
0082                     libDir.cd("x86");
0083 #endif
0084                     m_pluginPath = libDir.absolutePath();
0085                     break;
0086 #else
0087                     // on many systems this will be the actual lib dir (and krita subdir contains plugins)
0088                     if (libDir.cd("kritaplugins")) {
0089                         m_pluginPath = libDir.absolutePath();
0090                         break;
0091                     }
0092 
0093                     // on debian at least the actual libdir is a subdir named like "lib/x86_64-linux-gnu"
0094                     // so search there for the Krita subdir which will contain our plugins
0095                     // FIXME: what are the chances of there being more than one Krita install with different arch and compiler ABI?
0096                     Q_FOREACH (const QFileInfo &subInfo, libDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) {
0097                         QDir subDir(subInfo.absoluteFilePath());
0098                         if (subDir.cd("kritaplugins")) {
0099                             m_pluginPath = subDir.absolutePath();
0100                             break; // will only break inner loop so we need the extra check below
0101                         }
0102                     }
0103 
0104                     if (!m_pluginPath.isEmpty()) {
0105                         break;
0106                     }
0107 #endif
0108                 }
0109             }
0110 
0111             if (!m_pluginPath.isEmpty()) {
0112                 break;
0113             }
0114         }
0115         dbgPlugins << "KoJsonTrader will load its plugins from" << m_pluginPath;
0116     }
0117 
0118     initializePluginLoaderCache();
0119 }
0120 
0121 KoJsonTrader::~KoJsonTrader()
0122 {
0123 }
0124 
0125 
0126 Q_GLOBAL_STATIC(KoJsonTrader, s_instance)
0127 
0128 KoJsonTrader* KoJsonTrader::instance()
0129 {
0130     return s_instance;
0131 }
0132 
0133 void KoJsonTrader::initializePluginLoaderCache()
0134 {
0135     QMutexLocker l(&m_mutex);
0136 
0137     KIS_SAFE_ASSERT_RECOVER_RETURN(m_pluginLoaderCache.isEmpty());
0138 
0139     QList<QPluginLoader *>list;
0140     QDirIterator dirIter(m_pluginPath, QDirIterator::Subdirectories);
0141     while (dirIter.hasNext()) {
0142         dirIter.next();
0143 #ifdef Q_OS_ANDROID
0144         // files starting with lib_krita are plugins, it is needed because of the loading rules in NDK
0145         if (dirIter.fileInfo().isFile() && dirIter.fileName().startsWith("lib_krita")) {
0146 #elif defined(_MSC_VER)
0147         if (dirIter.fileInfo().isFile() && dirIter.fileName().startsWith("krita") && !dirIter.fileName().endsWith(".pdb") && !dirIter.fileName().endsWith(".lib")) {
0148 #else
0149         if (dirIter.fileInfo().isFile() && dirIter.fileName().startsWith("krita") && !dirIter.fileName().endsWith(".debug")) {
0150 #endif
0151 
0152             dbgPlugins << dirIter.fileName();
0153             QScopedPointer<QPluginLoader> loader(new QPluginLoader(dirIter.filePath()));
0154             QJsonObject json = loader->metaData().value("MetaData").toObject();
0155 
0156             dbgPlugins << json << json.value("X-KDE-ServiceTypes");
0157 
0158             if (json.isEmpty()) {
0159                 qWarning() << dirIter.filePath() << "has no json!";
0160                 continue;
0161             } else {
0162                 QJsonArray  serviceTypes = json.value("X-KDE-ServiceTypes").toArray();
0163                 if (serviceTypes.isEmpty()) {
0164                     qWarning() << dirIter.fileName() << "has no X-KDE-ServiceTypes";
0165                     continue;
0166                 }
0167 
0168                 QStringList mimeTypes = json.value("X-KDE-ExtraNativeMimeTypes").toString().split(',');
0169                 mimeTypes += json.value("MimeType").toString().split(';');
0170                 mimeTypes += json.value("X-KDE-NativeMimeType").toString();
0171 
0172                 PluginCacheEntry cacheEntry;
0173                 cacheEntry.filePath = dirIter.filePath();
0174                 cacheEntry.serviceTypes = serviceTypes;
0175                 cacheEntry.mimeTypes = mimeTypes;
0176                 cacheEntry.loader = toQShared(loader.take());
0177                 m_pluginLoaderCache << cacheEntry;
0178             }
0179         }
0180     }
0181 }
0182 
0183 QList<KoJsonTrader::Plugin> KoJsonTrader::query(const QString &servicetype, const QString &mimetype)
0184 {
0185     QMutexLocker l(&m_mutex);
0186 
0187     QList<Plugin> list;
0188     Q_FOREACH(const PluginCacheEntry &plugin, m_pluginLoaderCache) {
0189         if (!plugin.serviceTypes.contains(QJsonValue(servicetype))) {
0190             continue;
0191         }
0192 
0193         if (!mimetype.isEmpty() && !plugin.mimeTypes.contains(mimetype)) {
0194             continue;
0195         }
0196 
0197         list << Plugin(plugin.loader, &m_mutex);
0198     }
0199 
0200     return list;
0201 }
0202 
0203 KoJsonTrader::Plugin::Plugin(QSharedPointer<QPluginLoader> loader, QMutex *mutex)
0204     : m_loader(loader),
0205       m_mutex(mutex)
0206 {
0207 }
0208 
0209 KoJsonTrader::Plugin::~Plugin()
0210 {
0211 }
0212 
0213 QObject *KoJsonTrader::Plugin::instance() const
0214 {
0215     QMutexLocker l(m_mutex);
0216     return m_loader->instance();
0217 }
0218 
0219 QJsonObject KoJsonTrader::Plugin::metaData() const
0220 {
0221     return m_loader->metaData();
0222 }
0223 
0224 QString KoJsonTrader::Plugin::fileName() const
0225 {
0226     return m_loader->fileName();
0227 }
0228 
0229 QString KoJsonTrader::Plugin::errorString() const
0230 {
0231     return m_loader->errorString();
0232 }