kmail

index.cpp

Go to the documentation of this file.
00001 /* This file is part of KMail
00002  * Copyright (C) 2005 Luís Pedro Coelho <luis@luispedro.org>
00003  *
00004  * KMail is free software; you can redistribute it and/or modify it
00005  * under the terms of the GNU General Public License, version 2, as
00006  * published by the Free Software Foundation.
00007  *
00008  * KMail is distributed in the hope that it will be useful, but
00009  * WITHOUT ANY WARRANTY; without even the implied warranty of
00010  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00011  * General Public License for more details.
00012  *
00013  * You should have received a copy of the GNU General Public License
00014  * along with this program; if not, write to the Free Software
00015  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
00016  *
00017  * In addition, as a special exception, the copyright holders give
00018  * permission to link the code of this program with any edition of
00019  * the Qt library by Trolltech AS, Norway (or with modified versions
00020  * of Qt that use the same license as Qt), and distribute linked
00021  * combinations including the two.  You must obey the GNU General
00022  * Public License in all respects for all of the code used other than
00023  * Qt.  If you modify this file, you may extend this exception to
00024  * your version of the file, but you are not obligated to do so.  If
00025  * you do not wish to do so, delete this exception statement from
00026  * your version.
00027  */
00028 
00029 #include "index.h"
00030 
00031 #include "kmkernel.h"
00032 #include "kmfoldermgr.h"
00033 #include "kmmsgdict.h"
00034 #include "kmfolder.h"
00035 #include "kmsearchpattern.h"
00036 #include "kmfoldersearch.h"
00037 
00038 #include <kdebug.h>
00039 #include <kapplication.h>
00040 #include <qfile.h>
00041 #include <qtimer.h>
00042 #include <qvaluestack.h>
00043 #include <qptrlist.h>
00044 #include <qfileinfo.h>
00045 #ifdef HAVE_INDEXLIB
00046 #include <indexlib/create.h>
00047 #endif
00048 
00049 #include <sys/types.h>
00050 #include <sys/stat.h>
00051 
00052 #include <iostream>
00053 #include <algorithm>
00054 #include <cstdlib>
00055 
00056 namespace {
00057 const unsigned int MaintenanceLimit = 1000;
00058 const char* const folderIndexDisabledKey = "fulltextIndexDisabled";
00059 }
00060 
00061 #ifdef HAVE_INDEXLIB
00062 static
00063 QValueList<int> vectorToQValueList( const std::vector<Q_UINT32>& input ) {
00064     QValueList<int> res;
00065     std::copy( input.begin(), input.end(), std::back_inserter( res ) );
00066     return res;
00067 }
00068 
00069 static
00070 std::vector<Q_UINT32> QValueListToVector( const QValueList<int>& input ) {
00071     std::vector<Q_UINT32> res;
00072     // res.assign( input.begin(), input.end() ) doesn't work for some reason
00073     for ( QValueList<int>::const_iterator first = input.begin(), past = input.end(); first != past; ++first ) {
00074         res.push_back( *first );
00075     }
00076     return res;
00077 }
00078 #endif
00079 
00080 KMMsgIndex::KMMsgIndex( QObject* parent ):
00081     QObject( parent, "index" ),
00082     mState( s_idle ),
00083 #ifdef HAVE_INDEXLIB
00084     mLockFile( std::string( static_cast<const char*>( QFile::encodeName( defaultPath() ) + "/lock" ) ) ),
00085     mIndex( 0 ),
00086 #endif
00087     mIndexPath( QFile::encodeName( defaultPath() ) ),
00088     mTimer( new QTimer( this, "mTimer" ) ),
00089     //mSyncState( ss_none ),
00090     //mSyncTimer( new QTimer( this ) ),
00091     mSlowDown( false ) {
00092     kdDebug( 5006 ) << "KMMsgIndex::KMMsgIndex()" << endl;
00093 
00094     connect( kmkernel->folderMgr(), SIGNAL( msgRemoved( KMFolder*, Q_UINT32 ) ), SLOT( slotRemoveMessage( Q_UINT32 ) ) );
00095     connect( kmkernel->folderMgr(), SIGNAL( msgAdded( KMFolder*, Q_UINT32 ) ), SLOT( slotAddMessage( Q_UINT32 ) ) );
00096     connect( kmkernel->dimapFolderMgr(), SIGNAL( msgRemoved( KMFolder*, Q_UINT32 ) ), SLOT( slotRemoveMessage( Q_UINT32 ) ) );
00097     connect( kmkernel->dimapFolderMgr(), SIGNAL( msgAdded( KMFolder*, Q_UINT32 ) ), SLOT( slotAddMessage( Q_UINT32 ) ) );
00098 
00099     connect( mTimer, SIGNAL( timeout() ), SLOT( act() ) );
00100     //connect( mSyncTimer, SIGNAL( timeout() ), SLOT( syncIndex() ) );
00101 
00102 #ifdef HAVE_INDEXLIB
00103     KConfigGroup cfg( KMKernel::config(), "text-index" );
00104     if ( !cfg.readBoolEntry( "enabled", false ) ) {
00105         indexlib::remove( mIndexPath );
00106         mLockFile.force_unlock();
00107         mState = s_disabled;
00108         return;
00109     }
00110     if ( !mLockFile.trylock() ) {
00111         indexlib::remove( mIndexPath );
00112 
00113         mLockFile.force_unlock();
00114         mLockFile.trylock();
00115     } else {
00116         mIndex = indexlib::open( mIndexPath, indexlib::open_flags::fail_if_nonexistant ).release();
00117     }
00118     if ( !mIndex ) {
00119         QTimer::singleShot( 8000, this, SLOT( create() ) );
00120         mState = s_willcreate;
00121     } else {
00122         if ( cfg.readBoolEntry( "creating" ) ) {
00123             QTimer::singleShot( 8000, this, SLOT( continueCreation() ) );
00124             mState = s_creating;
00125         } else {
00126             mPendingMsgs = QValueListToVector( cfg.readIntListEntry( "pending" ) );
00127             mRemovedMsgs = QValueListToVector( cfg.readIntListEntry( "removed" ) );
00128         }
00129     }
00130     mIndex = 0;
00131 #else
00132     mState = s_error;
00133 #endif
00134     //if ( mState == s_idle ) mSyncState = ss_synced;
00135 }
00136 
00137 
00138 KMMsgIndex::~KMMsgIndex() {
00139     kdDebug( 5006 ) << "KMMsgIndex::~KMMsgIndex()" << endl;
00140 #ifdef HAVE_INDEXLIB
00141     KConfigGroup cfg( KMKernel::config(), "text-index" );
00142     cfg.writeEntry( "creating", mState == s_creating );
00143     QValueList<int> pendingMsg;
00144     if ( mState == s_processing ) {
00145         Q_ASSERT( mAddedMsgs.empty() );
00146         pendingMsg = vectorToQValueList( mPendingMsgs );
00147     }
00148     cfg.writeEntry( "pending", pendingMsg );
00149     cfg.writeEntry( "removed", vectorToQValueList( mRemovedMsgs ) );
00150     delete mIndex;
00151 #endif
00152 }
00153 
00154 bool KMMsgIndex::isIndexable( KMFolder* folder ) const {
00155     if ( !folder || !folder->parent() ) return false;
00156     const KMFolderMgr* manager = folder->parent()->manager();
00157     return manager == kmkernel->folderMgr() || manager == kmkernel->dimapFolderMgr();
00158 }
00159 
00160 bool KMMsgIndex::isIndexed( KMFolder* folder ) const {
00161     if ( !isIndexable( folder ) ) return false;
00162     KConfig* config = KMKernel::config();
00163     KConfigGroupSaver saver( config, "Folder-" + folder->idString() );
00164     return !config->readBoolEntry( folderIndexDisabledKey, false );
00165 }
00166 
00167 void KMMsgIndex::setEnabled( bool e ) {
00168     kdDebug( 5006 ) << "KMMsgIndex::setEnabled( " << e << " )" << endl;
00169     KConfig* config = KMKernel::config();
00170     KConfigGroupSaver saver( config, "text-index" );
00171     if ( config->readBoolEntry( "enabled", !e ) == e ) return;
00172     config->writeEntry( "enabled", e );
00173     if ( e ) {
00174         switch ( mState ) {
00175             case s_idle:
00176             case s_willcreate:
00177             case s_creating:
00178             case s_processing:
00179                 // nothing to do
00180                 return;
00181             case s_error:
00182                 // nothing can be done, probably
00183                 return;
00184             case s_disabled:
00185                 QTimer::singleShot( 8000, this, SLOT( create() ) );
00186                 mState = s_willcreate;
00187         }
00188     } else {
00189         clear();
00190     }
00191 }
00192 
00193 void KMMsgIndex::setIndexingEnabled( KMFolder* folder, bool e ) {
00194     KConfig* config = KMKernel::config();
00195     KConfigGroupSaver saver( config, "Folder-" + folder->idString() );
00196     if ( config->readBoolEntry( folderIndexDisabledKey, e ) == e ) return; // nothing to do
00197     config->writeEntry( folderIndexDisabledKey, e );
00198 
00199     if ( e ) {
00200         switch ( mState ) {
00201             case s_idle:
00202             case s_creating:
00203             case s_processing:
00204                 mPendingFolders.push_back( folder );
00205                 scheduleAction();
00206                 break;
00207             case s_willcreate:
00208                 // do nothing, create() will handle this
00209                 break;
00210             case s_error:
00211             case s_disabled:
00212                 // nothing can be done
00213                 break;
00214         }
00215 
00216     } else {
00217         switch ( mState ) {
00218             case s_willcreate:
00219                 // create() will notice that folder is disabled
00220                 break;
00221             case s_creating:
00222                 if ( std::find( mPendingFolders.begin(), mPendingFolders.end(), folder ) != mPendingFolders.end() ) {
00223                     // easy:
00224                     mPendingFolders.erase( std::find( mPendingFolders.begin(), mPendingFolders.end(), folder ) );
00225                     break;
00226                 }
00227                 //else  fall-through
00228             case s_idle:
00229             case s_processing:
00230 
00231             case s_error:
00232             case s_disabled:
00233                 // nothing can be done
00234                 break;
00235         }
00236     }
00237 }
00238 
00239 void KMMsgIndex::clear() {
00240     kdDebug( 5006 ) << "KMMsgIndex::clear()" << endl;
00241 #ifdef HAVE_INDEXLIB
00242     delete mIndex;
00243     mLockFile.force_unlock();
00244     mIndex = 0;
00245     indexlib::remove( mIndexPath );
00246     mPendingMsgs.clear();
00247     mPendingFolders.clear();
00248     mMaintenanceCount = 0;
00249     mAddedMsgs.clear();
00250     mRemovedMsgs.clear();
00251     mExisting.clear();
00252     mState = s_disabled;
00253     for ( std::set<KMFolder*>::const_iterator first = mOpenedFolders.begin(), past = mOpenedFolders.end(); first != past; ++first ) {
00254         ( *first )->close("msgindex");
00255     }
00256     mOpenedFolders.clear();
00257     for ( std::vector<Search*>::const_iterator first = mSearches.begin(), past = mSearches.end(); first != past; ++first ) {
00258         delete *first;
00259     }
00260     mSearches.clear();
00261     mTimer->stop();
00262 #endif
00263 }
00264 
00265 void KMMsgIndex::maintenance() {
00266 #ifdef HAVE_INDEXLIB
00267     if ( mState != s_idle || kapp->hasPendingEvents() ) {
00268         QTimer::singleShot( 8000, this, SLOT( maintenance() ) );
00269         return;
00270     }
00271     mIndex->maintenance();
00272 #endif
00273 }
00274 
00275 int KMMsgIndex::addMessage( Q_UINT32 serNum ) {
00276     kdDebug( 5006 ) << "KMMsgIndex::addMessage( " << serNum << " )" << endl;
00277     if ( mState == s_error ) return 0;
00278 #ifdef HAVE_INDEXLIB
00279     assert( mIndex );
00280     if ( !mExisting.empty() && std::binary_search( mExisting.begin(), mExisting.end(), serNum ) ) return 0;
00281 
00282     int idx = -1;
00283     KMFolder* folder = 0;
00284     KMMsgDict::instance()->getLocation( serNum, &folder, &idx );
00285     if ( !folder || idx == -1 ) return -1;
00286     if ( !mOpenedFolders.count( folder ) ) {
00287         mOpenedFolders.insert( folder );
00288         folder->open("msgindex");
00289     }
00290     KMMessage* msg = folder->getMsg( idx );
00291     /* I still don't know whether we should allow decryption or not.
00292      * Setting to false which makes more sense.
00293      * We keep signature to get the person's name
00294      */
00295     QString body = msg->asPlainText( false, false );
00296     if ( !body.isEmpty() && static_cast<const char*>( body.latin1() ) ) {
00297         mIndex->add( body.latin1(), QString::number( serNum ).latin1() );
00298     } else {
00299         kdDebug( 5006 ) << "Funny, no body" << endl;
00300     }
00301     folder->unGetMsg( idx );
00302 #endif
00303     return 0;
00304 }
00305 
00306 void KMMsgIndex::act() {
00307     kdDebug( 5006 ) << "KMMsgIndex::act()" << endl;
00308     if ( kapp->hasPendingEvents() ) {
00309         //nah, some other time..
00310         mTimer->start( 500 );
00311         mSlowDown = true;
00312         return;
00313     }
00314     if ( mSlowDown ) {
00315         mSlowDown = false;
00316         mTimer->start( 0 );
00317     }
00318     if ( !mPendingMsgs.empty() ) {
00319         addMessage( mPendingMsgs.back() );
00320         mPendingMsgs.pop_back();
00321         return;
00322     }
00323     if ( !mPendingFolders.empty() ) {
00324         KMFolder *f = mPendingFolders.back();
00325         mPendingFolders.pop_back();
00326         if ( !mOpenedFolders.count( f ) ) {
00327             mOpenedFolders.insert( f );
00328             f->open("msgindex");
00329         }
00330         const KMMsgDict* dict = KMMsgDict::instance();
00331         KConfig* config = KMKernel::config();
00332         KConfigGroupSaver saver( config, "Folder-" + f->idString() );
00333         if ( config->readBoolEntry( folderIndexDisabledKey, true ) ) {
00334             for ( int i = 0; i < f->count(); ++i ) {
00335                 mPendingMsgs.push_back( dict->getMsgSerNum( f, i ) );
00336             }
00337         }
00338         return;
00339     }
00340     if ( !mAddedMsgs.empty() ) {
00341         std::swap( mAddedMsgs, mPendingMsgs );
00342         mState = s_processing;
00343         return;
00344     }
00345     for ( std::set<KMFolder*>::const_iterator first = mOpenedFolders.begin(), past = mOpenedFolders.end();
00346             first != past;
00347             ++first ) {
00348         ( *first )->close("msgindex");
00349     }
00350     mOpenedFolders.clear();
00351     mState = s_idle;
00352     mTimer->stop();
00353 }
00354 
00355 void KMMsgIndex::continueCreation() {
00356     kdDebug( 5006 ) << "KMMsgIndex::continueCreation()" << endl;
00357 #ifdef HAVE_INDEXLIB
00358     create();
00359     unsigned count = mIndex->ndocs();
00360     mExisting.clear();
00361     mExisting.reserve( count );
00362     for ( unsigned i = 0; i != count; ++i ) {
00363         mExisting.push_back( std::atoi( mIndex->lookup_docname( i ).c_str() ) );
00364     }
00365     std::sort( mExisting.begin(), mExisting.end() );
00366 #endif
00367 }
00368 
00369 void KMMsgIndex::create() {
00370     kdDebug( 5006 ) << "KMMsgIndex::create()" << endl;
00371 
00372 #ifdef HAVE_INDEXLIB
00373     if ( !QFileInfo( mIndexPath ).exists() ) {
00374         ::mkdir( mIndexPath, S_IRWXU );
00375     }
00376     mState = s_creating;
00377     if ( !mIndex ) mIndex = indexlib::create( mIndexPath ).release();
00378     if ( !mIndex ) {
00379         kdDebug( 5006 ) << "Error creating index" << endl;
00380         mState = s_error;
00381         return;
00382     }
00383     QValueStack<KMFolderDir*> folders;
00384     folders.push(&(kmkernel->folderMgr()->dir()));
00385     folders.push(&(kmkernel->dimapFolderMgr()->dir()));
00386     while ( !folders.empty() ) {
00387         KMFolderDir *dir = folders.pop();
00388         for(KMFolderNode *child = dir->first(); child; child = dir->next()) {
00389             if ( child->isDir() )
00390                 folders.push((KMFolderDir*)child);
00391             else
00392                 mPendingFolders.push_back( (KMFolder*)child );
00393         }
00394     }
00395     mTimer->start( 4000 ); // wait a couple of seconds before starting up...
00396     mSlowDown = true;
00397 #endif
00398 }
00399 
00400 bool KMMsgIndex::startQuery( KMSearch* s ) {
00401     kdDebug( 5006 ) << "KMMsgIndex::startQuery( . )" << endl;
00402     if ( mState != s_idle ) return false;
00403     if ( !isIndexed( s->root() ) || !canHandleQuery( s->searchPattern() ) ) return false;
00404 
00405     kdDebug( 5006 ) << "KMMsgIndex::startQuery( . ) starting query" << endl;
00406     Search* search = new Search( s );
00407     connect( search, SIGNAL( finished( bool ) ), s, SIGNAL( finished( bool ) ) );
00408     connect( search, SIGNAL( finished( bool ) ), s, SLOT( indexFinished() ) );
00409     connect( search, SIGNAL( destroyed( QObject* ) ), SLOT( removeSearch( QObject* ) ) );
00410     connect( search, SIGNAL( found( Q_UINT32 ) ), s, SIGNAL( found( Q_UINT32 ) ) );
00411     mSearches.push_back( search );
00412     return true;
00413 }
00414 
00415 
00416 //void KMMsgIndex::startSync() {
00417 //  switch ( mSyncState ) {
00418 //      case ss_none:
00419 //          mIndex->start_sync();
00420 //          mSyncState = ss_started;
00421 //          mSyncTimer.start( 4000, true );
00422 //          break;
00423 //      case ss_started:
00424 //          mIndex->sync_now();
00425 //          mSyncState = ss_synced;
00426 //          mLockFile.unlock();
00427 //          break;
00428 //  }
00429 //}
00430 //
00431 //void KMMsgIndex::finishSync() {
00432 //
00433 //}
00434 
00435 void KMMsgIndex::removeSearch( QObject* destroyed ) {
00436     mSearches.erase( std::find( mSearches.begin(), mSearches.end(), destroyed ) );
00437 }
00438 
00439 
00440 bool KMMsgIndex::stopQuery( KMSearch* s ) {
00441     kdDebug( 5006 ) << "KMMsgIndex::stopQuery( . )" << endl;
00442     for ( std::vector<Search*>::iterator iter = mSearches.begin(), past = mSearches.end(); iter != past; ++iter ) {
00443         if ( ( *iter )->search() == s ) {
00444             delete *iter;
00445             mSearches.erase( iter );
00446             return true;
00447         }
00448     }
00449     return false;
00450 }
00451 
00452 std::vector<Q_UINT32> KMMsgIndex::simpleSearch( QString s, bool* ok ) const {
00453     kdDebug( 5006 ) << "KMMsgIndex::simpleSearch( -" << s.latin1() << "- )" << endl;
00454     if ( mState == s_error || mState == s_disabled ) {
00455         if ( ok ) *ok = false;
00456         return std::vector<Q_UINT32>();
00457     }
00458     std::vector<Q_UINT32> res;
00459 #ifdef HAVE_INDEXLIB
00460     assert( mIndex );
00461     std::vector<unsigned> residx = mIndex->search( s.latin1() )->list();
00462     res.reserve( residx.size() );
00463     for ( std::vector<unsigned>::const_iterator first = residx.begin(), past = residx.end();first != past; ++first ) {
00464         res.push_back( std::atoi( mIndex->lookup_docname( *first ).c_str() ) );
00465     }
00466     if ( ok ) *ok = true;
00467 #endif
00468     return res;
00469 }
00470 
00471 bool KMMsgIndex::canHandleQuery( const KMSearchPattern* pat ) const {
00472     kdDebug( 5006 ) << "KMMsgIndex::canHandleQuery( . )" << endl;
00473     if ( !pat ) return false;
00474     QPtrListIterator<KMSearchRule> it( *pat );
00475     KMSearchRule* rule;
00476     while ( (rule = it.current()) != 0 ) {
00477         ++it;
00478         if ( !rule->field().isEmpty() && !rule->contents().isEmpty() &&
00479                 rule->function() == KMSearchRule::FuncContains &&
00480                 rule->field() == "<body>" )  return true;
00481     }
00482     return false;
00483 }
00484 
00485 void KMMsgIndex::slotAddMessage( Q_UINT32 serNum ) {
00486     kdDebug( 5006 ) << "KMMsgIndex::slotAddMessage( . , " << serNum << " )" << endl;
00487     if ( mState == s_error || mState == s_disabled ) return;
00488 
00489     if ( mState == s_creating ) mAddedMsgs.push_back( serNum );
00490     else mPendingMsgs.push_back( serNum );
00491 
00492     if ( mState == s_idle ) mState = s_processing;
00493     scheduleAction();
00494 }
00495 
00496 void KMMsgIndex::slotRemoveMessage( Q_UINT32 serNum ) {
00497     kdDebug( 5006 ) << "KMMsgIndex::slotRemoveMessage( . , " << serNum << " )" << endl;
00498     if ( mState == s_error || mState == s_disabled ) return;
00499 
00500     if ( mState == s_idle ) mState = s_processing;
00501     mRemovedMsgs.push_back( serNum );
00502     scheduleAction();
00503 }
00504 
00505 void KMMsgIndex::scheduleAction() {
00506 #ifdef HAVE_INDEXLIB
00507     if ( mState == s_willcreate || !mIndex ) return;
00508     if ( !mSlowDown ) mTimer->start( 0 );
00509 #endif
00510 }
00511 
00512 void KMMsgIndex::removeMessage( Q_UINT32 serNum ) {
00513     kdDebug( 5006 ) << "KMMsgIndex::removeMessage( " << serNum << " )" << endl;
00514     if ( mState == s_error || mState == s_disabled ) return;
00515 
00516 #ifdef HAVE_INDEXLIB
00517     mIndex->remove_doc( QString::number( serNum ).latin1() );
00518     ++mMaintenanceCount;
00519     if ( mMaintenanceCount > MaintenanceLimit && mRemovedMsgs.empty() ) {
00520         QTimer::singleShot( 100, this, SLOT( maintenance() ) );
00521     }
00522 #endif
00523 }
00524 
00525 QString KMMsgIndex::defaultPath() {
00526     return KMKernel::localDataPath() + "text-index";
00527 }
00528 
00529 bool KMMsgIndex::creating() const {
00530     return !mPendingMsgs.empty() || !mPendingFolders.empty();
00531 }
00532 
00533 KMMsgIndex::Search::Search( KMSearch* s ):
00534     mSearch( s ),
00535     mTimer( new QTimer( this, "mTimer" ) ),
00536     mResidual( new KMSearchPattern ),
00537     mState( s_starting ) {
00538     connect( mTimer, SIGNAL( timeout() ), SLOT( act() ) );
00539     mTimer->start( 0 );
00540 }
00541 
00542 KMMsgIndex::Search::~Search() {
00543     delete mTimer;
00544 }
00545 
00546 void KMMsgIndex::Search::act() {
00547     switch ( mState ) {
00548         case s_starting: {
00549             KMSearchPattern* pat = mSearch->searchPattern();
00550             QString terms;
00551             for ( KMSearchRule* rule = pat->first(); rule; rule = pat->next() ) {
00552                 Q_ASSERT( rule->function() == KMSearchRule::FuncContains );
00553                 terms += QString::fromLatin1( " %1 " ).arg( rule->contents() );
00554             }
00555 
00556             mValues = kmkernel->msgIndex()->simpleSearch( terms, 0  );
00557             break;
00558          }
00559         case s_emitstopped:
00560             mTimer->start( 0 );
00561             mState = s_emitting;
00562             // fall throu
00563         case s_emitting:
00564             if ( kapp->hasPendingEvents() ) {
00565                 //nah, some other time..
00566                 mTimer->start( 250 );
00567                 mState = s_emitstopped;
00568                 return;
00569             }
00570             for ( int i = 0; i != 16 && !mValues.empty(); ++i ) {
00571                 KMFolder* folder;
00572                 int index;
00573                 KMMsgDict::instance()->getLocation( mValues.back(), &folder, &index );
00574                 if ( folder &&
00575                     mSearch->inScope( folder ) &&
00576                     ( !mResidual || mResidual->matches( mValues.back() ) ) ) {
00577 
00578                     emit found( mValues.back() );
00579                 }
00580                 mValues.pop_back();
00581             }
00582             if ( mValues.empty() ) {
00583                 emit finished( true );
00584                 mState = s_done;
00585                 mTimer->stop();
00586                 delete this;
00587             }
00588             break;
00589         default:
00590         Q_ASSERT( 0 );
00591     }
00592 }
00593 #include "index.moc"
00594