File indexing completed on 2024-04-21 03:56:53

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 1999-2000 Waldo Bastian <bastian@kde.org>
0004     SPDX-FileCopyrightText: 2005-2009 David Faure <faure@kde.org>
0005     SPDX-FileCopyrightText: 2008 Hamish Rodda <rodda@kde.org>
0006     SPDX-FileCopyrightText: 2020-2022 Harald Sitter <sitter@kde.org>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-only
0009 */
0010 
0011 #include "ksycoca.h"
0012 #include "ksycoca_p.h"
0013 #include "ksycocafactory_p.h"
0014 #include "ksycocatype.h"
0015 #include "ksycocautils_p.h"
0016 #include "sycocadebug.h"
0017 #include <KConfigGroup>
0018 #include <KSandbox>
0019 #include <KSharedConfig>
0020 
0021 #include <QCoreApplication>
0022 #include <QDataStream>
0023 #include <QFile>
0024 #include <QFileInfo>
0025 #include <QMetaMethod>
0026 #include <QStandardPaths>
0027 #include <QThread>
0028 #include <QThreadStorage>
0029 
0030 #include <QCryptographicHash>
0031 #include <fcntl.h>
0032 #include <kmimetypefactory_p.h>
0033 #include <kservicefactory_p.h>
0034 #include <kservicegroupfactory_p.h>
0035 
0036 #include "kbuildsycoca_p.h"
0037 #include "ksycocadevices_p.h"
0038 
0039 #ifdef Q_OS_UNIX
0040 #include <sys/time.h>
0041 #include <utime.h>
0042 #endif
0043 
0044 /**
0045  * Sycoca file version number.
0046  * If the existing file is outdated, it will not get read
0047  * but instead we'll regenerate a new one.
0048  * However running apps should still be able to read it, so
0049  * only add to the data, never remove/modify.
0050  */
0051 #define KSYCOCA_VERSION 305
0052 
0053 #if HAVE_MADVISE || HAVE_MMAP
0054 #include <sys/mman.h> // This #include was checked when looking for posix_madvise
0055 #endif
0056 
0057 #ifndef MAP_FAILED
0058 #define MAP_FAILED ((void *)-1)
0059 #endif
0060 
0061 QDataStream &operator>>(QDataStream &in, KSycocaHeader &h)
0062 {
0063     in >> h.prefixes >> h.timeStamp >> h.language >> h.updateSignature;
0064     return in;
0065 }
0066 
0067 // The following limitations are in place:
0068 // Maximum length of a single string: 8192 bytes
0069 // Maximum length of a string list: 1024 strings
0070 // Maximum number of entries: 8192
0071 //
0072 // The purpose of these limitations is to limit the impact
0073 // of database corruption.
0074 
0075 Q_DECLARE_OPERATORS_FOR_FLAGS(KSycocaPrivate::BehaviorsIfNotFound)
0076 
0077 KSycocaPrivate::KSycocaPrivate(KSycoca *qq)
0078     : databaseStatus(DatabaseNotOpen)
0079     , readError(false)
0080     , timeStamp(0)
0081     , m_databasePath()
0082     , updateSig(0)
0083     , m_fileWatcher(new KDirWatch)
0084     , m_haveListeners(false)
0085     , q(qq)
0086     , sycoca_size(0)
0087     , sycoca_mmap(nullptr)
0088     , m_mmapFile(nullptr)
0089     , m_device(nullptr)
0090     , m_mimeTypeFactory(nullptr)
0091     , m_serviceFactory(nullptr)
0092     , m_serviceGroupFactory(nullptr)
0093 {
0094 #ifdef Q_OS_WIN
0095     /*
0096       on windows we use KMemFile (QSharedMemory) to avoid problems
0097       with mmap (can't delete a mmap'd file)
0098     */
0099     m_sycocaStrategy = StrategyMemFile;
0100 #else
0101     m_sycocaStrategy = StrategyMmap;
0102 #endif
0103     KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("KSycoca"));
0104     setStrategyFromString(config.readEntry("strategy"));
0105 }
0106 
0107 void KSycocaPrivate::setStrategyFromString(const QString &strategy)
0108 {
0109     if (strategy == QLatin1String("mmap")) {
0110         m_sycocaStrategy = StrategyMmap;
0111     } else if (strategy == QLatin1String("file")) {
0112         m_sycocaStrategy = StrategyFile;
0113     } else if (strategy == QLatin1String("sharedmem")) {
0114         m_sycocaStrategy = StrategyMemFile;
0115     } else if (!strategy.isEmpty()) {
0116         qCWarning(SYCOCA) << "Unknown sycoca strategy:" << strategy;
0117     }
0118 }
0119 
0120 bool KSycocaPrivate::tryMmap()
0121 {
0122 #if HAVE_MMAP
0123     Q_ASSERT(!m_databasePath.isEmpty());
0124     m_mmapFile = new QFile(m_databasePath);
0125     const bool canRead = m_mmapFile->open(QIODevice::ReadOnly);
0126     Q_ASSERT(canRead);
0127     if (!canRead) {
0128         return false;
0129     }
0130     fcntl(m_mmapFile->handle(), F_SETFD, FD_CLOEXEC);
0131     sycoca_size = m_mmapFile->size();
0132     void *mmapRet = mmap(nullptr, sycoca_size, PROT_READ, MAP_SHARED, m_mmapFile->handle(), 0);
0133     /* POSIX mandates only MAP_FAILED, but we are paranoid so check for
0134        null pointer too.  */
0135     if (mmapRet == MAP_FAILED || mmapRet == nullptr) {
0136         qCDebug(SYCOCA).nospace() << "mmap failed. (length = " << sycoca_size << ")";
0137         sycoca_mmap = nullptr;
0138         return false;
0139     } else {
0140         sycoca_mmap = static_cast<const char *>(mmapRet);
0141 #if HAVE_MADVISE
0142         (void)posix_madvise(mmapRet, sycoca_size, POSIX_MADV_WILLNEED);
0143 #endif // HAVE_MADVISE
0144         return true;
0145     }
0146 #else
0147     return false;
0148 #endif // HAVE_MMAP
0149 }
0150 
0151 int KSycoca::version()
0152 {
0153     return KSYCOCA_VERSION;
0154 }
0155 
0156 class KSycocaSingleton
0157 {
0158 public:
0159     KSycocaSingleton()
0160     {
0161     }
0162     ~KSycocaSingleton()
0163     {
0164     }
0165 
0166     bool hasSycoca() const
0167     {
0168         return m_threadSycocas.hasLocalData();
0169     }
0170     KSycoca *sycoca()
0171     {
0172         if (!m_threadSycocas.hasLocalData()) {
0173             m_threadSycocas.setLocalData(new KSycoca);
0174         }
0175         return m_threadSycocas.localData();
0176     }
0177     void setSycoca(KSycoca *s)
0178     {
0179         m_threadSycocas.setLocalData(s);
0180     }
0181 
0182 private:
0183     QThreadStorage<KSycoca *> m_threadSycocas;
0184 };
0185 
0186 Q_GLOBAL_STATIC(KSycocaSingleton, ksycocaInstance)
0187 
0188 QString KSycocaPrivate::findDatabase()
0189 {
0190     Q_ASSERT(databaseStatus == DatabaseNotOpen);
0191 
0192     const QString path = KSycoca::absoluteFilePath();
0193     const QFileInfo info(path);
0194     if (info.isReadable()) {
0195         if (m_haveListeners && m_fileWatcher) {
0196             m_fileWatcher->addFile(path);
0197         }
0198         return path;
0199     }
0200     // Let's be notified when it gets created - by another process or by ourselves
0201     if (m_fileWatcher) {
0202         m_fileWatcher->addFile(path);
0203     }
0204     return QString();
0205 }
0206 
0207 // Read-only constructor
0208 // One instance per thread
0209 KSycoca::KSycoca()
0210     : d(new KSycocaPrivate(this))
0211 {
0212     if (d->m_fileWatcher) {
0213         // We always delete and recreate the DB, so KDirWatch normally emits created
0214         connect(d->m_fileWatcher.get(), &KDirWatch::created, this, [this]() {
0215             d->slotDatabaseChanged();
0216         });
0217         // In some cases, KDirWatch only thinks the file was modified though
0218         connect(d->m_fileWatcher.get(), &KDirWatch::dirty, this, [this]() {
0219             d->slotDatabaseChanged();
0220         });
0221     }
0222 }
0223 
0224 bool KSycocaPrivate::openDatabase()
0225 {
0226     Q_ASSERT(databaseStatus == DatabaseNotOpen);
0227 
0228     delete m_device;
0229     m_device = nullptr;
0230 
0231     if (m_databasePath.isEmpty()) {
0232         m_databasePath = findDatabase();
0233     }
0234 
0235     bool result = true;
0236     if (!m_databasePath.isEmpty()) {
0237         static bool firstTime = true;
0238         if (firstTime) {
0239             firstTime = false;
0240             if (KSandbox::isFlatpak()) {
0241                 // We're running inside flatpak, which sets all times to 1970
0242                 // So the first very time, don't use an existing database, recreate it
0243                 qCDebug(SYCOCA) << "flatpak detected, ignoring" << m_databasePath;
0244                 return false;
0245             }
0246         }
0247 
0248         qCDebug(SYCOCA) << "Opening ksycoca from" << m_databasePath;
0249         m_dbLastModified = QFileInfo(m_databasePath).lastModified();
0250         result = checkVersion();
0251     } else { // No database file
0252         // qCDebug(SYCOCA) << "Could not open ksycoca";
0253         result = false;
0254     }
0255     return result;
0256 }
0257 
0258 KSycocaAbstractDevice *KSycocaPrivate::device()
0259 {
0260     if (m_device) {
0261         return m_device;
0262     }
0263 
0264     KSycocaAbstractDevice *device = m_device;
0265     Q_ASSERT(!m_databasePath.isEmpty());
0266 #if HAVE_MMAP
0267     if (m_sycocaStrategy == StrategyMmap && tryMmap()) {
0268         device = new KSycocaMmapDevice(sycoca_mmap, sycoca_size);
0269         if (!device->device()->open(QIODevice::ReadOnly)) {
0270             delete device;
0271             device = nullptr;
0272         }
0273     }
0274 #endif
0275 #ifndef QT_NO_SHAREDMEMORY
0276     if (!device && m_sycocaStrategy == StrategyMemFile) {
0277         device = new KSycocaMemFileDevice(m_databasePath);
0278         if (!device->device()->open(QIODevice::ReadOnly)) {
0279             delete device;
0280             device = nullptr;
0281         }
0282     }
0283 #endif
0284     if (!device) {
0285         device = new KSycocaFileDevice(m_databasePath);
0286         if (!device->device()->open(QIODevice::ReadOnly)) {
0287             qCWarning(SYCOCA) << "Couldn't open" << m_databasePath << "even though it is readable? Impossible.";
0288             // delete device; device = 0; // this would crash in the return statement...
0289         }
0290     }
0291     if (device) {
0292         m_device = device;
0293     }
0294     return m_device;
0295 }
0296 
0297 QDataStream *&KSycocaPrivate::stream()
0298 {
0299     if (!m_device) {
0300         if (databaseStatus == DatabaseNotOpen) {
0301             checkDatabase(KSycocaPrivate::IfNotFoundRecreate);
0302         }
0303 
0304         device(); // create m_device
0305     }
0306 
0307     return m_device->stream();
0308 }
0309 
0310 void KSycocaPrivate::slotDatabaseChanged()
0311 {
0312     qCDebug(SYCOCA) << QThread::currentThread() << "got a notifyDatabaseChanged signal";
0313     // In case we have changed the database outselves, we have already notified the application
0314     if (!m_dbLastModified.isValid() || m_dbLastModified != QFileInfo(m_databasePath).lastModified()) {
0315         // KDirWatch tells us the database file changed
0316         // We would have found out in the next call to ensureCacheValid(), but for
0317         // now keep the call to closeDatabase, to help refcounting to 0 the old mmapped file earlier.
0318         closeDatabase();
0319         // Start monitoring the new file right away
0320         m_databasePath = findDatabase();
0321 
0322         // Now notify applications
0323         Q_EMIT q->databaseChanged();
0324     }
0325 }
0326 
0327 KMimeTypeFactory *KSycocaPrivate::mimeTypeFactory()
0328 {
0329     if (!m_mimeTypeFactory) {
0330         m_mimeTypeFactory = new KMimeTypeFactory(q);
0331     }
0332     return m_mimeTypeFactory;
0333 }
0334 
0335 KServiceFactory *KSycocaPrivate::serviceFactory()
0336 {
0337     if (!m_serviceFactory) {
0338         m_serviceFactory = new KServiceFactory(q);
0339     }
0340     return m_serviceFactory;
0341 }
0342 
0343 KServiceGroupFactory *KSycocaPrivate::serviceGroupFactory()
0344 {
0345     if (!m_serviceGroupFactory) {
0346         m_serviceGroupFactory = new KServiceGroupFactory(q);
0347     }
0348     return m_serviceGroupFactory;
0349 }
0350 
0351 // Read-write constructor - only for KBuildSycoca
0352 KSycoca::KSycoca(bool /* dummy */)
0353     : d(new KSycocaPrivate(this))
0354 {
0355 }
0356 
0357 KSycoca *KSycoca::self()
0358 {
0359     KSycoca *s = ksycocaInstance()->sycoca();
0360     Q_ASSERT(s);
0361     return s;
0362 }
0363 
0364 KSycoca::~KSycoca()
0365 {
0366     d->closeDatabase();
0367     delete d;
0368     // if (ksycocaInstance.exists()
0369     //    && ksycocaInstance->self == this)
0370     //    ksycocaInstance->self = 0;
0371 }
0372 
0373 bool KSycoca::isAvailable() // TODO KF6: make it non-static (mostly useful for unittests)
0374 {
0375     return self()->d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing);
0376 }
0377 
0378 void KSycocaPrivate::closeDatabase()
0379 {
0380     delete m_device;
0381     m_device = nullptr;
0382 
0383     // It is very important to delete all factories here
0384     // since they cache information about the database file
0385     // But other threads might be using them, so this class is
0386     // refcounted, and deleted when the last thread is done with them
0387     qDeleteAll(m_factories);
0388     m_factories.clear();
0389 
0390     m_mimeTypeFactory = nullptr;
0391     m_serviceFactory = nullptr;
0392     m_serviceGroupFactory = nullptr;
0393 
0394 #if HAVE_MMAP
0395     if (sycoca_mmap) {
0396         // Solaris has munmap(char*, size_t) and everything else should
0397         // be happy with a char* for munmap(void*, size_t)
0398         munmap(const_cast<char *>(sycoca_mmap), sycoca_size);
0399         sycoca_mmap = nullptr;
0400     }
0401     delete m_mmapFile;
0402     m_mmapFile = nullptr;
0403 #endif
0404 
0405     databaseStatus = DatabaseNotOpen;
0406     m_databasePath.clear();
0407     timeStamp = 0;
0408 }
0409 
0410 void KSycoca::addFactory(KSycocaFactory *factory)
0411 {
0412     d->addFactory(factory);
0413 }
0414 
0415 QDataStream *KSycoca::findEntry(int offset, KSycocaType &type)
0416 {
0417     QDataStream *str = stream();
0418     Q_ASSERT(str);
0419     // qCDebug(SYCOCA) << QString("KSycoca::_findEntry(offset=%1)").arg(offset,8,16);
0420     str->device()->seek(offset);
0421     qint32 aType;
0422     *str >> aType;
0423     type = KSycocaType(aType);
0424     // qCDebug(SYCOCA) << QString("KSycoca::found type %1").arg(aType);
0425     return str;
0426 }
0427 
0428 KSycocaFactoryList *KSycoca::factories()
0429 {
0430     return d->factories();
0431 }
0432 
0433 // Warning, checkVersion rewinds to the beginning of stream().
0434 bool KSycocaPrivate::checkVersion()
0435 {
0436     QDataStream *m_str = device()->stream();
0437     Q_ASSERT(m_str);
0438     m_str->device()->seek(0);
0439     qint32 aVersion;
0440     *m_str >> aVersion;
0441     if (aVersion < KSYCOCA_VERSION) {
0442         qCDebug(SYCOCA) << "Found version" << aVersion << ", expecting version" << KSYCOCA_VERSION << "or higher.";
0443         databaseStatus = BadVersion;
0444         return false;
0445     } else {
0446         databaseStatus = DatabaseOK;
0447         return true;
0448     }
0449 }
0450 
0451 // If it returns true, we have a valid database and the stream has rewinded to the beginning
0452 // and past the version number.
0453 bool KSycocaPrivate::checkDatabase(BehaviorsIfNotFound ifNotFound)
0454 {
0455     if (databaseStatus == DatabaseOK) {
0456         if (checkVersion()) { // we know the version is ok, but we must rewind the stream anyway
0457             return true;
0458         }
0459     }
0460 
0461     closeDatabase(); // close the dummy one
0462 
0463     // Check if new database already available
0464     if (openDatabase()) {
0465         // Database exists, and version is ok, we can read it.
0466 
0467         if (qAppName() != QLatin1String(KBUILDSYCOCA_EXENAME) && ifNotFound != IfNotFoundDoNothing) {
0468             // Ensure it's up-to-date, rebuild if needed
0469             checkDirectories();
0470 
0471             // Don't check again for some time
0472             m_lastCheck.start();
0473         }
0474 
0475         return true;
0476     }
0477 
0478     if (ifNotFound & IfNotFoundRecreate) {
0479         return buildSycoca();
0480     }
0481 
0482     return false;
0483 }
0484 
0485 QDataStream *KSycoca::findFactory(KSycocaFactoryId id)
0486 {
0487     // Ensure we have a valid database (right version, and rewinded to beginning)
0488     if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
0489         return nullptr;
0490     }
0491 
0492     QDataStream *str = stream();
0493     Q_ASSERT(str);
0494 
0495     qint32 aId;
0496     qint32 aOffset;
0497     while (true) {
0498         *str >> aId;
0499         if (aId == 0) {
0500             qCWarning(SYCOCA) << "Error, KSycocaFactory (id =" << int(id) << ") not found!";
0501             break;
0502         }
0503         *str >> aOffset;
0504         if (aId == id) {
0505             // qCDebug(SYCOCA) << "KSycoca::findFactory(" << id << ") offset " << aOffset;
0506             str->device()->seek(aOffset);
0507             return str;
0508         }
0509     }
0510     return nullptr;
0511 }
0512 
0513 bool KSycoca::needsRebuild()
0514 {
0515     return d->needsRebuild();
0516 }
0517 
0518 KSycocaHeader KSycocaPrivate::readSycocaHeader()
0519 {
0520     KSycocaHeader header;
0521     // do not try to launch kbuildsycoca from here; this code is also called by kbuildsycoca.
0522     if (!checkDatabase(KSycocaPrivate::IfNotFoundDoNothing)) {
0523         return header;
0524     }
0525     QDataStream *str = stream();
0526     qint64 oldPos = str->device()->pos();
0527 
0528     Q_ASSERT(str);
0529     qint32 aId;
0530     qint32 aOffset;
0531     // skip factories offsets
0532     while (true) {
0533         *str >> aId;
0534         if (aId) {
0535             *str >> aOffset;
0536         } else {
0537             break; // just read 0
0538         }
0539     }
0540     // We now point to the header
0541     QStringList directoryList;
0542     *str >> header >> directoryList;
0543     allResourceDirs.clear();
0544     for (int i = 0; i < directoryList.count(); ++i) {
0545         qint64 mtime;
0546         *str >> mtime;
0547         allResourceDirs.insert(directoryList.at(i), mtime);
0548     }
0549 
0550     QStringList fileList;
0551     *str >> fileList;
0552     extraFiles.clear();
0553     for (const auto &fileName : std::as_const(fileList)) {
0554         qint64 mtime;
0555         *str >> mtime;
0556         extraFiles.insert(fileName, mtime);
0557     }
0558 
0559     str->device()->seek(oldPos);
0560 
0561     timeStamp = header.timeStamp;
0562 
0563     // for the useless public accessors. KF6: remove these two lines, the accessors and the vars.
0564     language = header.language;
0565     updateSig = header.updateSignature;
0566 
0567     return header;
0568 }
0569 
0570 class TimestampChecker
0571 {
0572 public:
0573     TimestampChecker()
0574         : m_now(QDateTime::currentDateTime())
0575     {
0576     }
0577 
0578     // Check times of last modification of all directories on which ksycoca depends,
0579     // If none of them is newer than the mtime we stored for that directory at the
0580     // last rebuild, this means that there's no need to rebuild ksycoca.
0581     bool checkDirectoriesTimestamps(const QMap<QString, qint64> &dirs) const
0582     {
0583         Q_ASSERT(!dirs.isEmpty());
0584         // qCDebug(SYCOCA) << "checking file timestamps";
0585         for (auto it = dirs.begin(); it != dirs.end(); ++it) {
0586             const QString dir = it.key();
0587             const qint64 lastStamp = it.value();
0588 
0589             auto visitor = [&](const QFileInfo &fi) {
0590                 const QDateTime mtime = fi.lastModified();
0591                 if (mtime.toMSecsSinceEpoch() > lastStamp) {
0592                     if (mtime > m_now) {
0593                         qCDebug(SYCOCA) << fi.filePath() << "has a modification time in the future" << mtime;
0594                     }
0595                     qCDebug(SYCOCA) << "dir timestamp changed:" << fi.filePath() << mtime << ">" << QDateTime::fromMSecsSinceEpoch(lastStamp);
0596                     // no need to continue search
0597                     return false;
0598                 }
0599 
0600                 return true;
0601             };
0602 
0603             if (!KSycocaUtilsPrivate::visitResourceDirectory(dir, visitor)) {
0604                 return false;
0605             }
0606         }
0607         return true;
0608     }
0609 
0610     bool checkFilesTimestamps(const QMap<QString, qint64> &files) const
0611     {
0612         for (auto it = files.begin(); it != files.end(); ++it) {
0613             const QString fileName = it.key();
0614             const qint64 lastStamp = it.value();
0615 
0616             QFileInfo fi(fileName);
0617             if (!fi.exists()) {
0618                 return false;
0619             }
0620             const QDateTime mtime = fi.lastModified();
0621             if (mtime.toMSecsSinceEpoch() > lastStamp) {
0622                 if (mtime > m_now) {
0623                     qCDebug(SYCOCA) << fi.filePath() << "has a modification time in the future" << mtime;
0624                 }
0625                 qCDebug(SYCOCA) << "file timestamp changed:" << fi.filePath() << mtime << ">" << QDateTime::fromMSecsSinceEpoch(lastStamp);
0626                 return false;
0627             }
0628         }
0629         return true;
0630     }
0631 
0632 private:
0633     QDateTime m_now;
0634 };
0635 
0636 void KSycocaPrivate::checkDirectories()
0637 {
0638     if (needsRebuild()) {
0639         buildSycoca();
0640     }
0641 }
0642 
0643 bool KSycocaPrivate::needsRebuild()
0644 {
0645     // 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
0646     if (!timeStamp && databaseStatus != BadVersion) {
0647         (void)readSycocaHeader();
0648     }
0649     // these days timeStamp is really a "bool headerFound", the value itself doesn't matter...
0650     // KF6: replace it with bool.
0651     const auto timestampChecker = TimestampChecker();
0652     bool ret = timeStamp != 0
0653         && (!timestampChecker.checkDirectoriesTimestamps(allResourceDirs) //
0654             || !timestampChecker.checkFilesTimestamps(extraFiles));
0655     if (ret) {
0656         return true;
0657     }
0658     auto files = KBuildSycoca::factoryExtraFiles();
0659     // ensure files are ordered so next comparison works
0660     files.sort();
0661     // to cover cases when extra files were added
0662     return extraFiles.keys() != files;
0663 }
0664 
0665 bool KSycocaPrivate::buildSycoca()
0666 {
0667     KBuildSycoca builder;
0668     if (!builder.recreate()) {
0669         return false; // error
0670     }
0671 
0672     closeDatabase(); // close the dummy one
0673 
0674     // Ok, the new database should be here now, open it.
0675     if (!openDatabase()) {
0676         qCDebug(SYCOCA) << "Still no database...";
0677         return false;
0678     }
0679     Q_EMIT q->databaseChanged();
0680     return true;
0681 }
0682 
0683 QString KSycoca::absoluteFilePath()
0684 {
0685     const QStringList paths = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
0686     QString suffix = QLatin1Char('_') + QLocale().bcp47Name();
0687 
0688     const QByteArray ksycoca_env = qgetenv("KDESYCOCA");
0689     if (ksycoca_env.isEmpty()) {
0690         const QByteArray pathHash = QCryptographicHash::hash(paths.join(QLatin1Char(':')).toUtf8(), QCryptographicHash::Sha1);
0691         suffix += QLatin1Char('_') + QString::fromLatin1(pathHash.toBase64());
0692         suffix.replace(QLatin1Char('/'), QLatin1Char('_'));
0693 #ifdef Q_OS_WIN
0694         suffix.replace(QLatin1Char(':'), QLatin1Char('_'));
0695 #endif
0696         const QString fileName = QLatin1String("ksycoca6") + suffix;
0697         return QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1Char('/') + fileName;
0698     } else {
0699         return QFile::decodeName(ksycoca_env);
0700     }
0701 }
0702 
0703 QStringList KSycoca::allResourceDirs()
0704 {
0705     if (!d->timeStamp) {
0706         (void)d->readSycocaHeader();
0707     }
0708     return d->allResourceDirs.keys();
0709 }
0710 
0711 void KSycoca::flagError()
0712 {
0713     qCWarning(SYCOCA) << "ERROR: KSycoca database corruption!";
0714     KSycoca *sycoca = self();
0715     if (sycoca->d->readError) {
0716         return;
0717     }
0718     sycoca->d->readError = true;
0719     if (qAppName() != QLatin1String(KBUILDSYCOCA_EXENAME) && !sycoca->isBuilding()) {
0720         // Rebuild the damned thing.
0721         KBuildSycoca builder;
0722         (void)builder.recreate();
0723     }
0724 }
0725 
0726 bool KSycoca::isBuilding()
0727 {
0728     return false;
0729 }
0730 
0731 void KSycoca::disableAutoRebuild()
0732 {
0733     ksycocaInstance->sycoca()->d->m_fileWatcher = nullptr;
0734 }
0735 
0736 QDataStream *&KSycoca::stream()
0737 {
0738     return d->stream();
0739 }
0740 
0741 void KSycoca::connectNotify(const QMetaMethod &signal)
0742 {
0743     if (signal.name() == "databaseChanged" && !d->m_haveListeners) {
0744         d->m_haveListeners = true;
0745         if (d->m_databasePath.isEmpty()) {
0746             d->m_databasePath = d->findDatabase();
0747         } else if (d->m_fileWatcher) {
0748             d->m_fileWatcher->addFile(d->m_databasePath);
0749         }
0750     }
0751 }
0752 
0753 void KSycoca::clearCaches()
0754 {
0755     if (ksycocaInstance.exists() && ksycocaInstance()->hasSycoca()) {
0756         ksycocaInstance()->sycoca()->d->closeDatabase();
0757     }
0758 }
0759 
0760 extern KSERVICE_EXPORT int ksycoca_ms_between_checks;
0761 KSERVICE_EXPORT int ksycoca_ms_between_checks = 1500;
0762 
0763 void KSycoca::ensureCacheValid()
0764 {
0765     if (qAppName() == QLatin1String(KBUILDSYCOCA_EXENAME)) {
0766         return;
0767     }
0768 
0769     if (d->databaseStatus != KSycocaPrivate::DatabaseOK) {
0770         if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
0771             return;
0772         }
0773     }
0774 
0775     if (d->m_lastCheck.isValid() && d->m_lastCheck.elapsed() < ksycoca_ms_between_checks) {
0776         return;
0777     }
0778     d->m_lastCheck.start();
0779 
0780     // Check if the file on disk was modified since we last checked it.
0781     QFileInfo info(d->m_databasePath);
0782     if (info.lastModified() == d->m_dbLastModified) {
0783         // Check if the watched directories were modified, then the cache needs a rebuild.
0784         d->checkDirectories();
0785         return;
0786     }
0787 
0788     // Close the database and forget all about what we knew.
0789     // The next call to any public method will recreate
0790     // everything that's needed.
0791     d->closeDatabase();
0792 }
0793 
0794 void KSycoca::setupTestMenu()
0795 {
0796     const QByteArray content = R"(<?xml version="1.0"?>
0797 <!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN" "http://www.freedesktop.org/standards/menu-spec/menu-1.0.dtd">
0798 <Menu>
0799   <Name>Applications</Name>
0800   <Directory>Applications.directory</Directory>
0801   <DefaultAppDirs/>
0802   <DefaultDirectoryDirs/>
0803   <MergeDir>applications-merged</MergeDir>
0804   <LegacyDir>/usr/share/applnk</LegacyDir>
0805   <DefaultLayout>
0806     <Merge type="menus"/>
0807     <Merge type="files"/>
0808     <Separator/>
0809     <Menuname>More</Menuname>
0810   </DefaultLayout>
0811 </Menu>
0812 )";
0813 
0814     const QString destDir = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/menus");
0815     QDir(destDir).mkpath(QStringLiteral("."));
0816     QFile output(destDir + QLatin1String("/applications.menu"));
0817     output.open(QIODevice::ReadWrite | QIODevice::Truncate);
0818     output.write(content);
0819 }
0820 
0821 #include "moc_ksycoca.cpp"