diff --git a/autotests/kmimeassociationstest.cpp b/autotests/kmimeassociationstest.cpp index 2fc1da1..150eece 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 "setupdatadirs.h" +#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) { 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) { 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() { - setupDataDirs(); + setupXdgDirs(); QStandardPaths::enableTestMode(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) { 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()) { 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) { 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()) { 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")); qStableSort(offers); // like kbuildservicefactory.cpp does const QStringList expectedJpegApps = preferredApps[QStringLiteral("image/jpeg")]; QCOMPARE(assembleOffers(offers), expectedJpegApps); offers = offerHash.offersFor(QStringLiteral("text/html")); qStableSort(offers); 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")); qStableSort(offers); 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) { if (offer.service()->storageId() == serv->storageId()) { return true; } } return false; } static QStringList assembleOffers(const QList &offers) { QStringList lst; Q_FOREACH (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) { 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 3966f9d..5cb6e7a 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 "setupdatadirs.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 - setupDataDirs(); + setupXdgDirs(); QStandardPaths::enableTestMode(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 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 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) { 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) { 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) { QVERIFY(serv); } Q_FOREACH (const KService::Ptr &serv, 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) { 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/ksycocathreadtest.cpp b/autotests/ksycocathreadtest.cpp index f54aa64..ca17af9 100644 --- a/autotests/ksycocathreadtest.cpp +++ b/autotests/ksycocathreadtest.cpp @@ -1,373 +1,373 @@ /* This file is part of the KDE project Copyright 2009 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 ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include "setupdatadirs.h" +#include "setupxdgdirs.h" static QString fakeTextPluginDesktopFile() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + "threadtextplugin.desktop"; } static QString fakeServiceDesktopFile() { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kservices5/") + "threadfakeservice.desktop"; } // 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; } static QSet s_threadsWhoSawFakeService; static QMutex s_setMutex; static int threadsWhoSawFakeService() { QMutexLocker locker(&s_setMutex); return s_threadsWhoSawFakeService.count(); } static QAtomicInt s_fakeServiceDeleted = 0; class WorkerObject : public QObject { Q_OBJECT public: WorkerObject() : QObject() {} public Q_SLOTS: void work() { //qDebug() << QThread::currentThread() << "working..."; const KServiceType::List allServiceTypes = KServiceType::allServiceTypes(); Q_ASSERT(!allServiceTypes.isEmpty()); QMimeDatabase db; const QList allMimeTypes = db.allMimeTypes(); Q_ASSERT(!allMimeTypes.isEmpty()); const KService::List lst = KService::allServices(); Q_ASSERT(!lst.isEmpty()); for (KService::List::ConstIterator it = lst.begin(); it != lst.end(); ++it) { const KService::Ptr service = (*it); Q_ASSERT(service->isType(KST_KService)); const QString name = service->name(); const QString entryPath = service->entryPath(); //qDebug() << name << "entryPath=" << entryPath << "menuId=" << service->menuId(); Q_ASSERT(!name.isEmpty()); Q_ASSERT(!entryPath.isEmpty()); KService::Ptr lookedupService = KService::serviceByDesktopPath(entryPath); if (!lookedupService) { if (entryPath == "threadfakeservice.desktop" && s_fakeServiceDeleted) { // ok, it got deleted meanwhile continue; } qWarning() << entryPath << "is gone!"; } Q_ASSERT(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)); } Q_ASSERT(!menuId.isEmpty()); lookedupService = KService::serviceByMenuId(menuId); if (!lookedupService) { if (menuId == "threadfakeservice" && s_fakeServiceDeleted) { // ok, it got deleted meanwhile continue; } qWarning() << menuId << "is gone!"; } Q_ASSERT(lookedupService); // not null QCOMPARE(lookedupService->menuId(), menuId); } } KService::List offers = KServiceTypeTrader::self()->query(QStringLiteral("KPluginInfo")); Q_ASSERT(offerListHasService(offers, QStringLiteral("threadtextplugin.desktop"))); offers = KServiceTypeTrader::self()->query(QStringLiteral("KPluginInfo"), QStringLiteral("Library == 'threadtextplugin'")); Q_ASSERT(offers.count() == 1); QVERIFY(offerListHasService(offers, QStringLiteral("threadtextplugin.desktop"))); KServiceGroup::Ptr root = KServiceGroup::root(); Q_ASSERT(root); if (KService::serviceByDesktopPath(QStringLiteral("threadfakeservice.desktop"))) { QMutexLocker locker(&s_setMutex); s_threadsWhoSawFakeService.insert(QThread::currentThread()); } } }; class WorkerThread : public QThread { Q_OBJECT public: WorkerThread() : QThread() { m_stop = false; } void run() override { WorkerObject wo; while (!m_stop) { wo.work(); } } virtual void stop() { m_stop = true; } private: QAtomicInt m_stop; // bool }; /** * Threads with an event loop will be able to process "database changed" signals. * Threads without an event loop (like WorkerThread) cannot, so they will keep using * the old data. */ class EventLoopThread : public WorkerThread { Q_OBJECT public: void run() override { // WorkerObject must belong to this thread, this is why we don't // have the slot work() in WorkerThread itself. Typical QThread trap! WorkerObject wo; QTimer timer; connect(&timer, SIGNAL(timeout()), &wo, SLOT(work())); timer.start(100); exec(); } void stop() override { quit(); } }; // This code runs in the main thread class KSycocaThreadTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void cleanupTestCase(); void testCreateService(); void testDeleteService() { deleteFakeService(); QTimer::singleShot(1000, this, SLOT(slotFinish())); } void slotFinish() { qDebug() << "Terminating"; for (int i = 0; i < threads.size(); i++) { threads[i]->stop(); } for (int i = 0; i < threads.size(); i++) { threads[i]->wait(); } cleanupTestCase(); QCoreApplication::instance()->quit(); } private: void createFakeService(); void deleteFakeService(); QVector threads; }; static void runKBuildSycoca() { QSignalSpy spy(KSycoca::self(), SIGNAL(databaseChanged(QStringList))); KBuildSycoca builder; QVERIFY(builder.recreate()); qDebug() << "waiting for signal"; QVERIFY(spy.wait(20000)); qDebug() << "got signal"; } void KSycocaThreadTest::initTestCase() { // Set up a layer in the bin dir so ksycoca finds the KPluginInfo and Application servicetypes - setupDataDirs(); + setupXdgDirs(); QStandardPaths::enableTestMode(true); // This service is always there. Used in the trader queries from the thread. const QString fakeTextPlugin = fakeTextPluginDesktopFile(); if (!QFile::exists(fakeTextPlugin)) { KDesktopFile file(fakeTextPlugin); KConfigGroup group = file.desktopGroup(); group.writeEntry("Name", "ThreadTextPlugin"); group.writeEntry("Type", "Service"); group.writeEntry("X-KDE-Library", "threadtextplugin"); group.writeEntry("X-KDE-Protocols", "http,ftp"); group.writeEntry("ServiceTypes", "KPluginInfo"); group.writeEntry("MimeType", "text/plain;"); file.sync(); qDebug() << "Created" << fakeTextPlugin << ", running kbuilsycoca"; runKBuildSycoca(); // Process the event int count = 0; while (!KService::serviceByDesktopPath(QStringLiteral("threadtextplugin.desktop"))) { qApp->processEvents(); if (++count == 20) { qFatal("sycoca doesn't have threadtextplugin.desktop"); } } } // Start clean const QString servPath = fakeServiceDesktopFile(); if (QFile::exists(servPath)) { QFile::remove(servPath); } if (KService::serviceByDesktopPath(QStringLiteral("threadfakeservice.desktop"))) { deleteFakeService(); } threads.resize(5); for (int i = 0; i < threads.size(); i++) { threads[i] = i < 3 ? new WorkerThread : new EventLoopThread; threads[i]->start(); } } void KSycocaThreadTest::cleanupTestCase() { QFile::remove(fakeTextPluginDesktopFile()); } // duplicated from kcoreaddons/autotests/kdirwatch_unittest.cpp static void waitUntilAfter(const QDateTime &ctime) { int totalWait = 0; QDateTime now; Q_FOREVER { now = QDateTime::currentDateTime(); if (now.toTime_t() == ctime.toTime_t()) // truncate milliseconds { totalWait += 50; QTest::qWait(50); } else { QVERIFY(now > ctime); // can't go back in time ;) QTest::qWait(50); // be safe break; } } //if (totalWait > 0) qDebug() << "Waited" << totalWait << "ms so that now" << now.toString(Qt::ISODate) << "is >" << ctime.toString(Qt::ISODate); } void KSycocaThreadTest::testCreateService() { // Wait one second so that ksycoca can detect a mtime change // ## IMHO this is a Qt bug, QFileInfo::lastModified() should include milliseconds waitUntilAfter(QDateTime::currentDateTime()); createFakeService(); QVERIFY(QFile::exists(fakeServiceDesktopFile())); qDebug() << "executing kbuildsycoca (1)"; runKBuildSycoca(); QTRY_VERIFY(KService::serviceByDesktopPath(QStringLiteral("threadfakeservice.desktop"))); // Now wait to check that all threads saw that new service QTRY_COMPARE_WITH_TIMEOUT(threadsWhoSawFakeService(), threads.size(), 20000); } void KSycocaThreadTest::deleteFakeService() { s_fakeServiceDeleted = 1; qDebug() << "now deleting the fake service"; KService::Ptr fakeService = KService::serviceByDesktopPath(QStringLiteral("threadfakeservice.desktop")); QVERIFY(fakeService); const QString servPath = fakeServiceDesktopFile(); QFile::remove(servPath); QSignalSpy spy(KSycoca::self(), SIGNAL(databaseChanged(QStringList))); QVERIFY(spy.isValid()); qDebug() << "executing kbuildsycoca (2)"; runKBuildSycoca(); QVERIFY(!spy.isEmpty()); QVERIFY(spy[0][0].toStringList().contains(QStringLiteral("services"))); QVERIFY(fakeService); // the whole point of refcounting is that this KService instance is still valid. QVERIFY(!QFile::exists(servPath)); } void KSycocaThreadTest::createFakeService() { KDesktopFile file(fakeServiceDesktopFile()); KConfigGroup group = file.desktopGroup(); group.writeEntry("Name", "ThreadFakeService"); group.writeEntry("Type", "Service"); group.writeEntry("X-KDE-Library", "threadfakeservice"); group.writeEntry("X-KDE-Protocols", "http,ftp"); group.writeEntry("ServiceTypes", "KPluginInfo"); group.writeEntry("MimeType", "text/plain;"); } QTEST_MAIN(KSycocaThreadTest) #include "ksycocathreadtest.moc" diff --git a/autotests/setupdatadirs.h b/autotests/setupxdgdirs.h similarity index 95% rename from autotests/setupdatadirs.h rename to autotests/setupxdgdirs.h index 3909c2a..9ac4270 100644 --- a/autotests/setupdatadirs.h +++ b/autotests/setupxdgdirs.h @@ -1,41 +1,41 @@ /* This file is part of KDE Frameworks Copyright 2018 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 ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef SETUP_DATA_DIRS_H -#define SETUP_DATA_DIRS_H +#ifndef SETUP_XDG_DIRS_H +#define SETUP_XDG_DIRS_H #include #include #include #include -static void setupDataDirs() +static void setupXdgDirs() { const QByteArray defaultDataDirs = qEnvironmentVariableIsSet("XDG_DATA_DIRS") ? qgetenv("XDG_DATA_DIRS") : QByteArray("/usr/local/share:/usr/share"); const QByteArray modifiedDataDirs = QFile::encodeName(QCoreApplication::applicationDirPath()) + "/data:" + defaultDataDirs; qputenv("XDG_DATA_DIRS", modifiedDataDirs); const QByteArray defaultConfigDirs = qEnvironmentVariableIsSet("XDG_CONFIG_DIRS") ? qgetenv("XDG_CONFIG_DIRS") : QByteArray("/etc/xdg"); const QByteArray modifiedConfigDirs = QFile::encodeName(QCoreApplication::applicationDirPath()) + "/data:" + defaultConfigDirs; qputenv("XDG_CONFIG_DIRS", modifiedConfigDirs); } #endif