mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-23 18:32:49 +00:00
388 lines
16 KiB
C++
388 lines
16 KiB
C++
/* This file is part of the KDE libraries
|
|
* Copyright (C) 1999, 2007 David Faure <faure@kde.org>
|
|
* 1999 Waldo Bastian <bastian@kde.org>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License version 2 as published by the Free Software Foundation;
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public License
|
|
* along with this library; see the file COPYING.LIB. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
**/
|
|
|
|
#include "kbuildservicefactory.h"
|
|
#include "kbuildservicegroupfactory.h"
|
|
#include "kbuildmimetypefactory.h"
|
|
#include "kmimetyperepository_p.h"
|
|
#include "ksycoca.h"
|
|
#include "ksycocadict_p.h"
|
|
#include "ksycocaresourcelist.h"
|
|
#include "kdesktopfile.h"
|
|
|
|
#include <kglobal.h>
|
|
#include <kstandarddirs.h>
|
|
#include <klocale.h>
|
|
#include <kdebug.h>
|
|
#include <assert.h>
|
|
#include <kmimetypefactory.h>
|
|
|
|
KBuildServiceFactory::KBuildServiceFactory( KSycocaFactory *serviceTypeFactory,
|
|
KBuildMimeTypeFactory *mimeTypeFactory,
|
|
KBuildServiceGroupFactory *serviceGroupFactory ) :
|
|
KServiceFactory(),
|
|
m_nameMemoryHash(),
|
|
m_relNameMemoryHash(),
|
|
m_menuIdMemoryHash(),
|
|
m_dupeDict(),
|
|
m_serviceTypeFactory( serviceTypeFactory ),
|
|
m_mimeTypeFactory( mimeTypeFactory ),
|
|
m_serviceGroupFactory( serviceGroupFactory )
|
|
{
|
|
m_resourceList = new KSycocaResourceList();
|
|
// We directly care about services desktop files.
|
|
// All the application desktop files are parsed on demand from the vfolder menu code.
|
|
m_resourceList->add( "services", ".desktop" );
|
|
|
|
m_nameDict = new KSycocaDict();
|
|
m_relNameDict = new KSycocaDict();
|
|
m_menuIdDict = new KSycocaDict();
|
|
}
|
|
|
|
// return all service types for this factory
|
|
// i.e. first arguments to m_resourceList->add() above
|
|
QStringList KBuildServiceFactory::resourceTypes()
|
|
{
|
|
return QStringList() << "services";
|
|
}
|
|
|
|
KBuildServiceFactory::~KBuildServiceFactory()
|
|
{
|
|
delete m_resourceList;
|
|
}
|
|
|
|
KService::Ptr KBuildServiceFactory::findServiceByDesktopName(const QString &name)
|
|
{
|
|
return m_nameMemoryHash.value(name);
|
|
}
|
|
|
|
KService::Ptr KBuildServiceFactory::findServiceByDesktopPath(const QString &name)
|
|
{
|
|
return m_relNameMemoryHash.value(name);
|
|
}
|
|
|
|
KService::Ptr KBuildServiceFactory::findServiceByMenuId(const QString &menuId)
|
|
{
|
|
return m_menuIdMemoryHash.value(menuId);
|
|
}
|
|
|
|
KSycocaEntry* KBuildServiceFactory::createEntry( const QString& file, const char *resource ) const
|
|
{
|
|
QString name = file;
|
|
int pos = name.lastIndexOf('/');
|
|
if (pos != -1) {
|
|
name = name.mid(pos+1);
|
|
}
|
|
// Is it a .desktop file?
|
|
if (name.endsWith(QLatin1String(".desktop"))) {
|
|
KDesktopFile desktopFile(resource, file);
|
|
|
|
KService * serv = new KService(&desktopFile);
|
|
//kDebug(7021) << "Creating KService from" << file << "entryPath=" << serv->entryPath();
|
|
// Note that the menuId will be set by the vfolder_menu.cpp code just after
|
|
// createEntry returns.
|
|
|
|
if ( serv->isValid() && !serv->isDeleted() ) {
|
|
return serv;
|
|
} else {
|
|
if (!serv->isDeleted()) {
|
|
kWarning(7012) << "Invalid Service : " << file;
|
|
}
|
|
delete serv;
|
|
return 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void KBuildServiceFactory::saveHeader(QDataStream &str)
|
|
{
|
|
KSycocaFactory::saveHeader(str);
|
|
|
|
str << (qint32) m_nameDictOffset;
|
|
str << (qint32) m_relNameDictOffset;
|
|
str << (qint32) m_offerListOffset;
|
|
str << (qint32) m_menuIdDictOffset;
|
|
}
|
|
|
|
void KBuildServiceFactory::save(QDataStream &str)
|
|
{
|
|
KSycocaFactory::save(str);
|
|
|
|
m_nameDictOffset = str.device()->pos();
|
|
m_nameDict->save(str);
|
|
|
|
m_relNameDictOffset = str.device()->pos();
|
|
m_relNameDict->save(str);
|
|
|
|
saveOfferList(str);
|
|
|
|
m_menuIdDictOffset = str.device()->pos();
|
|
m_menuIdDict->save(str);
|
|
|
|
int endOfFactoryData = str.device()->pos();
|
|
|
|
// Update header (pass #3)
|
|
saveHeader(str);
|
|
|
|
// Seek to end.
|
|
str.device()->seek(endOfFactoryData);
|
|
}
|
|
|
|
void KBuildServiceFactory::collectInheritedServices()
|
|
{
|
|
// For each mimetype, go up the parent-mimetype chains and collect offers.
|
|
// For "removed associations" to work, we can't just grab everything from all parents.
|
|
// We need to process parents before children, hence the recursive call in
|
|
// collectInheritedServices(mime) and the QSet to process a given parent only once.
|
|
QSet<QString> visitedMimes;
|
|
const QStringList allMimeTypes = m_mimeTypeFactory->allMimeTypes();
|
|
Q_FOREACH(const QString& mimeType, allMimeTypes) {
|
|
collectInheritedServices(mimeType, visitedMimes);
|
|
}
|
|
// TODO do the same for all/all and all/allfiles, if (!KServiceTypeProfile::configurationMode())
|
|
}
|
|
|
|
void KBuildServiceFactory::collectInheritedServices(const QString& mimeTypeName, QSet<QString>& visitedMimes)
|
|
{
|
|
if (visitedMimes.contains(mimeTypeName))
|
|
return;
|
|
visitedMimes.insert(mimeTypeName);
|
|
|
|
// With multiple inheritance, the "mimeTypeInheritanceLevel" isn't exactly
|
|
// correct (it should only be increased when going up a level, not when iterating
|
|
// through the multiple parents at a given level). I don't think we care, though.
|
|
int mimeTypeInheritanceLevel = 0;
|
|
|
|
Q_FOREACH(const QString& parentMimeType, KMimeTypeRepository::self()->parents(mimeTypeName)) {
|
|
|
|
collectInheritedServices(parentMimeType, visitedMimes);
|
|
|
|
++mimeTypeInheritanceLevel;
|
|
const QList<KServiceOffer>& offers = m_offerHash.offersFor(parentMimeType);
|
|
foreach (const KServiceOffer itserv, offers ) {
|
|
if (!m_offerHash.hasRemovedOffer(mimeTypeName, itserv.service())) {
|
|
KServiceOffer offer(itserv);
|
|
offer.setMimeTypeInheritanceLevel(mimeTypeInheritanceLevel);
|
|
//kDebug(7021) << "INHERITANCE: Adding service" << itserv.service()->entryPath() << "to" << mimeTypeName << "mimeTypeInheritanceLevel=" << mimeTypeInheritanceLevel;
|
|
m_offerHash.addServiceOffer( mimeTypeName, offer );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void KBuildServiceFactory::postProcessServices()
|
|
{
|
|
// By doing all this here rather than in addEntry (and removing when replacing
|
|
// with local override), we only do it for the final applications.
|
|
|
|
// For every service...
|
|
KSycocaEntryDict::Iterator itserv = m_entryDict->begin();
|
|
const KSycocaEntryDict::Iterator endserv = m_entryDict->end();
|
|
for( ; itserv != endserv ; ++itserv ) {
|
|
|
|
KSycocaEntry::Ptr entry = *itserv;
|
|
KService::Ptr service = KService::Ptr::staticCast(entry);
|
|
|
|
if (!service->isDeleted()) {
|
|
const QString parent = service->parentApp();
|
|
if (!parent.isEmpty())
|
|
m_serviceGroupFactory->addNewChild(parent, KSycocaEntry::Ptr::staticCast(service));
|
|
}
|
|
|
|
const QString name = service->desktopEntryName();
|
|
m_nameDict->add(name, entry);
|
|
m_nameMemoryHash.insert(name, service);
|
|
|
|
const QString relName = service->entryPath();
|
|
//kDebug(7021) << "adding service" << service.data() << service->type() << "menuId=" << service->menuId() << "name=" << name << "relName=" << relName;
|
|
m_relNameDict->add(relName, entry);
|
|
m_relNameMemoryHash.insert(relName, service); // for KMimeAssociations
|
|
|
|
const QString menuId = service->menuId();
|
|
if (!menuId.isEmpty()) { // empty for services, non-empty for applications
|
|
m_menuIdDict->add(menuId, entry);
|
|
m_menuIdMemoryHash.insert(menuId, service); // for KMimeAssociations
|
|
}
|
|
}
|
|
populateServiceTypes();
|
|
}
|
|
|
|
void KBuildServiceFactory::populateServiceTypes()
|
|
{
|
|
// For every service...
|
|
KSycocaEntryDict::Iterator itserv = m_entryDict->begin();
|
|
const KSycocaEntryDict::Iterator endserv = m_entryDict->end();
|
|
for( ; itserv != endserv ; ++itserv ) {
|
|
|
|
KService::Ptr service = KService::Ptr::staticCast(*itserv);
|
|
QVector<KService::ServiceTypeAndPreference> serviceTypeList = service->_k_accessServiceTypes();
|
|
//bool hasAllAll = false;
|
|
//bool hasAllFiles = false;
|
|
|
|
// Add this service to all its servicetypes (and their parents) and to all its mimetypes
|
|
for (int i = 0; i < serviceTypeList.count() /*don't cache it, it can change during iteration!*/; ++i) {
|
|
const QString stName = serviceTypeList[i].serviceType;
|
|
// It could be a servicetype or a mimetype.
|
|
KServiceType::Ptr serviceType = KServiceType::serviceType(stName);
|
|
if (serviceType) {
|
|
const int preference = serviceTypeList[i].preference;
|
|
const QString parent = serviceType->parentServiceType();
|
|
if (!parent.isEmpty())
|
|
serviceTypeList.append(KService::ServiceTypeAndPreference(preference, parent));
|
|
|
|
//kDebug(7021) << "Adding service" << service->entryPath() << "to" << serviceType->name() << "pref=" << preference;
|
|
m_offerHash.addServiceOffer(stName, KServiceOffer(service, preference, 0, service->allowAsDefault()) );
|
|
} else {
|
|
KServiceOffer offer(service, serviceTypeList[i].preference, 0, service->allowAsDefault());
|
|
KMimeType::Ptr mime = KMimeType::mimeType(stName, KMimeType::ResolveAliases);
|
|
if (!mime) {
|
|
if (stName.startsWith(QLatin1String("x-scheme-handler/"))) {
|
|
// Create those on demand
|
|
m_mimeTypeFactory->createFakeMimeType(stName);
|
|
m_offerHash.addServiceOffer(stName, offer );
|
|
} else {
|
|
kDebug(7021) << service->entryPath() << "specifies undefined mimetype/servicetype" << stName;
|
|
// technically we could call addServiceOffer here, 'mime' isn't used. But it
|
|
// would be useless, since the loops for writing out the offers iterate
|
|
// over all known servicetypes and mimetypes. Unknown -> never written out.
|
|
continue;
|
|
}
|
|
} else {
|
|
bool shouldAdd = true;
|
|
foreach (const QString &otherType, service->serviceTypes()) {
|
|
// Skip derived types if the base class is listed (#321706)
|
|
if (stName != otherType && mime->is(otherType)) {
|
|
// But don't skip aliases (they got resolved into mime->name() already, but don't let two aliases cancel out)
|
|
if (KMimeTypeRepository::self()->canonicalName(otherType) != mime->name()) {
|
|
//kDebug() << "Skipping" << mime->name() << "because of" << otherType << "(canonical" << KMimeTypeRepository::self()->canonicalName(otherType) << ") while parsing" << service->entryPath();
|
|
shouldAdd = false;
|
|
}
|
|
}
|
|
}
|
|
if (shouldAdd) {
|
|
//kDebug(7021) << "Adding service" << service->entryPath() << "to" << mime->name();
|
|
m_offerHash.addServiceOffer(mime->name(), offer); // mime->name so that we resolve aliases
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read user preferences (added/removed associations) and add/remove serviceoffers to m_offerHash
|
|
KMimeAssociations mimeAssociations(m_offerHash);
|
|
mimeAssociations.parseAllMimeAppsList();
|
|
|
|
// Now for each mimetype, collect services from parent mimetypes
|
|
collectInheritedServices();
|
|
|
|
// Now collect the offsets into the (future) offer list
|
|
// The loops look very much like the ones in saveOfferList obviously.
|
|
int offersOffset = 0;
|
|
const int offerEntrySize = sizeof( qint32 ) * 4; // four qint32s, see saveOfferList.
|
|
|
|
// TODO: idea: we could iterate over m_offerHash, and look up the servicetype or mimetype.
|
|
// Would that be faster than iterating over all servicetypes and mimetypes?
|
|
|
|
KSycocaEntryDict::const_iterator itstf = m_serviceTypeFactory->entryDict()->constBegin();
|
|
const KSycocaEntryDict::const_iterator endstf = m_serviceTypeFactory->entryDict()->constEnd();
|
|
for( ; itstf != endstf; ++itstf ) {
|
|
KServiceType::Ptr entry = KServiceType::Ptr::staticCast( *itstf );
|
|
const int numOffers = m_offerHash.offersFor(entry->name()).count();
|
|
if ( numOffers ) {
|
|
entry->setServiceOffersOffset( offersOffset );
|
|
offersOffset += offerEntrySize * numOffers;
|
|
}
|
|
}
|
|
KSycocaEntryDict::const_iterator itmtf = m_mimeTypeFactory->entryDict()->constBegin();
|
|
const KSycocaEntryDict::const_iterator endmtf = m_mimeTypeFactory->entryDict()->constEnd();
|
|
for( ; itmtf != endmtf; ++itmtf )
|
|
{
|
|
KMimeTypeFactory::MimeTypeEntry::Ptr entry = KMimeTypeFactory::MimeTypeEntry::Ptr::staticCast( *itmtf );
|
|
const int numOffers = m_offerHash.offersFor(entry->name()).count();
|
|
if ( numOffers ) {
|
|
//kDebug() << entry->name() << "offset=" << offersOffset;
|
|
entry->setServiceOffersOffset( offersOffset );
|
|
offersOffset += offerEntrySize * numOffers;
|
|
}
|
|
}
|
|
}
|
|
|
|
void KBuildServiceFactory::saveOfferList(QDataStream &str)
|
|
{
|
|
m_offerListOffset = str.device()->pos();
|
|
|
|
// For each entry in servicetypeFactory
|
|
KSycocaEntryDict::const_iterator itstf = m_serviceTypeFactory->entryDict()->constBegin();
|
|
const KSycocaEntryDict::const_iterator endstf = m_serviceTypeFactory->entryDict()->constEnd();
|
|
for( ; itstf != endstf; ++itstf ) {
|
|
// export associated services
|
|
const KServiceType::Ptr entry = KServiceType::Ptr::staticCast( *itstf );
|
|
Q_ASSERT( entry );
|
|
|
|
QList<KServiceOffer> offers = m_offerHash.offersFor(entry->name());
|
|
qStableSort( offers ); // by initial preference
|
|
|
|
for(QList<KServiceOffer>::const_iterator it2 = offers.constBegin();
|
|
it2 != offers.constEnd(); ++it2) {
|
|
//kDebug(7021) << "servicetype offers list:" << entry->name() << "->" << (*it2).service()->entryPath();
|
|
|
|
str << (qint32) entry->offset();
|
|
str << (qint32) (*it2).service()->offset();
|
|
str << (qint32) (*it2).preference();
|
|
str << (qint32) 0; // mimeTypeInheritanceLevel
|
|
// update offerEntrySize in populateServiceTypes if you add/remove something here
|
|
}
|
|
}
|
|
|
|
// For each entry in mimeTypeFactory
|
|
KSycocaEntryDict::const_iterator itmtf = m_mimeTypeFactory->entryDict()->constBegin();
|
|
const KSycocaEntryDict::const_iterator endmtf = m_mimeTypeFactory->entryDict()->constEnd();
|
|
for( ; itmtf != endmtf; ++itmtf ) {
|
|
// export associated services
|
|
const KMimeTypeFactory::MimeTypeEntry::Ptr entry = KMimeTypeFactory::MimeTypeEntry::Ptr::staticCast( *itmtf );
|
|
Q_ASSERT( entry );
|
|
QList<KServiceOffer> offers = m_offerHash.offersFor(entry->name());
|
|
qStableSort( offers ); // by initial preference
|
|
|
|
for(QList<KServiceOffer>::const_iterator it2 = offers.constBegin();
|
|
it2 != offers.constEnd(); ++it2) {
|
|
//kDebug(7021) << "mimetype offers list:" << entry->name() << "->" << (*it2).service()->entryPath() << "pref" << (*it2).preference();
|
|
Q_ASSERT((*it2).service()->offset() != 0);
|
|
str << (qint32) entry->offset();
|
|
str << (qint32) (*it2).service()->offset();
|
|
str << (qint32) (*it2).preference();
|
|
str << (qint32) (*it2).mimeTypeInheritanceLevel();
|
|
// update offerEntrySize in populateServiceTypes if you add/remove something here
|
|
}
|
|
}
|
|
|
|
str << (qint32) 0; // End of list marker (0)
|
|
}
|
|
|
|
void KBuildServiceFactory::addEntry(const KSycocaEntry::Ptr& newEntry)
|
|
{
|
|
Q_ASSERT(newEntry);
|
|
if (m_dupeDict.contains(newEntry))
|
|
return;
|
|
|
|
const KService::Ptr service = KService::Ptr::staticCast( newEntry );
|
|
m_dupeDict.insert(newEntry);
|
|
KSycocaFactory::addEntry(newEntry);
|
|
}
|