diff --git a/autotests/data/servicetypes/example-input.desktop b/autotests/data/servicetypes/example-input.desktop index a0680b0..52f77cb 100644 --- a/autotests/data/servicetypes/example-input.desktop +++ b/autotests/data/servicetypes/example-input.desktop @@ -1,39 +1,39 @@ [Desktop Entry] Name=Example Name[ca]=Exemple Name[ca@valencia]=Exemple Name[da]=Eksempel Name[de]=Beispiel Name[el]=Παράδειγμα Name[en_GB]=Example Name[es]=Ejemplo Name[fi]=Esimerkki Name[gl]=Exemplo Name[it]=Esempio Name[ko]=예제 Name[nb]=Eksempel Name[nl]=Voorbeeld Name[pl]=Przykład Name[pt]=Exemplo Name[pt_BR]=Exemplo Name[sk]=Príklad Name[sl]=Primer Name[sr]=Пример Name[sr@ijekavian]=Пример Name[sr@ijekavianlatin]=Primer Name[sr@latin]=Primer Name[sv]=Exempel Name[uk]=Приклад 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 X-Test-String=foobar X-Test-Bool=true # not defined -> string X-Test-Unknown=true # QSize not supported -> string X-Test-Size=10,20 diff --git a/autotests/data/servicetypes/example-servicetype.desktop b/autotests/data/servicetypes/example-servicetype.desktop index c98ddfc..db84caa 100644 --- a/autotests/data/servicetypes/example-servicetype.desktop +++ b/autotests/data/servicetypes/example-servicetype.desktop @@ -1,18 +1,18 @@ [Desktop Entry] Type=ServiceType -#Name=Sample +X-KDE-ServiceType=example/servicetype [PropertyDef::X-Test-Integer] Type=int [PropertyDef::X-Test-Double] Type=double [PropertyDef::X-Test-Bool] Type=bool [PropertyDef::X-Test-List] Type=QStringList [PropertyDef::X-Test-String] Type=QString # this is not supported -> should not convert # was used by KDE4 plasma-applet.desktop but that is no longer the case [PropertyDef::X-Test-Size] Type=QSize diff --git a/autotests/kpluginmetadatatest.cpp b/autotests/kpluginmetadatatest.cpp index 145051e..6934b63 100644 --- a/autotests/kpluginmetadatatest.cpp +++ b/autotests/kpluginmetadatatest.cpp @@ -1,351 +1,352 @@ /* * Copyright 2014 Alex Richardson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . */ #include #include #include #include #include namespace QTest { template<> inline char *toString(const QJsonValue &val) { // simply reuse the QDebug representation QString result; QDebug(&result) << val; return QTest::toString(result); } } class KPluginMetaDataTest : public QObject { Q_OBJECT private Q_SLOTS: void testFromPluginLoader() { QString location = KPluginLoader::findPlugin(QStringLiteral("jsonplugin")); QVERIFY2(!location.isEmpty(),"Could not find jsonplugin"); // now that this file is translated we need to read it instead of hardcoding the contents here QString jsonLocation = QFINDTESTDATA("jsonplugin.json"); QVERIFY2(!jsonLocation.isEmpty(), "Could not find jsonplugin.json"); QFile jsonFile(jsonLocation); QVERIFY(jsonFile.open(QFile::ReadOnly)); QJsonParseError e; QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonFile.readAll(), &e); QCOMPARE(e.error, QJsonParseError::NoError); location = QFileInfo(location).absoluteFilePath(); KPluginMetaData fromQPluginLoader(QPluginLoader(QStringLiteral("jsonplugin"))); KPluginMetaData fromKPluginLoader(KPluginLoader(QStringLiteral("jsonplugin"))); KPluginMetaData fromFullPath(location); KPluginMetaData fromRelativePath(QStringLiteral("jsonplugin")); KPluginMetaData fromRawData(jsonDoc.object(), location); auto description = QStringLiteral("This is a plugin"); QVERIFY(fromQPluginLoader.isValid()); QCOMPARE(fromQPluginLoader.description(), description); QVERIFY(fromKPluginLoader.isValid()); QCOMPARE(fromKPluginLoader.description(), description); QVERIFY(fromFullPath.isValid()); QCOMPARE(fromFullPath.description(), description); QVERIFY(fromRelativePath.isValid()); QCOMPARE(fromRelativePath.description(), description); QVERIFY(fromRawData.isValid()); QCOMPARE(fromRawData.description(), description); // check operator== QCOMPARE(fromRawData, fromRawData); QCOMPARE(fromQPluginLoader, fromQPluginLoader); QCOMPARE(fromKPluginLoader, fromKPluginLoader); QCOMPARE(fromFullPath, fromFullPath); QCOMPARE(fromQPluginLoader, fromKPluginLoader); QCOMPARE(fromQPluginLoader, fromFullPath); QCOMPARE(fromQPluginLoader, fromRawData); QCOMPARE(fromKPluginLoader, fromQPluginLoader); QCOMPARE(fromKPluginLoader, fromFullPath); QCOMPARE(fromKPluginLoader, fromRawData); QCOMPARE(fromFullPath, fromQPluginLoader); QCOMPARE(fromFullPath, fromKPluginLoader); QCOMPARE(fromFullPath, fromRawData); QCOMPARE(fromRawData, fromQPluginLoader); QCOMPARE(fromRawData, fromKPluginLoader); QCOMPARE(fromRawData, fromFullPath); } void testAllKeys() { QJsonParseError e; QJsonObject jo = QJsonDocument::fromJson("{\n" " \"KPlugin\": {\n" " \"Name\": \"Date and Time\",\n" " \"Description\": \"Date and time by timezone\",\n" " \"Icon\": \"preferences-system-time\",\n" " \"Authors\": { \"Name\": \"Aaron Seigo\", \"Email\": \"aseigo@kde.org\" },\n" " \"Translators\": { \"Name\": \"No One\", \"Email\": \"no.one@kde.org\" },\n" " \"OtherContributors\": { \"Name\": \"No One\", \"Email\": \"no.one@kde.org\" },\n" " \"Category\": \"Date and Time\",\n" " \"Dependencies\": [ \"foo\", \"bar\"],\n" " \"EnabledByDefault\": \"true\",\n" " \"ExtraInformation\": \"Something else\",\n" " \"License\": \"LGPL\",\n" " \"Copyright\": \"(c) Alex Richardson 2015\",\n" " \"Id\": \"time\",\n" " \"Version\": \"1.0\",\n" " \"Website\": \"https://plasma.kde.org/\",\n" " \"MimeTypes\": [ \"image/png\" ],\n" " \"ServiceTypes\": [\"Plasma/DataEngine\"]\n" " }\n}\n", &e).object(); QCOMPARE(e.error, QJsonParseError::NoError); KPluginMetaData m(jo, QString()); QVERIFY(m.isValid()); QCOMPARE(m.pluginId(), QStringLiteral("time")); QCOMPARE(m.name(), QStringLiteral("Date and Time")); QCOMPARE(m.description(), QStringLiteral("Date and time by timezone")); QCOMPARE(m.extraInformation(), QStringLiteral("Something else")); QCOMPARE(m.iconName(), QStringLiteral("preferences-system-time")); QCOMPARE(m.category(), QStringLiteral("Date and Time")); QCOMPARE(m.dependencies(), QStringList() << QStringLiteral("foo") << QStringLiteral("bar")); QCOMPARE(m.authors().size(), 1); QCOMPARE(m.authors()[0].name(), QStringLiteral("Aaron Seigo")); QCOMPARE(m.authors()[0].emailAddress(), QStringLiteral("aseigo@kde.org")); QCOMPARE(m.translators().size(), 1); QCOMPARE(m.translators()[0].name(), QStringLiteral("No One")); QCOMPARE(m.translators()[0].emailAddress(), QStringLiteral("no.one@kde.org")); QCOMPARE(m.otherContributors().size(), 1); QCOMPARE(m.otherContributors()[0].name(), QStringLiteral("No One")); QCOMPARE(m.otherContributors()[0].emailAddress(), QStringLiteral("no.one@kde.org")); QVERIFY(m.isEnabledByDefault()); QCOMPARE(m.license(), QStringLiteral("LGPL")); QCOMPARE(m.copyrightText(), QStringLiteral("(c) Alex Richardson 2015")); QCOMPARE(m.version(), QStringLiteral("1.0")); QCOMPARE(m.website(), QStringLiteral("https://plasma.kde.org/")); QCOMPARE(m.serviceTypes(), QStringList() << QStringLiteral("Plasma/DataEngine")); QCOMPARE(m.mimeTypes(), QStringList() << QStringLiteral("image/png")); } void testTranslations() { QJsonParseError e; QJsonObject jo = QJsonDocument::fromJson("{ \"KPlugin\": {\n" "\"Name\": \"Name\",\n" "\"Name[de]\": \"Name (de)\",\n" "\"Name[de_DE]\": \"Name (de_DE)\",\n" "\"Description\": \"Description\",\n" "\"Description[de]\": \"Beschreibung (de)\",\n" "\"Description[de_DE]\": \"Beschreibung (de_DE)\"\n" "}\n}", &e).object(); KPluginMetaData m(jo, QString()); QLocale::setDefault(QLocale::c()); QCOMPARE(m.name(), QStringLiteral("Name")); QCOMPARE(m.description(), QStringLiteral("Description")); QLocale::setDefault(QLocale(QStringLiteral("de_DE"))); QCOMPARE(m.name(), QStringLiteral("Name (de_DE)")); QCOMPARE(m.description(), QStringLiteral("Beschreibung (de_DE)")); QLocale::setDefault(QLocale(QStringLiteral("de_CH"))); QCOMPARE(m.name(), QStringLiteral("Name (de)")); QCOMPARE(m.description(), QStringLiteral("Beschreibung (de)")); QLocale::setDefault(QLocale(QStringLiteral("fr_FR"))); QCOMPARE(m.name(), QStringLiteral("Name")); QCOMPARE(m.description(), QStringLiteral("Description")); } void testReadStringList() { QJsonParseError e; QJsonObject jo = QJsonDocument::fromJson("{\n" "\"String\": \"foo\",\n" "\"OneArrayEntry\": [ \"foo\" ],\n" "\"Bool\": true,\n" // make sure booleans are accepted "\"QuotedBool\": \"true\",\n" // make sure booleans are accepted "\"Number\": 12345,\n" // number should also work "\"QuotedNumber\": \"12345\",\n" // number should also work "\"EmptyArray\": [],\n" "\"NumberArray\": [1, 2, 3],\n" "\"BoolArray\": [true, false, true],\n" "\"StringArray\": [\"foo\", \"bar\"],\n" "\"Null\": null,\n" // should return empty list "\"QuotedNull\": \"null\",\n" // this is okay, it is a string "\"ArrayWithNull\": [ \"foo\", null, \"bar\"],\n" // TODO: null is converted to empty string, is this okay? "\"Object\": { \"foo\": \"bar\" }\n" // should return empty list "}", &e).object(); QCOMPARE(e.error, QJsonParseError::NoError); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"String\" to be a string list. Treating it as a list with a single entry: \"foo\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("String")), QStringList(QStringLiteral("foo")));; QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("OneArrayEntry")), QStringList(QStringLiteral("foo"))); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"Bool\" to be a string list. Treating it as a list with a single entry: \"true\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("Bool")), QStringList(QStringLiteral("true"))); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"QuotedBool\" to be a string list. Treating it as a list with a single entry: \"true\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("QuotedBool")), QStringList(QStringLiteral("true"))); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"Number\" to be a string list. Treating it as a list with a single entry: \"12345\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("Number")), QStringList(QStringLiteral("12345"))); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"QuotedNumber\" to be a string list. Treating it as a list with a single entry: \"12345\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("QuotedNumber")), QStringList(QStringLiteral("12345"))); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("EmptyArray")), QStringList()); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("NumberArray")), QStringList() << QStringLiteral("1") << QStringLiteral("2") << QStringLiteral("3")); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("BoolArray")), QStringList() << QStringLiteral("true") << QStringLiteral("false") << QStringLiteral("true")); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("StringArray")), QStringList() << QStringLiteral("foo") << QStringLiteral("bar")); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("Null")), QStringList()); QTest::ignoreMessage(QtWarningMsg, "Expected JSON property \"QuotedNull\" to be a string list. Treating it as a list with a single entry: \"null\" "); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("QuotedNull")), QStringList(QStringLiteral("null"))); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("ArrayWithNull")), QStringList() << QStringLiteral("foo") << QStringLiteral("") << QStringLiteral("bar")); QCOMPARE(KPluginMetaData::readStringList(jo, QStringLiteral("Object")), QStringList()); } void testFromDesktopFile() { const QString dfile = QFINDTESTDATA("data/fakeplugin.desktop"); KPluginMetaData md(dfile); QVERIFY(md.isValid()); QCOMPARE(md.pluginId(), QStringLiteral("fakeplugin")); QCOMPARE(md.fileName(), QStringLiteral("fakeplugin")); QCOMPARE(md.metaDataFileName(), dfile); QCOMPARE(md.iconName(), QStringLiteral("preferences-system-time")); QCOMPARE(md.license(), QStringLiteral("LGPL")); QCOMPARE(md.website(), QStringLiteral("https://kde.org/")); QCOMPARE(md.category(), QStringLiteral("Examples")); QCOMPARE(md.version(), QStringLiteral("1.0")); QCOMPARE(md.dependencies(), QStringList()); QCOMPARE(md.isHidden(), false); QCOMPARE(md.serviceTypes(), QStringList(QStringLiteral("KService/NSA"))); QCOMPARE(md.mimeTypes(), QStringList() << QStringLiteral("image/png") << QStringLiteral("application/pdf")); auto kp = md.rawData()[QStringLiteral("KPlugin")].toObject(); QStringList formFactors = KPluginMetaData::readStringList(kp, QStringLiteral("FormFactors")); QCOMPARE(formFactors, QStringList() << QStringLiteral("mediacenter") << QStringLiteral("desktop")); QCOMPARE(md.formFactors(), QStringList() << QStringLiteral("mediacenter") << QStringLiteral("desktop")); const QString dfilehidden = QFINDTESTDATA("data/hiddenplugin.desktop"); KPluginMetaData mdhidden(dfilehidden); QVERIFY(mdhidden.isValid()); QCOMPARE(mdhidden.isHidden(), true); } void twoStepsParseTest() { QStandardPaths::setTestModeEnabled(true); const QString dfile = QFINDTESTDATA("data/twostepsparsetest.desktop"); const QString typesPath = QFINDTESTDATA("data/servicetypes/example-servicetype.desktop"); KPluginMetaData md = KPluginMetaData::fromDesktopFile(dfile, QStringList() << typesPath); QVERIFY(md.isValid()); QStringList list = KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Test-List")); QCOMPARE(list, QStringList({QStringLiteral("first"), QStringLiteral("second")})); } void testServiceTypes_data() { const QString kdevServiceTypePath = QFINDTESTDATA("data/servicetypes/fake-kdevelopplugin.desktop"); const QString invalidServiceTypePath = QFINDTESTDATA("data/servicetypes/invalid-servicetype.desktop"); const QString exampleServiceTypePath = QFINDTESTDATA("data/servicetypes/example-servicetype.desktop"); QVERIFY(!kdevServiceTypePath.isEmpty()); QVERIFY(!invalidServiceTypePath.isEmpty()); QVERIFY(!exampleServiceTypePath.isEmpty()); } void testServiceType() { const QString typesPath = QFINDTESTDATA("data/servicetypes/example-servicetype.desktop"); 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()); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Integer")), QJsonValue(42)); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Bool")), QJsonValue(true)); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Double")), QJsonValue(42.42)); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-String")), QJsonValue(QStringLiteral("foobar"))); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-List")), QJsonValue(QJsonArray::fromStringList(QStringList() << QStringLiteral("a") << QStringLiteral("b") << QStringLiteral("c") << QStringLiteral("def")))); QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Size")), QJsonValue(QStringLiteral("10,20"))); // QSize no longer supported (and also no longer used) QCOMPARE(md.rawData().value(QStringLiteral("X-Test-Unknown")), QJsonValue(QStringLiteral("true"))); // unknown property -> string } void testBadGroupsInServiceType() { const QString typesPath = QFINDTESTDATA("data/servicetypes/bad-groups-servicetype.desktop"); QVERIFY(!typesPath.isEmpty()); const QString inputPath = QFINDTESTDATA("data/servicetypes/bad-groups-input.desktop"); QVERIFY(!inputPath.isEmpty()); QTest::ignoreMessage(QtWarningMsg, "Illegal .desktop group definition (does not end with ']'): \"[PropertyDef::MissingTerminator\""); QTest::ignoreMessage(QtWarningMsg, "Illegal .desktop group definition (does not end with ']'): \"[PropertyDef::\""); QTest::ignoreMessage(QtWarningMsg, "Illegal .desktop group definition (does not end with ']'): \"[\""); QTest::ignoreMessage(QtWarningMsg, "Read empty .desktop file group name! Invalid file?"); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral("Skipping invalid group \"\" in service type \".*/bad-groups-servicetype.desktop\""))); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral("Skipping invalid group \"DoesNotStartWithPropertyDef::SomeOtherProperty\" in service type \".+/data/servicetypes/bad-groups-servicetype.desktop\""))); QTest::ignoreMessage(QtWarningMsg, "Could not find Type= key in group \"PropertyDef::MissingType\""); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral("Property type \"integer\" is not a known QVariant type. Found while parsing property definition for \"InvalidType\" in \".+/data/servicetypes/bad-groups-servicetype.desktop\""))); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral(".+/data/servicetypes/bad-groups-input.desktop:\\d+: Key name is missing: \"=11\""))); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral(".+/data/servicetypes/bad-groups-input.desktop:\\d+: Key name is missing: \"=13\""))); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral(".+/data/servicetypes/bad-groups-input.desktop:\\d+: Key name is missing: \"=14\""))); KPluginMetaData md = KPluginMetaData::fromDesktopFile(inputPath, QStringList() << typesPath); QVERIFY(md.isValid()); QCOMPARE(md.name(), QStringLiteral("Bad Groups")); // qDebug().noquote() << QJsonDocument(md.rawData()).toJson(); QCOMPARE(md.rawData().size(), 8); QCOMPARE(md.rawData().value(QStringLiteral("ThisIsOkay")), QJsonValue(10)); // integer // 11 is empty group QCOMPARE(md.rawData().value(QStringLiteral("MissingTerminator")), QJsonValue(12)); // accept missing group terminator (for now) -> integer // 13 is empty group name // 14 is empty group name QCOMPARE(md.rawData().value(QStringLiteral("SomeOtherProperty")), QJsonValue(QStringLiteral("15"))); // does not start with PropertyDef:: -> fall back to string QCOMPARE(md.rawData().value(QStringLiteral("TrailingSpacesAreOkay")), QJsonValue(16)); // accept trailing spaces in group name -> integer QCOMPARE(md.rawData().value(QStringLiteral("MissingType")), QJsonValue(QStringLiteral("17"))); // Type= missing -> fall back to string QCOMPARE(md.rawData().value(QStringLiteral("InvalidType")), QJsonValue(QStringLiteral("18"))); // Type= is invalid -> fall back to string QCOMPARE(md.rawData().value(QStringLiteral("ThisIsOkayAgain")), QJsonValue(19)); // valid definition after invalid ones should still work -> integer } void testJSONMetadata() { const QString inputPath = QFINDTESTDATA("data/testmetadata.json"); KPluginMetaData md(inputPath); QVERIFY(md.isValid()); QCOMPARE(md.name(), QStringLiteral("Test")); QCOMPARE(md.value(QStringLiteral("X-Plasma-MainScript")), QStringLiteral("ui/main.qml")); QJsonArray expected; expected.append(QStringLiteral("Export")); QCOMPARE(md.rawData().value(QStringLiteral("X-Purpose-PluginTypes")).toArray(), expected); } }; QTEST_MAIN(KPluginMetaDataTest) #include "kpluginmetadatatest.moc" diff --git a/src/lib/plugin/desktopfileparser.cpp b/src/lib/plugin/desktopfileparser.cpp index c164f0a..150b4aa 100644 --- a/src/lib/plugin/desktopfileparser.cpp +++ b/src/lib/plugin/desktopfileparser.cpp @@ -1,595 +1,609 @@ /****************************************************************************** * Copyright 2013-2014 Sebastian Kügler * * Copyright 2014 Alex Richardson * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) version 3, or any * * later version accepted by the membership of KDE e.V. (or its * * successor approved by the membership of KDE e.V.), which shall * * act as a proxy defined in Section 6 of version 3 of the license. * * * * 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library. If not, see * * . * * * ******************************************************************************/ #include "desktopfileparser_p.h" #include #include #include #include #include #include #include #include #include // in the desktoptojson binary enable debug messages by default, in the library only warning messages #ifdef BUILDING_DESKTOPTOJSON_TOOL Q_LOGGING_CATEGORY(DESKTOPPARSER, "kf5.kcoreaddons.desktopparser", QtDebugMsg) #else Q_LOGGING_CATEGORY(DESKTOPPARSER, "kf5.kcoreaddons.desktopparser", QtWarningMsg) #endif #ifdef BUILDING_DESKTOPTOJSON_TOOL // use if not else to prevent wrong scoping #define DESKTOPTOJSON_VERBOSE_DEBUG if (!DesktopFileParser::s_verbose) {} else qCDebug(DESKTOPPARSER) #define DESKTOPTOJSON_VERBOSE_WARNING if (!DesktopFileParser::s_verbose) {} else qCWarning(DESKTOPPARSER) #else #define DESKTOPTOJSON_VERBOSE_DEBUG QT_NO_QDEBUG_MACRO() #define DESKTOPTOJSON_VERBOSE_WARNING QT_NO_QDEBUG_MACRO() #endif using namespace DesktopFileParser; // This code was taken from KConfigGroupPrivate::deserializeList QStringList DesktopFileParser::deserializeList(const QString &data, char separator) { if (data.isEmpty()) { return QStringList(); } if (data == QLatin1String("\\0")) { return QStringList(QString()); } QStringList value; QString val; val.reserve(data.size()); bool quoted = false; for (int p = 0; p < data.length(); p++) { if (quoted) { val += data[p]; quoted = false; } else if (data[p].unicode() == '\\') { quoted = true; } else if (data[p].unicode() == separator) { value.append(val); if (p == data.length() - 1) { // don't add an empty entry to the end if the last character is a separator return value; } val.clear(); val.reserve(data.size() - p); } else { val += data[p]; } } value.append(val); return value; } QByteArray DesktopFileParser::escapeValue(const QByteArray &input) { const int start = input.indexOf('\\'); if (start < 0) { return input; } // we could do this in place, but this code is simpler // this tool is probably only transitional, so no need to optimize QByteArray result; result.reserve(input.size()); result.append(input.data(), start); for (int i = start; i < input.length(); ++i) { if (input[i] != '\\') { result.append(input[i]); } else { if (i + 1 >= input.length()) { // just append the backslash if we are at end of line result.append(input[i]); break; } i++; // consume next character char nextChar = input[i]; switch (nextChar) { case 's': result.append(' '); break; case 'n': result.append('\n'); break; case 't': result.append('\t'); break; case 'r': result.append('\r'); break; case '\\': result.append('\\'); break; default: result.append('\\'); result.append(nextChar); // just ignore the escape sequence } } } return result; } struct CustomPropertyDefinition { // default ctor needed for QVector CustomPropertyDefinition() : type(QVariant::String) {} CustomPropertyDefinition(const QByteArray &key, QVariant::Type type) : key(key) , type(type) {} QJsonValue fromString(const QString &str) const { switch (type) { case QVariant::String: return str; case QVariant::StringList: return QJsonArray::fromStringList(deserializeList(str)); case QVariant::Int: { bool ok = false; int result = str.toInt(&ok); if (!ok) { qCWarning(DESKTOPPARSER) << "Invalid integer value for key" << key << "-" << str; return QJsonValue(); } return QJsonValue(result); } case QVariant::Double: { bool ok = false; double result = str.toDouble(&ok); if (!ok) { qCWarning(DESKTOPPARSER) << "Invalid double value for key" << key << "-" << str; return QJsonValue(); } return QJsonValue(result); } case QVariant::Bool: { bool result = str.compare(QLatin1String("true"), Qt::CaseInsensitive) == 0; if (!result && str.compare(QLatin1String("false"), Qt::CaseInsensitive) != 0) { qCWarning(DESKTOPPARSER) << "Invalid boolean value for key" << key << "-" << str; return QJsonValue(); } return QJsonValue(result); } default: // This was checked when parsing the file, no other QVariant::Type values are possible Q_UNREACHABLE(); } } QByteArray key; QVariant::Type type; }; namespace { bool readUntilDesktopEntryGroup(QFile &file, const QString &path, int &lineNr) { if (!file.open(QFile::ReadOnly)) { qCWarning(DESKTOPPARSER) << "Error: Failed to open " << path; return false; } // we only convert data inside the [Desktop Entry] group while (!file.atEnd()) { const QByteArray line = file.readLine().trimmed(); lineNr++; if (line == "[Desktop Entry]") { return true; } } qCWarning(DESKTOPPARSER) << "Error: Could not find [Desktop Entry] group in " << path; return false; } -QByteArray readTypeEntryForCurrentGroup(QFile &df, QByteArray *nextGroup) +QByteArray readTypeEntryForCurrentGroup(QFile &df, QByteArray *nextGroup, QByteArray *pName) { QByteArray group = *nextGroup; QByteArray type; if (group.isEmpty()) { qCWarning(DESKTOPPARSER, "Read empty .desktop file group name! Invalid file?"); } while (!df.atEnd()) { QByteArray line = df.readLine().trimmed(); // skip empty lines and comments if (line.isEmpty() || line.startsWith('#')) { continue; } if (line.startsWith('[')) { if (!line.endsWith(']')) { qCWarning(DESKTOPPARSER) << "Illegal .desktop group definition (does not end with ']'):" << line; } QByteArray name = line.mid(1, line.lastIndexOf(']') - 1).trimmed(); // we have reached the next group -> return current group and Type= value *nextGroup = name; break; } const static QRegularExpression typeEntryRegex( QStringLiteral("^Type\\s*=\\s*(.*)$")); 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; } bool tokenizeKeyValue(QFile &df, const QString &src, QByteArray &key, QString &value, int &lineNr) { const QByteArray line = df.readLine().trimmed(); lineNr++; if (line.isEmpty()) { DESKTOPTOJSON_VERBOSE_DEBUG << "Line " << lineNr << ": empty"; return true; } if (line.startsWith('#')) { DESKTOPTOJSON_VERBOSE_DEBUG << "Line " << lineNr << ": comment"; return true; // skip comments } if (line.startsWith('[')) { // start of new group -> doesn't interest us anymore DESKTOPTOJSON_VERBOSE_DEBUG << "Line " << lineNr << ": start of new group " << line; return false; } // must have form key=value now const int equalsIndex = line.indexOf('='); if (equalsIndex == -1) { qCWarning(DESKTOPPARSER).nospace() << qPrintable(src) << ':' << lineNr << ": Line is neither comment nor group " "and doesn't contain an '=' character: \"" << line.constData() << '\"'; return true; } // trim key and value to remove spaces around the '=' char key = line.mid(0, equalsIndex).trimmed(); if (key.isEmpty()) { qCWarning(DESKTOPPARSER).nospace() << qPrintable(src) << ':' << lineNr << ": Key name is missing: \"" << line.constData() << '\"'; return true; } const QByteArray valueRaw = line.mid(equalsIndex + 1).trimmed(); const QByteArray valueEscaped = escapeValue(valueRaw); value = QString::fromUtf8(valueEscaped); #ifdef BUILDING_DESKTOPTOJSON_TOOL DESKTOPTOJSON_VERBOSE_DEBUG.nospace() << "Line " << lineNr << ": key=" << key << ", value=" << value; if (valueEscaped != valueRaw) { DESKTOPTOJSON_VERBOSE_DEBUG << "Line " << lineNr << " contained escape sequences"; } #endif return true; } static QString locateRelativeServiceType(const QString &relPath) { return QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kservicetypes5/") + relPath); } -static QVector* parseServiceTypesFile(const QString &inputPath) +static ServiceTypeDefinition* parseServiceTypesFile(const QString &inputPath) { int lineNr = 0; QString path = inputPath; if (QDir::isRelativePath(path)) { path = locateRelativeServiceType(path); QString rcPath; if (path.isEmpty()) { rcPath = QStringLiteral(":/kservicetypes5/") + inputPath; if (QFileInfo::exists(rcPath)) { path = rcPath; } } if (path.isEmpty()) { qCWarning(DESKTOPPARSER).nospace() << "Could not locate service type file kservicetypes5/" << qPrintable(inputPath) << ", tried " << QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation) << " and " << rcPath; return nullptr; } } QFile df(path); if (!df.exists()) { qCCritical(DESKTOPPARSER) << "Service type file" << path << "does not exist"; return nullptr; } 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; } if (typeStr.isEmpty()) { qCWarning(DESKTOPPARSER) << "Could not find Type= key in group" << currentGroup; continue; } QByteArray propertyName = currentGroup.mid(qstrlen("PropertyDef::")); QVariant::Type type = QVariant::nameToType(typeStr.constData()); switch (type) { case QVariant::String: case QVariant::StringList: case QVariant::Int: 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." " Found while parsing property definition for" << propertyName << "in" << path; break; default: qCWarning(DESKTOPPARSER) << "Unsupported property type" << typeStr << "for property" << propertyName << "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) { bool added = ret.addFile(serviceTypePath); if (!added) { #ifdef BUILDING_DESKTOPTOJSON_TOOL exit(1); // this is a fatal error when using kcoreaddons_desktop_to_json() #endif } } 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); if (!def) { return false; } m_definitions << *def; // This must *precede* insert call, insert might delete s_serviceTypes->insert(path, def); } 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): Icon=mypluginicon Type=Service ServiceTypes=KPluginInfo MimeType=text/plain;image/png Name=User Visible Name (translatable) Comment=Description of what the plugin does (translatable) X-KDE-PluginInfo-Author=Author's Name X-KDE-PluginInfo-Email=author@foo.bar X-KDE-PluginInfo-Name=internalname X-KDE-PluginInfo-Version=1.1 X-KDE-PluginInfo-Website=http://www.plugin.org/ X-KDE-PluginInfo-Category=playlist X-KDE-PluginInfo-Depends=plugin1,plugin3 X-KDE-PluginInfo-License=GPL X-KDE-PluginInfo-EnabledByDefault=true X-KDE-FormFactors=desktop */ if (key == QByteArrayLiteral("Icon")) { kplugin[QStringLiteral("Icon")] = value; } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Name")) { kplugin[QStringLiteral("Id")] = value; } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Category")) { kplugin[QStringLiteral("Category")] = value; } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-License")) { kplugin[QStringLiteral("License")] = value; } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Version")) { kplugin[QStringLiteral("Version")] = value; } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Website")) { kplugin[QStringLiteral("Website")] = value; } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Depends")) { kplugin[QStringLiteral("Dependencies")] = QJsonArray::fromStringList(deserializeList(value)); } else if (key == QByteArrayLiteral("X-KDE-ServiceTypes") || key == QByteArrayLiteral("ServiceTypes")) { //NOTE: "X-KDE-ServiceTypes" and "ServiceTypes" were already managed in the first parse step, so this second one is almost a noop const auto services = deserializeList(value); kplugin[QStringLiteral("ServiceTypes")] = QJsonArray::fromStringList(services); } else if (key == QByteArrayLiteral("MimeType")) { // MimeType is a XDG string list and not a KConfig list so we need to use ';' as the separator kplugin[QStringLiteral("MimeTypes")] = QJsonArray::fromStringList(deserializeList(value, ';')); // make sure that applications using kcoreaddons_desktop_to_json() that depend on reading // the MimeType property still work (see https://git.reviewboard.kde.org/r/125527/) json[QStringLiteral("MimeType")] = value; // TODO KF6 remove this compatibility code } else if (key == QByteArrayLiteral("X-KDE-FormFactors")) { kplugin[QStringLiteral("FormFactors")] = QJsonArray::fromStringList(deserializeList(value)); } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-EnabledByDefault")) { bool boolValue = false; // should only be lower case, but be tolerant here if (value.toLower() == QLatin1String("true")) { boolValue = true; } else { if (value.toLower() != QLatin1String("false")) { qCWarning(DESKTOPPARSER).nospace() << "Expected boolean value for key \"" << key << "\" at line " << lineNr << "but got \"" << value << "\" instead."; } } kplugin[QStringLiteral("EnabledByDefault")] = boolValue; } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Author")) { QJsonObject authorsObject = kplugin.value(QStringLiteral("Authors")).toArray().at(0).toObject(); // if the authors object doesn't exist yet this will create it authorsObject[QStringLiteral("Name")] = value; QJsonArray array; array.append(authorsObject); kplugin[QStringLiteral("Authors")] = array; } else if (key == QByteArrayLiteral("X-KDE-PluginInfo-Email")) { QJsonObject authorsObject = kplugin.value(QStringLiteral("Authors")).toArray().at(0).toObject(); // if the authors object doesn't exist yet this will create it authorsObject[QStringLiteral("Email")] = value; QJsonArray array; array.append(authorsObject); kplugin[QStringLiteral("Authors")] = array; } else if (key == QByteArrayLiteral("Name") || key.startsWith(QByteArrayLiteral("Name["))) { // TODO: also handle GenericName? does that make any sense, or is X-KDE-PluginInfo-Category enough? kplugin[QString::fromUtf8(key)] = value; } else if (key == QByteArrayLiteral("Comment")) { kplugin[QStringLiteral("Description")] = value; } else if (key.startsWith(QByteArrayLiteral("Comment["))) { kplugin[QStringLiteral("Description") + QString::fromUtf8(key.mid(qstrlen("Comment")))] = value; } else if (key == QByteArrayLiteral("Hidden")) { DESKTOPTOJSON_VERBOSE_WARNING << "Hidden= key found in desktop file, this makes no sense" " with metadata inside the plugin."; kplugin[QString::fromUtf8(key)] = (value.toLower() == QLatin1String("true")); } else if (key == QByteArrayLiteral("Exec") || key == QByteArrayLiteral("Type") || key == QByteArrayLiteral("X-KDE-Library") || key == QByteArrayLiteral("Encoding")) { // Exec= doesn't make sense here, however some .desktop files (like e.g. in kdevelop) have a dummy value here // also the Type=Service entry is no longer needed // X-KDE-Library is also not needed since we already have the library to read this metadata // Encoding= is also not converted as we always use utf-8 for reading DESKTOPTOJSON_VERBOSE_DEBUG << "Not converting key " << key << "=" << value; } else { // check service type definitions or fall back to QString json[QString::fromUtf8(key)] = serviceTypes.parseValue(key, value); } } bool DesktopFileParser::convert(const QString &src, const QStringList &serviceTypes, QJsonObject &json, QString *libraryPath) { 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(); //parse it a first time to know servicetype while (!df.atEnd()) { QByteArray key; QString value; if (!tokenizeKeyValue(df, src, key, value, lineNr)) { break; } // some .desktop files still use the legacy ServiceTypes= key if (key == QByteArrayLiteral("X-KDE-ServiceTypes") || key == QByteArrayLiteral("ServiceTypes")) { const QString dotDesktop = QStringLiteral(".desktop"); const QChar slashChar(QLatin1Char('/')); 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; } } lineNr=0; df.seek(startPos); QJsonObject kplugin; // the "KPlugin" key of the metadata //QJsonObject json; while (!df.atEnd()) { QByteArray key; QString value; if (!tokenizeKeyValue(df, src, key, value, lineNr)) { break; } else if (key.isEmpty()) { continue; } #ifdef BUILDING_DESKTOPTOJSON_TOOL if (s_compatibilityMode) { convertToCompatibilityJson(QString::fromUtf8(key), value, json, lineNr); } else { convertToJson(key, serviceTypeDef, value, json, kplugin, lineNr); } #else convertToJson(key, serviceTypeDef, value, json, kplugin, lineNr); #endif if (libraryPath && key == QByteArrayLiteral("X-KDE-Library")) { *libraryPath = value; } } json[QStringLiteral("KPlugin")] = kplugin; return true; } diff --git a/src/lib/plugin/desktopfileparser_p.h b/src/lib/plugin/desktopfileparser_p.h index f1719bd..e3112a2 100644 --- a/src/lib/plugin/desktopfileparser_p.h +++ b/src/lib/plugin/desktopfileparser_p.h @@ -1,74 +1,80 @@ /****************************************************************************** * Copyright 2013-2014 Sebastian Kügler * * Copyright 2014 Alex Richardson * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) version 3, or any * * later version accepted by the membership of KDE e.V. (or its * * successor approved by the membership of KDE e.V.), which shall * * act as a proxy defined in Section 6 of version 3 of the license. * * * * 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library. If not, see * * . * * * ******************************************************************************/ #ifndef DESKTOPFILEPARSER_H #define DESKTOPFILEPARSER_H #include #include #include class QJsonObject; class QJsonValue; Q_DECLARE_LOGGING_CATEGORY(DESKTOPPARSER) 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 */ QJsonValue parseValue(const QByteArray &key, const QString &value) const; /** * Parses the service file in @p path and extracts its definitions * * @returns whether the action could be performed */ 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); extern bool s_verbose; extern bool s_compatibilityMode; #endif } #endif // DESKTOPFILEPARSER_H