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>
28 #include <ksavefile.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 );
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
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()
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()
99 m_pCompletion
->clear();
101 QFile
file( m_filename
);
102 if ( !file
.open( QIODevice::ReadOnly
) ) {
104 kWarning() << "Can't open " << file
.fileName() ;
106 // try to load the old completion history
107 bool ret
= loadFallback();
108 emit
loadingFinished();
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() ) {
119 fileStream
>> version
;
121 QDataStream
*stream
= &fileStream
;
123 bool crcChecked
= false;
126 if ( version
>= 2 && version
<= 4) {
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.
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]
157 if ( s_historyVersion
!= (int)version
|| ( crcChecked
&& !crcOk
) ) {
158 kWarning() << "The history version doesn't match, aborting loading" ;
160 emit
loadingFinished();
165 while ( !stream
->atEnd() ) {
166 KonqHistoryEntry 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
);
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 );
206 emit
loadingFinished();
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() ;
221 QDataStream
fileStream ( &file
);
222 fileStream
<< s_historyVersion
;
225 QDataStream
stream( &data
, QIODevice::WriteOnly
);
227 //We use QUrl for marshalling URLs in entries in the V4
229 KonqHistoryEntry::marshalURLAsStrings
= false;
230 QListIterator
<KonqHistoryEntry
> it( m_history
);
231 while ( it
.hasNext() ) {
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?
247 void KonqHistoryManager::adjustSize()
249 if (m_history
.isEmpty())
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() )
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) << "## addToHistory: " << _url
.prettyUrl() << " Typed URL: " << typedUrl
<< ", Title: " << title
;
295 if ( filterOut( _url
) ) // we only want remote URLs
298 // http URLs without a path will get redirected immediately to url + '/'
299 if ( _url
.path().isEmpty() && _url
.protocol().startsWith("http") )
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();
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
)
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() ) {
325 m_pending
.erase( it
);
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;
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
)
358 if ( !filterOut( u
) || u
.protocol() == "about" ) { // remote URL
361 // Local URL -> add to history
362 KonqHistoryEntry entry
;
364 entry
.firstVisited
= QDateTime::currentDateTime();
365 entry
.lastVisited
= entry
.firstVisited
;
366 emitAddToHistory( entry
);
369 void KonqHistoryManager::emitAddToHistory( const KonqHistoryEntry
& entry
)
372 QDataStream
stream( &data
, QIODevice::WriteOnly
);
373 stream
<< entry
<< dbusService();
374 // Protection against very long urls (like data:)
375 if ( data
.size() > 4096 )
377 emit
notifyHistoryEntry( data
);
381 void KonqHistoryManager::removePending( const KUrl
& url
)
383 // kDebug(1202) << "## Removing pending... " << url.prettyUrl();
385 if ( url
.isLocalFile() )
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
);
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() ) {
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()
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
)
444 QDataStream
stream( const_cast<QByteArray
*>( &data
), QIODevice::ReadOnly
);
446 kDebug(1202) << "Got new entry from Broadcast: " << e
.url
.prettyUrl();
448 KonqHistoryList::iterator existingEntry
= findEntry( e
.url
);
449 QString urlString
= e
.url
.url();
450 const bool newEntry
= existingEntry
== m_history
.end();
452 KonqHistoryEntry entry
;
455 entry
= *existingEntry
;
456 } else { // create a new history entry
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
;
471 m_history
.append( entry
);
473 *existingEntry
= entry
;
476 addToCompletion( entry
.url
.prettyUrl(), entry
.typedUrl
);
478 // bool pending = (e.numberOfTimesVisited != 0);
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
490 // note, bk save does not notify, and we don't want to!
492 m_bookmarkManager
->save();
495 addToUpdateList( urlString
);
496 emit
entryAdded( entry
);
499 void KonqHistoryManager::slotNotifyMaxCount( int count
, const QDBusMessage
& msg
)
505 KSharedConfig::Ptr config
= KGlobal::config();
506 KConfigGroup
cs( config
, "HistorySettings" );
507 cs
.writeEntry( "Maximum of History entries", m_maxCount
);
509 if ( isSenderOfSignal( msg
) ) {
515 void KonqHistoryManager::slotNotifyMaxAge( int days
, const QDBusMessage
& msg
)
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
) ) {
531 void KonqHistoryManager::slotNotifyClear( const QDBusMessage
& msg
)
535 m_pCompletion
->clear();
537 if ( isSenderOfSignal( msg
) )
540 KParts::HistoryProvider::clear(); // also emits the cleared() signal
543 void KonqHistoryManager::slotNotifyRemove( const QString
& urlStr
, const QDBusMessage
& msg
)
546 kDebug(1202) << "#### Broadcast: remove entry:: " << url
.prettyUrl();
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
) )
567 void KonqHistoryManager::slotNotifyRemoveList( const QStringList
& urls
, const QDBusMessage
& msg
)
569 kDebug(1202) << "#### Broadcast: removing list!";
572 QStringList::const_iterator it
= urls
.begin();
573 for ( ; it
!= urls
.end(); ++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
);
592 if ( doSave
&& isSenderOfSignal( msg
) )
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() )
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() );
619 qSort( m_history
.begin(), m_history
.end(), lastVisitedOrder
);
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();
635 // find out the weighting of this item (appended to the string as ":num")
636 int index
= item
.lastIndexOf(':');
639 weight
= item
.mid( index
+ 1 ).toUInt( &ok
);
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...
651 entry
.numberOfTimesVisited
= weight
;
652 // to make it not expire immediately...
653 entry
.lastVisited
= QDateTime::currentDateTime();
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();
679 QStringList
KonqHistoryManager::allURLs() const
682 QListIterator
<KonqHistoryEntry
> it( m_history
);
683 while ( it
.hasNext() ) {
684 list
.append( it
.next().url
.url() );
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() ) {
713 if ( (*it
).url
== url
)
719 void KonqHistoryList::removeEntry( const KUrl
& url
)
721 iterator it
= findEntry( url
);
726 #include "konqhistorymanager.moc"