diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index 450987a..ed5fd8d 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -1,58 +1,59 @@ remove_definitions(-DQT_NO_CAST_FROM_ASCII) include(ECMAddTests) ########### unittests ############### find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Concurrent Test) macro(KSERVICE_UNIT_TESTS) foreach(_testname ${ARGN}) ecm_add_test(${_testname}.cpp TEST_NAME ${_testname} LINK_LIBRARIES KF5::Service Qt5::Test Qt5::Concurrent Qt5::Xml) target_compile_definitions(${_testname} PRIVATE -DKBUILDSYCOCAEXE=\"$\") endforeach() endmacro(KSERVICE_UNIT_TESTS) kservice_unit_tests( ksycocatest ksycoca_xdgdirstest kautostarttest ksycocadicttest ksycocathreadtest kservicetest kplugintradertest kplugininfotest + kapplicationtradertest ) # the test plays with the timestamp of ~/.qttest/share/kservicetypes5, and with the ksycoca file, other tests can collide set_tests_properties(ksycocatest PROPERTIES RUN_SERIAL TRUE) # KServiceTest::testAllServices can fail if any service is deleted while the test runs set_tests_properties(kservicetest PROPERTIES RUN_SERIAL TRUE) -target_sources(kservicetest PUBLIC +target_sources(kapplicationtradertest PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../src/services/ktraderparsetree.cpp ) add_library(fakeplugin MODULE nsaplugin.cpp) ecm_mark_as_test(fakeplugin) target_link_libraries(fakeplugin KF5::Service) # generate new-style and old-style JSON from the .desktop file file(COPY fakeplugin.desktop DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) file(RENAME ${CMAKE_CURRENT_BINARY_DIR}/fakeplugin.desktop ${CMAKE_CURRENT_BINARY_DIR}/fakeplugin_json_old.desktop) kcoreaddons_desktop_to_json(fakeplugin ${CMAKE_CURRENT_BINARY_DIR}/fakeplugin_json_old.desktop COMPAT_MODE) file(COPY fakeplugin.desktop DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) file(RENAME ${CMAKE_CURRENT_BINARY_DIR}/fakeplugin.desktop ${CMAKE_CURRENT_BINARY_DIR}/fakeplugin_json_new.desktop) kcoreaddons_desktop_to_json(fakeplugin ${CMAKE_CURRENT_BINARY_DIR}/fakeplugin_json_new.desktop) ######### kmimeassociationstest ######## set(kmimeassociationstest_SRCS kmimeassociationstest.cpp ../src/sycoca/kmimeassociations.cpp) ecm_qt_declare_logging_category(kmimeassociationstest_SRCS HEADER sycocadebug.h IDENTIFIER SYCOCA CATEGORY_NAME kf5.kservice.sycoca) ecm_add_test(${kmimeassociationstest_SRCS} TEST_NAME kmimeassociationstest LINK_LIBRARIES KF5::Service Qt5::Test Qt5::Xml) diff --git a/autotests/kapplicationtradertest.cpp b/autotests/kapplicationtradertest.cpp new file mode 100644 index 0000000..4634790 --- /dev/null +++ b/autotests/kapplicationtradertest.cpp @@ -0,0 +1,322 @@ +/* + Copyright (C) 2006-2020 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + */ + +#include "setupxdgdirs.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include <../src/services/ktraderparsetree_p.h> + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +enum class ExpectedResult { + NoResults, + FakeApplicationOnly, + FakeApplicationAndOthers, + NotFakeApplication, +}; +Q_DECLARE_METATYPE(ExpectedResult) + +class KApplicationTraderTest : public QObject +{ + Q_OBJECT +public: +private Q_SLOTS: + void initTestCase(); + void testTraderConstraints_data(); + void testTraderConstraints(); + void testQueryByMimeType(); + void testThreads(); + void testTraderQueryMustRebuildSycoca(); + void cleanupTestCase(); + +private: + QString createFakeApplication(const QString &filename, const QString &name, const QMap &extraFields = {}); + void checkResult(const KService::List &offers, ExpectedResult expectedResult); + + QString m_fakeApplication; + QString m_fakeGnomeApplication; + QStringList m_createdDesktopFiles; +}; + +QTEST_MAIN(KApplicationTraderTest) + +extern KSERVICE_EXPORT int ksycoca_ms_between_checks; + +void KApplicationTraderTest::initTestCase() +{ + // Set up a layer in the bin dir so ksycoca finds the desktop files created by createFakeApplication + // Note that we still need /usr in there so that mimetypes are found + setupXdgDirs(); + + qputenv("XDG_CURRENT_DESKTOP", "KDE"); + + QStandardPaths::setTestModeEnabled(true); + + // A non-C locale is necessary for some tests. + // This locale must have the following properties: + // - some character other than dot as decimal separator + // If it cannot be set, locale-dependent tests are skipped. + setlocale(LC_ALL, "fr_FR.utf8"); + bool hasNonCLocale = (setlocale(LC_ALL, nullptr) == QByteArray("fr_FR.utf8")); + if (!hasNonCLocale) { + qDebug() << "Setting locale to fr_FR.utf8 failed"; + } + + // Ensure no leftovers from other tests + QDir(QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation)).removeRecursively(); + + // Create some fake services for the tests below, and ensure they are in ksycoca. + + // fakeservice_deleteme: deleted and recreated by testDeletingService, don't use in other tests + createFakeApplication(QStringLiteral("fakeservice_deleteme.desktop"), QStringLiteral("DeleteMe")); + + // fakeapplication + m_fakeApplication = createFakeApplication(QStringLiteral("fakeapplication.desktop"), QStringLiteral("FakeApplication")); + m_fakeApplication = QFileInfo(m_fakeApplication).canonicalFilePath(); + + // fakegnomeapplication (do not show in Plasma). Should never be returned. To test the filtering code in queryByMimeType. + QMap fields; + fields.insert("OnlyShowIn", "Gnome"); + m_fakeGnomeApplication = createFakeApplication(QStringLiteral("fakegnomeapplication.desktop"), QStringLiteral("FakeApplicationGnome"), + fields); + m_fakeGnomeApplication = QFileInfo(m_fakeGnomeApplication).canonicalFilePath(); + + ksycoca_ms_between_checks = 0; +} + +void KApplicationTraderTest::cleanupTestCase() +{ + for (const QString &file : qAsConst(m_createdDesktopFiles)) { + QFile::remove(file); + } +} + +// Helper method for all the trader tests +static bool offerListHasService(const KService::List &offers, + const QString &entryPath) +{ + bool found = false; + for (const auto &service : offers) { + if (service->entryPath() == entryPath) { + if (found) { // should be there only once + qWarning("ERROR: %s was found twice in the list", qPrintable(entryPath)); + return false; // make test fail + } + found = true; + } + } + return found; +} + +void KApplicationTraderTest::checkResult(const KService::List &offers, ExpectedResult expectedResult) +{ + switch (expectedResult) { + case ExpectedResult::NoResults: + if (!offers.isEmpty()) { + qWarning() << "Got" << offers.count() << "unexpected results, including" << offers.at(0)->entryPath(); + } + QCOMPARE(offers.count(), 0); + break; + case ExpectedResult::FakeApplicationOnly: + if (offers.count() != 1) { + for (const auto &service : offers) { + qWarning() << " " << service->entryPath(); + } + } + QCOMPARE(offers.count(), 1); + QCOMPARE(offers.at(0)->entryPath(), m_fakeApplication); + break; + case ExpectedResult::FakeApplicationAndOthers: + QVERIFY(!offers.isEmpty()); + if (!offerListHasService(offers, m_fakeApplication)) { + qWarning() << m_fakeApplication << "not found. Here's what we have:"; + for (const auto &service : offers) { + qWarning() << " " << service->entryPath(); + } + } + QVERIFY(offerListHasService(offers, m_fakeApplication)); + break; + case ExpectedResult::NotFakeApplication: + QVERIFY(!offerListHasService(offers, m_fakeApplication)); + break; + } + QVERIFY(!offerListHasService(offers, m_fakeGnomeApplication)); +} + +using FF = KApplicationTrader::FilterFunc; +Q_DECLARE_METATYPE(KApplicationTrader::FilterFunc) + +void KApplicationTraderTest::testTraderConstraints_data() +{ + QTest::addColumn("filterFunc"); + QTest::addColumn("expectedResult"); + + QTest::newRow("no_constraint") << FF([](const KService::Ptr &){ return true; }) << ExpectedResult::FakeApplicationAndOthers; + + // == tests + FF name_comparison = [](const KService::Ptr &serv) { return serv->name() == QLatin1String("FakeApplication"); }; + QTest::newRow("name_comparison") << name_comparison << ExpectedResult::FakeApplicationOnly; + FF isDontExist = [](const KService::Ptr &serv) { return serv->name() == QLatin1String("IDontExist"); }; + QTest::newRow("no_such_name") << isDontExist << ExpectedResult::NoResults; + FF no_such_name_by_case = [](const KService::Ptr &serv) { return serv->name() == QLatin1String("fakeapplication"); }; + QTest::newRow("no_such_name_by_case") << no_such_name_by_case << ExpectedResult::NoResults; + + // Name =~ 'fAkEaPPlicaTion' + FF match_case_insensitive = [](const KService::Ptr &serv) { return serv->name().compare("fAkEaPPlicaTion", Qt::CaseInsensitive) == 0; }; + QTest::newRow("match_case_insensitive") << match_case_insensitive << ExpectedResult::FakeApplicationOnly; + + // 'FakeApp' ~ Name + FF is_contained_in = [](const KService::Ptr &serv) { return serv->name().contains("FakeApp"); }; + QTest::newRow("is_contained_in") << is_contained_in << ExpectedResult::FakeApplicationOnly; + + // 'FakeApplicationNot' ~ Name + FF is_contained_in_fail = [](const KService::Ptr &serv) { return serv->name().contains("FakeApplicationNot"); }; + QTest::newRow("is_contained_in_fail") << is_contained_in_fail << ExpectedResult::NoResults; + + // 'faKeApP' ~~ Name + FF is_contained_in_case_insensitive = [](const KService::Ptr &serv) { return serv->name().contains("faKeApP", Qt::CaseInsensitive); }; + QTest::newRow("is_contained_in_case_insensitive") << is_contained_in_case_insensitive << ExpectedResult::FakeApplicationOnly; + + // 'faKeApPp' ~ Name + FF is_contained_in_case_in_fail = [](const KService::Ptr &serv) { return serv->name().contains("faKeApPp", Qt::CaseInsensitive); }; + QTest::newRow("is_contained_in_case_in_fail") << is_contained_in_case_in_fail << ExpectedResult::NoResults; + + // 'FkApli' subseq Name + FF subseq = [](const KService::Ptr &serv) { return KApplicationTrader::isSubsequence("FkApli", serv->name()); }; + QTest::newRow("subseq") << subseq << ExpectedResult::FakeApplicationOnly; + + // 'fkApli' subseq Name + FF subseq_fail = [](const KService::Ptr &serv) { return KApplicationTrader::isSubsequence("fkApli", serv->name()); }; + QTest::newRow("subseq_fail") << subseq_fail << ExpectedResult::NoResults; + + // 'fkApLI' ~subseq Name + FF subseq_case_insensitive = [](const KService::Ptr &serv) { return KApplicationTrader::isSubsequence("fkApLI", serv->name(), Qt::CaseInsensitive); }; + QTest::newRow("subseq_case_insensitive") << subseq_case_insensitive << ExpectedResult::FakeApplicationOnly; + + // 'fk_Apli' ~subseq Name + FF subseq_case_insensitive_fail = [](const KService::Ptr &serv) { return KApplicationTrader::isSubsequence("fk_Apli", serv->name(), Qt::CaseInsensitive); }; + QTest::newRow("subseq_case_insensitive_fail") << subseq_case_insensitive_fail << ExpectedResult::NoResults; + + // Test another property, parsed as a double + FF testVersion = [](const KService::Ptr &serv) { double d = serv->property("X-KDE-Version").toDouble(); return d > 5.559 && d < 5.561; }; + QTest::newRow("float_parsing") << testVersion << ExpectedResult::FakeApplicationAndOthers; +} + +void KApplicationTraderTest::testTraderConstraints() +{ + QFETCH(KApplicationTrader::FilterFunc, filterFunc); + QFETCH(ExpectedResult, expectedResult); + + const KService::List offers = KApplicationTrader::query(filterFunc); + checkResult(offers, expectedResult); +} + +void KApplicationTraderTest::testQueryByMimeType() +{ + KService::List offers; + + // Without constraint + + offers = KApplicationTrader::queryByMimeType(QStringLiteral("text/plain")); + checkResult(offers, ExpectedResult::FakeApplicationAndOthers); + + offers = KApplicationTrader::queryByMimeType(QStringLiteral("image/png")); + checkResult(offers, ExpectedResult::NotFakeApplication); + + QTest::ignoreMessage(QtWarningMsg, "KApplicationTrader: mimeType \"no/such/mimetype\" not found"); + offers = KApplicationTrader::queryByMimeType(QStringLiteral("no/such/mimetype")); + checkResult(offers, ExpectedResult::NoResults); + + // With constraint + + FF isFakeApplication = [](const KService::Ptr &serv) { return serv->name() == QLatin1String("FakeApplication"); }; + offers = KApplicationTrader::queryByMimeType(QStringLiteral("text/plain"), isFakeApplication); + checkResult(offers, ExpectedResult::FakeApplicationOnly); + + FF isDontExist = [](const KService::Ptr &serv) { return serv->name() == QLatin1String("IDontExist"); }; + offers = KApplicationTrader::queryByMimeType(QStringLiteral("text/plain"), isDontExist); + checkResult(offers, ExpectedResult::NoResults); +} + +QString KApplicationTraderTest::createFakeApplication(const QString &filename, const QString &name, const QMap &extraFields) +{ + const QString fakeService = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + QLatin1Char('/') + filename; + QFile::remove(fakeService); + m_createdDesktopFiles << fakeService; + KDesktopFile file(fakeService); + KConfigGroup group = file.desktopGroup(); + group.writeEntry("Name", name); + group.writeEntry("Type", "Application"); + group.writeEntry("Exec", "ls"); + group.writeEntry("Categories", "FakeCategory"); + group.writeEntry("X-KDE-Version", "5.56"); + group.writeEntry("MimeType", "text/plain;"); + for (auto it = extraFields.begin(); it != extraFields.end(); ++it) { + group.writeEntry(it.key(), it.value()); + } + return fakeService; +} + +#include +#include +#include + +// Testing for concurrent access to ksycoca from multiple threads +// Use thread-sanitizer to see the data races + +void KApplicationTraderTest::testThreads() +{ + QThreadPool::globalInstance()->setMaxThreadCount(10); + QFutureSynchronizer sync; + // Can't use data-driven tests here, QTestLib isn't threadsafe. + sync.addFuture(QtConcurrent::run(this, &KApplicationTraderTest::testQueryByMimeType)); + sync.addFuture(QtConcurrent::run(this, &KApplicationTraderTest::testQueryByMimeType)); + sync.waitForFinished(); +} + +void KApplicationTraderTest::testTraderQueryMustRebuildSycoca() +{ + auto filter = [](const KService::Ptr &serv) { return serv->name() == QLatin1String("MustRebuild"); }; + QCOMPARE(KApplicationTrader::query(filter).count(), 0); + createFakeApplication(QStringLiteral("fakeservice_querymustrebuild.desktop"), QStringLiteral("MustRebuild")); + KService::List offers = KApplicationTrader::query(filter); + QCOMPARE(offers.count(), 1); +} + +#include "kapplicationtradertest.moc" diff --git a/autotests/kservicetest.cpp b/autotests/kservicetest.cpp index b4ed4fc..d17d523 100644 --- a/autotests/kservicetest.cpp +++ b/autotests/kservicetest.cpp @@ -1,924 +1,924 @@ /* * Copyright (C) 2006 David Faure * * 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 "kservicetest.h" #include "setupxdgdirs.h" #include #include #include #include #include #include #include -#include <../src/services/kserviceutil_p.h> -#include <../src/services/ktraderparsetree_p.h> +#include <../src/services/kserviceutil_p.h> // for KServiceUtilPrivate +#include #include #include #include #include #include #include #include #include #include #include #include #include #include QTEST_MAIN(KServiceTest) extern KSERVICE_EXPORT int ksycoca_ms_between_checks; static void eraseProfiles() { QString profilerc = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/profilerc"; if (!profilerc.isEmpty()) { QFile::remove(profilerc); } profilerc = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/servicetype_profilerc"; if (!profilerc.isEmpty()) { QFile::remove(profilerc); } } void KServiceTest::initTestCase() { // Set up a layer in the bin dir so ksycoca finds the KPluginInfo and Application servicetypes setupXdgDirs(); QStandardPaths::setTestModeEnabled(true); QLoggingCategory::setFilterRules(QStringLiteral("*.debug=true")); // A non-C locale is necessary for some tests. // This locale must have the following properties: // - some character other than dot as decimal separator // If it cannot be set, locale-dependent tests are skipped. setlocale(LC_ALL, "fr_FR.utf8"); m_hasNonCLocale = (setlocale(LC_ALL, nullptr) == QByteArray("fr_FR.utf8")); if (!m_hasNonCLocale) { qDebug() << "Setting locale to fr_FR.utf8 failed"; } eraseProfiles(); if (!KSycoca::isAvailable()) { runKBuildSycoca(); } // Create some fake services for the tests below, and ensure they are in ksycoca. bool mustUpdateKSycoca = false; // fakeservice: deleted and recreated by testDeletingService, don't use in other tests const QString fakeServiceDeleteMe = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/fakeservice_deleteme.desktop"); if (!QFile::exists(fakeServiceDeleteMe)) { mustUpdateKSycoca = true; createFakeService(QStringLiteral("fakeservice_deleteme.desktop"), QString()); } // fakeservice: a plugin that implements FakePluginType const QString fakeService = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/fakeservice.desktop"); if (!QFile::exists(fakeService)) { mustUpdateKSycoca = true; createFakeService(QStringLiteral("fakeservice.desktop"), QStringLiteral("FakePluginType")); } // fakepart: a readwrite part, like katepart const QString fakePart = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + "fakepart.desktop"; if (!QFile::exists(fakePart)) { mustUpdateKSycoca = true; KDesktopFile file(fakePart); KConfigGroup group = file.desktopGroup(); group.writeEntry("Name", "FakePart"); group.writeEntry("Type", "Service"); group.writeEntry("X-KDE-Library", "fakepart"); group.writeEntry("X-KDE-Protocols", "http,ftp"); group.writeEntry("X-KDE-ServiceTypes", "FakeBasePart,FakeDerivedPart"); group.writeEntry("MimeType", "text/plain;text/html;"); group.writeEntry("X-KDE-FormFactors", "tablet,handset"); } const QString fakePart2 = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + "fakepart2.desktop"; if (!QFile::exists(fakePart2)) { mustUpdateKSycoca = true; KDesktopFile file(fakePart2); KConfigGroup group = file.desktopGroup(); group.writeEntry("Name", "FakePart2"); group.writeEntry("Type", "Service"); group.writeEntry("X-KDE-Library", "fakepart2"); group.writeEntry("X-KDE-ServiceTypes", "FakeBasePart"); group.writeEntry("MimeType", "text/plain;"); group.writeEntry("X-KDE-TestList", QStringList() << QStringLiteral("item1") << QStringLiteral("item2")); group.writeEntry("X-KDE-FormFactors", "tablet,handset"); } const QString preferredPart = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + "preferredpart.desktop"; if (!QFile::exists(preferredPart)) { mustUpdateKSycoca = true; KDesktopFile file(preferredPart); KConfigGroup group = file.desktopGroup(); group.writeEntry("Name", "PreferredPart"); group.writeEntry("Type", "Service"); group.writeEntry("X-KDE-Library", "preferredpart"); group.writeEntry("X-KDE-ServiceTypes", "FakeBasePart"); group.writeEntry("MimeType", "text/plain;"); } const QString otherPart = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + "otherpart.desktop"; if (!QFile::exists(otherPart)) { mustUpdateKSycoca = true; KDesktopFile file(otherPart); KConfigGroup group = file.desktopGroup(); group.writeEntry("Name", "OtherPart"); group.writeEntry("Type", "Service"); group.writeEntry("X-KDE-Library", "otherpart"); group.writeEntry("X-KDE-ServiceTypes", "FakeBasePart"); group.writeEntry("MimeType", "text/plain;"); } // faketextplugin: a ktexteditor plugin const QString fakeTextplugin = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + "faketextplugin.desktop"; if (!QFile::exists(fakeTextplugin)) { mustUpdateKSycoca = true; KDesktopFile file(fakeTextplugin); KConfigGroup group = file.desktopGroup(); group.writeEntry("Name", "FakeTextPlugin"); group.writeEntry("Type", "Service"); group.writeEntry("X-KDE-Library", "faketextplugin"); group.writeEntry("X-KDE-ServiceTypes", "FakePluginType"); group.writeEntry("MimeType", "text/plain;"); } // fakeplugintype: a servicetype const QString fakePluginType = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservicetypes5/") + "fakeplugintype.desktop"; if (!QFile::exists(fakePluginType)) { mustUpdateKSycoca = true; KDesktopFile file(fakePluginType); KConfigGroup group = file.desktopGroup(); group.writeEntry("Comment", "Fake Text Plugin"); group.writeEntry("Type", "ServiceType"); group.writeEntry("X-KDE-ServiceType", "FakePluginType"); file.group("PropertyDef::X-KDE-Version").writeEntry("Type", "double"); // like in ktexteditorplugin.desktop group.writeEntry("X-KDE-FormFactors", "tablet,handset"); } // fakebasepart: a servicetype (like ReadOnlyPart) const QString fakeBasePart = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservicetypes5/") + "fakebasepart.desktop"; if (!QFile::exists(fakeBasePart)) { mustUpdateKSycoca = true; KDesktopFile file(fakeBasePart); KConfigGroup group = file.desktopGroup(); group.writeEntry("Comment", "Fake Base Part"); group.writeEntry("Type", "ServiceType"); group.writeEntry("X-KDE-ServiceType", "FakeBasePart"); KConfigGroup listGroup(&file, "PropertyDef::X-KDE-TestList"); listGroup.writeEntry("Type", "QStringList"); } // fakederivedpart: a servicetype deriving from FakeBasePart (like ReadWritePart derives from ReadOnlyPart) const QString fakeDerivedPart = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservicetypes5/") + "fakederivedpart.desktop"; if (!QFile::exists(fakeDerivedPart)) { mustUpdateKSycoca = true; KDesktopFile file(fakeDerivedPart); KConfigGroup group = file.desktopGroup(); group.writeEntry("Comment", "Fake Derived Part"); group.writeEntry("Type", "ServiceType"); group.writeEntry("X-KDE-ServiceType", "FakeDerivedPart"); group.writeEntry("X-KDE-Derived", "FakeBasePart"); } // fakekdedmodule const QString fakeKdedModule = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservicetypes5/") + "fakekdedmodule.desktop"; if (!QFile::exists(fakeKdedModule)) { const QString src = QFINDTESTDATA("fakekdedmodule.desktop"); QVERIFY(QFile::copy(src, fakeKdedModule)); mustUpdateKSycoca = true; } // faketestapp.desktop const QString testApp = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + QLatin1String("/org.kde.faketestapp.desktop"); if (!QFile::exists(testApp)) { QVERIFY(QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation))); const QString src = QFINDTESTDATA("org.kde.faketestapp.desktop"); QVERIFY(!src.isEmpty()); QVERIFY2(QFile::copy(src, testApp), qPrintable(testApp)); qDebug() << "Created" << testApp; mustUpdateKSycoca = true; } if (mustUpdateKSycoca) { // Update ksycoca in ~/.qttest after creating the above runKBuildSycoca(true); } QVERIFY(KServiceType::serviceType(QStringLiteral("FakePluginType"))); QVERIFY(KServiceType::serviceType(QStringLiteral("FakeBasePart"))); QVERIFY(KServiceType::serviceType(QStringLiteral("FakeDerivedPart"))); QVERIFY(KService::serviceByDesktopName(QStringLiteral("org.kde.faketestapp"))); } void KServiceTest::runKBuildSycoca(bool noincremental) { QSignalSpy spy(KSycoca::self(), SIGNAL(databaseChanged(QStringList))); KBuildSycoca builder; QVERIFY(builder.recreate(!noincremental)); if (spy.isEmpty()) { qDebug() << "waiting for signal"; QVERIFY(spy.wait(10000)); qDebug() << "got signal"; } } void KServiceTest::cleanupTestCase() { // If I want the konqueror unit tests to work, then I better not have a non-working part // as the preferred part for text/plain... const QStringList services = QStringList() << QStringLiteral("fakeservice.desktop") << QStringLiteral("fakepart.desktop") << QStringLiteral("faketextplugin.desktop") << QStringLiteral("fakeservice_querymustrebuild.desktop"); for (const QString &service : services) { const QString fakeService = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + service; QFile::remove(fakeService); } const QStringList serviceTypes = QStringList() << QStringLiteral("fakeplugintype.desktop"); for (const QString &serviceType : serviceTypes) { const QString fakeServiceType = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservicetypes5/") + serviceType; //QFile::remove(fakeServiceType); } KBuildSycoca builder; builder.recreate(); } void KServiceTest::testByName() { if (!KSycoca::isAvailable()) { QSKIP("ksycoca not available"); } KServiceType::Ptr s0 = KServiceType::serviceType(QStringLiteral("FakeBasePart")); QVERIFY2(s0, "KServiceType::serviceType(\"FakeBasePart\") failed!"); QCOMPARE(s0->name(), QStringLiteral("FakeBasePart")); KService::Ptr myService = KService::serviceByDesktopPath(QStringLiteral("fakepart.desktop")); QVERIFY(myService); QCOMPARE(myService->name(), QStringLiteral("FakePart")); } void KServiceTest::testConstructorFullPath() { // Requirement: text/html must be a known mimetype QVERIFY(QMimeDatabase().mimeTypeForName("text/html").isValid()); const QString fakePart = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + "fakepart.desktop"; QVERIFY(QFile::exists(fakePart)); KService service(fakePart); QVERIFY(service.isValid()); QCOMPARE(service.mimeTypes(), QStringList() << "text/plain" << "text/html"); } void KServiceTest::testConstructorKDesktopFileFullPath() { const QString fakePart = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + "fakepart.desktop"; QVERIFY(QFile::exists(fakePart)); KDesktopFile desktopFile(fakePart); KService service(&desktopFile); QVERIFY(service.isValid()); QCOMPARE(service.mimeTypes(), QStringList() << "text/plain" << "text/html"); } void KServiceTest::testConstructorKDesktopFile() // as happens inside kbuildsycoca.cpp { KDesktopFile desktopFile(QStandardPaths::GenericDataLocation, "kservices5/fakepart.desktop"); QCOMPARE(KService(&desktopFile, "kservices5/fakepart.desktop").mimeTypes(), QStringList() << "text/plain" << "text/html"); } void KServiceTest::testCopyConstructor() { const QString fakePart = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + "fakepart.desktop"; QVERIFY(QFile::exists(fakePart)); KDesktopFile desktopFile(fakePart); // KRun needs to make a copy of a KService that will go out of scope, let's test that here. KService::Ptr service; { KService origService(&desktopFile); service = new KService(origService); } QVERIFY(service->isValid()); QCOMPARE(service->mimeTypes(), QStringList() << "text/plain" << "text/html"); } void KServiceTest::testCopyInvalidService() { KService::Ptr service; { KService origService{QString()}; // this still sets a d_ptr, so no problem; QVERIFY(!origService.isValid()); service = new KService(origService); } QVERIFY(!service->isValid()); } void KServiceTest::testProperty() { ksycoca_ms_between_checks = 0; // Let's try creating a desktop file and ensuring it's noticed by the timestamp check QTest::qWait(1000); const QString fakeCookie = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/kded/") + "fakekcookiejar.desktop"; if (!QFile::exists(fakeCookie)) { KDesktopFile file(fakeCookie); KConfigGroup group = file.desktopGroup(); group.writeEntry("Name", "OtherPart"); group.writeEntry("Type", "Service"); group.writeEntry("X-KDE-ServiceTypes", "FakeKDEDModule"); group.writeEntry("X-KDE-Library", "kcookiejar"); group.writeEntry("X-KDE-Kded-autoload", "false"); group.writeEntry("X-KDE-Kded-load-on-demand", "true"); qDebug() << "created" << fakeCookie; } KService::Ptr kdedkcookiejar = KService::serviceByDesktopPath(QStringLiteral("kded/fakekcookiejar.desktop")); QVERIFY(kdedkcookiejar); QCOMPARE(kdedkcookiejar->entryPath(), QStringLiteral("kded/fakekcookiejar.desktop")); QCOMPARE(kdedkcookiejar->property(QStringLiteral("ServiceTypes")).toStringList().join(QLatin1Char(',')), QString("FakeKDEDModule")); QCOMPARE(kdedkcookiejar->property(QStringLiteral("X-KDE-Kded-autoload")).toBool(), false); QCOMPARE(kdedkcookiejar->property(QStringLiteral("X-KDE-Kded-load-on-demand")).toBool(), true); QVERIFY(!kdedkcookiejar->property(QStringLiteral("Name")).toString().isEmpty()); QVERIFY(!kdedkcookiejar->property(QStringLiteral("Name[fr]"), QVariant::String).isValid()); // TODO: for this we must install a servicetype desktop file... //KService::Ptr kjavaappletviewer = KService::serviceByDesktopPath("kjavaappletviewer.desktop"); //QVERIFY(kjavaappletviewer); //QCOMPARE(kjavaappletviewer->property("X-KDE-BrowserView-PluginsInfo").toString(), QString("kjava/pluginsinfo")); // Test property("X-KDE-Protocols"), which triggers the KServiceReadProperty code. KService::Ptr fakePart = KService::serviceByDesktopPath(QStringLiteral("fakepart.desktop")); QVERIFY(fakePart); // see initTestCase; it should be found. QVERIFY(fakePart->propertyNames().contains(QLatin1String("X-KDE-Protocols"))); QCOMPARE(fakePart->mimeTypes(), QStringList() << QStringLiteral("text/plain") << QStringLiteral("text/html")); // okular relies on subclasses being kept here const QStringList protocols = fakePart->property(QStringLiteral("X-KDE-Protocols")).toStringList(); QCOMPARE(protocols, QStringList() << QStringLiteral("http") << QStringLiteral("ftp")); // Restore value ksycoca_ms_between_checks = 1500; } void KServiceTest::testAllServiceTypes() { if (!KSycoca::isAvailable()) { QSKIP("ksycoca not available"); } const KServiceType::List allServiceTypes = KServiceType::allServiceTypes(); // A bit of checking on the allServiceTypes list itself KServiceType::List::ConstIterator stit = allServiceTypes.begin(); const KServiceType::List::ConstIterator stend = allServiceTypes.end(); for (; stit != stend; ++stit) { const KServiceType::Ptr servtype = (*stit); const QString name = servtype->name(); QVERIFY(!name.isEmpty()); QVERIFY(servtype->sycocaType() == KST_KServiceType); } } void KServiceTest::testAllServices() { if (!KSycoca::isAvailable()) { QSKIP("ksycoca not available"); } const KService::List lst = KService::allServices(); QVERIFY(!lst.isEmpty()); bool foundTestApp = false; for (KService::List::ConstIterator it = lst.begin(); it != lst.end(); ++it) { const KService::Ptr service = (*it); QVERIFY(service->isType(KST_KService)); const QString name = service->name(); const QString entryPath = service->entryPath(); if (entryPath.contains("fake")) { qDebug() << name << "entryPath=" << entryPath << "menuId=" << service->menuId(); } QVERIFY(!name.isEmpty()); QVERIFY(!entryPath.isEmpty()); KService::Ptr lookedupService = KService::serviceByDesktopPath(entryPath); QVERIFY(lookedupService); // not null QCOMPARE(lookedupService->entryPath(), entryPath); if (service->isApplication()) { const QString menuId = service->menuId(); if (menuId.isEmpty()) { qWarning("%s has an empty menuId!", qPrintable(entryPath)); } else if (menuId == "org.kde.faketestapp.desktop") { foundTestApp = true; } QVERIFY(!menuId.isEmpty()); lookedupService = KService::serviceByMenuId(menuId); QVERIFY(lookedupService); // not null QCOMPARE(lookedupService->menuId(), menuId); } } QVERIFY(foundTestApp); } // Helper method for all the trader tests static bool offerListHasService(const KService::List &offers, const QString &entryPath) { bool found = false; KService::List::const_iterator it = offers.begin(); for (; it != offers.end(); ++it) { if ((*it)->entryPath() == entryPath) { if (found) { // should be there only once qWarning("ERROR: %s was found twice in the list", qPrintable(entryPath)); return false; // make test fail } found = true; } } return found; } void KServiceTest::testDBUSStartupType() { if (!KSycoca::isAvailable()) { QSKIP("ksycoca not available"); } KService::Ptr testapp = KService::serviceByDesktopName(QStringLiteral("org.kde.faketestapp")); QVERIFY(testapp); QCOMPARE(testapp->menuId(), QStringLiteral("org.kde.faketestapp.desktop")); //qDebug() << testapp->entryPath(); QCOMPARE(int(testapp->dbusStartupType()), int(KService::DBusUnique)); } void KServiceTest::testByStorageId() { if (!KSycoca::isAvailable()) { QSKIP("ksycoca not available"); } QVERIFY(!QStandardPaths::locate(QStandardPaths::ApplicationsLocation, QStringLiteral("org.kde.faketestapp.desktop")).isEmpty()); QVERIFY(KService::serviceByMenuId(QStringLiteral("org.kde.faketestapp.desktop"))); QVERIFY(!KService::serviceByMenuId(QStringLiteral("org.kde.faketestapp"))); // doesn't work, extension mandatory QVERIFY(!KService::serviceByMenuId(QStringLiteral("faketestapp.desktop"))); // doesn't work, full filename mandatory QVERIFY(KService::serviceByStorageId(QStringLiteral("org.kde.faketestapp.desktop"))); QVERIFY(KService::serviceByStorageId("org.kde.faketestapp")); QVERIFY(KService::serviceByDesktopName(QStringLiteral("org.kde.faketestapp"))); QCOMPARE(KService::serviceByDesktopName(QStringLiteral("org.kde.faketestapp"))->menuId(), QString("org.kde.faketestapp.desktop")); } void KServiceTest::testServiceTypeTraderForReadOnlyPart() { if (!KSycoca::isAvailable()) { QSKIP("ksycoca not available"); } // Querying trader for services associated with FakeBasePart KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("FakeBasePart")); QVERIFY(offers.count() > 0); if (!offerListHasService(offers, QStringLiteral("fakepart.desktop")) || !offerListHasService(offers, QStringLiteral("fakepart2.desktop")) || !offerListHasService(offers, QStringLiteral("otherpart.desktop")) || !offerListHasService(offers, QStringLiteral("preferredpart.desktop"))) { for (KService::Ptr service : qAsConst(offers)) { qDebug("%s %s", qPrintable(service->name()), qPrintable(service->entryPath())); } } m_firstOffer = offers[0]->entryPath(); QVERIFY(offerListHasService(offers, QStringLiteral("fakepart.desktop"))); QVERIFY(offerListHasService(offers, QStringLiteral("fakepart2.desktop"))); QVERIFY(offerListHasService(offers, QStringLiteral("otherpart.desktop"))); QVERIFY(offerListHasService(offers, QStringLiteral("preferredpart.desktop"))); // Check ordering according to InitialPreference int lastPreference = -1; bool lastAllowedAsDefault = true; for (KService::Ptr service : qAsConst(offers)) { const QString path = service->entryPath(); const int preference = service->initialPreference(); // ## might be wrong if we use per-servicetype preferences... //qDebug( "%s has preference %d, allowAsDefault=%d", qPrintable( path ), preference, service->allowAsDefault() ); if (lastAllowedAsDefault && !service->allowAsDefault()) { // first "not allowed as default" offer lastAllowedAsDefault = false; lastPreference = -1; // restart } if (lastPreference != -1) { QVERIFY(preference <= lastPreference); } lastPreference = preference; } // Now look for any FakePluginType offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType")); QVERIFY(offerListHasService(offers, QStringLiteral("fakeservice.desktop"))); QVERIFY(offerListHasService(offers, QStringLiteral("faketextplugin.desktop"))); } void KServiceTest::testTraderConstraints() { if (!KSycoca::isAvailable()) { QSKIP("ksycoca not available"); } KService::List offers; // Baseline: no constraints offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType")); QCOMPARE(offers.count(), 2); QVERIFY(offerListHasService(offers, QStringLiteral("faketextplugin.desktop"))); QVERIFY(offerListHasService(offers, QStringLiteral("fakeservice.desktop"))); // String-based constraint offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"), QStringLiteral("Library == 'faketextplugin'")); QCOMPARE(offers.count(), 1); QVERIFY(offerListHasService(offers, QStringLiteral("faketextplugin.desktop"))); // Match case insensitive offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"), QStringLiteral("Library =~ 'fAkEteXtpLuGin'")); QCOMPARE(offers.count(), 1); QVERIFY(offerListHasService(offers, QStringLiteral("faketextplugin.desktop"))); // "contains" offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"), QStringLiteral("'textplugin' ~ Library")); // note: "is contained in", not "contains"... QCOMPARE(offers.count(), 1); QVERIFY(offerListHasService(offers, QStringLiteral("faketextplugin.desktop"))); // "contains" case insensitive offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"), QStringLiteral("'teXtPluGin' ~~ Library")); // note: "is contained in", not "contains"... QCOMPARE(offers.count(), 1); QVERIFY(offerListHasService(offers, QStringLiteral("faketextplugin.desktop"))); // sub-sequence case sensitive offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"), QStringLiteral("'txtlug' subseq Library")); QCOMPARE(offers.count(), 1); QVERIFY(offerListHasService(offers, QStringLiteral("faketextplugin.desktop"))); // sub-sequence case insensitive offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"), QStringLiteral("'tXtLuG' ~subseq Library")); QCOMPARE(offers.count(), 1); QVERIFY(offerListHasService(offers, QStringLiteral("faketextplugin.desktop"))); if (m_hasNonCLocale) { // Test float parsing, must use dot as decimal separator independent of locale. offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"), QStringLiteral("([X-KDE-Version] > 4.559) and ([X-KDE-Version] < 4.561)")); QCOMPARE(offers.count(), 1); QVERIFY(offerListHasService(offers, QStringLiteral("fakeservice.desktop"))); } // A test with an invalid query, to test for memleaks offers = KServiceTypeTrader::self()->query(QStringLiteral("FakePluginType"), QStringLiteral("A == B OR C == D AND OR Foo == 'Parse Error'")); QVERIFY(offers.isEmpty()); } void KServiceTest::testSubseqConstraints() { auto test = [](const char* pattern, const char* text, bool sensitive) { - return KTraderParse::ParseTreeSubsequenceMATCH::isSubseq( + return KApplicationTrader::isSubsequence( QString(pattern), QString(text), sensitive? Qt::CaseSensitive : Qt::CaseInsensitive ); }; // Case Sensitive QVERIFY2(!test("", "", 1), "both empty"); QVERIFY2(!test("", "something", 1), "empty pattern"); QVERIFY2(!test("something", "", 1), "empty text"); QVERIFY2(test("lngfile", "somereallylongfile", 1), "match ending"); QVERIFY2(test("somelong", "somereallylongfile", 1), "match beginning"); QVERIFY2(test("reallylong", "somereallylongfile", 1), "match middle"); QVERIFY2(test("across", "a 23 c @#! r o01 o 5 s_s", 1), "match across"); QVERIFY2(!test("nocigar", "soclosebutnociga", 1), "close but no match"); QVERIFY2(!test("god", "dog", 1), "incorrect letter order"); QVERIFY2(!test("mismatch", "mIsMaTcH", 1), "case sensitive mismatch"); // Case insensitive QVERIFY2(test("mismatch", "mIsMaTcH", 0), "case insensitive match"); QVERIFY2(test("tryhards", "Try Your Hardest", 0), "uppercase text"); QVERIFY2(test("TRYHARDS", "try your hardest", 0), "uppercase pattern"); } void KServiceTest::testHasServiceType1() // with services constructed with a full path (rare) { QString fakepartPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kservices5/") + "fakepart.desktop"); QVERIFY(!fakepartPath.isEmpty()); KService fakepart(fakepartPath); QVERIFY(fakepart.hasServiceType(QStringLiteral("FakeBasePart"))); QVERIFY(fakepart.hasServiceType(QStringLiteral("FakeDerivedPart"))); QCOMPARE(fakepart.mimeTypes(), QStringList() << QStringLiteral("text/plain") << QStringLiteral("text/html")); QString faketextPluginPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("kservices5/") + "faketextplugin.desktop"); QVERIFY(!faketextPluginPath.isEmpty()); KService faketextPlugin(faketextPluginPath); QVERIFY(faketextPlugin.hasServiceType(QStringLiteral("FakePluginType"))); QVERIFY(!faketextPlugin.hasServiceType(QStringLiteral("FakeBasePart"))); } void KServiceTest::testHasServiceType2() // with services coming from ksycoca { KService::Ptr fakepart = KService::serviceByDesktopPath(QStringLiteral("fakepart.desktop")); QVERIFY(fakepart); QVERIFY(fakepart->hasServiceType(QStringLiteral("FakeBasePart"))); QVERIFY(fakepart->hasServiceType(QStringLiteral("FakeDerivedPart"))); QCOMPARE(fakepart->mimeTypes(), QStringList() << QStringLiteral("text/plain") << QStringLiteral("text/html")); KService::Ptr faketextPlugin = KService::serviceByDesktopPath(QStringLiteral("faketextplugin.desktop")); QVERIFY(faketextPlugin); QVERIFY(faketextPlugin->hasServiceType(QStringLiteral("FakePluginType"))); QVERIFY(!faketextPlugin->hasServiceType(QStringLiteral("FakeBasePart"))); } void KServiceTest::testWriteServiceTypeProfile() { const QString serviceType = QStringLiteral("FakeBasePart"); KService::List services, disabledServices; services.append(KService::serviceByDesktopPath(QStringLiteral("preferredpart.desktop"))); services.append(KService::serviceByDesktopPath(QStringLiteral("fakepart.desktop"))); disabledServices.append(KService::serviceByDesktopPath(QStringLiteral("fakepart2.desktop"))); for (const KService::Ptr &serv : qAsConst(services)) { QVERIFY(serv); } for (const KService::Ptr &serv : qAsConst(disabledServices)) { QVERIFY(serv); } KServiceTypeProfile::writeServiceTypeProfile(serviceType, services, disabledServices); // Check that the file got written QString profilerc = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/servicetype_profilerc"; QVERIFY(!profilerc.isEmpty()); QVERIFY(QFile::exists(profilerc)); KService::List offers = KServiceTypeTrader::self()->query(serviceType); QVERIFY(offers.count() > 0); // not empty //foreach( KService::Ptr service, offers ) // qDebug( "%s %s", qPrintable( service->name() ), qPrintable( service->entryPath() ) ); QVERIFY(offers.count() >= 2); QCOMPARE(offers[0]->entryPath(), QStringLiteral("preferredpart.desktop")); QCOMPARE(offers[1]->entryPath(), QStringLiteral("fakepart.desktop")); QVERIFY(offerListHasService(offers, QStringLiteral("otherpart.desktop"))); // should still be somewhere in there QVERIFY(!offerListHasService(offers, QStringLiteral("fakepart2.desktop"))); // it got disabled above } void KServiceTest::testDefaultOffers() { // Now that we have a user-profile, let's see if defaultOffers indeed gives us the default ordering. const QString serviceType = QStringLiteral("FakeBasePart"); KService::List offers = KServiceTypeTrader::self()->defaultOffers(serviceType); QVERIFY(offers.count() > 0); // not empty QVERIFY(offerListHasService(offers, QStringLiteral("fakepart2.desktop"))); // it's here even though it's disabled in the profile QVERIFY(offerListHasService(offers, QStringLiteral("otherpart.desktop"))); if (m_firstOffer.isEmpty()) { QSKIP("testServiceTypeTraderForReadOnlyPart not run"); } QCOMPARE(offers[0]->entryPath(), m_firstOffer); } void KServiceTest::testDeleteServiceTypeProfile() { const QString serviceType = QStringLiteral("FakeBasePart"); KServiceTypeProfile::deleteServiceTypeProfile(serviceType); KService::List offers = KServiceTypeTrader::self()->query(serviceType); QVERIFY(offers.count() > 0); // not empty QVERIFY(offerListHasService(offers, QStringLiteral("fakepart2.desktop"))); // it's back if (m_firstOffer.isEmpty()) { QSKIP("testServiceTypeTraderForReadOnlyPart not run"); } QCOMPARE(offers[0]->entryPath(), m_firstOffer); } void KServiceTest::testActionsAndDataStream() { KService::Ptr service = KService::serviceByStorageId(QStringLiteral("org.kde.faketestapp.desktop")); QVERIFY(service); QVERIFY(!service->property(QStringLiteral("Name[fr]"), QVariant::String).isValid()); const QList actions = service->actions(); QCOMPARE(actions.count(), 2); // NewWindow, NewTab const KServiceAction newTabAction = actions.at(1); QCOMPARE(newTabAction.name(), QStringLiteral("NewTab")); QCOMPARE(newTabAction.exec(), QStringLiteral("konsole --new-tab")); QCOMPARE(newTabAction.icon(), QStringLiteral("tab-new")); QCOMPARE(newTabAction.noDisplay(), false); QVERIFY(!newTabAction.isSeparator()); } void KServiceTest::testServiceGroups() { KServiceGroup::Ptr root = KServiceGroup::root(); QVERIFY(root); qDebug() << root->groupEntries().count(); KServiceGroup::Ptr group = root; QVERIFY(group); const KServiceGroup::List list = group->entries(true /* sorted */, true /* exclude no display entries */, false /* allow separators */, true /* sort by generic name */); qDebug() << list.count(); for (KServiceGroup::SPtr s : list) { qDebug() << s->name() << s->entryPath(); } // No unit test here yet, but at least this can be valgrinded for errors. } void KServiceTest::testDeletingService() { // workaround unexplained inotify issue (in CI only...) QTest::qWait(1000); const QString serviceName = QStringLiteral("fakeservice_deleteme.desktop"); KService::Ptr fakeService = KService::serviceByDesktopPath(serviceName); QVERIFY(fakeService); // see initTestCase; it should be found. // Test deleting a service const QString servPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + serviceName; QVERIFY(QFile::exists(servPath)); QFile::remove(servPath); runKBuildSycoca(); ksycoca_ms_between_checks = 0; // need it to check the ksycoca mtime QVERIFY(!KService::serviceByDesktopPath(serviceName)); // not in ksycoca anymore // Restore value ksycoca_ms_between_checks = 1500; QVERIFY(fakeService); // the whole point of refcounting is that this KService instance is still valid. QVERIFY(!QFile::exists(servPath)); // Recreate it, for future tests createFakeService(serviceName, QString()); QVERIFY(QFile::exists(servPath)); qDebug() << "executing kbuildsycoca (2)"; runKBuildSycoca(); if (QThread::currentThread() != QCoreApplication::instance()->thread()) { m_sycocaUpdateDone.ref(); } } void KServiceTest::createFakeService(const QString &filename, const QString& serviceType) { const QString fakeService = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + filename; KDesktopFile file(fakeService); KConfigGroup group = file.desktopGroup(); group.writeEntry("Name", "FakePlugin"); group.writeEntry("Type", "Service"); group.writeEntry("X-KDE-Library", "fakeservice"); group.writeEntry("X-KDE-Version", "4.56"); group.writeEntry("ServiceTypes", serviceType); group.writeEntry("MimeType", "text/plain;"); } #include #include #include // Testing for concurrent access to ksycoca from multiple threads // It's especially interesting to run this test as ./kservicetest testThreads // so that even the ksycoca initialization is happening from N threads at the same time. // Use valgrind --tool=helgrind to see the race conditions. void KServiceTest::testReaderThreads() { QThreadPool::globalInstance()->setMaxThreadCount(10); QFutureSynchronizer sync; sync.addFuture(QtConcurrent::run(this, &KServiceTest::testAllServices)); sync.addFuture(QtConcurrent::run(this, &KServiceTest::testAllServices)); sync.addFuture(QtConcurrent::run(this, &KServiceTest::testAllServices)); sync.addFuture(QtConcurrent::run(this, &KServiceTest::testHasServiceType1)); sync.addFuture(QtConcurrent::run(this, &KServiceTest::testAllServices)); sync.addFuture(QtConcurrent::run(this, &KServiceTest::testAllServices)); sync.waitForFinished(); QThreadPool::globalInstance()->setMaxThreadCount(1); // delete those threads } void KServiceTest::testThreads() { QThreadPool::globalInstance()->setMaxThreadCount(10); QFutureSynchronizer sync; sync.addFuture(QtConcurrent::run(this, &KServiceTest::testAllServices)); sync.addFuture(QtConcurrent::run(this, &KServiceTest::testHasServiceType1)); sync.addFuture(QtConcurrent::run(this, &KServiceTest::testDeletingService)); sync.addFuture(QtConcurrent::run(this, &KServiceTest::testTraderConstraints)); // process events (DBus, inotify...), until we get all expected signals #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) QTRY_COMPARE_WITH_TIMEOUT(m_sycocaUpdateDone.load(), 1, 15000); // not using a bool, just to silence helgrind #else QTRY_COMPARE_WITH_TIMEOUT(m_sycocaUpdateDone.loadRelaxed(), 1, 15000); // not using a bool, just to silence helgrind #endif qDebug() << "Joining all threads"; sync.waitForFinished(); } void KServiceTest::testOperatorKPluginName() { KService fservice(QFINDTESTDATA("fakeplugin.desktop")); KPluginName fname(fservice); QVERIFY(fname.isValid()); QCOMPARE(fname.name(), QStringLiteral("fakeplugin")); KPluginLoader fplugin(fservice); QVERIFY(fplugin.factory()); // make sure constness doesn't break anything const KService const_fservice(QFINDTESTDATA("fakeplugin.desktop")); KPluginName const_fname(const_fservice); QVERIFY(const_fname.isValid()); QCOMPARE(const_fname.name(), QStringLiteral("fakeplugin")); KPluginLoader const_fplugin(const_fservice); QVERIFY(const_fplugin.factory()); KService nservice(QFINDTESTDATA("noplugin.desktop")); KPluginName nname(nservice); QVERIFY(!nname.isValid()); QVERIFY2(nname.name().isEmpty(), qPrintable(nname.name())); QVERIFY(!nname.errorString().isEmpty()); KPluginLoader nplugin(nservice); QVERIFY(!nplugin.factory()); KService iservice(QStringLiteral("idonotexist.desktop")); KPluginName iname(iservice); QVERIFY(!iname.isValid()); QVERIFY2(iname.name().isEmpty(), qPrintable(iname.name())); QVERIFY(!iname.errorString().isEmpty()); KPluginLoader iplugin(iservice); QVERIFY(!iplugin.factory()); } void KServiceTest::testKPluginInfoQuery() { KPluginInfo info(KPluginMetaData(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + "fakepart2.desktop")); QCOMPARE(info.property(QStringLiteral("X-KDE-TestList")).toStringList().size(), 2); } void KServiceTest::testCompleteBaseName() { QCOMPARE(KServiceUtilPrivate::completeBaseName(QStringLiteral("/home/x/.qttest/share/kservices5/fakepart2.desktop")), QStringLiteral("fakepart2")); // dots in filename before .desktop extension: QCOMPARE(KServiceUtilPrivate::completeBaseName(QStringLiteral("/home/x/.qttest/share/kservices5/org.kde.fakeapp.desktop")), QStringLiteral("org.kde.fakeapp")); } void KServiceTest::testEntryPathToName() { QCOMPARE(KService(QStringLiteral("c.desktop")).name(), QStringLiteral("c")); QCOMPARE(KService(QStringLiteral("a.b.c.desktop")).name(), QStringLiteral("a.b.c")); // dots in filename before .desktop extension QCOMPARE(KService(QStringLiteral("/hallo/a.b.c.desktop")).name(), QStringLiteral("a.b.c")); } #if KSERVICE_ENABLE_DEPRECATED_SINCE(5, 0) void KServiceTest::testKPluginMetaData() { const QString fakePart = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + "fakepart.desktop"; KPluginMetaData md(fakePart); KService::Ptr service(new KService(fakePart)); KPluginInfo info(service); auto info_md = info.toMetaData(); QCOMPARE(info_md.formFactors(), md.formFactors()); } #endif void KServiceTest::testTraderQueryMustRebuildSycoca() { QVERIFY(!KServiceTypeProfile::hasProfile(QStringLiteral("FakeBasePart"))); QTest::qWait(1000); createFakeService(QStringLiteral("fakeservice_querymustrebuild.desktop"), QString()); // just to touch the dir KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("FakeBasePart")); QVERIFY(offers.count() > 0); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 29b3f7d..6f4a15c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,226 +1,228 @@ include(CheckSymbolExists) include(CheckFunctionExists) check_function_exists(mmap HAVE_MMAP) check_symbol_exists(posix_madvise "sys/mman.h" HAVE_MADVISE) configure_file(config-ksycoca.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ksycoca.h ) set(kservice_SRCS kdeinit/ktoolinvocation.cpp services/kautostart.cpp + services/kapplicationtrader.cpp services/kmimetypefactory.cpp services/kmimetypetrader.cpp services/kservice.cpp services/kserviceaction.cpp services/kservicefactory.cpp services/kservicegroup.cpp services/kservicegroupfactory.cpp services/kserviceoffer.cpp services/kservicetype.cpp services/kservicetypefactory.cpp services/kservicetypeprofile.cpp services/kservicetypetrader.cpp services/ktraderparse.cpp services/ktraderparsetree.cpp services/kplugininfo.cpp sycoca/ksycoca.cpp sycoca/ksycocadevices.cpp sycoca/ksycocadict.cpp sycoca/ksycocaentry.cpp sycoca/ksycocafactory.cpp sycoca/kmemfile.cpp sycoca/kbuildmimetypefactory.cpp sycoca/kbuildservicetypefactory.cpp sycoca/kbuildservicefactory.cpp sycoca/kbuildservicegroupfactory.cpp sycoca/kbuildsycoca.cpp sycoca/kctimefactory.cpp sycoca/kmimeassociations.cpp sycoca/vfolder_menu.cpp plugin/kplugintrader.cpp ) if (TARGET Qt5::DBus) list(APPEND kservice_SRCS plugin/kdbusservicestarter.cpp) endif() ecm_qt_declare_logging_category(kservice_SRCS HEADER servicesdebug.h IDENTIFIER SERVICES CATEGORY_NAME kf5.kservice.services) ecm_qt_declare_logging_category(kservice_SRCS HEADER sycocadebug.h IDENTIFIER SYCOCA CATEGORY_NAME kf5.kservice.sycoca) if (WIN32) LIST(APPEND kservice_SRCS kdeinit/ktoolinvocation_win.cpp ) endif() if (UNIX) LIST(APPEND kservice_SRCS kdeinit/ktoolinvocation_x11.cpp ) endif() bison_target(TraderParser services/yacc.y ${CMAKE_CURRENT_BINARY_DIR}/yacc.c COMPILE_FLAGS "-p kiotrader -d" ) flex_target(TraderLexer services/lex.l ${CMAKE_CURRENT_BINARY_DIR}/lex.c COMPILE_FLAGS "-Pkiotrader -B -i" ) add_flex_bison_dependency(TraderLexer TraderParser) list(APPEND kservice_SRCS ${BISON_TraderParser_OUTPUTS} ${FLEX_TraderLexer_OUTPUTS}) set_property(SOURCE ${CMAKE_CURRENT_BINARY_DIR}/yacc.h PROPERTY SKIP_AUTOMOC TRUE) # don't run automoc on this file # kservice cannot depend on kinit (because kinit->kio->kservice), so we need a copy of org.kde.KLauncher.xml here. # And I don't want to have it here as a source file (who wants to edit dbus xml by hand), so it can be # generated from klauncher's implementation header. if (TARGET Qt5::DBus) qt5_add_dbus_interface(kservice_SRCS kdeinit/org.kde.KLauncher.xml klauncher_iface) endif() add_library(KF5Service ${kservice_SRCS}) if(WIN32) #unistd.h does not exist on windows target_compile_definitions(KF5Service PRIVATE YY_NO_UNISTD_H=1) endif() add_library(KF5::Service ALIAS KF5Service) ecm_generate_export_header(KF5Service BASE_NAME KService GROUP_BASE_NAME KF VERSION ${KF5_VERSION} DEPRECATED_BASE_VERSION 0 DEPRECATION_VERSIONS 5.0 5.15 5.61 5.63 5.66 5.67 EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT} ) set(kservice_includes ${CMAKE_CURRENT_BINARY_DIR}/.. # Since we publicly include kservice_version.h ${CMAKE_CURRENT_SOURCE_DIR}/services ${CMAKE_CURRENT_SOURCE_DIR}/sycoca ${CMAKE_CURRENT_SOURCE_DIR}/plugin ${CMAKE_CURRENT_SOURCE_DIR}/kdeinit ) target_include_directories(KF5Service PUBLIC "$") target_include_directories(KF5Service INTERFACE "$") target_link_libraries(KF5Service PUBLIC KF5::ConfigCore # KConfig and friends KF5::CoreAddons # KShell KPluginLoader PRIVATE KF5::I18n Qt5::Xml # (for vfolder menu) QDomDocument ) if (TARGET KF5::DBusAddons) target_link_libraries(KF5Service PRIVATE KF5::DBusAddons) # KDEInitInterface endif() set_target_properties(KF5Service PROPERTIES VERSION ${KSERVICE_VERSION_STRING} SOVERSION ${KSERVICE_SOVERSION} EXPORT_NAME Service ) ecm_generate_headers(KService_HEADERS HEADER_NAMES KPluginTrader KDBusServiceStarter RELATIVE plugin REQUIRED_HEADERS KService_HEADERS ) ecm_generate_headers(KService_HEADERS HEADER_NAMES KSycoca KSycocaEntry KSycocaType RELATIVE sycoca REQUIRED_HEADERS KService_HEADERS ) ecm_generate_headers(KService_HEADERS HEADER_NAMES KToolInvocation RELATIVE kdeinit REQUIRED_HEADERS KService_HEADERS ) ecm_generate_headers(KService_HEADERS HEADER_NAMES + KApplicationTrader KAutostart KMimeTypeTrader KService KServiceAction KServiceGroup KServiceType KServiceTypeProfile KServiceTypeTrader KPluginInfo RELATIVE services REQUIRED_HEADERS KService_HEADERS ) install(FILES services/kplugininfo.desktop services/application.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR} ) # Local copy for the unittests add_custom_target(copy_servicetypes) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/bin/data/kservicetypes5) add_custom_command(TARGET copy_servicetypes PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/services/kplugininfo.desktop ${CMAKE_CURRENT_SOURCE_DIR}/services/application.desktop ${CMAKE_BINARY_DIR}/bin/data/kservicetypes5) # not using KDE_INSTALL_KSERVICETYPES5DIR because QStandardPaths wants "data" on Windows add_dependencies(KF5Service copy_servicetypes) if (WIN32) install( FILES applications.menu DESTINATION ${KDE_INSTALL_DATAROOTDIR}/xdg/menus RENAME ${APPLICATIONS_MENU_NAME} ) else () install( FILES applications.menu DESTINATION ${KDE_INSTALL_SYSCONFDIR}/xdg/menus RENAME ${APPLICATIONS_MENU_NAME} ) endif () # Local copy for the unittests add_custom_target(copy_menu) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/bin/data/menus) add_custom_command(TARGET copy_menu PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/applications.menu ${CMAKE_BINARY_DIR}/bin/data/menus/${APPLICATIONS_MENU_NAME}) add_dependencies(KF5Service copy_menu) install(TARGETS KF5Service EXPORT KF5ServiceTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/kservice_export.h" ${KService_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KService COMPONENT Devel ) if(BUILD_QCH) ecm_add_qch( KF5Service_QCH NAME KService BASE_NAME KF5Service VERSION ${KF5_VERSION} ORG_DOMAIN org.kde SOURCES # using only public headers, to cover only public API ${KService_HEADERS} MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" LINK_QCHS KF5Config_QCH KF5CoreAddons_QCH INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR} ${kservice_includes} BLANK_MACROS KSERVICE_EXPORT KSERVICE_DEPRECATED KSERVICE_DEPRECATED_EXPORT "KSERVICE_DEPRECATED_VERSION(x, y, t)" TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() add_subdirectory(kbuildsycoca) include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KService LIB_NAME KF5Service DEPS "KConfigCore" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KService) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/services/kapplicationtrader.cpp b/src/services/kapplicationtrader.cpp new file mode 100644 index 0000000..73ad463 --- /dev/null +++ b/src/services/kapplicationtrader.cpp @@ -0,0 +1,144 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Torben Weis + Copyright (C) 2006-2020 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kapplicationtrader.h" + +#include "ksycoca.h" +#include "ksycoca_p.h" +#include "kservicetypefactory_p.h" +#include "kservicefactory_p.h" +#include "kmimetypefactory_p.h" +#include "servicesdebug.h" + +#include + +static KService::List mimeTypeSycocaServiceOffers(const QString &mimeType) +{ + KService::List lst; + QMimeDatabase db; + QString mime = db.mimeTypeForName(mimeType).name(); + if (mime.isEmpty()) { + if (!mimeType.startsWith(QLatin1String("x-scheme-handler/"))) { // don't warn for unknown scheme handler mimetypes + qCWarning(SERVICES) << "KApplicationTrader: mimeType" << mimeType << "not found"; + return lst; // empty + } + mime = mimeType; + } + KSycoca::self()->ensureCacheValid(); + KMimeTypeFactory *factory = KSycocaPrivate::self()->mimeTypeFactory(); + const int offset = factory->entryOffset(mime); + if (!offset) { + qCWarning(SERVICES) << "KApplicationTrader: mimeType" << mimeType << "not found"; + return lst; // empty + } + const int serviceOffersOffset = factory->serviceOffersOffset(mime); + if (serviceOffersOffset > -1) { + lst = KSycocaPrivate::self()->serviceFactory()->serviceOffers(offset, serviceOffersOffset); + } + return lst; +} + +// Filter the offers for the requested mime type in order to keep only applications. +static void filterMimeTypeOffers(KService::List &list) // static, internal +{ + KServiceType::Ptr genericServiceTypePtr = KServiceType::serviceType(QStringLiteral("Application")); + Q_ASSERT(genericServiceTypePtr); + + KSycoca::self()->ensureCacheValid(); + KServiceFactory *serviceFactory = KSycocaPrivate::self()->serviceFactory(); + + // Remove non-Applications (TODO KF6: kill plugin desktop files, then kill this code) + auto removeFunc = [&](const KService::Ptr &serv) { + return !serviceFactory->hasOffer(genericServiceTypePtr, serv); + }; + list.erase(std::remove_if(list.begin(), list.end(), removeFunc), list.end()); +} + +static void applyFilter(KService::List &list, KApplicationTrader::FilterFunc filterFunc) +{ + if (list.isEmpty()) { + return; + } + + // Find all services matching the constraint + // and remove the other ones + auto removeFunc = [&](const KService::Ptr &serv) { + return !serv->showInCurrentDesktop() || (filterFunc && !filterFunc(serv)); + }; + list.erase(std::remove_if(list.begin(), list.end(), removeFunc), list.end()); +} + +KService::List KApplicationTrader::query(FilterFunc filterFunc) +{ + // Get all applications + KSycoca::self()->ensureCacheValid(); + KServiceType::Ptr servTypePtr = KSycocaPrivate::self()->serviceTypeFactory()->findServiceTypeByName(QStringLiteral("Application")); + Q_ASSERT(servTypePtr); + if (servTypePtr->serviceOffersOffset() == -1) { + return KService::List(); + } + + KService::List lst = KSycocaPrivate::self()->serviceFactory()->serviceOffers(servTypePtr); + + applyFilter(lst, filterFunc); + + qCDebug(SERVICES) << "query returning" << lst.count() << "offers"; + return lst; +} + +KService::List KApplicationTrader::queryByMimeType(const QString &mimeType, + FilterFunc filterFunc) +{ + // Get all services of this mime type. + KService::List lst = mimeTypeSycocaServiceOffers(mimeType); + filterMimeTypeOffers(lst); + + applyFilter(lst, filterFunc); + + qCDebug(SERVICES) << "query for mimeType" << mimeType << "returning" << lst.count() << "offers"; + return lst; +} + +KService::Ptr KApplicationTrader::preferredService(const QString &mimeType) +{ + const KService::List offers = queryByMimeType(mimeType); + if (!offers.isEmpty()) { + return offers.at(0); + } + return KService::Ptr(); +} + +bool KApplicationTrader::isSubsequence(const QString &pattern, const QString &text, Qt::CaseSensitivity cs) +{ + if (pattern.isEmpty()) { + return false; + } + const bool chk_case = cs == Qt::CaseSensitive; + + QString::const_iterator i = text.constBegin(), j = pattern.constBegin(); + for (; i != text.constEnd() && j != pattern.constEnd(); ++i) { + if ((chk_case && *i == *j) || (!chk_case && i->toLower() == j->toLower())) { + ++j; + } + } + return j == pattern.constEnd(); +} + + diff --git a/src/services/kapplicationtrader.h b/src/services/kapplicationtrader.h new file mode 100644 index 0000000..baa569f --- /dev/null +++ b/src/services/kapplicationtrader.h @@ -0,0 +1,104 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Torben Weis + Copyright (C) 2006-2020 David Faure + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KAPPLICATIONTRADER_H +#define KAPPLICATIONTRADER_H + +#include +#include + +/** + * @class KApplicationTrader kapplicationtrader.h + * + * The application trader is a convenient way to find installed applications + * based on specific criteria (association with a mimetype, name contains Foo, etc.) + * + * Example: say that you want to get the list of all applications that can handle PNG images. + * The code would look like: + * \code + * KService::List lst = KApplicationTrader::queryByMimeType("image/png"); + * \endcode + * + * If you want to get the preferred application for image/png you would use: + * @code + * KService::Ptr service = KApplicationTrader::preferredService("image/png"); + * @endcode + * + * @see KService + */ +namespace KApplicationTrader +{ + /** + * Filter function, used for filtering results of query and queryByMimeType. + */ + using FilterFunc = std::function; + + /** + * This method returns a list of services (applications) which are associated with a given mimetype. + * + * @param filter a callback function that returns @c true if the application + * should be selected and @c false if it should be skipped. + * + * @return A list of services that satisfy the query + * @since 5.68 + */ + KSERVICE_EXPORT KService::List query(FilterFunc filterFunc); + + /** + * This method returns a list of services (applications) which are associated with a given mimetype. + * + * @param mimeType A mime type like 'text/plain' or 'text/html'. + * @param filter a callback function that returns @c true if the application + * should be selected and @c false if it should be skipped. Do not return + * true for all services, this would return the complete list of all + * installed applications (slow). + * + * @return A list of services that satisfy the query, sorted by preference + * (preferred service first) + * @since 5.68 + */ + KSERVICE_EXPORT KService::List queryByMimeType(const QString &mimeType, + FilterFunc filterFunc = {}); + + /** + * Returns the preferred service for @p mimeType + * + * This a convenience method for queryByMimeType(mimeType).at(0), with a check for empty. + * + * @param mimeType the mime type (see query()) + * @return the preferred service, or @c nullptr if no service is available + * @since 5.68 + */ + KSERVICE_EXPORT KService::Ptr preferredService(const QString &mimeType); + + + /** + * Returns true if @p pattern matches a subsequence of the string @p text. + * For instance the pattern "libremath" matches the text "LibreOffice Math", assuming + * @p cs is Qt::CaseInsensitive. + * + * This can be useful from your filter function, e.g. with @p text being service->name(). + * @since 5.68 + */ + KSERVICE_EXPORT bool isSubsequence(const QString &pattern, const QString &text, Qt::CaseSensitivity cs = Qt::CaseSensitive); + +} + +#endif diff --git a/src/services/kmimetypetrader.cpp b/src/services/kmimetypetrader.cpp index 4722494..05e806c 100644 --- a/src/services/kmimetypetrader.cpp +++ b/src/services/kmimetypetrader.cpp @@ -1,207 +1,205 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Torben Weis Copyright (C) 2006 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kmimetypetrader.h" #include "ksycoca.h" #include "ksycoca_p.h" #include "kservicetypeprofile.h" #include "kservicetype.h" #include "kservicetypetrader.h" #include "kservicefactory_p.h" #include "kmimetypefactory_p.h" #include "servicesdebug.h" #include class KMimeTypeTraderPrivate { public: KMimeTypeTraderPrivate() {} }; class KMimeTypeTraderSingleton { public: KMimeTypeTrader instance; }; Q_GLOBAL_STATIC(KMimeTypeTraderSingleton, s_self) KMimeTypeTrader *KMimeTypeTrader::self() { return &s_self()->instance; } KMimeTypeTrader::KMimeTypeTrader() : d(new KMimeTypeTraderPrivate()) { } KMimeTypeTrader::~KMimeTypeTrader() { delete d; } static KServiceOfferList mimeTypeSycocaOffers(const QString &mimeType) { KServiceOfferList lst; QMimeDatabase db; QString mime = db.mimeTypeForName(mimeType).name(); if (mime.isEmpty()) { if (!mimeType.startsWith(QLatin1String("x-scheme-handler/"))) { // don't warn for unknown scheme handler mimetypes qCWarning(SERVICES) << "KMimeTypeTrader: mimeType" << mimeType << "not found"; return lst; // empty } mime = mimeType; } KSycoca::self()->ensureCacheValid(); KMimeTypeFactory *factory = KSycocaPrivate::self()->mimeTypeFactory(); const int offset = factory->entryOffset(mime); if (!offset) { // shouldn't happen, now that we know the mimetype exists if (!mimeType.startsWith(QLatin1String("x-scheme-handler/"))) { // don't warn for unknown scheme handler mimetypes qCDebug(SERVICES) << "KMimeTypeTrader: no entry offset for" << mimeType; } return lst; // empty } const int serviceOffersOffset = factory->serviceOffersOffset(mime); if (serviceOffersOffset > -1) { lst = KSycocaPrivate::self()->serviceFactory()->offers(offset, serviceOffersOffset); } return lst; } static KService::List mimeTypeSycocaServiceOffers(const QString &mimeType) { KService::List lst; QMimeDatabase db; QString mime = db.mimeTypeForName(mimeType).name(); if (mime.isEmpty()) { if (!mimeType.startsWith(QLatin1String("x-scheme-handler/"))) { // don't warn for unknown scheme handler mimetypes qCWarning(SERVICES) << "KMimeTypeTrader: mimeType" << mimeType << "not found"; return lst; // empty } mime = mimeType; } KSycoca::self()->ensureCacheValid(); KMimeTypeFactory *factory = KSycocaPrivate::self()->mimeTypeFactory(); const int offset = factory->entryOffset(mime); if (!offset) { qCWarning(SERVICES) << "KMimeTypeTrader: mimeType" << mimeType << "not found"; return lst; // empty } const int serviceOffersOffset = factory->serviceOffersOffset(mime); if (serviceOffersOffset > -1) { lst = KSycocaPrivate::self()->serviceFactory()->serviceOffers(offset, serviceOffersOffset); } return lst; } #define CHECK_SERVICETYPE(genericServiceTypePtr) \ if (!genericServiceTypePtr) { \ qCWarning(SERVICES) << "KMimeTypeTrader: couldn't find service type" << genericServiceType << \ "\nPlease ensure that the .desktop file for it is installed; then run kbuildsycoca5."; \ return; \ } /** * Filter the offers for the requested mime type for the genericServiceType. * * @param list list of offers (key=service, value=initialPreference) * @param genericServiceType the generic service type (e.g. "Application" or "KParts/ReadOnlyPart") */ void KMimeTypeTrader::filterMimeTypeOffers(KServiceOfferList &list, const QString &genericServiceType) // static, internal { KServiceType::Ptr genericServiceTypePtr = KServiceType::serviceType(genericServiceType); CHECK_SERVICETYPE(genericServiceTypePtr); KSycoca::self()->ensureCacheValid(); QMutableListIterator it(list); while (it.hasNext()) { const KService::Ptr servPtr = it.next().service(); // Expand servPtr->hasServiceType( genericServiceTypePtr ) to avoid lookup each time: - if (!KSycocaPrivate::self()->serviceFactory()->hasOffer(genericServiceTypePtr->offset(), - genericServiceTypePtr->serviceOffersOffset(), - servPtr->offset()) + if (!KSycocaPrivate::self()->serviceFactory()->hasOffer(genericServiceTypePtr, servPtr) || !servPtr->showInCurrentDesktop()) { it.remove(); } } } void KMimeTypeTrader::filterMimeTypeOffers(KService::List &list, const QString &genericServiceType) // static, internal { KServiceType::Ptr genericServiceTypePtr = KServiceType::serviceType(genericServiceType); CHECK_SERVICETYPE(genericServiceTypePtr); KSycoca::self()->ensureCacheValid(); QMutableListIterator it(list); while (it.hasNext()) { const KService::Ptr servPtr = it.next(); // Expand servPtr->hasServiceType( genericServiceTypePtr ) to avoid lookup each time: if (!KSycocaPrivate::self()->serviceFactory()->hasOffer(genericServiceTypePtr->offset(), genericServiceTypePtr->serviceOffersOffset(), servPtr->offset()) || !servPtr->showInCurrentDesktop()) { it.remove(); } } } #undef CHECK_SERVICETYPE KService::List KMimeTypeTrader::query(const QString &mimeType, const QString &genericServiceType, const QString &constraint) const { // Get all services of this mime type. KService::List lst = mimeTypeSycocaServiceOffers(mimeType); filterMimeTypeOffers(lst, genericServiceType); KServiceTypeTrader::applyConstraints(lst, constraint); //qCDebug(SERVICES) << "query for mimeType " << mimeType << ", " << genericServiceType // << " : returning " << lst.count() << " offers"; return lst; } KService::Ptr KMimeTypeTrader::preferredService(const QString &mimeType, const QString &genericServiceType) { // First, get all offers known to ksycoca. KServiceOfferList offers = mimeTypeSycocaOffers(mimeType); // Assign preferences from the profile to those offers - and filter for genericServiceType Q_ASSERT(!genericServiceType.isEmpty()); filterMimeTypeOffers(offers, genericServiceType); KServiceOfferList::const_iterator itOff = offers.constBegin(); // Look for the first one that is allowed as default. // Since the allowed-as-default are first anyway, we only have // to look at the first one to know. if (itOff != offers.constEnd() && (*itOff).allowAsDefault()) { return (*itOff).service(); } //qCDebug(SERVICES) << "No offers, or none allowed as default"; return KService::Ptr(); } diff --git a/src/services/kservicefactory.cpp b/src/services/kservicefactory.cpp index 9a06137..49f5663 100644 --- a/src/services/kservicefactory.cpp +++ b/src/services/kservicefactory.cpp @@ -1,357 +1,367 @@ /* This file is part of the KDE libraries * Copyright (C) 1999-2006 David Faure * * 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 of the License or ( at * your option ) version 3 or, at the discretion of KDE e.V. ( which shall * act as a proxy as in section 14 of the GPLv3 ), any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. **/ #include "kservicefactory_p.h" #include "ksycoca.h" #include "ksycocatype.h" #include "ksycocadict_p.h" #include "kservice.h" #include "servicesdebug.h" #include #include extern int servicesDebugArea(); KServiceFactory::KServiceFactory(KSycoca *db) : KSycocaFactory(KST_KServiceFactory, db), m_nameDict(nullptr), m_relNameDict(nullptr), m_menuIdDict(nullptr) { m_offerListOffset = 0; m_nameDictOffset = 0; m_relNameDictOffset = 0; m_menuIdDictOffset = 0; if (!sycoca()->isBuilding()) { QDataStream *str = stream(); Q_ASSERT(str); if (!str) { return; } // Read Header qint32 i; (*str) >> i; m_nameDictOffset = i; (*str) >> i; m_relNameDictOffset = i; (*str) >> i; m_offerListOffset = i; (*str) >> i; m_menuIdDictOffset = i; const qint64 saveOffset = str->device()->pos(); // Init index tables m_nameDict = new KSycocaDict(str, m_nameDictOffset); // Init index tables m_relNameDict = new KSycocaDict(str, m_relNameDictOffset); // Init index tables m_menuIdDict = new KSycocaDict(str, m_menuIdDictOffset); str->device()->seek(saveOffset); } } KServiceFactory::~KServiceFactory() { delete m_nameDict; delete m_relNameDict; delete m_menuIdDict; } KService::Ptr KServiceFactory::findServiceByName(const QString &_name) { if (!sycocaDict()) { return KService::Ptr(); // Error! } // Warning : this assumes we're NOT building a database // But since findServiceByName isn't called in that case... // [ see KServiceTypeFactory for how to do it if needed ] int offset = sycocaDict()->find_string(_name); if (!offset) { return KService::Ptr(); // Not found } KService::Ptr newService(createEntry(offset)); // Check whether the dictionary was right. if (newService && (newService->name() != _name)) { // No it wasn't... return KService::Ptr(); } return newService; } KService::Ptr KServiceFactory::findServiceByDesktopName(const QString &_name) { if (!m_nameDict) { return KService::Ptr(); // Error! } // Warning : this assumes we're NOT building a database // KBuildServiceFactory reimplements it for the case where we are building one int offset = m_nameDict->find_string(_name); if (!offset) { return KService::Ptr(); // Not found } KService::Ptr newService(createEntry(offset)); // Check whether the dictionary was right. if (newService && (newService->desktopEntryName() != _name)) { // No it wasn't... return KService::Ptr(); } return newService; } KService::Ptr KServiceFactory::findServiceByDesktopPath(const QString &_name) { if (!m_relNameDict) { return KService::Ptr(); // Error! } // Warning : this assumes we're NOT building a database // KBuildServiceFactory reimplements it for the case where we are building one int offset = m_relNameDict->find_string(_name); if (!offset) { //qCDebug(SERVICES) << "findServiceByDesktopPath:" << _name << "not found"; return KService::Ptr(); // Not found } KService::Ptr newService(createEntry(offset)); if (!newService) { qCDebug(SERVICES) << "createEntry failed!"; } // Check whether the dictionary was right // It's ok that it's wrong, for the case where we're looking up an unknown service, // and the hash value gave us another one. if (newService && (newService->entryPath() != _name)) { // No it wasn't... return KService::Ptr(); } return newService; } KService::Ptr KServiceFactory::findServiceByMenuId(const QString &_menuId) { if (!m_menuIdDict) { return KService::Ptr(); // Error! } // Warning : this assumes we're NOT building a database // KBuildServiceFactory reimplements it for the case where we are building one int offset = m_menuIdDict->find_string(_menuId); if (!offset) { return KService::Ptr(); // Not found } KService::Ptr newService(createEntry(offset)); // Check whether the dictionary was right. if (newService && (newService->menuId() != _menuId)) { // No it wasn't... return KService::Ptr(); } return newService; } KService::Ptr KServiceFactory::findServiceByStorageId(const QString &_storageId) { KService::Ptr service = findServiceByMenuId(_storageId); if (service) { return service; } service = findServiceByDesktopPath(_storageId); if (service) { return service; } if (!QDir::isRelativePath(_storageId) && QFile::exists(_storageId)) { return KService::Ptr(new KService(_storageId)); } QString tmp = _storageId; tmp = tmp.mid(tmp.lastIndexOf(QLatin1Char('/')) + 1); // Strip dir if (tmp.endsWith(QLatin1String(".desktop"))) { tmp.chop(8); } if (tmp.endsWith(QLatin1String(".kdelnk"))) { tmp.chop(7); } service = findServiceByDesktopName(tmp); return service; } KService *KServiceFactory::createEntry(int offset) const { KSycocaType type; QDataStream *str = sycoca()->findEntry(offset, type); if (type != KST_KService) { qCWarning(SERVICES) << "KServiceFactory: unexpected object entry in KSycoca database (type=" << int(type) << ")"; return nullptr; } KService *newEntry = new KService(*str, offset); if (!newEntry->isValid()) { qCWarning(SERVICES) << "KServiceFactory: corrupt object in KSycoca database!"; delete newEntry; newEntry = nullptr; } return newEntry; } KService::List KServiceFactory::allServices() { KService::List result; const KSycocaEntry::List list = allEntries(); KSycocaEntry::List::const_iterator it = list.begin(); const KSycocaEntry::List::const_iterator end = list.end(); for (; it != end; ++it) { const KSycocaEntry::Ptr entry = *it; if (entry->isType(KST_KService)) { KService::Ptr service(static_cast(entry.data())); result.append(service); } } return result; } QStringList KServiceFactory::resourceDirs() { return KSycocaFactory::allDirectories(QStringLiteral("kservices5")) + KSycocaFactory::allDirectories(QStringLiteral("applications")); } QList KServiceFactory::offers(int serviceTypeOffset, int serviceOffersOffset) { QList list; // Jump to the offer list QDataStream *str = stream(); str->device()->seek(m_offerListOffset + serviceOffersOffset); qint32 aServiceTypeOffset, aServiceOffset, initialPreference, mimeTypeInheritanceLevel; while (true) { (*str) >> aServiceTypeOffset; if (aServiceTypeOffset) { (*str) >> aServiceOffset; (*str) >> initialPreference; (*str) >> mimeTypeInheritanceLevel; if (aServiceTypeOffset == serviceTypeOffset) { // Save stream position ! const qint64 savedPos = str->device()->pos(); // Create Service KService *serv = createEntry(aServiceOffset); if (serv) { KService::Ptr servPtr(serv); list.append(KServiceOffer(servPtr, initialPreference, mimeTypeInheritanceLevel, servPtr->allowAsDefault())); } // Restore position str->device()->seek(savedPos); } else { break; // too far } } else { break; // 0 => end of list } } return list; } +KService::List KServiceFactory::serviceOffers(const KServiceType::Ptr &serviceType) +{ + return serviceOffers(serviceType->offset(), serviceType->serviceOffersOffset()); +} + KService::List KServiceFactory::serviceOffers(int serviceTypeOffset, int serviceOffersOffset) { KService::List list; // Jump to the offer list QDataStream *str = stream(); str->device()->seek(m_offerListOffset + serviceOffersOffset); qint32 aServiceTypeOffset, aServiceOffset, initialPreference, mimeTypeInheritanceLevel; while (true) { (*str) >> aServiceTypeOffset; if (aServiceTypeOffset) { (*str) >> aServiceOffset; (*str) >> initialPreference; (*str) >> mimeTypeInheritanceLevel; if (aServiceTypeOffset == serviceTypeOffset) { // Save stream position ! const qint64 savedPos = str->device()->pos(); // Create service KService *serv = createEntry(aServiceOffset); if (serv) { list.append(KService::Ptr(serv)); } // Restore position str->device()->seek(savedPos); } else { break; // too far } } else { break; // 0 => end of list } } return list; } +bool KServiceFactory::hasOffer(const KServiceType::Ptr &serviceType, const KService::Ptr &testedService) +{ + return hasOffer(serviceType->offset(), serviceType->serviceOffersOffset(), testedService->offset()); +} + bool KServiceFactory::hasOffer(int serviceTypeOffset, int serviceOffersOffset, int testedServiceOffset) { // Save stream position QDataStream *str = stream(); const qint64 savedPos = str->device()->pos(); // Jump to the offer list str->device()->seek(m_offerListOffset + serviceOffersOffset); bool found = false; qint32 aServiceTypeOffset, aServiceOffset, initialPreference, mimeTypeInheritanceLevel; while (!found) { (*str) >> aServiceTypeOffset; if (aServiceTypeOffset) { (*str) >> aServiceOffset; (*str) >> initialPreference; (*str) >> mimeTypeInheritanceLevel; if (aServiceTypeOffset == serviceTypeOffset) { if (aServiceOffset == testedServiceOffset) { found = true; } } else { break; // too far } } else { break; // 0 => end of list } } // Restore position str->device()->seek(savedPos); return found; } void KServiceFactory::virtual_hook(int id, void *data) { KSycocaFactory::virtual_hook(id, data); } diff --git a/src/services/kservicefactory_p.h b/src/services/kservicefactory_p.h index c763368..b3cf949 100644 --- a/src/services/kservicefactory_p.h +++ b/src/services/kservicefactory_p.h @@ -1,136 +1,139 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2006 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KSERVICEFACTORY_P_H #define KSERVICEFACTORY_P_H #include #include "kserviceoffer.h" #include "ksycocafactory_p.h" +#include "kservicetype.h" #include class KSycoca; class KSycocaDict; /** * @internal * A sycoca factory for services (e.g. applications) * It loads the services from parsing directories (e.g. prefix/share/applications/) * but can also create service from data streams or single config files * * Exported for unit tests */ class KSERVICE_EXPORT KServiceFactory : public KSycocaFactory { K_SYCOCAFACTORY(KST_KServiceFactory) public: /** * Create factory */ explicit KServiceFactory(KSycoca *sycoca); ~KServiceFactory() override; /** * Construct a KService from a config file. */ KSycocaEntry *createEntry(const QString &) const override { assert(0); return nullptr; } /** * Find a service (by translated name, e.g. "Terminal") * (Not virtual because not used inside kbuildsycoca4, only an external service for some KDE apps) */ KService::Ptr findServiceByName(const QString &_name); /** * Find a service (by desktop file name, e.g. "konsole") */ virtual KService::Ptr findServiceByDesktopName(const QString &_name); /** * Find a service ( by desktop path, e.g. "System/konsole.desktop") */ virtual KService::Ptr findServiceByDesktopPath(const QString &_name); /** * Find a service ( by menu id, e.g. "kde-konsole.desktop") */ virtual KService::Ptr findServiceByMenuId(const QString &_menuId); KService::Ptr findServiceByStorageId(const QString &_storageId); /** * @return the services supporting the given service type * The @p serviceOffersOffset allows to jump to the right entries directly. */ KServiceOfferList offers(int serviceTypeOffset, int serviceOffersOffset); /** * @return the services supporting the given service type; without information about initialPreference * The @p serviceOffersOffset allows to jump to the right entries directly. */ KService::List serviceOffers(int serviceTypeOffset, int serviceOffersOffset); + KService::List serviceOffers(const KServiceType::Ptr &serviceType); /** * Test if a specific service is associated with a specific servicetype * @param serviceTypeOffset the offset of the service type being tested * @param serviceOffersOffset allows to jump to the right entries for the service type directly. * @param testedServiceOffset the offset of the service being tested */ bool hasOffer(int serviceTypeOffset, int serviceOffersOffset, int testedServiceOffset); + bool hasOffer(const KServiceType::Ptr &serviceType, const KService::Ptr &testedService); /** * @return all services. Very memory consuming, avoid using. */ KService::List allServices(); /** * Returns the directories to watch for this factory. */ static QStringList resourceDirs(); /** * @return the unique service factory, creating it if necessary */ static KServiceFactory *self(); protected: KService *createEntry(int offset) const override; // All those variables are used by KBuildServiceFactory too int m_offerListOffset; KSycocaDict *m_nameDict; int m_nameDictOffset; KSycocaDict *m_relNameDict; int m_relNameDictOffset; KSycocaDict *m_menuIdDict; int m_menuIdDictOffset; protected: void virtual_hook(int id, void *data) override; private: class KServiceFactoryPrivate *d; }; #endif diff --git a/src/services/ktraderparsetree.cpp b/src/services/ktraderparsetree.cpp index 5290dcc..dd52e2e 100644 --- a/src/services/ktraderparsetree.cpp +++ b/src/services/ktraderparsetree.cpp @@ -1,808 +1,792 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ktraderparsetree_p.h" +#include "kapplicationtrader.h" namespace KTraderParse { QVariant ParseContext::property(const QString &_key) const { if (service) { return service->property(_key); } else if (info.isValid()) { return info.property(_key); } return QVariant(); } bool ParseTreeOR::eval(ParseContext *_context) const { ParseContext c1(_context); ParseContext c2(_context); // don't evaluate both expressions but return immediately // if the first one of them succeeds. Otherwise queries like // ((not exist Blah) or (Blah == 'Foo')) do not work, because // the evaluation of the second term ends up in a fatal error // (Simon) if (!m_pLeft->eval(&c1)) { return false; } if (c1.type != ParseContext::T_BOOL) { return false; } _context->b = c1.b; _context->type = ParseContext::T_BOOL; if (c1.b) { return true; } if (!m_pRight->eval(&c2)) { return false; } if (c2.type != ParseContext::T_BOOL) { return false; } _context->b = (c1.b || c2.b); _context->type = ParseContext::T_BOOL; return true; } bool ParseTreeAND::eval(ParseContext *_context) const { _context->type = ParseContext::T_BOOL; ParseContext c1(_context); ParseContext c2(_context); if (!m_pLeft->eval(&c1)) { return false; } if (c1.type != ParseContext::T_BOOL) { return false; } if (!c1.b) { _context->b = false; return true; } if (!m_pRight->eval(&c2)) { return false; } if (c2.type != ParseContext::T_BOOL) { return false; } _context->b = (c1.b && c2.b); return true; } bool ParseTreeCALC::eval(ParseContext *_context) const { ParseContext c1(_context); ParseContext c2(_context); if (!m_pLeft->eval(&c1)) { return false; } if (!m_pRight->eval(&c2)) { return false; } // Bool extension if (c1.type != ParseContext::T_NUM && c1.type != ParseContext::T_DOUBLE && c1.type != ParseContext::T_BOOL) { return false; } // Bool extension if (c2.type != ParseContext::T_NUM && c2.type != ParseContext::T_DOUBLE && c2.type != ParseContext::T_BOOL) { return false; } // Bool extension if (c1.type == ParseContext::T_BOOL && c2.type == ParseContext::T_BOOL) { return false; } /** * Make types compatible */ if (c1.type == ParseContext::T_NUM && c2.type == ParseContext::T_DOUBLE) { c1.type = ParseContext::T_DOUBLE; c1.f = c1.i; } else if (c1.type == ParseContext::T_DOUBLE && c2.type == ParseContext::T_NUM) { c2.type = ParseContext::T_DOUBLE; c2.f = c2.i; } // Bool extension else if (c1.type == ParseContext::T_BOOL && c2.type == ParseContext::T_NUM) { c1.type = ParseContext::T_NUM; if (c1.b) { c1.i = 1; } else { c1.i = -1; } } // Bool extension else if (c1.type == ParseContext::T_BOOL && c2.type == ParseContext::T_DOUBLE) { c1.type = ParseContext::T_DOUBLE; if (c1.b) { c1.f = 1.0; } else { c1.f = -1.0; } } // Bool extension else if (c1.type == ParseContext::T_NUM && c2.type == ParseContext::T_BOOL) { c2.type = ParseContext::T_NUM; if (c2.b) { c2.i = 1; } else { c2.i = -1; } } // Bool extension else if (c1.type == ParseContext::T_DOUBLE && c2.type == ParseContext::T_BOOL) { c2.type = ParseContext::T_DOUBLE; if (c2.b) { c2.f = 1.0; } else { c2.f = -1.0; } } _context->type = c1.type; /** * Calculate */ switch (m_cmd) { case 1: /* Add */ if (c1.type == ParseContext::T_DOUBLE) { _context->f = (c1.f + c2.f); return true; } if (c1.type == ParseContext::T_NUM) { _context->i = (c1.i + c2.i); return true; } break; case 2: /* Sub */ if (c1.type == ParseContext::T_DOUBLE) { _context->f = (c1.f - c2.f); return true; } if (c1.type == ParseContext::T_NUM) { _context->i = (c1.i - c2.i); return true; } break; case 3: /* Mul */ if (c1.type == ParseContext::T_DOUBLE) { //cout << "Double Mult" << endl; _context->f = (c1.f * c2.f); return true; } if (c1.type == ParseContext::T_NUM) { _context->i = (c1.i * c2.i); return true; } break; case 4: /* Div */ if (c1.type == ParseContext::T_DOUBLE) { _context->f = (c1.f / c2.f); return true; } if (c1.type == ParseContext::T_NUM) { _context->i = (c1.i / c2.i); return true; } break; } return false; } bool ParseTreeCMP::eval(ParseContext *_context) const { //cout << "CMP 1 cmd=" << m_cmd << endl; ParseContext c1(_context); ParseContext c2(_context); if (!m_pLeft->eval(&c1)) { return false; } if (!m_pRight->eval(&c2)) { return false; } /** * Make types compatible */ if (c1.type == ParseContext::T_NUM && c2.type == ParseContext::T_DOUBLE) { c1.type = ParseContext::T_DOUBLE; c1.f = c1.i; } else if (c1.type == ParseContext::T_DOUBLE && c2.type == ParseContext::T_NUM) { c2.type = ParseContext::T_DOUBLE; c2.f = c2.i; } /** * Compare */ _context->type = ParseContext::T_BOOL; switch (m_cmd) { case 1: /* EQ */ case 7: /* EQI */ if (c1.type != c2.type) { _context->b = false; return true; } if (c1.type == ParseContext::T_STRING) { if (m_cmd == 7) { _context->b = QString::compare(c1.str, c2.str, Qt::CaseInsensitive) == 0; } else { _context->b = (c1.str == c2.str); } return true; } if (c1.type == ParseContext::T_BOOL) { _context->b = (c1.b == c2.b); return true; } if (c1.type == ParseContext::T_DOUBLE) { _context->b = qFuzzyCompare(c1.f, c2.f); return true; } if (c1.type == ParseContext::T_NUM) { _context->b = (c1.i == c2.i); return true; } break; case 2: /* NEQ */ case 8: /* NEQI */ if (c1.type != c2.type) { _context->b = true; return true; } if (c1.type == ParseContext::T_STRING) { if (m_cmd == 8) { _context->b = QString::compare(c1.str, c2.str, Qt::CaseInsensitive) != 0; } else { _context->b = (c1.str != c2.str); } return true; } if (c1.type == ParseContext::T_BOOL) { _context->b = (c1.b != c2.b); return true; } if (c1.type == ParseContext::T_DOUBLE) { _context->b = !qFuzzyCompare(c1.f, c2.f); return true; } if (c1.type == ParseContext::T_NUM) { _context->b = (c1.i != c2.i); return true; } break; case 3: /* GEQ */ if (c1.type != c2.type) { _context->b = false; return true; } if (c1.type == ParseContext::T_DOUBLE) { _context->b = (c1.f >= c2.f); return true; } if (c1.type == ParseContext::T_NUM) { _context->b = (c1.i >= c2.i); return true; } _context->b = false; return true; case 4: /* LEQ */ if (c1.type != c2.type) { _context->b = false; return true; } if (c1.type == ParseContext::T_DOUBLE) { _context->b = (c1.f <= c2.f); return true; } if (c1.type == ParseContext::T_NUM) { _context->b = (c1.i <= c2.i); return true; } _context->b = false; return true; case 5: /* < */ if (c1.type != c2.type) { _context->b = false; return true; } if (c1.type == ParseContext::T_DOUBLE) { _context->b = (c1.f < c2.f); return true; } if (c1.type == ParseContext::T_NUM) { _context->b = (c1.i < c2.i); return true; } _context->b = false; return true; case 6: /* > */ if (c1.type != c2.type) { _context->b = false; return true; } if (c1.type == ParseContext::T_DOUBLE) { _context->b = (c1.f > c2.f); return true; } if (c1.type == ParseContext::T_NUM) { _context->b = (c1.i > c2.i); return true; } _context->b = false; return true; } return false; } bool ParseTreeNOT::eval(ParseContext *_context) const { ParseContext c1(_context); if (!m_pLeft->eval(&c1)) { return false; } if (c1.type != ParseContext::T_BOOL) { return false; } _context->b = !c1.b; _context->type = ParseContext::T_BOOL; return true; } bool ParseTreeEXIST::eval(ParseContext *_context) const { _context->type = ParseContext::T_BOOL; QVariant prop = _context->property(m_id); _context->b = prop.isValid(); return true; } bool ParseTreeMATCH::eval(ParseContext *_context) const { _context->type = ParseContext::T_BOOL; ParseContext c1(_context); ParseContext c2(_context); if (!m_pLeft->eval(&c1)) { return false; } if (!m_pRight->eval(&c2)) { return false; } if (c1.type != ParseContext::T_STRING || c2.type != ParseContext::T_STRING) { return false; } _context->b = c2.str.contains(c1.str, m_cs); return true; } bool ParseTreeSubsequenceMATCH::eval(ParseContext *_context) const { _context->type = ParseContext::T_BOOL; ParseContext c1(_context); ParseContext c2(_context); if (!m_pLeft->eval(&c1)) { return false; } if (!m_pRight->eval(&c2)) { return false; } if (c1.type != ParseContext::T_STRING || c2.type != ParseContext::T_STRING) { return false; } - _context->b = ParseTreeSubsequenceMATCH::isSubseq(c1.str, c2.str, m_cs); + _context->b = KApplicationTrader::isSubsequence(c1.str, c2.str, m_cs); return true; } -bool ParseTreeSubsequenceMATCH:: -isSubseq(const QString& pattern, const QString& text, Qt::CaseSensitivity cs) -{ - if (pattern.isEmpty()) { - return false; - } - bool chk_case = cs == Qt::CaseSensitive; - - QString::const_iterator i = text.constBegin(), j = pattern.constBegin(); - for (; i != text.constEnd() && j != pattern.constEnd(); ++i) { - if ((chk_case && *i == *j) || (!chk_case && i->toLower() == j->toLower())) { - ++j; - } - } - return j == pattern.constEnd(); -} - bool ParseTreeIN::eval(ParseContext *_context) const { _context->type = ParseContext::T_BOOL; ParseContext c1(_context); ParseContext c2(_context); if (!m_pLeft->eval(&c1)) { return false; } if (!m_pRight->eval(&c2)) { return false; } if ((c1.type == ParseContext::T_NUM) && (c2.type == ParseContext::T_SEQ) && ((*(c2.seq.begin())).type() == QVariant::Int)) { QList::ConstIterator it = c2.seq.constBegin(); QList::ConstIterator end = c2.seq.constEnd(); _context->b = false; for (; it != end; ++it) if ((*it).type() == QVariant::Int && (*it).toInt() == c1.i) { _context->b = true; break; } return true; } if (c1.type == ParseContext::T_DOUBLE && c2.type == ParseContext::T_SEQ && (*(c2.seq.begin())).type() == QVariant::Double) { QList::ConstIterator it = c2.seq.constBegin(); QList::ConstIterator end = c2.seq.constEnd(); _context->b = false; for (; it != end; ++it) if ((*it).type() == QVariant::Double && qFuzzyCompare((*it).toDouble(), c1.i)) { _context->b = true; break; } return true; } if (c1.type == ParseContext::T_STRING && c2.type == ParseContext::T_STR_SEQ) { if (false && m_substring) { _context->b = false; for (const QString &string : qAsConst(c2.strSeq)) { if (string.contains(c1.str, m_cs)) { _context->b = true; break; } } } else { _context->b = c2.strSeq.contains(c1.str, m_cs); } return true; } return false; } bool ParseTreeID::eval(ParseContext *_context) const { QVariant prop = _context->property(m_str); if (!prop.isValid()) { return false; } if (prop.type() == QVariant::String) { _context->str = prop.toString(); _context->type = ParseContext::T_STRING; return true; } if (prop.type() == QVariant::Int) { _context->i = prop.toInt(); _context->type = ParseContext::T_NUM; return true; } if (prop.type() == QVariant::Bool) { _context->b = prop.toBool(); _context->type = ParseContext::T_BOOL; return true; } if (prop.type() == QVariant::Double) { _context->f = prop.toDouble(); _context->type = ParseContext::T_DOUBLE; return true; } if (prop.type() == QVariant::List) { _context->seq = prop.toList(); _context->type = ParseContext::T_SEQ; return true; } if (prop.type() == QVariant::StringList) { _context->strSeq = prop.toStringList(); _context->type = ParseContext::T_STR_SEQ; return true; } // Value has unknown type return false; } bool ParseTreeMIN2::eval(ParseContext *_context) const { _context->type = ParseContext::T_DOUBLE; QVariant prop = _context->property(m_strId); if (!prop.isValid()) { return false; } if (!_context->initMaxima(m_strId)) { return false; } QMap::Iterator it = _context->maxima.find(m_strId); if (it == _context->maxima.end()) { return false; } if (prop.type() == QVariant::Int && it.value().type == PreferencesMaxima::PM_INT) { _context->f = double(prop.toInt() - it.value().iMin) / double(it.value().iMax - it.value().iMin) * (-2.0) + 1.0; return true; } else if (prop.type() == QVariant::Double && it.value().type == PreferencesMaxima::PM_DOUBLE) { _context->f = (prop.toDouble() - it.value().fMin) / (it.value().fMax - it.value().fMin) * (-2.0) + 1.0; return true; } return false; } bool ParseTreeMAX2::eval(ParseContext *_context) const { _context->type = ParseContext::T_DOUBLE; QVariant prop = _context->property(m_strId); if (!prop.isValid()) { return false; } // Create extrema if (!_context->initMaxima(m_strId)) { return false; } // Find extrema QMap::Iterator it = _context->maxima.find(m_strId); if (it == _context->maxima.end()) { return false; } if (prop.type() == QVariant::Int && it.value().type == PreferencesMaxima::PM_INT) { _context->f = double(prop.toInt() - it.value().iMin) / double(it.value().iMax - it.value().iMin) * 2.0 - 1.0; return true; } else if (prop.type() == QVariant::Double && it.value().type == PreferencesMaxima::PM_DOUBLE) { _context->f = (prop.toDouble() - it.value().fMin) / (it.value().fMax - it.value().fMin) * 2.0 - 1.0; return true; } return false; } int matchConstraint(const ParseTreeBase *_tree, const KService::Ptr &_service, const KService::List &_list) { // Empty tree matches always if (!_tree) { return 1; } QMap maxima; ParseContext c(_service, _list, maxima); // Error during evaluation ? if (!_tree->eval(&c)) { return -1; } // Did we get a bool ? if (c.type != ParseContext::T_BOOL) { return -1; } return (c.b ? 1 : 0); } int matchConstraintPlugin(const ParseTreeBase *_tree, const KPluginInfo &_info, const KPluginInfo::List &_list) { // Empty tree matches always if (!_tree) { return 1; } QMap maxima; ParseContext c(_info, _list, maxima); // Error during evaluation ? if (!_tree->eval(&c)) { return -1; } // Did we get a bool ? if (c.type != ParseContext::T_BOOL) { return -1; } return (c.b ? 1 : 0); } bool ParseContext::initMaxima(const QString &_prop) { // Is the property known ? QVariant prop = property(_prop); if (!prop.isValid()) { return false; } // Numeric ? if (prop.type() != QVariant::Int && prop.type() != QVariant::Double) { return false; } // Did we cache the result ? QMap::Iterator it = maxima.find(_prop); if (it != maxima.end()) return (it.value().type == PreferencesMaxima::PM_DOUBLE || it.value().type == PreferencesMaxima::PM_INT); // Double or Int ? PreferencesMaxima extrema; if (prop.type() == QVariant::Int) { extrema.type = PreferencesMaxima::PM_INVALID_INT; } else { extrema.type = PreferencesMaxima::PM_INVALID_DOUBLE; } // Iterate over all offers QVariantList offerValues; if (service) { KService::List::ConstIterator oit = offers.cbegin(); for (; oit != offers.cend(); ++oit) { offerValues << (*oit)->property(_prop); } } else if (info.isValid()) { KPluginInfo::List::ConstIterator oit = pluginOffers.cbegin(); for (; oit != pluginOffers.cend(); ++oit) { offerValues << (*oit).property(_prop); } } for (const QVariant &p : qAsConst(offerValues)) { if (p.isValid()) { // Determine new maximum/minimum if (extrema.type == PreferencesMaxima::PM_INVALID_INT) { extrema.type = PreferencesMaxima::PM_INT; extrema.iMin = p.toInt(); extrema.iMax = p.toInt(); } // Correct existing extrema else if (extrema.type == PreferencesMaxima::PM_INT) { if (p.toInt() < extrema.iMin) { extrema.iMin = p.toInt(); } if (p.toInt() > extrema.iMax) { extrema.iMax = p.toInt(); } } // Determine new maximum/minimum else if (extrema.type == PreferencesMaxima::PM_INVALID_DOUBLE) { extrema.type = PreferencesMaxima::PM_DOUBLE; extrema.fMin = p.toDouble(); extrema.fMax = p.toDouble(); } // Correct existing extrema else if (extrema.type == PreferencesMaxima::PM_DOUBLE) { if (p.toDouble() < it.value().fMin) { extrema.fMin = p.toDouble(); } if (p.toDouble() > it.value().fMax) { extrema.fMax = p.toDouble(); } } } } // Cache the result maxima.insert(_prop, extrema); // Did we succeed ? return (extrema.type == PreferencesMaxima::PM_DOUBLE || extrema.type == PreferencesMaxima::PM_INT); } ParseTreeBase::~ParseTreeBase() { } bool ParseTreeSTRING::eval(ParseContext *_context) const { _context->type = ParseContext::T_STRING; _context->str = m_str; return true; } bool ParseTreeNUM::eval(ParseContext *_context) const { _context->type = ParseContext::T_NUM; _context->i = m_int; return true; } bool ParseTreeBRACKETS::eval(ParseContext *_context) const { return m_pLeft->eval(_context); } bool ParseTreeDOUBLE::eval(ParseContext *_context) const { _context->type = ParseContext::T_DOUBLE; _context->f = m_double; return true; } bool ParseTreeBOOL::eval(ParseContext *_context) const { _context->type = ParseContext::T_BOOL; _context->b = m_bool; return true; } } diff --git a/src/services/ktraderparsetree_p.h b/src/services/ktraderparsetree_p.h index 0ebabd8..686a64e 100644 --- a/src/services/ktraderparsetree_p.h +++ b/src/services/ktraderparsetree_p.h @@ -1,452 +1,450 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __ktrader_parse_tree_h__ #define __ktrader_parse_tree_h__ #include #include #include #include #include namespace KTraderParse { class ParseTreeBase; /** * @internal * @return 0 => Does not match * 1 => Does match * <0 => Error */ int matchConstraint(const ParseTreeBase *_tree, const KService::Ptr &, const KService::List &); int matchConstraintPlugin(const ParseTreeBase *_tree, const KPluginInfo &_info, const KPluginInfo::List &_list); /** * @internal */ struct KSERVICE_EXPORT PreferencesMaxima { PreferencesMaxima() : iMax(0), iMin(0), fMax(0), fMin(0) { } enum Type { PM_ERROR, PM_INVALID_INT, PM_INVALID_DOUBLE, PM_DOUBLE, PM_INT }; Type type; int iMax; int iMin; double fMax; double fMin; }; /** * @internal */ class ParseContext { public: /** * This is NOT a copy constructor. */ explicit ParseContext(const ParseContext *_ctx) : service(_ctx->service), info(_ctx->info), maxima(_ctx->maxima), offers(_ctx->offers), pluginOffers(_ctx->pluginOffers) {} ParseContext(const KService::Ptr &_service, const KService::List &_offers, QMap &_m) : service(_service), info(KPluginInfo()), maxima(_m), offers(_offers), pluginOffers(KPluginInfo::List()) {} ParseContext(const KPluginInfo &_info, const KPluginInfo::List &_offers, QMap &_m) : service(nullptr), info(_info), maxima(_m), offers(KService::List()), pluginOffers(_offers) {} bool initMaxima(const QString &_prop); QVariant property(const QString &_key) const; enum Type { T_STRING = 1, T_DOUBLE = 2, T_NUM = 3, T_BOOL = 4, T_STR_SEQ = 5, T_SEQ = 6 }; QString str; int i; double f; bool b; QList seq; QStringList strSeq; Type type; KService::Ptr service; KPluginInfo info; QMap &maxima; KService::List offers; KPluginInfo::List pluginOffers; }; /** * @internal */ class ParseTreeBase : public QSharedData { public: typedef QExplicitlySharedDataPointer Ptr; ParseTreeBase() { } virtual ~ParseTreeBase(); virtual bool eval(ParseContext *_context) const = 0; }; ParseTreeBase::Ptr parseConstraints(const QString &_constr); /** * @internal */ class ParseTreeOR : public ParseTreeBase { public: ParseTreeOR(ParseTreeBase *_ptr1, ParseTreeBase *_ptr2) { m_pLeft = _ptr1; m_pRight = _ptr2; } bool eval(ParseContext *_context) const override; protected: ParseTreeBase::Ptr m_pLeft; ParseTreeBase::Ptr m_pRight; }; /** * @internal */ class ParseTreeAND : public ParseTreeBase { public: ParseTreeAND(ParseTreeBase *_ptr1, ParseTreeBase *_ptr2) { m_pLeft = _ptr1; m_pRight = _ptr2; } bool eval(ParseContext *_context) const override; protected: ParseTreeBase::Ptr m_pLeft; ParseTreeBase::Ptr m_pRight; }; /** * @internal */ class ParseTreeCMP : public ParseTreeBase { public: ParseTreeCMP(ParseTreeBase *_ptr1, ParseTreeBase *_ptr2, int _i) { m_pLeft = _ptr1; m_pRight = _ptr2; m_cmd = _i; } bool eval(ParseContext *_context) const override; protected: ParseTreeBase::Ptr m_pLeft; ParseTreeBase::Ptr m_pRight; int m_cmd; }; /** * @internal */ class ParseTreeIN : public ParseTreeBase { public: ParseTreeIN(ParseTreeBase *ptr1, ParseTreeBase *ptr2, Qt::CaseSensitivity cs, bool substring = false) : m_pLeft(ptr1), m_pRight(ptr2), m_cs(cs), m_substring(substring) { } bool eval(ParseContext *_context) const override; protected: ParseTreeBase::Ptr m_pLeft; ParseTreeBase::Ptr m_pRight; Qt::CaseSensitivity m_cs; bool m_substring; }; /** * @internal */ class ParseTreeMATCH : public ParseTreeBase { public: ParseTreeMATCH(ParseTreeBase *_ptr1, ParseTreeBase *_ptr2, Qt::CaseSensitivity cs) { m_pLeft = _ptr1; m_pRight = _ptr2; m_cs = cs; } bool eval(ParseContext *_context) const override; protected: ParseTreeBase::Ptr m_pLeft; ParseTreeBase::Ptr m_pRight; Qt::CaseSensitivity m_cs; }; /** * @internal * * A sub-sequence match is like a sub-string match except the characters * do not have to be contiguous. For example 'ct' is a sub-sequence of 'cat' * but not a sub-string. 'at' is both a sub-string and sub-sequence of 'cat'. * All sub-strings are sub-sequences. * * This is useful if you want to support a fuzzier search, say for instance * you are searching for `LibreOffice 6.0 Writer`, after typing `libre` you * see a list of all the LibreOffice apps, to narrow down that list you only * need to add `write` (so the search term is `librewrite`) instead of typing * out the entire app name until a distinguishing letter is reached. * It's also useful to allow the user to forget to type some characters. */ class ParseTreeSubsequenceMATCH : public ParseTreeBase { public: ParseTreeSubsequenceMATCH(ParseTreeBase *_ptr1, ParseTreeBase *_ptr2, Qt::CaseSensitivity cs) { m_pLeft = _ptr1; m_pRight = _ptr2; m_cs = cs; } bool eval(ParseContext *_context) const override; - static bool isSubseq(const QString& pattern, const QString& text, Qt::CaseSensitivity cs); - protected: ParseTreeBase::Ptr m_pLeft; ParseTreeBase::Ptr m_pRight; Qt::CaseSensitivity m_cs; }; /** * @internal */ class ParseTreeCALC : public ParseTreeBase { public: ParseTreeCALC(ParseTreeBase *_ptr1, ParseTreeBase *_ptr2, int _i) { m_pLeft = _ptr1; m_pRight = _ptr2; m_cmd = _i; } bool eval(ParseContext *_context) const override; protected: ParseTreeBase::Ptr m_pLeft; ParseTreeBase::Ptr m_pRight; int m_cmd; }; /** * @internal */ class ParseTreeBRACKETS : public ParseTreeBase { public: explicit ParseTreeBRACKETS(ParseTreeBase *_ptr) { m_pLeft = _ptr; } bool eval(ParseContext *_context) const override; protected: ParseTreeBase::Ptr m_pLeft; }; /** * @internal */ class ParseTreeNOT : public ParseTreeBase { public: explicit ParseTreeNOT(ParseTreeBase *_ptr) { m_pLeft = _ptr; } bool eval(ParseContext *_context) const override; protected: ParseTreeBase::Ptr m_pLeft; }; /** * @internal */ class ParseTreeEXIST : public ParseTreeBase { public: explicit ParseTreeEXIST(const char *_id) { m_id = QString::fromUtf8(_id); } bool eval(ParseContext *_context) const override; protected: QString m_id; }; /** * @internal */ class ParseTreeID : public ParseTreeBase { public: explicit ParseTreeID(const char *arg) { m_str = QString::fromUtf8(arg); } bool eval(ParseContext *_context) const override; protected: QString m_str; }; /** * @internal */ class ParseTreeSTRING : public ParseTreeBase { public: explicit ParseTreeSTRING(const char *arg) { m_str = QString::fromUtf8(arg); } bool eval(ParseContext *_context) const override; protected: QString m_str; }; /** * @internal */ class ParseTreeNUM : public ParseTreeBase { public: explicit ParseTreeNUM(int arg) { m_int = arg; } bool eval(ParseContext *_context) const override; protected: int m_int; }; /** * @internal */ class ParseTreeDOUBLE : public ParseTreeBase { public: explicit ParseTreeDOUBLE(double arg) { m_double = arg; } bool eval(ParseContext *_context) const override; protected: double m_double; }; /** * @internal */ class ParseTreeBOOL : public ParseTreeBase { public: explicit ParseTreeBOOL(bool arg) { m_bool = arg; } bool eval(ParseContext *_context) const override; protected: bool m_bool; }; /** * @internal */ class ParseTreeMAX2 : public ParseTreeBase { public: explicit ParseTreeMAX2(const char *_id) { m_strId = QString::fromUtf8(_id); } bool eval(ParseContext *_context) const override; protected: QString m_strId; }; /** * @internal */ class ParseTreeMIN2 : public ParseTreeBase { public: explicit ParseTreeMIN2(const char *_id) { m_strId = QString::fromUtf8(_id); } bool eval(ParseContext *_context) const override; protected: QString m_strId; }; } #endif diff --git a/src/sycoca/ksycocaentry.h b/src/sycoca/ksycocaentry.h index e8e675c..7f2251c 100644 --- a/src/sycoca/ksycocaentry.h +++ b/src/sycoca/ksycocaentry.h @@ -1,145 +1,146 @@ /* This file is part of the KDE libraries * Copyright (C) 1999 Waldo Bastian * * 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 KSYCOCAENTRY_H #define KSYCOCAENTRY_H #include #include #include #include #include #include class KSycocaEntryPrivate; /** * Base class for all Sycoca entries. * * You can't create an instance of KSycocaEntry, but it provides * the common functionality for servicetypes and services. * * @internal * @see http://techbase.kde.org/Development/Architecture/KDE3/System_Configuration_Cache */ class KSERVICE_EXPORT KSycocaEntry : public QSharedData { public: /* * constructs a invalid KSycocaEntry object */ KSycocaEntry(); virtual ~KSycocaEntry(); /** * Returns true if this sycoca entry is of the given type. */ bool isType(KSycocaType t) const; /** * internal */ KSycocaType sycocaType() const; typedef QExplicitlySharedDataPointer Ptr; typedef QList List; /** * @return the name of this entry */ QString name() const; /** * @return the path of this entry * The path can be absolute or relative. * The corresponding factory should know relative to what. */ QString entryPath() const; /** * @return the unique ID for this entry * In practice, this is storageId() for KService and name() for everything else. * \since 4.2.1 */ QString storageId() const; /** * @return true if valid */ bool isValid() const; /** * @return true if deleted */ bool isDeleted() const; /** * Returns the requested property. Some often used properties * have convenience access functions like exec(), * serviceTypes etc. * * @param name the name of the property * @return the property, or invalid if not found */ QVariant property(const QString &name) const; /** * Returns the list of all properties that this service can have. * That means, that some of these properties may be empty. * @return the list of supported properties */ QStringList propertyNames() const; /** * Sets whether or not this service is deleted */ void setDeleted(bool deleted); /** * @returns true, if this is a separator */ bool isSeparator() const; protected: KSycocaEntry(KSycocaEntryPrivate &d); KSycocaEntryPrivate *d_ptr; private: // All these need access to offset() friend class KSycocaFactory; friend class KBuildServiceFactory; + friend class KServiceFactory; friend class KMimeTypeTrader; friend class KServiceTypeTrader; friend class KService; friend class KSycocaDict; friend class KSycocaDictTest; /** * @internal * @return the position of the entry in the sycoca file */ int offset() const; Q_DISABLE_COPY(KSycocaEntry) Q_DECLARE_PRIVATE(KSycocaEntry) }; #endif