File indexing completed on 2024-10-06 12:24:00
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"