| 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 | QDirIterator::FollowSymlinks); |
| 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 | int ret; |
| 424 | ret = fchown(fd: database.handle(), owner: uid, group: gid); |
| 425 | if (ret < 0) { |
| 426 | qCWarning(SYCOCA) << "ERROR changing ownership of database" << database.fileName() << strerror(errno); |
| 427 | return false; |
| 428 | } |
| 429 | } |
| 430 | } |
| 431 | #endif |
| 432 | |
| 433 | if (!database.commit()) { |
| 434 | qCWarning(SYCOCA) << "ERROR writing database" << database.fileName() << database.errorString(); |
| 435 | return false; |
| 436 | } |
| 437 | } else { |
| 438 | delete str; |
| 439 | str = nullptr; |
| 440 | database.cancelWriting(); |
| 441 | if (m_menuTest) { |
| 442 | return true; |
| 443 | } |
| 444 | qCDebug(SYCOCA) << "Database is up to date" ; |
| 445 | } |
| 446 | |
| 447 | #ifndef QT_NO_SHAREDMEMORY |
| 448 | if (d->m_sycocaStrategy == KSycocaPrivate::StrategyMemFile) { |
| 449 | KMemFile::fileContentsChanged(filename: path); |
| 450 | } |
| 451 | #endif |
| 452 | |
| 453 | delete m_ctimeDict; |
| 454 | delete m_allEntries; |
| 455 | delete m_vfolder; |
| 456 | |
| 457 | return true; |
| 458 | } |
| 459 | |
| 460 | void KBuildSycoca::save(QDataStream *str) |
| 461 | { |
| 462 | // Write header (#pass 1) |
| 463 | str->device()->seek(pos: 0); |
| 464 | |
| 465 | (*str) << qint32(KSycoca::version()); |
| 466 | // KSycocaFactory * servicetypeFactory = 0; |
| 467 | // KBuildMimeTypeFactory * mimeTypeFactory = 0; |
| 468 | KBuildServiceFactory *serviceFactory = nullptr; |
| 469 | auto lst = *factories(); |
| 470 | for (KSycocaFactory *factory : std::as_const(t&: lst)) { |
| 471 | qint32 aId; |
| 472 | qint32 aOffset; |
| 473 | aId = factory->factoryId(); |
| 474 | // if ( aId == KST_KServiceTypeFactory ) |
| 475 | // servicetypeFactory = factory; |
| 476 | // else if ( aId == KST_KMimeTypeFactory ) |
| 477 | // mimeTypeFactory = static_cast<KBuildMimeTypeFactory *>( factory ); |
| 478 | if (aId == KST_KServiceFactory) { |
| 479 | serviceFactory = static_cast<KBuildServiceFactory *>(factory); |
| 480 | } |
| 481 | aOffset = factory->offset(); // not set yet, so always 0 |
| 482 | (*str) << aId; |
| 483 | (*str) << aOffset; |
| 484 | } |
| 485 | (*str) << qint32(0); // No more factories. |
| 486 | // Write XDG_DATA_DIRS |
| 487 | (*str) << QStandardPaths::standardLocations(type: QStandardPaths::GenericDataLocation).join(sep: QString(QLatin1Char(':'))); |
| 488 | (*str) << m_newTimestamp; |
| 489 | (*str) << QLocale().bcp47Name(); |
| 490 | // This makes it possible to trigger a ksycoca update for all users (KIOSK feature) |
| 491 | (*str) << calcResourceHash(QStringLiteral("kservices6" ), QStringLiteral("update_ksycoca" )); |
| 492 | (*str) << m_allResourceDirs.keys(); |
| 493 | for (auto it = m_allResourceDirs.constBegin(); it != m_allResourceDirs.constEnd(); ++it) { |
| 494 | (*str) << it.value(); |
| 495 | } |
| 496 | (*str) << m_extraFiles.keys(); |
| 497 | for (auto it = m_extraFiles.constBegin(); it != m_extraFiles.constEnd(); ++it) { |
| 498 | (*str) << it.value(); |
| 499 | } |
| 500 | |
| 501 | // Calculate per-servicetype/MIME type data |
| 502 | if (serviceFactory) { |
| 503 | serviceFactory->postProcessServices(); |
| 504 | } |
| 505 | |
| 506 | // Here so that it's the last debug message |
| 507 | qCDebug(SYCOCA) << "Saving" ; |
| 508 | |
| 509 | // Write factory data.... |
| 510 | lst = *factories(); |
| 511 | for (KSycocaFactory *factory : std::as_const(t&: lst)) { |
| 512 | factory->save(str&: *str); |
| 513 | if (str->status() != QDataStream::Ok) { // ######## TODO: does this detect write errors, e.g. disk full? |
| 514 | return; // error |
| 515 | } |
| 516 | } |
| 517 | |
| 518 | qint64 endOfData = str->device()->pos(); |
| 519 | |
| 520 | // Write header (#pass 2) |
| 521 | str->device()->seek(pos: 0); |
| 522 | |
| 523 | (*str) << qint32(KSycoca::version()); |
| 524 | lst = *factories(); |
| 525 | for (KSycocaFactory *factory : std::as_const(t&: lst)) { |
| 526 | qint32 aId; |
| 527 | qint32 aOffset; |
| 528 | aId = factory->factoryId(); |
| 529 | aOffset = factory->offset(); |
| 530 | (*str) << aId; |
| 531 | (*str) << aOffset; |
| 532 | } |
| 533 | (*str) << qint32(0); // No more factories. |
| 534 | |
| 535 | // Jump to end of database |
| 536 | str->device()->seek(pos: endOfData); |
| 537 | } |
| 538 | |
| 539 | QStringList KBuildSycoca::factoryResourceDirs() |
| 540 | { |
| 541 | static QStringList *dirs = nullptr; |
| 542 | if (dirs != nullptr) { |
| 543 | return *dirs; |
| 544 | } |
| 545 | dirs = new QStringList; |
| 546 | // these are all resource dirs cached by ksycoca |
| 547 | *dirs += KMimeTypeFactory::resourceDirs(); |
| 548 | *dirs += KServiceFactory::resourceDirs(); |
| 549 | |
| 550 | return *dirs; |
| 551 | } |
| 552 | |
| 553 | QStringList KBuildSycoca::() |
| 554 | { |
| 555 | QStringList files; |
| 556 | // these are the extra files cached by ksycoca |
| 557 | // and whose timestamps are checked |
| 558 | files += KMimeAssociations::mimeAppsFiles(); |
| 559 | |
| 560 | return files; |
| 561 | } |
| 562 | |
| 563 | QStringList KBuildSycoca::existingResourceDirs() |
| 564 | { |
| 565 | static QStringList *dirs = nullptr; |
| 566 | if (dirs != nullptr) { |
| 567 | return *dirs; |
| 568 | } |
| 569 | dirs = new QStringList(factoryResourceDirs()); |
| 570 | |
| 571 | auto checkDir = [](const QString &str) { |
| 572 | QFileInfo info(str); |
| 573 | return !info.exists() || !info.isReadable(); |
| 574 | }; |
| 575 | dirs->erase(abegin: std::remove_if(first: dirs->begin(), last: dirs->end(), pred: checkDir), aend: dirs->end()); |
| 576 | |
| 577 | return *dirs; |
| 578 | } |
| 579 | |
| 580 | static quint32 updateHash(const QString &file, quint32 hash) |
| 581 | { |
| 582 | QFileInfo fi(file); |
| 583 | if (fi.isReadable() && fi.isFile()) { |
| 584 | // This was using buff.st_ctime (in Waldo's initial commit to kstandarddirs.cpp in 2001), but that looks wrong? |
| 585 | // Surely we want to catch manual editing, while a chmod doesn't matter much? |
| 586 | qint64 timestamp = fi.lastModified().toSecsSinceEpoch(); |
| 587 | // On some systems (i.e. Fedora Kinoite), all files in /usr have a last |
| 588 | // modified timestamp of 0 (UNIX Epoch). In this case, always assume |
| 589 | // the file as been changed. |
| 590 | if (timestamp == 0) { |
| 591 | static qint64 now = QDateTime::currentDateTimeUtc().toSecsSinceEpoch(); |
| 592 | timestamp = now; |
| 593 | } |
| 594 | hash += timestamp; |
| 595 | } |
| 596 | return hash; |
| 597 | } |
| 598 | |
| 599 | quint32 KBuildSycoca::calcResourceHash(const QString &resourceSubDir, const QString &filename) |
| 600 | { |
| 601 | quint32 hash = 0; |
| 602 | if (!QDir::isRelativePath(path: filename)) { |
| 603 | return updateHash(file: filename, hash); |
| 604 | } |
| 605 | const QString filePath = resourceSubDir + QLatin1Char('/') + filename; |
| 606 | const QString qrcFilePath = QStringLiteral(":/" ) + filePath; |
| 607 | const QStringList files = |
| 608 | QFileInfo::exists(file: qrcFilePath) ? QStringList{qrcFilePath} : QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation, fileName: filePath); |
| 609 | for (const QString &file : files) { |
| 610 | hash = updateHash(file, hash); |
| 611 | } |
| 612 | if (hash == 0 && !filename.endsWith(s: QLatin1String("update_ksycoca" )) |
| 613 | && !filename.endsWith(s: QLatin1String(".directory" )) // bug? needs investigation from someone who understands the VFolder spec |
| 614 | ) { |
| 615 | if (files.isEmpty()) { |
| 616 | // This can happen if the file was deleted between directory listing and the above locateAll |
| 617 | qCDebug(SYCOCA) << "File not found anymore:" << filename << " -- probably deleted meanwhile" ; |
| 618 | } else { |
| 619 | // This can happen if the file was deleted between locateAll and QFileInfo |
| 620 | qCDebug(SYCOCA) << "File(s) found but not readable (or disappeared meanwhile)" << files; |
| 621 | } |
| 622 | } |
| 623 | return hash; |
| 624 | } |
| 625 | |
| 626 | bool KBuildSycoca::() |
| 627 | { |
| 628 | // Since it's part of the filename, we are 99% sure that the locale and prefixes will match. |
| 629 | const QString current_language = QLocale().bcp47Name(); |
| 630 | const quint32 current_update_sig = KBuildSycoca::calcResourceHash(QStringLiteral("kservices6" ), QStringLiteral("update_ksycoca" )); |
| 631 | const QString current_prefixes = QStandardPaths::standardLocations(type: QStandardPaths::GenericDataLocation).join(sep: QString(QLatin1Char(':'))); |
| 632 | |
| 633 | const KSycocaHeader = KSycocaPrivate::self()->readSycocaHeader(); |
| 634 | Q_ASSERT(!header.prefixes.split(QLatin1Char(':')).contains(QDir::homePath())); |
| 635 | |
| 636 | return (current_update_sig == header.updateSignature) // |
| 637 | && (current_language == header.language) // |
| 638 | && (current_prefixes == header.prefixes) // |
| 639 | && (header.timeStamp != 0); |
| 640 | } |
| 641 | |
| 642 | const char *KBuildSycoca::sycocaPath() |
| 643 | { |
| 644 | return s_cSycocaPath; |
| 645 | } |
| 646 | |
| 647 | #include "moc_kbuildsycoca_p.cpp" |
| 648 | |