File indexing completed on 2024-10-06 09:39:23
0001 /* -*- c++ -*- 0002 SPDX-FileCopyrightText: 2000 Daniel M. Duley <mosfet@kde.org> 0003 SPDX-FileCopyrightText: 2021 Martin Tobias Holmedahl Sandsmark 0004 SPDX-FileCopyrightText: 2022 Méven Car <meven.car@kdemail.net> 0005 0006 SPDX-License-Identifier: BSD-2-Clause 0007 */ 0008 0009 #include "krecentdocument.h" 0010 0011 #include "kiocoredebug.h" 0012 0013 #ifdef Q_OS_WIN 0014 #include <sys/utime.h> 0015 #else 0016 #include <utime.h> 0017 #endif 0018 0019 #include <KDesktopFile> 0020 #include <KService> 0021 #include <QCoreApplication> 0022 #include <QDir> 0023 #include <QLockFile> 0024 #include <QMimeDatabase> 0025 #include <QRegularExpression> 0026 #include <QSaveFile> 0027 #include <QXmlStreamWriter> 0028 #include <kio/global.h> 0029 0030 #include <KConfigGroup> 0031 #include <KSharedConfig> 0032 0033 static QString xbelPath() 0034 { 0035 return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/recently-used.xbel"); 0036 } 0037 0038 static inline QString stringForRecentDocumentGroup(int val) 0039 { 0040 switch (val) { 0041 case KRecentDocument::RecentDocumentGroup::Development: 0042 return QStringLiteral("Development"); 0043 case KRecentDocument::RecentDocumentGroup::Office: 0044 return QStringLiteral("Office"); 0045 case KRecentDocument::RecentDocumentGroup::Database: 0046 return QStringLiteral("Database"); 0047 case KRecentDocument::RecentDocumentGroup::Email: 0048 return QStringLiteral("Email"); 0049 case KRecentDocument::RecentDocumentGroup::Presentation: 0050 return QStringLiteral("Presentation"); 0051 case KRecentDocument::RecentDocumentGroup::Spreadsheet: 0052 return QStringLiteral("Spreadsheet"); 0053 case KRecentDocument::RecentDocumentGroup::WordProcessor: 0054 return QStringLiteral("WordProcessor"); 0055 case KRecentDocument::RecentDocumentGroup::Graphics: 0056 return QStringLiteral("Graphics"); 0057 case KRecentDocument::RecentDocumentGroup::TextEditor: 0058 return QStringLiteral("TextEditor"); 0059 case KRecentDocument::RecentDocumentGroup::Viewer: 0060 return QStringLiteral("Viewer"); 0061 case KRecentDocument::RecentDocumentGroup::Archive: 0062 return QStringLiteral("Archive"); 0063 case KRecentDocument::RecentDocumentGroup::Multimedia: 0064 return QStringLiteral("Multimedia"); 0065 case KRecentDocument::RecentDocumentGroup::Audio: 0066 return QStringLiteral("Audio"); 0067 case KRecentDocument::RecentDocumentGroup::Video: 0068 return QStringLiteral("Video"); 0069 case KRecentDocument::RecentDocumentGroup::Photo: 0070 return QStringLiteral("Photo"); 0071 case KRecentDocument::RecentDocumentGroup::Application: 0072 return QStringLiteral("Application"); 0073 }; 0074 Q_UNREACHABLE(); 0075 } 0076 0077 static KRecentDocument::RecentDocumentGroups groupsForMimeType(const QString mimeType) 0078 { 0079 // simple heuristics, feel free to expand as needed 0080 if (mimeType.startsWith(QStringLiteral("image/"))) { 0081 return KRecentDocument::RecentDocumentGroups{KRecentDocument::RecentDocumentGroup::Graphics}; 0082 } 0083 if (mimeType.startsWith(QStringLiteral("video/"))) { 0084 return KRecentDocument::RecentDocumentGroups{KRecentDocument::RecentDocumentGroup::Video}; 0085 } 0086 if (mimeType.startsWith(QStringLiteral("audio/"))) { 0087 return KRecentDocument::RecentDocumentGroups{KRecentDocument::RecentDocumentGroup::Audio}; 0088 } 0089 return KRecentDocument::RecentDocumentGroups{}; 0090 } 0091 0092 static bool addToXbel(const QUrl &url, const QString &desktopEntryName, KRecentDocument::RecentDocumentGroups groups) 0093 { 0094 QDir().mkpath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)); 0095 0096 // Won't help for GTK applications and whatnot, but we can be good citizens ourselves 0097 QLockFile lockFile(xbelPath() + QLatin1String(".lock")); 0098 lockFile.setStaleLockTime(0); 0099 if (!lockFile.tryLock(100)) { // give it 100ms 0100 qCWarning(KIO_CORE) << "Failed to lock recently used"; 0101 return false; 0102 } 0103 0104 QByteArray existingContent; 0105 QFile input(xbelPath()); 0106 if (input.open(QIODevice::ReadOnly)) { 0107 existingContent = input.readAll(); 0108 } else if (!input.exists()) { // That it doesn't exist is a very uncommon case 0109 qCDebug(KIO_CORE) << input.fileName() << "does not exist, creating new"; 0110 } else { 0111 qCWarning(KIO_CORE) << "Failed to open existing recently used" << input.errorString(); 0112 return false; 0113 } 0114 0115 // Marginally more readable to avoid all the QStringLiteral() spam below 0116 static const QLatin1String xbelTag("xbel"); 0117 static const QLatin1String versionAttribute("version"); 0118 static const QLatin1String expectedVersion("1.0"); 0119 0120 static const QLatin1String applicationsBookmarkTag("bookmark:applications"); 0121 static const QLatin1String applicationBookmarkTag("bookmark:application"); 0122 static const QLatin1String bookmarkTag("bookmark"); 0123 static const QLatin1String infoTag("info"); 0124 static const QLatin1String metadataTag("metadata"); 0125 static const QLatin1String mimeTypeTag("mime:mime-type"); 0126 static const QLatin1String bookmarkGroups("bookmark:groups"); 0127 static const QLatin1String bookmarkGroup("bookmark:group"); 0128 0129 static const QLatin1String nameAttribute("name"); 0130 static const QLatin1String countAttribute("count"); 0131 static const QLatin1String modifiedAttribute("modified"); 0132 static const QLatin1String visitedAttribute("visited"); 0133 static const QLatin1String hrefAttribute("href"); 0134 static const QLatin1String addedAttribute("added"); 0135 static const QLatin1String execAttribute("exec"); 0136 static const QLatin1String ownerAttribute("owner"); 0137 static const QLatin1String ownerValue("http://freedesktop.org"); 0138 static const QLatin1String typeAttribute("type"); 0139 0140 QXmlStreamReader xml(existingContent); 0141 0142 xml.readNextStartElement(); 0143 if (!existingContent.isEmpty()) { 0144 if (xml.name().isEmpty() || xml.name() != xbelTag || !xml.attributes().hasAttribute(versionAttribute)) { 0145 qCDebug(KIO_CORE) << "The recently-used.xbel is not an XBEL file, overwriting."; 0146 } else if (xml.attributes().value(versionAttribute) != expectedVersion) { 0147 qCDebug(KIO_CORE) << "The recently-used.xbel is not an XBEL version 1.0 file but has version: " << xml.attributes().value(versionAttribute) 0148 << ", overwriting."; 0149 } 0150 } 0151 0152 QSaveFile outputFile(xbelPath()); 0153 if (!outputFile.open(QIODevice::WriteOnly)) { 0154 qCWarning(KIO_CORE) << "Failed to recently-used.xbel for writing:" << outputFile.errorString(); 0155 return false; 0156 } 0157 0158 QXmlStreamWriter output(&outputFile); 0159 output.setAutoFormatting(true); 0160 output.setAutoFormattingIndent(2); 0161 output.writeStartDocument(); 0162 output.writeStartElement(xbelTag); 0163 0164 output.writeAttribute(versionAttribute, expectedVersion); 0165 output.writeNamespace(QStringLiteral("http://www.freedesktop.org/standards/desktop-bookmarks"), QStringLiteral("bookmark")); 0166 output.writeNamespace(QStringLiteral("http://www.freedesktop.org/standards/shared-mime-info"), QStringLiteral("mime")); 0167 0168 const QString newUrl = QString::fromLatin1(url.toEncoded()); 0169 const QString currentTimestamp = QDateTime::currentDateTimeUtc().toString(Qt::ISODateWithMs).chopped(1) + QStringLiteral("000Z"); 0170 0171 auto addApplicationTag = [&output, desktopEntryName, currentTimestamp]() { 0172 output.writeEmptyElement(applicationBookmarkTag); 0173 output.writeAttribute(nameAttribute, desktopEntryName); 0174 auto service = KService::serviceByDesktopName(desktopEntryName); 0175 if (service) { 0176 output.writeAttribute(execAttribute, service->exec() + QLatin1String(" %u")); 0177 } else { 0178 output.writeAttribute(execAttribute, QCoreApplication::instance()->applicationName() + QLatin1String(" %u")); 0179 } 0180 output.writeAttribute(modifiedAttribute, currentTimestamp); 0181 output.writeAttribute(countAttribute, QStringLiteral("1")); 0182 }; 0183 0184 bool foundExistingApp = false; 0185 bool inRightBookmark = false; 0186 bool foundMatchingBookmark = false; 0187 bool firstBookmark = true; 0188 while (!xml.atEnd() && !xml.hasError()) { 0189 if (xml.readNext() == QXmlStreamReader::EndElement && xml.name() == xbelTag) { 0190 break; 0191 } 0192 switch (xml.tokenType()) { 0193 case QXmlStreamReader::StartElement: { 0194 QString tagName = xml.qualifiedName().toString(); 0195 QXmlStreamAttributes attributes = xml.attributes(); 0196 0197 if (xml.name() == bookmarkTag) { 0198 foundExistingApp = false; 0199 firstBookmark = false; 0200 0201 inRightBookmark = attributes.value(hrefAttribute) == newUrl; 0202 0203 if (inRightBookmark) { 0204 foundMatchingBookmark = true; 0205 0206 QXmlStreamAttributes newAttributes; 0207 for (const QXmlStreamAttribute &old : attributes) { 0208 if (old.qualifiedName() == modifiedAttribute) { 0209 continue; 0210 } 0211 if (old.qualifiedName() == visitedAttribute) { 0212 continue; 0213 } 0214 newAttributes.append(old); 0215 } 0216 newAttributes.append(modifiedAttribute, currentTimestamp); 0217 newAttributes.append(visitedAttribute, currentTimestamp); 0218 attributes = newAttributes; 0219 } 0220 } 0221 0222 if (inRightBookmark && tagName == applicationBookmarkTag && attributes.value(nameAttribute) == desktopEntryName) { 0223 // case found right bookmark and same application 0224 const int count = attributes.value(countAttribute).toInt(); 0225 0226 QXmlStreamAttributes newAttributes; 0227 for (const QXmlStreamAttribute &old : std::as_const(attributes)) { 0228 if (old.qualifiedName() == countAttribute) { 0229 continue; 0230 } 0231 if (old.qualifiedName() == modifiedAttribute) { 0232 continue; 0233 } 0234 newAttributes.append(old); 0235 } 0236 newAttributes.append(modifiedAttribute, currentTimestamp); 0237 newAttributes.append(countAttribute, QString::number(count + 1)); 0238 attributes = newAttributes; 0239 0240 foundExistingApp = true; 0241 } 0242 0243 output.writeStartElement(tagName); 0244 output.writeAttributes(attributes); 0245 break; 0246 } 0247 case QXmlStreamReader::EndElement: { 0248 QString tagName = xml.qualifiedName().toString(); 0249 if (tagName == applicationsBookmarkTag && inRightBookmark && !foundExistingApp) { 0250 // add an application to the applications already known for the bookmark 0251 addApplicationTag(); 0252 } 0253 output.writeEndElement(); 0254 break; 0255 } 0256 case QXmlStreamReader::Characters: 0257 if (xml.isCDATA()) { 0258 output.writeCDATA(xml.text().toString()); 0259 } else { 0260 output.writeCharacters(xml.text().toString()); 0261 } 0262 break; 0263 case QXmlStreamReader::Comment: 0264 output.writeComment(xml.text().toString()); 0265 break; 0266 case QXmlStreamReader::EndDocument: 0267 qCWarning(KIO_CORE) << "Malformed, got end document before end of xbel" << xml.tokenString() << url; 0268 return false; 0269 default: 0270 qCWarning(KIO_CORE) << "unhandled token" << xml.tokenString() << url; 0271 break; 0272 } 0273 } 0274 0275 if (!foundMatchingBookmark) { 0276 // must create new bookmark tag 0277 if (firstBookmark) { 0278 output.writeCharacters(QStringLiteral("\n")); 0279 } 0280 output.writeCharacters(QStringLiteral(" ")); 0281 output.writeStartElement(bookmarkTag); 0282 0283 output.writeAttribute(hrefAttribute, newUrl); 0284 output.writeAttribute(addedAttribute, currentTimestamp); 0285 output.writeAttribute(modifiedAttribute, currentTimestamp); 0286 output.writeAttribute(visitedAttribute, currentTimestamp); 0287 0288 { 0289 QMimeDatabase mimeDb; 0290 const auto fileMime = mimeDb.mimeTypeForUrl(url).name(); 0291 0292 output.writeStartElement(infoTag); 0293 output.writeStartElement(metadataTag); 0294 output.writeAttribute(ownerAttribute, ownerValue); 0295 0296 output.writeEmptyElement(mimeTypeTag); 0297 output.writeAttribute(typeAttribute, fileMime); 0298 0299 // write groups metadata 0300 if (groups.isEmpty()) { 0301 groups = groupsForMimeType(fileMime); 0302 } 0303 if (!groups.isEmpty()) { 0304 output.writeStartElement(bookmarkGroups); 0305 for (const auto &group : std::as_const(groups)) { 0306 output.writeTextElement(bookmarkGroup, stringForRecentDocumentGroup(group)); 0307 } 0308 // bookmarkGroups 0309 output.writeEndElement(); 0310 } 0311 0312 { 0313 output.writeStartElement(applicationsBookmarkTag); 0314 addApplicationTag(); 0315 // end applicationsBookmarkTag 0316 output.writeEndElement(); 0317 } 0318 0319 // end infoTag 0320 output.writeEndElement(); 0321 // end metadataTag 0322 output.writeEndElement(); 0323 } 0324 0325 // end bookmarkTag 0326 output.writeEndElement(); 0327 } 0328 0329 // end xbelTag 0330 output.writeEndElement(); 0331 0332 // end document 0333 output.writeEndDocument(); 0334 0335 return outputFile.commit(); 0336 } 0337 0338 static QMap<QUrl, QDateTime> xbelRecentlyUsedList() 0339 { 0340 QMap<QUrl, QDateTime> ret; 0341 QFile input(xbelPath()); 0342 if (!input.open(QIODevice::ReadOnly)) { 0343 qCWarning(KIO_CORE) << "Failed to open" << input.fileName() << input.errorString(); 0344 return ret; 0345 } 0346 0347 QXmlStreamReader xml(&input); 0348 xml.readNextStartElement(); 0349 if (xml.name() != QLatin1String("xbel") || xml.attributes().value(QLatin1String("version")) != QLatin1String("1.0")) { 0350 qCWarning(KIO_CORE) << "The file is not an XBEL version 1.0 file."; 0351 return ret; 0352 } 0353 0354 while (!xml.atEnd() && !xml.hasError()) { 0355 if (xml.readNext() != QXmlStreamReader::StartElement || xml.name() != QLatin1String("bookmark")) { 0356 continue; 0357 } 0358 0359 const auto urlString = xml.attributes().value(QLatin1String("href")); 0360 if (urlString.isEmpty()) { 0361 qCInfo(KIO_CORE) << "Invalid bookmark in" << input.fileName(); 0362 continue; 0363 } 0364 const QUrl url = QUrl::fromEncoded(urlString.toLatin1()); 0365 if (url.isLocalFile() && !QFile(url.toLocalFile()).exists()) { 0366 continue; 0367 } 0368 const auto attributes = xml.attributes(); 0369 const QDateTime modified = QDateTime::fromString(attributes.value(QLatin1String("modified")).toString(), Qt::ISODate); 0370 const QDateTime visited = QDateTime::fromString(attributes.value(QLatin1String("visited")).toString(), Qt::ISODate); 0371 const QDateTime added = QDateTime::fromString(attributes.value(QLatin1String("added")).toString(), Qt::ISODate); 0372 if (modified > visited && modified > added) { 0373 ret[url] = modified; 0374 } else if (visited > added) { 0375 ret[url] = visited; 0376 } else { 0377 ret[url] = added; 0378 } 0379 } 0380 0381 if (xml.hasError()) { 0382 qCWarning(KIO_CORE) << "Failed to read" << input.fileName() << xml.errorString(); 0383 } 0384 0385 return ret; 0386 } 0387 0388 QString KRecentDocument::recentDocumentDirectory() 0389 { 0390 // need to change this path, not sure where 0391 return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/RecentDocuments/"); 0392 } 0393 0394 QList<QUrl> KRecentDocument::recentUrls() 0395 { 0396 QMap<QUrl, QDateTime> documents = xbelRecentlyUsedList(); 0397 0398 // TODO KF6: Revisit if we should still continue to fetch the old recentDocuments() 0399 // We need to do it to be compatible with older versions of ourselves, and 0400 // possibly others who for some reason did the same as us, but it could 0401 // possibly also be done as a one-time migration. 0402 const auto recentDocs = recentDocuments(); 0403 for (const QString &pathDesktop : recentDocs) { 0404 const KDesktopFile tmpDesktopFile(pathDesktop); 0405 const QUrl url(tmpDesktopFile.readUrl()); 0406 if (url.isEmpty()) { 0407 continue; 0408 } 0409 const QDateTime lastModified = QFileInfo(pathDesktop).lastModified(); 0410 const QDateTime documentLastModified = documents.value(url); 0411 if (documentLastModified.isValid() && documentLastModified > lastModified) { 0412 continue; 0413 } 0414 documents[url] = lastModified; 0415 } 0416 QList<QUrl> ret = documents.keys(); 0417 std::sort(ret.begin(), ret.end(), [&](const QUrl &doc1, const QUrl &doc2) { 0418 return documents.value(doc1) < documents.value(doc2); 0419 }); 0420 0421 return ret; 0422 } 0423 0424 QStringList KRecentDocument::recentDocuments() 0425 { 0426 // TODO KF6: Consider deprecating this, also see the comment above in recentUrls() 0427 static const auto flags = QDir::Files | QDir::Readable | QDir::Hidden; 0428 QDir d(recentDocumentDirectory(), QStringLiteral("*.desktop"), QDir::Time, flags); 0429 0430 if (!d.exists()) { 0431 d.mkdir(recentDocumentDirectory()); 0432 } 0433 0434 const QStringList list = d.entryList(); 0435 QStringList fullList; 0436 0437 for (const QString &fileName : list) { 0438 QString pathDesktop; 0439 if (fileName.startsWith(QLatin1Char(':'))) { 0440 // See: https://bugreports.qt.io/browse/QTBUG-11223 0441 pathDesktop = KRecentDocument::recentDocumentDirectory() + fileName; 0442 } else { 0443 pathDesktop = d.absoluteFilePath(fileName); 0444 } 0445 KDesktopFile tmpDesktopFile(pathDesktop); 0446 QUrl urlDesktopFile(tmpDesktopFile.desktopGroup().readPathEntry("URL", QString())); 0447 if (urlDesktopFile.isLocalFile() && !QFile(urlDesktopFile.toLocalFile()).exists()) { 0448 d.remove(pathDesktop); 0449 } else { 0450 fullList.append(pathDesktop); 0451 } 0452 } 0453 0454 return fullList; 0455 } 0456 0457 void KRecentDocument::add(const QUrl &url) 0458 { 0459 add(url, RecentDocumentGroups()); 0460 } 0461 0462 void KRecentDocument::add(const QUrl &url, KRecentDocument::RecentDocumentGroups groups) 0463 { 0464 // desktopFileName is in QGuiApplication but we're in KIO Core here 0465 QString desktopEntryName = QCoreApplication::instance()->property("desktopFileName").toString(); 0466 if (desktopEntryName.isEmpty()) { 0467 desktopEntryName = QCoreApplication::applicationName(); 0468 } 0469 add(url, desktopEntryName, groups); 0470 } 0471 0472 void KRecentDocument::add(const QUrl &url, const QString &desktopEntryName) 0473 { 0474 add(url, desktopEntryName, RecentDocumentGroups()); 0475 } 0476 0477 void KRecentDocument::add(const QUrl &url, const QString &desktopEntryName, KRecentDocument::RecentDocumentGroups groups) 0478 { 0479 if (url.isLocalFile() && url.toLocalFile().startsWith(QDir::tempPath())) { 0480 return; // inside tmp resource, do not save 0481 } 0482 0483 if (!addToXbel(url, desktopEntryName, groups)) { 0484 qCWarning(KIO_CORE) << "Failed to add to recently used bookmark file"; 0485 } 0486 0487 QString openStr = url.toDisplayString(); 0488 openStr.replace(QRegularExpression(QStringLiteral("\\$")), QStringLiteral("$$")); // Desktop files with type "Link" are $-variable expanded 0489 0490 // qDebug() << "KRecentDocument::add for " << openStr; 0491 KConfigGroup config = KSharedConfig::openConfig()->group(QByteArray("RecentDocuments")); 0492 bool useRecent = config.readEntry(QStringLiteral("UseRecent"), true); 0493 int maxEntries = config.readEntry(QStringLiteral("MaxEntries"), 10); 0494 bool ignoreHidden = config.readEntry(QStringLiteral("IgnoreHidden"), true); 0495 0496 if (!useRecent || maxEntries <= 0) { 0497 return; 0498 } 0499 if (ignoreHidden && QRegularExpression(QStringLiteral("/\\.")).match(url.toLocalFile()).hasMatch()) { 0500 return; 0501 } 0502 0503 const QString path = recentDocumentDirectory(); 0504 const QString fileName = url.fileName(); 0505 // don't create a file called ".desktop", it will lead to an empty name in kio_recentdocuments 0506 const QString dStr = path + (fileName.isEmpty() ? QStringLiteral("unnamed") : fileName); 0507 0508 QString ddesktop = dStr + QLatin1String(".desktop"); 0509 0510 int i = 1; 0511 // check for duplicates 0512 while (QFile::exists(ddesktop)) { 0513 // see if it points to the same file and application 0514 KDesktopFile tmp(ddesktop); 0515 if (tmp.desktopGroup().readPathEntry("URL", QString()) == url.toDisplayString() 0516 && tmp.desktopGroup().readEntry("X-KDE-LastOpenedWith") == desktopEntryName) { 0517 // Set access and modification time to current time 0518 ::utime(QFile::encodeName(ddesktop).constData(), nullptr); 0519 return; 0520 } 0521 // if not append a (num) to it 0522 ++i; 0523 if (i > maxEntries) { 0524 break; 0525 } 0526 ddesktop = dStr + QStringLiteral("[%1].desktop").arg(i); 0527 } 0528 0529 QDir dir(path); 0530 // check for max entries, delete oldest files if exceeded 0531 const QStringList list = dir.entryList(QDir::Files | QDir::Hidden, QFlags<QDir::SortFlag>(QDir::Time | QDir::Reversed)); 0532 i = list.count(); 0533 if (i > maxEntries - 1) { 0534 QStringList::ConstIterator it; 0535 it = list.begin(); 0536 while (i > maxEntries - 1) { 0537 QFile::remove(dir.absolutePath() + QLatin1Char('/') + (*it)); 0538 --i; 0539 ++it; 0540 } 0541 } 0542 0543 // create the applnk 0544 KDesktopFile configFile(ddesktop); 0545 KConfigGroup conf = configFile.desktopGroup(); 0546 conf.writeEntry("Type", QStringLiteral("Link")); 0547 conf.writePathEntry("URL", openStr); 0548 // If you change the line below, change the test in the above loop 0549 conf.writeEntry("X-KDE-LastOpenedWith", desktopEntryName); 0550 conf.writeEntry("Name", url.fileName()); 0551 conf.writeEntry("Icon", KIO::iconNameForUrl(url)); 0552 } 0553 0554 void KRecentDocument::clear() 0555 { 0556 const QStringList list = recentDocuments(); 0557 QDir dir; 0558 for (const QString &desktopFilePath : list) { 0559 dir.remove(desktopFilePath); 0560 } 0561 QFile(xbelPath()).remove(); 0562 } 0563 0564 int KRecentDocument::maximumItems() 0565 { 0566 KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("RecentDocuments")); 0567 return cg.readEntry(QStringLiteral("MaxEntries"), 10); 0568 }