diff options
Diffstat (limited to 'kmail/kmsearchpattern.cpp')
-rw-r--r-- | kmail/kmsearchpattern.cpp | 929 |
1 files changed, 929 insertions, 0 deletions
diff --git a/kmail/kmsearchpattern.cpp b/kmail/kmsearchpattern.cpp new file mode 100644 index 000000000..1b9e36530 --- /dev/null +++ b/kmail/kmsearchpattern.cpp @@ -0,0 +1,929 @@ +// -*- mode: C++; c-file-style: "gnu" -*- +// kmsearchpattern.cpp +// Author: Marc Mutz <Marc@Mutz.com> +// This code is under GPL! + +#include <config.h> + +#include "kmaddrbook.h" +#include "kmsearchpattern.h" +#include "kmmsgdict.h" +#include "filterlog.h" +#include "kmkernel.h" +#include "kmmsgdict.h" +#include "kmfolder.h" +using KMail::FilterLog; + +#include <libemailfunctions/email.h> + +#include <kglobal.h> +#include <klocale.h> +#include <kdebug.h> +#include <kconfig.h> + +#include <kabc/stdaddressbook.h> + +#include <qregexp.h> + +#include <mimelib/string.h> +#include <mimelib/boyermor.h> + +#include <assert.h> + +static const char* funcConfigNames[] = + { "contains", "contains-not", "equals", "not-equal", "regexp", + "not-regexp", "greater", "less-or-equal", "less", "greater-or-equal", + "is-in-addressbook", "is-not-in-addressbook" , "is-in-category", "is-not-in-category", + "has-attachment", "has-no-attachment"}; +static const int numFuncConfigNames = sizeof funcConfigNames / sizeof *funcConfigNames; + +struct _statusNames { + const char* name; + KMMsgStatus status; +}; + +static struct _statusNames statusNames[] = { + { "Important", KMMsgStatusFlag }, + { "New", KMMsgStatusNew }, + { "Unread", KMMsgStatusUnread | KMMsgStatusNew }, + { "Read", KMMsgStatusRead }, + { "Old", KMMsgStatusOld }, + { "Deleted", KMMsgStatusDeleted }, + { "Replied", KMMsgStatusReplied }, + { "Forwarded", KMMsgStatusForwarded }, + { "Queued", KMMsgStatusQueued }, + { "Sent", KMMsgStatusSent }, + { "Watched", KMMsgStatusWatched }, + { "Ignored", KMMsgStatusIgnored }, + { "To Do", KMMsgStatusTodo }, + { "Spam", KMMsgStatusSpam }, + { "Ham", KMMsgStatusHam }, + { "Has Attachment", KMMsgStatusHasAttach } +}; + +static const int numStatusNames = sizeof statusNames / sizeof ( struct _statusNames ); + + +//================================================== +// +// class KMSearchRule (was: KMFilterRule) +// +//================================================== + +KMSearchRule::KMSearchRule( const QCString & field, Function func, const QString & contents ) + : mField( field ), + mFunction( func ), + mContents( contents ) +{ +} + +KMSearchRule::KMSearchRule( const KMSearchRule & other ) + : mField( other.mField ), + mFunction( other.mFunction ), + mContents( other.mContents ) +{ +} + +const KMSearchRule & KMSearchRule::operator=( const KMSearchRule & other ) { + if ( this == &other ) + return *this; + + mField = other.mField; + mFunction = other.mFunction; + mContents = other.mContents; + + return *this; +} + +KMSearchRule * KMSearchRule::createInstance( const QCString & field, + Function func, + const QString & contents ) +{ + KMSearchRule *ret = 0; + if (field == "<status>") + ret = new KMSearchRuleStatus( field, func, contents ); + else if ( field == "<age in days>" || field == "<size>" ) + ret = new KMSearchRuleNumerical( field, func, contents ); + else + ret = new KMSearchRuleString( field, func, contents ); + + return ret; +} + +KMSearchRule * KMSearchRule::createInstance( const QCString & field, + const char *func, + const QString & contents ) +{ + return ( createInstance( field, configValueToFunc( func ), contents ) ); +} + +KMSearchRule * KMSearchRule::createInstance( const KMSearchRule & other ) +{ + return ( createInstance( other.field(), other.function(), other.contents() ) ); +} + +KMSearchRule * KMSearchRule::createInstanceFromConfig( const KConfig * config, int aIdx ) +{ + const char cIdx = char( int('A') + aIdx ); + + static const QString & field = KGlobal::staticQString( "field" ); + static const QString & func = KGlobal::staticQString( "func" ); + static const QString & contents = KGlobal::staticQString( "contents" ); + + const QCString &field2 = config->readEntry( field + cIdx ).latin1(); + Function func2 = configValueToFunc( config->readEntry( func + cIdx ).latin1() ); + const QString & contents2 = config->readEntry( contents + cIdx ); + + if ( field2 == "<To or Cc>" ) // backwards compat + return KMSearchRule::createInstance( "<recipients>", func2, contents2 ); + else + return KMSearchRule::createInstance( field2, func2, contents2 ); +} + +KMSearchRule::Function KMSearchRule::configValueToFunc( const char * str ) { + if ( !str ) + return FuncNone; + + for ( int i = 0 ; i < numFuncConfigNames ; ++i ) + if ( qstricmp( funcConfigNames[i], str ) == 0 ) return (Function)i; + + return FuncNone; +} + +QString KMSearchRule::functionToString( Function function ) +{ + if ( function != FuncNone ) + return funcConfigNames[int( function )]; + else + return "invalid"; +} + +void KMSearchRule::writeConfig( KConfig * config, int aIdx ) const { + const char cIdx = char('A' + aIdx); + static const QString & field = KGlobal::staticQString( "field" ); + static const QString & func = KGlobal::staticQString( "func" ); + static const QString & contents = KGlobal::staticQString( "contents" ); + + config->writeEntry( field + cIdx, QString(mField) ); + config->writeEntry( func + cIdx, functionToString( mFunction ) ); + config->writeEntry( contents + cIdx, mContents ); +} + +bool KMSearchRule::matches( const DwString & aStr, KMMessage & msg, + const DwBoyerMoore *, int ) const +{ + if ( !msg.isComplete() ) { + msg.fromDwString( aStr ); + msg.setComplete( true ); + } + return matches( &msg ); +} + +const QString KMSearchRule::asString() const +{ + QString result = "\"" + mField + "\" <"; + result += functionToString( mFunction ); + result += "> \"" + mContents + "\""; + + return result; +} + +//================================================== +// +// class KMSearchRuleString +// +//================================================== + +KMSearchRuleString::KMSearchRuleString( const QCString & field, + Function func, const QString & contents ) + : KMSearchRule(field, func, contents) +{ + if ( field.isEmpty() || field[0] == '<' ) + mBmHeaderField = 0; + else // make sure you handle the unrealistic case of the message starting with mField + mBmHeaderField = new DwBoyerMoore(("\n" + field + ": ").data()); +} + +KMSearchRuleString::KMSearchRuleString( const KMSearchRuleString & other ) + : KMSearchRule( other ), + mBmHeaderField( 0 ) +{ + if ( other.mBmHeaderField ) + mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField ); +} + +const KMSearchRuleString & KMSearchRuleString::operator=( const KMSearchRuleString & other ) +{ + if ( this == &other ) + return *this; + + setField( other.field() ); + mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField ); + setFunction( other.function() ); + setContents( other.contents() ); + delete mBmHeaderField; mBmHeaderField = 0; + if ( other.mBmHeaderField ) + mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField ); + + return *this; +} + +KMSearchRuleString::~KMSearchRuleString() +{ + delete mBmHeaderField; + mBmHeaderField = 0; +} + +bool KMSearchRuleString::isEmpty() const +{ + return field().stripWhiteSpace().isEmpty() || contents().isEmpty(); +} + +bool KMSearchRuleString::requiresBody() const +{ + if (mBmHeaderField || (field() == "<recipients>" )) + return false; + return true; +} + +bool KMSearchRuleString::matches( const DwString & aStr, KMMessage & msg, + const DwBoyerMoore * aHeaderField, int aHeaderLen ) const +{ + if ( isEmpty() ) + return false; + + bool rc = false; + + const DwBoyerMoore * headerField = aHeaderField ? aHeaderField : mBmHeaderField ; + + const int headerLen = ( aHeaderLen > -1 ? aHeaderLen : field().length() ) + 2 ; // +1 for ': ' + + if ( headerField ) { + static const DwBoyerMoore lflf( "\n\n" ); + static const DwBoyerMoore lfcrlf( "\n\r\n" ); + + size_t endOfHeader = lflf.FindIn( aStr, 0 ); + if ( endOfHeader == DwString::npos ) + endOfHeader = lfcrlf.FindIn( aStr, 0 ); + const DwString headers = ( endOfHeader == DwString::npos ) ? aStr : aStr.substr( 0, endOfHeader ); + // In case the searched header is at the beginning, we have to prepend + // a newline - see the comment in KMSearchRuleString constructor + DwString fakedHeaders( "\n" ); + size_t start = headerField->FindIn( fakedHeaders.append( headers ), 0, false ); + // if the header field doesn't exist then return false for positive + // functions and true for negated functions (e.g. "does not + // contain"); note that all negated string functions correspond + // to an odd value + if ( start == DwString::npos ) + rc = ( ( function() & 1 ) == 1 ); + else { + start += headerLen; + size_t stop = aStr.find( '\n', start ); + char ch = '\0'; + while ( stop != DwString::npos && ( ch = aStr.at( stop + 1 ) ) == ' ' || ch == '\t' ) + stop = aStr.find( '\n', stop + 1 ); + const int len = stop == DwString::npos ? aStr.length() - start : stop - start ; + const QCString codedValue( aStr.data() + start, len + 1 ); + const QString msgContents = KMMsgBase::decodeRFC2047String( codedValue ).stripWhiteSpace(); // FIXME: This needs to be changed for IDN support. + rc = matchesInternal( msgContents ); + } + } else if ( field() == "<recipients>" ) { + static const DwBoyerMoore to("\nTo: "); + static const DwBoyerMoore cc("\nCc: "); + static const DwBoyerMoore bcc("\nBcc: "); + // <recipients> "contains" "foo" is true if any of the fields contains + // "foo", while <recipients> "does not contain" "foo" is true if none + // of the fields contains "foo" + if ( ( function() & 1 ) == 0 ) { + // positive function, e.g. "contains" + rc = ( matches( aStr, msg, &to, 2 ) || + matches( aStr, msg, &cc, 2 ) || + matches( aStr, msg, &bcc, 3 ) ); + } + else { + // negated function, e.g. "does not contain" + rc = ( matches( aStr, msg, &to, 2 ) && + matches( aStr, msg, &cc, 2 ) && + matches( aStr, msg, &bcc, 3 ) ); + } + } + if ( FilterLog::instance()->isLogging() ) { + QString msg = ( rc ? "<font color=#00FF00>1 = </font>" + : "<font color=#FF0000>0 = </font>" ); + msg += FilterLog::recode( asString() ); + // only log headers bcause messages and bodies can be pretty large +// FIXME We have to separate the text which is used for filtering to be able to show it in the log +// if ( logContents ) +// msg += " (<i>" + FilterLog::recode( msgContents ) + "</i>)"; + FilterLog::instance()->add( msg, FilterLog::ruleResult ); + } + return rc; +} + +bool KMSearchRuleString::matches( const KMMessage * msg ) const +{ + assert( msg ); + + if ( isEmpty() ) + return false; + + QString msgContents; + // Show the value used to compare the rules against in the log. + // Overwrite the value for complete messages and all headers! + bool logContents = true; + + if( field() == "<message>" ) { + msgContents = msg->asString(); + logContents = false; + } else if ( field() == "<body>" ) { + msgContents = msg->bodyToUnicode(); + logContents = false; + } else if ( field() == "<any header>" ) { + msgContents = msg->headerAsString(); + logContents = false; + } else if ( field() == "<recipients>" ) { + // (mmutz 2001-11-05) hack to fix "<recipients> !contains foo" to + // meet user's expectations. See FAQ entry in KDE 2.2.2's KMail + // handbook + if ( function() == FuncEquals || function() == FuncNotEqual ) + // do we need to treat this case specially? Ie.: What shall + // "equality" mean for recipients. + return matchesInternal( msg->headerField("To") ) + || matchesInternal( msg->headerField("Cc") ) + || matchesInternal( msg->headerField("Bcc") ) + // sometimes messages have multiple Cc headers + || matchesInternal( msg->cc() ); + + msgContents = msg->headerField("To"); + if ( !msg->headerField("Cc").compare( msg->cc() ) ) + msgContents += ", " + msg->headerField("Cc"); + else + msgContents += ", " + msg->cc(); + msgContents += ", " + msg->headerField("Bcc"); + } else { + // make sure to treat messages with multiple header lines for + // the same header correctly + msgContents = msg->headerFields( field() ).join( " " ); + } + + if ( function() == FuncIsInAddressbook || + function() == FuncIsNotInAddressbook ) { + // I think only the "from"-field makes sense. + msgContents = msg->headerField( field() ); + if ( msgContents.isEmpty() ) + return ( function() == FuncIsInAddressbook ) ? false : true; + } + + // these two functions need the kmmessage therefore they don't call matchesInternal + if ( function() == FuncHasAttachment ) + return ( msg->toMsgBase().attachmentState() == KMMsgHasAttachment ); + if ( function() == FuncHasNoAttachment ) + return ( ((KMMsgAttachmentState) msg->toMsgBase().attachmentState()) == KMMsgHasNoAttachment ); + + bool rc = matchesInternal( msgContents ); + if ( FilterLog::instance()->isLogging() ) { + QString msg = ( rc ? "<font color=#00FF00>1 = </font>" + : "<font color=#FF0000>0 = </font>" ); + msg += FilterLog::recode( asString() ); + // only log headers bcause messages and bodies can be pretty large + if ( logContents ) + msg += " (<i>" + FilterLog::recode( msgContents ) + "</i>)"; + FilterLog::instance()->add( msg, FilterLog::ruleResult ); + } + return rc; +} + +// helper, does the actual comparing +bool KMSearchRuleString::matchesInternal( const QString & msgContents ) const +{ + switch ( function() ) { + case KMSearchRule::FuncEquals: + return ( QString::compare( msgContents.lower(), contents().lower() ) == 0 ); + + case KMSearchRule::FuncNotEqual: + return ( QString::compare( msgContents.lower(), contents().lower() ) != 0 ); + + case KMSearchRule::FuncContains: + return ( msgContents.find( contents(), 0, false ) >= 0 ); + + case KMSearchRule::FuncContainsNot: + return ( msgContents.find( contents(), 0, false ) < 0 ); + + case KMSearchRule::FuncRegExp: + { + QRegExp regexp( contents(), false ); + return ( regexp.search( msgContents ) >= 0 ); + } + + case KMSearchRule::FuncNotRegExp: + { + QRegExp regexp( contents(), false ); + return ( regexp.search( msgContents ) < 0 ); + } + + case FuncIsGreater: + return ( QString::compare( msgContents.lower(), contents().lower() ) > 0 ); + + case FuncIsLessOrEqual: + return ( QString::compare( msgContents.lower(), contents().lower() ) <= 0 ); + + case FuncIsLess: + return ( QString::compare( msgContents.lower(), contents().lower() ) < 0 ); + + case FuncIsGreaterOrEqual: + return ( QString::compare( msgContents.lower(), contents().lower() ) >= 0 ); + + case FuncIsInAddressbook: { + KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true ); + QStringList addressList = + KPIM::splitEmailAddrList( msgContents.lower() ); + for( QStringList::ConstIterator it = addressList.begin(); + ( it != addressList.end() ); + ++it ) { + if ( !stdAb->findByEmail( KPIM::getEmailAddress( *it ) ).isEmpty() ) + return true; + } + return false; + } + + case FuncIsNotInAddressbook: { + KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true ); + QStringList addressList = + KPIM::splitEmailAddrList( msgContents.lower() ); + for( QStringList::ConstIterator it = addressList.begin(); + ( it != addressList.end() ); + ++it ) { + if ( stdAb->findByEmail( KPIM::getEmailAddress( *it ) ).isEmpty() ) + return true; + } + return false; + } + + case FuncIsInCategory: { + QString category = contents(); + QStringList addressList = KPIM::splitEmailAddrList( msgContents.lower() ); + KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true ); + + for( QStringList::ConstIterator it = addressList.begin(); + it != addressList.end(); ++it ) { + KABC::Addressee::List addresses = stdAb->findByEmail( KPIM::getEmailAddress( *it ) ); + + for ( KABC::Addressee::List::Iterator itAd = addresses.begin(); itAd != addresses.end(); ++itAd ) + if ( (*itAd).hasCategory(category) ) + return true; + + } + return false; + } + + case FuncIsNotInCategory: { + QString category = contents(); + QStringList addressList = KPIM::splitEmailAddrList( msgContents.lower() ); + KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true ); + + for( QStringList::ConstIterator it = addressList.begin(); + it != addressList.end(); ++it ) { + KABC::Addressee::List addresses = stdAb->findByEmail( KPIM::getEmailAddress( *it ) ); + + for ( KABC::Addressee::List::Iterator itAd = addresses.begin(); itAd != addresses.end(); ++itAd ) + if ( (*itAd).hasCategory(category) ) + return false; + + } + return true; + } + default: + ; + } + + return false; +} + + +//================================================== +// +// class KMSearchRuleNumerical +// +//================================================== + +KMSearchRuleNumerical::KMSearchRuleNumerical( const QCString & field, + Function func, const QString & contents ) + : KMSearchRule(field, func, contents) +{ +} + +bool KMSearchRuleNumerical::isEmpty() const +{ + bool ok = false; + contents().toInt( &ok ); + + return !ok; +} + + +bool KMSearchRuleNumerical::matches( const KMMessage * msg ) const +{ + + QString msgContents; + int numericalMsgContents = 0; + int numericalValue = 0; + + if ( field() == "<size>" ) { + numericalMsgContents = int( msg->msgLength() ); + numericalValue = contents().toInt(); + msgContents.setNum( numericalMsgContents ); + } else if ( field() == "<age in days>" ) { + QDateTime msgDateTime; + msgDateTime.setTime_t( msg->date() ); + numericalMsgContents = msgDateTime.daysTo( QDateTime::currentDateTime() ); + numericalValue = contents().toInt(); + msgContents.setNum( numericalMsgContents ); + } + bool rc = matchesInternal( numericalValue, numericalMsgContents, msgContents ); + if ( FilterLog::instance()->isLogging() ) { + QString msg = ( rc ? "<font color=#00FF00>1 = </font>" + : "<font color=#FF0000>0 = </font>" ); + msg += FilterLog::recode( asString() ); + msg += " ( <i>" + QString::number( numericalMsgContents ) + "</i> )"; + FilterLog::instance()->add( msg, FilterLog::ruleResult ); + } + return rc; +} + +bool KMSearchRuleNumerical::matchesInternal( long numericalValue, + long numericalMsgContents, const QString & msgContents ) const +{ + switch ( function() ) { + case KMSearchRule::FuncEquals: + return ( numericalValue == numericalMsgContents ); + + case KMSearchRule::FuncNotEqual: + return ( numericalValue != numericalMsgContents ); + + case KMSearchRule::FuncContains: + return ( msgContents.find( contents(), 0, false ) >= 0 ); + + case KMSearchRule::FuncContainsNot: + return ( msgContents.find( contents(), 0, false ) < 0 ); + + case KMSearchRule::FuncRegExp: + { + QRegExp regexp( contents(), false ); + return ( regexp.search( msgContents ) >= 0 ); + } + + case KMSearchRule::FuncNotRegExp: + { + QRegExp regexp( contents(), false ); + return ( regexp.search( msgContents ) < 0 ); + } + + case FuncIsGreater: + return ( numericalMsgContents > numericalValue ); + + case FuncIsLessOrEqual: + return ( numericalMsgContents <= numericalValue ); + + case FuncIsLess: + return ( numericalMsgContents < numericalValue ); + + case FuncIsGreaterOrEqual: + return ( numericalMsgContents >= numericalValue ); + + case FuncIsInAddressbook: // since email-addresses are not numerical, I settle for false here + return false; + + case FuncIsNotInAddressbook: + return false; + + default: + ; + } + + return false; +} + + + +//================================================== +// +// class KMSearchRuleStatus +// +//================================================== +QString englishNameForStatus( const KMMsgStatus& status ) +{ + for ( int i=0; i< numStatusNames; i++ ) { + if ( statusNames[i].status == status ) { + return statusNames[i].name; + } + } + return QString::null; +} + +KMSearchRuleStatus::KMSearchRuleStatus( const QCString & field, + Function func, const QString & aContents ) + : KMSearchRule(field, func, aContents) +{ + // the values are always in english, both from the conf file as well as + // the patternedit gui + mStatus = statusFromEnglishName( aContents ); +} + +KMSearchRuleStatus::KMSearchRuleStatus( int status, Function func ) +: KMSearchRule( "<status>", func, englishNameForStatus( status ) ) +{ + mStatus = status; +} + +KMMsgStatus KMSearchRuleStatus::statusFromEnglishName( const QString & aStatusString ) +{ + for ( int i=0; i< numStatusNames; i++ ) { + if ( !aStatusString.compare( statusNames[i].name ) ) { + return statusNames[i].status; + } + } + return KMMsgStatusUnknown; +} + +bool KMSearchRuleStatus::isEmpty() const +{ + return field().stripWhiteSpace().isEmpty() || contents().isEmpty(); +} + +bool KMSearchRuleStatus::matches( const DwString &, KMMessage &, + const DwBoyerMoore *, int ) const +{ + assert( 0 ); + return false; // don't warn +} + +bool KMSearchRuleStatus::matches( const KMMessage * msg ) const +{ + + KMMsgStatus msgStatus = msg->status(); + bool rc = false; + + switch ( function() ) { + case FuncEquals: // fallthrough. So that "<status> 'is' 'read'" works + case FuncContains: + if (msgStatus & mStatus) + rc = true; + break; + case FuncNotEqual: // fallthrough. So that "<status> 'is not' 'read'" works + case FuncContainsNot: + if (! (msgStatus & mStatus) ) + rc = true; + break; + // FIXME what about the remaining funcs, how can they make sense for + // stati? + default: + break; + } + + if ( FilterLog::instance()->isLogging() ) { + QString msg = ( rc ? "<font color=#00FF00>1 = </font>" + : "<font color=#FF0000>0 = </font>" ); + msg += FilterLog::recode( asString() ); + FilterLog::instance()->add( msg, FilterLog::ruleResult ); + } + return rc; +} + +// ---------------------------------------------------------------------------- + +//================================================== +// +// class KMSearchPattern +// +//================================================== + +KMSearchPattern::KMSearchPattern( const KConfig * config ) + : QPtrList<KMSearchRule>() +{ + setAutoDelete( true ); + if ( config ) + readConfig( config ); + else + init(); +} + +KMSearchPattern::~KMSearchPattern() +{ +} + +bool KMSearchPattern::matches( const KMMessage * msg, bool ignoreBody ) const +{ + if ( isEmpty() ) + return true; + + QPtrListIterator<KMSearchRule> it( *this ); + switch ( mOperator ) { + case OpAnd: // all rules must match + for ( it.toFirst() ; it.current() ; ++it ) + if ( !((*it)->requiresBody() && ignoreBody) ) + if ( !(*it)->matches( msg ) ) + return false; + return true; + case OpOr: // at least one rule must match + for ( it.toFirst() ; it.current() ; ++it ) + if ( !((*it)->requiresBody() && ignoreBody) ) + if ( (*it)->matches( msg ) ) + return true; + // fall through + default: + return false; + } +} + +bool KMSearchPattern::matches( const DwString & aStr, bool ignoreBody ) const +{ + if ( isEmpty() ) + return true; + + KMMessage msg; + QPtrListIterator<KMSearchRule> it( *this ); + switch ( mOperator ) { + case OpAnd: // all rules must match + for ( it.toFirst() ; it.current() ; ++it ) + if ( !((*it)->requiresBody() && ignoreBody) ) + if ( !(*it)->matches( aStr, msg ) ) + return false; + return true; + case OpOr: // at least one rule must match + for ( it.toFirst() ; it.current() ; ++it ) + if ( !((*it)->requiresBody() && ignoreBody) ) + if ( (*it)->matches( aStr, msg ) ) + return true; + // fall through + default: + return false; + } +} + +bool KMSearchPattern::matches( Q_UINT32 serNum, bool ignoreBody ) const +{ + if ( isEmpty() ) + return true; + + bool res; + int idx = -1; + KMFolder *folder = 0; + KMMsgDict::instance()->getLocation(serNum, &folder, &idx); + if (!folder || (idx == -1) || (idx >= folder->count())) { + return false; + } + + KMFolderOpener openFolder(folder, "searchptr"); + KMMsgBase *msgBase = folder->getMsgBase(idx); + if (requiresBody() && !ignoreBody) { + bool unGet = !msgBase->isMessage(); + KMMessage *msg = folder->getMsg(idx); + res = false; + if ( msg ) { + res = matches( msg, ignoreBody ); + if (unGet) + folder->unGetMsg(idx); + } + } else { + res = matches( folder->getDwString(idx), ignoreBody ); + } + return res; +} + +bool KMSearchPattern::requiresBody() const { + QPtrListIterator<KMSearchRule> it( *this ); + for ( it.toFirst() ; it.current() ; ++it ) + if ( (*it)->requiresBody() ) + return true; + return false; +} + +void KMSearchPattern::purify() { + QPtrListIterator<KMSearchRule> it( *this ); + it.toLast(); + while ( it.current() ) + if ( (*it)->isEmpty() ) { +#ifndef NDEBUG + kdDebug(5006) << "KMSearchPattern::purify(): removing " << (*it)->asString() << endl; +#endif + remove( *it ); + } else { + --it; + } +} + +void KMSearchPattern::readConfig( const KConfig * config ) { + init(); + + mName = config->readEntry("name"); + if ( !config->hasKey("rules") ) { + kdDebug(5006) << "KMSearchPattern::readConfig: found legacy config! Converting." << endl; + importLegacyConfig( config ); + return; + } + + mOperator = config->readEntry("operator") == "or" ? OpOr : OpAnd; + + const int nRules = config->readNumEntry( "rules", 0 ); + + for ( int i = 0 ; i < nRules ; i++ ) { + KMSearchRule * r = KMSearchRule::createInstanceFromConfig( config, i ); + if ( r->isEmpty() ) + delete r; + else + append( r ); + } +} + +void KMSearchPattern::importLegacyConfig( const KConfig * config ) { + KMSearchRule * rule = KMSearchRule::createInstance( config->readEntry("fieldA").latin1(), + config->readEntry("funcA").latin1(), + config->readEntry("contentsA") ); + if ( rule->isEmpty() ) { + // if the first rule is invalid, + // we really can't do much heuristics... + delete rule; + return; + } + append( rule ); + + const QString sOperator = config->readEntry("operator"); + if ( sOperator == "ignore" ) return; + + rule = KMSearchRule::createInstance( config->readEntry("fieldB").latin1(), + config->readEntry("funcB").latin1(), + config->readEntry("contentsB") ); + if ( rule->isEmpty() ) { + delete rule; + return; + } + append( rule ); + + if ( sOperator == "or" ) { + mOperator = OpOr; + return; + } + // This is the interesting case... + if ( sOperator == "unless" ) { // meaning "and not", ie we need to... + // ...invert the function (e.g. "equals" <-> "doesn't equal") + // We simply toggle the last bit (xor with 0x1)... This assumes that + // KMSearchRule::Function's come in adjacent pairs of pros and cons + KMSearchRule::Function func = last()->function(); + unsigned int intFunc = (unsigned int)func; + func = KMSearchRule::Function( intFunc ^ 0x1 ); + + last()->setFunction( func ); + } + + // treat any other case as "and" (our default). +} + +void KMSearchPattern::writeConfig( KConfig * config ) const { + config->writeEntry("name", mName); + config->writeEntry("operator", (mOperator == KMSearchPattern::OpOr) ? "or" : "and" ); + + int i = 0; + for ( QPtrListIterator<KMSearchRule> it( *this ) ; it.current() && i < FILTER_MAX_RULES ; ++i , ++it ) + // we could do this ourselves, but we want the rules to be extensible, + // so we give the rule it's number and let it do the rest. + (*it)->writeConfig( config, i ); + + // save the total number of rules. + config->writeEntry( "rules", i ); +} + +void KMSearchPattern::init() { + clear(); + mOperator = OpAnd; + mName = '<' + i18n("name used for a virgin filter","unknown") + '>'; +} + +QString KMSearchPattern::asString() const { + QString result; + if ( mOperator == OpOr ) + result = i18n("(match any of the following)"); + else + result = i18n("(match all of the following)"); + + for ( QPtrListIterator<KMSearchRule> it( *this ) ; it.current() ; ++it ) + result += "\n\t" + FilterLog::recode( (*it)->asString() ); + + return result; +} + +const KMSearchPattern & KMSearchPattern::operator=( const KMSearchPattern & other ) { + if ( this == &other ) + return *this; + + setOp( other.op() ); + setName( other.name() ); + + clear(); // ### + + for ( QPtrListIterator<KMSearchRule> it( other ) ; it.current() ; ++it ) { + KMSearchRule * rule = KMSearchRule::createInstance( **it ); // deep copy + append( rule ); + } + + return *this; +} |