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:
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user