kmail

searchjob.cpp
1/*
2 * Copyright (c) 2004 Carsten Burghardt <burghardt@kde.org>
3 *
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
7 *
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.
12 *
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.
16 *
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 TQt library by Trolltech AS, Norway (or with modified versions
20 * of TQt that use the same license as TQt), 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 * TQt. 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.
27 */
28
29#include "searchjob.h"
30#include "kmfolderimap.h"
31#include "imapaccountbase.h"
32#include "kmsearchpattern.h"
33#include "kmfolder.h"
34#include "imapjob.h"
35#include "kmmsgdict.h"
36
37#include <progressmanager.h>
38using KPIM::ProgressItem;
39using KPIM::ProgressManager;
40
41#include <kdebug.h>
42#include <kurl.h>
43#include <tdeio/scheduler.h>
44#include <tdeio/job.h>
45#include <tdeio/global.h>
46#include <tdelocale.h>
47#include <tdemessagebox.h>
48
49#include <tqstylesheet.h>
50
51namespace KMail {
52
53SearchJob::SearchJob( KMFolderImap* folder, ImapAccountBase* account,
54 const KMSearchPattern* pattern, TQ_UINT32 serNum )
55 : FolderJob( 0, tOther, (folder ? folder->folder() : 0) ),
56 mFolder( folder ), mAccount( account ), mSearchPattern( pattern ),
57 mSerNum( serNum ), mRemainingMsgs( 0 ), mProgress( 0 ),
58 mUngetCurrentMsg( false )
59{
60}
61
62SearchJob::~SearchJob()
63{
64}
65
66void SearchJob::execute()
67{
68 if ( mSerNum == 0 )
69 {
70 searchCompleteFolder();
71 } else {
72 searchSingleMessage();
73 }
74}
75
76//-----------------------------------------------------------------------------
77void SearchJob::searchCompleteFolder()
78{
79 // generate imap search command and save local search patterns
80 TQString searchString = searchStringFromPattern( mSearchPattern );
81
82 if ( searchString.isEmpty() ) // skip imap search and download the messages
83 return slotSearchData( 0, TQString() );
84
85 // do the IMAP search
86 KURL url = mAccount->getUrl();
87 url.setPath( mFolder->imapPath() + ";SECTION=" + searchString );
88 TQByteArray packedArgs;
89 TQDataStream stream( packedArgs, IO_WriteOnly );
90 stream << (int) 'E' << url;
91 TDEIO::SimpleJob *job = TDEIO::special( url, packedArgs, false );
92 if ( mFolder->imapPath() != TQString( "/" ) )
93 {
94 TDEIO::Scheduler::assignJobToSlave( mAccount->slave(), job );
95 connect( job, TQ_SIGNAL( infoMessage( TDEIO::Job*, const TQString& ) ),
96 TQ_SLOT( slotSearchData( TDEIO::Job*, const TQString& ) ) );
97 connect( job, TQ_SIGNAL( result( TDEIO::Job * ) ),
98 TQ_SLOT( slotSearchResult( TDEIO::Job * ) ) );
99 }
100 else
101 { // for the "/ folder" of an imap account, searching blocks the tdeioslave
102 slotSearchData( job, TQString() );
103 slotSearchResult( job );
104 }
105}
106
107//-----------------------------------------------------------------------------
108TQString SearchJob::searchStringFromPattern( const KMSearchPattern* pattern )
109{
110 TQStringList parts;
111 // this is for the search pattern that can only be done local
112 mLocalSearchPattern = new KMSearchPattern();
113 mLocalSearchPattern->setOp( pattern->op() );
114
115 for ( TQPtrListIterator<KMSearchRule> it( *pattern ) ; it.current() ; ++it )
116 {
117 // construct an imap search command
118 bool accept = true;
119 TQString result;
120 TQString field = (*it)->field();
121 // check if the operation is supported
122 if ( (*it)->function() == KMSearchRule::FuncContainsNot ) {
123 result = "NOT ";
124 } else if ( (*it)->function() == KMSearchRule::FuncIsGreater &&
125 (*it)->field() == "<size>" ) {
126 result = "LARGER ";
127 } else if ( (*it)->function() == KMSearchRule::FuncIsLess &&
128 (*it)->field() == "<size>" ) {
129 result = "SMALLER ";
130 } else if ( (*it)->function() != KMSearchRule::FuncContains ) {
131 // can't be handled by imap
132 accept = false;
133 }
134
135 // now see what should be searched
136 if ( (*it)->field() == "<message>" ) {
137 result += "TEXT \"" + (*it)->contents() + "\"";
138 } else if ( (*it)->field() == "<body>" ) {
139 result += "BODY \"" + (*it)->contents() + "\"";
140 } else if ( (*it)->field() == "<recipients>" ) {
141 result += " (OR HEADER To \"" + (*it)->contents() + "\" HEADER Cc \"" +
142 (*it)->contents() + "\" HEADER Bcc \"" + (*it)->contents() + "\")";
143 } else if ( (*it)->field() == "<size>" ) {
144 result += (*it)->contents();
145 } else if ( (*it)->field() == "<age in days>" ||
146 (*it)->field() == "<status>" ||
147 (*it)->field() == "<any header>" ) {
148 accept = false;
149 } else {
150 result += "HEADER "+ field + " \"" + (*it)->contents() + "\"";
151 }
152
153 if ( result.isEmpty() ) {
154 accept = false;
155 }
156
157 if ( accept ) {
158 parts += result;
159 } else {
160 mLocalSearchPattern->append( *it );
161 }
162 }
163
164 TQString search;
165 if ( !parts.isEmpty() ) {
166 if ( pattern->op() == KMSearchPattern::OpOr && parts.size() > 1 ) {
167 search = "(OR " + parts.join(" ") + ")";
168 } else {
169 // and's are simply joined
170 search = parts.join(" ");
171 }
172 }
173
174 kdDebug(5006) << k_funcinfo << search << ";localSearch=" << mLocalSearchPattern->asString() << endl;
175 return search;
176}
177
178//-----------------------------------------------------------------------------
179void SearchJob::slotSearchData( TDEIO::Job* job, const TQString& data )
180{
181 if ( job && job->error() ) {
182 // error is handled in slotSearchResult
183 return;
184 }
185
186 if ( mLocalSearchPattern->isEmpty() && data.isEmpty() )
187 {
188 // no local search and the server found nothing
189 TQValueList<TQ_UINT32> serNums;
190 emit searchDone( serNums, mSearchPattern, true );
191 } else
192 {
193 // remember the uids the server found
194 mImapSearchHits = TQStringList::split( " ", data );
195
196 if ( canMapAllUIDs() )
197 {
198 slotSearchFolder();
199 } else
200 {
201 // get the folder to make sure we have all messages
202 connect ( mFolder, TQ_SIGNAL( folderComplete( KMFolderImap*, bool ) ),
203 this, TQ_SLOT( slotSearchFolder()) );
204 mFolder->getFolder();
205 }
206 }
207}
208
209//-----------------------------------------------------------------------------
210bool SearchJob::canMapAllUIDs()
211{
212 for ( TQStringList::Iterator it = mImapSearchHits.begin();
213 it != mImapSearchHits.end(); ++it )
214 {
215 if ( mFolder->serNumForUID( (*it).toULong() ) == 0 )
216 return false;
217 }
218 return true;
219}
220
221//-----------------------------------------------------------------------------
222void SearchJob::slotSearchFolder()
223{
224 disconnect ( mFolder, TQ_SIGNAL( folderComplete( KMFolderImap*, bool ) ),
225 this, TQ_SLOT( slotSearchFolder()) );
226
227 if ( mLocalSearchPattern->isEmpty() ) {
228 // pure imap search - now get the serial number for the UIDs
229 TQValueList<TQ_UINT32> serNums;
230 for ( TQStringList::Iterator it = mImapSearchHits.begin();
231 it != mImapSearchHits.end(); ++it )
232 {
233 ulong serNum = mFolder->serNumForUID( (*it).toULong() );
234 // we need to check that the local folder does contain a message for this UID.
235 // scenario: server responds with a list of UIDs. While the search was running, filtering or bad juju moved a message locally
236 // serNumForUID will happily return 0 for the missing message, and KMFolderSearch::addSerNum() will fail its assertion.
237 if ( serNum != 0 )
238 serNums.append( serNum );
239 }
240 emit searchDone( serNums, mSearchPattern, true );
241 } else {
242 // we have search patterns that can not be handled by the server
243 mRemainingMsgs = mFolder->count();
244 if ( mRemainingMsgs == 0 ) {
245 emit searchDone( mSearchSerNums, mSearchPattern, true );
246 return;
247 }
248
249 // Let's see if all we need is status, that we can do locally. Optimization.
250 bool needToDownload = needsDownload();
251 if ( needToDownload ) {
252 // so we need to download all messages and check
253 TQString question = i18n("To execute your search all messages of the folder %1 "
254 "have to be downloaded from the server. This may take some time. "
255 "Do you want to continue your search?").arg( mFolder->label() );
256 if ( KMessageBox::warningContinueCancel( 0, question,
257 i18n("Continue Search"), i18n("&Search"),
258 "continuedownloadingforsearch" ) != KMessageBox::Continue )
259 {
260 TQValueList<TQ_UINT32> serNums;
261 emit searchDone( serNums, mSearchPattern, true );
262 return;
263 }
264 }
265 unsigned int numMsgs = mRemainingMsgs;
266 // progress
267 mProgress = ProgressManager::createProgressItem(
268 "ImapSearchDownload" + ProgressManager::getUniqueID(),
269 i18n("Downloading emails from IMAP server"),
270 i18n( "URL: %1" ).arg( TQStyleSheet::escape( mFolder->folder()->prettyURL() ) ),
271 true,
272 mAccount->useSSL() || mAccount->useTLS() );
273 mProgress->setTotalItems( numMsgs );
274 connect ( mProgress, TQ_SIGNAL( progressItemCanceled( KPIM::ProgressItem*)),
275 this, TQ_SLOT( slotAbortSearch( KPIM::ProgressItem* ) ) );
276
277 for ( unsigned int i = 0; i < numMsgs ; ++i ) {
278 KMMessage * msg = mFolder->getMsg( i );
279 if ( needToDownload ) {
280 ImapJob *job = new ImapJob( msg );
281 job->setParentFolder( mFolder );
282 job->setParentProgressItem( mProgress );
283 connect( job, TQ_SIGNAL(messageRetrieved(KMMessage*)),
284 this, TQ_SLOT(slotSearchMessageArrived(KMMessage*)) );
285 job->start();
286 } else {
287 slotSearchMessageArrived( msg );
288 }
289 }
290 }
291}
292
293//-----------------------------------------------------------------------------
294void SearchJob::slotSearchMessageArrived( KMMessage* msg )
295{
296 if ( mProgress )
297 {
298 mProgress->incCompletedItems();
299 mProgress->updateProgress();
300 }
301 --mRemainingMsgs;
302 bool matches = false;
303 if ( msg ) { // messageRetrieved(0) is always possible
304 if ( mLocalSearchPattern->op() == KMSearchPattern::OpAnd ) {
305 // imap and local search have to match
306 if ( mLocalSearchPattern->matches( msg ) &&
307 ( mImapSearchHits.isEmpty() ||
308 mImapSearchHits.find( TQString::number(msg->UID() ) ) != mImapSearchHits.end() ) ) {
309 TQ_UINT32 serNum = msg->getMsgSerNum();
310 mSearchSerNums.append( serNum );
311 matches = true;
312 }
313 } else if ( mLocalSearchPattern->op() == KMSearchPattern::OpOr ) {
314 // imap or local search have to match
315 if ( mLocalSearchPattern->matches( msg ) ||
316 mImapSearchHits.find( TQString::number(msg->UID()) ) != mImapSearchHits.end() ) {
317 TQ_UINT32 serNum = msg->getMsgSerNum();
318 mSearchSerNums.append( serNum );
319 matches = true;
320 }
321 }
322 int idx = -1;
323 KMFolder * p = 0;
324 KMMsgDict::instance()->getLocation( msg, &p, &idx );
325 if ( idx != -1 && mUngetCurrentMsg )
326 mFolder->unGetMsg( idx );
327 }
328 if ( mSerNum > 0 )
329 {
330 emit searchDone( mSerNum, mSearchPattern, matches );
331 } else {
332 bool complete = ( mRemainingMsgs == 0 );
333 if ( complete && mProgress )
334 {
335 mProgress->setComplete();
336 mProgress = 0;
337 }
338 if ( matches || complete )
339 {
340 emit searchDone( mSearchSerNums, mSearchPattern, complete );
341 mSearchSerNums.clear();
342 }
343 }
344}
345
346//-----------------------------------------------------------------------------
347void SearchJob::slotSearchResult( TDEIO::Job *job )
348{
349 if ( job->error() )
350 {
351 mAccount->handleJobError( job, i18n("Error while searching.") );
352 if ( mSerNum == 0 )
353 {
354 // folder
355 TQValueList<TQ_UINT32> serNums;
356 emit searchDone( serNums, mSearchPattern, true );
357 } else {
358 // message
359 emit searchDone( mSerNum, mSearchPattern, false );
360 }
361 }
362}
363
364//-----------------------------------------------------------------------------
365void SearchJob::searchSingleMessage()
366{
367 TQString searchString = searchStringFromPattern( mSearchPattern );
368 if ( searchString.isEmpty() )
369 {
370 // no imap search
371 slotSearchDataSingleMessage( 0, TQString() );
372 } else
373 {
374 // imap search
375 int idx = -1;
376 KMFolder *aFolder = 0;
377 KMMsgDict::instance()->getLocation( mSerNum, &aFolder, &idx );
378 assert(aFolder && (idx != -1));
379 KMMsgBase *mb = mFolder->getMsgBase( idx );
380
381 // only search for that UID
382 searchString += " UID " + TQString::number( mb->UID() );
383 KURL url = mAccount->getUrl();
384 url.setPath( mFolder->imapPath() + ";SECTION=" + searchString );
385 TQByteArray packedArgs;
386 TQDataStream stream( packedArgs, IO_WriteOnly );
387 stream << (int) 'E' << url;
388 TDEIO::SimpleJob *job = TDEIO::special( url, packedArgs, false );
389 TDEIO::Scheduler::assignJobToSlave(mAccount->slave(), job);
390 connect( job, TQ_SIGNAL(infoMessage(TDEIO::Job*,const TQString&)),
391 TQ_SLOT(slotSearchDataSingleMessage(TDEIO::Job*,const TQString&)) );
392 connect( job, TQ_SIGNAL(result(TDEIO::Job *)),
393 TQ_SLOT(slotSearchResult(TDEIO::Job *)) );
394 }
395}
396
397//-----------------------------------------------------------------------------
398void SearchJob::slotSearchDataSingleMessage( TDEIO::Job* job, const TQString& data )
399{
400 if ( job && job->error() ) {
401 // error is handled in slotSearchResult
402 return;
403 }
404
405 if ( mLocalSearchPattern->isEmpty() ) {
406 // we are done
407 emit searchDone( mSerNum, mSearchPattern, !data.isEmpty() );
408 return;
409 }
410 // remember what the server found
411 mImapSearchHits = TQStringList::split( " ", data );
412
413 // add the local search
414 int idx = -1;
415 KMFolder *aFolder = 0;
416 KMMsgDict::instance()->getLocation( mSerNum, &aFolder, &idx );
417 assert(aFolder && (idx != -1));
418 mUngetCurrentMsg = !mFolder->getMsgBase( idx )->isMessage();
419 KMMessage * msg = mFolder->getMsg( idx );
420 if ( needsDownload() ) {
421 ImapJob *job = new ImapJob( msg );
422 job->setParentFolder( mFolder );
423 connect( job, TQ_SIGNAL(messageRetrieved(KMMessage*)),
424 this, TQ_SLOT(slotSearchMessageArrived(KMMessage*)) );
425 job->start();
426 } else {
427 slotSearchMessageArrived( msg );
428 }
429}
430
431//-----------------------------------------------------------------------------
432void SearchJob::slotAbortSearch( KPIM::ProgressItem* item )
433{
434 if ( item )
435 item->setComplete();
436 mAccount->killAllJobs();
437 TQValueList<TQ_UINT32> serNums;
438 emit searchDone( serNums, mSearchPattern, true );
439}
440
441//-----------------------------------------------------------------------------
442bool SearchJob::needsDownload()
443{
444 for ( TQPtrListIterator<KMSearchRule> it( *mLocalSearchPattern ) ; it.current() ; ++it ) {
445 if ( (*it)->field() != "<status>" ) {
446 return true;
447 }
448 }
449 return false;
450}
451
452} // namespace KMail
453
454#include "searchjob.moc"
Mail folder.
Definition: kmfolder.h:69
This is a Mime Message.
Definition: kmmessage.h:68
ulong UID() const
Get/set UID.
Definition: kmmessage.cpp:2225
void getLocation(unsigned long key, KMFolder **retFolder, int *retIndex) const
Returns the folder the message represented by the serial number key is in and the index in that folde...
Definition: kmmsgdict.cpp:319
static const KMMsgDict * instance()
Access the globally unique MessageDict.
Definition: kmmsgdict.cpp:167
This class is an abstraction of a search over messages.
KMSearchPattern::Operator op() const
Get the filter operator.
bool matches(const KMMessage *msg, bool ignoreBody=false) const
The central function of this class.
void setOp(KMSearchPattern::Operator aOp)
Set the filter operator.
TQString asString() const
Returns the pattern as string.
SearchJob(KMFolderImap *folder, ImapAccountBase *account, const KMSearchPattern *pattern, TQ_UINT32 serNum=0)
Creates a new job.
Definition: searchjob.cpp:53
folderdiaquotatab.h
Definition: aboutdata.cpp:40