Advance Wayland and KDE package bring-up

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -0,0 +1,150 @@
include(CheckSymbolExists)
include(CheckFunctionExists)
check_function_exists(mmap HAVE_MMAP)
check_symbol_exists(posix_madvise "sys/mman.h" HAVE_MADVISE)
configure_file(config-ksycoca.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-ksycoca.h )
add_library(KF6Service)
add_library(KF6::Service ALIAS KF6Service)
set_target_properties(KF6Service PROPERTIES
VERSION ${KSERVICE_VERSION}
SOVERSION ${KSERVICE_SOVERSION}
EXPORT_NAME Service
)
target_sources(KF6Service PRIVATE
services/kapplicationtrader.cpp
services/kmimetypefactory.cpp
services/kservice.cpp
services/kserviceaction.cpp
services/kservicefactory.cpp
services/kservicegroup.cpp
services/kservicegroupfactory.cpp
services/kserviceoffer.cpp
sycoca/ksycoca.cpp
sycoca/ksycocadevices.cpp
sycoca/ksycocadict.cpp
sycoca/ksycocaentry.cpp
sycoca/ksycocafactory.cpp
sycoca/kmemfile.cpp
sycoca/kbuildmimetypefactory.cpp
sycoca/kbuildservicefactory.cpp
sycoca/kbuildservicegroupfactory.cpp
sycoca/kbuildsycoca.cpp
sycoca/kctimefactory.cpp
sycoca/kmimeassociations.cpp
sycoca/vfolder_menu.cpp
)
ecm_qt_declare_logging_category(KF6Service
HEADER servicesdebug.h
IDENTIFIER SERVICES
CATEGORY_NAME kf.service.services
OLD_CATEGORY_NAMES kf5.kservice.services
EXPORT KSERVICE
DESCRIPTION "service (kservice lib)"
)
ecm_qt_declare_logging_category(KF6Service
HEADER sycocadebug.h
IDENTIFIER SYCOCA
CATEGORY_NAME kf.service.sycoca
OLD_CATEGORY_NAMES kf5.kservice.sycoca
EXPORT KSERVICE
DESCRIPTION "sycoca (kservice)"
)
ecm_generate_export_header(KF6Service
BASE_NAME KService
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
USE_VERSION_HEADER
DEPRECATED_BASE_VERSION 0
DEPRECATION_VERSIONS 5.90
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
)
set(kservice_includes
${CMAKE_CURRENT_BINARY_DIR}/.. # Since we publicly include kservice_version.h
${CMAKE_CURRENT_SOURCE_DIR}/services
${CMAKE_CURRENT_SOURCE_DIR}/sycoca
)
target_include_directories(KF6Service
PUBLIC
"$<BUILD_INTERFACE:${kservice_includes}>"
INTERFACE
"$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KService>"
)
target_link_libraries(KF6Service
PUBLIC
Qt6::Core
PRIVATE
Qt6::Xml # (for vfolder menu) QDomDocument
KF6::ConfigCore # KConfig and friends
KF6::CoreAddons # KShell
)
ecm_generate_headers(KService_HEADERS
HEADER_NAMES
KSycoca
KSycocaEntry
KSycocaType
RELATIVE sycoca REQUIRED_HEADERS KService_HEADERS
)
ecm_generate_headers(KService_HEADERS
HEADER_NAMES
KApplicationTrader
KService
KServiceAction
KServiceGroup
RELATIVE services REQUIRED_HEADERS KService_HEADERS
)
install(TARGETS KF6Service EXPORT KF6ServiceTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/kservice_export.h"
${KService_HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/services/kserviceconversioncheck_p.h # helper header included by kservice.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KService COMPONENT Devel
)
ecm_qt_install_logging_categories(
EXPORT KSERVICE
FILE kservice.categories
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
)
if(BUILD_QCH)
ecm_add_qch(
KF6Service_QCH
NAME KService
BASE_NAME KF6Service
VERSION ${KF_VERSION}
ORG_DOMAIN org.kde
SOURCES # using only public headers, to cover only public API
${KService_HEADERS}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
KF6Config_QCH
KF6CoreAddons_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
${kservice_includes}
BLANK_MACROS
KSERVICE_EXPORT
KSERVICE_DEPRECATED
KSERVICE_DEPRECATED_EXPORT
"KSERVICE_DEPRECATED_VERSION(x, y, t)"
"KSERVICE_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)"
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
add_subdirectory(kbuildsycoca)
+13
View File
@@ -0,0 +1,13 @@
#!/bin/sh
# Invoke the extractrc script on all .ui, .rc, and .kcfg files in the sources.
# The results are stored in a pseudo .cpp file to be picked up by xgettext.
lst=`find . -name \*.rc -o -name \*.ui -o -name \*.kcfg`
if [ -n "$lst" ] ; then
$EXTRACTRC $lst >> rc.cpp
fi
# Extract strings from all source files.
# If your framework depends on KI18n, use $XGETTEXT. If it uses Qt translation
# system, use $EXTRACT_TR_STRINGS.
$XGETTEXT `find . -name \*.cpp -o -name \*.h -o -name \*.qml` -o $podir/kservice6.pot
@@ -0,0 +1,2 @@
#cmakedefine01 HAVE_MMAP
#cmakedefine01 HAVE_MADVISE
@@ -0,0 +1,19 @@
include(ECMMarkNonGuiExecutable)
# We need to add a '6' so that kde3/kde4/kf5 apps running kbuildsycoca don't run this one.
add_executable(kbuildsycoca6)
# Mark it as non-gui so we won't create an app bundle on Mac OS X
ecm_mark_nongui_executable(kbuildsycoca6)
target_sources(kbuildsycoca6 PRIVATE
kbuildsycoca_main.cpp
)
target_link_libraries(kbuildsycoca6
KF6::Service
KF6::CoreAddons # KAboutData
KF6::I18n
Qt6::Xml
)
install(TARGETS kbuildsycoca6 ${KF_INSTALL_TARGETS_DEFAULT_ARGS} )
@@ -0,0 +1,133 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2002-2003 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include <kbuildsycoca_p.h>
#include <kservice_version.h>
#include <KAboutData>
#include <KLocalizedString>
#include <QCommandLineOption>
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QStandardPaths>
#include <qplatformdefs.h> // for unlink
#ifdef Q_OS_WIN
#include <qt_windows.h>
#endif
static void crashHandler(int)
{
// If we crash while reading sycoca, we delete the database
// in an attempt to recover.
if (KBuildSycoca::sycocaPath()) {
unlink(KBuildSycoca::sycocaPath());
}
}
#if defined(Q_OS_WIN)
// glue function for calling the unix signal handler from the windows unhandled exception filter
// Inspired from KCrash, but heavily simplified
LONG WINAPI win32UnhandledExceptionFilter(_EXCEPTION_POINTERS *)
{
crashHandler(0);
return EXCEPTION_EXECUTE_HANDLER; // allow windows to do the default action (terminate)
}
#endif
void setCrashHandler()
{
#if defined(Q_OS_WIN)
SetUnhandledExceptionFilter(win32UnhandledExceptionFilter);
#elif !defined(Q_OS_ANDROID)
sigset_t mask;
sigemptyset(&mask);
#ifdef SIGSEGV
signal(SIGSEGV, crashHandler);
sigaddset(&mask, SIGSEGV);
#endif
#ifdef SIGBUS
signal(SIGBUS, crashHandler);
sigaddset(&mask, SIGBUS);
#endif
#ifdef SIGFPE
signal(SIGFPE, crashHandler);
sigaddset(&mask, SIGFPE);
#endif
#ifdef SIGILL
signal(SIGILL, crashHandler);
sigaddset(&mask, SIGILL);
#endif
#ifdef SIGABRT
signal(SIGABRT, crashHandler);
sigaddset(&mask, SIGABRT);
#endif
sigprocmask(SIG_UNBLOCK, &mask, nullptr);
#endif
}
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
KLocalizedString::setApplicationDomain("kservice6");
KAboutData about(QStringLiteral(KBUILDSYCOCA_EXENAME),
i18nc("application name", "KBuildSycoca"),
QStringLiteral(KSERVICE_VERSION_STRING),
i18nc("application description", "Rebuilds the system configuration cache."),
KAboutLicense::GPL,
i18nc("@info:credit", "Copyright 1999-2014 KDE Developers"));
about.addAuthor(i18nc("@info:credit", "David Faure"), i18nc("@info:credit", "Author"), QStringLiteral("faure@kde.org"));
about.addAuthor(i18nc("@info:credit", "Waldo Bastian"), i18nc("@info:credit", "Author"), QStringLiteral("bastian@kde.org"));
KAboutData::setApplicationData(about);
QCommandLineParser parser;
about.setupCommandLine(&parser);
parser.addOption(
QCommandLineOption(QStringLiteral("noincremental"), i18nc("@info:shell command-line option", "Disable incremental update, re-read everything")));
parser.addOption(QCommandLineOption(QStringLiteral("menutest"), i18nc("@info:shell command-line option", "Perform menu generation test run only")));
parser.addOption(
QCommandLineOption(QStringLiteral("track"), i18nc("@info:shell command-line option", "Track menu id for debug purposes"), QStringLiteral("menu-id")));
parser.addOption(
QCommandLineOption(QStringLiteral("testmode"), i18nc("@info:shell command-line option", "Switch QStandardPaths to test mode, for unit tests only")));
parser.process(app);
about.processCommandLine(&parser);
const bool bMenuTest = parser.isSet(QStringLiteral("menutest"));
if (parser.isSet(QStringLiteral("testmode"))) {
QStandardPaths::setTestModeEnabled(true);
}
setCrashHandler();
fprintf(stderr, "%s running...\n", KBUILDSYCOCA_EXENAME);
const bool incremental = !parser.isSet(QStringLiteral("noincremental"));
KBuildSycoca sycoca; // Build data base
if (parser.isSet(QStringLiteral("track"))) {
sycoca.setTrackId(parser.value(QStringLiteral("track")));
}
sycoca.setMenuTest(bMenuTest);
if (!sycoca.recreate(incremental)) {
return -1;
}
return 0;
}
@@ -0,0 +1,136 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 2006-2020 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kapplicationtrader.h"
#include "kmimetypefactory_p.h"
#include "kservicefactory_p.h"
#include "ksycoca.h"
#include "ksycoca_p.h"
#include "servicesdebug.h"
#include <QMimeDatabase>
#include <KConfigGroup>
#include <KSharedConfig>
static KService::List mimeTypeSycocaServiceOffers(const QString &mimeType)
{
KService::List lst;
QMimeDatabase db;
QString mime = db.mimeTypeForName(mimeType).name();
if (mime.isEmpty()) {
if (!mimeType.startsWith(QLatin1String("x-scheme-handler/"))) { // don't warn for unknown scheme handler mimetypes
qCWarning(SERVICES) << "KApplicationTrader: mimeType" << mimeType << "not found";
return lst; // empty
}
mime = mimeType;
}
KSycoca::self()->ensureCacheValid();
KMimeTypeFactory *factory = KSycocaPrivate::self()->mimeTypeFactory();
const int offset = factory->entryOffset(mime);
if (!offset) {
if (!mimeType.startsWith(QLatin1String("x-scheme-handler/"))) { // don't warn for unknown scheme handler mimetypes
qCWarning(SERVICES) << "KApplicationTrader: mimeType" << mimeType << "not found";
}
return lst; // empty
}
const int serviceOffersOffset = factory->serviceOffersOffset(mime);
if (serviceOffersOffset > -1) {
lst = KSycocaPrivate::self()->serviceFactory()->serviceOffers(offset, serviceOffersOffset);
}
return lst;
}
static void applyFilter(KService::List &list, KApplicationTrader::FilterFunc filterFunc, bool mustShowInCurrentDesktop)
{
if (list.isEmpty()) {
return;
}
// Find all services matching the constraint
// and remove the other ones
auto removeFunc = [&](const KService::Ptr &serv) {
return (filterFunc && !filterFunc(serv)) || (mustShowInCurrentDesktop && !serv->showInCurrentDesktop());
};
list.erase(std::remove_if(list.begin(), list.end(), removeFunc), list.end());
}
KService::List KApplicationTrader::query(FilterFunc filterFunc)
{
// Get all applications
KSycoca::self()->ensureCacheValid();
KService::List lst = KSycocaPrivate::self()->serviceFactory()->allServices();
applyFilter(lst, filterFunc, true); // true = filter out service with NotShowIn=KDE or equivalent
qCDebug(SERVICES) << "query returning" << lst.count() << "offers";
return lst;
}
KService::List KApplicationTrader::queryByMimeType(const QString &mimeType, FilterFunc filterFunc)
{
// Get all services of this MIME type.
KService::List lst = mimeTypeSycocaServiceOffers(mimeType);
applyFilter(lst, filterFunc, false); // false = allow NotShowIn=KDE services listed in mimeapps.list
qCDebug(SERVICES) << "query for mimeType" << mimeType << "returning" << lst.count() << "offers";
return lst;
}
KService::Ptr KApplicationTrader::preferredService(const QString &mimeType)
{
const KService::List offers = queryByMimeType(mimeType);
if (!offers.isEmpty()) {
return offers.at(0);
}
return KService::Ptr();
}
void KApplicationTrader::setPreferredService(const QString &mimeType, const KService::Ptr service)
{
if (mimeType.isEmpty() || !(service && service->isValid())) {
return;
}
KSharedConfig::Ptr profile = KSharedConfig::openConfig(QStringLiteral("mimeapps.list"), KConfig::NoGlobals, QStandardPaths::GenericConfigLocation);
// Save the default application according to mime-apps-spec 1.0
KConfigGroup defaultApp(profile, QStringLiteral("Default Applications"));
defaultApp.writeXdgListEntry(mimeType, QStringList(service->storageId()));
KConfigGroup addedApps(profile, QStringLiteral("Added Associations"));
QStringList apps = addedApps.readXdgListEntry(mimeType);
apps.removeAll(service->storageId());
apps.prepend(service->storageId()); // make it the preferred app
addedApps.writeXdgListEntry(mimeType, apps);
profile->sync();
// Also make sure the "auto embed" setting for this MIME type is off
KSharedConfig::Ptr fileTypesConfig = KSharedConfig::openConfig(QStringLiteral("filetypesrc"), KConfig::NoGlobals);
fileTypesConfig->group(QStringLiteral("EmbedSettings")).writeEntry(QStringLiteral("embed-") + mimeType, false);
fileTypesConfig->sync();
}
bool KApplicationTrader::isSubsequence(const QString &pattern, const QString &text, Qt::CaseSensitivity cs)
{
if (pattern.isEmpty()) {
return false;
}
const bool chk_case = cs == Qt::CaseSensitive;
auto textIt = text.cbegin();
auto patternIt = pattern.cbegin();
for (; textIt != text.cend() && patternIt != pattern.cend(); ++textIt) {
if ((chk_case && *textIt == *patternIt) || (!chk_case && textIt->toLower() == patternIt->toLower())) {
++patternIt;
}
}
return patternIt == pattern.cend();
}
@@ -0,0 +1,100 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 2006-2020 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KAPPLICATIONTRADER_H
#define KAPPLICATIONTRADER_H
#include <functional>
#include <kservice.h>
/**
* @namespace KApplicationTrader
*
* The application trader is a convenient way to find installed applications
* based on specific criteria (association with a MIME type, name contains Foo, etc.)
*
* Example: say that you want to get the list of all applications that can handle PNG images.
* The code would look like:
* \code
* KService::List lst = KApplicationTrader::queryByMimeType("image/png");
* \endcode
*
* If you want to get the preferred application for image/png you would use:
* @code
* KService::Ptr service = KApplicationTrader::preferredService("image/png");
* @endcode
*
* @see KService
*/
namespace KApplicationTrader
{
/**
* Filter function, used for filtering results of query and queryByMimeType.
*/
using FilterFunc = std::function<bool(const KService::Ptr &)>;
/**
* This method returns a list of services (applications) that match a given filter.
*
* @param filter a callback function that returns @c true if the application
* should be selected and @c false if it should be skipped.
*
* @return A list of services that satisfy the query
* @since 5.68
*/
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc);
/**
* This method returns a list of services (applications) which are associated with a given MIME type.
*
* @param mimeType a MIME type like 'text/plain' or 'text/html'
* @param filter a callback function that returns @c true if the application
* should be selected and @c false if it should be skipped. Do not return
* true for all services, this would return the complete list of all
* installed applications (slow).
*
* @return A list of services that satisfy the query, sorted by preference
* (preferred service first)
* @since 5.68
*/
KSERVICE_EXPORT KService::List queryByMimeType(const QString &mimeType, FilterFunc filterFunc = {});
/**
* Returns the preferred service for @p mimeType
*
* This a convenience method for queryByMimeType(mimeType).at(0), with a check for empty.
*
* @param mimeType the MIME type (see query())
* @return the preferred service, or @c nullptr if no service is available
* @since 5.68
*/
KSERVICE_EXPORT KService::Ptr preferredService(const QString &mimeType);
/**
* Changes the preferred service for @p mimeType to @p service
*
* You may need to rebuild KSyCoca for the change to be reflected
*
* @param mimeType the MIME type
* @param service the service to set as the preferred one
* @since 5.101
*/
KSERVICE_EXPORT void setPreferredService(const QString &mimeType, const KService::Ptr service);
/**
* Returns true if @p pattern matches a subsequence of the string @p text.
* For instance the pattern "libremath" matches the text "LibreOffice Math", assuming
* @p cs is Qt::CaseInsensitive.
*
* This can be useful from your filter function, e.g. with @p text being service->name().
* @since 5.68
*/
KSERVICE_EXPORT bool isSubsequence(const QString &pattern, const QString &text, Qt::CaseSensitivity cs = Qt::CaseSensitive);
}
#endif
@@ -0,0 +1,168 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2006-2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kmimetypefactory_p.h"
#include "ksycocaentry_p.h"
#include "servicesdebug.h"
#include <QDataStream>
#include <ksycoca.h>
#include <ksycocadict_p.h>
extern int servicesDebugArea();
KMimeTypeFactory::KMimeTypeFactory(KSycoca *db)
: KSycocaFactory(KST_KMimeTypeFactory, db)
{
}
KMimeTypeFactory::~KMimeTypeFactory()
{
}
int KMimeTypeFactory::entryOffset(const QString &mimeTypeName)
{
if (!sycocaDict()) {
return -1; // Error!
}
assert(!sycoca()->isBuilding());
const int offset = sycocaDict()->find_string(mimeTypeName.toLower());
return offset;
}
int KMimeTypeFactory::serviceOffersOffset(const QString &mimeTypeName)
{
const int offset = entryOffset(mimeTypeName.toLower());
if (!offset) {
return -1; // Not found
}
MimeTypeEntry::Ptr newMimeType(createEntry(offset));
if (!newMimeType) {
return -1;
}
// Check whether the dictionary was right.
if (newMimeType->name() != mimeTypeName.toLower()) {
// No it wasn't...
return -1;
}
return newMimeType->serviceOffersOffset();
}
KMimeTypeFactory::MimeTypeEntry *KMimeTypeFactory::createEntry(int offset) const
{
KSycocaType type;
QDataStream *str = sycoca()->findEntry(offset, type);
if (!str) {
return nullptr;
}
if (type != KST_KMimeTypeEntry) {
qCWarning(SERVICES) << "KMimeTypeFactory: unexpected object entry in KSycoca database (type=" << int(type) << ")";
return nullptr;
}
MimeTypeEntry *newEntry = new MimeTypeEntry(*str, offset);
if (newEntry && !newEntry->isValid()) {
qCWarning(SERVICES) << "KMimeTypeFactory: corrupt object in KSycoca database!\n";
delete newEntry;
newEntry = nullptr;
}
return newEntry;
}
QStringList KMimeTypeFactory::allMimeTypes()
{
// TODO: reimplement in terms of "listing xdgdata-mime", to avoid ksycoca dependency,
// then move to KMimeTypeRepository
const KSycocaEntry::List list = allEntries();
QStringList result;
result.reserve(list.size());
std::transform(list.cbegin(), list.cend(), std::back_inserter(result), [](const KSycocaEntry::Ptr &entry) {
Q_ASSERT(entry->isType(KST_KMimeTypeEntry));
MimeTypeEntry::Ptr mimeType(static_cast<MimeTypeEntry *>(entry.data()));
return mimeType->name();
});
return result;
}
KMimeTypeFactory::MimeTypeEntry::Ptr KMimeTypeFactory::findMimeTypeEntryByName(const QString &name)
{
Q_ASSERT(sycoca()->isBuilding());
// We're building a database - the MIME type entry must be in memory
KSycocaEntry::Ptr servType = m_entryDict->value(name.toLower());
return MimeTypeEntry::Ptr(static_cast<MimeTypeEntry *>(servType.data()));
}
QStringList KMimeTypeFactory::resourceDirs()
{
return KSycocaFactory::allDirectories(QStringLiteral("mime"));
}
////
class KMimeTypeFactory::MimeTypeEntryPrivate : public KSycocaEntryPrivate
{
public:
K_SYCOCATYPE(KST_KMimeTypeEntry, KSycocaEntryPrivate)
MimeTypeEntryPrivate(const QString &file, const QString &name)
: KSycocaEntryPrivate(file)
, m_name(name)
, m_serviceOffersOffset(-1)
{
}
MimeTypeEntryPrivate(QDataStream &s, int offset)
: KSycocaEntryPrivate(s, offset)
, m_serviceOffersOffset(-1)
{
s >> m_name >> m_serviceOffersOffset;
}
QString name() const override
{
return m_name;
}
void save(QDataStream &s) override;
QString m_name;
int m_serviceOffersOffset;
};
void KMimeTypeFactory::MimeTypeEntryPrivate::save(QDataStream &s)
{
KSycocaEntryPrivate::save(s);
s << m_name << m_serviceOffersOffset;
}
////
KMimeTypeFactory::MimeTypeEntry::MimeTypeEntry(const QString &file, const QString &name)
: KSycocaEntry(*new MimeTypeEntryPrivate(file, name.toLower()))
{
}
KMimeTypeFactory::MimeTypeEntry::MimeTypeEntry(QDataStream &s, int offset)
: KSycocaEntry(*new MimeTypeEntryPrivate(s, offset))
{
}
KMimeTypeFactory::MimeTypeEntry::~MimeTypeEntry()
{
}
int KMimeTypeFactory::MimeTypeEntry::serviceOffersOffset() const
{
Q_D(const MimeTypeEntry);
return d->m_serviceOffersOffset;
}
void KMimeTypeFactory::MimeTypeEntry::setServiceOffersOffset(int off)
{
Q_D(MimeTypeEntry);
d->m_serviceOffersOffset = off;
}
@@ -0,0 +1,101 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2006-2007 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KMIMETYPEFACTORY_H
#define KMIMETYPEFACTORY_H
#include <assert.h>
#include <QStringList>
#include "ksycocafactory_p.h"
class KSycoca;
/**
* @internal - this header is not installed
*
* A sycoca factory for MIME type entries
* This is only used to point to the "service offers" in ksycoca for each MIME type.
* @see KMimeType
*/
class KMimeTypeFactory : public KSycocaFactory
{
K_SYCOCAFACTORY(KST_KMimeTypeFactory)
public:
/**
* Create factory
*/
explicit KMimeTypeFactory(KSycoca *db);
~KMimeTypeFactory() override;
/**
* Not meant to be called at this level
*/
KSycocaEntry *createEntry(const QString &) const override
{
assert(0);
return nullptr;
}
/**
* Returns the possible offset for a given MIME type entry.
*/
int entryOffset(const QString &mimeTypeName);
/**
* Returns the offset into the service offers for a given MIME type.
*/
int serviceOffersOffset(const QString &mimeTypeName);
/**
* Returns the directories to watch for this factory.
*/
static QStringList resourceDirs();
public:
/**
* @return all MIME types
* Slow and memory consuming, avoid using
*/
QStringList allMimeTypes();
/**
* @return the unique MIME type factory, creating it if necessary
*/
static KMimeTypeFactory *self();
public: // public for KBuildServiceFactory
// A small entry for each MIME type with name and offset into the services-offer-list.
class MimeTypeEntryPrivate;
class KSERVICE_EXPORT MimeTypeEntry : public KSycocaEntry
{
Q_DECLARE_PRIVATE(MimeTypeEntry)
public:
typedef QExplicitlySharedDataPointer<MimeTypeEntry> Ptr;
MimeTypeEntry(const QString &file, const QString &name);
MimeTypeEntry(QDataStream &s, int offset);
~MimeTypeEntry() override;
int serviceOffersOffset() const;
void setServiceOffersOffset(int off);
};
MimeTypeEntry::Ptr findMimeTypeEntryByName(const QString &name);
protected:
MimeTypeEntry *createEntry(int offset) const override;
private:
// d pointer: useless since this header is not installed
// class KMimeTypeFactoryPrivate* d;
};
#endif
@@ -0,0 +1,836 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999-2001 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 1999-2005 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kservice.h"
#include "kmimetypefactory_p.h"
#include "kservice_p.h"
#include "ksycoca.h"
#include "ksycoca_p.h"
#include <qplatformdefs.h>
#include <QDir>
#include <QMap>
#include <QMimeDatabase>
#include <KConfigGroup>
#include <KDesktopFile>
#include <KShell>
#include <QDebug>
#include <QStandardPaths>
#include "kservicefactory_p.h"
#include "kserviceutil_p.h"
#include "servicesdebug.h"
void KServicePrivate::init(const KDesktopFile *config, KService *q)
{
const QString entryPath = q->entryPath();
if (entryPath.isEmpty()) {
// We are opening a "" service, this means whatever warning we might get is going to be misleading
m_bValid = false;
return;
}
bool absPath = !QDir::isRelativePath(entryPath);
const KConfigGroup desktopGroup = config->desktopGroup();
QMap<QString, QString> entryMap = desktopGroup.entryMap();
entryMap.remove(QStringLiteral("Encoding")); // reserved as part of Desktop Entry Standard
entryMap.remove(QStringLiteral("Version")); // reserved as part of Desktop Entry Standard
q->setDeleted(desktopGroup.readEntry("Hidden", false));
entryMap.remove(QStringLiteral("Hidden"));
if (q->isDeleted()) {
m_bValid = false;
return;
}
m_strName = config->readName();
entryMap.remove(QStringLiteral("Name"));
if (m_strName.isEmpty()) {
// Try to make up a name.
m_strName = entryPath;
int i = m_strName.lastIndexOf(QLatin1Char('/'));
m_strName = m_strName.mid(i + 1);
i = m_strName.lastIndexOf(QLatin1Char('.'));
if (i != -1) {
m_strName.truncate(i);
}
}
m_strType = entryMap.take(QStringLiteral("Type"));
if (m_strType.isEmpty()) {
qCWarning(SERVICES) << "The desktop entry file" << entryPath << "does not have a \"Type=Application\" set.";
m_strType = QStringLiteral("Application");
} else if (m_strType != QLatin1String("Application") && m_strType != QLatin1String("Service")) {
qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType << "instead of \"Application\" or \"Service\"";
m_bValid = false;
return;
}
// NOT readPathEntry, it is not XDG-compliant: it performs
// various expansions, like $HOME. Note that the expansion
// behaviour still happens if the "e" flag is set, maintaining
// backwards compatibility.
m_strExec = entryMap.take(QStringLiteral("Exec"));
// In case Try Exec is set, check if the application is available
if (!config->tryExec()) {
q->setDeleted(true);
m_bValid = false;
return;
}
const QStandardPaths::StandardLocation locationType = config->locationType();
if ((m_strType == QLatin1String("Application")) && (locationType != QStandardPaths::ApplicationsLocation) && !absPath) {
qCWarning(SERVICES) << "The desktop entry file" << entryPath << "has Type=" << m_strType << "but is located under \""
<< QStandardPaths::displayName(locationType) << "\" instead of \"Applications\"";
m_bValid = false;
return;
}
// entryPath To desktopEntryName
// (e.g. "/usr/share/applications/org.kde.kate" --> "org.kde.kate")
QString _name = KServiceUtilPrivate::completeBaseName(entryPath);
m_strIcon = entryMap.take(QStringLiteral("Icon"));
m_bTerminal = desktopGroup.readEntry("Terminal", false);
entryMap.remove(QStringLiteral("Terminal"));
m_strTerminalOptions = entryMap.take(QStringLiteral("TerminalOptions"));
m_strWorkingDirectory = KShell::tildeExpand(entryMap.take(QStringLiteral("Path")));
m_strComment = entryMap.take(QStringLiteral("Comment"));
m_strGenName = entryMap.take(QStringLiteral("GenericName"));
// Store these as member variables too, because the lookup will be significanly faster
m_untranslatedGenericName = desktopGroup.readEntryUntranslated("GenericName");
m_untranslatedName = desktopGroup.readEntryUntranslated("Name");
m_lstFormFactors = entryMap.take(QStringLiteral("X-KDE-FormFactors")).split(QLatin1Char(' '), Qt::SkipEmptyParts);
if (entryMap.remove(QStringLiteral("Keywords"))) {
m_lstKeywords = desktopGroup.readXdgListEntry("Keywords");
}
m_lstKeywords += entryMap.take(QStringLiteral("X-KDE-Keywords")).split(QLatin1Char(' '), Qt::SkipEmptyParts);
if (entryMap.remove(QStringLiteral("Categories"))) {
categories = desktopGroup.readXdgListEntry("Categories");
}
if (entryMap.remove(QStringLiteral("MimeType"))) {
m_mimeTypes = desktopGroup.readXdgListEntry("MimeType");
}
m_strDesktopEntryName = _name;
if (entryMap.remove(QStringLiteral("AllowDefault"))) {
m_bAllowAsDefault = desktopGroup.readEntry("AllowDefault", true);
}
// Store all additional entries in the property map.
// A QMap<QString,QString> would be easier for this but we can't
// break BC, so we have to store it in m_mapProps.
// qDebug("Path = %s", entryPath.toLatin1().constData());
auto it = entryMap.constBegin();
for (; it != entryMap.constEnd(); ++it) {
const QString key = it.key();
// Ignore Actions, we parse that below
if (key == QLatin1String("Actions")) {
continue;
}
// do not store other translations like Name[fr]; kbuildsycoca will rerun if we change languages anyway
if (!key.contains(QLatin1Char('['))) {
// qCDebug(SERVICES) << " Key =" << key << " Data =" << it.value();
if (key == QLatin1String("X-Flatpak-RenamedFrom")) {
m_mapProps.insert(key, desktopGroup.readXdgListEntry(key));
} else {
m_mapProps.insert(key, QVariant(it.value()));
}
}
}
// parse actions last since that may clone the service
// we want all other information parsed by then
if (entryMap.contains(QLatin1String("Actions"))) {
parseActions(config, q);
}
}
void KServicePrivate::parseActions(const KDesktopFile *config, KService *q)
{
const QStringList keys = config->readActions();
if (keys.isEmpty()) {
return;
}
KService::Ptr serviceClone(new KService(*q));
for (const QString &group : keys) {
if (group == QLatin1String("_SEPARATOR_")) {
m_actions.append(KServiceAction(group, QString(), QString(), QString(), false, serviceClone));
continue;
}
if (config->hasActionGroup(group)) {
const KConfigGroup cg = config->actionGroup(group);
if (!cg.hasKey("Name") || !cg.hasKey("Exec")) {
qCWarning(SERVICES) << "The action" << group << "in the desktop file" << q->entryPath() << "has no Name or no Exec key";
} else {
const QMap<QString, QString> entries = cg.entryMap();
QVariantMap entriesVariants;
for (auto it = entries.constKeyValueBegin(); it != entries.constKeyValueEnd(); ++it) {
// Those are stored separately
if (it->first == QLatin1String("Name") || it->first == QLatin1String("Icon") || it->first == QLatin1String("Exec")
|| it->first == QLatin1String("NoDisplay")) {
continue;
}
entriesVariants.insert(it->first, it->second);
}
KServiceAction action(group, cg.readEntry("Name"), cg.readEntry("Icon"), cg.readEntry("Exec"), cg.readEntry("NoDisplay", false), serviceClone);
action.setData(QVariant::fromValue(entriesVariants));
m_actions.append(action);
}
} else {
qCWarning(SERVICES) << "The desktop file" << q->entryPath() << "references the action" << group << "but doesn't define it";
}
}
serviceClone->setActions(m_actions);
}
void KServicePrivate::load(QDataStream &s)
{
qint8 def;
qint8 term;
qint8 dst;
// WARNING: THIS NEEDS TO REMAIN COMPATIBLE WITH PREVIOUS KService 6.x VERSIONS!
// !! This data structure should remain binary compatible at all times !!
// You may add new fields at the end. Make sure to update KSYCOCA_VERSION
// number in ksycoca.cpp
// clang-format off
s >> m_strType >> m_strName >> m_strExec >> m_strIcon
>> term >> m_strTerminalOptions
>> m_strWorkingDirectory >> m_strComment >> def >> m_mapProps
>> m_strLibrary
>> dst
>> m_strDesktopEntryName
>> m_lstKeywords >> m_strGenName
>> categories >> menuId >> m_actions
>> m_lstFormFactors
>> m_untranslatedName >> m_untranslatedGenericName >> m_mimeTypes;
// clang-format on
m_bAllowAsDefault = bool(def);
m_bTerminal = bool(term);
m_bValid = true;
}
void KServicePrivate::save(QDataStream &s)
{
KSycocaEntryPrivate::save(s);
qint8 def = m_bAllowAsDefault;
qint8 term = m_bTerminal;
qint8 dst = 0;
// WARNING: THIS NEEDS TO REMAIN COMPATIBLE WITH PREVIOUS KService 6.x VERSIONS!
// !! This data structure should remain binary compatible at all times !!
// You may add new fields at the end. Make sure to update KSYCOCA_VERSION
// number in ksycoca.cpp
s << m_strType << m_strName << m_strExec << m_strIcon << term << m_strTerminalOptions << m_strWorkingDirectory << m_strComment << def << m_mapProps
<< m_strLibrary << dst << m_strDesktopEntryName << m_lstKeywords << m_strGenName << categories << menuId << m_actions << m_lstFormFactors
<< m_untranslatedName << m_untranslatedGenericName << m_mimeTypes;
}
////
KService::KService(const QString &_name, const QString &_exec, const QString &_icon)
: KSycocaEntry(*new KServicePrivate(QString()))
{
Q_D(KService);
d->m_strType = QStringLiteral("Application");
d->m_strName = _name;
d->m_strExec = _exec;
d->m_strIcon = _icon;
d->m_bTerminal = false;
d->m_bAllowAsDefault = true;
}
KService::KService(const QString &_fullpath)
: KSycocaEntry(*new KServicePrivate(_fullpath))
{
Q_D(KService);
KDesktopFile config(_fullpath);
d->init(&config, this);
}
KService::KService(const KDesktopFile *config, const QString &entryPath)
: KSycocaEntry(*new KServicePrivate(entryPath.isEmpty() ? config->fileName() : entryPath))
{
Q_D(KService);
d->init(config, this);
}
KService::KService(QDataStream &_str, int _offset)
: KSycocaEntry(*new KServicePrivate(_str, _offset))
{
Q_D(KService);
KService::Ptr serviceClone(new KService(*this));
for (KServiceAction &action : d->m_actions) {
action.setService(serviceClone);
}
}
KService::KService(const KService &other)
: KSycocaEntry(*new KServicePrivate(*other.d_func()))
{
}
KService::~KService()
{
}
bool KService::hasMimeType(const QString &mimeType) const
{
Q_D(const KService);
QMimeDatabase db;
const QString mime = db.mimeTypeForName(mimeType).name();
if (mime.isEmpty()) {
return false;
}
int serviceOffset = offset();
if (serviceOffset) {
KSycoca::self()->ensureCacheValid();
KMimeTypeFactory *factory = KSycocaPrivate::self()->mimeTypeFactory();
const int mimeOffset = factory->entryOffset(mime);
const int serviceOffersOffset = factory->serviceOffersOffset(mime);
if (serviceOffersOffset == -1) {
return false;
}
return KSycocaPrivate::self()->serviceFactory()->hasOffer(mimeOffset, serviceOffersOffset, serviceOffset);
}
return d->m_mimeTypes.contains(mime);
}
QVariant KService::property(const QString &_name, QMetaType::Type t) const
{
Q_D(const KService);
return d->property(_name, t);
}
template<>
QString KService::property<QString>(const QString &_name) const
{
Q_D(const KService);
if (_name == QLatin1String("Type")) {
return d->m_strType;
} else if (_name == QLatin1String("Name")) {
return d->m_strName;
} else if (_name == QLatin1String("Exec")) {
return d->m_strExec;
} else if (_name == QLatin1String("Icon")) {
return d->m_strIcon;
} else if (_name == QLatin1String("TerminalOptions")) {
return d->m_strTerminalOptions;
} else if (_name == QLatin1String("Path")) {
return d->m_strWorkingDirectory;
} else if (_name == QLatin1String("Comment")) {
return d->m_strComment;
} else if (_name == QLatin1String("GenericName")) {
return d->m_strGenName;
} else if (_name == QLatin1String("DesktopEntryPath")) {
return d->path;
} else if (_name == QLatin1String("DesktopEntryName")) {
return d->m_strDesktopEntryName;
} else if (_name == QLatin1String("UntranslatedName")) {
return d->m_untranslatedName;
} else if (_name == QLatin1String("UntranslatedGenericName")) {
return d->m_untranslatedGenericName;
}
auto it = d->m_mapProps.constFind(_name);
if (it != d->m_mapProps.cend()) {
return it.value().toString();
}
return QString();
}
QVariant KServicePrivate::property(const QString &_name, QMetaType::Type t) const
{
if (_name == QLatin1String("Terminal")) {
return QVariant(m_bTerminal);
} else if (_name == QLatin1String("AllowAsDefault")) {
return QVariant(m_bAllowAsDefault);
} else if (_name == QLatin1String("Categories")) {
return QVariant(categories);
} else if (_name == QLatin1String("Keywords")) {
return QVariant(m_lstKeywords);
} else if (_name == QLatin1String("FormFactors")) {
return QVariant(m_lstFormFactors);
}
auto it = m_mapProps.constFind(_name);
if (it == m_mapProps.cend() || !it.value().isValid()) {
// qCDebug(SERVICES) << "Property not found " << _name;
return QVariant(); // No property set.
}
if (it->typeId() == t) {
return it.value(); // no conversion necessary
} else {
// All others
// For instance properties defined as StringList, like MimeTypes.
// XXX This API is accessible only through a friend declaration.
return KConfigGroup::convertToQVariant(_name.toUtf8().constData(), it.value().toString().toUtf8(), QVariant(QMetaType(t)));
}
}
KService::List KService::allServices()
{
KSycoca::self()->ensureCacheValid();
return KSycocaPrivate::self()->serviceFactory()->allServices();
}
KService::Ptr KService::serviceByDesktopPath(const QString &_name)
{
KSycoca::self()->ensureCacheValid();
return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopPath(_name);
}
KService::Ptr KService::serviceByDesktopName(const QString &_name)
{
KSycoca::self()->ensureCacheValid();
return KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopName(_name);
}
KService::Ptr KService::serviceByMenuId(const QString &_name)
{
KSycoca::self()->ensureCacheValid();
return KSycocaPrivate::self()->serviceFactory()->findServiceByMenuId(_name);
}
KService::Ptr KService::serviceByStorageId(const QString &_storageId)
{
KSycoca::self()->ensureCacheValid();
return KSycocaPrivate::self()->serviceFactory()->findServiceByStorageId(_storageId);
}
bool KService::substituteUid() const
{
return property<bool>(QStringLiteral("X-KDE-SubstituteUID"));
}
QString KService::username() const
{
// See also KDesktopFile::tryExec()
QString user = property<QString>(QStringLiteral("X-KDE-Username"));
if (user.isEmpty()) {
user = QString::fromLocal8Bit(qgetenv("ADMIN_ACCOUNT"));
}
if (user.isEmpty()) {
user = QStringLiteral("root");
}
return user;
}
bool KService::showInCurrentDesktop() const
{
Q_D(const KService);
const QString envVar = QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP"));
QList<QStringView> currentDesktops = QStringView(envVar).split(QLatin1Char(':'), Qt::SkipEmptyParts);
const QString kde = QStringLiteral("KDE");
if (currentDesktops.isEmpty()) {
// This could be an old display manager, or e.g. a failsafe session with no desktop name
// In doubt, let's say we show KDE stuff.
currentDesktops.append(kde);
}
// This algorithm is described in the desktop entry spec
auto it = d->m_mapProps.constFind(QStringLiteral("OnlyShowIn"));
if (it != d->m_mapProps.cend()) {
const QVariant &val = it.value();
if (val.isValid()) {
const QStringList aList = val.toString().split(QLatin1Char(';'));
return std::any_of(currentDesktops.cbegin(), currentDesktops.cend(), [&aList](const auto desktop) {
return aList.contains(desktop);
});
}
}
it = d->m_mapProps.constFind(QStringLiteral("NotShowIn"));
if (it != d->m_mapProps.cend()) {
const QVariant &val = it.value();
if (val.isValid()) {
const QStringList aList = val.toString().split(QLatin1Char(';'));
return std::none_of(currentDesktops.cbegin(), currentDesktops.cend(), [&aList](const auto desktop) {
return aList.contains(desktop);
});
}
}
return true;
}
bool KService::showOnCurrentPlatform() const
{
Q_D(const KService);
const QString platform = QCoreApplication::instance()->property("platformName").toString();
if (platform.isEmpty()) {
return true;
}
auto it = d->m_mapProps.find(QStringLiteral("X-KDE-OnlyShowOnQtPlatforms"));
if ((it != d->m_mapProps.end()) && (it->isValid())) {
const QStringList aList = it->toString().split(QLatin1Char(';'));
if (!aList.contains(platform)) {
return false;
}
}
it = d->m_mapProps.find(QStringLiteral("X-KDE-NotShowOnQtPlatforms"));
if ((it != d->m_mapProps.end()) && (it->isValid())) {
const QStringList aList = it->toString().split(QLatin1Char(';'));
if (aList.contains(platform)) {
return false;
}
}
return true;
}
bool KService::noDisplay() const
{
if (property<bool>(QStringLiteral("NoDisplay"))) {
return true;
}
if (!showInCurrentDesktop()) {
return true;
}
if (!showOnCurrentPlatform()) {
return true;
}
return false;
}
QString KService::untranslatedGenericName() const
{
Q_D(const KService);
return d->m_untranslatedGenericName;
}
QString KService::untranslatedName() const
{
Q_D(const KService);
return d->m_untranslatedName;
}
QString KService::docPath() const
{
Q_D(const KService);
for (const QString &str : {QStringLiteral("X-DocPath"), QStringLiteral("DocPath")}) {
auto it = d->m_mapProps.constFind(str);
if (it != d->m_mapProps.cend()) {
const QVariant variant = it.value();
Q_ASSERT(variant.isValid());
const QString path = variant.toString();
if (!path.isEmpty()) {
return path;
}
}
}
return {};
}
bool KService::allowMultipleFiles() const
{
Q_D(const KService);
// Can we pass multiple files on the command line or do we have to start the application for every single file ?
return (d->m_strExec.contains(QLatin1String("%F")) //
|| d->m_strExec.contains(QLatin1String("%U")) //
|| d->m_strExec.contains(QLatin1String("%N")) //
|| d->m_strExec.contains(QLatin1String("%D")));
}
QStringList KService::categories() const
{
Q_D(const KService);
return d->categories;
}
QString KService::menuId() const
{
Q_D(const KService);
return d->menuId;
}
void KService::setMenuId(const QString &_menuId)
{
Q_D(KService);
d->menuId = _menuId;
}
QString KService::storageId() const
{
Q_D(const KService);
return d->storageId();
}
// not sure this is still used anywhere...
QString KService::locateLocal() const
{
Q_D(const KService);
if (d->menuId.isEmpty() //
|| entryPath().startsWith(QLatin1String(".hidden")) //
|| (QDir::isRelativePath(entryPath()) && d->categories.isEmpty())) {
return KDesktopFile::locateLocal(entryPath());
}
return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/applications/") + d->menuId;
}
QString KService::newServicePath(bool showInMenu, const QString &suggestedName, QString *menuId, const QStringList *reservedMenuIds)
{
Q_UNUSED(showInMenu); // TODO KDE5: remove argument
QString base = suggestedName;
QString result;
for (int i = 1; true; i++) {
if (i == 1) {
result = base + QStringLiteral(".desktop");
} else {
result = base + QStringLiteral("-%1.desktop").arg(i);
}
if (reservedMenuIds && reservedMenuIds->contains(result)) {
continue;
}
// Lookup service by menu-id
KService::Ptr s = serviceByMenuId(result);
if (s) {
continue;
}
if (!QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("applications/") + result).isEmpty()) {
continue;
}
break;
}
if (menuId) {
*menuId = result;
}
return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/applications/") + result;
}
bool KService::isApplication() const
{
Q_D(const KService);
return d->m_strType == QLatin1String("Application");
}
QString KService::exec() const
{
Q_D(const KService);
return d->m_strExec;
}
QString KService::icon() const
{
Q_D(const KService);
return d->m_strIcon;
}
QString KService::terminalOptions() const
{
Q_D(const KService);
return d->m_strTerminalOptions;
}
bool KService::terminal() const
{
Q_D(const KService);
return d->m_bTerminal;
}
bool KService::runOnDiscreteGpu() const
{
QVariant prop = property<bool>(QStringLiteral("PrefersNonDefaultGPU"));
if (!prop.isValid()) {
// For backwards compatibility
prop = property<bool>(QStringLiteral("X-KDE-RunOnDiscreteGpu"));
}
return prop.isValid() && prop.toBool();
}
QString KService::desktopEntryName() const
{
Q_D(const KService);
return d->m_strDesktopEntryName;
}
QString KService::workingDirectory() const
{
Q_D(const KService);
return d->m_strWorkingDirectory;
}
QString KService::comment() const
{
Q_D(const KService);
return d->m_strComment;
}
QString KService::genericName() const
{
Q_D(const KService);
return d->m_strGenName;
}
QStringList KService::keywords() const
{
Q_D(const KService);
return d->m_lstKeywords;
}
QStringList KService::mimeTypes() const
{
Q_D(const KService);
QMimeDatabase db;
QStringList ret;
for (const auto &mimeName : d->m_mimeTypes) {
if (db.mimeTypeForName(mimeName).isValid()) { // keep only mimetypes, filter out servicetypes
ret.append(mimeName);
}
}
return ret;
}
QStringList KService::schemeHandlers() const
{
Q_D(const KService);
QStringList ret;
const QLatin1String schemeHandlerPrefix("x-scheme-handler/");
for (const auto &mimeName : d->m_mimeTypes) {
if (mimeName.startsWith(schemeHandlerPrefix)) {
ret.append(mimeName.mid(schemeHandlerPrefix.size()));
}
}
return ret;
}
QStringList KService::supportedProtocols() const
{
Q_D(const KService);
QStringList ret;
ret << schemeHandlers();
const QStringList protocols = property<QStringList>(QStringLiteral("X-KDE-Protocols"));
for (const QString &protocol : protocols) {
if (!ret.contains(protocol)) {
ret.append(protocol);
}
}
return ret;
}
void KService::setTerminal(bool b)
{
Q_D(KService);
d->m_bTerminal = b;
}
void KService::setTerminalOptions(const QString &options)
{
Q_D(KService);
d->m_strTerminalOptions = options;
}
void KService::setExec(const QString &exec)
{
Q_D(KService);
if (!exec.isEmpty()) {
d->m_strExec = exec;
d->path.clear();
}
}
void KService::setWorkingDirectory(const QString &workingDir)
{
Q_D(KService);
if (!workingDir.isEmpty()) {
d->m_strWorkingDirectory = workingDir;
d->path.clear();
}
}
QList<KServiceAction> KService::actions() const
{
Q_D(const KService);
return d->m_actions;
}
QString KService::aliasFor() const
{
return KServiceUtilPrivate::completeBaseName(property<QString>(QStringLiteral("X-KDE-AliasFor")));
}
void KService::setActions(const QList<KServiceAction> &actions)
{
Q_D(KService);
d->m_actions = actions;
}
std::optional<bool> KService::startupNotify() const
{
Q_D(const KService);
if (QVariant value = d->m_mapProps.value(QStringLiteral("StartupNotify")); value.isValid()) {
return value.toBool();
}
if (QVariant value = d->m_mapProps.value(QStringLiteral("X-KDE-StartupNotify")); value.isValid()) {
return value.toBool();
}
return {};
}
@@ -0,0 +1,506 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 1999-2006 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KSERVICE_H
#define KSERVICE_H
#include "kserviceaction.h"
#include <QCoreApplication>
#include <QStringList>
#include <QVariant>
#include <kserviceconversioncheck_p.h>
#include <ksycocaentry.h>
#include <optional>
class QDataStream;
class KDesktopFile;
class QWidget;
class KServicePrivate;
/**
* @class KService kservice.h <KService>
*
* Represents an installed application.
*
* To obtain a KService instance for a specific application you typically use serviceByDesktopName(), e.g.:
*
* @code
* KService::Ptr service = KService::serviceByDesktopName("org.kde.kate");
* @endcode
*
* Other typical usage would be in combination with KApplicationTrader to obtain e.g. the default application for a given file type.
*
* @see <a href="https://specifications.freedesktop.org/desktop-entry-spec/latest/">Desktop Entry Specification</a>
*/
class KSERVICE_EXPORT KService : public KSycocaEntry
{
public:
/**
* A shared data pointer for KService.
*/
typedef QExplicitlySharedDataPointer<KService> Ptr;
/**
* A list of shared data pointers for KService.
*/
typedef QList<Ptr> List;
/**
* Construct a temporary service with a given name, exec-line and icon.
* @param name the name of the service
* @param exec the executable
* @param icon the name of the icon
*/
KService(const QString &name, const QString &exec, const QString &icon);
/**
* Construct a service and take all information from a .desktop file.
*
* @param fullpath Full path to the .desktop file.
*/
explicit KService(const QString &fullpath);
/**
* Construct a service and take all information from a desktop file.
* @param config the desktop file to read
* @param optional relative path to store for findByName
*/
explicit KService(const KDesktopFile *config, const QString &entryPath = QString());
KService(const KService &other);
~KService() override;
/**
* Whether this service is an application
* @return true if this service is an application, i.e. it has Type=Application in its
* .desktop file and exec() will not be empty.
*/
bool isApplication() const;
/**
* Returns the executable.
* @return the command that the service executes,
* or QString() if not set
*/
QString exec() const;
/**
* Returns the name of the icon.
* @return the icon associated with the service,
* or QString() if not set
*/
QString icon() const;
/**
* Checks whether the application should be run in a terminal.
*
* This corresponds to `Terminal=true` in the .desktop file.
*
* @return @c true if the application should be run in a terminal.
*/
bool terminal() const;
/**
* Returns any options associated with the terminal the application
* runs in, if it requires a terminal.
*
* The application must be a TTY-oriented program.
* @return the terminal options,
* or QString() if not set
*/
QString terminalOptions() const;
/**
* Returns @c true if the application indicates that it's preferred to run
* on a discrete graphics card, otherwise return @c false.
*
* In releases older than 5.86, this method checked for the @c X-KDE-RunOnDiscreteGpu
* key in the .desktop file represented by this service; starting from 5.86 this method
* now also checks for @c PrefersNonDefaultGPU key (added to the Freedesktop.org desktop
* entry spec in version 1.4 of the spec).
*
* @since 5.30
*/
bool runOnDiscreteGpu() const;
/**
* @brief Checks whether the application needs to run under a different UID.
* @return @c true if the application needs to run under a different UID.
* @see username()
*/
bool substituteUid() const;
/**
* Returns the user name if the application runs with a
* different user id.
* @return the username under which the service has to be run,
* or QString() if not set
* @see substituteUid()
*/
QString username() const;
/**
* Returns the filename of the desktop entry without any
* extension, e.g. "org.kde.kate"
* @return the name of the desktop entry without path or extension,
* or QString() if not set
*/
QString desktopEntryName() const;
/**
* Returns the menu ID of the application desktop entry.
* The menu ID is used to add or remove the entry to a menu.
* @return the menu ID
*/
QString menuId() const;
/**
* Returns a normalized ID suitable for storing in configuration files.
* It will be based on the menu-id when available and otherwise falls
* back to entryPath()
* @return the storage ID
*/
QString storageId() const;
/**
* @return the working directory to run the program in,
* or QString() if not set
* @since 5.63
*/
QString workingDirectory() const;
/**
* Returns the descriptive comment for the application, if there is one.
* @return the descriptive comment for the application, or QString()
* if not set
*/
QString comment() const;
/**
* Returns the generic name for the application, if there is one
* (e.g. "Mail Client").
* @return the generic name,
* or QString() if not set
*/
QString genericName() const;
/**
* Returns the untranslated (US English) generic name
* for the application, if there is one
* (e.g. "Mail Client").
* @return the generic name,
* or QString() if not set
*/
QString untranslatedGenericName() const;
/**
* @return untranslated name for the given service
*
* @since 6.0
*/
QString untranslatedName() const;
/**
* Returns a list of descriptive keywords for the application, if there are any.
* @return the list of keywords
*/
QStringList keywords() const;
/**
* Returns a list of VFolder categories.
* @return the list of VFolder categories
*/
QStringList categories() const;
/**
* Returns the list of MIME types that this application supports.
* Note that this doesn't include inherited MIME types,
* only the MIME types listed in the .desktop file.
* @since 4.8.3
*/
QStringList mimeTypes() const;
/**
* Returns the list of scheme handlers this application supports.
*
* For example a web browser could return {"http", "https"}.
*
* This is taken from the x-scheme-handler MIME types
* listed in the .desktop file.
*
* @since 6.0
*
*/
QStringList schemeHandlers() const;
/**
* Returns the list of protocols this application supports.
*
* This is taken from the x-scheme-handler MIME types
* listed in the .desktop file as well as the 'X-KDE-Protocols'
* entry
*
* For example a web browser could return {"http", "https"}.
* @since 6.0
*/
QStringList supportedProtocols() const;
/**
* Checks whether the application supports this MIME type
* @param mimeType The name of the MIME type you are
* interested in determining whether this service supports.
* @since 4.6
*/
bool hasMimeType(const QString &mimeType) const;
/**
* Returns the actions defined in this desktop file
*/
QList<KServiceAction> actions() const;
/**
* Checks whether this application can handle several files as
* startup arguments.
* @return true if multiple files may be passed to this service at
* startup. False if only one file at a time may be passed.
*/
bool allowMultipleFiles() const;
/**
* Whether the entry should be hidden from the menu.
* @return @c true to hide this application from the menu
*
* Such services still appear in trader queries, i.e. in
* "Open With" popup menus for instance.
*/
bool noDisplay() const;
/**
* Whether the application should be shown in the current desktop
* (including in context menus).
* @return true if the application should be shown in the current desktop.
*
* KApplicationTrader honors this and removes such services
* from its results.
*
* @since 5.0
*/
bool showInCurrentDesktop() const;
/**
* Whether the application should be shown on the current
* platform (e.g. on xcb or on wayland).
* @return @c true if the application should be shown on the current platform.
*
* @since 5.0
*/
bool showOnCurrentPlatform() const;
/**
* The path to the documentation for this application.
* @since 4.2
* @return the documentation path, or QString() if not set
*/
QString docPath() const;
/**
* Returns the requested property.
*
* @tparam T the type of the requested property.
*
* @param name the name of the property.
*
* @since 6.0
*/
template<typename T>
T property(const QString &name) const
{
KServiceConversionCheck::to_QVariant<T>();
return property(name, static_cast<QMetaType::Type>(qMetaTypeId<T>())).value<T>();
}
/**
* Returns a path that can be used for saving changes to this
* application
* @return path that can be used for saving changes to this application
*/
QString locateLocal() const;
/**
* @internal
* Set the menu id
*/
void setMenuId(const QString &menuId);
/**
* @internal
* Sets whether to use a terminal or not
*/
void setTerminal(bool b);
/**
* @internal
* Sets the terminal options to use
*/
void setTerminalOptions(const QString &options);
/**
* Overrides the "Exec=" line of the service.
*
* If @ref exec is not empty, its value will override the one
* the one set when this application was created.
*
* Please note that @ref entryPath is also cleared so the application
* will no longer be associated with a specific config file.
*
* @internal
* @since 4.11
*/
void setExec(const QString &exec);
/**
* Overrides the "Path=" line of the application.
*
* If @ref workingDir is not empty, its value will override
* the one set when this application was created.
*
* Please note that @ref entryPath is also cleared so the application
* will no longer be associated with a specific config file.
*
* @internal
* @param workingDir
* @since 5.79
*/
void setWorkingDirectory(const QString &workingDir);
/**
* Find a application based on its path as returned by entryPath().
* It's usually better to use serviceByStorageId() instead.
*
* @param _path the path of the configuration file
* @return a pointer to the requested application or @c nullptr if the application is
* unknown.
* @em Very @em important: Don't store the result in a KService* !
*/
static Ptr serviceByDesktopPath(const QString &_path);
/**
* Find an application by the name of its desktop file, not depending on
* its actual location (as long as it's under the applications or application
* directories). For instance "konqbrowser" or "kcookiejar". Note that
* the ".desktop" extension is implicit.
*
* This is the recommended method (safe even if the user moves stuff)
* but note that it assumes that no two entries have the same filename.
*
* @param _name the name of the configuration file
* @return a pointer to the requested application or @c nullptr if the application is
* unknown.
* @em Very @em important: Don't store the result in a KService* !
*/
static Ptr serviceByDesktopName(const QString &_name);
/**
* Find a application by its menu-id
*
* @param _menuId the menu id of the application
* @return a pointer to the requested application or @c nullptr if the application is
* unknown.
* @em Very @em important: Don't store the result in a KService* !
*/
static Ptr serviceByMenuId(const QString &_menuId);
/**
* Find a application by its storage-id or desktop-file path. This
* function will try very hard to find a matching application.
*
* @param _storageId the storage id or desktop-file path of the application
* @return a pointer to the requested application or @c nullptr if the application is
* unknown.
* @em Very @em important: Don't store the result in a KService* !
*/
static Ptr serviceByStorageId(const QString &_storageId);
/**
* Returns the whole list of applications.
*
* Useful for being able to
* to display them in a list box, for example.
* More memory consuming than the ones above, don't use unless
* really necessary.
* @return the list of all applications
*/
static List allServices();
/**
* Returns a path that can be used to create a new KService based
* on @p suggestedName.
* @param showInMenu @c true, if the application should be shown in the KDE menu
* @c false, if the application should be hidden from the menu
* This argument isn't used anymore, use `NoDisplay=true` to hide the application.
* @param suggestedName name to base the file on, if an application with such a
* name already exists, a suffix will be added to make it unique
* (e.g. foo.desktop, foo-1.desktop, foo-2.desktop).
* @param menuId If provided, menuId will be set to the menu id to use for
* the KService
* @param reservedMenuIds If provided, the path and menu id will be chosen
* in such a way that the new menu id does not conflict with any
* of the reservedMenuIds
* @return The path to use for the new KService.
*/
static QString newServicePath(bool showInMenu, const QString &suggestedName, QString *menuId = nullptr, const QStringList *reservedMenuIds = nullptr);
/**
* @brief A desktop file name that this application is an alias for.
*
* This is used when a `NoDisplay` application is used to enforce specific handling
* for an application. In that case the `NoDisplay` application is an `AliasFor` another
* application and be considered roughly equal to the `AliasFor` application (which should
* not be `NoDisplay=true`)
* For example Okular supplies a desktop file for each supported format (e.g. PDF), all
* of which `NoDisplay` and it is merely there to selectively support specific file formats.
* A UI may choose to display the aliased entry org.kde.okular instead of the NoDisplay entries.
*
* @since 5.96
*
* @return QString desktopName of the aliased application (excluding .desktop suffix)
*/
QString aliasFor() const;
/**
* Returns the value of StartupNotify for this service.
*
* If the service doesn't define a value nullopt is returned.
*
* See StartupNotify in the <a href="https://specifications.freedesktop.org/desktop-entry-spec/latest/">Desktop Entry Specification</a>.
*
* @since 6.0
*/
std::optional<bool> startupNotify() const;
private:
friend class KBuildServiceFactory;
QVariant property(const QString &_name, QMetaType::Type t) const;
void setActions(const QList<KServiceAction> &actions);
Q_DECLARE_PRIVATE(KService)
friend class KServiceFactory;
/**
* @internal
* Construct a service from a stream.
* The stream must already be positioned at the correct offset.
*/
KSERVICE_NO_EXPORT KService(QDataStream &str, int offset);
};
template<>
KSERVICE_EXPORT QString KService::property<QString>(const QString &name) const;
#endif
@@ -0,0 +1,84 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 1999-2006 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KSERVICEPRIVATE_H
#define KSERVICEPRIVATE_H
#include "kservice.h"
#include <QList>
#include <ksycocaentry_p.h>
class KServicePrivate : public KSycocaEntryPrivate
{
public:
K_SYCOCATYPE(KST_KService, KSycocaEntryPrivate)
explicit KServicePrivate(const QString &path)
: KSycocaEntryPrivate(path)
, m_bValid(true)
{
}
KServicePrivate(QDataStream &_str, int _offset)
: KSycocaEntryPrivate(_str, _offset)
, m_bValid(true)
{
load(_str);
}
KServicePrivate(const KServicePrivate &other) = default;
void init(const KDesktopFile *config, KService *q);
void parseActions(const KDesktopFile *config, KService *q);
void load(QDataStream &);
void save(QDataStream &) override;
QString name() const override
{
return m_strName;
}
QString storageId() const override
{
if (!menuId.isEmpty()) {
return menuId;
}
return path;
}
bool isValid() const override
{
return m_bValid;
}
QVariant property(const QString &_name, QMetaType::Type t) const;
QStringList categories;
QString menuId;
QString m_strType;
QString m_strName;
QString m_strExec;
QString m_strIcon;
QString m_strTerminalOptions;
QString m_strWorkingDirectory;
QString m_strComment;
QString m_strLibrary;
QStringList m_mimeTypes;
QString m_strDesktopEntryName;
QMap<QString, QVariant> m_mapProps;
QStringList m_lstFormFactors;
QStringList m_lstKeywords;
QString m_strGenName;
QString m_untranslatedGenericName;
QString m_untranslatedName;
QList<KServiceAction> m_actions;
bool m_bAllowAsDefault : 1;
bool m_bTerminal : 1;
bool m_bValid : 1;
};
#endif
@@ -0,0 +1,155 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kserviceaction.h"
#include "kservice.h"
#include "ksycoca.h"
#include "ksycoca_p.h"
#include <QDataStream>
#include <QVariant>
#include <KConfigGroup>
class KServiceActionPrivate : public QSharedData
{
public:
KServiceActionPrivate(const QString &name, const QString &text, const QString &icon, const QString &exec, bool noDisplay)
: m_name(name)
, m_text(text)
, m_icon(icon)
, m_exec(exec)
, m_noDisplay(noDisplay)
{
}
QString m_name;
QString m_text;
QString m_icon;
QString m_exec;
QVariant m_data;
bool m_noDisplay;
KServicePtr m_service;
// warning keep QDataStream operators in sync if adding data here
};
KServiceAction::KServiceAction()
: d(new KServiceActionPrivate(QString(), QString(), QString(), QString(), false))
{
}
KServiceAction::KServiceAction(const QString &name, const QString &text, const QString &icon, const QString &exec, bool noDisplay, const KServicePtr &service)
: d(new KServiceActionPrivate(name, text, icon, exec, noDisplay))
{
d->m_service = service;
}
KServiceAction::~KServiceAction()
{
}
KServiceAction::KServiceAction(const KServiceAction &other)
: d(other.d)
{
}
KServiceAction &KServiceAction::operator=(const KServiceAction &other)
{
d = other.d;
return *this;
}
QVariant KServiceAction::data() const
{
return d->m_data;
}
void KServiceAction::setData(const QVariant &data)
{
d->m_data = data;
}
QString KServiceAction::name() const
{
return d->m_name;
}
QString KServiceAction::text() const
{
return d->m_text;
}
QString KServiceAction::icon() const
{
return d->m_icon;
}
QString KServiceAction::exec() const
{
return d->m_exec;
}
bool KServiceAction::noDisplay() const
{
return d->m_noDisplay;
}
bool KServiceAction::isSeparator() const
{
return d->m_name == QLatin1String("_SEPARATOR_");
}
KServicePtr KServiceAction::service() const
{
return d->m_service;
}
void KServiceAction::setService(const KServicePtr &service)
{
d->m_service = service;
}
QDataStream &operator>>(QDataStream &str, KServiceAction &act)
{
KServiceActionPrivate *d = act.d;
str >> d->m_name;
str >> d->m_text;
str >> d->m_icon;
str >> d->m_exec;
str >> d->m_data;
str >> d->m_noDisplay;
return str;
}
QDataStream &operator<<(QDataStream &str, const KServiceAction &act)
{
const KServiceActionPrivate *d = act.d;
str << d->m_name;
str << d->m_text;
str << d->m_icon;
str << d->m_exec;
str << d->m_data;
str << d->m_noDisplay;
return str;
}
QVariant KServiceAction::property(const QString &_name, QMetaType::Type type) const
{
const auto dataMap = d->m_data.toMap();
auto it = dataMap.constFind(_name);
if (it == dataMap.cend() || !it.value().isValid()) {
return QVariant(); // No property set.
}
if (type == QMetaType::QString) {
return it.value(); // no conversion necessary
} else {
// All others
// For instance properties defined as StringList, like MimeTypes.
// XXX This API is accessible only through a friend declaration.
return KConfigGroup::convertToQVariant(_name.toUtf8().constData(), it.value().toString().toUtf8(), QVariant(QMetaType(type)));
}
}
@@ -0,0 +1,139 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KSERVICEACTION_H
#define KSERVICEACTION_H
#include <QSharedDataPointer>
#include <QVariant>
#include <kservice_export.h>
#include <kserviceconversioncheck_p.h>
class QVariant;
class KServiceActionPrivate;
class KService;
// we can't include kservice.h, it includes this header...
typedef QExplicitlySharedDataPointer<KService> KServicePtr;
/**
* @class KServiceAction kserviceaction.h <KServiceAction>
*
* Represents an action in a .desktop file
* Actions are defined with the config key Actions in the [Desktop Entry]
* group, followed by one group per action, as per the desktop entry standard.
* @see KService::actions
*/
class KSERVICE_EXPORT KServiceAction
{
public:
/**
* Creates a KServiceAction.
* Normally you don't have to do this, KService creates the actions
* when parsing the .desktop file.
* @since 5.69
*/
KServiceAction(const QString &name, const QString &text, const QString &icon, const QString &exec, bool noDisplay, const KServicePtr &service);
/**
* @internal
* Needed for operator>>
*/
KServiceAction();
/**
* Destroys a KServiceAction.
*/
~KServiceAction();
/**
* Copy constructor
*/
KServiceAction(const KServiceAction &other);
/**
* Assignment operator
*/
KServiceAction &operator=(const KServiceAction &other);
/**
* Sets the action's internal data to the given @p userData.
*/
void setData(const QVariant &userData);
/**
* @return the action's internal data.
*/
QVariant data() const;
/**
* @return the action's internal name
* For instance Actions=Setup;... and the group [Desktop Action Setup]
* define an action with the name "Setup".
*/
QString name() const;
/**
* @return the action's text, as defined by the Name key in the desktop action group
*/
QString text() const;
/**
* @return the action's icon, as defined by the Icon key in the desktop action group
*/
QString icon() const;
/**
* @return the action's exec command, as defined by the Exec key in the desktop action group
*/
QString exec() const;
/**
* Returns whether the action should be suppressed in menus.
* This is useful for having actions with a known name that the code
* looks for explicitly, like Setup and Root for kscreensaver actions,
* and which should not appear in popup menus.
* @return true to suppress this service
*/
bool noDisplay() const;
/**
* Returns whether the action is a separator.
* This is true when the Actions key contains "_SEPARATOR_".
*/
bool isSeparator() const;
/**
* Returns the service that this action comes from
* @since 5.69
*/
KServicePtr service() const;
/**
* Returns the requested property.
*
* @tparam T the type of the requested property
* @param name the name of the requested property
* @return the property
* @since 6.0
*/
template<typename T>
T property(const QString &name) const
{
KServiceConversionCheck::to_QVariant<T>();
return property(name, static_cast<QMetaType::Type>(qMetaTypeId<T>())).value<T>();
}
private:
QSharedDataPointer<KServiceActionPrivate> d;
friend KSERVICE_EXPORT QDataStream &operator>>(QDataStream &str, KServiceAction &act);
friend KSERVICE_EXPORT QDataStream &operator<<(QDataStream &str, const KServiceAction &act);
friend class KService;
KSERVICE_NO_EXPORT void setService(const KServicePtr &service);
QVariant property(const QString &_name, QMetaType::Type type) const;
};
KSERVICE_EXPORT QDataStream &operator>>(QDataStream &str, KServiceAction &act);
KSERVICE_EXPORT QDataStream &operator<<(QDataStream &str, const KServiceAction &act);
#endif /* KSERVICEACTION_H */
@@ -0,0 +1,115 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2006 Thomas Braxton <brax108@cox.net>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KSERVICE_CONVERSION_CHECK_P_H
#define KSERVICE_CONVERSION_CHECK_P_H
#include <QDate>
#include <QPoint>
#include <QRect>
#include <QSize>
#include <QString>
#include <QUuid>
#include <QVariant>
class QColor;
class QFont;
// Copied from kconfigconversioncheck_p.h in KConfig
namespace KServiceConversionCheck
{
// used to distinguish between supported/unsupported types
struct supported {
};
struct unsupported {
};
// traits type class to define support for constraints
template<typename T>
struct QVconvertible {
typedef unsupported toQString;
typedef unsupported toQVariant;
};
// constraint classes
template<typename T>
struct type_toQString {
void constraint()
{
supported x = y;
Q_UNUSED(x);
}
typename QVconvertible<T>::toQString y;
};
template<typename T>
struct type_toQVariant {
void constraint()
{
supported x = y;
Q_UNUSED(x);
}
typename QVconvertible<T>::toQVariant y;
};
// check if T is convertible to QString thru QVariant
// if not supported can't be used in QList<T> functions
template<typename T>
inline void to_QString()
{
void (type_toQString<T>::*x)() = &type_toQString<T>::constraint;
Q_UNUSED(x);
}
// check if T is convertible to QVariant & supported in readEntry/writeEntry
template<typename T>
inline void to_QVariant()
{
void (type_toQVariant<T>::*x)() = &type_toQVariant<T>::constraint;
Q_UNUSED(x);
}
// define for all types handled in readEntry/writeEntry
// string_support - is supported by QVariant(type).toString(),
// can be used in QList<T> functions
// variant_support - has a QVariant constructor
#define QVConversions(type, string_support, variant_support) \
template<> \
struct QVconvertible<type> { \
typedef string_support toQString; \
typedef variant_support toQVariant; \
}
// The only types needed here are the types handled in readEntry/writeEntry
// the default QVconvertible will take care of the rest.
QVConversions(bool, supported, supported);
QVConversions(int, supported, supported);
QVConversions(unsigned int, supported, supported);
QVConversions(long long, supported, supported);
QVConversions(unsigned long long, supported, supported);
QVConversions(float, supported, supported);
QVConversions(double, supported, supported);
QVConversions(QString, supported, supported);
QVConversions(QColor, unsupported, supported);
QVConversions(QFont, supported, supported);
QVConversions(QDateTime, unsupported, supported);
QVConversions(QDate, unsupported, supported);
QVConversions(QSize, unsupported, supported);
QVConversions(QRect, unsupported, supported);
QVConversions(QPoint, unsupported, supported);
QVConversions(QSizeF, unsupported, supported);
QVConversions(QRectF, unsupported, supported);
QVConversions(QPointF, unsupported, supported);
QVConversions(QByteArray, supported, supported);
QVConversions(QStringList, unsupported, supported);
QVConversions(QVariantList, unsupported, supported);
QVConversions(QUrl, supported, supported);
QVConversions(QList<QUrl>, unsupported, supported);
QVConversions(QUuid, supported, supported);
}
#endif
@@ -0,0 +1,347 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999-2006 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kservice.h"
#include "kservicefactory_p.h"
#include "ksycoca.h"
#include "ksycocadict_p.h"
#include "ksycocatype.h"
#include "servicesdebug.h"
#include <QDir>
#include <QFile>
extern int servicesDebugArea();
KServiceFactory::KServiceFactory(KSycoca *db)
: KSycocaFactory(KST_KServiceFactory, db)
, m_nameDict(nullptr)
, m_relNameDict(nullptr)
, m_menuIdDict(nullptr)
{
m_offerListOffset = 0;
m_nameDictOffset = 0;
m_relNameDictOffset = 0;
m_menuIdDictOffset = 0;
if (!sycoca()->isBuilding()) {
QDataStream *str = stream();
if (!str) {
qWarning() << "Could not open sycoca database, you must run kbuildsycoca first!";
return;
}
// Read Header
qint32 i;
(*str) >> i;
m_nameDictOffset = i;
(*str) >> i;
m_relNameDictOffset = i;
(*str) >> i;
m_offerListOffset = i;
(*str) >> i;
m_menuIdDictOffset = i;
const qint64 saveOffset = str->device()->pos();
// Init index tables
m_nameDict = new KSycocaDict(str, m_nameDictOffset);
// Init index tables
m_relNameDict = new KSycocaDict(str, m_relNameDictOffset);
// Init index tables
m_menuIdDict = new KSycocaDict(str, m_menuIdDictOffset);
str->device()->seek(saveOffset);
}
}
KServiceFactory::~KServiceFactory()
{
delete m_nameDict;
delete m_relNameDict;
delete m_menuIdDict;
}
KService::Ptr KServiceFactory::findServiceByName(const QString &_name)
{
if (!sycocaDict()) {
return KService::Ptr(); // Error!
}
// Warning : this assumes we're NOT building a database
// But since findServiceByName isn't called in that case...
// [ see KServiceTypeFactory for how to do it if needed ]
int offset = sycocaDict()->find_string(_name);
if (!offset) {
return KService::Ptr(); // Not found
}
KService::Ptr newService(createEntry(offset));
// Check whether the dictionary was right.
if (newService && (newService->name() != _name)) {
// No it wasn't...
return KService::Ptr();
}
return newService;
}
KService::Ptr KServiceFactory::findServiceByDesktopName(const QString &_name)
{
if (!m_nameDict) {
return KService::Ptr(); // Error!
}
// Warning : this assumes we're NOT building a database
// KBuildServiceFactory reimplements it for the case where we are building one
int offset = m_nameDict->find_string(_name);
if (!offset) {
return KService::Ptr(); // Not found
}
KService::Ptr newService(createEntry(offset));
// Check whether the dictionary was right.
if (newService && (newService->desktopEntryName() != _name)) {
// No it wasn't...
return KService::Ptr();
}
return newService;
}
KService::Ptr KServiceFactory::findServiceByDesktopPath(const QString &_name)
{
if (!m_relNameDict) {
return KService::Ptr(); // Error!
}
// Warning : this assumes we're NOT building a database
// KBuildServiceFactory reimplements it for the case where we are building one
int offset = m_relNameDict->find_string(_name);
if (!offset) {
// qCDebug(SERVICES) << "findServiceByDesktopPath:" << _name << "not found";
return KService::Ptr(); // Not found
}
KService::Ptr newService(createEntry(offset));
if (!newService) {
qCDebug(SERVICES) << "createEntry failed!";
}
// Check whether the dictionary was right
// It's ok that it's wrong, for the case where we're looking up an unknown service,
// and the hash value gave us another one.
if (newService && (newService->entryPath() != _name)) {
// No it wasn't...
return KService::Ptr();
}
return newService;
}
KService::Ptr KServiceFactory::findServiceByMenuId(const QString &_menuId)
{
if (!m_menuIdDict) {
return KService::Ptr(); // Error!
}
// Warning : this assumes we're NOT building a database
// KBuildServiceFactory reimplements it for the case where we are building one
int offset = m_menuIdDict->find_string(_menuId);
if (!offset) {
return KService::Ptr(); // Not found
}
KService::Ptr newService(createEntry(offset));
// Check whether the dictionary was right.
if (newService && (newService->menuId() != _menuId)) {
// No it wasn't...
return KService::Ptr();
}
return newService;
}
KService::Ptr KServiceFactory::findServiceByStorageId(const QString &_storageId)
{
KService::Ptr service = findServiceByMenuId(_storageId);
if (service) {
return service;
}
service = findServiceByDesktopPath(_storageId);
if (service) {
return service;
}
if (!QDir::isRelativePath(_storageId) && QFile::exists(_storageId)) {
return KService::Ptr(new KService(_storageId));
}
QString tmp = _storageId;
tmp = tmp.mid(tmp.lastIndexOf(QLatin1Char('/')) + 1); // Strip dir
if (tmp.endsWith(QLatin1String(".desktop"))) {
tmp.chop(8);
}
if (tmp.endsWith(QLatin1String(".kdelnk"))) {
tmp.chop(7);
}
service = findServiceByDesktopName(tmp);
return service;
}
KService *KServiceFactory::createEntry(int offset) const
{
KSycocaType type;
QDataStream *str = sycoca()->findEntry(offset, type);
if (type != KST_KService) {
qCWarning(SERVICES) << "KServiceFactory: unexpected object entry in KSycoca database (type=" << int(type) << ")";
return nullptr;
}
KService *newEntry = new KService(*str, offset);
if (!newEntry->isValid()) {
qCWarning(SERVICES) << "KServiceFactory: corrupt object in KSycoca database!";
delete newEntry;
newEntry = nullptr;
}
return newEntry;
}
KService::List KServiceFactory::allServices()
{
KService::List result;
const KSycocaEntry::List list = allEntries();
for (const auto &entryPtr : list) {
if (entryPtr->isType(KST_KService)) {
result.append(KService::Ptr(static_cast<KService *>(entryPtr.data())));
}
}
return result;
}
QStringList KServiceFactory::resourceDirs()
{
return KSycocaFactory::allDirectories(QStringLiteral("applications"));
}
QList<KServiceOffer> KServiceFactory::offers(int serviceTypeOffset, int serviceOffersOffset)
{
QList<KServiceOffer> list;
// Jump to the offer list
QDataStream *str = stream();
str->device()->seek(m_offerListOffset + serviceOffersOffset);
qint32 aServiceTypeOffset;
qint32 aServiceOffset;
qint32 offerPreference;
qint32 mimeTypeInheritanceLevel;
while (true) {
(*str) >> aServiceTypeOffset;
if (aServiceTypeOffset) {
(*str) >> aServiceOffset;
(*str) >> offerPreference;
(*str) >> mimeTypeInheritanceLevel;
if (aServiceTypeOffset == serviceTypeOffset) {
// Save stream position !
const qint64 savedPos = str->device()->pos();
// Create Service
KService *serv = createEntry(aServiceOffset);
if (serv) {
KService::Ptr servPtr(serv);
list.append(KServiceOffer(servPtr, 1, mimeTypeInheritanceLevel));
}
// Restore position
str->device()->seek(savedPos);
} else {
break; // too far
}
} else {
break; // 0 => end of list
}
}
return list;
}
KService::List KServiceFactory::serviceOffers(int serviceTypeOffset, int serviceOffersOffset)
{
KService::List list;
// Jump to the offer list
QDataStream *str = stream();
str->device()->seek(m_offerListOffset + serviceOffersOffset);
qint32 aServiceTypeOffset;
qint32 aServiceOffset;
qint32 offerPreference;
qint32 mimeTypeInheritanceLevel;
while (true) {
(*str) >> aServiceTypeOffset;
if (aServiceTypeOffset) {
(*str) >> aServiceOffset;
(*str) >> offerPreference; // unused (remove once KMimeTypeTrader/KServiceTypeTrader are gone)
(*str) >> mimeTypeInheritanceLevel; // unused (remove once KMimeTypeTrader/KServiceTypeTrader are gone)
if (aServiceTypeOffset == serviceTypeOffset) {
// Save stream position !
const qint64 savedPos = str->device()->pos();
// Create service
KService *serv = createEntry(aServiceOffset);
if (serv) {
list.append(KService::Ptr(serv));
}
// Restore position
str->device()->seek(savedPos);
} else {
break; // too far
}
} else {
break; // 0 => end of list
}
}
return list;
}
bool KServiceFactory::hasOffer(int serviceTypeOffset, int serviceOffersOffset, int testedServiceOffset)
{
// Save stream position
QDataStream *str = stream();
const qint64 savedPos = str->device()->pos();
// Jump to the offer list
str->device()->seek(m_offerListOffset + serviceOffersOffset);
bool found = false;
qint32 aServiceTypeOffset;
qint32 aServiceOffset;
qint32 offerPreference;
qint32 mimeTypeInheritanceLevel;
while (!found) {
(*str) >> aServiceTypeOffset;
if (aServiceTypeOffset) {
(*str) >> aServiceOffset;
(*str) >> offerPreference;
(*str) >> mimeTypeInheritanceLevel;
if (aServiceTypeOffset == serviceTypeOffset) {
if (aServiceOffset == testedServiceOffset) {
found = true;
}
} else {
break; // too far
}
} else {
break; // 0 => end of list
}
}
// Restore position
str->device()->seek(savedPos);
return found;
}
void KServiceFactory::virtual_hook(int id, void *data)
{
KSycocaFactory::virtual_hook(id, data);
}
@@ -0,0 +1,125 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 2006 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KSERVICEFACTORY_P_H
#define KSERVICEFACTORY_P_H
#include <QStringList>
#include "kserviceoffer.h"
#include "ksycocafactory_p.h"
#include <assert.h>
class KSycoca;
class KSycocaDict;
/**
* @internal
* A sycoca factory for services (e.g. applications)
* It loads the services from parsing directories (e.g. prefix/share/applications/)
* but can also create service from data streams or single config files
*
* Exported for unit tests
*/
class KSERVICE_EXPORT KServiceFactory : public KSycocaFactory
{
K_SYCOCAFACTORY(KST_KServiceFactory)
public:
/**
* Create factory
*/
explicit KServiceFactory(KSycoca *sycoca);
~KServiceFactory() override;
/**
* Construct a KService from a config file.
*/
KSycocaEntry *createEntry(const QString &) const override
{
assert(0);
return nullptr;
}
/**
* Find a service (by translated name, e.g. "Terminal")
* (Not virtual because not used inside kbuildsycoca4, only an external service for some KDE apps)
*/
KService::Ptr findServiceByName(const QString &_name);
/**
* Find a service (by desktop file name, e.g. "konsole")
*/
virtual KService::Ptr findServiceByDesktopName(const QString &_name);
/**
* Find a service ( by desktop path, e.g. "System/konsole.desktop")
*/
virtual KService::Ptr findServiceByDesktopPath(const QString &_name);
/**
* Find a service ( by menu id, e.g. "kde-konsole.desktop")
*/
virtual KService::Ptr findServiceByMenuId(const QString &_menuId);
KService::Ptr findServiceByStorageId(const QString &_storageId);
/**
* @return the services supporting the given service type
* The @p serviceOffersOffset allows to jump to the right entries directly.
*/
KServiceOfferList offers(int serviceTypeOffset, int serviceOffersOffset);
/**
* @return the services supporting the given service type
* The @p serviceOffersOffset allows to jump to the right entries directly.
*/
KService::List serviceOffers(int serviceTypeOffset, int serviceOffersOffset);
/**
* Test if a specific service is associated with a specific servicetype
* @param serviceTypeOffset the offset of the service type being tested
* @param serviceOffersOffset allows to jump to the right entries for the service type directly.
* @param testedServiceOffset the offset of the service being tested
*/
bool hasOffer(int serviceTypeOffset, int serviceOffersOffset, int testedServiceOffset);
/**
* @return all services. Very memory consuming, avoid using.
*/
KService::List allServices();
/**
* Returns the directories to watch for this factory.
*/
static QStringList resourceDirs();
/**
* @return the unique service factory, creating it if necessary
*/
static KServiceFactory *self();
protected:
KService *createEntry(int offset) const override;
// All those variables are used by KBuildServiceFactory too
int m_offerListOffset;
KSycocaDict *m_nameDict;
int m_nameDictOffset;
KSycocaDict *m_relNameDict;
int m_relNameDictOffset;
KSycocaDict *m_menuIdDict;
int m_menuIdDictOffset;
protected:
void virtual_hook(int id, void *data) override;
private:
class KServiceFactoryPrivate *d;
};
#endif
@@ -0,0 +1,697 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kservicegroup.h"
#include "kservice.h"
#include "kservicefactory_p.h"
#include "kservicegroup_p.h"
#include "kservicegroupfactory_p.h"
#include "ksycoca_p.h"
#include "servicesdebug.h"
#include <KConfigGroup>
#include <KDesktopFile>
#include <ksycoca.h>
KServiceGroup::KServiceGroup(const QString &name)
: KSycocaEntry(*new KServiceGroupPrivate(name))
{
}
KServiceGroup::KServiceGroup(const QString &configFile, const QString &_relpath)
: KSycocaEntry(*new KServiceGroupPrivate(_relpath))
{
Q_D(KServiceGroup);
QString cfg = configFile;
if (cfg.isEmpty()) {
cfg = _relpath + QLatin1String(".directory");
}
d->load(cfg);
}
void KServiceGroupPrivate::load(const QString &cfg)
{
directoryEntryPath = cfg;
const KDesktopFile desktopFile(cfg);
const KConfigGroup config = desktopFile.desktopGroup();
m_strCaption = config.readEntry("Name");
m_strIcon = config.readEntry("Icon");
m_strComment = config.readEntry("Comment");
deleted = config.readEntry("Hidden", false);
m_bNoDisplay = desktopFile.noDisplay();
m_strBaseGroupName = config.readEntry("X-KDE-BaseGroup");
suppressGenericNames = config.readEntry("X-KDE-SuppressGenericNames", QStringList());
// Fill in defaults.
if (m_strCaption.isEmpty()) {
m_strCaption = path;
if (m_strCaption.endsWith(QLatin1Char('/'))) {
m_strCaption.chop(1);
}
int i = m_strCaption.lastIndexOf(QLatin1Char('/'));
if (i > 0) {
m_strCaption.remove(0, i + 1);
}
}
if (m_strIcon.isEmpty()) {
m_strIcon = QStringLiteral("folder");
}
}
KServiceGroup::KServiceGroup(QDataStream &_str, int offset, bool deep)
: KSycocaEntry(*new KServiceGroupPrivate(_str, offset))
{
Q_D(KServiceGroup);
d->m_bDeep = deep;
d->load(_str);
}
KServiceGroup::~KServiceGroup()
{
}
QString KServiceGroup::relPath() const
{
return entryPath();
}
QString KServiceGroup::caption() const
{
Q_D(const KServiceGroup);
return d->m_strCaption;
}
QString KServiceGroup::icon() const
{
Q_D(const KServiceGroup);
return d->m_strIcon;
}
QString KServiceGroup::comment() const
{
Q_D(const KServiceGroup);
return d->m_strComment;
}
int KServiceGroup::childCount() const
{
Q_D(const KServiceGroup);
return d->childCount();
}
int KServiceGroupPrivate::childCount() const
{
if (m_childCount == -1) {
m_childCount = 0;
for (const KSycocaEntry::Ptr &entryPtr : m_serviceList) {
if (entryPtr->isType(KST_KService)) {
KService::Ptr service(static_cast<KService *>(entryPtr.data()));
if (!service->noDisplay()) {
m_childCount++;
}
} else if (entryPtr->isType(KST_KServiceGroup)) {
KServiceGroup::Ptr serviceGroup(static_cast<KServiceGroup *>(entryPtr.data()));
m_childCount += serviceGroup->childCount();
}
}
}
return m_childCount;
}
bool KServiceGroup::showInlineHeader() const
{
Q_D(const KServiceGroup);
return d->m_bShowInlineHeader;
}
bool KServiceGroup::showEmptyMenu() const
{
Q_D(const KServiceGroup);
return d->m_bShowEmptyMenu;
}
bool KServiceGroup::inlineAlias() const
{
Q_D(const KServiceGroup);
return d->m_bInlineAlias;
}
void KServiceGroup::setInlineAlias(bool _b)
{
Q_D(KServiceGroup);
d->m_bInlineAlias = _b;
}
void KServiceGroup::setShowEmptyMenu(bool _b)
{
Q_D(KServiceGroup);
d->m_bShowEmptyMenu = _b;
}
void KServiceGroup::setShowInlineHeader(bool _b)
{
Q_D(KServiceGroup);
d->m_bShowInlineHeader = _b;
}
int KServiceGroup::inlineValue() const
{
Q_D(const KServiceGroup);
return d->m_inlineValue;
}
void KServiceGroup::setInlineValue(int _val)
{
Q_D(KServiceGroup);
d->m_inlineValue = _val;
}
bool KServiceGroup::allowInline() const
{
Q_D(const KServiceGroup);
return d->m_bAllowInline;
}
void KServiceGroup::setAllowInline(bool _b)
{
Q_D(KServiceGroup);
d->m_bAllowInline = _b;
}
bool KServiceGroup::noDisplay() const
{
Q_D(const KServiceGroup);
return d->m_bNoDisplay || d->m_strCaption.startsWith(QLatin1Char('.'));
}
QStringList KServiceGroup::suppressGenericNames() const
{
Q_D(const KServiceGroup);
return d->suppressGenericNames;
}
void KServiceGroupPrivate::load(QDataStream &s)
{
QStringList groupList;
qint8 noDisplay;
qint8 _showEmptyMenu;
qint8 inlineHeader;
qint8 _inlineAlias;
qint8 _allowInline;
s >> m_strCaption >> m_strIcon >> m_strComment >> groupList >> m_strBaseGroupName >> m_childCount >> noDisplay >> suppressGenericNames >> directoryEntryPath
>> sortOrder >> _showEmptyMenu >> inlineHeader >> _inlineAlias >> _allowInline;
m_bNoDisplay = (noDisplay != 0);
m_bShowEmptyMenu = (_showEmptyMenu != 0);
m_bShowInlineHeader = (inlineHeader != 0);
m_bInlineAlias = (_inlineAlias != 0);
m_bAllowInline = (_allowInline != 0);
if (m_bDeep) {
for (const QString &path : std::as_const(groupList)) {
if (path.endsWith(QLatin1Char('/'))) {
KServiceGroup::Ptr serviceGroup;
serviceGroup = KSycocaPrivate::self()->serviceGroupFactory()->findGroupByDesktopPath(path, false);
if (serviceGroup) {
m_serviceList.append(KServiceGroup::SPtr(serviceGroup));
}
} else {
KService::Ptr service;
service = KSycocaPrivate::self()->serviceFactory()->findServiceByDesktopPath(path);
if (service) {
m_serviceList.append(KServiceGroup::SPtr(service));
}
}
}
}
}
void KServiceGroup::addEntry(const KSycocaEntry::Ptr &entry)
{
Q_D(KServiceGroup);
d->m_serviceList.append(entry);
}
void KServiceGroupPrivate::save(QDataStream &s)
{
KSycocaEntryPrivate::save(s);
QStringList groupList;
for (const KSycocaEntry::Ptr &p : std::as_const(m_serviceList)) {
if (p->isType(KST_KService)) {
KService::Ptr service(static_cast<KService *>(p.data()));
groupList.append(service->entryPath());
} else if (p->isType(KST_KServiceGroup)) {
KServiceGroup::Ptr serviceGroup(static_cast<KServiceGroup *>(p.data()));
groupList.append(serviceGroup->relPath());
} else {
// fprintf(stderr, "KServiceGroup: Unexpected object in list!\n");
}
}
(void)childCount();
qint8 noDisplay = m_bNoDisplay ? 1 : 0;
qint8 _showEmptyMenu = m_bShowEmptyMenu ? 1 : 0;
qint8 inlineHeader = m_bShowInlineHeader ? 1 : 0;
qint8 _inlineAlias = m_bInlineAlias ? 1 : 0;
qint8 _allowInline = m_bAllowInline ? 1 : 0;
s << m_strCaption << m_strIcon << m_strComment << groupList << m_strBaseGroupName << m_childCount << noDisplay << suppressGenericNames << directoryEntryPath
<< sortOrder << _showEmptyMenu << inlineHeader << _inlineAlias << _allowInline;
}
QList<KServiceGroup::Ptr> KServiceGroup::groupEntries(EntriesOptions options)
{
Q_D(KServiceGroup);
bool sort = options & SortEntries || options & AllowSeparators;
QList<KServiceGroup::Ptr> list;
const List tmp = d->entries(this, sort, options & ExcludeNoDisplay, options & AllowSeparators, options & SortByGenericName);
for (const SPtr &ptr : tmp) {
if (ptr->isType(KST_KServiceGroup)) {
KServiceGroup::Ptr serviceGroup(static_cast<KServiceGroup *>(ptr.data()));
list.append(serviceGroup);
} else if (ptr->isType(KST_KServiceSeparator)) {
list.append(KServiceGroup::Ptr(static_cast<KServiceGroup *>(new KSycocaEntry())));
} else if (sort && ptr->isType(KST_KService)) {
break;
}
}
return list;
}
KService::List KServiceGroup::serviceEntries(EntriesOptions options)
{
Q_D(KServiceGroup);
bool sort = options & SortEntries || options & AllowSeparators;
QList<KService::Ptr> list;
const List tmp = d->entries(this, sort, options & ExcludeNoDisplay, options & AllowSeparators, options & SortByGenericName);
bool foundService = false;
for (const SPtr &ptr : tmp) {
if (ptr->isType(KST_KService)) {
list.append(KService::Ptr(static_cast<KService *>(ptr.data())));
foundService = true;
} else if (ptr->isType(KST_KServiceSeparator) && foundService) {
list.append(KService::Ptr(static_cast<KService *>(new KSycocaEntry())));
}
}
return list;
}
KServiceGroup::List KServiceGroup::entries(bool sort)
{
Q_D(KServiceGroup);
return d->entries(this, sort, true, false, false);
}
KServiceGroup::List KServiceGroup::entries(bool sort, bool excludeNoDisplay)
{
Q_D(KServiceGroup);
return d->entries(this, sort, excludeNoDisplay, false, false);
}
KServiceGroup::List KServiceGroup::entries(bool sort, bool excludeNoDisplay, bool allowSeparators, bool sortByGenericName)
{
Q_D(KServiceGroup);
return d->entries(this, sort, excludeNoDisplay, allowSeparators, sortByGenericName);
}
static void addItem(KServiceGroup::List &sorted, const KSycocaEntry::Ptr &p, bool &addSeparator)
{
if (addSeparator && !sorted.isEmpty()) {
sorted.append(KSycocaEntry::Ptr(new KServiceSeparator()));
}
sorted.append(p);
addSeparator = false;
}
KServiceGroup::List KServiceGroupPrivate::entries(KServiceGroup *group, bool sort, bool excludeNoDisplay, bool allowSeparators, bool sortByGenericName)
{
KSycoca::self()->ensureCacheValid();
// If the entries haven't been loaded yet, we have to reload ourselves
// together with the entries. We can't only load the entries afterwards
// since the offsets could have been changed if the database has changed.
KServiceGroup::Ptr grp;
if (!m_bDeep) {
grp = KSycocaPrivate::self()->serviceGroupFactory()->findGroupByDesktopPath(path, true);
group = grp.data();
if (nullptr == group) { // No guarantee that we still exist!
return KServiceGroup::List();
}
}
if (!sort) {
return group->d_func()->m_serviceList;
}
// Sort the list alphabetically, according to locale.
// Groups come first, then services.
// We use a QMap, for sorting using a stored temporary key.
typedef QMap<QByteArray, KServiceGroup::SPtr> SortedContainer;
SortedContainer slist;
SortedContainer glist;
const auto listService = group->d_func()->m_serviceList;
for (const KSycocaEntry::Ptr &p : listService) {
bool noDisplay = p->isType(KST_KServiceGroup) ? static_cast<KServiceGroup *>(p.data())->noDisplay() : static_cast<KService *>(p.data())->noDisplay();
if (excludeNoDisplay && noDisplay) {
continue;
}
// Choose the right list
SortedContainer &list = p->isType(KST_KServiceGroup) ? glist : slist;
QString name;
if (p->isType(KST_KServiceGroup)) {
name = static_cast<KServiceGroup *>(p.data())->caption();
} else if (sortByGenericName) {
name = static_cast<KService *>(p.data())->genericName() + QLatin1Char(' ') + p->name();
} else {
name = p->name() + QLatin1Char(' ') + static_cast<KService *>(p.data())->genericName();
}
const QByteArray nameStr = name.toLocal8Bit();
QByteArray key;
// strxfrm() crashes on Solaris and strxfrm is not defined under wince
#if !defined(USE_SOLARIS) && !defined(_WIN32_WCE)
// maybe it'd be better to use wcsxfrm() where available
key.resize(name.length() * 4 + 1);
size_t ln = strxfrm(key.data(), nameStr.constData(), key.size());
if (ln != size_t(-1)) {
key.resize(ln);
if (int(ln) >= key.size()) {
// didn't fit?
ln = strxfrm(key.data(), nameStr.constData(), key.size());
if (ln == size_t(-1)) {
key = nameStr;
}
}
} else
#endif
{
key = nameStr;
}
list.insert(key, KServiceGroup::SPtr(p));
}
if (sortOrder.isEmpty()) {
sortOrder << QStringLiteral(":M");
sortOrder << QStringLiteral(":F");
sortOrder << QStringLiteral(":OIH IL[4]"); // just inline header
}
QString rp = path;
if (rp == QLatin1String("/")) {
rp.clear();
}
// Iterate through the sort spec list.
// If an entry gets mentioned explicitly, we remove it from the sorted list
for (const QString &item : std::as_const(sortOrder)) {
if (item.isEmpty()) {
continue;
}
if (item[0] == QLatin1Char('/')) {
QString groupPath = rp + QStringView(item).mid(1) + QLatin1Char('/');
// Remove entry from sorted list of services.
for (SortedContainer::iterator it2 = glist.begin(); it2 != glist.end(); ++it2) {
const KServiceGroup::Ptr group(static_cast<KServiceGroup *>(it2.value().data()));
if (group->relPath() == groupPath) {
glist.erase(it2);
break;
}
}
} else if (item[0] != QLatin1Char(':')) {
// Remove entry from sorted list of services.
// TODO: Remove item from sortOrder-list if not found
// TODO: This prevents duplicates
for (SortedContainer::iterator it2 = slist.begin(); it2 != slist.end(); ++it2) {
const KService::Ptr service(static_cast<KService *>(it2.value().data()));
if (service->menuId() == item) {
slist.erase(it2);
break;
}
}
}
}
KServiceGroup::List sorted;
bool needSeparator = false;
// Iterate through the sort spec list.
// Add the entries to the list according to the sort spec.
for (QStringList::ConstIterator it(sortOrder.constBegin()); it != sortOrder.constEnd(); ++it) {
const QString &item = *it;
if (item.isEmpty()) {
continue;
}
if (item[0] == QLatin1Char(':')) {
// Special condition...
if (item == QLatin1String(":S")) {
if (allowSeparators) {
needSeparator = true;
}
} else if (item.contains(QLatin1String(":O"))) {
// todo parse attribute:
QString tmp(item);
tmp.remove(QStringLiteral(":O"));
QStringList optionAttribute = tmp.split(QLatin1Char(' '), Qt::SkipEmptyParts);
if (optionAttribute.isEmpty()) {
optionAttribute.append(tmp);
}
bool showEmptyMenu = false;
bool showInline = false;
bool showInlineHeader = false;
bool showInlineAlias = false;
int inlineValue = -1;
for (QStringList::Iterator it3 = optionAttribute.begin(); it3 != optionAttribute.end(); ++it3) {
parseAttribute(*it3, showEmptyMenu, showInline, showInlineHeader, showInlineAlias, inlineValue);
}
for (SortedContainer::Iterator it2 = glist.begin(); it2 != glist.end(); ++it2) {
KServiceGroup::Ptr group(static_cast<KServiceGroup *>(it2.value().data()));
group->setShowEmptyMenu(showEmptyMenu);
group->setAllowInline(showInline);
group->setShowInlineHeader(showInlineHeader);
group->setInlineAlias(showInlineAlias);
group->setInlineValue(inlineValue);
}
} else if (item == QLatin1String(":M")) {
// Add sorted list of sub-menus
for (SortedContainer::const_iterator it2 = glist.constBegin(); it2 != glist.constEnd(); ++it2) {
addItem(sorted, it2.value(), needSeparator);
}
} else if (item == QLatin1String(":F")) {
// Add sorted list of services
for (SortedContainer::const_iterator it2 = slist.constBegin(); it2 != slist.constEnd(); ++it2) {
addItem(sorted, it2.value(), needSeparator);
}
} else if (item == QLatin1String(":A")) {
// Add sorted lists of services and submenus
SortedContainer::Iterator it_s = slist.begin();
SortedContainer::Iterator it_g = glist.begin();
while (true) {
if (it_s == slist.end()) {
if (it_g == glist.end()) {
break; // Done
}
// Insert remaining sub-menu
addItem(sorted, it_g.value(), needSeparator);
it_g++;
} else if (it_g == glist.end()) {
// Insert remaining service
addItem(sorted, it_s.value(), needSeparator);
it_s++;
} else if (it_g.key() < it_s.key()) {
// Insert sub-menu first
addItem(sorted, it_g.value(), needSeparator);
it_g++;
} else {
// Insert service first
addItem(sorted, it_s.value(), needSeparator);
it_s++;
}
}
}
} else if (item[0] == QLatin1Char('/')) {
QString groupPath = rp + QStringView(item).mid(1) + QLatin1Char('/');
for (KServiceGroup::List::ConstIterator it2(group->d_func()->m_serviceList.constBegin()); it2 != group->d_func()->m_serviceList.constEnd(); ++it2) {
if (!(*it2)->isType(KST_KServiceGroup)) {
continue;
}
KServiceGroup::Ptr group(static_cast<KServiceGroup *>((*it2).data()));
if (group->relPath() == groupPath) {
if (!excludeNoDisplay || !group->noDisplay()) {
++it;
const QString &nextItem = (it == sortOrder.constEnd()) ? QString() : *it;
if (nextItem.startsWith(QLatin1String(":O"))) {
QString tmp(nextItem);
tmp.remove(QStringLiteral(":O"));
QStringList optionAttribute = tmp.split(QLatin1Char(' '), Qt::SkipEmptyParts);
if (optionAttribute.isEmpty()) {
optionAttribute.append(tmp);
}
bool bShowEmptyMenu = false;
bool bShowInline = false;
bool bShowInlineHeader = false;
bool bShowInlineAlias = false;
int inlineValue = -1;
for (const QString &opt_attr : std::as_const(optionAttribute)) {
parseAttribute(opt_attr, bShowEmptyMenu, bShowInline, bShowInlineHeader, bShowInlineAlias, inlineValue);
group->setShowEmptyMenu(bShowEmptyMenu);
group->setAllowInline(bShowInline);
group->setShowInlineHeader(bShowInlineHeader);
group->setInlineAlias(bShowInlineAlias);
group->setInlineValue(inlineValue);
}
} else {
it--;
}
addItem(sorted, KServiceGroup::SPtr(group), needSeparator);
}
break;
}
}
} else {
for (KServiceGroup::List::ConstIterator it2(group->d_func()->m_serviceList.constBegin()); it2 != group->d_func()->m_serviceList.constEnd(); ++it2) {
if (!(*it2)->isType(KST_KService)) {
continue;
}
const KService::Ptr service(static_cast<KService *>((*it2).data()));
if (service->menuId() == item) {
if (!excludeNoDisplay || !service->noDisplay()) {
addItem(sorted, (*it2), needSeparator);
}
break;
}
}
}
}
return sorted;
}
void KServiceGroupPrivate::parseAttribute(const QString &item,
bool &showEmptyMenu,
bool &showInline,
bool &showInlineHeader,
bool &showInlineAlias,
int &inlineValue)
{
if (item == QLatin1String("ME")) { // menu empty
showEmptyMenu = true;
} else if (item == QLatin1String("NME")) { // not menu empty
showEmptyMenu = false;
} else if (item == QLatin1String("I")) { // inline menu !
showInline = true;
} else if (item == QLatin1String("NI")) { // not inline menu!
showInline = false;
} else if (item == QLatin1String("IH")) { // inline header!
showInlineHeader = true;
} else if (item == QLatin1String("NIH")) { // not inline header!
showInlineHeader = false;
} else if (item == QLatin1String("IA")) { // inline alias!
showInlineAlias = true;
} else if (item == QLatin1String("NIA")) { // not inline alias!
showInlineAlias = false;
} else if ((item).contains(QLatin1String("IL"))) { // inline limit!
QString tmp(item);
tmp.remove(QStringLiteral("IL["));
tmp.remove(QLatin1Char(']'));
bool ok;
int _inlineValue = tmp.toInt(&ok);
if (!ok) { // error
_inlineValue = -1;
}
inlineValue = _inlineValue;
} else {
qCDebug(SERVICES) << "This attribute is not supported:" << item;
}
}
void KServiceGroup::setLayoutInfo(const QStringList &layout)
{
Q_D(KServiceGroup);
d->sortOrder = layout;
}
QStringList KServiceGroup::layoutInfo() const
{
Q_D(const KServiceGroup);
return d->sortOrder;
}
KServiceGroup::Ptr KServiceGroup::root()
{
KSycoca::self()->ensureCacheValid();
return KSycocaPrivate::self()->serviceGroupFactory()->findGroupByDesktopPath(QStringLiteral("/"), true);
}
KServiceGroup::Ptr KServiceGroup::group(const QString &relPath)
{
if (relPath.isEmpty()) {
return root();
}
KSycoca::self()->ensureCacheValid();
return KSycocaPrivate::self()->serviceGroupFactory()->findGroupByDesktopPath(relPath, true);
}
KServiceGroup::Ptr KServiceGroup::childGroup(const QString &parent)
{
KSycoca::self()->ensureCacheValid();
return KSycocaPrivate::self()->serviceGroupFactory()->findGroupByDesktopPath(QLatin1String("#parent#") + parent, true);
}
QString KServiceGroup::baseGroupName() const
{
return d_func()->m_strBaseGroupName;
}
QString KServiceGroup::directoryEntryPath() const
{
Q_D(const KServiceGroup);
return d->directoryEntryPath;
}
class KServiceSeparatorPrivate : public KSycocaEntryPrivate
{
public:
K_SYCOCATYPE(KST_KServiceSeparator, KSycocaEntryPrivate)
KServiceSeparatorPrivate(const QString &name)
: KSycocaEntryPrivate(name)
{
}
QString name() const override;
};
QString KServiceSeparatorPrivate::name() const
{
return QStringLiteral("separator");
}
KServiceSeparator::KServiceSeparator()
: KSycocaEntry(*new KServiceSeparatorPrivate(QStringLiteral("separator")))
{
}
KServiceSeparator::~KServiceSeparator()
{
}
@@ -0,0 +1,279 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KSERVICEGROUP_H
#define KSERVICEGROUP_H
#include <kservice.h>
#include <kservice_export.h>
class KBuildServiceGroupFactory;
class KServiceGroupPrivate;
/**
* @class KServiceGroup kservicegroup.h <KServiceGroup>
*
* KServiceGroup represents a group of service, for example
* screensavers.
* This class is typically used like this:
*
* \code
* // Start from root group
* KServiceGroup::Ptr group = KServiceGroup::root();
* if (!group || !group->isValid()) return;
*
* KServiceGroup::List list = group->entries();
*
* // Iterate over all entries in the group
* for( KServiceGroup::List::ConstIterator it = list.begin();
* it != list.end(); it++)
* {
* KSycocaEntry *p = (*it);
* if (p->isType(KST_KService))
* {
* KService *s = static_cast<KService *>(p);
* printf("Name = %s\n", s->name().toLatin1());
* }
* else if (p->isType(KST_KServiceGroup))
* {
* KServiceGroup *g = static_cast<KServiceGroup *>(p);
* // Sub group ...
* }
* }
* \endcode
*
* @short Represents a group of services
*/
class KSERVICE_EXPORT KServiceGroup : public KSycocaEntry
{
friend class KBuildServiceGroupFactory;
public:
/**
* A shared data pointer for KServiceGroup.
*/
typedef QExplicitlySharedDataPointer<KServiceGroup> Ptr;
/**
* A shared data pointer for KSycocaEntry.
*/
typedef QExplicitlySharedDataPointer<KSycocaEntry> SPtr;
/**
* A list of shared data pointers for KSycocaEntry.
*/
typedef QList<SPtr> List;
public:
/**
* Construct a dummy servicegroup indexed with @p name.
* @param name the name of the service group
*/
KServiceGroup(const QString &name);
/**
* Construct a service and take all information from a config file
* @param _fullpath full path to the config file
* @param _relpath relative path to the config file
*/
KServiceGroup(const QString &_fullpath, const QString &_relpath);
~KServiceGroup() override;
/**
* Returns the relative path of the service group.
* @return the service group's relative path
*/
QString relPath() const;
/**
* Returns the caption of this group.
* @return the caption of this group
*/
QString caption() const;
/**
* Returns the name of the icon associated with the group.
* @return the name of the icon associated with the group,
* or QString() if not set
*/
QString icon() const;
/**
* Returns the comment about this service group.
* @return the descriptive comment for the group, if there is one,
* or QString() if not set
*/
QString comment() const;
/**
* Returns the total number of displayable services in this group and
* any of its subgroups.
* @return the number of child services
*/
int childCount() const;
/**
* Returns true if the NoDisplay flag was set, i.e. if this
* group should be hidden from menus, while still being in ksycoca.
* @return true to hide this service group, false to display it
*/
bool noDisplay() const;
/**
* Return true if we want to display empty menu entry
* @return true to show this service group as menu entry is empty, false to hide it
*/
bool showEmptyMenu() const;
void setShowEmptyMenu(bool b);
/**
* @return true to show an inline header into menu
*/
bool showInlineHeader() const;
void setShowInlineHeader(bool _b);
/**
* @return true to show an inline alias item into menu
*/
bool inlineAlias() const;
void setInlineAlias(bool _b);
/**
* @return true if we allow to inline menu.
*/
bool allowInline() const;
void setAllowInline(bool _b);
/**
* @return inline limit value
*/
int inlineValue() const;
void setInlineValue(int _val);
/**
* Returns a list of untranslated generic names that should be
* be suppressed when showing this group.
* E.g. The group "Games/Arcade" might want to suppress the generic name
* "Arcade Game" since it's redundant in this particular context.
*/
QStringList suppressGenericNames() const;
/**
* @internal
* Sets information related to the layout of services in this group.
*/
void setLayoutInfo(const QStringList &layout);
/**
* @internal
* Returns information related to the layout of services in this group.
*/
QStringList layoutInfo() const;
/**
* List of all Services and ServiceGroups within this
* ServiceGroup.
* @param sorted true to sort items
* @param excludeNoDisplay true to exclude items marked "NoDisplay"
* @param allowSeparators true to allow separator items to be included
* @param sortByGenericName true to sort GenericName+Name instead of Name+GenericName
* @return the list of entries
*/
List entries(bool sorted, bool excludeNoDisplay, bool allowSeparators, bool sortByGenericName = false);
List entries(bool sorted, bool excludeNoDisplay);
/**
* List of all Services and ServiceGroups within this
* ServiceGroup.
* @param sorted true to sort items
* @return the list of entried
*/
List entries(bool sorted = false);
/**
* options for groupEntries and serviceEntries
* @see EntriesOptions
*/
enum EntriesOption {
NoOptions = 0x0, /**< no options set */
SortEntries = 0x1, /**< sort items */
ExcludeNoDisplay = 0x2, /**< exclude items marked "NoDisplay" */
AllowSeparators = 0x4, /**< allow separator items to be included */
SortByGenericName = 0x8, /**< sort by GenericName+Name instead of Name+GenericName */
};
/**
* Stores a combination of #EntriesOption values.
*/
Q_DECLARE_FLAGS(EntriesOptions, EntriesOption)
/**
* subgroups for this service group
*/
QList<Ptr> groupEntries(EntriesOptions options = ExcludeNoDisplay);
/**
* entries of this service group
*/
KService::List serviceEntries(EntriesOptions options = ExcludeNoDisplay);
/**
* Returns a non-empty string if the group is a special base group.
* By default, "Settings/" is the kcontrol base group ("settings")
* and "System/Screensavers/" is the screensavers base group ("screensavers").
* This allows moving the groups without breaking those apps.
*
* The base group is defined by the X-KDE-BaseGroup key
* in the .directory file.
* @return the base group name, or null if no base group
*/
QString baseGroupName() const;
/**
* Returns a path to the .directory file describing this service group.
* The path is either absolute or relative to the "apps" resource.
*/
QString directoryEntryPath() const;
/**
* Returns the root service group.
* @return the root service group
*/
static Ptr root();
/**
* Returns the group with the given relative path.
* @param relPath the path of the service group
* @return the group with the given relative path name.
*/
static Ptr group(const QString &relPath);
/**
* Returns the group of services that have X-KDE-ParentApp equal
* to @p parent (siblings).
* @param parent the name of the service's parent
* @return the services group
*/
static Ptr childGroup(const QString &parent);
protected:
/**
* @internal
* Add a service to this group
*/
void addEntry(const KSycocaEntry::Ptr &entry);
private:
friend class KServiceGroupFactory;
/**
* @internal construct a service from a stream.
* The stream must already be positioned at the correct offset
*/
KSERVICE_NO_EXPORT KServiceGroup(QDataStream &_str, int offset, bool deep);
Q_DECLARE_PRIVATE(KServiceGroup)
};
#endif
@@ -0,0 +1,98 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KSERVICEGROUPPRIVATE_H
#define KSERVICEGROUPPRIVATE_H
#include "kservicegroup.h"
#include <ksycocaentry_p.h>
#include <QStringList>
class KServiceGroupPrivate : public KSycocaEntryPrivate
{
public:
K_SYCOCATYPE(KST_KServiceGroup, KSycocaEntryPrivate)
explicit KServiceGroupPrivate(const QString &path)
: KSycocaEntryPrivate(path)
, m_bNoDisplay(false)
, m_bShowEmptyMenu(false)
, m_bShowInlineHeader(false)
, m_bInlineAlias(false)
, m_bAllowInline(false)
, m_inlineValue(4)
, m_bDeep(false)
, m_childCount(-1)
{
}
KServiceGroupPrivate(QDataStream &str, int offset)
: KSycocaEntryPrivate(str, offset)
, m_bNoDisplay(false)
, m_bShowEmptyMenu(false)
, m_bShowInlineHeader(false)
, m_bInlineAlias(false)
, m_bAllowInline(false)
, m_inlineValue(4)
, m_bDeep(false)
, m_childCount(-1)
{
}
void save(QDataStream &s) override;
QString name() const override
{
return path;
}
void load(const QString &cfg);
void load(QDataStream &s);
int childCount() const;
KServiceGroup::List entries(KServiceGroup *group, bool sort, bool excludeNoDisplay, bool allowSeparators, bool sortByGenericName);
/**
* This function parse attributes into menu
*/
void parseAttribute(const QString &item, bool &showEmptyMenu, bool &showInline, bool &showInlineHeader, bool &showInlineAlias, int &inlineValue);
bool m_bNoDisplay : 1;
bool m_bShowEmptyMenu : 1;
bool m_bShowInlineHeader : 1;
bool m_bInlineAlias : 1;
bool m_bAllowInline : 1;
int m_inlineValue;
QStringList suppressGenericNames;
QString directoryEntryPath;
QStringList sortOrder;
QString m_strCaption;
QString m_strIcon;
QString m_strComment;
KServiceGroup::List m_serviceList;
bool m_bDeep;
QString m_strBaseGroupName;
mutable int m_childCount;
};
class KServiceSeparator : public KSycocaEntry // krazy:exclude=dpointer (dummy class)
{
public:
typedef QExplicitlySharedDataPointer<KServiceSeparator> Ptr;
public:
/**
* Construct a service separator
*/
KServiceSeparator();
~KServiceSeparator() override;
};
#endif
@@ -0,0 +1,116 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kservice.h"
#include "kservicegroupfactory_p.h"
#include "ksycoca.h"
#include "ksycocadict_p.h"
#include "ksycocatype.h"
#include "servicesdebug.h"
#include <QIODevice>
KServiceGroupFactory::KServiceGroupFactory(KSycoca *db)
: KSycocaFactory(KST_KServiceGroupFactory, db)
, m_baseGroupDict(nullptr)
, m_baseGroupDictOffset(0)
{
if (!sycoca()->isBuilding()) {
QDataStream *str = stream();
if (!str) {
return;
}
// Read Header
qint32 i;
(*str) >> i;
m_baseGroupDictOffset = i;
const qint64 saveOffset = str->device()->pos();
// Init index tables
m_baseGroupDict = new KSycocaDict(str, m_baseGroupDictOffset);
str->device()->seek(saveOffset);
}
}
KServiceGroupFactory::~KServiceGroupFactory()
{
delete m_baseGroupDict;
}
KServiceGroup::Ptr KServiceGroupFactory::findGroupByDesktopPath(const QString &_name, bool deep)
{
if (!sycocaDict()) {
return KServiceGroup::Ptr(); // Error!
}
int offset = sycocaDict()->find_string(_name);
if (!offset) {
return KServiceGroup::Ptr(); // Not found
}
KServiceGroup::Ptr newGroup(createGroup(offset, deep));
// Check whether the dictionary was right.
if (newGroup && (newGroup->relPath() != _name)) {
// No it wasn't...
newGroup = nullptr; // Not found
}
return newGroup;
}
KServiceGroup::Ptr KServiceGroupFactory::findBaseGroup(const QString &_baseGroupName, bool deep)
{
if (!m_baseGroupDict) {
return KServiceGroup::Ptr(); // Error!
}
// Warning : this assumes we're NOT building a database
// But since findBaseGroup isn't called in that case...
// [ see KServiceTypeFactory for how to do it if needed ]
int offset = m_baseGroupDict->find_string(_baseGroupName);
if (!offset) {
return KServiceGroup::Ptr(); // Not found
}
KServiceGroup::Ptr newGroup(createGroup(offset, deep));
// Check whether the dictionary was right.
if (newGroup && (newGroup->baseGroupName() != _baseGroupName)) {
// No it wasn't...
newGroup = nullptr; // Not found
}
return newGroup;
}
KServiceGroup *KServiceGroupFactory::createGroup(int offset, bool deep) const
{
KSycocaType type;
QDataStream *str = sycoca()->findEntry(offset, type);
if (type != KST_KServiceGroup) {
qCWarning(SERVICES) << "KServiceGroupFactory: unexpected object entry in KSycoca database (type = " << int(type) << ")";
return nullptr;
}
KServiceGroup *newEntry = new KServiceGroup(*str, offset, deep);
if (!newEntry->isValid()) {
qCWarning(SERVICES) << "KServiceGroupFactory: corrupt object in KSycoca database!";
delete newEntry;
newEntry = nullptr;
}
return newEntry;
}
KServiceGroup *KServiceGroupFactory::createEntry(int offset) const
{
return createGroup(offset, true);
}
void KServiceGroupFactory::virtual_hook(int id, void *data)
{
KSycocaFactory::virtual_hook(id, data);
}
@@ -0,0 +1,72 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KSERVICEGROUPFACTORY_P_H
#define KSERVICEGROUPFACTORY_P_H
#include "kservicegroup.h"
#include "ksycocafactory_p.h"
#include <assert.h>
class KSycoca;
class KSycocaDict;
/**
* @internal
* A sycoca factory for service groups (e.g. list of applications)
* It loads the services from parsing directories (e.g. share/applications/)
*
* Exported for kbuildsycoca, but not installed.
*/
class KServiceGroupFactory : public KSycocaFactory
{
K_SYCOCAFACTORY(KST_KServiceGroupFactory)
public:
/**
* Create factory
*/
explicit KServiceGroupFactory(KSycoca *db);
~KServiceGroupFactory() override;
/**
* Construct a KServiceGroup from a config file.
*/
KSycocaEntry *createEntry(const QString &) const override
{
assert(0);
return nullptr;
}
/**
* Find a group ( by desktop path, e.g. "Applications/Editors")
*/
virtual KServiceGroup::Ptr findGroupByDesktopPath(const QString &_name, bool deep = true);
/**
* Find a base group by name, e.g. "settings"
*/
KServiceGroup::Ptr findBaseGroup(const QString &_baseGroupName, bool deep = true);
/**
* @return the unique service group factory, creating it if necessary
*/
static KServiceGroupFactory *self();
protected:
KServiceGroup *createGroup(int offset, bool deep) const;
KServiceGroup *createEntry(int offset) const override;
KSycocaDict *m_baseGroupDict;
int m_baseGroupDictOffset;
protected:
void virtual_hook(int id, void *data) override;
private:
class KServiceGroupFactoryPrivate *d;
};
#endif
@@ -0,0 +1,111 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 2006 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kserviceoffer.h"
#include <QDebug>
class KServiceOfferPrivate
{
public:
KServiceOfferPrivate()
: preference(-1)
, mimeTypeInheritanceLevel(0)
, pService(nullptr)
{
}
int preference;
int mimeTypeInheritanceLevel;
KService::Ptr pService;
};
KServiceOffer::KServiceOffer()
: d(new KServiceOfferPrivate)
{
}
KServiceOffer::KServiceOffer(const KServiceOffer &_o)
: d(new KServiceOfferPrivate)
{
*d = *_o.d;
}
KServiceOffer::KServiceOffer(const KService::Ptr &_service, int _pref, int mimeTypeInheritanceLevel)
: d(new KServiceOfferPrivate)
{
d->pService = _service;
d->preference = _pref;
d->mimeTypeInheritanceLevel = mimeTypeInheritanceLevel;
}
KServiceOffer::~KServiceOffer() = default;
KServiceOffer &KServiceOffer::operator=(const KServiceOffer &rhs)
{
if (this == &rhs) {
return *this;
}
*d = *rhs.d;
return *this;
}
bool KServiceOffer::operator<(const KServiceOffer &_o) const
{
// First check mimetype inheritance level.
// Direct mimetype association is preferred above association via parent mimetype
// So, the smaller the better.
if (d->mimeTypeInheritanceLevel != _o.d->mimeTypeInheritanceLevel) {
return d->mimeTypeInheritanceLevel < _o.d->mimeTypeInheritanceLevel;
}
// Finally, use preference to sort them
// The bigger the better, but we want the better FIRST
return _o.d->preference < d->preference;
}
int KServiceOffer::preference() const
{
return d->preference;
}
void KServiceOffer::setPreference(int p)
{
d->preference = p;
}
KService::Ptr KServiceOffer::service() const
{
return d->pService;
}
bool KServiceOffer::isValid() const
{
return d->preference >= 0;
}
void KServiceOffer::setMimeTypeInheritanceLevel(int level)
{
d->mimeTypeInheritanceLevel = level;
}
int KServiceOffer::mimeTypeInheritanceLevel() const
{
return d->mimeTypeInheritanceLevel;
}
QDebug operator<<(QDebug dbg, const KServiceOffer &offer)
{
QDebugStateSaver saver(dbg);
dbg.nospace() << offer.service()->storageId() << " " << offer.preference();
if (offer.mimeTypeInheritanceLevel() > 0) {
dbg << " (inheritance level " << offer.mimeTypeInheritanceLevel() << ")";
}
return dbg;
}
@@ -0,0 +1,120 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 2006 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KSERVICEOFFER_H
#define KSERVICEOFFER_H
#include <kservice.h>
#include <memory>
class KServiceOfferPrivate;
/**
* @internal
*
* This class holds the user-specific preferences of a service
* (whether it can be a default offer or not, how big is the preference
* for this offer, ...). Basically it is a reference to a
* KService, a number that represents the user's preference (bigger
* is better) and a flag whether the KService can be used as default.
*
* @see KService
* @short Holds the user's preference of a service.
*/
class KSERVICE_EXPORT KServiceOffer // exported for kbuildsycoca
{
public:
/**
* Create an invalid service offer.
*/
KServiceOffer();
/**
* Copy constructor.
* Shallow copy (the KService will not be copied).
*/
KServiceOffer(const KServiceOffer &);
/**
* Creates a new KServiceOffer.
* @param service a pointer to the KService
* @param pref the user's preference value, must be positive,
* bigger is better
* @param mimeTypeInheritanceLevel level of MIME type inheritance
* which allows this service to handling the MIME type.
* 0 if no inheritance involved, 1 for parent MIME type, etc.
* @since 5.71
*/
KServiceOffer(const KService::Ptr &service, int pref, int mimeTypeInheritanceLevel);
~KServiceOffer();
/**
* A service is bigger that the other when it can be default
* (and the other is not) and its preference value it higher.
*/
bool operator<(const KServiceOffer &) const;
/**
* Assignment operator
*/
KServiceOffer &operator=(const KServiceOffer &other);
/**
* The bigger this number is, the better is this service.
* @return the preference number (negative numbers will be
* returned by invalid service offers)
*/
int preference() const;
/**
* The bigger this number is, the better is this service.
* Set the preference number
* @internal - only for KMimeTypeTrader
*/
void setPreference(int p);
/**
* The service which this offer is about.
* @return the service this offer is about, can be @c nullptr
* in valid offers or when not set
*/
KService::Ptr service() const;
/**
* Check whether the entry is valid. A service is valid if
* its preference value is positive.
* @return true if the service offer is valid
*/
bool isValid() const;
/**
* When copying an offer from a parent MIME type, remember that it's an inherited capability
* (for sorting purposes; we prefer a handler for postscript over any text/plain handler)
*/
void setMimeTypeInheritanceLevel(int level);
/**
* Mimetype inheritance level
* @internal
*/
int mimeTypeInheritanceLevel() const;
private:
std::unique_ptr<KServiceOfferPrivate> const d;
};
/**
* A list of weighted offers.
*/
typedef QList<KServiceOffer> KServiceOfferList;
QDebug operator<<(QDebug dbg, const KServiceOffer &offer);
#endif /* KSERVICEOFFER_H */
@@ -0,0 +1,39 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2015 Gregor Mi <codestruct@posteo.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KSERVICEUTIL_P_H
#define KSERVICEUTIL_P_H
#include <QString>
class KServiceUtilPrivate
{
public:
/**
* Lightweight implementation of QFileInfo::completeBaseName.
*
* Returns the complete base name of the file without the path.
* The complete base name consists of all characters in the file up to (but not including) the last '.' character.
*
* Example: "/tmp/archive.tar.gz" --> "archive.tar"
*/
static QString completeBaseName(const QString &filepath)
{
QString name = filepath;
int pos = name.lastIndexOf(QLatin1Char('/'));
if (pos != -1) {
name.remove(0, pos + 1);
}
pos = name.lastIndexOf(QLatin1Char('.'));
if (pos != -1) {
name.truncate(pos);
}
return name;
}
};
#endif
@@ -0,0 +1,94 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999-2007 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kbuildmimetypefactory_p.h"
#include "ksycoca.h"
#include "ksycocadict_p.h"
#include "ksycocaresourcelist_p.h"
#include <QDebug>
#include <QHash>
#include <QIODevice>
#include <QStandardPaths>
#include <assert.h>
KBuildMimeTypeFactory::KBuildMimeTypeFactory(KSycoca *db)
: KMimeTypeFactory(db)
{
// We want all xml files under xdgdata/mime - but not mime/packages/*.xml
m_resourceList.emplace_back("xdgdata-mime", QStringLiteral("mime"), QStringLiteral("*.xml"));
}
KBuildMimeTypeFactory::~KBuildMimeTypeFactory()
{
}
KSycocaEntry::List KBuildMimeTypeFactory::allEntries() const
{
assert(sycoca()->isBuilding());
return m_entryDict->values();
}
KSycocaEntry *KBuildMimeTypeFactory::createEntry(const QString &file) const
{
// file=text/plain.xml -> name=plain.xml dirName=text
Q_ASSERT(!file.startsWith(QLatin1String("mime/")));
const int pos = file.lastIndexOf(QLatin1Char('/'));
if (pos == -1) { // huh?
return nullptr;
}
const auto dirName = QStringView(file).left(pos);
if (dirName == QLatin1String("packages")) { // special subdir
return nullptr;
}
const int dot = file.lastIndexOf(QLatin1Char('.'));
if (dot == -1) { // huh?
return nullptr;
}
const QString name = file.left(dot);
// qDebug() << "Creating MIME type" << name << "from file" << file;
MimeTypeEntry *e = new MimeTypeEntry(file, name);
return e;
}
void KBuildMimeTypeFactory::saveHeader(QDataStream &str)
{
KSycocaFactory::saveHeader(str);
}
void KBuildMimeTypeFactory::save(QDataStream &str)
{
KSycocaFactory::save(str);
str << qint32(0);
const qint64 endOfFactoryData = str.device()->pos();
// Update header (pass #3)
saveHeader(str);
// Seek to end.
str.device()->seek(endOfFactoryData);
}
KMimeTypeFactory::MimeTypeEntry::Ptr KBuildMimeTypeFactory::createFakeMimeType(const QString &name)
{
const QString file = name; // hack
KSycocaEntry::Ptr entry = m_entryDict->value(file);
if (!entry) {
MimeTypeEntry *e = new MimeTypeEntry(file, name);
entry = e;
}
Q_ASSERT(entry && entry->isValid());
addEntry(entry);
return KMimeTypeFactory::MimeTypeEntry::Ptr(static_cast<MimeTypeEntry *>(entry.data()));
}
@@ -0,0 +1,57 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 1999-2007 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KBUILD_MIME_TYPE_FACTORY_H
#define KBUILD_MIME_TYPE_FACTORY_H
#include <QStringList>
#include <kmimetypefactory_p.h>
/**
* Mime-type factory for building ksycoca
* @internal
*/
class KBuildMimeTypeFactory : public KMimeTypeFactory
{
public:
/**
* Create factory
*/
explicit KBuildMimeTypeFactory(KSycoca *db);
~KBuildMimeTypeFactory() override;
KSycocaEntry::List allEntries() const override;
/**
* Construct a KMimeType from a config file.
*/
KSycocaEntry *createEntry(const QString &file) const override;
MimeTypeEntry *createEntry(int) const override
{
assert(0);
return nullptr;
}
KMimeTypeFactory::MimeTypeEntry::Ptr createFakeMimeType(const QString &name);
/**
* Write out MIME type specific index files.
*/
void save(QDataStream &str) override;
/**
* Write out header information
*
* Don't forget to call the parent first when you override
* this function.
*/
void saveHeader(QDataStream &str) override;
};
#endif
@@ -0,0 +1,356 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999, 2007 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kbuildmimetypefactory_p.h"
#include "kbuildservicefactory_p.h"
#include "kbuildservicegroupfactory_p.h"
#include "ksycoca.h"
#include "ksycocadict_p.h"
#include "sycocadebug.h"
#include <KDesktopFile>
#include <QDebug>
#include <QDir>
#include <QMimeDatabase>
#include <QStandardPaths>
#include <kmimetypefactory_p.h>
#include <kservice_p.h>
KBuildServiceFactory::KBuildServiceFactory(KBuildMimeTypeFactory *mimeTypeFactory)
: KServiceFactory(mimeTypeFactory->sycoca())
, m_nameMemoryHash()
, m_relNameMemoryHash()
, m_menuIdMemoryHash()
, m_dupeDict()
, m_mimeTypeFactory(mimeTypeFactory)
{
m_nameDict = new KSycocaDict();
m_relNameDict = new KSycocaDict();
m_menuIdDict = new KSycocaDict();
}
KBuildServiceFactory::~KBuildServiceFactory()
{
}
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
{
const QStringView name = QStringView(file).mid(file.lastIndexOf(QLatin1Char('/')) + 1);
// Is it a .desktop file?
if (name.endsWith(QLatin1String(".desktop"))) {
// qCDebug(SYCOCA) << file;
Q_ASSERT(QDir::isAbsolutePath(file));
KService *serv = new KService(file);
// qCDebug(SYCOCA) << "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()) {
// qCDebug(SYCOCA) << "Creating KService from" << file << "entryPath=" << serv->entryPath() << "storageId=" << serv->storageId();
return serv;
} else {
if (!serv->isDeleted()) {
qCWarning(SYCOCA) << "Invalid Service : " << file;
}
delete serv;
return nullptr;
}
} // TODO else if a Windows application, new KService(name, exec, icon)
return nullptr;
}
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);
qint64 endOfFactoryData = str.device()->pos();
// Update header (pass #3)
saveHeader(str);
// Seek to end.
str.device()->seek(endOfFactoryData);
}
void KBuildServiceFactory::collectInheritedServices()
{
// For each MIME type, go up the parent MIME type 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 auto lst = m_mimeTypeFactory->allMimeTypes();
for (const QString &mimeType : lst) {
collectInheritedServices(mimeType, visitedMimes);
}
}
void KBuildServiceFactory::collectInheritedServices(const QString &mimeTypeName, QSet<QString> &visitedMimes)
{
if (visitedMimes.contains(mimeTypeName)) {
return;
}
visitedMimes.insert(mimeTypeName);
QMimeDatabase db;
const QMimeType qmime = db.mimeTypeForName(mimeTypeName);
const auto lst = qmime.parentMimeTypes();
for (QString parentMimeType : lst) {
// Workaround issue in shared-mime-info and/or Qt, which sometimes return an alias as parent
parentMimeType = db.mimeTypeForName(parentMimeType).name();
collectInheritedServices(parentMimeType, visitedMimes);
const QList<KServiceOffer> &offers = m_offerHash.offersFor(parentMimeType);
for (const auto &serviceOffer : offers) {
if (!m_offerHash.hasRemovedOffer(mimeTypeName, serviceOffer.service())) {
KServiceOffer offer(serviceOffer);
offer.setMimeTypeInheritanceLevel(offer.mimeTypeInheritanceLevel() + 1);
// qCDebug(SYCOCA) << "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.
// Note that this also affects resolution of the by-desktop-name lookup,
// as name resolution is only performed *after* all the duplicates (based on
// storage ID) have been removed.
// For every service...
for (auto itserv = m_entryDict->cbegin(), endIt = m_entryDict->cend(); itserv != endIt; ++itserv) {
KSycocaEntry::Ptr entry = itserv.value();
KService::Ptr service(static_cast<KService *>(entry.data()));
const QString name = service->desktopEntryName();
KService::Ptr dup = m_nameMemoryHash.value(name);
if (dup) {
// The rule is that searching for the desktop name "foo" should find
// the desktop file with the storage id "foo.desktop" before it
// finds "bar/foo.desktop" (or "bar-foo.desktop").
// "bar/foo.desktop" and "baz/foo.desktop" are arbitrarily ordered
// (in practice, the one later in the alphabet wins).
if (dup->storageId().endsWith(service->storageId())) {
// allow dup to be overridden
m_nameDict->remove(name);
dup = nullptr;
}
}
if (!dup) {
m_nameDict->add(name, entry);
m_nameMemoryHash.insert(name, service);
}
const QString relName = service->entryPath();
// qCDebug(SYCOCA) << "adding service" << service.data() << "isApp=" << service->isApplication() << "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()
{
QMimeDatabase db;
// For every service...
for (auto servIt = m_entryDict->cbegin(), endIt = m_entryDict->cend(); servIt != endIt; ++servIt) {
KService::Ptr service(static_cast<KService *>(servIt.value().data()));
const bool hidden = !service->showInCurrentDesktop();
const auto mimeTypes = service->d_func()->m_mimeTypes;
// Add this service to all its MIME types
// Don't cache count(), it can change during iteration! (we can't use an iterator-based loop
// here the container could get reallocated which would invalidate iterators)
for (int i = 0; i < mimeTypes.count(); ++i) {
const QString &mimeName = mimeTypes.at(i);
if (hidden) {
continue;
}
KServiceOffer offer(service, 1 /* preference; always 1 here, may be higher based on KMimeAssociations */, 0);
QMimeType mime = db.mimeTypeForName(mimeName);
if (!mime.isValid()) {
if (mimeName.startsWith(QLatin1String("x-scheme-handler/"))) {
// Create those on demand
m_mimeTypeFactory->createFakeMimeType(mimeName);
m_offerHash.addServiceOffer(mimeName, offer);
} else {
qCDebug(SYCOCA) << service->entryPath() << "specifies undefined MIME type/servicetype" << mimeName;
// technically we could call addServiceOffer here, 'mime' isn't used. But it
// would be useless, since we have no MIME type entry where to write the offers offset.
continue;
}
} else {
bool shouldAdd = true;
const auto lst = service->mimeTypes();
for (const QString &otherType : lst) {
// Skip derived types if the base class is listed (#321706)
if (mimeName != otherType && mime.inherits(otherType)) {
// But don't skip aliases (they got resolved into mime.name() already, but don't let two aliases cancel out)
if (db.mimeTypeForName(otherType).name() != mime.name()) {
// qCDebug(SYCOCA) << "Skipping" << mime.name() << "because of" << otherType << "(canonical" << db.mimeTypeForName(otherType) <<
// ") while parsing" << service->entryPath();
shouldAdd = false;
}
}
}
if (shouldAdd) {
// qCDebug(SYCOCA) << "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, this);
mimeAssociations.parseAllMimeAppsList();
// Now for each MIME type, collect services from parent MIME types
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.
const auto &offerHash = m_offerHash.serviceTypeData();
auto it = offerHash.constBegin();
const auto end = offerHash.constEnd();
for (; it != end; ++it) {
const QString stName = it.key();
const ServiceTypeOffersData offersData = it.value();
const int numOffers = offersData.offers.count();
KMimeTypeFactory::MimeTypeEntry::Ptr entry = m_mimeTypeFactory->findMimeTypeEntryByName(stName);
if (entry) {
entry->setServiceOffersOffset(offersOffset);
offersOffset += offerEntrySize * numOffers;
} else if (stName.startsWith(QLatin1String("x-scheme-handler/"))) {
// Create those on demand
entry = m_mimeTypeFactory->createFakeMimeType(stName);
entry->setServiceOffersOffset(offersOffset);
offersOffset += offerEntrySize * numOffers;
} else {
if (stName.isEmpty()) {
qCDebug(SYCOCA) << "Empty service type";
} else {
qCWarning(SYCOCA) << "Service type not found:" << stName;
}
}
}
}
void KBuildServiceFactory::saveOfferList(QDataStream &str)
{
m_offerListOffset = str.device()->pos();
// qCDebug(SYCOCA) << "Saving offer list at offset" << m_offerListOffset;
const auto &offerHash = m_offerHash.serviceTypeData();
auto it = offerHash.constBegin();
const auto end = offerHash.constEnd();
for (; it != end; ++it) {
const QString stName = it.key();
const ServiceTypeOffersData offersData = it.value();
QList<KServiceOffer> offers = offersData.offers;
std::stable_sort(offers.begin(), offers.end()); // by initial preference
int offset = -1;
KMimeTypeFactory::MimeTypeEntry::Ptr entry = m_mimeTypeFactory->findMimeTypeEntryByName(stName);
if (entry) {
offset = entry->offset();
// Q_ASSERT(str.device()->pos() == entry->serviceOffersOffset() + m_offerListOffset);
}
if (offset == -1) {
qCDebug(SYCOCA) << "Didn't find servicetype or MIME type" << stName;
continue;
}
for (const auto &offer : std::as_const(offers)) {
// qCDebug(SYCOCA) << stName << ": writing offer" << offer.service()->desktopEntryName() << offset << offer.service()->offset() << "in sycoca at
// pos" << str.device()->pos();
Q_ASSERT(offer.service()->offset() != 0);
str << qint32(offset);
str << qint32(offer.service()->offset());
str << qint32(offer.preference());
str << qint32(offer.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(static_cast<KService *>(newEntry.data()));
m_dupeDict.insert(newEntry);
KSycocaFactory::addEntry(newEntry);
}
@@ -0,0 +1,88 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 1999, 2007 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KBUILD_SERVICE_FACTORY_H
#define KBUILD_SERVICE_FACTORY_H
#include <QStringList>
#include "kmimeassociations_p.h"
#include <kservicefactory_p.h>
// We export the services to the service group factory!
class KBuildServiceGroupFactory;
class KBuildMimeTypeFactory;
/**
* Service factory for building ksycoca
* @internal
*/
class KBuildServiceFactory : public KServiceFactory
{
public:
/**
* Create factory
*/
KBuildServiceFactory(KBuildMimeTypeFactory *mimeTypeFactory);
~KBuildServiceFactory() override;
/// Reimplemented from KServiceFactory
KService::Ptr findServiceByDesktopName(const QString &name) override;
/// Reimplemented from KServiceFactory
KService::Ptr findServiceByDesktopPath(const QString &name) override;
/// Reimplemented from KServiceFactory
KService::Ptr findServiceByMenuId(const QString &menuId) override;
/**
* Construct a KService from a config file.
*/
KSycocaEntry *createEntry(const QString &file) const override;
KService *createEntry(int) const override
{
assert(0);
return nullptr;
}
/**
* Add a new entry.
*/
void addEntry(const KSycocaEntry::Ptr &newEntry) override;
/**
* Write out service specific index files.
*/
void save(QDataStream &str) override;
/**
* Write out header information
*
* Don't forget to call the parent first when you override
* this function.
*/
void saveHeader(QDataStream &str) override;
void postProcessServices();
private:
void populateServiceTypes();
void saveOfferList(QDataStream &str);
void collectInheritedServices();
void collectInheritedServices(const QString &mime, QSet<QString> &visitedMimes);
QHash<QString, KService::Ptr> m_nameMemoryHash; // m_nameDict is not usable while building ksycoca
QHash<QString, KService::Ptr> m_relNameMemoryHash; // m_relNameDict is not usable while building ksycoca
QHash<QString, KService::Ptr> m_menuIdMemoryHash; // m_menuIdDict is not usable while building ksycoca
QSet<KSycocaEntry::Ptr> m_dupeDict;
KOfferHash m_offerHash;
KBuildMimeTypeFactory *m_mimeTypeFactory;
};
#endif
@@ -0,0 +1,155 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kbuildservicegroupfactory_p.h"
#include "ksycoca.h"
#include "ksycocadict_p.h"
#include "sycocadebug.h"
#include <kservicegroup_p.h>
#include <QDebug>
#include <QHash>
#include <QIODevice>
#include <assert.h>
KBuildServiceGroupFactory::KBuildServiceGroupFactory(KSycoca *db)
: KServiceGroupFactory(db)
{
m_baseGroupDict = new KSycocaDict();
}
KBuildServiceGroupFactory::~KBuildServiceGroupFactory()
{
}
KServiceGroup *KBuildServiceGroupFactory::createEntry(const QString &) const
{
// Unused
qCWarning(SYCOCA) << "called!";
return nullptr;
}
void KBuildServiceGroupFactory::addNewEntryTo(const QString &menuName, const KService::Ptr &newEntry)
{
KSycocaEntry::Ptr ptr = m_entryDict->value(menuName);
KServiceGroup::Ptr entry;
if (ptr && ptr->isType(KST_KServiceGroup)) {
entry = KServiceGroup::Ptr(static_cast<KServiceGroup *>(ptr.data()));
}
if (!entry) {
qCWarning(SYCOCA) << "( " << menuName << ", " << newEntry->name() << " ): menu does not exists!";
return;
}
entry->addEntry(KSycocaEntry::Ptr(newEntry));
}
KServiceGroup::Ptr KBuildServiceGroupFactory::addNew(const QString &menuName, const QString &file, KServiceGroup::Ptr entry, bool isDeleted)
{
KSycocaEntry::Ptr ptr = m_entryDict->value(menuName);
if (ptr) {
qCWarning(SYCOCA) << "( " << menuName << ", " << file << " ): menu already exists!";
return KServiceGroup::Ptr(static_cast<KServiceGroup *>(ptr.data()));
}
// Create new group entry
if (!entry) {
entry = new KServiceGroup(file, menuName);
}
entry->d_func()->m_childCount = -1; // Recalculate
addEntry(KSycocaEntry::Ptr(entry));
if (menuName != QLatin1String("/")) {
// Make sure parent dir exists.
QString parent = menuName.left(menuName.length() - 1);
int i = parent.lastIndexOf(QLatin1Char('/'));
if (i > 0) {
parent = parent.left(i + 1);
} else {
parent = QLatin1Char('/');
}
KServiceGroup::Ptr parentEntry;
ptr = m_entryDict->value(parent);
if (ptr && ptr->isType(KST_KServiceGroup)) {
parentEntry = KServiceGroup::Ptr(static_cast<KServiceGroup *>(ptr.data()));
}
if (!parentEntry) {
qCWarning(SYCOCA) << "( " << menuName << ", " << file << " ): parent menu does not exist!";
} else {
if (!isDeleted && !entry->isDeleted()) {
parentEntry->addEntry(KSycocaEntry::Ptr(entry));
}
}
}
return entry;
}
void KBuildServiceGroupFactory::addNewChild(const QString &parent, const KSycocaEntry::Ptr &newEntry)
{
QString name = QLatin1String("#parent#") + parent;
KServiceGroup::Ptr entry;
KSycocaEntry::Ptr ptr = m_entryDict->value(name);
if (ptr && ptr->isType(KST_KServiceGroup)) {
entry = KServiceGroup::Ptr(static_cast<KServiceGroup *>(ptr.data()));
}
if (!entry) {
entry = new KServiceGroup(name);
addEntry(KSycocaEntry::Ptr(entry));
}
if (newEntry) {
entry->addEntry(newEntry);
}
}
void KBuildServiceGroupFactory::addEntry(const KSycocaEntry::Ptr &newEntry)
{
KSycocaFactory::addEntry(newEntry);
KServiceGroup::Ptr serviceGroup(static_cast<KServiceGroup *>(newEntry.data()));
serviceGroup->d_func()->m_serviceList.clear();
if (!serviceGroup->baseGroupName().isEmpty()) {
m_baseGroupDict->add(serviceGroup->baseGroupName(), newEntry);
}
}
void KBuildServiceGroupFactory::saveHeader(QDataStream &str)
{
KSycocaFactory::saveHeader(str);
str << qint32(m_baseGroupDictOffset);
}
void KBuildServiceGroupFactory::save(QDataStream &str)
{
KSycocaFactory::save(str);
m_baseGroupDictOffset = str.device()->pos();
m_baseGroupDict->save(str);
qint64 endOfFactoryData = str.device()->pos();
// Update header (pass #3)
saveHeader(str);
// Seek to end.
str.device()->seek(endOfFactoryData);
}
KServiceGroup::Ptr KBuildServiceGroupFactory::findGroupByDesktopPath(const QString &_name, bool deep)
{
assert(sycoca()->isBuilding());
Q_UNUSED(deep); // ?
// We're building a database - the service type must be in memory
KSycocaEntry::Ptr group = m_entryDict->value(_name);
return KServiceGroup::Ptr(static_cast<KServiceGroup *>(group.data()));
}
@@ -0,0 +1,81 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KBUILD_SERVICE_GROUP_FACTORY_H
#define KBUILD_SERVICE_GROUP_FACTORY_H
#include <QStringList>
#include <kservice.h>
#include <kservicegroupfactory_p.h>
/**
* Service group factory for building ksycoca
* @internal
*/
class KBuildServiceGroupFactory : public KServiceGroupFactory
{
public:
/**
* Create factory
*/
explicit KBuildServiceGroupFactory(KSycoca *db);
~KBuildServiceGroupFactory() override;
/**
* Create new entry.
*/
KServiceGroup *createEntry(const QString &) const override;
KServiceGroup *createEntry(int) const override
{
assert(0);
return nullptr;
}
/**
* Adds the entry @p newEntry to the menu @p menuName
*/
void addNewEntryTo(const QString &menuName, const KService::Ptr &newEntry);
/**
* Adds the entry @p newEntry to the "parent group" @p parent, creating
* the group if necessary.
* A "parent group" is a group of services that all have the same
* "X-KDE-ParentApp".
*/
void addNewChild(const QString &parent, const KSycocaEntry::Ptr &newEntry);
/**
* Add new menu @p menuName defined by @p file
* When @p entry is non-null it is re-used, otherwise a new group is created.
* A pointer to the group is returned.
*/
KServiceGroup::Ptr addNew(const QString &menuName, const QString &file, KServiceGroup::Ptr entry, bool isDeleted);
/**
* Find a group ( by desktop path, e.g. "Applications/Editors")
*/
KServiceGroup::Ptr findGroupByDesktopPath(const QString &_name, bool deep = true) override;
/**
* Add a new menu entry
*/
void addEntry(const KSycocaEntry::Ptr &newEntry) override;
/**
* Write out servicegroup specific index files.
*/
void save(QDataStream &str) override;
/**
* Write out header information
*/
void saveHeader(QDataStream &str) override;
};
#endif
@@ -0,0 +1,642 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2002-2003 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kbuildsycoca_p.h"
#include "ksycoca_p.h"
#include "ksycocaresourcelist_p.h"
#include "ksycocautils_p.h"
#include "sycocadebug.h"
#include "vfolder_menu_p.h"
#include "kbuildmimetypefactory_p.h"
#include "kbuildservicefactory_p.h"
#include "kbuildservicegroupfactory_p.h"
#include "kctimefactory_p.h"
#include <QDataStream>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QEventLoop>
#include <QFile>
#include <QLocale>
#include <QSaveFile>
#include <QTimer>
#include <config-ksycoca.h>
#include <kservice.h>
#include <kservicegroup.h>
#include <kmemfile_p.h>
#include <QLockFile>
#include <QStandardPaths>
#include <qplatformdefs.h>
static const char *s_cSycocaPath = nullptr;
KBuildSycocaInterface::~KBuildSycocaInterface()
{
}
KBuildSycoca::KBuildSycoca()
: KSycoca(true)
, m_allEntries(nullptr)
, m_ctimeFactory(nullptr)
, m_ctimeDict(nullptr)
, m_currentEntryDict(nullptr)
, m_serviceGroupEntryDict(nullptr)
, m_vfolder(nullptr)
, m_newTimestamp(0)
, m_menuTest(false)
, m_changed(false)
{
}
KBuildSycoca::~KBuildSycoca()
{
// Delete the factories while we exist, so that the virtual isBuilding() still works
qDeleteAll(*factories());
factories()->clear();
}
KSycocaEntry::Ptr KBuildSycoca::createEntry(KSycocaFactory *currentFactory, const QString &file)
{
quint32 timeStamp = m_ctimeFactory->dict()->ctime(file, m_resource);
if (!timeStamp) {
timeStamp = calcResourceHash(m_resourceSubdir, file);
if (!timeStamp) { // file disappeared meanwhile
return {};
}
}
KSycocaEntry::Ptr entry;
if (m_allEntries) {
Q_ASSERT(m_ctimeDict);
quint32 oldTimestamp = m_ctimeDict->ctime(file, m_resource);
if (file.contains(QLatin1String("fake"))) {
qCDebug(SYCOCA) << "m_ctimeDict->ctime(" << file << ") = " << oldTimestamp << "compared with" << timeStamp;
}
if (timeStamp && (timeStamp == oldTimestamp)) {
// Re-use old entry
if (currentFactory == d->m_serviceFactory) { // Strip .directory from service-group entries
entry = m_currentEntryDict->value(file.left(file.length() - 10));
} else {
entry = m_currentEntryDict->value(file);
}
// remove from m_ctimeDict; if m_ctimeDict is not empty
// after all files have been processed, it means
// some files were removed since last time
if (file.contains(QLatin1String("fake"))) {
qCDebug(SYCOCA) << "reusing (and removing) old entry for:" << file << "entry=" << entry;
}
m_ctimeDict->remove(file, m_resource);
} else if (oldTimestamp) {
m_changed = true;
m_ctimeDict->remove(file, m_resource);
qCDebug(SYCOCA) << "modified:" << file;
} else {
m_changed = true;
qCDebug(SYCOCA) << "new:" << file;
}
}
m_ctimeFactory->dict()->addCTime(file, m_resource, timeStamp);
if (!entry) {
// Create a new entry
entry = currentFactory->createEntry(file);
}
if (entry && entry->isValid()) {
return entry;
}
return KSycocaEntry::Ptr();
}
KService::Ptr KBuildSycoca::createService(const QString &path)
{
KSycocaEntry::Ptr entry = createEntry(d->m_serviceFactory, path);
if (entry) {
m_tempStorage.append(entry);
}
return KService::Ptr(static_cast<KService *>(entry.data()));
}
// returns false if the database is up to date, true if it needs to be saved
bool KBuildSycoca::build()
{
using KBSEntryDictList = QList<KBSEntryDict *>;
KBSEntryDictList entryDictList;
KBSEntryDict *serviceEntryDict = nullptr;
// Convert for each factory the entryList to a Dict.
entryDictList.reserve(factories()->size());
int i = 0;
// For each factory
const auto &factoryList = *factories();
for (KSycocaFactory *factory : factoryList) {
KBSEntryDict *entryDict = new KBSEntryDict;
if (m_allEntries) { // incremental build
for (const KSycocaEntry::Ptr &entry : std::as_const((*m_allEntries).at(i++))) {
// if (entry->entryPath().contains("fake"))
// qCDebug(SYCOCA) << "inserting into entryDict:" << entry->entryPath() << entry;
entryDict->insert(entry->entryPath(), entry);
}
}
if (factory == d->m_serviceFactory) {
serviceEntryDict = entryDict;
} else if (factory == m_buildServiceGroupFactory) {
m_serviceGroupEntryDict = entryDict;
}
entryDictList.append(entryDict);
}
// Save the mtime of each dir, just before we list them
// ## should we convert to UTC to avoid surprises when summer time kicks in?
const auto lstDirs = factoryResourceDirs();
for (const QString &dir : lstDirs) {
qint64 stamp = 0;
KSycocaUtilsPrivate::visitResourceDirectory(dir, [&stamp](const QFileInfo &info) {
stamp = qMax(stamp, info.lastModified().toMSecsSinceEpoch());
return true;
});
m_allResourceDirs.insert(dir, stamp);
}
const auto lstFiles = factoryExtraFiles();
for (const QString &file : lstFiles) {
m_extraFiles.insert(file, QFileInfo(file).lastModified().toMSecsSinceEpoch());
}
QMap<QString, QByteArray> allResourcesSubDirs; // dirs, kstandarddirs-resource-name
// For each factory
for (KSycocaFactory *factory : factoryList) {
// For each resource the factory deals with
const KSycocaResourceList resourceList = factory->resourceList();
for (const KSycocaResource &res : resourceList) {
// With this we would get dirs, but not a unique list of relative files (for global+local merging to work)
// const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, res.subdir, QStandardPaths::LocateDirectory);
// allResourcesSubDirs[res.resource] += dirs;
allResourcesSubDirs.insert(res.subdir, res.resource);
}
}
m_ctimeFactory = new KCTimeFactory(this); // This is a build factory too, don't delete!!
for (auto it1 = allResourcesSubDirs.cbegin(); it1 != allResourcesSubDirs.cend(); ++it1) {
m_changed = false;
m_resourceSubdir = it1.key();
m_resource = it1.value();
QSet<QString> relFiles;
const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, m_resourceSubdir, QStandardPaths::LocateDirectory);
qCDebug(SYCOCA) << "Looking for subdir" << m_resourceSubdir << "=>" << dirs;
for (const QString &dir : dirs) {
QDirIterator it(dir, QDirIterator::Subdirectories);
while (it.hasNext()) {
const QString filePath = it.next();
Q_ASSERT(filePath.startsWith(dir)); // due to the line below...
const QString relPath = filePath.mid(dir.length() + 1);
relFiles.insert(relPath);
}
}
// Now find all factories that use this resource....
// For each factory -- and its corresponding entryDict (iterate over both lists in parallel)
for (int f = 0; f < factoryList.count(); ++f) {
KSycocaFactory *currentFactory = factoryList.at(f);
// m_ctimeInfo gets created after the initial loop, so it has no entryDict.
m_currentEntryDict = f == entryDictList.size() ? nullptr : entryDictList.at(f);
// For each resource the factory deals with
const KSycocaResourceList &resourceList = currentFactory->resourceList();
for (const KSycocaResource &res : resourceList) {
if (res.resource != m_resource) {
continue;
}
// For each file in the resource
for (const QString &entryPath : std::as_const(relFiles)) {
// Check if file matches filter
if (entryPath.endsWith(res.extension)) {
KSycocaEntry::Ptr entry = createEntry(currentFactory, entryPath);
if (entry) {
currentFactory->addEntry(entry);
}
}
}
}
}
}
bool result = true;
const bool createVFolder = true; // we need to always run the VFolderMenu code
if (createVFolder || m_menuTest) {
m_resource = "apps";
m_resourceSubdir = QStringLiteral("applications");
m_currentEntryDict = serviceEntryDict;
m_changed = false;
m_vfolder = new VFolderMenu(d->m_serviceFactory, this);
if (!m_trackId.isEmpty()) {
m_vfolder->setTrackId(m_trackId);
}
VFolderMenu::SubMenu *kdeMenu = m_vfolder->parseMenu(QStringLiteral("applications.menu"));
KServiceGroup::Ptr entry = m_buildServiceGroupFactory->addNew(QStringLiteral("/"), kdeMenu->directoryFile, KServiceGroup::Ptr(), false);
entry->setLayoutInfo(kdeMenu->layoutList);
createMenu(QString(), QString(), kdeMenu);
// Storing the mtime *after* looking at these dirs is a tiny race condition,
// but I'm not sure how to get the vfolder dirs upfront...
const auto allDirectories = m_vfolder->allDirectories();
for (QString dir : allDirectories) {
if (dir.endsWith(QLatin1Char('/'))) {
dir.chop(1); // remove trailing slash, to avoid having ~/.local/share/applications twice
}
if (!m_allResourceDirs.contains(dir)) {
qint64 stamp = 0;
KSycocaUtilsPrivate::visitResourceDirectory(dir, [&stamp](const QFileInfo &info) {
stamp = qMax(stamp, info.lastModified().toMSecsSinceEpoch());
return true;
});
m_allResourceDirs.insert(dir, stamp);
}
}
if (m_menuTest) {
result = false;
}
}
if (m_ctimeDict && !m_ctimeDict->isEmpty()) {
qCDebug(SYCOCA) << "Still in time dict:";
m_ctimeDict->dump();
}
qDeleteAll(entryDictList);
return result;
}
void KBuildSycoca::createMenu(const QString &caption_, const QString &name_, VFolderMenu::SubMenu *menu)
{
QString caption = caption_;
QString name = name_;
for (VFolderMenu::SubMenu *subMenu : std::as_const(menu->subMenus)) {
QString subName = name + subMenu->name + QLatin1Char('/');
QString directoryFile = subMenu->directoryFile;
if (directoryFile.isEmpty()) {
directoryFile = subName + QLatin1String(".directory");
}
quint32 timeStamp = m_ctimeFactory->dict()->ctime(directoryFile, m_resource);
if (!timeStamp) {
timeStamp = calcResourceHash(m_resourceSubdir, directoryFile);
}
KServiceGroup::Ptr entry;
if (m_allEntries) {
const quint32 oldTimestamp = m_ctimeDict->ctime(directoryFile, m_resource);
if (timeStamp && (timeStamp == oldTimestamp)) {
KSycocaEntry::Ptr group = m_serviceGroupEntryDict->value(subName);
if (group) {
entry = KServiceGroup::Ptr(static_cast<KServiceGroup *>(group.data()));
if (entry->directoryEntryPath() != directoryFile) {
entry = nullptr; // Can't reuse this one!
}
}
}
}
if (timeStamp) { // bug? (see calcResourceHash). There might not be a .directory file...
m_ctimeFactory->dict()->addCTime(directoryFile, m_resource, timeStamp);
}
entry = m_buildServiceGroupFactory->addNew(subName, subMenu->directoryFile, entry, subMenu->isDeleted);
entry->setLayoutInfo(subMenu->layoutList);
if (!(m_menuTest && entry->noDisplay())) {
createMenu(caption + entry->caption() + QLatin1Char('/'), subName, subMenu);
}
}
if (caption.isEmpty()) {
caption += QLatin1Char('/');
}
if (name.isEmpty()) {
name += QLatin1Char('/');
}
for (const KService::Ptr &p : std::as_const(menu->items)) {
if (m_menuTest) {
if (!menu->isDeleted && !p->noDisplay()) {
printf("%s\t%s\t%s\n",
qPrintable(caption),
qPrintable(p->menuId()),
qPrintable(QStandardPaths::locate(QStandardPaths::ApplicationsLocation, p->entryPath())));
}
} else {
m_buildServiceGroupFactory->addNewEntryTo(name, p);
}
}
}
bool KBuildSycoca::recreate(bool incremental)
{
QFileInfo fi(KSycoca::absoluteFilePath());
if (!QDir().mkpath(fi.absolutePath())) {
qCWarning(SYCOCA) << "Couldn't create" << fi.absolutePath();
return false;
}
QString path(fi.absoluteFilePath());
QLockFile lockFile(path + QLatin1String(".lock"));
if (!lockFile.tryLock()) {
qCDebug(SYCOCA) << "Waiting for already running" << KBUILDSYCOCA_EXENAME << "to finish.";
if (!lockFile.lock()) {
qCWarning(SYCOCA) << "Couldn't lock" << path + QLatin1String(".lock");
return false;
}
if (!needsRebuild()) {
// qCDebug(SYCOCA) << "Up-to-date, skipping.";
return true;
}
}
QByteArray qSycocaPath = QFile::encodeName(path);
s_cSycocaPath = qSycocaPath.data();
m_allEntries = nullptr;
m_ctimeDict = nullptr;
if (incremental && checkGlobalHeader()) {
qCDebug(SYCOCA) << "Reusing existing ksycoca";
KSycoca *oldSycoca = KSycoca::self();
m_allEntries = new KSycocaEntryListList;
m_ctimeDict = new KCTimeDict;
// Must be in same order as in KBuildSycoca::recreate()!
m_allEntries->append(KSycocaPrivate::self()->mimeTypeFactory()->allEntries());
m_allEntries->append(KSycocaPrivate::self()->serviceGroupFactory()->allEntries());
m_allEntries->append(KSycocaPrivate::self()->serviceFactory()->allEntries());
KCTimeFactory *ctimeInfo = new KCTimeFactory(oldSycoca);
*m_ctimeDict = ctimeInfo->loadDict();
}
s_cSycocaPath = nullptr;
QSaveFile database(path);
bool openedOK = database.open(QIODevice::WriteOnly);
if (!openedOK && database.error() == QFile::WriteError && QFile::exists(path)) {
QFile::remove(path);
openedOK = database.open(QIODevice::WriteOnly);
}
if (!openedOK) {
qCWarning(SYCOCA) << "ERROR creating database" << path << ":" << database.errorString();
return false;
}
QDataStream *str = new QDataStream(&database);
str->setVersion(QDataStream::Qt_5_3);
m_newTimestamp = QDateTime::currentMSecsSinceEpoch();
qCDebug(SYCOCA).nospace() << "Recreating ksycoca file (" << path << ", version " << KSycoca::version() << ")";
KBuildMimeTypeFactory *buildMimeTypeFactory = new KBuildMimeTypeFactory(this);
d->m_mimeTypeFactory = buildMimeTypeFactory;
m_buildServiceGroupFactory = new KBuildServiceGroupFactory(this);
d->m_serviceGroupFactory = m_buildServiceGroupFactory;
d->m_serviceFactory = new KBuildServiceFactory(buildMimeTypeFactory);
if (build()) { // Parse dirs
save(str); // Save database
if (str->status() != QDataStream::Ok) { // Probably unnecessary now in Qt5, since QSaveFile detects write errors
database.cancelWriting(); // Error
}
delete str;
str = nullptr;
// if we are currently via sudo, preserve the original owner
// as $HOME may also be that of another user rather than /root
#ifdef Q_OS_UNIX
if (qEnvironmentVariableIsSet("SUDO_UID")) {
const int uid = qEnvironmentVariableIntValue("SUDO_UID");
const int gid = qEnvironmentVariableIntValue("SUDO_GID");
if (uid && gid) {
fchown(database.handle(), uid, gid);
}
}
#endif
if (!database.commit()) {
qCWarning(SYCOCA) << "ERROR writing database" << database.fileName() << database.errorString();
return false;
}
} else {
delete str;
str = nullptr;
database.cancelWriting();
if (m_menuTest) {
return true;
}
qCDebug(SYCOCA) << "Database is up to date";
}
#ifndef QT_NO_SHAREDMEMORY
if (d->m_sycocaStrategy == KSycocaPrivate::StrategyMemFile) {
KMemFile::fileContentsChanged(path);
}
#endif
delete m_ctimeDict;
delete m_allEntries;
delete m_vfolder;
return true;
}
void KBuildSycoca::save(QDataStream *str)
{
// Write header (#pass 1)
str->device()->seek(0);
(*str) << qint32(KSycoca::version());
// KSycocaFactory * servicetypeFactory = 0;
// KBuildMimeTypeFactory * mimeTypeFactory = 0;
KBuildServiceFactory *serviceFactory = nullptr;
auto lst = *factories();
for (KSycocaFactory *factory : std::as_const(lst)) {
qint32 aId;
qint32 aOffset;
aId = factory->factoryId();
// if ( aId == KST_KServiceTypeFactory )
// servicetypeFactory = factory;
// else if ( aId == KST_KMimeTypeFactory )
// mimeTypeFactory = static_cast<KBuildMimeTypeFactory *>( factory );
if (aId == KST_KServiceFactory) {
serviceFactory = static_cast<KBuildServiceFactory *>(factory);
}
aOffset = factory->offset(); // not set yet, so always 0
(*str) << aId;
(*str) << aOffset;
}
(*str) << qint32(0); // No more factories.
// Write XDG_DATA_DIRS
(*str) << QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).join(QString(QLatin1Char(':')));
(*str) << m_newTimestamp;
(*str) << QLocale().bcp47Name();
// This makes it possible to trigger a ksycoca update for all users (KIOSK feature)
(*str) << calcResourceHash(QStringLiteral("kservices6"), QStringLiteral("update_ksycoca"));
(*str) << m_allResourceDirs.keys();
for (auto it = m_allResourceDirs.constBegin(); it != m_allResourceDirs.constEnd(); ++it) {
(*str) << it.value();
}
(*str) << m_extraFiles.keys();
for (auto it = m_extraFiles.constBegin(); it != m_extraFiles.constEnd(); ++it) {
(*str) << it.value();
}
// Calculate per-servicetype/MIME type data
if (serviceFactory) {
serviceFactory->postProcessServices();
}
// Here so that it's the last debug message
qCDebug(SYCOCA) << "Saving";
// Write factory data....
lst = *factories();
for (KSycocaFactory *factory : std::as_const(lst)) {
factory->save(*str);
if (str->status() != QDataStream::Ok) { // ######## TODO: does this detect write errors, e.g. disk full?
return; // error
}
}
qint64 endOfData = str->device()->pos();
// Write header (#pass 2)
str->device()->seek(0);
(*str) << qint32(KSycoca::version());
lst = *factories();
for (KSycocaFactory *factory : std::as_const(lst)) {
qint32 aId;
qint32 aOffset;
aId = factory->factoryId();
aOffset = factory->offset();
(*str) << aId;
(*str) << aOffset;
}
(*str) << qint32(0); // No more factories.
// Jump to end of database
str->device()->seek(endOfData);
}
QStringList KBuildSycoca::factoryResourceDirs()
{
static QStringList *dirs = nullptr;
if (dirs != nullptr) {
return *dirs;
}
dirs = new QStringList;
// these are all resource dirs cached by ksycoca
*dirs += KMimeTypeFactory::resourceDirs();
*dirs += KServiceFactory::resourceDirs();
return *dirs;
}
QStringList KBuildSycoca::factoryExtraFiles()
{
QStringList files;
// these are the extra files cached by ksycoca
// and whose timestamps are checked
files += KMimeAssociations::mimeAppsFiles();
return files;
}
QStringList KBuildSycoca::existingResourceDirs()
{
static QStringList *dirs = nullptr;
if (dirs != nullptr) {
return *dirs;
}
dirs = new QStringList(factoryResourceDirs());
auto checkDir = [](const QString &str) {
QFileInfo info(str);
return !info.exists() || !info.isReadable();
};
dirs->erase(std::remove_if(dirs->begin(), dirs->end(), checkDir), dirs->end());
return *dirs;
}
static quint32 updateHash(const QString &file, quint32 hash)
{
QFileInfo fi(file);
if (fi.isReadable() && fi.isFile()) {
// This was using buff.st_ctime (in Waldo's initial commit to kstandarddirs.cpp in 2001), but that looks wrong?
// Surely we want to catch manual editing, while a chmod doesn't matter much?
qint64 timestamp = fi.lastModified().toSecsSinceEpoch();
// On some systems (i.e. Fedora Kinoite), all files in /usr have a last
// modified timestamp of 0 (UNIX Epoch). In this case, always assume
// the file as been changed.
if (timestamp == 0) {
static qint64 now = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
timestamp = now;
}
hash += timestamp;
}
return hash;
}
quint32 KBuildSycoca::calcResourceHash(const QString &resourceSubDir, const QString &filename)
{
quint32 hash = 0;
if (!QDir::isRelativePath(filename)) {
return updateHash(filename, hash);
}
const QString filePath = resourceSubDir + QLatin1Char('/') + filename;
const QString qrcFilePath = QStringLiteral(":/") + filePath;
const QStringList files =
QFileInfo::exists(qrcFilePath) ? QStringList{qrcFilePath} : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, filePath);
for (const QString &file : files) {
hash = updateHash(file, hash);
}
if (hash == 0 && !filename.endsWith(QLatin1String("update_ksycoca"))
&& !filename.endsWith(QLatin1String(".directory")) // bug? needs investigation from someone who understands the VFolder spec
) {
if (files.isEmpty()) {
// This can happen if the file was deleted between directory listing and the above locateAll
qCDebug(SYCOCA) << "File not found anymore:" << filename << " -- probably deleted meanwhile";
} else {
// This can happen if the file was deleted between locateAll and QFileInfo
qCDebug(SYCOCA) << "File(s) found but not readable (or disappeared meanwhile)" << files;
}
}
return hash;
}
bool KBuildSycoca::checkGlobalHeader()
{
// Since it's part of the filename, we are 99% sure that the locale and prefixes will match.
const QString current_language = QLocale().bcp47Name();
const quint32 current_update_sig = KBuildSycoca::calcResourceHash(QStringLiteral("kservices6"), QStringLiteral("update_ksycoca"));
const QString current_prefixes = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).join(QString(QLatin1Char(':')));
const KSycocaHeader header = KSycocaPrivate::self()->readSycocaHeader();
Q_ASSERT(!header.prefixes.split(QLatin1Char(':')).contains(QDir::homePath()));
return (current_update_sig == header.updateSignature) //
&& (current_language == header.language) //
&& (current_prefixes == header.prefixes) //
&& (header.timeStamp != 0);
}
const char *KBuildSycoca::sycocaPath()
{
return s_cSycocaPath;
}
#include "moc_kbuildsycoca_p.cpp"
@@ -0,0 +1,138 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KBUILDSYCOCA_H
#define KBUILDSYCOCA_H
#include "kbuildsycocainterface_p.h"
#include <kservice.h>
#include <ksycoca.h>
#include "vfolder_menu_p.h"
class KBuildServiceGroupFactory;
class QDataStream;
class KCTimeFactory;
class KCTimeDict;
/**
* @internal
* Exported for kbuildsycoca, but not installed.
*/
class KSERVICE_EXPORT KBuildSycoca : public KSycoca, public KBuildSycocaInterface
{
Q_OBJECT
public:
explicit KBuildSycoca();
~KBuildSycoca() override;
/**
* Recreate the database file.
* @return true if it was indeed recreated (by us or possibly by someone else), false on error
*/
bool recreate(bool incremental = true);
void setTrackId(const QString &id)
{
m_trackId = id;
}
void setMenuTest(bool b)
{
m_menuTest = b;
}
static QStringList factoryResourceDirs();
static QStringList factoryExtraFiles();
static QStringList existingResourceDirs();
/**
* Returns a number that identifies the current version of the file @p filename,
* which is located under GenericDataLocation (including local overrides).
*
* When a change is made to the file this number will change.
*/
static quint32 calcResourceHash(const QString &subdir, const QString &filename);
/**
* Compare our current settings (language, prefixes...) with the ones from the existing ksycoca global header.
* @return true if they match (= we can reuse this ksycoca), false otherwise (full build)
*/
bool checkGlobalHeader();
/**
* @brief path to the sycoca file, for the crash handler in kbuildsycoca
*/
static const char *sycocaPath();
private:
/**
* Add single entry to the sycoca database.
* Either from a previous database or regenerated from file.
*/
KSERVICE_NO_EXPORT KSycocaEntry::Ptr createEntry(KSycocaFactory *currentFactory, const QString &file);
/**
* Implementation of KBuildSycocaInterface
* Create service and return it. The caller must add it to the servicefactory.
*/
KService::Ptr createService(const QString &path) override;
/**
* Convert a VFolderMenu::SubMenu to KServiceGroups.
*/
KSERVICE_NO_EXPORT void createMenu(const QString &caption, const QString &name, VFolderMenu::SubMenu *menu);
/**
* Build the whole system cache, from .desktop files
*/
KSERVICE_NO_EXPORT bool build();
/**
* Save the ksycoca file
*/
KSERVICE_NO_EXPORT void save(QDataStream *str);
/**
* Clear the factories
*/
KSERVICE_NO_EXPORT void clear();
/**
* @internal
* @return true if building (i.e. if a KBuildSycoca);
*/
bool isBuilding() override
{
return true;
}
QMap<QString, qint64> m_allResourceDirs; // dir, mtime in ms since epoch
QMap<QString, qint64> m_extraFiles; // file, mtime in ms since epoch
QString m_trackId;
QByteArray m_resource; // e.g. "services" (old resource name, now only used for the signal, see kctimefactory.cpp)
QString m_resourceSubdir; // e.g. "mime" (xdgdata subdir)
KSycocaEntry::List m_tempStorage;
typedef QList<KSycocaEntry::List> KSycocaEntryListList;
KSycocaEntryListList *m_allEntries; // entries from existing ksycoca
KBuildServiceGroupFactory *m_buildServiceGroupFactory = nullptr;
KCTimeFactory *m_ctimeFactory = nullptr;
KCTimeDict *m_ctimeDict; // old timestamps
typedef QHash<QString, KSycocaEntry::Ptr> KBSEntryDict;
KBSEntryDict *m_currentEntryDict = nullptr;
KBSEntryDict *m_serviceGroupEntryDict = nullptr;
VFolderMenu *m_vfolder = nullptr;
qint64 m_newTimestamp;
bool m_menuTest;
bool m_changed;
};
#endif
@@ -0,0 +1,20 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KBUILDSYCOCAINTERFACE_H
#define KBUILDSYCOCAINTERFACE_H
#include <kservice.h>
class KBuildSycocaInterface
{
public:
virtual ~KBuildSycocaInterface();
virtual KService::Ptr createService(const QString &path) = 0;
};
#endif /* KBUILDSYCOCAINTERFACE_H */
@@ -0,0 +1,110 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "kctimefactory_p.h"
#include "sycocadebug.h"
#include <ksycoca.h>
#include <ksycocatype.h>
#include <ksycocautils_p.h>
#include <assert.h>
// NOTE: the storing of "resource" here is now completely useless (since everything is under GenericDataLocation)
static inline QString key(const QString &path, const QByteArray &resource)
{
return QString::fromLatin1(resource) + QLatin1Char('|') + path;
}
void KCTimeDict::addCTime(const QString &path, const QByteArray &resource, quint32 ctime)
{
Q_ASSERT(ctime != 0);
assert(!path.isEmpty());
m_hash.insert(key(path, resource), ctime);
}
quint32 KCTimeDict::ctime(const QString &path, const QByteArray &resource) const
{
return m_hash.value(key(path, resource), 0);
}
void KCTimeDict::remove(const QString &path, const QByteArray &resource)
{
m_hash.remove(key(path, resource));
}
void KCTimeDict::dump() const
{
qCDebug(SYCOCA) << m_hash.keys();
}
void KCTimeDict::load(QDataStream &str)
{
QString key;
quint32 ctime;
while (true) {
str >> key >> ctime;
if (key.isEmpty()) {
break;
}
m_hash.insert(key, ctime);
}
}
void KCTimeDict::save(QDataStream &str) const
{
for (auto it = m_hash.cbegin(), endIt = m_hash.cend(); it != endIt; ++it) {
str << it.key() << it.value();
}
str << QString() << quint32(0);
}
///////////
KCTimeFactory::KCTimeFactory(KSycoca *db)
: KSycocaFactory(KST_CTimeInfo, db)
, m_ctimeDict()
{
if (!sycoca()->isBuilding()) {
QDataStream *str = stream();
(*str) >> m_dictOffset;
} else {
m_dictOffset = 0;
}
}
KCTimeFactory::~KCTimeFactory()
{
}
void KCTimeFactory::saveHeader(QDataStream &str)
{
KSycocaFactory::saveHeader(str);
str << m_dictOffset;
}
void KCTimeFactory::save(QDataStream &str)
{
KSycocaFactory::save(str);
m_dictOffset = str.device()->pos();
m_ctimeDict.save(str);
const qint64 endOfFactoryData = str.device()->pos();
saveHeader(str);
str.device()->seek(endOfFactoryData);
}
KCTimeDict KCTimeFactory::loadDict() const
{
KCTimeDict dict;
QDataStream *str = stream();
assert(str);
str->device()->seek(m_dictOffset);
dict.load(*str);
return dict;
}
@@ -0,0 +1,86 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2000 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KCTIME_FACTORY_H
#define KCTIME_FACTORY_H
#include <QHash>
#include <ksycocafactory_p.h>
/**
* Simple dict for associating a timestamp with each file in ksycoca
*/
class KCTimeDict
{
public:
void addCTime(const QString &path, const QByteArray &resource, quint32 ctime);
quint32 ctime(const QString &path, const QByteArray &resource) const;
void remove(const QString &path, const QByteArray &resource);
void dump() const;
bool isEmpty() const
{
return m_hash.isEmpty();
}
void load(QDataStream &str);
void save(QDataStream &str) const;
private:
typedef QHash<QString, quint32> Hash;
Hash m_hash;
};
/**
* Internal factory for storing the timestamp of each file in ksycoca
* @internal
*/
class KCTimeFactory : public KSycocaFactory
{
K_SYCOCAFACTORY(KST_CTimeInfo)
public:
/**
* Create factory
*/
explicit KCTimeFactory(KSycoca *db);
~KCTimeFactory() override;
/**
* Write out header information
*/
void saveHeader(QDataStream &str) override;
/**
* Write out data
*/
void save(QDataStream &str) override;
KSycocaEntry *createEntry(const QString &) const override
{
return nullptr;
}
KSycocaEntry *createEntry(int) const override
{
return nullptr;
}
// Loads the dict and returns it; does not set m_ctimeDict;
// this is only used in incremental mode for loading the old timestamps.
KCTimeDict loadDict() const;
// The API for inserting/looking up entries is in KCTimeDict.
KCTimeDict *dict()
{
return &m_ctimeDict;
}
private:
KCTimeDict m_ctimeDict;
int m_dictOffset;
};
#endif
@@ -0,0 +1,250 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2008 Christian Ehrlicher <ch.ehrlicher@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "kmemfile_p.h"
#ifndef QT_NO_SHAREDMEMORY
#include <QCoreApplication>
#include <QCryptographicHash>
#include <QDir>
#include <QFile>
#include <QSharedMemory>
class KMemFile::Private
{
public:
struct sharedInfoData {
int shmCounter;
qint64 shmDataSize;
sharedInfoData()
{
memset(this, 0, sizeof(*this));
}
};
Private(KMemFile *_parent)
: readWritePos(0)
, shmDataSize(0)
, parent(_parent)
{
}
QString getShmKey(int iCounter = -1);
static QString getShmKey(const QString &filename, int iCounter = -1);
bool loadContentsFromFile();
void close();
QString filename;
QSharedMemory shmInfo;
QSharedMemory shmData;
qint64 readWritePos;
qint64 shmDataSize;
KMemFile *parent;
};
QString KMemFile::Private::getShmKey(int iCounter)
{
return getShmKey(filename, iCounter);
}
QString KMemFile::Private::getShmKey(const QString &filename, int iCounter)
{
QByteArray tmp = QString(QDir(filename).canonicalPath() + QString::number(iCounter)).toUtf8();
return QString::fromLatin1(QCryptographicHash::hash(tmp, QCryptographicHash::Sha1));
}
bool KMemFile::Private::loadContentsFromFile()
{
QFile f(filename);
if (!f.exists()) {
close();
parent->setErrorString(QCoreApplication::translate("", "File %1 does not exist").arg(filename));
return false;
}
if (!f.open(QIODevice::ReadOnly)) {
close();
parent->setErrorString(QCoreApplication::translate("", "Cannot open %1 for reading").arg(filename));
return false;
}
sharedInfoData *infoPtr = static_cast<sharedInfoData *>(shmInfo.data());
infoPtr->shmDataSize = f.size();
shmData.setKey(getShmKey(infoPtr->shmCounter));
if (!shmData.create(infoPtr->shmDataSize)) {
close();
parent->setErrorString(QCoreApplication::translate("", "Cannot create memory segment for file %1").arg(filename));
return false;
}
shmData.lock();
qint64 size = 0;
qint64 bytesRead;
char *data = static_cast<char *>(shmData.data());
bytesRead = f.read(data, infoPtr->shmDataSize);
if (bytesRead != infoPtr->shmDataSize) {
close();
parent->setErrorString(QCoreApplication::translate("", "Could not read data from %1 into shm").arg(filename));
return false;
}
shmDataSize = size;
shmData.unlock();
return true;
}
void KMemFile::Private::close()
{
shmData.unlock();
shmData.detach();
shmInfo.unlock();
shmInfo.detach();
readWritePos = 0;
shmDataSize = 0;
}
KMemFile::KMemFile(const QString &filename, QObject *parent)
: QIODevice(parent)
, d(new Private(this))
{
d->filename = filename;
}
KMemFile::~KMemFile()
{
close();
}
void KMemFile::close()
{
QIODevice::close();
if (!isOpen()) {
return;
}
d->close();
}
bool KMemFile::isSequential() const
{
return false;
}
bool KMemFile::open(OpenMode mode)
{
if (isOpen()) {
QIODevice::open(mode);
return false;
}
if (mode != QIODevice::ReadOnly) {
setErrorString(QCoreApplication::translate("", "Only 'ReadOnly' allowed"));
return false;
}
if (!QFile::exists(d->filename)) {
setErrorString(QCoreApplication::translate("", "File %1 does not exist").arg(d->filename));
return false;
}
QSharedMemory lock(QDir(d->filename).canonicalPath());
lock.lock();
Private::sharedInfoData *infoPtr;
d->shmInfo.setKey(d->getShmKey());
// see if it's already in memory
if (!d->shmInfo.attach(QSharedMemory::ReadWrite)) {
if (!d->shmInfo.create(sizeof(Private::sharedInfoData))) {
lock.unlock();
setErrorString(QCoreApplication::translate("", "Cannot create memory segment for file %1").arg(d->filename));
return false;
}
d->shmInfo.lock();
// no -> create it
infoPtr = static_cast<Private::sharedInfoData *>(d->shmInfo.data());
memset(infoPtr, 0, sizeof(Private::sharedInfoData));
infoPtr->shmCounter = 1;
if (!d->loadContentsFromFile()) {
d->shmInfo.unlock();
d->shmInfo.detach();
lock.unlock();
return false;
}
} else {
d->shmInfo.lock();
infoPtr = static_cast<Private::sharedInfoData *>(d->shmInfo.data());
d->shmData.setKey(d->getShmKey(infoPtr->shmCounter));
if (!d->shmData.attach(QSharedMemory::ReadOnly)) {
if (!d->loadContentsFromFile()) {
d->shmInfo.unlock();
d->shmInfo.detach();
lock.unlock();
return false;
}
}
}
d->shmDataSize = infoPtr->shmDataSize;
d->shmInfo.unlock();
lock.unlock();
setOpenMode(mode);
return true;
}
bool KMemFile::seek(qint64 pos)
{
if (d->shmDataSize < pos) {
setErrorString(QCoreApplication::translate("", "Cannot seek past eof"));
return false;
}
d->readWritePos = pos;
QIODevice::seek(pos);
return true;
}
qint64 KMemFile::size() const
{
return d->shmDataSize;
}
qint64 KMemFile::readData(char *data, qint64 maxSize)
{
if ((openMode() & QIODevice::ReadOnly) == 0) {
return -1;
}
qint64 maxRead = size() - d->readWritePos;
qint64 bytesToRead = qMin(maxRead, maxSize);
const char *src = static_cast<const char *>(d->shmData.data());
memcpy(data, &src[d->readWritePos], bytesToRead);
d->readWritePos += bytesToRead;
return bytesToRead;
}
qint64 KMemFile::writeData(const char *, qint64)
{
return -1;
}
void KMemFile::fileContentsChanged(const QString &filename)
{
QSharedMemory lock(QDir(filename).canonicalPath());
lock.lock();
QSharedMemory shmData(Private::getShmKey(filename));
if (!shmData.attach()) {
return;
}
shmData.lock();
Private::sharedInfoData *infoPtr = static_cast<Private::sharedInfoData *>(shmData.data());
infoPtr->shmCounter++;
infoPtr->shmDataSize = 0;
shmData.unlock();
}
#endif // QT_NO_SHAREDMEMORY
#include "moc_kmemfile_p.cpp"
@@ -0,0 +1,91 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2008 Christian Ehrlicher <ch.ehrlicher@gmx.de>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KMEMFILE_H
#define KMEMFILE_H
#include <QIODevice>
#include <kservice_export.h>
#include <memory>
#ifndef QT_NO_SHAREDMEMORY
/**
* @internal
* Simple QIODevice for QSharedMemory to keep ksycoca cache in memory only once
* The first call to open() loads the file into a shm segment. Every
* subsequent call only attaches to this segment. When the file content changed,
* you have to execute KMemFile::fileContentsChanged() to update the internal
* structures. The next call to open() creates a new shm segment. The old one
* is automatically destroyed when the last process closed KMemFile.
*/
class KMemFile : public QIODevice
{
Q_OBJECT
public:
/**
* ctor
*
* @param filename the file to load into memory
* @param parent our parent
*/
explicit KMemFile(const QString &filename, QObject *parent = nullptr);
/**
* dtor
*/
~KMemFile() override;
/**
* closes the KMemFile
*
* @reimp
*/
void close() override;
/**
* As KMemFile is a random access device, it returns false
*
* @reimp
*/
bool isSequential() const override;
/**
* @reimp
* @param mode only QIODevice::ReadOnly is accepted
*/
bool open(OpenMode mode) override;
/**
* Sets the current read/write position to pos
* @reimp
* @param pos the new read/write position
*/
bool seek(qint64 pos) override;
/**
* Returns the size of the file
* @reimp
*/
qint64 size() const override;
/**
* This static function updates the internal information about the file
* loaded into shared memory. The next time the file is opened, the file is
* reread from the file system.
*/
static void fileContentsChanged(const QString &filename);
protected:
/** @reimp */
qint64 readData(char *data, qint64 maxSize) override;
/** @reimp */
qint64 writeData(const char *data, qint64 maxSize) override;
private:
class Private;
friend class Private;
std::unique_ptr<Private> const d;
};
#endif // QT_NO_SHAREDMEMORY
#endif // KMEMFILE_H
@@ -0,0 +1,196 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2008 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kmimeassociations_p.h"
#include "sycocadebug.h"
#include <KConfig>
#include <KConfigGroup>
#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QMimeDatabase>
#include <QStandardPaths>
#include <kservice.h>
#include <kservicefactory_p.h>
KMimeAssociations::KMimeAssociations(KOfferHash &offerHash, KServiceFactory *serviceFactory)
: m_offerHash(offerHash)
, m_serviceFactory(serviceFactory)
{
}
/*
The goal of this class is to parse mimeapps.list files, which are used to
let users configure the application-MIME type associations.
Example file:
[Added Associations]
text/plain=gnome-gedit.desktop;gnu-emacs.desktop;
[Removed Associations]
text/plain=gnome-gedit.desktop;gnu-emacs.desktop;
[Default Applications]
text/plain=kate.desktop;
*/
QStringList KMimeAssociations::mimeAppsFiles()
{
QStringList mimeappsFileNames;
// make the list of possible filenames from the spec ($desktop-mimeapps.list, then mimeapps.list)
const QString desktops = QString::fromLocal8Bit(qgetenv("XDG_CURRENT_DESKTOP"));
const auto list = desktops.split(QLatin1Char(':'), Qt::SkipEmptyParts);
for (const QString &desktop : list) {
mimeappsFileNames.append(desktop.toLower() + QLatin1String("-mimeapps.list"));
}
mimeappsFileNames.append(QStringLiteral("mimeapps.list"));
const QStringList mimeappsDirs = mimeAppsDirs();
QStringList mimeappsFiles;
// collect existing files
for (const QString &dir : mimeappsDirs) {
for (const QString &file : std::as_const(mimeappsFileNames)) {
const QFileInfo fileInfo(dir + QLatin1Char('/') + file);
const QString filePath = fileInfo.canonicalFilePath();
if (!filePath.isEmpty() && !mimeappsFiles.contains(filePath)) {
mimeappsFiles.append(filePath);
}
}
}
return mimeappsFiles;
}
QStringList KMimeAssociations::mimeAppsDirs()
{
// list the dirs in the order of the spec (XDG_CONFIG_HOME, XDG_CONFIG_DIRS, XDG_DATA_HOME, XDG_DATA_DIRS)
return QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation) + QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
}
void KMimeAssociations::parseAllMimeAppsList()
{
int basePreference = 1000; // start high :)
const QStringList files = KMimeAssociations::mimeAppsFiles();
// Global first, then local
auto it = files.crbegin();
auto endIt = files.crend();
for (; it != endIt; ++it) {
// qDebug() << "Parsing" << mimeappsFile;
parseMimeAppsList(*it, basePreference);
basePreference += 50;
}
}
void KMimeAssociations::parseMimeAppsList(const QString &file, int basePreference)
{
KConfig profile(file, KConfig::SimpleConfig);
if (file.endsWith(QLatin1String("/mimeapps.list"))) { // not for $desktop-mimeapps.list
parseAddedAssociations(KConfigGroup(&profile, QStringLiteral("Added Associations")), file, basePreference);
parseRemovedAssociations(KConfigGroup(&profile, QStringLiteral("Removed Associations")), file);
// KDE extension for parts and plugins, see settings/filetypes/mimetypedata.cpp
parseAddedAssociations(KConfigGroup(&profile, QStringLiteral("Added KDE Service Associations")), file, basePreference);
parseRemovedAssociations(KConfigGroup(&profile, QStringLiteral("Removed KDE Service Associations")), file);
}
// Default Applications is preferred over Added Associations.
// Other than that, they work the same...
// add 25 to the basePreference to make sure those service offers will have higher preferences
// 25 is arbitrary half of the allocated preference indices for the current parsed mimeapps.list file, defined line 86
parseAddedAssociations(KConfigGroup(&profile, QStringLiteral("Default Applications")), file, basePreference + 25);
}
void KMimeAssociations::parseAddedAssociations(const KConfigGroup &group, const QString &file, int basePreference)
{
Q_UNUSED(file) // except in debug statements
QMimeDatabase db;
const auto keyList = group.keyList();
for (const QString &mimeName : keyList) {
const QStringList services = group.readXdgListEntry(mimeName);
const QString resolvedMimeName = mimeName.startsWith(QLatin1String("x-scheme-handler/")) ? mimeName : db.mimeTypeForName(mimeName).name();
if (resolvedMimeName.isEmpty()) {
qCDebug(SYCOCA) << file << "specifies unknown MIME type" << mimeName << "in" << group.name();
} else {
int pref = basePreference;
for (const QString &service : services) {
KService::Ptr pService = m_serviceFactory->findServiceByStorageId(service);
if (!pService) {
qCDebug(SYCOCA) << file << "specifies unknown service" << service << "in" << group.name();
} else {
// qDebug() << "adding mime" << resolvedMimeName << "to service" << pService->entryPath() << "pref=" << pref;
m_offerHash.addServiceOffer(resolvedMimeName, KServiceOffer(pService, pref, 0));
--pref;
}
}
}
}
}
void KMimeAssociations::parseRemovedAssociations(const KConfigGroup &group, const QString &file)
{
Q_UNUSED(file) // except in debug statements
const auto keyList = group.keyList();
for (const QString &mime : keyList) {
const QStringList services = group.readXdgListEntry(mime);
for (const QString &service : services) {
KService::Ptr pService = m_serviceFactory->findServiceByStorageId(service);
if (!pService) {
// qDebug() << file << "specifies unknown service" << service << "in" << group.name();
} else {
// qDebug() << "removing mime" << mime << "from service" << pService.data() << pService->entryPath();
m_offerHash.removeServiceOffer(mime, pService);
}
}
}
}
void KOfferHash::addServiceOffer(const QString &serviceType, const KServiceOffer &offer)
{
KService::Ptr service = offer.service();
// qDebug() << "Adding" << service->entryPath() << "to" << serviceType << offer.preference();
ServiceTypeOffersData &data = m_serviceTypeData[serviceType]; // find or create
QList<KServiceOffer> &offers = data.offers;
QSet<KService::Ptr> &offerSet = data.offerSet;
if (!offerSet.contains(service)) {
offers.append(offer);
offerSet.insert(service);
} else {
const int initPref = offer.preference();
// qDebug() << service->entryPath() << "already in" << serviceType;
// This happens when mimeapps.list mentions a service (to make it preferred)
// Update initialPreference to std::max(existing offer, new offer)
for (KServiceOffer &servOffer : data.offers) {
if (servOffer.service() == service) { // we can compare KService::Ptrs because they are from the memory hash
servOffer.setPreference(std::max(servOffer.preference(), initPref));
}
}
}
}
void KOfferHash::removeServiceOffer(const QString &serviceType, const KService::Ptr &service)
{
ServiceTypeOffersData &data = m_serviceTypeData[serviceType]; // find or create
data.removedOffers.insert(service);
data.offerSet.remove(service);
const QString id = service->storageId();
auto &list = data.offers;
auto it = std::remove_if(list.begin(), list.end(), [&id](const KServiceOffer &offer) {
return offer.service()->storageId() == id;
});
list.erase(it, list.end());
}
bool KOfferHash::hasRemovedOffer(const QString &serviceType, const KService::Ptr &service) const
{
auto it = m_serviceTypeData.constFind(serviceType);
if (it != m_serviceTypeData.cend()) {
return it.value().removedOffers.contains(service);
}
return false;
}
@@ -0,0 +1,79 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2008 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KMIMEASSOCIATIONS_H
#define KMIMEASSOCIATIONS_H
#include <QHash>
#include <QSet>
#include <kserviceoffer.h>
class KConfigGroup;
class KServiceFactory;
struct ServiceTypeOffersData {
QList<KServiceOffer> offers; // service + initial preference + allow as default
QSet<KService::Ptr> offerSet; // for quick contains() check
QSet<KService::Ptr> removedOffers; // remember removed offers explicitly
};
class KOfferHash
{
public:
KOfferHash()
{
}
KOfferHash(const KOfferHash &) = delete;
KOfferHash &operator=(const KOfferHash &) = delete;
QList<KServiceOffer> offersFor(const QString &serviceType) const
{
auto it = m_serviceTypeData.constFind(serviceType);
if (it != m_serviceTypeData.cend()) {
return it.value().offers;
}
return QList<KServiceOffer>();
}
void addServiceOffer(const QString &serviceType, const KServiceOffer &offer);
void removeServiceOffer(const QString &serviceType, const KService::Ptr &service);
bool hasRemovedOffer(const QString &serviceType, const KService::Ptr &service) const;
const QHash<QString, ServiceTypeOffersData> &serviceTypeData() const
{
return m_serviceTypeData;
}
private:
QHash<QString, ServiceTypeOffersData> m_serviceTypeData;
};
/**
* Parse mimeapps.list files and:
* - modify MIME type associations in the relevant services (using KServiceFactory)
* - remember preference order specified by user
*/
class KMimeAssociations
{
public:
explicit KMimeAssociations(KOfferHash &offerHash, KServiceFactory *serviceFactory);
static QStringList mimeAppsFiles();
// Read mimeapps.list files
void parseAllMimeAppsList();
void parseMimeAppsList(const QString &file, int basePreference);
private:
static QStringList mimeAppsDirs();
void parseAddedAssociations(const KConfigGroup &group, const QString &file, int basePreference);
void parseRemovedAssociations(const KConfigGroup &group, const QString &file);
KOfferHash &m_offerHash;
KServiceFactory *m_serviceFactory;
};
#endif /* KMIMEASSOCIATIONS_H */
@@ -0,0 +1,821 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999-2000 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2005-2009 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2008 Hamish Rodda <rodda@kde.org>
SPDX-FileCopyrightText: 2020-2022 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "ksycoca.h"
#include "ksycoca_p.h"
#include "ksycocafactory_p.h"
#include "ksycocatype.h"
#include "ksycocautils_p.h"
#include "sycocadebug.h"
#include <KConfigGroup>
#include <KSandbox>
#include <KSharedConfig>
#include <QCoreApplication>
#include <QDataStream>
#include <QFile>
#include <QFileInfo>
#include <QMetaMethod>
#include <QStandardPaths>
#include <QThread>
#include <QThreadStorage>
#include <QCryptographicHash>
#include <fcntl.h>
#include <kmimetypefactory_p.h>
#include <kservicefactory_p.h>
#include <kservicegroupfactory_p.h>
#include "kbuildsycoca_p.h"
#include "ksycocadevices_p.h"
#ifdef Q_OS_UNIX
#include <sys/time.h>
#include <utime.h>
#endif
/**
* Sycoca file version number.
* If the existing file is outdated, it will not get read
* but instead we'll regenerate a new one.
* However running apps should still be able to read it, so
* only add to the data, never remove/modify.
*/
#define KSYCOCA_VERSION 306
#if HAVE_MADVISE || HAVE_MMAP
#include <sys/mman.h> // This #include was checked when looking for posix_madvise
#endif
#ifndef MAP_FAILED
#define MAP_FAILED ((void *)-1)
#endif
QDataStream &operator>>(QDataStream &in, KSycocaHeader &h)
{
in >> h.prefixes >> h.timeStamp >> h.language >> h.updateSignature;
return in;
}
// The following limitations are in place:
// Maximum length of a single string: 8192 bytes
// Maximum length of a string list: 1024 strings
// Maximum number of entries: 8192
//
// The purpose of these limitations is to limit the impact
// of database corruption.
Q_DECLARE_OPERATORS_FOR_FLAGS(KSycocaPrivate::BehaviorsIfNotFound)
KSycocaPrivate::KSycocaPrivate(KSycoca *qq)
: databaseStatus(DatabaseNotOpen)
, readError(false)
, timeStamp(0)
, m_databasePath()
, updateSig(0)
, m_fileWatcher(new KDirWatch)
, m_haveListeners(false)
, q(qq)
, sycoca_size(0)
, sycoca_mmap(nullptr)
, m_mmapFile(nullptr)
, m_device(nullptr)
, m_mimeTypeFactory(nullptr)
, m_serviceFactory(nullptr)
, m_serviceGroupFactory(nullptr)
{
#ifdef Q_OS_WIN
/*
on windows we use KMemFile (QSharedMemory) to avoid problems
with mmap (can't delete a mmap'd file)
*/
m_sycocaStrategy = StrategyMemFile;
#else
m_sycocaStrategy = StrategyMmap;
#endif
KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("KSycoca"));
setStrategyFromString(config.readEntry("strategy"));
}
void KSycocaPrivate::setStrategyFromString(const QString &strategy)
{
if (strategy == QLatin1String("mmap")) {
m_sycocaStrategy = StrategyMmap;
} else if (strategy == QLatin1String("file")) {
m_sycocaStrategy = StrategyFile;
} else if (strategy == QLatin1String("sharedmem")) {
m_sycocaStrategy = StrategyMemFile;
} else if (!strategy.isEmpty()) {
qCWarning(SYCOCA) << "Unknown sycoca strategy:" << strategy;
}
}
bool KSycocaPrivate::tryMmap()
{
#if HAVE_MMAP
Q_ASSERT(!m_databasePath.isEmpty());
m_mmapFile = new QFile(m_databasePath);
const bool canRead = m_mmapFile->open(QIODevice::ReadOnly);
Q_ASSERT(canRead);
if (!canRead) {
return false;
}
fcntl(m_mmapFile->handle(), F_SETFD, FD_CLOEXEC);
sycoca_size = m_mmapFile->size();
void *mmapRet = mmap(nullptr, sycoca_size, PROT_READ, MAP_SHARED, m_mmapFile->handle(), 0);
/* POSIX mandates only MAP_FAILED, but we are paranoid so check for
null pointer too. */
if (mmapRet == MAP_FAILED || mmapRet == nullptr) {
qCDebug(SYCOCA).nospace() << "mmap failed. (length = " << sycoca_size << ")";
sycoca_mmap = nullptr;
return false;
} else {
sycoca_mmap = static_cast<const char *>(mmapRet);
#if HAVE_MADVISE
(void)posix_madvise(mmapRet, sycoca_size, POSIX_MADV_WILLNEED);
#endif // HAVE_MADVISE
return true;
}
#else
return false;
#endif // HAVE_MMAP
}
int KSycoca::version()
{
return KSYCOCA_VERSION;
}
class KSycocaSingleton
{
public:
KSycocaSingleton()
{
}
~KSycocaSingleton()
{
}
bool hasSycoca() const
{
return m_threadSycocas.hasLocalData();
}
KSycoca *sycoca()
{
if (!m_threadSycocas.hasLocalData()) {
m_threadSycocas.setLocalData(new KSycoca);
}
return m_threadSycocas.localData();
}
void setSycoca(KSycoca *s)
{
m_threadSycocas.setLocalData(s);
}
private:
QThreadStorage<KSycoca *> m_threadSycocas;
};
Q_GLOBAL_STATIC(KSycocaSingleton, ksycocaInstance)
QString KSycocaPrivate::findDatabase()
{
Q_ASSERT(databaseStatus == DatabaseNotOpen);
const QString path = KSycoca::absoluteFilePath();
const QFileInfo info(path);
if (info.isReadable()) {
if (m_haveListeners && m_fileWatcher) {
m_fileWatcher->addFile(path);
}
return path;
}
// Let's be notified when it gets created - by another process or by ourselves
if (m_fileWatcher) {
m_fileWatcher->addFile(path);
}
return QString();
}
// Read-only constructor
// One instance per thread
KSycoca::KSycoca()
: d(new KSycocaPrivate(this))
{
if (d->m_fileWatcher) {
// We always delete and recreate the DB, so KDirWatch normally emits created
connect(d->m_fileWatcher.get(), &KDirWatch::created, this, [this]() {
d->slotDatabaseChanged();
});
// In some cases, KDirWatch only thinks the file was modified though
connect(d->m_fileWatcher.get(), &KDirWatch::dirty, this, [this]() {
d->slotDatabaseChanged();
});
}
}
bool KSycocaPrivate::openDatabase()
{
Q_ASSERT(databaseStatus == DatabaseNotOpen);
delete m_device;
m_device = nullptr;
if (m_databasePath.isEmpty()) {
m_databasePath = findDatabase();
}
bool result = true;
if (!m_databasePath.isEmpty()) {
static bool firstTime = true;
if (firstTime) {
firstTime = false;
if (KSandbox::isFlatpak()) {
// We're running inside flatpak, which sets all times to 1970
// So the first very time, don't use an existing database, recreate it
qCDebug(SYCOCA) << "flatpak detected, ignoring" << m_databasePath;
return false;
}
}
qCDebug(SYCOCA) << "Opening ksycoca from" << m_databasePath;
m_dbLastModified = QFileInfo(m_databasePath).lastModified();
result = checkVersion();
} else { // No database file
// qCDebug(SYCOCA) << "Could not open ksycoca";
result = false;
}
return result;
}
KSycocaAbstractDevice *KSycocaPrivate::device()
{
if (m_device) {
return m_device;
}
KSycocaAbstractDevice *device = m_device;
Q_ASSERT(!m_databasePath.isEmpty());
#if HAVE_MMAP
if (m_sycocaStrategy == StrategyMmap && tryMmap()) {
device = new KSycocaMmapDevice(sycoca_mmap, sycoca_size);
if (!device->device()->open(QIODevice::ReadOnly)) {
delete device;
device = nullptr;
}
}
#endif
#ifndef QT_NO_SHAREDMEMORY
if (!device && m_sycocaStrategy == StrategyMemFile) {
device = new KSycocaMemFileDevice(m_databasePath);
if (!device->device()->open(QIODevice::ReadOnly)) {
delete device;
device = nullptr;
}
}
#endif
if (!device) {
device = new KSycocaFileDevice(m_databasePath);
if (!device->device()->open(QIODevice::ReadOnly)) {
qCWarning(SYCOCA) << "Couldn't open" << m_databasePath << "even though it is readable? Impossible.";
// delete device; device = 0; // this would crash in the return statement...
}
}
if (device) {
m_device = device;
}
return m_device;
}
QDataStream *&KSycocaPrivate::stream()
{
if (!m_device) {
if (databaseStatus == DatabaseNotOpen) {
checkDatabase(KSycocaPrivate::IfNotFoundRecreate);
}
device(); // create m_device
}
return m_device->stream();
}
void KSycocaPrivate::slotDatabaseChanged()
{
qCDebug(SYCOCA) << QThread::currentThread() << "got a notifyDatabaseChanged signal";
// In case we have changed the database outselves, we have already notified the application
if (!m_dbLastModified.isValid() || m_dbLastModified != QFileInfo(m_databasePath).lastModified()) {
// KDirWatch tells us the database file changed
// We would have found out in the next call to ensureCacheValid(), but for
// now keep the call to closeDatabase, to help refcounting to 0 the old mmapped file earlier.
closeDatabase();
// Start monitoring the new file right away
m_databasePath = findDatabase();
// Now notify applications
Q_EMIT q->databaseChanged();
}
}
KMimeTypeFactory *KSycocaPrivate::mimeTypeFactory()
{
if (!m_mimeTypeFactory) {
m_mimeTypeFactory = new KMimeTypeFactory(q);
}
return m_mimeTypeFactory;
}
KServiceFactory *KSycocaPrivate::serviceFactory()
{
if (!m_serviceFactory) {
m_serviceFactory = new KServiceFactory(q);
}
return m_serviceFactory;
}
KServiceGroupFactory *KSycocaPrivate::serviceGroupFactory()
{
if (!m_serviceGroupFactory) {
m_serviceGroupFactory = new KServiceGroupFactory(q);
}
return m_serviceGroupFactory;
}
// Read-write constructor - only for KBuildSycoca
KSycoca::KSycoca(bool /* dummy */)
: d(new KSycocaPrivate(this))
{
}
KSycoca *KSycoca::self()
{
KSycoca *s = ksycocaInstance()->sycoca();
Q_ASSERT(s);
return s;
}
KSycoca::~KSycoca()
{
d->closeDatabase();
delete d;
// if (ksycocaInstance.exists()
// && ksycocaInstance->self == this)
// ksycocaInstance->self = 0;
}
bool KSycoca::isAvailable() // TODO KF6: make it non-static (mostly useful for unittests)
{
return self()->d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing);
}
void KSycocaPrivate::closeDatabase()
{
delete m_device;
m_device = nullptr;
// It is very important to delete all factories here
// since they cache information about the database file
// But other threads might be using them, so this class is
// refcounted, and deleted when the last thread is done with them
qDeleteAll(m_factories);
m_factories.clear();
m_mimeTypeFactory = nullptr;
m_serviceFactory = nullptr;
m_serviceGroupFactory = nullptr;
#if HAVE_MMAP
if (sycoca_mmap) {
// Solaris has munmap(char*, size_t) and everything else should
// be happy with a char* for munmap(void*, size_t)
munmap(const_cast<char *>(sycoca_mmap), sycoca_size);
sycoca_mmap = nullptr;
}
delete m_mmapFile;
m_mmapFile = nullptr;
#endif
databaseStatus = DatabaseNotOpen;
m_databasePath.clear();
timeStamp = 0;
}
void KSycoca::addFactory(KSycocaFactory *factory)
{
d->addFactory(factory);
}
QDataStream *KSycoca::findEntry(int offset, KSycocaType &type)
{
QDataStream *str = stream();
Q_ASSERT(str);
// qCDebug(SYCOCA) << QString("KSycoca::_findEntry(offset=%1)").arg(offset,8,16);
str->device()->seek(offset);
qint32 aType;
*str >> aType;
type = KSycocaType(aType);
// qCDebug(SYCOCA) << QString("KSycoca::found type %1").arg(aType);
return str;
}
KSycocaFactoryList *KSycoca::factories()
{
return d->factories();
}
// Warning, checkVersion rewinds to the beginning of stream().
bool KSycocaPrivate::checkVersion()
{
QDataStream *m_str = device()->stream();
Q_ASSERT(m_str);
m_str->device()->seek(0);
qint32 aVersion;
*m_str >> aVersion;
if (aVersion < KSYCOCA_VERSION) {
qCDebug(SYCOCA) << "Found version" << aVersion << ", expecting version" << KSYCOCA_VERSION << "or higher.";
databaseStatus = BadVersion;
return false;
} else {
databaseStatus = DatabaseOK;
return true;
}
}
// If it returns true, we have a valid database and the stream has rewinded to the beginning
// and past the version number.
bool KSycocaPrivate::checkDatabase(BehaviorsIfNotFound ifNotFound)
{
if (databaseStatus == DatabaseOK) {
if (checkVersion()) { // we know the version is ok, but we must rewind the stream anyway
return true;
}
}
closeDatabase(); // close the dummy one
// Check if new database already available
if (openDatabase()) {
// Database exists, and version is ok, we can read it.
if (qAppName() != QLatin1String(KBUILDSYCOCA_EXENAME) && ifNotFound != IfNotFoundDoNothing) {
// Ensure it's up-to-date, rebuild if needed
checkDirectories();
// Don't check again for some time
m_lastCheck.start();
}
return true;
}
if (ifNotFound & IfNotFoundRecreate) {
return buildSycoca();
}
return false;
}
QDataStream *KSycoca::findFactory(KSycocaFactoryId id)
{
// Ensure we have a valid database (right version, and rewinded to beginning)
if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
return nullptr;
}
QDataStream *str = stream();
Q_ASSERT(str);
qint32 aId;
qint32 aOffset;
while (true) {
*str >> aId;
if (aId == 0) {
qCWarning(SYCOCA) << "Error, KSycocaFactory (id =" << int(id) << ") not found!";
break;
}
*str >> aOffset;
if (aId == id) {
// qCDebug(SYCOCA) << "KSycoca::findFactory(" << id << ") offset " << aOffset;
str->device()->seek(aOffset);
return str;
}
}
return nullptr;
}
bool KSycoca::needsRebuild()
{
return d->needsRebuild();
}
KSycocaHeader KSycocaPrivate::readSycocaHeader()
{
KSycocaHeader header;
// do not try to launch kbuildsycoca from here; this code is also called by kbuildsycoca.
if (!checkDatabase(KSycocaPrivate::IfNotFoundDoNothing)) {
return header;
}
QDataStream *str = stream();
qint64 oldPos = str->device()->pos();
Q_ASSERT(str);
qint32 aId;
qint32 aOffset;
// skip factories offsets
while (true) {
*str >> aId;
if (aId) {
*str >> aOffset;
} else {
break; // just read 0
}
}
// We now point to the header
QStringList directoryList;
*str >> header >> directoryList;
allResourceDirs.clear();
for (int i = 0; i < directoryList.count(); ++i) {
qint64 mtime;
*str >> mtime;
allResourceDirs.insert(directoryList.at(i), mtime);
}
QStringList fileList;
*str >> fileList;
extraFiles.clear();
for (const auto &fileName : std::as_const(fileList)) {
qint64 mtime;
*str >> mtime;
extraFiles.insert(fileName, mtime);
}
str->device()->seek(oldPos);
timeStamp = header.timeStamp;
// for the useless public accessors. KF6: remove these two lines, the accessors and the vars.
language = header.language;
updateSig = header.updateSignature;
return header;
}
class TimestampChecker
{
public:
TimestampChecker()
: m_now(QDateTime::currentDateTime())
{
}
// Check times of last modification of all directories on which ksycoca depends,
// If none of them is newer than the mtime we stored for that directory at the
// last rebuild, this means that there's no need to rebuild ksycoca.
bool checkDirectoriesTimestamps(const QMap<QString, qint64> &dirs) const
{
Q_ASSERT(!dirs.isEmpty());
// qCDebug(SYCOCA) << "checking file timestamps";
for (auto it = dirs.begin(); it != dirs.end(); ++it) {
const QString dir = it.key();
const qint64 lastStamp = it.value();
auto visitor = [&](const QFileInfo &fi) {
const QDateTime mtime = fi.lastModified();
if (mtime.toMSecsSinceEpoch() > lastStamp) {
if (mtime > m_now) {
qCDebug(SYCOCA) << fi.filePath() << "has a modification time in the future" << mtime;
}
qCDebug(SYCOCA) << "dir timestamp changed:" << fi.filePath() << mtime << ">" << QDateTime::fromMSecsSinceEpoch(lastStamp);
// no need to continue search
return false;
}
return true;
};
if (!KSycocaUtilsPrivate::visitResourceDirectory(dir, visitor)) {
return false;
}
}
return true;
}
bool checkFilesTimestamps(const QMap<QString, qint64> &files) const
{
for (auto it = files.begin(); it != files.end(); ++it) {
const QString fileName = it.key();
const qint64 lastStamp = it.value();
QFileInfo fi(fileName);
if (!fi.exists()) {
return false;
}
const QDateTime mtime = fi.lastModified();
if (mtime.toMSecsSinceEpoch() > lastStamp) {
if (mtime > m_now) {
qCDebug(SYCOCA) << fi.filePath() << "has a modification time in the future" << mtime;
}
qCDebug(SYCOCA) << "file timestamp changed:" << fi.filePath() << mtime << ">" << QDateTime::fromMSecsSinceEpoch(lastStamp);
return false;
}
}
return true;
}
private:
QDateTime m_now;
};
void KSycocaPrivate::checkDirectories()
{
if (needsRebuild()) {
buildSycoca();
}
}
bool KSycocaPrivate::needsRebuild()
{
// In case it is not open, it might be due to another process/thread having rebuild it. Thus we read the header for both the not open and ok state
if (!timeStamp && databaseStatus != BadVersion) {
(void)readSycocaHeader();
}
// these days timeStamp is really a "bool headerFound", the value itself doesn't matter...
// KF6: replace it with bool.
const auto timestampChecker = TimestampChecker();
bool ret = timeStamp != 0
&& (!timestampChecker.checkDirectoriesTimestamps(allResourceDirs) //
|| !timestampChecker.checkFilesTimestamps(extraFiles));
if (ret) {
return true;
}
auto files = KBuildSycoca::factoryExtraFiles();
// ensure files are ordered so next comparison works
files.sort();
// to cover cases when extra files were added
return extraFiles.keys() != files;
}
bool KSycocaPrivate::buildSycoca()
{
KBuildSycoca builder;
if (!builder.recreate()) {
return false; // error
}
closeDatabase(); // close the dummy one
// Ok, the new database should be here now, open it.
if (!openDatabase()) {
qCDebug(SYCOCA) << "Still no database...";
return false;
}
Q_EMIT q->databaseChanged();
return true;
}
QString KSycoca::absoluteFilePath()
{
const QStringList paths = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
QString suffix = QLatin1Char('_') + QLocale().bcp47Name();
const QByteArray ksycoca_env = qgetenv("KDESYCOCA");
if (ksycoca_env.isEmpty()) {
const QByteArray pathHash = QCryptographicHash::hash(paths.join(QLatin1Char(':')).toUtf8(), QCryptographicHash::Sha1);
suffix += QLatin1Char('_') + QString::fromLatin1(pathHash.toBase64());
suffix.replace(QLatin1Char('/'), QLatin1Char('_'));
#ifdef Q_OS_WIN
suffix.replace(QLatin1Char(':'), QLatin1Char('_'));
#endif
const QString fileName = QLatin1String("ksycoca6") + suffix;
return QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1Char('/') + fileName;
} else {
return QFile::decodeName(ksycoca_env);
}
}
QStringList KSycoca::allResourceDirs()
{
if (!d->timeStamp) {
(void)d->readSycocaHeader();
}
return d->allResourceDirs.keys();
}
void KSycoca::flagError()
{
qCWarning(SYCOCA) << "ERROR: KSycoca database corruption!";
KSycoca *sycoca = self();
if (sycoca->d->readError) {
return;
}
sycoca->d->readError = true;
if (qAppName() != QLatin1String(KBUILDSYCOCA_EXENAME) && !sycoca->isBuilding()) {
// Rebuild the damned thing.
KBuildSycoca builder;
(void)builder.recreate();
}
}
bool KSycoca::isBuilding()
{
return false;
}
void KSycoca::disableAutoRebuild()
{
ksycocaInstance->sycoca()->d->m_fileWatcher = nullptr;
}
QDataStream *&KSycoca::stream()
{
return d->stream();
}
void KSycoca::connectNotify(const QMetaMethod &signal)
{
if (signal.name() == "databaseChanged" && !d->m_haveListeners) {
d->m_haveListeners = true;
if (d->m_databasePath.isEmpty()) {
d->m_databasePath = d->findDatabase();
} else if (d->m_fileWatcher) {
d->m_fileWatcher->addFile(d->m_databasePath);
}
}
}
void KSycoca::clearCaches()
{
if (ksycocaInstance.exists() && ksycocaInstance()->hasSycoca()) {
ksycocaInstance()->sycoca()->d->closeDatabase();
}
}
extern KSERVICE_EXPORT int ksycoca_ms_between_checks;
KSERVICE_EXPORT int ksycoca_ms_between_checks = 1500;
void KSycoca::ensureCacheValid()
{
if (qAppName() == QLatin1String(KBUILDSYCOCA_EXENAME)) {
return;
}
if (d->databaseStatus != KSycocaPrivate::DatabaseOK) {
if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
return;
}
}
if (d->m_lastCheck.isValid() && d->m_lastCheck.elapsed() < ksycoca_ms_between_checks) {
return;
}
d->m_lastCheck.start();
// Check if the file on disk was modified since we last checked it.
QFileInfo info(d->m_databasePath);
if (info.lastModified() == d->m_dbLastModified) {
// Check if the watched directories were modified, then the cache needs a rebuild.
d->checkDirectories();
return;
}
// Close the database and forget all about what we knew.
// The next call to any public method will recreate
// everything that's needed.
d->closeDatabase();
}
void KSycoca::setupTestMenu()
{
const QByteArray content = R"(<?xml version="1.0"?>
<!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN" "http://www.freedesktop.org/standards/menu-spec/menu-1.0.dtd">
<Menu>
<Name>Applications</Name>
<Directory>Applications.directory</Directory>
<DefaultAppDirs/>
<DefaultDirectoryDirs/>
<MergeDir>applications-merged</MergeDir>
<LegacyDir>/usr/share/applnk</LegacyDir>
<DefaultLayout>
<Merge type="menus"/>
<Merge type="files"/>
<Separator/>
<Menuname>More</Menuname>
</DefaultLayout>
</Menu>
)";
const QString destDir = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/menus");
QDir(destDir).mkpath(QStringLiteral("."));
QFile output(destDir + QLatin1String("/applications.menu"));
output.open(QIODevice::ReadWrite | QIODevice::Truncate);
output.write(content);
}
#include "moc_ksycoca.cpp"
@@ -0,0 +1,200 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2005-2008 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KSYCOCA_H
#define KSYCOCA_H
#include <kservice_export.h>
#include <ksycocatype.h>
#include <QObject>
#include <QStringList>
class QDataStream;
class KSycocaFactory;
class KSycocaFactoryList;
class KSycocaPrivate;
/**
* Executable name of the kbuildsycoca program
*/
#define KBUILDSYCOCA_EXENAME "kbuildsycoca6"
/**
* @internal
* Read-only SYstem COnfiguration CAche
*/
class KSERVICE_EXPORT KSycoca : public QObject
{
Q_OBJECT
// Q_CLASSINFO("D-Bus Interface", "org.kde.KSycoca")
protected:
/**
* @internal
* Building database
*/
explicit KSycoca(bool /* buildDatabase */);
public:
/**
* Read-only database
*/
KSycoca();
/**
* Get or create the only instance of KSycoca (read-only)
*/
static KSycoca *self();
~KSycoca() override;
/**
* @return the compiled-in version, i.e. the one used when writing a new ksycoca
*/
static int version();
/**
* @return true if the ksycoca database is available
* This is usually the case, except if KDE isn't installed yet,
* or before kded is started.
*/
static bool isAvailable();
/**
* @internal - called by factories in read-only mode
* This is how factories get a stream to an entry
*/
QDataStream *findEntry(int offset, KSycocaType &type); // KF6: make it private
/**
* @internal - called by factories in read-only mode
* Returns stream(), but positioned for reading this factory, 0 on error.
*/
QDataStream *findFactory(KSycocaFactoryId id); // KF6: make it private
/**
* @internal - returns absolute file path of the database
*
* For the global database type, the database is searched under
* the 'share/ksycoca' install path.
* Otherwise, the value from the environment variable KDESYCOCA
* is returned if set. If not set the path is built based on
* QStandardPaths cache save location, typically ~/.cache on Unix.
*
* Since 5.15, the filename includes language and a sha1 of the directories
* in GenericDataLocation, i.e. the directories with the desktop files.
* This allows to have one database per setup, when using different install prefixes
* or when switching languages.
*/
static QString absoluteFilePath();
/**
* @internal - returns all directories with information
* stored inside sycoca.
*/
QStringList allResourceDirs(); // KF6: make it private
/**
* @internal - add a factory
*/
void addFactory(KSycocaFactory *); // KF6: make it private
/**
* @internal
* @return true if building (i.e. if a KBuildSycoca);
*/
virtual bool isBuilding();
/**
* Disables automatic rebuilding of the cache on service file changes.
* Be extremely careful when using this. Only threads that definitely have no use for
* automatic reloading should use this. Specifically shared runner threads (as seen in
* the threadweaver framework) can avoid claiming persistent resources this way
* (e.g. inotify instances on Linux).
*/
static void disableAutoRebuild();
/**
* A read error occurs.
* @internal
*/
static void flagError();
/**
* Ensures the ksycoca database is up to date.
* If the database was modified by another process, close it, so the next use reopens it.
* If the desktop files have been modified more recently than the database, update it.
*
* Update the sycoca file from the files on disk (e.g. desktop files or mimeapps.list).
* You don't normally have to call this because the next use of KSycoca
* (e.g. via KMimeTypeTrader, KService etc.) will notice that the sycoca
* database is out of date, by looking a directory modification times.
* In addition, in a full KDE session, kded monitors directories to detect changes.
*
* This is however useful for GUIs that allow to create a new desktop file
* and then want to ensure it is available immediately in KSycoca.
* This is also useful after modifying a mimeapps.list file.
*
* KBuildSycocaProgressDialog can also be used instead of this method, in GUI apps.
*
* @since 5.15
*/
void ensureCacheValid(); // Warning for kservice code: this can delete all the factories.
/**
* Sets up a minimal applications.menu file in the appropriate location.
* This is useful when writing unit tests that interact with KService.
*
* You should call QStandardPaths::setTestModeEnabled(true) before calling this.
*
* @since 6.0
*/
static void setupTestMenu();
/**
* Connect to this to get notified when the database changes.
*
* Example: after creating a .desktop file in KOpenWithDialog, it
* must wait until kbuildsycoca6 finishes until the KService::Ptr is available.
* Other examples: anything that displays a list of apps or plugins to the user
* and which is always visible (otherwise querying sycoca before showing
* could be enough).
*/
Q_SIGNAL void databaseChanged();
protected:
// @internal used by kbuildsycoca
KSycocaFactoryList *factories();
// @internal used by factories and kbuildsycoca
QDataStream *&stream();
friend class KSycocaFactory;
friend class KSycocaDict;
void connectNotify(const QMetaMethod &signal) override;
private:
/**
* Clear all caches related to ksycoca contents.
* @internal only used by kded and kbuildsycoca.
*/
static void clearCaches();
KSERVICE_NO_EXPORT bool needsRebuild();
friend class KBuildSycoca;
friend class Kded;
friend class KSycocaPrivate;
friend class KSycocaXdgDirsTest;
Q_DISABLE_COPY(KSycoca)
KSycocaPrivate *const d;
};
#endif
@@ -0,0 +1,149 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999-2000 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2005-2009 David Faure <faure@kde.org>
SPDX-FileCopyrightText: 2008 Hamish Rodda <rodda@kde.org>
SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KSYCOCA_P_H
#define KSYCOCA_P_H
#include "ksycocafactory_p.h"
#include <KDirWatch>
#include <QDateTime>
#include <QElapsedTimer>
#include <QStringList>
#include <memory>
class QFile;
class QDataStream;
class KSycocaAbstractDevice;
class KMimeTypeFactory;
class KServiceFactory;
class KServiceGroupFactory;
// This is for the part of the global header that we don't need to store,
// i.e. it's just a struct for returning temp data from readSycocaHeader().
struct KSycocaHeader {
KSycocaHeader()
: timeStamp(0)
, updateSignature(0)
{
}
QString prefixes;
QString language;
qint64 timeStamp; // in ms
quint32 updateSignature;
};
QDataStream &operator>>(QDataStream &in, KSycocaHeader &h);
/**
* \internal
* Exported for unittests
*/
class KSERVICE_EXPORT KSycocaPrivate
{
public:
explicit KSycocaPrivate(KSycoca *qq);
// per-thread "singleton"
static KSycocaPrivate *self()
{
return KSycoca::self()->d;
}
bool checkVersion();
bool openDatabase();
enum BehaviorIfNotFound {
IfNotFoundDoNothing = 0,
IfNotFoundRecreate = 1,
};
Q_DECLARE_FLAGS(BehaviorsIfNotFound, BehaviorIfNotFound)
bool checkDatabase(BehaviorsIfNotFound ifNotFound);
void closeDatabase();
void setStrategyFromString(const QString &strategy);
bool tryMmap();
/**
* Check if the on-disk cache needs to be rebuilt, and do it then.
*/
void checkDirectories();
/**
* Check if the on-disk cache needs to be rebuilt, and return true
*/
bool needsRebuild();
/**
* Recreate the cache and reopen the database
*/
bool buildSycoca();
KSycocaHeader readSycocaHeader();
KSycocaAbstractDevice *device();
QDataStream *&stream();
QString findDatabase();
void slotDatabaseChanged();
KMimeTypeFactory *mimeTypeFactory();
KServiceFactory *serviceFactory();
KServiceGroupFactory *serviceGroupFactory();
enum {
DatabaseNotOpen, // openDatabase must be called
BadVersion, // it's opened, but it's not usable
DatabaseOK,
} databaseStatus;
bool readError;
qint64 timeStamp; // in ms since epoch
enum { StrategyMmap, StrategyMemFile, StrategyFile } m_sycocaStrategy;
QString m_databasePath;
QString language;
quint32 updateSig;
QMap<QString, qint64> allResourceDirs; // path, modification time in "ms since epoch"
QMap<QString, qint64> extraFiles; // path, modification time in "ms since epoch"
void addFactory(KSycocaFactory *factory)
{
m_factories.append(factory);
}
KSycocaFactoryList *factories()
{
return &m_factories;
}
QElapsedTimer m_lastCheck;
QDateTime m_dbLastModified;
// Using KDirWatch because it will reliably tell us every time ksycoca is recreated.
// QFileSystemWatcher's inotify implementation easily gets confused between "removed" and "changed",
// and fails to re-add an inotify watch after the file was replaced at some point (KServiceTest::testThreads),
// thinking it only got changed and not removed+recreated.
// NOTE: this may be nullptr when file watching is disabled on the current thread
std::unique_ptr<KDirWatch> m_fileWatcher;
bool m_haveListeners;
KSycoca *q;
private:
KSycocaFactoryList m_factories;
size_t sycoca_size;
const char *sycoca_mmap;
QFile *m_mmapFile;
KSycocaAbstractDevice *m_device;
public:
KMimeTypeFactory *m_mimeTypeFactory;
KServiceFactory *m_serviceFactory;
KServiceGroupFactory *m_serviceGroupFactory;
};
#endif /* KSYCOCA_P_H */
@@ -0,0 +1,95 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "kmemfile_p.h"
#include "ksycocadevices_p.h"
#include <QBuffer>
#include <QDataStream>
#include <QFile>
#include <fcntl.h>
KSycocaAbstractDevice::~KSycocaAbstractDevice()
{
delete m_stream;
}
QDataStream *&KSycocaAbstractDevice::stream()
{
if (!m_stream) {
m_stream = new QDataStream(device());
m_stream->setVersion(QDataStream::Qt_5_3);
}
return m_stream;
}
#if HAVE_MMAP
KSycocaMmapDevice::KSycocaMmapDevice(const char *sycoca_mmap, size_t sycoca_size)
{
m_buffer = new QBuffer;
m_buffer->setData(QByteArray::fromRawData(sycoca_mmap, sycoca_size));
}
KSycocaMmapDevice::~KSycocaMmapDevice()
{
delete m_buffer;
}
QIODevice *KSycocaMmapDevice::device()
{
return m_buffer;
}
#endif
KSycocaFileDevice::KSycocaFileDevice(const QString &path)
{
m_database = new QFile(path);
#ifndef Q_OS_WIN
(void)fcntl(m_database->handle(), F_SETFD, FD_CLOEXEC);
#endif
}
KSycocaFileDevice::~KSycocaFileDevice()
{
delete m_database;
}
QIODevice *KSycocaFileDevice::device()
{
return m_database;
}
#ifndef QT_NO_SHAREDMEMORY
KSycocaMemFileDevice::KSycocaMemFileDevice(const QString &path)
{
m_database = new KMemFile(path);
}
KSycocaMemFileDevice::~KSycocaMemFileDevice()
{
delete m_database;
}
QIODevice *KSycocaMemFileDevice::device()
{
return m_database;
}
#endif
KSycocaBufferDevice::KSycocaBufferDevice()
{
m_buffer = new QBuffer;
}
KSycocaBufferDevice::~KSycocaBufferDevice()
{
delete m_buffer;
}
QIODevice *KSycocaBufferDevice::device()
{
return m_buffer;
}
@@ -0,0 +1,95 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2009 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#ifndef KSYCOCADEVICES_P_H
#define KSYCOCADEVICES_P_H
#include <config-ksycoca.h>
#include <stdlib.h>
// TODO: remove mmap() from kdewin32 and use QFile::mmap() when needed
#ifdef Q_OS_WIN
#define HAVE_MMAP 0
#endif
class QString;
class QDataStream;
class QBuffer;
class QFile;
class QIODevice;
class KMemFile;
class KSycocaAbstractDevice
{
public:
KSycocaAbstractDevice()
: m_stream(nullptr)
{
}
virtual ~KSycocaAbstractDevice();
virtual QIODevice *device() = 0;
QDataStream *&stream();
private:
QDataStream *m_stream;
};
#if HAVE_MMAP
// Reading from a mmap'ed file
class KSycocaMmapDevice : public KSycocaAbstractDevice
{
public:
KSycocaMmapDevice(const char *sycoca_mmap, size_t sycoca_size);
~KSycocaMmapDevice() override;
QIODevice *device() override;
private:
QBuffer *m_buffer;
};
#endif
// Reading from a QFile
class KSycocaFileDevice : public KSycocaAbstractDevice
{
public:
explicit KSycocaFileDevice(const QString &path);
~KSycocaFileDevice() override;
QIODevice *device() override;
private:
QFile *m_database;
};
#ifndef QT_NO_SHAREDMEMORY
// Reading from a KMemFile
class KSycocaMemFileDevice : public KSycocaAbstractDevice
{
public:
explicit KSycocaMemFileDevice(const QString &path);
~KSycocaMemFileDevice() override;
QIODevice *device() override;
private:
KMemFile *m_database;
};
#endif
// Reading from a dummy memory buffer
class KSycocaBufferDevice : public KSycocaAbstractDevice
{
public:
KSycocaBufferDevice();
~KSycocaBufferDevice() override;
QIODevice *device() override;
private:
QBuffer *m_buffer;
};
#endif /* KSYCOCADEVICES_P_H */
@@ -0,0 +1,534 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "ksycoca.h"
#include "ksycocadict_p.h"
#include "ksycocaentry.h"
#include "sycocadebug.h"
#include <kservice.h>
#include <QBitArray>
#include <QIODevice>
#include <QList>
namespace
{
struct string_entry {
string_entry(const QString &_key, const KSycocaEntry::Ptr &_payload)
: hash(0)
, length(_key.length())
, keyStr(_key)
, key(keyStr.unicode())
, payload(_payload)
{
}
uint hash;
const int length;
const QString keyStr;
const QChar *const key; // always points to keyStr.unicode(); just an optimization
const KSycocaEntry::Ptr payload;
};
}
class KSycocaDictPrivate
{
public:
KSycocaDictPrivate()
: stream(nullptr)
, offset(0)
, hashTableSize(0)
{
}
~KSycocaDictPrivate()
{
}
// Helper for find_string and findMultiString
qint32 offsetForKey(const QString &key) const;
// Calculate hash - can be used during loading and during saving.
quint32 hashKey(const QString &key) const;
std::vector<std::unique_ptr<string_entry>> m_stringentries;
QDataStream *stream;
qint64 offset;
quint32 hashTableSize;
QList<qint32> hashList;
};
KSycocaDict::KSycocaDict()
: d(new KSycocaDictPrivate)
{
}
KSycocaDict::KSycocaDict(QDataStream *str, int offset)
: d(new KSycocaDictPrivate)
{
d->stream = str;
d->offset = offset;
quint32 test1;
quint32 test2;
str->device()->seek(offset);
(*str) >> test1 >> test2;
if ((test1 > 0x000fffff) || (test2 > 1024)) {
KSycoca::flagError();
d->hashTableSize = 0;
d->offset = 0;
return;
}
str->device()->seek(offset);
(*str) >> d->hashTableSize;
(*str) >> d->hashList;
d->offset = str->device()->pos(); // Start of hashtable
}
KSycocaDict::~KSycocaDict() = default;
void KSycocaDict::add(const QString &key, const KSycocaEntry::Ptr &payload)
{
if (key.isEmpty()) {
return; // Not allowed (should never happen)
}
if (!payload) {
return; // Not allowed!
}
d->m_stringentries.push_back(std::make_unique<string_entry>(key, payload));
}
void KSycocaDict::remove(const QString &key)
{
if (!d) {
return;
}
auto it = std::find_if(d->m_stringentries.begin(), d->m_stringentries.end(), [&key](const std::unique_ptr<string_entry> &entry) {
return entry->keyStr == key;
});
if (it != d->m_stringentries.end()) {
d->m_stringentries.erase(it);
} else {
qCDebug(SYCOCA) << "key not found:" << key;
}
}
int KSycocaDict::find_string(const QString &key) const
{
Q_ASSERT(d);
// qCDebug(SYCOCA) << QString("KSycocaDict::find_string(%1)").arg(key);
qint32 offset = d->offsetForKey(key);
// qCDebug(SYCOCA) << QString("offset is %1").arg(offset,8,16);
if (offset == 0) {
return 0;
}
if (offset > 0) {
return offset; // Positive ID
}
// Lookup duplicate list.
offset = -offset;
d->stream->device()->seek(offset);
// qCDebug(SYCOCA) << QString("Looking up duplicate list at %1").arg(offset,8,16);
while (true) {
(*d->stream) >> offset;
if (offset == 0) {
break;
}
QString dupkey;
(*d->stream) >> dupkey;
// qCDebug(SYCOCA) << QString(">> %1 %2").arg(offset,8,16).arg(dupkey);
if (dupkey == key) {
return offset;
}
}
// qCDebug(SYCOCA) << "Not found!";
return 0;
}
QList<int> KSycocaDict::findMultiString(const QString &key) const
{
qint32 offset = d->offsetForKey(key);
QList<int> offsetList;
if (offset == 0) {
return offsetList;
}
if (offset > 0) { // Positive ID: one entry found
offsetList.append(offset);
return offsetList;
}
// Lookup duplicate list.
offset = -offset;
d->stream->device()->seek(offset);
// qCDebug(SYCOCA) << QString("Looking up duplicate list at %1").arg(offset,8,16);
while (true) {
(*d->stream) >> offset;
if (offset == 0) {
break;
}
QString dupkey;
(*d->stream) >> dupkey;
// qCDebug(SYCOCA) << QString(">> %1 %2").arg(offset,8,16).arg(dupkey);
if (dupkey == key) {
offsetList.append(offset);
}
}
return offsetList;
}
uint KSycocaDict::count() const
{
if (!d) {
return 0;
}
return d->m_stringentries.size();
}
void KSycocaDict::clear()
{
d.reset();
}
uint KSycocaDictPrivate::hashKey(const QString &key) const
{
int len = key.length();
uint h = 0;
for (int i = 0; i < hashList.count(); i++) {
int pos = hashList[i];
if (pos == 0) {
continue;
} else if (pos < 0) {
pos = -pos;
if (pos < len) {
h = ((h * 13) + (key[len - pos].cell() % 29)) & 0x3ffffff;
}
} else {
pos = pos - 1;
if (pos < len) {
h = ((h * 13) + (key[pos].cell() % 29)) & 0x3ffffff;
}
}
}
return h;
}
// If we have the strings
// hello
// world
// kde
// Then we end up with
// ABCDE
// where A = diversity of 'h' + 'w' + 'k' etc.
// Also, diversity(-2) == 'l'+'l'+'d' (second character from the end)
// The hasList is used for hashing:
// hashList = (-2, 1, 3) means that the hash key comes from
// the 2nd character from the right, then the 1st from the left, then the 3rd from the left.
// Calculate the diversity of the strings at position 'pos'
// NOTE: this code is slow, it takes 12% of the _overall_ `kbuildsycoca5 --noincremental` running time
static int calcDiversity(std::vector<std::unique_ptr<string_entry>> *stringlist, int inPos, uint sz)
{
if (inPos == 0) {
return 0;
}
QBitArray matrix(sz);
int pos;
// static const int s_maxItems = 50;
// int numItem = 0;
if (inPos < 0) {
pos = -inPos;
for (const auto &entryPtr : *stringlist) {
const int rpos = entryPtr->length - pos;
if (rpos > 0) {
const uint hash = ((entryPtr->hash * 13) + (entryPtr->key[rpos].cell() % 29)) & 0x3ffffff;
matrix.setBit(hash % sz, true);
}
// if (++numItem == s_maxItems)
// break;
}
} else {
pos = inPos - 1;
for (const auto &entryPtr : *stringlist) {
if (pos < entryPtr->length) {
const uint hash = ((entryPtr->hash * 13) + (entryPtr->key[pos].cell() % 29)) & 0x3ffffff;
matrix.setBit(hash % sz, true);
}
// if (++numItem == s_maxItems)
// break;
}
}
return matrix.count(true);
}
//
// Add the diversity of the strings at position 'pos'
static void addDiversity(std::vector<std::unique_ptr<string_entry>> *stringlist, int pos)
{
if (pos == 0) {
return;
}
if (pos < 0) {
pos = -pos;
for (auto &entryPtr : *stringlist) {
const int rpos = entryPtr->length - pos;
if (rpos > 0) {
entryPtr->hash = ((entryPtr->hash * 13) + (entryPtr->key[rpos].cell() % 29)) & 0x3fffffff;
}
}
} else {
pos = pos - 1;
for (auto &entryPtr : *stringlist) {
if (pos < entryPtr->length) {
entryPtr->hash = ((entryPtr->hash * 13) + (entryPtr->key[pos].cell() % 29)) & 0x3fffffff;
}
}
}
}
void KSycocaDict::save(QDataStream &str)
{
if (count() == 0) {
d->hashTableSize = 0;
d->hashList.clear();
str << d->hashTableSize;
str << d->hashList;
return;
}
d->offset = str.device()->pos();
// qCDebug(SYCOCA) << "KSycocaDict:" << count() << "entries.";
// qCDebug(SYCOCA) << "Calculating hash keys..";
int maxLength = 0;
// qCDebug(SYCOCA) << "Finding maximum string length";
for (auto &entryPtr : d->m_stringentries) {
entryPtr->hash = 0;
if (entryPtr->length > maxLength) {
maxLength = entryPtr->length;
}
}
// qCDebug(SYCOCA) << "Max string length=" << maxLength << "existing hashList=" << d->hashList;
// use "almost prime" number for sz (to calculate diversity) and later
// for the table size of big tables
// int sz = d->stringlist.count()*5-1;
unsigned int sz = count() * 4 + 1;
while (!(((sz % 3) && (sz % 5) && (sz % 7) && (sz % 11) && (sz % 13)))) {
sz += 2;
}
d->hashList.clear();
// Times (with warm caches, i.e. after multiple runs)
// kbuildsycoca5 --noincremental 2.83s user 0.20s system 95% cpu 3.187 total
// kbuildsycoca5 --noincremental 2.74s user 0.25s system 93% cpu 3.205 total
// unittest: 0.50-60 msec per iteration / 0.40-50 msec per iteration
// Now that MimeTypes are not parsed anymore:
// kbuildsycoca5 --noincremental 2.18s user 0.30s system 91% cpu 2.719 total
// kbuildsycoca5 --noincremental 2.07s user 0.34s system 89% cpu 2.681 total
// If I enabled s_maxItems = 50, it goes down to
// but I don't know if that's a good idea.
// kbuildsycoca5 --noincremental 1.73s user 0.31s system 85% cpu 2.397 total
// kbuildsycoca5 --noincremental 1.84s user 0.29s system 95% cpu 2.230 total
// try to limit diversity scan by "predicting" positions
// with high diversity
QList<int> oldvec(maxLength * 2 + 1);
oldvec.fill(0);
int mindiv = 0;
int lastDiv = 0;
while (true) {
int divsum = 0;
int divnum = 0;
int maxDiv = 0;
int maxPos = 0;
for (int pos = -maxLength; pos <= maxLength; ++pos) {
// cut off
if (oldvec[pos + maxLength] < mindiv) {
oldvec[pos + maxLength] = 0;
continue;
}
const int diversity = calcDiversity(&(d->m_stringentries), pos, sz);
if (diversity > maxDiv) {
maxDiv = diversity;
maxPos = pos;
}
oldvec[pos + maxLength] = diversity;
divsum += diversity;
++divnum;
}
// arbitrary cut-off value 3/4 of average seems to work
if (divnum) {
mindiv = (3 * divsum) / (4 * divnum);
}
if (maxDiv <= lastDiv) {
break;
}
// qCDebug(SYCOCA) << "Max Div=" << maxDiv << "at pos" << maxPos;
lastDiv = maxDiv;
addDiversity(&(d->m_stringentries), maxPos);
d->hashList.append(maxPos);
}
for (auto &entryPtr : d->m_stringentries) {
entryPtr->hash = d->hashKey(entryPtr->keyStr);
}
// fprintf(stderr, "Calculating minimum table size..\n");
d->hashTableSize = sz;
// qCDebug(SYCOCA) << "hashTableSize=" << sz << "hashList=" << d->hashList << "oldvec=" << oldvec;
struct hashtable_entry {
string_entry *entry;
QList<string_entry *> *duplicates;
qint64 duplicate_offset;
};
hashtable_entry *hashTable = new hashtable_entry[sz];
// qCDebug(SYCOCA) << "Clearing hashtable...";
for (unsigned int i = 0; i < sz; i++) {
hashTable[i].entry = nullptr;
hashTable[i].duplicates = nullptr;
}
// qCDebug(SYCOCA) << "Filling hashtable...";
for (const auto &entryPtr : d->m_stringentries) {
// qCDebug(SYCOCA) << "entry keyStr=" << entry->keyStr << entry->payload.data() << entry->payload->entryPath();
const int hash = entryPtr->hash % sz;
if (!hashTable[hash].entry) {
// First entry
hashTable[hash].entry = entryPtr.get();
} else {
if (!hashTable[hash].duplicates) {
// Second entry, build duplicate list.
hashTable[hash].duplicates = new QList<string_entry *>;
hashTable[hash].duplicates->append(hashTable[hash].entry);
hashTable[hash].duplicate_offset = 0;
}
hashTable[hash].duplicates->append(entryPtr.get());
}
}
str << d->hashTableSize;
str << d->hashList;
d->offset = str.device()->pos(); // d->offset points to start of hashTable
// qCDebug(SYCOCA) << QString("Start of Hash Table, offset = %1").arg(d->offset,8,16);
// Write the hashtable + the duplicates twice.
// The duplicates are after the normal hashtable, but the offset of each
// duplicate entry is written into the normal hashtable.
for (int pass = 1; pass <= 2; pass++) {
str.device()->seek(d->offset);
// qCDebug(SYCOCA) << QString("Writing hash table (pass #%1)").arg(pass);
for (uint i = 0; i < d->hashTableSize; i++) {
qint32 tmpid;
if (!hashTable[i].entry) {
tmpid = 0;
} else if (!hashTable[i].duplicates) {
tmpid = hashTable[i].entry->payload->offset(); // Positive ID
} else {
tmpid = -hashTable[i].duplicate_offset; // Negative ID
}
str << tmpid;
// qCDebug(SYCOCA) << QString("Hash table : %1").arg(tmpid,8,16);
}
// qCDebug(SYCOCA) << QString("End of Hash Table, offset = %1").arg(str.device()->at(),8,16);
// qCDebug(SYCOCA) << QString("Writing duplicate lists (pass #%1)").arg(pass);
for (uint i = 0; i < d->hashTableSize; i++) {
const QList<string_entry *> *dups = hashTable[i].duplicates;
if (dups) {
hashTable[i].duplicate_offset = str.device()->pos();
/*qCDebug(SYCOCA) << QString("Duplicate lists: Offset = %1 list_size = %2") .arg(hashTable[i].duplicate_offset,8,16).arg(dups->count());
*/
for (string_entry *dup : std::as_const(*dups)) {
const qint32 offset = dup->payload->offset();
if (!offset) {
const QString storageId = dup->payload->storageId();
qCDebug(SYCOCA) << "about to assert! dict=" << this << "storageId=" << storageId << dup->payload.data();
if (dup->payload->isType(KST_KService)) {
KService::Ptr service(static_cast<KService *>(dup->payload.data()));
qCDebug(SYCOCA) << service->storageId() << service->entryPath();
}
// save() must have been called on the entry
Q_ASSERT_X(offset,
"KSycocaDict::save",
QByteArray("entry offset is 0, save() was not called on " + dup->payload->storageId().toLatin1()
+ " entryPath=" + dup->payload->entryPath().toLatin1())
.constData());
}
str << offset; // Positive ID
str << dup->keyStr; // Key (QString)
}
str << qint32(0); // End of list marker (0)
}
}
// qCDebug(SYCOCA) << QString("End of Dict, offset = %1").arg(str.device()->at(),8,16);
}
// qCDebug(SYCOCA) << "Cleaning up hash table.";
for (uint i = 0; i < d->hashTableSize; i++) {
delete hashTable[i].duplicates;
}
delete[] hashTable;
}
qint32 KSycocaDictPrivate::offsetForKey(const QString &key) const
{
if (!stream || !offset) {
qCWarning(SYCOCA) << "No ksycoca database available! Tried running" << KBUILDSYCOCA_EXENAME << "?";
return 0;
}
if (hashTableSize == 0) {
return 0; // Unlikely to find anything :-]
}
// Read hash-table data
const uint hash = hashKey(key) % hashTableSize;
// qCDebug(SYCOCA) << "hash is" << hash;
const qint64 off = offset + sizeof(qint32) * hash;
// qCDebug(SYCOCA) << QString("off is %1").arg(off,8,16);
stream->device()->seek(off);
qint32 retOffset;
(*stream) >> retOffset;
return retOffset;
}
@@ -0,0 +1,127 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KSYCOCADICT_H
#define KSYCOCADICT_H
#include "ksycocaentry.h"
#include <kservice_export.h>
#include <QList>
#include <memory>
class KSycocaDictPrivate;
class QString;
class QDataStream;
/**
* @internal
* Hash table implementation for the sycoca database file
*
* Only exported for the unit test
*/
class KSERVICE_EXPORT KSycocaDict // krazy:exclude=dpointer (not const because it gets deleted by clear())
{
public:
/**
* Create an empty dict, for building the database
*/
KSycocaDict();
/**
* Create a dict from an existing database
*/
KSycocaDict(QDataStream *str, int offset);
~KSycocaDict();
/**
* Adds a 'payload' to the dictionary with key 'key'.
*
* 'payload' should have a valid offset by the time
* the dictionary gets saved.
**/
void add(const QString &key, const KSycocaEntry::Ptr &payload);
/**
* Removes the 'payload' from the dictionary with key 'key'.
*
* Not very fast, use with care O(N)
**/
void remove(const QString &key);
/**
* Looks up an entry identified by 'key'.
*
* If 0 is returned, no matching entry exists.
* Otherwise, the offset of the entry is returned.
*
* NOTE: It is not guaranteed that this entry is
* indeed the one you were looking for.
* After loading the entry you should check that it
* indeed matches the search key. If it doesn't
* then no matching entry exists.
*/
int find_string(const QString &key) const;
/**
* Looks up all entries identified by 'key'.
* This is useful when the dict is used as a multi-hash.
*
* If an empty list is returned, no matching entry exists.
* Otherwise, the offset of the matching entries are returned.
*
* NOTE: It is not guaranteed that each entry is
* indeed among the ones you were looking for.
* After loading each entry you should check that it
* indeed matches the search key.
*/
QList<int> findMultiString(const QString &key) const;
/**
* The number of entries in the dictionary.
*
* Only valid when building the database.
*/
uint count() const;
/**
* Reset the dictionary.
*
* Only valid when building the database.
*/
void clear();
/**
* Save the dictionary to the stream
* A reasonable fast hash algorithm will be created.
*
* Typically this will find 90% of the entries directly.
* Average hash table size: nrOfItems * 20 bytes.
* Average duplicate list size: nrOfItms * avgKeyLength / 5.
*
* Unknown keys have an average 20% chance to give a false hit.
* (That's why your program should check the result)
*
* Example:
* Assume 1000 items with an average key length of 60 bytes.
*
* Approx. 900 items will hash directly to the right entry.
* Approx. 100 items require a lookup in the duplicate list.
*
* The hash table size will be approx. 20Kb.
* The duplicate list size will be approx. 12Kb.
**/
void save(QDataStream &str);
private:
Q_DISABLE_COPY(KSycocaDict)
std::unique_ptr<KSycocaDictPrivate> d;
};
#endif
@@ -0,0 +1,93 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "ksycocaentry.h"
#include "ksycocaentry_p.h"
#include <QIODevice>
#include <ksycoca.h>
KSycocaEntryPrivate::KSycocaEntryPrivate(QDataStream &_str, int iOffset)
: offset(iOffset)
, deleted(false)
{
_str >> path;
}
KSycocaEntry::KSycocaEntry()
: d_ptr(nullptr)
{
}
KSycocaEntry::KSycocaEntry(KSycocaEntryPrivate &d)
: d_ptr(&d)
{
}
KSycocaEntry::~KSycocaEntry() = default;
bool KSycocaEntry::isType(KSycocaType t) const
{
return d_ptr->isType(t);
}
KSycocaType KSycocaEntry::sycocaType() const
{
return d_ptr->sycocaType();
}
QString KSycocaEntry::entryPath() const
{
Q_D(const KSycocaEntry);
return d->path;
}
QString KSycocaEntry::storageId() const
{
Q_D(const KSycocaEntry);
return d->storageId();
}
bool KSycocaEntry::isDeleted() const
{
Q_D(const KSycocaEntry);
return d->deleted;
}
void KSycocaEntry::setDeleted(bool deleted)
{
Q_D(KSycocaEntry);
d->deleted = deleted;
}
bool KSycocaEntry::isSeparator() const
{
return d_ptr == nullptr || isType(KST_KServiceSeparator);
}
int KSycocaEntry::offset() const
{
Q_D(const KSycocaEntry);
return d->offset;
}
void KSycocaEntryPrivate::save(QDataStream &s)
{
offset = s.device()->pos(); // store position in member variable
s << qint32(sycocaType()) << path;
}
bool KSycocaEntry::isValid() const
{
Q_D(const KSycocaEntry);
return d && d->isValid();
}
QString KSycocaEntry::name() const
{
Q_D(const KSycocaEntry);
return d->name();
}
@@ -0,0 +1,123 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KSYCOCAENTRY_H
#define KSYCOCAENTRY_H
#include <kservice_export.h>
#include <ksycocatype.h>
#include <QDataStream>
#include <QExplicitlySharedDataPointer>
#include <QStringList>
#include <QVariant>
#include <memory>
class KSycocaEntryPrivate;
/**
* Base class for all Sycoca entries.
*
* You can't create an instance of KSycocaEntry, but it provides
* the common functionality for servicetypes and services.
*
* @internal
* @see http://techbase.kde.org/Development/Architecture/KDE3/System_Configuration_Cache
*/
class KSERVICE_EXPORT KSycocaEntry : public QSharedData
{
public:
/*
* constructs a invalid KSycocaEntry object
*/
KSycocaEntry();
virtual ~KSycocaEntry();
/**
* Returns true if this sycoca entry is of the given type.
*/
bool isType(KSycocaType t) const;
/**
* internal
*/
KSycocaType sycocaType() const;
/**
* A shared data pointer for KSycocaEntry.
*/
typedef QExplicitlySharedDataPointer<KSycocaEntry> Ptr;
/**
* A list of shared data pointers for KSycocaEntry.
*/
typedef QList<Ptr> List;
/**
* @return the name of this entry
*/
QString name() const;
/**
* @return the path of this entry
* The path can be absolute or relative.
* The corresponding factory should know relative to what.
*/
QString entryPath() const;
/**
* @return the unique ID for this entry
* In practice, this is storageId() for KService and name() for everything else.
* \since 4.2.1
*/
QString storageId() const;
/**
* @return true if valid
*/
bool isValid() const;
/**
* @return true if deleted
*/
bool isDeleted() const;
/**
* Sets whether or not this service is deleted
*/
void setDeleted(bool deleted);
/**
* @returns true, if this is a separator
*/
bool isSeparator() const;
protected:
KSERVICE_NO_EXPORT explicit KSycocaEntry(KSycocaEntryPrivate &d);
std::unique_ptr<KSycocaEntryPrivate> const d_ptr;
private:
// All these need access to offset()
friend class KSycocaFactory;
friend class KBuildServiceFactory;
friend class KServiceFactory;
friend class KService;
friend class KSycocaDict;
friend class KSycocaDictTest;
/**
* @internal
* @return the position of the entry in the sycoca file
*/
KSERVICE_NO_EXPORT int offset() const;
Q_DISABLE_COPY(KSycocaEntry)
Q_DECLARE_PRIVATE(KSycocaEntry)
};
#endif
@@ -0,0 +1,66 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KSYCOCAENTRYPRIVATE_H
#define KSYCOCAENTRYPRIVATE_H
#include "ksycocaentry.h"
// clang-format off
#define K_SYCOCATYPE( type, baseclass ) \
bool isType(KSycocaType t) const override { if (t == type) return true; return baseclass::isType(t);} \
KSycocaType sycocaType() const override { return type; }
// clang-format on
class KSycocaEntryPrivate
{
public:
explicit KSycocaEntryPrivate(const QString &path_)
: offset(0)
, deleted(false)
, path(path_)
{
}
KSycocaEntryPrivate(QDataStream &_str, int iOffset);
virtual ~KSycocaEntryPrivate()
{
}
// Don't forget to call the parent class
// first if you override this function.
virtual void save(QDataStream &s);
virtual bool isType(KSycocaType t) const
{
return (t == KST_KSycocaEntry);
}
virtual KSycocaType sycocaType() const
{
return KST_KSycocaEntry;
}
virtual bool isValid() const
{
return !name().isEmpty();
}
virtual QString name() const = 0;
virtual QString storageId() const
{
return name();
}
int offset;
bool deleted;
QString path;
};
#endif
@@ -0,0 +1,249 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#include "ksycoca.h"
#include "ksycocadict_p.h"
#include "ksycocaentry.h"
#include "ksycocaentry_p.h"
#include "ksycocafactory_p.h"
#include "ksycocatype.h"
#include "sycocadebug.h"
#include <QDebug>
#include <QHash>
#include <QIODevice>
#include <QThread>
class KSycocaFactoryPrivate
{
public:
KSycocaFactoryPrivate()
{
}
~KSycocaFactoryPrivate()
{
delete m_sycocaDict;
}
int mOffset = 0;
int m_sycocaDictOffset = 0;
int m_beginEntryOffset = 0;
int m_endEntryOffset = 0;
KSycocaDict *m_sycocaDict = nullptr;
};
KSycocaFactory::KSycocaFactory(KSycocaFactoryId factory_id, KSycoca *sycoca)
: m_sycoca(sycoca)
, d(new KSycocaFactoryPrivate)
{
if (!m_sycoca->isBuilding() && (m_str = m_sycoca->findFactory(factory_id))) {
// Read position of index tables....
qint32 i;
(*m_str) >> i;
d->m_sycocaDictOffset = i;
(*m_str) >> i;
d->m_beginEntryOffset = i;
(*m_str) >> i;
d->m_endEntryOffset = i;
QDataStream *str = stream();
qint64 saveOffset = str->device()->pos();
// Init index tables
d->m_sycocaDict = new KSycocaDict(str, d->m_sycocaDictOffset);
saveOffset = str->device()->seek(saveOffset);
} else {
// We are in kbuildsycoca -- build new database!
m_entryDict = new KSycocaEntryDict;
d->m_sycocaDict = new KSycocaDict;
d->m_beginEntryOffset = 0;
d->m_endEntryOffset = 0;
// m_resourceList will be filled in by inherited constructors
}
m_sycoca->addFactory(this);
}
KSycocaFactory::~KSycocaFactory()
{
delete m_entryDict;
}
void KSycocaFactory::saveHeader(QDataStream &str)
{
// Write header
str.device()->seek(d->mOffset);
str << qint32(d->m_sycocaDictOffset);
str << qint32(d->m_beginEntryOffset);
str << qint32(d->m_endEntryOffset);
}
void KSycocaFactory::save(QDataStream &str)
{
if (!m_entryDict) {
return; // Error! Function should only be called when building database
}
if (!d->m_sycocaDict) {
return; // Error!
}
d->mOffset = str.device()->pos(); // store position in member variable
d->m_sycocaDictOffset = 0;
// Write header (pass #1)
saveHeader(str);
d->m_beginEntryOffset = str.device()->pos();
// Write all entries.
int entryCount = 0;
for (KSycocaEntry::Ptr entry : std::as_const(*m_entryDict)) {
entry->d_ptr->save(str);
entryCount++;
}
d->m_endEntryOffset = str.device()->pos();
// Write indices...
// Linear index
str << qint32(entryCount);
for (const KSycocaEntry::Ptr &entry : std::as_const(*m_entryDict)) {
str << qint32(entry.data()->offset());
}
// Dictionary index
d->m_sycocaDictOffset = str.device()->pos();
d->m_sycocaDict->save(str);
qint64 endOfFactoryData = str.device()->pos();
// Update header (pass #2)
saveHeader(str);
// Seek to end.
str.device()->seek(endOfFactoryData);
}
void KSycocaFactory::addEntry(const KSycocaEntry::Ptr &newEntry)
{
if (!m_entryDict) {
return; // Error! Function should only be called when
}
// building database
if (!d->m_sycocaDict) {
return; // Error!
}
KSycocaEntry::Ptr oldEntry = m_entryDict->value(newEntry->storageId());
if (oldEntry) {
// Already exists -> replace
// We found a more-local override, e.g. ~/.local/share/applications/kde5/foo.desktop
// So forget about the more global file.
//
// This can also happen with two .protocol files using the same protocol= entry.
// If we didn't remove one here, we would end up asserting because save()
// wasn't called on one of the entries.
// qDebug() << "removing" << oldEntry.data() << oldEntry->entryPath() << "because of" << newEntry->entryPath() << "they have the same storageId" <<
// newEntry->storageId();
removeEntry(newEntry->storageId());
}
const QString name = newEntry->storageId();
m_entryDict->insert(name, newEntry);
d->m_sycocaDict->add(name, newEntry);
}
void KSycocaFactory::removeEntry(const QString &entryName)
{
if (!m_entryDict) {
return; // Error! Function should only be called when
}
// building database
if (!d->m_sycocaDict) {
return; // Error!
}
m_entryDict->remove(entryName);
d->m_sycocaDict->remove(entryName); // O(N)
}
KSycocaEntry::List KSycocaFactory::allEntries() const
{
KSycocaEntry::List list;
// Assume we're NOT building a database
QDataStream *str = stream();
if (!str) {
return list;
}
str->device()->seek(d->m_endEntryOffset);
qint32 entryCount;
(*str) >> entryCount;
if (entryCount > 8192) {
qCWarning(SYCOCA) << QThread::currentThread() << "error detected in factory" << this;
KSycoca::flagError();
return list;
}
// offsetList is needed because createEntry() modifies the stream position
qint32 *offsetList = new qint32[entryCount];
for (int i = 0; i < entryCount; i++) {
(*str) >> offsetList[i];
}
for (int i = 0; i < entryCount; i++) {
KSycocaEntry *newEntry = createEntry(offsetList[i]);
if (newEntry) {
list.append(KSycocaEntry::Ptr(newEntry));
}
}
delete[] offsetList;
return list;
}
int KSycocaFactory::offset() const
{
return d->mOffset;
}
const KSycocaResourceList &KSycocaFactory::resourceList() const
{
return m_resourceList;
}
const KSycocaDict *KSycocaFactory::sycocaDict() const
{
return d->m_sycocaDict;
}
bool KSycocaFactory::isEmpty() const
{
return d->m_beginEntryOffset == d->m_endEntryOffset;
}
QDataStream *KSycocaFactory::stream() const
{
return m_str;
}
QStringList KSycocaFactory::allDirectories(const QString &subdir)
{
// We don't use QStandardPaths::locateAll() because we want all paths, even those that don't exist yet
QStringList topDirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
for (auto &dir : topDirs) {
dir += QLatin1Char('/') + subdir;
}
return topDirs;
}
void KSycocaFactory::virtual_hook(int /*id*/, void * /*data*/)
{
/*BASE::virtual_hook( id, data );*/
}
@@ -0,0 +1,168 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KSYCOCAFACTORY_H
#define KSYCOCAFACTORY_H
#include "ksycocaresourcelist_p.h"
#include <QStandardPaths>
#include <ksycocaentry.h>
#include <ksycoca.h> // for KSycoca::self()
#include <memory>
class QString;
class KSycoca;
class KSycocaDict;
template<typename T>
class QList;
template<typename KT, typename VT>
class QHash;
typedef QHash<QString, KSycocaEntry::Ptr> KSycocaEntryDict;
class KSycocaFactoryPrivate;
/**
* @internal
* Base class for sycoca factories
* Exported for unit tests
*/
class KSERVICE_EXPORT KSycocaFactory
{
public:
virtual KSycocaFactoryId factoryId() const = 0;
protected: // virtual class
/**
* Create a factory which can be used to lookup from/create a database
* (depending on KSycoca::isBuilding())
*/
explicit KSycocaFactory(KSycocaFactoryId factory_id, KSycoca *sycoca);
public:
virtual ~KSycocaFactory();
/**
* @return the position of the factory in the sycoca file
*/
int offset() const;
/**
* @return the dict, for special use by KBuildSycoca
*/
KSycocaEntryDict *entryDict()
{
return m_entryDict;
}
/**
* Construct an entry from a config file.
* To be implemented in the real factories.
*/
virtual KSycocaEntry *createEntry(const QString &file) const = 0;
/**
* Add an entry
*/
virtual void addEntry(const KSycocaEntry::Ptr &newEntry);
/**
* Remove all entries with the given name.
* Not very fast (O(N)), use with care.
*/
void removeEntry(const QString &entryName);
/**
* Read an entry from the database
*/
virtual KSycocaEntry *createEntry(int offset) const = 0;
/**
* Get a list of all entries from the database.
*/
virtual KSycocaEntry::List allEntries() const;
/**
* Saves all entries it maintains as well as index files
* for these entries to the stream 'str'.
*
* Also sets mOffset to the starting position.
*
* The stream is positioned at the end of the last index.
*
* Don't forget to call the parent first when you override
* this function.
*/
virtual void save(QDataStream &str);
/**
* Writes out a header to the stream 'str'.
* The baseclass positions the stream correctly.
*
* Don't forget to call the parent first when you override
* this function.
*/
virtual void saveHeader(QDataStream &str);
/**
* @return the resources for which this factory is responsible.
* @internal to kbuildsycoca
*/
const KSycocaResourceList &resourceList() const;
/**
* @return the sycoca dict, for factories to find entries by name.
*/
const KSycocaDict *sycocaDict() const;
/**
* @return true if the factory is completely empty - no entries defined
*/
bool isEmpty() const;
KSycoca *sycoca() const
{
return m_sycoca;
}
protected:
QDataStream *stream() const;
KSycocaResourceList m_resourceList;
KSycocaEntryDict *m_entryDict = nullptr;
/**
* Returns all directories for the given @p subdir of GenericDataLocation.
* Helper function for AnyFactory::resourceDirs().
*/
static QStringList allDirectories(const QString &subdir);
private:
QDataStream *m_str = nullptr;
KSycoca *m_sycoca = nullptr;
std::unique_ptr<KSycocaFactoryPrivate> const d;
protected:
/** Virtual hook, used to add new "virtual" functions while maintaining
binary compatibility. Unused in this class.
*/
virtual void virtual_hook(int id, void *data);
};
/**
* This, instead of a typedef, allows to declare "class ..." in header files.
* @internal
*/
class KSycocaFactoryList : public QList<KSycocaFactory *> // krazy:exclude=dpointer (acts as a typedef)
{
public:
KSycocaFactoryList()
{
}
};
#endif
@@ -0,0 +1,31 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KSYCOCARESOURCELIST_H
#define KSYCOCARESOURCELIST_H
#include <QString>
#include <vector>
struct KSycocaResource {
// resource is just used in the databaseChanged signal
// subdir is always under QStandardPaths::GenericDataLocation. E.g. mime, etc.
KSycocaResource(const QByteArray &resource, const QString &subdir, const QString &filter)
: resource(resource)
, subdir(subdir)
, extension(filter.mid(1))
{
}
const QByteArray resource;
const QString subdir;
const QString extension;
};
using KSycocaResourceList = std::vector<KSycocaResource>;
#endif
@@ -0,0 +1,58 @@
/*
This file is part of the KDE project
SPDX-FileCopyrightText: 1998, 1999 Torben Weis <weis@kde.org>
SPDX-FileCopyrightText: 1998, 1999 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KSYCOCATYPE_H
#define KSYCOCATYPE_H
/**
* \relates KSycocaEntry
* A KSycocaType is a code (out of the KSycocaType enum) assigned to each
* class type derived from KSycocaEntry .
* To use it, call the macro K_SYCOCATYPE( your_typecode, parent_class )
* at the top of your class definition.
*/
enum KSycocaType {
KST_KSycocaEntry = 0,
KST_KService = 1,
KST_KServiceType = 2,
KST_KMimeType = 3,
KST_KMimeTypeEntry = 6 /*internal*/,
KST_KServiceGroup = 7,
// 8 was KST_KImageIOFormat in kdelibs4
// 9 was KST_KProtocolInfo in kdelibs4
KST_KServiceSeparator = 10,
KST_KCustom = 1000,
};
/**
* \relates KSycocaFactory
* A KSycocaFactoryId is a code (out of the KSycocaFactoryId enum)
* assigned to each class type derived from KSycocaFactory.
* To use it, call the macro K_SYCOCAFACTORY( your_factory_id )
* at the top of your class definition.
*/
enum KSycocaFactoryId {
KST_KServiceFactory = 1,
KST_KServiceTypeFactory = 2,
KST_KServiceGroupFactory = 3,
// 4 was KST_KImageIO in kdelibs4
// 5 was KST_KProtocolInfoFactory in kdelibs4
KST_KMimeTypeFactory = 6,
KST_CTimeInfo = 100,
};
#define K_SYCOCAFACTORY(factory_id) \
public: \
KSycocaFactoryId factoryId() const override \
{ \
return factory_id; \
} \
\
private:
#endif
@@ -0,0 +1,59 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org>
SPDX-FileCopyrightText: 2005-2013 David Faure <faure@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef KSYCOCAUTILS_P_H
#define KSYCOCAUTILS_P_H
#include <QDir>
#include <QFileInfo>
#include <QString>
namespace KSycocaUtilsPrivate
{
// helper function for visitResourceDirectory
template<typename Visitor>
bool visitResourceDirectoryHelper(const QString &dirname, Visitor visitor)
{
QDir dir(dirname);
const QFileInfoList list = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::Dirs, QDir::Unsorted);
for (const QFileInfo &fi : list) {
if (fi.isDir() && !fi.isSymLink() && !fi.isBundle()) { // same check as in vfolder_menu.cpp
if (!visitor(fi)) {
return false;
}
if (!visitResourceDirectoryHelper(fi.filePath(), visitor)) {
return false;
}
}
}
return true;
}
// visitor is a function/functor accepts QFileInfo as argument and returns bool
// visitResourceDirectory will visit the resource directory in a depth-first way.
// visitor can terminate the visit by returning false, and visitResourceDirectory
// will also return false in this case, otherwise it will return true.
template<typename Visitor>
bool visitResourceDirectory(const QString &dirname, Visitor visitor)
{
QFileInfo info(dirname);
if (!visitor(info)) {
return false;
}
// Recurse only for services and menus.
if (!dirname.contains(QLatin1String("/applications"))) {
return visitResourceDirectoryHelper(dirname, visitor);
}
return true;
}
}
#endif /* KSYCOCAUTILS_P_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,272 @@
/*
This file is part of the KDE libraries
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
SPDX-License-Identifier: LGPL-2.0-only
*/
#ifndef VFOLDER_MENU_H
#define VFOLDER_MENU_H
#include <QDomDocument>
#include <QHash>
#include <QObject>
#include <QSet>
#include <QStack>
#include <QStringList>
#include <kservice.h>
class KBuildSycocaInterface;
class KServiceFactory;
class VFolderMenu : public QObject
{
Q_OBJECT
public:
class AppsInfo;
class SubMenu
{
public:
SubMenu()
: isDeleted(false)
, apps_info(nullptr)
{
items.reserve(43);
}
~SubMenu()
{
qDeleteAll(subMenus);
}
SubMenu(const SubMenu &) = delete;
SubMenu &operator=(const SubMenu &) = delete;
public:
QString name;
QString directoryFile;
QList<SubMenu *> subMenus;
QHash<QString, KService::Ptr> items;
QHash<QString, KService::Ptr> excludeItems; // Needed when merging due to Move.
QDomElement defaultLayoutNode;
QDomElement layoutNode;
bool isDeleted;
QStringList layoutList;
AppsInfo *apps_info;
};
VFolderMenu(KServiceFactory *serviceFactory, KBuildSycocaInterface *kbuildsycocaInterface);
~VFolderMenu() override;
/**
* Parses VFolder menu definition and generates a menu layout.
* The newService signals is used as callback to load
* a specific service description.
*
* @param file Menu file to load
*/
SubMenu *parseMenu(const QString &file);
/**
* Returns a list of all directories involved in the last call to
* parseMenu().
*
* A change in any of these directories or in any of their child-
* directories can result in changes to the menu.
*/
QStringList allDirectories();
/**
* Debug function to enable tracking of what happens with a specific
* menu item id
*/
void setTrackId(const QString &id);
public:
struct MenuItem {
enum Type { MI_Service, MI_SubMenu, MI_Separator };
Type type;
KService::Ptr service;
SubMenu *submenu;
};
public:
QStringList m_allDirectories; // A list of all the directories that we touch
QStringList m_defaultAppDirs;
QStringList m_defaultDirectoryDirs;
QStringList m_defaultMergeDirs;
QStringList m_directoryDirs; // Current set of applicable <DirectoryDir> dirs
QHash<QString, SubMenu *> m_legacyNodes; // Dictionary that stores Menu nodes
// associated with legacy tree.
class DocInfo
{
public:
QString baseDir; // Relative base dir of current menu file
QString baseName; // Filename of current menu file without ".menu"
QString path; // Full path of current menu file including ".menu"
};
DocInfo m_docInfo; // DocInfo for current doc
QStack<VFolderMenu::DocInfo> m_docInfoStack;
class AppsInfo
{
public:
AppsInfo()
{
dictCategories.reserve(53);
applications.reserve(997);
appRelPaths.reserve(997);
}
~AppsInfo()
{
}
QHash<QString, KService::List> dictCategories; // category -> apps
QHash<QString, KService::Ptr> applications; // rel path -> service
QHash<KService::Ptr, QString> appRelPaths; // service -> rel path
};
AppsInfo *m_appsInfo; // AppsInfo for current menu
QList<AppsInfo *> m_appsInfoStack; // All applicable AppsInfo for current menu
QList<AppsInfo *> m_appsInfoList; // List of all AppsInfo objects.
QSet<QString /*menuId*/> m_usedAppsDict; // all applications that have been allocated
QDomDocument m_doc;
SubMenu *m_rootMenu;
SubMenu *m_currentMenu;
bool m_track;
QString m_trackId;
private:
/**
* Lookup application by relative path
*/
KService::Ptr findApplication(const QString &relPath);
/**
* Lookup applications by category
*/
QList<KService::List *> findCategory(const QString &category);
/**
* Add new application
*/
void addApplication(const QString &id, KService::Ptr service);
/**
* Build application indices
*/
void buildApplicationIndex(bool unusedOnly);
/**
* Create a AppsInfo frame for current menu
*/
void createAppsInfo();
/**
* Load additional AppsInfo frame for current menu
*/
void loadAppsInfo();
/**
* Unload additional AppsInfo frame for current menu
*/
void unloadAppsInfo();
QDomDocument loadDoc();
void mergeMenus(QDomElement &docElem, QString &name);
void mergeFile(QDomElement &docElem, const QDomNode &mergeHere);
void loadMenu(const QString &filename);
/**
* Merge the items2 set into the items1 set
*/
void includeItems(QHash<QString, KService::Ptr> &items1, const QHash<QString, KService::Ptr> &items2);
/**
* Remove all items from the items1 set that aren't also in the items2 set
*/
void matchItems(QHash<QString, KService::Ptr> &items1, const QHash<QString, KService::Ptr> &items2);
/**
* Remove all items in the items2 set from the items1 set
*/
void excludeItems(QHash<QString, KService::Ptr> &items1, const QHash<QString, KService::Ptr> &items2);
/**
* Search the parentMenu tree for the menu menuName and takes it
* out.
*
* This function returns a pointer to the menu if it was found
* or @c nullptr if it was not found.
*/
SubMenu *takeSubMenu(SubMenu *parentMenu, const QString &menuName);
/**
* Insert the menu newMenu with name menuName into the parentMenu.
* If such menu already exist the result is merged, if any additional
* submenus are required they are created.
* If reversePriority is false, newMenu has priority over the existing
* menu during merging.
* If reversePriority is true, the existing menu has priority over newMenu
* during merging.
*/
void insertSubMenu(VFolderMenu::SubMenu *parentMenu, const QString &menuName, VFolderMenu::SubMenu *newMenu, bool reversePriority = false);
/**
* Merge menu2 and its submenus into menu1 and its submenus
* If reversePriority is false, menu2 has priority over menu1
* If reversePriority is true, menu1 has priority over menu2
*/
void mergeMenu(SubMenu *menu1, SubMenu *menu2, bool reversePriority = false);
/**
* Inserts service into the menu using name relative to parentMenu
* Any missing sub-menus are created.
*/
void insertService(SubMenu *parentMenu, const QString &name, KService::Ptr newService);
/**
* Register the directory that @p file is in.
* @see allDirectories()
*/
void registerFile(const QString &file);
/**
* Fill m_usedAppsDict with all applications from @p items
*/
void markUsedApplications(const QHash<QString, KService::Ptr> &items);
/**
* Register @p directory
* @see allDirectories()
*/
void registerDirectory(const QString &directory);
void processLegacyDir(const QString &dir, const QString &relDir, const QString &prefix);
void processMenu(QDomElement &docElem, int pass);
void layoutMenu(VFolderMenu::SubMenu *menu, QStringList defaultLayout);
void processCondition(QDomElement &docElem, QHash<QString, KService::Ptr> &items);
void initDirs();
void pushDocInfo(const QString &fileName, const QString &baseDir = QString());
void pushDocInfoParent(const QString &basePath, const QString &baseDir);
void popDocInfo();
QString absoluteDir(const QString &_dir, const QString &baseDir, bool keepRelativeToCfg = false);
QString locateMenuFile(const QString &fileName);
QString locateDirectoryFile(const QString &fileName);
void loadApplications(const QString &, const QString &);
QStringList parseLayoutNode(const QDomElement &docElem) const;
private:
KServiceFactory *m_serviceFactory;
KBuildSycocaInterface *m_kbuildsycocaInterface;
};
#endif