9#include "krecentdocument.h"
11#include "kiocoredebug.h"
13#include <QCoreApplication>
15#include <QDomDocument>
17#include <QMimeDatabase>
19#include <QXmlStreamWriter>
21#include <KConfigGroup>
23#include <KSharedConfig>
25using namespace Qt::StringLiterals;
32static inline QString stringForRecentDocumentGroup(
int val)
35 case KRecentDocument::RecentDocumentGroup::Development:
36 return "Development"_L1;
37 case KRecentDocument::RecentDocumentGroup::Office:
39 case KRecentDocument::RecentDocumentGroup::Database:
41 case KRecentDocument::RecentDocumentGroup::Email:
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:
51 case KRecentDocument::RecentDocumentGroup::TextEditor:
52 return "TextEditor"_L1;
53 case KRecentDocument::RecentDocumentGroup::Viewer:
55 case KRecentDocument::RecentDocumentGroup::Archive:
57 case KRecentDocument::RecentDocumentGroup::Multimedia:
58 return "Multimedia"_L1;
59 case KRecentDocument::RecentDocumentGroup::Audio:
61 case KRecentDocument::RecentDocumentGroup::Video:
63 case KRecentDocument::RecentDocumentGroup::Photo:
65 case KRecentDocument::RecentDocumentGroup::Application:
66 return "Application"_L1;
91static const QLatin1String applicationsBookmarkTag(
"bookmark:applications");
92static const QLatin1String applicationBookmarkTag(
"bookmark:application");
108static const QLatin1String ownerValue(
"http://freedesktop.org");
111static bool removeOldestEntries(
int &maxEntries)
113 QFile input(xbelPath());
114 if (!input.exists()) {
120 lockFile.setStaleLockTime(0);
121 if (!lockFile.tryLock(100)) {
122 qCWarning(KIO_CORE) <<
"Failed to lock recently used";
127 qCWarning(KIO_CORE) <<
"Failed to open existing recently used" << input.errorString();
136 if (xbelTags.length() != 1) {
137 qCWarning(KIO_CORE) <<
"Invalid Xbel file, missing xbel element";
140 auto xbelElement = xbelTags.
item(0);
142 if (bookmarkList.length() <= maxEntries) {
147 for (
int i = 0; i < bookmarkList.length(); ++i) {
148 const auto node = bookmarkList.item(i);
149 const auto modifiedString = node.attributes().namedItem(modifiedAttribute);
152 bookmarksByModifiedDate.
insert(modifiedTime, node);
157 for (
auto entry = bookmarksByModifiedDate.
keyValueBegin(); entry != bookmarksByModifiedDate.
keyValueEnd(); ++entry) {
159 if (bookmarksByModifiedDate.
size() - i > maxEntries) {
160 xbelElement.removeChild(entry->second);
174 qCWarning(KIO_CORE) <<
"Could not create GenericDataLocation";
180 lockFile.setStaleLockTime(0);
181 if (!lockFile.tryLock(100)) {
182 qCWarning(KIO_CORE) <<
"Failed to lock recently used";
187 QFile input(xbelPath());
189 existingContent = input.readAll();
190 }
else if (!input.exists()) {
191 qCDebug(KIO_CORE) << input.fileName() <<
"does not exist, creating new";
193 qCWarning(KIO_CORE) <<
"Failed to open existing recently used" << input.errorString();
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)
211 qCWarning(KIO_CORE) <<
"Failed to recently-used.xbel for writing:" << outputFile.errorString();
216 output.setAutoFormatting(
true);
217 output.setAutoFormattingIndent(2);
218 output.writeStartDocument();
219 output.writeStartElement(xbelTag);
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);
228 auto addApplicationTag = [&output, desktopEntryName, currentTimestamp, url]() {
229 output.writeEmptyElement(applicationBookmarkTag);
230 output.writeAttribute(nameAttribute, desktopEntryName);
233 bool shouldAddParameter =
true;
235 exec = service->exec();
242 if (shouldAddParameter) {
249 output.writeAttribute(execAttribute, exec);
250 output.writeAttribute(modifiedAttribute, currentTimestamp);
251 output.writeAttribute(countAttribute,
"1"_L1);
254 bool foundExistingApp =
false;
255 bool inRightBookmark =
false;
256 bool foundMatchingBookmark =
false;
257 bool firstBookmark =
true;
259 while (!xml.atEnd() && !xml.hasError()) {
263 switch (xml.tokenType()) {
268 if (tagName == bookmarkTag) {
269 foundExistingApp =
false;
270 firstBookmark =
false;
273 inRightBookmark = hrefValue == newUrl;
277 xml.skipCurrentElement();
281 if (inRightBookmark) {
282 foundMatchingBookmark =
true;
286 if (old.name() == modifiedAttribute) {
289 if (old.name() == visitedAttribute) {
292 newAttributes.
append(old);
294 newAttributes.
append(modifiedAttribute, currentTimestamp);
295 newAttributes.
append(visitedAttribute, currentTimestamp);
296 attributes = newAttributes;
302 else if (inRightBookmark && tagName == applicationBookmarkTag && attributes.value(nameAttribute) == desktopEntryName) {
304 const int count = attributes.value(countAttribute).toInt();
308 if (old.name() == countAttribute) {
311 if (old.name() == modifiedAttribute) {
314 newAttributes.
append(old);
316 newAttributes.
append(modifiedAttribute, currentTimestamp);
318 attributes = newAttributes;
320 foundExistingApp =
true;
323 output.writeStartElement(tagName.
toString());
324 output.writeAttributes(attributes);
329 if (tagName == applicationsBookmarkTag && inRightBookmark && !foundExistingApp) {
333 output.writeEndElement();
338 output.writeCDATA(xml.text().toString());
340 output.writeCharacters(xml.text().toString());
344 output.writeComment(xml.text().toString());
347 qCWarning(KIO_CORE) <<
"Malformed, got end document before end of xbel" << xml.tokenString() << url;
350 qCWarning(KIO_CORE) <<
"unhandled token" << xml.tokenString() << url;
355 if (!foundMatchingBookmark) {
358 output.writeCharacters(
"\n"_L1);
360 output.writeCharacters(
" "_L1);
361 output.writeStartElement(bookmarkTag);
363 output.writeAttribute(hrefAttribute, newUrl);
364 output.writeAttribute(addedAttribute, currentTimestamp);
365 output.writeAttribute(modifiedAttribute, currentTimestamp);
366 output.writeAttribute(visitedAttribute, currentTimestamp);
372 output.writeStartElement(infoTag);
373 output.writeStartElement(metadataTag);
374 output.writeAttribute(ownerAttribute, ownerValue);
376 output.writeEmptyElement(mimeTypeTag);
377 output.writeAttribute(typeAttribute, fileMime);
381 groups = groupsForMimeType(fileMime);
384 output.writeStartElement(bookmarkGroups);
385 for (
const auto &group : std::as_const(groups)) {
386 output.writeTextElement(bookmarkGroup, stringForRecentDocumentGroup(group));
389 output.writeEndElement();
393 output.writeStartElement(applicationsBookmarkTag);
396 output.writeEndElement();
400 output.writeEndElement();
402 output.writeEndElement();
406 output.writeEndElement();
410 output.writeEndElement();
413 output.writeEndDocument();
415 if (outputFile.commit()) {
418 return nbEntries - maxEntries > 10 || removeOldestEntries(maxEntries);
426 QFile input(xbelPath());
428 qCWarning(KIO_CORE) <<
"Failed to open" << input.fileName() << input.errorString();
433 xml.readNextStartElement();
435 qCWarning(KIO_CORE) <<
"The file is not an XBEL version 1.0 file.";
439 while (!xml.atEnd() && !xml.hasError()) {
444 const auto urlString = xml.attributes().value(
QLatin1String(
"href"));
445 if (urlString.isEmpty()) {
446 qCInfo(KIO_CORE) <<
"Invalid bookmark in" << input.fileName();
453 const auto attributes = xml.attributes();
457 if (modified > visited && modified > added) {
459 }
else if (visited > added) {
466 if (xml.hasError()) {
467 qCWarning(KIO_CORE) <<
"Failed to read" << input.fileName() << xml.errorString();
479 return documents.value(doc1) < documents.value(doc2);
494 if (desktopEntryName.
isEmpty()) {
497 add(url, desktopEntryName, groups);
513 bool useRecent = config.
readEntry(
"UseRecent"_L1,
true);
514 int maxEntries = config.
readEntry(
"MaxEntries"_L1, 300);
515 bool ignoreHidden = config.
readEntry(
"IgnoreHidden"_L1,
true);
517 if (!useRecent || maxEntries == 0) {
525 if (!addToXbel(url, desktopEntryName, groups, maxEntries, ignoreHidden)) {
526 qCWarning(KIO_CORE) <<
"Failed to add to recently used bookmark file";
538 return cg.
readEntry(
"MaxEntries"_L1, 300);
543 QFile input(xbelPath());
552 qCWarning(KIO_CORE) <<
"Failed to lock recently used";
557 qCWarning(KIO_CORE) <<
"Failed to open existing recently used" << input.
errorString();
566 if (xbelTags.length() != 1) {
567 qCWarning(KIO_CORE) <<
"Invalid Xbel file, missing xbel elememt";
570 auto xbelElement = xbelTags.
item(0);
573 bool fileChanged =
false;
574 for (
int i = 0; i < bookmarkList.length(); ++i) {
575 const auto node = bookmarkList.item(i);
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";
584 if (hrefUrl == url) {
585 xbelElement.removeChild(node);
592 qCWarning(KIO_CORE) <<
"Couldn't save bookmark file " << input.
fileName();
599 QFile input(xbelPath());
608 qCWarning(KIO_CORE) <<
"Failed to lock recently used";
613 qCWarning(KIO_CORE) <<
"Failed to open existing recently used" << input.
errorString();
622 if (xbelTags.length() != 1) {
623 qCWarning(KIO_CORE) <<
"Invalid Xbel file, missing xbel element";
626 auto xbelElement = xbelTags.
item(0);
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";
637 const auto metadataElement = infoNode.firstChild();
638 if (!metadataElement.isElement()) {
639 qCWarning(KIO_CORE) <<
"Invalid Xbel file, missing metadata element";
643 auto bookmarksElement = metadataElement.firstChildElement(applicationsBookmarkTag);
644 if (!bookmarksElement.isElement()) {
645 qCWarning(KIO_CORE) <<
"Invalid Xbel file, missing bookmarks element";
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();
654 if (appName == desktopEntryName) {
655 bookmarksElement.removeChild(appNode);
659 if (bookmarksElement.childNodes().length() == 0) {
661 xbelElement.removeChild(bookmarkNode);
667 qCWarning(KIO_CORE) <<
"Couldn't save bookmark file " << input.
fileName();
674 QFile input(xbelPath());
683 qCWarning(KIO_CORE) <<
"Failed to lock recently used";
688 qCWarning(KIO_CORE) <<
"Failed to open existing recently used" << input.
errorString();
697 if (xbelTags.length() != 1) {
698 qCWarning(KIO_CORE) <<
"Invalid Xbel file, missing xbel element";
701 auto xbelElement = xbelTags.
item(0);
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);
710 if (modifiedTime >= since) {
712 xbelElement.removeChild(node);
717 qCWarning(KIO_CORE) <<
"Couldn't save bookmark file " << input.
fileName();
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
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)
virtual void close() override
QString errorString() const const
qint64 write(const QByteArray &data)
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