kmail

kmsearchpattern.cpp

Go to the documentation of this file.
00001 // -*- mode: C++; c-file-style: "gnu" -*-
00002 // kmsearchpattern.cpp
00003 // Author: Marc Mutz <Marc@Mutz.com>
00004 // This code is under GPL!
00005 
00006 #include <config.h>
00007 
00008 #include "kmaddrbook.h"
00009 #include "kmsearchpattern.h"
00010 #include "kmmsgdict.h"
00011 #include "filterlog.h"
00012 #include "kmkernel.h"
00013 #include "kmmsgdict.h"
00014 #include "kmfolder.h"
00015 using KMail::FilterLog;
00016 
00017 #include <libemailfunctions/email.h>
00018 
00019 #include <kglobal.h>
00020 #include <klocale.h>
00021 #include <kdebug.h>
00022 #include <kconfig.h>
00023 
00024 #include <kabc/stdaddressbook.h>
00025 
00026 #include <qregexp.h>
00027 
00028 #include <mimelib/string.h>
00029 #include <mimelib/boyermor.h>
00030 
00031 #include <assert.h>
00032 
00033 static const char* funcConfigNames[] =
00034   { "contains", "contains-not", "equals", "not-equal", "regexp",
00035     "not-regexp", "greater", "less-or-equal", "less", "greater-or-equal",
00036     "is-in-addressbook", "is-not-in-addressbook" , "is-in-category", "is-not-in-category",
00037     "has-attachment", "has-no-attachment"};
00038 static const int numFuncConfigNames = sizeof funcConfigNames / sizeof *funcConfigNames;
00039 
00040 struct _statusNames {
00041   const char* name;
00042   KMMsgStatus status;
00043 };
00044 
00045 static struct _statusNames statusNames[] = {
00046   { "Important", KMMsgStatusFlag },
00047   { "New", KMMsgStatusNew },
00048   { "Unread", KMMsgStatusUnread | KMMsgStatusNew },
00049   { "Read", KMMsgStatusRead },
00050   { "Old", KMMsgStatusOld },
00051   { "Deleted", KMMsgStatusDeleted },
00052   { "Replied", KMMsgStatusReplied },
00053   { "Forwarded", KMMsgStatusForwarded },
00054   { "Queued", KMMsgStatusQueued },
00055   { "Sent", KMMsgStatusSent },
00056   { "Watched", KMMsgStatusWatched },
00057   { "Ignored", KMMsgStatusIgnored },
00058   { "To Do", KMMsgStatusTodo },
00059   { "Spam", KMMsgStatusSpam },
00060   { "Ham", KMMsgStatusHam },
00061   { "Has Attachment", KMMsgStatusHasAttach }
00062 };
00063 
00064 static const int numStatusNames = sizeof statusNames / sizeof ( struct _statusNames );
00065 
00066 
00067 //==================================================
00068 //
00069 // class KMSearchRule (was: KMFilterRule)
00070 //
00071 //==================================================
00072 
00073 KMSearchRule::KMSearchRule( const QCString & field, Function func, const QString & contents )
00074   : mField( field ),
00075     mFunction( func ),
00076     mContents( contents )
00077 {
00078 }
00079 
00080 KMSearchRule::KMSearchRule( const KMSearchRule & other )
00081   : mField( other.mField ),
00082     mFunction( other.mFunction ),
00083     mContents( other.mContents )
00084 {
00085 }
00086 
00087 const KMSearchRule & KMSearchRule::operator=( const KMSearchRule & other ) {
00088   if ( this == &other )
00089     return *this;
00090 
00091   mField = other.mField;
00092   mFunction = other.mFunction;
00093   mContents = other.mContents;
00094 
00095   return *this;
00096 }
00097 
00098 KMSearchRule * KMSearchRule::createInstance( const QCString & field,
00099                                              Function func,
00100                                              const QString & contents )
00101 {
00102   KMSearchRule *ret = 0;
00103   if (field == "<status>")
00104     ret = new KMSearchRuleStatus( field, func, contents );
00105   else if ( field == "<age in days>" || field == "<size>" )
00106     ret = new KMSearchRuleNumerical( field, func, contents );
00107   else
00108     ret = new KMSearchRuleString( field, func, contents );
00109 
00110   return ret;
00111 }
00112 
00113 KMSearchRule * KMSearchRule::createInstance( const QCString & field,
00114                                      const char *func,
00115                                      const QString & contents )
00116 {
00117   return ( createInstance( field, configValueToFunc( func ), contents ) );
00118 }
00119 
00120 KMSearchRule * KMSearchRule::createInstance( const KMSearchRule & other )
00121 {
00122   return ( createInstance( other.field(), other.function(), other.contents() ) );
00123 }
00124 
00125 KMSearchRule * KMSearchRule::createInstanceFromConfig( const KConfig * config, int aIdx )
00126 {
00127   const char cIdx = char( int('A') + aIdx );
00128 
00129   static const QString & field = KGlobal::staticQString( "field" );
00130   static const QString & func = KGlobal::staticQString( "func" );
00131   static const QString & contents = KGlobal::staticQString( "contents" );
00132 
00133   const QCString &field2 = config->readEntry( field + cIdx ).latin1();
00134   Function func2 = configValueToFunc( config->readEntry( func + cIdx ).latin1() );
00135   const QString & contents2 = config->readEntry( contents + cIdx );
00136 
00137   if ( field2 == "<To or Cc>" ) // backwards compat
00138     return KMSearchRule::createInstance( "<recipients>", func2, contents2 );
00139   else
00140     return KMSearchRule::createInstance( field2, func2, contents2 );
00141 }
00142 
00143 KMSearchRule::Function KMSearchRule::configValueToFunc( const char * str ) {
00144   if ( !str )
00145     return FuncNone;
00146 
00147   for ( int i = 0 ; i < numFuncConfigNames ; ++i )
00148     if ( qstricmp( funcConfigNames[i], str ) == 0 ) return (Function)i;
00149 
00150   return FuncNone;
00151 }
00152 
00153 QString KMSearchRule::functionToString( Function function )
00154 {
00155   if ( function != FuncNone )
00156     return funcConfigNames[int( function )];
00157   else
00158     return "invalid";
00159 }
00160 
00161 void KMSearchRule::writeConfig( KConfig * config, int aIdx ) const {
00162   const char cIdx = char('A' + aIdx);
00163   static const QString & field = KGlobal::staticQString( "field" );
00164   static const QString & func = KGlobal::staticQString( "func" );
00165   static const QString & contents = KGlobal::staticQString( "contents" );
00166 
00167   config->writeEntry( field + cIdx, QString(mField) );
00168   config->writeEntry( func + cIdx, functionToString( mFunction ) );
00169   config->writeEntry( contents + cIdx, mContents );
00170 }
00171 
00172 bool KMSearchRule::matches( const DwString & aStr, KMMessage & msg,
00173                        const DwBoyerMoore *, int ) const
00174 {
00175   if ( !msg.isComplete() ) {
00176     msg.fromDwString( aStr );
00177     msg.setComplete( true );
00178   }
00179   return matches( &msg );
00180 }
00181 
00182 const QString KMSearchRule::asString() const
00183 {
00184   QString result  = "\"" + mField + "\" <";
00185   result += functionToString( mFunction );
00186   result += "> \"" + mContents + "\"";
00187 
00188   return result;
00189 }
00190 
00191 //==================================================
00192 //
00193 // class KMSearchRuleString
00194 //
00195 //==================================================
00196 
00197 KMSearchRuleString::KMSearchRuleString( const QCString & field,
00198                                         Function func, const QString & contents )
00199           : KMSearchRule(field, func, contents)
00200 {
00201   if ( field.isEmpty() || field[0] == '<' )
00202     mBmHeaderField = 0;
00203   else // make sure you handle the unrealistic case of the message starting with mField
00204     mBmHeaderField = new DwBoyerMoore(("\n" + field + ": ").data());
00205 }
00206 
00207 KMSearchRuleString::KMSearchRuleString( const KMSearchRuleString & other )
00208   : KMSearchRule( other ),
00209     mBmHeaderField( 0 )
00210 {
00211   if ( other.mBmHeaderField )
00212     mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
00213 }
00214 
00215 const KMSearchRuleString & KMSearchRuleString::operator=( const KMSearchRuleString & other )
00216 {
00217   if ( this == &other )
00218     return *this;
00219 
00220   setField( other.field() );
00221   mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
00222   setFunction( other.function() );
00223   setContents( other.contents() );
00224   delete mBmHeaderField; mBmHeaderField = 0;
00225   if ( other.mBmHeaderField )
00226     mBmHeaderField = new DwBoyerMoore( *other.mBmHeaderField );
00227 
00228   return *this;
00229 }
00230 
00231 KMSearchRuleString::~KMSearchRuleString()
00232 {
00233   delete mBmHeaderField;
00234   mBmHeaderField = 0;
00235 }
00236 
00237 bool KMSearchRuleString::isEmpty() const
00238 {
00239   return field().stripWhiteSpace().isEmpty() || contents().isEmpty();
00240 }
00241 
00242 bool KMSearchRuleString::requiresBody() const
00243 {
00244   if (mBmHeaderField || (field() == "<recipients>" ))
00245     return false;
00246   return true;
00247 }
00248 
00249 bool KMSearchRuleString::matches( const DwString & aStr, KMMessage & msg,
00250                        const DwBoyerMoore * aHeaderField, int aHeaderLen ) const
00251 {
00252   if ( isEmpty() )
00253     return false;
00254 
00255   bool rc = false;
00256 
00257   const DwBoyerMoore * headerField = aHeaderField ? aHeaderField : mBmHeaderField ;
00258 
00259   const int headerLen = ( aHeaderLen > -1 ? aHeaderLen : field().length() ) + 2 ; // +1 for ': '
00260 
00261   if ( headerField ) {
00262     static const DwBoyerMoore lflf( "\n\n" );
00263     static const DwBoyerMoore lfcrlf( "\n\r\n" );
00264 
00265     size_t endOfHeader = lflf.FindIn( aStr, 0 );
00266     if ( endOfHeader == DwString::npos )
00267       endOfHeader = lfcrlf.FindIn( aStr, 0 );
00268     const DwString headers = ( endOfHeader == DwString::npos ) ? aStr : aStr.substr( 0, endOfHeader );
00269     // In case the searched header is at the beginning, we have to prepend
00270     // a newline - see the comment in KMSearchRuleString constructor
00271     DwString fakedHeaders( "\n" );
00272     size_t start = headerField->FindIn( fakedHeaders.append( headers ), 0, false );
00273     // if the header field doesn't exist then return false for positive
00274     // functions and true for negated functions (e.g. "does not
00275     // contain"); note that all negated string functions correspond
00276     // to an odd value
00277     if ( start == DwString::npos )
00278       rc = ( ( function() & 1 ) == 1 );
00279     else {
00280       start += headerLen;
00281       size_t stop = aStr.find( '\n', start );
00282       char ch = '\0';
00283       while ( stop != DwString::npos && ( ch = aStr.at( stop + 1 ) ) == ' ' || ch == '\t' )
00284         stop = aStr.find( '\n', stop + 1 );
00285       const int len = stop == DwString::npos ? aStr.length() - start : stop - start ;
00286       const QCString codedValue( aStr.data() + start, len + 1 );
00287       const QString msgContents = KMMsgBase::decodeRFC2047String( codedValue ).stripWhiteSpace(); // FIXME: This needs to be changed for IDN support.
00288       rc = matchesInternal( msgContents );
00289     }
00290   } else if ( field() == "<recipients>" ) {
00291     static const DwBoyerMoore to("\nTo: ");
00292     static const DwBoyerMoore cc("\nCc: ");
00293     static const DwBoyerMoore bcc("\nBcc: ");
00294     // <recipients> "contains" "foo" is true if any of the fields contains
00295     // "foo", while <recipients> "does not contain" "foo" is true if none
00296     // of the fields contains "foo"
00297     if ( ( function() & 1 ) == 0 ) {
00298       // positive function, e.g. "contains"
00299       rc = ( matches( aStr, msg, &to, 2 ) ||
00300              matches( aStr, msg, &cc, 2 ) ||
00301              matches( aStr, msg, &bcc, 3 ) );
00302     }
00303     else {
00304       // negated function, e.g. "does not contain"
00305       rc = ( matches( aStr, msg, &to, 2 ) &&
00306              matches( aStr, msg, &cc, 2 ) &&
00307              matches( aStr, msg, &bcc, 3 ) );
00308     }
00309   }
00310   if ( FilterLog::instance()->isLogging() ) {
00311     QString msg = ( rc ? "<font color=#00FF00>1 = </font>"
00312                        : "<font color=#FF0000>0 = </font>" );
00313     msg += FilterLog::recode( asString() );
00314     // only log headers bcause messages and bodies can be pretty large
00315 // FIXME We have to separate the text which is used for filtering to be able to show it in the log
00316 //    if ( logContents )
00317 //      msg += " (<i>" + FilterLog::recode( msgContents ) + "</i>)";
00318     FilterLog::instance()->add( msg, FilterLog::ruleResult );
00319   }
00320   return rc;
00321 }
00322 
00323 bool KMSearchRuleString::matches( const KMMessage * msg ) const
00324 {
00325   assert( msg );
00326 
00327   if ( isEmpty() )
00328     return false;
00329 
00330   QString msgContents;
00331   // Show the value used to compare the rules against in the log.
00332   // Overwrite the value for complete messages and all headers!
00333   bool logContents = true;
00334 
00335   if( field() == "<message>" ) {
00336     msgContents = msg->asString();
00337     logContents = false;
00338   } else if ( field() == "<body>" ) {
00339     msgContents = msg->bodyToUnicode();
00340     logContents = false;
00341   } else if ( field() == "<any header>" ) {
00342     msgContents = msg->headerAsString();
00343     logContents = false;
00344   } else if ( field() == "<recipients>" ) {
00345     // (mmutz 2001-11-05) hack to fix "<recipients> !contains foo" to
00346     // meet user's expectations. See FAQ entry in KDE 2.2.2's KMail
00347     // handbook
00348     if ( function() == FuncEquals || function() == FuncNotEqual )
00349       // do we need to treat this case specially? Ie.: What shall
00350       // "equality" mean for recipients.
00351       return matchesInternal( msg->headerField("To") )
00352           || matchesInternal( msg->headerField("Cc") )
00353           || matchesInternal( msg->headerField("Bcc") )
00354           // sometimes messages have multiple Cc headers
00355           || matchesInternal( msg->cc() );
00356 
00357     msgContents = msg->headerField("To");
00358     if ( !msg->headerField("Cc").compare( msg->cc() ) )
00359       msgContents += ", " + msg->headerField("Cc");
00360     else
00361       msgContents += ", " + msg->cc();
00362     msgContents += ", " + msg->headerField("Bcc");
00363   }  else {
00364     // make sure to treat messages with multiple header lines for
00365     // the same header correctly
00366     msgContents = msg->headerFields( field() ).join( " " );
00367   }
00368 
00369   if ( function() == FuncIsInAddressbook ||
00370        function() == FuncIsNotInAddressbook ) {
00371     // I think only the "from"-field makes sense.
00372     msgContents = msg->headerField( field() );
00373     if ( msgContents.isEmpty() )
00374       return ( function() == FuncIsInAddressbook ) ? false : true;
00375   }
00376 
00377   // these two functions need the kmmessage therefore they don't call matchesInternal
00378   if ( function() == FuncHasAttachment )
00379     return ( msg->toMsgBase().attachmentState() == KMMsgHasAttachment );
00380   if ( function() == FuncHasNoAttachment )
00381     return ( ((KMMsgAttachmentState) msg->toMsgBase().attachmentState()) == KMMsgHasNoAttachment );
00382 
00383   bool rc = matchesInternal( msgContents );
00384   if ( FilterLog::instance()->isLogging() ) {
00385     QString msg = ( rc ? "<font color=#00FF00>1 = </font>"
00386                        : "<font color=#FF0000>0 = </font>" );
00387     msg += FilterLog::recode( asString() );
00388     // only log headers bcause messages and bodies can be pretty large
00389     if ( logContents )
00390       msg += " (<i>" + FilterLog::recode( msgContents ) + "</i>)";
00391     FilterLog::instance()->add( msg, FilterLog::ruleResult );
00392   }
00393   return rc;
00394 }
00395 
00396 // helper, does the actual comparing
00397 bool KMSearchRuleString::matchesInternal( const QString & msgContents ) const
00398 {
00399   switch ( function() ) {
00400   case KMSearchRule::FuncEquals:
00401       return ( QString::compare( msgContents.lower(), contents().lower() ) == 0 );
00402 
00403   case KMSearchRule::FuncNotEqual:
00404       return ( QString::compare( msgContents.lower(), contents().lower() ) != 0 );
00405 
00406   case KMSearchRule::FuncContains:
00407     return ( msgContents.find( contents(), 0, false ) >= 0 );
00408 
00409   case KMSearchRule::FuncContainsNot:
00410     return ( msgContents.find( contents(), 0, false ) < 0 );
00411 
00412   case KMSearchRule::FuncRegExp:
00413     {
00414       QRegExp regexp( contents(), false );
00415       return ( regexp.search( msgContents ) >= 0 );
00416     }
00417 
00418   case KMSearchRule::FuncNotRegExp:
00419     {
00420       QRegExp regexp( contents(), false );
00421       return ( regexp.search( msgContents ) < 0 );
00422     }
00423 
00424   case FuncIsGreater:
00425       return ( QString::compare( msgContents.lower(), contents().lower() ) > 0 );
00426 
00427   case FuncIsLessOrEqual:
00428       return ( QString::compare( msgContents.lower(), contents().lower() ) <= 0 );
00429 
00430   case FuncIsLess:
00431       return ( QString::compare( msgContents.lower(), contents().lower() ) < 0 );
00432 
00433   case FuncIsGreaterOrEqual:
00434       return ( QString::compare( msgContents.lower(), contents().lower() ) >= 0 );
00435 
00436   case FuncIsInAddressbook: {
00437     KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true );
00438     QStringList addressList =
00439       KPIM::splitEmailAddrList( msgContents.lower() );
00440     for( QStringList::ConstIterator it = addressList.begin();
00441          ( it != addressList.end() );
00442          ++it ) {
00443       if ( !stdAb->findByEmail( KPIM::getEmailAddress( *it ) ).isEmpty() )
00444         return true;
00445     }
00446     return false;
00447   }
00448 
00449   case FuncIsNotInAddressbook: {
00450     KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true );
00451     QStringList addressList =
00452       KPIM::splitEmailAddrList( msgContents.lower() );
00453     for( QStringList::ConstIterator it = addressList.begin();
00454          ( it != addressList.end() );
00455          ++it ) {
00456       if ( stdAb->findByEmail( KPIM::getEmailAddress( *it ) ).isEmpty() )
00457         return true;
00458     }
00459     return false;
00460   }
00461 
00462   case FuncIsInCategory: {
00463     QString category = contents();
00464     QStringList addressList =  KPIM::splitEmailAddrList( msgContents.lower() );
00465     KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true );
00466 
00467     for( QStringList::ConstIterator it = addressList.begin();
00468       it != addressList.end(); ++it ) {
00469         KABC::Addressee::List addresses = stdAb->findByEmail( KPIM::getEmailAddress( *it ) );
00470 
00471           for ( KABC::Addressee::List::Iterator itAd = addresses.begin(); itAd != addresses.end(); ++itAd )
00472               if ( (*itAd).hasCategory(category) )
00473                 return true;
00474 
00475       }
00476       return false;
00477     }
00478 
00479     case FuncIsNotInCategory: {
00480       QString category = contents();
00481       QStringList addressList =  KPIM::splitEmailAddrList( msgContents.lower() );
00482       KABC::AddressBook *stdAb = KABC::StdAddressBook::self( true );
00483 
00484       for( QStringList::ConstIterator it = addressList.begin();
00485         it != addressList.end(); ++it ) {
00486           KABC::Addressee::List addresses = stdAb->findByEmail( KPIM::getEmailAddress( *it ) );
00487 
00488             for ( KABC::Addressee::List::Iterator itAd = addresses.begin(); itAd != addresses.end(); ++itAd )
00489                 if ( (*itAd).hasCategory(category) )
00490                   return false;
00491 
00492       }
00493       return true;
00494     }
00495   default:
00496     ;
00497   }
00498 
00499   return false;
00500 }
00501 
00502 
00503 //==================================================
00504 //
00505 // class KMSearchRuleNumerical
00506 //
00507 //==================================================
00508 
00509 KMSearchRuleNumerical::KMSearchRuleNumerical( const QCString & field,
00510                                         Function func, const QString & contents )
00511           : KMSearchRule(field, func, contents)
00512 {
00513 }
00514 
00515 bool KMSearchRuleNumerical::isEmpty() const
00516 {
00517   bool ok = false;
00518   contents().toInt( &ok );
00519 
00520   return !ok;
00521 }
00522 
00523 
00524 bool KMSearchRuleNumerical::matches( const KMMessage * msg ) const
00525 {
00526 
00527   QString msgContents;
00528   int numericalMsgContents = 0;
00529   int numericalValue = 0;
00530 
00531   if ( field() == "<size>" ) {
00532     numericalMsgContents = int( msg->msgLength() );
00533     numericalValue = contents().toInt();
00534     msgContents.setNum( numericalMsgContents );
00535   } else if ( field() == "<age in days>" ) {
00536     QDateTime msgDateTime;
00537     msgDateTime.setTime_t( msg->date() );
00538     numericalMsgContents = msgDateTime.daysTo( QDateTime::currentDateTime() );
00539     numericalValue = contents().toInt();
00540     msgContents.setNum( numericalMsgContents );
00541   }
00542   bool rc = matchesInternal( numericalValue, numericalMsgContents, msgContents );
00543   if ( FilterLog::instance()->isLogging() ) {
00544     QString msg = ( rc ? "<font color=#00FF00>1 = </font>"
00545                        : "<font color=#FF0000>0 = </font>" );
00546     msg += FilterLog::recode( asString() );
00547     msg += " ( <i>" + QString::number( numericalMsgContents ) + "</i> )";
00548     FilterLog::instance()->add( msg, FilterLog::ruleResult );
00549   }
00550   return rc;
00551 }
00552 
00553 bool KMSearchRuleNumerical::matchesInternal( long numericalValue,
00554     long numericalMsgContents, const QString & msgContents ) const
00555 {
00556   switch ( function() ) {
00557   case KMSearchRule::FuncEquals:
00558       return ( numericalValue == numericalMsgContents );
00559 
00560   case KMSearchRule::FuncNotEqual:
00561       return ( numericalValue != numericalMsgContents );
00562 
00563   case KMSearchRule::FuncContains:
00564     return ( msgContents.find( contents(), 0, false ) >= 0 );
00565 
00566   case KMSearchRule::FuncContainsNot:
00567     return ( msgContents.find( contents(), 0, false ) < 0 );
00568 
00569   case KMSearchRule::FuncRegExp:
00570     {
00571       QRegExp regexp( contents(), false );
00572       return ( regexp.search( msgContents ) >= 0 );
00573     }
00574 
00575   case KMSearchRule::FuncNotRegExp:
00576     {
00577       QRegExp regexp( contents(), false );
00578       return ( regexp.search( msgContents ) < 0 );
00579     }
00580 
00581   case FuncIsGreater:
00582       return ( numericalMsgContents > numericalValue );
00583 
00584   case FuncIsLessOrEqual:
00585       return ( numericalMsgContents <= numericalValue );
00586 
00587   case FuncIsLess:
00588       return ( numericalMsgContents < numericalValue );
00589 
00590   case FuncIsGreaterOrEqual:
00591       return ( numericalMsgContents >= numericalValue );
00592 
00593   case FuncIsInAddressbook:  // since email-addresses are not numerical, I settle for false here
00594     return false;
00595 
00596   case FuncIsNotInAddressbook:
00597     return false;
00598 
00599   default:
00600     ;
00601   }
00602 
00603   return false;
00604 }
00605 
00606 
00607 
00608 //==================================================
00609 //
00610 // class KMSearchRuleStatus
00611 //
00612 //==================================================
00613 QString englishNameForStatus( const KMMsgStatus& status )
00614 {
00615   for ( int i=0; i< numStatusNames; i++ ) {
00616     if ( statusNames[i].status == status ) {
00617       return statusNames[i].name;
00618     }
00619   }
00620   return QString::null;
00621 }
00622 
00623 KMSearchRuleStatus::KMSearchRuleStatus( const QCString & field,
00624                                         Function func, const QString & aContents )
00625           : KMSearchRule(field, func, aContents)
00626 {
00627   // the values are always in english, both from the conf file as well as
00628   // the patternedit gui
00629   mStatus = statusFromEnglishName( aContents );
00630 }
00631 
00632 KMSearchRuleStatus::KMSearchRuleStatus( int status, Function func )
00633 : KMSearchRule( "<status>", func, englishNameForStatus( status ) )
00634 {
00635     mStatus = status;
00636 }
00637 
00638 KMMsgStatus KMSearchRuleStatus::statusFromEnglishName( const QString & aStatusString )
00639 {
00640   for ( int i=0; i< numStatusNames; i++ ) {
00641     if ( !aStatusString.compare( statusNames[i].name ) ) {
00642       return statusNames[i].status;
00643     }
00644   }
00645   return KMMsgStatusUnknown;
00646 }
00647 
00648 bool KMSearchRuleStatus::isEmpty() const
00649 {
00650   return field().stripWhiteSpace().isEmpty() || contents().isEmpty();
00651 }
00652 
00653 bool KMSearchRuleStatus::matches( const DwString &, KMMessage &,
00654                   const DwBoyerMoore *, int ) const
00655 {
00656   assert( 0 );
00657   return false; // don't warn
00658 }
00659 
00660 bool KMSearchRuleStatus::matches( const KMMessage * msg ) const
00661 {
00662 
00663   KMMsgStatus msgStatus = msg->status();
00664   bool rc = false;
00665 
00666   switch ( function() ) {
00667     case FuncEquals: // fallthrough. So that "<status> 'is' 'read'" works
00668     case FuncContains:
00669       if (msgStatus & mStatus)
00670         rc = true;
00671       break;
00672     case FuncNotEqual: // fallthrough. So that "<status> 'is not' 'read'" works
00673     case FuncContainsNot:
00674       if (! (msgStatus & mStatus) )
00675         rc = true;
00676       break;
00677     // FIXME what about the remaining funcs, how can they make sense for
00678     // stati?
00679     default:
00680       break;
00681   }
00682 
00683   if ( FilterLog::instance()->isLogging() ) {
00684     QString msg = ( rc ? "<font color=#00FF00>1 = </font>"
00685                        : "<font color=#FF0000>0 = </font>" );
00686     msg += FilterLog::recode( asString() );
00687     FilterLog::instance()->add( msg, FilterLog::ruleResult );
00688   }
00689   return rc;
00690 }
00691 
00692 // ----------------------------------------------------------------------------
00693 
00694 //==================================================
00695 //
00696 // class KMSearchPattern
00697 //
00698 //==================================================
00699 
00700 KMSearchPattern::KMSearchPattern( const KConfig * config )
00701   : QPtrList<KMSearchRule>()
00702 {
00703   setAutoDelete( true );
00704   if ( config )
00705     readConfig( config );
00706   else
00707     init();
00708 }
00709 
00710 KMSearchPattern::~KMSearchPattern()
00711 {
00712 }
00713 
00714 bool KMSearchPattern::matches( const KMMessage * msg, bool ignoreBody ) const
00715 {
00716   if ( isEmpty() )
00717     return true;
00718 
00719   QPtrListIterator<KMSearchRule> it( *this );
00720   switch ( mOperator ) {
00721   case OpAnd: // all rules must match
00722     for ( it.toFirst() ; it.current() ; ++it )
00723       if ( !((*it)->requiresBody() && ignoreBody) )
00724         if ( !(*it)->matches( msg ) )
00725           return false;
00726     return true;
00727   case OpOr:  // at least one rule must match
00728     for ( it.toFirst() ; it.current() ; ++it )
00729       if ( !((*it)->requiresBody() && ignoreBody) )
00730         if ( (*it)->matches( msg ) )
00731           return true;
00732     // fall through
00733   default:
00734     return false;
00735   }
00736 }
00737 
00738 bool KMSearchPattern::matches( const DwString & aStr, bool ignoreBody ) const
00739 {
00740   if ( isEmpty() )
00741     return true;
00742 
00743   KMMessage msg;
00744   QPtrListIterator<KMSearchRule> it( *this );
00745   switch ( mOperator ) {
00746   case OpAnd: // all rules must match
00747     for ( it.toFirst() ; it.current() ; ++it )
00748       if ( !((*it)->requiresBody() && ignoreBody) )
00749         if ( !(*it)->matches( aStr, msg ) )
00750           return false;
00751     return true;
00752   case OpOr:  // at least one rule must match
00753     for ( it.toFirst() ; it.current() ; ++it )
00754       if ( !((*it)->requiresBody() && ignoreBody) )
00755         if ( (*it)->matches( aStr, msg ) )
00756           return true;
00757     // fall through
00758   default:
00759     return false;
00760   }
00761 }
00762 
00763 bool KMSearchPattern::matches( Q_UINT32 serNum, bool ignoreBody ) const
00764 {
00765   if ( isEmpty() )
00766     return true;
00767 
00768   bool res;
00769   int idx = -1;
00770   KMFolder *folder = 0;
00771   KMMsgDict::instance()->getLocation(serNum, &folder, &idx);
00772   if (!folder || (idx == -1) || (idx >= folder->count())) {
00773     return false;
00774   }
00775 
00776   KMFolderOpener openFolder(folder, "searchptr");
00777   KMMsgBase *msgBase = folder->getMsgBase(idx);
00778   if (requiresBody() && !ignoreBody) {
00779     bool unGet = !msgBase->isMessage();
00780     KMMessage *msg = folder->getMsg(idx);
00781     res = false;
00782     if ( msg ) {
00783       res = matches( msg, ignoreBody );
00784       if (unGet)
00785         folder->unGetMsg(idx);
00786     }
00787   } else {
00788     res = matches( folder->getDwString(idx), ignoreBody );
00789   }
00790   return res;
00791 }
00792 
00793 bool KMSearchPattern::requiresBody() const {
00794   QPtrListIterator<KMSearchRule> it( *this );
00795     for ( it.toFirst() ; it.current() ; ++it )
00796       if ( (*it)->requiresBody() )
00797     return true;
00798   return false;
00799 }
00800 
00801 void KMSearchPattern::purify() {
00802   QPtrListIterator<KMSearchRule> it( *this );
00803   it.toLast();
00804   while ( it.current() )
00805     if ( (*it)->isEmpty() ) {
00806 #ifndef NDEBUG
00807       kdDebug(5006) << "KMSearchPattern::purify(): removing " << (*it)->asString() << endl;
00808 #endif
00809       remove( *it );
00810     } else {
00811       --it;
00812     }
00813 }
00814 
00815 void KMSearchPattern::readConfig( const KConfig * config ) {
00816   init();
00817 
00818   mName = config->readEntry("name");
00819   if ( !config->hasKey("rules") ) {
00820     kdDebug(5006) << "KMSearchPattern::readConfig: found legacy config! Converting." << endl;
00821     importLegacyConfig( config );
00822     return;
00823   }
00824 
00825   mOperator = config->readEntry("operator") == "or" ? OpOr : OpAnd;
00826 
00827   const int nRules = config->readNumEntry( "rules", 0 );
00828 
00829   for ( int i = 0 ; i < nRules ; i++ ) {
00830     KMSearchRule * r = KMSearchRule::createInstanceFromConfig( config, i );
00831     if ( r->isEmpty() )
00832       delete r;
00833     else
00834       append( r );
00835   }
00836 }
00837 
00838 void KMSearchPattern::importLegacyConfig( const KConfig * config ) {
00839   KMSearchRule * rule = KMSearchRule::createInstance( config->readEntry("fieldA").latin1(),
00840                       config->readEntry("funcA").latin1(),
00841                       config->readEntry("contentsA") );
00842   if ( rule->isEmpty() ) {
00843     // if the first rule is invalid,
00844     // we really can't do much heuristics...
00845     delete rule;
00846     return;
00847   }
00848   append( rule );
00849 
00850   const QString sOperator = config->readEntry("operator");
00851   if ( sOperator == "ignore" ) return;
00852 
00853   rule = KMSearchRule::createInstance( config->readEntry("fieldB").latin1(),
00854                config->readEntry("funcB").latin1(),
00855                config->readEntry("contentsB") );
00856   if ( rule->isEmpty() ) {
00857     delete rule;
00858     return;
00859   }
00860   append( rule );
00861 
00862   if ( sOperator == "or"  ) {
00863     mOperator = OpOr;
00864     return;
00865   }
00866   // This is the interesting case...
00867   if ( sOperator == "unless" ) { // meaning "and not", ie we need to...
00868     // ...invert the function (e.g. "equals" <-> "doesn't equal")
00869     // We simply toggle the last bit (xor with 0x1)... This assumes that
00870     // KMSearchRule::Function's come in adjacent pairs of pros and cons
00871     KMSearchRule::Function func = last()->function();
00872     unsigned int intFunc = (unsigned int)func;
00873     func = KMSearchRule::Function( intFunc ^ 0x1 );
00874 
00875     last()->setFunction( func );
00876   }
00877 
00878   // treat any other case as "and" (our default).
00879 }
00880 
00881 void KMSearchPattern::writeConfig( KConfig * config ) const {
00882   config->writeEntry("name", mName);
00883   config->writeEntry("operator", (mOperator == KMSearchPattern::OpOr) ? "or" : "and" );
00884 
00885   int i = 0;
00886   for ( QPtrListIterator<KMSearchRule> it( *this ) ; it.current() && i < FILTER_MAX_RULES ; ++i , ++it )
00887     // we could do this ourselves, but we want the rules to be extensible,
00888     // so we give the rule it's number and let it do the rest.
00889     (*it)->writeConfig( config, i );
00890 
00891   // save the total number of rules.
00892   config->writeEntry( "rules", i );
00893 }
00894 
00895 void KMSearchPattern::init() {
00896   clear();
00897   mOperator = OpAnd;
00898   mName = '<' + i18n("name used for a virgin filter","unknown") + '>';
00899 }
00900 
00901 QString KMSearchPattern::asString() const {
00902   QString result;
00903   if ( mOperator == OpOr )
00904     result = i18n("(match any of the following)");
00905   else
00906     result = i18n("(match all of the following)");
00907 
00908   for ( QPtrListIterator<KMSearchRule> it( *this ) ; it.current() ; ++it )
00909     result += "\n\t" + FilterLog::recode( (*it)->asString() );
00910 
00911   return result;
00912 }
00913 
00914 const KMSearchPattern & KMSearchPattern::operator=( const KMSearchPattern & other ) {
00915   if ( this == &other )
00916     return *this;
00917 
00918   setOp( other.op() );
00919   setName( other.name() );
00920 
00921   clear(); // ###
00922 
00923   for ( QPtrListIterator<KMSearchRule> it( other ) ; it.current() ; ++it ) {
00924     KMSearchRule * rule = KMSearchRule::createInstance( **it ); // deep copy
00925     append( rule );
00926   }
00927 
00928   return *this;
00929 }