1 | /* -*- c++ -*- |
2 | SPDX-FileCopyrightText: 2000 Daniel M. Duley <mosfet@kde.org> |
3 | SPDX-FileCopyrightText: 2021 Martin Tobias Holmedahl Sandsmark |
4 | SPDX-FileCopyrightText: 2022 Méven Car <meven.car@kdemail.net> |
5 | |
6 | SPDX-License-Identifier: BSD-2-Clause |
7 | */ |
8 | |
9 | #include "krecentdocument.h" |
10 | |
11 | #include "kiocoredebug.h" |
12 | |
13 | #include <QCoreApplication> |
14 | #include <QDir> |
15 | #include <QDomDocument> |
16 | #include <QLockFile> |
17 | #include <QMimeDatabase> |
18 | #include <QSaveFile> |
19 | #include <QXmlStreamWriter> |
20 | |
21 | #include <KConfigGroup> |
22 | #include <KService> |
23 | #include <KSharedConfig> |
24 | |
25 | static QString xbelPath() |
26 | { |
27 | return QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation) + QLatin1String("/recently-used.xbel" ); |
28 | } |
29 | |
30 | static inline QString stringForRecentDocumentGroup(int val) |
31 | { |
32 | switch (val) { |
33 | case KRecentDocument::RecentDocumentGroup::Development: |
34 | return QStringLiteral("Development" ); |
35 | case KRecentDocument::RecentDocumentGroup::Office: |
36 | return QStringLiteral("Office" ); |
37 | case KRecentDocument::RecentDocumentGroup::Database: |
38 | return QStringLiteral("Database" ); |
39 | case KRecentDocument::RecentDocumentGroup::Email: |
40 | return QStringLiteral("Email" ); |
41 | case KRecentDocument::RecentDocumentGroup::Presentation: |
42 | return QStringLiteral("Presentation" ); |
43 | case KRecentDocument::RecentDocumentGroup::Spreadsheet: |
44 | return QStringLiteral("Spreadsheet" ); |
45 | case KRecentDocument::RecentDocumentGroup::WordProcessor: |
46 | return QStringLiteral("WordProcessor" ); |
47 | case KRecentDocument::RecentDocumentGroup::Graphics: |
48 | return QStringLiteral("Graphics" ); |
49 | case KRecentDocument::RecentDocumentGroup::TextEditor: |
50 | return QStringLiteral("TextEditor" ); |
51 | case KRecentDocument::RecentDocumentGroup::Viewer: |
52 | return QStringLiteral("Viewer" ); |
53 | case KRecentDocument::RecentDocumentGroup::Archive: |
54 | return QStringLiteral("Archive" ); |
55 | case KRecentDocument::RecentDocumentGroup::Multimedia: |
56 | return QStringLiteral("Multimedia" ); |
57 | case KRecentDocument::RecentDocumentGroup::Audio: |
58 | return QStringLiteral("Audio" ); |
59 | case KRecentDocument::RecentDocumentGroup::Video: |
60 | return QStringLiteral("Video" ); |
61 | case KRecentDocument::RecentDocumentGroup::Photo: |
62 | return QStringLiteral("Photo" ); |
63 | case KRecentDocument::RecentDocumentGroup::Application: |
64 | return QStringLiteral("Application" ); |
65 | }; |
66 | Q_UNREACHABLE(); |
67 | } |
68 | |
69 | static KRecentDocument::RecentDocumentGroups groupsForMimeType(const QString mimeType) |
70 | { |
71 | // simple heuristics, feel free to expand as needed |
72 | if (mimeType.startsWith(QStringLiteral("image/" ))) { |
73 | return KRecentDocument::RecentDocumentGroups{KRecentDocument::RecentDocumentGroup::Graphics}; |
74 | } |
75 | if (mimeType.startsWith(QStringLiteral("video/" ))) { |
76 | return KRecentDocument::RecentDocumentGroups{KRecentDocument::RecentDocumentGroup::Video}; |
77 | } |
78 | if (mimeType.startsWith(QStringLiteral("audio/" ))) { |
79 | return KRecentDocument::RecentDocumentGroups{KRecentDocument::RecentDocumentGroup::Audio}; |
80 | } |
81 | return KRecentDocument::RecentDocumentGroups{}; |
82 | } |
83 | |
84 | // Marginally more readable to avoid all the QStringLiteral() spam below |
85 | static const QLatin1String xbelTag("xbel" ); |
86 | static const QLatin1String versionAttribute("version" ); |
87 | static const QLatin1String expectedVersion("1.0" ); |
88 | |
89 | static const QLatin1String applicationsBookmarkTag("bookmark:applications" ); |
90 | static const QLatin1String applicationBookmarkTag("bookmark:application" ); |
91 | static const QLatin1String bookmarkTag("bookmark" ); |
92 | static const QLatin1String infoTag("info" ); |
93 | static const QLatin1String metadataTag("metadata" ); |
94 | static const QLatin1String mimeTypeTag("mime:mime-type" ); |
95 | static const QLatin1String bookmarkGroups("bookmark:groups" ); |
96 | static const QLatin1String bookmarkGroup("bookmark:group" ); |
97 | |
98 | static const QLatin1String nameAttribute("name" ); |
99 | static const QLatin1String countAttribute("count" ); |
100 | static const QLatin1String modifiedAttribute("modified" ); |
101 | static const QLatin1String visitedAttribute("visited" ); |
102 | static const QLatin1String hrefAttribute("href" ); |
103 | static const QLatin1String addedAttribute("added" ); |
104 | static const QLatin1String execAttribute("exec" ); |
105 | static const QLatin1String ownerAttribute("owner" ); |
106 | static const QLatin1String ownerValue("http://freedesktop.org" ); |
107 | static const QLatin1String typeAttribute("type" ); |
108 | |
109 | static bool removeOldestEntries(int &maxEntries) |
110 | { |
111 | QFile input(xbelPath()); |
112 | if (!input.exists()) { |
113 | return true; |
114 | } |
115 | |
116 | // Won't help for GTK applications and whatnot, but we can be good citizens ourselves |
117 | QLockFile lockFile(xbelPath() + QLatin1String(".lock" )); |
118 | lockFile.setStaleLockTime(0); |
119 | if (!lockFile.tryLock(timeout: 100)) { // give it 100ms |
120 | qCWarning(KIO_CORE) << "Failed to lock recently used" ; |
121 | return false; |
122 | } |
123 | |
124 | if (!input.open(flags: QIODevice::ReadOnly)) { |
125 | qCWarning(KIO_CORE) << "Failed to open existing recently used" << input.errorString(); |
126 | return false; |
127 | } |
128 | |
129 | QDomDocument document; |
130 | document.setContent(device: &input); |
131 | input.close(); |
132 | |
133 | auto xbelTags = document.elementsByTagName(tagname: xbelTag); |
134 | if (xbelTags.length() != 1) { |
135 | qCWarning(KIO_CORE) << "Invalid Xbel file" << input.errorString(); |
136 | return false; |
137 | } |
138 | auto xbelElement = document.elementsByTagName(tagname: xbelTag).item(index: 0); |
139 | auto bookmarkList = xbelElement.childNodes(); |
140 | if (bookmarkList.length() <= maxEntries) { |
141 | return true; |
142 | } |
143 | |
144 | QMultiMap<QDateTime, QDomNode> bookmarksByModifiedDate; |
145 | for (int i = 0; i < bookmarkList.length(); ++i) { |
146 | const auto node = bookmarkList.item(index: i); |
147 | const auto modifiedString = node.attributes().namedItem(name: modifiedAttribute); |
148 | const auto modifiedTime = QDateTime::fromString(string: modifiedString.nodeValue(), format: Qt::ISODate); |
149 | |
150 | bookmarksByModifiedDate.insert(key: modifiedTime, value: node); |
151 | } |
152 | |
153 | int i = 0; |
154 | // entries are traversed in ascending key order |
155 | for (auto entry = bookmarksByModifiedDate.keyValueBegin(); entry != bookmarksByModifiedDate.keyValueEnd(); ++entry) { |
156 | // only keep the maxEntries last nodes |
157 | if (bookmarksByModifiedDate.size() - i > maxEntries) { |
158 | xbelElement.removeChild(oldChild: entry->second); |
159 | } |
160 | ++i; |
161 | } |
162 | |
163 | if (input.open(flags: QIODevice::WriteOnly) && input.write(data: document.toByteArray(2)) != -1) { |
164 | input.close(); |
165 | return true; |
166 | } |
167 | input.close(); |
168 | return false; |
169 | } |
170 | |
171 | static bool addToXbel(const QUrl &url, const QString &desktopEntryName, KRecentDocument::RecentDocumentGroups groups, int maxEntries, bool ignoreHidden) |
172 | { |
173 | if (!QDir().mkpath(dirPath: QStandardPaths::writableLocation(type: QStandardPaths::GenericDataLocation))) { |
174 | qCWarning(KIO_CORE) << "Could not create GenericDataLocation" ; |
175 | return false; |
176 | } |
177 | |
178 | // Won't help for GTK applications and whatnot, but we can be good citizens ourselves |
179 | QLockFile lockFile(xbelPath() + QLatin1String(".lock" )); |
180 | lockFile.setStaleLockTime(0); |
181 | if (!lockFile.tryLock(timeout: 100)) { // give it 100ms |
182 | qCWarning(KIO_CORE) << "Failed to lock recently used" ; |
183 | return false; |
184 | } |
185 | |
186 | QByteArray existingContent; |
187 | QFile input(xbelPath()); |
188 | if (input.open(flags: QIODevice::ReadOnly)) { |
189 | existingContent = input.readAll(); |
190 | } else if (!input.exists()) { // That it doesn't exist is a very uncommon case |
191 | qCDebug(KIO_CORE) << input.fileName() << "does not exist, creating new" ; |
192 | } else { |
193 | qCWarning(KIO_CORE) << "Failed to open existing recently used" << input.errorString(); |
194 | return false; |
195 | } |
196 | |
197 | QXmlStreamReader xml(existingContent); |
198 | |
199 | xml.readNextStartElement(); |
200 | if (!existingContent.isEmpty()) { |
201 | if (xml.name().isEmpty() || xml.name() != xbelTag || !xml.attributes().hasAttribute(qualifiedName: versionAttribute)) { |
202 | qCDebug(KIO_CORE) << "The recently-used.xbel is not an XBEL file, overwriting." ; |
203 | } else if (xml.attributes().value(qualifiedName: versionAttribute) != expectedVersion) { |
204 | qCDebug(KIO_CORE) << "The recently-used.xbel is not an XBEL version 1.0 file but has version: " << xml.attributes().value(qualifiedName: versionAttribute) |
205 | << ", overwriting." ; |
206 | } |
207 | } |
208 | |
209 | QSaveFile outputFile(xbelPath()); |
210 | if (!outputFile.open(flags: QIODevice::WriteOnly)) { |
211 | qCWarning(KIO_CORE) << "Failed to recently-used.xbel for writing:" << outputFile.errorString(); |
212 | return false; |
213 | } |
214 | |
215 | QXmlStreamWriter output(&outputFile); |
216 | output.setAutoFormatting(true); |
217 | output.setAutoFormattingIndent(2); |
218 | output.writeStartDocument(); |
219 | output.writeStartElement(qualifiedName: xbelTag); |
220 | |
221 | output.writeAttribute(qualifiedName: versionAttribute, value: expectedVersion); |
222 | output.writeNamespace(QStringLiteral("http://www.freedesktop.org/standards/desktop-bookmarks" ), QStringLiteral("bookmark" )); |
223 | output.writeNamespace(QStringLiteral("http://www.freedesktop.org/standards/shared-mime-info" ), QStringLiteral("mime" )); |
224 | |
225 | const QString newUrl = QString::fromLatin1(ba: url.toEncoded()); |
226 | const QString currentTimestamp = QDateTime::currentDateTimeUtc().toString(format: Qt::ISODateWithMs).chopped(n: 1) + QStringLiteral("000Z" ); |
227 | |
228 | auto addApplicationTag = [&output, desktopEntryName, currentTimestamp, url]() { |
229 | output.writeEmptyElement(qualifiedName: applicationBookmarkTag); |
230 | output.writeAttribute(qualifiedName: nameAttribute, value: desktopEntryName); |
231 | auto service = KService::serviceByDesktopName(name: desktopEntryName); |
232 | QString exec; |
233 | bool shouldAddParameter = true; |
234 | if (service) { |
235 | exec = service->exec(); |
236 | exec.replace(before: QLatin1String(" %U" ), after: QLatin1String(" %u" )); |
237 | exec.replace(before: QLatin1String(" %F" ), after: QLatin1String(" %f" )); |
238 | shouldAddParameter = !exec.contains(s: QLatin1String(" %u" )) && !exec.contains(s: QLatin1String(" %f" )); |
239 | } else { |
240 | exec = QCoreApplication::instance()->applicationName(); |
241 | } |
242 | if (shouldAddParameter) { |
243 | if (url.isLocalFile()) { |
244 | exec += QLatin1String(" %f" ); |
245 | } else { |
246 | exec += QLatin1String(" %u" ); |
247 | } |
248 | } |
249 | output.writeAttribute(qualifiedName: execAttribute, value: exec); |
250 | output.writeAttribute(qualifiedName: modifiedAttribute, value: currentTimestamp); |
251 | output.writeAttribute(qualifiedName: countAttribute, QStringLiteral("1" )); |
252 | }; |
253 | |
254 | bool foundExistingApp = false; |
255 | bool inRightBookmark = false; |
256 | bool foundMatchingBookmark = false; |
257 | bool firstBookmark = true; |
258 | int nbEntries = 0; |
259 | while (!xml.atEnd() && !xml.hasError()) { |
260 | if (xml.readNext() == QXmlStreamReader::EndElement && xml.name() == xbelTag) { |
261 | break; |
262 | } |
263 | switch (xml.tokenType()) { |
264 | case QXmlStreamReader::StartElement: { |
265 | const QStringView tagName = xml.qualifiedName(); |
266 | QXmlStreamAttributes attributes = xml.attributes(); |
267 | |
268 | if (tagName == bookmarkTag) { |
269 | foundExistingApp = false; |
270 | firstBookmark = false; |
271 | |
272 | const QStringView hrefValue = attributes.value(qualifiedName: hrefAttribute); |
273 | inRightBookmark = hrefValue == newUrl; |
274 | |
275 | // remove hidden files if some were added by GTK |
276 | if (ignoreHidden && hrefValue.contains(s: QLatin1String("/." ))) { |
277 | xml.skipCurrentElement(); |
278 | break; |
279 | } |
280 | |
281 | if (inRightBookmark) { |
282 | foundMatchingBookmark = true; |
283 | |
284 | QXmlStreamAttributes newAttributes; |
285 | for (const QXmlStreamAttribute &old : attributes) { |
286 | if (old.name() == modifiedAttribute) { |
287 | continue; |
288 | } |
289 | if (old.name() == visitedAttribute) { |
290 | continue; |
291 | } |
292 | newAttributes.append(t: old); |
293 | } |
294 | newAttributes.append(qualifiedName: modifiedAttribute, value: currentTimestamp); |
295 | newAttributes.append(qualifiedName: visitedAttribute, value: currentTimestamp); |
296 | attributes = newAttributes; |
297 | } |
298 | |
299 | nbEntries += 1; |
300 | } |
301 | |
302 | else if (inRightBookmark && tagName == applicationBookmarkTag && attributes.value(qualifiedName: nameAttribute) == desktopEntryName) { |
303 | // case found right bookmark and same application |
304 | const int count = attributes.value(qualifiedName: countAttribute).toInt(); |
305 | |
306 | QXmlStreamAttributes newAttributes; |
307 | for (const QXmlStreamAttribute &old : std::as_const(t&: attributes)) { |
308 | if (old.name() == countAttribute) { |
309 | continue; |
310 | } |
311 | if (old.name() == modifiedAttribute) { |
312 | continue; |
313 | } |
314 | newAttributes.append(t: old); |
315 | } |
316 | newAttributes.append(qualifiedName: modifiedAttribute, value: currentTimestamp); |
317 | newAttributes.append(qualifiedName: countAttribute, value: QString::number(count + 1)); |
318 | attributes = newAttributes; |
319 | |
320 | foundExistingApp = true; |
321 | } |
322 | |
323 | output.writeStartElement(qualifiedName: tagName.toString()); |
324 | output.writeAttributes(attributes); |
325 | break; |
326 | } |
327 | case QXmlStreamReader::EndElement: { |
328 | const QStringView tagName = xml.qualifiedName(); |
329 | if (tagName == applicationsBookmarkTag && inRightBookmark && !foundExistingApp) { |
330 | // add an application to the applications already known for the bookmark |
331 | addApplicationTag(); |
332 | } |
333 | output.writeEndElement(); |
334 | break; |
335 | } |
336 | case QXmlStreamReader::Characters: |
337 | if (xml.isCDATA()) { |
338 | output.writeCDATA(text: xml.text().toString()); |
339 | } else { |
340 | output.writeCharacters(text: xml.text().toString()); |
341 | } |
342 | break; |
343 | case QXmlStreamReader::Comment: |
344 | output.writeComment(text: xml.text().toString()); |
345 | break; |
346 | case QXmlStreamReader::EndDocument: |
347 | qCWarning(KIO_CORE) << "Malformed, got end document before end of xbel" << xml.tokenString() << url; |
348 | return false; |
349 | default: |
350 | qCWarning(KIO_CORE) << "unhandled token" << xml.tokenString() << url; |
351 | break; |
352 | } |
353 | } |
354 | |
355 | if (!foundMatchingBookmark) { |
356 | // must create new bookmark tag |
357 | if (firstBookmark) { |
358 | output.writeCharacters(QStringLiteral("\n" )); |
359 | } |
360 | output.writeCharacters(QStringLiteral(" " )); |
361 | output.writeStartElement(qualifiedName: bookmarkTag); |
362 | |
363 | output.writeAttribute(qualifiedName: hrefAttribute, value: newUrl); |
364 | output.writeAttribute(qualifiedName: addedAttribute, value: currentTimestamp); |
365 | output.writeAttribute(qualifiedName: modifiedAttribute, value: currentTimestamp); |
366 | output.writeAttribute(qualifiedName: visitedAttribute, value: currentTimestamp); |
367 | |
368 | { |
369 | QMimeDatabase mimeDb; |
370 | const auto fileMime = mimeDb.mimeTypeForUrl(url).name(); |
371 | |
372 | output.writeStartElement(qualifiedName: infoTag); |
373 | output.writeStartElement(qualifiedName: metadataTag); |
374 | output.writeAttribute(qualifiedName: ownerAttribute, value: ownerValue); |
375 | |
376 | output.writeEmptyElement(qualifiedName: mimeTypeTag); |
377 | output.writeAttribute(qualifiedName: typeAttribute, value: fileMime); |
378 | |
379 | // write groups metadata |
380 | if (groups.isEmpty()) { |
381 | groups = groupsForMimeType(mimeType: fileMime); |
382 | } |
383 | if (!groups.isEmpty()) { |
384 | output.writeStartElement(qualifiedName: bookmarkGroups); |
385 | for (const auto &group : std::as_const(t&: groups)) { |
386 | output.writeTextElement(qualifiedName: bookmarkGroup, text: stringForRecentDocumentGroup(val: group)); |
387 | } |
388 | // bookmarkGroups |
389 | output.writeEndElement(); |
390 | } |
391 | |
392 | { |
393 | output.writeStartElement(qualifiedName: applicationsBookmarkTag); |
394 | addApplicationTag(); |
395 | // end applicationsBookmarkTag |
396 | output.writeEndElement(); |
397 | } |
398 | |
399 | // end infoTag |
400 | output.writeEndElement(); |
401 | // end metadataTag |
402 | output.writeEndElement(); |
403 | } |
404 | |
405 | // end bookmarkTag |
406 | output.writeEndElement(); |
407 | } |
408 | |
409 | // end xbelTag |
410 | output.writeEndElement(); |
411 | |
412 | // end document |
413 | output.writeEndDocument(); |
414 | |
415 | if (outputFile.commit()) { |
416 | lockFile.unlock(); |
417 | // tolerate 10 more entries than threshold to limit overhead of cleaning old data |
418 | return nbEntries - maxEntries > 10 || removeOldestEntries(maxEntries); |
419 | } |
420 | return false; |
421 | } |
422 | |
423 | static QMap<QUrl, QDateTime> xbelRecentlyUsedList() |
424 | { |
425 | QMap<QUrl, QDateTime> ret; |
426 | QFile input(xbelPath()); |
427 | if (!input.open(flags: QIODevice::ReadOnly)) { |
428 | qCWarning(KIO_CORE) << "Failed to open" << input.fileName() << input.errorString(); |
429 | return ret; |
430 | } |
431 | |
432 | QXmlStreamReader xml(&input); |
433 | xml.readNextStartElement(); |
434 | if (xml.name() != QLatin1String("xbel" ) || xml.attributes().value(qualifiedName: QLatin1String("version" )) != QLatin1String("1.0" )) { |
435 | qCWarning(KIO_CORE) << "The file is not an XBEL version 1.0 file." ; |
436 | return ret; |
437 | } |
438 | |
439 | while (!xml.atEnd() && !xml.hasError()) { |
440 | if (xml.readNext() != QXmlStreamReader::StartElement || xml.name() != QLatin1String("bookmark" )) { |
441 | continue; |
442 | } |
443 | |
444 | const auto urlString = xml.attributes().value(qualifiedName: QLatin1String("href" )); |
445 | if (urlString.isEmpty()) { |
446 | qCInfo(KIO_CORE) << "Invalid bookmark in" << input.fileName(); |
447 | continue; |
448 | } |
449 | const QUrl url = QUrl::fromEncoded(url: urlString.toLatin1()); |
450 | if (url.isLocalFile() && !QFile(url.toLocalFile()).exists()) { |
451 | continue; |
452 | } |
453 | const auto attributes = xml.attributes(); |
454 | const QDateTime modified = QDateTime::fromString(string: attributes.value(qualifiedName: QLatin1String("modified" )).toString(), format: Qt::ISODate); |
455 | const QDateTime visited = QDateTime::fromString(string: attributes.value(qualifiedName: QLatin1String("visited" )).toString(), format: Qt::ISODate); |
456 | const QDateTime added = QDateTime::fromString(string: attributes.value(qualifiedName: QLatin1String("added" )).toString(), format: Qt::ISODate); |
457 | if (modified > visited && modified > added) { |
458 | ret[url] = modified; |
459 | } else if (visited > added) { |
460 | ret[url] = visited; |
461 | } else { |
462 | ret[url] = added; |
463 | } |
464 | } |
465 | |
466 | if (xml.hasError()) { |
467 | qCWarning(KIO_CORE) << "Failed to read" << input.fileName() << xml.errorString(); |
468 | } |
469 | |
470 | return ret; |
471 | } |
472 | |
473 | QList<QUrl> KRecentDocument::recentUrls() |
474 | { |
475 | QMap<QUrl, QDateTime> documents = xbelRecentlyUsedList(); |
476 | |
477 | QList<QUrl> ret = documents.keys(); |
478 | std::sort(first: ret.begin(), last: ret.end(), comp: [&](const QUrl &doc1, const QUrl &doc2) { |
479 | return documents.value(key: doc1) < documents.value(key: doc2); |
480 | }); |
481 | |
482 | return ret; |
483 | } |
484 | |
485 | void KRecentDocument::add(const QUrl &url) |
486 | { |
487 | add(url, groups: RecentDocumentGroups()); |
488 | } |
489 | |
490 | void KRecentDocument::add(const QUrl &url, KRecentDocument::RecentDocumentGroups groups) |
491 | { |
492 | // desktopFileName is in QGuiApplication but we're in KIO Core here |
493 | QString desktopEntryName = QCoreApplication::instance()->property(name: "desktopFileName" ).toString(); |
494 | if (desktopEntryName.isEmpty()) { |
495 | desktopEntryName = QCoreApplication::applicationName(); |
496 | } |
497 | add(url, desktopEntryName, groups); |
498 | } |
499 | |
500 | void KRecentDocument::add(const QUrl &url, const QString &desktopEntryName) |
501 | { |
502 | add(url, desktopEntryName, groups: RecentDocumentGroups()); |
503 | } |
504 | |
505 | void KRecentDocument::add(const QUrl &url, const QString &desktopEntryName, KRecentDocument::RecentDocumentGroups groups) |
506 | { |
507 | if (url.isLocalFile() && url.toLocalFile().startsWith(s: QDir::tempPath())) { |
508 | return; // inside tmp resource, do not save |
509 | } |
510 | |
511 | // qDebug() << "KRecentDocument::add for " << openStr; |
512 | KConfigGroup config = KSharedConfig::openConfig()->group(QStringLiteral("RecentDocuments" )); |
513 | bool useRecent = config.readEntry(QStringLiteral("UseRecent" ), aDefault: true); |
514 | int maxEntries = config.readEntry(QStringLiteral("MaxEntries" ), aDefault: 300); |
515 | bool ignoreHidden = config.readEntry(QStringLiteral("IgnoreHidden" ), aDefault: true); |
516 | |
517 | if (!useRecent || maxEntries == 0) { |
518 | clear(); |
519 | return; |
520 | } |
521 | if (ignoreHidden && url.toLocalFile().contains(s: QLatin1String("/." ))) { |
522 | return; |
523 | } |
524 | |
525 | if (!addToXbel(url, desktopEntryName, groups, maxEntries, ignoreHidden)) { |
526 | qCWarning(KIO_CORE) << "Failed to add to recently used bookmark file" ; |
527 | } |
528 | } |
529 | |
530 | void KRecentDocument::clear() |
531 | { |
532 | QFile(xbelPath()).remove(); |
533 | } |
534 | |
535 | int KRecentDocument::maximumItems() |
536 | { |
537 | KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("RecentDocuments" )); |
538 | return cg.readEntry(QStringLiteral("MaxEntries" ), aDefault: 10); |
539 | } |
540 | |