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
30 #include "searchjob.h"
31 #include "kmfolderimap.h"
32 #include "imapaccountbase.h"
33 #include "kmsearchpattern.h"
36 #include "kmmsgdict.h"
38 #include <progressmanager.h>
40 using KPIM::ProgressItem
;
41 using KPIM::ProgressManager
;
45 #include <kio/scheduler.h>
47 #include <kio/global.h>
49 #include <kmessagebox.h>
50 #include <QTextDocument>
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()
71 searchCompleteFolder();
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() );
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
*)) );
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
)
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
122 QString field
= (*it
)->field();
123 // check if the operation is supported
124 if ( (*it
)->function() == KMSearchRule::FuncContainsNot
) {
126 } else if ( (*it
)->function() == KMSearchRule::FuncIsGreater
&&
127 (*it
)->field() == "<size>" ) {
129 } else if ( (*it
)->function() == KMSearchRule::FuncIsLess
&&
130 (*it
)->field() == "<size>" ) {
132 } else if ( (*it
)->function() != KMSearchRule::FuncContains
) {
133 // can't be handled by imap
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>" ) {
153 result
+= "HEADER "+ field
+ " \"" + (*it
)->contents() + "\"";
156 if ( result
.isEmpty() ) {
163 mLocalSearchPattern
->append( *it
);
168 if ( !parts
.isEmpty() ) {
169 if ( pattern
->op() == KMSearchPattern::OpOr
&& parts
.size() > 1 ) {
170 search
= "(OR " + parts
.join(" ") + ')';
172 // and's are simply joined
173 search
= parts
.join(" ");
177 kDebug() << search
<<";localSearch=" << mLocalSearchPattern
->asString();
181 //-----------------------------------------------------------------------------
182 void SearchJob::slotSearchData( KJob
* job
, const QString
& data
, const QString
& )
184 if ( job
&& job
->error() ) {
185 // error is handled in slotSearchResult
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 );
196 // remember the uids the server found
197 mImapSearchHits
= data
.split( ' ', QString::SkipEmptyParts
);
199 if ( canMapAllUIDs() )
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 )
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.
242 serNums
.append( serNum
);
245 emit
searchDone( serNums
, mSearchPattern
, true );
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 );
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 );
271 unsigned int numMsgs
= mRemainingMsgs
;
273 mProgress
= ProgressManager::createProgressItem(
274 "ImapSearchDownload" + ProgressManager::getUniqueID(),
275 i18n("Downloading emails from IMAP server"),
276 i18n( "URL: %1", Qt::escape( mFolder
->folder()->prettyUrl() ) ),
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
*)) );
293 slotSearchMessageArrived( msg
);
299 //-----------------------------------------------------------------------------
300 void SearchJob::slotSearchMessageArrived( KMMessage
* msg
)
304 mProgress
->incCompletedItems();
305 mProgress
->updateProgress();
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
);
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
);
330 KMMsgDict::instance()->getLocation( msg
, &p
, &idx
);
331 if ( idx
!= -1 && mUngetCurrentMsg
)
332 mFolder
->unGetMsg( idx
);
336 emit
searchDone( mSerNum
, mSearchPattern
, matches
);
338 bool complete
= ( mRemainingMsgs
== 0 );
339 if ( complete
&& mProgress
)
341 mProgress
->setComplete();
344 if ( matches
|| complete
)
346 emit
searchDone( mSearchSerNums
, mSearchPattern
, complete
);
347 mSearchSerNums
.clear();
352 //-----------------------------------------------------------------------------
353 void SearchJob::slotSearchResult( KJob
*job
)
357 mAccount
->handleJobError( static_cast<KIO::Job
*>(job
), i18n("Error while searching.") );
361 QList
<quint32
> serNums
;
362 emit
searchDone( serNums
, mSearchPattern
, true );
365 emit
searchDone( mSerNum
, mSearchPattern
, false );
370 //-----------------------------------------------------------------------------
371 void SearchJob::searchSingleMessage()
373 QString searchString
= searchStringFromPattern( mSearchPattern
);
374 if ( searchString
.isEmpty() )
377 slotSearchDataSingleMessage( 0, QString(), QString() );
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
411 if ( mLocalSearchPattern
->isEmpty() ) {
413 emit
searchDone( mSerNum
, mSearchPattern
, !data
.isEmpty() );
416 // remember what the server found
417 mImapSearchHits
= data
.split( ' ', QString::SkipEmptyParts
);
419 // add the local search
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
*)) );
433 slotSearchMessageArrived( msg
);
437 //-----------------------------------------------------------------------------
438 void SearchJob::slotAbortSearch( KPIM::ProgressItem
* item
)
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>" ) {
462 #include "searchjob.moc"