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.
20 #include "kbuildservicefactory.h"
21 #include "kbuildservicegroupfactory.h"
22 #include "kbuildmimetypefactory.h"
24 #include "ksycocadict.h"
25 #include "kresourcelist.h"
26 #include "kdesktopfile.h"
29 #include <kstandarddirs.h>
33 #include <kmimetypefactory.h>
35 KBuildServiceFactory::KBuildServiceFactory( KSycocaFactory
*serviceTypeFactory
,
36 KBuildMimeTypeFactory
*mimeTypeFactory
,
37 KBuildServiceGroupFactory
*serviceGroupFactory
) :
40 m_relNameMemoryHash(),
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
87 int pos
= name
.lastIndexOf('/');
89 name
= name
.mid(pos
+1);
91 // Is it a .desktop file?
92 if (!name
.endsWith(".desktop"))
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() ) {
105 if (!serv
->isDeleted())
106 kWarning(7012) << "Invalid Service : " << file
;
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
);
134 m_menuIdDictOffset
= str
.device()->pos();
135 m_menuIdDict
->save(str
);
137 int endOfFactoryData
= str
.device()->pos();
139 // Update header (pass #3)
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
))
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
);
194 kWarning(7012) << "parent mimetype not found:" << parent
;
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
);
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...
262 if ( stName
== QLatin1String( "all/all" ) ) {
265 } else if ( stName
== QLatin1String( "all/allfiles" ) ) {
273 kDebug(7021) << service
->entryPath() << "specifies undefined mimetype/servicetype" << stName
;
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();
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();
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
);
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
);
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
)
377 if (m_dupeDict
.contains(newEntry
))
380 const KService::Ptr service
= KService::Ptr::staticCast( newEntry
);
381 m_dupeDict
.insert(newEntry
);
382 KSycocaFactory::addEntry(newEntry
);