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>" ) {
152 result
+= "HEADER "+ field
+ " \"" + (*it
)->contents() + "\"";
155 if ( result
.isEmpty() ) {
162 mLocalSearchPattern
->append( *it
);
167 if ( !parts
.isEmpty() ) {
168 if ( pattern
->op() == KMSearchPattern::OpOr
&& parts
.size() > 1 ) {
169 search
= "(OR " + parts
.join(" ") + ')';
171 // and's are simply joined
172 search
= parts
.join(" ");
176 kDebug(5006) << search
<<";localSearch=" << mLocalSearchPattern
->asString();
180 //-----------------------------------------------------------------------------
181 void SearchJob::slotSearchData( KJob
* job
, const QString
& data
, const QString
& )
183 if ( job
&& job
->error() ) {
184 // error is handled in slotSearchResult
188 if ( mLocalSearchPattern
->isEmpty() && data
.isEmpty() )
190 // no local search and the server found nothing
191 QList
<quint32
> serNums
;
192 emit
searchDone( serNums
, mSearchPattern
, true );
195 // remember the uids the server found
196 mImapSearchHits
= data
.split( ' ', QString::SkipEmptyParts
);
198 if ( canMapAllUIDs() )
203 // get the folder to make sure we have all messages
204 connect ( mFolder
, SIGNAL( folderComplete( KMFolderImap
*, bool ) ),
205 this, SLOT( slotSearchFolder()) );
206 mFolder
->getFolder();
211 //-----------------------------------------------------------------------------
212 bool SearchJob::canMapAllUIDs()
214 for ( QStringList::Iterator it
= mImapSearchHits
.begin();
215 it
!= mImapSearchHits
.end(); ++it
)
217 if ( mFolder
->serNumForUID( (*it
).toULong() ) == 0 )
223 //-----------------------------------------------------------------------------
224 void SearchJob::slotSearchFolder()
226 disconnect ( mFolder
, SIGNAL( folderComplete( KMFolderImap
*, bool ) ),
227 this, SLOT( slotSearchFolder()) );
229 if ( mLocalSearchPattern
->isEmpty() ) {
230 // pure imap search - now get the serial number for the UIDs
231 QList
<quint32
> serNums
;
232 for ( QStringList::Iterator it
= mImapSearchHits
.begin();
233 it
!= mImapSearchHits
.end(); ++it
) {
234 ulong serNum
= mFolder
->serNumForUID( (*it
).toULong() );
235 // Check that the local folder does contain a message for this UID.
236 // Scenario: server responds with a list of UIDs.
237 // While the search was running, filtering or bad juju moved a message
238 // locally serNumForUID will happily return 0 for the missing message,
239 // and KMFolderSearch::addSerNum() will fail its assertion.
241 serNums
.append( serNum
);
244 emit
searchDone( serNums
, mSearchPattern
, true );
246 // we have search patterns that can not be handled by the server
247 mRemainingMsgs
= mFolder
->count();
248 if ( mRemainingMsgs
== 0 ) {
249 emit
searchDone( mSearchSerNums
, mSearchPattern
, true );
253 // Let's see if all we need is status, that we can do locally. Optimization.
254 bool needToDownload
= needsDownload();
255 if ( needToDownload
) {
256 // so we need to download all messages and check
257 QString question
= i18n("To execute your search all messages of the folder %1 "
258 "have to be downloaded from the server. This may take some time. "
259 "Do you want to continue your search?", mFolder
->label() );
260 if ( KMessageBox::warningContinueCancel( 0, question
261 , i18n("Continue Search"), KGuiItem( i18nc( "Continue search button.", "&Search") )
262 , KStandardGuiItem::cancel(), "continuedownloadingforsearch" )
263 != KMessageBox::Continue
)
265 QList
<quint32
> serNums
;
266 emit
searchDone( serNums
, mSearchPattern
, true );
270 unsigned int numMsgs
= mRemainingMsgs
;
272 mProgress
= ProgressManager::createProgressItem(
273 "ImapSearchDownload" + ProgressManager::getUniqueID(),
274 i18n("Downloading emails from IMAP server"),
275 i18n( "URL: %1", Qt::escape( mFolder
->folder()->prettyUrl() ) ),
277 mAccount
->useSSL() || mAccount
->useTLS() );
278 mProgress
->setTotalItems( numMsgs
);
279 connect ( mProgress
, SIGNAL( progressItemCanceled( KPIM::ProgressItem
*)),
280 this, SLOT( slotAbortSearch( KPIM::ProgressItem
* ) ) );
282 for ( unsigned int i
= 0; i
< numMsgs
; ++i
) {
283 KMMessage
* msg
= mFolder
->getMsg( i
);
284 if ( needToDownload
) {
285 ImapJob
*job
= new ImapJob( msg
);
286 job
->setParentFolder( mFolder
);
287 job
->setParentProgressItem( mProgress
);
288 connect( job
, SIGNAL(messageRetrieved(KMMessage
*)),
289 this, SLOT(slotSearchMessageArrived(KMMessage
*)) );
292 slotSearchMessageArrived( msg
);
298 //-----------------------------------------------------------------------------
299 void SearchJob::slotSearchMessageArrived( KMMessage
* msg
)
303 mProgress
->incCompletedItems();
304 mProgress
->updateProgress();
307 bool matches
= false;
308 if ( msg
) { // messageRetrieved(0) is always possible
309 if ( mLocalSearchPattern
->op() == KMSearchPattern::OpAnd
) {
310 // imap and local search have to match
311 if ( mLocalSearchPattern
->matches( msg
) &&
312 ( mImapSearchHits
.isEmpty() ||
313 mImapSearchHits
.contains( QString::number(msg
->UID() ) ) ) ) {
314 quint32 serNum
= msg
->getMsgSerNum();
315 mSearchSerNums
.append( serNum
);
318 } else if ( mLocalSearchPattern
->op() == KMSearchPattern::OpOr
) {
319 // imap or local search have to match
320 if ( mLocalSearchPattern
->matches( msg
) ||
321 mImapSearchHits
.contains( QString::number(msg
->UID()) ) ) {
322 quint32 serNum
= msg
->getMsgSerNum();
323 mSearchSerNums
.append( serNum
);
329 KMMsgDict::instance()->getLocation( msg
, &p
, &idx
);
330 if ( idx
!= -1 && mUngetCurrentMsg
)
331 mFolder
->unGetMsg( idx
);
335 emit
searchDone( mSerNum
, mSearchPattern
, matches
);
337 bool complete
= ( mRemainingMsgs
== 0 );
338 if ( complete
&& mProgress
)
340 mProgress
->setComplete();
343 if ( matches
|| complete
)
345 emit
searchDone( mSearchSerNums
, mSearchPattern
, complete
);
346 mSearchSerNums
.clear();
351 //-----------------------------------------------------------------------------
352 void SearchJob::slotSearchResult( KJob
*job
)
356 mAccount
->handleJobError( static_cast<KIO::Job
*>(job
), i18n("Error while searching.") );
360 QList
<quint32
> serNums
;
361 emit
searchDone( serNums
, mSearchPattern
, true );
364 emit
searchDone( mSerNum
, mSearchPattern
, false );
369 //-----------------------------------------------------------------------------
370 void SearchJob::searchSingleMessage()
372 QString searchString
= searchStringFromPattern( mSearchPattern
);
373 if ( searchString
.isEmpty() )
376 slotSearchDataSingleMessage( 0, QString(), QString() );
381 KMFolder
*aFolder
= 0;
382 KMMsgDict::instance()->getLocation( mSerNum
, &aFolder
, &idx
);
383 assert(aFolder
&& (idx
!= -1));
384 KMMsgBase
*mb
= mFolder
->getMsgBase( idx
);
386 // only search for that UID
387 searchString
+= " UID " + QString::number( mb
->UID() );
388 KUrl url
= mAccount
->getUrl();
389 url
.setPath( mFolder
->imapPath() + ";SECTION=" + searchString
);
390 QByteArray packedArgs
;
391 QDataStream
stream( &packedArgs
, QIODevice::WriteOnly
);
392 stream
<< (int) 'E' << url
;
393 KIO::SimpleJob
*job
= KIO::special( url
, packedArgs
, KIO::HideProgressInfo
);
394 KIO::Scheduler::assignJobToSlave(mAccount
->slave(), job
);
395 connect( job
, SIGNAL(infoMessage(KJob
*,const QString
&,const QString
&)),
396 SLOT(slotSearchDataSingleMessage(KJob
*,const QString
&,const QString
&)) );
397 connect( job
, SIGNAL(result(KJob
*)),
398 SLOT(slotSearchResult(KJob
*)) );
402 //-----------------------------------------------------------------------------
403 void SearchJob::slotSearchDataSingleMessage( KJob
* job
, const QString
& data
,const QString
& )
405 if ( job
&& job
->error() ) {
406 // error is handled in slotSearchResult
410 if ( mLocalSearchPattern
->isEmpty() ) {
412 emit
searchDone( mSerNum
, mSearchPattern
, !data
.isEmpty() );
415 // remember what the server found
416 mImapSearchHits
= data
.split( ' ', QString::SkipEmptyParts
);
418 // add the local search
420 KMFolder
*aFolder
= 0;
421 KMMsgDict::instance()->getLocation( mSerNum
, &aFolder
, &idx
);
422 assert(aFolder
&& (idx
!= -1));
423 mUngetCurrentMsg
= !mFolder
->getMsgBase( idx
)->isMessage();
424 KMMessage
* msg
= mFolder
->getMsg( idx
);
425 if ( needsDownload() ) {
426 ImapJob
*job
= new ImapJob( msg
);
427 job
->setParentFolder( mFolder
);
428 connect( job
, SIGNAL(messageRetrieved(KMMessage
*)),
429 this, SLOT(slotSearchMessageArrived(KMMessage
*)) );
432 slotSearchMessageArrived( msg
);
436 //-----------------------------------------------------------------------------
437 void SearchJob::slotAbortSearch( KPIM::ProgressItem
* item
)
441 mAccount
->killAllJobs();
442 QList
<quint32
> serNums
;
443 emit
searchDone( serNums
, mSearchPattern
, true );
446 //-----------------------------------------------------------------------------
447 bool SearchJob::needsDownload()
449 QList
<KMSearchRule
*>::const_iterator it
;
450 for ( it
= mLocalSearchPattern
->begin() ;
451 it
!= mLocalSearchPattern
->end() ; ++it
) {
452 if ( (*it
)->field() != "<status>" ) {
461 #include "searchjob.moc"