KIO

krecentdocument.cpp
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
25using namespace Qt::StringLiterals;
26
27static QString xbelPath()
28{
30}
31
32static inline QString stringForRecentDocumentGroup(int val)
33{
34 switch (val) {
35 case KRecentDocument::RecentDocumentGroup::Development:
36 return "Development"_L1;
37 case KRecentDocument::RecentDocumentGroup::Office:
38 return "Office"_L1;
39 case KRecentDocument::RecentDocumentGroup::Database:
40 return "Database"_L1;
41 case KRecentDocument::RecentDocumentGroup::Email:
42 return "Email"_L1;
43 case KRecentDocument::RecentDocumentGroup::Presentation:
44 return "Presentation"_L1;
45 case KRecentDocument::RecentDocumentGroup::Spreadsheet:
46 return "Spreadsheet"_L1;
47 case KRecentDocument::RecentDocumentGroup::WordProcessor:
48 return "WordProcessor"_L1;
49 case KRecentDocument::RecentDocumentGroup::Graphics:
50 return "Graphics"_L1;
51 case KRecentDocument::RecentDocumentGroup::TextEditor:
52 return "TextEditor"_L1;
53 case KRecentDocument::RecentDocumentGroup::Viewer:
54 return "Viewer"_L1;
55 case KRecentDocument::RecentDocumentGroup::Archive:
56 return "Archive"_L1;
57 case KRecentDocument::RecentDocumentGroup::Multimedia:
58 return "Multimedia"_L1;
59 case KRecentDocument::RecentDocumentGroup::Audio:
60 return "Audio"_L1;
61 case KRecentDocument::RecentDocumentGroup::Video:
62 return "Video"_L1;
63 case KRecentDocument::RecentDocumentGroup::Photo:
64 return "Photo"_L1;
65 case KRecentDocument::RecentDocumentGroup::Application:
66 return "Application"_L1;
67 };
68 Q_UNREACHABLE();
69}
70
71static KRecentDocument::RecentDocumentGroups groupsForMimeType(const QString mimeType)
72{
73 // simple heuristics, feel free to expand as needed
74 if (mimeType.startsWith("image/"_L1)) {
75 return KRecentDocument::RecentDocumentGroups{KRecentDocument::RecentDocumentGroup::Graphics};
76 }
77 if (mimeType.startsWith("video/"_L1)) {
78 return KRecentDocument::RecentDocumentGroups{KRecentDocument::RecentDocumentGroup::Video};
79 }
80 if (mimeType.startsWith("audio/"_L1)) {
81 return KRecentDocument::RecentDocumentGroups{KRecentDocument::RecentDocumentGroup::Audio};
82 }
84}
85
86// Marginally more readable to avoid all the QStringLiteral() spam below
87static const QLatin1String xbelTag("xbel");
88static const QLatin1String versionAttribute("version");
89static const QLatin1String expectedVersion("1.0");
90
91static const QLatin1String applicationsBookmarkTag("bookmark:applications");
92static const QLatin1String applicationBookmarkTag("bookmark:application");
93static const QLatin1String bookmarkTag("bookmark");
94static const QLatin1String infoTag("info");
95static const QLatin1String metadataTag("metadata");
96static const QLatin1String mimeTypeTag("mime:mime-type");
97static const QLatin1String bookmarkGroups("bookmark:groups");
98static const QLatin1String bookmarkGroup("bookmark:group");
99
100static const QLatin1String nameAttribute("name");
101static const QLatin1String countAttribute("count");
102static const QLatin1String modifiedAttribute("modified");
103static const QLatin1String visitedAttribute("visited");
104static const QLatin1String hrefAttribute("href");
105static const QLatin1String addedAttribute("added");
106static const QLatin1String execAttribute("exec");
107static const QLatin1String ownerAttribute("owner");
108static const QLatin1String ownerValue("http://freedesktop.org");
109static const QLatin1String typeAttribute("type");
110
111static bool removeOldestEntries(int &maxEntries)
112{
113 QFile input(xbelPath());
114 if (!input.exists()) {
115 return true;
116 }
117
118 // Won't help for GTK applications and whatnot, but we can be good citizens ourselves
119 QLockFile lockFile(xbelPath() + QLatin1String(".lock"));
120 lockFile.setStaleLockTime(0);
121 if (!lockFile.tryLock(100)) { // give it 100ms
122 qCWarning(KIO_CORE) << "Failed to lock recently used";
123 return false;
124 }
125
126 if (!input.open(QIODevice::ReadOnly)) {
127 qCWarning(KIO_CORE) << "Failed to open existing recently used" << input.errorString();
128 return false;
129 }
130
131 QDomDocument document;
132 document.setContent(&input);
133 input.close();
134
135 auto xbelTags = document.elementsByTagName(xbelTag);
136 if (xbelTags.length() != 1) {
137 qCWarning(KIO_CORE) << "Invalid Xbel file, missing xbel element";
138 return false;
139 }
140 auto xbelElement = xbelTags.item(0);
141 auto bookmarkList = xbelElement.childNodes();
142 if (bookmarkList.length() <= maxEntries) {
143 return true;
144 }
145
146 QMultiMap<QDateTime, QDomNode> bookmarksByModifiedDate;
147 for (int i = 0; i < bookmarkList.length(); ++i) {
148 const auto node = bookmarkList.item(i);
149 const auto modifiedString = node.attributes().namedItem(modifiedAttribute);
150 const auto modifiedTime = QDateTime::fromString(modifiedString.nodeValue(), Qt::ISODate);
151
152 bookmarksByModifiedDate.insert(modifiedTime, node);
153 }
154
155 int i = 0;
156 // entries are traversed in ascending key order
157 for (auto entry = bookmarksByModifiedDate.keyValueBegin(); entry != bookmarksByModifiedDate.keyValueEnd(); ++entry) {
158 // only keep the maxEntries last nodes
159 if (bookmarksByModifiedDate.size() - i > maxEntries) {
160 xbelElement.removeChild(entry->second);
161 }
162 ++i;
163 }
164
165 if (input.open(QIODevice::WriteOnly) && input.write(document.toByteArray(2)) != -1) {
166 return true;
167 }
168 return false;
169}
170
171static bool addToXbel(const QUrl &url, const QString &desktopEntryName, KRecentDocument::RecentDocumentGroups groups, int maxEntries, bool ignoreHidden)
172{
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(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(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(versionAttribute)) {
202 qCDebug(KIO_CORE) << "The recently-used.xbel is not an XBEL file, overwriting.";
203 } else if (xml.attributes().value(versionAttribute) != expectedVersion) {
204 qCDebug(KIO_CORE) << "The recently-used.xbel is not an XBEL version 1.0 file but has version: " << xml.attributes().value(versionAttribute)
205 << ", overwriting.";
206 }
207 }
208
209 QSaveFile outputFile(xbelPath());
210 if (!outputFile.open(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(xbelTag);
220
221 output.writeAttribute(versionAttribute, expectedVersion);
222 output.writeNamespace("http://www.freedesktop.org/standards/desktop-bookmarks"_L1, bookmarkTag);
223 output.writeNamespace("http://www.freedesktop.org/standards/shared-mime-info"_L1, "mime"_L1);
224
225 const QString newUrl = QString::fromLatin1(url.toEncoded());
226 const QString currentTimestamp = QDateTime::currentDateTimeUtc().toString(Qt::ISODateWithMs).chopped(1) + "000Z"_L1;
227
228 auto addApplicationTag = [&output, desktopEntryName, currentTimestamp, url]() {
229 output.writeEmptyElement(applicationBookmarkTag);
230 output.writeAttribute(nameAttribute, desktopEntryName);
231 auto service = KService::serviceByDesktopName(desktopEntryName);
232 QString exec;
233 bool shouldAddParameter = true;
234 if (service) {
235 exec = service->exec();
236 exec.replace(QLatin1String(" %U"), QLatin1String(" %u"));
237 exec.replace(QLatin1String(" %F"), QLatin1String(" %f"));
238 shouldAddParameter = !exec.contains(QLatin1String(" %u")) && !exec.contains(QLatin1String(" %f"));
239 } else {
241 }
242 if (shouldAddParameter) {
243 if (url.isLocalFile()) {
244 exec += QLatin1String(" %f");
245 } else {
246 exec += QLatin1String(" %u");
247 }
248 }
249 output.writeAttribute(execAttribute, exec);
250 output.writeAttribute(modifiedAttribute, currentTimestamp);
251 output.writeAttribute(countAttribute, "1"_L1);
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()) {
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(hrefAttribute);
273 inRightBookmark = hrefValue == newUrl;
274
275 // remove hidden files if some were added by GTK
276 if (ignoreHidden && hrefValue.contains(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(old);
293 }
294 newAttributes.append(modifiedAttribute, currentTimestamp);
295 newAttributes.append(visitedAttribute, currentTimestamp);
296 attributes = newAttributes;
297 }
298
299 nbEntries += 1;
300 }
301
302 else if (inRightBookmark && tagName == applicationBookmarkTag && attributes.value(nameAttribute) == desktopEntryName) {
303 // case found right bookmark and same application
304 const int count = attributes.value(countAttribute).toInt();
305
306 QXmlStreamAttributes newAttributes;
307 for (const QXmlStreamAttribute &old : std::as_const(attributes)) {
308 if (old.name() == countAttribute) {
309 continue;
310 }
311 if (old.name() == modifiedAttribute) {
312 continue;
313 }
314 newAttributes.append(old);
315 }
316 newAttributes.append(modifiedAttribute, currentTimestamp);
317 newAttributes.append(countAttribute, QString::number(count + 1));
318 attributes = newAttributes;
319
320 foundExistingApp = true;
321 }
322
323 output.writeStartElement(tagName.toString());
324 output.writeAttributes(attributes);
325 break;
326 }
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 }
337 if (xml.isCDATA()) {
338 output.writeCDATA(xml.text().toString());
339 } else {
340 output.writeCharacters(xml.text().toString());
341 }
342 break;
344 output.writeComment(xml.text().toString());
345 break;
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("\n"_L1);
359 }
360 output.writeCharacters(" "_L1);
361 output.writeStartElement(bookmarkTag);
362
363 output.writeAttribute(hrefAttribute, newUrl);
364 output.writeAttribute(addedAttribute, currentTimestamp);
365 output.writeAttribute(modifiedAttribute, currentTimestamp);
366 output.writeAttribute(visitedAttribute, currentTimestamp);
367
368 {
369 QMimeDatabase mimeDb;
370 const auto fileMime = mimeDb.mimeTypeForUrl(url).name();
371
372 output.writeStartElement(infoTag);
373 output.writeStartElement(metadataTag);
374 output.writeAttribute(ownerAttribute, ownerValue);
375
376 output.writeEmptyElement(mimeTypeTag);
377 output.writeAttribute(typeAttribute, fileMime);
378
379 // write groups metadata
380 if (groups.isEmpty()) {
381 groups = groupsForMimeType(fileMime);
382 }
383 if (!groups.isEmpty()) {
384 output.writeStartElement(bookmarkGroups);
385 for (const auto &group : std::as_const(groups)) {
386 output.writeTextElement(bookmarkGroup, stringForRecentDocumentGroup(group));
387 }
388 // bookmarkGroups
389 output.writeEndElement();
390 }
391
392 {
393 output.writeStartElement(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
423static QMap<QUrl, QDateTime> xbelRecentlyUsedList()
424{
426 QFile input(xbelPath());
427 if (!input.open(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(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(QLatin1String("href"));
445 if (urlString.isEmpty()) {
446 qCInfo(KIO_CORE) << "Invalid bookmark in" << input.fileName();
447 continue;
448 }
449 const QUrl url = QUrl::fromEncoded(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(attributes.value(QLatin1String("modified")).toString(), Qt::ISODate);
455 const QDateTime visited = QDateTime::fromString(attributes.value(QLatin1String("visited")).toString(), Qt::ISODate);
456 const QDateTime added = QDateTime::fromString(attributes.value(QLatin1String("added")).toString(), 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
474{
475 QMap<QUrl, QDateTime> documents = xbelRecentlyUsedList();
476
477 QList<QUrl> ret = documents.keys();
478 std::sort(ret.begin(), ret.end(), [&](const QUrl &doc1, const QUrl &doc2) {
479 return documents.value(doc1) < documents.value(doc2);
480 });
481
482 return ret;
483}
484
486{
488}
489
491{
492 // desktopFileName is in QGuiApplication but we're in KIO Core here
493 QString desktopEntryName = QCoreApplication::instance()->property("desktopFileName").toString();
494 if (desktopEntryName.isEmpty()) {
495 desktopEntryName = QCoreApplication::applicationName();
496 }
497 add(url, desktopEntryName, groups);
498}
499
500void KRecentDocument::add(const QUrl &url, const QString &desktopEntryName)
501{
502 add(url, desktopEntryName, RecentDocumentGroups());
503}
504
505void KRecentDocument::add(const QUrl &url, const QString &desktopEntryName, KRecentDocument::RecentDocumentGroups groups)
506{
507 if (url.isLocalFile() && url.toLocalFile().startsWith(QDir::tempPath())) {
508 return; // inside tmp resource, do not save
509 }
510
511 // qDebug() << "KRecentDocument::add for " << openStr;
512 KConfigGroup config = KSharedConfig::openConfig()->group("RecentDocuments"_L1);
513 bool useRecent = config.readEntry("UseRecent"_L1, true);
514 int maxEntries = config.readEntry("MaxEntries"_L1, 300);
515 bool ignoreHidden = config.readEntry("IgnoreHidden"_L1, true);
516
517 if (!useRecent || maxEntries == 0) {
518 clear();
519 return;
520 }
521 if (ignoreHidden && url.toLocalFile().contains(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
531{
532 QFile(xbelPath()).remove();
533}
534
536{
537 KConfigGroup cg(KSharedConfig::openConfig(), "RecentDocuments"_L1);
538 return cg.readEntry("MaxEntries"_L1, 300);
539}
540
542{
543 QFile input(xbelPath());
544 if (!input.exists()) {
545 return;
546 }
547
548 // Won't help for GTK applications and whatnot, but we can be good citizens ourselves
549 QLockFile lockFile(xbelPath() + QLatin1String(".lock"));
550 lockFile.setStaleLockTime(0);
551 if (!lockFile.tryLock(100)) { // give it 100ms
552 qCWarning(KIO_CORE) << "Failed to lock recently used";
553 return;
554 }
555
556 if (!input.open(QIODevice::ReadOnly)) {
557 qCWarning(KIO_CORE) << "Failed to open existing recently used" << input.errorString();
558 return;
559 }
560
561 QDomDocument document;
562 document.setContent(&input);
563 input.close();
564
565 auto xbelTags = document.elementsByTagName(xbelTag);
566 if (xbelTags.length() != 1) {
567 qCWarning(KIO_CORE) << "Invalid Xbel file, missing xbel elememt";
568 return;
569 }
570 auto xbelElement = xbelTags.item(0);
571 auto bookmarkList = xbelElement.childNodes();
572
573 bool fileChanged = false;
574 for (int i = 0; i < bookmarkList.length(); ++i) {
575 const auto node = bookmarkList.item(i);
576
577 const auto hrefValue = node.attributes().namedItem(hrefAttribute);
578 if (!hrefValue.isAttr() || hrefValue.nodeValue().isEmpty()) {
579 qCInfo(KIO_CORE) << "Invalid bookmark in" << input.fileName() << "invalid href attribute";
580 continue;
581 }
582
583 const QUrl hrefUrl = QUrl::fromEncoded(hrefValue.nodeValue().toLatin1());
584 if (hrefUrl == url) {
585 xbelElement.removeChild(node);
586 fileChanged = true;
587 }
588 }
589
590 if (fileChanged) {
591 if (!input.open(QIODevice::WriteOnly) || (input.write(document.toByteArray(2)) < 0)) {
592 qCWarning(KIO_CORE) << "Couldn't save bookmark file " << input.fileName();
593 }
594 }
595}
596
597void KRecentDocument::removeApplication(const QString &desktopEntryName)
598{
599 QFile input(xbelPath());
600 if (!input.exists()) {
601 return;
602 }
603
604 // Won't help for GTK applications and whatnot, but we can be good citizens ourselves
605 QLockFile lockFile(xbelPath() + QLatin1String(".lock"));
606 lockFile.setStaleLockTime(0);
607 if (!lockFile.tryLock(100)) { // give it 100ms
608 qCWarning(KIO_CORE) << "Failed to lock recently used";
609 return;
610 }
611
612 if (!input.open(QIODevice::ReadOnly)) {
613 qCWarning(KIO_CORE) << "Failed to open existing recently used" << input.errorString();
614 return;
615 }
616
617 QDomDocument document;
618 document.setContent(&input);
619 input.close();
620
621 auto xbelTags = document.elementsByTagName(xbelTag);
622 if (xbelTags.length() != 1) {
623 qCWarning(KIO_CORE) << "Invalid Xbel file, missing xbel element";
624 return;
625 }
626 auto xbelElement = xbelTags.item(0);
627 auto bookmarkList = xbelElement.childNodes();
628
629 bool fileChanged = false;
630 for (int i = 0; i < bookmarkList.length(); ++i) {
631 const auto bookmarkNode = bookmarkList.item(i);
632 const auto infoNode = bookmarkNode.firstChild();
633 if (!infoNode.isElement()) {
634 qCWarning(KIO_CORE) << "Invalid Xbel file, missing info element";
635 return;
636 }
637 const auto metadataElement = infoNode.firstChild();
638 if (!metadataElement.isElement()) {
639 qCWarning(KIO_CORE) << "Invalid Xbel file, missing metadata element";
640 return;
641 }
642
643 auto bookmarksElement = metadataElement.firstChildElement(applicationsBookmarkTag);
644 if (!bookmarksElement.isElement()) {
645 qCWarning(KIO_CORE) << "Invalid Xbel file, missing bookmarks element";
646 return;
647 }
648
649 auto applicationList = bookmarksElement.childNodes();
650 for (int i = 0; i < applicationList.length(); ++i) {
651 auto appNode = applicationList.item(i);
652 const auto appName = appNode.attributes().namedItem(nameAttribute).nodeValue();
653
654 if (appName == desktopEntryName) {
655 bookmarksElement.removeChild(appNode);
656 fileChanged = true;
657 }
658 }
659 if (bookmarksElement.childNodes().length() == 0) {
660 // no more application associated with the file
661 xbelElement.removeChild(bookmarkNode);
662 }
663 }
664
665 if (fileChanged) {
666 if (!input.open(QIODevice::WriteOnly) || (input.write(document.toByteArray(2)) < 0)) {
667 qCWarning(KIO_CORE) << "Couldn't save bookmark file " << input.fileName();
668 }
669 }
670}
671
673{
674 QFile input(xbelPath());
675 if (!input.exists()) {
676 return;
677 }
678
679 // Won't help for GTK applications and whatnot, but we can be good citizens ourselves
680 QLockFile lockFile(xbelPath() + QLatin1String(".lock"));
681 lockFile.setStaleLockTime(0);
682 if (!lockFile.tryLock(100)) { // give it 100ms
683 qCWarning(KIO_CORE) << "Failed to lock recently used";
684 return;
685 }
686
687 if (!input.open(QIODevice::ReadOnly)) {
688 qCWarning(KIO_CORE) << "Failed to open existing recently used" << input.errorString();
689 return;
690 }
691
692 QDomDocument document;
693 document.setContent(&input);
694 input.close();
695
696 auto xbelTags = document.elementsByTagName(xbelTag);
697 if (xbelTags.length() != 1) {
698 qCWarning(KIO_CORE) << "Invalid Xbel file, missing xbel element";
699 return;
700 }
701 auto xbelElement = xbelTags.item(0);
702 auto bookmarkList = xbelElement.childNodes();
703
704 bool fileChanged = false;
705 for (int i = 0; i < bookmarkList.length(); ++i) {
706 const auto node = bookmarkList.item(i);
707 const auto modifiedString = node.attributes().namedItem(modifiedAttribute);
708 const auto modifiedTime = QDateTime::fromString(modifiedString.nodeValue(), Qt::ISODate);
709
710 if (modifiedTime >= since) {
711 fileChanged = true;
712 xbelElement.removeChild(node);
713 }
714 }
715 if (fileChanged) {
716 if (!input.open(QIODevice::WriteOnly) || (input.write(document.toByteArray(2)) < 0)) {
717 qCWarning(KIO_CORE) << "Couldn't save bookmark file " << input.fileName();
718 }
719 }
720}
KConfigGroup group(const QString &group)
QString readEntry(const char *key, const char *aDefault=nullptr) const
static int maximumItems()
Returns the maximum amount of recent document entries allowed.
static void removeBookmarksModifiedSince(const QDateTime &since)
Remove bookmarks whose modification date is after since parameter.
static void clear()
Clear the recent document menu of all entries.
static void removeApplication(const QString &desktopEntryName)
static void add(const QUrl &url)
Add a new item to the Recent Document menu.
static QList< QUrl > recentUrls()
Return a list of recent URLs.
static void removeFile(const QUrl &url)
static Ptr serviceByDesktopName(const QString &_name)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
char * toString(const EngineQuery &query)
KCALUTILS_EXPORT QString mimeType()
KIOCORE_EXPORT MkpathJob * mkpath(const QUrl &url, const QUrl &baseUrl=QUrl(), JobFlags flags=DefaultFlags)
Creates a directory, creating parent directories as needed.
bool isEmpty() const const
QCoreApplication * instance()
QDateTime currentDateTimeUtc()
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
QString toString(QStringView format, QCalendar cal) const const
QString tempPath()
QDomNodeList elementsByTagName(const QString &tagname) const const
ParseResult setContent(QAnyStringView text, ParseOptions options)
QByteArray toByteArray(int indent) const const
QDomNodeList childNodes() const const
QDomNode item(int index) const const
bool exists(const QString &fileName)
virtual QString fileName() const const override
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
bool remove()
virtual void close() override
QString errorString() const const
qint64 write(const QByteArray &data)
iterator begin()
iterator end()
bool isEmpty() const const
void setStaleLockTime(int staleLockTime)
bool tryLock(int timeout)
QList< Key > keys() const const
QMimeType mimeTypeForUrl(const QUrl &url) const const
iterator insert(const Key &key, const T &value)
key_value_iterator keyValueBegin()
key_value_iterator keyValueEnd()
size_type size() const const
QVariant property(const char *name) const const
QString writableLocation(StandardLocation type)
QString chopped(qsizetype len) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
bool contains(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QByteArray toLatin1() const const
QString toString() const const
QUrl fromEncoded(const QByteArray &input, ParsingMode parsingMode)
bool isLocalFile() const const
QByteArray toEncoded(FormattingOptions options) const const
QString toLocalFile() const const
QString toString() const const
void append(const QString &namespaceUri, const QString &name, const QString &value)
QStringView value(QAnyStringView namespaceUri, QAnyStringView name) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:16:27 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.