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("/")) && mAccount->slave() )
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"