diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -23,14 +23,15 @@ 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 ) diff --git a/autotests/kapplicationtradertest.cpp b/autotests/kapplicationtradertest.cpp new file mode 100644 --- /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 --- a/autotests/kservicetest.cpp +++ b/autotests/kservicetest.cpp @@ -29,8 +29,8 @@ #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 @@ -601,7 +601,7 @@ 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 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,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.67 + */ + 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.67 + */ + 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.67 + */ + 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.67 + */ + KSERVICE_EXPORT bool isSubsequence(const QString& pattern, const QString& text, Qt::CaseSensitivity cs = Qt::CaseSensitive); + +} + +#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,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/kmimetypetrader.cpp b/src/services/kmimetypetrader.cpp --- a/src/services/kmimetypetrader.cpp +++ b/src/services/kmimetypetrader.cpp @@ -139,9 +139,7 @@ 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(); } diff --git a/src/services/kservicefactory.cpp b/src/services/kservicefactory.cpp --- a/src/services/kservicefactory.cpp +++ b/src/services/kservicefactory.cpp @@ -283,6 +283,11 @@ 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; @@ -318,6 +323,11 @@ 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 diff --git a/src/services/kservicefactory_p.h b/src/services/kservicefactory_p.h --- a/src/services/kservicefactory_p.h +++ b/src/services/kservicefactory_p.h @@ -25,6 +25,7 @@ #include "kserviceoffer.h" #include "ksycocafactory_p.h" +#include "kservicetype.h" #include class KSycoca; @@ -91,14 +92,16 @@ * 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. diff --git a/src/services/ktraderparsetree.cpp b/src/services/ktraderparsetree.cpp --- a/src/services/ktraderparsetree.cpp +++ b/src/services/ktraderparsetree.cpp @@ -18,6 +18,7 @@ */ #include "ktraderparsetree_p.h" +#include "kapplicationtrader.h" namespace KTraderParse { @@ -438,27 +439,10 @@ 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; diff --git a/src/services/ktraderparsetree_p.h b/src/services/ktraderparsetree_p.h --- a/src/services/ktraderparsetree_p.h +++ b/src/services/ktraderparsetree_p.h @@ -248,8 +248,6 @@ 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; 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 KServiceFactory; friend class KMimeTypeTrader; friend class KServiceTypeTrader; friend class KService;