diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -23,14 +23,15 @@ kservicetest pluginlocatortest 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 ) diff --git a/autotests/kapplicationtradertest.cpp b/autotests/kapplicationtradertest.cpp new file mode 100644 --- /dev/null +++ b/autotests/kapplicationtradertest.cpp @@ -0,0 +1,296 @@ +/* + Copyright (C) 2006-2019 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 testSubseqConstraints(); + void testQueryByMimeType(); + void testThreads(); + void testTraderQueryMustRebuildSycoca(); + void cleanupTestCase(); + +private: + QString createFakeApplication(const QString &filename, const QString &name); + void checkResult(const KService::List &offers, ExpectedResult expectedResult); + + QString m_fakeApplication; + 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(); + + 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"; + } + + // 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")); + + 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; + } + } + if (!found) { + qWarning() << entryPath << "not found. Here's what we have:"; + for (const auto &service : offers) { + qWarning() << " " << service->entryPath(); + } + } + 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()); + QVERIFY(offerListHasService(offers, m_fakeApplication)); + break; + case ExpectedResult::NotFakeApplication: + QVERIFY(!offerListHasService(offers, m_fakeApplication)); + break; + } +} + +void KApplicationTraderTest::testTraderConstraints_data() +{ + QTest::addColumn("constraint"); + QTest::addColumn("expectedResult"); + + QTest::newRow("no_constraint") << QString() << ExpectedResult::FakeApplicationAndOthers; + QTest::newRow("name_comparison") << QStringLiteral("Name == 'FakeApplication'") << ExpectedResult::FakeApplicationOnly; + QTest::newRow("no_such_name") << QStringLiteral("Name == 'IDontExist'") << ExpectedResult::NoResults; + QTest::newRow("no_such_name_by_case") << QStringLiteral("Name == 'fakeapplication'") << ExpectedResult::NoResults; + QTest::newRow("match_case_insensitive") << "Name =~ 'fAkEaPPlicaTion'" << ExpectedResult::FakeApplicationOnly; + QTest::newRow("is_contained_in") << "'FakeApp' ~ Name" << ExpectedResult::FakeApplicationOnly; + QTest::newRow("is_contained_in_fail") << "'FakeApplicationNot' ~ Name" << ExpectedResult::NoResults; + QTest::newRow("is_contained_in_case_insensitive") << "'faKeApP' ~~ Name" << ExpectedResult::FakeApplicationOnly; + QTest::newRow("is_contained_in_case_in_fail") << "'faKeApPp' ~ Name" << ExpectedResult::NoResults; + QTest::newRow("subseq") << "'FkApli' subseq Name" << ExpectedResult::FakeApplicationOnly; + QTest::newRow("subseq_fail") << "'fkApli' subseq Name" << ExpectedResult::NoResults; + QTest::newRow("subseq_case_insensitive") << "'fkApLI' ~subseq Name" << ExpectedResult::FakeApplicationOnly; + QTest::newRow("subseq_case_insensitive_fail") << "'fk_Apli' ~subseq Name" << ExpectedResult::NoResults; + // Test float parsing, must use dot as decimal separator independent of locale. + QTest::newRow("float_parsing") << "([X-KDE-Version] > 5.559) and ([X-KDE-Version] < 5.561)" << ExpectedResult::FakeApplicationAndOthers; + // A test with an invalid query, to test for memleaks + QTest::newRow("invalid_query") << "A == B OR C == D AND OR Foo == 'Parse Error'" << ExpectedResult::NoResults; +} + +void KApplicationTraderTest::testTraderConstraints() +{ + QFETCH(QString, constraint); + QFETCH(ExpectedResult, expectedResult); + + const KService::List offers = KApplicationTrader::self()->query(constraint); + checkResult(offers, expectedResult); +} + +void KApplicationTraderTest::testSubseqConstraints() +{ + auto test = [](const char* pattern, const char* text, bool sensitive) { + return KTraderParse::ParseTreeSubsequenceMATCH::isSubseq( + 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 KApplicationTraderTest::testQueryByMimeType() +{ + KService::List offers; + + // Without constraint + + offers = KApplicationTrader::self()->queryByMimeType(QStringLiteral("text/plain")); + checkResult(offers, ExpectedResult::FakeApplicationAndOthers); + + offers = KApplicationTrader::self()->queryByMimeType(QStringLiteral("image/png")); + checkResult(offers, ExpectedResult::NotFakeApplication); + + QTest::ignoreMessage(QtWarningMsg, "KApplicationTrader: mimeType \"no/such/mimetype\" not found"); + offers = KApplicationTrader::self()->queryByMimeType(QStringLiteral("no/such/mimetype")); + checkResult(offers, ExpectedResult::NoResults); + + // With constraint + + offers = KApplicationTrader::self()->queryByMimeType(QStringLiteral("text/plain"), QStringLiteral("Name == 'FakeApplication'")); + checkResult(offers, ExpectedResult::FakeApplicationAndOthers); + + offers = KApplicationTrader::self()->queryByMimeType(QStringLiteral("text/plain"), QStringLiteral("Name == 'IDontExist'")); + checkResult(offers, ExpectedResult::NoResults); +} + +QString KApplicationTraderTest::createFakeApplication(const QString &filename, const QString &name) +{ + 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;"); + 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::testSubseqConstraints)); + sync.addFuture(QtConcurrent::run(this, &KApplicationTraderTest::testSubseqConstraints)); + sync.addFuture(QtConcurrent::run(this, &KApplicationTraderTest::testQueryByMimeType)); + sync.waitForFinished(); +} + +void KApplicationTraderTest::testTraderQueryMustRebuildSycoca() +{ + QCOMPARE(KApplicationTrader::self()->query(QStringLiteral("Name == 'MustRebuild'")).count(), 0); + createFakeApplication(QStringLiteral("fakeservice_querymustrebuild.desktop"), QStringLiteral("MustRebuild")); + KService::List offers = KApplicationTrader::self()->query(QStringLiteral("Name == 'MustRebuild'")); + QCOMPARE(offers.count(), 1); +} + +#include "kapplicationtradertest.moc" diff --git a/autotests/kservicetest.h b/autotests/kservicetest.h --- a/autotests/kservicetest.h +++ b/autotests/kservicetest.h @@ -45,7 +45,6 @@ void testAllServices(); void testServiceTypeTraderForReadOnlyPart(); void testTraderConstraints(); - void testSubseqConstraints(); void testHasServiceType1(); void testHasServiceType2(); void testWriteServiceTypeProfile(); diff --git a/autotests/kservicetest.cpp b/autotests/kservicetest.cpp --- a/autotests/kservicetest.cpp +++ b/autotests/kservicetest.cpp @@ -30,7 +30,6 @@ #include #include #include <../src/services/kserviceutil_p.h> -#include <../src/services/ktraderparsetree_p.h> #include #include @@ -598,34 +597,6 @@ QVERIFY(offers.isEmpty()); } -void KServiceTest::testSubseqConstraints() -{ - auto test = [](const char* pattern, const char* text, bool sensitive) { - return KTraderParse::ParseTreeSubsequenceMATCH::isSubseq( - 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"); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,6 +7,7 @@ set(kservice_SRCS kdeinit/ktoolinvocation.cpp services/kautostart.cpp + services/kapplicationtrader.cpp services/kmimetypefactory.cpp services/kmimetypetrader.cpp services/kservice.cpp @@ -141,6 +142,7 @@ ) ecm_generate_headers(KService_HEADERS HEADER_NAMES + KApplicationTrader KAutostart KMimeTypeTrader KService diff --git a/src/services/kapplicationtrader.h b/src/services/kapplicationtrader.h new file mode 100644 --- /dev/null +++ b/src/services/kapplicationtrader.h @@ -0,0 +1,130 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Torben Weis + Copyright (C) 2006-2019 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 +class KApplicationTraderPrivate; + +/** + * @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::self()->queryByMimeType("image/png"); + * \endcode + * + * If you want to get the preferred application for image/png you would use: + * @code + * KService::Ptr service = KApplicationTrader::self()->preferredService("image/png"); + * @endcode + * + * @see KService + * @since 5.66 + */ +class KSERVICE_EXPORT KApplicationTrader +{ +public: + + /** + * Destructor + */ + ~KApplicationTrader(); + + + /** + * This method returns a list of services (applications) which are associated with a given mimetype. + * + * The constraint parameter is used to limit the possible choices + * returned based on the constraints you give it. + * + * The @p constraint language is rather full. The most common + * keywords are AND, OR, NOT, IN, and EXIST, all used in an + * almost spoken-word form. An example is: + * \code + * 'Email' in Categories and 'km' ~ Name + * \endcode + * + * The keys used in the query (Categories, Name) are all + * fields found in the .desktop files. + * + * @param constraint A constraint to limit the choices returned. Do not pass an empty string, + * this would return the complete list of all installed applications (slow). + * + * @return A list of services that satisfy the query + * @see http://techbase.kde.org/Development/Tutorials/Services/Traders#The_KTrader_Query_Language + */ + KService::List query(const QString &constraint) const; + + /** + * This method returns a list of services (applications) which are associated with a given mimetype. + * + * See the documentation for query() about the constraint string. + * + * @param mimeType A mime type like 'text/plain' or 'text/html'. + * @param constraint A constraint to limit the choices returned, QString() to + * get all applications that can handle the given @p mimetype + * + * @return A list of services that satisfy the query, sorted by preference + * (preferred service first) + * @see http://techbase.kde.org/Development/Tutorials/Services/Traders#The_KTrader_Query_Language + */ + KService::List queryByMimeType(const QString &mimeType, + const QString &constraint = QString()) const; + + /** + * Returns the preferred service for @p mimeType + * + * This a convenience methode 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 + */ + KService::Ptr preferredService(const QString &mimeType); + + /** + * This is a static pointer to the KApplicationTrader singleton. + * + * You will need to use this to access the KApplicationTrader functionality since the + * constructor is protected. + * + * @return Static KApplicationTrader instance + */ + static KApplicationTrader *self(); + +private: + /** + * @internal + */ + KApplicationTrader(); + +private: + KApplicationTraderPrivate * d; + + // class-static so that it can access KSycocaEntry::offset() + static void filterMimeTypeOffers(KService::List &list); +}; + +#endif diff --git a/src/services/kapplicationtrader.cpp b/src/services/kapplicationtrader.cpp new file mode 100644 --- /dev/null +++ b/src/services/kapplicationtrader.cpp @@ -0,0 +1,140 @@ +/* This file is part of the KDE libraries + Copyright (C) 2000 Torben Weis + Copyright (C) 2006-2019 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 "kservicetypetrader.h" +#include "kservicetypefactory_p.h" +#include "kservicefactory_p.h" +#include "kmimetypefactory_p.h" +#include "servicesdebug.h" + +#include + +class KApplicationTraderPrivate +{ +public: + KApplicationTraderPrivate() {} +}; + +KApplicationTrader *KApplicationTrader::self() +{ + static KApplicationTrader trader; + return &trader; +} + +KApplicationTrader::KApplicationTrader() + //: d(new KApplicationTraderPrivate()) +{ +} + +KApplicationTrader::~KApplicationTrader() +{ + //delete d; +} + +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. +void KApplicationTrader::filterMimeTypeOffers(KService::List &list) // static, internal +{ + KServiceType::Ptr genericServiceTypePtr = KServiceType::serviceType(QStringLiteral("Application")); + Q_ASSERT(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(); + } + } +} + +KService::List KApplicationTrader::query(const QString &constraint) const +{ + // 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->offset(), servTypePtr->serviceOffersOffset()); + + KServiceTypeTrader::applyConstraints(lst, constraint); + + qCDebug(SERVICES) << "query" << constraint << "returning" << lst.count() << "offers"; + return lst; +} + +KService::List KApplicationTrader::queryByMimeType(const QString &mimeType, + const QString &constraint) const +{ + // Get all services of this mime type. + KService::List lst = mimeTypeSycocaServiceOffers(mimeType); + filterMimeTypeOffers(lst); + + KServiceTypeTrader::applyConstraints(lst, constraint); + + 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(); +} diff --git a/src/services/kservicetypetrader.h b/src/services/kservicetypetrader.h --- a/src/services/kservicetypetrader.h +++ b/src/services/kservicetypetrader.h @@ -203,7 +203,7 @@ * @internal (public for KMimeTypeTrader) */ static void applyConstraints(KService::List &lst, - const QString &constraint); + const QString &constraint); // KF6 TODO: move to kapplicationtrader.cpp private: /** diff --git a/src/sycoca/ksycocaentry.h b/src/sycoca/ksycocaentry.h --- a/src/sycoca/ksycocaentry.h +++ b/src/sycoca/ksycocaentry.h @@ -125,6 +125,7 @@ // All these need access to offset() friend class KSycocaFactory; friend class KBuildServiceFactory; + friend class KApplicationTrader; friend class KMimeTypeTrader; friend class KServiceTypeTrader; friend class KService;