Fix crash if key bindings specified in profile cannot be found. Improve
[personal-kdebase.git] / apps / konqueror / src / konqhistorymanager.cpp
blob131efcf2c818328eb112c277981d7d09f87a2135
1 /* This file is part of the KDE project
2 Copyright (C) 2000,2001 Carsten Pfeiffer <pfeiffer@kde.org>
3 Copyright 2007 David Faure <faure@kde.org>
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; see the file COPYING. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
21 #include "konqhistorymanager.h"
22 #include "konqhistorymanageradaptor.h"
23 #include <kbookmarkmanager.h>
25 #include <QtDBus/QtDBus>
26 #include <kapplication.h>
27 #include <kdebug.h>
28 #include <ksavefile.h>
29 #include <kconfig.h>
30 #include <kstandarddirs.h>
31 #include <kcompletion.h>
33 #include <zlib.h> // for crc32
34 #include <kconfiggroup.h>
37 const int KonqHistoryManager::s_historyVersion = 4;
39 KonqHistoryManager::KonqHistoryManager( KBookmarkManager* bookmarkManager, QObject *parent )
40 : KParts::HistoryProvider( parent ),
41 m_bookmarkManager(bookmarkManager)
43 m_updateTimer = new QTimer( this );
45 // defaults
46 KSharedConfig::Ptr config = KGlobal::config();
47 KConfigGroup cs( config, "HistorySettings" );
48 m_maxCount = cs.readEntry( "Maximum of History entries", 500 );
49 m_maxCount = qMax( 1, m_maxCount );
50 m_maxAgeDays = cs.readEntry( "Maximum age of History entries", 90);
52 m_filename = KStandardDirs::locateLocal( "data", QLatin1String("konqueror/konq_history"));
54 // take care of the completion object
55 m_pCompletion = new KCompletion;
56 m_pCompletion->setOrder( KCompletion::Weighted );
58 // and load the history
59 loadHistory();
61 connect( m_updateTimer, SIGNAL( timeout() ), SLOT( slotEmitUpdated() ));
63 (void) new KonqHistoryManagerAdaptor( this );
64 const QString dbusPath = "/KonqHistoryManager";
65 const QString dbusInterface = "org.kde.Konqueror.HistoryManager";
67 QDBusConnection dbus = QDBusConnection::sessionBus();
68 dbus.registerObject( dbusPath, this );
69 dbus.connect(QString(), dbusPath, dbusInterface, "notifyClear", this, SLOT(slotNotifyClear(QDBusMessage)));
70 dbus.connect(QString(), dbusPath, dbusInterface, "notifyHistoryEntry", this, SLOT(slotNotifyHistoryEntry(QByteArray,QDBusMessage)));
71 dbus.connect(QString(), dbusPath, dbusInterface, "notifyMaxAge", this, SLOT(slotNotifyMaxAge(int,QDBusMessage)));
72 dbus.connect(QString(), dbusPath, dbusInterface, "notifyMaxCount", this, SLOT(slotNotifyMaxCount(int,QDBusMessage)));
73 dbus.connect(QString(), dbusPath, dbusInterface, "notifyRemove", this, SLOT(slotNotifyRemove(QString,QDBusMessage)));
74 dbus.connect(QString(), dbusPath, dbusInterface, "notifyRemoveList", this, SLOT(slotNotifyRemoveList(QStringList,QDBusMessage)));
78 KonqHistoryManager::~KonqHistoryManager()
80 delete m_pCompletion;
81 clearPending();
84 static QString dbusService()
86 return QDBusConnection::sessionBus().baseService();
89 bool KonqHistoryManager::isSenderOfSignal( const QDBusMessage& msg )
91 return dbusService() == msg.service();
94 // loads the entire history
95 bool KonqHistoryManager::loadHistory()
97 clearPending();
98 m_history.clear();
99 m_pCompletion->clear();
101 QFile file( m_filename );
102 if ( !file.open( QIODevice::ReadOnly ) ) {
103 if ( file.exists() )
104 kWarning() << "Can't open " << file.fileName() ;
106 // try to load the old completion history
107 bool ret = loadFallback();
108 emit loadingFinished();
109 return ret;
112 QDataStream fileStream( &file );
113 QByteArray data; // only used for version == 2
114 // we construct the stream object now but fill in the data later.
115 QDataStream crcStream( &data, QIODevice::ReadOnly );
117 if ( !fileStream.atEnd() ) {
118 quint32 version;
119 fileStream >> version;
121 QDataStream *stream = &fileStream;
123 bool crcChecked = false;
124 bool crcOk = false;
126 if ( version >= 2 && version <= 4) {
127 quint32 crc;
128 crcChecked = true;
129 fileStream >> crc >> data;
130 crcOk = crc32( 0, reinterpret_cast<unsigned char *>( data.data() ), data.size() ) == crc;
131 stream = &crcStream; // pick up the right stream
134 // We can't read v3 history anymore, because operator<<(KURL) disappeared.
135 if ( version == 4)
137 // Use QUrl marshalling for V4 format.
138 KonqHistoryEntry::marshalURLAsStrings = false;
141 if ( version != 0 && version < 3 ) //Versions 1,2 (but not 0) are also valid
143 //Turn on backwards compatibility mode..
144 KonqHistoryEntry::marshalURLAsStrings = true;
145 // it doesn't make sense to save to save maxAge and maxCount in the
146 // binary file, this would make backups impossible (they would clear
147 // themselves on startup, because all entries expire).
148 // [But V1 and V2 formats did it, so we do a dummy read]
149 quint32 dummy;
150 *stream >> dummy;
151 *stream >> dummy;
153 //OK.
154 version = 3;
157 if ( s_historyVersion != (int)version || ( crcChecked && !crcOk ) ) {
158 kWarning() << "The history version doesn't match, aborting loading" ;
159 file.close();
160 emit loadingFinished();
161 return false;
165 while ( !stream->atEnd() ) {
166 KonqHistoryEntry entry;
167 *stream >> entry;
168 // kDebug(1202) << "loaded entry:" << entry.url << ", Title:" << entry.title;
169 m_history.append( entry );
170 QString urlString2 = entry.url.prettyUrl();
172 addToCompletion( urlString2, entry.typedUrl, entry.numberOfTimesVisited );
174 // and fill our baseclass.
175 QString urlString = entry.url.url();
176 KParts::HistoryProvider::insert( urlString );
177 // DF: also insert the "pretty" version if different
178 // This helps getting 'visited' links on websites which don't use fully-escaped urls.
180 if ( urlString != urlString2 )
181 KParts::HistoryProvider::insert( urlString2 );
184 //kDebug(1202) << "loaded:" << m_history.count() << "entries.";
186 qSort( m_history.begin(), m_history.end(), lastVisitedOrder );
187 adjustSize();
191 //This is important - we need to switch to a consistent marshalling format for
192 //communicating between different konqueror instances. Since during an upgrade
193 //some "old" copies may still running, we use the old format for the DBUS transfers.
194 //This doesn't make that much difference performance-wise for single entries anyway.
195 KonqHistoryEntry::marshalURLAsStrings = true;
198 // Theoretically, we should emit update() here, but as we only ever
199 // load items on startup up to now, this doesn't make much sense. Same
200 // thing for the above loadFallback().
201 // emit KParts::HistoryProvider::update( some list );
205 file.close();
206 emit loadingFinished();
208 return true;
212 // saves the entire history
213 bool KonqHistoryManager::saveHistory()
215 KSaveFile file( m_filename );
216 if ( !file.open() ) {
217 kWarning() << "Can't open " << file.fileName() ;
218 return false;
221 QDataStream fileStream ( &file );
222 fileStream << s_historyVersion;
224 QByteArray data;
225 QDataStream stream( &data, QIODevice::WriteOnly );
227 //We use QUrl for marshalling URLs in entries in the V4
228 //file format
229 KonqHistoryEntry::marshalURLAsStrings = false;
230 QListIterator<KonqHistoryEntry> it( m_history );
231 while ( it.hasNext() ) {
232 stream << it.next();
235 //For DBUS, transfer strings instead - wire compat.
236 KonqHistoryEntry::marshalURLAsStrings = true;
238 quint32 crc = crc32( 0, reinterpret_cast<unsigned char *>( data.data() ), data.size() );
239 fileStream << crc << data;
241 file.finalize(); //check for error here?
243 return true;
247 void KonqHistoryManager::adjustSize()
249 if (m_history.isEmpty())
250 return;
252 KonqHistoryEntry entry = m_history.first();
253 const QDateTime expirationDate( QDate::currentDate().addDays( -m_maxAgeDays ) );
255 while ( m_history.count() > (qint32)m_maxCount ||
256 (m_maxAgeDays > 0 && entry.lastVisited.isValid() && entry.lastVisited < expirationDate) ) // i.e. entry is expired
258 removeFromCompletion( entry.url.prettyUrl(), entry.typedUrl );
260 QString urlString = entry.url.url();
261 KParts::HistoryProvider::remove( urlString );
263 addToUpdateList( urlString );
265 emit entryRemoved( m_history.first() );
266 m_history.removeFirst();
268 if ( m_history.isEmpty() )
269 break;
270 entry = m_history.first();
275 void KonqHistoryManager::addPending( const KUrl& url, const QString& typedUrl,
276 const QString& title )
278 addToHistory( true, url, typedUrl, title );
281 void KonqHistoryManager::confirmPending( const KUrl& url,
282 const QString& typedUrl,
283 const QString& title )
285 addToHistory( false, url, typedUrl, title );
289 void KonqHistoryManager::addToHistory( bool pending, const KUrl& _url,
290 const QString& typedUrl,
291 const QString& title )
293 //kDebug(1202) << _url << "Typed URL:" << typedUrl << ", Title:" << title;
295 if ( filterOut( _url ) ) // we only want remote URLs
296 return;
298 // http URLs without a path will get redirected immediately to url + '/'
299 if ( _url.path().isEmpty() && _url.protocol().startsWith("http") )
300 return;
302 KUrl url( _url );
303 bool hasPass = url.hasPass();
304 url.setPass( QString() ); // No password in the history, especially not in the completion!
305 url.setHost( url.host().toLower() ); // All host parts lower case
306 KonqHistoryEntry entry;
307 QString u = url.prettyUrl();
308 entry.url = url;
309 if ( (u != typedUrl) && !hasPass )
310 entry.typedUrl = typedUrl;
312 // we only keep the title if we are confirming an entry. Otherwise,
313 // we might get bogus titles from the previous url (actually it's just
314 // konqueror's window caption).
315 if ( !pending && u != title )
316 entry.title = title;
317 entry.firstVisited = QDateTime::currentDateTime();
318 entry.lastVisited = entry.firstVisited;
320 // always remove from pending if available, otherwise the else branch leaks
321 // if the map already contains an entry for this key.
322 QMap<QString,KonqHistoryEntry*>::iterator it = m_pending.find( u );
323 if ( it != m_pending.end() ) {
324 delete it.value();
325 m_pending.erase( it );
328 if ( !pending ) {
329 if ( it != m_pending.end() ) {
330 // we make a pending entry official, so we just have to update
331 // and not increment the counter. No need to care about
332 // firstVisited, as this is not taken into account on update.
333 entry.numberOfTimesVisited = 0;
337 else {
338 // We add a copy of the current history entry of the url to the
339 // pending list, so that we can restore it if the user canceled.
340 // If there is no entry for the url yet, we just store the url.
341 KonqHistoryList::iterator oldEntry = findEntry( url );
342 m_pending.insert( u, oldEntry != m_history.end() ?
343 new KonqHistoryEntry( *oldEntry ) : 0 );
346 // notify all konqueror instances about the entry
347 emitAddToHistory( entry );
350 // interface of KParts::HistoryManager
351 // Usually, we only record the history for non-local URLs (i.e. filterOut()
352 // returns false). But when using the HistoryProvider interface, we record
353 // exactly those filtered-out urls.
354 // Moreover, we don't get any pending/confirming entries, just one insert()
355 void KonqHistoryManager::insert( const QString& url )
357 KUrl u ( url );
358 if ( !filterOut( u ) || u.protocol() == "about" ) { // remote URL
359 return;
361 // Local URL -> add to history
362 KonqHistoryEntry entry;
363 entry.url = u;
364 entry.firstVisited = QDateTime::currentDateTime();
365 entry.lastVisited = entry.firstVisited;
366 emitAddToHistory( entry );
369 void KonqHistoryManager::emitAddToHistory( const KonqHistoryEntry& entry )
371 QByteArray data;
372 QDataStream stream( &data, QIODevice::WriteOnly );
373 stream << entry << dbusService();
374 // Protection against very long urls (like data:)
375 if ( data.size() > 4096 )
376 return;
377 emit notifyHistoryEntry( data );
381 void KonqHistoryManager::removePending( const KUrl& url )
383 // kDebug(1202) << "Removing pending..." << url;
385 if ( url.isLocalFile() )
386 return;
388 QMap<QString,KonqHistoryEntry*>::iterator it = m_pending.find( url.prettyUrl() );
389 if ( it != m_pending.end() ) {
390 KonqHistoryEntry *oldEntry = it.value(); // the old entry, may be 0
391 emitRemoveFromHistory( url ); // remove the current pending entry
393 if ( oldEntry ) // we had an entry before, now use that instead
394 emitAddToHistory( *oldEntry );
396 delete oldEntry;
397 m_pending.erase( it );
401 // clears the pending list and makes sure the entries get deleted.
402 void KonqHistoryManager::clearPending()
404 QMap<QString,KonqHistoryEntry*>::iterator it = m_pending.begin();
405 while ( it != m_pending.end() ) {
406 delete it.value();
407 ++it;
409 m_pending.clear();
412 void KonqHistoryManager::emitRemoveFromHistory( const KUrl& url )
414 emit notifyRemove( url.url() );
417 void KonqHistoryManager::emitRemoveListFromHistory( const KUrl::List& urls )
419 emit notifyRemoveList( urls.toStringList() );
422 void KonqHistoryManager::emitClear()
424 emit notifyClear();
427 void KonqHistoryManager::emitSetMaxCount( int count )
429 emit notifyMaxCount( count );
432 void KonqHistoryManager::emitSetMaxAge( int days )
434 emit notifyMaxAge( days );
437 ///////////////////////////////////////////////////////////////////
438 // DBUS called methods
440 void KonqHistoryManager::slotNotifyHistoryEntry( const QByteArray & data,
441 const QDBusMessage& msg )
443 KonqHistoryEntry e;
444 QDataStream stream( const_cast<QByteArray *>( &data ), QIODevice::ReadOnly );
445 stream >> e;
446 //kDebug(1202) << "Got new entry from Broadcast:" << e.url;
448 KonqHistoryList::iterator existingEntry = findEntry( e.url );
449 QString urlString = e.url.url();
450 const bool newEntry = existingEntry == m_history.end();
452 KonqHistoryEntry entry;
454 if ( !newEntry ) {
455 entry = *existingEntry;
456 } else { // create a new history entry
457 entry.url = e.url;
458 entry.firstVisited = e.firstVisited;
459 entry.numberOfTimesVisited = 0; // will get set to 1 below
460 KParts::HistoryProvider::insert( urlString );
463 if ( !e.typedUrl.isEmpty() )
464 entry.typedUrl = e.typedUrl;
465 if ( !e.title.isEmpty() )
466 entry.title = e.title;
467 entry.numberOfTimesVisited += e.numberOfTimesVisited;
468 entry.lastVisited = e.lastVisited;
470 if ( newEntry )
471 m_history.append( entry );
472 else {
473 *existingEntry = entry;
476 addToCompletion( entry.url.prettyUrl(), entry.typedUrl );
478 // bool pending = (e.numberOfTimesVisited != 0);
480 adjustSize();
482 // note, no need to do the updateBookmarkMetadata for every
483 // history object, only need to for the broadcast sender as
484 // the history object itself keeps the data consistant.
485 bool updated = m_bookmarkManager ? m_bookmarkManager->updateAccessMetadata( urlString ) : false;
487 if ( isSenderOfSignal( msg ) ) {
488 // we are the sender of the broadcast, so we save
489 saveHistory();
490 // note, bk save does not notify, and we don't want to!
491 if (updated)
492 m_bookmarkManager->save();
495 addToUpdateList( urlString );
496 emit entryAdded( entry );
499 void KonqHistoryManager::slotNotifyMaxCount( int count, const QDBusMessage& msg )
501 m_maxCount = count;
502 clearPending();
503 adjustSize();
505 KSharedConfig::Ptr config = KGlobal::config();
506 KConfigGroup cs( config, "HistorySettings" );
507 cs.writeEntry( "Maximum of History entries", m_maxCount );
509 if ( isSenderOfSignal( msg ) ) {
510 saveHistory();
511 cs.sync();
515 void KonqHistoryManager::slotNotifyMaxAge( int days, const QDBusMessage& msg )
517 m_maxAgeDays = days;
518 clearPending();
519 adjustSize();
521 KSharedConfig::Ptr config = KGlobal::config();
522 KConfigGroup cs( config, "HistorySettings" );
523 cs.writeEntry( "Maximum age of History entries", m_maxAgeDays );
525 if ( isSenderOfSignal( msg ) ) {
526 saveHistory();
527 cs.sync();
531 void KonqHistoryManager::slotNotifyClear( const QDBusMessage& msg )
533 clearPending();
534 m_history.clear();
535 m_pCompletion->clear();
537 if ( isSenderOfSignal( msg ) )
538 saveHistory();
540 KParts::HistoryProvider::clear(); // also emits the cleared() signal
543 void KonqHistoryManager::slotNotifyRemove( const QString& urlStr, const QDBusMessage& msg )
545 KUrl url( urlStr );
546 kDebug(1202) << "Broadcast: remove entry:" << url;
548 KonqHistoryList::iterator existingEntry = findEntry( url );
550 if ( existingEntry != m_history.end() ) {
551 const KonqHistoryEntry entry = *existingEntry; // make copy, due to erase call below
552 removeFromCompletion( entry.url.prettyUrl(), entry.typedUrl );
554 const QString urlString = entry.url.url();
555 KParts::HistoryProvider::remove( urlString );
557 addToUpdateList( urlString );
559 m_history.erase( existingEntry );
560 emit entryRemoved( entry );
562 if ( isSenderOfSignal( msg ) )
563 saveHistory();
567 void KonqHistoryManager::slotNotifyRemoveList( const QStringList& urls, const QDBusMessage& msg )
569 kDebug(1202) << "Broadcast: removing list!";
571 bool doSave = false;
572 QStringList::const_iterator it = urls.begin();
573 for ( ; it != urls.end(); ++it ) {
574 KUrl url = *it;
575 KonqHistoryList::iterator existingEntry = m_history.findEntry( url );
576 if ( existingEntry != m_history.end() ) {
577 const KonqHistoryEntry entry = *existingEntry; // make copy, due to erase call below
578 removeFromCompletion( entry.url.prettyUrl(), entry.typedUrl );
580 const QString urlString = entry.url.url();
581 KParts::HistoryProvider::remove( urlString );
583 addToUpdateList( urlString );
585 m_history.erase( existingEntry );
586 emit entryRemoved( entry );
588 doSave = true;
592 if ( doSave && isSenderOfSignal( msg ) )
593 saveHistory();
596 // compatibility fallback, try to load the old completion history
597 bool KonqHistoryManager::loadFallback()
599 QString file = KStandardDirs::locateLocal( "config", QLatin1String("konq_history"));
600 if ( file.isEmpty() )
601 return false;
603 KConfig config( file, KConfig::SimpleConfig);
604 const KConfigGroup group = config.group("History");
605 const QStringList items = group.readEntry( "CompletionItems", QStringList() );
606 QStringList::const_iterator it = items.begin();
608 while ( it != items.end() ) {
609 KonqHistoryEntry entry = createFallbackEntry( *it );
610 if ( entry.url.isValid() ) {
611 m_history.append( entry );
612 addToCompletion( entry.url.prettyUrl(), QString(), entry.numberOfTimesVisited );
614 KParts::HistoryProvider::insert( entry.url.url() );
616 ++it;
619 qSort( m_history.begin(), m_history.end(), lastVisitedOrder );
620 adjustSize();
621 saveHistory();
623 return true;
626 // tries to create a small KonqHistoryEntry out of a string, where the string
627 // looks like "http://www.bla.com/bla.html:23"
628 // the attached :23 is the weighting from KCompletion
629 KonqHistoryEntry KonqHistoryManager::createFallbackEntry(const QString& item) const
631 // code taken from KCompletion::addItem(), adjusted to use weight = 1
632 uint len = item.length();
633 uint weight = 1;
635 // find out the weighting of this item (appended to the string as ":num")
636 int index = item.lastIndexOf(':');
637 if ( index > 0 ) {
638 bool ok;
639 weight = item.mid( index + 1 ).toUInt( &ok );
640 if ( !ok )
641 weight = 1;
643 len = index; // only insert until the ':'
647 KonqHistoryEntry entry;
648 KUrl u( item.left( len ));
649 // that's the only entries we know about...
650 entry.url = u;
651 entry.numberOfTimesVisited = weight;
652 // to make it not expire immediately...
653 entry.lastVisited = QDateTime::currentDateTime();
655 return entry;
658 KonqHistoryList::iterator KonqHistoryManager::findEntry( const KUrl& url )
660 // small optimization (dict lookup) for items _not_ in our history
661 if ( !KParts::HistoryProvider::contains( url.url() ) )
662 return m_history.end();
664 return m_history.findEntry( url );
667 bool KonqHistoryManager::filterOut( const KUrl& url )
669 return ( url.isLocalFile() || url.host().isEmpty() );
672 void KonqHistoryManager::slotEmitUpdated()
674 emit KParts::HistoryProvider::updated( m_updateURLs );
675 m_updateURLs.clear();
678 #if 0 // unused
679 QStringList KonqHistoryManager::allURLs() const
681 QStringList list;
682 QListIterator<KonqHistoryEntry> it( m_history );
683 while ( it.hasNext() ) {
684 list.append( it.next().url.url() );
686 return list;
688 #endif
690 void KonqHistoryManager::addToCompletion( const QString& url, const QString& typedUrl,
691 int numberOfTimesVisited )
693 m_pCompletion->addItem( url, numberOfTimesVisited );
694 // typed urls have a higher priority
695 m_pCompletion->addItem( typedUrl, numberOfTimesVisited +10 );
698 void KonqHistoryManager::removeFromCompletion( const QString& url, const QString& typedUrl )
700 m_pCompletion->removeItem( url );
701 m_pCompletion->removeItem( typedUrl );
704 //////////////////////////////////////////////////////////////////
707 KonqHistoryList::iterator KonqHistoryList::findEntry( const KUrl& url )
709 // we search backwards, probably faster to find an entry
710 KonqHistoryList::iterator it = end();
711 while ( it != begin() ) {
712 --it;
713 if ( (*it).url == url )
714 return it;
716 return end();
719 void KonqHistoryList::removeEntry( const KUrl& url )
721 iterator it = findEntry( url );
722 if ( it != end() )
723 erase( it );
726 #include "konqhistorymanager.moc"