diff --git a/CMakeLists.txt b/CMakeLists.txt index 27bd7db..472aedc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,130 +1,130 @@ cmake_minimum_required(VERSION 3.5) set(KF5_VERSION "5.57.0") # handled by release scripts set(KF5_DEP_VERSION "5.56.0") # handled by release scripts project(KService VERSION ${KF5_VERSION}) # Disallow in-source build if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") message(FATAL_ERROR "KService requires an out of source build. Please create a separate build directory and run 'cmake path_to_kservice [options]' there.") endif() # ECM setup include(FeatureSummary) find_package(ECM 5.56.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake ) include(KDEInstallDirs) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(ECMSetupVersion) include(ECMGenerateHeaders) include(ECMAddQch) include(ECMQtDeclareLoggingCategory) include(GenerateExportHeader) option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") ecm_setup_version(PROJECT VARIABLE_PREFIX KSERVICE VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kservice_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5ServiceConfigVersion.cmake" SOVERSION 5) set(APPLICATIONS_MENU_NAME applications.menu CACHE STRING "Name to install the applications.menu file as.") # Dependencies set(REQUIRED_QT_VERSION 5.10.0) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED Xml) if (NOT ANDROID) find_package(Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED DBus) endif() find_package(KF5Config ${KF5_DEP_VERSION} REQUIRED) find_package(KF5CoreAddons ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Crash ${KF5_DEP_VERSION} REQUIRED) if (NOT ANDROID) find_package(KF5DBusAddons ${KF5_DEP_VERSION} REQUIRED) endif() find_package(KF5I18n ${KF5_DEP_VERSION} REQUIRED) find_package(KF5DocTools ${KF5_DEP_VERSION}) find_package(FLEX REQUIRED) set_package_properties(FLEX PROPERTIES URL "http://flex.sourceforge.net" DESCRIPTION "Fast Lexical Analyzer" TYPE REQUIRED PURPOSE "Required for the Trader parser" ) find_package(BISON REQUIRED) set_package_properties(BISON PROPERTIES URL "http://www.gnu.org/software/bison" DESCRIPTION "general-purpose parser generator" TYPE REQUIRED PURPOSE "Required for the Trader parser" ) add_definitions(-DTRANSLATION_DOMAIN=\"kservice5\") add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050c00) - +add_definitions(-DQT_NO_FOREACH) if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install(po) if (KF5DocTools_FOUND) kdoctools_install(po) endif() endif() if (KF5DocTools_FOUND) add_subdirectory(docs) endif() add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(autotests) add_subdirectory(tests) endif() # create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5Service") if (BUILD_QCH) ecm_install_qch_export( TARGETS KF5Service_QCH FILE KF5ServiceQchTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5ServiceQchTargets.cmake\")") endif() include(CMakePackageConfigHelpers) configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KF5ServiceConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5ServiceConfig.cmake" INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR} ) install(EXPORT KF5ServiceTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5ServiceTargets.cmake NAMESPACE KF5:: ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5ServiceConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5ServiceConfigVersion.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/KF5ServiceMacros.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kservice_version.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5} COMPONENT Devel ) # contains list of debug categories, for kdebugsettings install(FILES kservice.categories DESTINATION ${KDE_INSTALL_CONFDIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/autotests/kmimeassociationstest.cpp b/autotests/kmimeassociationstest.cpp index a51a54a..45abcc0 100644 --- a/autotests/kmimeassociationstest.cpp +++ b/autotests/kmimeassociationstest.cpp @@ -1,543 +1,543 @@ /* This file is part of the KDE libraries Copyright (c) 2008 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 Lesser 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 #include #include #include #include #include #include #include #include #include "setupxdgdirs.h" #include "kmimeassociations_p.h" #include #include #include "ksycoca_p.h" #include #include #include // We need a factory that returns the same KService::Ptr every time it's asked for a given service. // Otherwise the changes to the service's serviceTypes by KMimeAssociationsTest have no effect class FakeServiceFactory : public KServiceFactory { public: FakeServiceFactory(KSycoca *db) : KServiceFactory(db) {} ~FakeServiceFactory(); KService::Ptr findServiceByMenuId(const QString &name) override { //qDebug() << name; KService::Ptr result = m_cache.value(name); if (!result) { result = KServiceFactory::findServiceByMenuId(name); m_cache.insert(name, result); } //qDebug() << name << result.data(); return result; } KService::Ptr findServiceByDesktopPath(const QString &name) override { KService::Ptr result = m_cache.value(name); // yeah, same cache, I don't care :) if (!result) { result = KServiceFactory::findServiceByDesktopPath(name); m_cache.insert(name, result); } return result; } private: QMap m_cache; }; // Helper method for all the trader tests, comes from kmimetypetest.cpp static bool offerListHasService(const KService::List &offers, const QString &entryPath, bool expected /* if set, show error if not found */) { bool found = false; - Q_FOREACH (const KService::Ptr &serv, offers) { + for (const KService::Ptr &serv : offers) { if (serv->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 && expected) { qWarning() << "ERROR:" << entryPath << "not found in offer list. Here's the full list:"; - Q_FOREACH (const KService::Ptr &serv, offers) { + for (const KService::Ptr &serv : offers) { qDebug() << serv->entryPath(); } } return found; } static void writeAppDesktopFile(const QString &path, const QStringList &mimeTypes, int initialPreference = 1) { KDesktopFile file(path); KConfigGroup group = file.desktopGroup(); group.writeEntry("Name", "FakeApplication"); group.writeEntry("Type", "Application"); group.writeEntry("Exec", "ls"); group.writeEntry("OnlyShowIn", "KDE;UDE"); group.writeEntry("NotShowIn", "GNOME"); group.writeEntry("InitialPreference", initialPreference); group.writeXdgListEntry("MimeType", mimeTypes); } /** * This unit test verifies the parsing of mimeapps.list files, both directly * and via kbuildsycoca (and making trader queries). */ class KMimeAssociationsTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { setupXdgDirs(); QStandardPaths::setTestModeEnabled(true); qputenv("XDG_CURRENT_DESKTOP", "KDE"); m_localConfig = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/'); QDir(m_localConfig).removeRecursively(); QVERIFY(QDir().mkpath(m_localConfig)); m_localApps = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + QLatin1Char('/'); QDir(m_localApps).removeRecursively(); QVERIFY(QDir().mkpath(m_localApps)); QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1Char('/'); QDir(cacheDir).removeRecursively(); // Create fake application (associated with text/plain in mimeapps.list) fakeTextApplication = m_localApps + "faketextapplication.desktop"; writeAppDesktopFile(fakeTextApplication, QStringList() << QStringLiteral("text/plain")); // Create fake application (associated with text/plain in mimeapps.list) fakeTextApplicationPrefixed = m_localApps + "fakepfx/faketextapplicationpfx.desktop"; writeAppDesktopFile(fakeTextApplicationPrefixed, QStringList() << QStringLiteral("text/plain")); // A fake "default" application for text/plain (high initial preference, but not in mimeapps.list) fakeDefaultTextApplication = m_localApps + "fakedefaulttextapplication.desktop"; writeAppDesktopFile(fakeDefaultTextApplication, QStringList() << QStringLiteral("text/plain"), 9); // An app (like emacs) listing explicitly the derived mimetype (c-src); not in mimeapps.list // This interacted badly with mimeapps.list listing another app for text/plain, but the // lookup found this app first, due to c-src. The fix: ignoring derived mimetypes when // the base mimetype is already listed. // // Also include aliases (msword), to check they don't cancel each other out. fakeCSrcApplication = m_localApps + "fakecsrcmswordapplication.desktop"; writeAppDesktopFile(fakeCSrcApplication, QStringList() << QStringLiteral("text/plain") << QStringLiteral("text/c-src") << QStringLiteral("application/vnd.ms-word") << QStringLiteral("application/msword"), 8); fakeJpegApplication = m_localApps + "fakejpegapplication.desktop"; writeAppDesktopFile(fakeJpegApplication, QStringList() << QStringLiteral("image/jpeg")); fakeArkApplication = m_localApps + "fakearkapplication.desktop"; writeAppDesktopFile(fakeArkApplication, QStringList() << QStringLiteral("application/zip")); fakeHtmlApplication = m_localApps + "fakehtmlapplication.desktop"; writeAppDesktopFile(fakeHtmlApplication, QStringList() << QStringLiteral("text/html")); fakeHtmlApplicationPrefixed = m_localApps + "fakepfx/fakehtmlapplicationpfx.desktop"; writeAppDesktopFile(fakeHtmlApplicationPrefixed, QStringList() << QStringLiteral("text/html")); // Update ksycoca in ~/.qttest after creating the above runKBuildSycoca(); // Create factory on the heap and don't delete it. This must happen after // Sycoca is built, in case it did not exist before. // It registers to KSycoca, which deletes it at end of program execution. KServiceFactory *factory = new FakeServiceFactory(KSycoca::self()); KSycocaPrivate::self()->m_serviceFactory = factory; QCOMPARE(KSycocaPrivate::self()->serviceFactory(), factory); // For debugging: print all services and their storageId #if 0 const KService::List lst = KService::allServices(); QVERIFY(!lst.isEmpty()); - Q_FOREACH (const KService::Ptr &serv, lst) { + for (const KService::Ptr &serv : lst) { qDebug() << serv->entryPath() << serv->storageId() /*<< serv->desktopEntryName()*/; } #endif KService::Ptr fakeApplicationService = KService::serviceByStorageId(QStringLiteral("faketextapplication.desktop")); QVERIFY(fakeApplicationService); m_mimeAppsFileContents = "[Added Associations]\n" "image/jpeg=fakejpegapplication.desktop;\n" "text/html=fakehtmlapplication.desktop;fakehtmlapplicationpfx.desktop;\n" "text/plain=faketextapplication.desktop;fakepfx-faketextapplicationpfx.desktop;gvim.desktop;wine.desktop;idontexist.desktop;\n" // test alias resolution "application/x-pdf=fakejpegapplication.desktop;\n" // test x-scheme-handler (#358159) (missing trailing ';' as per xdg-mime bug...) "x-scheme-handler/mailto=faketextapplication.desktop\n" "[Added KParts/ReadOnlyPart Associations]\n" "text/plain=katepart.desktop;\n" "[Removed Associations]\n" "image/jpeg=firefox.desktop;\n" "text/html=gvim.desktop;abiword.desktop;\n"; // Expected results preferredApps[QStringLiteral("image/jpeg")] << QStringLiteral("fakejpegapplication.desktop"); preferredApps[QStringLiteral("application/pdf")] << QStringLiteral("fakejpegapplication.desktop"); preferredApps[QStringLiteral("text/plain")] << QStringLiteral("faketextapplication.desktop") << QStringLiteral("fakepfx-faketextapplicationpfx.desktop") << QStringLiteral("gvim.desktop"); preferredApps[QStringLiteral("text/x-csrc")] << QStringLiteral("faketextapplication.desktop") << QStringLiteral("fakepfx-faketextapplicationpfx.desktop") << QStringLiteral("gvim.desktop"); preferredApps[QStringLiteral("text/html")] << QStringLiteral("fakehtmlapplication.desktop") << QStringLiteral("fakepfx-fakehtmlapplicationpfx.desktop"); preferredApps[QStringLiteral("application/msword")] << QStringLiteral("fakecsrcmswordapplication.desktop"); preferredApps[QStringLiteral("x-scheme-handler/mailto")] << QStringLiteral("faketextapplication.desktop"); removedApps[QStringLiteral("image/jpeg")] << QStringLiteral("firefox.desktop"); removedApps[QStringLiteral("text/html")] << QStringLiteral("gvim.desktop") << QStringLiteral("abiword.desktop"); // Clean-up non-existing apps removeNonExisting(preferredApps); removeNonExisting(removedApps); } void cleanupTestCase() { QFile::remove(m_localConfig + "/mimeapps.list"); runKBuildSycoca(); } void testParseSingleFile() { KOfferHash offerHash; KMimeAssociations parser(offerHash, KSycocaPrivate::self()->serviceFactory()); QTemporaryDir tempDir; QVERIFY(tempDir.isValid()); QFile tempFile(tempDir.path() + "/mimeapps.list"); QVERIFY(tempFile.open(QIODevice::WriteOnly)); tempFile.write(m_mimeAppsFileContents); const QString fileName = tempFile.fileName(); tempFile.close(); //QTest::ignoreMessage(QtDebugMsg, "findServiceByDesktopPath: idontexist.desktop not found"); parser.parseMimeAppsList(fileName, 100); for (ExpectedResultsMap::const_iterator it = preferredApps.constBegin(), end = preferredApps.constEnd(); it != end; ++it) { const QString mime = it.key(); // The data for derived types and aliases isn't for this test (which only looks at mimeapps.list) if (mime == QLatin1String("text/x-csrc") || mime == QLatin1String("application/msword")) { continue; } const QList offers = offerHash.offersFor(mime); - Q_FOREACH (const QString &service, it.value()) { + for (const QString &service : it.value()) { KService::Ptr serv = KService::serviceByStorageId(service); if (serv && !offersContains(offers, serv)) { qDebug() << "expected offer" << serv->entryPath() << "not in offers for" << mime << ":"; - Q_FOREACH (const KServiceOffer &offer, offers) { + for (const KServiceOffer &offer : offers) { qDebug() << offer.service()->storageId(); } QFAIL("offer does not have servicetype"); } } } for (ExpectedResultsMap::const_iterator it = removedApps.constBegin(), end = removedApps.constEnd(); it != end; ++it) { const QString mime = it.key(); const QList offers = offerHash.offersFor(mime); - Q_FOREACH (const QString &service, it.value()) { + for (const QString &service : it.value()) { KService::Ptr serv = KService::serviceByStorageId(service); if (serv && offersContains(offers, serv)) { //qDebug() << serv.data() << serv->entryPath() << "does not have" << mime; QFAIL("offer should not have servicetype"); } } } } void testGlobalAndLocalFiles() { KOfferHash offerHash; KMimeAssociations parser(offerHash, KSycocaPrivate::self()->serviceFactory()); // Write global file QTemporaryDir tempDirGlobal; QVERIFY(tempDirGlobal.isValid()); QFile tempFileGlobal(tempDirGlobal.path() + "/mimeapps.list"); QVERIFY(tempFileGlobal.open(QIODevice::WriteOnly)); QByteArray globalAppsFileContents = "[Added Associations]\n" "image/jpeg=firefox.desktop;\n" // removed by local config "text/html=firefox.desktop;\n" // mdv "image/png=fakejpegapplication.desktop;\n"; tempFileGlobal.write(globalAppsFileContents); const QString globalFileName = tempFileGlobal.fileName(); tempFileGlobal.close(); // We didn't keep it, so we need to write the local file again QTemporaryDir tempDir; QVERIFY(tempDir.isValid()); QFile tempFile(tempDir.path() + "/mimeapps.list"); QVERIFY(tempFile.open(QIODevice::WriteOnly)); tempFile.write(m_mimeAppsFileContents); const QString fileName = tempFile.fileName(); tempFile.close(); parser.parseMimeAppsList(globalFileName, 1000); parser.parseMimeAppsList(fileName, 1050); // += 50 is correct. QList offers = offerHash.offersFor(QStringLiteral("image/jpeg")); std::stable_sort(offers.begin(), offers.end()); // like kbuildservicefactory.cpp does const QStringList expectedJpegApps = preferredApps[QStringLiteral("image/jpeg")]; QCOMPARE(assembleOffers(offers), expectedJpegApps); offers = offerHash.offersFor(QStringLiteral("text/html")); std::stable_sort(offers.begin(), offers.end()); QStringList textHtmlApps = preferredApps[QStringLiteral("text/html")]; if (KService::serviceByStorageId(QStringLiteral("firefox.desktop"))) { textHtmlApps.append(QStringLiteral("firefox.desktop")); } qDebug() << assembleOffers(offers); QCOMPARE(assembleOffers(offers), textHtmlApps); offers = offerHash.offersFor(QStringLiteral("image/png")); std::stable_sort(offers.begin(), offers.end()); QCOMPARE(assembleOffers(offers), QStringList() << QStringLiteral("fakejpegapplication.desktop")); } void testSetupRealFile() { writeToMimeApps(m_mimeAppsFileContents); // Test a trader query KService::List offers = KMimeTypeTrader::self()->query(QStringLiteral("image/jpeg")); QVERIFY(!offers.isEmpty()); //qDebug() << m_mimeAppsFileContents; //qDebug() << "preferred apps for jpeg: " << preferredApps.value("image/jpeg"); //for (int i = 0; i < offers.count(); ++i) { // qDebug() << "offers for" << "image/jpeg" << ":" << i << offers[i]->storageId(); //} QCOMPARE(offers.first()->storageId(), QStringLiteral("fakejpegapplication.desktop")); // Now the generic variant of the above test: // for each mimetype, check that the preferred apps are as specified for (ExpectedResultsMap::const_iterator it = preferredApps.constBegin(), end = preferredApps.constEnd(); it != end; ++it) { const QString mime = it.key(); const KService::List offers = KMimeTypeTrader::self()->query(mime); const QStringList offerIds = assembleServices(offers, it.value().count()); if (offerIds != it.value()) { qDebug() << "offers for" << mime << ":"; for (int i = 0; i < offers.count(); ++i) { qDebug() << " " << i << ":" << offers[i]->storageId(); } qDebug() << " Expected:" << it.value(); const QStringList expectedPreferredServices = it.value(); for (int i = 0; i < expectedPreferredServices.count(); ++i) { qDebug() << mime << i << expectedPreferredServices[i]; //QCOMPARE(expectedPreferredServices[i], offers[i]->storageId()); } } QCOMPARE(offerIds, it.value()); } } void testMultipleInheritance() { // application/x-shellscript inherits from both text/plain and application/x-executable KService::List offers = KMimeTypeTrader::self()->query(QStringLiteral("application/x-shellscript")); QVERIFY(offerListHasService(offers, fakeTextApplication, true)); } void testRemoveAssociationFromParent() { // I removed kate from text/plain, and it would still appear in text/x-java. // First, let's check our fake app is associated with text/plain KService::List offers = KMimeTypeTrader::self()->query(QStringLiteral("text/plain")); QVERIFY(offerListHasService(offers, fakeTextApplication, true)); writeToMimeApps(QByteArray("[Removed Associations]\n" "text/plain=faketextapplication.desktop;\n")); offers = KMimeTypeTrader::self()->query(QStringLiteral("text/plain")); QVERIFY(!offerListHasService(offers, fakeTextApplication, false)); offers = KMimeTypeTrader::self()->query(QStringLiteral("text/x-java")); QVERIFY(!offerListHasService(offers, fakeTextApplication, false)); } void testRemovedImplicitAssociation() // remove (implicit) assoc from derived mimetype { // #164584: Removing ark from opendocument.text didn't work const QString opendocument = QStringLiteral("application/vnd.oasis.opendocument.text"); // [sanity checking of s-m-i installation] QMimeType mime = QMimeDatabase().mimeTypeForName(opendocument); QVERIFY(mime.isValid()); if (!mime.inherits(QStringLiteral("application/zip"))) { // CentOS patches out the application/zip inheritance from application/vnd.oasis.opendocument.text!! Grmbl. QSKIP("Broken distro where application/vnd.oasis.opendocument.text doesn't inherit from application/zip"); } KService::List offers = KMimeTypeTrader::self()->query(opendocument); QVERIFY(offerListHasService(offers, fakeArkApplication, true)); writeToMimeApps(QByteArray("[Removed Associations]\n" "application/vnd.oasis.opendocument.text=fakearkapplication.desktop;\n")); offers = KMimeTypeTrader::self()->query(opendocument); QVERIFY(!offerListHasService(offers, fakeArkApplication, false)); offers = KMimeTypeTrader::self()->query(QStringLiteral("application/zip")); QVERIFY(offerListHasService(offers, fakeArkApplication, true)); } void testRemovedImplicitAssociation178560() { // #178560: Removing ark from interface/x-winamp-skin didn't work // Using application/x-kns (another zip-derived mimetype) nowadays. const QString mime = QStringLiteral("application/x-kns"); KService::List offers = KMimeTypeTrader::self()->query(mime); QVERIFY(offerListHasService(offers, fakeArkApplication, true)); writeToMimeApps(QByteArray("[Removed Associations]\n" "application/x-kns=fakearkapplication.desktop;\n")); offers = KMimeTypeTrader::self()->query(mime); QVERIFY(!offerListHasService(offers, fakeArkApplication, false)); offers = KMimeTypeTrader::self()->query(QStringLiteral("application/zip")); QVERIFY(offerListHasService(offers, fakeArkApplication, true)); } // remove assoc from a mime which is both a parent and a derived mimetype void testRemovedMiddleAssociation() { // More tricky: x-theme inherits x-desktop inherits text/plain, // if we remove an association for x-desktop then x-theme shouldn't // get it from text/plain... KService::List offers; writeToMimeApps(QByteArray("[Removed Associations]\n" "application/x-desktop=faketextapplication.desktop;\n")); offers = KMimeTypeTrader::self()->query(QStringLiteral("text/plain")); QVERIFY(offerListHasService(offers, fakeTextApplication, true)); offers = KMimeTypeTrader::self()->query(QStringLiteral("application/x-desktop")); QVERIFY(!offerListHasService(offers, fakeTextApplication, false)); offers = KMimeTypeTrader::self()->query(QStringLiteral("application/x-theme")); QVERIFY(!offerListHasService(offers, fakeTextApplication, false)); } private: typedef QMap ExpectedResultsMap; void runKBuildSycoca() { // Wait for notifyDatabaseChanged DBus signal // (The real KCM code simply does the refresh in a slot, asynchronously) QSignalSpy spy(KSycoca::self(), SIGNAL(databaseChanged(QStringList))); KBuildSycoca builder; QVERIFY(builder.recreate()); if (spy.isEmpty()) { spy.wait(); } } void writeToMimeApps(const QByteArray &contents) { QString mimeAppsPath = m_localConfig + "/mimeapps.list"; QFile mimeAppsFile(mimeAppsPath); QVERIFY(mimeAppsFile.open(QIODevice::WriteOnly)); mimeAppsFile.write(contents); mimeAppsFile.close(); runKBuildSycoca(); } static bool offersContains(const QList &offers, KService::Ptr serv) { - Q_FOREACH (const KServiceOffer &offer, offers) { + for (const KServiceOffer &offer : offers) { if (offer.service()->storageId() == serv->storageId()) { return true; } } return false; } static QStringList assembleOffers(const QList &offers) { QStringList lst; - Q_FOREACH (const KServiceOffer &offer, offers) { + for (const KServiceOffer &offer : offers) { lst.append(offer.service()->storageId()); } return lst; } static QStringList assembleServices(const QList &services, int maxCount = -1) { QStringList lst; - Q_FOREACH (const KService::Ptr &service, services) { + for (const KService::Ptr &service : services) { lst.append(service->storageId()); if (maxCount > -1 && lst.count() == maxCount) { break; } } return lst; } void removeNonExisting(ExpectedResultsMap &erm) { for (ExpectedResultsMap::iterator it = erm.begin(), end = erm.end(); it != end; ++it) { QMutableStringListIterator serv_it(it.value()); while (serv_it.hasNext()) { if (!KService::serviceByStorageId(serv_it.next())) { //qDebug() << "removing non-existing entry" << serv_it.value(); serv_it.remove(); } } } } QString m_localApps; QString m_localConfig; QByteArray m_mimeAppsFileContents; QString fakeTextApplication; QString fakeTextApplicationPrefixed; QString fakeDefaultTextApplication; QString fakeCSrcApplication; QString fakeJpegApplication; QString fakeHtmlApplication; QString fakeHtmlApplicationPrefixed; QString fakeArkApplication; ExpectedResultsMap preferredApps; ExpectedResultsMap removedApps; }; FakeServiceFactory::~FakeServiceFactory() { } QTEST_GUILESS_MAIN(KMimeAssociationsTest) #include "kmimeassociationstest.moc" diff --git a/autotests/kservicetest.cpp b/autotests/kservicetest.cpp index 88f3200..6c624cd 100644 --- a/autotests/kservicetest.cpp +++ b/autotests/kservicetest.cpp @@ -1,894 +1,894 @@ /* * 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 #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("kf5.kcoreaddons.kdirwatch.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"; } m_hasKde5Konsole = false; 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; } 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"))); } 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... - QStringList services; services << QStringLiteral("fakeservice.desktop") << QStringLiteral("fakepart.desktop") << QStringLiteral("faketextplugin.desktop") << QStringLiteral("fakeservice_querymustrebuild.desktop"); - Q_FOREACH (const QString &service, services) { + 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); } - QStringList serviceTypes; serviceTypes << QStringLiteral("fakeplugintype.desktop"); - Q_FOREACH (const QString &serviceType, serviceTypes) { + 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::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(QStringLiteral("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()); 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(); //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.konsole.desktop") { m_hasKde5Konsole = true; } QVERIFY(!menuId.isEmpty()); lookedupService = KService::serviceByMenuId(menuId); QVERIFY(lookedupService); // not null QCOMPARE(lookedupService->menuId(), menuId); } } } // 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"); } if (!m_hasKde5Konsole) { QSKIP("org.kde.konsole.desktop not available"); } //KService::Ptr konsole = KService::serviceByMenuId( "org.kde.konsole.desktop" ); KService::Ptr konsole = KService::serviceByDesktopName(QStringLiteral("org.kde.konsole")); QVERIFY(konsole); QCOMPARE(konsole->menuId(), QStringLiteral("org.kde.konsole.desktop")); //qDebug() << konsole->entryPath(); QCOMPARE(int(konsole->dbusStartupType()), int(KService::DBusUnique)); } void KServiceTest::testByStorageId() { if (!KSycoca::isAvailable()) { QSKIP("ksycoca not available"); } if (QStandardPaths::locate(QStandardPaths::ApplicationsLocation, QStringLiteral("org.kde.konsole.desktop")).isEmpty()) { QSKIP("org.kde.konsole.desktop not available"); } QVERIFY(KService::serviceByMenuId(QStringLiteral("org.kde.konsole.desktop"))); QVERIFY(!KService::serviceByMenuId(QStringLiteral("org.kde.konsole"))); // doesn't work, extension mandatory QVERIFY(!KService::serviceByMenuId(QStringLiteral("konsole.desktop"))); // doesn't work, full filename mandatory QVERIFY(KService::serviceByStorageId(QStringLiteral("org.kde.konsole.desktop"))); QVERIFY(KService::serviceByStorageId("org.kde.konsole")); // This one fails here; probably because there are two such files, so this would be too // ambiguous... According to the testAllServices output, the entryPaths are // entryPath="/d/kde/inst/kde5/share/applications/org.kde.konsole.desktop" // entryPath= "/usr/share/applications/org.kde.konsole.desktop" // //QVERIFY(KService::serviceByDesktopPath("org.kde.konsole.desktop")); QVERIFY(KService::serviceByDesktopName(QStringLiteral("org.kde.konsole"))); QCOMPARE(KService::serviceByDesktopName(QStringLiteral("org.kde.konsole"))->menuId(), QString("org.kde.konsole.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"))) { - foreach (KService::Ptr service, offers) { + 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; - Q_FOREACH (KService::Ptr service, offers) { + 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( 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"))); - Q_FOREACH (const KService::Ptr &serv, services) { + for (const KService::Ptr &serv : qAsConst(services)) { QVERIFY(serv); } - Q_FOREACH (const KService::Ptr &serv, disabledServices) { + 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() { if (QStandardPaths::locate(QStandardPaths::ApplicationsLocation, QStringLiteral("org.kde.konsole.desktop")).isEmpty()) { QSKIP("org.kde.konsole.desktop not available"); } KService::Ptr service = KService::serviceByStorageId(QStringLiteral("org.kde.konsole.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")); QVERIFY(newTabAction.icon().isEmpty()); 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(); - Q_FOREACH (KServiceGroup::SPtr s, list) { + 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 QTRY_COMPARE_WITH_TIMEOUT(m_sycocaUpdateDone.load(), 1, 15000); // not using a bool, just to silence helgrind 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")); } 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()); } 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/autotests/ksycoca_xdgdirstest.cpp b/autotests/ksycoca_xdgdirstest.cpp index cc4b7e2..2cc4766 100644 --- a/autotests/ksycoca_xdgdirstest.cpp +++ b/autotests/ksycoca_xdgdirstest.cpp @@ -1,135 +1,135 @@ /* This file is part of the KDE project Copyright (C) 2015 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 #include #include #include #include #include #include #include #include #include #include #include #include // taken from tst_qstandardpaths #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_BLACKBERRY) && !defined(Q_OS_ANDROID) #define Q_XDG_PLATFORM #endif class KSycocaXdgDirsTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { QStandardPaths::setTestModeEnabled(true); QVERIFY(m_tempDir.isValid()); } void cleanupTestCase() { QFile::remove(KSycoca::absoluteFilePath()); } void testOtherAppDir(); private: static void runKBuildSycoca(const QProcessEnvironment &environment); QTemporaryDir m_tempDir; }; QTEST_MAIN(KSycocaXdgDirsTest) void KSycocaXdgDirsTest::runKBuildSycoca(const QProcessEnvironment &environment) { QProcess proc; const QString kbuildsycoca = QStringLiteral(KBUILDSYCOCAEXE); QVERIFY(!kbuildsycoca.isEmpty()); QStringList args; args << QStringLiteral("--testmode"); //proc.setProcessChannelMode(QProcess::ForwardedChannels); proc.start(kbuildsycoca, args); proc.setProcessEnvironment(environment); proc.waitForFinished(); QCOMPARE(proc.exitStatus(), QProcess::NormalExit); } // Ensure two apps with different XDG_DATA_DIRS/XDG_DATA_HOME don't use the same sycoca. void KSycocaXdgDirsTest::testOtherAppDir() { #ifndef Q_XDG_PLATFORM QSKIP("This test requires XDG_DATA_DIRS"); #endif // KSycoca::self() represents application 1, running with one set of dirs KSycoca::self()->ensureCacheValid(); // Create another xdg data dir const QString dataDir = m_tempDir.path(); qputenv("XDG_DATA_DIRS", QFile::encodeName(dataDir)); QCOMPARE(dataDir, QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).last()); QVERIFY(!KService::serviceByDesktopPath(QStringLiteral("test_app_other.desktop"))); const QString appDir = dataDir + "/applications"; // test_app_other: live in a different application directory const QString testAppOther = appDir + "/test_app_other.desktop"; KDesktopFile file(testAppOther); KConfigGroup group = file.desktopGroup(); group.writeEntry("Type", "Application"); group.writeEntry("Exec", "kded5"); group.writeEntry("Name", "Test App Other"); qDebug() << "Just created" << testAppOther; file.sync(); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert(QStringLiteral("XDG_DATA_DIRS"), dataDir); runKBuildSycoca(env); #if 0 // for debugging const KService::List lst = KService::allServices(); QVERIFY(!lst.isEmpty()); - Q_FOREACH (const KService::Ptr &serv, lst) { + for (const KService::Ptr &serv : lst) { qDebug() << serv->entryPath() << serv->storageId() /*<< serv->desktopEntryName()*/; } #endif // This is still NOT available to application 1. // kbuildsycoca created a different DB file, the one we read from hasn't changed. // Changing XDG_DATA_DIRS at runtime isn't supported, so this test isn't doing what apps would do. // The point however is that another app using different dirs cannot mess up our DB. QVERIFY(!KService::serviceByStorageId(QStringLiteral("test_app_other.desktop"))); // Check here what the application 2 would see, by creating another sycoca instance. KSycoca otherAppSycoca; // do what serviceByStorageId does: otherAppSycoca.ensureCacheValid(); QVERIFY(otherAppSycoca.d->serviceFactory()->findServiceByStorageId(QStringLiteral("test_app_other.desktop"))); QVERIFY(otherAppSycoca.d->m_databasePath != KSycoca::self()->d->m_databasePath); // check that they use a different filename // check that the timestamp code works QVERIFY(!otherAppSycoca.d->needsRebuild()); } #include "ksycoca_xdgdirstest.moc" diff --git a/autotests/ksycocadicttest.cpp b/autotests/ksycocadicttest.cpp index 09892f9..db225c8 100644 --- a/autotests/ksycocadicttest.cpp +++ b/autotests/ksycocadicttest.cpp @@ -1,138 +1,138 @@ /* This file is part of the KDE project Copyright (C) 2007 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 #include #include #include #include #include #include #include #include class KSycocaDictTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { QStandardPaths::setTestModeEnabled(true); // dicttestplugintype: a servicetype const QString dictTestPluginType = serviceTypesDir() + "/dicttestplugintype.desktop"; if (!QFile::exists(dictTestPluginType)) { KDesktopFile file(dictTestPluginType); KConfigGroup group = file.desktopGroup(); group.writeEntry("Comment", "Fake Text Plugin"); group.writeEntry("Type", "ServiceType"); group.writeEntry("X-KDE-ServiceType", "DictTestPluginType"); file.group("PropertyDef::X-KDE-Version").writeEntry("Type", "double"); // like in ktexteditorplugin.desktop qDebug() << "Just created" << dictTestPluginType; } runKBuildSycoca(); } void testStandardDict(); private: QString serviceTypesDir() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/kservicetypes5"; } void add(KSycocaDict &dict, const QString &key, const QString &name) { KServiceType::Ptr ptr = KServiceType::serviceType(name); if (!ptr) { qWarning() << "serviceType not found" << name; } dict.add(key, KSycocaEntry::Ptr(ptr)); } static void runKBuildSycoca(); }; QTEST_MAIN(KSycocaDictTest) void KSycocaDictTest::runKBuildSycoca() { QSignalSpy spy(KSycoca::self(), SIGNAL(databaseChanged(QStringList))); KBuildSycoca builder; QVERIFY(builder.recreate()); if (spy.isEmpty()) { qDebug() << "waiting for signal"; QVERIFY(spy.wait(10000)); qDebug() << "got signal"; } } // Standard use of KSycocaDict: mapping entry name to entry void KSycocaDictTest::testStandardDict() { QVERIFY(KSycoca::isAvailable()); QStringList serviceTypes; serviceTypes << QStringLiteral("DictTestPluginType") << QStringLiteral("KUriFilter/Plugin") << QStringLiteral("KDataTool") << QStringLiteral("KCModule") << QStringLiteral("KScan/KScanDialog") << QStringLiteral("Browser/View") << QStringLiteral("Plasma/Applet") << QStringLiteral("Plasma/Runner"); // Skip servicetypes that are not installed QMutableListIterator it(serviceTypes); while (it.hasNext()) { if (!KServiceType::serviceType(it.next())) { it.remove(); } } qDebug() << serviceTypes; QBENCHMARK { QByteArray buffer; { KSycocaDict dict; - foreach (const QString &str, serviceTypes) + for (const QString &str : qAsConst(serviceTypes)) { add(dict, str, str); } dict.remove(QStringLiteral("DictTestPluginType")); // just to test remove add(dict, QStringLiteral("DictTestPluginType"), QStringLiteral("DictTestPluginType")); QCOMPARE(int(dict.count()), serviceTypes.count()); QDataStream saveStream(&buffer, QIODevice::WriteOnly); dict.save(saveStream); } QDataStream stream(buffer); KSycocaDict loadingDict(&stream, 0); int offset = loadingDict.find_string(QStringLiteral("DictTestPluginType")); QVERIFY(offset > 0); QCOMPARE(offset, KServiceType::serviceType(QStringLiteral("DictTestPluginType"))->offset()); - foreach (const QString &str, serviceTypes) + for (const QString &str : qAsConst(serviceTypes)) { int offset = loadingDict.find_string(str); QVERIFY(offset > 0); QCOMPARE(offset, KServiceType::serviceType(str)->offset()); } offset = loadingDict.find_string(QStringLiteral("doesnotexist")); // TODO QCOMPARE(offset, 0); // could be non 0 according to the docs, too; if non 0, we should check that the pointed mimetype doesn't have this name. } } #include "ksycocadicttest.moc" diff --git a/src/kdeinit/ktoolinvocation_win.cpp b/src/kdeinit/ktoolinvocation_win.cpp index c3caafa..42cfadd 100644 --- a/src/kdeinit/ktoolinvocation_win.cpp +++ b/src/kdeinit/ktoolinvocation_win.cpp @@ -1,87 +1,87 @@ /* This file is part of the KDE libraries Copyright (C) 2004-2008 Jarosław Staniek Copyright (C) 2006 Ralf Habacker 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 "ktoolinvocation.h" #include "kmessage.h" #include "klocalizedstring.h" #include #include #include #include #include #include #include "windows.h" #include "shellapi.h" void KToolInvocation::invokeBrowser(const QString &url, const QByteArray &startup_id) { #ifndef _WIN32_WCE QString sOpen = QString::fromLatin1("open"); ShellExecuteW(0, (LPCWSTR)sOpen.utf16(), (LPCWSTR)url.utf16(), 0, 0, SW_NORMAL); #else SHELLEXECUTEINFO cShellExecuteInfo = {0}; cShellExecuteInfo.cbSize = sizeof(SHELLEXECUTEINFO); cShellExecuteInfo.fMask = SEE_MASK_NOCLOSEPROCESS; cShellExecuteInfo.hwnd = NULL; cShellExecuteInfo.lpVerb = L"Open"; cShellExecuteInfo.lpFile = (LPCWSTR)url.utf16(); cShellExecuteInfo.nShow = SW_SHOWNORMAL; ShellExecuteEx(&cShellExecuteInfo); #endif } void KToolInvocation::invokeMailer(const QString &_to, const QString &_cc, const QString &_bcc, const QString &subject, const QString &body, const QString & /*messageFile TODO*/, const QStringList &attachURLs, const QByteArray &startup_id) { QUrl url(QLatin1String("mailto:") + _to); QUrlQuery query; query.addQueryItem(QStringLiteral("subject"), subject); query.addQueryItem(QStringLiteral("cc"), _cc); query.addQueryItem(QStringLiteral("bcc"), _bcc); query.addQueryItem(QStringLiteral("body"), body); - foreach (const QString &attachURL, attachURLs) { + for (const QString &attachURL : attachURLs) { query.addQueryItem(QStringLiteral("attach"), attachURL); } url.setQuery(query); #ifndef _WIN32_WCE QString sOpen = QLatin1String("open"); ShellExecuteW(0, (LPCWSTR)sOpen.utf16(), (LPCWSTR)url.url().utf16(), 0, 0, SW_NORMAL); #else SHELLEXECUTEINFO cShellExecuteInfo = {0}; cShellExecuteInfo.cbSize = sizeof(SHELLEXECUTEINFO); cShellExecuteInfo.fMask = SEE_MASK_NOCLOSEPROCESS; cShellExecuteInfo.hwnd = NULL; cShellExecuteInfo.lpVerb = L"Open"; cShellExecuteInfo.lpFile = (LPCWSTR)url.url().utf16(); cShellExecuteInfo.nShow = SW_SHOWNORMAL; ShellExecuteEx(&cShellExecuteInfo); #endif } void KToolInvocation::invokeTerminal(const QString &command, const QString &workdir, const QByteArray &startup_id) { //TODO } diff --git a/src/kdeinit/ktoolinvocation_x11.cpp b/src/kdeinit/ktoolinvocation_x11.cpp index 4b8437b..1e7a345 100644 --- a/src/kdeinit/ktoolinvocation_x11.cpp +++ b/src/kdeinit/ktoolinvocation_x11.cpp @@ -1,387 +1,387 @@ /* This file is part of the KDE libraries Copyright (c) 1997,1998 Matthias Kalle Dalheimer Copyright (c) 1999 Espen Sand Copyright (c) 2000-2004 Frerich Raabe Copyright (c) 2003,2004 Oswald Buddenhagen Copyright (c) 2006 Thiago Macieira Copyright (C) 2008 Aaron Seigo 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 "ktoolinvocation.h" #include #include #include #include "kconfig.h" #include "kshell.h" #include "kmacroexpander.h" #include "klocalizedstring.h" #include "kmessage.h" #include "kservice.h" #include #include #include #include #include #include #include #include static QStringList splitEmailAddressList(const QString &aStr) { // This is a copy of KPIM::splitEmailAddrList(). // Features: // - always ignores quoted characters // - ignores everything (including parentheses and commas) // inside quoted strings // - supports nested comments // - ignores everything (including double quotes and commas) // inside comments QStringList list; if (aStr.isEmpty()) { return list; } QString addr; int addrstart = 0; int commentlevel = 0; bool insidequote = false; for (int index = 0; index < aStr.length(); index++) { // the following conversion to latin1 is o.k. because // we can safely ignore all non-latin1 characters switch (aStr[index].toLatin1()) { case '"' : // start or end of quoted string if (commentlevel == 0) { insidequote = !insidequote; } break; case '(' : // start of comment if (!insidequote) { commentlevel++; } break; case ')' : // end of comment if (!insidequote) { if (commentlevel > 0) { commentlevel--; } else { //qDebug() << "Error in address splitting: Unmatched ')'" // << endl; return list; } } break; case '\\' : // quoted character index++; // ignore the quoted character break; case ',' : if (!insidequote && (commentlevel == 0)) { addr = aStr.mid(addrstart, index - addrstart); if (!addr.isEmpty()) { list += addr.simplified(); } addrstart = index + 1; } break; } } // append the last address to the list if (!insidequote && (commentlevel == 0)) { addr = aStr.mid(addrstart, aStr.length() - addrstart); if (!addr.isEmpty()) { list += addr.simplified(); } } //else // qDebug() << "Error in address splitting: " // << "Unexpected end of address list" // << endl; return list; } void KToolInvocation::invokeMailer(const QString &_to, const QString &_cc, const QString &_bcc, const QString &subject, const QString &body, const QString & /*messageFile TODO*/, const QStringList &attachURLs, const QByteArray &startup_id) { if (!isMainThreadActive()) { return; } KConfig config(QStringLiteral("emaildefaults")); KConfigGroup defaultsGrp(&config, "Defaults"); QString group = defaultsGrp.readEntry("Profile", "Default"); KConfigGroup profileGrp(&config, QStringLiteral("PROFILE_%1").arg(group)); QString command = profileGrp.readPathEntry("EmailClient", QString()); QString to, cc, bcc; if (command.isEmpty() || command == QLatin1String("kmail") || command.endsWith(QLatin1String("/kmail"))) { command = QStringLiteral("kmail --composer -s %s -c %c -b %b --body %B --attach %A -- %t"); if (!_to.isEmpty()) { QUrl url; url.setScheme(QStringLiteral("mailto")); url.setPath(_to); to = QString::fromLatin1(url.toEncoded()); } if (!_cc.isEmpty()) { QUrl url; url.setScheme(QStringLiteral("mailto")); url.setPath(_cc); cc = QString::fromLatin1(url.toEncoded()); } if (!_bcc.isEmpty()) { QUrl url; url.setScheme(QStringLiteral("mailto")); url.setPath(_bcc); bcc = QString::fromLatin1(url.toEncoded()); } } else { to = _to; cc = _cc; bcc = _bcc; if (!command.contains(QLatin1Char('%'))) { command += QLatin1String(" %u"); } } if (profileGrp.readEntry("TerminalClient", false)) { KConfigGroup confGroup(KSharedConfig::openConfig(), "General"); QString preferredTerminal = confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole")); command = preferredTerminal + QLatin1String(" -e ") + command; } QStringList cmdTokens = KShell::splitArgs(command); QString cmd = cmdTokens.takeFirst(); QUrl url; QUrlQuery query; if (!to.isEmpty()) { QStringList tos = splitEmailAddressList(to); url.setPath(tos.first()); tos.erase(tos.begin()); for (QStringList::ConstIterator it = tos.constBegin(); it != tos.constEnd(); ++it) { query.addQueryItem(QStringLiteral("to"), *it); } } const QStringList ccs = splitEmailAddressList(cc); for (QStringList::ConstIterator it = ccs.constBegin(); it != ccs.constEnd(); ++it) { query.addQueryItem(QStringLiteral("cc"), *it); } const QStringList bccs = splitEmailAddressList(bcc); for (QStringList::ConstIterator it = bccs.constBegin(); it != bccs.constEnd(); ++it) { query.addQueryItem(QStringLiteral("bcc"), *it); } for (QStringList::ConstIterator it = attachURLs.constBegin(); it != attachURLs.constEnd(); ++it) { query.addQueryItem(QStringLiteral("attach"), *it); } if (!subject.isEmpty()) { query.addQueryItem(QStringLiteral("subject"), subject); } if (!body.isEmpty()) { query.addQueryItem(QStringLiteral("body"), body); } url.setQuery(query); if (!(to.isEmpty() && (!url.hasQuery()))) { url.setScheme(QStringLiteral("mailto")); } QHash keyMap; keyMap.insert(QLatin1Char('t'), to); keyMap.insert(QLatin1Char('s'), subject); keyMap.insert(QLatin1Char('c'), cc); keyMap.insert(QLatin1Char('b'), bcc); keyMap.insert(QLatin1Char('B'), body); keyMap.insert(QLatin1Char('u'), url.toString()); QString attachlist = attachURLs.join(QLatin1Char(',')); attachlist.prepend(QLatin1Char('\'')); attachlist.append(QLatin1Char('\'')); keyMap.insert(QLatin1Char('A'), attachlist); for (int i = 0; i < cmdTokens.count(); ++i) { if (cmdTokens.at(i) == QLatin1String("%A")) { if (attachURLs.isEmpty()) { cmdTokens.removeAt(i); } else { const QString previousStr = cmdTokens.at(i-1); cmdTokens.removeAt(i); const int currentPos = i; - Q_FOREACH(const QString &url, attachURLs) { + for(const QString &url : attachURLs) { cmdTokens.insert(currentPos, previousStr); cmdTokens.insert(currentPos, url); i += 2; } } } else { const QString str = KMacroExpander::expandMacros(cmdTokens.at(i), keyMap); cmdTokens[i] = str; } } QString error; // TODO this should check if cmd has a .desktop file, and use data from it, together // with sending more ASN data if (kdeinitExec(cmd, cmdTokens, &error, nullptr, startup_id)) { KMessage::message(KMessage::Error, i18n("Could not launch the mail client:\n\n%1", error), i18n("Could not launch Mail Client")); } } void KToolInvocation::invokeBrowser(const QString &url, const QByteArray &startup_id) { if (!isMainThreadActive()) { return; } QStringList args; args << url; QString error; // This method should launch a webbrowser, preferably without doing a mimetype // check first, like KRun (i.e. kde-open) would do. // In a KDE session, honour BrowserApplication if set, otherwise use preferred app for text/html if any, // otherwise xdg-open, otherwise kde-open (which does a mimetype check first though). // Outside KDE, call xdg-open if present, otherwise fallback to the above logic. QString exe; // the binary we are going to launch. const QString xdg_open = QStandardPaths::findExecutable(QStringLiteral("xdg-open")); if (qEnvironmentVariableIsEmpty("KDE_FULL_SESSION")) { exe = xdg_open; } if (exe.isEmpty()) { // We're in a KDE session (or there's no xdg-open installed) KConfigGroup config(KSharedConfig::openConfig(), "General"); const QString browserApp = config.readPathEntry("BrowserApplication", QString()); if (!browserApp.isEmpty()) { exe = browserApp; if (exe.startsWith(QLatin1Char('!'))) { exe = exe.mid(1); // Literal command QStringList cmdTokens = KShell::splitArgs(exe); exe = cmdTokens.takeFirst(); args = cmdTokens + args; } else { // desktop file ID KService::Ptr service = KService::serviceByStorageId(exe); if (service) { //qDebug() << "Starting service" << service->entryPath(); if (startServiceByDesktopPath(service->entryPath(), args, &error, nullptr, nullptr, startup_id)) { KMessage::message(KMessage::Error, // TODO: i18n("Could not launch %1:\n\n%2", exe, error), i18n("Could not launch the browser:\n\n%1", error), i18n("Could not launch Browser")); } return; } } } else { const KService::Ptr htmlApp = KMimeTypeTrader::self()->preferredService(QStringLiteral("text/html")); if (htmlApp) { // WORKAROUND: For bugs 264562 and 265474: // In order to correctly handle non-HTML urls we change the service // desktop file name to "kfmclient.desktop" whenever the above query // returns "kfmclient_html.desktop".Otherwise, the hard coded mime-type // "text/html" mime-type parameter in the kfmclient_html will cause all // URLs to be treated as if they are HTML page. QString entryPath = htmlApp->entryPath(); if (entryPath.endsWith(QLatin1String("kfmclient_html.desktop"))) { entryPath.remove(entryPath.length() - 13, 5); } QString error; int pid = 0; int err = startServiceByDesktopPath(entryPath, url, &error, nullptr, &pid, startup_id); if (err != 0) { KMessage::message(KMessage::Error, // TODO: i18n("Could not launch %1:\n\n%2", htmlApp->exec(), error), i18n("Could not launch the browser:\n\n%1", error), i18n("Could not launch Browser")); } else { // success return; } } else { exe = xdg_open; } } } if (exe.isEmpty()) { exe = QStringLiteral("kde-open"); // it's from kdebase-runtime, it has to be there. } //qDebug() << "Using" << exe << "to open" << url; if (kdeinitExec(exe, args, &error, nullptr, startup_id)) { KMessage::message(KMessage::Error, // TODO: i18n("Could not launch %1:\n\n%2", exe, error), i18n("Could not launch the browser:\n\n%1", error), i18n("Could not launch Browser")); } } void KToolInvocation::invokeTerminal(const QString &command, const QString &workdir, const QByteArray &startup_id) { if (!isMainThreadActive()) { return; } KConfigGroup confGroup(KSharedConfig::openConfig(), "General"); QString exec = confGroup.readPathEntry("TerminalApplication", QStringLiteral("konsole")); if (!command.isEmpty()) { if (exec == QLatin1String("konsole")) { exec += QLatin1String(" --noclose"); } else if (exec == QLatin1String("xterm")) { exec += QLatin1String(" -hold"); } exec += QLatin1String(" -e ") + command; } QStringList cmdTokens = KShell::splitArgs(exec); QString cmd = cmdTokens.takeFirst(); if (exec == QLatin1String("konsole") && !workdir.isEmpty()) { cmdTokens << QStringLiteral("--workdir"); cmdTokens << workdir; // For other terminals like xterm, we'll simply change the working // directory before launching them, see below. } QString error; if (self()->startServiceInternal("kdeinit_exec_with_workdir", cmd, cmdTokens, &error, nullptr, nullptr, startup_id, false, workdir)) { KMessage::message(KMessage::Error, i18n("Could not launch the terminal client:\n\n%1", error), i18n("Could not launch Terminal Client")); } } diff --git a/src/plugin/kplugintrader.h b/src/plugin/kplugintrader.h index daddb2e..9774447 100644 --- a/src/plugin/kplugintrader.h +++ b/src/plugin/kplugintrader.h @@ -1,268 +1,268 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Torben Weis Copyright (C) 2006 David Faure Copyright 2013 Sebastian Kügler 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 __kplugintrader_h__ #define __kplugintrader_h__ #include "kplugininfo.h" class KPluginTraderPrivate; /** * \class KPluginTrader kplugintrader.h * * A trader interface which provides a way to query specific subdirectories in the Qt * plugin paths for plugins. KPluginTrader provides an easy way to load a plugin * instance from a KPluginFactory, or just querying for existing plugins. * * KPluginTrader provides a way for an application to query directories in the * Qt plugin paths, accessed through QCoreApplication::libraryPaths(). * Plugins may match a specific set of requirements. This allows to find * specific plugins at run-time without having to hard-code their names and/or * paths. KPluginTrader does not search recursively, you are rather encouraged * to install plugins into specific subdirectories to further speed searching. * * KPluginTrader exclusively searches within the plugin binaries' metadata * (via QPluginLoader::metaData()). It does not search these directories recursively. * * KPluginTrader does not use KServiceTypeTrader or KSyCoCa. As such, it will * only find binary plugins. If you are looking for a generic way to query for * services, use KServiceTypeTrader. For anything relating to mimetypes (type * of files), use KMimeTypeTrader. * * \par Example * * If you want to find all plugins for your application, * you would define a KMyApp/Plugin servicetype, and then you can query * the trader for it: * \code * KPluginInfo::List offers = * KPluginTrader::self()->query("KMyApp/Plugin", "kf5"); * \endcode * * You can add a constraint in the "trader query language". For instance: * \code * KPluginTrader::self()->query("KMyApp/Plugin", "kf5", * "[X-KMyApp-InterfaceVersion] > 15"); * \endcode * * Please note that when including property names containing arithmetic operators like - or +, then you have * to put brackets around the property name, in order to correctly separate arithmetic operations from * the name. So for example a constraint expression like * \code * X-KMyApp-InterfaceVersion > 4 // wrong! * \endcode * needs to be written as * \code * [X-KMyApp-InterfaceVersion] > 4 * \endcode * otherwise it could also be interpreted as * Subtract the numeric value of the property "KMyApp" and "InterfaceVersion" from the * property "X" and make sure it is greater than 4.\n * Instead of the other meaning, make sure that the numeric value of "X-KMyApp-InterfaceVersion" is * greater than 4. * * @see KMimeTypeTrader, KServiceTypeTrader, KPluginInfo * @see QCoreApplication::libraryPaths * @see QT_PLUGIN_PATH (env variable) * @see KPluginFactory * @see kservice_desktop_to_json (Cmake macro) * @see K_PLUGIN_FACTORY_WITH_JSON (macro defined in KPluginFactory) * * @since 5.0 */ class KSERVICE_EXPORT KPluginTrader { public: /** * Standard destructor */ ~KPluginTrader(); /** * The main function in the KPluginTrader class. * * It will return a list of plugins that match your specifications. Required parameter is the * service type and subdirectory. This method will append the subDirectory to every path found * in QCoreApplication::libraryPaths(), append the subDirectory parameter, and search through * the plugin's metadata * * KPluginTrader exclusively searches within the plugin binaries' metadata * (via QPluginLoader::metaData()). It does not search these directories recursively. * * 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 * (Type == 'Service') and (('KParts/ReadOnlyPart' in ServiceTypes) or (exist Exec)) * \endcode * * If you want to load a list of plugins from a specific subdirectory, you can do the following: * * \code * * KPluginInfo::List plugins = KPluginTrader::self()->query("plasma/engines"); * * foreach (const KPluginInfo &info, plugins) { * KPluginLoader loader(info.libraryPath()); * const QVariantList argsWithMetaData = QVariantList() << loader.metaData().toVariantMap(); * // In many cases, plugins are actually based on KPluginFactory, this is how that works: * KPluginFactory* factory = loader.factory(); * if (factory) { * Engine* component = factory->create(parent, argsWithMetaData); * if (component) { * // Do whatever you want to do with the resulting object * } * } * // Otherwise, just use the normal QPluginLoader methods * Engine *myengine = qobject_cast(loader.instance()); * if (myengine) { * // etc. ... * } * } * \endcode * * If you have a specific query for just one plugin, use the createInstanceFromQuery method. * * The keys used in the query (Type, ServiceType, Exec) are all fields found in the .json files * which are compiled into the plugin binaries. * * @param subDirectory The subdirectory under the Qt plugin path * @param servicetype A service type like 'KMyApp/Plugin' or 'KFilePlugin' * @param constraint A constraint to limit the choices returned, QString() to * get all services of the given @p servicetype * * @return A list of services that satisfy the query * @see http://techbase.kde.org/Development/Tutorials/Services/Traders#The_KTrader_Query_Language */ KPluginInfo::List query(const QString &subDirectory, const QString &serviceType = QString(), const QString &constraint = QString()); /** * This is a static pointer to the KPluginTrader singleton. * * You will need to use this to access the KPluginTrader functionality since the * constructors are protected. * * @return Static KPluginTrader instance */ static KPluginTrader *self(); /** * Get a plugin from a trader query * * Example: * \code * KMyAppPlugin* plugin = KPluginTrader::createInstanceFromQuery(subDirectory, serviceType, QString(), parentObject ); * if ( plugin ) { * .... * } * \endcode * * @param subDirectory The subdirectory under the Qt plugin pathes to search in * @param serviceType The type of service for which to find a plugin * @param constraint An optional constraint to pass to the trader (see KTrader) * @param parent The parent object for the part itself * @param args A list of arguments passed to the service component * @param error The string passed here will contain an error description. * @return A pointer to the newly created object or a null pointer if the * factory was unable to create an object of the given type. */ template static T *createInstanceFromQuery(const QString &subDirectory, const QString &serviceType = QString(), const QString &constraint = QString(), QObject *parent = nullptr, const QVariantList &args = QVariantList(), QString *error = nullptr) { return createInstanceFromQuery(subDirectory, serviceType, constraint, parent, nullptr, args, error); } /** * Get a plugin from a trader query * * This method works like * createInstanceFromQuery(const QString&, const QString& ,const QString&, QObject*, * const QVariantList&, QString*), * but you can specify an additional parent widget. This is important for a KPart, for example. * * @param subDirectory The subdirectory under the Qt plugin pathes to search in * @param serviceType the type of service for which to find a plugin * @param constraint an optional constraint to pass to the trader (see KTrader) * @param parent the parent object for the part itself * @param parentWidget the parent widget for the plugin * @param args A list of arguments passed to the service component * @param error The string passed here will contain an error description. * @return A pointer to the newly created object or a null pointer if the * factory was unable to create an object of the given type. */ template static T *createInstanceFromQuery(const QString &subDirectory, const QString &serviceType, const QString &constraint, QObject *parent, QWidget *parentWidget, const QVariantList &args = QVariantList(), QString *error = nullptr) { Q_UNUSED(parentWidget) Q_UNUSED(args) if (error) { error->clear(); } const KPluginInfo::List offers = self()->query(subDirectory, serviceType, constraint); - Q_FOREACH (const KPluginInfo &info, offers) { + for (const KPluginInfo &info : offers) { KPluginLoader loader(info.libraryPath()); const QVariantList argsWithMetaData = QVariantList() << loader.metaData().toVariantMap(); KPluginFactory *factory = loader.factory(); if (factory) { T *component = factory->create(parent, argsWithMetaData); if (component) { return component; } } } if (error && error->isEmpty()) { *error = QCoreApplication::translate("", "No service matching the requirements was found"); } return nullptr; } static void applyConstraints(KPluginInfo::List &lst, const QString &constraint); private: /** * @internal */ KPluginTrader(); // disallow copy ctor and assignment operator KPluginTrader(const KPluginTrader &other); KPluginTrader &operator=(const KPluginTrader &rhs); KPluginTraderPrivate *const d; friend class KPluginTraderSingleton; }; #endif diff --git a/src/services/kmimetypetrader.h b/src/services/kmimetypetrader.h index bc16c7a..520b184 100644 --- a/src/services/kmimetypetrader.h +++ b/src/services/kmimetypetrader.h @@ -1,208 +1,208 @@ /* 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 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 KMIMETYPETRADER_H #define KMIMETYPETRADER_H #include class KMimeTypeTraderPrivate; class KServiceOffer; typedef QList KServiceOfferList; /** * @class KMimeTypeTrader kmimetypetrader.h * * KDE's trader for services associated to a given mimetype. * * Example: say that you want to the list of all KParts components that can handle HTML. * Our code would look like: * \code * KService::List lst = KMimeTypeTrader::self()->query("text/html", * "KParts/ReadOnlyPart"); * \endcode * * If you want to get the preferred KParts component for text/html you would use: * @code * KService::Ptr service = KMimeTypeTrader::self()->preferredService("text/html", * "KParts/ReadOnlyPart"); * @endcode * Although if this is about loading that component you would use createPartInstanceFromQuery() directly. * * @see KServiceTypeTrader, KService */ class KSERVICE_EXPORT KMimeTypeTrader { public: /** * Standard destructor */ ~KMimeTypeTrader(); /** * This method returns a list of services which are associated with a given mimetype. * * Example usage: * To get list of applications that can handle a given mimetype, * set @p genericServiceType to "Application" (which is the default). * To get list of embeddable components that can handle a given mimetype, * set @p genericServiceType to "KParts/ReadOnlyPart". * * 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 * (Type == 'Service') and (('Browser/View' in ServiceTypes) and (exist Library)) * \endcode * * The keys used in the query (Type, ServiceTypes, Library) are all * fields found in the .desktop files. * * @param mimeType A mime type like 'text/plain' or 'text/html'. * @param genericServiceType a basic service type, like 'KParts/ReadOnlyPart' or 'Application' * @param constraint A constraint to limit the choices returned, QString() to * get all services 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 query(const QString &mimeType, const QString &genericServiceType = QStringLiteral("Application"), const QString &constraint = QString()) const; /** * Returns the preferred service for @p mimeType and @p genericServiceType * * This is almost like query().first(), except that it also checks * if the service is allowed as a preferred service (see KService::allowAsDefault). * * @param mimeType the mime type (see query()) * @param genericServiceType the service type (see query()) * @return the preferred service, or @c nullptr if no service is available */ KService::Ptr preferredService(const QString &mimeType, const QString &genericServiceType = QStringLiteral("Application")); /** * This method creates and returns a part object from the trader query for a given \p mimeType. * * Example: * \code * KParts::ReadOnlyPart* part = KMimeTypeTrader::createPartInstanceFromQuery("text/plain", parentWidget, parentObject); * if (part) { * part->openUrl(url); * part->widget()->show(); // also insert the widget into a layout * } * \endcode * * @param mimeType the mimetype which this part is associated with * @param parentWidget the parent widget, will be set as the parent of the part's widget * @param parent the parent object for the part itself * @param constraint an optional constraint to pass to the trader * @param args A list of arguments passed to the service component * @param error The string passed here will contain an error description. * @return A pointer to the newly created object or a null pointer if the * factory was unable to create an object of the given type. */ template static T *createPartInstanceFromQuery(const QString &mimeType, QWidget *parentWidget = nullptr, QObject *parent = nullptr, const QString &constraint = QString(), const QVariantList &args = QVariantList(), QString *error = nullptr) { const KService::List offers = self()->query(mimeType, QStringLiteral("KParts/ReadOnlyPart"), constraint); - Q_FOREACH (const KService::Ptr &ptr, offers) { + for (const KService::Ptr &ptr : offers) { T *component = ptr->template createInstance(parentWidget, parent, args, error); if (component) { if (error) { error->clear(); } return component; } } if (error) { *error = QCoreApplication::translate("", "No service matching the requirements was found"); } return nullptr; } /** * This can be used to create a service instance from a mime type query * * @param mimeType A mime type like 'text/plain' or 'text/html'. * @param serviceType a basic service type * @param parent the parent object for the plugin itself * @param constraint A constraint to limit the choices returned, QString() to * get all services that can handle the given @p mimetype * @param args A list of arguments passed to the service component * @param error The string passed here will contain an error description. * @return A pointer to the newly created object or a null pointer if the * factory was unable to create an object of the given type. */ template static T *createInstanceFromQuery(const QString &mimeType, const QString &serviceType, QObject *parent = nullptr, const QString &constraint = QString(), const QVariantList &args = QVariantList(), QString *error = nullptr) { const KService::List offers = self()->query(mimeType, serviceType, constraint); - Q_FOREACH (const KService::Ptr &ptr, offers) { + for (const KService::Ptr &ptr : offers) { T *component = ptr->template createInstance(parent, args, error); if (component) { if (error) { error->clear(); } return component; } } if (error) { *error = QCoreApplication::translate("", "No service matching the requirements was found"); } return nullptr; } /** * This is a static pointer to the KMimeTypeTrader singleton. * * You will need to use this to access the KMimeTypeTrader functionality since the * constructors are protected. * * @return Static KMimeTypeTrader instance */ static KMimeTypeTrader *self(); private: /** * @internal */ KMimeTypeTrader(); private: KMimeTypeTraderPrivate *const d; // class-static so that it can access KSycocaEntry::offset() static void filterMimeTypeOffers(KServiceOfferList &list, const QString &genericServiceType); static void filterMimeTypeOffers(KService::List &list, const QString &genericServiceType); friend class KMimeTypeTraderSingleton; }; #endif /* KMIMETYPETRADER_H */ diff --git a/src/services/kplugininfo.cpp b/src/services/kplugininfo.cpp index b18904a..53fc1e2 100644 --- a/src/services/kplugininfo.cpp +++ b/src/services/kplugininfo.cpp @@ -1,769 +1,770 @@ /* This file is part of the KDE project Copyright (C) 2003,2007 Matthias Kretz Copyright 2013 Sebastian Kügler 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 "kplugininfo.h" #include #include "servicesdebug.h" #include #include #include #include #include "ksycoca.h" #include "ksycoca_p.h" #include #include #include #include #include #include #include //#ifndef NDEBUG #define KPLUGININFO_ISVALID_ASSERTION \ do { \ if (!d) { \ qFatal("Accessed invalid KPluginInfo object"); \ } \ } while (false) //#else //#define KPLUGININFO_ISVALID_ASSERTION //#endif #define GlobalQStringLiteral(name, str) inline QString name() { return QStringLiteral(str); } namespace { GlobalQStringLiteral(s_hiddenKey, "Hidden") GlobalQStringLiteral(s_nameKey, "Name") GlobalQStringLiteral(s_commentKey, "Comment") GlobalQStringLiteral(s_iconKey, "Icon") GlobalQStringLiteral(s_libraryKey, "X-KDE-Library") GlobalQStringLiteral(s_authorKey, "X-KDE-PluginInfo-Author") GlobalQStringLiteral(s_emailKey, "X-KDE-PluginInfo-Email") GlobalQStringLiteral(s_pluginNameKey, "X-KDE-PluginInfo-Name") GlobalQStringLiteral(s_versionKey, "X-KDE-PluginInfo-Version") GlobalQStringLiteral(s_websiteKey, "X-KDE-PluginInfo-Website") GlobalQStringLiteral(s_categoryKey, "X-KDE-PluginInfo-Category") GlobalQStringLiteral(s_licenseKey, "X-KDE-PluginInfo-License") GlobalQStringLiteral(s_dependenciesKey, "X-KDE-PluginInfo-Depends") GlobalQStringLiteral(s_serviceTypesKey, "ServiceTypes") GlobalQStringLiteral(s_xKDEServiceTypes, "X-KDE-ServiceTypes") GlobalQStringLiteral(s_mimeTypeKey, "MimeType") GlobalQStringLiteral(s_formFactorsKey, "X-KDE-FormFactors") GlobalQStringLiteral(s_enabledbyDefaultKey, "X-KDE-PluginInfo-EnabledByDefault") GlobalQStringLiteral(s_enabledKey, "Enabled") // these keys are used in the json metadata GlobalQStringLiteral(s_jsonDescriptionKey, "Description") GlobalQStringLiteral(s_jsonAuthorsKey, "Authors") GlobalQStringLiteral(s_jsonEmailKey, "Email") GlobalQStringLiteral(s_jsonCategoryKey, "Category") GlobalQStringLiteral(s_jsonDependenciesKey, "Dependencies") GlobalQStringLiteral(s_jsonEnabledByDefaultKey, "EnabledByDefault") GlobalQStringLiteral(s_jsonFormFactorsKey, "FormFactors") GlobalQStringLiteral(s_jsonLicenseKey, "License") GlobalQStringLiteral(s_jsonIdKey, "Id") GlobalQStringLiteral(s_jsonVersionKey, "Version") GlobalQStringLiteral(s_jsonWebsiteKey, "Website") GlobalQStringLiteral(s_jsonMimeTypesKey, "MimeTypes") GlobalQStringLiteral(s_jsonKPluginKey, "KPlugin") } class KPluginInfoPrivate : public QSharedData { public: KPluginInfoPrivate() : hidden(false) , pluginenabled(false) , kcmservicesCached(false) {} static QStringList deserializeList(const QString &data); bool hidden : 1; bool pluginenabled : 1; mutable bool kcmservicesCached : 1; KPluginMetaData metaData; KConfigGroup config; KService::Ptr service; mutable QList kcmservices; /** assigns the @p md to @c metaData, but also ensures that compatibility values are handled */ void setMetaData(const KPluginMetaData &md, bool warnOnOldStyle); }; //This comes from KConfigGroupPrivate::deserializeList() QStringList KPluginInfoPrivate::deserializeList(const QString &data) { if (data.isEmpty()) { return QStringList(); } if (data == QLatin1String("\\0")) { return QStringList(QString()); } QStringList value; QString val; val.reserve(data.size()); bool quoted = false; for (int p = 0; p < data.length(); p++) { if (quoted) { val += data[p]; quoted = false; } else if (data[p].unicode() == '\\') { quoted = true; } else if (data[p].unicode() == ',' || data[p].unicode() == ';') { val.squeeze(); // release any unused memory value.append(val); val.clear(); val.reserve(data.size() - p); } else { val += data[p]; } } value.append(val); return value; } // maps the KService, QVariant and KDesktopFile keys to the new KPluginMetaData keys template static QJsonObject mapToJsonKPluginKey(const QString &name, const QString &description, const QStringList &dependencies, const QStringList &serviceTypes, const QStringList &formFactors, const T &data, Func accessor) { /* Metadata structure is as follows: "KPlugin": { "Name": "Date and Time", "Description": "Date and time by timezone", "Icon": "preferences-system-time", "Authors": { "Name": "Aaron Seigo", "Email": "aseigo@kde.org" }, "Category": "Date and Time", "Dependencies": [], "EnabledByDefault": "true", "License": "LGPL", "Id": "time", "Version": "1.0", "Website": "http://plasma.kde.org/", "ServiceTypes": ["Plasma/DataEngine"] "FormFactors": ["tablet", "handset"] } */ QJsonObject kplugin; kplugin[s_nameKey()] = name; kplugin[s_jsonDescriptionKey()] = description; kplugin[s_iconKey()] = accessor(data, s_iconKey()); QJsonObject authors; authors[s_nameKey()] = accessor(data, s_authorKey()); authors[s_jsonEmailKey()] = accessor(data, s_emailKey()); kplugin[s_jsonAuthorsKey()] = authors; kplugin[s_jsonCategoryKey()] = accessor(data, s_categoryKey()); QJsonValue enabledByDefault = accessor(data, s_enabledbyDefaultKey()); // make sure that enabledByDefault is bool and not string if (!enabledByDefault.isBool()) { enabledByDefault = enabledByDefault.toString().compare(QLatin1String("true"), Qt::CaseInsensitive) == 0; } kplugin[s_jsonEnabledByDefaultKey()] = enabledByDefault; kplugin[s_jsonLicenseKey()] = accessor(data, s_licenseKey()); kplugin[s_jsonIdKey()] = accessor(data, s_pluginNameKey()); kplugin[s_jsonVersionKey()] = accessor(data, s_versionKey()); kplugin[s_jsonWebsiteKey()] = accessor(data, s_websiteKey()); kplugin[s_jsonFormFactorsKey()] = QJsonArray::fromStringList(formFactors); kplugin[s_serviceTypesKey()] = QJsonArray::fromStringList(serviceTypes); kplugin[s_jsonDependenciesKey()] = QJsonArray::fromStringList(dependencies); QJsonValue mimeTypes = accessor(data, s_mimeTypeKey()); if (mimeTypes.isString()) { QStringList mimeList = KPluginInfoPrivate::deserializeList(mimeTypes.toString()); if (!mimeList.isEmpty()) { mimeTypes = QJsonArray::fromStringList(mimeList); } else { mimeTypes = QJsonValue(); } } kplugin[s_jsonMimeTypesKey()] = mimeTypes; return kplugin; } // TODO: KF6 remove static KPluginMetaData fromCompatibilityJson(const QJsonObject &json, const QString &lib, const QString &metaDataFile, bool warnOnOldStyle) { // This is not added to KPluginMetaData(QJsonObject, QString) to ensure that all the compatility code // remains in kservice and does not increase the size of kcoreaddons QStringList serviceTypes = KPluginMetaData::readStringList(json, s_xKDEServiceTypes()); if (serviceTypes.isEmpty()) { serviceTypes = KPluginMetaData::readStringList(json, s_serviceTypesKey()); } QJsonObject obj = json; QString name = KPluginMetaData::readTranslatedString(json, s_nameKey()); if (warnOnOldStyle) { qWarning("Constructing a KPluginInfo object from old style JSON. Please use" " kcoreaddons_desktop_to_json() for \"%s\" instead of kservice_desktop_to_json()" " in your CMake code.", qPrintable(lib)); } QString description = KPluginMetaData::readTranslatedString(json, s_commentKey()); QStringList formfactors = KPluginMetaData::readStringList(json, s_jsonFormFactorsKey()); QJsonObject kplugin = mapToJsonKPluginKey(name, description, KPluginMetaData::readStringList(json, s_dependenciesKey()), serviceTypes, formfactors, json, [](const QJsonObject &o, const QString &key) { return o.value(key); }); obj.insert(s_jsonKPluginKey(), kplugin); return KPluginMetaData(obj, lib, metaDataFile); } void KPluginInfoPrivate::setMetaData(const KPluginMetaData& md, bool warnOnOldStyle) { const QJsonObject json = md.rawData(); if (!json.contains(s_jsonKPluginKey())) { // "KPlugin" key does not exists -> convert from compatibility mode metaData = fromCompatibilityJson(json, md.fileName(), md.metaDataFileName(), warnOnOldStyle); } else { metaData = md; } } KPluginInfo::KPluginInfo(const KPluginMetaData &md) :d(new KPluginInfoPrivate) { d->setMetaData(md, true); if (!d->metaData.isValid()) { d.reset(); } } KPluginInfo::KPluginInfo(const QString &filename /*, QStandardPaths::StandardLocation resource*/) : d(new KPluginInfoPrivate) { KDesktopFile file(/*resource,*/ filename); KConfigGroup cg = file.desktopGroup(); if (!cg.exists()) { qCWarning(SERVICES) << filename << "has no desktop group, cannot construct a KPluginInfo object from it."; d.reset(); return; } d->hidden = cg.readEntry(s_hiddenKey(), false); if (d->hidden) { return; } d->setMetaData(KPluginMetaData(file.fileName()), true); if (!d->metaData.isValid()) { qCWarning(SERVICES) << "Failed to read metadata from .desktop file" << file.fileName(); d.reset(); } } KPluginInfo::KPluginInfo(const QVariantList &args, const QString &libraryPath) : d(new KPluginInfoPrivate) { const QString metaData = QStringLiteral("MetaData"); - foreach (const QVariant &v, args) { + for (const QVariant &v : args) { if (v.canConvert()) { const QVariantMap &m = v.toMap(); const QVariant &_metadata = m.value(metaData); if (_metadata.canConvert()) { const QVariantMap &map = _metadata.toMap(); if (map.value(s_hiddenKey()).toBool()) { d->hidden = true; break; } d->setMetaData(KPluginMetaData(QJsonObject::fromVariantMap(map), libraryPath), true); break; } } } if (!d->metaData.isValid()) { d.reset(); } } #ifndef KSERVICE_NO_DEPRECATED KPluginInfo::KPluginInfo(const KService::Ptr service) : d(new KPluginInfoPrivate) { if (!service) { d = nullptr; // isValid() == false return; } d->service = service; if (service->isDeleted()) { d->hidden = true; return; } KSycoca::self()->ensureCacheValid(); QJsonObject json; - foreach (const QString &key, service->propertyNames()) { + const auto propertyList = service->propertyNames(); + for (const QString &key : propertyList) { QVariant::Type t = KSycocaPrivate::self()->serviceTypeFactory()->findPropertyTypeByName(key); if (t == QVariant::Invalid) { t = QVariant::String; // default to string if the type is not known } QVariant v = service->property(key, t); if (v.isValid()) { json[key] = QJsonValue::fromVariant(v); } } // reintroduce the separation between MimeType= and X-KDE-ServiceTypes= // we could do this by modifying KService and KSyCoCa, but as this is only compatibility // code we just query QMimeDatabase whether a ServiceType is a valid MIME type. // TODO: should we also make sure invalid MimeType= entries end up in KPluginMetaData::mimeTypes()? const QStringList services = service->serviceTypes(); if (!services.isEmpty()) { QMimeDatabase db; QStringList mimeTypes; mimeTypes.reserve(services.size()); QStringList newServiceTypes; newServiceTypes.reserve(services.size()); - foreach (const QString& s, services) { + for (const QString& s : services) { if (db.mimeTypeForName(s).isValid()) { mimeTypes << s; } else { newServiceTypes << s; } } json[s_mimeTypeKey()] = QJsonArray::fromStringList(mimeTypes); json[s_xKDEServiceTypes()] = QJsonArray::fromStringList(newServiceTypes); json[s_serviceTypesKey()] = QJsonValue(); } d->setMetaData(KPluginMetaData(json, service->library(), service->entryPath()), false); if (!d->metaData.isValid()) { d.reset(); } } #endif KPluginInfo::KPluginInfo() : d(nullptr) // isValid() == false { } bool KPluginInfo::isValid() const { return d.data() != nullptr; } KPluginInfo::KPluginInfo(const KPluginInfo &rhs) : d(rhs.d) { } KPluginInfo &KPluginInfo::operator=(const KPluginInfo &rhs) { d = rhs.d; return *this; } bool KPluginInfo::operator==(const KPluginInfo &rhs) const { return d == rhs.d; } bool KPluginInfo::operator!=(const KPluginInfo &rhs) const { return d != rhs.d; } bool KPluginInfo::operator<(const KPluginInfo &rhs) const { if (category() < rhs.category()) { return true; } if (category() == rhs.category()) { return name() < rhs.name(); } return false; } bool KPluginInfo::operator>(const KPluginInfo &rhs) const { if (category() > rhs.category()) { return true; } if (category() == rhs.category()) { return name() > rhs.name(); } return false; } KPluginInfo::~KPluginInfo() { } #ifndef KSERVICE_NO_DEPRECATED QList KPluginInfo::fromServices(const KService::List &services, const KConfigGroup &config) { QList infolist; for (KService::List::ConstIterator it = services.begin(); it != services.end(); ++it) { KPluginInfo info(*it); if (info.isValid()) { info.setConfig(config); infolist += info; } } return infolist; } #endif QList KPluginInfo::fromFiles(const QStringList &files, const KConfigGroup &config) { QList infolist; for (QStringList::ConstIterator it = files.begin(); it != files.end(); ++it) { KPluginInfo info(*it); info.setConfig(config); infolist += info; } return infolist; } QList KPluginInfo::fromKPartsInstanceName(const QString &name, const KConfigGroup &config) { QStringList files; const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, name + QStringLiteral("/kpartplugins"), QStandardPaths::LocateDirectory); - Q_FOREACH (const QString &dir, dirs) { + for (const QString &dir : dirs) { QDirIterator it(dir, QStringList() << QStringLiteral("*.desktop")); while (it.hasNext()) { files.append(it.next()); } } return fromFiles(files, config); } bool KPluginInfo::isHidden() const { KPLUGININFO_ISVALID_ASSERTION; return d->hidden; } void KPluginInfo::setPluginEnabled(bool enabled) { KPLUGININFO_ISVALID_ASSERTION; //qDebug() << Q_FUNC_INFO; d->pluginenabled = enabled; } bool KPluginInfo::isPluginEnabled() const { KPLUGININFO_ISVALID_ASSERTION; //qDebug() << Q_FUNC_INFO; return d->pluginenabled; } bool KPluginInfo::isPluginEnabledByDefault() const { KPLUGININFO_ISVALID_ASSERTION; //qDebug() << Q_FUNC_INFO; return d->metaData.isEnabledByDefault(); } QString KPluginInfo::name() const { KPLUGININFO_ISVALID_ASSERTION; return d->metaData.name(); } QString KPluginInfo::comment() const { KPLUGININFO_ISVALID_ASSERTION; return d->metaData.description(); } QString KPluginInfo::icon() const { KPLUGININFO_ISVALID_ASSERTION; return d->metaData.iconName(); } QString KPluginInfo::entryPath() const { KPLUGININFO_ISVALID_ASSERTION; return d->metaData.metaDataFileName(); } QString KPluginInfo::author() const { KPLUGININFO_ISVALID_ASSERTION; const QList &authors = d->metaData.authors(); return authors.isEmpty() ? QString() : authors[0].name(); } QString KPluginInfo::email() const { KPLUGININFO_ISVALID_ASSERTION; const QList &authors = d->metaData.authors(); return authors.isEmpty() ? QString() : authors[0].emailAddress(); } QString KPluginInfo::category() const { KPLUGININFO_ISVALID_ASSERTION; return d->metaData.category(); } QStringList KPluginInfo::formFactors() const { KPLUGININFO_ISVALID_ASSERTION; return d->metaData.formFactors(); } QString KPluginInfo::pluginName() const { KPLUGININFO_ISVALID_ASSERTION; return d->metaData.pluginId(); } QString KPluginInfo::libraryPath() const { KPLUGININFO_ISVALID_ASSERTION; return d->metaData.fileName(); } QString KPluginInfo::version() const { KPLUGININFO_ISVALID_ASSERTION; return d->metaData.version(); } QString KPluginInfo::website() const { KPLUGININFO_ISVALID_ASSERTION; return d->metaData.website(); } QString KPluginInfo::license() const { KPLUGININFO_ISVALID_ASSERTION; return d->metaData.license(); } #if 0 KAboutLicense KPluginInfo::fullLicense() const { KPLUGININFO_ISVALID_ASSERTION; return KAboutLicense::byKeyword(d->license); } #endif QStringList KPluginInfo::dependencies() const { KPLUGININFO_ISVALID_ASSERTION; return d->metaData.dependencies(); } QStringList KPluginInfo::serviceTypes() const { KPLUGININFO_ISVALID_ASSERTION; // KService/KPluginInfo include the MIME types in serviceTypes() return d->metaData.serviceTypes() + d->metaData.mimeTypes(); } KService::Ptr KPluginInfo::service() const { KPLUGININFO_ISVALID_ASSERTION; return d->service; } QList KPluginInfo::kcmServices() const { KPLUGININFO_ISVALID_ASSERTION; if (!d->kcmservicesCached) { d->kcmservices = KServiceTypeTrader::self()->query(QStringLiteral("KCModule"), QLatin1Char('\'') + pluginName() + QStringLiteral("' in [X-KDE-ParentComponents]")); //qDebug() << "found" << d->kcmservices.count() << "offers for" << d->pluginName; d->kcmservicesCached = true; } return d->kcmservices; } void KPluginInfo::setConfig(const KConfigGroup &config) { KPLUGININFO_ISVALID_ASSERTION; d->config = config; } KConfigGroup KPluginInfo::config() const { KPLUGININFO_ISVALID_ASSERTION; return d->config; } QVariant KPluginInfo::property(const QString &key) const { KPLUGININFO_ISVALID_ASSERTION; if (d->service) { return d->service->property(key); } QVariant result = d->metaData.rawData().value(key).toVariant(); if (result.isValid()) { KSycoca::self()->ensureCacheValid(); const QVariant::Type t = KSycocaPrivate::self()->serviceTypeFactory()->findPropertyTypeByName(key); //special case if we want a stringlist: split values by ',' or ';' and construct the list if (t == QVariant::StringList) { if (result.canConvert()) { result = KPluginInfoPrivate::deserializeList(result.toString()); } else if (result.canConvert()) { - QVariantList list = result.toList(); + const QVariantList list = result.toList(); QStringList newResult; - foreach (const QVariant &value, list) { + for (const QVariant &value : list) { newResult += value.toString(); } result = newResult; } else qCWarning(SERVICES) << "Cannot interpret" << result << "into a string list"; } return result; } // If the key was not found check compatibility for old key names and print a warning // These warnings should only happen if JSON was generated with kcoreaddons_desktop_to_json // but the application uses KPluginTrader::query() instead of KPluginLoader::findPlugins() // TODO: KF6 remove #define RETURN_WITH_DEPRECATED_WARNING(ret) \ qWarning("Calling KPluginInfo::property(\"%s\") is deprecated, use KPluginInfo::" #ret " in \"%s\" instead.", qPrintable(key), qPrintable(d->metaData.fileName()));\ return ret; if (key == s_authorKey()) { RETURN_WITH_DEPRECATED_WARNING(author()); } else if (key == s_categoryKey()) { RETURN_WITH_DEPRECATED_WARNING(category()); } else if (key == s_commentKey()) { RETURN_WITH_DEPRECATED_WARNING(comment()); } else if (key == s_dependenciesKey()) { RETURN_WITH_DEPRECATED_WARNING(dependencies()); } else if (key == s_emailKey()) { RETURN_WITH_DEPRECATED_WARNING(email()); } else if (key == s_enabledbyDefaultKey()) { RETURN_WITH_DEPRECATED_WARNING(isPluginEnabledByDefault()); } else if (key == s_libraryKey()) { RETURN_WITH_DEPRECATED_WARNING(libraryPath()); } else if (key == s_licenseKey()) { RETURN_WITH_DEPRECATED_WARNING(license()); } else if (key == s_nameKey()) { RETURN_WITH_DEPRECATED_WARNING(name()); } else if (key == s_pluginNameKey()) { RETURN_WITH_DEPRECATED_WARNING(pluginName()); } else if (key == s_serviceTypesKey()) { RETURN_WITH_DEPRECATED_WARNING(serviceTypes()); } else if (key == s_versionKey()) { RETURN_WITH_DEPRECATED_WARNING(version()); } else if (key == s_websiteKey()) { RETURN_WITH_DEPRECATED_WARNING(website()); } else if (key == s_xKDEServiceTypes()) { RETURN_WITH_DEPRECATED_WARNING(serviceTypes()); } else if (key == s_formFactorsKey()) { RETURN_WITH_DEPRECATED_WARNING(formFactors()); } #undef RETURN_WITH_DEPRECATED_WARNING // not a compatibility key -> return invalid QVariant return result; } QVariantMap KPluginInfo::properties() const { return d->metaData.rawData().toVariantMap(); } void KPluginInfo::save(KConfigGroup config) { KPLUGININFO_ISVALID_ASSERTION; //qDebug() << Q_FUNC_INFO; if (config.isValid()) { config.writeEntry(pluginName() + s_enabledKey(), isPluginEnabled()); } else { if (!d->config.isValid()) { qCWarning(SERVICES) << "no KConfigGroup, cannot save"; return; } d->config.writeEntry(pluginName() + s_enabledKey(), isPluginEnabled()); } } void KPluginInfo::load(const KConfigGroup &config) { KPLUGININFO_ISVALID_ASSERTION; //qDebug() << Q_FUNC_INFO; if (config.isValid()) { setPluginEnabled(config.readEntry(pluginName() + s_enabledKey(), isPluginEnabledByDefault())); } else { if (!d->config.isValid()) { qCWarning(SERVICES) << "no KConfigGroup, cannot load"; return; } setPluginEnabled(d->config.readEntry(pluginName() + s_enabledKey(), isPluginEnabledByDefault())); } } void KPluginInfo::defaults() { //qDebug() << Q_FUNC_INFO; setPluginEnabled(isPluginEnabledByDefault()); } uint qHash(const KPluginInfo &p) { return qHash(reinterpret_cast(p.d.data())); } KPluginInfo KPluginInfo::fromMetaData(const KPluginMetaData &md) { return KPluginInfo(md); } KPluginMetaData KPluginInfo::toMetaData() const { KPLUGININFO_ISVALID_ASSERTION; return d->metaData; } KPluginMetaData KPluginInfo::toMetaData(const KPluginInfo& info) { return info.toMetaData(); } KPluginInfo::List KPluginInfo::fromMetaData(const QVector &list) { KPluginInfo::List ret; ret.reserve(list.size()); - foreach(const KPluginMetaData &md, list) { + for(const KPluginMetaData &md : list) { ret.append(KPluginInfo::fromMetaData(md)); } return ret; } QVector KPluginInfo::toMetaData(const KPluginInfo::List &list) { QVector ret; ret.reserve(list.size()); - foreach(const KPluginInfo &info, list) { + for(const KPluginInfo &info : list) { ret.append(info.toMetaData()); } return ret; } #undef KPLUGININFO_ISVALID_ASSERTION diff --git a/src/services/kservice.cpp b/src/services/kservice.cpp index 78016df..1750846 100644 --- a/src/services/kservice.cpp +++ b/src/services/kservice.cpp @@ -1,1027 +1,1027 @@ /* This file is part of the KDE libraries * Copyright (C) 1999 - 2001 Waldo Bastian * Copyright (C) 1999 - 2005 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 "kservice.h" #include "kservice_p.h" #include "kmimetypefactory_p.h" #include "ksycoca.h" #include "ksycoca_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kservicefactory_p.h" #include "kservicetypefactory_p.h" #include "kserviceutil_p.h" #include "servicesdebug.h" QDataStream &operator<<(QDataStream &s, const KService::ServiceTypeAndPreference &st) { s << st.preference << st.serviceType; return s; } QDataStream &operator>>(QDataStream &s, KService::ServiceTypeAndPreference &st) { s >> st.preference >> st.serviceType; return s; } void KServicePrivate::init(const KDesktopFile *config, KService *q) { const QString entryPath = q->entryPath(); bool absPath = !QDir::isRelativePath(entryPath); // TODO: it makes sense to have a KConstConfigGroup I guess const KConfigGroup desktopGroup = const_cast(config)->desktopGroup(); QMap entryMap = desktopGroup.entryMap(); entryMap.remove(QStringLiteral("Encoding")); // reserved as part of Desktop Entry Standard entryMap.remove(QStringLiteral("Version")); // reserved as part of Desktop Entry Standard q->setDeleted(desktopGroup.readEntry("Hidden", false)); entryMap.remove(QStringLiteral("Hidden")); if (q->isDeleted()) { m_bValid = false; return; } m_strName = config->readName(); entryMap.remove(QStringLiteral("Name")); if (m_strName.isEmpty()) { // Try to make up a name. m_strName = entryPath; int i = m_strName.lastIndexOf(QLatin1Char('/')); m_strName = m_strName.mid(i + 1); i = m_strName.lastIndexOf(QLatin1Char('.')); if (i != -1) { m_strName.truncate(i); } } m_strType = config->readType(); entryMap.remove(QStringLiteral("Type")); if (m_strType.isEmpty()) { /*kWarning(servicesDebugArea()) << "The desktop entry file " << entryPath << " has no Type=... entry." << " It should be \"Application\" or \"Service\""; m_bValid = false; return;*/ m_strType = QStringLiteral("Application"); } else if (m_strType != QLatin1String("Application") && m_strType != QLatin1String("Service")) { qCWarning(SERVICES) << "The desktop entry file " << entryPath << " has Type=" << m_strType << " instead of \"Application\" or \"Service\""; m_bValid = false; return; } // NOT readPathEntry, it is not XDG-compliant: it performs // various expansions, like $HOME. Note that the expansion // behaviour still happens if the "e" flag is set, maintaining // backwards compatibility. m_strExec = desktopGroup.readEntry("Exec", QString()); entryMap.remove(QStringLiteral("Exec")); if (m_strType == QLatin1String("Application")) { // It's an application? Should have an Exec line then, otherwise we can't run it if (m_strExec.isEmpty()) { qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType << "but no Exec line"; m_bValid = false; return; } } // In case Try Exec is set, check if the application is available if (!config->tryExec()) { q->setDeleted(true); m_bValid = false; return; } const QStandardPaths::StandardLocation resource = config->resource(); if ((m_strType == QLatin1String("Application")) && (resource != QStandardPaths::ApplicationsLocation) && !absPath) { qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType << "but is located under \"" << QStandardPaths::displayName(resource) << "\" instead of \"Applications\""; m_bValid = false; return; } if ((m_strType == QLatin1String("Service")) && (resource != QStandardPaths::GenericDataLocation) && !absPath) { qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType << "but is located under \"" << QStandardPaths::displayName(resource) << "\" instead of \"Shared Data\"/kservices5"; m_bValid = false; return; } // entryPath To desktopEntryName // (e.g. "/home/x/.qttest/share/kservices5/fakepart2.desktop" --> "fakepart2") QString _name = KServiceUtilPrivate::completeBaseName(entryPath); m_strIcon = config->readIcon(); entryMap.remove(QStringLiteral("Icon")); m_bTerminal = desktopGroup.readEntry("Terminal", false); // should be a property IMHO entryMap.remove(QStringLiteral("Terminal")); m_strTerminalOptions = desktopGroup.readEntry("TerminalOptions"); // should be a property IMHO entryMap.remove(QStringLiteral("TerminalOptions")); m_strPath = config->readPath(); entryMap.remove(QStringLiteral("Path")); m_strComment = config->readComment(); entryMap.remove(QStringLiteral("Comment")); m_strGenName = config->readGenericName(); entryMap.remove(QStringLiteral("GenericName")); QString _untranslatedGenericName = desktopGroup.readEntryUntranslated("GenericName"); if (!_untranslatedGenericName.isEmpty()) { entryMap.insert(QStringLiteral("UntranslatedGenericName"), _untranslatedGenericName); } m_lstFormFactors = desktopGroup.readEntry("X-KDE-FormFactors", QStringList()); entryMap.remove(QStringLiteral("X-KDE-FormFactors")); m_lstKeywords = desktopGroup.readXdgListEntry("Keywords", QStringList()); entryMap.remove(QStringLiteral("Keywords")); m_lstKeywords += desktopGroup.readEntry("X-KDE-Keywords", QStringList()); entryMap.remove(QStringLiteral("X-KDE-Keywords")); categories = desktopGroup.readXdgListEntry("Categories"); entryMap.remove(QStringLiteral("Categories")); // TODO KDE5: only care for X-KDE-Library in Type=Service desktop files // This will prevent people defining a part and an app in the same desktop file // which makes user-preference handling difficult. m_strLibrary = desktopGroup.readEntry("X-KDE-Library"); entryMap.remove(QStringLiteral("X-KDE-Library")); if (!m_strLibrary.isEmpty() && m_strType == QLatin1String("Application")) { qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType << "but also has a X-KDE-Library key. This works for now," " but makes user-preference handling difficult, so support for this might" " be removed at some point. Consider splitting it into two desktop files."; } QStringList lstServiceTypes = desktopGroup.readEntry("ServiceTypes", QStringList()); entryMap.remove(QStringLiteral("ServiceTypes")); lstServiceTypes += desktopGroup.readEntry("X-KDE-ServiceTypes", QStringList()); entryMap.remove(QStringLiteral("X-KDE-ServiceTypes")); lstServiceTypes += desktopGroup.readXdgListEntry("MimeType"); entryMap.remove(QStringLiteral("MimeType")); if (m_strType == QLatin1String("Application") && !lstServiceTypes.contains(QLatin1String("Application"))) // Applications implement the service type "Application" ;-) { lstServiceTypes += QStringLiteral("Application"); } m_initialPreference = desktopGroup.readEntry("InitialPreference", 1); entryMap.remove(QStringLiteral("InitialPreference")); // Assign the "initial preference" to each mimetype/servicetype // (and to set such preferences in memory from kbuildsycoca) m_serviceTypes.reserve(lstServiceTypes.size()); QListIterator st_it(lstServiceTypes); while (st_it.hasNext()) { const QString st = st_it.next(); if (st.isEmpty()) { qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has an empty mimetype!"; continue; } int initialPreference = m_initialPreference; if (st_it.hasNext()) { // TODO better syntax - separate group with mimetype=number entries? bool isNumber; const int val = st_it.peekNext().toInt(&isNumber); if (isNumber) { initialPreference = val; st_it.next(); } } m_serviceTypes.push_back(KService::ServiceTypeAndPreference(initialPreference, st)); } if (entryMap.contains(QStringLiteral("Actions"))) { parseActions(config, q); } QString dbusStartupType = desktopGroup.readEntry("X-DBUS-StartupType").toLower(); entryMap.remove(QStringLiteral("X-DBUS-StartupType")); if (dbusStartupType == QLatin1String("unique")) { m_DBUSStartusType = KService::DBusUnique; } else if (dbusStartupType == QLatin1String("multi")) { m_DBUSStartusType = KService::DBusMulti; } else { m_DBUSStartusType = KService::DBusNone; } m_strDesktopEntryName = _name.toLower(); m_bAllowAsDefault = desktopGroup.readEntry("AllowDefault", true); entryMap.remove(QStringLiteral("AllowDefault")); // allow plugin users to translate categories without needing a separate key QMap::Iterator entry = entryMap.find(QStringLiteral("X-KDE-PluginInfo-Category")); if (entry != entryMap.end()) { const QString &key = entry.key(); m_mapProps.insert(key, QVariant(desktopGroup.readEntryUntranslated(key))); m_mapProps.insert(key + QStringLiteral("-Translated"), QVariant(*entry)); entryMap.erase(entry); } // Store all additional entries in the property map. // A QMap would be easier for this but we can't // break BC, so we have to store it in m_mapProps. // qDebug("Path = %s", entryPath.toLatin1().constData()); QMap::ConstIterator it = entryMap.constBegin(); for (; it != entryMap.constEnd(); ++it) { const QString key = it.key(); // do not store other translations like Name[fr]; kbuildsycoca will rerun if we change languages anyway if (!key.contains(QLatin1Char('['))) { //qCDebug(SERVICES) << " Key =" << key << " Data =" << *it; m_mapProps.insert(key, QVariant(*it)); } } } void KServicePrivate::parseActions(const KDesktopFile *config, KService *q) { const QStringList keys = config->readActions(); if (keys.isEmpty()) { return; } QStringList::ConstIterator it = keys.begin(); const QStringList::ConstIterator end = keys.end(); for (; it != end; ++it) { const QString group = *it; if (group == QLatin1String("_SEPARATOR_")) { m_actions.append(KServiceAction(group, QString(), QString(), QString(), false)); continue; } if (config->hasActionGroup(group)) { const KConfigGroup cg = config->actionGroup(group); if (!cg.hasKey("Name") || !cg.hasKey("Exec")) { qCWarning(SERVICES) << "The action" << group << "in the desktop file" << q->entryPath() << "has no Name or no Exec key"; } else { m_actions.append(KServiceAction(group, cg.readEntry("Name"), cg.readEntry("Icon"), cg.readEntry("Exec"), cg.readEntry("NoDisplay", false))); } } else { qCWarning(SERVICES) << "The desktop file" << q->entryPath() << "references the action" << group << "but doesn't define it"; } } } void KServicePrivate::load(QDataStream &s) { qint8 def, term; qint8 dst, initpref; // WARNING: THIS NEEDS TO REMAIN COMPATIBLE WITH PREVIOUS KService 5.x VERSIONS! // !! This data structure should remain binary compatible at all times !! // You may add new fields at the end. Make sure to update KSYCOCA_VERSION // number in ksycoca.cpp s >> m_strType >> m_strName >> m_strExec >> m_strIcon >> term >> m_strTerminalOptions >> m_strPath >> m_strComment >> def >> m_mapProps >> m_strLibrary >> dst >> m_strDesktopEntryName >> initpref >> m_lstKeywords >> m_strGenName >> categories >> menuId >> m_actions >> m_serviceTypes >> m_lstFormFactors; m_bAllowAsDefault = bool(def); m_bTerminal = bool(term); m_DBUSStartusType = static_cast(dst); m_initialPreference = initpref; m_bValid = true; } void KServicePrivate::save(QDataStream &s) { KSycocaEntryPrivate::save(s); qint8 def = m_bAllowAsDefault, initpref = m_initialPreference; qint8 term = m_bTerminal; qint8 dst = qint8(m_DBUSStartusType); // WARNING: THIS NEEDS TO REMAIN COMPATIBLE WITH PREVIOUS KService 5.x VERSIONS! // !! This data structure should remain binary compatible at all times !! // You may add new fields at the end. Make sure to update KSYCOCA_VERSION // number in ksycoca.cpp s << m_strType << m_strName << m_strExec << m_strIcon << term << m_strTerminalOptions << m_strPath << m_strComment << def << m_mapProps << m_strLibrary << dst << m_strDesktopEntryName << initpref << m_lstKeywords << m_strGenName << categories << menuId << m_actions << m_serviceTypes << m_lstFormFactors; } //// KService::KService(const QString &_name, const QString &_exec, const QString &_icon) : KSycocaEntry(*new KServicePrivate(QString())) { Q_D(KService); d->m_strType = QStringLiteral("Application"); d->m_strName = _name; d->m_strExec = _exec; d->m_strIcon = _icon; d->m_bTerminal = false; d->m_bAllowAsDefault = true; d->m_initialPreference = 10; } KService::KService(const QString &_fullpath) : KSycocaEntry(*new KServicePrivate(_fullpath)) { Q_D(KService); KDesktopFile config(_fullpath); d->init(&config, this); } KService::KService(const KDesktopFile *config, const QString &entryPath) : KSycocaEntry(*new KServicePrivate(entryPath.isEmpty() ? config->fileName() : entryPath)) { Q_D(KService); d->init(config, this); } KService::KService(QDataStream &_str, int _offset) : KSycocaEntry(*new KServicePrivate(_str, _offset)) { } KService::~KService() { } bool KService::hasServiceType(const QString &serviceType) const { Q_D(const KService); if (!d->m_bValid) { return false; // (useless) safety test } const KServiceType::Ptr ptr = KServiceType::serviceType(serviceType); if (!ptr) { return false; } const int serviceOffset = offset(); // doesn't seem to work: //if ( serviceOffset == 0 ) // serviceOffset = serviceByStorageId( storageId() ); if (serviceOffset) { KSycoca::self()->ensureCacheValid(); return KSycocaPrivate::self()->serviceFactory()->hasOffer(ptr->offset(), ptr->serviceOffersOffset(), serviceOffset); } // fall-back code for services that are NOT from ksycoca // For each service type we are associated with, if it doesn't // match then we try its parent service types. QVector::ConstIterator it = d->m_serviceTypes.begin(); for (; it != d->m_serviceTypes.end(); ++it) { const QString &st = (*it).serviceType; //qCDebug(SERVICES) << " has " << (*it); if (st == ptr->name()) { return true; } // also the case of parent servicetypes KServiceType::Ptr p = KServiceType::serviceType(st); if (p && p->inherits(ptr->name())) { return true; } } return false; } bool KService::hasMimeType(const QString &mimeType) const { Q_D(const KService); QMimeDatabase db; const QString mime = db.mimeTypeForName(mimeType).name(); if (mime.isEmpty()) { return false; } int serviceOffset = offset(); if (serviceOffset) { KSycoca::self()->ensureCacheValid(); KMimeTypeFactory *factory = KSycocaPrivate::self()->mimeTypeFactory(); const int mimeOffset = factory->entryOffset(mime); const int serviceOffersOffset = factory->serviceOffersOffset(mime); if (serviceOffersOffset == -1) { return false; } return KSycocaPrivate::self()->serviceFactory()->hasOffer(mimeOffset, serviceOffersOffset, serviceOffset); } // fall-back code for services that are NOT from ksycoca QVector::ConstIterator it = d->m_serviceTypes.begin(); for (; it != d->m_serviceTypes.end(); ++it) { const QString &st = (*it).serviceType; //qCDebug(SERVICES) << " has " << (*it); if (st == mime) { return true; } // TODO: should we handle inherited mimetypes here? // KMimeType was in kio when this code was written, this is the only reason it's not done. // But this should matter only in a very rare case, since most code gets KServices from ksycoca. // Warning, change hasServiceType if you implement this here (and check kbuildservicefactory). } return false; } QVariant KServicePrivate::property(const QString &_name) const { return property(_name, QVariant::Invalid); } // Return a string QVariant if string isn't null, and invalid variant otherwise // (the variant must be invalid if the field isn't in the .desktop file) // This allows trader queries like "exist Library" to work. static QVariant makeStringVariant(const QString &string) { // Using isEmpty here would be wrong. // Empty is "specified but empty", null is "not specified" (in the .desktop file) return string.isNull() ? QVariant() : QVariant(string); } QVariant KService::property(const QString &_name, QVariant::Type t) const { Q_D(const KService); return d->property(_name, t); } QVariant KServicePrivate::property(const QString &_name, QVariant::Type t) const { if (_name == QLatin1String("Type")) { return QVariant(m_strType); // can't be null } else if (_name == QLatin1String("Name")) { return QVariant(m_strName); // can't be null } else if (_name == QLatin1String("Exec")) { return makeStringVariant(m_strExec); } else if (_name == QLatin1String("Icon")) { return makeStringVariant(m_strIcon); } else if (_name == QLatin1String("Terminal")) { return QVariant(m_bTerminal); } else if (_name == QLatin1String("TerminalOptions")) { return makeStringVariant(m_strTerminalOptions); } else if (_name == QLatin1String("Path")) { return makeStringVariant(m_strPath); } else if (_name == QLatin1String("Comment")) { return makeStringVariant(m_strComment); } else if (_name == QLatin1String("GenericName")) { return makeStringVariant(m_strGenName); } else if (_name == QLatin1String("ServiceTypes")) { return QVariant(serviceTypes()); } else if (_name == QLatin1String("AllowAsDefault")) { return QVariant(m_bAllowAsDefault); } else if (_name == QLatin1String("InitialPreference")) { return QVariant(m_initialPreference); } else if (_name == QLatin1String("Library")) { return makeStringVariant(m_strLibrary); } else if (_name == QLatin1String("DesktopEntryPath")) { // can't be null return QVariant(path); } else if (_name == QLatin1String("DesktopEntryName")) { return QVariant(m_strDesktopEntryName); // can't be null } else if (_name == QLatin1String("Categories")) { return QVariant(categories); } else if (_name == QLatin1String("Keywords")) { return QVariant(m_lstKeywords); } else if (_name == QLatin1String("FormFactors")) { return QVariant(m_lstFormFactors); } // Ok we need to convert the property from a QString to its real type. // Maybe the caller helped us. if (t == QVariant::Invalid) { // No luck, let's ask KServiceTypeFactory what the type of this property // is supposed to be. // ######### this looks in all servicetypes, not just the ones this service supports! KSycoca::self()->ensureCacheValid(); t = KSycocaPrivate::self()->serviceTypeFactory()->findPropertyTypeByName(_name); if (t == QVariant::Invalid) { qCDebug(SERVICES) << "Request for unknown property" << _name; return QVariant(); // Unknown property: Invalid variant. } } QMap::ConstIterator it = m_mapProps.find(_name); if ((it == m_mapProps.end()) || (!it->isValid())) { //qCDebug(SERVICES) << "Property not found " << _name; return QVariant(); // No property set. } if (t == QVariant::String) { return *it; // no conversion necessary } else { // All others // For instance properties defined as StringList, like MimeTypes. // XXX This API is accessible only through a friend declaration. return KConfigGroup::convertToQVariant(_name.toUtf8().constData(), it->toString().toUtf8(), t); } } QStringList KServicePrivate::propertyNames() const { QStringList res; QMap::ConstIterator it = m_mapProps.begin(); for (; it != m_mapProps.end(); ++it) { res.append(it.key()); } res.append(QStringLiteral("Type")); res.append(QStringLiteral("Name")); res.append(QStringLiteral("Comment")); res.append(QStringLiteral("GenericName")); res.append(QStringLiteral("Icon")); res.append(QStringLiteral("Exec")); res.append(QStringLiteral("Terminal")); res.append(QStringLiteral("TerminalOptions")); res.append(QStringLiteral("Path")); res.append(QStringLiteral("ServiceTypes")); res.append(QStringLiteral("AllowAsDefault")); res.append(QStringLiteral("InitialPreference")); res.append(QStringLiteral("Library")); res.append(QStringLiteral("DesktopEntryPath")); res.append(QStringLiteral("DesktopEntryName")); res.append(QStringLiteral("Keywords")); res.append(QStringLiteral("FormFactors")); res.append(QStringLiteral("Categories")); return res; } KService::List KService::allServices() { KSycoca::self()->ensureCacheValid(); return KSycocaPrivate::self()->serviceFactory()->allServices(); } KService::Ptr KService::serviceByDesktopPath(const QString &_name) { KSycoca::self()->ensureCacheValid(); return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopPath(_name); } KService::Ptr KService::serviceByDesktopName(const QString &_name) { KSycoca::self()->ensureCacheValid(); return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopName(_name); } KService::Ptr KService::serviceByMenuId(const QString &_name) { KSycoca::self()->ensureCacheValid(); return KSycocaPrivate::self()->serviceFactory()->findServiceByMenuId(_name); } KService::Ptr KService::serviceByStorageId(const QString &_storageId) { KSycoca::self()->ensureCacheValid(); return KSycocaPrivate::self()->serviceFactory()->findServiceByStorageId(_storageId); } bool KService::substituteUid() const { QVariant v = property(QStringLiteral("X-KDE-SubstituteUID"), QVariant::Bool); return v.isValid() && v.toBool(); } QString KService::username() const { // See also KDesktopFile::tryExec() QString user; QVariant v = property(QStringLiteral("X-KDE-Username"), QVariant::String); user = v.isValid() ? v.toString() : QString(); if (user.isEmpty()) { user = QString::fromLocal8Bit(qgetenv("ADMIN_ACCOUNT")); } if (user.isEmpty()) { user = QStringLiteral("root"); } return user; } bool KService::showInCurrentDesktop() const { Q_D(const KService); QStringList currentDesktops(QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP"))); if (currentDesktops.isEmpty()) { // This could be an old display manager, or e.g. a failsafe session with no desktop name // In doubt, let's say we show KDE stuff. currentDesktops << QStringLiteral("KDE"); } // This algorithm is described in the desktop entry spec QMap::ConstIterator it = d->m_mapProps.find(QStringLiteral("OnlyShowIn")); if ((it != d->m_mapProps.end()) && (it->isValid())) { const QStringList aList = it->toString().split(QLatin1Char(';')); - foreach (const QString &desktop, currentDesktops) { + for (const QString &desktop : qAsConst(currentDesktops)) { if (aList.contains(desktop)) { return true; } } return false; } it = d->m_mapProps.find(QStringLiteral("NotShowIn")); if ((it != d->m_mapProps.end()) && (it->isValid())) { const QStringList aList = it->toString().split(QLatin1Char(';')); - foreach (const QString &desktop, currentDesktops) { + for (const QString &desktop : qAsConst(currentDesktops)) { if (aList.contains(desktop)) { return false; } } } return true; } bool KService::showOnCurrentPlatform() const { Q_D(const KService); const QString platform = QCoreApplication::instance()->property("platformName").toString(); if (platform.isEmpty()) { return true; } auto it = d->m_mapProps.find(QStringLiteral("X-KDE-OnlyShowOnQtPlatforms")); if ((it != d->m_mapProps.end()) && (it->isValid())) { const QStringList aList = it->toString().split(QLatin1Char(';')); if (!aList.contains(platform)) { return false; } } it = d->m_mapProps.find(QStringLiteral("X-KDE-NotShowOnQtPlatforms")); if ((it != d->m_mapProps.end()) && (it->isValid())) { const QStringList aList = it->toString().split(QLatin1Char(';')); if (aList.contains(platform)) { return false; } } return true; } bool KService::noDisplay() const { if (qvariant_cast(property(QStringLiteral("NoDisplay"), QVariant::Bool))) { return true; } if (!showInCurrentDesktop()) { return true; } if (!showOnCurrentPlatform()) { return true; } if (!KAuthorized::authorizeControlModule(storageId())) { return true; } return false; } QString KService::untranslatedGenericName() const { QVariant v = property(QStringLiteral("UntranslatedGenericName"), QVariant::String); return v.isValid() ? v.toString() : QString(); } QString KService::parentApp() const { Q_D(const KService); QMap::ConstIterator it = d->m_mapProps.find(QStringLiteral("X-KDE-ParentApp")); if ((it == d->m_mapProps.end()) || (!it->isValid())) { return QString(); } return it->toString(); } QString KService::pluginKeyword() const { Q_D(const KService); QMap::ConstIterator it = d->m_mapProps.find(QStringLiteral("X-KDE-PluginKeyword")); if ((it == d->m_mapProps.end()) || (!it->isValid())) { return QString(); } return it->toString(); } QString KService::docPath() const { Q_D(const KService); QMap::ConstIterator it = d->m_mapProps.find(QStringLiteral("X-DocPath")); if ((it == d->m_mapProps.end()) || (!it->isValid())) { it = d->m_mapProps.find(QStringLiteral("DocPath")); if ((it == d->m_mapProps.end()) || (!it->isValid())) { return QString(); } } return it->toString(); } bool KService::allowMultipleFiles() const { Q_D(const KService); // Can we pass multiple files on the command line or do we have to start the application for every single file ? return (d->m_strExec.contains(QLatin1String("%F")) || d->m_strExec.contains(QLatin1String("%U")) || d->m_strExec.contains(QLatin1String("%N")) || d->m_strExec.contains(QLatin1String("%D"))); } QStringList KService::categories() const { Q_D(const KService); return d->categories; } QString KService::menuId() const { Q_D(const KService); return d->menuId; } void KService::setMenuId(const QString &_menuId) { Q_D(KService); d->menuId = _menuId; } QString KService::storageId() const { Q_D(const KService); return d->storageId(); } // not sure this is still used anywhere... QString KService::locateLocal() const { Q_D(const KService); if (d->menuId.isEmpty() || entryPath().startsWith(QLatin1String(".hidden")) || (QDir::isRelativePath(entryPath()) && d->categories.isEmpty())) { return KDesktopFile::locateLocal(entryPath()); } return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/applications/") + d->menuId; } QString KService::newServicePath(bool showInMenu, const QString &suggestedName, QString *menuId, const QStringList *reservedMenuIds) { Q_UNUSED(showInMenu); // TODO KDE5: remove argument QString base = suggestedName; QString result; for (int i = 1; true; i++) { if (i == 1) { result = base + QStringLiteral(".desktop"); } else { result = base + QStringLiteral("-%1.desktop").arg(i); } if (reservedMenuIds && reservedMenuIds->contains(result)) { continue; } // Lookup service by menu-id KService::Ptr s = serviceByMenuId(result); if (s) { continue; } if (!QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("applications/") + result).isEmpty()) { continue; } break; } if (menuId) { *menuId = result; } return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/applications/") + result; } bool KService::isApplication() const { Q_D(const KService); return d->m_strType == QLatin1String("Application"); } QString KService::exec() const { Q_D(const KService); if (d->m_strType == QLatin1String("Application") && d->m_strExec.isEmpty()) { qCWarning(SERVICES) << "The desktop entry file " << entryPath() << " has Type=" << d->m_strType << " but has no Exec field."; } return d->m_strExec; } QString KService::library() const { Q_D(const KService); return d->m_strLibrary; } QString KService::icon() const { Q_D(const KService); return d->m_strIcon; } QString KService::terminalOptions() const { Q_D(const KService); return d->m_strTerminalOptions; } bool KService::terminal() const { Q_D(const KService); return d->m_bTerminal; } bool KService::runOnDiscreteGpu() const { QVariant v = property(QStringLiteral("X-KDE-RunOnDiscreteGpu"), QVariant::Bool); return v.isValid() && v.toBool(); } QString KService::desktopEntryName() const { Q_D(const KService); return d->m_strDesktopEntryName; } KService::DBusStartupType KService::dbusStartupType() const { Q_D(const KService); return d->m_DBUSStartusType; } QString KService::path() const { Q_D(const KService); return d->m_strPath; } QString KService::comment() const { Q_D(const KService); return d->m_strComment; } QString KService::genericName() const { Q_D(const KService); return d->m_strGenName; } QStringList KService::keywords() const { Q_D(const KService); return d->m_lstKeywords; } QStringList KServicePrivate::serviceTypes() const { QStringList ret; QVector::const_iterator it = m_serviceTypes.begin(); for (; it < m_serviceTypes.end(); ++it) { Q_ASSERT(!(*it).serviceType.isEmpty()); ret.append((*it).serviceType); } return ret; } QStringList KService::serviceTypes() const { Q_D(const KService); return d->serviceTypes(); } QStringList KService::mimeTypes() const { Q_D(const KService); QStringList ret; QMimeDatabase db; QVector::const_iterator it = d->m_serviceTypes.begin(); for (; it < d->m_serviceTypes.end(); ++it) { const QString sv = (*it).serviceType; if (db.mimeTypeForName(sv).isValid()) { // keep only mimetypes, filter out servicetypes ret.append(sv); } } return ret; } bool KService::allowAsDefault() const { Q_D(const KService); return d->m_bAllowAsDefault; } int KService::initialPreference() const { Q_D(const KService); return d->m_initialPreference; } void KService::setTerminal(bool b) { Q_D(KService); d->m_bTerminal = b; } void KService::setTerminalOptions(const QString &options) { Q_D(KService); d->m_strTerminalOptions = options; } void KService::setExec(const QString &exec) { Q_D(KService); if (!exec.isEmpty()) { d->m_strExec = exec; d->path.clear(); } } QVector &KService::_k_accessServiceTypes() { Q_D(KService); return d->m_serviceTypes; } QList KService::actions() const { Q_D(const KService); return d->m_actions; } KService::operator KPluginName() const { if (!isValid()) { return KPluginName::fromErrorString(i18n("The provided service is not valid")); } if (library().isEmpty()) { return KPluginName::fromErrorString(i18n("The service '%1' provides no library or the Library key is missing", entryPath())); } return KPluginName(library()); } diff --git a/src/services/kservicegroup.cpp b/src/services/kservicegroup.cpp index e3acc2a..2fdc2c3 100644 --- a/src/services/kservicegroup.cpp +++ b/src/services/kservicegroup.cpp @@ -1,723 +1,724 @@ /* This file is part of the KDE libraries * Copyright (C) 2000 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. **/ #include "kservicegroup.h" #include "kservicegroup_p.h" #include "kservicefactory_p.h" #include "kservicegroupfactory_p.h" #include "kservice.h" #include "ksycoca_p.h" #include "servicesdebug.h" #include #include #include KServiceGroup::KServiceGroup(const QString &name) : KSycocaEntry(*new KServiceGroupPrivate(name)) { } KServiceGroup::KServiceGroup(const QString &configFile, const QString &_relpath) : KSycocaEntry(*new KServiceGroupPrivate(_relpath)) { Q_D(KServiceGroup); QString cfg = configFile; if (cfg.isEmpty()) { cfg = _relpath + QLatin1String(".directory"); } d->load(cfg); } void KServiceGroupPrivate::load(const QString &cfg) { directoryEntryPath = cfg; const KDesktopFile desktopFile(cfg); const KConfigGroup config = desktopFile.desktopGroup(); m_strCaption = config.readEntry("Name"); m_strIcon = config.readEntry("Icon"); m_strComment = config.readEntry("Comment"); deleted = config.readEntry("Hidden", false); m_bNoDisplay = desktopFile.noDisplay(); m_strBaseGroupName = config.readEntry("X-KDE-BaseGroup"); suppressGenericNames = config.readEntry("X-KDE-SuppressGenericNames", QStringList()); // Fill in defaults. if (m_strCaption.isEmpty()) { m_strCaption = path; if (m_strCaption.endsWith(QLatin1Char('/'))) { m_strCaption = m_strCaption.left(m_strCaption.length() - 1); } int i = m_strCaption.lastIndexOf(QLatin1Char('/')); if (i > 0) { m_strCaption = m_strCaption.mid(i + 1); } } if (m_strIcon.isEmpty()) { m_strIcon = QStringLiteral("folder"); } } KServiceGroup::KServiceGroup(QDataStream &_str, int offset, bool deep) : KSycocaEntry(*new KServiceGroupPrivate(_str, offset)) { Q_D(KServiceGroup); d->m_bDeep = deep; d->load(_str); } KServiceGroup::~KServiceGroup() { } QString KServiceGroup::relPath() const { return entryPath(); } QString KServiceGroup::caption() const { Q_D(const KServiceGroup); return d->m_strCaption; } QString KServiceGroup::icon() const { Q_D(const KServiceGroup); return d->m_strIcon; } QString KServiceGroup::comment() const { Q_D(const KServiceGroup); return d->m_strComment; } int KServiceGroup::childCount() const { Q_D(const KServiceGroup); return d->childCount(); } int KServiceGroupPrivate::childCount() const { if (m_childCount == -1) { m_childCount = 0; for (KServiceGroup::List::ConstIterator it = m_serviceList.begin(); it != m_serviceList.end(); ++it) { KSycocaEntry::Ptr p = *it; if (p->isType(KST_KService)) { KService::Ptr service(static_cast(p.data())); if (!service->noDisplay()) { m_childCount++; } } else if (p->isType(KST_KServiceGroup)) { KServiceGroup::Ptr serviceGroup(static_cast(p.data())); m_childCount += serviceGroup->childCount(); } } } return m_childCount; } bool KServiceGroup::showInlineHeader() const { Q_D(const KServiceGroup); return d->m_bShowInlineHeader; } bool KServiceGroup::showEmptyMenu() const { Q_D(const KServiceGroup); return d->m_bShowEmptyMenu; } bool KServiceGroup::inlineAlias() const { Q_D(const KServiceGroup); return d->m_bInlineAlias; } void KServiceGroup::setInlineAlias(bool _b) { Q_D(KServiceGroup); d->m_bInlineAlias = _b; } void KServiceGroup::setShowEmptyMenu(bool _b) { Q_D(KServiceGroup); d->m_bShowEmptyMenu = _b; } void KServiceGroup::setShowInlineHeader(bool _b) { Q_D(KServiceGroup); d->m_bShowInlineHeader = _b; } int KServiceGroup::inlineValue() const { Q_D(const KServiceGroup); return d->m_inlineValue; } void KServiceGroup::setInlineValue(int _val) { Q_D(KServiceGroup); d->m_inlineValue = _val; } bool KServiceGroup::allowInline() const { Q_D(const KServiceGroup); return d->m_bAllowInline; } void KServiceGroup::setAllowInline(bool _b) { Q_D(KServiceGroup); d->m_bAllowInline = _b; } bool KServiceGroup::noDisplay() const { Q_D(const KServiceGroup); return d->m_bNoDisplay || d->m_strCaption.startsWith(QLatin1Char('.')); } QStringList KServiceGroup::suppressGenericNames() const { Q_D(const KServiceGroup); return d->suppressGenericNames; } void KServiceGroupPrivate::load(QDataStream &s) { QStringList groupList; qint8 noDisplay; qint8 _showEmptyMenu; qint8 inlineHeader; qint8 _inlineAlias; qint8 _allowInline; s >> m_strCaption >> m_strIcon >> m_strComment >> groupList >> m_strBaseGroupName >> m_childCount >> noDisplay >> suppressGenericNames >> directoryEntryPath >> sortOrder >> _showEmptyMenu >> inlineHeader >> _inlineAlias >> _allowInline; m_bNoDisplay = (noDisplay != 0); m_bShowEmptyMenu = (_showEmptyMenu != 0); m_bShowInlineHeader = (inlineHeader != 0); m_bInlineAlias = (_inlineAlias != 0); m_bAllowInline = (_allowInline != 0); if (m_bDeep) { - Q_FOREACH (const QString &path, groupList) { + for (const QString &path : qAsConst(groupList)) { if (path.endsWith(QLatin1Char('/'))) { KServiceGroup::Ptr serviceGroup; serviceGroup = KSycocaPrivate::self()->serviceGroupFactory()->findGroupByDesktopPath(path, false); if (serviceGroup) { m_serviceList.append(KServiceGroup::SPtr(serviceGroup)); } } else { KService::Ptr service; service = KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopPath(path); if (service) { m_serviceList.append(KServiceGroup::SPtr(service)); } } } } } void KServiceGroup::addEntry(const KSycocaEntry::Ptr &entry) { Q_D(KServiceGroup); d->m_serviceList.append(entry); } void KServiceGroupPrivate::save(QDataStream &s) { KSycocaEntryPrivate::save(s); QStringList groupList; - Q_FOREACH (KSycocaEntry::Ptr p, m_serviceList) { + for (const KSycocaEntry::Ptr &p : qAsConst(m_serviceList)) { if (p->isType(KST_KService)) { KService::Ptr service(static_cast(p.data())); groupList.append(service->entryPath()); } else if (p->isType(KST_KServiceGroup)) { KServiceGroup::Ptr serviceGroup(static_cast(p.data())); groupList.append(serviceGroup->relPath()); } else { //fprintf(stderr, "KServiceGroup: Unexpected object in list!\n"); } } (void) childCount(); qint8 noDisplay = m_bNoDisplay ? 1 : 0; qint8 _showEmptyMenu = m_bShowEmptyMenu ? 1 : 0; qint8 inlineHeader = m_bShowInlineHeader ? 1 : 0; qint8 _inlineAlias = m_bInlineAlias ? 1 : 0; qint8 _allowInline = m_bAllowInline ? 1 : 0; s << m_strCaption << m_strIcon << m_strComment << groupList << m_strBaseGroupName << m_childCount << noDisplay << suppressGenericNames << directoryEntryPath << sortOrder << _showEmptyMenu << inlineHeader << _inlineAlias << _allowInline; } QList KServiceGroup::groupEntries(EntriesOptions options) { Q_D(KServiceGroup); bool sort = options & SortEntries || options & AllowSeparators; QList list; - List tmp = d->entries(this, sort, options & ExcludeNoDisplay, options & AllowSeparators, options & SortByGenericName); - foreach (const SPtr &ptr, tmp) { + const List tmp = d->entries(this, sort, options & ExcludeNoDisplay, options & AllowSeparators, options & SortByGenericName); + for (const SPtr &ptr : tmp) { if (ptr->isType(KST_KServiceGroup)) { KServiceGroup::Ptr serviceGroup(static_cast(ptr.data())); list.append(serviceGroup); } else if (ptr->isType(KST_KServiceSeparator)) { list.append(KServiceGroup::Ptr(static_cast(new KSycocaEntry()))); } else if (sort && ptr->isType(KST_KService)) { break; } } return list; } KService::List KServiceGroup::serviceEntries(EntriesOptions options) { Q_D(KServiceGroup); bool sort = options & SortEntries || options & AllowSeparators; QList list; - List tmp = d->entries(this, sort, options & ExcludeNoDisplay, options & AllowSeparators, options & SortByGenericName); + const List tmp = d->entries(this, sort, options & ExcludeNoDisplay, options & AllowSeparators, options & SortByGenericName); bool foundService = false; - foreach (const SPtr &ptr, tmp) { + for (const SPtr &ptr : tmp) { if (ptr->isType(KST_KService)) { list.append(KService::Ptr(static_cast(ptr.data()))); foundService = true; } else if (ptr->isType(KST_KServiceSeparator) && foundService) { list.append(KService::Ptr(static_cast(new KSycocaEntry()))); } } return list; } KServiceGroup::List KServiceGroup::entries(bool sort) { Q_D(KServiceGroup); return d->entries(this, sort, true, false, false); } KServiceGroup::List KServiceGroup::entries(bool sort, bool excludeNoDisplay) { Q_D(KServiceGroup); return d->entries(this, sort, excludeNoDisplay, false, false); } KServiceGroup::List KServiceGroup::entries(bool sort, bool excludeNoDisplay, bool allowSeparators, bool sortByGenericName) { Q_D(KServiceGroup); return d->entries(this, sort, excludeNoDisplay, allowSeparators, sortByGenericName); } static void addItem(KServiceGroup::List &sorted, const KSycocaEntry::Ptr &p, bool &addSeparator) { if (addSeparator && !sorted.isEmpty()) { sorted.append(KSycocaEntry::Ptr(new KServiceSeparator())); } sorted.append(p); addSeparator = false; } KServiceGroup::List KServiceGroupPrivate::entries(KServiceGroup *group, bool sort, bool excludeNoDisplay, bool allowSeparators, bool sortByGenericName) { KSycoca::self()->ensureCacheValid(); // If the entries haven't been loaded yet, we have to reload ourselves // together with the entries. We can't only load the entries afterwards // since the offsets could have been changed if the database has changed. KServiceGroup::Ptr grp; if (!m_bDeep) { grp = KSycocaPrivate::self()->serviceGroupFactory()->findGroupByDesktopPath(path, true); group = grp.data(); if (nullptr == group) { // No guarantee that we still exist! return KServiceGroup::List(); } } if (!sort) { return group->d_func()->m_serviceList; } // Sort the list alphabetically, according to locale. // Groups come first, then services. // We use a QMap, for sorting using a stored temporary key. typedef QMap SortedContainer; SortedContainer slist; SortedContainer glist; - Q_FOREACH (KSycocaEntry::Ptr p, group->d_func()->m_serviceList) { + const auto listService = group->d_func()->m_serviceList; + for (const KSycocaEntry::Ptr &p : listService) { bool noDisplay = p->isType(KST_KServiceGroup) ? static_cast(p.data())->noDisplay() : static_cast(p.data())->noDisplay(); if (excludeNoDisplay && noDisplay) { continue; } // Choose the right list SortedContainer &list = p->isType(KST_KServiceGroup) ? glist : slist; QString name; if (p->isType(KST_KServiceGroup)) { name = static_cast(p.data())->caption(); } else if (sortByGenericName) { name = static_cast(p.data())->genericName() + QLatin1Char(' ') + p->name(); } else { name = p->name() + QLatin1Char(' ') + static_cast(p.data())->genericName(); } const QByteArray nameStr = name.toLocal8Bit(); QByteArray key; // strxfrm() crashes on Solaris and strxfrm is not defined under wince #if !defined(USE_SOLARIS) && !defined(_WIN32_WCE) // maybe it'd be better to use wcsxfrm() where available key.resize(name.length() * 4 + 1); size_t ln = strxfrm(key.data(), nameStr.constData(), key.size()); if (ln != size_t(-1)) { key.resize(ln); if (int(ln) >= key.size()) { // didn't fit? ln = strxfrm(key.data(), nameStr.constData(), key.size()); if (ln == size_t(-1)) { key = nameStr; } } } else #endif { key = nameStr; } list.insert(key, KServiceGroup::SPtr(p)); } if (sortOrder.isEmpty()) { sortOrder << QStringLiteral(":M"); sortOrder << QStringLiteral(":F"); sortOrder << QStringLiteral(":OIH IL[4]"); //just inline header } QString rp = path; if (rp == QLatin1String("/")) { rp.clear(); } // Iterate through the sort spec list. // If an entry gets mentioned explicitly, we remove it from the sorted list - Q_FOREACH (const QString &item, sortOrder) { + for (const QString &item : qAsConst(sortOrder)) { if (item.isEmpty()) { continue; } if (item[0] == QLatin1Char('/')) { QString groupPath = rp + item.mid(1) + QLatin1Char('/'); // Remove entry from sorted list of services. for (SortedContainer::iterator it2 = glist.begin(); it2 != glist.end(); ++it2) { const KServiceGroup::Ptr group(static_cast(it2.value().data())); if (group->relPath() == groupPath) { glist.erase(it2); break; } } } else if (item[0] != QLatin1Char(':')) { // Remove entry from sorted list of services. // TODO: Remove item from sortOrder-list if not found // TODO: This prevents duplicates for (SortedContainer::iterator it2 = slist.begin(); it2 != slist.end(); ++it2) { const KService::Ptr service(static_cast(it2.value().data())); if (service->menuId() == item) { slist.erase(it2); break; } } } } KServiceGroup::List sorted; bool needSeparator = false; // Iterate through the sort spec list. // Add the entries to the list according to the sort spec. for (QStringList::ConstIterator it(sortOrder.constBegin()); it != sortOrder.constEnd(); ++it) { const QString &item = *it; if (item.isEmpty()) { continue; } if (item[0] == QLatin1Char(':')) { // Special condition... if (item == QLatin1String(":S")) { if (allowSeparators) { needSeparator = true; } } else if (item.contains(QLatin1String(":O"))) { //todo parse attribute: QString tmp(item); tmp = tmp.remove(QStringLiteral(":O")); QStringList optionAttribute = tmp.split(QLatin1Char(' '), QString::SkipEmptyParts); if (optionAttribute.isEmpty()) { optionAttribute.append(tmp); } bool showEmptyMenu = false; bool showInline = false; bool showInlineHeader = false; bool showInlineAlias = false; int inlineValue = -1; for (QStringList::Iterator it3 = optionAttribute.begin(); it3 != optionAttribute.end(); ++it3) { parseAttribute(*it3, showEmptyMenu, showInline, showInlineHeader, showInlineAlias, inlineValue); } for (SortedContainer::Iterator it2 = glist.begin(); it2 != glist.end(); ++it2) { KServiceGroup::Ptr group(static_cast(it2.value().data())); group->setShowEmptyMenu(showEmptyMenu); group->setAllowInline(showInline); group->setShowInlineHeader(showInlineHeader); group->setInlineAlias(showInlineAlias); group->setInlineValue(inlineValue); } } else if (item == QLatin1String(":M")) { // Add sorted list of sub-menus for (SortedContainer::const_iterator it2 = glist.constBegin(); it2 != glist.constEnd(); ++it2) { addItem(sorted, it2.value(), needSeparator); } } else if (item == QLatin1String(":F")) { // Add sorted list of services for (SortedContainer::const_iterator it2 = slist.constBegin(); it2 != slist.constEnd(); ++it2) { addItem(sorted, it2.value(), needSeparator); } } else if (item == QLatin1String(":A")) { // Add sorted lists of services and submenus SortedContainer::Iterator it_s = slist.begin(); SortedContainer::Iterator it_g = glist.begin(); while (true) { if (it_s == slist.end()) { if (it_g == glist.end()) { break; // Done } // Insert remaining sub-menu addItem(sorted, it_g.value(), needSeparator); it_g++; } else if (it_g == glist.end()) { // Insert remaining service addItem(sorted, it_s.value(), needSeparator); it_s++; } else if (it_g.key() < it_s.key()) { // Insert sub-menu first addItem(sorted, it_g.value(), needSeparator); it_g++; } else { // Insert service first addItem(sorted, it_s.value(), needSeparator); it_s++; } } } } else if (item[0] == QLatin1Char('/')) { QString groupPath = rp + item.mid(1) + QLatin1Char('/'); for (KServiceGroup::List::ConstIterator it2(group->d_func()->m_serviceList.constBegin()); it2 != group->d_func()->m_serviceList.constEnd(); ++it2) { if (!(*it2)->isType(KST_KServiceGroup)) { continue; } KServiceGroup::Ptr group(static_cast((*it2).data())); if (group->relPath() == groupPath) { if (!excludeNoDisplay || !group->noDisplay()) { ++it; const QString &nextItem = (it == sortOrder.constEnd()) ? QString() : *it; if (nextItem.startsWith(QLatin1String(":O"))) { QString tmp(nextItem); tmp = tmp.remove(QStringLiteral(":O")); QStringList optionAttribute = tmp.split(QLatin1Char(' '), QString::SkipEmptyParts); if (optionAttribute.isEmpty()) { optionAttribute.append(tmp); } bool bShowEmptyMenu = false; bool bShowInline = false; bool bShowInlineHeader = false; bool bShowInlineAlias = false; int inlineValue = -1; - Q_FOREACH (const QString &opt_attr, optionAttribute) { + for (const QString &opt_attr : qAsConst(optionAttribute)) { parseAttribute(opt_attr, bShowEmptyMenu, bShowInline, bShowInlineHeader, bShowInlineAlias, inlineValue); group->setShowEmptyMenu(bShowEmptyMenu); group->setAllowInline(bShowInline); group->setShowInlineHeader(bShowInlineHeader); group->setInlineAlias(bShowInlineAlias); group->setInlineValue(inlineValue); } } else { it--; } addItem(sorted, KServiceGroup::SPtr(group), needSeparator); } break; } } } else { for (KServiceGroup::List::ConstIterator it2(group->d_func()->m_serviceList.constBegin()); it2 != group->d_func()->m_serviceList.constEnd(); ++it2) { if (!(*it2)->isType(KST_KService)) { continue; } const KService::Ptr service(static_cast((*it2).data())); if (service->menuId() == item) { if (!excludeNoDisplay || !service->noDisplay()) { addItem(sorted, (*it2), needSeparator); } break; } } } } return sorted; } void KServiceGroupPrivate::parseAttribute(const QString &item, bool &showEmptyMenu, bool &showInline, bool &showInlineHeader, bool &showInlineAlias, int &inlineValue) { if (item == QLatin1String("ME")) { //menu empty showEmptyMenu = true; } else if (item == QLatin1String("NME")) { //not menu empty showEmptyMenu = false; } else if (item == QLatin1String("I")) { //inline menu ! showInline = true; } else if (item == QLatin1String("NI")) { //not inline menu! showInline = false; } else if (item == QLatin1String("IH")) { //inline header! showInlineHeader = true; } else if (item == QLatin1String("NIH")) { //not inline header! showInlineHeader = false; } else if (item == QLatin1String("IA")) { //inline alias! showInlineAlias = true; } else if (item == QLatin1String("NIA")) { //not inline alias! showInlineAlias = false; } else if ((item).contains(QLatin1String("IL"))) { //inline limite! QString tmp(item); tmp = tmp.remove(QStringLiteral("IL[")); tmp = tmp.remove(QLatin1Char(']')); bool ok; int _inlineValue = tmp.toInt(&ok); if (!ok) { //error _inlineValue = -1; } inlineValue = _inlineValue; } else { qCDebug(SERVICES) << "This attribute is not supported:" << item; } } void KServiceGroup::setLayoutInfo(const QStringList &layout) { Q_D(KServiceGroup); d->sortOrder = layout; } QStringList KServiceGroup::layoutInfo() const { Q_D(const KServiceGroup); return d->sortOrder; } KServiceGroup::Ptr KServiceGroup::root() { KSycoca::self()->ensureCacheValid(); return KSycocaPrivate::self()->serviceGroupFactory()->findGroupByDesktopPath(QStringLiteral("/"), true); } KServiceGroup::Ptr KServiceGroup::group(const QString &relPath) { if (relPath.isEmpty()) { return root(); } KSycoca::self()->ensureCacheValid(); return KSycocaPrivate::self()->serviceGroupFactory()->findGroupByDesktopPath(relPath, true); } KServiceGroup::Ptr KServiceGroup::childGroup(const QString &parent) { KSycoca::self()->ensureCacheValid(); return KSycocaPrivate::self()->serviceGroupFactory()->findGroupByDesktopPath(QLatin1String("#parent#") + parent, true); } QString KServiceGroup::baseGroupName() const { return d_func()->m_strBaseGroupName; } QString KServiceGroup::directoryEntryPath() const { Q_D(const KServiceGroup); return d->directoryEntryPath; } class KServiceSeparatorPrivate : public KSycocaEntryPrivate { public: K_SYCOCATYPE(KST_KServiceSeparator, KSycocaEntryPrivate) KServiceSeparatorPrivate(const QString &name) : KSycocaEntryPrivate(name) { } QString name() const override; }; QString KServiceSeparatorPrivate::name() const { return QStringLiteral("separator"); } KServiceSeparator::KServiceSeparator() : KSycocaEntry(*new KServiceSeparatorPrivate(QStringLiteral("separator"))) { } KServiceSeparator::~KServiceSeparator() { } diff --git a/src/services/kservicetypetrader.h b/src/services/kservicetypetrader.h index 7751e97..b75d426 100644 --- a/src/services/kservicetypetrader.h +++ b/src/services/kservicetypetrader.h @@ -1,225 +1,225 @@ /* 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 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 __kservicetypetrader_h__ #define __kservicetypetrader_h__ #include "kservice.h" class KServiceOffer; typedef QList KServiceOfferList; class KServiceTypeTraderPrivate; /** * @class KServiceTypeTrader kservicetypetrader.h * * KDE's trader interface (similar to the CORBA Trader), which provides a way * to query the KDE infrastructure for specific applications or components. * * Basically, KServiceTypeTrader provides a way for an application to query * all KDE services (that is, applications, components, plugins) that match * a specific set of requirements. This allows to find specific services * at run-time without having to hard-code their names and/or paths. * * For anything relating to mimetypes (type of files), ignore KServiceTypeTrader * and use KMimeTypeTrader instead. * * \par Example * * If you want to find all plugins for your application, * you would define a KMyApp/Plugin servicetype, and then you can query * the trader for it: * \code * KService::List offers = * KServiceTypeTrader::self()->query("KMyApp/Plugin"); * \endcode * * You can add a constraint in the "trader query language". For instance: * \code * KServiceTypeTrader::self()->query("KMyApp/Plugin", * "[X-KMyApp-InterfaceVersion] > 15"); * \endcode * * Please note that when including property names containing arithmetic operators like - or +, then you have * to put brackets around the property name, in order to correctly separate arithmetic operations from * the name. So for example a constraint expression like * \code * X-KMyApp-InterfaceVersion > 4 // wrong! * \endcode * needs to be written as * \code * [X-KMyApp-InterfaceVersion] > 4 * \endcode * otherwise it could also be interpreted as * Subtract the numeric value of the property "KMyApp" and "InterfaceVersion" from the * property "X" and make sure it is greater than 4. * Instead of the other meaning, make sure that the numeric value of "X-KMyApp-InterfaceVersion" is * greater than 4. * * @see KMimeTypeTrader, KService */ class KSERVICE_EXPORT KServiceTypeTrader { public: /** * Standard destructor */ ~KServiceTypeTrader(); /** * The main function in the KServiceTypeTrader class. * * It will return a list of services that match your * specifications. The only required parameter is the service * type. This is something like 'text/plain' or 'text/html'. 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 * (Type == 'Service') and (('KParts/ReadOnlyPart' in ServiceTypes) or (exist Exec)) * \endcode * * The keys used in the query (Type, ServiceType, Exec) are all * fields found in the .desktop files. * * @param servicetype A service type like 'KMyApp/Plugin' or 'KFilePlugin'. * @param constraint A constraint to limit the choices returned, QString() to * get all services of the given @p servicetype * * @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 &servicetype, const QString &constraint = QString()) const; /** * Returns all offers associated with a given servicetype, IGNORING the * user preference. The sorting will be the one coming from the InitialPreference * in the .desktop files, and services disabled by the user will still be listed here. * This is used for "Revert to defaults" buttons in GUIs. */ KService::List defaultOffers(const QString &serviceType, const QString &constraint = QString()) const; /** * Returns the preferred service for @p serviceType. * * @param serviceType the service type (e.g. "KMyApp/Plugin") * @return the preferred service, or @c nullptr if no service is available */ KService::Ptr preferredService(const QString &serviceType) const; /** * This is a static pointer to the KServiceTypeTrader singleton. * * You will need to use this to access the KServiceTypeTrader functionality since the * constructors are protected. * * @return Static KServiceTypeTrader instance */ static KServiceTypeTrader *self(); /** * Get a plugin from a trader query * * Example: * \code * KMyAppPlugin* plugin = KServiceTypeTrader::createInstanceFromQuery( serviceType, QString(), parentObject ); * if ( plugin ) { * .... * } * \endcode * * @param serviceType the type of service for which to find a plugin * @param constraint an optional constraint to pass to the trader (see KTrader) * @param parent the parent object for the part itself * @param args A list of arguments passed to the service component * @param error The string passed here will contain an error description. * @return A pointer to the newly created object or a null pointer if the * factory was unable to create an object of the given type. */ template static T *createInstanceFromQuery(const QString &serviceType, const QString &constraint = QString(), QObject *parent = nullptr, const QVariantList &args = QVariantList(), QString *error = nullptr) { return createInstanceFromQuery(serviceType, nullptr, parent, constraint, args, error); } /** * Get a plugin from a trader query * * This method works like * createInstanceFromQuery(const QString&, const QString&, QObject*, const QVariantList&, QString*), * but you can specify an additional parent widget. This is important for * a KPart, for example. * * @param serviceType the type of service for which to find a plugin * @param parentWidget the parent widget for the plugin * @param parent the parent object for the part itself * @param constraint an optional constraint to pass to the trader (see KTrader) * @param args A list of arguments passed to the service component * @param error The string passed here will contain an error description. * @return A pointer to the newly created object or a null pointer if the * factory was unable to create an object of the given type. */ template static T *createInstanceFromQuery(const QString &serviceType, QWidget *parentWidget, QObject *parent, const QString &constraint = QString(), const QVariantList &args = QVariantList(), QString *error = nullptr) { const KService::List offers = self()->query(serviceType, constraint); if (error) { error->clear(); } - Q_FOREACH (const KService::Ptr &ptr, offers) { + for (const KService::Ptr &ptr : offers) { T *component = ptr->template createInstance(parentWidget, parent, args, error); if (component) { return component; } } if (error && error->isEmpty()) { *error = QCoreApplication::translate("", "No service matching the requirements was found"); } return nullptr; } /** * @internal (public for KMimeTypeTrader) */ static void applyConstraints(KService::List &lst, const QString &constraint); private: /** * @internal */ KServiceTypeTrader(); // disallow copy ctor and assignment operator KServiceTypeTrader(const KServiceTypeTrader &other); KServiceTypeTrader &operator=(const KServiceTypeTrader &rhs); static KServiceOfferList weightedOffers(const QString &serviceType); KServiceTypeTraderPrivate *const d; friend class KServiceTypeTraderSingleton; }; #endif diff --git a/src/services/ktraderparsetree.cpp b/src/services/ktraderparsetree.cpp index 70335db..5290dcc 100644 --- a/src/services/ktraderparsetree.cpp +++ b/src/services/ktraderparsetree.cpp @@ -1,808 +1,808 @@ /* 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" 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); 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; - foreach (const QString &string, c2.strSeq) { + 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); } } - foreach (const QVariant &p, offerValues) { + 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/sycoca/kbuildservicefactory.cpp b/src/sycoca/kbuildservicefactory.cpp index e67712c..c8f95f6 100644 --- a/src/sycoca/kbuildservicefactory.cpp +++ b/src/sycoca/kbuildservicefactory.cpp @@ -1,422 +1,425 @@ /* This file is part of the KDE libraries * Copyright (C) 1999, 2007 David Faure * 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. **/ #include "kbuildservicefactory_p.h" #include "kbuildservicegroupfactory_p.h" #include "kbuildmimetypefactory_p.h" #include "kservicetypefactory_p.h" #include "ksycoca.h" #include "ksycocadict_p.h" #include "ksycocaresourcelist_p.h" #include "kdesktopfile.h" #include "kservicetype.h" #include "sycocadebug.h" #include #include #include #include #include #include KBuildServiceFactory::KBuildServiceFactory(KServiceTypeFactory *serviceTypeFactory, KBuildMimeTypeFactory *mimeTypeFactory, KBuildServiceGroupFactory *serviceGroupFactory) : KServiceFactory(serviceTypeFactory->sycoca()), m_nameMemoryHash(), m_relNameMemoryHash(), m_menuIdMemoryHash(), m_dupeDict(), m_serviceTypeFactory(serviceTypeFactory), m_mimeTypeFactory(mimeTypeFactory), m_serviceGroupFactory(serviceGroupFactory) { m_resourceList = new KSycocaResourceList(); // We directly care about services desktop files. // All the application desktop files are parsed on demand from the vfolder menu code. m_resourceList->add("services", QStringLiteral("kservices5"), QStringLiteral("*.desktop")); m_nameDict = new KSycocaDict(); m_relNameDict = new KSycocaDict(); m_menuIdDict = new KSycocaDict(); } KBuildServiceFactory::~KBuildServiceFactory() { delete m_resourceList; } KService::Ptr KBuildServiceFactory::findServiceByDesktopName(const QString &name) { return m_nameMemoryHash.value(name); } KService::Ptr KBuildServiceFactory::findServiceByDesktopPath(const QString &name) { return m_relNameMemoryHash.value(name); } KService::Ptr KBuildServiceFactory::findServiceByMenuId(const QString &menuId) { return m_menuIdMemoryHash.value(menuId); } KSycocaEntry *KBuildServiceFactory::createEntry(const QString &file) const { Q_ASSERT(!file.startsWith(QLatin1String("kservices5/"))); // we add this ourselves, below const QStringRef name = file.midRef(file.lastIndexOf(QLatin1Char('/')) + 1); // Is it a .desktop file? if (name.endsWith(QLatin1String(".desktop"))) { //qCDebug(SYCOCA) << file; KService *serv; if (QDir::isAbsolutePath(file)) { // vfolder sends us full paths for applications serv = new KService(file); } else { // we get relative paths for services KDesktopFile desktopFile(QStandardPaths::GenericDataLocation, QStringLiteral("kservices5/") + file); // Note that the second arg below MUST be 'file', unchanged. // If the entry path doesn't match the 'file' parameter to createEntry, reusing old entries // (via time dict, which uses the entry path as key) cannot work. serv = new KService(&desktopFile, file); } //qCDebug(SYCOCA) << "Creating KService from" << file << "entryPath=" << serv->entryPath(); // Note that the menuId will be set by the vfolder_menu.cpp code just after // createEntry returns. if (serv->isValid() && !serv->isDeleted()) { //qCDebug(SYCOCA) << "Creating KService from" << file << "entryPath=" << serv->entryPath() << "storageId=" << serv->storageId(); return serv; } else { if (!serv->isDeleted()) { qCWarning(SYCOCA) << "Invalid Service : " << file; } delete serv; return nullptr; } } // TODO else if a Windows application, new KService(name, exec, icon) return nullptr; } void KBuildServiceFactory::saveHeader(QDataStream &str) { KSycocaFactory::saveHeader(str); str << qint32(m_nameDictOffset); str << qint32(m_relNameDictOffset); str << qint32(m_offerListOffset); str << qint32(m_menuIdDictOffset); } void KBuildServiceFactory::save(QDataStream &str) { KSycocaFactory::save(str); m_nameDictOffset = str.device()->pos(); m_nameDict->save(str); m_relNameDictOffset = str.device()->pos(); m_relNameDict->save(str); saveOfferList(str); m_menuIdDictOffset = str.device()->pos(); m_menuIdDict->save(str); qint64 endOfFactoryData = str.device()->pos(); // Update header (pass #3) saveHeader(str); // Seek to end. str.device()->seek(endOfFactoryData); } void KBuildServiceFactory::collectInheritedServices() { // For each mimetype, go up the parent-mimetype chains and collect offers. // For "removed associations" to work, we can't just grab everything from all parents. // We need to process parents before children, hence the recursive call in // collectInheritedServices(mime) and the QSet to process a given parent only once. QSet visitedMimes; - Q_FOREACH (const QString &mimeType, m_mimeTypeFactory->allMimeTypes()) { + const auto lst = m_mimeTypeFactory->allMimeTypes(); + for (const QString &mimeType : lst) { collectInheritedServices(mimeType, visitedMimes); } } void KBuildServiceFactory::collectInheritedServices(const QString &mimeTypeName, QSet &visitedMimes) { if (visitedMimes.contains(mimeTypeName)) { return; } visitedMimes.insert(mimeTypeName); // With multiple inheritance, the "mimeTypeInheritanceLevel" isn't exactly // correct (it should only be increased when going up a level, not when iterating // through the multiple parents at a given level). I don't think we care, though. int mimeTypeInheritanceLevel = 0; QMimeDatabase db; - QMimeType qmime = db.mimeTypeForName(mimeTypeName); - Q_FOREACH (QString parentMimeType, qmime.parentMimeTypes()) { + const QMimeType qmime = db.mimeTypeForName(mimeTypeName); + const auto lst = qmime.parentMimeTypes(); + for (QString parentMimeType : lst) { // Workaround issue in shared-mime-info and/or Qt, which sometimes return an alias as parent parentMimeType = db.mimeTypeForName(parentMimeType).name(); collectInheritedServices(parentMimeType, visitedMimes); ++mimeTypeInheritanceLevel; const QList &offers = m_offerHash.offersFor(parentMimeType); QList::const_iterator itserv = offers.begin(); const QList::const_iterator endserv = offers.end(); for (; itserv != endserv; ++itserv) { if (!m_offerHash.hasRemovedOffer(mimeTypeName, (*itserv).service())) { KServiceOffer offer(*itserv); offer.setMimeTypeInheritanceLevel(mimeTypeInheritanceLevel); //qCDebug(SYCOCA) << "INHERITANCE: Adding service" << (*itserv).service()->entryPath() << "to" << mimeTypeName << "mimeTypeInheritanceLevel=" << mimeTypeInheritanceLevel; m_offerHash.addServiceOffer(mimeTypeName, offer); } } } } void KBuildServiceFactory::postProcessServices() { // By doing all this here rather than in addEntry (and removing when replacing // with local override), we only do it for the final applications. // Note that this also affects resolution of the by-desktop-name lookup, // as name resolution is only performed *after* all the duplicates (based on // storage ID) have been removed. // For every service... KSycocaEntryDict::const_iterator itserv = m_entryDict->constBegin(); const KSycocaEntryDict::const_iterator endserv = m_entryDict->constEnd(); for (; itserv != endserv; ++itserv) { KSycocaEntry::Ptr entry = *itserv; KService::Ptr service(static_cast(entry.data())); if (!service->isDeleted()) { const QString parent = service->parentApp(); if (!parent.isEmpty()) { m_serviceGroupFactory->addNewChild(parent, KSycocaEntry::Ptr(service)); } } const QString name = service->desktopEntryName(); KService::Ptr dup = m_nameMemoryHash.value(name); if (dup) { // The rule is that searching for the desktop name "foo" should find // the desktop file with the storage id "foo.desktop" before it // finds "bar/foo.desktop" (or "bar-foo.desktop"). // "bar/foo.desktop" and "baz/foo.desktop" are arbitrarily ordered // (in practice, the one later in the alphabet wins). if (dup->storageId().endsWith(service->storageId())) { // allow dup to be overridden m_nameDict->remove(name); dup = nullptr; } } if (!dup) { m_nameDict->add(name, entry); m_nameMemoryHash.insert(name, service); } const QString relName = service->entryPath(); //qCDebug(SYCOCA) << "adding service" << service.data() << "isApp=" << service->isApplication() << "menuId=" << service->menuId() << "name=" << name << "relName=" << relName; m_relNameDict->add(relName, entry); m_relNameMemoryHash.insert(relName, service); // for KMimeAssociations const QString menuId = service->menuId(); if (!menuId.isEmpty()) { // empty for services, non-empty for applications m_menuIdDict->add(menuId, entry); m_menuIdMemoryHash.insert(menuId, service); // for KMimeAssociations } } populateServiceTypes(); } void KBuildServiceFactory::populateServiceTypes() { QMimeDatabase db; // For every service... KSycocaEntryDict::const_iterator itserv = m_entryDict->constBegin(); const KSycocaEntryDict::const_iterator endserv = m_entryDict->constEnd(); for (; itserv != endserv; ++itserv) { KService::Ptr service(static_cast((*itserv).data())); QVector serviceTypeList = service->_k_accessServiceTypes(); //bool hasAllAll = false; //bool hasAllFiles = false; // Add this service to all its servicetypes (and their parents) and to all its mimetypes for (int i = 0; i < serviceTypeList.count() /*don't cache it, it can change during iteration!*/; ++i) { const QString stName = serviceTypeList[i].serviceType; // It could be a servicetype or a mimetype. KServiceType::Ptr serviceType = m_serviceTypeFactory->findServiceTypeByName(stName); if (serviceType) { const int preference = serviceTypeList[i].preference; const QString parent = serviceType->parentServiceType(); if (!parent.isEmpty()) { serviceTypeList.append(KService::ServiceTypeAndPreference(preference, parent)); } //qCDebug(SYCOCA) << "Adding service" << service->entryPath() << "to" << serviceType->name() << "pref=" << preference; m_offerHash.addServiceOffer(stName, KServiceOffer(service, preference, 0, service->allowAsDefault())); } else { KServiceOffer offer(service, serviceTypeList[i].preference, 0, service->allowAsDefault()); QMimeType mime = db.mimeTypeForName(stName); if (!mime.isValid()) { if (stName.startsWith(QLatin1String("x-scheme-handler/"))) { // Create those on demand m_mimeTypeFactory->createFakeMimeType(stName); m_offerHash.addServiceOffer(stName, offer); } else { //qCDebug(SYCOCA) << service->entryPath() << "specifies undefined mimetype/servicetype" << stName; // technically we could call addServiceOffer here, 'mime' isn't used. But it // would be useless, since we have no mimetype entry where to write the offers offset. continue; } } else { bool shouldAdd = true; - foreach (const QString &otherType, service->serviceTypes()) { + const auto lst = service->serviceTypes(); + for (const QString &otherType : lst) { // Skip derived types if the base class is listed (#321706) if (stName != otherType && mime.inherits(otherType)) { // But don't skip aliases (they got resolved into mime->name() already, but don't let two aliases cancel out) if (db.mimeTypeForName(otherType).name() != mime.name()) { //qCDebug(SYCOCA) << "Skipping" << mime->name() << "because of" << otherType << "(canonical" << KMimeTypeRepository::self()->canonicalName(otherType) << ") while parsing" << service->entryPath(); shouldAdd = false; } } } if (shouldAdd) { //qCDebug(SYCOCA) << "Adding service" << service->entryPath() << "to" << mime->name(); m_offerHash.addServiceOffer(mime.name(), offer); // mime->name so that we resolve aliases } } } } } // Read user preferences (added/removed associations) and add/remove serviceoffers to m_offerHash KMimeAssociations mimeAssociations(m_offerHash, this); mimeAssociations.parseAllMimeAppsList(); // Now for each mimetype, collect services from parent mimetypes collectInheritedServices(); // Now collect the offsets into the (future) offer list // The loops look very much like the ones in saveOfferList obviously. int offersOffset = 0; const int offerEntrySize = sizeof(qint32) * 4; // four qint32s, see saveOfferList. const auto &offerHash = m_offerHash.serviceTypeData(); auto it = offerHash.constBegin(); const auto end = offerHash.constEnd(); for ( ; it != end ; ++it ) { const QString stName = it.key(); const ServiceTypeOffersData offersData = it.value(); const int numOffers = offersData.offers.count(); KServiceType::Ptr serviceType = m_serviceTypeFactory->findServiceTypeByName(stName); if (serviceType) { serviceType->setServiceOffersOffset(offersOffset); offersOffset += offerEntrySize * numOffers; } else { KMimeTypeFactory::MimeTypeEntry::Ptr entry = m_mimeTypeFactory->findMimeTypeEntryByName(stName); if (entry) { entry->setServiceOffersOffset(offersOffset); offersOffset += offerEntrySize * numOffers; } else if (stName.startsWith(QLatin1String("x-scheme-handler/"))) { // Create those on demand entry = m_mimeTypeFactory->createFakeMimeType(stName); entry->setServiceOffersOffset(offersOffset); offersOffset += offerEntrySize * numOffers; } else { if (stName.isEmpty()) { qCDebug(SYCOCA) << "Empty service type"; } else { qCWarning(SYCOCA) << "Service type not found:" << stName; } } } } } void KBuildServiceFactory::saveOfferList(QDataStream &str) { m_offerListOffset = str.device()->pos(); //qCDebug(SYCOCA) << "Saving offer list at offset" << m_offerListOffset; const auto &offerHash = m_offerHash.serviceTypeData(); auto it = offerHash.constBegin(); const auto end = offerHash.constEnd(); for ( ; it != end ; ++it ) { const QString stName = it.key(); const ServiceTypeOffersData offersData = it.value(); QList offers = offersData.offers; std::stable_sort(offers.begin(), offers.end()); // by initial preference int offset = -1; KServiceType::Ptr serviceType = m_serviceTypeFactory->findServiceTypeByName(stName); if (serviceType) { offset = serviceType->offset(); } else { KMimeTypeFactory::MimeTypeEntry::Ptr entry = m_mimeTypeFactory->findMimeTypeEntryByName(stName); if (entry) { offset = entry->offset(); //Q_ASSERT(str.device()->pos() == entry->serviceOffersOffset() + m_offerListOffset); } } if (offset == -1) { qCDebug(SYCOCA) << "Didn't find servicetype or mimetype" << stName; continue; } for (QList::const_iterator it2 = offers.constBegin(); it2 != offers.constEnd(); ++it2) { //qCDebug(SYCOCA) << stName << ":" << "writing offer" << (*it2).service()->desktopEntryName() << offset << (*it2).service()->offset() << "in sycoca at pos" << str.device()->pos(); Q_ASSERT((*it2).service()->offset() != 0); str << qint32(offset); str << qint32((*it2).service()->offset()); str << qint32((*it2).preference()); str << qint32((*it2).mimeTypeInheritanceLevel()); // update offerEntrySize in populateServiceTypes if you add/remove something here } } str << qint32(0); // End of list marker (0) } void KBuildServiceFactory::addEntry(const KSycocaEntry::Ptr &newEntry) { Q_ASSERT(newEntry); if (m_dupeDict.contains(newEntry)) { return; } const KService::Ptr service(static_cast(newEntry.data())); m_dupeDict.insert(newEntry); KSycocaFactory::addEntry(newEntry); } diff --git a/src/sycoca/kbuildsycoca.cpp b/src/sycoca/kbuildsycoca.cpp index f078491..b125299 100644 --- a/src/sycoca/kbuildsycoca.cpp +++ b/src/sycoca/kbuildsycoca.cpp @@ -1,661 +1,668 @@ /* This file is part of the KDE libraries * Copyright (C) 1999 David Faure * Copyright (C) 2002-2003 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. **/ #include "kbuildsycoca_p.h" #include "ksycoca_p.h" #include "ksycocaresourcelist_p.h" #include "vfolder_menu_p.h" #include "ksycocautils_p.h" #include "sycocadebug.h" #include #include #include #include "kbuildservicetypefactory_p.h" #include "kbuildmimetypefactory_p.h" #include "kbuildservicefactory_p.h" #include "kbuildservicegroupfactory_p.h" #include "kctimefactory_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // auto_ptr #include #include static const char *s_cSycocaPath = nullptr; KBuildSycocaInterface::~KBuildSycocaInterface() {} KBuildSycoca::KBuildSycoca(bool globalDatabase) : KSycoca(true), m_allEntries(nullptr), m_currentFactory(nullptr), m_ctimeFactory(nullptr), m_ctimeDict(nullptr), m_currentEntryDict(nullptr), m_serviceGroupEntryDict(nullptr), m_vfolder(nullptr), m_newTimestamp(0), m_globalDatabase(globalDatabase), m_menuTest(false), m_changed(false) { } KBuildSycoca::~KBuildSycoca() { // Delete the factories while we exist, so that the virtual isBuilding() still works qDeleteAll(*factories()); factories()->clear(); } KSycocaEntry::Ptr KBuildSycoca::createEntry(const QString &file, bool addToFactory) { quint32 timeStamp = m_ctimeFactory->dict()->ctime(file, m_resource); if (!timeStamp) { timeStamp = calcResourceHash(m_resourceSubdir, file); } KSycocaEntry::Ptr entry; if (m_allEntries) { Q_ASSERT(m_ctimeDict); quint32 oldTimestamp = m_ctimeDict->ctime(file, m_resource); if (file.contains(QLatin1String("fake"))) { qCDebug(SYCOCA) << "m_ctimeDict->ctime(" << file << ") = " << oldTimestamp << "compared with" << timeStamp; } if (timeStamp && (timeStamp == oldTimestamp)) { // Re-use old entry if (m_currentFactory == d->m_serviceFactory) { // Strip .directory from service-group entries entry = m_currentEntryDict->value(file.left(file.length() - 10)); } else { entry = m_currentEntryDict->value(file); } // remove from m_ctimeDict; if m_ctimeDict is not empty // after all files have been processed, it means // some files were removed since last time if (file.contains(QLatin1String("fake"))) { qCDebug(SYCOCA) << "reusing (and removing) old entry for:" << file << "entry=" << entry; } m_ctimeDict->remove(file, m_resource); } else if (oldTimestamp) { m_changed = true; m_ctimeDict->remove(file, m_resource); qCDebug(SYCOCA) << "modified:" << file; } else { m_changed = true; qCDebug(SYCOCA) << "new:" << file; } } m_ctimeFactory->dict()->addCTime(file, m_resource, timeStamp); if (!entry) { // Create a new entry entry = m_currentFactory->createEntry(file); } if (entry && entry->isValid()) { if (addToFactory) { m_currentFactory->addEntry(entry); } else { m_tempStorage.append(entry); } return entry; } return KSycocaEntry::Ptr(); } KService::Ptr KBuildSycoca::createService(const QString &path) { KSycocaEntry::Ptr entry = createEntry(path, false); return KService::Ptr(static_cast(entry.data())); } // returns false if the database is up to date, true if it needs to be saved bool KBuildSycoca::build() { typedef QList KBSEntryDictList; KBSEntryDictList entryDictList; KBSEntryDict *serviceEntryDict = nullptr; // Convert for each factory the entryList to a Dict. entryDictList.reserve(factories()->size()); int i = 0; // For each factory - Q_FOREACH (KSycocaFactory* factory, *factories()) { + auto list = *factories(); + for (KSycocaFactory* factory : qAsConst(list)) { KBSEntryDict *entryDict = new KBSEntryDict; if (m_allEntries) { // incremental build - Q_FOREACH (const KSycocaEntry::Ptr &entry, (*m_allEntries)[i++]) { + for (const KSycocaEntry::Ptr &entry : qAsConst((*m_allEntries)[i++])) { //if (entry->entryPath().contains("fake")) // qCDebug(SYCOCA) << "inserting into entryDict:" << entry->entryPath() << entry; entryDict->insert(entry->entryPath(), entry); } } if (factory == d->m_serviceFactory) { serviceEntryDict = entryDict; } else if (factory == m_buildServiceGroupFactory) { m_serviceGroupEntryDict = entryDict; } entryDictList.append(entryDict); } // Save the mtime of each dir, just before we list them // ## should we convert to UTC to avoid surprises when summer time kicks in? - Q_FOREACH (const QString &dir, factoryResourceDirs()) { + const auto lstDirs = factoryResourceDirs(); + for (const QString &dir : lstDirs) { qint64 stamp = 0; KSycocaUtilsPrivate::visitResourceDirectory(dir, [&stamp] (const QFileInfo &info) { stamp = qMax(stamp, info.lastModified().toMSecsSinceEpoch()); return true; }); m_allResourceDirs.insert(dir, stamp); } QMap allResourcesSubDirs; // dirs, kstandarddirs-resource-name // For each factory - Q_FOREACH (KSycocaFactory* factory, *factories()) { + list = *factories(); + for (KSycocaFactory* factory : qAsConst(list)) { // For each resource the factory deals with const KSycocaResourceList *list = factory->resourceList(); if (!list) { continue; } - Q_FOREACH (const KSycocaResource &res, *list) { + for (const KSycocaResource &res : qAsConst(*list)) { // With this we would get dirs, but not a unique list of relative files (for global+local merging to work) //const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, res.subdir, QStandardPaths::LocateDirectory); //allResourcesSubDirs[res.resource] += dirs; allResourcesSubDirs.insert(res.subdir, res.resource); } } m_ctimeFactory = new KCTimeFactory(this); // This is a build factory too, don't delete!! for (QMap::ConstIterator it1 = allResourcesSubDirs.constBegin(); it1 != allResourcesSubDirs.constEnd(); ++it1) { m_changed = false; m_resourceSubdir = it1.key(); m_resource = it1.value(); QSet relFiles; const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, m_resourceSubdir, QStandardPaths::LocateDirectory); qCDebug(SYCOCA) << "Looking for subdir" << m_resourceSubdir << "=>" << dirs; - Q_FOREACH (const QString &dir, dirs) { + for (const QString &dir : dirs) { QDirIterator it(dir, QDirIterator::Subdirectories); while (it.hasNext()) { const QString filePath = it.next(); Q_ASSERT(filePath.startsWith(dir)); // due to the line below... const QString relPath = filePath.mid(dir.length() + 1); relFiles.insert(relPath); } } // Now find all factories that use this resource.... // For each factory KBSEntryDictList::const_iterator ed_it = entryDictList.constBegin(); const KBSEntryDictList::const_iterator ed_end = entryDictList.constEnd(); KSycocaFactoryList::const_iterator it = factories()->constBegin(); const KSycocaFactoryList::const_iterator end = factories()->constEnd(); for (; it != end; ++it, ++ed_it) { m_currentFactory = (*it); // m_ctimeInfo gets created after the initial loop, so it has no entryDict. m_currentEntryDict = ed_it == ed_end ? nullptr : *ed_it; // For each resource the factory deals with const KSycocaResourceList *list = m_currentFactory->resourceList(); if (!list) { continue; } - Q_FOREACH (const KSycocaResource &res, *list) { + for (const KSycocaResource &res : qAsConst(*list)) { if (res.resource != (*it1)) { continue; } // For each file in the resource for (auto entryPath = relFiles.constBegin(); entryPath != relFiles.constEnd(); ++entryPath) { // Check if file matches filter if ((*entryPath).endsWith(res.extension)) { createEntry(*entryPath, true); } } } } if (m_changed || !m_allEntries) { //qCDebug(SYCOCA) << "CHANGED:" << m_resource; m_changedResources.append(QString::fromLatin1(m_resource)); } } bool result = true; const bool createVFolder = true; // we need to always run the VFolderMenu code if (createVFolder || m_menuTest) { m_resource = "apps"; m_resourceSubdir = QStringLiteral("applications"); m_currentFactory = d->m_serviceFactory; m_currentEntryDict = serviceEntryDict; m_changed = false; m_vfolder = new VFolderMenu(d->m_serviceFactory, this); if (!m_trackId.isEmpty()) { m_vfolder->setTrackId(m_trackId); } VFolderMenu::SubMenu *kdeMenu = m_vfolder->parseMenu(QStringLiteral(APPLICATIONS_MENU_NAME)); KServiceGroup::Ptr entry = m_buildServiceGroupFactory->addNew(QStringLiteral("/"), kdeMenu->directoryFile, KServiceGroup::Ptr(), false); entry->setLayoutInfo(kdeMenu->layoutList); createMenu(QString(), QString(), kdeMenu); // Storing the mtime *after* looking at these dirs is a tiny race condition, // but I'm not sure how to get the vfolder dirs upfront... - Q_FOREACH (QString dir, m_vfolder->allDirectories()) { + const auto allDirectories = m_vfolder->allDirectories(); + for (QString dir : allDirectories) { if (dir.endsWith(QLatin1Char('/'))) { dir.chop(1); // remove trailing slash, to avoid having ~/.local/share/applications twice } if (!m_allResourceDirs.contains(dir)) { qint64 stamp = 0; KSycocaUtilsPrivate::visitResourceDirectory(dir, [&stamp] (const QFileInfo &info) { stamp = qMax(stamp, info.lastModified().toMSecsSinceEpoch()); return true; }); m_allResourceDirs.insert(dir, stamp); } } if (m_changed || !m_allEntries) { //qCDebug(SYCOCA) << "CHANGED:" << m_resource; m_changedResources.append(QString::fromLatin1(m_resource)); } if (m_menuTest) { result = false; } } if (m_ctimeDict && !m_ctimeDict->isEmpty()) { qCDebug(SYCOCA) << "Still in time dict:"; m_ctimeDict->dump(); // Get the list of resources from which some files were deleted QStringList resources = m_ctimeDict->remainingResourceList(); qCDebug(SYCOCA) << "Still in the time dict (i.e. deleted files)" << resources; m_changedResources += resources; } qDeleteAll(entryDictList); return result; } void KBuildSycoca::createMenu(const QString &caption_, const QString &name_, VFolderMenu::SubMenu *menu) { QString caption = caption_; QString name = name_; - foreach (VFolderMenu::SubMenu *subMenu, menu->subMenus) { + for (VFolderMenu::SubMenu *subMenu : qAsConst(menu->subMenus)) { QString subName = name + subMenu->name + QLatin1Char('/'); QString directoryFile = subMenu->directoryFile; if (directoryFile.isEmpty()) { directoryFile = subName + QStringLiteral(".directory"); } quint32 timeStamp = m_ctimeFactory->dict()->ctime(directoryFile, m_resource); if (!timeStamp) { timeStamp = calcResourceHash(m_resourceSubdir, directoryFile); } KServiceGroup::Ptr entry; if (m_allEntries) { const quint32 oldTimestamp = m_ctimeDict->ctime(directoryFile, m_resource); if (timeStamp && (timeStamp == oldTimestamp)) { KSycocaEntry::Ptr group = m_serviceGroupEntryDict->value(subName); if (group) { entry = KServiceGroup::Ptr(static_cast(group.data())); if (entry->directoryEntryPath() != directoryFile) { entry = nullptr; // Can't reuse this one! } } } } if (timeStamp) { // bug? (see calcResourceHash). There might not be a .directory file... m_ctimeFactory->dict()->addCTime(directoryFile, m_resource, timeStamp); } entry = m_buildServiceGroupFactory->addNew(subName, subMenu->directoryFile, entry, subMenu->isDeleted); entry->setLayoutInfo(subMenu->layoutList); if (!(m_menuTest && entry->noDisplay())) { createMenu(caption + entry->caption() + QLatin1Char('/'), subName, subMenu); } } if (caption.isEmpty()) { caption += QLatin1Char('/'); } if (name.isEmpty()) { name += QLatin1Char('/'); } - foreach (const KService::Ptr &p, menu->items) { + for (const KService::Ptr &p : qAsConst(menu->items)) { if (m_menuTest) { if (!menu->isDeleted && !p->noDisplay()) printf("%s\t%s\t%s\n", qPrintable(caption), qPrintable(p->menuId()), qPrintable(QStandardPaths::locate(QStandardPaths::ApplicationsLocation, p->entryPath()))); } else { m_buildServiceGroupFactory->addNewEntryTo(name, p); } } } bool KBuildSycoca::recreate(bool incremental) { QFileInfo fi(KSycoca::absoluteFilePath(m_globalDatabase ? KSycoca::GlobalDatabase : KSycoca::LocalDatabase)); if (!QDir().mkpath(fi.absolutePath())) { qCWarning(SYCOCA) << "Couldn't create" << fi.absolutePath(); return false; } QString path(fi.absoluteFilePath()); QLockFile lockFile(path + QLatin1String(".lock")); if (!lockFile.tryLock()) { qCDebug(SYCOCA) << "Waiting for already running" << KBUILDSYCOCA_EXENAME << "to finish."; if (!lockFile.lock()) { qCWarning(SYCOCA) << "Couldn't lock" << path + QStringLiteral(".lock"); return false; } if (!needsRebuild()) { //qCDebug(SYCOCA) << "Up-to-date, skipping."; return true; } } QByteArray qSycocaPath = QFile::encodeName(path); s_cSycocaPath = qSycocaPath.data(); m_allEntries = nullptr; m_ctimeDict = nullptr; if (incremental && checkGlobalHeader()) { qCDebug(SYCOCA) << "Reusing existing ksycoca"; KSycoca *oldSycoca = KSycoca::self(); m_allEntries = new KSycocaEntryListList; m_ctimeDict = new KCTimeDict; // Must be in same order as in KBuildSycoca::recreate()! m_allEntries->append(KSycocaPrivate::self()->serviceTypeFactory()->allEntries()); m_allEntries->append(KSycocaPrivate::self()->mimeTypeFactory()->allEntries()); m_allEntries->append(KSycocaPrivate::self()->serviceGroupFactory()->allEntries()); m_allEntries->append(KSycocaPrivate::self()->serviceFactory()->allEntries()); KCTimeFactory *ctimeInfo = new KCTimeFactory(oldSycoca); *m_ctimeDict = ctimeInfo->loadDict(); } s_cSycocaPath = nullptr; QSaveFile database(path); bool openedOK = database.open(QIODevice::WriteOnly); if (!openedOK && database.error() == QFile::WriteError && QFile::exists(path)) { QFile::remove(path); openedOK = database.open(QIODevice::WriteOnly); } if (!openedOK) { qCWarning(SYCOCA) << "ERROR creating database" << path << ":" << database.errorString(); return false; } QDataStream *str = new QDataStream(&database); str->setVersion(QDataStream::Qt_5_3); m_newTimestamp = QDateTime::currentMSecsSinceEpoch(); qCDebug(SYCOCA).nospace() << "Recreating ksycoca file (" << path << ", version " << KSycoca::version() << ")"; // It is very important to build the servicetype one first KBuildServiceTypeFactory *buildServiceTypeFactory = new KBuildServiceTypeFactory(this); d->m_serviceTypeFactory = buildServiceTypeFactory; KBuildMimeTypeFactory *buildMimeTypeFactory = new KBuildMimeTypeFactory(this); d->m_mimeTypeFactory = buildMimeTypeFactory; m_buildServiceGroupFactory = new KBuildServiceGroupFactory(this); d->m_serviceGroupFactory = m_buildServiceGroupFactory; d->m_serviceFactory = new KBuildServiceFactory(buildServiceTypeFactory, buildMimeTypeFactory, m_buildServiceGroupFactory); if (build()) { // Parse dirs save(str); // Save database if (str->status() != QDataStream::Ok) { // Probably unnecessary now in Qt5, since QSaveFile detects write errors database.cancelWriting(); // Error } delete str; str = nullptr; //if we are currently via sudo, preserve the original owner //as $HOME may also be that of another user rather than /root #ifdef Q_OS_UNIX if (qEnvironmentVariableIsSet("SUDO_UID")) { const int uid = qEnvironmentVariableIntValue("SUDO_UID"); const int gid = qEnvironmentVariableIntValue("SUDO_GID"); if (uid && gid) { fchown(database.handle(), uid, gid); } } #endif if (!database.commit()) { qCWarning(SYCOCA) << "ERROR writing database" << database.fileName() << ". Disk full?"; return false; } if (!m_globalDatabase) { // Compatibility code for KF < 5.15: provide a ksycoca5 symlink after the filename change, for old apps to keep working during the upgrade const QString oldSycoca = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/ksycoca5"); if (QFile::exists(oldSycoca)) { QFile::remove(oldSycoca); #ifdef Q_OS_UNIX if (::link(QFile::encodeName(path).constData(), QFile::encodeName(oldSycoca).constData()) != 0) { QFile::copy(path, oldSycoca); } #else QFile::copy(path, oldSycoca); #endif } } } else { delete str; str = nullptr; database.cancelWriting(); if (m_menuTest) { return true; } qCDebug(SYCOCA) << "Database is up to date"; } if (m_globalDatabase) { // These directories may have been created with 0700 permission // better delete them if they are empty QString appsDir = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); QDir().remove(appsDir); // was doing the same with servicetypes, but I don't think any of these gets created-by-mistake anymore. } if (d->m_sycocaStrategy == KSycocaPrivate::StrategyMemFile) { KMemFile::fileContentsChanged(path); } delete m_ctimeDict; delete m_allEntries; delete m_vfolder; return true; } void KBuildSycoca::save(QDataStream *str) { // Write header (#pass 1) str->device()->seek(0); (*str) << qint32(KSycoca::version()); //KSycocaFactory * servicetypeFactory = 0; //KBuildMimeTypeFactory * mimeTypeFactory = 0; KBuildServiceFactory *serviceFactory = nullptr; - Q_FOREACH (KSycocaFactory* factory, *factories()) { + auto lst = *factories(); + for (KSycocaFactory* factory : qAsConst(lst)) { qint32 aId; qint32 aOffset; aId = factory->factoryId(); //if ( aId == KST_KServiceTypeFactory ) // servicetypeFactory = factory; //else if ( aId == KST_KMimeTypeFactory ) // mimeTypeFactory = static_cast( factory ); if (aId == KST_KServiceFactory) { serviceFactory = static_cast(factory); } aOffset = factory->offset(); // not set yet, so always 0 (*str) << aId; (*str) << aOffset; } (*str) << qint32(0); // No more factories. // Write XDG_DATA_DIRS (*str) << QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).join(QString(QLatin1Char(':'))); (*str) << m_newTimestamp; (*str) << QLocale().bcp47Name(); // This makes it possible to trigger a ksycoca update for all users (KIOSK feature) (*str) << calcResourceHash(QStringLiteral("kservices5"), QStringLiteral("update_ksycoca")); (*str) << m_allResourceDirs.keys(); for (auto it = m_allResourceDirs.constBegin(); it != m_allResourceDirs.constEnd(); ++it) { (*str) << it.value(); } // Calculate per-servicetype/mimetype data if (serviceFactory) serviceFactory->postProcessServices(); // Here so that it's the last debug message qCDebug(SYCOCA) << "Saving"; // Write factory data.... - Q_FOREACH (KSycocaFactory* factory, *factories()) { + lst = *factories(); + for (KSycocaFactory* factory : qAsConst(lst)) { factory->save(*str); if (str->status() != QDataStream::Ok) { // ######## TODO: does this detect write errors, e.g. disk full? return; // error } } qint64 endOfData = str->device()->pos(); // Write header (#pass 2) str->device()->seek(0); (*str) << qint32(KSycoca::version()); - Q_FOREACH (KSycocaFactory* factory, *factories()) { + lst = *factories(); + for (KSycocaFactory* factory : qAsConst(lst)) { qint32 aId; qint32 aOffset; aId = factory->factoryId(); aOffset = factory->offset(); (*str) << aId; (*str) << aOffset; } (*str) << qint32(0); // No more factories. // Jump to end of database str->device()->seek(endOfData); } QStringList KBuildSycoca::factoryResourceDirs() { static QStringList *dirs = nullptr; if (dirs != nullptr) { return *dirs; } dirs = new QStringList; // these are all resource dirs cached by ksycoca *dirs += KServiceTypeFactory::resourceDirs(); *dirs += KMimeTypeFactory::resourceDirs(); *dirs += KServiceFactory::resourceDirs(); return *dirs; } QStringList KBuildSycoca::existingResourceDirs() { static QStringList *dirs = nullptr; if (dirs != nullptr) { return *dirs; } dirs = new QStringList(factoryResourceDirs()); for (QStringList::Iterator it = dirs->begin(); it != dirs->end();) { QFileInfo inf(*it); if (!inf.exists() || !inf.isReadable()) { it = dirs->erase(it); } else { ++it; } } return *dirs; } static quint32 updateHash(const QString &file, quint32 hash) { QFileInfo fi(file); if (fi.isReadable() && fi.isFile()) { // This was using buff.st_ctime (in Waldo's initial commit to kstandarddirs.cpp in 2001), but that looks wrong? // Surely we want to catch manual editing, while a chmod doesn't matter much? hash += fi.lastModified().toSecsSinceEpoch(); } return hash; } quint32 KBuildSycoca::calcResourceHash(const QString &resourceSubDir, const QString &filename) { quint32 hash = 0; if (!QDir::isRelativePath(filename)) { return updateHash(filename, hash); } const QStringList files = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, resourceSubDir + QLatin1Char('/') + filename); - Q_FOREACH (const QString &file, files) { + for (const QString &file : files) { hash = updateHash(file, hash); } if (hash == 0 && !filename.endsWith(QLatin1String("update_ksycoca")) && !filename.endsWith(QLatin1String(".directory")) // bug? needs investigation from someone who understands the VFolder spec ) { qCWarning(SYCOCA) << "File not found or not readable:" << filename << "found:" << files; Q_ASSERT(hash != 0); } return hash; } bool KBuildSycoca::checkGlobalHeader() { // Since it's part of the filename, we are 99% sure that the locale and prefixes will match. const QString current_language = QLocale().bcp47Name(); const quint32 current_update_sig = KBuildSycoca::calcResourceHash(QStringLiteral("kservices5"), QStringLiteral("update_ksycoca")); const QString current_prefixes = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).join(QString(QLatin1Char(':'))); const KSycocaHeader header = KSycocaPrivate::self()->readSycocaHeader(); Q_ASSERT(!header.prefixes.split(QLatin1Char(':')).contains(QDir::homePath())); return (current_update_sig == header.updateSignature) && (current_language == header.language) && (current_prefixes == header.prefixes) && (header.timeStamp != 0); } const char *KBuildSycoca::sycocaPath() { return s_cSycocaPath; } diff --git a/src/sycoca/kmimeassociations.cpp b/src/sycoca/kmimeassociations.cpp index 4713e58..98de2fb 100644 --- a/src/sycoca/kmimeassociations.cpp +++ b/src/sycoca/kmimeassociations.cpp @@ -1,189 +1,192 @@ /* This file is part of the KDE libraries * Copyright 2008 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 Lesser 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 "kmimeassociations_p.h" #include #include #include #include #include #include #include #include #include "sycocadebug.h" KMimeAssociations::KMimeAssociations(KOfferHash &offerHash, KServiceFactory *serviceFactory) : m_offerHash(offerHash), m_serviceFactory(serviceFactory) { } /* The goal of this class is to parse mimeapps.list files, which are used to let users configure the application-mimetype associations. Example file: [Added Associations] text/plain=kate.desktop; [Removed Associations] text/plain=gnome-gedit.desktop;gnu-emacs.desktop; */ void KMimeAssociations::parseAllMimeAppsList() { QStringList mimeappsFileNames; // make the list of possible filenames from the spec ($desktop-mimeapps.list, then mimeapps.list) const QString desktops = QString::fromLocal8Bit(qgetenv("XDG_CURRENT_DESKTOP")); - foreach (const QString &desktop, desktops.split(QLatin1Char(':'), QString::SkipEmptyParts)) { + const auto list = desktops.split(QLatin1Char(':'), QString::SkipEmptyParts); + for (const QString &desktop : list) { mimeappsFileNames.append(desktop.toLower() + QLatin1String("-mimeapps.list")); } mimeappsFileNames.append(QStringLiteral("mimeapps.list")); // list the dirs in the order of the spec (XDG_CONFIG_HOME, XDG_CONFIG_DIRS, XDG_DATA_HOME, XDG_DATA_DIRS) const QStringList mimeappsDirs = QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation) + QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); QStringList mimeappsFiles; // collect existing files - foreach (const QString &dir, mimeappsDirs) { - foreach (const QString &file, mimeappsFileNames) { + for (const QString &dir : mimeappsDirs) { + for (const QString &file : qAsConst(mimeappsFileNames)) { const QString filePath = dir + QLatin1Char('/') + file; if (QFile::exists(filePath)) { mimeappsFiles.append(filePath); } } } //qDebug() << "FILE LIST:" << mimeappsFiles; int basePreference = 1000; // start high :) QListIterator mimeappsIter(mimeappsFiles); mimeappsIter.toBack(); while (mimeappsIter.hasPrevious()) { // global first, then local. const QString mimeappsFile = mimeappsIter.previous(); //qDebug() << "Parsing" << mimeappsFile; parseMimeAppsList(mimeappsFile, basePreference); basePreference += 50; } } void KMimeAssociations::parseMimeAppsList(const QString &file, int basePreference) { KConfig profile(file, KConfig::SimpleConfig); if (file.endsWith(QLatin1String("/mimeapps.list"))) { // not for $desktop-mimeapps.list parseAddedAssociations(KConfigGroup(&profile, "Added Associations"), file, basePreference); parseRemovedAssociations(KConfigGroup(&profile, "Removed Associations"), file); // KDE extension for parts and plugins, see settings/filetypes/mimetypedata.cpp parseAddedAssociations(KConfigGroup(&profile, "Added KDE Service Associations"), file, basePreference); parseRemovedAssociations(KConfigGroup(&profile, "Removed KDE Service Associations"), file); } // TODO "Default Applications" is a separate query and a separate algorithm, says the spec. // For now this is better than nothing though. parseAddedAssociations(KConfigGroup(&profile, "Default Applications"), file, basePreference); } void KMimeAssociations::parseAddedAssociations(const KConfigGroup &group, const QString &file, int basePreference) { Q_UNUSED(file) // except in debug statements QMimeDatabase db; - Q_FOREACH (const QString &mimeName, group.keyList()) { + const auto keyList = group.keyList(); + for (const QString &mimeName : keyList) { const QStringList services = group.readXdgListEntry(mimeName); const QString resolvedMimeName = mimeName.startsWith(QLatin1String("x-scheme-handler/")) ? mimeName : db.mimeTypeForName(mimeName).name(); if (resolvedMimeName.isEmpty()) { qCDebug(SYCOCA) << file << "specifies unknown mimeType" << mimeName << "in" << group.name(); } else { int pref = basePreference; - Q_FOREACH (const QString &service, services) { + for (const QString &service : services) { KService::Ptr pService = m_serviceFactory->findServiceByStorageId(service); if (!pService) { qCDebug(SYCOCA) << file << "specifies unknown service" << service << "in" << group.name(); } else { //qDebug() << "adding mime" << resolvedMimeName << "to service" << pService->entryPath() << "pref=" << pref; m_offerHash.addServiceOffer(resolvedMimeName, KServiceOffer(pService, pref, 0, pService->allowAsDefault())); --pref; } } } } } void KMimeAssociations::parseRemovedAssociations(const KConfigGroup &group, const QString &file) { Q_UNUSED(file) // except in debug statements - Q_FOREACH (const QString &mime, group.keyList()) { + const auto keyList = group.keyList(); + for (const QString &mime : keyList) { const QStringList services = group.readXdgListEntry(mime); - Q_FOREACH (const QString &service, services) { + for (const QString &service : services) { KService::Ptr pService = m_serviceFactory->findServiceByStorageId(service); if (!pService) { //qDebug() << file << "specifies unknown service" << service << "in" << group.name(); } else { //qDebug() << "removing mime" << mime << "from service" << pService.data() << pService->entryPath(); m_offerHash.removeServiceOffer(mime, pService); } } } } void KOfferHash::addServiceOffer(const QString &serviceType, const KServiceOffer &offer) { KService::Ptr service = offer.service(); //qDebug() << "Adding" << service->entryPath() << "to" << serviceType << offer.preference(); ServiceTypeOffersData &data = m_serviceTypeData[serviceType]; // find or create QList &offers = data.offers; QSet &offerSet = data.offerSet; if (!offerSet.contains(service)) { offers.append(offer); offerSet.insert(service); } else { //qDebug() << service->entryPath() << "already in" << serviceType; // This happens when mimeapps.list mentions a service (to make it preferred) // Update initialPreference to qMax(existing offer, new offer) QMutableListIterator sfit(data.offers); while (sfit.hasNext()) { if (sfit.next().service() == service) { // we can compare KService::Ptrs because they are from the memory hash sfit.value().setPreference(qMax(sfit.value().preference(), offer.preference())); } } } } void KOfferHash::removeServiceOffer(const QString &serviceType, const KService::Ptr &service) { ServiceTypeOffersData &data = m_serviceTypeData[serviceType]; // find or create data.removedOffers.insert(service); data.offerSet.remove(service); QMutableListIterator sfit(data.offers); while (sfit.hasNext()) { if (sfit.next().service()->storageId() == service->storageId()) { sfit.remove(); } } } bool KOfferHash::hasRemovedOffer(const QString &serviceType, const KService::Ptr &service) const { QHash::const_iterator it = m_serviceTypeData.find(serviceType); if (it != m_serviceTypeData.end()) { return (*it).removedOffers.contains(service); } return false; } diff --git a/src/sycoca/ksycocadict.cpp b/src/sycoca/ksycocadict.cpp index 3b186a3..c5d1b4c 100644 --- a/src/sycoca/ksycocadict.cpp +++ b/src/sycoca/ksycocadict.cpp @@ -1,572 +1,572 @@ /* 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. **/ #include "ksycocadict_p.h" #include "sycocadebug.h" #include #include "ksycocaentry.h" #include "ksycoca.h" #include #include #include namespace { struct string_entry { string_entry(const QString &_key, const KSycocaEntry::Ptr &_payload) : hash(0), length(_key.length()), keyStr(_key), key(keyStr.unicode()), payload(_payload) {} uint hash; const int length; const QString keyStr; const QChar *const key; // always points to keyStr.unicode(); just an optimization const KSycocaEntry::Ptr payload; }; } class KSycocaDictStringList : public QList { public: KSycocaDictStringList() { } ~KSycocaDictStringList() { qDeleteAll(*this); } KSycocaDictStringList(const KSycocaDictStringList &) = delete; KSycocaDictStringList& operator=(const KSycocaDictStringList &) = delete; }; class KSycocaDictPrivate { public: KSycocaDictPrivate() : stream(nullptr) , offset(0) , hashTableSize(0) { } ~KSycocaDictPrivate() { } // Helper for find_string and findMultiString qint32 offsetForKey(const QString &key) const; // Calculate hash - can be used during loading and during saving. quint32 hashKey(const QString &key) const; KSycocaDictStringList stringlist; QDataStream *stream; qint64 offset; quint32 hashTableSize; QList hashList; }; KSycocaDict::KSycocaDict() : d(new KSycocaDictPrivate) { } KSycocaDict::KSycocaDict(QDataStream *str, int offset) : d(new KSycocaDictPrivate) { d->stream = str; d->offset = offset; quint32 test1, test2; str->device()->seek(offset); (*str) >> test1 >> test2; if ((test1 > 0x000fffff) || (test2 > 1024)) { KSycoca::flagError(); d->hashTableSize = 0; d->offset = 0; return; } str->device()->seek(offset); (*str) >> d->hashTableSize; (*str) >> d->hashList; d->offset = str->device()->pos(); // Start of hashtable } KSycocaDict::~KSycocaDict() { delete d; } void KSycocaDict::add(const QString &key, const KSycocaEntry::Ptr &payload) { if (key.isEmpty()) { return; // Not allowed (should never happen) } if (!payload) { return; // Not allowed! } d->stringlist.append(new string_entry(key, payload)); } void KSycocaDict::remove(const QString &key) { if (!d) { return; } bool found = false; for (KSycocaDictStringList::Iterator it = d->stringlist.begin(); it != d->stringlist.end(); ++it) { string_entry *entry = *it; if (entry->keyStr == key) { d->stringlist.erase(it); delete entry; found = true; break; } } if (!found) { qCDebug(SYCOCA) << "key not found:" << key; } } int KSycocaDict::find_string(const QString &key) const { Q_ASSERT(d); //qCDebug(SYCOCA) << QString("KSycocaDict::find_string(%1)").arg(key); qint32 offset = d->offsetForKey(key); //qCDebug(SYCOCA) << QString("offset is %1").arg(offset,8,16); if (offset == 0) { return 0; } if (offset > 0) { return offset; // Positive ID } // Lookup duplicate list. offset = -offset; d->stream->device()->seek(offset); //qCDebug(SYCOCA) << QString("Looking up duplicate list at %1").arg(offset,8,16); while (true) { (*d->stream) >> offset; if (offset == 0) { break; } QString dupkey; (*d->stream) >> dupkey; //qCDebug(SYCOCA) << QString(">> %1 %2").arg(offset,8,16).arg(dupkey); if (dupkey == key) { return offset; } } //qCDebug(SYCOCA) << "Not found!"; return 0; } QList KSycocaDict::findMultiString(const QString &key) const { qint32 offset = d->offsetForKey(key); QList offsetList; if (offset == 0) { return offsetList; } if (offset > 0) { // Positive ID: one entry found offsetList.append(offset); return offsetList; } // Lookup duplicate list. offset = -offset; d->stream->device()->seek(offset); //qCDebug(SYCOCA) << QString("Looking up duplicate list at %1").arg(offset,8,16); while (true) { (*d->stream) >> offset; if (offset == 0) { break; } QString dupkey; (*d->stream) >> dupkey; //qCDebug(SYCOCA) << QString(">> %1 %2").arg(offset,8,16).arg(dupkey); if (dupkey == key) { offsetList.append(offset); } } return offsetList; } uint KSycocaDict::count() const { if (!d) { return 0; } return d->stringlist.count(); } void KSycocaDict::clear() { delete d; d = nullptr; } uint KSycocaDictPrivate::hashKey(const QString &key) const { int len = key.length(); uint h = 0; for (int i = 0; i < hashList.count(); i++) { int pos = hashList[i]; if (pos == 0) { continue; } else if (pos < 0) { pos = -pos; if (pos < len) { h = ((h * 13) + (key[len - pos].cell() % 29)) & 0x3ffffff; } } else { pos = pos - 1; if (pos < len) { h = ((h * 13) + (key[pos].cell() % 29)) & 0x3ffffff; } } } return h; } // If we have the strings // hello // world // kde // Then we end up with // ABCDE // where A = diversity of 'h' + 'w' + 'k' etc. // Also, diversity(-2) == 'l'+'l'+'d' (second character from the end) // The hasList is used for hashing: // hashList = (-2, 1, 3) means that the hash key comes from // the 2nd character from the right, then the 1st from the left, then the 3rd from the left. // Calculate the diversity of the strings at position 'pos' // NOTE: this code is slow, it takes 12% of the _overall_ `kbuildsycoca5 --noincremental` running time static int calcDiversity(KSycocaDictStringList* stringlistp, int inPos, uint sz) { if (inPos == 0) { return 0; } KSycocaDictStringList& stringlist = *stringlistp; QBitArray matrix(sz); int pos; //static const int s_maxItems = 50; //int numItem = 0; if (inPos < 0) { pos = -inPos; for (auto it = stringlist.constBegin(), end = stringlist.constEnd(); it != end; ++it) { string_entry *entry = *it; int rpos = entry->length - pos; if (rpos > 0) { uint hash = ((entry->hash * 13) + (entry->key[rpos].cell() % 29)) & 0x3ffffff; matrix.setBit(hash % sz, true); } //if (++numItem == s_maxItems) // break; } } else { pos = inPos - 1; for (auto it = stringlist.constBegin(), end = stringlist.constEnd(); it != end; ++it) { string_entry *entry = *it; if (pos < entry->length) { uint hash = ((entry->hash * 13) + (entry->key[pos].cell() % 29)) & 0x3ffffff; matrix.setBit(hash % sz, true); } //if (++numItem == s_maxItems) // break; } } return matrix.count(true); } // // Add the diversity of the strings at position 'pos' static void addDiversity(KSycocaDictStringList* stringlistp, int pos) { if (pos == 0) { return; } KSycocaDictStringList& stringlist = *stringlistp; if (pos < 0) { pos = -pos; for (auto it = stringlist.constBegin(), end = stringlist.constEnd(); it != end; ++it) { string_entry *entry = *it; int rpos = entry->length - pos; if (rpos > 0) { entry->hash = ((entry->hash * 13) + (entry->key[rpos].cell() % 29)) & 0x3fffffff; } } } else { pos = pos - 1; for (auto it = stringlist.constBegin(), end = stringlist.constEnd(); it != end; ++it) { string_entry *entry = *it; if (pos < entry->length) { entry->hash = ((entry->hash * 13) + (entry->key[pos].cell() % 29)) & 0x3fffffff; } } } } void KSycocaDict::save(QDataStream &str) { if (count() == 0) { d->hashTableSize = 0; d->hashList.clear(); str << d->hashTableSize; str << d->hashList; return; } d->offset = str.device()->pos(); //qCDebug(SYCOCA) << "KSycocaDict:" << count() << "entries."; //qCDebug(SYCOCA) << "Calculating hash keys.."; int maxLength = 0; //qCDebug(SYCOCA) << "Finding maximum string length"; for (KSycocaDictStringList::const_iterator it = d->stringlist.constBegin(); it != d->stringlist.constEnd(); ++it) { string_entry *entry = *it; entry->hash = 0; if (entry->length > maxLength) { maxLength = entry->length; } } //qCDebug(SYCOCA) << "Max string length=" << maxLength << "existing hashList=" << d->hashList; // use "almost prime" number for sz (to calculate diversity) and later // for the table size of big tables // int sz = d->stringlist.count()*5-1; unsigned int sz = count() * 4 + 1; while (!(((sz % 3) && (sz % 5) && (sz % 7) && (sz % 11) && (sz % 13)))) { sz += 2; } d->hashList.clear(); // Times (with warm caches, i.e. after multiple runs) // kbuildsycoca5 --noincremental 2.83s user 0.20s system 95% cpu 3.187 total // kbuildsycoca5 --noincremental 2.74s user 0.25s system 93% cpu 3.205 total // unittest: 0.50-60 msec per iteration / 0.40-50 msec per iteration // Now that MimeTypes are not parsed anymore: // kbuildsycoca5 --noincremental 2.18s user 0.30s system 91% cpu 2.719 total // kbuildsycoca5 --noincremental 2.07s user 0.34s system 89% cpu 2.681 total // If I enabled s_maxItems = 50, it goes down to // but I don't know if that's a good idea. // kbuildsycoca5 --noincremental 1.73s user 0.31s system 85% cpu 2.397 total // kbuildsycoca5 --noincremental 1.84s user 0.29s system 95% cpu 2.230 total // try to limit diversity scan by "predicting" positions // with high diversity QVector oldvec(maxLength * 2 + 1); oldvec.fill(0); int mindiv = 0; int lastDiv = 0; while (true) { int divsum = 0, divnum = 0; int maxDiv = 0; int maxPos = 0; for (int pos = -maxLength; pos <= maxLength; ++pos) { // cut off if (oldvec[pos + maxLength] < mindiv) { oldvec[pos + maxLength] = 0; continue; } const int diversity = calcDiversity(&(d->stringlist), pos, sz); if (diversity > maxDiv) { maxDiv = diversity; maxPos = pos; } oldvec[pos + maxLength] = diversity; divsum += diversity; ++divnum; } // arbitrary cut-off value 3/4 of average seems to work if (divnum) { mindiv = (3 * divsum) / (4 * divnum); } if (maxDiv <= lastDiv) { break; } //qCDebug(SYCOCA) << "Max Div=" << maxDiv << "at pos" << maxPos; lastDiv = maxDiv; addDiversity(&(d->stringlist), maxPos); d->hashList.append(maxPos); } for (auto it = d->stringlist.constBegin(); it != d->stringlist.constEnd(); ++it) { (*it)->hash = d->hashKey((*it)->keyStr); } // fprintf(stderr, "Calculating minimum table size..\n"); d->hashTableSize = sz; //qCDebug(SYCOCA) << "hashTableSize=" << sz << "hashList=" << d->hashList << "oldvec=" << oldvec; struct hashtable_entry { string_entry *entry; QList *duplicates; qint64 duplicate_offset; }; hashtable_entry *hashTable = new hashtable_entry[ sz ]; //qCDebug(SYCOCA) << "Clearing hashtable..."; for (unsigned int i = 0; i < sz; i++) { hashTable[i].entry = nullptr; hashTable[i].duplicates = nullptr; } //qCDebug(SYCOCA) << "Filling hashtable..."; for (auto it = d->stringlist.constBegin(); it != d->stringlist.constEnd(); ++it) { string_entry *entry = *it; //qCDebug(SYCOCA) << "entry keyStr=" << entry->keyStr << entry->payload.data() << entry->payload->entryPath(); int hash = entry->hash % sz; if (!hashTable[hash].entry) { // First entry hashTable[hash].entry = entry; } else { if (!hashTable[hash].duplicates) { // Second entry, build duplicate list. hashTable[hash].duplicates = new QList; hashTable[hash].duplicates->append(hashTable[hash].entry); hashTable[hash].duplicate_offset = 0; } hashTable[hash].duplicates->append(entry); } } str << d->hashTableSize; str << d->hashList; d->offset = str.device()->pos(); // d->offset points to start of hashTable //qCDebug(SYCOCA) << QString("Start of Hash Table, offset = %1").arg(d->offset,8,16); // Write the hashtable + the duplicates twice. // The duplicates are after the normal hashtable, but the offset of each // duplicate entry is written into the normal hashtable. for (int pass = 1; pass <= 2; pass++) { str.device()->seek(d->offset); //qCDebug(SYCOCA) << QString("Writing hash table (pass #%1)").arg(pass); for (uint i = 0; i < d->hashTableSize; i++) { qint32 tmpid; if (!hashTable[i].entry) { tmpid = 0; } else if (!hashTable[i].duplicates) { tmpid = hashTable[i].entry->payload->offset(); // Positive ID } else { tmpid = - hashTable[i].duplicate_offset; // Negative ID } str << tmpid; //qCDebug(SYCOCA) << QString("Hash table : %1").arg(tmpid,8,16); } //qCDebug(SYCOCA) << QString("End of Hash Table, offset = %1").arg(str.device()->at(),8,16); //qCDebug(SYCOCA) << QString("Writing duplicate lists (pass #%1)").arg(pass); for (uint i = 0; i < d->hashTableSize; i++) { const QList *dups = hashTable[i].duplicates; if (dups) { hashTable[i].duplicate_offset = str.device()->pos(); /*qCDebug(SYCOCA) << QString("Duplicate lists: Offset = %1 list_size = %2") .arg(hashTable[i].duplicate_offset,8,16).arg(dups->count()); */ - Q_FOREACH (string_entry* dup, *dups) { + for (string_entry* dup : qAsConst(*dups)) { const qint32 offset = dup->payload->offset(); if (!offset) { const QString storageId = dup->payload->storageId(); qCDebug(SYCOCA) << "about to assert! dict=" << this << "storageId=" << storageId << dup->payload.data(); if (dup->payload->isType(KST_KService)) { KService::Ptr service(static_cast(dup->payload.data())); qCDebug(SYCOCA) << service->storageId() << service->entryPath(); } // save() must have been called on the entry Q_ASSERT_X(offset, "KSycocaDict::save", QByteArray("entry offset is 0, save() was not called on " + dup->payload->storageId().toLatin1() + " entryPath=" + dup->payload->entryPath().toLatin1()).constData() ); } str << offset; // Positive ID str << dup->keyStr; // Key (QString) } str << qint32(0); // End of list marker (0) } } //qCDebug(SYCOCA) << QString("End of Dict, offset = %1").arg(str.device()->at(),8,16); } //qCDebug(SYCOCA) << "Cleaning up hash table."; for (uint i = 0; i < d->hashTableSize; i++) { delete hashTable[i].duplicates; } delete [] hashTable; } qint32 KSycocaDictPrivate::offsetForKey(const QString &key) const { if (!stream || !offset) { qCWarning(SYCOCA) << "No ksycoca database available! Tried running" << KBUILDSYCOCA_EXENAME << "?"; return 0; } if (hashTableSize == 0) { return 0; // Unlikely to find anything :-] } // Read hash-table data const uint hash = hashKey(key) % hashTableSize; //qCDebug(SYCOCA) << "hash is" << hash; const qint64 off = offset + sizeof(qint32) * hash; //qCDebug(SYCOCA) << QString("off is %1").arg(off,8,16); stream->device()->seek(off); qint32 retOffset; (*stream) >> retOffset; return retOffset; } diff --git a/src/sycoca/ksycocafactory.cpp b/src/sycoca/ksycocafactory.cpp index 6ee4806..94ade5d 100644 --- a/src/sycoca/ksycocafactory.cpp +++ b/src/sycoca/ksycocafactory.cpp @@ -1,265 +1,265 @@ /* This file is part of the KDE libraries * Copyright (C) 1999 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 "ksycocafactory_p.h" #include "ksycoca.h" #include "ksycocatype.h" #include "ksycocaentry.h" #include "ksycocaentry_p.h" #include "ksycocadict_p.h" #include "sycocadebug.h" #include #include #include class KSycocaFactoryPrivate { public: KSycocaFactoryPrivate() {} ~KSycocaFactoryPrivate() { delete m_sycocaDict; } int mOffset = 0; int m_sycocaDictOffset = 0; int m_beginEntryOffset = 0; int m_endEntryOffset = 0; KSycocaDict *m_sycocaDict = nullptr; }; KSycocaFactory::KSycocaFactory(KSycocaFactoryId factory_id, KSycoca *sycoca) : m_resourceList(nullptr), m_entryDict(nullptr), m_str(nullptr), m_sycoca(sycoca), d(new KSycocaFactoryPrivate) { if (!m_sycoca->isBuilding() && (m_str = m_sycoca->findFactory(factory_id))) { // Read position of index tables.... qint32 i; (*m_str) >> i; d->m_sycocaDictOffset = i; (*m_str) >> i; d->m_beginEntryOffset = i; (*m_str) >> i; d->m_endEntryOffset = i; QDataStream *str = stream(); qint64 saveOffset = str->device()->pos(); // Init index tables d->m_sycocaDict = new KSycocaDict(str, d->m_sycocaDictOffset); saveOffset = str->device()->seek(saveOffset); } else { // We are in kbuildsycoca -- build new database! m_entryDict = new KSycocaEntryDict; d->m_sycocaDict = new KSycocaDict; d->m_beginEntryOffset = 0; d->m_endEntryOffset = 0; // m_resourceList will be filled in by inherited constructors } m_sycoca->addFactory(this); } KSycocaFactory::~KSycocaFactory() { delete m_entryDict; delete d; } void KSycocaFactory::saveHeader(QDataStream &str) { // Write header str.device()->seek(d->mOffset); str << qint32(d->m_sycocaDictOffset); str << qint32(d->m_beginEntryOffset); str << qint32(d->m_endEntryOffset); } void KSycocaFactory::save(QDataStream &str) { if (!m_entryDict) { return; // Error! Function should only be called when } // building database if (!d->m_sycocaDict) { return; // Error! } d->mOffset = str.device()->pos(); // store position in member variable d->m_sycocaDictOffset = 0; // Write header (pass #1) saveHeader(str); d->m_beginEntryOffset = str.device()->pos(); // Write all entries. int entryCount = 0; - Q_FOREACH(KSycocaEntry::Ptr entry, *m_entryDict) { + for(KSycocaEntry::Ptr entry : qAsConst(*m_entryDict)) { entry->d_ptr->save(str); entryCount++; } d->m_endEntryOffset = str.device()->pos(); // Write indices... // Linear index str << qint32(entryCount); - Q_FOREACH(KSycocaEntry::Ptr entry, *m_entryDict) { + for(KSycocaEntry::Ptr entry : qAsConst(*m_entryDict)) { str << qint32(entry.data()->offset()); } // Dictionary index d->m_sycocaDictOffset = str.device()->pos(); d->m_sycocaDict->save(str); qint64 endOfFactoryData = str.device()->pos(); // Update header (pass #2) saveHeader(str); // Seek to end. str.device()->seek(endOfFactoryData); } void KSycocaFactory::addEntry(const KSycocaEntry::Ptr &newEntry) { if (!m_entryDict) { return; // Error! Function should only be called when } // building database if (!d->m_sycocaDict) { return; // Error! } KSycocaEntry::Ptr oldEntry = m_entryDict->value(newEntry->storageId()); if (oldEntry) { // Already exists -> replace // We found a more-local override, e.g. ~/.local/share/applications/kde5/foo.desktop // So forget about the more global file. // // This can also happen with two .protocol files using the same protocol= entry. // If we didn't remove one here, we would end up asserting because save() // wasn't called on one of the entries. //qDebug() << "removing" << oldEntry.data() << oldEntry->entryPath() << "because of" << newEntry->entryPath() << "they have the same storageId" << newEntry->storageId(); removeEntry(newEntry->storageId()); } const QString name = newEntry->storageId(); m_entryDict->insert(name, newEntry); d->m_sycocaDict->add(name, newEntry); } void KSycocaFactory::removeEntry(const QString &entryName) { if (!m_entryDict) { return; // Error! Function should only be called when } // building database if (!d->m_sycocaDict) { return; // Error! } m_entryDict->remove(entryName); d->m_sycocaDict->remove(entryName); // O(N) } KSycocaEntry::List KSycocaFactory::allEntries() const { KSycocaEntry::List list; // Assume we're NOT building a database QDataStream *str = stream(); if (!str) { return list; } str->device()->seek(d->m_endEntryOffset); qint32 entryCount; (*str) >> entryCount; if (entryCount > 8192) { qCWarning(SYCOCA) << QThread::currentThread() << "error detected in factory" << this; KSycoca::flagError(); return list; } // offsetList is needed because createEntry() modifies the stream position qint32 *offsetList = new qint32[entryCount]; for (int i = 0; i < entryCount; i++) { (*str) >> offsetList[i]; } for (int i = 0; i < entryCount; i++) { KSycocaEntry *newEntry = createEntry(offsetList[i]); if (newEntry) { list.append(KSycocaEntry::Ptr(newEntry)); } } delete [] offsetList; return list; } int KSycocaFactory::offset() const { return d->mOffset; } const KSycocaResourceList *KSycocaFactory::resourceList() const { return m_resourceList; } const KSycocaDict *KSycocaFactory::sycocaDict() const { return d->m_sycocaDict; } bool KSycocaFactory::isEmpty() const { return d->m_beginEntryOffset == d->m_endEntryOffset; } QDataStream *KSycocaFactory::stream() const { return m_str; } QStringList KSycocaFactory::allDirectories(const QString &subdir) { // We don't use QStandardPaths::locateAll() because we want all paths, even those that don't exist yet const QStringList topDirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); QStringList dirs; dirs.reserve(topDirs.size()); for (QStringList::const_iterator dir = topDirs.constBegin(); dir != topDirs.constEnd(); ++dir) { dirs.append(*dir + QLatin1Char('/') + subdir); } return dirs; } void KSycocaFactory::virtual_hook(int /*id*/, void * /*data*/) { /*BASE::virtual_hook( id, data );*/ } diff --git a/src/sycoca/ksycocautils_p.h b/src/sycoca/ksycocautils_p.h index d6fc85f..8064f8a 100644 --- a/src/sycoca/ksycocautils_p.h +++ b/src/sycoca/ksycocautils_p.h @@ -1,77 +1,77 @@ /* This file is part of the KDE libraries * Copyright (C) 1999 Waldo Bastian * Copyright (C) 2005-2013 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. **/ #ifndef KSYCOCAUTILS_P_H #define KSYCOCAUTILS_P_H #include #include #include #include class QStringList; class QDataStream; namespace KSycocaUtilsPrivate { // helper function for visitResourceDirectory template bool visitResourceDirectoryHelper(const QString &dirname, Visitor visitor) { QDir dir(dirname); const QFileInfoList list = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs, QDir::Unsorted); - foreach (const QFileInfo &fi, list) { + for (const QFileInfo &fi : list) { if (fi.isDir() && !fi.isSymLink() && !fi.isBundle()) { // same check as in vfolder_menu.cpp if (!visitor(fi)) { return false; } if (!visitResourceDirectoryHelper(fi.filePath(), visitor)) { return false; } } } return true; } // visitor is a function/functor accepts QFileInfo as argument and returns bool // visitResourceDirectory will visit the resource directory in a depth-first way. // visitor can terminate the visit by returning false, and visitResourceDirectory // will also return false in this case, otherwise it will return true. template bool visitResourceDirectory(const QString &dirname, Visitor visitor) { QFileInfo info(dirname); if (!visitor(info)) { return false; } // Recurse only for services and menus. // Apps and servicetypes don't need recursion, so save the directory listing. if (!dirname.contains(QLatin1String("/applications")) && !dirname.contains(QLatin1String("/kservicetypes5"))) { return visitResourceDirectoryHelper(dirname, visitor); } return true; } } #endif /* KSYCOCAUTILS_P_H */ diff --git a/src/sycoca/vfolder_menu.cpp b/src/sycoca/vfolder_menu.cpp index 8d5a496..080d133 100644 --- a/src/sycoca/vfolder_menu.cpp +++ b/src/sycoca/vfolder_menu.cpp @@ -1,1437 +1,1440 @@ /* This file is part of the KDE libraries * Copyright (C) 2003 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. **/ #include "vfolder_menu_p.h" #include "kbuildservicefactory_p.h" #include "kbuildsycocainterface_p.h" #include "sycocadebug.h" #include #include #include #include #include #include #include #include static void foldNode(QDomElement &docElem, QDomElement &e, QMap &dupeList, QString s = QString()) //krazy:exclude=passbyvalue { if (s.isEmpty()) { s = e.text(); } QMap::iterator it = dupeList.find(s); if (it != dupeList.end()) { //qCDebug(SYCOCA) << e.tagName() << "and" << s << "requires combining!"; docElem.removeChild(*it); dupeList.erase(it); } dupeList.insert(s, e); } static void replaceNode(QDomElement &docElem, QDomNode &n, const QStringList &list, const QString &tag) { for (QStringList::ConstIterator it = list.begin(); it != list.end(); ++it) { QDomElement e = docElem.ownerDocument().createElement(tag); QDomText txt = docElem.ownerDocument().createTextNode(*it); e.appendChild(txt); docElem.insertAfter(e, n); } QDomNode next = n.nextSibling(); docElem.removeChild(n); n = next; // qCDebug(SYCOCA) << "Next tag = " << n.toElement().tagName(); } void VFolderMenu::registerFile(const QString &file) { int i = file.lastIndexOf(QLatin1Char('/')); if (i < 0) { return; } QString dir = file.left(i + 1); // Include trailing '/' registerDirectory(dir); } void VFolderMenu::registerDirectory(const QString &directory) { m_allDirectories.append(directory); } QStringList VFolderMenu::allDirectories() { if (m_allDirectories.isEmpty()) { return m_allDirectories; } m_allDirectories.sort(); QStringList::Iterator it = m_allDirectories.begin(); QString previous = *it++; for (; it != m_allDirectories.end();) { #ifndef Q_OS_WIN if ((*it).startsWith(previous)) #else if ((*it).startsWith(previous, Qt::CaseInsensitive)) #endif { it = m_allDirectories.erase(it); } else { previous = *it; ++it; } } return m_allDirectories; } static void track(const QString &menuId, const QString &menuName, const QHash &includeList, const QHash &excludeList, const QHash &itemList, const QString &comment) { if (itemList.contains(menuId)) { printf("%s: %s INCL %d EXCL %d\n", qPrintable(menuName), qPrintable(comment), includeList.contains(menuId) ? 1 : 0, excludeList.contains(menuId) ? 1 : 0); } } void VFolderMenu::includeItems(QHash &items1, const QHash &items2) { - foreach (const KService::Ptr &p, items2) { + for (const KService::Ptr &p : items2) { items1.insert(p->menuId(), p); } } void VFolderMenu::matchItems(QHash &items1, const QHash &items2) { - foreach (const KService::Ptr &p, items1) { + const QHash tmpItems1 = items1; + for (const KService::Ptr &p : tmpItems1) { QString id = p->menuId(); if (!items2.contains(id)) { items1.remove(id); } } } void VFolderMenu::excludeItems(QHash &items1, const QHash &items2) { - foreach (const KService::Ptr &p, items2) { + for (const KService::Ptr &p : items2) { items1.remove(p->menuId()); } } VFolderMenu::SubMenu * VFolderMenu::takeSubMenu(SubMenu *parentMenu, const QString &menuName) { const int i = menuName.indexOf(QLatin1Char('/')); const QString s1 = i > 0 ? menuName.left(i) : menuName; const QString s2 = menuName.mid(i + 1); // Look up menu for (QList::Iterator it = parentMenu->subMenus.begin(); it != parentMenu->subMenus.end(); ++it) { SubMenu *menu = *it; if (menu->name == s1) { if (i == -1) { // Take it out parentMenu->subMenus.erase(it); return menu; } else { return takeSubMenu(menu, s2); } } } return nullptr; // Not found } void VFolderMenu::mergeMenu(SubMenu *menu1, SubMenu *menu2, bool reversePriority) { if (m_track) { track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->items, QStringLiteral("Before MenuMerge w. %1 (incl)").arg(menu2->name)); track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->excludeItems, QStringLiteral("Before MenuMerge w. %1 (excl)").arg(menu2->name)); } if (reversePriority) { // Merge menu1 with menu2, menu1 takes precedent excludeItems(menu2->items, menu1->excludeItems); includeItems(menu1->items, menu2->items); excludeItems(menu2->excludeItems, menu1->items); includeItems(menu1->excludeItems, menu2->excludeItems); } else { // Merge menu1 with menu2, menu2 takes precedent excludeItems(menu1->items, menu2->excludeItems); includeItems(menu1->items, menu2->items); includeItems(menu1->excludeItems, menu2->excludeItems); menu1->isDeleted = menu2->isDeleted; } while (!menu2->subMenus.isEmpty()) { SubMenu *subMenu = menu2->subMenus.takeFirst(); insertSubMenu(menu1, subMenu->name, subMenu, reversePriority); } if (reversePriority) { // Merge menu1 with menu2, menu1 takes precedent if (menu1->directoryFile.isEmpty()) { menu1->directoryFile = menu2->directoryFile; } if (menu1->defaultLayoutNode.isNull()) { menu1->defaultLayoutNode = menu2->defaultLayoutNode; } if (menu1->layoutNode.isNull()) { menu1->layoutNode = menu2->layoutNode; } } else { // Merge menu1 with menu2, menu2 takes precedent if (!menu2->directoryFile.isEmpty()) { menu1->directoryFile = menu2->directoryFile; } if (!menu2->defaultLayoutNode.isNull()) { menu1->defaultLayoutNode = menu2->defaultLayoutNode; } if (!menu2->layoutNode.isNull()) { menu1->layoutNode = menu2->layoutNode; } } if (m_track) { track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->items, QStringLiteral("After MenuMerge w. %1 (incl)").arg(menu2->name)); track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->excludeItems, QStringLiteral("After MenuMerge w. %1 (excl)").arg(menu2->name)); } delete menu2; } void VFolderMenu::insertSubMenu(SubMenu *parentMenu, const QString &menuName, SubMenu *newMenu, bool reversePriority) { const int i = menuName.indexOf(QLatin1Char('/')); const QString s1 = menuName.left(i); const QString s2 = menuName.mid(i + 1); // Look up menu - foreach (SubMenu *menu, parentMenu->subMenus) { + for (SubMenu *menu : qAsConst(parentMenu->subMenus)) { if (menu->name == s1) { if (i == -1) { mergeMenu(menu, newMenu, reversePriority); return; } else { insertSubMenu(menu, s2, newMenu, reversePriority); return; } } } if (i == -1) { // Add it here newMenu->name = menuName; parentMenu->subMenus.append(newMenu); } else { SubMenu *menu = new SubMenu; menu->name = s1; parentMenu->subMenus.append(menu); insertSubMenu(menu, s2, newMenu); } } void VFolderMenu::insertService(SubMenu *parentMenu, const QString &name, KService::Ptr newService) { const int i = name.indexOf(QLatin1Char('/')); if (i == -1) { // Add it here parentMenu->items.insert(newService->menuId(), newService); return; } QString s1 = name.left(i); QString s2 = name.mid(i + 1); // Look up menu - foreach (SubMenu *menu, parentMenu->subMenus) { + for (SubMenu *menu : qAsConst(parentMenu->subMenus)) { if (menu->name == s1) { insertService(menu, s2, newService); return; } } SubMenu *menu = new SubMenu; menu->name = s1; parentMenu->subMenus.append(menu); insertService(menu, s2, newService); } VFolderMenu::VFolderMenu(KServiceFactory *serviceFactory, KBuildSycocaInterface *kbuildsycocaInterface) : m_appsInfo(nullptr) , m_rootMenu(nullptr) , m_currentMenu(nullptr) , m_track(false) , m_serviceFactory(serviceFactory) , m_kbuildsycocaInterface(kbuildsycocaInterface) { m_usedAppsDict.reserve(797); initDirs(); } VFolderMenu::~VFolderMenu() { qDeleteAll(m_appsInfoList); delete m_rootMenu; } #define FOR_ALL_APPLICATIONS(it) \ - foreach (AppsInfo *info, m_appsInfoStack) \ + for (AppsInfo *info : qAsConst(m_appsInfoStack)) \ { \ QHashIterator it = info->applications; \ while (it.hasNext()) \ { \ it.next(); #define FOR_ALL_APPLICATIONS_END } } #define FOR_CATEGORY(category, it) \ - foreach (AppsInfo *info, m_appsInfoStack) \ + for (AppsInfo *info : qAsConst(m_appsInfoStack)) \ { \ const KService::List list = info->dictCategories.value(category); \ for(KService::List::ConstIterator it = list.constBegin(); \ it != list.constEnd(); ++it) \ { #define FOR_CATEGORY_END } } KService::Ptr VFolderMenu::findApplication(const QString &relPath) { - foreach (AppsInfo *info, m_appsInfoStack) { + for (AppsInfo *info : qAsConst(m_appsInfoStack)) { if (info->applications.contains(relPath)) { KService::Ptr s = info->applications[relPath]; if (s) { return s; } } } return KService::Ptr(); } void VFolderMenu::addApplication(const QString &id, KService::Ptr service) { service->setMenuId(id); m_appsInfo->applications.insert(id, service); // replaces, if already there m_serviceFactory->addEntry(KSycocaEntry::Ptr(service)); } void VFolderMenu::buildApplicationIndex(bool unusedOnly) { - foreach (AppsInfo *info, m_appsInfoList) { + for (AppsInfo *info : qAsConst(m_appsInfoList)) { info->dictCategories.clear(); QMutableHashIterator it = info->applications; while (it.hasNext()) { KService::Ptr s = it.next().value(); if (unusedOnly && m_usedAppsDict.contains(s->menuId())) { // Remove and skip this one it.remove(); continue; } - Q_FOREACH (const QString &cat, s->categories()) { + const auto categories = s->categories(); + for (const QString &cat : categories) { info->dictCategories[cat].append(s); // find or insert entry in hash } } } } void VFolderMenu::createAppsInfo() { if (m_appsInfo) { return; } m_appsInfo = new AppsInfo; m_appsInfoStack.prepend(m_appsInfo); m_appsInfoList.append(m_appsInfo); m_currentMenu->apps_info = m_appsInfo; } void VFolderMenu::loadAppsInfo() { m_appsInfo = m_currentMenu->apps_info; if (!m_appsInfo) { return; // No appsInfo for this menu } if (!m_appsInfoStack.isEmpty() && m_appsInfoStack.first() == m_appsInfo) { return; // Already added (By createAppsInfo?) } m_appsInfoStack.prepend(m_appsInfo); // Add } void VFolderMenu::unloadAppsInfo() { m_appsInfo = m_currentMenu->apps_info; if (!m_appsInfo) { return; // No appsInfo for this menu } if (m_appsInfoStack.first() != m_appsInfo) { return; // Already removed (huh?) } m_appsInfoStack.removeAll(m_appsInfo); // Remove m_appsInfo = nullptr; } QString VFolderMenu::absoluteDir(const QString &_dir, const QString &baseDir, bool keepRelativeToCfg) { QString dir = _dir; if (QDir::isRelativePath(dir)) { dir = baseDir + dir; } bool relative = QDir::isRelativePath(dir); if (relative && !keepRelativeToCfg) { relative = false; dir = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("menus/") + dir, QStandardPaths::LocateDirectory); } if (!relative) { QString resolved = QDir(dir).canonicalPath(); if (!resolved.isEmpty()) { dir = resolved; } } if (!dir.endsWith(QLatin1Char('/'))) { dir += QLatin1Char('/'); } return dir; } static void tagBaseDir(QDomDocument &doc, const QString &tag, const QString &dir) { QDomNodeList mergeFileList = doc.elementsByTagName(tag); for (int i = 0; i < mergeFileList.count(); i++) { QDomAttr attr = doc.createAttribute(QStringLiteral("__BaseDir")); attr.setValue(dir); mergeFileList.item(i).toElement().setAttributeNode(attr); } } static void tagBasePath(QDomDocument &doc, const QString &tag, const QString &path) { QDomNodeList mergeFileList = doc.elementsByTagName(tag); for (int i = 0; i < mergeFileList.count(); i++) { QDomAttr attr = doc.createAttribute(QStringLiteral("__BasePath")); attr.setValue(path); mergeFileList.item(i).toElement().setAttributeNode(attr); } } QDomDocument VFolderMenu::loadDoc() { QDomDocument doc; if (m_docInfo.path.isEmpty()) { return doc; } QFile file(m_docInfo.path); if (!file.open(QIODevice::ReadOnly)) { qCWarning(SYCOCA) << "Could not open " << m_docInfo.path; return doc; } QString errorMsg; int errorRow; int errorCol; if (!doc.setContent(&file, &errorMsg, &errorRow, &errorCol)) { qCWarning(SYCOCA) << "Parse error in " << m_docInfo.path << ", line " << errorRow << ", col " << errorCol << ": " << errorMsg; file.close(); return doc; } file.close(); tagBaseDir(doc, QStringLiteral("MergeFile"), m_docInfo.baseDir); tagBasePath(doc, QStringLiteral("MergeFile"), m_docInfo.path); tagBaseDir(doc, QStringLiteral("MergeDir"), m_docInfo.baseDir); tagBaseDir(doc, QStringLiteral("DirectoryDir"), m_docInfo.baseDir); tagBaseDir(doc, QStringLiteral("AppDir"), m_docInfo.baseDir); tagBaseDir(doc, QStringLiteral("LegacyDir"), m_docInfo.baseDir); return doc; } void VFolderMenu::mergeFile(QDomElement &parent, const QDomNode &mergeHere) { //qCDebug(SYCOCA) << m_docInfo.path; QDomDocument doc = loadDoc(); QDomElement docElem = doc.documentElement(); QDomNode n = docElem.firstChild(); QDomNode last = mergeHere; while (!n.isNull()) { QDomElement e = n.toElement(); // try to convert the node to an element. QDomNode next = n.nextSibling(); if (e.isNull()) { // Skip } // The spec says we must ignore any Name nodes else if (e.tagName() != QLatin1String("Name")) { parent.insertAfter(n, last); last = n; } docElem.removeChild(n); n = next; } } void VFolderMenu::mergeMenus(QDomElement &docElem, QString &name) { QMap menuNodes; QMap directoryNodes; QMap appDirNodes; QMap directoryDirNodes; QMap legacyDirNodes; QDomElement defaultLayoutNode; QDomElement layoutNode; QDomNode n = docElem.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); // try to convert the node to an element. if (e.isNull()) { // qCDebug(SYCOCA) << "Empty node"; } else if (e.tagName() == QLatin1String("DefaultAppDirs")) { // Replace with m_defaultAppDirs replaceNode(docElem, n, m_defaultAppDirs, QStringLiteral("AppDir")); continue; } else if (e.tagName() == QLatin1String("DefaultDirectoryDirs")) { // Replace with m_defaultDirectoryDirs replaceNode(docElem, n, m_defaultDirectoryDirs, QStringLiteral("DirectoryDir")); continue; } else if (e.tagName() == QLatin1String("DefaultMergeDirs")) { // Replace with m_defaultMergeDirs replaceNode(docElem, n, m_defaultMergeDirs, QStringLiteral("MergeDir")); continue; } else if (e.tagName() == QLatin1String("AppDir")) { // Filter out dupes foldNode(docElem, e, appDirNodes); } else if (e.tagName() == QLatin1String("DirectoryDir")) { // Filter out dupes foldNode(docElem, e, directoryDirNodes); } else if (e.tagName() == QLatin1String("LegacyDir")) { // Filter out dupes foldNode(docElem, e, legacyDirNodes); } else if (e.tagName() == QLatin1String("Directory")) { // Filter out dupes foldNode(docElem, e, directoryNodes); } else if (e.tagName() == QLatin1String("Move")) { // Filter out dupes QString orig; QDomNode n2 = e.firstChild(); while (!n2.isNull()) { QDomElement e2 = n2.toElement(); // try to convert the node to an element. if (e2.tagName() == QLatin1String("Old")) { orig = e2.text(); break; } n2 = n2.nextSibling(); } foldNode(docElem, e, appDirNodes, orig); } else if (e.tagName() == QLatin1String("Menu")) { QString name; mergeMenus(e, name); QMap::iterator it = menuNodes.find(name); if (it != menuNodes.end()) { QDomElement docElem2 = *it; QDomNode n2 = docElem2.firstChild(); QDomNode first = e.firstChild(); while (!n2.isNull()) { QDomElement e2 = n2.toElement(); // try to convert the node to an element. QDomNode n3 = n2.nextSibling(); e.insertBefore(n2, first); docElem2.removeChild(n2); n2 = n3; } // We still have duplicated Name entries // but we don't care about that docElem.removeChild(docElem2); menuNodes.erase(it); } menuNodes.insert(name, e); } else if (e.tagName() == QLatin1String("MergeFile")) { if ((e.attribute(QStringLiteral("type")) == QLatin1String("parent"))) { // Ignore e.text(), as per the standard. We'll just look up the parent (more global) xml file. pushDocInfoParent(e.attribute(QStringLiteral("__BasePath")), e.attribute(QStringLiteral("__BaseDir"))); } else { pushDocInfo(e.text(), e.attribute(QStringLiteral("__BaseDir"))); } if (!m_docInfo.path.isEmpty()) { mergeFile(docElem, n); } popDocInfo(); QDomNode last = n; n = n.nextSibling(); docElem.removeChild(last); // Remove the MergeFile node continue; } else if (e.tagName() == QLatin1String("MergeDir")) { QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir")), true); Q_ASSERT(dir.endsWith(QLatin1Char('/'))); const bool relative = QDir::isRelativePath(dir); const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("menus/") + dir, QStandardPaths::LocateDirectory); - Q_FOREACH (const QString &menuDir, dirs) { + for (const QString &menuDir : dirs) { registerDirectory(menuDir); } QStringList fileList; - Q_FOREACH (const QString &menuDir, dirs) { + for (const QString &menuDir : dirs) { const QStringList fileNames = QDir(menuDir).entryList(QStringList() << QStringLiteral("*.menu")); - Q_FOREACH (const QString &file, fileNames) { + for (const QString &file : fileNames) { const QString fileToAdd = relative ? dir + file : menuDir + file; if (!fileList.contains(fileToAdd)) { fileList.append(fileToAdd); } } } - Q_FOREACH (const QString &file, fileList) { + for (const QString &file : qAsConst(fileList)) { pushDocInfo(file); mergeFile(docElem, n); popDocInfo(); } QDomNode last = n; n = n.nextSibling(); docElem.removeChild(last); // Remove the MergeDir node continue; } else if (e.tagName() == QLatin1String("Name")) { name = e.text(); } else if (e.tagName() == QLatin1String("DefaultLayout")) { if (!defaultLayoutNode.isNull()) { docElem.removeChild(defaultLayoutNode); } defaultLayoutNode = e; } else if (e.tagName() == QLatin1String("Layout")) { if (!layoutNode.isNull()) { docElem.removeChild(layoutNode); } layoutNode = e; } n = n.nextSibling(); } } static QString makeRelative(const QString &dir) { const QString canonical = QDir(dir).canonicalPath(); - Q_FOREACH (const QString &base, QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("menus"), QStandardPaths::LocateDirectory)) { + const auto list = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("menus"), QStandardPaths::LocateDirectory); + for (const QString &base : list) { if (canonical.startsWith(base)) { return canonical.mid(base.length() + 1); } } return dir; } void VFolderMenu::pushDocInfo(const QString &fileName, const QString &baseDir) { m_docInfoStack.push(m_docInfo); if (!baseDir.isEmpty()) { if (!QDir::isRelativePath(baseDir)) { m_docInfo.baseDir = makeRelative(baseDir); } else { m_docInfo.baseDir = baseDir; } } QString baseName = fileName; if (!QDir::isRelativePath(baseName)) { registerFile(baseName); } else { baseName = m_docInfo.baseDir + baseName; } m_docInfo.path = locateMenuFile(fileName); if (m_docInfo.path.isEmpty()) { m_docInfo.baseDir.clear(); m_docInfo.baseName.clear(); qCDebug(SYCOCA) << "Menu" << fileName << "not found."; return; } int i; i = baseName.lastIndexOf(QLatin1Char('/')); if (i > 0) { m_docInfo.baseDir = baseName.left(i + 1); m_docInfo.baseName = baseName.mid(i + 1, baseName.length() - i - 6); } else { m_docInfo.baseDir.clear(); m_docInfo.baseName = baseName.left(baseName.length() - 5); } } void VFolderMenu::pushDocInfoParent(const QString &basePath, const QString &baseDir) { m_docInfoStack.push(m_docInfo); m_docInfo.baseDir = baseDir; QString fileName = basePath.mid(basePath.lastIndexOf(QLatin1Char('/')) + 1); m_docInfo.baseName = fileName.left(fileName.length() - 5); // without ".menu" QString baseName = QDir::cleanPath(m_docInfo.baseDir + fileName); QStringList result = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("menus/") + baseName); // Remove anything "more local" than basePath. while (!result.isEmpty() && (result.at(0) != basePath)) { result.removeFirst(); } if (result.count() <= 1) { m_docInfo.path.clear(); // No parent found return; } // Now result.at(0) == basePath, take the next one, i.e. the one in the parent dir. m_docInfo.path = result.at(1); } void VFolderMenu::popDocInfo() { m_docInfo = m_docInfoStack.pop(); } QString VFolderMenu::locateMenuFile(const QString &fileName) { if (!QDir::isRelativePath(fileName)) { if (QFile::exists(fileName)) { return fileName; } return QString(); } QString result; QString xdgMenuPrefix = QString::fromLocal8Bit(qgetenv("XDG_MENU_PREFIX")); if (!xdgMenuPrefix.isEmpty()) { QFileInfo fileInfo(fileName); QString fileNameOnly = fileInfo.fileName(); if (!fileNameOnly.startsWith(xdgMenuPrefix)) { fileNameOnly = xdgMenuPrefix + fileNameOnly; } QString baseName = QDir::cleanPath(m_docInfo.baseDir + fileInfo.path() + QLatin1Char('/') + fileNameOnly); result = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("menus/") + baseName); } if (result.isEmpty()) { QString baseName = QDir::cleanPath(m_docInfo.baseDir + fileName); result = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("menus/") + baseName); } return result; } QString VFolderMenu::locateDirectoryFile(const QString &fileName) { if (fileName.isEmpty()) { return QString(); } if (!QDir::isRelativePath(fileName)) { if (QFile::exists(fileName)) { return fileName; } return QString(); } // First location in the list wins for (QStringList::ConstIterator it = m_directoryDirs.constBegin(); it != m_directoryDirs.constEnd(); ++it) { QString tmp = (*it) + fileName; if (QFile::exists(tmp)) { return tmp; } } return QString(); } void VFolderMenu::initDirs() { m_defaultAppDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); m_defaultDirectoryDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("desktop-directories"), QStandardPaths::LocateDirectory); } void VFolderMenu::loadMenu(const QString &fileName) { m_defaultMergeDirs.clear(); if (!fileName.endsWith(QLatin1String(".menu"))) { return; } pushDocInfo(fileName); m_defaultMergeDirs << QStringLiteral("applications-merged/"); m_doc = loadDoc(); popDocInfo(); if (m_doc.isNull()) { if (m_docInfo.path.isEmpty()) { qCritical() << fileName << " not found in " << m_allDirectories << endl; } else { qCWarning(SYCOCA) << "Load error (" << m_docInfo.path << ")"; } return; } QDomElement e = m_doc.documentElement(); QString name; mergeMenus(e, name); } void VFolderMenu::processCondition(QDomElement &domElem, QHash &items) { if (domElem.tagName() == QLatin1String("And")) { QDomNode n = domElem.firstChild(); // Look for the first child element while (!n.isNull()) { // loop in case of comments QDomElement e = n.toElement(); n = n.nextSibling(); if (!e.isNull()) { processCondition(e, items); break; // we only want the first one } } QHash andItems; while (!n.isNull()) { QDomElement e = n.toElement(); if (e.tagName() == QLatin1String("Not")) { // Special handling for "and not" QDomNode n2 = e.firstChild(); while (!n2.isNull()) { QDomElement e2 = n2.toElement(); andItems.clear(); processCondition(e2, andItems); excludeItems(items, andItems); n2 = n2.nextSibling(); } } else { andItems.clear(); processCondition(e, andItems); matchItems(items, andItems); } n = n.nextSibling(); } } else if (domElem.tagName() == QLatin1String("Or")) { QDomNode n = domElem.firstChild(); // Look for the first child element while (!n.isNull()) { // loop in case of comments QDomElement e = n.toElement(); n = n.nextSibling(); if (!e.isNull()) { processCondition(e, items); break; // we only want the first one } } QHash orItems; while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { orItems.clear(); processCondition(e, orItems); includeItems(items, orItems); } n = n.nextSibling(); } } else if (domElem.tagName() == QLatin1String("Not")) { FOR_ALL_APPLICATIONS(it) { KService::Ptr s = it.value(); items.insert(s->menuId(), s); } FOR_ALL_APPLICATIONS_END QHash notItems; QDomNode n = domElem.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { notItems.clear(); processCondition(e, notItems); excludeItems(items, notItems); } n = n.nextSibling(); } } else if (domElem.tagName() == QLatin1String("Category")) { FOR_CATEGORY(domElem.text(), it) { KService::Ptr s = *it; items.insert(s->menuId(), s); } FOR_CATEGORY_END } else if (domElem.tagName() == QLatin1String("All")) { FOR_ALL_APPLICATIONS(it) { KService::Ptr s = it.value(); items.insert(s->menuId(), s); } FOR_ALL_APPLICATIONS_END } else if (domElem.tagName() == QLatin1String("Filename")) { const QString filename = domElem.text(); //qCDebug(SYCOCA) << "Adding file" << filename; KService::Ptr s = findApplication(filename); if (s) { items.insert(filename, s); } } } void VFolderMenu::loadApplications(const QString &dir, const QString &prefix) { //qCDebug(SYCOCA) << "Looking up applications under" << dir; QDirIterator it(dir); while (it.hasNext()) { it.next(); const QFileInfo fi = it.fileInfo(); const QString fn = fi.fileName(); if (fi.isDir() && !fi.isSymLink() && !fi.isBundle()) { // same check as in ksycocautils_p.h if (fn == QLatin1String(".") || fn == QLatin1String("..")) { continue; } loadApplications(fi.filePath(), prefix + fn + QLatin1Char('-')); continue; } if (fi.isFile()) { if (!fn.endsWith(QLatin1String(".desktop"))) { continue; } KService::Ptr service = m_kbuildsycocaInterface->createService(fi.absoluteFilePath()); if (service) { addApplication(prefix + fn, service); } } } } void VFolderMenu::processLegacyDir(const QString &dir, const QString &relDir, const QString &prefix) { //qCDebug(SYCOCA).nospace() << "processLegacyDir(" << dir << ", " << relDir << ", " << prefix << ")"; QHash items; QDirIterator it(dir); while (it.hasNext()) { it.next(); const QFileInfo fi = it.fileInfo(); const QString fn = fi.fileName(); if (fi.isDir()) { if (fn == QLatin1String(".") || fn == QLatin1String("..")) { continue; } SubMenu *parentMenu = m_currentMenu; m_currentMenu = new SubMenu; m_currentMenu->name = fn; m_currentMenu->directoryFile = fi.absoluteFilePath() + QStringLiteral("/.directory"); parentMenu->subMenus.append(m_currentMenu); processLegacyDir(fi.filePath(), relDir + fn + QLatin1Char('/'), prefix); m_currentMenu = parentMenu; continue; } if (fi.isFile() /*&& !fi.isSymLink() ?? */) { if (!fn.endsWith(QLatin1String(".desktop"))) { continue; } KService::Ptr service = m_kbuildsycocaInterface->createService(fi.absoluteFilePath()); if (service) { const QString id = prefix + fn; // TODO: Add legacy category addApplication(id, service); items.insert(service->menuId(), service); if (service->categories().isEmpty()) { m_currentMenu->items.insert(id, service); } } } } markUsedApplications(items); } void VFolderMenu::processMenu(QDomElement &docElem, int pass) { SubMenu *parentMenu = m_currentMenu; int oldDirectoryDirsCount = m_directoryDirs.count(); QString name; QString directoryFile; bool onlyUnallocated = false; bool isDeleted = false; QDomElement defaultLayoutNode; QDomElement layoutNode; QDomElement query; QDomNode n = docElem.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); // try to convert the node to an element. if (e.tagName() == QLatin1String("Name")) { name = e.text(); } else if (e.tagName() == QLatin1String("Directory")) { directoryFile = e.text(); } else if (e.tagName() == QLatin1String("DirectoryDir")) { QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir"))); m_directoryDirs.prepend(dir); } else if (e.tagName() == QLatin1String("OnlyUnallocated")) { onlyUnallocated = true; } else if (e.tagName() == QLatin1String("NotOnlyUnallocated")) { onlyUnallocated = false; } else if (e.tagName() == QLatin1String("Deleted")) { isDeleted = true; } else if (e.tagName() == QLatin1String("NotDeleted")) { isDeleted = false; } else if (e.tagName() == QLatin1String("DefaultLayout")) { defaultLayoutNode = e; } else if (e.tagName() == QLatin1String("Layout")) { layoutNode = e; } n = n.nextSibling(); } // Setup current menu entry if (pass == 0) { m_currentMenu = nullptr; // Look up menu if (parentMenu) { - foreach (SubMenu *menu, parentMenu->subMenus) { + for (SubMenu *menu : qAsConst(parentMenu->subMenus)) { if (menu->name == name) { m_currentMenu = menu; break; } } } if (!m_currentMenu) { // Not found? // Create menu m_currentMenu = new SubMenu; m_currentMenu->name = name; if (parentMenu) { parentMenu->subMenus.append(m_currentMenu); } else { m_rootMenu = m_currentMenu; } } if (directoryFile.isEmpty()) { //qCDebug(SYCOCA) << "Menu" << name << "does not specify a directory file."; } // Override previous directoryFile iff available QString tmp = locateDirectoryFile(directoryFile); if (! tmp.isEmpty()) { m_currentMenu->directoryFile = tmp; } m_currentMenu->isDeleted = isDeleted; m_currentMenu->defaultLayoutNode = defaultLayoutNode; m_currentMenu->layoutNode = layoutNode; } else { // Look up menu if (parentMenu) { - foreach (SubMenu *menu, parentMenu->subMenus) { + for (SubMenu *menu : qAsConst(parentMenu->subMenus)) { if (menu->name == name) { m_currentMenu = menu; break; } } } else { m_currentMenu = m_rootMenu; } } // Process AppDir and LegacyDir if (pass == 0) { QDomElement query; QDomNode n = docElem.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); // try to convert the node to an element. if (e.tagName() == QLatin1String("AppDir")) { createAppsInfo(); QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir"))); registerDirectory(dir); loadApplications(dir, QString()); } else if (e.tagName() == QLatin1String("LegacyDir")) { createAppsInfo(); QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir"))); QString prefix = e.attributes().namedItem(QStringLiteral("prefix")).toAttr().value(); SubMenu *oldMenu = m_currentMenu; m_currentMenu = new SubMenu; registerDirectory(dir); processLegacyDir(dir, QString(), prefix); m_legacyNodes.insert(dir, m_currentMenu); m_currentMenu = oldMenu; } n = n.nextSibling(); } } loadAppsInfo(); // Update the scope wrt the list of applications if (((pass == 1) && !onlyUnallocated) || ((pass == 2) && onlyUnallocated)) { n = docElem.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); // try to convert the node to an element. if (e.tagName() == QLatin1String("Include")) { QHash items; QDomNode n2 = e.firstChild(); while (!n2.isNull()) { QDomElement e2 = n2.toElement(); items.clear(); processCondition(e2, items); if (m_track) { track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, QStringLiteral("Before ")); } includeItems(m_currentMenu->items, items); excludeItems(m_currentMenu->excludeItems, items); markUsedApplications(items); if (m_track) { track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, QStringLiteral("After ")); } n2 = n2.nextSibling(); } } else if (e.tagName() == QLatin1String("Exclude")) { QHash items; QDomNode n2 = e.firstChild(); while (!n2.isNull()) { QDomElement e2 = n2.toElement(); items.clear(); processCondition(e2, items); if (m_track) { track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, QStringLiteral("Before ")); } excludeItems(m_currentMenu->items, items); includeItems(m_currentMenu->excludeItems, items); if (m_track) { track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, QStringLiteral("After ")); } n2 = n2.nextSibling(); } } n = n.nextSibling(); } } n = docElem.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); // try to convert the node to an element. if (e.tagName() == QLatin1String("Menu")) { processMenu(e, pass); } // We insert legacy dir in pass 0, this way the order in the .menu-file determines // which .directory file gets used, but the menu-entries of legacy-menus will always // have the lowest priority. // else if (((pass == 1) && !onlyUnallocated) || ((pass == 2) && onlyUnallocated)) else if (pass == 0) { if (e.tagName() == QLatin1String("LegacyDir")) { // Add legacy nodes to Menu structure QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir"))); SubMenu *legacyMenu = m_legacyNodes[dir]; if (legacyMenu) { mergeMenu(m_currentMenu, legacyMenu); } } } n = n.nextSibling(); } if (pass == 2) { n = docElem.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); // try to convert the node to an element. if (e.tagName() == QLatin1String("Move")) { QString orig; QString dest; QDomNode n2 = e.firstChild(); while (!n2.isNull()) { QDomElement e2 = n2.toElement(); // try to convert the node to an element. if (e2.tagName() == QLatin1String("Old")) { orig = e2.text(); } if (e2.tagName() == QLatin1String("New")) { dest = e2.text(); } n2 = n2.nextSibling(); } //qCDebug(SYCOCA) << "Moving" << orig << "to" << dest; if (!orig.isEmpty() && !dest.isEmpty()) { SubMenu *menu = takeSubMenu(m_currentMenu, orig); if (menu) { insertSubMenu(m_currentMenu, dest, menu, true); // Destination has priority } } } n = n.nextSibling(); } } unloadAppsInfo(); // Update the scope wrt the list of applications while (m_directoryDirs.count() > oldDirectoryDirsCount) { m_directoryDirs.pop_front(); } m_currentMenu = parentMenu; } static QString parseAttribute(const QDomElement &e) { QString option; const QString SHOW_EMPTY=QStringLiteral("show_empty"); if (e.hasAttribute(SHOW_EMPTY)) { QString str = e.attribute(SHOW_EMPTY); if (str == QLatin1String("true")) { option = QStringLiteral("ME "); } else if (str == QLatin1String("false")) { option = QStringLiteral("NME "); } else { //qCDebug(SYCOCA)<<" Error in parsing show_empty attribute :"<defaultLayoutNode.isNull()) { defaultLayout = parseLayoutNode(menu->defaultLayoutNode); } if (menu->layoutNode.isNull()) { menu->layoutList = defaultLayout; } else { menu->layoutList = parseLayoutNode(menu->layoutNode); if (menu->layoutList.isEmpty()) { menu->layoutList = defaultLayout; } } - foreach (VFolderMenu::SubMenu *subMenu, menu->subMenus) { + for (VFolderMenu::SubMenu *subMenu : qAsConst(menu->subMenus)) { layoutMenu(subMenu, defaultLayout); } } void VFolderMenu::markUsedApplications(const QHash &items) { - foreach (const KService::Ptr &p, items) { + for (const KService::Ptr &p : items) { m_usedAppsDict.insert(p->menuId()); } } VFolderMenu::SubMenu *VFolderMenu::parseMenu(const QString &file) { m_appsInfo = nullptr; const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("menus"), QStandardPaths::LocateDirectory); for (QStringList::ConstIterator it = dirs.begin(); it != dirs.end(); ++it) { registerDirectory(*it); } loadMenu(file); delete m_rootMenu; m_rootMenu = m_currentMenu = nullptr; QDomElement docElem = m_doc.documentElement(); for (int pass = 0; pass <= 2; pass++) { // pass 0: load application desktop files // pass 1: the normal processing // pass 2: process to put unused files into "Lost & Found". processMenu(docElem, pass); switch (pass) { case 0: // Fill the dictCategories for each AppsInfo in m_appsInfoList, // in preparation for processMenu pass 1. buildApplicationIndex(false); break; case 1: // Fill the dictCategories for each AppsInfo in m_appsInfoList, // with only the unused apps, in preparation for processMenu pass 2. buildApplicationIndex(true /* unusedOnly */); break; case 2: { QStringList defaultLayout; defaultLayout << QStringLiteral(":M"); // Sub-Menus defaultLayout << QStringLiteral(":F"); // Individual entries layoutMenu(m_rootMenu, defaultLayout); break; } default: break; } } return m_rootMenu; } void VFolderMenu::setTrackId(const QString &id) { m_track = !id.isEmpty(); m_trackId = id; } diff --git a/tests/kmimeassociations_dumper.cpp b/tests/kmimeassociations_dumper.cpp index 9a2c8c7..d922168 100644 --- a/tests/kmimeassociations_dumper.cpp +++ b/tests/kmimeassociations_dumper.cpp @@ -1,63 +1,64 @@ /* * Copyright (C) 2016 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 #include #include #include #include #include int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QCommandLineParser parser; parser.setApplicationDescription(QStringLiteral("Parses mimeapps.list files and reports results for a mimetype")); parser.addHelpOption(); parser.addVersionOption(); parser.addPositionalArgument(QStringLiteral("mime"), QStringLiteral("mimetype name")); parser.process(app); if (parser.positionalArguments().count() != 1) { QTextStream(stderr) << "Exactly one mimetype required\n"; parser.showHelp(1); } const QString mime = parser.positionalArguments().at(0); // To see which files are being parsed, run this command: // strace -e file ./kmimeassociations_dumper inode/directory |& grep mimeapps // It should be the same as the output of these commands (assuming $XDG_CURRENT_DESKTOP=KDE) // qtpaths --locate-files GenericConfigLocation kde-mimeapps.list // qtpaths --locate-files GenericConfigLocation mimeapps.list // qtpaths --locate-files ApplicationsLocation kde-mimeapps.list // qtpaths --locate-files ApplicationsLocation mimeapps.list KOfferHash offers; KMimeAssociations mimeAppsParser(offers, KSycocaPrivate::self()->serviceFactory()); mimeAppsParser.parseAllMimeAppsList(); QTextStream output(stdout); - foreach (const KServiceOffer &offer, offers.offersFor(mime)) { + const auto list = offers.offersFor(mime); + for (const KServiceOffer &offer : list) { output << offer.service()->desktopEntryName() << " " << offer.service()->entryPath() << "\n"; } return 0; } diff --git a/tests/pluginlocator/plugintest.cpp b/tests/pluginlocator/plugintest.cpp index 8229dd7..1f90438 100644 --- a/tests/pluginlocator/plugintest.cpp +++ b/tests/pluginlocator/plugintest.cpp @@ -1,201 +1,201 @@ /****************************************************************************** * Copyright 2013 Sebastian Kügler * * * * 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 "plugintest.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include static QTextStream cout(stdout); class PluginTestPrivate { public: QString pluginName; }; PluginTest::PluginTest() : QObject(nullptr) { d = new PluginTestPrivate; } PluginTest::~PluginTest() { delete d; } int PluginTest::runMain() { // measure performance QElapsedTimer timer; int runs = 1; QList timings; cout << "-- PluginLocator Test --" << endl; bool ok = true; // KSycoca querying timer.start(); for (int _i = 0; _i < runs; _i++) { timer.restart(); if (!loadFromKService(QStringLiteral("time"))) { ok = false; } timings << timer.nsecsElapsed(); } report(timings, QStringLiteral("KServiceTypeTrader")); timings.clear(); // -- Metadata querying for (int _i = 0; _i < runs; _i++) { timer.restart(); if (!loadFromMetaData()) { ok = false; } //if (!loadFromMetaData2("Plasma/ContainmentActions")) ok = false; timings << timer.nsecsElapsed(); } report(timings, QStringLiteral("Metadata")); timings.clear(); findPlugins(); if (ok) { qDebug() << "All tests finished successfully"; return 0; } return 0; // We return successfully in any case, since plugins aren't installed for most people } void PluginTest::report(const QList timings, const QString &msg) { qulonglong totalTime = 0; int unitDiv = 1000; QString unit = QStringLiteral("microsec"); int i = 0; - foreach (qint64 t, timings) { + for (qint64 t : timings) { int msec = t / 1000000; qDebug() << " Run " << i << ": " << msec << " msec"; totalTime += t; i++; } QString av = QString::number((totalTime / timings.count() / unitDiv), 'f', 1); qDebug() << " Average: " << av << " " << unit << " (" << msg << ")" << endl; } bool PluginTest::loadFromKService(const QString &name) { bool ok = false; QString constraint = QStringLiteral("[X-KDE-PluginInfo-Name] == '%1'").arg(name); KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("Plasma/DataEngine"), constraint); QString error; if (offers.isEmpty()) { qDebug() << "offers are empty for " << name << " with constraint " << constraint; } else { QVariantList allArgs; allArgs << offers.first()->storageId(); const QString _n = offers.first()->property(QStringLiteral("Name")).toString(); if (!_n.isEmpty()) { qDebug() << "Found Dataengine: " << _n << endl; ok = true; } else { qDebug() << "Nothing found. " << endl; } } return ok; } bool PluginTest::loadFromMetaData(const QString &serviceType) { bool ok = false; QString pluginName("time"); QString constraint = QStringLiteral("[X-KDE-PluginInfo-Name] == '%1'").arg(pluginName); - KPluginInfo::List res = KPluginTrader::self()->query(QStringLiteral("kf5"), serviceType, QString()); + const KPluginInfo::List res = KPluginTrader::self()->query(QStringLiteral("kf5"), serviceType, QString()); qDebug() << "----------- Found " << res.count() << " Plugins" << constraint; ok = res.count() > 0; - foreach (const KPluginInfo &info, res) { + for (const KPluginInfo &info : res) { qDebug() << " file: " << info.libraryPath(); } return ok; } bool PluginTest::findPlugins() { QElapsedTimer timer; QList timings; const QString pluginDir("/media/storage/testdata/"); const QStringList sizes = QStringList() << QStringLiteral("50") << QStringLiteral("100") << QStringLiteral("150") << QStringLiteral("200") << QStringLiteral("250") << QStringLiteral("300") << QStringLiteral("400") << QStringLiteral("500") << QStringLiteral("600") << QStringLiteral("700") << QStringLiteral("800") << QStringLiteral("1000") << QStringLiteral("1500") << QStringLiteral("2000") << QStringLiteral("5000"); QStringList datadirs; - foreach (const QString &_s, sizes) { + for (const QString &_s : sizes) { datadirs << pluginDir + _s; } - foreach (const QString &subdir, datadirs) { + for (const QString &subdir : qAsConst(datadirs)) { const QString pluginName; const QString constraint; const QString serviceType; timer.restart(); KPluginInfo::List res = KPluginTrader::self()->query(subdir, serviceType, constraint); timings << timer.nsecsElapsed(); qDebug() << "Found " << res.count() << " Plugins in " << subdir; } report(timings, QStringLiteral("reading monsterdirs")); return true; } #include "moc_plugintest.cpp"