1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 1999 David Faure <faure@kde.org>
4 SPDX-FileCopyrightText: 2002-2003 Waldo Bastian <bastian@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-only
7*/
8
9#include "kbuildsycoca_p.h"
10#include "ksycoca_p.h"
11#include "ksycocaresourcelist_p.h"
12#include "ksycocautils_p.h"
13#include "sycocadebug.h"
14#include "vfolder_menu_p.h"
15
16#include "kbuildmimetypefactory_p.h"
17#include "kbuildservicefactory_p.h"
18#include "kbuildservicegroupfactory_p.h"
19#include "kctimefactory_p.h"
20#include <QDataStream>
21#include <QDateTime>
22#include <QDebug>
23#include <QDir>
24#include <QDirIterator>
25#include <QEventLoop>
26#include <QFile>
27#include <QLocale>
28#include <QSaveFile>
29#include <QTimer>
30#include <config-ksycoca.h>
31#include <kservice.h>
32#include <kservicegroup.h>
33
34#include <kmemfile_p.h>
35
36#include <QLockFile>
37#include <QStandardPaths>
38#include <qplatformdefs.h>
39
40static const char *s_cSycocaPath = nullptr;
41
42KBuildSycocaInterface::~KBuildSycocaInterface()
43{
44}
45
46KBuildSycoca::KBuildSycoca()
47 : KSycoca(true)
48 , m_allEntries(nullptr)
49 , m_ctimeFactory(nullptr)
50 , m_ctimeDict(nullptr)
51 , m_currentEntryDict(nullptr)
52 , m_serviceGroupEntryDict(nullptr)
53 , m_vfolder(nullptr)
54 , m_newTimestamp(0)
55 , m_menuTest(false)
56 , m_changed(false)
57{
58}
59
60KBuildSycoca::~KBuildSycoca()
61{
62 // Delete the factories while we exist, so that the virtual isBuilding() still works
63 qDeleteAll(c: *factories());
64 factories()->clear();
65}
66
67KSycocaEntry::Ptr KBuildSycoca::createEntry(KSycocaFactory *currentFactory, const QString &file)
68{
69 quint32 timeStamp = m_ctimeFactory->dict()->ctime(path: file, resource: m_resource);
70 if (!timeStamp) {
71 timeStamp = calcResourceHash(subdir: m_resourceSubdir, filename: file);
72 if (!timeStamp) { // file disappeared meanwhile
73 qCDebug(SYCOCA) << "Couldn't generate timeStamp. Has the file disappeared?";
74 return {};
75 }
76 }
77 KSycocaEntry::Ptr entry;
78 if (m_allEntries) {
79 Q_ASSERT(m_ctimeDict);
80 quint32 oldTimestamp = m_ctimeDict->ctime(path: file, resource: m_resource);
81 if (file.contains(s: QLatin1String("fake"))) {
82 qCDebug(SYCOCA) << "m_ctimeDict->ctime(" << file << ") = " << oldTimestamp << "compared with" << timeStamp;
83 }
84
85 if (timeStamp && (timeStamp == oldTimestamp)) {
86 // Re-use old entry
87 if (currentFactory == d->m_serviceFactory) { // Strip .directory from service-group entries
88 entry = m_currentEntryDict->value(key: file.left(n: file.length() - 10));
89 } else {
90 entry = m_currentEntryDict->value(key: file);
91 }
92 // remove from m_ctimeDict; if m_ctimeDict is not empty
93 // after all files have been processed, it means
94 // some files were removed since last time
95 if (file.contains(s: QLatin1String("fake"))) {
96 qCDebug(SYCOCA) << "reusing (and removing) old entry for:" << file << "entry=" << entry;
97 }
98 m_ctimeDict->remove(path: file, resource: m_resource);
99 } else if (oldTimestamp) {
100 m_changed = true;
101 m_ctimeDict->remove(path: file, resource: m_resource);
102 qCDebug(SYCOCA) << "modified:" << file;
103 } else {
104 m_changed = true;
105 qCDebug(SYCOCA) << "new:" << file;
106 }
107 }
108 m_ctimeFactory->dict()->addCTime(path: file, resource: m_resource, ctime: timeStamp);
109 if (!entry) {
110 // Create a new entry
111 entry = currentFactory->createEntry(file);
112 }
113 if (entry && entry->isValid()) {
114 return entry;
115 }
116 qCDebug(SYCOCA) << "No valid entry created for" << file;
117 return KSycocaEntry::Ptr();
118}
119
120KService::Ptr KBuildSycoca::createService(const QString &path)
121{
122 KSycocaEntry::Ptr entry = createEntry(currentFactory: d->m_serviceFactory, file: path);
123 if (entry) {
124 m_tempStorage.append(t: entry);
125 }
126 return KService::Ptr(static_cast<KService *>(entry.data()));
127}
128
129// returns false if the database is up to date, true if it needs to be saved
130bool KBuildSycoca::build()
131{
132 using KBSEntryDictList = QList<KBSEntryDict *>;
133 KBSEntryDictList entryDictList;
134 KBSEntryDict *serviceEntryDict = nullptr;
135
136 // Convert for each factory the entryList to a Dict.
137 entryDictList.reserve(asize: factories()->size());
138 int i = 0;
139 // For each factory
140 const auto &factoryList = *factories();
141 for (KSycocaFactory *factory : factoryList) {
142 KBSEntryDict *entryDict = new KBSEntryDict;
143 if (m_allEntries) { // incremental build
144 for (const KSycocaEntry::Ptr &entry : std::as_const(t: (*m_allEntries).at(i: i++))) {
145 // if (entry->entryPath().contains("fake"))
146 // qCDebug(SYCOCA) << "inserting into entryDict:" << entry->entryPath() << entry;
147 entryDict->insert(key: entry->entryPath(), value: entry);
148 }
149 }
150 if (factory == d->m_serviceFactory) {
151 serviceEntryDict = entryDict;
152 } else if (factory == m_buildServiceGroupFactory) {
153 m_serviceGroupEntryDict = entryDict;
154 }
155 entryDictList.append(t: entryDict);
156 }
157
158 // Save the mtime of each dir, just before we list them
159 // ## should we convert to UTC to avoid surprises when summer time kicks in?
160 const auto lstDirs = factoryResourceDirs();
161 for (const QString &dir : lstDirs) {
162 qint64 stamp = 0;
163 KSycocaUtilsPrivate::visitResourceDirectory(dirname: dir, visitor: [&stamp](const QFileInfo &info) {
164 stamp = qMax(a: stamp, b: info.lastModified().toMSecsSinceEpoch());
165 return true;
166 });
167 m_allResourceDirs.insert(key: dir, value: stamp);
168 }
169
170 const auto lstFiles = factoryExtraFiles();
171 for (const QString &file : lstFiles) {
172 m_extraFiles.insert(key: file, value: QFileInfo(file).lastModified().toMSecsSinceEpoch());
173 }
174
175 QMap<QString, QByteArray> allResourcesSubDirs; // dirs, kstandarddirs-resource-name
176 // For each factory
177 for (KSycocaFactory *factory : factoryList) {
178 // For each resource the factory deals with
179 const KSycocaResourceList resourceList = factory->resourceList();
180 for (const KSycocaResource &res : resourceList) {
181 // With this we would get dirs, but not a unique list of relative files (for global+local merging to work)
182 // const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, res.subdir, QStandardPaths::LocateDirectory);
183 // allResourcesSubDirs[res.resource] += dirs;
184 allResourcesSubDirs.insert(key: res.subdir, value: res.resource);
185 }
186 }
187
188 m_ctimeFactory = new KCTimeFactory(this); // This is a build factory too, don't delete!!
189 for (auto it1 = allResourcesSubDirs.cbegin(); it1 != allResourcesSubDirs.cend(); ++it1) {
190 m_changed = false;
191 m_resourceSubdir = it1.key();
192 m_resource = it1.value();
193
194 QSet<QString> relFiles;
195 const QStringList dirs = QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation, fileName: m_resourceSubdir, options: QStandardPaths::LocateDirectory);
196 qCDebug(SYCOCA) << "Looking for subdir" << m_resourceSubdir << "=>" << dirs;
197 for (const QString &dir : dirs) {
198 QDirIterator it(dir, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
199 while (it.hasNext()) {
200 const QString filePath = it.next();
201 Q_ASSERT(filePath.startsWith(dir)); // due to the line below...
202 const QString relPath = filePath.mid(position: dir.length() + 1);
203 relFiles.insert(value: relPath);
204 }
205 }
206 // Now find all factories that use this resource....
207 // For each factory -- and its corresponding entryDict (iterate over both lists in parallel)
208 for (int f = 0; f < factoryList.count(); ++f) {
209 KSycocaFactory *currentFactory = factoryList.at(i: f);
210 // m_ctimeInfo gets created after the initial loop, so it has no entryDict.
211 m_currentEntryDict = f == entryDictList.size() ? nullptr : entryDictList.at(i: f);
212 // For each resource the factory deals with
213 const KSycocaResourceList &resourceList = currentFactory->resourceList();
214 for (const KSycocaResource &res : resourceList) {
215 if (res.resource != m_resource) {
216 continue;
217 }
218
219 // For each file in the resource
220 for (const QString &entryPath : std::as_const(t&: relFiles)) {
221 // Check if file matches filter
222 if (entryPath.endsWith(s: res.extension)) {
223 KSycocaEntry::Ptr entry = createEntry(currentFactory, file: entryPath);
224 if (entry) {
225 currentFactory->addEntry(newEntry: entry);
226 }
227 }
228 }
229 }
230 }
231 }
232
233 bool result = true;
234 const bool createVFolder = true; // we need to always run the VFolderMenu code
235 if (createVFolder || m_menuTest) {
236 m_resource = "apps";
237 m_resourceSubdir = QStringLiteral("applications");
238 m_currentEntryDict = serviceEntryDict;
239 m_changed = false;
240
241 m_vfolder = new VFolderMenu(d->m_serviceFactory, this);
242 if (!m_trackId.isEmpty()) {
243 m_vfolder->setTrackId(m_trackId);
244 }
245
246 VFolderMenu::SubMenu *kdeMenu = m_vfolder->parseMenu(QStringLiteral("applications.menu"));
247
248 KServiceGroup::Ptr entry = m_buildServiceGroupFactory->addNew(QStringLiteral("/"), file: kdeMenu->directoryFile, entry: KServiceGroup::Ptr(), isDeleted: false);
249 entry->setLayoutInfo(kdeMenu->layoutList);
250 createMenu(caption: QString(), name: QString(), menu: kdeMenu);
251
252 // Storing the mtime *after* looking at these dirs is a tiny race condition,
253 // but I'm not sure how to get the vfolder dirs upfront...
254 const auto allDirectories = m_vfolder->allDirectories();
255 for (QString dir : allDirectories) {
256 if (dir.endsWith(c: QLatin1Char('/'))) {
257 dir.chop(n: 1); // remove trailing slash, to avoid having ~/.local/share/applications twice
258 }
259 if (!m_allResourceDirs.contains(key: dir)) {
260 qint64 stamp = 0;
261 KSycocaUtilsPrivate::visitResourceDirectory(dirname: dir, visitor: [&stamp](const QFileInfo &info) {
262 stamp = qMax(a: stamp, b: info.lastModified().toMSecsSinceEpoch());
263 return true;
264 });
265 m_allResourceDirs.insert(key: dir, value: stamp);
266 }
267 }
268
269 if (m_menuTest) {
270 result = false;
271 }
272 }
273
274 if (m_ctimeDict && !m_ctimeDict->isEmpty()) {
275 qCDebug(SYCOCA) << "Still in time dict:";
276 m_ctimeDict->dump();
277 }
278
279 qDeleteAll(c: entryDictList);
280 return result;
281}
282
283void KBuildSycoca::createMenu(const QString &caption_, const QString &name_, VFolderMenu::SubMenu *menu)
284{
285 QString caption = caption_;
286 QString name = name_;
287 for (VFolderMenu::SubMenu *subMenu : std::as_const(t&: menu->subMenus)) {
288 QString subName = name + subMenu->name + QLatin1Char('/');
289
290 QString directoryFile = subMenu->directoryFile;
291 if (directoryFile.isEmpty()) {
292 directoryFile = subName + QLatin1String(".directory");
293 }
294 quint32 timeStamp = m_ctimeFactory->dict()->ctime(path: directoryFile, resource: m_resource);
295 if (!timeStamp) {
296 timeStamp = calcResourceHash(subdir: m_resourceSubdir, filename: directoryFile);
297 }
298
299 KServiceGroup::Ptr entry;
300 if (m_allEntries) {
301 const quint32 oldTimestamp = m_ctimeDict->ctime(path: directoryFile, resource: m_resource);
302
303 if (timeStamp && (timeStamp == oldTimestamp)) {
304 KSycocaEntry::Ptr group = m_serviceGroupEntryDict->value(key: subName);
305 if (group) {
306 entry = KServiceGroup::Ptr(static_cast<KServiceGroup *>(group.data()));
307 if (entry->directoryEntryPath() != directoryFile) {
308 entry = nullptr; // Can't reuse this one!
309 }
310 }
311 }
312 }
313 if (timeStamp) { // bug? (see calcResourceHash). There might not be a .directory file...
314 m_ctimeFactory->dict()->addCTime(path: directoryFile, resource: m_resource, ctime: timeStamp);
315 }
316
317 entry = m_buildServiceGroupFactory->addNew(menuName: subName, file: subMenu->directoryFile, entry, isDeleted: subMenu->isDeleted);
318 entry->setLayoutInfo(subMenu->layoutList);
319 if (!(m_menuTest && entry->noDisplay())) {
320 createMenu(caption_: caption + entry->caption() + QLatin1Char('/'), name_: subName, menu: subMenu);
321 }
322 }
323 if (caption.isEmpty()) {
324 caption += QLatin1Char('/');
325 }
326 if (name.isEmpty()) {
327 name += QLatin1Char('/');
328 }
329 for (const KService::Ptr &p : std::as_const(t&: menu->items)) {
330 if (m_menuTest) {
331 if (!menu->isDeleted && !p->noDisplay()) {
332 printf(format: "%s\t%s\t%s\n",
333 qPrintable(caption),
334 qPrintable(p->menuId()),
335 qPrintable(QStandardPaths::locate(QStandardPaths::ApplicationsLocation, p->entryPath())));
336 }
337 } else {
338 m_buildServiceGroupFactory->addNewEntryTo(menuName: name, newEntry: p);
339 }
340 }
341}
342
343bool KBuildSycoca::recreate(bool incremental)
344{
345 QFileInfo fi(KSycoca::absoluteFilePath());
346 if (!QDir().mkpath(dirPath: fi.absolutePath())) {
347 qCWarning(SYCOCA) << "Couldn't create" << fi.absolutePath();
348 return false;
349 }
350 QString path(fi.absoluteFilePath());
351
352 QLockFile lockFile(path + QLatin1String(".lock"));
353 if (!lockFile.tryLock()) {
354 qCDebug(SYCOCA) << "Waiting for already running" << KBUILDSYCOCA_EXENAME << "to finish.";
355 if (!lockFile.lock()) {
356 qCWarning(SYCOCA) << "Couldn't lock" << path + QLatin1String(".lock");
357 return false;
358 }
359 if (!needsRebuild()) {
360 // qCDebug(SYCOCA) << "Up-to-date, skipping.";
361 return true;
362 }
363 }
364
365 QByteArray qSycocaPath = QFile::encodeName(fileName: path);
366 s_cSycocaPath = qSycocaPath.data();
367
368 m_allEntries = nullptr;
369 m_ctimeDict = nullptr;
370 if (incremental && checkGlobalHeader()) {
371 qCDebug(SYCOCA) << "Reusing existing ksycoca";
372 KSycoca *oldSycoca = KSycoca::self();
373 m_allEntries = new KSycocaEntryListList;
374 m_ctimeDict = new KCTimeDict;
375
376 // Must be in same order as in KBuildSycoca::recreate()!
377 m_allEntries->append(t: KSycocaPrivate::self()->mimeTypeFactory()->allEntries());
378 m_allEntries->append(t: KSycocaPrivate::self()->serviceGroupFactory()->allEntries());
379 m_allEntries->append(t: KSycocaPrivate::self()->serviceFactory()->allEntries());
380
381 KCTimeFactory *ctimeInfo = new KCTimeFactory(oldSycoca);
382 *m_ctimeDict = ctimeInfo->loadDict();
383 }
384 s_cSycocaPath = nullptr;
385
386 QSaveFile database(path);
387 bool openedOK = database.open(flags: QIODevice::WriteOnly);
388
389 if (!openedOK && database.error() == QFile::WriteError && QFile::exists(fileName: path)) {
390 QFile::remove(fileName: path);
391 openedOK = database.open(flags: QIODevice::WriteOnly);
392 }
393 if (!openedOK) {
394 qCWarning(SYCOCA) << "ERROR creating database" << path << ":" << database.errorString();
395 return false;
396 }
397
398 QDataStream *str = new QDataStream(&database);
399 str->setVersion(QDataStream::Qt_5_3);
400
401 m_newTimestamp = QDateTime::currentMSecsSinceEpoch();
402 qCDebug(SYCOCA).nospace() << "Recreating ksycoca file (" << path << ", version " << KSycoca::version() << ")";
403
404 KBuildMimeTypeFactory *buildMimeTypeFactory = new KBuildMimeTypeFactory(this);
405 d->m_mimeTypeFactory = buildMimeTypeFactory;
406 m_buildServiceGroupFactory = new KBuildServiceGroupFactory(this);
407 d->m_serviceGroupFactory = m_buildServiceGroupFactory;
408 d->m_serviceFactory = new KBuildServiceFactory(buildMimeTypeFactory);
409
410 if (build()) { // Parse dirs
411 save(str); // Save database
412 if (str->status() != QDataStream::Ok) { // Probably unnecessary now in Qt5, since QSaveFile detects write errors
413 database.cancelWriting(); // Error
414 }
415 delete str;
416 str = nullptr;
417
418 // if we are currently via sudo, preserve the original owner
419 // as $HOME may also be that of another user rather than /root
420#ifdef Q_OS_UNIX
421 if (qEnvironmentVariableIsSet(varName: "SUDO_UID")) {
422 const int uid = qEnvironmentVariableIntValue(varName: "SUDO_UID");
423 const int gid = qEnvironmentVariableIntValue(varName: "SUDO_GID");
424 if (uid && gid) {
425 int ret;
426 ret = fchown(fd: database.handle(), owner: uid, group: gid);
427 if (ret < 0) {
428 qCWarning(SYCOCA) << "ERROR changing ownership of database" << database.fileName() << strerror(errno);
429 return false;
430 }
431 }
432 }
433#endif
434
435 if (!database.commit()) {
436 qCWarning(SYCOCA) << "ERROR writing database" << database.fileName() << database.errorString();
437 return false;
438 }
439 } else {
440 delete str;
441 str = nullptr;
442 database.cancelWriting();
443 if (m_menuTest) {
444 return true;
445 }
446 qCDebug(SYCOCA) << "Database is up to date";
447 }
448
449#ifndef QT_NO_SHAREDMEMORY
450 if (d->m_sycocaStrategy == KSycocaPrivate::StrategyMemFile) {
451 KMemFile::fileContentsChanged(filename: path);
452 }
453#endif
454
455 delete m_ctimeDict;
456 delete m_allEntries;
457 delete m_vfolder;
458
459 return true;
460}
461
462void KBuildSycoca::save(QDataStream *str)
463{
464 // Write header (#pass 1)
465 str->device()->seek(pos: 0);
466
467 (*str) << qint32(KSycoca::version());
468 // KSycocaFactory * servicetypeFactory = 0;
469 // KBuildMimeTypeFactory * mimeTypeFactory = 0;
470 KBuildServiceFactory *serviceFactory = nullptr;
471 auto lst = *factories();
472 for (KSycocaFactory *factory : std::as_const(t&: lst)) {
473 qint32 aId;
474 qint32 aOffset;
475 aId = factory->factoryId();
476 // if ( aId == KST_KServiceTypeFactory )
477 // servicetypeFactory = factory;
478 // else if ( aId == KST_KMimeTypeFactory )
479 // mimeTypeFactory = static_cast<KBuildMimeTypeFactory *>( factory );
480 if (aId == KST_KServiceFactory) {
481 serviceFactory = static_cast<KBuildServiceFactory *>(factory);
482 }
483 aOffset = factory->offset(); // not set yet, so always 0
484 (*str) << aId;
485 (*str) << aOffset;
486 }
487 (*str) << qint32(0); // No more factories.
488 // Write XDG_DATA_DIRS
489 (*str) << QStandardPaths::standardLocations(type: QStandardPaths::GenericDataLocation).join(sep: QString(QLatin1Char(':')));
490 (*str) << m_newTimestamp;
491 (*str) << QLocale().bcp47Name();
492 // This makes it possible to trigger a ksycoca update for all users (KIOSK feature)
493 (*str) << calcResourceHash(QStringLiteral("kservices6"), QStringLiteral("update_ksycoca"));
494 (*str) << m_allResourceDirs.keys();
495 for (auto it = m_allResourceDirs.constBegin(); it != m_allResourceDirs.constEnd(); ++it) {
496 (*str) << it.value();
497 }
498 (*str) << m_extraFiles.keys();
499 for (auto it = m_extraFiles.constBegin(); it != m_extraFiles.constEnd(); ++it) {
500 (*str) << it.value();
501 }
502
503 // Calculate per-servicetype/MIME type data
504 if (serviceFactory) {
505 serviceFactory->postProcessServices();
506 }
507
508 // Here so that it's the last debug message
509 qCDebug(SYCOCA) << "Saving";
510
511 // Write factory data....
512 lst = *factories();
513 for (KSycocaFactory *factory : std::as_const(t&: lst)) {
514 factory->save(str&: *str);
515 if (str->status() != QDataStream::Ok) { // ######## TODO: does this detect write errors, e.g. disk full?
516 return; // error
517 }
518 }
519
520 qint64 endOfData = str->device()->pos();
521
522 // Write header (#pass 2)
523 str->device()->seek(pos: 0);
524
525 (*str) << qint32(KSycoca::version());
526 lst = *factories();
527 for (KSycocaFactory *factory : std::as_const(t&: lst)) {
528 qint32 aId;
529 qint32 aOffset;
530 aId = factory->factoryId();
531 aOffset = factory->offset();
532 (*str) << aId;
533 (*str) << aOffset;
534 }
535 (*str) << qint32(0); // No more factories.
536
537 // Jump to end of database
538 str->device()->seek(pos: endOfData);
539}
540
541QStringList KBuildSycoca::factoryResourceDirs()
542{
543 static QStringList *dirs = nullptr;
544 if (dirs != nullptr) {
545 return *dirs;
546 }
547 dirs = new QStringList;
548 // these are all resource dirs cached by ksycoca
549 *dirs += KMimeTypeFactory::resourceDirs();
550 *dirs += KServiceFactory::resourceDirs();
551
552 return *dirs;
553}
554
555QStringList KBuildSycoca::factoryExtraFiles()
556{
557 QStringList files;
558 // these are the extra files cached by ksycoca
559 // and whose timestamps are checked
560 files += KMimeAssociations::mimeAppsFiles();
561
562 return files;
563}
564
565QStringList KBuildSycoca::existingResourceDirs()
566{
567 static QStringList *dirs = nullptr;
568 if (dirs != nullptr) {
569 return *dirs;
570 }
571 dirs = new QStringList(factoryResourceDirs());
572
573 auto checkDir = [](const QString &str) {
574 QFileInfo info(str);
575 return !info.exists() || !info.isReadable();
576 };
577 dirs->erase(abegin: std::remove_if(first: dirs->begin(), last: dirs->end(), pred: checkDir), aend: dirs->end());
578
579 return *dirs;
580}
581
582static quint32 updateHash(const QString &file, quint32 hash)
583{
584 QFileInfo fi(file);
585 if (fi.isReadable() && fi.isFile()) {
586 // This was using buff.st_ctime (in Waldo's initial commit to kstandarddirs.cpp in 2001), but that looks wrong?
587 // Surely we want to catch manual editing, while a chmod doesn't matter much?
588 qint64 timestamp = fi.lastModified().toSecsSinceEpoch();
589 // On some systems (i.e. Fedora Kinoite), all files in /usr have a last
590 // modified timestamp of 0 (UNIX Epoch). In this case, always assume
591 // the file as been changed.
592 if (timestamp == 0) {
593 static qint64 now = QDateTime::currentSecsSinceEpoch();
594 timestamp = now;
595 }
596 hash += timestamp;
597 }
598 return hash;
599}
600
601quint32 KBuildSycoca::calcResourceHash(const QString &resourceSubDir, const QString &filename)
602{
603 quint32 hash = 0;
604 if (!QDir::isRelativePath(path: filename)) {
605 return updateHash(file: filename, hash);
606 }
607 const QString filePath = resourceSubDir + QLatin1Char('/') + filename;
608 const QString qrcFilePath = QStringLiteral(":/") + filePath;
609 const QStringList files =
610 QFileInfo::exists(file: qrcFilePath) ? QStringList{qrcFilePath} : QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation, fileName: filePath);
611 for (const QString &file : files) {
612 hash = updateHash(file, hash);
613 }
614 if (hash == 0 && !filename.endsWith(s: QLatin1String("update_ksycoca"))
615 && !filename.endsWith(s: QLatin1String(".directory")) // bug? needs investigation from someone who understands the VFolder spec
616 ) {
617 if (files.isEmpty()) {
618 // This can happen if the file was deleted between directory listing and the above locateAll
619 qCDebug(SYCOCA) << "File not found anymore:" << filename << " -- probably deleted meanwhile";
620 } else {
621 // This can happen if the file was deleted between locateAll and QFileInfo
622 qCDebug(SYCOCA) << "File(s) found but not readable (or disappeared meanwhile)" << files;
623 }
624 }
625 return hash;
626}
627
628bool KBuildSycoca::checkGlobalHeader()
629{
630 // Since it's part of the filename, we are 99% sure that the locale and prefixes will match.
631 const QString current_language = QLocale().bcp47Name();
632 const quint32 current_update_sig = KBuildSycoca::calcResourceHash(QStringLiteral("kservices6"), QStringLiteral("update_ksycoca"));
633 const QString current_prefixes = QStandardPaths::standardLocations(type: QStandardPaths::GenericDataLocation).join(sep: QString(QLatin1Char(':')));
634
635 const KSycocaHeader header = KSycocaPrivate::self()->readSycocaHeader();
636 Q_ASSERT(!header.prefixes.split(QLatin1Char(':')).contains(QDir::homePath()));
637
638 return (current_update_sig == header.updateSignature) //
639 && (current_language == header.language) //
640 && (current_prefixes == header.prefixes) //
641 && (header.timeStamp != 0);
642}
643
644const char *KBuildSycoca::sycocaPath()
645{
646 return s_cSycocaPath;
647}
648
649#include "moc_kbuildsycoca_p.cpp"
650

source code of kservice/src/sycoca/kbuildsycoca.cpp