13#include "ksycocafactory_p.h"
14#include "ksycocatype.h"
15#include "ksycocautils_p.h"
16#include "sycocadebug.h"
17#include <KConfigGroup>
19#include <KSharedConfig>
21#include <QCoreApplication>
26#include <QStandardPaths>
28#include <QThreadStorage>
30#include <QCryptographicHash>
32#include <kmimetypefactory_p.h>
33#include <kservicefactory_p.h>
34#include <kservicegroupfactory_p.h>
36#include "kbuildsycoca_p.h"
37#include "ksycocadevices_p.h"
51#define KSYCOCA_VERSION 306
53#if HAVE_MADVISE || HAVE_MMAP
58#define MAP_FAILED ((void *)-1)
63 in >> h.prefixes >> h.timeStamp >> h.language >> h.updateSignature;
75Q_DECLARE_OPERATORS_FOR_FLAGS(KSycocaPrivate::BehaviorsIfNotFound)
77KSycocaPrivate::KSycocaPrivate(
KSycoca *qq)
78 : databaseStatus(DatabaseNotOpen)
84 , m_haveListeners(false)
87 , sycoca_mmap(nullptr)
90 , m_mimeTypeFactory(nullptr)
91 , m_serviceFactory(nullptr)
92 , m_serviceGroupFactory(nullptr)
99 m_sycocaStrategy = StrategyMemFile;
101 m_sycocaStrategy = StrategyMmap;
104 setStrategyFromString(config.readEntry(
"strategy"));
107void KSycocaPrivate::setStrategyFromString(
const QString &strategy)
110 m_sycocaStrategy = StrategyMmap;
112 m_sycocaStrategy = StrategyFile;
114 m_sycocaStrategy = StrategyMemFile;
115 }
else if (!strategy.
isEmpty()) {
116 qCWarning(SYCOCA) <<
"Unknown sycoca strategy:" << strategy;
120bool KSycocaPrivate::tryMmap()
123 Q_ASSERT(!m_databasePath.isEmpty());
124 m_mmapFile =
new QFile(m_databasePath);
130 fcntl(m_mmapFile->handle(), F_SETFD, FD_CLOEXEC);
131 sycoca_size = m_mmapFile->size();
132 void *mmapRet = mmap(
nullptr, sycoca_size, PROT_READ, MAP_SHARED, m_mmapFile->handle(), 0);
135 if (mmapRet == MAP_FAILED || mmapRet ==
nullptr) {
136 qCDebug(SYCOCA).nospace() <<
"mmap failed. (length = " << sycoca_size <<
")";
137 sycoca_mmap =
nullptr;
140 sycoca_mmap =
static_cast<const char *
>(mmapRet);
142 (void)posix_madvise(mmapRet, sycoca_size, POSIX_MADV_WILLNEED);
153 return KSYCOCA_VERSION;
156class KSycocaSingleton
166 bool hasSycoca()
const
186Q_GLOBAL_STATIC(KSycocaSingleton, ksycocaInstance)
188QString KSycocaPrivate::findDatabase()
190 Q_ASSERT(databaseStatus == DatabaseNotOpen);
194 if (info.isReadable()) {
195 if (m_haveListeners && m_fileWatcher) {
196 m_fileWatcher->addFile(path);
202 m_fileWatcher->addFile(path);
210 : d(new KSycocaPrivate(this))
212 if (d->m_fileWatcher) {
214 connect(d->m_fileWatcher.get(), &KDirWatch::created, this, [this]() {
215 d->slotDatabaseChanged();
219 d->slotDatabaseChanged();
224bool KSycocaPrivate::openDatabase()
226 Q_ASSERT(databaseStatus == DatabaseNotOpen);
231 if (m_databasePath.isEmpty()) {
232 m_databasePath = findDatabase();
236 if (!m_databasePath.isEmpty()) {
237 static bool firstTime =
true;
243 qCDebug(SYCOCA) <<
"flatpak detected, ignoring" << m_databasePath;
248 qCDebug(SYCOCA) <<
"Opening ksycoca from" << m_databasePath;
258KSycocaAbstractDevice *KSycocaPrivate::device()
264 KSycocaAbstractDevice *device = m_device;
265 Q_ASSERT(!m_databasePath.isEmpty());
267 if (m_sycocaStrategy == StrategyMmap && tryMmap()) {
268 device =
new KSycocaMmapDevice(sycoca_mmap, sycoca_size);
275#ifndef QT_NO_SHAREDMEMORY
276 if (!device && m_sycocaStrategy == StrategyMemFile) {
277 device =
new KSycocaMemFileDevice(m_databasePath);
285 device =
new KSycocaFileDevice(m_databasePath);
287 qCWarning(SYCOCA) <<
"Couldn't open" << m_databasePath <<
"even though it is readable? Impossible.";
300 if (databaseStatus == DatabaseNotOpen) {
301 checkDatabase(KSycocaPrivate::IfNotFoundRecreate);
307 return m_device->stream();
310void KSycocaPrivate::slotDatabaseChanged()
314 if (!m_dbLastModified.isValid() || m_dbLastModified !=
QFileInfo(m_databasePath).lastModified()) {
320 m_databasePath = findDatabase();
323 Q_EMIT q->databaseChanged();
327KMimeTypeFactory *KSycocaPrivate::mimeTypeFactory()
329 if (!m_mimeTypeFactory) {
330 m_mimeTypeFactory =
new KMimeTypeFactory(q);
332 return m_mimeTypeFactory;
335KServiceFactory *KSycocaPrivate::serviceFactory()
337 if (!m_serviceFactory) {
338 m_serviceFactory =
new KServiceFactory(q);
340 return m_serviceFactory;
343KServiceGroupFactory *KSycocaPrivate::serviceGroupFactory()
345 if (!m_serviceGroupFactory) {
346 m_serviceGroupFactory =
new KServiceGroupFactory(q);
348 return m_serviceGroupFactory;
353 : d(new KSycocaPrivate(this))
359 KSycoca *s = ksycocaInstance()->sycoca();
375 return self()->d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing);
378void KSycocaPrivate::closeDatabase()
387 qDeleteAll(m_factories);
390 m_mimeTypeFactory =
nullptr;
391 m_serviceFactory =
nullptr;
392 m_serviceGroupFactory =
nullptr;
398 munmap(
const_cast<char *
>(sycoca_mmap), sycoca_size);
399 sycoca_mmap =
nullptr;
402 m_mmapFile =
nullptr;
405 databaseStatus = DatabaseNotOpen;
406 m_databasePath.clear();
412 d->addFactory(factory);
423 type = KSycocaType(aType);
428KSycocaFactoryList *KSycoca::factories()
430 return d->factories();
434bool KSycocaPrivate::checkVersion()
441 if (aVersion < KSYCOCA_VERSION) {
442 qCDebug(SYCOCA) <<
"Found version" << aVersion <<
", expecting version" << KSYCOCA_VERSION <<
"or higher.";
443 databaseStatus = BadVersion;
446 databaseStatus = DatabaseOK;
453bool KSycocaPrivate::checkDatabase(BehaviorsIfNotFound ifNotFound)
455 if (databaseStatus == DatabaseOK) {
464 if (openDatabase()) {
467 if (qAppName() !=
QLatin1String(KBUILDSYCOCA_EXENAME) && ifNotFound != IfNotFoundDoNothing) {
478 if (ifNotFound & IfNotFoundRecreate) {
479 return buildSycoca();
488 if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
500 qCWarning(SYCOCA) <<
"Error, KSycocaFactory (id =" << int(
id) <<
") not found!";
513bool KSycoca::needsRebuild()
515 return d->needsRebuild();
518KSycocaHeader KSycocaPrivate::readSycocaHeader()
520 KSycocaHeader header;
522 if (!checkDatabase(KSycocaPrivate::IfNotFoundDoNothing)) {
542 *str >> header >> directoryList;
543 allResourceDirs.
clear();
544 for (
int i = 0; i < directoryList.
count(); ++i) {
547 allResourceDirs.insert(directoryList.
at(i), mtime);
553 for (
const auto &fileName : std::as_const(fileList)) {
556 extraFiles.insert(fileName, mtime);
561 timeStamp = header.timeStamp;
564 language = header.language;
565 updateSig = header.updateSignature;
570class TimestampChecker
585 for (
auto it = dirs.
begin(); it != dirs.
end(); ++it) {
587 const qint64 lastStamp = it.value();
589 auto visitor = [&](
const QFileInfo &fi) {
590 const QDateTime mtime = fi.lastModified();
593 qCDebug(SYCOCA) << fi.filePath() <<
"has a modification time in the future" << mtime;
603 if (!KSycocaUtilsPrivate::visitResourceDirectory(dir, visitor)) {
612 for (
auto it = files.
begin(); it != files.
end(); ++it) {
613 const QString fileName = it.key();
614 const qint64 lastStamp = it.value();
620 const QDateTime mtime = fi.lastModified();
623 qCDebug(SYCOCA) << fi.filePath() <<
"has a modification time in the future" << mtime;
636void KSycocaPrivate::checkDirectories()
638 if (needsRebuild()) {
643bool KSycocaPrivate::needsRebuild()
646 if (!timeStamp && databaseStatus != BadVersion) {
647 (void)readSycocaHeader();
651 const auto timestampChecker = TimestampChecker();
652 bool ret = timeStamp != 0
653 && (!timestampChecker.checkDirectoriesTimestamps(allResourceDirs)
654 || !timestampChecker.checkFilesTimestamps(extraFiles));
658 auto files = KBuildSycoca::factoryExtraFiles();
662 return extraFiles.
keys() != files;
665bool KSycocaPrivate::buildSycoca()
667 KBuildSycoca builder;
668 if (!builder.recreate()) {
675 if (!openDatabase()) {
676 qCDebug(SYCOCA) <<
"Still no database...";
679 Q_EMIT q->databaseChanged();
688 const QByteArray ksycoca_env = qgetenv(
"KDESYCOCA");
706 (void)d->readSycocaHeader();
708 return d->allResourceDirs.keys();
713 qCWarning(SYCOCA) <<
"ERROR: KSycoca database corruption!";
715 if (sycoca->d->readError) {
718 sycoca->d->readError =
true;
721 KBuildSycoca builder;
722 (void)builder.recreate();
733 ksycocaInstance->sycoca()->d->m_fileWatcher =
nullptr;
741void KSycoca::connectNotify(
const QMetaMethod &signal)
743 if (signal.
name() ==
"databaseChanged" && !d->m_haveListeners) {
744 d->m_haveListeners =
true;
745 if (d->m_databasePath.isEmpty()) {
746 d->m_databasePath = d->findDatabase();
747 }
else if (d->m_fileWatcher) {
748 d->m_fileWatcher->addFile(d->m_databasePath);
753void KSycoca::clearCaches()
755 if (ksycocaInstance.exists() && ksycocaInstance()->hasSycoca()) {
756 ksycocaInstance()->sycoca()->d->closeDatabase();
760extern KSERVICE_EXPORT
int ksycoca_ms_between_checks;
761KSERVICE_EXPORT
int ksycoca_ms_between_checks = 1500;
769 if (d->databaseStatus != KSycocaPrivate::DatabaseOK) {
770 if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
775 if (d->m_lastCheck.isValid() && d->m_lastCheck.elapsed() < ksycoca_ms_between_checks) {
778 d->m_lastCheck.start();
784 d->checkDirectories();
796 const QByteArray content = R
"(<?xml version="1.0"?>
797<!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN" "http://www.freedesktop.org/standards/menu-spec/menu-1.0.dtd">
799 <Name>Applications</Name>
800 <Directory>Applications.directory</Directory>
802 <DefaultDirectoryDirs/>
803 <MergeDir>applications-merged</MergeDir>
804 <LegacyDir>/usr/share/applnk</LegacyDir>
806 <Merge type="menus"/>
807 <Merge type="files"/>
809 <Menuname>More</Menuname>
818 output.
write(content);
821#include "moc_ksycoca.cpp"
void dirty(const QString &path)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
QStringList allResourceDirs()
static KSycoca * self()
Get or create the only instance of KSycoca (read-only)
static void setupTestMenu()
Sets up a minimal applications.menu file in the appropriate location.
virtual bool isBuilding()
KSycoca()
Read-only database.
QDataStream * findEntry(int offset, KSycocaType &type)
static bool isAvailable()
QDataStream * findFactory(KSycocaFactoryId id)
void addFactory(KSycocaFactory *)
static void flagError()
A read error occurs.
static QString absoluteFilePath()
static void disableAutoRebuild()
Disables automatic rebuilding of the cache on service file changes.
void ensureCacheValid()
Ensures the ksycoca database is up to date.
KSycocaFactoryId
A KSycocaFactoryId is a code (out of the KSycocaFactoryId enum) assigned to each class type derived f...
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
KCOREADDONS_EXPORT bool isFlatpak()
NETWORKMANAGERQT_EXPORT bool checkVersion(const int x, const int y, const int z)
bool isEmpty() const const
QByteArray toBase64(Base64Options options) const const
QByteArray hash(QByteArrayView data, Algorithm method)
QIODevice * device() const const
QDateTime fromMSecsSinceEpoch(qint64 msecs)
qint64 toMSecsSinceEpoch() const const
bool mkpath(const QString &dirPath) const const
QString decodeName(const QByteArray &localFileName)
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
QDateTime lastModified() const const
virtual qint64 pos() const const
virtual bool seek(qint64 pos)
qint64 write(const QByteArray &data)
const_reference at(qsizetype i) const const
qsizetype count() const const
QString bcp47Name() const const
bool isEmpty() const const
QList< Key > keys() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QStringList standardLocations(StandardLocation type)
QString writableLocation(StandardLocation type)
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QString join(QChar separator) const const
QThread * currentThread()
bool hasLocalData() const const
void setLocalData(T data)