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>
38 using KPIM::ProgressItem;
39 using 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 
51 namespace KMail {
52 
53 SearchJob::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 
62 SearchJob::~SearchJob()
63 {
64 }
65 
66 void SearchJob::execute()
67 {
68  if ( mSerNum == 0 )
69  {
70  searchCompleteFolder();
71  } else {
72  searchSingleMessage();
73  }
74 }
75 
76 //-----------------------------------------------------------------------------
77 void 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 //-----------------------------------------------------------------------------
108 TQString 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 //-----------------------------------------------------------------------------
179 void 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 //-----------------------------------------------------------------------------
210 bool 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 //-----------------------------------------------------------------------------
222 void 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 //-----------------------------------------------------------------------------
294 void 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 //-----------------------------------------------------------------------------
347 void 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 //-----------------------------------------------------------------------------
365 void 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 //-----------------------------------------------------------------------------
398 void 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 //-----------------------------------------------------------------------------
432 void 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 //-----------------------------------------------------------------------------
442 bool 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