00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
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
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
00090
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
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
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
00180 return;
00181 case s_error:
00182
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;
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
00209 break;
00210 case s_error:
00211 case s_disabled:
00212
00213 break;
00214 }
00215
00216 } else {
00217 switch ( mState ) {
00218 case s_willcreate:
00219
00220 break;
00221 case s_creating:
00222 if ( std::find( mPendingFolders.begin(), mPendingFolders.end(), folder ) != mPendingFolders.end() ) {
00223
00224 mPendingFolders.erase( std::find( mPendingFolders.begin(), mPendingFolders.end(), folder ) );
00225 break;
00226 }
00227
00228 case s_idle:
00229 case s_processing:
00230
00231 case s_error:
00232 case s_disabled:
00233
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
00292
00293
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
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 );
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
00417
00418
00419
00420
00421
00422
00423
00424
00425
00426
00427
00428
00429
00430
00431
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
00563 case s_emitting:
00564 if ( kapp->hasPendingEvents() ) {
00565
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