File indexing completed on 2024-09-15 03:39:51
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 1999, 2007 David Faure <faure@kde.org> 0004 SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-only 0007 */ 0008 0009 #include "kbuildmimetypefactory_p.h" 0010 #include "kbuildservicefactory_p.h" 0011 #include "kbuildservicegroupfactory_p.h" 0012 #include "ksycoca.h" 0013 0014 #include "ksycocadict_p.h" 0015 #include "sycocadebug.h" 0016 #include <KDesktopFile> 0017 0018 #include <QDebug> 0019 #include <QDir> 0020 #include <QMimeDatabase> 0021 0022 #include <QStandardPaths> 0023 #include <kmimetypefactory_p.h> 0024 0025 KBuildServiceFactory::KBuildServiceFactory(KBuildMimeTypeFactory *mimeTypeFactory, KBuildServiceGroupFactory *serviceGroupFactory) 0026 : KServiceFactory(mimeTypeFactory->sycoca()) 0027 , m_nameMemoryHash() 0028 , m_relNameMemoryHash() 0029 , m_menuIdMemoryHash() 0030 , m_dupeDict() 0031 , m_mimeTypeFactory(mimeTypeFactory) 0032 , m_serviceGroupFactory(serviceGroupFactory) 0033 { 0034 m_nameDict = new KSycocaDict(); 0035 m_relNameDict = new KSycocaDict(); 0036 m_menuIdDict = new KSycocaDict(); 0037 } 0038 0039 KBuildServiceFactory::~KBuildServiceFactory() 0040 { 0041 } 0042 0043 KService::Ptr KBuildServiceFactory::findServiceByDesktopName(const QString &name) 0044 { 0045 return m_nameMemoryHash.value(name); 0046 } 0047 0048 KService::Ptr KBuildServiceFactory::findServiceByDesktopPath(const QString &name) 0049 { 0050 return m_relNameMemoryHash.value(name); 0051 } 0052 0053 KService::Ptr KBuildServiceFactory::findServiceByMenuId(const QString &menuId) 0054 { 0055 return m_menuIdMemoryHash.value(menuId); 0056 } 0057 0058 KSycocaEntry *KBuildServiceFactory::createEntry(const QString &file) const 0059 { 0060 const QStringView name = QStringView(file).mid(file.lastIndexOf(QLatin1Char('/')) + 1); 0061 0062 // Is it a .desktop file? 0063 if (name.endsWith(QLatin1String(".desktop"))) { 0064 // qCDebug(SYCOCA) << file; 0065 0066 Q_ASSERT(QDir::isAbsolutePath(file)); 0067 KService *serv = new KService(file); 0068 0069 // qCDebug(SYCOCA) << "Creating KService from" << file << "entryPath=" << serv->entryPath(); 0070 // Note that the menuId will be set by the vfolder_menu.cpp code just after 0071 // createEntry returns. 0072 0073 if (serv->isValid() && !serv->isDeleted()) { 0074 // qCDebug(SYCOCA) << "Creating KService from" << file << "entryPath=" << serv->entryPath() << "storageId=" << serv->storageId(); 0075 return serv; 0076 } else { 0077 if (!serv->isDeleted()) { 0078 qCWarning(SYCOCA) << "Invalid Service : " << file; 0079 } 0080 delete serv; 0081 return nullptr; 0082 } 0083 } // TODO else if a Windows application, new KService(name, exec, icon) 0084 return nullptr; 0085 } 0086 0087 void KBuildServiceFactory::saveHeader(QDataStream &str) 0088 { 0089 KSycocaFactory::saveHeader(str); 0090 0091 str << qint32(m_nameDictOffset); 0092 str << qint32(m_relNameDictOffset); 0093 str << qint32(m_offerListOffset); 0094 str << qint32(m_menuIdDictOffset); 0095 } 0096 0097 void KBuildServiceFactory::save(QDataStream &str) 0098 { 0099 KSycocaFactory::save(str); 0100 0101 m_nameDictOffset = str.device()->pos(); 0102 m_nameDict->save(str); 0103 0104 m_relNameDictOffset = str.device()->pos(); 0105 m_relNameDict->save(str); 0106 0107 saveOfferList(str); 0108 0109 m_menuIdDictOffset = str.device()->pos(); 0110 m_menuIdDict->save(str); 0111 0112 qint64 endOfFactoryData = str.device()->pos(); 0113 0114 // Update header (pass #3) 0115 saveHeader(str); 0116 0117 // Seek to end. 0118 str.device()->seek(endOfFactoryData); 0119 } 0120 0121 void KBuildServiceFactory::collectInheritedServices() 0122 { 0123 // For each MIME type, go up the parent MIME type chains and collect offers. 0124 // For "removed associations" to work, we can't just grab everything from all parents. 0125 // We need to process parents before children, hence the recursive call in 0126 // collectInheritedServices(mime) and the QSet to process a given parent only once. 0127 QSet<QString> visitedMimes; 0128 const auto lst = m_mimeTypeFactory->allMimeTypes(); 0129 for (const QString &mimeType : lst) { 0130 collectInheritedServices(mimeType, visitedMimes); 0131 } 0132 } 0133 0134 void KBuildServiceFactory::collectInheritedServices(const QString &mimeTypeName, QSet<QString> &visitedMimes) 0135 { 0136 if (visitedMimes.contains(mimeTypeName)) { 0137 return; 0138 } 0139 visitedMimes.insert(mimeTypeName); 0140 0141 QMimeDatabase db; 0142 const QMimeType qmime = db.mimeTypeForName(mimeTypeName); 0143 const auto lst = qmime.parentMimeTypes(); 0144 for (QString parentMimeType : lst) { 0145 // Workaround issue in shared-mime-info and/or Qt, which sometimes return an alias as parent 0146 parentMimeType = db.mimeTypeForName(parentMimeType).name(); 0147 0148 collectInheritedServices(parentMimeType, visitedMimes); 0149 0150 const QList<KServiceOffer> &offers = m_offerHash.offersFor(parentMimeType); 0151 for (const auto &serviceOffer : offers) { 0152 if (!m_offerHash.hasRemovedOffer(mimeTypeName, serviceOffer.service())) { 0153 KServiceOffer offer(serviceOffer); 0154 offer.setMimeTypeInheritanceLevel(offer.mimeTypeInheritanceLevel() + 1); 0155 // qCDebug(SYCOCA) << "INHERITANCE: Adding service" << (*itserv).service()->entryPath() << "to" << mimeTypeName << "mimeTypeInheritanceLevel=" 0156 // << mimeTypeInheritanceLevel; 0157 m_offerHash.addServiceOffer(mimeTypeName, offer); 0158 } 0159 } 0160 } 0161 } 0162 0163 void KBuildServiceFactory::postProcessServices() 0164 { 0165 // By doing all this here rather than in addEntry (and removing when replacing 0166 // with local override), we only do it for the final applications. 0167 // Note that this also affects resolution of the by-desktop-name lookup, 0168 // as name resolution is only performed *after* all the duplicates (based on 0169 // storage ID) have been removed. 0170 0171 // For every service... 0172 for (auto itserv = m_entryDict->cbegin(), endIt = m_entryDict->cend(); itserv != endIt; ++itserv) { 0173 KSycocaEntry::Ptr entry = itserv.value(); 0174 KService::Ptr service(static_cast<KService *>(entry.data())); 0175 0176 const QString name = service->desktopEntryName(); 0177 KService::Ptr dup = m_nameMemoryHash.value(name); 0178 if (dup) { 0179 // The rule is that searching for the desktop name "foo" should find 0180 // the desktop file with the storage id "foo.desktop" before it 0181 // finds "bar/foo.desktop" (or "bar-foo.desktop"). 0182 // "bar/foo.desktop" and "baz/foo.desktop" are arbitrarily ordered 0183 // (in practice, the one later in the alphabet wins). 0184 if (dup->storageId().endsWith(service->storageId())) { 0185 // allow dup to be overridden 0186 m_nameDict->remove(name); 0187 dup = nullptr; 0188 } 0189 } 0190 if (!dup) { 0191 m_nameDict->add(name, entry); 0192 m_nameMemoryHash.insert(name, service); 0193 } 0194 0195 const QString relName = service->entryPath(); 0196 // qCDebug(SYCOCA) << "adding service" << service.data() << "isApp=" << service->isApplication() << "menuId=" << service->menuId() << "name=" << name << 0197 // "relName=" << relName; 0198 m_relNameDict->add(relName, entry); 0199 m_relNameMemoryHash.insert(relName, service); // for KMimeAssociations 0200 0201 const QString menuId = service->menuId(); 0202 if (!menuId.isEmpty()) { // empty for services, non-empty for applications 0203 m_menuIdDict->add(menuId, entry); 0204 m_menuIdMemoryHash.insert(menuId, service); // for KMimeAssociations 0205 } 0206 } 0207 populateServiceTypes(); 0208 } 0209 0210 void KBuildServiceFactory::populateServiceTypes() 0211 { 0212 QMimeDatabase db; 0213 // For every service... 0214 for (auto servIt = m_entryDict->cbegin(), endIt = m_entryDict->cend(); servIt != endIt; ++servIt) { 0215 KService::Ptr service(static_cast<KService *>(servIt.value().data())); 0216 const bool hidden = !service->showInCurrentDesktop(); 0217 0218 QList<KService::ServiceTypeAndPreference> serviceTypeList = service->_k_accessServiceTypes(); 0219 0220 // Add this service to all its MIME types 0221 // Don't cache count(), it can change during iteration! (we can't use an iterator-based loop 0222 // here the container could get reallocated which would invalidate iterators) 0223 for (int i = 0; i < serviceTypeList.count(); ++i) { 0224 const KService::ServiceTypeAndPreference &typeAndPref = serviceTypeList.at(i); 0225 const QString stName = typeAndPref.serviceType; 0226 0227 if (hidden) { 0228 continue; 0229 } 0230 const int preference = typeAndPref.preference; 0231 0232 KServiceOffer offer(service, preference, 0); 0233 QMimeType mime = db.mimeTypeForName(stName); 0234 if (!mime.isValid()) { 0235 if (stName.startsWith(QLatin1String("x-scheme-handler/"))) { 0236 // Create those on demand 0237 m_mimeTypeFactory->createFakeMimeType(stName); 0238 m_offerHash.addServiceOffer(stName, offer); 0239 } else { 0240 qCDebug(SYCOCA) << service->entryPath() << "specifies undefined MIME type/servicetype" << stName; 0241 // technically we could call addServiceOffer here, 'mime' isn't used. But it 0242 // would be useless, since we have no MIME type entry where to write the offers offset. 0243 continue; 0244 } 0245 } else { 0246 bool shouldAdd = true; 0247 const auto lst = service->mimeTypes(); 0248 0249 for (const QString &otherType : lst) { 0250 // Skip derived types if the base class is listed (#321706) 0251 if (stName != otherType && mime.inherits(otherType)) { 0252 // But don't skip aliases (they got resolved into mime.name() already, but don't let two aliases cancel out) 0253 if (db.mimeTypeForName(otherType).name() != mime.name()) { 0254 // qCDebug(SYCOCA) << "Skipping" << mime.name() << "because of" << otherType << "(canonical" << db.mimeTypeForName(otherType) << 0255 // ") while parsing" << service->entryPath(); 0256 shouldAdd = false; 0257 } 0258 } 0259 } 0260 if (shouldAdd) { 0261 // qCDebug(SYCOCA) << "Adding service" << service->entryPath() << "to" << mime.name(); 0262 m_offerHash.addServiceOffer(mime.name(), offer); // mime.name() so that we resolve aliases 0263 } 0264 } 0265 } 0266 } 0267 0268 // Read user preferences (added/removed associations) and add/remove serviceoffers to m_offerHash 0269 KMimeAssociations mimeAssociations(m_offerHash, this); 0270 mimeAssociations.parseAllMimeAppsList(); 0271 0272 // Now for each MIME type, collect services from parent MIME types 0273 collectInheritedServices(); 0274 0275 // Now collect the offsets into the (future) offer list 0276 // The loops look very much like the ones in saveOfferList obviously. 0277 int offersOffset = 0; 0278 const int offerEntrySize = sizeof(qint32) * 4; // four qint32s, see saveOfferList. 0279 0280 const auto &offerHash = m_offerHash.serviceTypeData(); 0281 auto it = offerHash.constBegin(); 0282 const auto end = offerHash.constEnd(); 0283 for (; it != end; ++it) { 0284 const QString stName = it.key(); 0285 const ServiceTypeOffersData offersData = it.value(); 0286 const int numOffers = offersData.offers.count(); 0287 0288 KMimeTypeFactory::MimeTypeEntry::Ptr entry = m_mimeTypeFactory->findMimeTypeEntryByName(stName); 0289 if (entry) { 0290 entry->setServiceOffersOffset(offersOffset); 0291 offersOffset += offerEntrySize * numOffers; 0292 } else if (stName.startsWith(QLatin1String("x-scheme-handler/"))) { 0293 // Create those on demand 0294 entry = m_mimeTypeFactory->createFakeMimeType(stName); 0295 entry->setServiceOffersOffset(offersOffset); 0296 offersOffset += offerEntrySize * numOffers; 0297 } else { 0298 if (stName.isEmpty()) { 0299 qCDebug(SYCOCA) << "Empty service type"; 0300 } else { 0301 qCWarning(SYCOCA) << "Service type not found:" << stName; 0302 } 0303 } 0304 } 0305 } 0306 0307 void KBuildServiceFactory::saveOfferList(QDataStream &str) 0308 { 0309 m_offerListOffset = str.device()->pos(); 0310 // qCDebug(SYCOCA) << "Saving offer list at offset" << m_offerListOffset; 0311 0312 const auto &offerHash = m_offerHash.serviceTypeData(); 0313 auto it = offerHash.constBegin(); 0314 const auto end = offerHash.constEnd(); 0315 for (; it != end; ++it) { 0316 const QString stName = it.key(); 0317 const ServiceTypeOffersData offersData = it.value(); 0318 QList<KServiceOffer> offers = offersData.offers; 0319 std::stable_sort(offers.begin(), offers.end()); // by initial preference 0320 0321 int offset = -1; 0322 KMimeTypeFactory::MimeTypeEntry::Ptr entry = m_mimeTypeFactory->findMimeTypeEntryByName(stName); 0323 if (entry) { 0324 offset = entry->offset(); 0325 // Q_ASSERT(str.device()->pos() == entry->serviceOffersOffset() + m_offerListOffset); 0326 } 0327 if (offset == -1) { 0328 qCDebug(SYCOCA) << "Didn't find servicetype or MIME type" << stName; 0329 continue; 0330 } 0331 0332 for (const auto &offer : std::as_const(offers)) { 0333 // qCDebug(SYCOCA) << stName << ": writing offer" << offer.service()->desktopEntryName() << offset << offer.service()->offset() << "in sycoca at 0334 // pos" << str.device()->pos(); 0335 Q_ASSERT(offer.service()->offset() != 0); 0336 0337 str << qint32(offset); 0338 str << qint32(offer.service()->offset()); 0339 str << qint32(offer.preference()); 0340 str << qint32(offer.mimeTypeInheritanceLevel()); 0341 // update offerEntrySize in populateServiceTypes if you add/remove something here 0342 } 0343 } 0344 0345 str << qint32(0); // End of list marker (0) 0346 } 0347 0348 void KBuildServiceFactory::addEntry(const KSycocaEntry::Ptr &newEntry) 0349 { 0350 Q_ASSERT(newEntry); 0351 if (m_dupeDict.contains(newEntry)) { 0352 return; 0353 } 0354 0355 const KService::Ptr service(static_cast<KService *>(newEntry.data())); 0356 m_dupeDict.insert(newEntry); 0357 KSycocaFactory::addEntry(newEntry); 0358 }