KBookmarks

kbookmarkmanager.cpp
1// -*- c-basic-offset:4; indent-tabs-mode:nil -*-
2/*
3 This file is part of the KDE libraries
4 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org>
5 SPDX-FileCopyrightText: 2003 Alexander Kellett <lypanov@kde.org>
6 SPDX-FileCopyrightText: 2008 Norbert Frese <nf2@scheinwelt.at>
7
8 SPDX-License-Identifier: LGPL-2.0-only
9*/
10
11#include "kbookmarkmanager.h"
12#include "kbookmarks_debug.h"
13
14#include <QDir>
15#include <QFile>
16#include <QFileInfo>
17#include <QRegularExpression>
18#include <QSaveFile>
19#include <QStandardPaths>
20#include <QTextStream>
21
22#include <KBackup>
23#include <KConfig>
24#include <KConfigGroup>
25#include <KDirWatch>
26
27namespace
28{
29namespace Strings
30{
31QString piData()
32{
33 return QStringLiteral("version=\"1.0\" encoding=\"UTF-8\"");
34}
35}
36}
37
38class KBookmarkMap : private KBookmarkGroupTraverser
39{
40public:
41 KBookmarkMap()
42 : m_mapNeedsUpdate(true)
43 {
44 }
45 void setNeedsUpdate()
46 {
47 m_mapNeedsUpdate = true;
48 }
49 void update(KBookmarkManager *);
50 QList<KBookmark> find(const QString &url) const
51 {
52 return m_bk_map.value(url);
53 }
54
55private:
56 void visit(const KBookmark &) override;
57 void visitEnter(const KBookmarkGroup &) override
58 {
59 ;
60 }
61 void visitLeave(const KBookmarkGroup &) override
62 {
63 ;
64 }
65
66private:
67 typedef QList<KBookmark> KBookmarkList;
69 bool m_mapNeedsUpdate;
70};
71
72void KBookmarkMap::update(KBookmarkManager *manager)
73{
74 if (m_mapNeedsUpdate) {
75 m_mapNeedsUpdate = false;
76
77 m_bk_map.clear();
78 KBookmarkGroup root = manager->root();
79 traverse(root);
80 }
81}
82
83void KBookmarkMap::visit(const KBookmark &bk)
84{
85 if (!bk.isSeparator()) {
86 // add bookmark to url map
87 m_bk_map[bk.internalElement().attribute(QStringLiteral("href"))].append(bk);
88 }
89}
90
91// #########################
92// KBookmarkManagerPrivate
93class KBookmarkManagerPrivate
94{
95public:
96 KBookmarkManagerPrivate(bool bDocIsloaded)
97 : m_doc(QStringLiteral("xbel"))
98 , m_docIsLoaded(bDocIsloaded)
99 , m_dirWatch(nullptr)
100 {
101 }
102
103 mutable QDomDocument m_doc;
104 mutable QDomDocument m_toolbarDoc;
105 QString m_bookmarksFile;
106 mutable bool m_docIsLoaded;
107
108 KDirWatch *m_dirWatch; // for monitoring changes on bookmark files
109
110 KBookmarkMap m_map;
111};
112
113// ################
114// KBookmarkManager
115
116static QDomElement createXbelTopLevelElement(QDomDocument &doc)
117{
118 QDomElement topLevel = doc.createElement(QStringLiteral("xbel"));
119 topLevel.setAttribute(QStringLiteral("xmlns:mime"), QStringLiteral("http://www.freedesktop.org/standards/shared-mime-info"));
120 topLevel.setAttribute(QStringLiteral("xmlns:bookmark"), QStringLiteral("http://www.freedesktop.org/standards/desktop-bookmarks"));
121 topLevel.setAttribute(QStringLiteral("xmlns:kdepriv"), QStringLiteral("http://www.kde.org/kdepriv"));
122 doc.appendChild(topLevel);
123 doc.insertBefore(doc.createProcessingInstruction(QStringLiteral("xml"), Strings::piData()), topLevel);
124 return topLevel;
125}
126
128 : QObject(parent)
129 , d(new KBookmarkManagerPrivate(false))
130{
131 Q_ASSERT(!bookmarksFile.isEmpty());
132 d->m_bookmarksFile = bookmarksFile;
133
134 if (!QFile::exists(d->m_bookmarksFile)) {
135 createXbelTopLevelElement(d->m_doc);
136 } else {
137 parse();
138 }
139 d->m_docIsLoaded = true;
140
141 // start KDirWatch
142 KDirWatch::self()->addFile(d->m_bookmarksFile);
143 QObject::connect(KDirWatch::self(), &KDirWatch::dirty, this, &KBookmarkManager::slotFileChanged);
144 QObject::connect(KDirWatch::self(), &KDirWatch::created, this, &KBookmarkManager::slotFileChanged);
145 QObject::connect(KDirWatch::self(), &KDirWatch::deleted, this, &KBookmarkManager::slotFileChanged);
146
147 // qCDebug(KBOOKMARKS_LOG) << "starting KDirWatch for" << d->m_bookmarksFile;
148}
149
150void KBookmarkManager::slotFileChanged(const QString &path)
151{
152 if (path == d->m_bookmarksFile) {
153 // qCDebug(KBOOKMARKS_LOG) << "file changed (KDirWatch) " << path ;
154 // Reparse
155 parse();
156 // Tell our GUI
157 // (emit where group is "" to directly mark the root menu as dirty)
159 }
160}
161
165
167{
168 if (!d->m_docIsLoaded) {
169 parse();
170 d->m_toolbarDoc.clear();
171 }
172 return d->m_doc;
173}
174
175void KBookmarkManager::parse() const
176{
177 d->m_docIsLoaded = true;
178 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::parse " << d->m_bookmarksFile;
179 QFile file(d->m_bookmarksFile);
180 if (!file.open(QIODevice::ReadOnly)) {
181 qCWarning(KBOOKMARKS_LOG) << "Can't open" << d->m_bookmarksFile;
182 d->m_doc = QDomDocument(QStringLiteral("xbel"));
183 createXbelTopLevelElement(d->m_doc);
184 return;
185 }
186 d->m_doc = QDomDocument(QStringLiteral("xbel"));
187 d->m_doc.setContent(&file);
188
189 if (d->m_doc.documentElement().isNull()) {
190 qCWarning(KBOOKMARKS_LOG) << "KBookmarkManager::parse : main tag is missing, creating default " << d->m_bookmarksFile;
191 QDomElement element = d->m_doc.createElement(QStringLiteral("xbel"));
192 d->m_doc.appendChild(element);
193 }
194
195 QDomElement docElem = d->m_doc.documentElement();
196
197 QString mainTag = docElem.tagName();
198 if (mainTag != QLatin1String("xbel")) {
199 qCWarning(KBOOKMARKS_LOG) << "KBookmarkManager::parse : unknown main tag " << mainTag;
200 }
201
202 QDomNode n = d->m_doc.documentElement().previousSibling();
203 if (n.isProcessingInstruction()) {
205 pi.parentNode().removeChild(pi);
206 }
207
209 pi = d->m_doc.createProcessingInstruction(QStringLiteral("xml"), Strings::piData());
210 d->m_doc.insertBefore(pi, docElem);
211
212 file.close();
213
214 d->m_map.setNeedsUpdate();
215}
216
217bool KBookmarkManager::save(bool toolbarCache) const
218{
219 return saveAs(d->m_bookmarksFile, toolbarCache);
220}
221
222bool KBookmarkManager::saveAs(const QString &filename, bool toolbarCache) const
223{
224 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::save " << filename;
225
226 // Save the bookmark toolbar folder for quick loading
227 // but only when it will actually make things quicker
228 const QString cacheFilename = filename + QLatin1String(".tbcache");
229 if (toolbarCache && !root().isToolbarGroup()) {
230 QSaveFile cacheFile(cacheFilename);
231 if (cacheFile.open(QIODevice::WriteOnly)) {
232 QString str;
233 QTextStream stream(&str, QIODevice::WriteOnly);
234 stream << root().findToolbar();
235 const QByteArray cstr = str.toUtf8();
236 cacheFile.write(cstr.data(), cstr.length());
237 cacheFile.commit();
238 }
239 } else { // remove any (now) stale cache
240 QFile::remove(cacheFilename);
241 }
242
243 // Create parent dirs
244 QFileInfo info(filename);
245 QDir().mkpath(info.absolutePath());
246
247 if (filename == d->m_bookmarksFile) {
248 KDirWatch::self()->removeFile(d->m_bookmarksFile);
249 }
250
251 QSaveFile file(filename);
252 bool success = false;
253 if (file.open(QIODevice::WriteOnly)) {
254 KBackup::simpleBackupFile(file.fileName(), QString(), QStringLiteral(".bak"));
255 QTextStream stream(&file);
256 // In Qt6 it's UTF-8 by default
257 stream << internalDocument().toString();
258 stream.flush();
259 success = file.commit();
260 }
261
262 if (filename == d->m_bookmarksFile) {
263 KDirWatch::self()->addFile(d->m_bookmarksFile);
264 }
265
266 if (!success) {
267 QString err = tr("Unable to save bookmarks in %1. Reported error was: %2. "
268 "This error message will only be shown once. The cause "
269 "of the error needs to be fixed as quickly as possible, "
270 "which is most likely a full hard drive.")
271 .arg(filename, file.errorString());
272 qCCritical(KBOOKMARKS_LOG)
273 << QStringLiteral("Unable to save bookmarks in %1. File reported the following error-code: %2.").arg(filename).arg(file.error());
274 Q_EMIT const_cast<KBookmarkManager *>(this)->error(err);
275 }
276
277 return success;
278}
279
281{
282 return d->m_bookmarksFile;
283}
284
286{
287 return KBookmarkGroup(internalDocument().documentElement());
288}
289
291{
292 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::toolbar begin";
293 // Only try to read from a toolbar cache if the full document isn't loaded
294 if (!d->m_docIsLoaded) {
295 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::toolbar trying cache";
296 const QString cacheFilename = d->m_bookmarksFile + QLatin1String(".tbcache");
297 QFileInfo bmInfo(d->m_bookmarksFile);
298 QFileInfo cacheInfo(cacheFilename);
299 if (d->m_toolbarDoc.isNull() && QFile::exists(cacheFilename) && bmInfo.lastModified() < cacheInfo.lastModified()) {
300 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::toolbar reading file";
301 QFile file(cacheFilename);
302
303 if (file.open(QIODevice::ReadOnly)) {
304 d->m_toolbarDoc = QDomDocument(QStringLiteral("cache"));
305 d->m_toolbarDoc.setContent(&file);
306 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::toolbar opened";
307 }
308 }
309 if (!d->m_toolbarDoc.isNull()) {
310 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::toolbar returning element";
311 QDomElement elem = d->m_toolbarDoc.firstChild().toElement();
312 return KBookmarkGroup(elem);
313 }
314 }
315
316 // Fallback to the normal way if there is no cache or if the bookmark file
317 // is already loaded
318 QDomElement elem = root().findToolbar();
319 if (elem.isNull()) {
320 // Root is the bookmark toolbar if none has been set.
321 // Make it explicit to speed up invocations of findToolbar()
322 root().internalElement().setAttribute(QStringLiteral("toolbar"), QStringLiteral("yes"));
323 return root();
324 } else {
325 return KBookmarkGroup(elem);
326 }
327}
328
330{
331 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::findByAddress " << address;
332 KBookmark result = root();
333 // The address is something like /5/10/2+
334 static const QRegularExpression separator(QStringLiteral("[/+]"));
335 const QStringList addresses = address.split(separator, Qt::SkipEmptyParts);
336 // qCWarning(KBOOKMARKS_LOG) << addresses.join(",");
337 for (QStringList::const_iterator it = addresses.begin(); it != addresses.end();) {
338 bool append = ((*it) == QLatin1String("+"));
339 uint number = (*it).toUInt();
340 Q_ASSERT(result.isGroup());
341 KBookmarkGroup group = result.toGroup();
342 KBookmark bk = group.first();
343 KBookmark lbk = bk; // last non-null bookmark
344 for (uint i = 0; ((i < number) || append) && !bk.isNull(); ++i) {
345 lbk = bk;
346 bk = group.next(bk);
347 // qCWarning(KBOOKMARKS_LOG) << i;
348 }
349 it++;
350 // qCWarning(KBOOKMARKS_LOG) << "found section";
351 result = bk;
352 }
353 if (result.isNull()) {
354 qCWarning(KBOOKMARKS_LOG) << "KBookmarkManager::findByAddress: couldn't find item " << address;
355 }
356 // qCWarning(KBOOKMARKS_LOG) << "found " << result.address();
357 return result;
358}
359
364
366{
367 (void)save(); // KDE5 TODO: emitChanged should return a bool? Maybe rename it to saveAndEmitChanged?
368
369 // Tell the other processes too
370 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::emitChanged : broadcasting change " << group.address();
371
372 Q_EMIT changed(group.address());
373}
374
375///////
377{
378 d->m_map.update(this);
379 QList<KBookmark> list = d->m_map.find(url);
380 if (list.isEmpty()) {
381 return false;
382 }
383
384 for (QList<KBookmark>::iterator it = list.begin(); it != list.end(); ++it) {
385 (*it).updateAccessMetadata();
386 }
387
388 return true;
389}
390
391#include "moc_kbookmarkmanager.cpp"
A class to traverse bookarm groups.
Definition kbookmark.h:427
A group of bookmarks.
Definition kbookmark.h:316
KBookmark next(const KBookmark &current) const
Return the next sibling of a child bookmark of this group.
KBookmark first() const
Return the first child bookmark of this group.
QDomElement findToolbar() const
This class implements the reading/writing of bookmarks in XML.
KBookmark findByAddress(const QString &address)
QDomDocument internalDocument() const
bool updateAccessMetadata(const QString &url)
Update access time stamps for a given url.
KBookmarkGroup toolbar()
This returns the root of the toolbar menu.
bool save(bool toolbarCache=true) const
Save the bookmarks to an XML file on disk.
QString path() const
This will return the path that this manager is using to read the bookmarks.
KBookmarkGroup root() const
This will return the root bookmark.
void error(const QString &errorMessage)
Emitted when an error occurs.
bool saveAs(const QString &filename, bool toolbarCache=true) const
Save the bookmarks to the given XML file on disk.
~KBookmarkManager() override
Destructor.
void changed(const QString &groupAddress)
Signals that the group (or any of its children) with the address groupAddress (e.g.
void emitChanged()
Saves the bookmark file and notifies everyone.
KBookmarkManager(const QString &bookmarksFile, QObject *parent=nullptr)
Create a KBookmarkManager responsible for the given bookmarksFile.
A class representing a bookmark.
Definition kbookmark.h:27
bool isNull() const
bool isGroup() const
Whether the bookmark is a group or a normal bookmark.
KBookmarkGroup toGroup() const
Convert this to a group - do this only if isGroup() returns true.
bool isSeparator() const
Whether the bookmark is a separator.
QDomElement internalElement() const
QString address() const
Return the "address" of this bookmark in the whole tree.
void addFile(const QString &file)
void removeFile(const QString &file)
static KDirWatch * self()
void deleted(const QString &path)
void dirty(const QString &path)
void created(const QString &path)
bool simpleBackupFile(const QString &filename, const QString &backupDir=QString(), const QString &backupExtension=QStringLiteral("~"))
char * data()
qsizetype length() const const
bool mkpath(const QString &dirPath) const const
QDomElement createElement(const QString &tagName)
QDomProcessingInstruction createProcessingInstruction(const QString &target, const QString &data)
QString toString(int indent) const const
QString attribute(const QString &name, const QString &defValue) const const
void setAttribute(const QString &name, const QString &value)
QString tagName() const const
QDomNode appendChild(const QDomNode &newChild)
QDomNode insertBefore(const QDomNode &newChild, const QDomNode &refChild)
bool isNull() const const
bool isProcessingInstruction() const const
QDomNode parentNode() const const
QDomNode removeChild(const QDomNode &oldChild)
QDomProcessingInstruction toProcessingInstruction() const const
bool exists() const const
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
bool remove()
FileError error() const const
QString absolutePath() const const
QDateTime lastModified() const const
QString errorString() const const
qint64 write(const QByteArray &data)
iterator begin()
iterator end()
bool isEmpty() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString tr(const char *sourceText, const char *disambiguation, int n)
bool commit()
virtual QString fileName() const const override
virtual bool open(OpenMode mode) override
QString arg(Args &&... args) const const
bool isEmpty() const const
uint toUInt(bool *ok, int base) const const
QByteArray toUtf8() const const
SkipEmptyParts
void flush()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:59 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.