diff options
Diffstat (limited to 'kmail/index.cpp')
-rw-r--r-- | kmail/index.cpp | 594 |
1 files changed, 594 insertions, 0 deletions
diff --git a/kmail/index.cpp b/kmail/index.cpp new file mode 100644 index 000000000..5b156a999 --- /dev/null +++ b/kmail/index.cpp @@ -0,0 +1,594 @@ +/* This file is part of KMail + * Copyright (C) 2005 Luís Pedro Coelho <luis@luispedro.org> + * + * KMail is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * KMail 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of this program with any edition of + * the Qt library by Trolltech AS, Norway (or with modified versions + * of Qt that use the same license as Qt), and distribute linked + * combinations including the two. You must obey the GNU General + * Public License in all respects for all of the code used other than + * Qt. If you modify this file, you may extend this exception to + * your version of the file, but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from + * your version. + */ + +#include "index.h" + +#include "kmkernel.h" +#include "kmfoldermgr.h" +#include "kmmsgdict.h" +#include "kmfolder.h" +#include "kmsearchpattern.h" +#include "kmfoldersearch.h" + +#include <kdebug.h> +#include <kapplication.h> +#include <qfile.h> +#include <qtimer.h> +#include <qvaluestack.h> +#include <qptrlist.h> +#include <qfileinfo.h> +#ifdef HAVE_INDEXLIB +#include <indexlib/create.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> + +#include <iostream> +#include <algorithm> +#include <cstdlib> + +namespace { +const unsigned int MaintenanceLimit = 1000; +const char* const folderIndexDisabledKey = "fulltextIndexDisabled"; +} + +#ifdef HAVE_INDEXLIB +static +QValueList<int> vectorToQValueList( const std::vector<Q_UINT32>& input ) { + QValueList<int> res; + std::copy( input.begin(), input.end(), std::back_inserter( res ) ); + return res; +} + +static +std::vector<Q_UINT32> QValueListToVector( const QValueList<int>& input ) { + std::vector<Q_UINT32> res; + // res.assign( input.begin(), input.end() ) doesn't work for some reason + for ( QValueList<int>::const_iterator first = input.begin(), past = input.end(); first != past; ++first ) { + res.push_back( *first ); + } + return res; +} +#endif + +KMMsgIndex::KMMsgIndex( QObject* parent ): + QObject( parent, "index" ), + mState( s_idle ), +#ifdef HAVE_INDEXLIB + mLockFile( std::string( static_cast<const char*>( QFile::encodeName( defaultPath() ) + "/lock" ) ) ), + mIndex( 0 ), +#endif + mIndexPath( QFile::encodeName( defaultPath() ) ), + mTimer( new QTimer( this, "mTimer" ) ), + //mSyncState( ss_none ), + //mSyncTimer( new QTimer( this ) ), + mSlowDown( false ) { + kdDebug( 5006 ) << "KMMsgIndex::KMMsgIndex()" << endl; + + connect( kmkernel->folderMgr(), SIGNAL( msgRemoved( KMFolder*, Q_UINT32 ) ), SLOT( slotRemoveMessage( Q_UINT32 ) ) ); + connect( kmkernel->folderMgr(), SIGNAL( msgAdded( KMFolder*, Q_UINT32 ) ), SLOT( slotAddMessage( Q_UINT32 ) ) ); + connect( kmkernel->dimapFolderMgr(), SIGNAL( msgRemoved( KMFolder*, Q_UINT32 ) ), SLOT( slotRemoveMessage( Q_UINT32 ) ) ); + connect( kmkernel->dimapFolderMgr(), SIGNAL( msgAdded( KMFolder*, Q_UINT32 ) ), SLOT( slotAddMessage( Q_UINT32 ) ) ); + + connect( mTimer, SIGNAL( timeout() ), SLOT( act() ) ); + //connect( mSyncTimer, SIGNAL( timeout() ), SLOT( syncIndex() ) ); + +#ifdef HAVE_INDEXLIB + KConfigGroup cfg( KMKernel::config(), "text-index" ); + if ( !cfg.readBoolEntry( "enabled", false ) ) { + indexlib::remove( mIndexPath ); + mLockFile.force_unlock(); + mState = s_disabled; + return; + } + if ( !mLockFile.trylock() ) { + indexlib::remove( mIndexPath ); + + mLockFile.force_unlock(); + mLockFile.trylock(); + } else { + mIndex = indexlib::open( mIndexPath, indexlib::open_flags::fail_if_nonexistant ).release(); + } + if ( !mIndex ) { + QTimer::singleShot( 8000, this, SLOT( create() ) ); + mState = s_willcreate; + } else { + if ( cfg.readBoolEntry( "creating" ) ) { + QTimer::singleShot( 8000, this, SLOT( continueCreation() ) ); + mState = s_creating; + } else { + mPendingMsgs = QValueListToVector( cfg.readIntListEntry( "pending" ) ); + mRemovedMsgs = QValueListToVector( cfg.readIntListEntry( "removed" ) ); + } + } + mIndex = 0; +#else + mState = s_error; +#endif + //if ( mState == s_idle ) mSyncState = ss_synced; +} + + +KMMsgIndex::~KMMsgIndex() { + kdDebug( 5006 ) << "KMMsgIndex::~KMMsgIndex()" << endl; +#ifdef HAVE_INDEXLIB + KConfigGroup cfg( KMKernel::config(), "text-index" ); + cfg.writeEntry( "creating", mState == s_creating ); + QValueList<int> pendingMsg; + if ( mState == s_processing ) { + Q_ASSERT( mAddedMsgs.empty() ); + pendingMsg = vectorToQValueList( mPendingMsgs ); + } + cfg.writeEntry( "pending", pendingMsg ); + cfg.writeEntry( "removed", vectorToQValueList( mRemovedMsgs ) ); + delete mIndex; +#endif +} + +bool KMMsgIndex::isIndexable( KMFolder* folder ) const { + if ( !folder || !folder->parent() ) return false; + const KMFolderMgr* manager = folder->parent()->manager(); + return manager == kmkernel->folderMgr() || manager == kmkernel->dimapFolderMgr(); +} + +bool KMMsgIndex::isIndexed( KMFolder* folder ) const { + if ( !isIndexable( folder ) ) return false; + KConfig* config = KMKernel::config(); + KConfigGroupSaver saver( config, "Folder-" + folder->idString() ); + return !config->readBoolEntry( folderIndexDisabledKey, false ); +} + +void KMMsgIndex::setEnabled( bool e ) { + kdDebug( 5006 ) << "KMMsgIndex::setEnabled( " << e << " )" << endl; + KConfig* config = KMKernel::config(); + KConfigGroupSaver saver( config, "text-index" ); + if ( config->readBoolEntry( "enabled", !e ) == e ) return; + config->writeEntry( "enabled", e ); + if ( e ) { + switch ( mState ) { + case s_idle: + case s_willcreate: + case s_creating: + case s_processing: + // nothing to do + return; + case s_error: + // nothing can be done, probably + return; + case s_disabled: + QTimer::singleShot( 8000, this, SLOT( create() ) ); + mState = s_willcreate; + } + } else { + clear(); + } +} + +void KMMsgIndex::setIndexingEnabled( KMFolder* folder, bool e ) { + KConfig* config = KMKernel::config(); + KConfigGroupSaver saver( config, "Folder-" + folder->idString() ); + if ( config->readBoolEntry( folderIndexDisabledKey, e ) == e ) return; // nothing to do + config->writeEntry( folderIndexDisabledKey, e ); + + if ( e ) { + switch ( mState ) { + case s_idle: + case s_creating: + case s_processing: + mPendingFolders.push_back( folder ); + scheduleAction(); + break; + case s_willcreate: + // do nothing, create() will handle this + break; + case s_error: + case s_disabled: + // nothing can be done + break; + } + + } else { + switch ( mState ) { + case s_willcreate: + // create() will notice that folder is disabled + break; + case s_creating: + if ( std::find( mPendingFolders.begin(), mPendingFolders.end(), folder ) != mPendingFolders.end() ) { + // easy: + mPendingFolders.erase( std::find( mPendingFolders.begin(), mPendingFolders.end(), folder ) ); + break; + } + //else fall-through + case s_idle: + case s_processing: + + case s_error: + case s_disabled: + // nothing can be done + break; + } + } +} + +void KMMsgIndex::clear() { + kdDebug( 5006 ) << "KMMsgIndex::clear()" << endl; +#ifdef HAVE_INDEXLIB + delete mIndex; + mLockFile.force_unlock(); + mIndex = 0; + indexlib::remove( mIndexPath ); + mPendingMsgs.clear(); + mPendingFolders.clear(); + mMaintenanceCount = 0; + mAddedMsgs.clear(); + mRemovedMsgs.clear(); + mExisting.clear(); + mState = s_disabled; + for ( std::set<KMFolder*>::const_iterator first = mOpenedFolders.begin(), past = mOpenedFolders.end(); first != past; ++first ) { + ( *first )->close("msgindex"); + } + mOpenedFolders.clear(); + for ( std::vector<Search*>::const_iterator first = mSearches.begin(), past = mSearches.end(); first != past; ++first ) { + delete *first; + } + mSearches.clear(); + mTimer->stop(); +#endif +} + +void KMMsgIndex::maintenance() { +#ifdef HAVE_INDEXLIB + if ( mState != s_idle || kapp->hasPendingEvents() ) { + QTimer::singleShot( 8000, this, SLOT( maintenance() ) ); + return; + } + mIndex->maintenance(); +#endif +} + +int KMMsgIndex::addMessage( Q_UINT32 serNum ) { + kdDebug( 5006 ) << "KMMsgIndex::addMessage( " << serNum << " )" << endl; + if ( mState == s_error ) return 0; +#ifdef HAVE_INDEXLIB + assert( mIndex ); + if ( !mExisting.empty() && std::binary_search( mExisting.begin(), mExisting.end(), serNum ) ) return 0; + + int idx = -1; + KMFolder* folder = 0; + KMMsgDict::instance()->getLocation( serNum, &folder, &idx ); + if ( !folder || idx == -1 ) return -1; + if ( !mOpenedFolders.count( folder ) ) { + mOpenedFolders.insert( folder ); + folder->open("msgindex"); + } + KMMessage* msg = folder->getMsg( idx ); + /* I still don't know whether we should allow decryption or not. + * Setting to false which makes more sense. + * We keep signature to get the person's name + */ + QString body = msg->asPlainText( false, false ); + if ( !body.isEmpty() && static_cast<const char*>( body.latin1() ) ) { + mIndex->add( body.latin1(), QString::number( serNum ).latin1() ); + } else { + kdDebug( 5006 ) << "Funny, no body" << endl; + } + folder->unGetMsg( idx ); +#endif + return 0; +} + +void KMMsgIndex::act() { + kdDebug( 5006 ) << "KMMsgIndex::act()" << endl; + if ( kapp->hasPendingEvents() ) { + //nah, some other time.. + mTimer->start( 500 ); + mSlowDown = true; + return; + } + if ( mSlowDown ) { + mSlowDown = false; + mTimer->start( 0 ); + } + if ( !mPendingMsgs.empty() ) { + addMessage( mPendingMsgs.back() ); + mPendingMsgs.pop_back(); + return; + } + if ( !mPendingFolders.empty() ) { + KMFolder *f = mPendingFolders.back(); + mPendingFolders.pop_back(); + if ( !mOpenedFolders.count( f ) ) { + mOpenedFolders.insert( f ); + f->open("msgindex"); + } + const KMMsgDict* dict = KMMsgDict::instance(); + KConfig* config = KMKernel::config(); + KConfigGroupSaver saver( config, "Folder-" + f->idString() ); + if ( config->readBoolEntry( folderIndexDisabledKey, true ) ) { + for ( int i = 0; i < f->count(); ++i ) { + mPendingMsgs.push_back( dict->getMsgSerNum( f, i ) ); + } + } + return; + } + if ( !mAddedMsgs.empty() ) { + std::swap( mAddedMsgs, mPendingMsgs ); + mState = s_processing; + return; + } + for ( std::set<KMFolder*>::const_iterator first = mOpenedFolders.begin(), past = mOpenedFolders.end(); + first != past; + ++first ) { + ( *first )->close("msgindex"); + } + mOpenedFolders.clear(); + mState = s_idle; + mTimer->stop(); +} + +void KMMsgIndex::continueCreation() { + kdDebug( 5006 ) << "KMMsgIndex::continueCreation()" << endl; +#ifdef HAVE_INDEXLIB + create(); + unsigned count = mIndex->ndocs(); + mExisting.clear(); + mExisting.reserve( count ); + for ( unsigned i = 0; i != count; ++i ) { + mExisting.push_back( std::atoi( mIndex->lookup_docname( i ).c_str() ) ); + } + std::sort( mExisting.begin(), mExisting.end() ); +#endif +} + +void KMMsgIndex::create() { + kdDebug( 5006 ) << "KMMsgIndex::create()" << endl; + +#ifdef HAVE_INDEXLIB + if ( !QFileInfo( mIndexPath ).exists() ) { + ::mkdir( mIndexPath, S_IRWXU ); + } + mState = s_creating; + if ( !mIndex ) mIndex = indexlib::create( mIndexPath ).release(); + if ( !mIndex ) { + kdDebug( 5006 ) << "Error creating index" << endl; + mState = s_error; + return; + } + QValueStack<KMFolderDir*> folders; + folders.push(&(kmkernel->folderMgr()->dir())); + folders.push(&(kmkernel->dimapFolderMgr()->dir())); + while ( !folders.empty() ) { + KMFolderDir *dir = folders.pop(); + for(KMFolderNode *child = dir->first(); child; child = dir->next()) { + if ( child->isDir() ) + folders.push((KMFolderDir*)child); + else + mPendingFolders.push_back( (KMFolder*)child ); + } + } + mTimer->start( 4000 ); // wait a couple of seconds before starting up... + mSlowDown = true; +#endif +} + +bool KMMsgIndex::startQuery( KMSearch* s ) { + kdDebug( 5006 ) << "KMMsgIndex::startQuery( . )" << endl; + if ( mState != s_idle ) return false; + if ( !isIndexed( s->root() ) || !canHandleQuery( s->searchPattern() ) ) return false; + + kdDebug( 5006 ) << "KMMsgIndex::startQuery( . ) starting query" << endl; + Search* search = new Search( s ); + connect( search, SIGNAL( finished( bool ) ), s, SIGNAL( finished( bool ) ) ); + connect( search, SIGNAL( finished( bool ) ), s, SLOT( indexFinished() ) ); + connect( search, SIGNAL( destroyed( QObject* ) ), SLOT( removeSearch( QObject* ) ) ); + connect( search, SIGNAL( found( Q_UINT32 ) ), s, SIGNAL( found( Q_UINT32 ) ) ); + mSearches.push_back( search ); + return true; +} + + +//void KMMsgIndex::startSync() { +// switch ( mSyncState ) { +// case ss_none: +// mIndex->start_sync(); +// mSyncState = ss_started; +// mSyncTimer.start( 4000, true ); +// break; +// case ss_started: +// mIndex->sync_now(); +// mSyncState = ss_synced; +// mLockFile.unlock(); +// break; +// } +//} +// +//void KMMsgIndex::finishSync() { +// +//} + +void KMMsgIndex::removeSearch( QObject* destroyed ) { + mSearches.erase( std::find( mSearches.begin(), mSearches.end(), destroyed ) ); +} + + +bool KMMsgIndex::stopQuery( KMSearch* s ) { + kdDebug( 5006 ) << "KMMsgIndex::stopQuery( . )" << endl; + for ( std::vector<Search*>::iterator iter = mSearches.begin(), past = mSearches.end(); iter != past; ++iter ) { + if ( ( *iter )->search() == s ) { + delete *iter; + mSearches.erase( iter ); + return true; + } + } + return false; +} + +std::vector<Q_UINT32> KMMsgIndex::simpleSearch( QString s, bool* ok ) const { + kdDebug( 5006 ) << "KMMsgIndex::simpleSearch( -" << s.latin1() << "- )" << endl; + if ( mState == s_error || mState == s_disabled ) { + if ( ok ) *ok = false; + return std::vector<Q_UINT32>(); + } + std::vector<Q_UINT32> res; +#ifdef HAVE_INDEXLIB + assert( mIndex ); + std::vector<unsigned> residx = mIndex->search( s.latin1() )->list(); + res.reserve( residx.size() ); + for ( std::vector<unsigned>::const_iterator first = residx.begin(), past = residx.end();first != past; ++first ) { + res.push_back( std::atoi( mIndex->lookup_docname( *first ).c_str() ) ); + } + if ( ok ) *ok = true; +#endif + return res; +} + +bool KMMsgIndex::canHandleQuery( const KMSearchPattern* pat ) const { + kdDebug( 5006 ) << "KMMsgIndex::canHandleQuery( . )" << endl; + if ( !pat ) return false; + QPtrListIterator<KMSearchRule> it( *pat ); + KMSearchRule* rule; + while ( (rule = it.current()) != 0 ) { + ++it; + if ( !rule->field().isEmpty() && !rule->contents().isEmpty() && + rule->function() == KMSearchRule::FuncContains && + rule->field() == "<body>" ) return true; + } + return false; +} + +void KMMsgIndex::slotAddMessage( Q_UINT32 serNum ) { + kdDebug( 5006 ) << "KMMsgIndex::slotAddMessage( . , " << serNum << " )" << endl; + if ( mState == s_error || mState == s_disabled ) return; + + if ( mState == s_creating ) mAddedMsgs.push_back( serNum ); + else mPendingMsgs.push_back( serNum ); + + if ( mState == s_idle ) mState = s_processing; + scheduleAction(); +} + +void KMMsgIndex::slotRemoveMessage( Q_UINT32 serNum ) { + kdDebug( 5006 ) << "KMMsgIndex::slotRemoveMessage( . , " << serNum << " )" << endl; + if ( mState == s_error || mState == s_disabled ) return; + + if ( mState == s_idle ) mState = s_processing; + mRemovedMsgs.push_back( serNum ); + scheduleAction(); +} + +void KMMsgIndex::scheduleAction() { +#ifdef HAVE_INDEXLIB + if ( mState == s_willcreate || !mIndex ) return; + if ( !mSlowDown ) mTimer->start( 0 ); +#endif +} + +void KMMsgIndex::removeMessage( Q_UINT32 serNum ) { + kdDebug( 5006 ) << "KMMsgIndex::removeMessage( " << serNum << " )" << endl; + if ( mState == s_error || mState == s_disabled ) return; + +#ifdef HAVE_INDEXLIB + mIndex->remove_doc( QString::number( serNum ).latin1() ); + ++mMaintenanceCount; + if ( mMaintenanceCount > MaintenanceLimit && mRemovedMsgs.empty() ) { + QTimer::singleShot( 100, this, SLOT( maintenance() ) ); + } +#endif +} + +QString KMMsgIndex::defaultPath() { + return KMKernel::localDataPath() + "text-index"; +} + +bool KMMsgIndex::creating() const { + return !mPendingMsgs.empty() || !mPendingFolders.empty(); +} + +KMMsgIndex::Search::Search( KMSearch* s ): + mSearch( s ), + mTimer( new QTimer( this, "mTimer" ) ), + mResidual( new KMSearchPattern ), + mState( s_starting ) { + connect( mTimer, SIGNAL( timeout() ), SLOT( act() ) ); + mTimer->start( 0 ); +} + +KMMsgIndex::Search::~Search() { + delete mTimer; +} + +void KMMsgIndex::Search::act() { + switch ( mState ) { + case s_starting: { + KMSearchPattern* pat = mSearch->searchPattern(); + QString terms; + for ( KMSearchRule* rule = pat->first(); rule; rule = pat->next() ) { + Q_ASSERT( rule->function() == KMSearchRule::FuncContains ); + terms += QString::fromLatin1( " %1 " ).arg( rule->contents() ); + } + + mValues = kmkernel->msgIndex()->simpleSearch( terms, 0 ); + break; + } + case s_emitstopped: + mTimer->start( 0 ); + mState = s_emitting; + // fall throu + case s_emitting: + if ( kapp->hasPendingEvents() ) { + //nah, some other time.. + mTimer->start( 250 ); + mState = s_emitstopped; + return; + } + for ( int i = 0; i != 16 && !mValues.empty(); ++i ) { + KMFolder* folder; + int index; + KMMsgDict::instance()->getLocation( mValues.back(), &folder, &index ); + if ( folder && + mSearch->inScope( folder ) && + ( !mResidual || mResidual->matches( mValues.back() ) ) ) { + + emit found( mValues.back() ); + } + mValues.pop_back(); + } + if ( mValues.empty() ) { + emit finished( true ); + mState = s_done; + mTimer->stop(); + delete this; + } + break; + default: + Q_ASSERT( 0 ); + } +} +#include "index.moc" + |