diff --git a/src/lib/plugin/kpluginmetadata.cpp b/src/lib/plugin/kpluginmetadata.cpp index 10e7cab..72092f5 100644 --- a/src/lib/plugin/kpluginmetadata.cpp +++ b/src/lib/plugin/kpluginmetadata.cpp @@ -1,393 +1,398 @@ /* This file is part of the KDE project Copyright (C) 2014 Alex Richardson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 "kpluginmetadata.h" #include "desktopfileparser_p.h" #include #include #include #include #include #include #include #include "kcoreaddons_debug.h" #include "kpluginloader.h" #include "kaboutdata.h" class KPluginMetaDataPrivate : public QSharedData { public: QString metaDataFileName; }; KPluginMetaData::KPluginMetaData() { } KPluginMetaData::KPluginMetaData(const KPluginMetaData &other) : m_metaData(other.m_metaData), m_fileName(other.fileName()), d(other.d) { } KPluginMetaData &KPluginMetaData::operator=(const KPluginMetaData &other) { m_metaData = other.m_metaData; m_fileName = other.m_fileName; d = other.d; return *this; } KPluginMetaData::~KPluginMetaData() { } KPluginMetaData::KPluginMetaData(const QString &file) { if (file.endsWith(QLatin1String(".desktop"))) { loadFromDesktopFile(file, QStringList()); } else if (file.endsWith(QLatin1String(".json"))) { d = new KPluginMetaDataPrivate; QFile f(file); bool b = f.open(QIODevice::ReadOnly); if (!b) { qCWarning(KCOREADDONS_DEBUG) << "Couldn't open" << file; return; } QJsonParseError error; m_metaData = QJsonDocument::fromJson(f.readAll(), &error).object(); if (error.error) { qCWarning(KCOREADDONS_DEBUG) << "error parsing" << file << error.errorString(); } m_fileName = file; d->metaDataFileName = file; } else { QPluginLoader loader(file); m_fileName = QFileInfo(loader.fileName()).absoluteFilePath(); m_metaData = loader.metaData().value(QStringLiteral("MetaData")).toObject(); } } KPluginMetaData::KPluginMetaData(const QPluginLoader &loader) { m_fileName = QFileInfo(loader.fileName()).absoluteFilePath(); m_metaData = loader.metaData().value(QStringLiteral("MetaData")).toObject(); } KPluginMetaData::KPluginMetaData(const KPluginLoader &loader) { m_fileName = QFileInfo(loader.fileName()).absoluteFilePath(); m_metaData = loader.metaData().value(QStringLiteral("MetaData")).toObject(); } KPluginMetaData::KPluginMetaData(const QJsonObject &metaData, const QString &file) { m_fileName = file; m_metaData = metaData; } KPluginMetaData::KPluginMetaData(const QJsonObject &metaData, const QString &pluginFile, const QString &metaDataFile) { m_fileName = pluginFile; m_metaData = metaData; if (!metaDataFile.isEmpty()) { d = new KPluginMetaDataPrivate; d->metaDataFileName = metaDataFile; } } KPluginMetaData KPluginMetaData::fromDesktopFile(const QString &file, const QStringList &serviceTypes) { KPluginMetaData result; result.loadFromDesktopFile(file, serviceTypes); return result; } void KPluginMetaData::loadFromDesktopFile(const QString &file, const QStringList &serviceTypes) { QString libraryPath; if (!DesktopFileParser::convert(file, serviceTypes, m_metaData, &libraryPath)) { Q_ASSERT(!isValid()); return; // file could not be parsed for some reason, leave this object invalid } d = new KPluginMetaDataPrivate; d->metaDataFileName = QFileInfo(file).absoluteFilePath(); if (!libraryPath.isEmpty()) { // this was a plugin with a shared library m_fileName = libraryPath; } else { // no library, make filename point to the .desktop file m_fileName = d->metaDataFileName; } } QJsonObject KPluginMetaData::rawData() const { return m_metaData; } QString KPluginMetaData::fileName() const { return m_fileName; } QString KPluginMetaData::metaDataFileName() const { return d ? d->metaDataFileName : m_fileName; } bool KPluginMetaData::isValid() const { // it can be valid even if m_fileName is empty (as long as the plugin id is set in the metadata) return !pluginId().isEmpty() && !m_metaData.isEmpty(); } bool KPluginMetaData::isHidden() const { return rootObject()[QStringLiteral("Hidden")].toBool(); } QJsonObject KPluginMetaData::rootObject() const { return m_metaData[QStringLiteral("KPlugin")].toObject(); } QStringList KPluginMetaData::readStringList(const QJsonObject &obj, const QString &key) { const QJsonValue value = obj.value(key); if (value.isUndefined() || value.isObject() || value.isNull()) { return QStringList(); } else if (value.isArray()) { return value.toVariant().toStringList(); } else { QString asString = value.isString() ? value.toString() : value.toVariant().toString(); if (asString.isEmpty()) { return QStringList(); } const QString id = obj.value(QStringLiteral("KPlugin")).toObject().value(QStringLiteral("Id")).toString(); qCWarning(KCOREADDONS_DEBUG) << "Expected JSON property" << key << "to be a string list." " Treating it as a list with a single entry:" << asString << id.toLatin1().constData(); return QStringList(asString); } } QJsonValue KPluginMetaData::readTranslatedValue(const QJsonObject &jo, const QString &key, const QJsonValue &defaultValue) { QString languageWithCountry = QLocale().name(); auto it = jo.constFind(key + QLatin1Char('[') + languageWithCountry + QLatin1Char(']')); if (it != jo.constEnd()) { return it.value(); } const QStringRef language = languageWithCountry.midRef(0, languageWithCountry.indexOf(QLatin1Char('_'))); it = jo.constFind(key + QLatin1Char('[') + language + QLatin1Char(']')); if (it != jo.constEnd()) { return it.value(); } // no translated value found -> check key it = jo.constFind(key); if (it != jo.constEnd()) { return jo.value(key); } return defaultValue; } QString KPluginMetaData::readTranslatedString(const QJsonObject &jo, const QString &key, const QString &defaultValue) { return readTranslatedValue(jo, key, defaultValue).toString(defaultValue); } static inline void addPersonFromJson(const QJsonObject &obj, QList* out) { KAboutPerson person = KAboutPerson::fromJSON(obj); if (person.name().isEmpty()) { qCWarning(KCOREADDONS_DEBUG) << "Invalid plugin metadata: Attempting to create a KAboutPerson from json without 'Name' property:" << obj; return; } out->append(person); } static QList aboutPersonFromJSON(const QJsonValue &people) { QList ret; if (people.isObject()) { // single author addPersonFromJson(people.toObject(), &ret); } else if (people.isArray()) { const QJsonArray peopleArray = people.toArray(); for (const QJsonValue &val : peopleArray) { if (val.isObject()) { addPersonFromJson(val.toObject(), &ret); } } } return ret; } QList KPluginMetaData::authors() const { return aboutPersonFromJSON(rootObject()[QStringLiteral("Authors")]); } QList KPluginMetaData::translators() const { return aboutPersonFromJSON(rootObject()[QStringLiteral("Translators")]); } QList KPluginMetaData::otherContributors() const { return aboutPersonFromJSON(rootObject()[QStringLiteral("OtherContributors")]); } QString KPluginMetaData::category() const { return rootObject()[QStringLiteral("Category")].toString(); } QString KPluginMetaData::description() const { return readTranslatedString(rootObject(), QStringLiteral("Description")); } QString KPluginMetaData::iconName() const { return rootObject()[QStringLiteral("Icon")].toString(); } QString KPluginMetaData::license() const { return rootObject()[QStringLiteral("License")].toString(); } QString KPluginMetaData::name() const { return readTranslatedString(rootObject(), QStringLiteral("Name")); } QString KPluginMetaData::copyrightText() const { return readTranslatedString(rootObject(), QStringLiteral("Copyright")); } QString KPluginMetaData::extraInformation() const { return readTranslatedString(rootObject(), QStringLiteral("ExtraInformation")); } QString KPluginMetaData::pluginId() const { QJsonObject root = rootObject(); auto nameFromMetaData = root.constFind(QStringLiteral("Id")); if (nameFromMetaData != root.constEnd()) { const QString id = nameFromMetaData.value().toString(); if (!id.isEmpty()) { return id; } } // passing QFileInfo an empty string gives the CWD, which is not what we want if (m_fileName.isEmpty()) { return QString(); } return QFileInfo(m_fileName).baseName(); } QString KPluginMetaData::version() const { return rootObject()[QStringLiteral("Version")].toString(); } QString KPluginMetaData::website() const { return rootObject()[QStringLiteral("Website")].toString(); } QStringList KPluginMetaData::dependencies() const { return readStringList(rootObject(), QStringLiteral("Dependencies")); } QStringList KPluginMetaData::serviceTypes() const { return readStringList(rootObject(), QStringLiteral("ServiceTypes")); } QStringList KPluginMetaData::mimeTypes() const { return readStringList(rootObject(), QStringLiteral("MimeTypes")); } bool KPluginMetaData::supportsMimeType(const QString &mimeType) const { QMimeDatabase db; const QMimeType mime = db.mimeTypeForName(mimeType); const QStringList mimes = mimeTypes(); auto inherits = [&](const QString &supportedMimeName) { return mime.inherits(supportedMimeName); }; return std::find_if(mimes.begin(), mimes.end(), inherits) != mimes.end(); } QStringList KPluginMetaData::formFactors() const { return readStringList(rootObject(), QStringLiteral("FormFactors")); } bool KPluginMetaData::isEnabledByDefault() const { QJsonValue val = rootObject()[QStringLiteral("EnabledByDefault")]; if (val.isBool()) { return val.toBool(); } else if (val.isString()) { return val.toString() == QLatin1String("true"); } return false; } +int KPluginMetaData::initialPreference() const +{ + return rootObject()[QStringLiteral("InitialPreference")].toInt(); +} + QString KPluginMetaData::value(const QString &key, const QString &defaultValue) const { const QJsonValue value = m_metaData.value(key); if (value.isString()) { return value.toString(defaultValue); } else if (value.isArray()) { qCWarning(KCOREADDONS_DEBUG) << "Expected JSON property" << key << "to be a single string." " but it is a stringlist"; const QStringList list = value.toVariant().toStringList(); if (list.isEmpty()) { return defaultValue; } return list.join(QChar::fromLatin1(',')); } else if (value.isBool()) { qCWarning(KCOREADDONS_DEBUG) << "Expected JSON property" << key << "to be a single string." " but it is a bool"; return value.toBool() ? QStringLiteral("true") : QStringLiteral("false"); } return defaultValue; } bool KPluginMetaData::operator==(const KPluginMetaData &other) const { return m_fileName == other.m_fileName && m_metaData == other.m_metaData; } QObject* KPluginMetaData::instantiate() const { return QPluginLoader(m_fileName).instance(); } diff --git a/src/lib/plugin/kpluginmetadata.h b/src/lib/plugin/kpluginmetadata.h index cab7db3..b093185 100644 --- a/src/lib/plugin/kpluginmetadata.h +++ b/src/lib/plugin/kpluginmetadata.h @@ -1,414 +1,423 @@ /* This file is part of the KDE project Copyright (C) 2014 Alex Richardson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. 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 KPLUGINMETADATA_H #define KPLUGINMETADATA_H #include "kcoreaddons_export.h" #include #include #include #include #include #include class KPluginLoader; class QPluginLoader; class QStringList; class KPluginMetaDataPrivate; class KAboutPerson; class QObject; /** * @class KPluginMetaData kpluginmetadata.h KPluginMetaData * * This class allows easily accessing some standardized values from the JSON metadata that * can be embedded into Qt plugins. Additional plugin-specific metadata can be retrieved by * directly reading from the QJsonObject returned by KPluginMetaData::rawData(). * * This class can be used instead of KPluginInfo from KService for applications that only load * Qt C++ plugins. * * The following keys will be read from an object "KPlugin" inside the metadata JSON: * * Key | Accessor function | JSON Type * -------------------| -------------------- | --------------------- * Name | name() | string * Description | description() | string * ExtraInformation | extraInformation() | string * Icon | iconName() | string * Authors | authors() | object array (KAboutPerson) * Category | category() | string * License | license() | string * Copyright | copyrightText() | string * Id | pluginId() | string * Version | version() | string * Website | website() | string * EnabledByDefault | isEnabledByDefault() | bool * ServiceTypes | serviceTypes() | string array * MimeTypes | mimeTypes() | string array * FormFactors | formFactors() | string array * Translators | translators() | object array (KAboutPerson) * OtherContributors | otherContributors() | object array (KAboutPerson) * * The Authors, Translators and OtherContributors keys are expected to be * list of objects that match the structure expected by KAboutPerson::fromJSON(). * * An example metadata json file could look like this: * @verbatim { "KPlugin": { "Name": "Date and Time", "Description": "Date and time by timezone", "Icon": "preferences-system-time", "Authors": { "Name": "Aaron Seigo", "Email": "aseigo@kde.org" }, "Category": "Date and Time", "Dependencies": [], "EnabledByDefault": "true", "License": "LGPL", "Id": "time", "Version": "1.0", "Website": "https://plasma.kde.org/", "ServiceTypes": ["Plasma/DataEngine"] } } @endverbatim * * @sa KAboutPerson::fromJSON() * @since 5.1 */ class KCOREADDONS_EXPORT KPluginMetaData { public: /** Creates an invalid KPluginMetaData instance */ KPluginMetaData(); /** * Reads the plugin metadata from a KPluginLoader instance. You must call KPluginLoader::setFileName() * or use the appropriate constructor on @p loader before calling this. */ KPluginMetaData(const KPluginLoader &loader); /** * Reads the plugin metadata from a QPluginLoader instance. You must call QPluginLoader::setFileName() * or use the appropriate constructor on @p loader before calling this. */ KPluginMetaData(const QPluginLoader &loader); /** * Reads the plugin metadata from a plugin or .desktop which can be loaded from @p file. * * For plugins, platform-specific library suffixes may be omitted since @p file will be resolved * using the same logic as QPluginLoader. * * If the file name ends with ".desktop", the .desktop file will be parsed instead of * reading the metadata from the QPluginLoader. This is the same as calling * KPluginMetaData::fromDesktopFile() without the serviceTypes parameter. * * If @p file ends with .json, the file will be loaded as the QJsonObject metadata. * * @see QPluginLoader::setFileName() * @see KPluginMetaData::fromDesktopFile() */ KPluginMetaData(const QString &file); /** * Creates a KPluginMetaData from a QJsonObject holding the metadata and a file name * This can be used if the data is not retrieved from a Qt C++ plugin library but from some * other source. * @see KPluginMetaData(const QJsonObject &, const QString &, const QString &) */ KPluginMetaData(const QJsonObject &metaData, const QString &file); // TODO: KF6: merge with the above and make metaDataFile default to QString() /** * Creates a KPluginMetaData * @param metaData the JSON metadata to use for this object * @param pluginFile the file that the plugin can be loaded from * @param metaDataFile the file that the JSON metadata was read from * * This can be used if the data is not retrieved from a Qt C++ plugin library but from some * other source. * * @since 5.5 */ KPluginMetaData(const QJsonObject &metaData, const QString &pluginFile, const QString &metaDataFile); /** * Copy contructor */ KPluginMetaData(const KPluginMetaData &); /** * Copy assignment */ KPluginMetaData &operator=(const KPluginMetaData &); /** * Destructor */ ~KPluginMetaData(); /** * Load a KPluginMetaData instace from a .desktop file. Unlike the constructor which takes * a single file parameter this method allows you to specify which service type files should * be parsed to determine the correct type for a given .desktop property. * This ensures that a e.g. comma-separated string list field in the .desktop file will correctly * be converted to a JSON string array. * * @note This function mostly exists for backwards-compatibility. It is recommended * that new applications load JSON files directly instead of using .desktop files for plugin metadata. * * @param file the .desktop file to load * @param serviceTypes a list of files to parse If one of these paths is a relative path it * will be resolved relative to the "kservicetypes5" subdirectory in QStandardPaths::GenericDataLocation. * If the list is empty only the default set of properties will be treated specially and all other entries * will be read as the JSON string type. * * @since 5.16 */ static KPluginMetaData fromDesktopFile(const QString &file, const QStringList &serviceTypes = QStringList()); /** * @return whether this object holds valid information about a plugin. * If this is @c true pluginId() will return a non-empty string. */ bool isValid() const; /** * @return whether this object should be hidden, this is usually not used for binary * plugins, when loading a KPluginMetaData from a .desktop file, this will reflect * the value of the "Hidden" key. * * @since 5.8 */ bool isHidden() const; /** * @return the path to the plugin. This string can be passed to the KPluginLoader * or QPluginLoader constructors in order to attempt to load this plugin. * @note It is not guaranteed that this is a valid path to a shared library (i.e. loadable * by QPluginLoader) since the metadata could also refer to a non-C++ plugin. */ QString fileName() const; /** * @return the file that the metadata was read from. This is not necessarily the same as * fileName(), since not all plugins have the metadata embedded. The metadata could also be * stored in a separate .desktop file. * * @since 5.5 */ QString metaDataFileName() const; /** * @return the full metadata stored inside the plugin file. */ QJsonObject rawData() const; /** * Tries to instantiate this plugin using KPluginMetaData::fileName(). * @note The value of KPluginMetaData::dependencies() is not used here, dependencies must be * resolved manually. * * @return The plugin root object or @c nullptr if it could not be loaded * @see QPluginLoader::instance(), KPluginLoader::instance() */ QObject *instantiate() const; /** * @return the user visible name of the plugin. */ QString name() const; /** * @return a short description of the plugin. */ QString description() const; /** * @return additional information about this plugin (e.g. for use in an "about plugin" dialog) * * @since 5.18 */ QString extraInformation() const; /** * @return the author(s) of this plugin. */ QList authors() const; /** * @return the translator(s) of this plugin. * * @since 5.18 */ QList translators() const; /** * @return a list of people that contributed to this plugin (other than the authors and translators). * * @since 5.18 */ QList otherContributors() const; /** * @return the categories of this plugin (e.g. "playlist/skin"). */ QString category() const; /** * @return the icon name for this plugin * @see QIcon::fromTheme() */ QString iconName() const; /** * @return the short license identifier (e.g. LGPL). * @see KAboutLicense::byKeyword() for retrieving the full license information */ QString license() const; /** * @return a short copyright statement * * @since 5.18 */ QString copyrightText() const; /** * @return the internal name of the plugin (for KParts Plugins this is * the same name as set in the .rc file). If the plugin name property is not set in * the metadata this will return the plugin file name without the file extension. */ QString pluginId() const; /** * @return the version of the plugin. */ QString version() const; /** * @return the website of the plugin. */ QString website() const; /** * @return a list of plugins that this plugin depends on so that it can function properly * @see KJsonPluginInfo::pluginId() */ QStringList dependencies() const; /** * @note Unlike KService this does not contain the MIME types. To get the handled MIME types * use the KPluginMetaData::mimeTypes() function. * @return a list of service types this plugin implements (e.g. "Plasma/DataEngine") */ QStringList serviceTypes() const; /** * @return a list of MIME types this plugin can handle (e.g. "application/pdf", "image/png", etc.) * @since 5.16 */ QStringList mimeTypes() const; /** * @return true if this plugin can handle the given mimetype * This is more accurate than mimeTypes().contains(mimeType) because it also * takes MIME type inheritance into account. * @since 5.66 */ bool supportsMimeType(const QString &mimeType) const; /** * @return A string list of formfactors this plugin is useful for, e.g. desktop, tablet, * handset, mediacenter, etc. * The keys for this are not formally defined. * * @since 5.12 */ QStringList formFactors() const; /** * @return whether the plugin should be enabled by default. * This is only a recommendation, applications can ignore this value if they want to. */ bool isEnabledByDefault() const; + /** + * @return the initial preference of the plugin. + * This is the preference to associate with this plugin initially (before + * the user has had any chance to define preferences for it). + * Higher values indicate stronger preference. + * @since 5.67 + */ + int initialPreference() const; + /** * @return the value for @p key from the metadata or @p defaultValue if the key does not exist * or the value for @p key is not of type string * * @see KPluginMetaData::rawData() if QString is not the correct type for @p key */ QString value(const QString &key, const QString &defaultValue = QString()) const; /** @return the value for @p key inside @p jo as a string list. If the type of @p key is string, a list with containing * just that string will be returned, if it is an array the list will contain one entry for each array member. * If the key cannot be found an empty list will be returned. */ static QStringList readStringList(const QJsonObject &jo, const QString &key); /** * Reads a value from @p jo but unlike QJsonObject::value() it allows different entries for each locale * This is done by appending the locale identifier in brackets to the key (e.g. "[de_DE]" or "[es]") * When looking for a key "foo" with German (Germany) locale we will first attempt to read "foo[de_DE]", * if that does not exist "foo[de]", finally falling back to "foo" if that also doesn't exist. * @return the translated value for @p key from @p jo or @p defaultValue if @p key was not found */ static QJsonValue readTranslatedValue(const QJsonObject &jo, const QString &key, const QJsonValue &defaultValue = QJsonValue()); /** * @return the translated value of @p key from @p jo as a string or @p defaultValue if @p key was not found * or the value for @p key is not of type string * @see KPluginMetaData::readTranslatedValue(const QJsonObject &jo, const QString &key) */ static QString readTranslatedString(const QJsonObject &jo, const QString &key, const QString &defaultValue = QString()); /** * @return @c true if this object is equal to @p other, otherwise @c false */ bool operator==(const KPluginMetaData &other) const; /** * @return @c true if this object is not equal to @p other, otherwise @c false. */ inline bool operator!=(const KPluginMetaData &other) const { return !(*this == other); } private: QJsonObject rootObject() const; void loadFromDesktopFile(const QString &file, const QStringList &serviceTypes); private: QJsonObject m_metaData; QString m_fileName; QExplicitlySharedDataPointer d; // for future binary compatible extensions }; inline uint qHash(const KPluginMetaData &md, uint seed) { return qHash(md.pluginId(), seed); } Q_DECLARE_METATYPE(KPluginMetaData) #endif // KPLUGINMETADATA_H