diff --git a/src/core/kexipartmanager.cpp b/src/core/kexipartmanager.cpp index 7a8bc0856..9c93a08a9 100644 --- a/src/core/kexipartmanager.cpp +++ b/src/core/kexipartmanager.cpp @@ -1,326 +1,341 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch Copyright (C) 2003-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kexipartmanager.h" #include "kexipart.h" #include "kexiinternalpart.h" #include "kexipartinfo.h" //! @todo KEXI3 #include "kexistaticpart.h" #include "KexiVersion.h" #include "KexiJsonTrader.h" #include #include #include #include #include #include #include #include #include using namespace KexiPart; typedef QHash KexiInternalPartDict; Q_GLOBAL_STATIC_WITH_ARGS(KexiJsonTrader, KexiPartTrader_instance, (KEXI_BASE_PATH)) class Q_DECL_HIDDEN Manager::Private { public: explicit Private(Manager *manager_); ~Private(); Manager *manager; PartDict parts; KexiInternalPartDict internalParts; PartInfoList partlist; PartInfoDict partsByPluginId; bool lookupDone; bool lookupResult; }; Manager::Private::Private(Manager *manager_) : manager(manager_) , lookupDone(false) , lookupResult(false) { } Manager::Private::~Private() { qDeleteAll(partlist); partlist.clear(); } //--- Manager::Manager(QObject *parent) : QObject(parent), KDbResultable(), d(new Private(this)) { } Manager::~Manager() { delete d; } template PartClass* Manager::part(Info *info, QHash *partDict) { if (!info) { return 0; } clearResult(); KDbMessageGuard mg(this); if (!lookup()) { return 0; } if (!info->isValid()) { m_result = KDbResult(info->errorMessage()); return 0; } PartClass *p = partDict->value(info->pluginId()); if (p) { return p; } // actual loading KPluginFactory *factory = qobject_cast(info->instantiate()); if (!factory) { m_result = KDbResult(ERR_CANNOT_LOAD_OBJECT, xi18nc("@info", "Could not load plugin file %1 " "for %2.", info->fileName(), QApplication::applicationDisplayName())); QPluginLoader loader(info->fileName()); // use this to get the message (void)loader.load(); m_result.setServerMessage(loader.errorString()); info->setErrorMessage(m_result.message()); qWarning() << m_result.message() << m_result.serverMessage(); return 0; } p = factory->create(this); if (!p) { m_result = KDbResult( ERR_CANNOT_LOAD_OBJECT, xi18nc( "@info", "Could not open plugin %1 for %2.", info->fileName(), QApplication::applicationDisplayName())); qWarning() << m_result.message(); return 0; } p->setInfo(info); p->setObjectName(QString("%1 plugin").arg(info->id())); partDict->insert(info->pluginId(), p); return p; } //! @return a string list @a list with removed whitespace from the beginning and end of each string. //! Empty strings are also removed. static QStringList cleanupStringList(const QStringList &list) { QStringList result; foreach(const QString &item, list) { QString cleanedItem = item.trimmed(); if (!cleanedItem.isEmpty()) { result.append(cleanedItem); } } return result; } bool Manager::lookup() { if (d->lookupDone) { return d->lookupResult; } d->lookupDone = true; d->lookupResult = false; d->partlist.clear(); d->partsByPluginId.clear(); d->parts.clear(); // load visual order of plugins KConfigGroup cg(KSharedConfig::openConfig()->group("Parts")); const QStringList orderedPluginIds = cleanupStringList( cg.readEntry("Order", "org.kexi-project.table," "org.kexi-project.query," "org.kexi-project.form," "org.kexi-project.report," "org.kexi-project.macro," "org.kexi-project.script").split(',')); QVector orderedInfos(orderedPluginIds.count()); QStringList serviceTypes; serviceTypes << "Kexi/Viewer" << "Kexi/Designer" << "Kexi/Editor" << "Kexi/Internal"; QList offers = KexiPartTrader_instance->query(serviceTypes); foreach(const QPluginLoader *loader, offers) { QScopedPointer info(new Info(*loader)); if (info->id().isEmpty()) { qWarning() << "No plugin ID specified for Kexi Part" << info->fileName() << "-- skipping!"; continue; } // check version const QString expectedVersion = KexiPart::version(); if (info->version() != expectedVersion) { qWarning() << "Kexi plugin" << info->id() << info->fileName() << "has version" << info->version() << "but version required by Kexi is" << expectedVersion << "-- skipping this plugin!"; continue; } // skip experimental types if ( (!Kexi::tempShowMacros() && info->id() == "org.kexi-project.macro") || (!Kexi::tempShowScripts() && info->id() == "org.kexi-project.script") ) { continue; } // skip duplicates if (d->partsByPluginId.contains(info->id())) { qWarning() << "More than one Kexi plugin with ID" << info->id() << info->fileName() << "-- skipping this one"; continue; } // find correct place for plugins visible in Navigator if (info->isVisibleInNavigator()) { const int index = orderedPluginIds.indexOf(info->id()); if (index != -1) { orderedInfos[index] = info.data(); } else { orderedInfos.append(info.data()); } // append later when we know order } else { // append now d->partlist.append(info.data()); } d->partsByPluginId.insert(info->pluginId(), info.data()); info.take(); } qDeleteAll(offers); offers.clear(); if (d->partsByPluginId.isEmpty()) { - m_result = KDbResult(xi18nc( - "@info", "Could not find any plugins for %1, e.g. for " + + QString message + = xi18nc("@info", + "Could not find any plugins for %1, e.g. for " "tables or forms. " "%1 would not be functional so it will exit." "Please check if %1 is properly " "installed.", - QApplication::applicationDisplayName())); + QApplication::applicationDisplayName()); + + // It can happen primarily on development machines, but worth mentioning: lack of + // plugin configuration. + if (KexiPartTrader_instance->pluginPaths().isEmpty()) { + message += xi18nc( + "@info", + "No single %1 directory has been located so it is possible " + "that custom plugin configuration was requested but it is not set up properly. Set " + "the QT_PLUGIN_PATH environment variable properly before running %2.", + KEXI_BASE_PATH, QApplication::applicationDisplayName()); + } + + m_result = KDbResult(message); return false; } // fill the final list using computed order for (int i = 0; i < orderedInfos.size(); i++) { Info *info = orderedInfos[i]; if (!info) { continue; } //qDebug() << "adding Kexi part info" << info->pluginId(); d->partlist.insert(i, info); } // now the d->partlist is: [ordered plugins visible in Navigator] [other plugins in unspecified order] d->lookupResult = true; return true; } Part* Manager::part(Info *info) { KDbMessageGuard mg(this); Part *p = part(info, &d->parts); if (p) { emit partLoaded(p); } return p; } static QString realPluginId(const QString &pluginId) { if (pluginId.contains('.')) { return pluginId; } else { // not like "org.kexi-project.table" - construct return QString::fromLatin1("org.kexi-project.") + QString(pluginId).remove("kexi/"); } } Part* Manager::partForPluginId(const QString &pluginId) { Info* info = infoForPluginId(pluginId); return part(info); } Info* Manager::infoForPluginId(const QString &pluginId) { KDbMessageGuard mg(this); if (!lookup()) return 0; const QString realId = realPluginId(pluginId); Info *i = realId.isEmpty() ? 0 : d->partsByPluginId.value(realId); if (i) return i; m_result = KDbResult(kxi18nc("@info", "No plugin for ID %1") .subs(realId) .toString(Kuit::VisualFormat::PlainText)); return 0; } /*! @todo KEXI3 void Manager::insertStaticPart(StaticPart* part) { if (!part) return; KDbMessageGuard mg(this); if (!lookup()) return; d->partlist.append(part->info()); if (!part->info()->pluginId().isEmpty()) d->partsByPluginId.insert(part->info()->pluginId(), part->info()); d->parts.insert(part->info()->pluginId(), part); } */ KexiInternalPart* Manager::internalPartForPluginId(const QString& pluginId) { Info* info = infoForPluginId(pluginId); if (!info || !info->serviceTypes().contains("Kexi/Internal")) { return nullptr; } return part(info, &d->internalParts); } PartInfoList* Manager::infoList() { KDbMessageGuard mg(this); if (!lookup()) { return 0; } return &d->partlist; } diff --git a/src/kexiutils/KexiJsonTrader.cpp b/src/kexiutils/KexiJsonTrader.cpp index 219018097..d938e6b41 100644 --- a/src/kexiutils/KexiJsonTrader.cpp +++ b/src/kexiutils/KexiJsonTrader.cpp @@ -1,172 +1,179 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2007 David Faure Copyright (C) 2015 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KexiJsonTrader.h" #include "utils.h" #include #include #include #include #include #include #include #include class Q_DECL_HIDDEN KexiJsonTrader::Private { public: - Private() : pluginPathFound(false) + Private(const QString &subDir) : m_subDir(subDir) { } - QString subDir; - bool pluginPathFound; - QStringList pluginPaths; + + QStringList pluginPaths() + { + if (!m_pluginPathFound) { + QStringList searchDirs; + searchDirs += QCoreApplication::libraryPaths(); + for (const QString &dir : searchDirs) { + const QString possiblePath = dir + QLatin1Char('/') + m_subDir; + if (QDir(possiblePath).exists()) { + m_pluginPaths += possiblePath; + } + } + m_pluginPathFound = true; + } + return m_pluginPaths; + } + +private: + QString m_subDir; + QStringList m_pluginPaths; + bool m_pluginPathFound = false; }; // --- KexiJsonTrader::KexiJsonTrader(const QString& subDir) - : d(new Private) + : d(new Private(subDir)) { - Q_ASSERT(!subDir.isEmpty()); - Q_ASSERT(!subDir.contains(' ')); - d->subDir = subDir; } KexiJsonTrader::~KexiJsonTrader() { delete d; } //! @return true if at least one service type from @a serviceTypeNames exists in @a foundServiceTypes static bool supportsAtLeastServiceType(const QStringList &foundServiceTypes, const QStringList &serviceTypeNames) { foreach(const QString &serviceTypeName, serviceTypeNames) { if (foundServiceTypes.contains(serviceTypeName)) { return true; } } return false; } //static QJsonObject KexiJsonTrader::metaDataObjectForPluginLoader(const QPluginLoader &pluginLoader) { return pluginLoader.metaData().value(QLatin1String("MetaData")).toObject(); } //static QJsonObject KexiJsonTrader::rootObjectForPluginLoader(const QPluginLoader &pluginLoader) { QJsonObject json = metaDataObjectForPluginLoader(pluginLoader); if (json.isEmpty()) { return QJsonObject(); } return json.value(QLatin1String("KPlugin")).toObject(); } //! Checks loader @a loader static bool checkLoader(QPluginLoader *loader, const QStringList &servicetypes, const QString &mimetype) { const QJsonObject pluginData = KexiJsonTrader::rootObjectForPluginLoader(*loader); if (pluginData.isEmpty()) { //qDebug() << dirIter.filePath() << "has no json!"; return false; } const QJsonArray foundServiceTypesAray = pluginData.value(QLatin1String("ServiceTypes")).toArray(); if (foundServiceTypesAray.isEmpty()) { qWarning() << "No ServiceTypes defined for plugin" << loader->fileName() << "-- skipping!"; return false; } QStringList foundServiceTypes = KexiUtils::convertTypesUsingMethod(foundServiceTypesAray.toVariantList()); if (!supportsAtLeastServiceType(foundServiceTypes, servicetypes)) { return false; } if (!mimetype.isEmpty()) { QJsonObject json = KexiJsonTrader::metaDataObjectForPluginLoader(*loader); QStringList mimeTypes = json.value(QLatin1String("X-KDE-ExtraNativeMimeTypes")) .toString().split(QLatin1Char(',')); mimeTypes += json.value(QLatin1String("MimeType")).toString().split(QLatin1Char(';')); mimeTypes += json.value(QLatin1String("X-KDE-NativeMimeType")).toString(); if (! mimeTypes.contains(mimetype)) { return false; } } return true; } static QList findPlugins(const QString &path, const QStringList &servicetypes, const QString &mimetype) { QList list; QDirIterator dirIter(path, /* QDirIterator::Subdirectories -- Since 3.0.1: Don't look into subdirs because there may be 3.x dirs from future Kexi versions. We will look into subdirs since 3.1 again. */ QDirIterator::FollowSymlinks); while (dirIter.hasNext()) { dirIter.next(); if (dirIter.fileInfo().isFile()) { QPluginLoader *loader = new QPluginLoader(dirIter.filePath()); if (checkLoader(loader, servicetypes, mimetype)) { list.append(loader); } else { delete loader; } } } return list; } QList KexiJsonTrader::query(const QStringList &servicetypes, const QString &mimetype) { - if (!d->pluginPathFound) { - QStringList searchDirs; - searchDirs += QCoreApplication::libraryPaths(); - foreach(const QString &dir, searchDirs) { - //qDebug() << dir; - QString possiblePath = dir + QLatin1Char('/') + d->subDir; - if (QDir(possiblePath).exists()) { - d->pluginPaths += possiblePath; - } - } - d->pluginPathFound = true; - } - QList list; - foreach(const QString &path, d->pluginPaths) { + foreach(const QString &path, d->pluginPaths()) { list += findPlugins(path, servicetypes, mimetype); } return list; } QList KexiJsonTrader::query(const QString &servicetype, const QString &mimetype) { QStringList servicetypes; servicetypes << servicetype; return query(servicetypes, mimetype); } + +QStringList KexiJsonTrader::pluginPaths() const +{ + return d->pluginPaths(); +} diff --git a/src/kexiutils/KexiJsonTrader.h b/src/kexiutils/KexiJsonTrader.h index dd10c5312..783430060 100644 --- a/src/kexiutils/KexiJsonTrader.h +++ b/src/kexiutils/KexiJsonTrader.h @@ -1,90 +1,97 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2007 David Faure Copyright (C) 2015 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KEXI_JSONTRADER_H #define KEXI_JSONTRADER_H #include "kexiutils_export.h" #include #include class QPluginLoader; class QJsonObject; /** * Support class to fetch a list of relevant plugins */ class KEXIUTILS_EXPORT KexiJsonTrader { public: //! Creates instance of the trader. //! @a subDir is a name of subdirectory in which plugin files of given type are stored, e.g. "kexi". explicit KexiJsonTrader(const QString& subDir); ~KexiJsonTrader(); /** * The main function in the KexiJsonTrader class. * * It will return a list of QPluginLoader objects that match your * specifications. The only required parameter is the @a servicetypes. * The @a mimetype parameter is used to limit the possible choices * returned based on the constraints you give it. * * The keys used in the query (Type, ServiceType, Exec) are all * fields found in the .desktop files. * * @param servicetypes A list of service types like 'KMyApp/Plugin' or 'KFilePlugin'. * At least one has to be found in a plugin. * @param mimetype A mimetype constraint to limit the choices returned, QString() to * get all services of the given @p servicetypes. * * @return A list of QPluginLoader that satisfy the query * @see https://techbase.kde.org/Development/Tutorials/Services/Traders#The_KTrader_Query_Language * * @note Ownership of the QPluginLoader objects is transferred to the caller. */ QList query(const QStringList &servicetypes, const QString &mimetype = QString()); /** * @overload QList query(const QStringList &, const QString &); */ QList query(const QString &servicetype, const QString &mimetype = QString()); /** * @return very top-level metadata JSON object for @a pluginLoader */ static QJsonObject metaDataObjectForPluginLoader(const QPluginLoader &pluginLoader); /** * @return root JSON object for @a pluginLoader, a "KPlugin" child item of object * returned by metaDataObjectForPluginLoader() */ static QJsonObject rootObjectForPluginLoader(const QPluginLoader &pluginLoader); + /** + * @return plugin paths that are used to find plugins + * + * Empty list means invalid configuration, e.g. missing QT_PLUGIN_PATH. + */ + QStringList pluginPaths() const; + private: Q_DISABLE_COPY(KexiJsonTrader) class Private; Private * const d; }; #endif