File indexing completed on 2024-04-28 05:27:04
0001 /* This file is part of the KDE project 0002 SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org> 0003 SPDX-FileCopyrightText: 2003, 2007 David Faure <faure@kde.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only 0006 */ 0007 0008 #include "mimetypedata.h" 0009 #include "mimetypewriter.h" 0010 #include <KApplicationTrader> 0011 #include <KConfigGroup> 0012 #include <KParts/PartLoader> 0013 #include <KProtocolManager> 0014 #include <KService> 0015 #include <KSharedConfig> 0016 #include <QDebug> 0017 #include <QFileInfo> 0018 #include <QMimeDatabase> 0019 #include <QStandardPaths> 0020 #include <QXmlStreamReader> 0021 0022 MimeTypeData::MimeTypeData(const QString &major) 0023 : m_askSave(AskSaveDefault) 0024 , m_bNewItem(false) 0025 , m_bFullInit(true) 0026 , m_isGroup(true) 0027 , m_appServicesModified(false) 0028 , m_embedServicesModified(false) 0029 , m_userSpecifiedIconModified(false) 0030 , m_major(major) 0031 { 0032 m_autoEmbed = readAutoEmbed(); 0033 } 0034 0035 MimeTypeData::MimeTypeData(const QMimeType &mime) 0036 : m_mimetype(mime) 0037 , m_askSave(AskSaveDefault) 0038 , // TODO: the code for initializing this is missing. FileTypeDetails initializes the checkbox instead... 0039 m_bNewItem(false) 0040 , m_bFullInit(false) 0041 , m_isGroup(false) 0042 , m_appServicesModified(false) 0043 , m_embedServicesModified(false) 0044 , m_userSpecifiedIconModified(false) 0045 { 0046 const QString mimeName = m_mimetype.name(); 0047 Q_ASSERT(!mimeName.isEmpty()); 0048 const int index = mimeName.indexOf(QLatin1Char('/')); 0049 if (index != -1) { 0050 m_major = mimeName.left(index); 0051 m_minor = mimeName.mid(index + 1); 0052 } else { 0053 m_major = mimeName; 0054 } 0055 initFromQMimeType(); 0056 } 0057 0058 MimeTypeData::MimeTypeData(const QString &mimeName, bool) 0059 : m_askSave(AskSaveDefault) 0060 , m_bNewItem(true) 0061 , m_bFullInit(false) 0062 , m_isGroup(false) 0063 , m_appServicesModified(false) 0064 , m_embedServicesModified(false) 0065 , m_userSpecifiedIconModified(false) 0066 { 0067 const int index = mimeName.indexOf(QLatin1Char('/')); 0068 if (index != -1) { 0069 m_major = mimeName.left(index); 0070 m_minor = mimeName.mid(index + 1); 0071 } else { 0072 m_major = mimeName; 0073 } 0074 m_autoEmbed = UseGroupSetting; 0075 // all the rest is empty by default 0076 } 0077 0078 void MimeTypeData::initFromQMimeType() 0079 { 0080 m_comment = m_mimetype.comment(); 0081 setPatterns(m_mimetype.globPatterns()); 0082 m_autoEmbed = readAutoEmbed(); 0083 0084 // Parse XML file to find out if the user specified a custom icon name 0085 QString file = name().toLower() + QLatin1String(".xml"); 0086 QStringList mimeFiles = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("mime/") + file); 0087 if (mimeFiles.isEmpty()) { 0088 // This is for shared-mime-info < 1.3 that did not lowecase mime names 0089 file = name() + QLatin1String(".xml"); 0090 mimeFiles = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("mime/") + file); 0091 if (mimeFiles.isEmpty()) { 0092 qWarning() << "No file found for" << file << ", even though the file appeared in a directory listing."; 0093 qWarning() << "Either it was just removed, or the directory doesn't have executable permission..."; 0094 qWarning() << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("mime"), QStandardPaths::LocateDirectory); 0095 return; 0096 } 0097 } 0098 0099 // Reverse iterator to get global first, then local. 0100 for (auto rIt = mimeFiles.crbegin(); rIt != mimeFiles.crend(); ++rIt) { 0101 const QString fullPath = *rIt; 0102 QFile qfile(fullPath); 0103 if (!qfile.open(QFile::ReadOnly)) { 0104 continue; 0105 } 0106 0107 QXmlStreamReader xml(&qfile); 0108 if (xml.readNextStartElement()) { 0109 if (xml.name() != QLatin1String("mime-type")) { 0110 continue; 0111 } 0112 const QString mimeName = xml.attributes().value(QLatin1String("type")).toString(); 0113 if (mimeName.isEmpty()) { 0114 continue; 0115 } 0116 if (QString::compare(mimeName, name(), Qt::CaseInsensitive) != 0) { 0117 qWarning() << "Got name" << mimeName << "in file" << file << "expected" << name(); 0118 } 0119 0120 while (xml.readNextStartElement()) { 0121 const auto tag = xml.name(); 0122 if (tag == QLatin1String("icon")) { 0123 m_userSpecifiedIcon = xml.attributes().value(QLatin1String("name")).toString(); 0124 } 0125 xml.skipCurrentElement(); 0126 } 0127 } 0128 } 0129 } 0130 0131 MimeTypeData::AutoEmbed MimeTypeData::readAutoEmbed() const 0132 { 0133 const KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("filetypesrc"), KConfig::NoGlobals); 0134 const QString key = QStringLiteral("embed-") + name(); 0135 const KConfigGroup group(config, "EmbedSettings"); 0136 if (m_isGroup) { 0137 // embedding is false by default except for image/*, multipart/* and inode/* (hardcoded in konq) 0138 const bool defaultValue = (m_major == QLatin1String("image") || m_major == QLatin1String("multipart") || m_major == QLatin1String("inode")); 0139 return group.readEntry(key, defaultValue) ? Yes : No; 0140 } else { 0141 if (group.hasKey(key)) { 0142 return group.readEntry(key, false) ? Yes : No; 0143 } 0144 // TODO if ( !mimetype.property( "X-KDE-LocalProtocol" ).toString().isEmpty() ) 0145 // TODO return MimeTypeData::Yes; // embed by default for zip, tar etc. 0146 return MimeTypeData::UseGroupSetting; 0147 } 0148 } 0149 0150 void MimeTypeData::writeAutoEmbed() 0151 { 0152 KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("filetypesrc"), KConfig::NoGlobals); 0153 if (!config->isConfigWritable(true)) { 0154 return; 0155 } 0156 0157 const QString key = QStringLiteral("embed-") + name(); 0158 KConfigGroup group(config, "EmbedSettings"); 0159 if (m_isGroup) { 0160 group.writeEntry(key, m_autoEmbed == Yes); 0161 } else { 0162 if (m_autoEmbed == UseGroupSetting) { 0163 group.deleteEntry(key); 0164 } else { 0165 group.writeEntry(key, m_autoEmbed == Yes); 0166 } 0167 } 0168 } 0169 0170 bool MimeTypeData::isEssential() const 0171 { 0172 // Keep in sync with KMimeType::checkEssentialMimeTypes 0173 const QString n = name(); 0174 if (n == QLatin1String("application/octet-stream")) { 0175 return true; 0176 } 0177 if (n == QLatin1String("inode/directory")) { 0178 return true; 0179 } 0180 if (n == QLatin1String("inode/blockdevice")) { 0181 return true; 0182 } 0183 if (n == QLatin1String("inode/chardevice")) { 0184 return true; 0185 } 0186 if (n == QLatin1String("inode/socket")) { 0187 return true; 0188 } 0189 if (n == QLatin1String("inode/fifo")) { 0190 return true; 0191 } 0192 if (n == QLatin1String("application/x-shellscript")) { 0193 return true; 0194 } 0195 if (n == QLatin1String("application/x-executable")) { 0196 return true; 0197 } 0198 if (n == QLatin1String("application/x-desktop")) { 0199 return true; 0200 } 0201 return false; 0202 } 0203 0204 void MimeTypeData::setUserSpecifiedIcon(const QString &icon) 0205 { 0206 if (icon == m_userSpecifiedIcon) { 0207 return; 0208 } 0209 m_userSpecifiedIcon = icon; 0210 m_userSpecifiedIconModified = true; 0211 } 0212 0213 QStringList MimeTypeData::getAppOffers() const 0214 { 0215 QStringList serviceIds; 0216 const KService::List offerList = KApplicationTrader::queryByMimeType(name()); 0217 for (const auto &servicePtr : offerList) { 0218 serviceIds.append(servicePtr->storageId()); 0219 } 0220 return serviceIds; 0221 } 0222 0223 QStringList MimeTypeData::getPartOffers() const 0224 { 0225 QStringList servicesIds; 0226 // We do not do any sorting, because KParts uses the order in which the entries are saved 0227 const auto partOfferList = KParts::PartLoader::partsForMimeType(name()); 0228 for (const auto &metaData : partOfferList) { 0229 servicesIds.append(metaData.pluginId()); 0230 } 0231 return servicesIds; 0232 } 0233 0234 void MimeTypeData::getMyServiceOffers() const 0235 { 0236 m_appServices = getAppOffers(); 0237 m_embedParts = getPartOffers(); 0238 m_bFullInit = true; 0239 } 0240 0241 QStringList MimeTypeData::appServices() const 0242 { 0243 if (!m_bFullInit) { 0244 getMyServiceOffers(); 0245 } 0246 return m_appServices; 0247 } 0248 0249 QStringList MimeTypeData::embedParts() const 0250 { 0251 if (!m_bFullInit) { 0252 getMyServiceOffers(); 0253 } 0254 return m_embedParts; 0255 } 0256 0257 bool MimeTypeData::isMimeTypeDirty() const 0258 { 0259 Q_ASSERT(!m_isGroup); 0260 if (m_bNewItem) { 0261 return true; 0262 } 0263 0264 if (!m_mimetype.isValid()) { 0265 qWarning() << "MimeTypeData for" << name() << "says 'not new' but is without a mimetype? Should not happen."; 0266 return true; 0267 } 0268 0269 if (m_mimetype.comment() != m_comment) { 0270 qDebug() << "Mimetype Comment Dirty: old=" << m_mimetype.comment() << "m_comment=" << m_comment; 0271 return true; 0272 } 0273 if (m_userSpecifiedIconModified) { 0274 qDebug() << "m_userSpecifiedIcon has changed. Now set to" << m_userSpecifiedIcon; 0275 return true; 0276 } 0277 0278 QStringList storedPatterns = m_mimetype.globPatterns(); 0279 storedPatterns.sort(); // see ctor 0280 if (storedPatterns != m_patterns) { 0281 qDebug() << "Mimetype Patterns Dirty: old=" << storedPatterns << "m_patterns=" << m_patterns; 0282 return true; 0283 } 0284 0285 if (readAutoEmbed() != m_autoEmbed) { 0286 return true; 0287 } 0288 return false; 0289 } 0290 0291 bool MimeTypeData::isServiceListDirty() const 0292 { 0293 return !m_isGroup && (m_appServicesModified || m_embedServicesModified); 0294 } 0295 0296 bool MimeTypeData::isDirty() const 0297 { 0298 if (m_bNewItem) { 0299 qDebug() << "New item, need to save it"; 0300 return true; 0301 } 0302 0303 if (!m_isGroup) { 0304 if (isServiceListDirty()) { 0305 return true; 0306 } 0307 if (isMimeTypeDirty()) { 0308 return true; 0309 } 0310 } else { // is a group 0311 if (readAutoEmbed() != m_autoEmbed) { 0312 return true; 0313 } 0314 } 0315 0316 if (m_askSave != AskSaveDefault) { 0317 return true; 0318 } 0319 0320 // nothing seems to have changed, it's not dirty. 0321 return false; 0322 } 0323 0324 bool MimeTypeData::sync() 0325 { 0326 if (m_isGroup) { 0327 writeAutoEmbed(); 0328 return false; 0329 } 0330 0331 if (m_askSave != AskSaveDefault) { 0332 KSharedConfig::Ptr config = KSharedConfig::openConfig(QStringLiteral("filetypesrc"), KConfig::NoGlobals); 0333 if (!config->isConfigWritable(true)) { 0334 return false; 0335 } 0336 KConfigGroup cg = config->group("Notification Messages"); 0337 if (m_askSave == AskSaveYes) { 0338 // Ask 0339 cg.deleteEntry(QStringLiteral("askSave") + name()); 0340 cg.deleteEntry(QStringLiteral("askEmbedOrSave") + name()); 0341 } else { 0342 // Do not ask, open 0343 cg.writeEntry(QStringLiteral("askSave") + name(), QStringLiteral("no")); 0344 cg.writeEntry(QStringLiteral("askEmbedOrSave") + name(), QStringLiteral("no")); 0345 } 0346 } 0347 0348 writeAutoEmbed(); 0349 0350 bool needUpdateMimeDb = false; 0351 if (isMimeTypeDirty()) { 0352 MimeTypeWriter mimeTypeWriter(name()); 0353 mimeTypeWriter.setComment(m_comment); 0354 if (!m_userSpecifiedIcon.isEmpty()) { 0355 mimeTypeWriter.setIconName(m_userSpecifiedIcon); 0356 } 0357 mimeTypeWriter.setPatterns(m_patterns); 0358 if (!mimeTypeWriter.write()) { 0359 return false; 0360 } 0361 m_userSpecifiedIconModified = false; 0362 needUpdateMimeDb = true; 0363 } 0364 0365 syncServices(); 0366 0367 return needUpdateMimeDb; 0368 } 0369 0370 static const char s_DefaultApplications[] = "Default Applications"; 0371 static const char s_AddedAssociations[] = "Added Associations"; 0372 static const char s_RemovedAssociations[] = "Removed Associations"; 0373 0374 void MimeTypeData::syncServices() 0375 { 0376 if (!m_bFullInit) { 0377 return; 0378 } 0379 0380 KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation); 0381 0382 if (!profile->isConfigWritable(true)) { // warn user if mimeapps.list is root-owned (#155126/#94504) 0383 return; 0384 } 0385 0386 const QStringList oldAppServices = getAppOffers(); 0387 if (oldAppServices != m_appServices) { 0388 // Save the default application according to mime-apps-spec 1.0 0389 KConfigGroup defaultApp(profile, s_DefaultApplications); 0390 saveDefaultApplication(defaultApp, m_appServices); 0391 // Save preferred services 0392 KConfigGroup addedApps(profile, s_AddedAssociations); 0393 saveServices(addedApps, m_appServices); 0394 KConfigGroup removedApps(profile, s_RemovedAssociations); 0395 saveRemovedServices(removedApps, m_appServices, oldAppServices); 0396 } 0397 0398 KSharedConfig::Ptr kpartsProfile = KSharedConfig::openConfig(QStringLiteral("kpartsrc"), KConfig::NoGlobals); 0399 const QStringList oldPartServices = getPartOffers(); 0400 if (oldPartServices != m_embedParts) { 0401 KConfigGroup addedParts(kpartsProfile, "Added KDE Part Associations"); 0402 if (m_embedParts.isEmpty()) { 0403 addedParts.deleteEntry(name()); 0404 } else { 0405 addedParts.writeXdgListEntry(name(), m_embedParts); 0406 } 0407 KConfigGroup removedParts(kpartsProfile, "Removed KDE Part Associations"); 0408 saveRemovedServices(removedParts, m_embedParts, oldPartServices); 0409 } 0410 0411 // Clean out any kde-mimeapps.list which would take precedence any cancel our changes. 0412 const QString desktops = QString::fromLocal8Bit(qgetenv("XDG_CURRENT_DESKTOP")); 0413 const auto desktopsSplit = desktops.split(QLatin1Char(':'), Qt::SkipEmptyParts); 0414 for (const QString &desktop : desktopsSplit) { 0415 const QString file = 0416 QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + desktop.toLower() + QLatin1String("-mimeapps.list"); 0417 if (QFileInfo::exists(file)) { 0418 qDebug() << "Cleaning up" << file; 0419 KConfig conf(file, KConfig::NoGlobals); 0420 KConfigGroup(&conf, s_DefaultApplications).deleteEntry(name()); 0421 KConfigGroup(&conf, s_AddedAssociations).deleteEntry(name()); 0422 KConfigGroup(&conf, s_RemovedAssociations).deleteEntry(name()); 0423 } 0424 } 0425 0426 m_appServicesModified = false; 0427 m_embedServicesModified = false; 0428 } 0429 0430 static QStringList collectStorageIds(const QStringList &services) 0431 { 0432 QStringList storageIds; 0433 0434 for (const QString &service : services) { 0435 KService::Ptr pService = KService::serviceByStorageId(service); 0436 if (!pService) { 0437 qWarning() << "service with storage id" << service << "not found"; 0438 continue; // Where did that one go? 0439 } 0440 0441 storageIds.append(pService->storageId()); 0442 } 0443 0444 return storageIds; 0445 } 0446 0447 void MimeTypeData::saveRemovedServices(KConfigGroup &config, const QStringList &services, const QStringList &oldServices) 0448 { 0449 QStringList removedServiceList = config.readXdgListEntry(name()); 0450 0451 for (const QString &service : services) { 0452 // If removedServiceList.contains(service), then it was previously removed but has been added back 0453 removedServiceList.removeAll(service); 0454 } 0455 for (const QString &oldService : oldServices) { 0456 if (!services.contains(oldService)) { 0457 // The service was in m_appServices (or m_embedServices) but has been removed 0458 removedServiceList.append(oldService); 0459 } 0460 } 0461 if (removedServiceList.isEmpty()) { 0462 config.deleteEntry(name()); 0463 } else { 0464 config.writeXdgListEntry(name(), removedServiceList); 0465 } 0466 } 0467 0468 void MimeTypeData::saveServices(KConfigGroup &config, const QStringList &services) 0469 { 0470 if (services.isEmpty()) { 0471 config.deleteEntry(name()); 0472 } else { 0473 config.writeXdgListEntry(name(), collectStorageIds(services)); 0474 } 0475 } 0476 0477 void MimeTypeData::saveDefaultApplication(KConfigGroup &config, const QStringList &services) 0478 { 0479 if (services.isEmpty()) { 0480 config.deleteEntry(name()); 0481 return; 0482 } 0483 0484 const QStringList storageIds = collectStorageIds(services); 0485 if (!storageIds.isEmpty()) { 0486 const QString firstStorageId = storageIds.at(0); 0487 config.writeXdgListEntry(name(), {firstStorageId}); 0488 } 0489 } 0490 0491 void MimeTypeData::refresh() 0492 { 0493 if (m_isGroup) { 0494 return; 0495 } 0496 QMimeDatabase db; 0497 m_mimetype = db.mimeTypeForName(name()); 0498 if (m_mimetype.isValid()) { 0499 if (m_bNewItem) { 0500 qDebug() << "OK, created" << name(); 0501 m_bNewItem = false; // if this was a new mimetype, we just created it 0502 } 0503 if (!isMimeTypeDirty()) { 0504 // Update from the xml, in case something was changed from out of this kcm 0505 // (e.g. using KOpenWithDialog, or keditfiletype + kcmshell filetypes) 0506 initFromQMimeType(); 0507 } 0508 if (!m_appServicesModified && !m_embedServicesModified) { 0509 m_bFullInit = false; // refresh services too 0510 } 0511 } 0512 } 0513 0514 void MimeTypeData::getAskSave(bool &_askSave) 0515 { 0516 if (m_askSave == AskSaveYes) { 0517 _askSave = true; 0518 } 0519 if (m_askSave == AskSaveNo) { 0520 _askSave = false; 0521 } 0522 } 0523 0524 void MimeTypeData::setAskSave(bool _askSave) 0525 { 0526 m_askSave = _askSave ? AskSaveYes : AskSaveNo; 0527 } 0528 0529 bool MimeTypeData::canUseGroupSetting() const 0530 { 0531 // "Use group settings" isn't available for zip, tar etc.; those have a builtin default... 0532 if (!m_mimetype.isValid()) { // e.g. new mimetype 0533 return true; 0534 } 0535 const bool hasLocalProtocolRedirect = !KProtocolManager::protocolForArchiveMimetype(name()).isEmpty(); 0536 return !hasLocalProtocolRedirect; 0537 } 0538 0539 void MimeTypeData::setPatterns(const QStringList &p) 0540 { 0541 m_patterns = p; 0542 // Sort them, since update-mime-database doesn't respect order (order of globs file != order of xml), 0543 // and this code says things like if (m_mimetype.patterns() == m_patterns). 0544 // We could also sort in KMimeType::setPatterns but this would just slow down the 0545 // normal use case (anything else than this KCM) for no good reason. 0546 m_patterns.sort(); 0547 } 0548 0549 bool MimeTypeData::matchesFilter(const QString &filter) const 0550 { 0551 if (name().contains(filter, Qt::CaseInsensitive)) { 0552 return true; 0553 } 0554 0555 if (m_comment.contains(filter, Qt::CaseInsensitive)) { 0556 return true; 0557 } 0558 0559 if (!m_patterns.filter(filter, Qt::CaseInsensitive).isEmpty()) { 0560 return true; 0561 } 0562 0563 return false; 0564 } 0565 0566 void MimeTypeData::setAppServices(const QStringList &dsl) 0567 { 0568 if (!m_bFullInit) { 0569 getMyServiceOffers(); // so that m_bFullInit is true 0570 } 0571 m_appServices = dsl; 0572 m_appServicesModified = true; 0573 } 0574 0575 void MimeTypeData::setEmbedParts(const QStringList &dsl) 0576 { 0577 if (!m_bFullInit) { 0578 getMyServiceOffers(); // so that m_bFullInit is true 0579 } 0580 m_embedParts = dsl; 0581 m_embedServicesModified = true; 0582 } 0583 0584 QString MimeTypeData::icon() const 0585 { 0586 if (!m_userSpecifiedIcon.isEmpty()) { 0587 return m_userSpecifiedIcon; 0588 } 0589 if (m_mimetype.isValid()) { 0590 return m_mimetype.iconName(); 0591 } 0592 return QString(); 0593 }