File indexing completed on 2024-05-12 16:59:22
0001 /* This file is part of the KDE project 0002 SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org> 0003 0004 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include <kservice.h> 0008 0009 #include <kconfiggroup.h> 0010 #include <kdesktopfile.h> 0011 #include <ksycoca.h> 0012 0013 // Qt 0014 #include <QDir> 0015 #include <QLoggingCategory> 0016 #include <QMimeDatabase> 0017 #include <QProcess> 0018 #include <QSignalSpy> 0019 #include <QStandardPaths> 0020 #include <QTest> 0021 0022 #include <mimetypedata.h> 0023 #include <mimetypewriter.h> 0024 0025 // Unfortunately this isn't available in non-developer builds of Qt... 0026 // extern Q_CORE_EXPORT int qmime_secondsBetweenChecks; // see qmimeprovider.cpp 0027 0028 class FileTypesTest : public QObject 0029 { 0030 Q_OBJECT 0031 0032 private Q_SLOTS: 0033 void initTestCase() 0034 { 0035 QLoggingCategory::setFilterRules(QStringLiteral("kf.coreaddons.kdirwatch.debug=true")); 0036 0037 QStandardPaths::setTestModeEnabled(true); 0038 0039 // update-mime-database needs to know about that test directory for the mimetype pattern change in testMimeTypePatterns to have an effect 0040 qputenv("XDG_DATA_HOME", QFile::encodeName(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation))); 0041 0042 m_mimeTypeCreatedSuccessfully = false; 0043 QStringList appsDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); 0044 // qDebug() << appsDirs; 0045 m_localApps = appsDirs.first() + QLatin1Char('/'); 0046 m_localConfig = QDir(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)); 0047 QVERIFY(QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/mime/packages"))); 0048 0049 QFile::remove(m_localConfig.filePath(QStringLiteral("mimeapps.list"))); 0050 QFile::remove(m_localConfig.filePath(QStringLiteral("filetypesrc"))); 0051 0052 // Create fake applications for some tests below. 0053 fakeApplication = QStringLiteral("fakeapplication.desktop"); 0054 createDesktopFile(m_localApps + fakeApplication, {QStringLiteral("image/png")}); 0055 fakeApplication2 = QStringLiteral("fakeapplication2.desktop"); 0056 createDesktopFile(m_localApps + fakeApplication2, {QStringLiteral("image/png"), QStringLiteral("text/plain")}); 0057 0058 // Cleanup after testMimeTypePatterns if it failed mid-way 0059 const QString packageFileName = 0060 QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/mime/") + QStringLiteral("packages/text-plain.xml"); 0061 if (!packageFileName.isEmpty()) { 0062 QFile::remove(packageFileName); 0063 MimeTypeWriter::runUpdateMimeDatabase(); 0064 } 0065 0066 KService::Ptr fakeApplicationService = KService::serviceByStorageId(fakeApplication); 0067 QVERIFY(fakeApplicationService); 0068 } 0069 0070 void testMimeTypeGroupAutoEmbed() 0071 { 0072 MimeTypeData data(QStringLiteral("text")); 0073 QCOMPARE(data.majorType(), QStringLiteral("text")); 0074 QCOMPARE(data.name(), QStringLiteral("text")); 0075 QVERIFY(data.isMeta()); 0076 QCOMPARE(data.autoEmbed(), MimeTypeData::No); // text doesn't autoembed by default 0077 QVERIFY(!data.isDirty()); 0078 data.setAutoEmbed(MimeTypeData::Yes); 0079 QCOMPARE(data.autoEmbed(), MimeTypeData::Yes); 0080 QVERIFY(data.isDirty()); 0081 QVERIFY(!data.sync()); // save to disk. Should succeed, but return false (no need to run update-mime-database) 0082 QVERIFY(!data.isDirty()); 0083 // Check what's on disk by creating another MimeTypeData instance 0084 MimeTypeData data2(QStringLiteral("text")); 0085 QCOMPARE(data2.autoEmbed(), MimeTypeData::Yes); 0086 QVERIFY(!data2.isDirty()); 0087 data2.setAutoEmbed(MimeTypeData::No); // revert to default, for next time 0088 QVERIFY(data2.isDirty()); 0089 QVERIFY(!data2.sync()); 0090 QVERIFY(!data2.isDirty()); 0091 0092 // TODO test askSave after cleaning up the code 0093 } 0094 0095 void testMimeTypeAutoEmbed() 0096 { 0097 QMimeDatabase db; 0098 MimeTypeData data(db.mimeTypeForName(QStringLiteral("text/plain"))); 0099 QCOMPARE(data.majorType(), QStringLiteral("text")); 0100 QCOMPARE(data.minorType(), QStringLiteral("plain")); 0101 QCOMPARE(data.name(), QStringLiteral("text/plain")); 0102 QVERIFY(!data.isMeta()); 0103 QCOMPARE(data.autoEmbed(), MimeTypeData::UseGroupSetting); 0104 QVERIFY(!data.isDirty()); 0105 data.setAutoEmbed(MimeTypeData::Yes); 0106 QCOMPARE(data.autoEmbed(), MimeTypeData::Yes); 0107 QVERIFY(data.isDirty()); 0108 QVERIFY(!data.sync()); // save to disk. Should succeed, but return false (no need to run update-mime-database) 0109 QVERIFY(!data.isDirty()); 0110 // Check what's on disk by creating another MimeTypeData instance 0111 MimeTypeData data2(db.mimeTypeForName(QStringLiteral("text/plain"))); 0112 QCOMPARE(data2.autoEmbed(), MimeTypeData::Yes); 0113 QVERIFY(!data2.isDirty()); 0114 data2.setAutoEmbed(MimeTypeData::UseGroupSetting); // revert to default, for next time 0115 QVERIFY(data2.isDirty()); 0116 QVERIFY(!data2.sync()); 0117 QVERIFY(!data2.isDirty()); 0118 } 0119 0120 void testMimeTypePatterns() 0121 { 0122 // Given the text/plain mimetype 0123 QMimeDatabase db; 0124 MimeTypeData data(db.mimeTypeForName(QStringLiteral("text/plain"))); 0125 QCOMPARE(data.name(), QStringLiteral("text/plain")); 0126 QCOMPARE(data.majorType(), QStringLiteral("text")); 0127 QCOMPARE(data.minorType(), QStringLiteral("plain")); 0128 QVERIFY(!data.isMeta()); 0129 QStringList patterns = data.patterns(); 0130 QVERIFY(patterns.contains(QStringLiteral("*.txt"))); 0131 QVERIFY(!patterns.contains(QStringLiteral("*.toto"))); 0132 0133 // When the user changes the patterns 0134 const QStringList origPatterns = patterns; 0135 patterns.removeAll(QStringLiteral("*.txt")); 0136 patterns.append(QStringLiteral("*.toto")); // yes, a french guy wrote this, as you can see 0137 patterns.sort(); // for future comparisons 0138 QVERIFY(!data.isDirty()); 0139 data.setPatterns(patterns); 0140 QVERIFY(data.isDirty()); 0141 const bool needUpdateMimeDb = data.sync(); 0142 QVERIFY(needUpdateMimeDb); 0143 MimeTypeWriter::runUpdateMimeDatabase(); 0144 0145 // Then the GUI and the QMimeDatabase API should show the new patterns 0146 QCOMPARE(data.patterns(), patterns); 0147 data.refresh(); // reload from the xml 0148 QCOMPARE(data.patterns(), patterns); 0149 // Check what's in QMimeDatabase 0150 QStringList newPatterns = db.mimeTypeForName(QStringLiteral("text/plain")).globPatterns(); 0151 newPatterns.sort(); 0152 QCOMPARE(newPatterns, patterns); 0153 if (newPatterns == patterns) { // TODO Qt6: remove the if (keep the QVERIFY!) 0154 QVERIFY(!data.isDirty()); 0155 } 0156 0157 // And then removing the custom file by hand should revert to the initial state 0158 const QString packageFileName = 0159 QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/mime/") + QStringLiteral("packages/text-plain.xml"); 0160 QVERIFY(!packageFileName.isEmpty()); 0161 QFile::remove(packageFileName); 0162 MimeTypeWriter::runUpdateMimeDatabase(); 0163 // Check what's in QMimeDatabase 0164 newPatterns = db.mimeTypeForName(QStringLiteral("text/plain")).globPatterns(); 0165 newPatterns.sort(); 0166 QCOMPARE(newPatterns, origPatterns); 0167 } 0168 0169 void testAddService() 0170 { 0171 QMimeDatabase db; 0172 QString mimeTypeName = QStringLiteral("application/rtf"); // use inherited mimetype to test #321706 0173 MimeTypeData data(db.mimeTypeForName(mimeTypeName)); 0174 QStringList appServices = data.appServices(); 0175 // qDebug() << appServices; 0176 QVERIFY(appServices.contains(fakeApplication2)); 0177 QVERIFY(!appServices.contains(fakeApplication)); // already there? hmm can't really test then 0178 QVERIFY(!data.isDirty()); 0179 appServices.prepend(fakeApplication); 0180 data.setAppServices(appServices); 0181 QVERIFY(data.isDirty()); 0182 QVERIFY(!data.sync()); // success, but no need to run update-mime-database 0183 runKBuildSycoca(); 0184 QVERIFY(!data.isDirty()); 0185 // Check what's in ksycoca 0186 checkMimeTypeServices(mimeTypeName, appServices); 0187 // Check what's in mimeapps.list 0188 checkAddedAssociationsContains(mimeTypeName, fakeApplication); 0189 0190 // Test reordering apps, i.e. move fakeApplication under oldPreferredApp 0191 appServices.removeFirst(); 0192 appServices.insert(1, fakeApplication); 0193 data.setAppServices(appServices); 0194 QVERIFY(!data.sync()); // success, but no need to run update-mime-database 0195 runKBuildSycoca(); 0196 QVERIFY(!data.isDirty()); 0197 // Check what's in ksycoca 0198 checkMimeTypeServices(mimeTypeName, appServices); 0199 // Check what's in mimeapps.list 0200 checkAddedAssociationsContains(mimeTypeName, fakeApplication); 0201 0202 // Then we get the signal that kbuildsycoca changed 0203 data.refresh(); 0204 0205 // Now test removing (in the same test, since it's inter-dependent) 0206 QVERIFY(appServices.removeAll(fakeApplication) > 0); 0207 data.setAppServices(appServices); 0208 QVERIFY(data.isDirty()); 0209 QVERIFY(!data.sync()); // success, but no need to run update-mime-database 0210 runKBuildSycoca(); 0211 // Check what's in ksycoca 0212 checkMimeTypeServices(mimeTypeName, appServices); 0213 // Check what's in mimeapps.list 0214 checkRemovedAssociationsContains(mimeTypeName, fakeApplication); 0215 } 0216 0217 void testRemoveTwice() 0218 { 0219 QMimeDatabase db; 0220 // Remove fakeApplication from image/png 0221 QString mimeTypeName = QStringLiteral("image/png"); 0222 MimeTypeData data(db.mimeTypeForName(mimeTypeName)); 0223 QStringList appServices = data.appServices(); 0224 qDebug() << "initial list for" << mimeTypeName << appServices; 0225 QVERIFY(appServices.removeAll(fakeApplication) > 0); 0226 data.setAppServices(appServices); 0227 QVERIFY(!data.sync()); // success, but no need to run update-mime-database 0228 runKBuildSycoca(); 0229 // Check what's in ksycoca 0230 checkMimeTypeServices(mimeTypeName, appServices); 0231 // Check what's in mimeapps.list 0232 checkRemovedAssociationsContains(mimeTypeName, fakeApplication); 0233 0234 // Remove fakeApplication2 from image/png; must keep the previous entry in "Removed Associations" 0235 qDebug() << "Removing fakeApplication2"; 0236 QVERIFY(appServices.removeAll(fakeApplication2) > 0); 0237 data.setAppServices(appServices); 0238 QVERIFY(!data.sync()); // success, but no need to run update-mime-database 0239 runKBuildSycoca(); 0240 // Check what's in ksycoca 0241 checkMimeTypeServices(mimeTypeName, appServices); 0242 // Check what's in mimeapps.list 0243 checkRemovedAssociationsContains(mimeTypeName, fakeApplication); 0244 // Check what's in mimeapps.list 0245 checkRemovedAssociationsContains(mimeTypeName, fakeApplication2); 0246 0247 // And now re-add fakeApplication2... 0248 qDebug() << "Re-adding fakeApplication2"; 0249 appServices.prepend(fakeApplication2); 0250 data.setAppServices(appServices); 0251 QVERIFY(!data.sync()); // success, but no need to run update-mime-database 0252 runKBuildSycoca(); 0253 // Check what's in ksycoca 0254 checkMimeTypeServices(mimeTypeName, appServices); 0255 // Check what's in mimeapps.list 0256 checkRemovedAssociationsContains(mimeTypeName, fakeApplication); 0257 checkRemovedAssociationsDoesNotContain(mimeTypeName, fakeApplication2); 0258 } 0259 0260 void testCreateMimeType() 0261 { 0262 QMimeDatabase db; 0263 const QString mimeTypeName = QStringLiteral("fake/unit-test-fake-mimetype"); 0264 // Clean up after previous runs if necessary 0265 if (MimeTypeWriter::hasDefinitionFile(mimeTypeName)) { 0266 MimeTypeWriter::removeOwnMimeType(mimeTypeName); 0267 } 0268 0269 MimeTypeData data(mimeTypeName, true); 0270 data.setComment(QStringLiteral("Fake MimeType")); 0271 QStringList patterns = QStringList() << QStringLiteral("*.pkg.tar.gz"); 0272 data.setPatterns(patterns); 0273 QVERIFY(data.isDirty()); 0274 QVERIFY(data.sync()); 0275 MimeTypeWriter::runUpdateMimeDatabase(); 0276 // QMimeDatabase doesn't even try to update the cache if less than 0277 // 5000 ms have passed (can't use qmime_secondsBetweenChecks) 0278 QTest::qSleep(5000); 0279 QMimeType mime = db.mimeTypeForName(mimeTypeName); 0280 QVERIFY(mime.isValid()); 0281 QCOMPARE(mime.comment(), QStringLiteral("Fake MimeType")); 0282 QCOMPARE(mime.globPatterns(), patterns); // must sort them if more than one 0283 0284 // Testcase for the shaman.xml bug 0285 QCOMPARE(db.mimeTypeForFile(QStringLiteral("/whatever/foo.pkg.tar.gz")).name(), QStringLiteral("fake/unit-test-fake-mimetype")); 0286 0287 m_mimeTypeCreatedSuccessfully = true; 0288 } 0289 0290 void testDeleteMimeType() 0291 { 0292 QMimeDatabase db; 0293 if (!m_mimeTypeCreatedSuccessfully) { 0294 QSKIP("This test relies on testCreateMimeType"); 0295 } 0296 const QString mimeTypeName = QStringLiteral("fake/unit-test-fake-mimetype"); 0297 QVERIFY(MimeTypeWriter::hasDefinitionFile(mimeTypeName)); 0298 MimeTypeWriter::removeOwnMimeType(mimeTypeName); 0299 MimeTypeWriter::runUpdateMimeDatabase(); 0300 // QMimeDatabase doesn't even try to update the cache if less than 0301 // 5000 ms have passed (can't use qmime_secondsBetweenChecks) 0302 QTest::qSleep(5000); 0303 const QMimeType mime = db.mimeTypeForName(mimeTypeName); 0304 QVERIFY2(!mime.isValid(), qPrintable(mimeTypeName)); 0305 } 0306 0307 void testModifyMimeTypeComment() // of a system mimetype. And check that it's re-read correctly. 0308 { 0309 QMimeDatabase db; 0310 QString mimeTypeName = QStringLiteral("image/png"); 0311 MimeTypeData data(db.mimeTypeForName(mimeTypeName)); 0312 QCOMPARE(data.comment(), QString::fromLatin1("PNG image")); 0313 QString fakeComment = QStringLiteral("PNG image [testing]"); 0314 data.setComment(fakeComment); 0315 QVERIFY(data.isDirty()); 0316 QVERIFY(data.sync()); 0317 MimeTypeWriter::runUpdateMimeDatabase(); 0318 QMimeType mime = db.mimeTypeForName(mimeTypeName); 0319 QVERIFY(mime.isValid()); 0320 QCOMPARE(mime.comment(), fakeComment); 0321 0322 // Cleanup 0323 QVERIFY(MimeTypeWriter::hasDefinitionFile(mimeTypeName)); 0324 MimeTypeWriter::removeOwnMimeType(mimeTypeName); 0325 } 0326 0327 void cleanupTestCase() 0328 { 0329 const QString localAppsDir = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation); 0330 QFile::remove(localAppsDir + QLatin1String("/fakeapplication.desktop")); 0331 QFile::remove(localAppsDir + QLatin1String("/fakeapplication2.desktop")); 0332 } 0333 0334 private: // helper methods 0335 void checkAddedAssociationsContains(const QString &mimeTypeName, const QString &application) 0336 { 0337 const KConfig config(m_localConfig.filePath(QStringLiteral("mimeapps.list")), KConfig::NoGlobals); 0338 const KConfigGroup group(&config, "Added Associations"); 0339 const QStringList addedEntries = group.readXdgListEntry(mimeTypeName); 0340 if (!addedEntries.contains(application)) { 0341 qWarning() << addedEntries << "does not contain" << application; 0342 QVERIFY(addedEntries.contains(application)); 0343 } 0344 } 0345 0346 void checkRemovedAssociationsContains(const QString &mimeTypeName, const QString &application) 0347 { 0348 const KConfig config(m_localConfig.filePath(QStringLiteral("mimeapps.list")), KConfig::NoGlobals); 0349 const KConfigGroup group(&config, "Removed Associations"); 0350 const QStringList removedEntries = group.readXdgListEntry(mimeTypeName); 0351 if (!removedEntries.contains(application)) { 0352 qWarning() << removedEntries << "does not contain" << application; 0353 QVERIFY(removedEntries.contains(application)); 0354 } 0355 } 0356 0357 void checkRemovedAssociationsDoesNotContain(const QString &mimeTypeName, const QString &application) 0358 { 0359 const KConfig config(m_localConfig.filePath(QStringLiteral("mimeapps.list")), KConfig::NoGlobals); 0360 const KConfigGroup group(&config, "Removed Associations"); 0361 const QStringList removedEntries = group.readXdgListEntry(mimeTypeName); 0362 if (removedEntries.contains(application)) { 0363 qWarning() << removedEntries << "contains" << application; 0364 QVERIFY(!removedEntries.contains(application)); 0365 } 0366 } 0367 0368 void runKBuildSycoca() 0369 { 0370 // Wait for notifyDatabaseChanged DBus signal 0371 // (The real KCM code simply does the refresh in a slot, asynchronously) 0372 0373 QProcess proc; 0374 // proc.setProcessChannelMode(QProcess::ForwardedChannels); 0375 const QString kbuildsycoca = QStandardPaths::findExecutable(QLatin1String(KBUILDSYCOCA_EXENAME)); 0376 QVERIFY(!kbuildsycoca.isEmpty()); 0377 QStringList args; 0378 args << QStringLiteral("--testmode"); 0379 proc.start(kbuildsycoca, args); 0380 QSignalSpy spy(KSycoca::self(), qOverload<>(&KSycoca::databaseChanged)); 0381 proc.waitForFinished(); 0382 qDebug() << "waiting for signal"; 0383 QVERIFY(spy.wait(10000)); 0384 qDebug() << "got signal"; 0385 } 0386 0387 void createDesktopFile(const QString &path, const QStringList &mimeTypes) 0388 { 0389 KDesktopFile file(path); 0390 KConfigGroup group = file.desktopGroup(); 0391 group.writeEntry("Name", "FakeApplication"); 0392 group.writeEntry("Type", "Application"); 0393 group.writeEntry("Exec", "ls"); 0394 group.writeXdgListEntry("MimeType", mimeTypes); 0395 } 0396 0397 void checkMimeTypeServices(const QString &mimeTypeName, const QStringList &expectedServices) 0398 { 0399 QMimeDatabase db; 0400 MimeTypeData data2(db.mimeTypeForName(mimeTypeName)); 0401 if (data2.appServices() != expectedServices) { 0402 qDebug() << "got" << data2.appServices() << "expected" << expectedServices; 0403 } 0404 QCOMPARE(data2.appServices(), expectedServices); 0405 } 0406 0407 QString fakeApplication; // storage id of the fake application 0408 QString fakeApplication2; // storage id of the fake application2 0409 QString m_localApps; 0410 QDir m_localConfig; 0411 bool m_mimeTypeCreatedSuccessfully; 0412 }; 0413 0414 QTEST_MAIN(FileTypesTest) 0415 0416 #include "filetypestest.moc"