// -*- c-basic-offset:4; indent-tabs-mode:nil -*- // vim: set ts=4 sts=4 sw=4 et: /* This file is part of the KDE libraries Copyright (C) 2000 David Faure Copyright (C) 2003 Alexander Kellett Copyright (C) 2008 Norbert Frese This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kbookmarkmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kbookmarkmenu.h" #include "kbookmarkmenu_p.h" #include "kbookmarkimporter.h" #include "kbookmarkdialog.h" #include "kbookmarkmanageradaptor_p.h" #define BOOKMARK_CHANGE_NOTIFY_INTERFACE "org.kde.KIO.KBookmarkManager" class KBookmarkManagerList : public QList { public: ~KBookmarkManagerList() { qDeleteAll( begin() , end() ); // auto-delete functionality } QMutex mutex; }; K_GLOBAL_STATIC(KBookmarkManagerList, s_pSelf) class KBookmarkMap : private KBookmarkGroupTraverser { public: KBookmarkMap() : m_mapNeedsUpdate(true) {} void setNeedsUpdate() { m_mapNeedsUpdate = true; } void update(KBookmarkManager*); QList find( const QString &url ) const { return m_bk_map.value(url); } private: virtual void visit(const KBookmark &); virtual void visitEnter(const KBookmarkGroup &) { ; } virtual void visitLeave(const KBookmarkGroup &) { ; } private: typedef QList KBookmarkList; QMap m_bk_map; bool m_mapNeedsUpdate; }; void KBookmarkMap::update(KBookmarkManager *manager) { if (m_mapNeedsUpdate) { m_mapNeedsUpdate = false; m_bk_map.clear(); KBookmarkGroup root = manager->root(); traverse(root); QMapIterator iter(m_bk_map); while (iter.hasNext()) { iter.next(); foreach (KBookmark it, iter.value()) { kDebug() << "updating favicon for" << it.url(); it.setIcon(KMimeType::favIconForUrl(it.url())); } } } } void KBookmarkMap::visit(const KBookmark &bk) { if (!bk.isSeparator()) { // add bookmark to url map m_bk_map[bk.internalElement().attribute("href")].append(bk); } } // ######################### // KBookmarkManager::Private class KBookmarkManager::Private { public: Private(bool bDocIsloaded, const QString &dbusObjectName = QString()) : m_doc("xbel") , m_dbusObjectName(dbusObjectName) , m_docIsLoaded(bDocIsloaded) , m_update(false) , m_dialogAllowed(true) , m_dialogParent(0) , m_browserEditor(false) , m_typeExternal(false) , m_kDirWatch(0) {} ~Private() { delete m_kDirWatch; } mutable QDomDocument m_doc; mutable QDomDocument m_toolbarDoc; QString m_bookmarksFile; QString m_dbusObjectName; mutable bool m_docIsLoaded; bool m_update; bool m_dialogAllowed; QWidget *m_dialogParent; bool m_browserEditor; QString m_editorCaption; bool m_typeExternal; KDirWatch * m_kDirWatch; // for external bookmark files KBookmarkMap m_map; }; // ################ // KBookmarkManager static KBookmarkManager* lookupExisting(const QString& bookmarksFile) { for ( KBookmarkManagerList::ConstIterator bmit = s_pSelf->constBegin(), bmend = s_pSelf->constEnd(); bmit != bmend; ++bmit ) { if ( (*bmit)->path() == bookmarksFile ) return *bmit; } return 0; } KBookmarkManager* KBookmarkManager::managerForFile( const QString& bookmarksFile, const QString& dbusObjectName ) { QMutexLocker lock(&s_pSelf->mutex); KBookmarkManager* mgr = lookupExisting(bookmarksFile); if (mgr) { return mgr; } mgr = new KBookmarkManager( bookmarksFile, dbusObjectName ); s_pSelf->append( mgr ); return mgr; } KBookmarkManager* KBookmarkManager::managerForExternalFile( const QString& bookmarksFile ) { QMutexLocker lock(&s_pSelf->mutex); KBookmarkManager* mgr = lookupExisting(bookmarksFile); if (mgr) { return mgr; } mgr = new KBookmarkManager( bookmarksFile ); s_pSelf->append( mgr ); return mgr; } // principally used for filtered toolbars KBookmarkManager* KBookmarkManager::createTempManager() { KBookmarkManager* mgr = new KBookmarkManager(); s_pSelf->append( mgr ); return mgr; } #define PI_DATA "version=\"1.0\" encoding=\"UTF-8\"" static QDomElement createXbelTopLevelElement(QDomDocument & doc) { QDomElement topLevel = doc.createElement("xbel"); topLevel.setAttribute("xmlns:mime", "http://www.freedesktop.org/standards/shared-mime-info"); topLevel.setAttribute("xmlns:bookmark", "http://www.freedesktop.org/standards/desktop-bookmarks"); topLevel.setAttribute("xmlns:kdepriv", "http://www.kde.org/kdepriv"); doc.appendChild( topLevel ); doc.insertBefore( doc.createProcessingInstruction( "xml", PI_DATA), topLevel ); return topLevel; } KBookmarkManager::KBookmarkManager( const QString & bookmarksFile, const QString & dbusObjectName) : d(new Private(false, dbusObjectName)) { if(dbusObjectName.isNull()) // get dbusObjectName from file if ( QFile::exists(d->m_bookmarksFile) ) parse(); //sets d->m_dbusObjectName init( "/KBookmarkManager/"+d->m_dbusObjectName ); d->m_update = true; Q_ASSERT( !bookmarksFile.isEmpty() ); d->m_bookmarksFile = bookmarksFile; if ( !QFile::exists(d->m_bookmarksFile) ) { QDomElement topLevel = createXbelTopLevelElement(d->m_doc); topLevel.setAttribute("dbusName", dbusObjectName); d->m_docIsLoaded = true; } } KBookmarkManager::KBookmarkManager(const QString & bookmarksFile) : d(new Private(false)) { // use KDirWatch to monitor this bookmarks file d->m_typeExternal = true; d->m_update = true; Q_ASSERT( !bookmarksFile.isEmpty() ); d->m_bookmarksFile = bookmarksFile; if ( !QFile::exists(d->m_bookmarksFile) ) { createXbelTopLevelElement(d->m_doc); } else { parse(); } d->m_docIsLoaded = true; // start KDirWatch d->m_kDirWatch = new KDirWatch; d->m_kDirWatch->addFile(d->m_bookmarksFile); QObject::connect( d->m_kDirWatch, SIGNAL(dirty(const QString&)), this, SLOT(slotFileChanged(const QString&))); kDebug(7043) << "starting KDirWatch for " << d->m_bookmarksFile; } KBookmarkManager::KBookmarkManager( ) : d(new Private(true)) { init( "/KBookmarkManager/generated" ); d->m_update = false; // TODO - make it read/write createXbelTopLevelElement(d->m_doc); } void KBookmarkManager::init( const QString& dbusPath ) { // A KBookmarkManager without a dbus name is a temporary one, like those used by importers; // no need to register them to dbus if ( dbusPath != "/KBookmarkManager/" && dbusPath != "/KBookmarkManager/generated") { new KBookmarkManagerAdaptor(this); QDBusConnection::sessionBus().registerObject( dbusPath, this ); QDBusConnection::sessionBus().connect(QString(), dbusPath, BOOKMARK_CHANGE_NOTIFY_INTERFACE, "bookmarksChanged", this, SLOT(notifyChanged(QString,QDBusMessage))); QDBusConnection::sessionBus().connect(QString(), dbusPath, BOOKMARK_CHANGE_NOTIFY_INTERFACE, "bookmarkConfigChanged", this, SLOT(notifyConfigChanged())); } } void KBookmarkManager::slotFileChanged(const QString& path) { if (path == d->m_bookmarksFile) { kDebug(7043) << "file changed (KDirWatch) " << path ; // Reparse parse(); // Tell our GUI // (emit where group is "" to directly mark the root menu as dirty) emit changed( "", QString() ); } } KBookmarkManager::~KBookmarkManager() { if (!s_pSelf.isDestroyed()) { s_pSelf->removeAll(this); } delete d; } bool KBookmarkManager::autoErrorHandlingEnabled() const { return d->m_dialogAllowed; } void KBookmarkManager::setAutoErrorHandlingEnabled( bool enable, QWidget *parent ) { d->m_dialogAllowed = enable; d->m_dialogParent = parent; } void KBookmarkManager::setUpdate( bool update ) { d->m_update = update; } QDomDocument KBookmarkManager::internalDocument() const { if(!d->m_docIsLoaded) { parse(); d->m_toolbarDoc.clear(); } return d->m_doc; } void KBookmarkManager::parse() const { d->m_docIsLoaded = true; //kDebug(7043) << "KBookmarkManager::parse " << d->m_bookmarksFile; QFile file( d->m_bookmarksFile ); if ( !file.open( QIODevice::ReadOnly ) ) { kWarning() << "Can't open " << d->m_bookmarksFile; return; } d->m_doc = QDomDocument("xbel"); d->m_doc.setContent( &file ); if ( d->m_doc.documentElement().isNull() ) { kWarning() << "KBookmarkManager::parse : main tag is missing, creating default " << d->m_bookmarksFile; QDomElement element = d->m_doc.createElement("xbel"); d->m_doc.appendChild(element); } QDomElement docElem = d->m_doc.documentElement(); QString mainTag = docElem.tagName(); if ( mainTag != "xbel" ) kWarning() << "KBookmarkManager::parse : unknown main tag " << mainTag; if(d->m_dbusObjectName.isNull()) { d->m_dbusObjectName = docElem.attribute("dbusName"); } else if(docElem.attribute("dbusName") != d->m_dbusObjectName) { docElem.setAttribute("dbusName", d->m_dbusObjectName); save(); } QDomNode n = d->m_doc.documentElement().previousSibling(); if ( n.isProcessingInstruction() ) { QDomProcessingInstruction pi = n.toProcessingInstruction(); pi.parentNode().removeChild(pi); } QDomProcessingInstruction pi; pi = d->m_doc.createProcessingInstruction( "xml", PI_DATA ); d->m_doc.insertBefore( pi, docElem ); file.close(); d->m_map.setNeedsUpdate(); } bool KBookmarkManager::save( bool toolbarCache ) const { return saveAs( d->m_bookmarksFile, toolbarCache ); } bool KBookmarkManager::saveAs( const QString & filename, bool toolbarCache ) const { kDebug(7043) << "KBookmarkManager::save " << filename; // Save the bookmark toolbar folder for quick loading // but only when it will actually make things quicker const QString cacheFilename = filename + QLatin1String(".tbcache"); if(toolbarCache && !root().isToolbarGroup()) { KSaveFile cacheFile( cacheFilename ); if ( cacheFile.open() ) { QString str; QTextStream stream(&str, QIODevice::WriteOnly); stream << root().findToolbar(); const QByteArray cstr = str.toUtf8(); cacheFile.write( cstr.data(), cstr.length() ); cacheFile.finalize(); } } else // remove any (now) stale cache { QFile::remove( cacheFilename ); } KSaveFile file( filename ); if ( file.open() ) { QTextStream stream(&file); stream.setCodec( QTextCodec::codecForName( "UTF-8" ) ); stream << internalDocument().toString(); stream.flush(); if ( file.finalize() ) { return true; } } static int hadSaveError = false; file.abort(); if ( !hadSaveError ) { QString err = i18n("Unable to save bookmarks in %1. Reported error was: %2. " "This error message will only be shown once. The cause " "of the error needs to be fixed as quickly as possible, " "which is most likely a full hard drive.", filename, file.errorString()); if (d->m_dialogAllowed && qApp->type() != QApplication::Tty && QThread::currentThread() == qApp->thread()) KMessageBox::error( QApplication::activeWindow(), err ); kError() << QString("Unable to save bookmarks in %1. File reported the following error-code: %2.").arg(filename).arg(file.error()); emit const_cast(this)->error(err); } hadSaveError = true; return false; } QString KBookmarkManager::path() const { return d->m_bookmarksFile; } KBookmarkGroup KBookmarkManager::root() const { return KBookmarkGroup(internalDocument().documentElement()); } KBookmarkGroup KBookmarkManager::toolbar() { kDebug(7043) << "KBookmarkManager::toolbar begin"; // Only try to read from a toolbar cache if the full document isn't loaded if(!d->m_docIsLoaded) { kDebug(7043) << "KBookmarkManager::toolbar trying cache"; const QString cacheFilename = d->m_bookmarksFile + QLatin1String(".tbcache"); QFileInfo bmInfo(d->m_bookmarksFile); QFileInfo cacheInfo(cacheFilename); if (d->m_toolbarDoc.isNull() && QFile::exists(cacheFilename) && bmInfo.lastModified() < cacheInfo.lastModified()) { kDebug(7043) << "KBookmarkManager::toolbar reading file"; QFile file( cacheFilename ); if ( file.open( QIODevice::ReadOnly ) ) { d->m_toolbarDoc = QDomDocument("cache"); d->m_toolbarDoc.setContent( &file ); kDebug(7043) << "KBookmarkManager::toolbar opened"; } } if (!d->m_toolbarDoc.isNull()) { kDebug(7043) << "KBookmarkManager::toolbar returning element"; QDomElement elem = d->m_toolbarDoc.firstChild().toElement(); return KBookmarkGroup(elem); } } // Fallback to the normal way if there is no cache or if the bookmark file // is already loaded QDomElement elem = root().findToolbar(); if (elem.isNull()) { // Root is the bookmark toolbar if none has been set. // Make it explicit to speed up invocations of findToolbar() root().internalElement().setAttribute("toolbar", "yes"); return root(); } else return KBookmarkGroup(elem); } KBookmark KBookmarkManager::findByAddress( const QString & address ) { //kDebug(7043) << "KBookmarkManager::findByAddress " << address; KBookmark result = root(); // The address is something like /5/10/2+ const QStringList addresses = address.split(QRegExp("[/+]"),QString::SkipEmptyParts); // kWarning() << addresses.join(","); foreach (const QString it, addresses) { bool append = (it == "+"); uint number = it.toUInt(); Q_ASSERT(result.isGroup()); KBookmarkGroup group = result.toGroup(); KBookmark bk = group.first(), lbk = bk; // last non-null bookmark for ( uint i = 0 ; ( (im_update) return; kDebug(7043) << "KBookmarkManager::notifyCompleteChange"; // The bk editor tells us we should reload everything // Reparse parse(); // Tell our GUI // (emit where group is "" to directly mark the root menu as dirty) emit changed( "", caller ); } void KBookmarkManager::notifyConfigChanged() // DBUS call { kDebug() << "reloaded bookmark config!"; KBookmarkSettings::self()->readSettings(); parse(); // reload, and thusly recreate the menus emit configChanged(); } void KBookmarkManager::notifyChanged( const QString &groupAddress, const QDBusMessage &msg ) // DBUS call { kDebug() << "KBookmarkManager::notifyChanged ( "<m_update) return; // Reparse (the whole file, no other choice) // if someone else notified us if (msg.service() != QDBusConnection::sessionBus().baseService()) parse(); //kDebug(7043) << "KBookmarkManager::notifyChanged " << groupAddress; //KBookmarkGroup group = findByAddress( groupAddress ).toGroup(); //Q_ASSERT(!group.isNull()); emit changed( groupAddress, QString() ); } void KBookmarkManager::setEditorOptions( const QString& caption, bool browser ) { d->m_editorCaption = caption; d->m_browserEditor = browser; } void KBookmarkManager::slotEditBookmarks() { QStringList args; if ( !d->m_editorCaption.isEmpty() ) args << QLatin1String("--customcaption") << d->m_editorCaption; if ( !d->m_browserEditor ) args << QLatin1String("--nobrowser"); if( !d->m_dbusObjectName.isEmpty() ) args << QLatin1String("--dbusObjectName") << d->m_dbusObjectName; args << d->m_bookmarksFile; QProcess::startDetached("keditbookmarks", args); } void KBookmarkManager::slotEditBookmarksAtAddress( const QString& address ) { QStringList args; if ( !d->m_editorCaption.isEmpty() ) args << QLatin1String("--customcaption") << d->m_editorCaption; if ( !d->m_browserEditor ) args << QLatin1String("--nobrowser"); if( !d->m_dbusObjectName.isEmpty() ) args << QLatin1String("--dbusObjectName") << d->m_dbusObjectName; args << QLatin1String("--address") << address << d->m_bookmarksFile; QProcess::startDetached("keditbookmarks", args); } /////// bool KBookmarkManager::updateAccessMetadata( const QString & url ) { d->m_map.update(this); QList list = d->m_map.find(url); foreach ( KBookmark it, list ) { it.updateAccessMetadata(); } return true; } void KBookmarkManager::updateFavicon( const QString &url, const QString &faviconurl ) { d->m_map.update(this); QList list = d->m_map.find(url); foreach ( KBookmark it, list ) { KUrl iconurl(faviconurl); it.setIcon(KMimeType::favIconForUrl(iconurl)); } } KBookmarkManager* KBookmarkManager::userBookmarksManager() { const QString bookmarksFile = KStandardDirs::locateLocal("data", QString::fromLatin1("konqueror/bookmarks.xml")); KBookmarkManager* bookmarkManager = KBookmarkManager::managerForFile( bookmarksFile, "konqueror" ); bookmarkManager->setEditorOptions(KGlobal::caption(), true); return bookmarkManager; } KBookmarkSettings* KBookmarkSettings::s_self = 0; void KBookmarkSettings::readSettings() { KConfig config("kbookmarkrc", KConfig::NoGlobals); KConfigGroup cg(&config, "Bookmarks"); // add bookmark dialog usage - no reparse s_self->m_advancedaddbookmark = cg.readEntry("AdvancedAddBookmarkDialog", false); // this one alters the menu, therefore it needs a reparse s_self->m_contextmenu = cg.readEntry("ContextMenuActions", true); } KBookmarkSettings *KBookmarkSettings::self() { if (!s_self) { s_self = new KBookmarkSettings; readSettings(); } return s_self; } /////////// KBookmarkOwner bool KBookmarkOwner::enableOption(BookmarkOption action) const { if(action == ShowAddBookmark) return true; if(action == ShowEditBookmark) return true; return false; } KBookmarkDialog * KBookmarkOwner::bookmarkDialog(KBookmarkManager * mgr, QWidget * parent) { return new KBookmarkDialog(mgr, parent); } void KBookmarkOwner::openFolderinTabs(const KBookmarkGroup &) { } #include "moc_kbookmarkmanager.cpp"