libkonq Library API Documentation

konq_historymgr.cc

00001 /* This file is part of the KDE project
00002    Copyright (C) 2000,2001 Carsten Pfeiffer <pfeiffer@kde.org>
00003 
00004    This program is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012     General Public License for more details.
00013 
00014    You should have received a copy of the GNU General Public License
00015    along with this program; see the file COPYING.  If not, write to
00016    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
00017    Boston, MA 02111-1307, USA.
00018 */
00019 
00020 #include "konq_historymgr.h"
00021 
00022 
00023 #include <dcopclient.h>
00024 
00025 #include <kapplication.h>
00026 #include <kdebug.h>
00027 #include <ksavefile.h>
00028 #include <ksimpleconfig.h>
00029 #include <kstandarddirs.h>
00030 
00031 #include <zlib.h>
00032 
00033 const Q_UINT32 KonqHistoryManager::s_historyVersion = 2;
00034 
00035 KonqHistoryManager::KonqHistoryManager( QObject *parent, const char *name )
00036     : KParts::HistoryProvider( parent, name ),
00037               KonqHistoryComm( "KonqHistoryManager" )
00038 {
00039     m_updateTimer = new QTimer( this );
00040 
00041     // defaults
00042     KConfig *config = KGlobal::config();
00043     KConfigGroupSaver cs( config, "HistorySettings" );
00044     m_maxCount = config->readNumEntry( "Maximum of History entries", 500 );
00045     m_maxCount = QMAX( 1, m_maxCount );
00046     m_maxAgeDays = config->readNumEntry( "Maximum age of History entries", 90);
00047 
00048     m_history.setAutoDelete( true );
00049     m_filename = locateLocal( "data",
00050                               QString::fromLatin1("konqueror/konq_history" ));
00051 
00052     if ( !kapp->dcopClient()->isAttached() )
00053         kapp->dcopClient()->attach();
00054 
00055 
00056     // take care of the completion object
00057     m_pCompletion = new KCompletion;
00058     m_pCompletion->setOrder( KCompletion::Weighted );
00059 
00060     // and load the history
00061     loadHistory();
00062 
00063     connect( m_updateTimer, SIGNAL( timeout() ), SLOT( slotEmitUpdated() ));
00064 }
00065 
00066 
00067 KonqHistoryManager::~KonqHistoryManager()
00068 {
00069     delete m_pCompletion;
00070     clearPending();
00071 }
00072 
00073 bool KonqHistoryManager::isSenderOfBroadcast()
00074 {
00075     DCOPClient *dc = callingDcopClient();
00076     return !dc || (dc->senderId() == dc->appId());
00077 }
00078 
00079 // loads the entire history
00080 bool KonqHistoryManager::loadHistory()
00081 {
00082     clearPending();
00083     m_history.clear();
00084     m_pCompletion->clear();
00085 
00086     QFile file( m_filename );
00087     if ( !file.open( IO_ReadOnly ) ) {
00088         if ( file.exists() )
00089             kdWarning() << "Can't open " << file.name() << endl;
00090 
00091         // try to load the old completion history
00092         bool ret = loadFallback();
00093         emit loadingFinished();
00094         return ret;
00095     }
00096 
00097     QDataStream fileStream( &file );
00098     QByteArray data; // only used for version == 2
00099     // we construct the stream object now but fill in the data later.
00100     // thanks to QBA's explicit sharing this works :)
00101     QDataStream crcStream( data, IO_ReadOnly );
00102 
00103     if ( !fileStream.atEnd() ) {
00104         Q_UINT32 version;
00105         fileStream >> version;
00106 
00107         QDataStream *stream = &fileStream;
00108 
00109         bool crcChecked = false;
00110         bool crcOk = false;
00111 
00112         if ( version == 2 ) {
00113             Q_UINT32 crc;
00114 
00115             crcChecked = true;
00116 
00117             fileStream >> crc >> data;
00118 
00119             crcOk = crc32( 0, reinterpret_cast<unsigned char *>( data.data() ), data.size() ) == crc;
00120 
00121             stream = &crcStream; // pick up the right stream
00122         }
00123         else if ( version == 1 ) // fake, as we still support v1
00124             version = 2;
00125 
00126         if ( s_historyVersion != version || ( crcChecked && !crcOk ) ) {
00127             kdWarning() << "The history version doesn't match, aborting loading" << endl;
00128             file.close();
00129             emit loadingFinished();
00130             return false;
00131         }
00132 
00133         // it doesn't make sense to save to save maxAge and maxCount  in the
00134         // binary file, this would make backups impossible (they would clear
00135         // themselves on startup, because all entries expire).
00136         Q_UINT32 dummy;
00137         *stream >> dummy;
00138         *stream >> dummy;
00139 
00140         while ( !stream->atEnd() ) {
00141             KonqHistoryEntry *entry = new KonqHistoryEntry;
00142             Q_CHECK_PTR( entry );
00143             *stream >> *entry;
00144 
00145             // kdDebug(1203) << "## loaded entry: " << entry->url << ",  Title: " << entry->title << endl;
00146             m_history.append( entry );
00147 
00148             // insert the completion item weighted
00149             m_pCompletion->addItem( entry->url.prettyURL(),
00150                                     entry->numberOfTimesVisited );
00151             m_pCompletion->addItem( entry->typedURL,
00152                                     entry->numberOfTimesVisited );
00153 
00154             // and fill our baseclass.
00155             QString urlString = entry->url.url();
00156             KParts::HistoryProvider::insert( urlString );
00157             // DF: also insert the "pretty" version if different
00158             // This helps getting 'visited' links on websites which don't use fully-escaped urls.
00159             QString urlString2 = entry->url.prettyURL();
00160             if ( urlString != urlString2 )
00161                 KParts::HistoryProvider::insert( urlString2 );
00162         }
00163 
00164         kdDebug(1203) << "## loaded: " << m_history.count() << " entries." << endl;
00165 
00166         m_history.sort();
00167         adjustSize();
00168     }
00169 
00170     // Theoretically, we should emit update() here, but as we only ever
00171     // load items on startup up to now, this doesn't make much sense. Same
00172     // thing for the above loadFallback().
00173     // emit KParts::HistoryProvider::update( some list );
00174 
00175     file.close();
00176     emit loadingFinished();
00177 
00178     return true;
00179 }
00180 
00181 
00182 // saves the entire history
00183 bool KonqHistoryManager::saveHistory()
00184 {
00185     KSaveFile file( m_filename );
00186     if ( file.status() != 0 ) {
00187         kdWarning() << "Can't open " << file.name() << endl;
00188         return false;
00189     }
00190 
00191     QDataStream *fileStream = file.dataStream();
00192     *fileStream << s_historyVersion;
00193 
00194     QByteArray data;
00195     QDataStream stream( data, IO_WriteOnly );
00196 
00197     stream << m_maxCount;   // not saved in history anymore, just a dummy here
00198     stream << m_maxAgeDays; // not saved in history anymore, just a dummy here
00199 
00200     QPtrListIterator<KonqHistoryEntry> it( m_history );
00201     KonqHistoryEntry *entry;
00202     while ( (entry = it.current()) ) {
00203         stream << *entry;
00204         ++it;
00205     }
00206 
00207     Q_UINT32 crc = crc32( 0, reinterpret_cast<unsigned char *>( data.data() ), data.size() );
00208     *fileStream << crc << data;
00209 
00210     file.close();
00211 
00212     return true;
00213 }
00214 
00215 
00216 void KonqHistoryManager::adjustSize()
00217 {
00218     KonqHistoryEntry *entry = m_history.getFirst();
00219 
00220     while ( m_history.count() > m_maxCount || isExpired( entry ) ) {
00221         m_pCompletion->removeItem( entry->url.prettyURL() );
00222         m_pCompletion->removeItem( entry->typedURL );
00223 
00224         QString urlString = entry->url.url();
00225         KParts::HistoryProvider::remove( urlString );
00226 
00227         addToUpdateList( urlString );
00228 
00229         emit entryRemoved( m_history.getFirst() );
00230         m_history.removeFirst(); // deletes the entry
00231 
00232         entry = m_history.getFirst();
00233     }
00234 }
00235 
00236 
00237 void KonqHistoryManager::addPending( const KURL& url, const QString& typedURL,
00238                                      const QString& title )
00239 {
00240     addToHistory( true, url, typedURL, title );
00241 }
00242 
00243 void KonqHistoryManager::confirmPending( const KURL& url,
00244                                          const QString& typedURL,
00245                                          const QString& title )
00246 {
00247     addToHistory( false, url, typedURL, title );
00248 }
00249 
00250 
00251 void KonqHistoryManager::addToHistory( bool pending, const KURL& _url,
00252                                        const QString& typedURL,
00253                                        const QString& title )
00254 {
00255     kdDebug(1203) << "## addToHistory: " << _url.prettyURL() << "Typed URL: " << typedURL << ", Title: " << title << endl;
00256 
00257     if ( filterOut( _url ) ) // we only want remote URLs
00258         return;
00259     KURL url( _url );
00260     url.setPass( "" ); // No password in the history, especially not in the completion !
00261     url.setHost( url.host().lower() ); // All host parts lower case
00262     KonqHistoryEntry entry;
00263     QString u = url.prettyURL();
00264     entry.url = url;
00265     if ( u != typedURL )
00266         entry.typedURL = typedURL;
00267 
00268     // we only keep the title if we are confirming an entry. Otherwise,
00269     // we might get bogus titles from the previous url (actually it's just
00270     // konqueror's window caption).
00271     if ( !pending && u != title )
00272         entry.title = title;
00273     entry.firstVisited = QDateTime::currentDateTime();
00274     entry.lastVisited = entry.firstVisited;
00275 
00276     if ( !pending ) { // remove from pending if available.
00277         QMapIterator<QString,KonqHistoryEntry*> it = m_pending.find( u );
00278 
00279         if ( it != m_pending.end() ) {
00280             delete it.data();
00281             m_pending.remove( it );
00282 
00283             // we make a pending entry official, so we just have to update
00284             // and not increment the counter. No need to care about
00285             // firstVisited, as this is not taken into account on update.
00286             entry.numberOfTimesVisited = 0;
00287         }
00288     }
00289 
00290     else {
00291         // We add a copy of the current history entry of the url to the
00292         // pending list, so that we can restore it if the user canceled.
00293         // If there is no entry for the url yet, we just store the url.
00294         KonqHistoryEntry *oldEntry = findEntry( url );
00295         m_pending.insert( u, oldEntry ?
00296                           new KonqHistoryEntry( *oldEntry ) : 0L );
00297     }
00298 
00299     // notify all konqueror instances about the entry
00300     emitAddToHistory( entry );
00301 }
00302 
00303 // interface of KParts::HistoryManager
00304 // Usually, we only record the history for non-local URLs (i.e. filterOut()
00305 // returns false). But when using the HistoryProvider interface, we record
00306 // exactly those filtered-out urls.
00307 // Moreover, we  don't get any pending/confirming entries, just one insert()
00308 void KonqHistoryManager::insert( const QString& url )
00309 {
00310     KURL u = url;
00311     if ( !filterOut( url ) || u.protocol() == "about" ) { // remote URL
00312         return;
00313     }
00314     // Local URL -> add to history
00315     KonqHistoryEntry entry;
00316     entry.url = u;
00317     entry.firstVisited = QDateTime::currentDateTime();
00318     entry.lastVisited = entry.firstVisited;
00319     emitAddToHistory( entry );
00320 }
00321 
00322 void KonqHistoryManager::emitAddToHistory( const KonqHistoryEntry& entry )
00323 {
00324     QByteArray data;
00325     QDataStream stream( data, IO_WriteOnly );
00326     stream << entry << objId();
00327     kapp->dcopClient()->send( "konqueror*", "KonqHistoryManager",
00328                               "notifyHistoryEntry(KonqHistoryEntry, QCString)",
00329                               data );
00330 }
00331 
00332 
00333 void KonqHistoryManager::removePending( const KURL& url )
00334 {
00335     // kdDebug(1203) << "## Removing pending... " << url.prettyURL() << endl;
00336 
00337     if ( url.isLocalFile() )
00338         return;
00339 
00340     QMapIterator<QString,KonqHistoryEntry*> it = m_pending.find( url.prettyURL() );
00341     if ( it != m_pending.end() ) {
00342         KonqHistoryEntry *oldEntry = it.data(); // the old entry, may be 0L
00343         emitRemoveFromHistory( url ); // remove the current pending entry
00344 
00345         if ( oldEntry ) // we had an entry before, now use that instead
00346             emitAddToHistory( *oldEntry );
00347 
00348         delete oldEntry;
00349         m_pending.remove( it );
00350     }
00351 }
00352 
00353 // clears the pending list and makes sure the entries get deleted.
00354 void KonqHistoryManager::clearPending()
00355 {
00356     QMapIterator<QString,KonqHistoryEntry*> it = m_pending.begin();
00357     while ( it != m_pending.end() ) {
00358         delete it.data();
00359         ++it;
00360     }
00361     m_pending.clear();
00362 }
00363 
00364 void KonqHistoryManager::emitRemoveFromHistory( const KURL& url )
00365 {
00366     QByteArray data;
00367     QDataStream stream( data, IO_WriteOnly );
00368     stream << url << objId();
00369     kapp->dcopClient()->send( "konqueror*", "KonqHistoryManager",
00370                               "notifyRemove(KURL, QCString)", data );
00371 }
00372 
00373 void KonqHistoryManager::emitRemoveFromHistory( const KURL::List& urls )
00374 {
00375     QByteArray data;
00376     QDataStream stream( data, IO_WriteOnly );
00377     stream << urls << objId();
00378     kapp->dcopClient()->send( "konqueror*", "KonqHistoryManager",
00379                               "notifyRemove(KURL::List, QCString)", data );
00380 }
00381 
00382 void KonqHistoryManager::emitClear()
00383 {
00384     QByteArray data;
00385     QDataStream stream( data, IO_WriteOnly );
00386     stream << objId();
00387     kapp->dcopClient()->send( "konqueror*", "KonqHistoryManager",
00388                               "notifyClear(QCString)", data );
00389 }
00390 
00391 void KonqHistoryManager::emitSetMaxCount( Q_UINT32 count )
00392 {
00393     QByteArray data;
00394     QDataStream stream( data, IO_WriteOnly );
00395     stream << count << objId();
00396     kapp->dcopClient()->send( "konqueror*", "KonqHistoryManager",
00397                               "notifyMaxCount(Q_UINT32, QCString)", data );
00398 }
00399 
00400 void KonqHistoryManager::emitSetMaxAge( Q_UINT32 days )
00401 {
00402     QByteArray data;
00403     QDataStream stream( data, IO_WriteOnly );
00404     stream << days << objId();
00405     kapp->dcopClient()->send( "konqueror*", "KonqHistoryManager",
00406                               "notifyMaxAge(Q_UINT32, QCString)", data );
00407 }
00408 
00410 // DCOP called methods
00411 
00412 void KonqHistoryManager::notifyHistoryEntry( KonqHistoryEntry e,
00413                                              QCString  )
00414 {
00415     //kdDebug(1203) << "Got new entry from Broadcast: " << e.url.prettyURL() << endl;
00416 
00417     KonqHistoryEntry *entry = findEntry( e.url );
00418     QString urlString = e.url.url();
00419 
00420     if ( !entry ) { // create a new history entry
00421         entry = new KonqHistoryEntry;
00422         entry->url = e.url;
00423         entry->firstVisited = e.firstVisited;
00424         entry->numberOfTimesVisited = 0; // will get set to 1 below
00425         m_history.append( entry );
00426         KParts::HistoryProvider::insert( urlString );
00427     }
00428 
00429     if ( !e.typedURL.isEmpty() )
00430         entry->typedURL = e.typedURL;
00431     if ( !e.title.isEmpty() )
00432         entry->title = e.title;
00433     entry->numberOfTimesVisited += e.numberOfTimesVisited;
00434     entry->lastVisited = e.lastVisited;
00435 
00436     m_pCompletion->addItem( entry->url.prettyURL() );
00437     m_pCompletion->addItem( entry->typedURL );
00438 
00439     // bool pending = (e.numberOfTimesVisited != 0);
00440 
00441     adjustSize();
00442 
00443     if ( isSenderOfBroadcast() )
00444         saveHistory();
00445 
00446     addToUpdateList( urlString );
00447     emit entryAdded( entry );
00448 }
00449 
00450 void KonqHistoryManager::notifyMaxCount( Q_UINT32 count, QCString )
00451 {
00452     m_maxCount = count;
00453     clearPending();
00454     adjustSize();
00455 
00456     KConfig *config = KGlobal::config();
00457     KConfigGroupSaver cs( config, "HistorySettings" );
00458     config->writeEntry( "Maximum of History entries", m_maxCount );
00459 
00460     if ( isSenderOfBroadcast() ) { 
00461         saveHistory();
00462         config->sync();
00463     }
00464 }
00465 
00466 void KonqHistoryManager::notifyMaxAge( Q_UINT32 days, QCString  )
00467 {
00468     m_maxAgeDays = days;
00469     clearPending();
00470     adjustSize();
00471 
00472     KConfig *config = KGlobal::config();
00473     KConfigGroupSaver cs( config, "HistorySettings" );
00474     config->writeEntry( "Maximum age of History entries", m_maxAgeDays );
00475 
00476     if ( isSenderOfBroadcast() ) { 
00477         saveHistory();
00478         config->sync();
00479     }
00480 }
00481 
00482 void KonqHistoryManager::notifyClear( QCString )
00483 {
00484     clearPending();
00485     m_history.clear();
00486     m_pCompletion->clear();
00487 
00488     if ( isSenderOfBroadcast() )
00489         saveHistory();
00490 
00491     KParts::HistoryProvider::clear(); // also emits the cleared() signal
00492 }
00493 
00494 void KonqHistoryManager::notifyRemove( KURL url, QCString )
00495 {
00496     kdDebug(1203) << "#### Broadcast: remove entry:: " << url.prettyURL() << endl;
00497 
00498     KonqHistoryEntry *entry = m_history.findEntry( url );
00499     if ( entry ) { // entry is now the current item
00500         m_pCompletion->removeItem( entry->url.prettyURL() );
00501         m_pCompletion->removeItem( entry->typedURL );
00502 
00503         QString urlString = entry->url.url();
00504         KParts::HistoryProvider::remove( urlString );
00505 
00506         addToUpdateList( urlString );
00507 
00508         m_history.take(); // does not delete
00509         emit entryRemoved( entry );
00510         delete entry;
00511 
00512         if ( isSenderOfBroadcast() )
00513             saveHistory();
00514     }
00515 }
00516 
00517 void KonqHistoryManager::notifyRemove( KURL::List urls, QCString )
00518 {
00519     kdDebug(1203) << "#### Broadcast: removing list!" << endl;
00520 
00521     bool doSave = false;
00522     KURL::List::Iterator it = urls.begin();
00523     while ( it != urls.end() ) {
00524         KonqHistoryEntry *entry = m_history.findEntry( *it );
00525         if ( entry ) { // entry is now the current item
00526             m_pCompletion->removeItem( entry->url.prettyURL() );
00527             m_pCompletion->removeItem( entry->typedURL );
00528 
00529             QString urlString = entry->url.url();
00530             KParts::HistoryProvider::remove( urlString );
00531 
00532             addToUpdateList( urlString );
00533 
00534             m_history.take(); // does not delete
00535             emit entryRemoved( entry );
00536             delete entry;
00537             doSave = true;
00538         }
00539 
00540         ++it;
00541     }
00542 
00543     if (doSave && isSenderOfBroadcast())
00544         saveHistory();
00545 }
00546 
00547 
00548 // compatibility fallback, try to load the old completion history
00549 bool KonqHistoryManager::loadFallback()
00550 {
00551     QString file = locateLocal( "config", QString::fromLatin1("konq_history"));
00552     if ( file.isEmpty() )
00553         return false;
00554 
00555     KonqHistoryEntry *entry;
00556     KSimpleConfig config( file );
00557     config.setGroup("History");
00558     QStringList items = config.readListEntry( "CompletionItems" );
00559     QStringList::Iterator it = items.begin();
00560 
00561     while ( it != items.end() ) {
00562         entry = createFallbackEntry( *it );
00563         if ( entry ) {
00564             m_history.append( entry );
00565             m_pCompletion->addItem( entry->url.prettyURL(),
00566                                     entry->numberOfTimesVisited );
00567 
00568             KParts::HistoryProvider::insert( entry->url.url() );
00569         }
00570         ++it;
00571     }
00572 
00573     m_history.sort();
00574     adjustSize();
00575     saveHistory();
00576 
00577     return true;
00578 }
00579 
00580 // tries to create a small KonqHistoryEntry out of a string, where the string
00581 // looks like "http://www.bla.com/bla.html:23"
00582 // the attached :23 is the weighting from KCompletion
00583 KonqHistoryEntry * KonqHistoryManager::createFallbackEntry(const QString& item) const
00584 {
00585     // code taken from KCompletion::addItem(), adjusted to use weight = 1
00586     uint len = item.length();
00587     uint weight = 1;
00588 
00589     // find out the weighting of this item (appended to the string as ":num")
00590     int index = item.findRev(':');
00591     if ( index > 0 ) {
00592         bool ok;
00593         weight = item.mid( index + 1 ).toUInt( &ok );
00594         if ( !ok )
00595             weight = 1;
00596 
00597         len = index; // only insert until the ':'
00598     }
00599 
00600 
00601     KonqHistoryEntry *entry = 0L;
00602     KURL u( item.left( len ));
00603     if ( !u.isMalformed() ) {
00604         entry = new KonqHistoryEntry;
00605         // that's the only entries we know about...
00606         entry->url = u;
00607         entry->numberOfTimesVisited = weight;
00608         // to make it not expire immediately...
00609         entry->lastVisited = QDateTime::currentDateTime();
00610     }
00611 
00612     return entry;
00613 }
00614 
00615 KonqHistoryEntry * KonqHistoryManager::findEntry( const KURL& url )
00616 {
00617     // small optimization (dict lookup) for items _not_ in our history
00618     if ( !KParts::HistoryProvider::contains( url.url() ) )
00619         return 0L;
00620 
00621     return m_history.findEntry( url );
00622 }
00623 
00624 bool KonqHistoryManager::filterOut( const KURL& url )
00625 {
00626     return ( url.isLocalFile() || url.host().isEmpty() );
00627 }
00628 
00629 void KonqHistoryManager::slotEmitUpdated()
00630 {
00631     emit KParts::HistoryProvider::updated( m_updateURLs );
00632     m_updateURLs.clear();
00633 }
00634 
00635 QStringList KonqHistoryManager::allURLs() const
00636 {
00637     QStringList list;
00638     KonqHistoryIterator it ( m_history );
00639     for ( ; it.current(); ++it )
00640         list.append( it.current()->url.url() );
00641     
00642     return list;
00643 }
00644 
00646 
00647 
00648 KonqHistoryEntry * KonqHistoryList::findEntry( const KURL& url )
00649 {
00650     // we search backwards, probably faster to find an entry
00651     KonqHistoryEntry *entry = last();
00652     while ( entry ) {
00653         if ( entry->url == url )
00654             return entry;
00655 
00656         entry = prev();
00657     }
00658 
00659     return 0L;
00660 }
00661 
00662 // sort by lastVisited date (oldest go first)
00663 int KonqHistoryList::compareItems( QPtrCollection::Item item1,
00664                                    QPtrCollection::Item item2 )
00665 {
00666     KonqHistoryEntry *entry1 = static_cast<KonqHistoryEntry *>( item1 );
00667     KonqHistoryEntry *entry2 = static_cast<KonqHistoryEntry *>( item2 );
00668 
00669     if ( entry1->lastVisited > entry2->lastVisited )
00670         return 1;
00671     else if ( entry1->lastVisited < entry2->lastVisited )
00672         return -1;
00673     else
00674         return 0;
00675 }
00676 
00677 using namespace KParts; // for IRIX
00678 
00679 #include "konq_historymgr.moc"
KDE Logo
This file is part of the documentation for kdelibs Version 3.1.5.
Documentation copyright © 1996-2002 the KDE developers.
Generated on Thu Jan 29 23:03:28 2004 by doxygen 1.3.4 written by Dimitri van Heesch, © 1997-2001