File indexing completed on 2024-04-28 15:29:54

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 1999 David Faure <faure@kde.org>
0004     SPDX-FileCopyrightText: 2002-2003 Waldo Bastian <bastian@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-only
0007 */
0008 
0009 #include "kbuildsycoca_p.h"
0010 #include "ksycoca_p.h"
0011 #include "ksycocaresourcelist_p.h"
0012 #include "ksycocautils_p.h"
0013 #include "sycocadebug.h"
0014 #include "vfolder_menu_p.h"
0015 
0016 #include "kbuildmimetypefactory_p.h"
0017 #include "kbuildservicefactory_p.h"
0018 #include "kbuildservicegroupfactory_p.h"
0019 #include "kbuildservicetypefactory_p.h"
0020 #include "kctimefactory_p.h"
0021 #include <QDataStream>
0022 #include <QDateTime>
0023 #include <QDebug>
0024 #include <QDir>
0025 #include <QDirIterator>
0026 #include <QEventLoop>
0027 #include <QFile>
0028 #include <QLocale>
0029 #include <QSaveFile>
0030 #include <QTimer>
0031 #include <config-ksycoca.h>
0032 #include <kservice.h>
0033 #include <kservicegroup.h>
0034 
0035 #include <kmemfile_p.h>
0036 
0037 #include <QLockFile>
0038 #include <QStandardPaths>
0039 #include <memory> // auto_ptr
0040 #include <qplatformdefs.h>
0041 #include <time.h>
0042 
0043 static const char *s_cSycocaPath = nullptr;
0044 
0045 KBuildSycocaInterface::~KBuildSycocaInterface()
0046 {
0047 }
0048 
0049 KBuildSycoca::KBuildSycoca()
0050     : KSycoca(true)
0051     , m_allEntries(nullptr)
0052     , m_ctimeFactory(nullptr)
0053     , m_ctimeDict(nullptr)
0054     , m_currentEntryDict(nullptr)
0055     , m_serviceGroupEntryDict(nullptr)
0056     , m_vfolder(nullptr)
0057     , m_newTimestamp(0)
0058     , m_menuTest(false)
0059     , m_changed(false)
0060 {
0061 }
0062 
0063 KBuildSycoca::~KBuildSycoca()
0064 {
0065     // Delete the factories while we exist, so that the virtual isBuilding() still works
0066     qDeleteAll(*factories());
0067     factories()->clear();
0068 }
0069 
0070 KSycocaEntry::Ptr KBuildSycoca::createEntry(KSycocaFactory *currentFactory, const QString &file)
0071 {
0072     quint32 timeStamp = m_ctimeFactory->dict()->ctime(file, m_resource);
0073     if (!timeStamp) {
0074         timeStamp = calcResourceHash(m_resourceSubdir, file);
0075         if (!timeStamp) { // file disappeared meanwhile
0076             return {};
0077         }
0078     }
0079     KSycocaEntry::Ptr entry;
0080     if (m_allEntries) {
0081         Q_ASSERT(m_ctimeDict);
0082         quint32 oldTimestamp = m_ctimeDict->ctime(file, m_resource);
0083         if (file.contains(QLatin1String("fake"))) {
0084             qCDebug(SYCOCA) << "m_ctimeDict->ctime(" << file << ") = " << oldTimestamp << "compared with" << timeStamp;
0085         }
0086 
0087         if (timeStamp && (timeStamp == oldTimestamp)) {
0088             // Re-use old entry
0089             if (currentFactory == d->m_serviceFactory) { // Strip .directory from service-group entries
0090                 entry = m_currentEntryDict->value(file.left(file.length() - 10));
0091             } else {
0092                 entry = m_currentEntryDict->value(file);
0093             }
0094             // remove from m_ctimeDict; if m_ctimeDict is not empty
0095             // after all files have been processed, it means
0096             // some files were removed since last time
0097             if (file.contains(QLatin1String("fake"))) {
0098                 qCDebug(SYCOCA) << "reusing (and removing) old entry for:" << file << "entry=" << entry;
0099             }
0100             m_ctimeDict->remove(file, m_resource);
0101         } else if (oldTimestamp) {
0102             m_changed = true;
0103             m_ctimeDict->remove(file, m_resource);
0104             qCDebug(SYCOCA) << "modified:" << file;
0105         } else {
0106             m_changed = true;
0107             qCDebug(SYCOCA) << "new:" << file;
0108         }
0109     }
0110     m_ctimeFactory->dict()->addCTime(file, m_resource, timeStamp);
0111     if (!entry) {
0112         // Create a new entry
0113         entry = currentFactory->createEntry(file);
0114     }
0115     if (entry && entry->isValid()) {
0116         return entry;
0117     }
0118     return KSycocaEntry::Ptr();
0119 }
0120 
0121 KService::Ptr KBuildSycoca::createService(const QString &path)
0122 {
0123     KSycocaEntry::Ptr entry = createEntry(d->m_serviceFactory, path);
0124     if (entry) {
0125         m_tempStorage.append(entry);
0126     }
0127     return KService::Ptr(static_cast<KService *>(entry.data()));
0128 }
0129 
0130 static QStringList locateDirInResource(const QString &resourceSubdir)
0131 {
0132     const QString dir = QStringLiteral(":/") + resourceSubdir; // e.g. :/kservicetypes5
0133     if (QDir(dir).exists()) {
0134         return {dir};
0135     }
0136     return {};
0137 }
0138 
0139 // returns false if the database is up to date, true if it needs to be saved
0140 bool KBuildSycoca::build()
0141 {
0142     using KBSEntryDictList = QList<KBSEntryDict *>;
0143     KBSEntryDictList entryDictList;
0144     KBSEntryDict *serviceEntryDict = nullptr;
0145 
0146     // Convert for each factory the entryList to a Dict.
0147     entryDictList.reserve(factories()->size());
0148     int i = 0;
0149     // For each factory
0150     const auto &factoryList = *factories();
0151     for (KSycocaFactory *factory : factoryList) {
0152         KBSEntryDict *entryDict = new KBSEntryDict;
0153         if (m_allEntries) { // incremental build
0154             for (const KSycocaEntry::Ptr &entry : std::as_const((*m_allEntries).at(i++))) {
0155                 // if (entry->entryPath().contains("fake"))
0156                 //    qCDebug(SYCOCA) << "inserting into entryDict:" << entry->entryPath() << entry;
0157                 entryDict->insert(entry->entryPath(), entry);
0158             }
0159         }
0160         if (factory == d->m_serviceFactory) {
0161             serviceEntryDict = entryDict;
0162         } else if (factory == m_buildServiceGroupFactory) {
0163             m_serviceGroupEntryDict = entryDict;
0164         }
0165         entryDictList.append(entryDict);
0166     }
0167 
0168     // Save the mtime of each dir, just before we list them
0169     // ## should we convert to UTC to avoid surprises when summer time kicks in?
0170     const auto lstDirs = factoryResourceDirs();
0171     for (const QString &dir : lstDirs) {
0172         qint64 stamp = 0;
0173         KSycocaUtilsPrivate::visitResourceDirectory(dir, [&stamp](const QFileInfo &info) {
0174             stamp = qMax(stamp, info.lastModified().toMSecsSinceEpoch());
0175             return true;
0176         });
0177         m_allResourceDirs.insert(dir, stamp);
0178     }
0179 
0180     const auto lstFiles = factoryExtraFiles();
0181     for (const QString &file : lstFiles) {
0182         m_extraFiles.insert(file, QFileInfo(file).lastModified().toMSecsSinceEpoch());
0183     }
0184 
0185     QMap<QString, QByteArray> allResourcesSubDirs; // dirs, kstandarddirs-resource-name
0186     // For each factory
0187     for (KSycocaFactory *factory : factoryList) {
0188         // For each resource the factory deals with
0189         const KSycocaResourceList resourceList = factory->resourceList();
0190         for (const KSycocaResource &res : resourceList) {
0191             // With this we would get dirs, but not a unique list of relative files (for global+local merging to work)
0192             // const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, res.subdir, QStandardPaths::LocateDirectory);
0193             // allResourcesSubDirs[res.resource] += dirs;
0194             allResourcesSubDirs.insert(res.subdir, res.resource);
0195         }
0196     }
0197 
0198     m_ctimeFactory = new KCTimeFactory(this); // This is a build factory too, don't delete!!
0199     for (auto it1 = allResourcesSubDirs.cbegin(); it1 != allResourcesSubDirs.cend(); ++it1) {
0200         m_changed = false;
0201         m_resourceSubdir = it1.key();
0202         m_resource = it1.value();
0203 
0204         QSet<QString> relFiles;
0205         const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, m_resourceSubdir, QStandardPaths::LocateDirectory)
0206             + locateDirInResource(m_resourceSubdir);
0207         qCDebug(SYCOCA) << "Looking for subdir" << m_resourceSubdir << "=>" << dirs;
0208         for (const QString &dir : dirs) {
0209             QDirIterator it(dir, QDirIterator::Subdirectories);
0210             while (it.hasNext()) {
0211                 const QString filePath = it.next();
0212                 Q_ASSERT(filePath.startsWith(dir)); // due to the line below...
0213                 const QString relPath = filePath.mid(dir.length() + 1);
0214                 relFiles.insert(relPath);
0215             }
0216         }
0217         // Now find all factories that use this resource....
0218         // For each factory -- and its corresponding entryDict (iterate over both lists in parallel)
0219         for (int f = 0; f < factoryList.count(); ++f) {
0220             KSycocaFactory *currentFactory = factoryList.at(f);
0221             // m_ctimeInfo gets created after the initial loop, so it has no entryDict.
0222             m_currentEntryDict = f == entryDictList.size() ? nullptr : entryDictList.at(f);
0223             // For each resource the factory deals with
0224             const KSycocaResourceList &resourceList = currentFactory->resourceList();
0225             for (const KSycocaResource &res : resourceList) {
0226                 if (res.resource != m_resource) {
0227                     continue;
0228                 }
0229 
0230                 // For each file in the resource
0231                 for (const QString &entryPath : std::as_const(relFiles)) {
0232                     // Check if file matches filter
0233                     if (entryPath.endsWith(res.extension)) {
0234                         KSycocaEntry::Ptr entry = createEntry(currentFactory, entryPath);
0235                         if (entry) {
0236                             currentFactory->addEntry(entry);
0237                         }
0238                     }
0239                 }
0240             }
0241         }
0242     }
0243 
0244     bool result = true;
0245     const bool createVFolder = true; // we need to always run the VFolderMenu code
0246     if (createVFolder || m_menuTest) {
0247         m_resource = "apps";
0248         m_resourceSubdir = QStringLiteral("applications");
0249         m_currentEntryDict = serviceEntryDict;
0250         m_changed = false;
0251 
0252         m_vfolder = new VFolderMenu(d->m_serviceFactory, this);
0253         if (!m_trackId.isEmpty()) {
0254             m_vfolder->setTrackId(m_trackId);
0255         }
0256 
0257         VFolderMenu::SubMenu *kdeMenu = m_vfolder->parseMenu(QStringLiteral(APPLICATIONS_MENU_NAME));
0258 
0259         KServiceGroup::Ptr entry = m_buildServiceGroupFactory->addNew(QStringLiteral("/"), kdeMenu->directoryFile, KServiceGroup::Ptr(), false);
0260         entry->setLayoutInfo(kdeMenu->layoutList);
0261         createMenu(QString(), QString(), kdeMenu);
0262 
0263         // Storing the mtime *after* looking at these dirs is a tiny race condition,
0264         // but I'm not sure how to get the vfolder dirs upfront...
0265         const auto allDirectories = m_vfolder->allDirectories();
0266         for (QString dir : allDirectories) {
0267             if (dir.endsWith(QLatin1Char('/'))) {
0268                 dir.chop(1); // remove trailing slash, to avoid having ~/.local/share/applications twice
0269             }
0270             if (!m_allResourceDirs.contains(dir)) {
0271                 qint64 stamp = 0;
0272                 KSycocaUtilsPrivate::visitResourceDirectory(dir, [&stamp](const QFileInfo &info) {
0273                     stamp = qMax(stamp, info.lastModified().toMSecsSinceEpoch());
0274                     return true;
0275                 });
0276                 m_allResourceDirs.insert(dir, stamp);
0277             }
0278         }
0279 
0280         if (m_menuTest) {
0281             result = false;
0282         }
0283     }
0284 
0285     if (m_ctimeDict && !m_ctimeDict->isEmpty()) {
0286         qCDebug(SYCOCA) << "Still in time dict:";
0287         m_ctimeDict->dump();
0288     }
0289 
0290     qDeleteAll(entryDictList);
0291     return result;
0292 }
0293 
0294 void KBuildSycoca::createMenu(const QString &caption_, const QString &name_, VFolderMenu::SubMenu *menu)
0295 {
0296     QString caption = caption_;
0297     QString name = name_;
0298     for (VFolderMenu::SubMenu *subMenu : std::as_const(menu->subMenus)) {
0299         QString subName = name + subMenu->name + QLatin1Char('/');
0300 
0301         QString directoryFile = subMenu->directoryFile;
0302         if (directoryFile.isEmpty()) {
0303             directoryFile = subName + QLatin1String(".directory");
0304         }
0305         quint32 timeStamp = m_ctimeFactory->dict()->ctime(directoryFile, m_resource);
0306         if (!timeStamp) {
0307             timeStamp = calcResourceHash(m_resourceSubdir, directoryFile);
0308         }
0309 
0310         KServiceGroup::Ptr entry;
0311         if (m_allEntries) {
0312             const quint32 oldTimestamp = m_ctimeDict->ctime(directoryFile, m_resource);
0313 
0314             if (timeStamp && (timeStamp == oldTimestamp)) {
0315                 KSycocaEntry::Ptr group = m_serviceGroupEntryDict->value(subName);
0316                 if (group) {
0317                     entry = KServiceGroup::Ptr(static_cast<KServiceGroup *>(group.data()));
0318                     if (entry->directoryEntryPath() != directoryFile) {
0319                         entry = nullptr; // Can't reuse this one!
0320                     }
0321                 }
0322             }
0323         }
0324         if (timeStamp) { // bug? (see calcResourceHash). There might not be a .directory file...
0325             m_ctimeFactory->dict()->addCTime(directoryFile, m_resource, timeStamp);
0326         }
0327 
0328         entry = m_buildServiceGroupFactory->addNew(subName, subMenu->directoryFile, entry, subMenu->isDeleted);
0329         entry->setLayoutInfo(subMenu->layoutList);
0330         if (!(m_menuTest && entry->noDisplay())) {
0331             createMenu(caption + entry->caption() + QLatin1Char('/'), subName, subMenu);
0332         }
0333     }
0334     if (caption.isEmpty()) {
0335         caption += QLatin1Char('/');
0336     }
0337     if (name.isEmpty()) {
0338         name += QLatin1Char('/');
0339     }
0340     for (const KService::Ptr &p : std::as_const(menu->items)) {
0341         if (m_menuTest) {
0342             if (!menu->isDeleted && !p->noDisplay()) {
0343                 printf("%s\t%s\t%s\n",
0344                        qPrintable(caption),
0345                        qPrintable(p->menuId()),
0346                        qPrintable(QStandardPaths::locate(QStandardPaths::ApplicationsLocation, p->entryPath())));
0347             }
0348         } else {
0349             m_buildServiceGroupFactory->addNewEntryTo(name, p);
0350         }
0351     }
0352 }
0353 
0354 bool KBuildSycoca::recreate(bool incremental)
0355 {
0356     QFileInfo fi(KSycoca::absoluteFilePath());
0357     if (!QDir().mkpath(fi.absolutePath())) {
0358         qCWarning(SYCOCA) << "Couldn't create" << fi.absolutePath();
0359         return false;
0360     }
0361     QString path(fi.absoluteFilePath());
0362 
0363     QLockFile lockFile(path + QLatin1String(".lock"));
0364     if (!lockFile.tryLock()) {
0365         qCDebug(SYCOCA) << "Waiting for already running" << KBUILDSYCOCA_EXENAME << "to finish.";
0366         if (!lockFile.lock()) {
0367             qCWarning(SYCOCA) << "Couldn't lock" << path + QLatin1String(".lock");
0368             return false;
0369         }
0370         if (!needsRebuild()) {
0371             // qCDebug(SYCOCA) << "Up-to-date, skipping.";
0372             return true;
0373         }
0374     }
0375 
0376     QByteArray qSycocaPath = QFile::encodeName(path);
0377     s_cSycocaPath = qSycocaPath.data();
0378 
0379     m_allEntries = nullptr;
0380     m_ctimeDict = nullptr;
0381     if (incremental && checkGlobalHeader()) {
0382         qCDebug(SYCOCA) << "Reusing existing ksycoca";
0383         KSycoca *oldSycoca = KSycoca::self();
0384         m_allEntries = new KSycocaEntryListList;
0385         m_ctimeDict = new KCTimeDict;
0386 
0387         // Must be in same order as in KBuildSycoca::recreate()!
0388         m_allEntries->append(KSycocaPrivate::self()->serviceTypeFactory()->allEntries());
0389         m_allEntries->append(KSycocaPrivate::self()->mimeTypeFactory()->allEntries());
0390         m_allEntries->append(KSycocaPrivate::self()->serviceGroupFactory()->allEntries());
0391         m_allEntries->append(KSycocaPrivate::self()->serviceFactory()->allEntries());
0392 
0393         KCTimeFactory *ctimeInfo = new KCTimeFactory(oldSycoca);
0394         *m_ctimeDict = ctimeInfo->loadDict();
0395     }
0396     s_cSycocaPath = nullptr;
0397 
0398     QSaveFile database(path);
0399     bool openedOK = database.open(QIODevice::WriteOnly);
0400 
0401     if (!openedOK && database.error() == QFile::WriteError && QFile::exists(path)) {
0402         QFile::remove(path);
0403         openedOK = database.open(QIODevice::WriteOnly);
0404     }
0405     if (!openedOK) {
0406         qCWarning(SYCOCA) << "ERROR creating database" << path << ":" << database.errorString();
0407         return false;
0408     }
0409 
0410     QDataStream *str = new QDataStream(&database);
0411     str->setVersion(QDataStream::Qt_5_3);
0412 
0413     m_newTimestamp = QDateTime::currentMSecsSinceEpoch();
0414     qCDebug(SYCOCA).nospace() << "Recreating ksycoca file (" << path << ", version " << KSycoca::version() << ")";
0415 
0416     // It is very important to build the servicetype one first
0417     KBuildServiceTypeFactory *buildServiceTypeFactory = new KBuildServiceTypeFactory(this);
0418     d->m_serviceTypeFactory = buildServiceTypeFactory;
0419     KBuildMimeTypeFactory *buildMimeTypeFactory = new KBuildMimeTypeFactory(this);
0420     d->m_mimeTypeFactory = buildMimeTypeFactory;
0421     m_buildServiceGroupFactory = new KBuildServiceGroupFactory(this);
0422     d->m_serviceGroupFactory = m_buildServiceGroupFactory;
0423     d->m_serviceFactory = new KBuildServiceFactory(buildServiceTypeFactory, buildMimeTypeFactory, m_buildServiceGroupFactory);
0424 
0425     if (build()) { // Parse dirs
0426         save(str); // Save database
0427         if (str->status() != QDataStream::Ok) { // Probably unnecessary now in Qt5, since QSaveFile detects write errors
0428             database.cancelWriting(); // Error
0429         }
0430         delete str;
0431         str = nullptr;
0432 
0433         // if we are currently via sudo, preserve the original owner
0434         // as $HOME may also be that of another user rather than /root
0435 #ifdef Q_OS_UNIX
0436         if (qEnvironmentVariableIsSet("SUDO_UID")) {
0437             const int uid = qEnvironmentVariableIntValue("SUDO_UID");
0438             const int gid = qEnvironmentVariableIntValue("SUDO_GID");
0439             if (uid && gid) {
0440                 fchown(database.handle(), uid, gid);
0441             }
0442         }
0443 #endif
0444 
0445         if (!database.commit()) {
0446             qCWarning(SYCOCA) << "ERROR writing database" << database.fileName() << database.errorString();
0447             return false;
0448         }
0449 
0450         // Compatibility code for KF < 5.15: provide a ksycoca5 symlink after the filename change, for old apps to keep working during the upgrade
0451         const QString oldSycoca = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/ksycoca5");
0452         if (QFile::exists(oldSycoca)) {
0453             QFile::remove(oldSycoca);
0454 #ifdef Q_OS_UNIX
0455             if (::link(QFile::encodeName(path).constData(), QFile::encodeName(oldSycoca).constData()) != 0) {
0456                 QFile::copy(path, oldSycoca);
0457             }
0458 #else
0459             QFile::copy(path, oldSycoca);
0460 #endif
0461         }
0462     } else {
0463         delete str;
0464         str = nullptr;
0465         database.cancelWriting();
0466         if (m_menuTest) {
0467             return true;
0468         }
0469         qCDebug(SYCOCA) << "Database is up to date";
0470     }
0471 
0472     if (d->m_sycocaStrategy == KSycocaPrivate::StrategyMemFile) {
0473         KMemFile::fileContentsChanged(path);
0474     }
0475 
0476     delete m_ctimeDict;
0477     delete m_allEntries;
0478     delete m_vfolder;
0479 
0480     return true;
0481 }
0482 
0483 void KBuildSycoca::save(QDataStream *str)
0484 {
0485     // Write header (#pass 1)
0486     str->device()->seek(0);
0487 
0488     (*str) << qint32(KSycoca::version());
0489     // KSycocaFactory * servicetypeFactory = 0;
0490     // KBuildMimeTypeFactory * mimeTypeFactory = 0;
0491     KBuildServiceFactory *serviceFactory = nullptr;
0492     auto lst = *factories();
0493     for (KSycocaFactory *factory : std::as_const(lst)) {
0494         qint32 aId;
0495         qint32 aOffset;
0496         aId = factory->factoryId();
0497         // if ( aId == KST_KServiceTypeFactory )
0498         //   servicetypeFactory = factory;
0499         // else if ( aId == KST_KMimeTypeFactory )
0500         //   mimeTypeFactory = static_cast<KBuildMimeTypeFactory *>( factory );
0501         if (aId == KST_KServiceFactory) {
0502             serviceFactory = static_cast<KBuildServiceFactory *>(factory);
0503         }
0504         aOffset = factory->offset(); // not set yet, so always 0
0505         (*str) << aId;
0506         (*str) << aOffset;
0507     }
0508     (*str) << qint32(0); // No more factories.
0509     // Write XDG_DATA_DIRS
0510     (*str) << QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).join(QString(QLatin1Char(':')));
0511     (*str) << m_newTimestamp;
0512     (*str) << QLocale().bcp47Name();
0513     // This makes it possible to trigger a ksycoca update for all users (KIOSK feature)
0514     (*str) << calcResourceHash(QStringLiteral("kservices5"), QStringLiteral("update_ksycoca"));
0515     (*str) << m_allResourceDirs.keys();
0516     for (auto it = m_allResourceDirs.constBegin(); it != m_allResourceDirs.constEnd(); ++it) {
0517         (*str) << it.value();
0518     }
0519     (*str) << m_extraFiles.keys();
0520     for (auto it = m_extraFiles.constBegin(); it != m_extraFiles.constEnd(); ++it) {
0521         (*str) << it.value();
0522     }
0523 
0524     // Calculate per-servicetype/MIME type data
0525     if (serviceFactory) {
0526         serviceFactory->postProcessServices();
0527     }
0528 
0529     // Here so that it's the last debug message
0530     qCDebug(SYCOCA) << "Saving";
0531 
0532     // Write factory data....
0533     lst = *factories();
0534     for (KSycocaFactory *factory : std::as_const(lst)) {
0535         factory->save(*str);
0536         if (str->status() != QDataStream::Ok) { // ######## TODO: does this detect write errors, e.g. disk full?
0537             return; // error
0538         }
0539     }
0540 
0541     qint64 endOfData = str->device()->pos();
0542 
0543     // Write header (#pass 2)
0544     str->device()->seek(0);
0545 
0546     (*str) << qint32(KSycoca::version());
0547     lst = *factories();
0548     for (KSycocaFactory *factory : std::as_const(lst)) {
0549         qint32 aId;
0550         qint32 aOffset;
0551         aId = factory->factoryId();
0552         aOffset = factory->offset();
0553         (*str) << aId;
0554         (*str) << aOffset;
0555     }
0556     (*str) << qint32(0); // No more factories.
0557 
0558     // Jump to end of database
0559     str->device()->seek(endOfData);
0560 }
0561 
0562 QStringList KBuildSycoca::factoryResourceDirs()
0563 {
0564     static QStringList *dirs = nullptr;
0565     if (dirs != nullptr) {
0566         return *dirs;
0567     }
0568     dirs = new QStringList;
0569     // these are all resource dirs cached by ksycoca
0570     *dirs += KServiceTypeFactory::resourceDirs();
0571     *dirs += KMimeTypeFactory::resourceDirs();
0572     *dirs += KServiceFactory::resourceDirs();
0573 
0574     return *dirs;
0575 }
0576 
0577 QStringList KBuildSycoca::factoryExtraFiles()
0578 {
0579     QStringList files;
0580     // these are the extra files cached by ksycoca
0581     // and whose timestamps are checked
0582     files += KMimeAssociations::mimeAppsFiles();
0583 
0584     return files;
0585 }
0586 
0587 QStringList KBuildSycoca::existingResourceDirs()
0588 {
0589     static QStringList *dirs = nullptr;
0590     if (dirs != nullptr) {
0591         return *dirs;
0592     }
0593     dirs = new QStringList(factoryResourceDirs());
0594 
0595     auto checkDir = [](const QString &str) {
0596         QFileInfo info(str);
0597         return !info.exists() || !info.isReadable();
0598     };
0599     dirs->erase(std::remove_if(dirs->begin(), dirs->end(), checkDir), dirs->end());
0600 
0601     return *dirs;
0602 }
0603 
0604 static quint32 updateHash(const QString &file, quint32 hash)
0605 {
0606     QFileInfo fi(file);
0607     if (fi.isReadable() && fi.isFile()) {
0608         // This was using buff.st_ctime (in Waldo's initial commit to kstandarddirs.cpp in 2001), but that looks wrong?
0609         // Surely we want to catch manual editing, while a chmod doesn't matter much?
0610         qint64 timestamp = fi.lastModified().toSecsSinceEpoch();
0611         // On some systems (i.e. Fedora Kinoite), all files in /usr have a last
0612         // modified timestamp of 0 (UNIX Epoch). In this case, always assume
0613         // the file as been changed.
0614         if (timestamp == 0) {
0615             static qint64 now = QDateTime::currentDateTimeUtc().toSecsSinceEpoch();
0616             timestamp = now;
0617         }
0618         hash += timestamp;
0619     }
0620     return hash;
0621 }
0622 
0623 quint32 KBuildSycoca::calcResourceHash(const QString &resourceSubDir, const QString &filename)
0624 {
0625     quint32 hash = 0;
0626     if (!QDir::isRelativePath(filename)) {
0627         return updateHash(filename, hash);
0628     }
0629     const QString filePath = resourceSubDir + QLatin1Char('/') + filename;
0630     const QString qrcFilePath = QStringLiteral(":/") + filePath;
0631     const QStringList files =
0632         QFileInfo::exists(qrcFilePath) ? QStringList{qrcFilePath} : QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, filePath);
0633     for (const QString &file : files) {
0634         hash = updateHash(file, hash);
0635     }
0636     if (hash == 0 && !filename.endsWith(QLatin1String("update_ksycoca"))
0637         && !filename.endsWith(QLatin1String(".directory")) // bug? needs investigation from someone who understands the VFolder spec
0638     ) {
0639         if (files.isEmpty()) {
0640             // This can happen if the file was deleted between directory listing and the above locateAll
0641             qCDebug(SYCOCA) << "File not found anymore:" << filename << " -- probably deleted meanwhile";
0642         } else {
0643             // This can happen if the file was deleted between locateAll and QFileInfo
0644             qCDebug(SYCOCA) << "File(s) found but not readable (or disappeared meanwhile)" << files;
0645         }
0646     }
0647     return hash;
0648 }
0649 
0650 bool KBuildSycoca::checkGlobalHeader()
0651 {
0652     // Since it's part of the filename, we are 99% sure that the locale and prefixes will match.
0653     const QString current_language = QLocale().bcp47Name();
0654     const quint32 current_update_sig = KBuildSycoca::calcResourceHash(QStringLiteral("kservices5"), QStringLiteral("update_ksycoca"));
0655     const QString current_prefixes = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation).join(QString(QLatin1Char(':')));
0656 
0657     const KSycocaHeader header = KSycocaPrivate::self()->readSycocaHeader();
0658     Q_ASSERT(!header.prefixes.split(QLatin1Char(':')).contains(QDir::homePath()));
0659 
0660     return (current_update_sig == header.updateSignature) //
0661         && (current_language == header.language) //
0662         && (current_prefixes == header.prefixes) //
0663         && (header.timeStamp != 0);
0664 }
0665 
0666 const char *KBuildSycoca::sycocaPath()
0667 {
0668     return s_cSycocaPath;
0669 }
0670 
0671 #include "moc_kbuildsycoca_p.cpp"