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
168 return m_threadSycocas.hasLocalData();
172 if (!m_threadSycocas.hasLocalData()) {
173 m_threadSycocas.setLocalData(
new KSycoca);
175 return m_threadSycocas.localData();
177 void setSycoca(KSycoca *s)
179 m_threadSycocas.setLocalData(s);
183 QThreadStorage<KSycoca *> m_threadSycocas;
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
574 : m_now(QDateTime::currentDateTime())
581 bool checkDirectoriesTimestamps(
const QMap<QString, qint64> &dirs)
const
585 for (
auto it = dirs.
begin(); it != dirs.
end(); ++it) {
586 const QString
dir = it.key();
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)) {
610 bool checkFilesTimestamps(
const QMap<QString, qint64> &files)
const
612 for (
auto it = files.
begin(); it != files.
end(); ++it) {
613 const QString fileName = it.key();
614 const qint64 lastStamp = it.value();
616 QFileInfo fi(fileName);
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.
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()