diff --git a/autotests/data/servicetypes/example-input.desktop b/autotests/data/servicetypes/example-input.desktop --- a/autotests/data/servicetypes/example-input.desktop +++ b/autotests/data/servicetypes/example-input.desktop @@ -27,7 +27,7 @@ Name[x-test]=xxExamplexx Name[zh_CN]=例子 Type=Service -X-KDE-ServiceTypes=foo/bar,bar/foo +X-KDE-ServiceTypes=example/servicetype,bar/foo X-Test-Integer=42 X-Test-Double=42.42 X-Test-List=a,b,c,def diff --git a/autotests/data/servicetypes/example-servicetype.desktop b/autotests/data/servicetypes/example-servicetype.desktop --- a/autotests/data/servicetypes/example-servicetype.desktop +++ b/autotests/data/servicetypes/example-servicetype.desktop @@ -1,6 +1,6 @@ [Desktop Entry] Type=ServiceType -#Name=Sample +X-KDE-ServiceType=example/servicetype [PropertyDef::X-Test-Integer] Type=int diff --git a/autotests/kpluginmetadatatest.cpp b/autotests/kpluginmetadatatest.cpp --- a/autotests/kpluginmetadatatest.cpp +++ b/autotests/kpluginmetadatatest.cpp @@ -281,10 +281,11 @@ QVERIFY(!typesPath.isEmpty()); const QString inputPath = QFINDTESTDATA("data/servicetypes/example-input.desktop"); QVERIFY(!inputPath.isEmpty()); + QTest::ignoreMessage(QtWarningMsg, qPrintable(QStringLiteral("Unable to find service type for service \"bar/foo\" listed in \"") + inputPath + QLatin1Char('"'))); KPluginMetaData md = KPluginMetaData::fromDesktopFile(inputPath, QStringList() << typesPath); QVERIFY(md.isValid()); QCOMPARE(md.name(), QStringLiteral("Example")); - QCOMPARE(md.serviceTypes(), QStringList() << QStringLiteral("foo/bar") << QStringLiteral("bar/foo")); + QCOMPARE(md.serviceTypes(), QStringList() << QStringLiteral("example/servicetype") << QStringLiteral("bar/foo")); // qDebug().noquote() << QJsonDocument(md.rawData()).toJson(); QCOMPARE(md.rawData().size(), 8); QVERIFY(md.rawData().value(QStringLiteral("KPlugin")).isObject()); diff --git a/src/lib/plugin/desktopfileparser.cpp b/src/lib/plugin/desktopfileparser.cpp --- a/src/lib/plugin/desktopfileparser.cpp +++ b/src/lib/plugin/desktopfileparser.cpp @@ -206,7 +206,7 @@ } -QByteArray readTypeEntryForCurrentGroup(QFile &df, QByteArray *nextGroup) +QByteArray readTypeEntryForCurrentGroup(QFile &df, QByteArray *nextGroup, QByteArray *pName) { QByteArray group = *nextGroup; QByteArray type; @@ -234,6 +234,13 @@ const auto match = typeEntryRegex.match(QString::fromUtf8(line)); if (match.hasMatch()) { type = match.captured(1).toUtf8(); + } else if (pName) { + const static QRegularExpression nameEntryRegex( + QStringLiteral("^X-KDE-ServiceType\\s*=\\s*(.*)$")); + const auto nameMatch = nameEntryRegex.match(QString::fromUtf8(line)); + if (nameMatch.hasMatch()) { + *pName = nameMatch.captured(1).toUtf8(); + } } } return type; @@ -290,7 +297,7 @@ QStringLiteral("kservicetypes5/") + relPath); } -static QVector* parseServiceTypesFile(const QString &inputPath) +static ServiceTypeDefinition* parseServiceTypesFile(const QString &inputPath) { int lineNr = 0; QString path = inputPath; @@ -316,20 +323,20 @@ if (!readUntilDesktopEntryGroup(df, path, lineNr)) { return nullptr; } - QVector result; + ServiceTypeDefinition result; // TODO: passing nextGroup by pointer is inefficient as it will make deep copies every time // Not exactly performance critical code though so low priority QByteArray nextGroup = "Desktop Entry"; // Type must be ServiceType now - QByteArray typeStr = readTypeEntryForCurrentGroup(df, &nextGroup); + QByteArray typeStr = readTypeEntryForCurrentGroup(df, &nextGroup, &result.m_serviceTypeName); if (typeStr != QByteArrayLiteral("ServiceType")) { qCWarning(DESKTOPPARSER) << path << "is not a valid service type: Type entry should be 'ServiceType', got" << typeStr << "instead."; return nullptr; } while (!df.atEnd()) { QByteArray currentGroup = nextGroup; - typeStr = readTypeEntryForCurrentGroup(df, &nextGroup); + typeStr = readTypeEntryForCurrentGroup(df, &nextGroup, nullptr); if (!currentGroup.startsWith(QByteArrayLiteral("PropertyDef::"))) { qCWarning(DESKTOPPARSER) << "Skipping invalid group" << currentGroup << "in service type" << path; continue; @@ -347,7 +354,7 @@ case QVariant::Double: case QVariant::Bool: qCDebug(DESKTOPPARSER) << "Found property definition" << propertyName << "with type" << typeStr; - result.push_back(CustomPropertyDefinition(propertyName, type)); + result.m_propertyDefs.push_back(CustomPropertyDefinition(propertyName, type)); break; case QVariant::Invalid: qCWarning(DESKTOPPARSER) << "Property type" << typeStr << "is not a known QVariant type." @@ -358,24 +365,20 @@ << "found in" << path << "\nOnly QString, QStringList, int, double and bool are supported."; } } - return new QVector(result); + return new ServiceTypeDefinition(result); } // a lazy map of service type definitions -typedef QCache> ServiceTypesHash; +typedef QCache ServiceTypesHash; Q_GLOBAL_STATIC(ServiceTypesHash, s_serviceTypes) // access must be guarded by serviceTypesMutex as this code could be executed by multiple threads QBasicMutex s_serviceTypesMutex; } // end of anonymous namespace -ServiceTypeDefinition::ServiceTypeDefinition() -{ -} - -ServiceTypeDefinition ServiceTypeDefinition::fromFiles(const QStringList &paths) +ServiceTypeDefinitions ServiceTypeDefinitions::fromFiles(const QStringList &paths) { - ServiceTypeDefinition ret; + ServiceTypeDefinitions ret; ret.m_definitions.reserve(paths.size()); // as we might modify the cache we need to acquire a mutex here for (const QString &serviceTypePath : paths) { @@ -389,16 +392,15 @@ return ret; } -bool ServiceTypeDefinition::addFile(const QString& path) +bool ServiceTypeDefinitions::addFile(const QString& path) { QMutexLocker lock(&s_serviceTypesMutex); - QVector* def = s_serviceTypes->object(path); + ServiceTypeDefinition* def = s_serviceTypes->object(path); if (def) { // in cache but we still must make our own copy m_definitions << *def; - } - else { + } else { // not found in cache -> we need to parse the file qCDebug(DESKTOPPARSER) << "About to parse service type file" << path; def = parseServiceTypesFile(path); @@ -412,19 +414,29 @@ return true; } -QJsonValue ServiceTypeDefinition::parseValue(const QByteArray &key, const QString &value) const +QJsonValue ServiceTypeDefinitions::parseValue(const QByteArray &key, const QString &value) const { // check whether the key has a special type associated with it - for (const CustomPropertyDefinition &propertyDef : qAsConst(m_definitions)) { - if (propertyDef.key == key) { - return propertyDef.fromString(value); + for (const auto &def : m_definitions) { + for (const CustomPropertyDefinition &propertyDef : def.m_propertyDefs) { + if (propertyDef.key == key) { + return propertyDef.fromString(value); + } } } qCDebug(DESKTOPPARSER) << "Unknown property type for key" << key << "-> falling back to string"; return QJsonValue(value); } -void DesktopFileParser::convertToJson(const QByteArray &key, ServiceTypeDefinition &serviceTypes, const QString &value, +bool ServiceTypeDefinitions::hasServiceType(const QByteArray &serviceTypeName) const +{ + const auto it = std::find_if(m_definitions.begin(), m_definitions.end(), [&serviceTypeName](const ServiceTypeDefinition &def) { + return def.m_serviceTypeName == serviceTypeName; + }); + return it != m_definitions.end(); +} + +void DesktopFileParser::convertToJson(const QByteArray &key, ServiceTypeDefinitions &serviceTypes, const QString &value, QJsonObject &json, QJsonObject &kplugin, int lineNr) { /* The following keys are recognized (and added to a "KPlugin" object): @@ -528,7 +540,7 @@ { QFile df(src); int lineNr = 0; - ServiceTypeDefinition serviceTypeDef = ServiceTypeDefinition::fromFiles(serviceTypes); + ServiceTypeDefinitions serviceTypeDef = ServiceTypeDefinitions::fromFiles(serviceTypes); readUntilDesktopEntryGroup(df, src, lineNr); DESKTOPTOJSON_VERBOSE_DEBUG << "Found [Desktop Entry] group in line" << lineNr; auto startPos = df.pos(); @@ -547,17 +559,19 @@ const auto serviceList = deserializeList(value); for (const auto &service : serviceList) { - // Make up the filename from the service type name. This assumes consistent naming... - QString absFileName = locateRelativeServiceType( - service.toLower().replace(slashChar, QLatin1Char('-')) + dotDesktop); - if (absFileName.isEmpty()) { - absFileName = locateRelativeServiceType( - service.toLower().remove(slashChar) + dotDesktop); - } - if (absFileName.isEmpty()) { - qCWarning(DESKTOPPARSER) << "Unable to find service type for service" << service << "listed in" << src; - } else { - serviceTypeDef.addFile(absFileName); + if (!serviceTypeDef.hasServiceType(service.toLatin1())) { + // Make up the filename from the service type name. This assumes consistent naming... + QString absFileName = locateRelativeServiceType( + service.toLower().replace(slashChar, QLatin1Char('-')) + dotDesktop); + if (absFileName.isEmpty()) { + absFileName = locateRelativeServiceType( + service.toLower().remove(slashChar) + dotDesktop); + } + if (absFileName.isEmpty()) { + qCWarning(DESKTOPPARSER) << "Unable to find service type for service" << service << "listed in" << src; + } else { + serviceTypeDef.addFile(absFileName); + } } } break; diff --git a/src/lib/plugin/desktopfileparser_p.h b/src/lib/plugin/desktopfileparser_p.h --- a/src/lib/plugin/desktopfileparser_p.h +++ b/src/lib/plugin/desktopfileparser_p.h @@ -36,9 +36,13 @@ struct CustomPropertyDefinition; struct ServiceTypeDefinition { - ServiceTypeDefinition(); + QVector m_propertyDefs; + QByteArray m_serviceTypeName; +}; - static ServiceTypeDefinition fromFiles(const QStringList &paths); +struct ServiceTypeDefinitions +{ + static ServiceTypeDefinitions fromFiles(const QStringList &paths); /** * @return @p value converted to the correct JSON type. * If there is no custom property definition for @p key this will simply return the string value @@ -52,16 +56,18 @@ */ bool addFile(const QString &path); + bool hasServiceType(const QByteArray &serviceTypeName) const; + private: - QVector m_definitions; + QVector m_definitions; }; namespace DesktopFileParser { QByteArray escapeValue(const QByteArray &input); QStringList deserializeList(const QString &data, char separator = ','); bool convert(const QString &src, const QStringList &serviceTypes, QJsonObject &json, QString *libraryPath); - void convertToJson(const QByteArray &key, ServiceTypeDefinition &serviceTypes, const QString &value, + void convertToJson(const QByteArray &key, ServiceTypeDefinitions &serviceTypes, const QString &value, QJsonObject &json, QJsonObject &kplugin, int lineNr); #ifdef BUILDING_DESKTOPTOJSON_TOOL void convertToCompatibilityJson(const QString &key, const QString &value, QJsonObject &json, int lineNr);