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