1
// -*- c-basic-offset:4; indent-tabs-mode:nil -*-
2
// vim: set ts=4 sts=4 sw=4 et:
3
/* This file is part of the KDE libraries
4
Copyright (C) 2000 David Faure <faure@kde.org>
5
Copyright (C) 2003 Alexander Kellett <lypanov@kde.org>
6
Copyright (C) 2008 Norbert Frese <nf2@scheinwelt.at>
8
This library is free software; you can redistribute it and/or
9
modify it under the terms of the GNU Library General Public
10
License version 2 as published by the Free Software Foundation.
12
This library is distributed in the hope that it will be useful,
13
but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
Library General Public License for more details.
17
You should have received a copy of the GNU Library General Public License
18
along with this library; see the file COPYING.LIB. If not, write to
19
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20
Boston, MA 02110-1301, USA.
23
#include "kbookmarkmanager.h"
25
#include <QtCore/QFile>
26
#include <QtCore/QFileInfo>
27
#include <QtCore/QProcess>
28
#include <QtCore/QRegExp>
29
#include <QtCore/QTextStream>
30
#include <QtDBus/QtDBus>
31
#include <QtGui/QApplication>
33
#include <kconfiggroup.h>
35
#include <kdirwatch.h>
37
#include <kmessagebox.h>
38
#include <ksavefile.h>
39
#include <kstandarddirs.h>
41
#include "kbookmarkmenu.h"
42
#include "kbookmarkmenu_p.h"
43
#include "kbookmarkimporter.h"
44
#include "kbookmarkdialog.h"
45
#include "kbookmarkmanageradaptor_p.h"
47
#define BOOKMARK_CHANGE_NOTIFY_INTERFACE "org.kde.KIO.KBookmarkManager"
49
class KBookmarkManagerList : public QList<KBookmarkManager *>
52
~KBookmarkManagerList() {
53
qDeleteAll( begin() , end() ); // auto-delete functionality
59
K_GLOBAL_STATIC(KBookmarkManagerList, s_pSelf)
61
class KBookmarkMap : private KBookmarkGroupTraverser {
63
KBookmarkMap() : m_mapNeedsUpdate(true) {}
64
void setNeedsUpdate() { m_mapNeedsUpdate = true; }
65
void update(KBookmarkManager*);
66
QList<KBookmark> find( const QString &url ) const
67
{ return m_bk_map.value(url); }
69
virtual void visit(const KBookmark &);
70
virtual void visitEnter(const KBookmarkGroup &) { ; }
71
virtual void visitLeave(const KBookmarkGroup &) { ; }
73
typedef QList<KBookmark> KBookmarkList;
74
QMap<QString, KBookmarkList> m_bk_map;
75
bool m_mapNeedsUpdate;
78
void KBookmarkMap::update(KBookmarkManager *manager)
80
if (m_mapNeedsUpdate) {
81
m_mapNeedsUpdate = false;
84
KBookmarkGroup root = manager->root();
89
void KBookmarkMap::visit(const KBookmark &bk)
91
if (!bk.isSeparator()) {
92
// add bookmark to url map
93
m_bk_map[bk.internalElement().attribute("href")].append(bk);
97
// #########################
98
// KBookmarkManager::Private
99
class KBookmarkManager::Private
102
Private(bool bDocIsloaded, const QString &dbusObjectName = QString())
104
, m_dbusObjectName(dbusObjectName)
105
, m_docIsLoaded(bDocIsloaded)
107
, m_dialogAllowed(true)
109
, m_browserEditor(false)
110
, m_typeExternal(false)
118
mutable QDomDocument m_doc;
119
mutable QDomDocument m_toolbarDoc;
120
QString m_bookmarksFile;
121
QString m_dbusObjectName;
122
mutable bool m_docIsLoaded;
124
bool m_dialogAllowed;
125
QWidget *m_dialogParent;
127
bool m_browserEditor;
128
QString m_editorCaption;
131
KDirWatch * m_kDirWatch; // for external bookmark files
139
static KBookmarkManager* lookupExisting(const QString& bookmarksFile)
141
for ( KBookmarkManagerList::ConstIterator bmit = s_pSelf->constBegin(), bmend = s_pSelf->constEnd();
142
bmit != bmend; ++bmit ) {
143
if ( (*bmit)->path() == bookmarksFile )
150
KBookmarkManager* KBookmarkManager::managerForFile( const QString& bookmarksFile, const QString& dbusObjectName )
152
KBookmarkManager* mgr(0);
154
QReadLocker readLock(&s_pSelf->lock);
155
mgr = lookupExisting(bookmarksFile);
161
QWriteLocker writeLock(&s_pSelf->lock);
162
mgr = lookupExisting(bookmarksFile);
167
mgr = new KBookmarkManager( bookmarksFile, dbusObjectName );
168
s_pSelf->append( mgr );
172
KBookmarkManager* KBookmarkManager::managerForExternalFile( const QString& bookmarksFile )
174
KBookmarkManager* mgr(0);
176
QReadLocker readLock(&s_pSelf->lock);
177
mgr = lookupExisting(bookmarksFile);
183
QWriteLocker writeLock(&s_pSelf->lock);
184
mgr = lookupExisting(bookmarksFile);
189
mgr = new KBookmarkManager( bookmarksFile );
190
s_pSelf->append( mgr );
195
// principally used for filtered toolbars
196
KBookmarkManager* KBookmarkManager::createTempManager()
198
KBookmarkManager* mgr = new KBookmarkManager();
199
s_pSelf->append( mgr );
203
#define PI_DATA "version=\"1.0\" encoding=\"UTF-8\""
205
static QDomElement createXbelTopLevelElement(QDomDocument & doc)
207
QDomElement topLevel = doc.createElement("xbel");
208
topLevel.setAttribute("xmlns:mime", "http://www.freedesktop.org/standards/shared-mime-info");
209
topLevel.setAttribute("xmlns:bookmark", "http://www.freedesktop.org/standards/desktop-bookmarks");
210
topLevel.setAttribute("xmlns:kdepriv", "http://www.kde.org/kdepriv");
211
doc.appendChild( topLevel );
212
doc.insertBefore( doc.createProcessingInstruction( "xml", PI_DATA), topLevel );
216
KBookmarkManager::KBookmarkManager( const QString & bookmarksFile, const QString & dbusObjectName)
217
: d(new Private(false, dbusObjectName))
219
if(dbusObjectName.isNull()) // get dbusObjectName from file
220
if ( QFile::exists(d->m_bookmarksFile) )
221
parse(); //sets d->m_dbusObjectName
223
init( "/KBookmarkManager/"+d->m_dbusObjectName );
227
Q_ASSERT( !bookmarksFile.isEmpty() );
228
d->m_bookmarksFile = bookmarksFile;
230
if ( !QFile::exists(d->m_bookmarksFile) )
232
QDomElement topLevel = createXbelTopLevelElement(d->m_doc);
233
topLevel.setAttribute("dbusName", dbusObjectName);
234
d->m_docIsLoaded = true;
238
KBookmarkManager::KBookmarkManager(const QString & bookmarksFile)
239
: d(new Private(false))
241
// use KDirWatch to monitor this bookmarks file
242
d->m_typeExternal = true;
245
Q_ASSERT( !bookmarksFile.isEmpty() );
246
d->m_bookmarksFile = bookmarksFile;
248
if ( !QFile::exists(d->m_bookmarksFile) )
250
createXbelTopLevelElement(d->m_doc);
256
d->m_docIsLoaded = true;
259
d->m_kDirWatch = new KDirWatch;
260
d->m_kDirWatch->addFile(d->m_bookmarksFile);
261
QObject::connect( d->m_kDirWatch, SIGNAL(dirty(const QString&)),
262
this, SLOT(slotFileChanged(const QString&)));
263
QObject::connect( d->m_kDirWatch, SIGNAL(created(const QString&)),
264
this, SLOT(slotFileChanged(const QString&)));
265
QObject::connect( d->m_kDirWatch, SIGNAL(deleted(const QString&)),
266
this, SLOT(slotFileChanged(const QString&)));
267
kDebug(7043) << "starting KDirWatch for " << d->m_bookmarksFile;
270
KBookmarkManager::KBookmarkManager( )
271
: d(new Private(true))
273
init( "/KBookmarkManager/generated" );
274
d->m_update = false; // TODO - make it read/write
276
createXbelTopLevelElement(d->m_doc);
279
void KBookmarkManager::init( const QString& dbusPath )
281
// A KBookmarkManager without a dbus name is a temporary one, like those used by importers;
282
// no need to register them to dbus
283
if ( dbusPath != "/KBookmarkManager/" && dbusPath != "/KBookmarkManager/generated")
285
new KBookmarkManagerAdaptor(this);
286
QDBusConnection::sessionBus().registerObject( dbusPath, this );
288
QDBusConnection::sessionBus().connect(QString(), dbusPath, BOOKMARK_CHANGE_NOTIFY_INTERFACE,
289
"bookmarksChanged", this, SLOT(notifyChanged(QString,QDBusMessage)));
290
QDBusConnection::sessionBus().connect(QString(), dbusPath, BOOKMARK_CHANGE_NOTIFY_INTERFACE,
291
"bookmarkConfigChanged", this, SLOT(notifyConfigChanged()));
295
void KBookmarkManager::slotFileChanged(const QString& path)
297
if (path == d->m_bookmarksFile)
299
kDebug(7043) << "file changed (KDirWatch) " << path ;
303
// (emit where group is "" to directly mark the root menu as dirty)
304
emit changed( "", QString() );
308
KBookmarkManager::~KBookmarkManager()
310
if (!s_pSelf.isDestroyed()) {
311
s_pSelf->removeAll(this);
317
bool KBookmarkManager::autoErrorHandlingEnabled() const
319
return d->m_dialogAllowed;
322
void KBookmarkManager::setAutoErrorHandlingEnabled( bool enable, QWidget *parent )
324
d->m_dialogAllowed = enable;
325
d->m_dialogParent = parent;
328
void KBookmarkManager::setUpdate( bool update )
330
d->m_update = update;
333
QDomDocument KBookmarkManager::internalDocument() const
335
if(!d->m_docIsLoaded)
338
d->m_toolbarDoc.clear();
344
void KBookmarkManager::parse() const
346
d->m_docIsLoaded = true;
347
//kDebug(7043) << "KBookmarkManager::parse " << d->m_bookmarksFile;
348
QFile file( d->m_bookmarksFile );
349
if ( !file.open( QIODevice::ReadOnly ) )
351
kWarning() << "Can't open " << d->m_bookmarksFile;
354
d->m_doc = QDomDocument("xbel");
355
d->m_doc.setContent( &file );
357
if ( d->m_doc.documentElement().isNull() )
359
kWarning() << "KBookmarkManager::parse : main tag is missing, creating default " << d->m_bookmarksFile;
360
QDomElement element = d->m_doc.createElement("xbel");
361
d->m_doc.appendChild(element);
364
QDomElement docElem = d->m_doc.documentElement();
366
QString mainTag = docElem.tagName();
367
if ( mainTag != "xbel" )
368
kWarning() << "KBookmarkManager::parse : unknown main tag " << mainTag;
370
if(d->m_dbusObjectName.isNull())
372
d->m_dbusObjectName = docElem.attribute("dbusName");
374
else if(docElem.attribute("dbusName") != d->m_dbusObjectName)
376
docElem.setAttribute("dbusName", d->m_dbusObjectName);
380
QDomNode n = d->m_doc.documentElement().previousSibling();
381
if ( n.isProcessingInstruction() )
383
QDomProcessingInstruction pi = n.toProcessingInstruction();
384
pi.parentNode().removeChild(pi);
387
QDomProcessingInstruction pi;
388
pi = d->m_doc.createProcessingInstruction( "xml", PI_DATA );
389
d->m_doc.insertBefore( pi, docElem );
393
d->m_map.setNeedsUpdate();
396
bool KBookmarkManager::save( bool toolbarCache ) const
398
return saveAs( d->m_bookmarksFile, toolbarCache );
401
bool KBookmarkManager::saveAs( const QString & filename, bool toolbarCache ) const
403
kDebug(7043) << "KBookmarkManager::save " << filename;
405
// Save the bookmark toolbar folder for quick loading
406
// but only when it will actually make things quicker
407
const QString cacheFilename = filename + QLatin1String(".tbcache");
408
if(toolbarCache && !root().isToolbarGroup())
410
KSaveFile cacheFile( cacheFilename );
411
if ( cacheFile.open() )
414
QTextStream stream(&str, QIODevice::WriteOnly);
415
stream << root().findToolbar();
416
const QByteArray cstr = str.toUtf8();
417
cacheFile.write( cstr.data(), cstr.length() );
418
cacheFile.finalize();
421
else // remove any (now) stale cache
423
QFile::remove( cacheFilename );
426
KSaveFile file( filename );
429
file.simpleBackupFile( file.fileName(), QString(), ".bak" );
430
QTextStream stream(&file);
431
stream.setCodec( QTextCodec::codecForName( "UTF-8" ) );
432
stream << internalDocument().toString();
434
if ( file.finalize() )
440
static int hadSaveError = false;
442
if ( !hadSaveError ) {
443
QString err = i18n("Unable to save bookmarks in %1. Reported error was: %2. "
444
"This error message will only be shown once. The cause "
445
"of the error needs to be fixed as quickly as possible, "
446
"which is most likely a full hard drive.",
447
filename, file.errorString());
449
if (d->m_dialogAllowed && qApp->type() != QApplication::Tty && QThread::currentThread() == qApp->thread())
450
KMessageBox::error( QApplication::activeWindow(), err );
452
kError() << QString("Unable to save bookmarks in %1. File reported the following error-code: %2.").arg(filename).arg(file.error());
453
emit const_cast<KBookmarkManager*>(this)->error(err);
459
QString KBookmarkManager::path() const
461
return d->m_bookmarksFile;
464
KBookmarkGroup KBookmarkManager::root() const
466
return KBookmarkGroup(internalDocument().documentElement());
469
KBookmarkGroup KBookmarkManager::toolbar()
471
kDebug(7043) << "KBookmarkManager::toolbar begin";
472
// Only try to read from a toolbar cache if the full document isn't loaded
473
if(!d->m_docIsLoaded)
475
kDebug(7043) << "KBookmarkManager::toolbar trying cache";
476
const QString cacheFilename = d->m_bookmarksFile + QLatin1String(".tbcache");
477
QFileInfo bmInfo(d->m_bookmarksFile);
478
QFileInfo cacheInfo(cacheFilename);
479
if (d->m_toolbarDoc.isNull() &&
480
QFile::exists(cacheFilename) &&
481
bmInfo.lastModified() < cacheInfo.lastModified())
483
kDebug(7043) << "KBookmarkManager::toolbar reading file";
484
QFile file( cacheFilename );
486
if ( file.open( QIODevice::ReadOnly ) )
488
d->m_toolbarDoc = QDomDocument("cache");
489
d->m_toolbarDoc.setContent( &file );
490
kDebug(7043) << "KBookmarkManager::toolbar opened";
493
if (!d->m_toolbarDoc.isNull())
495
kDebug(7043) << "KBookmarkManager::toolbar returning element";
496
QDomElement elem = d->m_toolbarDoc.firstChild().toElement();
497
return KBookmarkGroup(elem);
501
// Fallback to the normal way if there is no cache or if the bookmark file
503
QDomElement elem = root().findToolbar();
505
return root(); // Root is the bookmark toolbar if none has been set.
507
return KBookmarkGroup(root().findToolbar());
510
KBookmark KBookmarkManager::findByAddress( const QString & address )
512
//kDebug(7043) << "KBookmarkManager::findByAddress " << address;
513
KBookmark result = root();
514
// The address is something like /5/10/2+
515
const QStringList addresses = address.split(QRegExp("[/+]"),QString::SkipEmptyParts);
516
// kWarning() << addresses.join(",");
517
for ( QStringList::const_iterator it = addresses.begin() ; it != addresses.end() ; )
519
bool append = ((*it) == "+");
520
uint number = (*it).toUInt();
521
Q_ASSERT(result.isGroup());
522
KBookmarkGroup group = result.toGroup();
523
KBookmark bk = group.first(), lbk = bk; // last non-null bookmark
524
for ( uint i = 0 ; ( (i<number) || append ) && !bk.isNull() ; ++i ) {
530
//kWarning() << "found section";
533
if (result.isNull()) {
534
kWarning() << "KBookmarkManager::findByAddress: couldn't find item " << address;
536
//kWarning() << "found " << result.address();
540
void KBookmarkManager::emitChanged()
546
void KBookmarkManager::emitChanged( const KBookmarkGroup & group )
548
(void) save(); // KDE5 TODO: emitChanged should return a bool? Maybe rename it to saveAndEmitChanged?
550
// Tell the other processes too
551
// kDebug(7043) << "KBookmarkManager::emitChanged : broadcasting change " << group.address();
553
emit bookmarksChanged(group.address());
555
// We do get our own broadcast, so no need for this anymore
556
//emit changed( group );
559
void KBookmarkManager::emitConfigChanged()
561
emit bookmarkConfigChanged();
564
void KBookmarkManager::notifyCompleteChange( const QString &caller ) // DBUS call
569
kDebug(7043) << "KBookmarkManager::notifyCompleteChange";
570
// The bk editor tells us we should reload everything
574
// (emit where group is "" to directly mark the root menu as dirty)
575
emit changed( "", caller );
578
void KBookmarkManager::notifyConfigChanged() // DBUS call
580
kDebug() << "reloaded bookmark config!";
581
KBookmarkSettings::self()->readSettings();
582
parse(); // reload, and thusly recreate the menus
583
emit configChanged();
586
void KBookmarkManager::notifyChanged( const QString &groupAddress, const QDBusMessage &msg ) // DBUS call
588
kDebug() << "KBookmarkManager::notifyChanged ( "<<groupAddress<<")";
592
// Reparse (the whole file, no other choice)
593
// if someone else notified us
594
if (msg.service() != QDBusConnection::sessionBus().baseService())
597
//kDebug(7043) << "KBookmarkManager::notifyChanged " << groupAddress;
598
//KBookmarkGroup group = findByAddress( groupAddress ).toGroup();
599
//Q_ASSERT(!group.isNull());
600
emit changed( groupAddress, QString() );
603
void KBookmarkManager::setEditorOptions( const QString& caption, bool browser )
605
d->m_editorCaption = caption;
606
d->m_browserEditor = browser;
609
void KBookmarkManager::slotEditBookmarks()
612
if ( !d->m_editorCaption.isEmpty() )
613
args << QLatin1String("--customcaption") << d->m_editorCaption;
614
if ( !d->m_browserEditor )
615
args << QLatin1String("--nobrowser");
616
if( !d->m_dbusObjectName.isEmpty() )
617
args << QLatin1String("--dbusObjectName") << d->m_dbusObjectName;
618
args << d->m_bookmarksFile;
619
QProcess::startDetached("keditbookmarks", args);
622
void KBookmarkManager::slotEditBookmarksAtAddress( const QString& address )
625
if ( !d->m_editorCaption.isEmpty() )
626
args << QLatin1String("--customcaption") << d->m_editorCaption;
627
if ( !d->m_browserEditor )
628
args << QLatin1String("--nobrowser");
629
if( !d->m_dbusObjectName.isEmpty() )
630
args << QLatin1String("--dbusObjectName") << d->m_dbusObjectName;
631
args << QLatin1String("--address") << address
632
<< d->m_bookmarksFile;
633
QProcess::startDetached("keditbookmarks", args);
637
bool KBookmarkManager::updateAccessMetadata( const QString & url )
639
d->m_map.update(this);
640
QList<KBookmark> list = d->m_map.find(url);
641
if ( list.count() == 0 )
644
for ( QList<KBookmark>::iterator it = list.begin();
645
it != list.end(); ++it )
646
(*it).updateAccessMetadata();
651
void KBookmarkManager::updateFavicon( const QString &url, const QString &faviconurl )
653
d->m_map.update(this);
654
QList<KBookmark> list = d->m_map.find(url);
655
for ( QList<KBookmark>::iterator it = list.begin();
656
it != list.end(); ++it )
658
// TODO - update favicon data based on faviconurl
659
// but only when the previously used icon
660
// isn't a manually set one.
664
KBookmarkManager* KBookmarkManager::userBookmarksManager()
666
const QString bookmarksFile = KStandardDirs::locateLocal("data", QString::fromLatin1("konqueror/bookmarks.xml"));
667
KBookmarkManager* bookmarkManager = KBookmarkManager::managerForFile( bookmarksFile, "konqueror" );
668
bookmarkManager->setEditorOptions(KGlobal::caption(), true);
669
return bookmarkManager;
672
KBookmarkSettings* KBookmarkSettings::s_self = 0;
674
void KBookmarkSettings::readSettings()
676
KConfig config("kbookmarkrc", KConfig::NoGlobals);
677
KConfigGroup cg(&config, "Bookmarks");
679
// add bookmark dialog usage - no reparse
680
s_self->m_advancedaddbookmark = cg.readEntry("AdvancedAddBookmarkDialog", false);
682
// this one alters the menu, therefore it needs a reparse
683
s_self->m_contextmenu = cg.readEntry("ContextMenuActions", true);
686
KBookmarkSettings *KBookmarkSettings::self()
690
s_self = new KBookmarkSettings;
696
/////////// KBookmarkOwner
698
bool KBookmarkOwner::enableOption(BookmarkOption action) const
700
if(action == ShowAddBookmark)
702
if(action == ShowEditBookmark)
707
KBookmarkDialog * KBookmarkOwner::bookmarkDialog(KBookmarkManager * mgr, QWidget * parent)
709
return new KBookmarkDialog(mgr, parent);
712
void KBookmarkOwner::openFolderinTabs(const KBookmarkGroup &)
717
#include "kbookmarkmanager.moc"