searchjob.cpp
00001 /* 00002 * Copyright (c) 2004 Carsten Burghardt <burghardt@kde.org> 00003 * 00004 * This program is free software; you can redistribute it and/or modify 00005 * it under the terms of the GNU General Public License as published by 00006 * the Free Software Foundation; version 2 of the License 00007 * 00008 * This program is distributed in the hope that it will be useful, 00009 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00011 * GNU General Public License for more details. 00012 * 00013 * You should have received a copy of the GNU General Public License 00014 * along with this program; if not, write to the Free Software 00015 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00016 * 00017 * In addition, as a special exception, the copyright holders give 00018 * permission to link the code of this program with any edition of 00019 * the TQt library by Trolltech AS, Norway (or with modified versions 00020 * of TQt that use the same license as TQt), and distribute linked 00021 * combinations including the two. You must obey the GNU General 00022 * Public License in all respects for all of the code used other than 00023 * TQt. If you modify this file, you may extend this exception to 00024 * your version of the file, but you are not obligated to do so. If 00025 * you do not wish to do so, delete this exception statement from 00026 * your version. 00027 */ 00028 00029 #include "searchjob.h" 00030 #include "kmfolderimap.h" 00031 #include "imapaccountbase.h" 00032 #include "kmsearchpattern.h" 00033 #include "kmfolder.h" 00034 #include "imapjob.h" 00035 #include "kmmsgdict.h" 00036 00037 #include <progressmanager.h> 00038 using KPIM::ProgressItem; 00039 using KPIM::ProgressManager; 00040 00041 #include <kdebug.h> 00042 #include <kurl.h> 00043 #include <kio/scheduler.h> 00044 #include <kio/job.h> 00045 #include <kio/global.h> 00046 #include <klocale.h> 00047 #include <kmessagebox.h> 00048 00049 #include <tqstylesheet.h> 00050 00051 namespace KMail { 00052 00053 SearchJob::SearchJob( KMFolderImap* folder, ImapAccountBase* account, 00054 const KMSearchPattern* pattern, TQ_UINT32 serNum ) 00055 : FolderJob( 0, tOther, (folder ? folder->folder() : 0) ), 00056 mFolder( folder ), mAccount( account ), mSearchPattern( pattern ), 00057 mSerNum( serNum ), mRemainingMsgs( 0 ), mProgress( 0 ), 00058 mUngetCurrentMsg( false ) 00059 { 00060 } 00061 00062 SearchJob::~SearchJob() 00063 { 00064 } 00065 00066 void SearchJob::execute() 00067 { 00068 if ( mSerNum == 0 ) 00069 { 00070 searchCompleteFolder(); 00071 } else { 00072 searchSingleMessage(); 00073 } 00074 } 00075 00076 //----------------------------------------------------------------------------- 00077 void SearchJob::searchCompleteFolder() 00078 { 00079 // generate imap search command and save local search patterns 00080 TQString searchString = searchStringFromPattern( mSearchPattern ); 00081 00082 if ( searchString.isEmpty() ) // skip imap search and download the messages 00083 return slotSearchData( 0, TQString() ); 00084 00085 // do the IMAP search 00086 KURL url = mAccount->getUrl(); 00087 url.setPath( mFolder->imapPath() + ";SECTION=" + searchString ); 00088 TQByteArray packedArgs; 00089 TQDataStream stream( packedArgs, IO_WriteOnly ); 00090 stream << (int) 'E' << url; 00091 KIO::SimpleJob *job = KIO::special( url, packedArgs, false ); 00092 if ( mFolder->imapPath() != TQString( "/" ) ) 00093 { 00094 KIO::Scheduler::assignJobToSlave( mAccount->slave(), job ); 00095 connect( job, TQT_SIGNAL( infoMessage( KIO::Job*, const TQString& ) ), 00096 TQT_SLOT( slotSearchData( KIO::Job*, const TQString& ) ) ); 00097 connect( job, TQT_SIGNAL( result( KIO::Job * ) ), 00098 TQT_SLOT( slotSearchResult( KIO::Job * ) ) ); 00099 } 00100 else 00101 { // for the "/ folder" of an imap account, searching blocks the kioslave 00102 slotSearchData( job, TQString() ); 00103 slotSearchResult( job ); 00104 } 00105 } 00106 00107 //----------------------------------------------------------------------------- 00108 TQString SearchJob::searchStringFromPattern( const KMSearchPattern* pattern ) 00109 { 00110 TQStringList parts; 00111 // this is for the search pattern that can only be done local 00112 mLocalSearchPattern = new KMSearchPattern(); 00113 mLocalSearchPattern->setOp( pattern->op() ); 00114 00115 for ( TQPtrListIterator<KMSearchRule> it( *pattern ) ; it.current() ; ++it ) 00116 { 00117 // construct an imap search command 00118 bool accept = true; 00119 TQString result; 00120 TQString field = (*it)->field(); 00121 // check if the operation is supported 00122 if ( (*it)->function() == KMSearchRule::FuncContainsNot ) { 00123 result = "NOT "; 00124 } else if ( (*it)->function() == KMSearchRule::FuncIsGreater && 00125 (*it)->field() == "<size>" ) { 00126 result = "LARGER "; 00127 } else if ( (*it)->function() == KMSearchRule::FuncIsLess && 00128 (*it)->field() == "<size>" ) { 00129 result = "SMALLER "; 00130 } else if ( (*it)->function() != KMSearchRule::FuncContains ) { 00131 // can't be handled by imap 00132 accept = false; 00133 } 00134 00135 // now see what should be searched 00136 if ( (*it)->field() == "<message>" ) { 00137 result += "TEXT \"" + (*it)->contents() + "\""; 00138 } else if ( (*it)->field() == "<body>" ) { 00139 result += "BODY \"" + (*it)->contents() + "\""; 00140 } else if ( (*it)->field() == "<recipients>" ) { 00141 result += " (OR HEADER To \"" + (*it)->contents() + "\" HEADER Cc \"" + 00142 (*it)->contents() + "\" HEADER Bcc \"" + (*it)->contents() + "\")"; 00143 } else if ( (*it)->field() == "<size>" ) { 00144 result += (*it)->contents(); 00145 } else if ( (*it)->field() == "<age in days>" || 00146 (*it)->field() == "<status>" || 00147 (*it)->field() == "<any header>" ) { 00148 accept = false; 00149 } else { 00150 result += "HEADER "+ field + " \"" + (*it)->contents() + "\""; 00151 } 00152 00153 if ( result.isEmpty() ) { 00154 accept = false; 00155 } 00156 00157 if ( accept ) { 00158 parts += result; 00159 } else { 00160 mLocalSearchPattern->append( *it ); 00161 } 00162 } 00163 00164 TQString search; 00165 if ( !parts.isEmpty() ) { 00166 if ( pattern->op() == KMSearchPattern::OpOr && parts.size() > 1 ) { 00167 search = "(OR " + parts.join(" ") + ")"; 00168 } else { 00169 // and's are simply joined 00170 search = parts.join(" "); 00171 } 00172 } 00173 00174 kdDebug(5006) << k_funcinfo << search << ";localSearch=" << mLocalSearchPattern->asString() << endl; 00175 return search; 00176 } 00177 00178 //----------------------------------------------------------------------------- 00179 void SearchJob::slotSearchData( KIO::Job* job, const TQString& data ) 00180 { 00181 if ( job && job->error() ) { 00182 // error is handled in slotSearchResult 00183 return; 00184 } 00185 00186 if ( mLocalSearchPattern->isEmpty() && data.isEmpty() ) 00187 { 00188 // no local search and the server found nothing 00189 TQValueList<TQ_UINT32> serNums; 00190 emit searchDone( serNums, mSearchPattern, true ); 00191 } else 00192 { 00193 // remember the uids the server found 00194 mImapSearchHits = TQStringList::split( " ", data ); 00195 00196 if ( canMapAllUIDs() ) 00197 { 00198 slotSearchFolder(); 00199 } else 00200 { 00201 // get the folder to make sure we have all messages 00202 connect ( mFolder, TQT_SIGNAL( folderComplete( KMFolderImap*, bool ) ), 00203 this, TQT_SLOT( slotSearchFolder()) ); 00204 mFolder->getFolder(); 00205 } 00206 } 00207 } 00208 00209 //----------------------------------------------------------------------------- 00210 bool SearchJob::canMapAllUIDs() 00211 { 00212 for ( TQStringList::Iterator it = mImapSearchHits.begin(); 00213 it != mImapSearchHits.end(); ++it ) 00214 { 00215 if ( mFolder->serNumForUID( (*it).toULong() ) == 0 ) 00216 return false; 00217 } 00218 return true; 00219 } 00220 00221 //----------------------------------------------------------------------------- 00222 void SearchJob::slotSearchFolder() 00223 { 00224 disconnect ( mFolder, TQT_SIGNAL( folderComplete( KMFolderImap*, bool ) ), 00225 this, TQT_SLOT( slotSearchFolder()) ); 00226 00227 if ( mLocalSearchPattern->isEmpty() ) { 00228 // pure imap search - now get the serial number for the UIDs 00229 TQValueList<TQ_UINT32> serNums; 00230 for ( TQStringList::Iterator it = mImapSearchHits.begin(); 00231 it != mImapSearchHits.end(); ++it ) 00232 { 00233 ulong serNum = mFolder->serNumForUID( (*it).toULong() ); 00234 // we need to check that the local folder does contain a message for this UID. 00235 // scenario: server responds with a list of UIDs. While the search was running, filtering or bad juju moved a message locally 00236 // serNumForUID will happily return 0 for the missing message, and KMFolderSearch::addSerNum() will fail its assertion. 00237 if ( serNum != 0 ) 00238 serNums.append( serNum ); 00239 } 00240 emit searchDone( serNums, mSearchPattern, true ); 00241 } else { 00242 // we have search patterns that can not be handled by the server 00243 mRemainingMsgs = mFolder->count(); 00244 if ( mRemainingMsgs == 0 ) { 00245 emit searchDone( mSearchSerNums, mSearchPattern, true ); 00246 return; 00247 } 00248 00249 // Let's see if all we need is status, that we can do locally. Optimization. 00250 bool needToDownload = needsDownload(); 00251 if ( needToDownload ) { 00252 // so we need to download all messages and check 00253 TQString question = i18n("To execute your search all messages of the folder %1 " 00254 "have to be downloaded from the server. This may take some time. " 00255 "Do you want to continue your search?").arg( mFolder->label() ); 00256 if ( KMessageBox::warningContinueCancel( 0, question, 00257 i18n("Continue Search"), i18n("&Search"), 00258 "continuedownloadingforsearch" ) != KMessageBox::Continue ) 00259 { 00260 TQValueList<TQ_UINT32> serNums; 00261 emit searchDone( serNums, mSearchPattern, true ); 00262 return; 00263 } 00264 } 00265 unsigned int numMsgs = mRemainingMsgs; 00266 // progress 00267 mProgress = ProgressManager::createProgressItem( 00268 "ImapSearchDownload" + ProgressManager::getUniqueID(), 00269 i18n("Downloading emails from IMAP server"), 00270 i18n( "URL: %1" ).arg( TQStyleSheet::escape( mFolder->folder()->prettyURL() ) ), 00271 true, 00272 mAccount->useSSL() || mAccount->useTLS() ); 00273 mProgress->setTotalItems( numMsgs ); 00274 connect ( mProgress, TQT_SIGNAL( progressItemCanceled( KPIM::ProgressItem*)), 00275 this, TQT_SLOT( slotAbortSearch( KPIM::ProgressItem* ) ) ); 00276 00277 for ( unsigned int i = 0; i < numMsgs ; ++i ) { 00278 KMMessage * msg = mFolder->getMsg( i ); 00279 if ( needToDownload ) { 00280 ImapJob *job = new ImapJob( msg ); 00281 job->setParentFolder( mFolder ); 00282 job->setParentProgressItem( mProgress ); 00283 connect( job, TQT_SIGNAL(messageRetrieved(KMMessage*)), 00284 this, TQT_SLOT(slotSearchMessageArrived(KMMessage*)) ); 00285 job->start(); 00286 } else { 00287 slotSearchMessageArrived( msg ); 00288 } 00289 } 00290 } 00291 } 00292 00293 //----------------------------------------------------------------------------- 00294 void SearchJob::slotSearchMessageArrived( KMMessage* msg ) 00295 { 00296 if ( mProgress ) 00297 { 00298 mProgress->incCompletedItems(); 00299 mProgress->updateProgress(); 00300 } 00301 --mRemainingMsgs; 00302 bool matches = false; 00303 if ( msg ) { // messageRetrieved(0) is always possible 00304 if ( mLocalSearchPattern->op() == KMSearchPattern::OpAnd ) { 00305 // imap and local search have to match 00306 if ( mLocalSearchPattern->matches( msg ) && 00307 ( mImapSearchHits.isEmpty() || 00308 mImapSearchHits.find( TQString::number(msg->UID() ) ) != mImapSearchHits.end() ) ) { 00309 TQ_UINT32 serNum = msg->getMsgSerNum(); 00310 mSearchSerNums.append( serNum ); 00311 matches = true; 00312 } 00313 } else if ( mLocalSearchPattern->op() == KMSearchPattern::OpOr ) { 00314 // imap or local search have to match 00315 if ( mLocalSearchPattern->matches( msg ) || 00316 mImapSearchHits.find( TQString::number(msg->UID()) ) != mImapSearchHits.end() ) { 00317 TQ_UINT32 serNum = msg->getMsgSerNum(); 00318 mSearchSerNums.append( serNum ); 00319 matches = true; 00320 } 00321 } 00322 int idx = -1; 00323 KMFolder * p = 0; 00324 KMMsgDict::instance()->getLocation( msg, &p, &idx ); 00325 if ( idx != -1 && mUngetCurrentMsg ) 00326 mFolder->unGetMsg( idx ); 00327 } 00328 if ( mSerNum > 0 ) 00329 { 00330 emit searchDone( mSerNum, mSearchPattern, matches ); 00331 } else { 00332 bool complete = ( mRemainingMsgs == 0 ); 00333 if ( complete && mProgress ) 00334 { 00335 mProgress->setComplete(); 00336 mProgress = 0; 00337 } 00338 if ( matches || complete ) 00339 { 00340 emit searchDone( mSearchSerNums, mSearchPattern, complete ); 00341 mSearchSerNums.clear(); 00342 } 00343 } 00344 } 00345 00346 //----------------------------------------------------------------------------- 00347 void SearchJob::slotSearchResult( KIO::Job *job ) 00348 { 00349 if ( job->error() ) 00350 { 00351 mAccount->handleJobError( job, i18n("Error while searching.") ); 00352 if ( mSerNum == 0 ) 00353 { 00354 // folder 00355 TQValueList<TQ_UINT32> serNums; 00356 emit searchDone( serNums, mSearchPattern, true ); 00357 } else { 00358 // message 00359 emit searchDone( mSerNum, mSearchPattern, false ); 00360 } 00361 } 00362 } 00363 00364 //----------------------------------------------------------------------------- 00365 void SearchJob::searchSingleMessage() 00366 { 00367 TQString searchString = searchStringFromPattern( mSearchPattern ); 00368 if ( searchString.isEmpty() ) 00369 { 00370 // no imap search 00371 slotSearchDataSingleMessage( 0, TQString() ); 00372 } else 00373 { 00374 // imap search 00375 int idx = -1; 00376 KMFolder *aFolder = 0; 00377 KMMsgDict::instance()->getLocation( mSerNum, &aFolder, &idx ); 00378 assert(aFolder && (idx != -1)); 00379 KMMsgBase *mb = mFolder->getMsgBase( idx ); 00380 00381 // only search for that UID 00382 searchString += " UID " + TQString::number( mb->UID() ); 00383 KURL url = mAccount->getUrl(); 00384 url.setPath( mFolder->imapPath() + ";SECTION=" + searchString ); 00385 TQByteArray packedArgs; 00386 TQDataStream stream( packedArgs, IO_WriteOnly ); 00387 stream << (int) 'E' << url; 00388 KIO::SimpleJob *job = KIO::special( url, packedArgs, false ); 00389 KIO::Scheduler::assignJobToSlave(mAccount->slave(), job); 00390 connect( job, TQT_SIGNAL(infoMessage(KIO::Job*,const TQString&)), 00391 TQT_SLOT(slotSearchDataSingleMessage(KIO::Job*,const TQString&)) ); 00392 connect( job, TQT_SIGNAL(result(KIO::Job *)), 00393 TQT_SLOT(slotSearchResult(KIO::Job *)) ); 00394 } 00395 } 00396 00397 //----------------------------------------------------------------------------- 00398 void SearchJob::slotSearchDataSingleMessage( KIO::Job* job, const TQString& data ) 00399 { 00400 if ( job && job->error() ) { 00401 // error is handled in slotSearchResult 00402 return; 00403 } 00404 00405 if ( mLocalSearchPattern->isEmpty() ) { 00406 // we are done 00407 emit searchDone( mSerNum, mSearchPattern, !data.isEmpty() ); 00408 return; 00409 } 00410 // remember what the server found 00411 mImapSearchHits = TQStringList::split( " ", data ); 00412 00413 // add the local search 00414 int idx = -1; 00415 KMFolder *aFolder = 0; 00416 KMMsgDict::instance()->getLocation( mSerNum, &aFolder, &idx ); 00417 assert(aFolder && (idx != -1)); 00418 mUngetCurrentMsg = !mFolder->getMsgBase( idx )->isMessage(); 00419 KMMessage * msg = mFolder->getMsg( idx ); 00420 if ( needsDownload() ) { 00421 ImapJob *job = new ImapJob( msg ); 00422 job->setParentFolder( mFolder ); 00423 connect( job, TQT_SIGNAL(messageRetrieved(KMMessage*)), 00424 this, TQT_SLOT(slotSearchMessageArrived(KMMessage*)) ); 00425 job->start(); 00426 } else { 00427 slotSearchMessageArrived( msg ); 00428 } 00429 } 00430 00431 //----------------------------------------------------------------------------- 00432 void SearchJob::slotAbortSearch( KPIM::ProgressItem* item ) 00433 { 00434 if ( item ) 00435 item->setComplete(); 00436 mAccount->killAllJobs(); 00437 TQValueList<TQ_UINT32> serNums; 00438 emit searchDone( serNums, mSearchPattern, true ); 00439 } 00440 00441 //----------------------------------------------------------------------------- 00442 bool SearchJob::needsDownload() 00443 { 00444 for ( TQPtrListIterator<KMSearchRule> it( *mLocalSearchPattern ) ; it.current() ; ++it ) { 00445 if ( (*it)->field() != "<status>" ) { 00446 return true; 00447 } 00448 } 00449 return false; 00450 } 00451 00452 } // namespace KMail 00453 00454 #include "searchjob.moc"