kmail Library API Documentation

kmsearchpattern.cpp

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