File indexing completed on 2023-10-03 10:29:19
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 "kbookmarkmenu_p.h" 0013 #include "kbookmarks_debug.h" 0014 0015 #include <QApplication> 0016 #include <QDir> 0017 #include <QFile> 0018 #include <QFileInfo> 0019 #include <QMessageBox> 0020 #include <QProcess> 0021 #include <QReadWriteLock> 0022 #include <QRegularExpression> 0023 #include <QTextStream> 0024 #include <QThread> 0025 0026 #include <KBackup> 0027 #include <KConfig> 0028 #include <KConfigGroup> 0029 #include <KDirWatch> 0030 #include <QSaveFile> 0031 #include <QStandardPaths> 0032 0033 namespace 0034 { 0035 namespace Strings 0036 { 0037 QString piData() 0038 { 0039 return QStringLiteral("version=\"1.0\" encoding=\"UTF-8\""); 0040 } 0041 } 0042 } 0043 0044 class KBookmarkManagerList : public QList<KBookmarkManager *> 0045 { 0046 public: 0047 KBookmarkManagerList(); 0048 ~KBookmarkManagerList() 0049 { 0050 cleanup(); 0051 } 0052 void cleanup() 0053 { 0054 QList<KBookmarkManager *> copy = *this; 0055 qDeleteAll(copy); // auto-delete functionality 0056 clear(); 0057 } 0058 0059 QReadWriteLock lock; 0060 }; 0061 0062 Q_GLOBAL_STATIC(KBookmarkManagerList, s_pSelf) 0063 0064 KBookmarkManagerList::KBookmarkManagerList() 0065 { 0066 if (s_pSelf.exists()) { 0067 s_pSelf->cleanup(); 0068 } 0069 } 0070 0071 class KBookmarkMap : private KBookmarkGroupTraverser 0072 { 0073 public: 0074 KBookmarkMap() 0075 : m_mapNeedsUpdate(true) 0076 { 0077 } 0078 void setNeedsUpdate() 0079 { 0080 m_mapNeedsUpdate = true; 0081 } 0082 void update(KBookmarkManager *); 0083 QList<KBookmark> find(const QString &url) const 0084 { 0085 return m_bk_map.value(url); 0086 } 0087 0088 private: 0089 void visit(const KBookmark &) override; 0090 void visitEnter(const KBookmarkGroup &) override 0091 { 0092 ; 0093 } 0094 void visitLeave(const KBookmarkGroup &) override 0095 { 0096 ; 0097 } 0098 0099 private: 0100 typedef QList<KBookmark> KBookmarkList; 0101 QMap<QString, KBookmarkList> m_bk_map; 0102 bool m_mapNeedsUpdate; 0103 }; 0104 0105 void KBookmarkMap::update(KBookmarkManager *manager) 0106 { 0107 if (m_mapNeedsUpdate) { 0108 m_mapNeedsUpdate = false; 0109 0110 m_bk_map.clear(); 0111 KBookmarkGroup root = manager->root(); 0112 traverse(root); 0113 } 0114 } 0115 0116 void KBookmarkMap::visit(const KBookmark &bk) 0117 { 0118 if (!bk.isSeparator()) { 0119 // add bookmark to url map 0120 m_bk_map[bk.internalElement().attribute(QStringLiteral("href"))].append(bk); 0121 } 0122 } 0123 0124 // ######################### 0125 // KBookmarkManagerPrivate 0126 class KBookmarkManagerPrivate 0127 { 0128 public: 0129 KBookmarkManagerPrivate(bool bDocIsloaded) 0130 : m_doc(QStringLiteral("xbel")) 0131 , m_docIsLoaded(bDocIsloaded) 0132 , m_update(false) 0133 , m_dialogAllowed(true) 0134 , m_dialogParent(nullptr) 0135 , m_dirWatch(nullptr) 0136 { 0137 } 0138 0139 mutable QDomDocument m_doc; 0140 mutable QDomDocument m_toolbarDoc; 0141 QString m_bookmarksFile; 0142 mutable bool m_docIsLoaded; 0143 bool m_update; 0144 bool m_dialogAllowed; 0145 QWidget *m_dialogParent; 0146 0147 KDirWatch *m_dirWatch; // for monitoring changes on bookmark files 0148 0149 KBookmarkMap m_map; 0150 }; 0151 0152 // ################ 0153 // KBookmarkManager 0154 0155 static KBookmarkManager *lookupExisting(const QString &bookmarksFile) 0156 { 0157 for (KBookmarkManagerList::ConstIterator bmit = s_pSelf()->constBegin(), bmend = s_pSelf()->constEnd(); bmit != bmend; ++bmit) { 0158 if ((*bmit)->path() == bookmarksFile) { 0159 return *bmit; 0160 } 0161 } 0162 return nullptr; 0163 } 0164 0165 KBookmarkManager *KBookmarkManager::managerForFile(const QString &bookmarksFile) 0166 { 0167 KBookmarkManager *mgr(nullptr); 0168 { 0169 QReadLocker readLock(&s_pSelf()->lock); 0170 mgr = lookupExisting(bookmarksFile); 0171 if (mgr) { 0172 return mgr; 0173 } 0174 } 0175 0176 QWriteLocker writeLock(&s_pSelf()->lock); 0177 mgr = lookupExisting(bookmarksFile); 0178 if (mgr) { 0179 return mgr; 0180 } 0181 0182 mgr = new KBookmarkManager(bookmarksFile); 0183 s_pSelf()->append(mgr); 0184 return mgr; 0185 } 0186 0187 static QDomElement createXbelTopLevelElement(QDomDocument &doc) 0188 { 0189 QDomElement topLevel = doc.createElement(QStringLiteral("xbel")); 0190 topLevel.setAttribute(QStringLiteral("xmlns:mime"), QStringLiteral("http://www.freedesktop.org/standards/shared-mime-info")); 0191 topLevel.setAttribute(QStringLiteral("xmlns:bookmark"), QStringLiteral("http://www.freedesktop.org/standards/desktop-bookmarks")); 0192 topLevel.setAttribute(QStringLiteral("xmlns:kdepriv"), QStringLiteral("http://www.kde.org/kdepriv")); 0193 doc.appendChild(topLevel); 0194 doc.insertBefore(doc.createProcessingInstruction(QStringLiteral("xml"), Strings::piData()), topLevel); 0195 return topLevel; 0196 } 0197 0198 KBookmarkManager::KBookmarkManager(const QString &bookmarksFile) 0199 : d(new KBookmarkManagerPrivate(false)) 0200 { 0201 d->m_update = true; 0202 0203 Q_ASSERT(!bookmarksFile.isEmpty()); 0204 d->m_bookmarksFile = bookmarksFile; 0205 0206 if (!QFile::exists(d->m_bookmarksFile)) { 0207 createXbelTopLevelElement(d->m_doc); 0208 } else { 0209 parse(); 0210 } 0211 d->m_docIsLoaded = true; 0212 0213 // start KDirWatch 0214 KDirWatch::self()->addFile(d->m_bookmarksFile); 0215 QObject::connect(KDirWatch::self(), &KDirWatch::dirty, this, &KBookmarkManager::slotFileChanged); 0216 QObject::connect(KDirWatch::self(), &KDirWatch::created, this, &KBookmarkManager::slotFileChanged); 0217 QObject::connect(KDirWatch::self(), &KDirWatch::deleted, this, &KBookmarkManager::slotFileChanged); 0218 0219 // qCDebug(KBOOKMARKS_LOG) << "starting KDirWatch for" << d->m_bookmarksFile; 0220 } 0221 0222 KBookmarkManager::KBookmarkManager() 0223 : d(new KBookmarkManagerPrivate(true)) 0224 { 0225 d->m_update = false; // TODO - make it read/write 0226 0227 createXbelTopLevelElement(d->m_doc); 0228 } 0229 0230 void KBookmarkManager::slotFileChanged(const QString &path) 0231 { 0232 if (path == d->m_bookmarksFile) { 0233 // qCDebug(KBOOKMARKS_LOG) << "file changed (KDirWatch) " << path ; 0234 // Reparse 0235 parse(); 0236 // Tell our GUI 0237 // (emit where group is "" to directly mark the root menu as dirty) 0238 Q_EMIT changed(QLatin1String(""), QString()); 0239 } 0240 } 0241 0242 KBookmarkManager::~KBookmarkManager() 0243 { 0244 if (!s_pSelf.isDestroyed()) { 0245 s_pSelf()->removeAll(this); 0246 } 0247 } 0248 0249 bool KBookmarkManager::autoErrorHandlingEnabled() const 0250 { 0251 return d->m_dialogAllowed; 0252 } 0253 0254 void KBookmarkManager::setAutoErrorHandlingEnabled(bool enable, QWidget *parent) 0255 { 0256 d->m_dialogAllowed = enable; 0257 d->m_dialogParent = parent; 0258 } 0259 0260 void KBookmarkManager::setUpdate(bool update) 0261 { 0262 d->m_update = update; 0263 } 0264 0265 QDomDocument KBookmarkManager::internalDocument() const 0266 { 0267 if (!d->m_docIsLoaded) { 0268 parse(); 0269 d->m_toolbarDoc.clear(); 0270 } 0271 return d->m_doc; 0272 } 0273 0274 void KBookmarkManager::parse() const 0275 { 0276 d->m_docIsLoaded = true; 0277 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::parse " << d->m_bookmarksFile; 0278 QFile file(d->m_bookmarksFile); 0279 if (!file.open(QIODevice::ReadOnly)) { 0280 qCWarning(KBOOKMARKS_LOG) << "Can't open" << d->m_bookmarksFile; 0281 d->m_doc = QDomDocument(QStringLiteral("xbel")); 0282 createXbelTopLevelElement(d->m_doc); 0283 return; 0284 } 0285 d->m_doc = QDomDocument(QStringLiteral("xbel")); 0286 d->m_doc.setContent(&file); 0287 0288 if (d->m_doc.documentElement().isNull()) { 0289 qCWarning(KBOOKMARKS_LOG) << "KBookmarkManager::parse : main tag is missing, creating default " << d->m_bookmarksFile; 0290 QDomElement element = d->m_doc.createElement(QStringLiteral("xbel")); 0291 d->m_doc.appendChild(element); 0292 } 0293 0294 QDomElement docElem = d->m_doc.documentElement(); 0295 0296 QString mainTag = docElem.tagName(); 0297 if (mainTag != QLatin1String("xbel")) { 0298 qCWarning(KBOOKMARKS_LOG) << "KBookmarkManager::parse : unknown main tag " << mainTag; 0299 } 0300 0301 QDomNode n = d->m_doc.documentElement().previousSibling(); 0302 if (n.isProcessingInstruction()) { 0303 QDomProcessingInstruction pi = n.toProcessingInstruction(); 0304 pi.parentNode().removeChild(pi); 0305 } 0306 0307 QDomProcessingInstruction pi; 0308 pi = d->m_doc.createProcessingInstruction(QStringLiteral("xml"), Strings::piData()); 0309 d->m_doc.insertBefore(pi, docElem); 0310 0311 file.close(); 0312 0313 d->m_map.setNeedsUpdate(); 0314 } 0315 0316 bool KBookmarkManager::save(bool toolbarCache) const 0317 { 0318 return saveAs(d->m_bookmarksFile, toolbarCache); 0319 } 0320 0321 bool KBookmarkManager::saveAs(const QString &filename, bool toolbarCache) const 0322 { 0323 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::save " << filename; 0324 0325 // Save the bookmark toolbar folder for quick loading 0326 // but only when it will actually make things quicker 0327 const QString cacheFilename = filename + QLatin1String(".tbcache"); 0328 if (toolbarCache && !root().isToolbarGroup()) { 0329 QSaveFile cacheFile(cacheFilename); 0330 if (cacheFile.open(QIODevice::WriteOnly)) { 0331 QString str; 0332 QTextStream stream(&str, QIODevice::WriteOnly); 0333 stream << root().findToolbar(); 0334 const QByteArray cstr = str.toUtf8(); 0335 cacheFile.write(cstr.data(), cstr.length()); 0336 cacheFile.commit(); 0337 } 0338 } else { // remove any (now) stale cache 0339 QFile::remove(cacheFilename); 0340 } 0341 0342 // Create parent dirs 0343 QFileInfo info(filename); 0344 QDir().mkpath(info.absolutePath()); 0345 0346 QSaveFile file(filename); 0347 if (file.open(QIODevice::WriteOnly)) { 0348 KBackup::simpleBackupFile(file.fileName(), QString(), QStringLiteral(".bak")); 0349 QTextStream stream(&file); 0350 // In Qt6 it's UTF-8 by default 0351 stream << internalDocument().toString(); 0352 stream.flush(); 0353 if (file.commit()) { 0354 return true; 0355 } 0356 } 0357 0358 static int hadSaveError = false; 0359 if (!hadSaveError) { 0360 QString err = tr("Unable to save bookmarks in %1. Reported error was: %2. " 0361 "This error message will only be shown once. The cause " 0362 "of the error needs to be fixed as quickly as possible, " 0363 "which is most likely a full hard drive.") 0364 .arg(filename, file.errorString()); 0365 0366 if (d->m_dialogAllowed && qobject_cast<QApplication *>(qApp) && QThread::currentThread() == qApp->thread()) { 0367 QMessageBox::critical(QApplication::activeWindow(), QApplication::applicationName(), err); 0368 } 0369 0370 qCCritical(KBOOKMARKS_LOG) 0371 << QStringLiteral("Unable to save bookmarks in %1. File reported the following error-code: %2.").arg(filename).arg(file.error()); 0372 Q_EMIT const_cast<KBookmarkManager *>(this)->error(err); 0373 } 0374 hadSaveError = true; 0375 return false; 0376 } 0377 0378 QString KBookmarkManager::path() const 0379 { 0380 return d->m_bookmarksFile; 0381 } 0382 0383 KBookmarkGroup KBookmarkManager::root() const 0384 { 0385 return KBookmarkGroup(internalDocument().documentElement()); 0386 } 0387 0388 KBookmarkGroup KBookmarkManager::toolbar() 0389 { 0390 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::toolbar begin"; 0391 // Only try to read from a toolbar cache if the full document isn't loaded 0392 if (!d->m_docIsLoaded) { 0393 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::toolbar trying cache"; 0394 const QString cacheFilename = d->m_bookmarksFile + QLatin1String(".tbcache"); 0395 QFileInfo bmInfo(d->m_bookmarksFile); 0396 QFileInfo cacheInfo(cacheFilename); 0397 if (d->m_toolbarDoc.isNull() && QFile::exists(cacheFilename) && bmInfo.lastModified() < cacheInfo.lastModified()) { 0398 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::toolbar reading file"; 0399 QFile file(cacheFilename); 0400 0401 if (file.open(QIODevice::ReadOnly)) { 0402 d->m_toolbarDoc = QDomDocument(QStringLiteral("cache")); 0403 d->m_toolbarDoc.setContent(&file); 0404 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::toolbar opened"; 0405 } 0406 } 0407 if (!d->m_toolbarDoc.isNull()) { 0408 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::toolbar returning element"; 0409 QDomElement elem = d->m_toolbarDoc.firstChild().toElement(); 0410 return KBookmarkGroup(elem); 0411 } 0412 } 0413 0414 // Fallback to the normal way if there is no cache or if the bookmark file 0415 // is already loaded 0416 QDomElement elem = root().findToolbar(); 0417 if (elem.isNull()) { 0418 // Root is the bookmark toolbar if none has been set. 0419 // Make it explicit to speed up invocations of findToolbar() 0420 root().internalElement().setAttribute(QStringLiteral("toolbar"), QStringLiteral("yes")); 0421 return root(); 0422 } else { 0423 return KBookmarkGroup(elem); 0424 } 0425 } 0426 0427 KBookmark KBookmarkManager::findByAddress(const QString &address) 0428 { 0429 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::findByAddress " << address; 0430 KBookmark result = root(); 0431 // The address is something like /5/10/2+ 0432 static const QRegularExpression separator(QStringLiteral("[/+]")); 0433 const QStringList addresses = address.split(separator, Qt::SkipEmptyParts); 0434 // qCWarning(KBOOKMARKS_LOG) << addresses.join(","); 0435 for (QStringList::const_iterator it = addresses.begin(); it != addresses.end();) { 0436 bool append = ((*it) == QLatin1String("+")); 0437 uint number = (*it).toUInt(); 0438 Q_ASSERT(result.isGroup()); 0439 KBookmarkGroup group = result.toGroup(); 0440 KBookmark bk = group.first(); 0441 KBookmark lbk = bk; // last non-null bookmark 0442 for (uint i = 0; ((i < number) || append) && !bk.isNull(); ++i) { 0443 lbk = bk; 0444 bk = group.next(bk); 0445 // qCWarning(KBOOKMARKS_LOG) << i; 0446 } 0447 it++; 0448 // qCWarning(KBOOKMARKS_LOG) << "found section"; 0449 result = bk; 0450 } 0451 if (result.isNull()) { 0452 qCWarning(KBOOKMARKS_LOG) << "KBookmarkManager::findByAddress: couldn't find item " << address; 0453 } 0454 // qCWarning(KBOOKMARKS_LOG) << "found " << result.address(); 0455 return result; 0456 } 0457 0458 void KBookmarkManager::emitChanged() 0459 { 0460 emitChanged(root()); 0461 } 0462 0463 void KBookmarkManager::emitChanged(const KBookmarkGroup &group) 0464 { 0465 (void)save(); // KDE5 TODO: emitChanged should return a bool? Maybe rename it to saveAndEmitChanged? 0466 0467 // Tell the other processes too 0468 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::emitChanged : broadcasting change " << group.address(); 0469 0470 Q_EMIT bookmarksChanged(group.address()); 0471 0472 // We do get our own broadcast, so no need for this anymore 0473 // emit changed( group ); 0474 } 0475 0476 void KBookmarkManager::emitConfigChanged() 0477 { 0478 Q_EMIT bookmarkConfigChanged(); 0479 } 0480 0481 void KBookmarkManager::notifyCompleteChange(const QString &caller) // DBUS call 0482 { 0483 if (!d->m_update) { 0484 return; 0485 } 0486 0487 // qCDebug(KBOOKMARKS_LOG) << "KBookmarkManager::notifyCompleteChange"; 0488 // The bk editor tells us we should reload everything 0489 // Reparse 0490 parse(); 0491 // Tell our GUI 0492 // (emit where group is "" to directly mark the root menu as dirty) 0493 Q_EMIT changed(QLatin1String(""), caller); 0494 } 0495 0496 /////// 0497 bool KBookmarkManager::updateAccessMetadata(const QString &url) 0498 { 0499 d->m_map.update(this); 0500 QList<KBookmark> list = d->m_map.find(url); 0501 if (list.isEmpty()) { 0502 return false; 0503 } 0504 0505 for (QList<KBookmark>::iterator it = list.begin(); it != list.end(); ++it) { 0506 (*it).updateAccessMetadata(); 0507 } 0508 0509 return true; 0510 } 0511 0512 KBookmarkSettings *KBookmarkSettings::s_self = nullptr; 0513 0514 void KBookmarkSettings::readSettings() 0515 { 0516 KConfig config(QStringLiteral("kbookmarkrc"), KConfig::NoGlobals); 0517 KConfigGroup cg(&config, "Bookmarks"); 0518 0519 // add bookmark dialog usage - no reparse 0520 s_self->m_advancedaddbookmark = cg.readEntry("AdvancedAddBookmarkDialog", false); 0521 0522 // this one alters the menu, therefore it needs a reparse 0523 s_self->m_contextmenu = cg.readEntry("ContextMenuActions", true); 0524 } 0525 0526 KBookmarkSettings *KBookmarkSettings::self() 0527 { 0528 if (!s_self) { 0529 s_self = new KBookmarkSettings; 0530 readSettings(); 0531 } 0532 return s_self; 0533 } 0534 0535 #include "moc_kbookmarkmanager.cpp"