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