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>
4 *
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;
8 *
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.
13 *
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 **/
19
20#include "kbuildservicefactory.h"
21#include "kbuildservicegroupfactory.h"
22#include "kbuildmimetypefactory.h"
23#include "kmimetyperepository_p.h"
24#include "ksycoca.h"
25#include "ksycocadict_p.h"
26#include "ksycocaresourcelist.h"
27#include "kdesktopfile.h"
28
29#include <kglobal.h>
30#include <kstandarddirs.h>
31#include <klocale.h>
32#include <kdebug.h>
33#include <assert.h>
34#include <kmimetypefactory.h>
35
36KBuildServiceFactory::KBuildServiceFactory( KSycocaFactory *serviceTypeFactory,
37 KBuildMimeTypeFactory *mimeTypeFactory,
38 KBuildServiceGroupFactory *serviceGroupFactory ) :
39 KServiceFactory(),
40 m_nameMemoryHash(),
41 m_relNameMemoryHash(),
42 m_menuIdMemoryHash(),
43 m_dupeDict(),
44 m_serviceTypeFactory( serviceTypeFactory ),
45 m_mimeTypeFactory( mimeTypeFactory ),
46 m_serviceGroupFactory( serviceGroupFactory )
47{
48 m_resourceList = new KSycocaResourceList();
49 // We directly care about services desktop files.
50 // All the application desktop files are parsed on demand from the vfolder menu code.
51 m_resourceList->add( "services", "*.desktop" );
52
53 m_nameDict = new KSycocaDict();
54 m_relNameDict = new KSycocaDict();
55 m_menuIdDict = new KSycocaDict();
56}
57
58// return all service types for this factory
59// i.e. first arguments to m_resourceList->add() above
60QStringList KBuildServiceFactory::resourceTypes()
61{
62 return QStringList() << "services";
63}
64
65KBuildServiceFactory::~KBuildServiceFactory()
66{
67 delete m_resourceList;
68}
69
70KService::Ptr KBuildServiceFactory::findServiceByDesktopName(const QString &name)
71{
72 return m_nameMemoryHash.value(name);
73}
74
75KService::Ptr KBuildServiceFactory::findServiceByDesktopPath(const QString &name)
76{
77 return m_relNameMemoryHash.value(name);
78}
79
80KService::Ptr KBuildServiceFactory::findServiceByMenuId(const QString &menuId)
81{
82 return m_menuIdMemoryHash.value(menuId);
83}
84
85KSycocaEntry* KBuildServiceFactory::createEntry( const QString& file, const char *resource ) const
86{
87 QString name = file;
88 int pos = name.lastIndexOf('/');
89 if (pos != -1) {
90 name = name.mid(pos+1);
91 }
92 // Is it a .desktop file?
93 if (name.endsWith(QLatin1String(".desktop"))) {
94 KDesktopFile desktopFile(resource, file);
95
96 KService * serv = new KService(&desktopFile);
97 //kDebug(7021) << "Creating KService from" << file << "entryPath=" << serv->entryPath();
98 // Note that the menuId will be set by the vfolder_menu.cpp code just after
99 // createEntry returns.
100
101 if ( serv->isValid() && !serv->isDeleted() ) {
102 return serv;
103 } else {
104 if (!serv->isDeleted()) {
105 kWarning(7012) << "Invalid Service : " << file;
106 }
107 delete serv;
108 return 0;
109 }
110 } // TODO else if a Windows application, new KService(name, exec, icon)
111 return 0;
112}
113
114void KBuildServiceFactory::saveHeader(QDataStream &str)
115{
116 KSycocaFactory::saveHeader(str);
117
118 str << (qint32) m_nameDictOffset;
119 str << (qint32) m_relNameDictOffset;
120 str << (qint32) m_offerListOffset;
121 str << (qint32) m_menuIdDictOffset;
122}
123
124void KBuildServiceFactory::save(QDataStream &str)
125{
126 KSycocaFactory::save(str);
127
128 m_nameDictOffset = str.device()->pos();
129 m_nameDict->save(str);
130
131 m_relNameDictOffset = str.device()->pos();
132 m_relNameDict->save(str);
133
134 saveOfferList(str);
135
136 m_menuIdDictOffset = str.device()->pos();
137 m_menuIdDict->save(str);
138
139 int endOfFactoryData = str.device()->pos();
140
141 // Update header (pass #3)
142 saveHeader(str);
143
144 // Seek to end.
145 str.device()->seek(endOfFactoryData);
146}
147
148void KBuildServiceFactory::collectInheritedServices()
149{
150 // For each mimetype, go up the parent-mimetype chains and collect offers.
151 // For "removed associations" to work, we can't just grab everything from all parents.
152 // We need to process parents before children, hence the recursive call in
153 // collectInheritedServices(mime) and the QSet to process a given parent only once.
154 QSet<QString> visitedMimes;
155 const QStringList allMimeTypes = m_mimeTypeFactory->allMimeTypes();
156 Q_FOREACH(const QString& mimeType, allMimeTypes) {
157 collectInheritedServices(mimeType, visitedMimes);
158 }
159 // TODO do the same for all/all and all/allfiles, if (!KServiceTypeProfile::configurationMode())
160}
161
162void KBuildServiceFactory::collectInheritedServices(const QString& mimeTypeName, QSet<QString>& visitedMimes)
163{
164 if (visitedMimes.contains(mimeTypeName))
165 return;
166 visitedMimes.insert(mimeTypeName);
167
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;
172
173 Q_FOREACH(const QString& parentMimeType, KMimeTypeRepository::self()->parents(mimeTypeName)) {
174
175 collectInheritedServices(parentMimeType, visitedMimes);
176
177 ++mimeTypeInheritanceLevel;
178 const QList<KServiceOffer>& offers = m_offerHash.offersFor(parentMimeType);
179 QList<KServiceOffer>::const_iterator itserv = offers.begin();
180 const QList<KServiceOffer>::const_iterator endserv = offers.end();
181 for ( ; itserv != endserv; ++itserv ) {
182 if (!m_offerHash.hasRemovedOffer(mimeTypeName, (*itserv).service())) {
183 KServiceOffer offer(*itserv);
184 offer.setMimeTypeInheritanceLevel(mimeTypeInheritanceLevel);
185 //kDebug(7021) << "INHERITANCE: Adding service" << (*itserv).service()->entryPath() << "to" << mimeTypeName << "mimeTypeInheritanceLevel=" << mimeTypeInheritanceLevel;
186 m_offerHash.addServiceOffer( mimeTypeName, offer );
187 }
188 }
189 }
190}
191
192void KBuildServiceFactory::postProcessServices()
193{
194 // By doing all this here rather than in addEntry (and removing when replacing
195 // with local override), we only do it for the final applications.
196
197 // For every service...
198 KSycocaEntryDict::Iterator itserv = m_entryDict->begin();
199 const KSycocaEntryDict::Iterator endserv = m_entryDict->end();
200 for( ; itserv != endserv ; ++itserv ) {
201
202 KSycocaEntry::Ptr entry = *itserv;
203 KService::Ptr service = KService::Ptr::staticCast(entry);
204
205 if (!service->isDeleted()) {
206 const QString parent = service->parentApp();
207 if (!parent.isEmpty())
208 m_serviceGroupFactory->addNewChild(parent, KSycocaEntry::Ptr::staticCast(service));
209 }
210
211 const QString name = service->desktopEntryName();
212 m_nameDict->add(name, entry);
213 m_nameMemoryHash.insert(name, service);
214
215 const QString relName = service->entryPath();
216 //kDebug(7021) << "adding service" << service.data() << service->type() << "menuId=" << service->menuId() << "name=" << name << "relName=" << relName;
217 m_relNameDict->add(relName, entry);
218 m_relNameMemoryHash.insert(relName, service); // for KMimeAssociations
219
220 const QString menuId = service->menuId();
221 if (!menuId.isEmpty()) { // empty for services, non-empty for applications
222 m_menuIdDict->add(menuId, entry);
223 m_menuIdMemoryHash.insert(menuId, service); // for KMimeAssociations
224 }
225 }
226 populateServiceTypes();
227}
228
229void KBuildServiceFactory::populateServiceTypes()
230{
231 // For every service...
232 KSycocaEntryDict::Iterator itserv = m_entryDict->begin();
233 const KSycocaEntryDict::Iterator endserv = m_entryDict->end();
234 for( ; itserv != endserv ; ++itserv ) {
235
236 KService::Ptr service = KService::Ptr::staticCast(*itserv);
237 QVector<KService::ServiceTypeAndPreference> serviceTypeList = service->_k_accessServiceTypes();
238 //bool hasAllAll = false;
239 //bool hasAllFiles = false;
240
241 // Add this service to all its servicetypes (and their parents) and to all its mimetypes
242 for (int i = 0; i < serviceTypeList.count() /*don't cache it, it can change during iteration!*/; ++i) {
243 const QString stName = serviceTypeList[i].serviceType;
244 // It could be a servicetype or a mimetype.
245 KServiceType::Ptr serviceType = KServiceType::serviceType(stName);
246 if (serviceType) {
247 const int preference = serviceTypeList[i].preference;
248 const QString parent = serviceType->parentServiceType();
249 if (!parent.isEmpty())
250 serviceTypeList.append(KService::ServiceTypeAndPreference(preference, parent));
251
252 //kDebug(7021) << "Adding service" << service->entryPath() << "to" << serviceType->name() << "pref=" << preference;
253 m_offerHash.addServiceOffer(stName, KServiceOffer(service, preference, 0, service->allowAsDefault()) );
254 } else {
255 KServiceOffer offer(service, serviceTypeList[i].preference, 0, service->allowAsDefault());
256 KMimeType::Ptr mime = KMimeType::mimeType(stName, KMimeType::ResolveAliases);
257 if (!mime) {
258 if (stName.startsWith(QLatin1String("x-scheme-handler/"))) {
259 // Create those on demand
260 m_mimeTypeFactory->createFakeMimeType(stName);
261 m_offerHash.addServiceOffer(stName, offer );
262 } else {
263 kDebug(7021) << service->entryPath() << "specifies undefined mimetype/servicetype" << stName;
264 // technically we could call addServiceOffer here, 'mime' isn't used. But it
265 // would be useless, since the loops for writing out the offers iterate
266 // over all known servicetypes and mimetypes. Unknown -> never written out.
267 continue;
268 }
269 } else {
270 bool shouldAdd = true;
271 foreach (const QString &otherType, service->serviceTypes()) {
272 // Skip derived types if the base class is listed (#321706)
273 if (stName != otherType && mime->is(otherType)) {
274 // But don't skip aliases (they got resolved into mime->name() already, but don't let two aliases cancel out)
275 if (KMimeTypeRepository::self()->canonicalName(otherType) != mime->name()) {
276 //kDebug() << "Skipping" << mime->name() << "because of" << otherType << "(canonical" << KMimeTypeRepository::self()->canonicalName(otherType) << ") while parsing" << service->entryPath();
277 shouldAdd = false;
278 }
279 }
280 }
281 if (shouldAdd) {
282 //kDebug(7021) << "Adding service" << service->entryPath() << "to" << mime->name();
283 m_offerHash.addServiceOffer(mime->name(), offer); // mime->name so that we resolve aliases
284 }
285 }
286 }
287 }
288 }
289
290 // Read user preferences (added/removed associations) and add/remove serviceoffers to m_offerHash
291 KMimeAssociations mimeAssociations(m_offerHash);
292 mimeAssociations.parseAllMimeAppsList();
293
294 // Now for each mimetype, collect services from parent mimetypes
295 collectInheritedServices();
296
297 // Now collect the offsets into the (future) offer list
298 // The loops look very much like the ones in saveOfferList obviously.
299 int offersOffset = 0;
300 const int offerEntrySize = sizeof( qint32 ) * 4; // four qint32s, see saveOfferList.
301
302 // TODO: idea: we could iterate over m_offerHash, and look up the servicetype or mimetype.
303 // Would that be faster than iterating over all servicetypes and mimetypes?
304
305 KSycocaEntryDict::const_iterator itstf = m_serviceTypeFactory->entryDict()->constBegin();
306 const KSycocaEntryDict::const_iterator endstf = m_serviceTypeFactory->entryDict()->constEnd();
307 for( ; itstf != endstf; ++itstf ) {
308 KServiceType::Ptr entry = KServiceType::Ptr::staticCast( *itstf );
309 const int numOffers = m_offerHash.offersFor(entry->name()).count();
310 if ( numOffers ) {
311 entry->setServiceOffersOffset( offersOffset );
312 offersOffset += offerEntrySize * numOffers;
313 }
314 }
315 KSycocaEntryDict::const_iterator itmtf = m_mimeTypeFactory->entryDict()->constBegin();
316 const KSycocaEntryDict::const_iterator endmtf = m_mimeTypeFactory->entryDict()->constEnd();
317 for( ; itmtf != endmtf; ++itmtf )
318 {
319 KMimeTypeFactory::MimeTypeEntry::Ptr entry = KMimeTypeFactory::MimeTypeEntry::Ptr::staticCast( *itmtf );
320 const int numOffers = m_offerHash.offersFor(entry->name()).count();
321 if ( numOffers ) {
322 //kDebug() << entry->name() << "offset=" << offersOffset;
323 entry->setServiceOffersOffset( offersOffset );
324 offersOffset += offerEntrySize * numOffers;
325 }
326 }
327}
328
329void KBuildServiceFactory::saveOfferList(QDataStream &str)
330{
331 m_offerListOffset = str.device()->pos();
332
333 // For each entry in servicetypeFactory
334 KSycocaEntryDict::const_iterator itstf = m_serviceTypeFactory->entryDict()->constBegin();
335 const KSycocaEntryDict::const_iterator endstf = m_serviceTypeFactory->entryDict()->constEnd();
336 for( ; itstf != endstf; ++itstf ) {
337 // export associated services
338 const KServiceType::Ptr entry = KServiceType::Ptr::staticCast( *itstf );
339 Q_ASSERT( entry );
340
341 QList<KServiceOffer> offers = m_offerHash.offersFor(entry->name());
342 qStableSort( offers ); // by initial preference
343
344 for(QList<KServiceOffer>::const_iterator it2 = offers.constBegin();
345 it2 != offers.constEnd(); ++it2) {
346 //kDebug(7021) << "servicetype offers list:" << entry->name() << "->" << (*it2).service()->entryPath();
347
348 str << (qint32) entry->offset();
349 str << (qint32) (*it2).service()->offset();
350 str << (qint32) (*it2).preference();
351 str << (qint32) 0; // mimeTypeInheritanceLevel
352 // update offerEntrySize in populateServiceTypes if you add/remove something here
353 }
354 }
355
356 // For each entry in mimeTypeFactory
357 KSycocaEntryDict::const_iterator itmtf = m_mimeTypeFactory->entryDict()->constBegin();
358 const KSycocaEntryDict::const_iterator endmtf = m_mimeTypeFactory->entryDict()->constEnd();
359 for( ; itmtf != endmtf; ++itmtf ) {
360 // export associated services
361 const KMimeTypeFactory::MimeTypeEntry::Ptr entry = KMimeTypeFactory::MimeTypeEntry::Ptr::staticCast( *itmtf );
362 Q_ASSERT( entry );
363 QList<KServiceOffer> offers = m_offerHash.offersFor(entry->name());
364 qStableSort( offers ); // by initial preference
365
366 for(QList<KServiceOffer>::const_iterator it2 = offers.constBegin();
367 it2 != offers.constEnd(); ++it2) {
368 //kDebug(7021) << "mimetype offers list:" << entry->name() << "->" << (*it2).service()->entryPath() << "pref" << (*it2).preference();
369 Q_ASSERT((*it2).service()->offset() != 0);
370 str << (qint32) entry->offset();
371 str << (qint32) (*it2).service()->offset();
372 str << (qint32) (*it2).preference();
373 str << (qint32) (*it2).mimeTypeInheritanceLevel();
374 // update offerEntrySize in populateServiceTypes if you add/remove something here
375 }
376 }
377
378 str << (qint32) 0; // End of list marker (0)
379}
380
381void KBuildServiceFactory::addEntry(const KSycocaEntry::Ptr& newEntry)
382{
383 Q_ASSERT(newEntry);
384 if (m_dupeDict.contains(newEntry))
385 return;
386
387 const KService::Ptr service = KService::Ptr::staticCast( newEntry );
388 m_dupeDict.insert(newEntry);
389 KSycocaFactory::addEntry(newEntry);
390}
391