krop's commit fixes my problem in a better way, reverting
[kdepim.git] / kmail / searchjob.cpp
blob6fcd6484e8d4ab6162662a861d14ca77d7d48edf
1 /*
2 * Copyright (c) 2004 Carsten Burghardt <burghardt@kde.org>
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 2 of the License
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 * In addition, as a special exception, the copyright holders give
18 * permission to link the code of this program with any edition of
19 * the Qt library by Trolltech AS, Norway (or with modified versions
20 * of Qt that use the same license as Qt), and distribute linked
21 * combinations including the two. You must obey the GNU General
22 * Public License in all respects for all of the code used other than
23 * Qt. If you modify this file, you may extend this exception to
24 * your version of the file, but you are not obligated to do so. If
25 * you do not wish to do so, delete this exception statement from
26 * your version.
30 #include "searchjob.h"
31 #include "kmfolderimap.h"
32 #include "imapaccountbase.h"
33 #include "kmsearchpattern.h"
34 #include "kmfolder.h"
35 #include "imapjob.h"
36 #include "kmmsgdict.h"
38 #include <progressmanager.h>
40 using KPIM::ProgressItem;
41 using KPIM::ProgressManager;
43 #include <kdebug.h>
44 #include <kurl.h>
45 #include <kio/scheduler.h>
46 #include <kio/job.h>
47 #include <kio/global.h>
48 #include <klocale.h>
49 #include <kmessagebox.h>
50 #include <QTextDocument>
52 namespace KMail {
54 SearchJob::SearchJob( KMFolderImap* folder, ImapAccountBase* account,
55 const KMSearchPattern* pattern, quint32 serNum )
56 : FolderJob( 0, tOther, (folder ? folder->folder() : 0) ),
57 mFolder( folder ), mAccount( account ), mSearchPattern( pattern ),
58 mSerNum( serNum ), mRemainingMsgs( 0 ), mProgress( 0 ),
59 mUngetCurrentMsg( false )
63 SearchJob::~SearchJob()
67 void SearchJob::execute()
69 if ( mSerNum == 0 )
71 searchCompleteFolder();
72 } else {
73 searchSingleMessage();
77 //-----------------------------------------------------------------------------
78 void SearchJob::searchCompleteFolder()
80 // generate imap search command and save local search patterns
81 QString searchString = searchStringFromPattern( mSearchPattern );
83 if ( searchString.isEmpty() ) // skip imap search and download the messages
84 return slotSearchData( 0, QString(),QString() );
86 // do the IMAP search
87 KUrl url = mAccount->getUrl();
88 url.setPath( mFolder->imapPath() + ";SECTION=" + searchString );
89 QByteArray packedArgs;
90 QDataStream stream( &packedArgs, QIODevice::WriteOnly );
91 stream << (int) 'E' << url;
92 KIO::SimpleJob *job = KIO::special( url, packedArgs, KIO::HideProgressInfo );
93 if ( mFolder->imapPath() != QString("/") )
94 { // the "/ folder" of an imap account makes the kioslave stall
95 KIO::Scheduler::assignJobToSlave(mAccount->slave(), job);
96 connect( job, SIGNAL(infoMessage(KJob*,const QString&,const QString&)),
97 SLOT(slotSearchData(KJob*,const QString&,const QString&)) );
98 connect( job, SIGNAL(result(KJob *)),
99 SLOT(slotSearchResult(KJob *)) );
101 else
102 { // for the "/ folder" of an imap account, searching blocks the kioslave
103 slotSearchData( job, QString(), QString() );
104 slotSearchResult( job );
108 //-----------------------------------------------------------------------------
109 QString SearchJob::searchStringFromPattern( const KMSearchPattern* pattern )
111 QStringList parts;
112 // this is for the search pattern that can only be done local
113 mLocalSearchPattern = new KMSearchPattern();
114 mLocalSearchPattern->setOp( pattern->op() );
116 QList<KMSearchRule*>::const_iterator it;
117 for ( it = pattern->begin() ; it != pattern->end() ; ++it )
119 // construct an imap search command
120 bool accept = true;
121 QString result;
122 QString field = (*it)->field();
123 // check if the operation is supported
124 if ( (*it)->function() == KMSearchRule::FuncContainsNot ) {
125 result = "NOT ";
126 } else if ( (*it)->function() == KMSearchRule::FuncIsGreater &&
127 (*it)->field() == "<size>" ) {
128 result = "LARGER ";
129 } else if ( (*it)->function() == KMSearchRule::FuncIsLess &&
130 (*it)->field() == "<size>" ) {
131 result = "SMALLER ";
132 } else if ( (*it)->function() != KMSearchRule::FuncContains ) {
133 // can't be handled by imap
134 accept = false;
137 // now see what should be searched
138 if ( (*it)->field() == "<message>" ) {
139 result += "TEXT \"" + (*it)->contents() + "\"";
140 } else if ( (*it)->field() == "<body>" ) {
141 result += "BODY \"" + (*it)->contents() + "\"";
142 } else if ( (*it)->field() == "<recipients>" ) {
143 result += " (OR HEADER To \"" + (*it)->contents() + "\" HEADER Cc \"" +
144 (*it)->contents() + "\" HEADER Bcc \"" + (*it)->contents() + "\")";
145 } else if ( (*it)->field() == "<size>" ) {
146 result += (*it)->contents();
147 } else if ( (*it)->field() == "<age in days>" ||
148 (*it)->field() == "<status>" ||
149 (*it)->field() == "<any header>" ||
150 (*it)->field() == "<tag>" ) {
151 accept = false;
152 } else {
153 result += "HEADER "+ field + " \"" + (*it)->contents() + "\"";
156 if ( result.isEmpty() ) {
157 accept = false;
160 if ( accept ) {
161 parts += result;
162 } else {
163 mLocalSearchPattern->append( *it );
167 QString search;
168 if ( !parts.isEmpty() ) {
169 if ( pattern->op() == KMSearchPattern::OpOr && parts.size() > 1 ) {
170 search = "(OR " + parts.join(" ") + ')';
171 } else {
172 // and's are simply joined
173 search = parts.join(" ");
177 kDebug() << search <<";localSearch=" << mLocalSearchPattern->asString();
178 return search;
181 //-----------------------------------------------------------------------------
182 void SearchJob::slotSearchData( KJob* job, const QString& data, const QString& )
184 if ( job && job->error() ) {
185 // error is handled in slotSearchResult
186 return;
189 if ( mLocalSearchPattern->isEmpty() && data.isEmpty() )
191 // no local search and the server found nothing
192 QList<quint32> serNums;
193 emit searchDone( serNums, mSearchPattern, true );
194 } else
196 // remember the uids the server found
197 mImapSearchHits = data.split( ' ', QString::SkipEmptyParts );
199 if ( canMapAllUIDs() )
201 slotSearchFolder();
202 } else
204 // get the folder to make sure we have all messages
205 connect ( mFolder, SIGNAL( folderComplete( KMFolderImap*, bool ) ),
206 this, SLOT( slotSearchFolder()) );
207 mFolder->getFolder();
212 //-----------------------------------------------------------------------------
213 bool SearchJob::canMapAllUIDs()
215 for ( QStringList::ConstIterator it = mImapSearchHits.constBegin();
216 it != mImapSearchHits.constEnd(); ++it )
218 if ( mFolder->serNumForUID( (*it).toULong() ) == 0 )
219 return false;
221 return true;
224 //-----------------------------------------------------------------------------
225 void SearchJob::slotSearchFolder()
227 disconnect ( mFolder, SIGNAL( folderComplete( KMFolderImap*, bool ) ),
228 this, SLOT( slotSearchFolder()) );
230 if ( mLocalSearchPattern->isEmpty() ) {
231 // pure imap search - now get the serial number for the UIDs
232 QList<quint32> serNums;
233 for ( QStringList::Iterator it = mImapSearchHits.begin();
234 it != mImapSearchHits.end(); ++it ) {
235 ulong serNum = mFolder->serNumForUID( (*it).toULong() );
236 // Check that the local folder does contain a message for this UID.
237 // Scenario: server responds with a list of UIDs.
238 // While the search was running, filtering or bad juju moved a message
239 // locally serNumForUID will happily return 0 for the missing message,
240 // and KMFolderSearch::addSerNum() will fail its assertion.
241 if ( serNum != 0 ) {
242 serNums.append( serNum );
245 emit searchDone( serNums, mSearchPattern, true );
246 } else {
247 // we have search patterns that can not be handled by the server
248 mRemainingMsgs = mFolder->count();
249 if ( mRemainingMsgs == 0 ) {
250 emit searchDone( mSearchSerNums, mSearchPattern, true );
251 return;
254 // Let's see if all we need is status, that we can do locally. Optimization.
255 bool needToDownload = needsDownload();
256 if ( needToDownload ) {
257 // so we need to download all messages and check
258 QString question = i18n("To execute your search all messages of the folder %1 "
259 "have to be downloaded from the server. This may take some time. "
260 "Do you want to continue your search?", mFolder->label() );
261 if ( KMessageBox::warningContinueCancel( 0, question
262 , i18n("Continue Search"), KGuiItem( i18nc( "Continue search button.", "&Search") )
263 , KStandardGuiItem::cancel(), "continuedownloadingforsearch" )
264 != KMessageBox::Continue )
266 QList<quint32> serNums;
267 emit searchDone( serNums, mSearchPattern, true );
268 return;
271 unsigned int numMsgs = mRemainingMsgs;
272 // progress
273 mProgress = ProgressManager::createProgressItem(
274 "ImapSearchDownload" + ProgressManager::getUniqueID(),
275 i18n("Downloading emails from IMAP server"),
276 i18n( "URL: %1", Qt::escape( mFolder->folder()->prettyUrl() ) ),
277 true,
278 mAccount->useSSL() || mAccount->useTLS() );
279 mProgress->setTotalItems( numMsgs );
280 connect ( mProgress, SIGNAL( progressItemCanceled( KPIM::ProgressItem*)),
281 this, SLOT( slotAbortSearch( KPIM::ProgressItem* ) ) );
283 for ( unsigned int i = 0; i < numMsgs ; ++i ) {
284 KMMessage * msg = mFolder->getMsg( i );
285 if ( needToDownload ) {
286 ImapJob *job = new ImapJob( msg );
287 job->setParentFolder( mFolder );
288 job->setParentProgressItem( mProgress );
289 connect( job, SIGNAL(messageRetrieved(KMMessage*)),
290 this, SLOT(slotSearchMessageArrived(KMMessage*)) );
291 job->start();
292 } else {
293 slotSearchMessageArrived( msg );
299 //-----------------------------------------------------------------------------
300 void SearchJob::slotSearchMessageArrived( KMMessage* msg )
302 if ( mProgress )
304 mProgress->incCompletedItems();
305 mProgress->updateProgress();
307 --mRemainingMsgs;
308 bool matches = false;
309 if ( msg ) { // messageRetrieved(0) is always possible
310 if ( mLocalSearchPattern->op() == KMSearchPattern::OpAnd ) {
311 // imap and local search have to match
312 if ( mLocalSearchPattern->matches( msg ) &&
313 ( mImapSearchHits.isEmpty() ||
314 mImapSearchHits.contains( QString::number(msg->UID() ) ) ) ) {
315 quint32 serNum = msg->getMsgSerNum();
316 mSearchSerNums.append( serNum );
317 matches = true;
319 } else if ( mLocalSearchPattern->op() == KMSearchPattern::OpOr ) {
320 // imap or local search have to match
321 if ( mLocalSearchPattern->matches( msg ) ||
322 mImapSearchHits.contains( QString::number(msg->UID()) ) ) {
323 quint32 serNum = msg->getMsgSerNum();
324 mSearchSerNums.append( serNum );
325 matches = true;
328 int idx = -1;
329 KMFolder * p = 0;
330 KMMsgDict::instance()->getLocation( msg, &p, &idx );
331 if ( idx != -1 && mUngetCurrentMsg )
332 mFolder->unGetMsg( idx );
334 if ( mSerNum > 0 )
336 emit searchDone( mSerNum, mSearchPattern, matches );
337 } else {
338 bool complete = ( mRemainingMsgs == 0 );
339 if ( complete && mProgress )
341 mProgress->setComplete();
342 mProgress = 0;
344 if ( matches || complete )
346 emit searchDone( mSearchSerNums, mSearchPattern, complete );
347 mSearchSerNums.clear();
352 //-----------------------------------------------------------------------------
353 void SearchJob::slotSearchResult( KJob *job )
355 if ( job->error() )
357 mAccount->handleJobError( static_cast<KIO::Job*>(job), i18n("Error while searching.") );
358 if ( mSerNum == 0 )
360 // folder
361 QList<quint32> serNums;
362 emit searchDone( serNums, mSearchPattern, true );
363 } else {
364 // message
365 emit searchDone( mSerNum, mSearchPattern, false );
370 //-----------------------------------------------------------------------------
371 void SearchJob::searchSingleMessage()
373 QString searchString = searchStringFromPattern( mSearchPattern );
374 if ( searchString.isEmpty() )
376 // no imap search
377 slotSearchDataSingleMessage( 0, QString(), QString() );
378 } else
380 // imap search
381 int idx = -1;
382 KMFolder *aFolder = 0;
383 KMMsgDict::instance()->getLocation( mSerNum, &aFolder, &idx );
384 assert(aFolder && (idx != -1));
385 KMMsgBase *mb = mFolder->getMsgBase( idx );
387 // only search for that UID
388 searchString += " UID " + QString::number( mb->UID() );
389 KUrl url = mAccount->getUrl();
390 url.setPath( mFolder->imapPath() + ";SECTION=" + searchString );
391 QByteArray packedArgs;
392 QDataStream stream( &packedArgs, QIODevice::WriteOnly );
393 stream << (int) 'E' << url;
394 KIO::SimpleJob *job = KIO::special( url, packedArgs, KIO::HideProgressInfo );
395 KIO::Scheduler::assignJobToSlave(mAccount->slave(), job);
396 connect( job, SIGNAL(infoMessage(KJob*,const QString&,const QString&)),
397 SLOT(slotSearchDataSingleMessage(KJob*,const QString&,const QString&)) );
398 connect( job, SIGNAL(result(KJob *)),
399 SLOT(slotSearchResult(KJob *)) );
403 //-----------------------------------------------------------------------------
404 void SearchJob::slotSearchDataSingleMessage( KJob* job, const QString& data,const QString& )
406 if ( job && job->error() ) {
407 // error is handled in slotSearchResult
408 return;
411 if ( mLocalSearchPattern->isEmpty() ) {
412 // we are done
413 emit searchDone( mSerNum, mSearchPattern, !data.isEmpty() );
414 return;
416 // remember what the server found
417 mImapSearchHits = data.split( ' ', QString::SkipEmptyParts );
419 // add the local search
420 int idx = -1;
421 KMFolder *aFolder = 0;
422 KMMsgDict::instance()->getLocation( mSerNum, &aFolder, &idx );
423 assert(aFolder && (idx != -1));
424 mUngetCurrentMsg = !mFolder->getMsgBase( idx )->isMessage();
425 KMMessage * msg = mFolder->getMsg( idx );
426 if ( needsDownload() ) {
427 ImapJob *job = new ImapJob( msg );
428 job->setParentFolder( mFolder );
429 connect( job, SIGNAL(messageRetrieved(KMMessage*)),
430 this, SLOT(slotSearchMessageArrived(KMMessage*)) );
431 job->start();
432 } else {
433 slotSearchMessageArrived( msg );
437 //-----------------------------------------------------------------------------
438 void SearchJob::slotAbortSearch( KPIM::ProgressItem* item )
440 if ( item )
441 item->setComplete();
442 mAccount->killAllJobs();
443 QList<quint32> serNums;
444 emit searchDone( serNums, mSearchPattern, true );
447 //-----------------------------------------------------------------------------
448 bool SearchJob::needsDownload()
450 QList<KMSearchRule*>::const_iterator it;
451 for ( it = mLocalSearchPattern->constBegin() ;
452 it != mLocalSearchPattern->constEnd() ; ++it ) {
453 if ( (*it)->field() != "<status>" ) {
454 return true;
457 return false;
460 } // namespace KMail
462 #include "searchjob.moc"