fix tricky regression noticed by Vyacheslav Tokarev on Google Reader.
[kdelibs.git] / kded / kbuildservicefactory.cpp
blob94d573869929ddf144f6499f96d69ba0442b3077
1 /* This file is part of the KDE libraries
2 * Copyright (C) 1999, 2007 David Faure <faure@kde.org>
3 * 1999 Waldo Bastian <bastian@kde.org>
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License version 2 as published by the Free Software Foundation;
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB. If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 **/
20 #include "kbuildservicefactory.h"
21 #include "kbuildservicegroupfactory.h"
22 #include "kbuildmimetypefactory.h"
23 #include "ksycoca.h"
24 #include "ksycocadict.h"
25 #include "kresourcelist.h"
26 #include "kdesktopfile.h"
28 #include <kglobal.h>
29 #include <kstandarddirs.h>
30 #include <klocale.h>
31 #include <kdebug.h>
32 #include <assert.h>
33 #include <kmimetypefactory.h>
35 KBuildServiceFactory::KBuildServiceFactory( KSycocaFactory *serviceTypeFactory,
36 KBuildMimeTypeFactory *mimeTypeFactory,
37 KBuildServiceGroupFactory *serviceGroupFactory ) :
38 KServiceFactory(),
39 m_nameMemoryHash(),
40 m_relNameMemoryHash(),
41 m_menuIdMemoryHash(),
42 m_dupeDict(),
43 m_serviceTypeFactory( serviceTypeFactory ),
44 m_mimeTypeFactory( mimeTypeFactory ),
45 m_serviceGroupFactory( serviceGroupFactory )
47 m_resourceList = new KSycocaResourceList();
48 // We directly care about services desktop files.
49 // All the application desktop files are parsed on demand from the vfolder menu code.
50 m_resourceList->add( "services", "*.desktop" );
52 m_nameDict = new KSycocaDict();
53 m_relNameDict = new KSycocaDict();
54 m_menuIdDict = new KSycocaDict();
57 // return all service types for this factory
58 // i.e. first arguments to m_resourceList->add() above
59 QStringList KBuildServiceFactory::resourceTypes()
61 return QStringList() << "services";
64 KBuildServiceFactory::~KBuildServiceFactory()
66 delete m_resourceList;
69 KService::Ptr KBuildServiceFactory::findServiceByDesktopName(const QString &name)
71 return m_nameMemoryHash.value(name);
74 KService::Ptr KBuildServiceFactory::findServiceByDesktopPath(const QString &name)
76 return m_relNameMemoryHash.value(name);
79 KService::Ptr KBuildServiceFactory::findServiceByMenuId(const QString &menuId)
81 return m_menuIdMemoryHash.value(menuId);
84 KSycocaEntry* KBuildServiceFactory::createEntry( const QString& file, const char *resource ) const
86 QString name = file;
87 int pos = name.lastIndexOf('/');
88 if (pos != -1) {
89 name = name.mid(pos+1);
91 // Is it a .desktop file?
92 if (!name.endsWith(".desktop"))
93 return 0;
95 KDesktopFile desktopFile(resource, file);
97 KService * serv = new KService(&desktopFile);
98 //kDebug(7021) << "Creating KService from" << file << "entryPath=" << serv->entryPath();
99 // Note that the menuId will be set by the vfolder_menu.cpp code just after
100 // createEntry returns.
102 if ( serv->isValid() && !serv->isDeleted() ) {
103 return serv;
104 } else {
105 if (!serv->isDeleted())
106 kWarning(7012) << "Invalid Service : " << file;
107 delete serv;
108 return 0;
112 void KBuildServiceFactory::saveHeader(QDataStream &str)
114 KSycocaFactory::saveHeader(str);
116 str << (qint32) m_nameDictOffset;
117 str << (qint32) m_relNameDictOffset;
118 str << (qint32) m_offerListOffset;
119 str << (qint32) m_menuIdDictOffset;
122 void KBuildServiceFactory::save(QDataStream &str)
124 KSycocaFactory::save(str);
126 m_nameDictOffset = str.device()->pos();
127 m_nameDict->save(str);
129 m_relNameDictOffset = str.device()->pos();
130 m_relNameDict->save(str);
132 saveOfferList(str);
134 m_menuIdDictOffset = str.device()->pos();
135 m_menuIdDict->save(str);
137 int endOfFactoryData = str.device()->pos();
139 // Update header (pass #3)
140 saveHeader(str);
142 // Seek to end.
143 str.device()->seek(endOfFactoryData);
146 void KBuildServiceFactory::collectInheritedServices()
148 // For each mimetype, go up the parent-mimetype chains and collect offers.
149 // For "removed associations" to work, we can't just grab everything from all parents.
150 // We need to process parents before children, hence the recursive call in
151 // collectInheritedServices(mime) and the QSet to process a given parent only once.
152 QSet<KMimeType::Ptr> visitedMimes;
153 const KMimeType::List allMimeTypes = m_mimeTypeFactory->allMimeTypes();
154 KMimeType::List::const_iterator itm = allMimeTypes.begin();
155 for( ; itm != allMimeTypes.end(); ++itm ) {
156 const KMimeType::Ptr mimeType = *itm;
157 collectInheritedServices(mimeType, visitedMimes);
159 // TODO do the same for all/all and all/allfiles, if (!KServiceTypeProfile::configurationMode())
162 void KBuildServiceFactory::collectInheritedServices(KMimeType::Ptr mimeType, QSet<KMimeType::Ptr>& visitedMimes)
164 if (visitedMimes.contains(mimeType))
165 return;
166 visitedMimes.insert(mimeType);
168 // With multiple inheritance, the "mimeTypeInheritanceLevel" isn't exactly
169 // correct (it should only be increased when going up a level, not when iterating
170 // through the multiple parents at a given level). I don't think we care, though.
171 int mimeTypeInheritanceLevel = 0;
173 const QString mimeTypeName = mimeType->name();
174 Q_FOREACH(const QString& parent, mimeType->parentMimeTypes()) {
175 const KMimeType::Ptr parentMimeType =
176 m_mimeTypeFactory->findMimeTypeByName(parent, KMimeType::ResolveAliases);
178 if ( parentMimeType ) {
179 collectInheritedServices(parentMimeType, visitedMimes);
181 ++mimeTypeInheritanceLevel;
182 const QList<KServiceOffer>& offers = m_offerHash.offersFor(parent);
183 QList<KServiceOffer>::const_iterator itserv = offers.begin();
184 const QList<KServiceOffer>::const_iterator endserv = offers.end();
185 for ( ; itserv != endserv; ++itserv ) {
186 if (!m_offerHash.hasRemovedOffer(mimeTypeName, (*itserv).service())) {
187 KServiceOffer offer(*itserv);
188 offer.setMimeTypeInheritanceLevel(mimeTypeInheritanceLevel);
189 //kDebug(7021) << "INHERITANCE: Adding service" << (*itserv).service()->entryPath() << "to" << mimeTypeName << "mimeTypeInheritanceLevel=" << mimeTypeInheritanceLevel;
190 m_offerHash.addServiceOffer( mimeTypeName, offer );
193 } else {
194 kWarning(7012) << "parent mimetype not found:" << parent;
195 break;
200 void KBuildServiceFactory::postProcessServices()
202 // By doing all this here rather than in addEntry (and removing when replacing
203 // with local override), we only do it for the final applications.
205 // For every service...
206 KSycocaEntryDict::Iterator itserv = m_entryDict->begin();
207 const KSycocaEntryDict::Iterator endserv = m_entryDict->end();
208 for( ; itserv != endserv ; ++itserv ) {
210 KSycocaEntry::Ptr entry = *itserv;
211 KService::Ptr service = KService::Ptr::staticCast(entry);
213 if (!service->isDeleted()) {
214 const QString parent = service->parentApp();
215 if (!parent.isEmpty())
216 m_serviceGroupFactory->addNewChild(parent, KSycocaEntry::Ptr::staticCast(service));
219 const QString name = service->desktopEntryName();
220 m_nameDict->add(name, entry);
221 m_nameMemoryHash.insert(name, service);
223 const QString relName = service->entryPath();
224 //kDebug(7021) << "adding service" << service.data() << service->menuId() << "name=" << name << "relName=" << relName;
225 m_relNameDict->add(relName, entry);
226 m_relNameMemoryHash.insert(relName, service); // for KMimeAssociations
228 const QString menuId = service->menuId();
229 if (!menuId.isEmpty()) { // empty for services, non-empty for applications
230 m_menuIdDict->add(menuId, entry);
231 m_menuIdMemoryHash.insert(menuId, service); // for KMimeAssociations
234 populateServiceTypes();
237 void KBuildServiceFactory::populateServiceTypes()
239 // For every service...
240 KSycocaEntryDict::Iterator itserv = m_entryDict->begin();
241 const KSycocaEntryDict::Iterator endserv = m_entryDict->end();
242 for( ; itserv != endserv ; ++itserv ) {
244 KService::Ptr service = KService::Ptr::staticCast(*itserv);
245 QVector<KService::ServiceTypeAndPreference> serviceTypeList = service->_k_accessServiceTypes();
246 //bool hasAllAll = false;
247 //bool hasAllFiles = false;
249 // Add this service to all its servicetypes (and their parents) and to all its mimetypes
250 for (int i = 0; i < serviceTypeList.count() /*don't cache it, it can change during iteration!*/; ++i) {
251 const QString stName = serviceTypeList[i].serviceType;
252 // It could be a servicetype or a mimetype.
253 KServiceType::Ptr serviceType = KServiceType::serviceType(stName);
254 if (!serviceType) {
255 serviceType = KServiceType::Ptr::staticCast(m_mimeTypeFactory->findMimeTypeByName(stName, KMimeType::ResolveAliases));
257 // TODO. But maybe we should rename all/all to */*, to also support image/*?
258 // Not sure how to model all/allfiles then, though
259 // Also this kind of thing isn't in the XDG standards...
260 #if 0
261 if (!serviceType) {
262 if ( stName == QLatin1String( "all/all" ) ) {
263 hasAllAll = true;
264 continue;
265 } else if ( stName == QLatin1String( "all/allfiles" ) ) {
266 hasAllFiles = true;
267 continue;
270 #endif
272 if (!serviceType) {
273 kDebug(7021) << service->entryPath() << "specifies undefined mimetype/servicetype" << stName;
274 continue;
277 const int preference = serviceTypeList[i].preference;
278 const QString parent = serviceType->parentServiceType();
279 if (!parent.isEmpty())
280 serviceTypeList.append(KService::ServiceTypeAndPreference(preference, parent));
282 //kDebug(7021) << "Adding service" << service->entryPath() << "to" << serviceType->name() << "pref=" << preference;
283 m_offerHash.addServiceOffer(stName, KServiceOffer(service, preference, 0, service->allowAsDefault()) );
287 // Read user preferences (added/removed associations) and add/remove serviceoffers to m_offerHash
288 KMimeAssociations mimeAssociations(m_offerHash);
289 mimeAssociations.parseAllMimeAppsList();
291 // Now for each mimetype, collect services from parent mimetypes
292 collectInheritedServices();
294 // Now collect the offsets into the (future) offer list
295 // The loops look very much like the ones in saveOfferList obviously.
296 int offersOffset = 0;
297 const int offerEntrySize = sizeof( qint32 ) * 4; // four qint32s, see saveOfferList.
299 KSycocaEntryDict::const_iterator itstf = m_serviceTypeFactory->entryDict()->constBegin();
300 const KSycocaEntryDict::const_iterator endstf = m_serviceTypeFactory->entryDict()->constEnd();
301 for( ; itstf != endstf; ++itstf ) {
302 KServiceType::Ptr entry = KServiceType::Ptr::staticCast( *itstf );
303 const int numOffers = m_offerHash.offersFor(entry->name()).count();
304 if ( numOffers ) {
305 entry->setServiceOffersOffset( offersOffset );
306 offersOffset += offerEntrySize * numOffers;
309 KSycocaEntryDict::const_iterator itmtf = m_mimeTypeFactory->entryDict()->constBegin();
310 const KSycocaEntryDict::const_iterator endmtf = m_mimeTypeFactory->entryDict()->constEnd();
311 for( ; itmtf != endmtf; ++itmtf )
313 KMimeType::Ptr entry = KMimeType::Ptr::staticCast( *itmtf );
314 const int numOffers = m_offerHash.offersFor(entry->name()).count();
315 if ( numOffers ) {
316 entry->setServiceOffersOffset( offersOffset );
317 offersOffset += offerEntrySize * numOffers;
322 void KBuildServiceFactory::saveOfferList(QDataStream &str)
324 m_offerListOffset = str.device()->pos();
326 // For each entry in servicetypeFactory
327 KSycocaEntryDict::const_iterator itstf = m_serviceTypeFactory->entryDict()->constBegin();
328 const KSycocaEntryDict::const_iterator endstf = m_serviceTypeFactory->entryDict()->constEnd();
329 for( ; itstf != endstf; ++itstf ) {
330 // export associated services
331 const KServiceType::Ptr entry = KServiceType::Ptr::staticCast( *itstf );
332 Q_ASSERT( entry );
334 QList<KServiceOffer> offers = m_offerHash.offersFor(entry->name());
335 qStableSort( offers ); // by initial preference
337 for(QList<KServiceOffer>::const_iterator it2 = offers.constBegin();
338 it2 != offers.constEnd(); ++it2) {
339 //kDebug(7021) << "servicetype offers list:" << entry->name() << "->" << (*it2).service()->entryPath();
341 str << (qint32) entry->offset();
342 str << (qint32) (*it2).service()->offset();
343 str << (qint32) (*it2).preference();
344 str << (qint32) 0; // mimeTypeInheritanceLevel
345 // update offerEntrySize in populateServiceTypes if you add/remove something here
349 // For each entry in mimeTypeFactory
350 KSycocaEntryDict::const_iterator itmtf = m_mimeTypeFactory->entryDict()->constBegin();
351 const KSycocaEntryDict::const_iterator endmtf = m_mimeTypeFactory->entryDict()->constEnd();
352 for( ; itmtf != endmtf; ++itmtf ) {
353 // export associated services
354 const KMimeType::Ptr entry = KMimeType::Ptr::staticCast( *itmtf );
355 Q_ASSERT( entry );
356 QList<KServiceOffer> offers = m_offerHash.offersFor(entry->name());
357 qStableSort( offers ); // by initial preference
359 for(QList<KServiceOffer>::const_iterator it2 = offers.constBegin();
360 it2 != offers.constEnd(); ++it2) {
361 //kDebug(7021) << "mimetype offers list:" << entry->name() << "->" << (*it2).service()->entryPath() << "pref" << (*it2).preference();
362 Q_ASSERT((*it2).service()->offset() != 0);
363 str << (qint32) entry->offset();
364 str << (qint32) (*it2).service()->offset();
365 str << (qint32) (*it2).preference();
366 str << (qint32) (*it2).mimeTypeInheritanceLevel();
367 // update offerEntrySize in populateServiceTypes if you add/remove something here
371 str << (qint32) 0; // End of list marker (0)
374 void KBuildServiceFactory::addEntry(const KSycocaEntry::Ptr& newEntry)
376 Q_ASSERT(newEntry);
377 if (m_dupeDict.contains(newEntry))
378 return;
380 const KService::Ptr service = KService::Ptr::staticCast( newEntry );
381 m_dupeDict.insert(newEntry);
382 KSycocaFactory::addEntry(newEntry);