File indexing completed on 2024-04-21 03:52:37
0001 // -*- c-basic-offset:4; indent-tabs-mode:nil -*- 0002 /* 0003 This file is part of the KDE libraries 0004 SPDX-FileCopyrightText: 2000 David Faure <faure@kde.org> 0005 SPDX-FileCopyrightText: 2003 Alexander Kellett <lypanov@kde.org> 0006 SPDX-FileCopyrightText: 2008 Norbert Frese <nf2@scheinwelt.at> 0007 0008 SPDX-License-Identifier: LGPL-2.0-only 0009 */ 0010 0011 #include "kbookmarkmanager.h" 0012 #include "kbookmarks_debug.h" 0013 0014 #include <QDir> 0015 #include <QFile> 0016 #include <QFileInfo> 0017 #include <QRegularExpression> 0018 #include <QSaveFile> 0019 #include <QStandardPaths> 0020 #include <QTextStream> 0021 0022 #include <KBackup> 0023 #include <KConfig> 0024 #include <KConfigGroup> 0025 #include <KDirWatch> 0026 0027 namespace 0028 { 0029 namespace Strings 0030 { 0031 QString piData() 0032 { 0033 return QStringLiteral("version=\"1.0\" encoding=\"UTF-8\""); 0034 } 0035 } 0036 } 0037 0038 class KBookmarkMap : private KBookmarkGroupTraverser 0039 { 0040 public: 0041 KBookmarkMap() 0042 : m_mapNeedsUpdate(true) 0043 { 0044 } 0045 void setNeedsUpdate() 0046 { 0047 m_mapNeedsUpdate = true; 0048 } 0049 void update(KBookmarkManager *); 0050 QList<KBookmark> find(const QString &url) const 0051 { 0052 return m_bk_map.value(url); 0053 } 0054 0055 private: 0056 void visit(const KBookmark &) override; 0057 void visitEnter(const KBookmarkGroup &) override 0058 { 0059 ; 0060 } 0061 void visitLeave(const KBookmarkGroup &) override 0062 { 0063 ; 0064 } 0065 0066 private: 0067 typedef QList<KBookmark> KBookmarkList; 0068 QMap<QString, KBookmarkList> m_bk_map; 0069 bool m_mapNeedsUpdate; 0070 }; 0071 0072 void KBookmarkMap::update(KBookmarkManager *manager) 0073 { 0074 if (m_mapNeedsUpdate) { 0075 m_mapNeedsUpdate = false; 0076 0077 m_bk_map.clear(); 0078 KBookmarkGroup root = manager->root(); 0079 traverse(root); 0080 } 0081 } 0082 0083 void KBookmarkMap::visit(const KBookmark &bk) 0084 { 0085 if (!bk.isSeparator()) { 0086 // add bookmark to url map 0087 m_bk_map[bk.internalElement().attribute(QStringLiteral("href"))].append(bk); 0088 } 0089 } 0090 0091 // ######################### 0092 // KBookmarkManagerPrivate 0093 class KBookmarkManagerPrivate 0094 { 0095 public: 0096 KBookmarkManagerPrivate(bool bDocIsloaded) 0097 : m_doc(QStringLiteral("xbel")) 0098 , m_docIsLoaded(bDocIsloaded) 0099 , m_dirWatch(nullptr) 0100 { 0101 } 0102 0103 mutable QDomDocument m_doc; 0104 mutable QDomDocument m_toolbarDoc; 0105 QString m_bookmarksFile; 0106 mutable bool m_docIsLoaded; 0107 0108 KDirWatch *m_dirWatch; // for monitoring changes on bookmark files 0109 0110 KBookmarkMap m_map; 0111 }; 0112 0113 // ################ 0114 // KBookmarkManager 0115 0116 static QDomElement createXbelTopLevelElement(QDomDocument &doc) 0117 { 0118 QDomElement topLevel = doc.createElement(QStringLiteral("xbel")); 0119 topLevel.setAttribute(QStringLiteral("xmlns:mime"), QStringLiteral("http://www.freedesktop.org/standards/shared-mime-info")); 0120 topLevel.setAttribute(QStringLiteral("xmlns:bookmark"), QStringLiteral("http://www.freedesktop.org/standards/desktop-bookmarks")); 0121 topLevel.setAttribute(QStringLiteral("xmlns:kdepriv"), QStringLiteral("http://www.kde.org/kdepriv")); 0122 doc.appendChild(topLevel); 0123 doc.insertBefore(doc.createProcessingInstruction(QStringLiteral("xml"), Strings::piData()), topLevel); 0124 return topLevel; 0125 } 0126 0127 KBookmarkManager::KBookmarkManager(const QString &bookmarksFile, QObject *parent) 0128 : QObject(parent) 0129 , d(new KBookmarkManagerPrivate(false)) 0130 { 0131 Q_ASSERT(!bookmarksFile.isEmpty()); 0132 d->m_bookmarksFile = bookmarksFile; 0133 0134 if (!QFile::exists(d->m_bookmarksFile)) { 0135 createXbelTopLevelElement(d->m_doc); 0136 } else { 0137 parse(); 0138 } 0139 d->m_docIsLoaded = true; 0140 0141 // start KDirWatch 0142 KDirWatch::self()->addFile(d->m_bookmarksFile); 0143 QObject::connect(KDirWatch::self(), &KDirWatch::dirty, this, &KBookmarkManager::slotFileChanged); 0144 QObject::connect(KDirWatch::self(), &KDirWatch::created, this, &KBookmarkManager::slotFileChanged); 0145 QObject::connect(KDirWatch::self(), &KDirWatch::deleted, this, &KBookmarkManager::slotFileChanged); 0146 0147 // qCDebug(KBOOKMARKS_LOG) << "starting KDirWatch for" << d->m_bookmarksFile; 0148 } 0149 0150 void KBookmarkManager::slotFileChanged(const QString &path) 0151 { 0152 if (path == d->m_bookmarksFile) { 0153 // qCDebug(KBOOKMARKS_LOG) << "file changed (KDirWatch) " << path ; 0154 // Reparse 0155 parse(); 0156 // Tell our GUI 0157 // (emit where group is "" to directly mark the root menu as dirty) 0158 Q_EMIT changed(QLatin1String("")); 0159 } 0160 } 0161 0162 KBookmarkManager::~KBookmarkManager() 0163 { 0164 } 0165 0166 QDomDocument KBookmarkManager::internalDocument() const 0167 { 0168 if (!d->m_docIsLoaded) { 0169 parse(); 0170 d->m_toolbarDoc.clear(); 0171 } 0172 return d->m_doc; 0173 } 0174 0175 void KBookmarkManager::parse() const 0176 { 0177 d->m_docIsLoaded = true; 0178 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::parse " << d->m_bookmarksFile; 0179 QFile file(d->m_bookmarksFile); 0180 if (!file.open(QIODevice::ReadOnly)) { 0181 qCWarning(KBOOKMARKS_LOG) << "Can't open" << d->m_bookmarksFile; 0182 d->m_doc = QDomDocument(QStringLiteral("xbel")); 0183 createXbelTopLevelElement(d->m_doc); 0184 return; 0185 } 0186 d->m_doc = QDomDocument(QStringLiteral("xbel")); 0187 d->m_doc.setContent(&file); 0188 0189 if (d->m_doc.documentElement().isNull()) { 0190 qCWarning(KBOOKMARKS_LOG) << "KBookmarkManager::parse : main tag is missing, creating default " << d->m_bookmarksFile; 0191 QDomElement element = d->m_doc.createElement(QStringLiteral("xbel")); 0192 d->m_doc.appendChild(element); 0193 } 0194 0195 QDomElement docElem = d->m_doc.documentElement(); 0196 0197 QString mainTag = docElem.tagName(); 0198 if (mainTag != QLatin1String("xbel")) { 0199 qCWarning(KBOOKMARKS_LOG) << "KBookmarkManager::parse : unknown main tag " << mainTag; 0200 } 0201 0202 QDomNode n = d->m_doc.documentElement().previousSibling(); 0203 if (n.isProcessingInstruction()) { 0204 QDomProcessingInstruction pi = n.toProcessingInstruction(); 0205 pi.parentNode().removeChild(pi); 0206 } 0207 0208 QDomProcessingInstruction pi; 0209 pi = d->m_doc.createProcessingInstruction(QStringLiteral("xml"), Strings::piData()); 0210 d->m_doc.insertBefore(pi, docElem); 0211 0212 file.close(); 0213 0214 d->m_map.setNeedsUpdate(); 0215 } 0216 0217 bool KBookmarkManager::save(bool toolbarCache) const 0218 { 0219 return saveAs(d->m_bookmarksFile, toolbarCache); 0220 } 0221 0222 bool KBookmarkManager::saveAs(const QString &filename, bool toolbarCache) const 0223 { 0224 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::save " << filename; 0225 0226 // Save the bookmark toolbar folder for quick loading 0227 // but only when it will actually make things quicker 0228 const QString cacheFilename = filename + QLatin1String(".tbcache"); 0229 if (toolbarCache && !root().isToolbarGroup()) { 0230 QSaveFile cacheFile(cacheFilename); 0231 if (cacheFile.open(QIODevice::WriteOnly)) { 0232 QString str; 0233 QTextStream stream(&str, QIODevice::WriteOnly); 0234 stream << root().findToolbar(); 0235 const QByteArray cstr = str.toUtf8(); 0236 cacheFile.write(cstr.data(), cstr.length()); 0237 cacheFile.commit(); 0238 } 0239 } else { // remove any (now) stale cache 0240 QFile::remove(cacheFilename); 0241 } 0242 0243 // Create parent dirs 0244 QFileInfo info(filename); 0245 QDir().mkpath(info.absolutePath()); 0246 0247 QSaveFile file(filename); 0248 if (file.open(QIODevice::WriteOnly)) { 0249 KBackup::simpleBackupFile(file.fileName(), QString(), QStringLiteral(".bak")); 0250 QTextStream stream(&file); 0251 // In Qt6 it's UTF-8 by default 0252 stream << internalDocument().toString(); 0253 stream.flush(); 0254 if (file.commit()) { 0255 return true; 0256 } 0257 } 0258 0259 QString err = tr("Unable to save bookmarks in %1. Reported error was: %2. " 0260 "This error message will only be shown once. The cause " 0261 "of the error needs to be fixed as quickly as possible, " 0262 "which is most likely a full hard drive.") 0263 .arg(filename, file.errorString()); 0264 qCCritical(KBOOKMARKS_LOG) << QStringLiteral("Unable to save bookmarks in %1. File reported the following error-code: %2.").arg(filename).arg(file.error()); 0265 Q_EMIT const_cast<KBookmarkManager *>(this)->error(err); 0266 0267 return false; 0268 } 0269 0270 QString KBookmarkManager::path() const 0271 { 0272 return d->m_bookmarksFile; 0273 } 0274 0275 KBookmarkGroup KBookmarkManager::root() const 0276 { 0277 return KBookmarkGroup(internalDocument().documentElement()); 0278 } 0279 0280 KBookmarkGroup KBookmarkManager::toolbar() 0281 { 0282 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::toolbar begin"; 0283 // Only try to read from a toolbar cache if the full document isn't loaded 0284 if (!d->m_docIsLoaded) { 0285 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::toolbar trying cache"; 0286 const QString cacheFilename = d->m_bookmarksFile + QLatin1String(".tbcache"); 0287 QFileInfo bmInfo(d->m_bookmarksFile); 0288 QFileInfo cacheInfo(cacheFilename); 0289 if (d->m_toolbarDoc.isNull() && QFile::exists(cacheFilename) && bmInfo.lastModified() < cacheInfo.lastModified()) { 0290 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::toolbar reading file"; 0291 QFile file(cacheFilename); 0292 0293 if (file.open(QIODevice::ReadOnly)) { 0294 d->m_toolbarDoc = QDomDocument(QStringLiteral("cache")); 0295 d->m_toolbarDoc.setContent(&file); 0296 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::toolbar opened"; 0297 } 0298 } 0299 if (!d->m_toolbarDoc.isNull()) { 0300 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::toolbar returning element"; 0301 QDomElement elem = d->m_toolbarDoc.firstChild().toElement(); 0302 return KBookmarkGroup(elem); 0303 } 0304 } 0305 0306 // Fallback to the normal way if there is no cache or if the bookmark file 0307 // is already loaded 0308 QDomElement elem = root().findToolbar(); 0309 if (elem.isNull()) { 0310 // Root is the bookmark toolbar if none has been set. 0311 // Make it explicit to speed up invocations of findToolbar() 0312 root().internalElement().setAttribute(QStringLiteral("toolbar"), QStringLiteral("yes")); 0313 return root(); 0314 } else { 0315 return KBookmarkGroup(elem); 0316 } 0317 } 0318 0319 KBookmark KBookmarkManager::findByAddress(const QString &address) 0320 { 0321 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::findByAddress " << address; 0322 KBookmark result = root(); 0323 // The address is something like /5/10/2+ 0324 static const QRegularExpression separator(QStringLiteral("[/+]")); 0325 const QStringList addresses = address.split(separator, Qt::SkipEmptyParts); 0326 // qCWarning(KBOOKMARKS_LOG) << addresses.join(","); 0327 for (QStringList::const_iterator it = addresses.begin(); it != addresses.end();) { 0328 bool append = ((*it) == QLatin1String("+")); 0329 uint number = (*it).toUInt(); 0330 Q_ASSERT(result.isGroup()); 0331 KBookmarkGroup group = result.toGroup(); 0332 KBookmark bk = group.first(); 0333 KBookmark lbk = bk; // last non-null bookmark 0334 for (uint i = 0; ((i < number) || append) && !bk.isNull(); ++i) { 0335 lbk = bk; 0336 bk = group.next(bk); 0337 // qCWarning(KBOOKMARKS_LOG) << i; 0338 } 0339 it++; 0340 // qCWarning(KBOOKMARKS_LOG) << "found section"; 0341 result = bk; 0342 } 0343 if (result.isNull()) { 0344 qCWarning(KBOOKMARKS_LOG) << "KBookmarkManager::findByAddress: couldn't find item " << address; 0345 } 0346 // qCWarning(KBOOKMARKS_LOG) << "found " << result.address(); 0347 return result; 0348 } 0349 0350 void KBookmarkManager::emitChanged() 0351 { 0352 emitChanged(root()); 0353 } 0354 0355 void KBookmarkManager::emitChanged(const KBookmarkGroup &group) 0356 { 0357 (void)save(); // KDE5 TODO: emitChanged should return a bool? Maybe rename it to saveAndEmitChanged? 0358 0359 // Tell the other processes too 0360 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::emitChanged : broadcasting change " << group.address(); 0361 0362 Q_EMIT changed(group.address()); 0363 } 0364 0365 /////// 0366 bool KBookmarkManager::updateAccessMetadata(const QString &url) 0367 { 0368 d->m_map.update(this); 0369 QList<KBookmark> list = d->m_map.find(url); 0370 if (list.isEmpty()) { 0371 return false; 0372 } 0373 0374 for (QList<KBookmark>::iterator it = list.begin(); it != list.end(); ++it) { 0375 (*it).updateAccessMetadata(); 0376 } 0377 0378 return true; 0379 } 0380 0381 #include "moc_kbookmarkmanager.cpp"