summaryrefslogtreecommitdiffstats
path: root/libkonq/konq_historymgr.cc
diff options
context:
space:
mode:
Diffstat (limited to 'libkonq/konq_historymgr.cc')
-rw-r--r--libkonq/konq_historymgr.cc733
1 files changed, 733 insertions, 0 deletions
diff --git a/libkonq/konq_historymgr.cc b/libkonq/konq_historymgr.cc
new file mode 100644
index 000000000..72c6be3de
--- /dev/null
+++ b/libkonq/konq_historymgr.cc
@@ -0,0 +1,733 @@
+/* This file is part of the KDE project
+ Copyright (C) 2000,2001 Carsten Pfeiffer <pfeiffer@kde.org>
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This program 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "konq_historymgr.h"
+
+#include <dcopclient.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+#include <ksavefile.h>
+#include <ksimpleconfig.h>
+#include <kstandarddirs.h>
+
+#include <zlib.h>
+
+#include "konqbookmarkmanager.h"
+
+const Q_UINT32 KonqHistoryManager::s_historyVersion = 3;
+
+KonqHistoryManager::KonqHistoryManager( QObject *parent, const char *name )
+ : KParts::HistoryProvider( parent, name ),
+ KonqHistoryComm( "KonqHistoryManager" )
+{
+ m_updateTimer = new QTimer( this );
+
+ // defaults
+ KConfig *config = KGlobal::config();
+ KConfigGroupSaver cs( config, "HistorySettings" );
+ m_maxCount = config->readNumEntry( "Maximum of History entries", 500 );
+ m_maxCount = QMAX( 1, m_maxCount );
+ m_maxAgeDays = config->readNumEntry( "Maximum age of History entries", 90);
+
+ m_history.setAutoDelete( true );
+ m_filename = locateLocal( "data",
+ QString::fromLatin1("konqueror/konq_history" ));
+
+ if ( !kapp->dcopClient()->isAttached() )
+ kapp->dcopClient()->attach();
+
+
+ // take care of the completion object
+ m_pCompletion = new KCompletion;
+ m_pCompletion->setOrder( KCompletion::Weighted );
+
+ // and load the history
+ loadHistory();
+
+ connect( m_updateTimer, SIGNAL( timeout() ), SLOT( slotEmitUpdated() ));
+}
+
+
+KonqHistoryManager::~KonqHistoryManager()
+{
+ delete m_pCompletion;
+ clearPending();
+}
+
+bool KonqHistoryManager::isSenderOfBroadcast()
+{
+ DCOPClient *dc = callingDcopClient();
+ return !dc || (dc->senderId() == dc->appId());
+}
+
+// loads the entire history
+bool KonqHistoryManager::loadHistory()
+{
+ clearPending();
+ m_history.clear();
+ m_pCompletion->clear();
+
+ QFile file( m_filename );
+ if ( !file.open( IO_ReadOnly ) ) {
+ if ( file.exists() )
+ kdWarning() << "Can't open " << file.name() << endl;
+
+ // try to load the old completion history
+ bool ret = loadFallback();
+ emit loadingFinished();
+ return ret;
+ }
+
+ QDataStream fileStream( &file );
+ QByteArray data; // only used for version == 2
+ // we construct the stream object now but fill in the data later.
+ // thanks to QBA's explicit sharing this works :)
+ QDataStream crcStream( data, IO_ReadOnly );
+
+ if ( !fileStream.atEnd() ) {
+ Q_UINT32 version;
+ fileStream >> version;
+
+ QDataStream *stream = &fileStream;
+
+ bool crcChecked = false;
+ bool crcOk = false;
+
+ if ( version == 2 || version == 3) {
+ Q_UINT32 crc;
+ crcChecked = true;
+ fileStream >> crc >> data;
+ crcOk = crc32( 0, reinterpret_cast<unsigned char *>( data.data() ), data.size() ) == crc;
+ stream = &crcStream; // pick up the right stream
+ }
+
+ if ( version == 3 )
+ {
+ //Use KURL marshalling for V3 format.
+ KonqHistoryEntry::marshalURLAsStrings = false;
+ }
+
+ if ( version != 0 && version < 3 ) //Versions 1,2 (but not 0) are also valid
+ {
+ //Turn on backwards compatibility mode..
+ KonqHistoryEntry::marshalURLAsStrings = true;
+ // it doesn't make sense to save to save maxAge and maxCount in the
+ // binary file, this would make backups impossible (they would clear
+ // themselves on startup, because all entries expire).
+ // [But V1 and V2 formats did it, so we do a dummy read]
+ Q_UINT32 dummy;
+ *stream >> dummy;
+ *stream >> dummy;
+
+ //OK.
+ version = 3;
+ }
+
+ if ( s_historyVersion != version || ( crcChecked && !crcOk ) ) {
+ kdWarning() << "The history version doesn't match, aborting loading" << endl;
+ file.close();
+ emit loadingFinished();
+ return false;
+ }
+
+
+ while ( !stream->atEnd() ) {
+ KonqHistoryEntry *entry = new KonqHistoryEntry;
+ Q_CHECK_PTR( entry );
+ *stream >> *entry;
+ // kdDebug(1203) << "## loaded entry: " << entry->url << ", Title: " << entry->title << endl;
+ m_history.append( entry );
+ QString urlString2 = entry->url.prettyURL();
+
+ addToCompletion( urlString2, entry->typedURL, entry->numberOfTimesVisited );
+
+ // and fill our baseclass.
+ QString urlString = entry->url.url();
+ KParts::HistoryProvider::insert( urlString );
+ // DF: also insert the "pretty" version if different
+ // This helps getting 'visited' links on websites which don't use fully-escaped urls.
+
+ if ( urlString != urlString2 )
+ KParts::HistoryProvider::insert( urlString2 );
+ }
+
+ kdDebug(1203) << "## loaded: " << m_history.count() << " entries." << endl;
+
+ m_history.sort();
+ adjustSize();
+ }
+
+
+ //This is important - we need to switch to a consistent marshalling format for
+ //communicating between different konqueror instances. Since during an upgrade
+ //some "old" copies may still running, we use the old format for the DCOP transfers.
+ //This doesn't make that much difference performance-wise for single entries anyway.
+ KonqHistoryEntry::marshalURLAsStrings = true;
+
+
+ // Theoretically, we should emit update() here, but as we only ever
+ // load items on startup up to now, this doesn't make much sense. Same
+ // thing for the above loadFallback().
+ // emit KParts::HistoryProvider::update( some list );
+
+
+
+ file.close();
+ emit loadingFinished();
+
+ return true;
+}
+
+
+// saves the entire history
+bool KonqHistoryManager::saveHistory()
+{
+ KSaveFile file( m_filename );
+ if ( file.status() != 0 ) {
+ kdWarning() << "Can't open " << file.name() << endl;
+ return false;
+ }
+
+ QDataStream *fileStream = file.dataStream();
+ *fileStream << s_historyVersion;
+
+ QByteArray data;
+ QDataStream stream( data, IO_WriteOnly );
+
+ //We use KURL for marshalling URLs in entries in the V3
+ //file format
+ KonqHistoryEntry::marshalURLAsStrings = false;
+ QPtrListIterator<KonqHistoryEntry> it( m_history );
+ KonqHistoryEntry *entry;
+ while ( (entry = it.current()) ) {
+ stream << *entry;
+ ++it;
+ }
+
+ //For DCOP, transfer strings instead - wire compat.
+ KonqHistoryEntry::marshalURLAsStrings = true;
+
+ Q_UINT32 crc = crc32( 0, reinterpret_cast<unsigned char *>( data.data() ), data.size() );
+ *fileStream << crc << data;
+
+ file.close();
+
+ return true;
+}
+
+
+void KonqHistoryManager::adjustSize()
+{
+ KonqHistoryEntry *entry = m_history.getFirst();
+
+ while ( m_history.count() > m_maxCount || isExpired( entry ) ) {
+ removeFromCompletion( entry->url.prettyURL(), entry->typedURL );
+
+ QString urlString = entry->url.url();
+ KParts::HistoryProvider::remove( urlString );
+
+ addToUpdateList( urlString );
+
+ emit entryRemoved( m_history.getFirst() );
+ m_history.removeFirst(); // deletes the entry
+
+ entry = m_history.getFirst();
+ }
+}
+
+
+void KonqHistoryManager::addPending( const KURL& url, const QString& typedURL,
+ const QString& title )
+{
+ addToHistory( true, url, typedURL, title );
+}
+
+void KonqHistoryManager::confirmPending( const KURL& url,
+ const QString& typedURL,
+ const QString& title )
+{
+ addToHistory( false, url, typedURL, title );
+}
+
+
+void KonqHistoryManager::addToHistory( bool pending, const KURL& _url,
+ const QString& typedURL,
+ const QString& title )
+{
+ kdDebug(1203) << "## addToHistory: " << _url.prettyURL() << " Typed URL: " << typedURL << ", Title: " << title << endl;
+
+ if ( filterOut( _url ) ) // we only want remote URLs
+ return;
+
+ // http URLs without a path will get redirected immediately to url + '/'
+ if ( _url.path().isEmpty() && _url.protocol().startsWith("http") )
+ return;
+
+ KURL url( _url );
+ bool hasPass = url.hasPass();
+ url.setPass( QString::null ); // No password in the history, especially not in the completion!
+ url.setHost( url.host().lower() ); // All host parts lower case
+ KonqHistoryEntry entry;
+ QString u = url.prettyURL();
+ entry.url = url;
+ if ( (u != typedURL) && !hasPass )
+ entry.typedURL = typedURL;
+
+ // we only keep the title if we are confirming an entry. Otherwise,
+ // we might get bogus titles from the previous url (actually it's just
+ // konqueror's window caption).
+ if ( !pending && u != title )
+ entry.title = title;
+ entry.firstVisited = QDateTime::currentDateTime();
+ entry.lastVisited = entry.firstVisited;
+
+ // always remove from pending if available, otherwise the else branch leaks
+ // if the map already contains an entry for this key.
+ QMapIterator<QString,KonqHistoryEntry*> it = m_pending.find( u );
+ if ( it != m_pending.end() ) {
+ delete it.data();
+ m_pending.remove( it );
+ }
+
+ if ( !pending ) {
+ if ( it != m_pending.end() ) {
+ // we make a pending entry official, so we just have to update
+ // and not increment the counter. No need to care about
+ // firstVisited, as this is not taken into account on update.
+ entry.numberOfTimesVisited = 0;
+ }
+ }
+
+ else {
+ // We add a copy of the current history entry of the url to the
+ // pending list, so that we can restore it if the user canceled.
+ // If there is no entry for the url yet, we just store the url.
+ KonqHistoryEntry *oldEntry = findEntry( url );
+ m_pending.insert( u, oldEntry ?
+ new KonqHistoryEntry( *oldEntry ) : 0L );
+ }
+
+ // notify all konqueror instances about the entry
+ emitAddToHistory( entry );
+}
+
+// interface of KParts::HistoryManager
+// Usually, we only record the history for non-local URLs (i.e. filterOut()
+// returns false). But when using the HistoryProvider interface, we record
+// exactly those filtered-out urls.
+// Moreover, we don't get any pending/confirming entries, just one insert()
+void KonqHistoryManager::insert( const QString& url )
+{
+ KURL u ( url );
+ if ( !filterOut( u ) || u.protocol() == "about" ) { // remote URL
+ return;
+ }
+ // Local URL -> add to history
+ KonqHistoryEntry entry;
+ entry.url = u;
+ entry.firstVisited = QDateTime::currentDateTime();
+ entry.lastVisited = entry.firstVisited;
+ emitAddToHistory( entry );
+}
+
+void KonqHistoryManager::emitAddToHistory( const KonqHistoryEntry& entry )
+{
+ QByteArray data;
+ QDataStream stream( data, IO_WriteOnly );
+ stream << entry << objId();
+ // Protection against very long urls (like data:)
+ if ( data.size() > 4096 )
+ return;
+ kapp->dcopClient()->send( "konqueror*", "KonqHistoryManager",
+ "notifyHistoryEntry(KonqHistoryEntry, QCString)",
+ data );
+}
+
+
+void KonqHistoryManager::removePending( const KURL& url )
+{
+ // kdDebug(1203) << "## Removing pending... " << url.prettyURL() << endl;
+
+ if ( url.isLocalFile() )
+ return;
+
+ QMapIterator<QString,KonqHistoryEntry*> it = m_pending.find( url.prettyURL() );
+ if ( it != m_pending.end() ) {
+ KonqHistoryEntry *oldEntry = it.data(); // the old entry, may be 0L
+ emitRemoveFromHistory( url ); // remove the current pending entry
+
+ if ( oldEntry ) // we had an entry before, now use that instead
+ emitAddToHistory( *oldEntry );
+
+ delete oldEntry;
+ m_pending.remove( it );
+ }
+}
+
+// clears the pending list and makes sure the entries get deleted.
+void KonqHistoryManager::clearPending()
+{
+ QMapIterator<QString,KonqHistoryEntry*> it = m_pending.begin();
+ while ( it != m_pending.end() ) {
+ delete it.data();
+ ++it;
+ }
+ m_pending.clear();
+}
+
+void KonqHistoryManager::emitRemoveFromHistory( const KURL& url )
+{
+ QByteArray data;
+ QDataStream stream( data, IO_WriteOnly );
+ stream << url << objId();
+ kapp->dcopClient()->send( "konqueror*", "KonqHistoryManager",
+ "notifyRemove(KURL, QCString)", data );
+}
+
+void KonqHistoryManager::emitRemoveFromHistory( const KURL::List& urls )
+{
+ QByteArray data;
+ QDataStream stream( data, IO_WriteOnly );
+ stream << urls << objId();
+ kapp->dcopClient()->send( "konqueror*", "KonqHistoryManager",
+ "notifyRemove(KURL::List, QCString)", data );
+}
+
+void KonqHistoryManager::emitClear()
+{
+ QByteArray data;
+ QDataStream stream( data, IO_WriteOnly );
+ stream << objId();
+ kapp->dcopClient()->send( "konqueror*", "KonqHistoryManager",
+ "notifyClear(QCString)", data );
+}
+
+void KonqHistoryManager::emitSetMaxCount( Q_UINT32 count )
+{
+ QByteArray data;
+ QDataStream stream( data, IO_WriteOnly );
+ stream << count << objId();
+ kapp->dcopClient()->send( "konqueror*", "KonqHistoryManager",
+ "notifyMaxCount(Q_UINT32, QCString)", data );
+}
+
+void KonqHistoryManager::emitSetMaxAge( Q_UINT32 days )
+{
+ QByteArray data;
+ QDataStream stream( data, IO_WriteOnly );
+ stream << days << objId();
+ kapp->dcopClient()->send( "konqueror*", "KonqHistoryManager",
+ "notifyMaxAge(Q_UINT32, QCString)", data );
+}
+
+///////////////////////////////////////////////////////////////////
+// DCOP called methods
+
+void KonqHistoryManager::notifyHistoryEntry( KonqHistoryEntry e,
+ QCString )
+{
+ //kdDebug(1203) << "Got new entry from Broadcast: " << e.url.prettyURL() << endl;
+
+ KonqHistoryEntry *entry = findEntry( e.url );
+ QString urlString = e.url.url();
+
+ if ( !entry ) { // create a new history entry
+ entry = new KonqHistoryEntry;
+ entry->url = e.url;
+ entry->firstVisited = e.firstVisited;
+ entry->numberOfTimesVisited = 0; // will get set to 1 below
+ m_history.append( entry );
+ KParts::HistoryProvider::insert( urlString );
+ }
+
+ if ( !e.typedURL.isEmpty() )
+ entry->typedURL = e.typedURL;
+ if ( !e.title.isEmpty() )
+ entry->title = e.title;
+ entry->numberOfTimesVisited += e.numberOfTimesVisited;
+ entry->lastVisited = e.lastVisited;
+
+ addToCompletion( entry->url.prettyURL(), entry->typedURL );
+
+ // bool pending = (e.numberOfTimesVisited != 0);
+
+ adjustSize();
+
+ // note, no need to do the updateBookmarkMetadata for every
+ // history object, only need to for the broadcast sender as
+ // the history object itself keeps the data consistant.
+ bool updated = KonqBookmarkManager::self()->updateAccessMetadata( urlString );
+
+ if ( isSenderOfBroadcast() ) {
+ // we are the sender of the broadcast, so we save
+ saveHistory();
+ // note, bk save does not notify, and we don't want to!
+ if (updated)
+ KonqBookmarkManager::self()->save();
+ }
+
+ addToUpdateList( urlString );
+ emit entryAdded( entry );
+}
+
+void KonqHistoryManager::notifyMaxCount( Q_UINT32 count, QCString )
+{
+ m_maxCount = count;
+ clearPending();
+ adjustSize();
+
+ KConfig *config = KGlobal::config();
+ KConfigGroupSaver cs( config, "HistorySettings" );
+ config->writeEntry( "Maximum of History entries", m_maxCount );
+
+ if ( isSenderOfBroadcast() ) {
+ saveHistory();
+ config->sync();
+ }
+}
+
+void KonqHistoryManager::notifyMaxAge( Q_UINT32 days, QCString )
+{
+ m_maxAgeDays = days;
+ clearPending();
+ adjustSize();
+
+ KConfig *config = KGlobal::config();
+ KConfigGroupSaver cs( config, "HistorySettings" );
+ config->writeEntry( "Maximum age of History entries", m_maxAgeDays );
+
+ if ( isSenderOfBroadcast() ) {
+ saveHistory();
+ config->sync();
+ }
+}
+
+void KonqHistoryManager::notifyClear( QCString )
+{
+ clearPending();
+ m_history.clear();
+ m_pCompletion->clear();
+
+ if ( isSenderOfBroadcast() )
+ saveHistory();
+
+ KParts::HistoryProvider::clear(); // also emits the cleared() signal
+}
+
+void KonqHistoryManager::notifyRemove( KURL url, QCString )
+{
+ kdDebug(1203) << "#### Broadcast: remove entry:: " << url.prettyURL() << endl;
+
+
+ KonqHistoryEntry *entry = m_history.findEntry( url );
+
+ if ( entry ) { // entry is now the current item
+ removeFromCompletion( entry->url.prettyURL(), entry->typedURL );
+
+ QString urlString = entry->url.url();
+ KParts::HistoryProvider::remove( urlString );
+
+ addToUpdateList( urlString );
+
+ m_history.take(); // does not delete
+ emit entryRemoved( entry );
+ delete entry;
+
+ if ( isSenderOfBroadcast() )
+ saveHistory();
+ }
+}
+
+void KonqHistoryManager::notifyRemove( KURL::List urls, QCString )
+{
+ kdDebug(1203) << "#### Broadcast: removing list!" << endl;
+
+ bool doSave = false;
+ KURL::List::Iterator it = urls.begin();
+ while ( it != urls.end() ) {
+ KonqHistoryEntry *entry = m_history.findEntry( *it );
+
+ if ( entry ) { // entry is now the current item
+ removeFromCompletion( entry->url.prettyURL(), entry->typedURL );
+
+ QString urlString = entry->url.url();
+ KParts::HistoryProvider::remove( urlString );
+
+ addToUpdateList( urlString );
+
+ m_history.take(); // does not delete
+ emit entryRemoved( entry );
+ delete entry;
+ doSave = true;
+ }
+
+ ++it;
+ }
+
+ if (doSave && isSenderOfBroadcast())
+ saveHistory();
+}
+
+
+// compatibility fallback, try to load the old completion history
+bool KonqHistoryManager::loadFallback()
+{
+ QString file = locateLocal( "config", QString::fromLatin1("konq_history"));
+ if ( file.isEmpty() )
+ return false;
+
+ KonqHistoryEntry *entry;
+ KSimpleConfig config( file );
+ config.setGroup("History");
+ QStringList items = config.readListEntry( "CompletionItems" );
+ QStringList::Iterator it = items.begin();
+
+ while ( it != items.end() ) {
+ entry = createFallbackEntry( *it );
+ if ( entry ) {
+ m_history.append( entry );
+ addToCompletion( entry->url.prettyURL(), QString::null, entry->numberOfTimesVisited );
+
+ KParts::HistoryProvider::insert( entry->url.url() );
+ }
+ ++it;
+ }
+
+ m_history.sort();
+ adjustSize();
+ saveHistory();
+
+ return true;
+}
+
+// tries to create a small KonqHistoryEntry out of a string, where the string
+// looks like "http://www.bla.com/bla.html:23"
+// the attached :23 is the weighting from KCompletion
+KonqHistoryEntry * KonqHistoryManager::createFallbackEntry(const QString& item) const
+{
+ // code taken from KCompletion::addItem(), adjusted to use weight = 1
+ uint len = item.length();
+ uint weight = 1;
+
+ // find out the weighting of this item (appended to the string as ":num")
+ int index = item.findRev(':');
+ if ( index > 0 ) {
+ bool ok;
+ weight = item.mid( index + 1 ).toUInt( &ok );
+ if ( !ok )
+ weight = 1;
+
+ len = index; // only insert until the ':'
+ }
+
+
+ KonqHistoryEntry *entry = 0L;
+ KURL u( item.left( len ));
+ if ( u.isValid() ) {
+ entry = new KonqHistoryEntry;
+ // that's the only entries we know about...
+ entry->url = u;
+ entry->numberOfTimesVisited = weight;
+ // to make it not expire immediately...
+ entry->lastVisited = QDateTime::currentDateTime();
+ }
+
+ return entry;
+}
+
+KonqHistoryEntry * KonqHistoryManager::findEntry( const KURL& url )
+{
+ // small optimization (dict lookup) for items _not_ in our history
+ if ( !KParts::HistoryProvider::contains( url.url() ) )
+ return 0L;
+
+ return m_history.findEntry( url );
+}
+
+bool KonqHistoryManager::filterOut( const KURL& url )
+{
+ return ( url.isLocalFile() || url.host().isEmpty() );
+}
+
+void KonqHistoryManager::slotEmitUpdated()
+{
+ emit KParts::HistoryProvider::updated( m_updateURLs );
+ m_updateURLs.clear();
+}
+
+QStringList KonqHistoryManager::allURLs() const
+{
+ QStringList list;
+ KonqHistoryIterator it ( m_history );
+ for ( ; it.current(); ++it )
+ list.append( it.current()->url.url() );
+
+ return list;
+}
+
+void KonqHistoryManager::addToCompletion( const QString& url, const QString& typedURL,
+ int numberOfTimesVisited )
+{
+ m_pCompletion->addItem( url, numberOfTimesVisited );
+ // typed urls have a higher priority
+ m_pCompletion->addItem( typedURL, numberOfTimesVisited +10 );
+}
+
+void KonqHistoryManager::removeFromCompletion( const QString& url, const QString& typedURL )
+{
+ m_pCompletion->removeItem( url );
+ m_pCompletion->removeItem( typedURL );
+}
+
+//////////////////////////////////////////////////////////////////
+
+
+KonqHistoryEntry * KonqHistoryList::findEntry( const KURL& url )
+{
+ // we search backwards, probably faster to find an entry
+ KonqHistoryEntry *entry = last();
+ while ( entry ) {
+ if ( entry->url == url )
+ return entry;
+
+ entry = prev();
+ }
+
+ return 0L;
+}
+
+// sort by lastVisited date (oldest go first)
+int KonqHistoryList::compareItems( QPtrCollection::Item item1,
+ QPtrCollection::Item item2 )
+{
+ KonqHistoryEntry *entry1 = static_cast<KonqHistoryEntry *>( item1 );
+ KonqHistoryEntry *entry2 = static_cast<KonqHistoryEntry *>( item2 );
+
+ if ( entry1->lastVisited > entry2->lastVisited )
+ return 1;
+ else if ( entry1->lastVisited < entry2->lastVisited )
+ return -1;
+ else
+ return 0;
+}
+
+using namespace KParts; // for IRIX
+
+#include "konq_historymgr.moc"