1 /*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 1999-2000 Waldo Bastian <bastian@kde.org>
4 SPDX-FileCopyrightText: 2005-2009 David Faure <faure@kde.org>
5 SPDX-FileCopyrightText: 2008 Hamish Rodda <rodda@kde.org>
6 SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
7
8 SPDX-License-Identifier: LGPL-2.0-only
9 */
10
11 #include "ksycoca.h"
12 #include "ksycoca_p.h"
13 #include "ksycocafactory_p.h"
14 #include "ksycocatype.h"
15 #include "ksycocautils_p.h"
16 #include "sycocadebug.h"
17 #include <KConfigGroup>
18 #include <KSharedConfig>
19
20 #include <QCoreApplication>
21 #include <QDataStream>
22 #include <QFile>
23 #include <QFileInfo>
24 #include <QMetaMethod>
25 #include <QStandardPaths>
26 #include <QThread>
27 #include <QThreadStorage>
28
29 #include <QCryptographicHash>
30 #include <fcntl.h>
31 #include <kmimetypefactory_p.h>
32 #include <kservicefactory_p.h>
33 #include <kservicegroupfactory_p.h>
34 #include <kservicetypefactory_p.h>
35 #include <stdlib.h>
36
37 #include "kbuildsycoca_p.h"
38 #include "ksycocadevices_p.h"
39
40 #ifdef Q_OS_UNIX
41 #include <sys/time.h>
42 #include <utime.h>
43 #endif
44
45 /**
46 * Sycoca file version number.
47 * If the existing file is outdated, it will not get read
48 * but instead we'll regenerate a new one.
49 * However running apps should still be able to read it, so
50 * only add to the data, never remove/modify.
51 */
52 #define KSYCOCA_VERSION 304
53
54 #if HAVE_MADVISE || HAVE_MMAP
55 #include <sys/mman.h> // This #include was checked when looking for posix_madvise
56 #endif
57
58 #ifndef MAP_FAILED
59 #define MAP_FAILED ((void *)-1)
60 #endif
61
operator >>(QDataStream & in,KSycocaHeader & h)62 QDataStream &operator>>(QDataStream &in, KSycocaHeader &h)
63 {
64 in >> h.prefixes >> h.timeStamp >> h.language >> h.updateSignature;
65 return in;
66 }
67
68 // The following limitations are in place:
69 // Maximum length of a single string: 8192 bytes
70 // Maximum length of a string list: 1024 strings
71 // Maximum number of entries: 8192
72 //
73 // The purpose of these limitations is to limit the impact
74 // of database corruption.
75
Q_DECLARE_OPERATORS_FOR_FLAGS(KSycocaPrivate::BehaviorsIfNotFound)76 Q_DECLARE_OPERATORS_FOR_FLAGS(KSycocaPrivate::BehaviorsIfNotFound)
77
78 KSycocaPrivate::KSycocaPrivate(KSycoca *qq)
79 : databaseStatus(DatabaseNotOpen)
80 , readError(false)
81 , timeStamp(0)
82 , m_databasePath()
83 , updateSig(0)
84 , m_fileWatcher(new KDirWatch)
85 , m_haveListeners(false)
86 , q(qq)
87 , sycoca_size(0)
88 , sycoca_mmap(nullptr)
89 , m_mmapFile(nullptr)
90 , m_device(nullptr)
91 , m_mimeTypeFactory(nullptr)
92 , m_serviceTypeFactory(nullptr)
93 , m_serviceFactory(nullptr)
94 , m_serviceGroupFactory(nullptr)
95 {
96 #ifdef Q_OS_WIN
97 /*
98 on windows we use KMemFile (QSharedMemory) to avoid problems
99 with mmap (can't delete a mmap'd file)
100 */
101 m_sycocaStrategy = StrategyMemFile;
102 #else
103 m_sycocaStrategy = StrategyMmap;
104 #endif
105 KConfigGroup config(KSharedConfig::openConfig(), "KSycoca");
106 setStrategyFromString(config.readEntry("strategy"));
107 }
108
setStrategyFromString(const QString & strategy)109 void KSycocaPrivate::setStrategyFromString(const QString &strategy)
110 {
111 if (strategy == QLatin1String("mmap")) {
112 m_sycocaStrategy = StrategyMmap;
113 } else if (strategy == QLatin1String("file")) {
114 m_sycocaStrategy = StrategyFile;
115 } else if (strategy == QLatin1String("sharedmem")) {
116 m_sycocaStrategy = StrategyMemFile;
117 } else if (!strategy.isEmpty()) {
118 qCWarning(SYCOCA) << "Unknown sycoca strategy:" << strategy;
119 }
120 }
121
tryMmap()122 bool KSycocaPrivate::tryMmap()
123 {
124 #if HAVE_MMAP
125 Q_ASSERT(!m_databasePath.isEmpty());
126 m_mmapFile = new QFile(m_databasePath);
127 const bool canRead = m_mmapFile->open(QIODevice::ReadOnly);
128 Q_ASSERT(canRead);
129 if (!canRead) {
130 return false;
131 }
132 fcntl(m_mmapFile->handle(), F_SETFD, FD_CLOEXEC);
133 sycoca_size = m_mmapFile->size();
134 void *mmapRet = mmap(nullptr, sycoca_size, PROT_READ, MAP_SHARED, m_mmapFile->handle(), 0);
135 /* POSIX mandates only MAP_FAILED, but we are paranoid so check for
136 null pointer too. */
137 if (mmapRet == MAP_FAILED || mmapRet == nullptr) {
138 qCDebug(SYCOCA).nospace() << "mmap failed. (length = " << sycoca_size << ")";
139 sycoca_mmap = nullptr;
140 return false;
141 } else {
142 sycoca_mmap = static_cast<const char *>(mmapRet);
143 #if HAVE_MADVISE
144 (void)posix_madvise(mmapRet, sycoca_size, POSIX_MADV_WILLNEED);
145 #endif // HAVE_MADVISE
146 return true;
147 }
148 #else
149 return false;
150 #endif // HAVE_MMAP
151 }
152
version()153 int KSycoca::version()
154 {
155 return KSYCOCA_VERSION;
156 }
157
158 class KSycocaSingleton
159 {
160 public:
KSycocaSingleton()161 KSycocaSingleton()
162 {
163 }
~KSycocaSingleton()164 ~KSycocaSingleton()
165 {
166 }
167
hasSycoca() const168 bool hasSycoca() const
169 {
170 return m_threadSycocas.hasLocalData();
171 }
sycoca()172 KSycoca *sycoca()
173 {
174 if (!m_threadSycocas.hasLocalData()) {
175 m_threadSycocas.setLocalData(new KSycoca);
176 }
177 return m_threadSycocas.localData();
178 }
setSycoca(KSycoca * s)179 void setSycoca(KSycoca *s)
180 {
181 m_threadSycocas.setLocalData(s);
182 }
183
184 private:
185 QThreadStorage<KSycoca *> m_threadSycocas;
186 };
187
Q_GLOBAL_STATIC(KSycocaSingleton,ksycocaInstance)188 Q_GLOBAL_STATIC(KSycocaSingleton, ksycocaInstance)
189
190 QString KSycocaPrivate::findDatabase()
191 {
192 Q_ASSERT(databaseStatus == DatabaseNotOpen);
193
194 const QString path = KSycoca::absoluteFilePath();
195 const QFileInfo info(path);
196 if (info.isReadable()) {
197 if (m_haveListeners && m_fileWatcher) {
198 m_fileWatcher->addFile(path);
199 }
200 return path;
201 }
202 // Let's be notified when it gets created - by another process or by ourselves
203 if (m_fileWatcher) {
204 m_fileWatcher->addFile(path);
205 }
206 return QString();
207 }
208
209 // Read-only constructor
210 // One instance per thread
KSycoca()211 KSycoca::KSycoca()
212 : d(new KSycocaPrivate(this))
213 {
214 if (d->m_fileWatcher) {
215 // We always delete and recreate the DB, so KDirWatch normally emits created
216 connect(d->m_fileWatcher.get(), &KDirWatch::created, this, [this]() {
217 d->slotDatabaseChanged();
218 });
219 // In some cases, KDirWatch only thinks the file was modified though
220 connect(d->m_fileWatcher.get(), &KDirWatch::dirty, this, [this]() {
221 d->slotDatabaseChanged();
222 });
223 }
224 }
225
openDatabase()226 bool KSycocaPrivate::openDatabase()
227 {
228 Q_ASSERT(databaseStatus == DatabaseNotOpen);
229
230 delete m_device;
231 m_device = nullptr;
232
233 if (m_databasePath.isEmpty()) {
234 m_databasePath = findDatabase();
235 }
236
237 bool result = true;
238 if (!m_databasePath.isEmpty()) {
239 static bool firstTime = true;
240 if (firstTime) {
241 firstTime = false;
242 if (QFileInfo::exists(QStringLiteral("/.flatpak-info"))) {
243 // We're running inside flatpak, which sets all times to 1970
244 // So the first very time, don't use an existing database, recreate it
245 qCDebug(SYCOCA) << "flatpak detected, ignoring" << m_databasePath;
246 return false;
247 }
248 }
249
250 qCDebug(SYCOCA) << "Opening ksycoca from" << m_databasePath;
251 m_dbLastModified = QFileInfo(m_databasePath).lastModified();
252 result = checkVersion();
253 } else { // No database file
254 // qCDebug(SYCOCA) << "Could not open ksycoca";
255 result = false;
256 }
257 return result;
258 }
259
device()260 KSycocaAbstractDevice *KSycocaPrivate::device()
261 {
262 if (m_device) {
263 return m_device;
264 }
265
266 KSycocaAbstractDevice *device = m_device;
267 Q_ASSERT(!m_databasePath.isEmpty());
268 #if HAVE_MMAP
269 if (m_sycocaStrategy == StrategyMmap && tryMmap()) {
270 device = new KSycocaMmapDevice(sycoca_mmap, sycoca_size);
271 if (!device->device()->open(QIODevice::ReadOnly)) {
272 delete device;
273 device = nullptr;
274 }
275 }
276 #endif
277 #ifndef QT_NO_SHAREDMEMORY
278 if (!device && m_sycocaStrategy == StrategyMemFile) {
279 device = new KSycocaMemFileDevice(m_databasePath);
280 if (!device->device()->open(QIODevice::ReadOnly)) {
281 delete device;
282 device = nullptr;
283 }
284 }
285 #endif
286 if (!device) {
287 device = new KSycocaFileDevice(m_databasePath);
288 if (!device->device()->open(QIODevice::ReadOnly)) {
289 qCWarning(SYCOCA) << "Couldn't open" << m_databasePath << "even though it is readable? Impossible.";
290 // delete device; device = 0; // this would crash in the return statement...
291 }
292 }
293 if (device) {
294 m_device = device;
295 }
296 return m_device;
297 }
298
stream()299 QDataStream *&KSycocaPrivate::stream()
300 {
301 if (!m_device) {
302 if (databaseStatus == DatabaseNotOpen) {
303 checkDatabase(KSycocaPrivate::IfNotFoundRecreate);
304 }
305
306 device(); // create m_device
307 }
308
309 return m_device->stream();
310 }
311
slotDatabaseChanged()312 void KSycocaPrivate::slotDatabaseChanged()
313 {
314 // We don't have information anymore on what resources changed, so emit them all
315 changeList = QStringList() << QStringLiteral("services") << QStringLiteral("servicetypes") << QStringLiteral("xdgdata-mime") << QStringLiteral("apps");
316
317 qCDebug(SYCOCA) << QThread::currentThread() << "got a notifyDatabaseChanged signal";
318 // KDirWatch tells us the database file changed
319 // We would have found out in the next call to ensureCacheValid(), but for
320 // now keep the call to closeDatabase, to help refcounting to 0 the old mmapped file earlier.
321 closeDatabase();
322 // Start monitoring the new file right away
323 m_databasePath = findDatabase();
324
325 // Now notify applications
326 Q_EMIT q->databaseChanged();
327
328 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 80)
329 Q_EMIT q->databaseChanged(changeList);
330 #endif
331 }
332
mimeTypeFactory()333 KMimeTypeFactory *KSycocaPrivate::mimeTypeFactory()
334 {
335 if (!m_mimeTypeFactory) {
336 m_mimeTypeFactory = new KMimeTypeFactory(q);
337 }
338 return m_mimeTypeFactory;
339 }
340
serviceTypeFactory()341 KServiceTypeFactory *KSycocaPrivate::serviceTypeFactory()
342 {
343 if (!m_serviceTypeFactory) {
344 m_serviceTypeFactory = new KServiceTypeFactory(q);
345 }
346 return m_serviceTypeFactory;
347 }
348
serviceFactory()349 KServiceFactory *KSycocaPrivate::serviceFactory()
350 {
351 if (!m_serviceFactory) {
352 m_serviceFactory = new KServiceFactory(q);
353 }
354 return m_serviceFactory;
355 }
356
serviceGroupFactory()357 KServiceGroupFactory *KSycocaPrivate::serviceGroupFactory()
358 {
359 if (!m_serviceGroupFactory) {
360 m_serviceGroupFactory = new KServiceGroupFactory(q);
361 }
362 return m_serviceGroupFactory;
363 }
364
365 // Add local paths to the list of dirs we got from the global database
addLocalResourceDir(const QString & path)366 void KSycocaPrivate::addLocalResourceDir(const QString &path)
367 {
368 // If any local path is more recent than the time the global sycoca was created, build a local sycoca.
369 allResourceDirs.insert(path, timeStamp);
370 }
371
372 // Read-write constructor - only for KBuildSycoca
KSycoca(bool)373 KSycoca::KSycoca(bool /* dummy */)
374 : d(new KSycocaPrivate(this))
375 {
376 }
377
self()378 KSycoca *KSycoca::self()
379 {
380 KSycoca *s = ksycocaInstance()->sycoca();
381 Q_ASSERT(s);
382 return s;
383 }
384
~KSycoca()385 KSycoca::~KSycoca()
386 {
387 d->closeDatabase();
388 delete d;
389 // if (ksycocaInstance.exists()
390 // && ksycocaInstance->self == this)
391 // ksycocaInstance->self = 0;
392 }
393
isAvailable()394 bool KSycoca::isAvailable() // TODO KF6: make it non-static (mostly useful for unittests)
395 {
396 return self()->d->checkDatabase(KSycocaPrivate::IfNotFoundDoNothing);
397 }
398
closeDatabase()399 void KSycocaPrivate::closeDatabase()
400 {
401 delete m_device;
402 m_device = nullptr;
403
404 // It is very important to delete all factories here
405 // since they cache information about the database file
406 // But other threads might be using them, so this class is
407 // refcounted, and deleted when the last thread is done with them
408 qDeleteAll(m_factories);
409 m_factories.clear();
410
411 m_mimeTypeFactory = nullptr;
412 m_serviceFactory = nullptr;
413 m_serviceTypeFactory = nullptr;
414 m_serviceGroupFactory = nullptr;
415
416 #if HAVE_MMAP
417 if (sycoca_mmap) {
418 // Solaris has munmap(char*, size_t) and everything else should
419 // be happy with a char* for munmap(void*, size_t)
420 munmap(const_cast<char *>(sycoca_mmap), sycoca_size);
421 sycoca_mmap = nullptr;
422 }
423 delete m_mmapFile;
424 m_mmapFile = nullptr;
425 #endif
426
427 databaseStatus = DatabaseNotOpen;
428 m_databasePath.clear();
429 timeStamp = 0;
430 }
431
addFactory(KSycocaFactory * factory)432 void KSycoca::addFactory(KSycocaFactory *factory)
433 {
434 d->addFactory(factory);
435 }
436
437 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 0)
isChanged(const char * type)438 bool KSycoca::isChanged(const char *type)
439 {
440 return self()->d->changeList.contains(QString::fromLatin1(type));
441 }
442 #endif
443
findEntry(int offset,KSycocaType & type)444 QDataStream *KSycoca::findEntry(int offset, KSycocaType &type)
445 {
446 QDataStream *str = stream();
447 Q_ASSERT(str);
448 // qCDebug(SYCOCA) << QString("KSycoca::_findEntry(offset=%1)").arg(offset,8,16);
449 str->device()->seek(offset);
450 qint32 aType;
451 *str >> aType;
452 type = KSycocaType(aType);
453 // qCDebug(SYCOCA) << QString("KSycoca::found type %1").arg(aType);
454 return str;
455 }
456
factories()457 KSycocaFactoryList *KSycoca::factories()
458 {
459 return d->factories();
460 }
461
462 // Warning, checkVersion rewinds to the beginning of stream().
checkVersion()463 bool KSycocaPrivate::checkVersion()
464 {
465 QDataStream *m_str = device()->stream();
466 Q_ASSERT(m_str);
467 m_str->device()->seek(0);
468 qint32 aVersion;
469 *m_str >> aVersion;
470 if (aVersion < KSYCOCA_VERSION) {
471 qCDebug(SYCOCA) << "Found version" << aVersion << ", expecting version" << KSYCOCA_VERSION << "or higher.";
472 databaseStatus = BadVersion;
473 return false;
474 } else {
475 databaseStatus = DatabaseOK;
476 return true;
477 }
478 }
479
480 // This is now completely useless. KF6: remove
481 extern KSERVICE_EXPORT bool kservice_require_kded;
482 KSERVICE_EXPORT bool kservice_require_kded = true;
483
484 // If it returns true, we have a valid database and the stream has rewinded to the beginning
485 // and past the version number.
checkDatabase(BehaviorsIfNotFound ifNotFound)486 bool KSycocaPrivate::checkDatabase(BehaviorsIfNotFound ifNotFound)
487 {
488 if (databaseStatus == DatabaseOK) {
489 if (checkVersion()) { // we know the version is ok, but we must rewind the stream anyway
490 return true;
491 }
492 }
493
494 closeDatabase(); // close the dummy one
495
496 // Check if new database already available
497 if (openDatabase()) {
498 // Database exists, and version is ok, we can read it.
499
500 if (qAppName() != QLatin1String(KBUILDSYCOCA_EXENAME) && ifNotFound != IfNotFoundDoNothing) {
501 // Ensure it's up-to-date, rebuild if needed
502 checkDirectories();
503
504 // Don't check again for some time
505 m_lastCheck.start();
506 }
507
508 return true;
509 }
510
511 if (ifNotFound & IfNotFoundRecreate) {
512 return buildSycoca();
513 }
514
515 return false;
516 }
517
findFactory(KSycocaFactoryId id)518 QDataStream *KSycoca::findFactory(KSycocaFactoryId id)
519 {
520 // Ensure we have a valid database (right version, and rewinded to beginning)
521 if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
522 return nullptr;
523 }
524
525 QDataStream *str = stream();
526 Q_ASSERT(str);
527
528 qint32 aId;
529 qint32 aOffset;
530 while (true) {
531 *str >> aId;
532 if (aId == 0) {
533 qCWarning(SYCOCA) << "Error, KSycocaFactory (id =" << int(id) << ") not found!";
534 break;
535 }
536 *str >> aOffset;
537 if (aId == id) {
538 // qCDebug(SYCOCA) << "KSycoca::findFactory(" << id << ") offset " << aOffset;
539 str->device()->seek(aOffset);
540 return str;
541 }
542 }
543 return nullptr;
544 }
545
needsRebuild()546 bool KSycoca::needsRebuild()
547 {
548 return d->needsRebuild();
549 }
550
readSycocaHeader()551 KSycocaHeader KSycocaPrivate::readSycocaHeader()
552 {
553 KSycocaHeader header;
554 // do not try to launch kbuildsycoca from here; this code is also called by kbuildsycoca.
555 if (!checkDatabase(KSycocaPrivate::IfNotFoundDoNothing)) {
556 return header;
557 }
558 QDataStream *str = stream();
559 qint64 oldPos = str->device()->pos();
560
561 Q_ASSERT(str);
562 qint32 aId;
563 qint32 aOffset;
564 // skip factories offsets
565 while (true) {
566 *str >> aId;
567 if (aId) {
568 *str >> aOffset;
569 } else {
570 break; // just read 0
571 }
572 }
573 // We now point to the header
574 QStringList directoryList;
575 *str >> header >> directoryList;
576 allResourceDirs.clear();
577 for (int i = 0; i < directoryList.count(); ++i) {
578 qint64 mtime;
579 *str >> mtime;
580 allResourceDirs.insert(directoryList.at(i), mtime);
581 }
582
583 QStringList fileList;
584 *str >> fileList;
585 extraFiles.clear();
586 for (const auto &fileName : std::as_const(fileList)) {
587 qint64 mtime;
588 *str >> mtime;
589 extraFiles.insert(fileName, mtime);
590 }
591
592 str->device()->seek(oldPos);
593
594 timeStamp = header.timeStamp;
595
596 // for the useless public accessors. KF6: remove these two lines, the accessors and the vars.
597 language = header.language;
598 updateSig = header.updateSignature;
599
600 return header;
601 }
602
603 class TimestampChecker
604 {
605 public:
TimestampChecker()606 TimestampChecker()
607 : m_now(QDateTime::currentDateTime())
608 {
609 }
610
611 // Check times of last modification of all directories on which ksycoca depends,
612 // If none of them is newer than the mtime we stored for that directory at the
613 // last rebuild, this means that there's no need to rebuild ksycoca.
checkDirectoriesTimestamps(const QMap<QString,qint64> & dirs) const614 bool checkDirectoriesTimestamps(const QMap<QString, qint64> &dirs) const
615 {
616 Q_ASSERT(!dirs.isEmpty());
617 // qCDebug(SYCOCA) << "checking file timestamps";
618 for (auto it = dirs.begin(); it != dirs.end(); ++it) {
619 const QString dir = it.key();
620 const qint64 lastStamp = it.value();
621
622 auto visitor = [&](const QFileInfo &fi) {
623 const QDateTime mtime = fi.lastModified();
624 if (mtime.toMSecsSinceEpoch() > lastStamp) {
625 if (mtime > m_now) {
626 qCDebug(SYCOCA) << fi.filePath() << "has a modification time in the future" << mtime;
627 }
628 qCDebug(SYCOCA) << "dir timestamp changed:" << fi.filePath() << mtime << ">" << QDateTime::fromMSecsSinceEpoch(lastStamp);
629 // no need to continue search
630 return false;
631 }
632
633 return true;
634 };
635
636 if (!KSycocaUtilsPrivate::visitResourceDirectory(dir, visitor)) {
637 return false;
638 }
639 }
640 return true;
641 }
642
checkFilesTimestamps(const QMap<QString,qint64> & files) const643 bool checkFilesTimestamps(const QMap<QString, qint64> &files) const
644 {
645 for (auto it = files.begin(); it != files.end(); ++it) {
646 const QString fileName = it.key();
647 const qint64 lastStamp = it.value();
648
649 QFileInfo fi(fileName);
650 if (!fi.exists()) {
651 return false;
652 }
653 const QDateTime mtime = fi.lastModified();
654 if (mtime.toMSecsSinceEpoch() > lastStamp) {
655 if (mtime > m_now) {
656 qCDebug(SYCOCA) << fi.filePath() << "has a modification time in the future" << mtime;
657 }
658 qCDebug(SYCOCA) << "file timestamp changed:" << fi.filePath() << mtime << ">" << QDateTime::fromMSecsSinceEpoch(lastStamp);
659 return false;
660 }
661 }
662 return true;
663 }
664
665 private:
666 QDateTime m_now;
667 };
668
checkDirectories()669 void KSycocaPrivate::checkDirectories()
670 {
671 if (needsRebuild()) {
672 buildSycoca();
673 }
674 }
675
needsRebuild()676 bool KSycocaPrivate::needsRebuild()
677 {
678 if (!timeStamp && databaseStatus == DatabaseOK) {
679 (void)readSycocaHeader();
680 }
681 // these days timeStamp is really a "bool headerFound", the value itself doesn't matter...
682 // KF6: replace it with bool.
683 const auto timestampChecker = TimestampChecker();
684 bool ret = timeStamp != 0
685 && (!timestampChecker.checkDirectoriesTimestamps(allResourceDirs) //
686 || !timestampChecker.checkFilesTimestamps(extraFiles));
687 if (ret) {
688 return true;
689 }
690 auto files = KBuildSycoca::factoryExtraFiles();
691 // ensure files are ordered so next comparison works
692 files.sort();
693 // to cover cases when extra files were added
694 return extraFiles.keys() != files;
695 }
696
buildSycoca()697 bool KSycocaPrivate::buildSycoca()
698 {
699 KBuildSycoca builder;
700 if (!builder.recreate()) {
701 return false; // error
702 }
703
704 closeDatabase(); // close the dummy one
705
706 // Ok, the new database should be here now, open it.
707 if (!openDatabase()) {
708 qCDebug(SYCOCA) << "Still no database...";
709 return false;
710 }
711 return true;
712 }
713
714 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 15)
timeStamp()715 quint32 KSycoca::timeStamp()
716 {
717 if (!d->timeStamp) {
718 (void)d->readSycocaHeader();
719 }
720 return d->timeStamp / 1000; // from ms to s
721 }
722 #endif
723
724 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 15)
updateSignature()725 quint32 KSycoca::updateSignature()
726 {
727 if (!d->timeStamp) {
728 (void)d->readSycocaHeader();
729 }
730 return d->updateSig;
731 }
732 #endif
733
absoluteFilePath(DatabaseType type)734 QString KSycoca::absoluteFilePath(DatabaseType type)
735 {
736 Q_UNUSED(type); // GlobalDatabase concept removed in 5.61
737 const QStringList paths = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
738 QString suffix = QLatin1Char('_') + QLocale().bcp47Name();
739
740 const QByteArray ksycoca_env = qgetenv("KDESYCOCA");
741 if (ksycoca_env.isEmpty()) {
742 const QByteArray pathHash = QCryptographicHash::hash(paths.join(QLatin1Char(':')).toUtf8(), QCryptographicHash::Sha1);
743 suffix += QLatin1Char('_') + QString::fromLatin1(pathHash.toBase64());
744 suffix.replace(QLatin1Char('/'), QLatin1Char('_'));
745 #ifdef Q_OS_WIN
746 suffix.replace(QLatin1Char(':'), QLatin1Char('_'));
747 #endif
748 const QString fileName = QLatin1String("ksycoca5") + suffix;
749 return QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1Char('/') + fileName;
750 } else {
751 return QFile::decodeName(ksycoca_env);
752 }
753 }
754
755 #if KSERVICE_BUILD_DEPRECATED_SINCE(5, 15)
language()756 QString KSycoca::language()
757 {
758 if (d->language.isEmpty()) {
759 (void)d->readSycocaHeader();
760 }
761 return d->language;
762 }
763 #endif
764
allResourceDirs()765 QStringList KSycoca::allResourceDirs()
766 {
767 if (!d->timeStamp) {
768 (void)d->readSycocaHeader();
769 }
770 return d->allResourceDirs.keys();
771 }
772
flagError()773 void KSycoca::flagError()
774 {
775 qCWarning(SYCOCA) << "ERROR: KSycoca database corruption!";
776 KSycoca *sycoca = self();
777 if (sycoca->d->readError) {
778 return;
779 }
780 sycoca->d->readError = true;
781 if (qAppName() != QLatin1String(KBUILDSYCOCA_EXENAME) && !sycoca->isBuilding()) {
782 // Rebuild the damned thing.
783 KBuildSycoca builder;
784 (void)builder.recreate();
785 }
786 }
787
isBuilding()788 bool KSycoca::isBuilding()
789 {
790 return false;
791 }
792
disableAutoRebuild()793 void KSycoca::disableAutoRebuild()
794 {
795 ksycocaInstance->sycoca()->d->m_fileWatcher = nullptr;
796 }
797
stream()798 QDataStream *&KSycoca::stream()
799 {
800 return d->stream();
801 }
802
connectNotify(const QMetaMethod & signal)803 void KSycoca::connectNotify(const QMetaMethod &signal)
804 {
805 if (signal.name() == "databaseChanged" && !d->m_haveListeners) {
806 d->m_haveListeners = true;
807 if (d->m_databasePath.isEmpty()) {
808 d->m_databasePath = d->findDatabase();
809 } else if (d->m_fileWatcher) {
810 d->m_fileWatcher->addFile(d->m_databasePath);
811 }
812 }
813 }
814
clearCaches()815 void KSycoca::clearCaches()
816 {
817 if (ksycocaInstance.exists() && ksycocaInstance()->hasSycoca()) {
818 ksycocaInstance()->sycoca()->d->closeDatabase();
819 }
820 }
821
822 extern KSERVICE_EXPORT int ksycoca_ms_between_checks;
823 KSERVICE_EXPORT int ksycoca_ms_between_checks = 1500;
824
ensureCacheValid()825 void KSycoca::ensureCacheValid()
826 {
827 if (qAppName() == QLatin1String(KBUILDSYCOCA_EXENAME)) {
828 return;
829 }
830
831 if (d->databaseStatus != KSycocaPrivate::DatabaseOK) {
832 if (!d->checkDatabase(KSycocaPrivate::IfNotFoundRecreate)) {
833 return;
834 }
835 }
836
837 if (d->m_lastCheck.isValid() && d->m_lastCheck.elapsed() < ksycoca_ms_between_checks) {
838 return;
839 }
840 d->m_lastCheck.start();
841
842 // Check if the file on disk was modified since we last checked it.
843 QFileInfo info(d->m_databasePath);
844 if (info.lastModified() == d->m_dbLastModified) {
845 // Check if the watched directories were modified, then the cache needs a rebuild.
846 d->checkDirectories();
847 return;
848 }
849
850 // Close the database and forget all about what we knew.
851 // The next call to any public method will recreate
852 // everything that's needed.
853 d->closeDatabase();
854 }
855