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

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