File indexing completed on 2024-09-08 03:40:17
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"