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 | |
36 | KBuildServiceFactory::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 |
60 | QStringList KBuildServiceFactory::resourceTypes() |
61 | { |
62 | return QStringList() << "services" ; |
63 | } |
64 | |
65 | KBuildServiceFactory::~KBuildServiceFactory() |
66 | { |
67 | delete m_resourceList; |
68 | } |
69 | |
70 | KService::Ptr KBuildServiceFactory::findServiceByDesktopName(const QString &name) |
71 | { |
72 | return m_nameMemoryHash.value(name); |
73 | } |
74 | |
75 | KService::Ptr KBuildServiceFactory::findServiceByDesktopPath(const QString &name) |
76 | { |
77 | return m_relNameMemoryHash.value(name); |
78 | } |
79 | |
80 | KService::Ptr KBuildServiceFactory::(const QString &) |
81 | { |
82 | return m_menuIdMemoryHash.value(menuId); |
83 | } |
84 | |
85 | KSycocaEntry* 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 | |
114 | void KBuildServiceFactory::(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 | |
124 | void 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 | |
148 | void 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 | |
162 | void 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 | |
192 | void 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 = 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 | |
229 | void 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 | |
329 | void 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 | |
381 | void 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 | |