kmail

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