File indexing completed on 2024-05-12 15:59:03

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