1 /*
2     This file is part of the KDE libraries
3     SPDX-FileCopyrightText: 2000 Torben Weis <weis@kde.org>
4     SPDX-FileCopyrightText: 2006 David Faure <faure@kde.org>
5     SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
6 
7     SPDX-License-Identifier: LGPL-2.0-only
8 */
9 
10 #ifndef __kplugintrader_h__
11 #define __kplugintrader_h__
12 
13 #include "kplugininfo.h"
14 
15 #if KSERVICE_ENABLE_DEPRECATED_SINCE(5, 82)
16 
17 class KPluginTraderPrivate;
18 /**
19  * \class KPluginTrader kplugintrader.h <KPluginTrader>
20  *
21  * A trader interface which provides a way to query specific subdirectories in the Qt
22  * plugin paths for plugins. KPluginTrader provides an easy way to load a plugin
23  * instance from a KPluginFactory, or just querying for existing plugins.
24  *
25  * KPluginTrader provides a way for an application to query directories in the
26  * Qt plugin paths, accessed through QCoreApplication::libraryPaths().
27  * Plugins may match a specific set of requirements. This allows to find
28  * specific plugins at run-time without having to hard-code their names and/or
29  * paths. KPluginTrader does not search recursively, you are rather encouraged
30  * to install plugins into specific subdirectories to further speed searching.
31  *
32  * KPluginTrader exclusively searches within the plugin binaries' metadata
33  * (via QPluginLoader::metaData()). It does not search these directories recursively.
34  *
35  * KPluginTrader does not use KServiceTypeTrader or KSyCoCa. As such, it will
36  * only find binary plugins. If you are looking for a generic way to query for
37  * services, use KServiceTypeTrader. For anything relating to MIME types (type
38  * of files), use KMimeTypeTrader.
39  *
40  * \par Example
41  *
42  * If you want to find all plugins for your application,
43  * you would define a KMyApp/Plugin servicetype, and then you can query
44  * the trader for it:
45  * \code
46  * KPluginInfo::List offers =
47  *     KPluginTrader::self()->query("KMyApp/Plugin", "kf5");
48  * \endcode
49  *
50  * You can add a constraint in the "trader query language". For instance:
51  * \code
52  * KPluginTrader::self()->query("KMyApp/Plugin", "kf5",
53  *                                   "[X-KMyApp-InterfaceVersion] > 15");
54  * \endcode
55  *
56  * Please note that when including property names containing arithmetic operators like - or +, then you have
57  * to put brackets around the property name, in order to correctly separate arithmetic operations from
58  * the name. So for example a constraint expression like
59  * \code
60  * X-KMyApp-InterfaceVersion > 4 // wrong!
61  * \endcode
62  * needs to be written as
63  * \code
64  * [X-KMyApp-InterfaceVersion] > 4
65  * \endcode
66  * otherwise it could also be interpreted as
67  * Subtract the numeric value of the property "KMyApp" and "InterfaceVersion" from the
68  * property "X" and make sure it is greater than 4.\n
69  * Instead of the other meaning, make sure that the numeric value of "X-KMyApp-InterfaceVersion" is
70  * greater than 4.
71  *
72  * @see KMimeTypeTrader, KServiceTypeTrader, KPluginInfo
73  * @see QCoreApplication::libraryPaths
74  * @see QT_PLUGIN_PATH (env variable)
75  * @see KPluginFactory
76  * @see kservice_desktop_to_json (Cmake macro)
77  * @see K_PLUGIN_FACTORY_WITH_JSON (macro defined in KPluginFactory)
78  *
79  * @since 5.0
80  */
81 class KSERVICE_EXPORT KPluginTrader
82 {
83 public:
84     /**
85      * Standard destructor
86      */
87     ~KPluginTrader();
88 
89     /**
90      * The main function in the KPluginTrader class.
91      *
92      * It will return a list of plugins that match your specifications. Required parameter is the
93      * service type and subdirectory. This method will append the subDirectory to every path found
94      * in QCoreApplication::libraryPaths(), append the subDirectory parameter, and search through
95      * the plugin's metadata
96      *
97      * KPluginTrader exclusively searches within the plugin binaries' metadata
98      * (via QPluginLoader::metaData()). It does not search these directories recursively.
99      *
100      * The constraint parameter is used to limit the possible choices returned based on the
101      * constraints you give it.
102      *
103      * The @p constraint language is rather full.  The most common
104      * keywords are AND, OR, NOT, IN, and EXIST, all used in an
105      * almost spoken-word form.  An example is:
106      * \code
107      * (Type == 'Service') and (('KParts/ReadOnlyPart' in ServiceTypes) or (exist Exec))
108      * \endcode
109      *
110      * If you want to load a list of plugins from a specific subdirectory, you can do the following:
111      *
112      * \code
113      *
114      * const KPluginInfo::List plugins = KPluginTrader::self()->query("plasma/engines");
115      *
116      * for (const KPluginInfo &info : plugins) {
117      *      KPluginLoader loader(info.libraryPath());
118      *      const QVariantList argsWithMetaData = QVariantList() << loader.metaData().toVariantMap();
119      *      // In many cases, plugins are actually based on KPluginFactory, this is how that works:
120      *      KPluginFactory* factory = loader.factory();
121      *      if (factory) {
122      *          Engine* component = factory->create<Engine>(parent, argsWithMetaData);
123      *          if (component) {
124      *              // Do whatever you want to do with the resulting object
125      *          }
126      *      }
127      *      // Otherwise, just use the normal QPluginLoader methods
128      *      Engine *myengine = qobject_cast<Engine*>(loader.instance());
129      *      if (myengine) {
130      *          // etc. ...
131      *      }
132      * }
133      * \endcode
134      *
135      * If you have a specific query for just one plugin, use the createInstanceFromQuery method.
136      *
137      * The keys used in the query (Type, ServiceType, Exec) are all fields found in the .json files
138      * which are compiled into the plugin binaries.
139      *
140      * @param subDirectory The subdirectory under the Qt plugin path
141      * @param servicetype A service type like 'KMyApp/Plugin' or 'KFilePlugin'
142      * @param constraint  A constraint to limit the choices returned, QString() to
143      *                    get all services of the given @p servicetype
144      *
145      * @return A list of services that satisfy the query
146      * @see http://techbase.kde.org/Development/Tutorials/Services/Traders#The_KTrader_Query_Language
147      * @deprecated since 5.82, use KPluginMetaData::findPlugins.
148      */
149     KSERVICE_DEPRECATED_VERSION(5, 82, "Use KPluginMetaData::findPlugins")
150     KPluginInfo::List query(const QString &subDirectory, const QString &serviceType = QString(), const QString &constraint = QString());
151 
152     /**
153      * This is a static pointer to the KPluginTrader singleton.
154      *
155      * You will need to use this to access the KPluginTrader functionality since the
156      * constructors are protected.
157      *
158      * @deprecated since 5.82, use KPluginMetaData and KPluginFactory.
159      *
160      * @return Static KPluginTrader instance
161      */
162     KSERVICE_DEPRECATED_VERSION(5, 82, "Use KPluginMetaData and KPluginFactory")
163     static KPluginTrader *self();
164 
165     /**
166      * Get a plugin from a trader query
167      *
168      * Example:
169      * \code
170      * KMyAppPlugin* plugin = KPluginTrader::createInstanceFromQuery<KMyAppPlugin>(subDirectory, serviceType, QString(), parentObject );
171      * if ( plugin ) {
172      *     ....
173      * }
174      * \endcode
175      *
176      * @param subDirectory The subdirectory under the Qt plugin paths to search in
177      * @param serviceType The type of service for which to find a plugin
178      * @param constraint An optional constraint to pass to the trader (see KTrader)
179      * @param parent The parent object for the part itself
180      * @param args A list of arguments passed to the service component
181      * @param error The string passed here will contain an error description.
182      * @return A pointer to the newly created object or a null pointer if the
183      *         factory was unable to create an object of the given type.
184      * @deprecated since 5.82, use KPluginLoader API.
185      */
186     template<class T>
187     KSERVICE_DEPRECATED_VERSION(5, 82, "Use KPluginLoader API")
188     static T *createInstanceFromQuery(const QString &subDirectory,
189                                       const QString &serviceType = QString(),
190                                       const QString &constraint = QString(),
191                                       QObject *parent = nullptr,
192                                       const QVariantList &args = QVariantList(),
193                                       QString *error = nullptr)
194     {
195         return createInstanceFromQuery<T>(subDirectory, serviceType, constraint, parent, nullptr, args, error);
196     }
197 
198     /**
199      * Get a plugin from a trader query
200      *
201      * This method works like
202      * createInstanceFromQuery(const QString&, const QString& ,const QString&, QObject*,
203      * const QVariantList&, QString*),
204      * but you can specify an additional parent widget.  This is important for a KPart, for example.
205      *
206      * @param subDirectory The subdirectory under the Qt plugin paths to search in
207      * @param serviceType the type of service for which to find a plugin
208      * @param constraint an optional constraint to pass to the trader (see KTrader)
209      * @param parent the parent object for the part itself
210      * @param parentWidget the parent widget for the plugin
211      * @param args A list of arguments passed to the service component
212      * @param error The string passed here will contain an error description.
213      * @return A pointer to the newly created object or a null pointer if the
214      *         factory was unable to create an object of the given type.
215      * @deprecated since 5.82, use KPluginLoader API.
216      */
217     template<class T>
218     KSERVICE_DEPRECATED_VERSION(5, 82, "Use KPluginLoader API")
219     static T *createInstanceFromQuery(const QString &subDirectory,
220                                       const QString &serviceType,
221                                       const QString &constraint,
222                                       QObject *parent,
223                                       QWidget *parentWidget,
224                                       const QVariantList &args = QVariantList(),
225                                       QString *error = nullptr)
226     {
227         Q_UNUSED(parentWidget)
228         Q_UNUSED(args)
229         if (error) {
230             error->clear();
231         }
232         const KPluginInfo::List offers = self()->query(subDirectory, serviceType, constraint);
233 
234         for (const KPluginInfo &info : offers) {
235             KPluginLoader loader(info.libraryPath());
236             const QVariantList argsWithMetaData = QVariantList() << loader.metaData().toVariantMap();
237             KPluginFactory *factory = loader.factory();
238             if (factory) {
239                 T *component = factory->create<T>(parent, argsWithMetaData);
240                 if (component) {
241                     return component;
242                 }
243             }
244         }
245         if (error && error->isEmpty()) {
246             *error = QCoreApplication::translate("", "No service matching the requirements was found");
247         }
248         return nullptr;
249     }
250 
251     KSERVICE_DEPRECATED_VERSION(5, 82, "No users.")
252     static void applyConstraints(KPluginInfo::List &lst, const QString &constraint);
253 
254 private:
255     /**
256      * @internal
257      */
258     KPluginTrader();
259 
260     // disallow copy ctor and assignment operator
261     KPluginTrader(const KPluginTrader &other);
262     KPluginTrader &operator=(const KPluginTrader &rhs);
263 
264     KPluginTraderPrivate *const d;
265 };
266 #endif
267 
268 #endif
269