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