kmail

popaccount.cpp
1 /*
2  This file is part of KMail, the KDE mail client.
3  Copyright (c) 2000 Don Sanders <sanders@kde.org>
4 
5  Based on popaccount by:
6  Stefan Taferner <taferner@kde.org>
7  Markus Wuebben <markus.wuebben@kde.org>
8 
9  KMail is free software; you can redistribute it and/or modify it
10  under the terms of the GNU General Public License, version 2, as
11  published by the Free Software Foundation.
12 
13  KMail is distributed in the hope that it will be useful, but
14  WITHOUT ANY WARRANTY; without even the implied warranty of
15  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  General Public License for more details.
17 
18  You should have received a copy of the GNU General Public License
19  along with this program; if not, write to the Free Software
20  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22 
23 #ifdef HAVE_CONFIG_H
24 #include <config.h>
25 #endif
26 
27 #include "popaccount.h"
28 
29 #include "broadcaststatus.h"
30 using KPIM::BroadcastStatus;
31 #include "progressmanager.h"
32 #include "kmfoldermgr.h"
33 #include "kmfiltermgr.h"
34 #include "kmpopfiltercnfrmdlg.h"
35 #include "protocols.h"
36 #include "kmglobal.h"
37 #include "util.h"
38 #include "accountmanager.h"
39 
40 #include <kdebug.h>
41 #include <kstandarddirs.h>
42 #include <tdelocale.h>
43 #include <tdemessagebox.h>
44 #include <tdemainwindow.h>
45 #include <tdeio/scheduler.h>
46 #include <tdeio/passdlg.h>
47 #include <tdeconfig.h>
48 using TDEIO::MetaData;
49 
50 #include <tqstylesheet.h>
51 
52 static const unsigned short int pop3DefaultPort = 110;
53 
54 namespace KMail {
55 //-----------------------------------------------------------------------------
56 PopAccount::PopAccount(AccountManager* aOwner, const TQString& aAccountName, uint id)
57  : NetworkAccount(aOwner, aAccountName, id),
58  headerIt(headersOnServer),
59  processMsgsTimer( 0, "processMsgsTimer" )
60 {
61  init();
62  job = 0;
63  mSlave = 0;
64  mPort = defaultPort();
65  stage = Idle;
66  indexOfCurrentMsg = -1;
67  curMsgStrm = 0;
68  processingDelay = 2*100;
69  mProcessing = false;
70  dataCounter = 0;
71  mUidsOfSeenMsgsDict.setAutoDelete( false );
72  mUidsOfNextSeenMsgsDict.setAutoDelete( false );
73 
74  headersOnServer.setAutoDelete(true);
75  connect(&processMsgsTimer,TQ_SIGNAL(timeout()),TQ_SLOT(slotProcessPendingMsgs()));
76  TDEIO::Scheduler::connect(
77  TQ_SIGNAL(slaveError(TDEIO::Slave *, int, const TQString &)),
78  this, TQ_SLOT(slotSlaveError(TDEIO::Slave *, int, const TQString &)));
79 
80  mHeaderDeleteUids.clear();
81  mHeaderDownUids.clear();
82  mHeaderLaterUids.clear();
83 }
84 
85 
86 //-----------------------------------------------------------------------------
87 PopAccount::~PopAccount()
88 {
89  if (job) {
90  job->kill();
91  mMsgsPendingDownload.clear();
92  processRemainingQueuedMessages();
93  saveUidList();
94  }
95 }
96 
97 
98 //-----------------------------------------------------------------------------
99 TQString PopAccount::type(void) const
100 {
101  return "pop";
102 }
103 
104 TQString PopAccount::protocol() const {
105  return useSSL() ? POP_SSL_PROTOCOL : POP_PROTOCOL;
106 }
107 
108 unsigned short int PopAccount::defaultPort() const {
109  return pop3DefaultPort;
110 }
111 
112 //-----------------------------------------------------------------------------
113 void PopAccount::init(void)
114 {
115  NetworkAccount::init();
116 
117  mUsePipelining = false;
118  mLeaveOnServer = false;
119  mLeaveOnServerDays = -1;
120  mLeaveOnServerCount = -1;
121  mLeaveOnServerSize = -1;
122  mFilterOnServer = false;
123  //tz todo
124  mFilterOnServerCheckSize = 50000;
125 }
126 
127 //-----------------------------------------------------------------------------
128 void PopAccount::pseudoAssign( const KMAccount * a ) {
129  slotAbortRequested();
130  NetworkAccount::pseudoAssign( a );
131 
132  const PopAccount * p = dynamic_cast<const PopAccount*>( a );
133  if ( !p ) return;
134 
135  setUsePipelining( p->usePipelining() );
136  setLeaveOnServer( p->leaveOnServer() );
137  setLeaveOnServerDays( p->leaveOnServerDays() );
138  setLeaveOnServerCount( p->leaveOnServerCount() );
139  setLeaveOnServerSize( p->leaveOnServerSize() );
140  setFilterOnServer( p->filterOnServer() );
141  setFilterOnServerCheckSize( p->filterOnServerCheckSize() );
142 }
143 
144 //-----------------------------------------------------------------------------
145 void PopAccount::processNewMail(bool _interactive)
146 {
147  if (stage == Idle) {
148 
149  if ( (mAskAgain || passwd().isEmpty() || mLogin.isEmpty()) &&
150  mAuth != "GSSAPI" ) {
151  TQString passwd = NetworkAccount::passwd();
152  bool b = storePasswd();
153  if (TDEIO::PasswordDialog::getNameAndPassword(mLogin, passwd, &b,
154  i18n("You need to supply a username and a password to access this "
155  "mailbox."), false, TQString(), mName, i18n("Account:"))
156  != TQDialog::Accepted)
157  {
158  checkDone( false, CheckAborted );
159  return;
160  } else {
161  setPasswd( passwd, b );
162  if ( b ) {
163  kmkernel->acctMgr()->writeConfig( true );
164  }
165  mAskAgain = false;
166  }
167  }
168 
169  TQString seenUidList = locateLocal( "data", "kmail/" + mLogin + ":" + "@" +
170  mHost + ":" + TQString("%1").arg(mPort) );
171  TDEConfig config( seenUidList );
172  TQStringList uidsOfSeenMsgs = config.readListEntry( "seenUidList" );
173  TQValueList<int> timeOfSeenMsgs = config.readIntListEntry( "seenUidTimeList" );
174  mUidsOfSeenMsgsDict.clear();
175  mUidsOfSeenMsgsDict.resize( KMail::nextPrime( ( uidsOfSeenMsgs.count() * 11 ) / 10 ) );
176  int idx = 1;
177  for ( TQStringList::ConstIterator it = uidsOfSeenMsgs.begin();
178  it != uidsOfSeenMsgs.end(); ++it, idx++ ) {
179  // we use mUidsOfSeenMsgsDict to just provide fast random access to the
180  // keys, so we can store the index(+1) that corresponds to the index of
181  // mTimeOfSeenMsgsVector for use in PopAccount::slotData()
182  mUidsOfSeenMsgsDict.insert( *it, (const int *)idx );
183  }
184  mTimeOfSeenMsgsVector.clear();
185  mTimeOfSeenMsgsVector.reserve( timeOfSeenMsgs.size() );
186  for ( TQValueList<int>::ConstIterator it = timeOfSeenMsgs.begin();
187  it != timeOfSeenMsgs.end(); ++it) {
188  mTimeOfSeenMsgsVector.append( *it );
189  }
190  // If the counts differ then the config file has presumably been tampered
191  // with and so to avoid possible unwanted message deletion we'll treat
192  // them all as newly seen by clearing the seen times vector
193  if ( mTimeOfSeenMsgsVector.count() != mUidsOfSeenMsgsDict.count() )
194  mTimeOfSeenMsgsVector.clear();
195  TQStringList downloadLater = config.readListEntry( "downloadLater" );
196  for ( TQStringList::Iterator it = downloadLater.begin(); it != downloadLater.end(); ++it ) {
197  mHeaderLaterUids.insert( *it, true );
198  }
199  mUidsOfNextSeenMsgsDict.clear();
200  mTimeOfNextSeenMsgsMap.clear();
201  mSizeOfNextSeenMsgsDict.clear();
202 
203  interactive = _interactive;
204  mUidlFinished = false;
205  startJob();
206  }
207  else {
208  checkDone( false, CheckIgnored );
209  return;
210  }
211 }
212 
213 
214 //-----------------------------------------------------------------------------
215 void PopAccount::readConfig(TDEConfig& config)
216 {
217  NetworkAccount::readConfig(config);
218 
219  mUsePipelining = config.readNumEntry("pipelining", false);
220  mLeaveOnServer = config.readNumEntry("leave-on-server", false);
221  mLeaveOnServerDays = config.readNumEntry("leave-on-server-days", -1);
222  mLeaveOnServerCount = config.readNumEntry("leave-on-server-count", -1);
223  mLeaveOnServerSize = config.readNumEntry("leave-on-server-size", -1);
224  mFilterOnServer = config.readNumEntry("filter-on-server", false);
225  mFilterOnServerCheckSize = config.readUnsignedNumEntry("filter-os-check-size", 50000);
226 }
227 
228 
229 //-----------------------------------------------------------------------------
230 void PopAccount::writeConfig(TDEConfig& config)
231 {
232  NetworkAccount::writeConfig(config);
233 
234  config.writeEntry("pipelining", mUsePipelining);
235  config.writeEntry("leave-on-server", mLeaveOnServer);
236  config.writeEntry("leave-on-server-days", mLeaveOnServerDays);
237  config.writeEntry("leave-on-server-count", mLeaveOnServerCount);
238  config.writeEntry("leave-on-server-size", mLeaveOnServerSize);
239  config.writeEntry("filter-on-server", mFilterOnServer);
240  config.writeEntry("filter-os-check-size", mFilterOnServerCheckSize);
241 }
242 
243 
244 //-----------------------------------------------------------------------------
245 void PopAccount::setUsePipelining(bool b)
246 {
247  mUsePipelining = b;
248 }
249 
250 //-----------------------------------------------------------------------------
251 void PopAccount::setLeaveOnServer(bool b)
252 {
253  mLeaveOnServer = b;
254 }
255 
256 //-----------------------------------------------------------------------------
257 void PopAccount::setLeaveOnServerDays(int days)
258 {
259  mLeaveOnServerDays = days;
260 }
261 
262 //-----------------------------------------------------------------------------
263 void PopAccount::setLeaveOnServerCount(int count)
264 {
265  mLeaveOnServerCount = count;
266 }
267 
268 //-----------------------------------------------------------------------------
269 void PopAccount::setLeaveOnServerSize(int size)
270 {
271  mLeaveOnServerSize = size;
272 }
273 
274 //---------------------------------------------------------------------------
275 void PopAccount::setFilterOnServer(bool b)
276 {
277  mFilterOnServer = b;
278 }
279 
280 //---------------------------------------------------------------------------
281 void PopAccount::setFilterOnServerCheckSize(unsigned int aSize)
282 {
283  mFilterOnServerCheckSize = aSize;
284 }
285 
286 //-----------------------------------------------------------------------------
287 void PopAccount::connectJob() {
288  TDEIO::Scheduler::assignJobToSlave(mSlave, job);
289  connect(job, TQ_SIGNAL( data( TDEIO::Job*, const TQByteArray &)),
290  TQ_SLOT( slotData( TDEIO::Job*, const TQByteArray &)));
291  connect(job, TQ_SIGNAL( result( TDEIO::Job * ) ),
292  TQ_SLOT( slotResult( TDEIO::Job * ) ) );
293  connect(job, TQ_SIGNAL(infoMessage( TDEIO::Job*, const TQString & )),
294  TQ_SLOT( slotMsgRetrieved(TDEIO::Job*, const TQString &)));
295 }
296 
297 
298 //-----------------------------------------------------------------------------
299 void PopAccount::slotCancel()
300 {
301  mMsgsPendingDownload.clear();
302  processRemainingQueuedMessages();
303  saveUidList();
304  slotJobFinished();
305 }
306 
307 
308 //-----------------------------------------------------------------------------
309 void PopAccount::slotProcessPendingMsgs()
310 {
311  if (mProcessing) // not reentrant
312  return;
313  mProcessing = true;
314 
315  bool addedOk;
316  TQValueList<KMMessage*>::Iterator cur = msgsAwaitingProcessing.begin();
317  TQStringList::Iterator curId = msgIdsAwaitingProcessing.begin();
318  TQStringList::Iterator curUid = msgUidsAwaitingProcessing.begin();
319 
320  while (cur != msgsAwaitingProcessing.end()) {
321  // note we can actually end up processing events in processNewMsg
322  // this happens when send receipts is turned on
323  // hence the check for re-entry at the start of this method.
324  // -sanders Update processNewMsg should no longer process events
325 
326  addedOk = processNewMsg(*cur); //added ok? Error displayed if not.
327 
328  if (!addedOk) {
329  mMsgsPendingDownload.clear();
330  msgIdsAwaitingProcessing.clear();
331  msgUidsAwaitingProcessing.clear();
332  break;
333  }
334  else {
335  idsOfMsgsToDelete.append( *curId );
336  mUidsOfNextSeenMsgsDict.insert( *curUid, (const int *)1 );
337  mTimeOfNextSeenMsgsMap.insert( *curUid, time(0) );
338  }
339  ++cur;
340  ++curId;
341  ++curUid;
342  }
343 
344  msgsAwaitingProcessing.clear();
345  msgIdsAwaitingProcessing.clear();
346  msgUidsAwaitingProcessing.clear();
347  mProcessing = false;
348 }
349 
350 
351 //-----------------------------------------------------------------------------
352 void PopAccount::slotAbortRequested()
353 {
354  if (stage == Idle) return;
355  if ( mMailCheckProgressItem )
356  disconnect( mMailCheckProgressItem, TQ_SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
357  this, TQ_SLOT( slotAbortRequested() ) );
358  stage = Quit;
359  if (job) job->kill();
360  job = 0;
361  mSlave = 0;
362  slotCancel();
363 }
364 
365 
366 //-----------------------------------------------------------------------------
367 void PopAccount::startJob()
368 {
369  // Run the precommand
370  if (!runPrecommand(precommand()))
371  {
372  KMessageBox::sorry(0,
373  i18n("Could not execute precommand: %1").arg(precommand()),
374  i18n("KMail Error Message"));
375  checkDone( false, CheckError );
376  return;
377  }
378  // end precommand code
379 
380  KURL url = getUrl();
381 
382  if ( !url.isValid() ) {
383  KMessageBox::error(0, i18n("Source URL is malformed"),
384  i18n("Kioslave Error Message") );
385  return;
386  }
387 
388  mMsgsPendingDownload.clear();
389  idsOfMsgs.clear();
390  mUidForIdMap.clear();
391  idsOfMsgsToDelete.clear();
392  idsOfForcedDeletes.clear();
393 
394  //delete any headers if there are some this have to be done because of check again
395  headersOnServer.clear();
396  headers = false;
397  indexOfCurrentMsg = -1;
398 
399  Q_ASSERT( !mMailCheckProgressItem );
400  TQString escapedName = TQStyleSheet::escape( mName );
401  mMailCheckProgressItem = KPIM::ProgressManager::createProgressItem(
402  "MailCheck" + mName,
403  escapedName,
404  i18n("Preparing transmission from \"%1\"...").arg( escapedName ),
405  true, // can be canceled
406  useSSL() || useTLS() );
407  connect( mMailCheckProgressItem, TQ_SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
408  this, TQ_SLOT( slotAbortRequested() ) );
409 
410  numBytes = 0;
411  numBytesRead = 0;
412  stage = List;
413  mSlave = TDEIO::Scheduler::getConnectedSlave( url, slaveConfig() );
414  if (!mSlave)
415  {
416  slotSlaveError(0, TDEIO::ERR_CANNOT_LAUNCH_PROCESS, url.protocol());
417  return;
418  }
419  url.setPath(TQString("/index"));
420  job = TDEIO::get( url, false, false );
421  connectJob();
422 }
423 
424 MetaData PopAccount::slaveConfig() const {
425  MetaData m = NetworkAccount::slaveConfig();
426 
427  m.insert("progress", "off");
428  m.insert("pipelining", (mUsePipelining) ? "on" : "off");
429  if (mAuth == "PLAIN" || mAuth == "LOGIN" || mAuth == "CRAM-MD5" ||
430  mAuth == "DIGEST-MD5" || mAuth == "NTLM" || mAuth == "GSSAPI") {
431  m.insert("auth", "SASL");
432  m.insert("sasl", mAuth);
433  } else if ( mAuth == "*" )
434  m.insert("auth", "USER");
435  else
436  m.insert("auth", mAuth);
437 
438  return m;
439 }
440 
441 //-----------------------------------------------------------------------------
442 // one message is finished
443 // add data to a KMMessage
444 void PopAccount::slotMsgRetrieved(TDEIO::Job*, const TQString & infoMsg)
445 {
446  if (infoMsg != "message complete") return;
447  KMMessage *msg = new KMMessage;
448  msg->setComplete(true);
449  // Make sure to use LF as line ending to make the processing easier
450  // when piping through external programs
451  uint newSize = Util::crlf2lf( curMsgData.data(), curMsgData.size() );
452  curMsgData.resize( newSize );
453  msg->fromByteArray( curMsgData , true );
454  if (stage == Head)
455  {
456  int size = mMsgsPendingDownload[ headerIt.current()->id() ];
457  kdDebug(5006) << "Size of Message: " << size << endl;
458  msg->setMsgLength( size );
459  headerIt.current()->setHeader(msg);
460  ++headerIt;
461  slotGetNextHdr();
462  } else {
463  //kdDebug(5006) << kfuncinfo << "stage == Retr" << endl;
464  //kdDebug(5006) << "curMsgData.size() = " << curMsgData.size() << endl;
465  msg->setMsgLength( curMsgData.size() );
466  msgsAwaitingProcessing.append(msg);
467  msgIdsAwaitingProcessing.append(idsOfMsgs[indexOfCurrentMsg]);
468  msgUidsAwaitingProcessing.append( mUidForIdMap[idsOfMsgs[indexOfCurrentMsg]] );
469  slotGetNextMsg();
470  }
471 }
472 
473 
474 //-----------------------------------------------------------------------------
475 // finit state machine to cycle trow the stages
476 void PopAccount::slotJobFinished() {
477  TQStringList emptyList;
478  if (stage == List) {
479  kdDebug(5006) << k_funcinfo << "stage == List" << endl;
480  // set the initial size of mUidsOfNextSeenMsgsDict to the number of
481  // messages on the server + 10%
482  mUidsOfNextSeenMsgsDict.resize( KMail::nextPrime( ( idsOfMsgs.count() * 11 ) / 10 ) );
483  KURL url = getUrl();
484  url.setPath(TQString("/uidl"));
485  job = TDEIO::get( url, false, false );
486  connectJob();
487  stage = Uidl;
488  }
489  else if (stage == Uidl) {
490  kdDebug(5006) << k_funcinfo << "stage == Uidl" << endl;
491  mUidlFinished = true;
492 
493  if ( mLeaveOnServer && mUidForIdMap.isEmpty() &&
494  mUidsOfNextSeenMsgsDict.isEmpty() && !idsOfMsgs.isEmpty() ) {
495  KMessageBox::sorry(0, i18n("Your POP3 server (Account: %1) does not support "
496  "the UIDL command: this command is required to determine, in a reliable way, "
497  "which of the mails on the server KMail has already seen before;\n"
498  "the feature to leave the mails on the server will therefore not "
499  "work properly.").arg(NetworkAccount::name()) );
500  // An attempt to work around buggy pop servers, these seem to be popular.
501  mUidsOfNextSeenMsgsDict = mUidsOfSeenMsgsDict;
502  }
503 
504  //check if filter on server
505  if (mFilterOnServer == true) {
506  TQMap<TQString, int>::Iterator hids;
507  for ( hids = mMsgsPendingDownload.begin();
508  hids != mMsgsPendingDownload.end(); hids++ ) {
509  kdDebug(5006) << "Length: " << hids.data() << endl;
510  //check for mails bigger mFilterOnServerCheckSize
511  if ( (unsigned int)hids.data() >= mFilterOnServerCheckSize ) {
512  kdDebug(5006) << "bigger than " << mFilterOnServerCheckSize << endl;
513  headersOnServer.append(new KMPopHeaders( hids.key(),
514  mUidForIdMap[hids.key()],
515  Later));//TODO
516  //set Action if already known
517  if( mHeaderDeleteUids.contains( headersOnServer.current()->uid() ) ) {
518  headersOnServer.current()->setAction(Delete);
519  }
520  else if( mHeaderDownUids.contains( headersOnServer.current()->uid() ) ) {
521  headersOnServer.current()->setAction(Down);
522  }
523  else if( mHeaderLaterUids.contains( headersOnServer.current()->uid() ) ) {
524  headersOnServer.current()->setAction(Later);
525  }
526  }
527  }
528  // delete the uids so that you don't get them twice in the list
529  mHeaderDeleteUids.clear();
530  mHeaderDownUids.clear();
531  mHeaderLaterUids.clear();
532  }
533  // kdDebug(5006) << "Num of Msgs to Filter: " << headersOnServer.count() << endl;
534  // if there are mails which should be checkedc download the headers
535  if ((headersOnServer.count() > 0) && (mFilterOnServer == true)) {
536  headerIt.toFirst();
537  KURL url = getUrl();
538  TQString headerIds;
539  while (headerIt.current())
540  {
541  headerIds += headerIt.current()->id();
542  if (!headerIt.atLast()) headerIds += ",";
543  ++headerIt;
544  }
545  headerIt.toFirst();
546  url.setPath(TQString("/headers/") + headerIds);
547  job = TDEIO::get( url, false, false );
548  connectJob();
549  slotGetNextHdr();
550  stage = Head;
551  }
552  else {
553  stage = Retr;
554  numMsgs = mMsgsPendingDownload.count();
555  numBytesToRead = 0;
556  TQMap<TQString, int>::Iterator len;
557  for ( len = mMsgsPendingDownload.begin();
558  len != mMsgsPendingDownload.end(); len++ )
559  numBytesToRead += len.data();
560  idsOfMsgs = TQStringList( mMsgsPendingDownload.keys() );
561  KURL url = getUrl();
562  url.setPath( "/download/" + idsOfMsgs.join(",") );
563  job = TDEIO::get( url, false, false );
564  connectJob();
565  slotGetNextMsg();
566  processMsgsTimer.start(processingDelay);
567  }
568  }
569  else if (stage == Head) {
570  kdDebug(5006) << k_funcinfo << "stage == Head" << endl;
571 
572  // All headers have been downloaded, check which mail you want to get
573  // data is in list headersOnServer
574 
575  // check if headers apply to a filter
576  // if set the action of the filter
577  KMPopFilterAction action;
578  bool dlgPopup = false;
579  for (headersOnServer.first(); headersOnServer.current(); headersOnServer.next()) {
580  action = (KMPopFilterAction)kmkernel->popFilterMgr()->process(headersOnServer.current()->header());
581  //debug todo
582  switch ( action ) {
583  case NoAction:
584  kdDebug(5006) << "PopFilterAction = NoAction" << endl;
585  break;
586  case Later:
587  kdDebug(5006) << "PopFilterAction = Later" << endl;
588  break;
589  case Delete:
590  kdDebug(5006) << "PopFilterAction = Delete" << endl;
591  break;
592  case Down:
593  kdDebug(5006) << "PopFilterAction = Down" << endl;
594  break;
595  default:
596  kdDebug(5006) << "PopFilterAction = default oops!" << endl;
597  break;
598  }
599  switch ( action ) {
600  case NoAction:
601  //kdDebug(5006) << "PopFilterAction = NoAction" << endl;
602  dlgPopup = true;
603  break;
604  case Later:
605  if (kmkernel->popFilterMgr()->showLaterMsgs())
606  dlgPopup = true;
607  // fall through
608  default:
609  headersOnServer.current()->setAction(action);
610  headersOnServer.current()->setRuleMatched(true);
611  break;
612  }
613  }
614 
615  // if there are some messages which are not coverd by a filter
616  // show the dialog
617  headers = true;
618  if (dlgPopup) {
619  KMPopFilterCnfrmDlg dlg(&headersOnServer, this->name(), kmkernel->popFilterMgr()->showLaterMsgs());
620  dlg.exec();
621  }
622 
623  for (headersOnServer.first(); headersOnServer.current(); headersOnServer.next()) {
624  if (headersOnServer.current()->action() == Delete ||
625  headersOnServer.current()->action() == Later) {
626  //remove entries from the lists when the mails should not be downloaded
627  //(deleted or downloaded later)
628  if ( mMsgsPendingDownload.contains( headersOnServer.current()->id() ) ) {
629  mMsgsPendingDownload.remove( headersOnServer.current()->id() );
630  }
631  if (headersOnServer.current()->action() == Delete) {
632  mHeaderDeleteUids.insert(headersOnServer.current()->uid(), true);
633  mUidsOfNextSeenMsgsDict.insert( headersOnServer.current()->uid(),
634  (const int *)1 );
635  idsOfMsgsToDelete.append(headersOnServer.current()->id());
636  mTimeOfNextSeenMsgsMap.insert( headersOnServer.current()->uid(),
637  time(0) );
638  }
639  else {
640  mHeaderLaterUids.insert(headersOnServer.current()->uid(), true);
641  }
642  }
643  else if (headersOnServer.current()->action() == Down) {
644  mHeaderDownUids.insert(headersOnServer.current()->uid(), true);
645  }
646  }
647 
648  headersOnServer.clear();
649  stage = Retr;
650  numMsgs = mMsgsPendingDownload.count();
651  numBytesToRead = 0;
652  TQMap<TQString, int>::Iterator len;
653  for (len = mMsgsPendingDownload.begin();
654  len != mMsgsPendingDownload.end(); len++)
655  numBytesToRead += len.data();
656  idsOfMsgs = TQStringList( mMsgsPendingDownload.keys() );
657  KURL url = getUrl();
658  url.setPath( "/download/" + idsOfMsgs.join(",") );
659  job = TDEIO::get( url, false, false );
660  connectJob();
661  slotGetNextMsg();
662  processMsgsTimer.start(processingDelay);
663  }
664  else if (stage == Retr) {
665  if ( mMailCheckProgressItem )
666  mMailCheckProgressItem->setProgress( 100 );
667  processRemainingQueuedMessages();
668 
669  mHeaderDeleteUids.clear();
670  mHeaderDownUids.clear();
671  mHeaderLaterUids.clear();
672 
673  kmkernel->folderMgr()->syncAllFolders();
674 
675  KURL url = getUrl();
676  TQMap< TQPair<time_t, TQString>, int > idsToSave;
677  idsToSave.clear();
678  // Check if we want to keep any messages
679  if ( mLeaveOnServer && !idsOfMsgsToDelete.isEmpty() ) {
680  // Keep all messages on server
681  if ( mLeaveOnServerDays == -1 && mLeaveOnServerCount <= 0 &&
682  mLeaveOnServerSize <= 0)
683  idsOfMsgsToDelete.clear();
684  // Delete old messages
685  else if ( mLeaveOnServerDays > 0 && !mTimeOfNextSeenMsgsMap.isEmpty() ) {
686  time_t timeLimit = time(0) - (86400 * mLeaveOnServerDays);
687  kdDebug() << "timeLimit is " << timeLimit << endl;
688  TQStringList::Iterator cur = idsOfMsgsToDelete.begin();
689  for ( ; cur != idsOfMsgsToDelete.end(); ++cur) {
690  time_t msgTime = mTimeOfNextSeenMsgsMap[mUidForIdMap[*cur]];
691  kdDebug() << "id: " << *cur << " msgTime: " << msgTime << endl;
692  if (msgTime >= timeLimit ||
693  !mTimeOfNextSeenMsgsMap[mUidForIdMap[*cur]]) {
694  kdDebug() << "Saving msg id " << *cur << endl;
695  TQPair<time_t, TQString> msg(msgTime, *cur);
696  idsToSave.insert( msg, 1 );
697  }
698  }
699  }
700  // Delete more old messages if there are more than mLeaveOnServerCount
701  if ( mLeaveOnServerCount > 0 ) {
702  int numToDelete = idsToSave.count() - mLeaveOnServerCount;
703  kdDebug() << "numToDelete is " << numToDelete << endl;
704  if ( numToDelete > 0 && (unsigned)numToDelete < idsToSave.count() ) {
705  TQMap< TQPair<time_t, TQString>, int >::Iterator cur = idsToSave.begin();
706  for ( int deleted = 0; deleted < numToDelete && cur != idsToSave.end()
707  ; deleted++, cur++ ) {
708  kdDebug() << "deleting msg id " << cur.key().second << endl;
709  idsToSave.remove( cur );
710  }
711  }
712  else if ( numToDelete > 0 && (unsigned)numToDelete >= idsToSave.count() )
713  idsToSave.clear();
714  }
715  // Delete more old messages until we're under mLeaveOnServerSize MBs
716  if ( mLeaveOnServerSize > 0 ) {
717  double sizeOnServer = 0;
718  TQMap< TQPair<time_t, TQString>, int >::Iterator cur = idsToSave.begin();
719  for ( ; cur != idsToSave.end(); cur++ ) {
720  sizeOnServer +=
721  *mSizeOfNextSeenMsgsDict[ mUidForIdMap[ cur.key().second ] ];
722  }
723  kdDebug() << "sizeOnServer is " << sizeOnServer/(1024*1024) << "MB" << endl;
724  long limitInBytes = mLeaveOnServerSize * ( 1024 * 1024 );
725  for ( cur = idsToSave.begin(); cur != idsToSave.end()
726  && sizeOnServer > limitInBytes; cur++ ) {
727  sizeOnServer -=
728  *mSizeOfNextSeenMsgsDict[ mUidForIdMap[ cur.key().second ] ];
729  idsToSave.remove( cur );
730  }
731  }
732  // Save msgs from deletion
733  TQMap< TQPair<time_t, TQString>, int >::Iterator it = idsToSave.begin();
734  kdDebug() << "Going to save " << idsToSave.count() << endl;
735  for ( ; it != idsToSave.end(); ++it ) {
736  kdDebug() << "saving msg id " << it.key().second << endl;
737  idsOfMsgsToDelete.remove( it.key().second );
738  }
739  }
740 
741  if ( !idsOfForcedDeletes.isEmpty() ) {
742  idsOfMsgsToDelete += idsOfForcedDeletes;
743  idsOfForcedDeletes.clear();
744  }
745 
746  // If there are messages to delete then delete them
747  if ( !idsOfMsgsToDelete.isEmpty() ) {
748  stage = Dele;
749  if ( mMailCheckProgressItem )
750  mMailCheckProgressItem->setStatus(
751  i18n( "Fetched 1 message from %1. Deleting messages from server...",
752  "Fetched %n messages from %1. Deleting messages from server...",
753  numMsgs )
754  .arg( mHost ) );
755  url.setPath("/remove/" + idsOfMsgsToDelete.join(","));
756  kdDebug(5006) << "url: " << url.prettyURL() << endl;
757  } else {
758  stage = Quit;
759  if ( mMailCheckProgressItem )
760  mMailCheckProgressItem->setStatus(
761  i18n( "Fetched 1 message from %1. Terminating transmission...",
762  "Fetched %n messages from %1. Terminating transmission...",
763  numMsgs )
764  .arg( mHost ) );
765  url.setPath(TQString("/commit"));
766  kdDebug(5006) << "url: " << url.prettyURL() << endl;
767  }
768  job = TDEIO::get( url, false, false );
769  connectJob();
770  }
771  else if (stage == Dele) {
772  kdDebug(5006) << k_funcinfo << "stage == Dele" << endl;
773  // remove the uids of all messages which have been deleted
774  for ( TQStringList::ConstIterator it = idsOfMsgsToDelete.begin();
775  it != idsOfMsgsToDelete.end(); ++it ) {
776  mUidsOfNextSeenMsgsDict.remove( mUidForIdMap[*it] );
777  }
778  idsOfMsgsToDelete.clear();
779  if ( mMailCheckProgressItem )
780  mMailCheckProgressItem->setStatus(
781  i18n( "Fetched 1 message from %1. Terminating transmission...",
782  "Fetched %n messages from %1. Terminating transmission...",
783  numMsgs )
784  .arg( mHost ) );
785  KURL url = getUrl();
786  url.setPath(TQString("/commit"));
787  job = TDEIO::get( url, false, false );
788  stage = Quit;
789  connectJob();
790  }
791  else if (stage == Quit) {
792  kdDebug(5006) << k_funcinfo << "stage == Quit" << endl;
793  saveUidList();
794  job = 0;
795  if (mSlave) TDEIO::Scheduler::disconnectSlave(mSlave);
796  mSlave = 0;
797  stage = Idle;
798  if( mMailCheckProgressItem ) { // do this only once...
799  bool canceled = !kmkernel || kmkernel->mailCheckAborted() || mMailCheckProgressItem->canceled();
800  int numMessages = canceled ? indexOfCurrentMsg : idsOfMsgs.count();
801  BroadcastStatus::instance()->setStatusMsgTransmissionCompleted(
802  this->name(), numMessages, numBytes, numBytesRead, numBytesToRead, mLeaveOnServer, mMailCheckProgressItem );
803  // set mMailCheckProgressItem = 0 before calling setComplete() to prevent
804  // a race condition
805  ProgressItem *savedMailCheckProgressItem = mMailCheckProgressItem;
806  mMailCheckProgressItem = 0;
807  savedMailCheckProgressItem->setComplete(); // that will delete it
808  checkDone( ( numMessages > 0 ), canceled ? CheckAborted : CheckOK );
809  }
810  }
811 }
812 
813 
814 //-----------------------------------------------------------------------------
815 void PopAccount::processRemainingQueuedMessages()
816 {
817  kdDebug(5006) << k_funcinfo << endl;
818  slotProcessPendingMsgs(); // Force processing of any messages still in the queue
819  processMsgsTimer.stop();
820 
821  stage = Quit;
822  if ( kmkernel && kmkernel->folderMgr() ) {
823  kmkernel->folderMgr()->syncAllFolders();
824  }
825 }
826 
827 
828 //-----------------------------------------------------------------------------
829 void PopAccount::saveUidList()
830 {
831  kdDebug(5006) << k_funcinfo << endl;
832  // Don't update the seen uid list unless we successfully got
833  // a new list from the server
834  if (!mUidlFinished) return;
835 
836  TQStringList uidsOfNextSeenMsgs;
837  TQValueList<int> seenUidTimeList;
838  TQDictIterator<int> it( mUidsOfNextSeenMsgsDict );
839  for( ; it.current(); ++it ) {
840  uidsOfNextSeenMsgs.append( it.currentKey() );
841  seenUidTimeList.append( mTimeOfNextSeenMsgsMap[it.currentKey()] );
842  }
843  TQString seenUidList = locateLocal( "data", "kmail/" + mLogin + ":" + "@" +
844  mHost + ":" + TQString("%1").arg(mPort) );
845  TDEConfig config( seenUidList );
846  config.writeEntry( "seenUidList", uidsOfNextSeenMsgs );
847  config.writeEntry( "seenUidTimeList", seenUidTimeList );
848  config.writeEntry( "downloadLater", TQStringList( mHeaderLaterUids.keys() ) );
849  config.sync();
850 }
851 
852 
853 //-----------------------------------------------------------------------------
854 void PopAccount::slotGetNextMsg()
855 {
856  TQMap<TQString, int>::Iterator next = mMsgsPendingDownload.begin();
857 
858  curMsgData.resize(0);
859  numMsgBytesRead = 0;
860  curMsgLen = 0;
861  delete curMsgStrm;
862  curMsgStrm = 0;
863 
864  if ( next != mMsgsPendingDownload.end() ) {
865  // get the next message
866  int nextLen = next.data();
867  curMsgStrm = new TQDataStream( curMsgData, IO_WriteOnly );
868  curMsgLen = nextLen;
869  ++indexOfCurrentMsg;
870  kdDebug(5006) << TQString("Length of message about to get %1").arg( nextLen ) << endl;
871  mMsgsPendingDownload.remove( next.key() );
872  }
873 }
874 
875 
876 //-----------------------------------------------------------------------------
877 void PopAccount::slotData( TDEIO::Job* job, const TQByteArray &data)
878 {
879  if (data.size() == 0) {
880  kdDebug(5006) << "Data: <End>" << endl;
881  if ((stage == Retr) && (numMsgBytesRead < curMsgLen))
882  numBytesRead += curMsgLen - numMsgBytesRead;
883  else if (stage == Head){
884  kdDebug(5006) << "Head: <End>" << endl;
885  }
886  return;
887  }
888 
889  int oldNumMsgBytesRead = numMsgBytesRead;
890  if (stage == Retr) {
891  headers = false;
892  curMsgStrm->writeRawBytes( data.data(), data.size() );
893  numMsgBytesRead += data.size();
894  if (numMsgBytesRead > curMsgLen)
895  numMsgBytesRead = curMsgLen;
896  numBytesRead += numMsgBytesRead - oldNumMsgBytesRead;
897  dataCounter++;
898  if ( mMailCheckProgressItem &&
899  ( dataCounter % 5 == 0 ||
900  ( indexOfCurrentMsg + 1 == numMsgs && numMsgBytesRead == curMsgLen ) ) )
901  {
902  TQString msg;
903  if (numBytes != numBytesToRead && mLeaveOnServer)
904  {
905  msg = i18n("Fetching message %1 of %2 (%3 of %4 KB) for %5@%6 "
906  "(%7 KB remain on the server).")
907  .arg(indexOfCurrentMsg+1).arg(numMsgs).arg(numBytesRead/1024)
908  .arg(numBytesToRead/1024).arg(mLogin).arg(mHost).arg(numBytes/1024);
909  }
910  else
911  {
912  msg = i18n("Fetching message %1 of %2 (%3 of %4 KB) for %5@%6.")
913  .arg(indexOfCurrentMsg+1).arg(numMsgs).arg(numBytesRead/1024)
914  .arg(numBytesToRead/1024).arg(mLogin).arg(mHost);
915  }
916  mMailCheckProgressItem->setStatus( msg );
917  mMailCheckProgressItem->setProgress(
918  (numBytesToRead <= 100) ? 50 // We never know what the server tells us
919  // This way of dividing is required for > 21MB of mail
920  : (numBytesRead / (numBytesToRead / 100)) );
921  }
922  return;
923  }
924 
925  if (stage == Head) {
926  curMsgStrm->writeRawBytes( data.data(), data.size() );
927  return;
928  }
929 
930  // otherwise stage is List Or Uidl
931  TQString qdata = data;
932  qdata = qdata.simplifyWhiteSpace(); // Workaround for Maillennium POP3/UNIBOX
933  int spc = qdata.find( ' ' );
934  if ( stage == List ) {
935  if ( spc > 0 ) {
936  TQString length = qdata.mid(spc+1);
937  if (length.find(' ') != -1) length.truncate(length.find(' '));
938  int len = length.toInt();
939  numBytes += len;
940  TQString id = qdata.left(spc);
941  idsOfMsgs.append( id );
942  mMsgsPendingDownload.insert( id, len );
943  }
944  else {
945  stage = Idle;
946  if ( job ) job->kill();
947  job = 0;
948  mSlave = 0;
949  KMessageBox::error( 0, i18n( "Unable to complete LIST operation." ),
950  i18n( "Invalid Response From Server") );
951  return;
952  }
953  }
954  else { // stage == Uidl
955  Q_ASSERT ( stage == Uidl);
956 
957  TQString id;
958  TQString uid;
959 
960  if ( spc <= 0 ) {
961  // an invalid uidl line. we might just need to skip it, but
962  // some servers generate invalid uids with valid ids. in that
963  // case we will just make up a uid - which will cause us to
964  // not cache the document, but we will be able to interoperate
965 
966  int testid = atoi ( qdata.ascii() );
967  if ( testid < 1 ) {
968  // we'll just have to skip this
969  kdDebug(5006) << "PopAccount::slotData skipping UIDL entry due to parse error "
970  << endl << qdata.ascii() << endl;
971  return;
972  }
973  id.setNum (testid, 10);
974 
975  TQString datestring, serialstring;
976 
977  serialstring.setNum ( ++dataCounter, 10 );
978  datestring.setNum ( time(NULL),10 );
979  uid = TQString( "uidlgen" ) + datestring + TQString( "." ) + serialstring;
980  kdDebug(5006) << "PopAccount::slotData message " << id.ascii()
981  << "%d has bad UIDL, cannot keep a copy on server" << endl;
982  idsOfForcedDeletes.append( id );
983  }
984  else {
985  id = qdata.left( spc );
986  uid = qdata.mid( spc + 1 );
987  }
988 
989  int *size = new int; //malloc(size_of(int));
990  *size = mMsgsPendingDownload[id];
991  mSizeOfNextSeenMsgsDict.insert( uid, size );
992  if ( mUidsOfSeenMsgsDict.find( uid ) != 0 ) {
993  if ( mMsgsPendingDownload.contains( id ) ) {
994  mMsgsPendingDownload.remove( id );
995  }
996  else
997  kdDebug(5006) << "PopAccount::slotData synchronization failure." << endl;
998  idsOfMsgsToDelete.append( id );
999  mUidsOfNextSeenMsgsDict.insert( uid, (const int *)1 );
1000  if ( mTimeOfSeenMsgsVector.empty() ) {
1001  mTimeOfNextSeenMsgsMap.insert( uid, time(0) );
1002  }
1003  else {
1004  // cast the int* with a long to can convert it to a int, BTW
1005  // works with g++-4.0 and amd64
1006  mTimeOfNextSeenMsgsMap.insert( uid, mTimeOfSeenMsgsVector[(int)( long )
1007  mUidsOfSeenMsgsDict[uid] - 1] );
1008  }
1009  }
1010  mUidForIdMap.insert( id, uid );
1011  }
1012 }
1013 
1014 //-----------------------------------------------------------------------------
1015 void PopAccount::slotResult( TDEIO::Job* )
1016 {
1017  if (!job) return;
1018  if ( job->error() )
1019  {
1020  if (interactive) {
1021  if (headers) { // nothing to be done for headers
1022  idsOfMsgs.clear();
1023  }
1024  if (stage == Head && job->error() == TDEIO::ERR_COULD_NOT_READ)
1025  {
1026  KMessageBox::error(0, i18n("Your server does not support the "
1027  "TOP command. Therefore it is not possible to fetch the headers "
1028  "of large emails first, before downloading them."));
1029  slotCancel();
1030  return;
1031  }
1032  // force the dialog to be shown next time the account is checked
1033  if (!mStorePasswd) mPasswd = "";
1034  job->showErrorDialog();
1035  }
1036  slotCancel();
1037  }
1038  else
1039  slotJobFinished();
1040 }
1041 
1042 
1043 //-----------------------------------------------------------------------------
1044 void PopAccount::slotSlaveError(TDEIO::Slave *aSlave, int error,
1045  const TQString &errorMsg)
1046 {
1047  if (aSlave != mSlave) return;
1048  if (error == TDEIO::ERR_SLAVE_DIED) mSlave = 0;
1049 
1050  // explicitely disconnect the slave if the connection went down
1051  if ( error == TDEIO::ERR_CONNECTION_BROKEN && mSlave ) {
1052  TDEIO::Scheduler::disconnectSlave( mSlave );
1053  mSlave = 0;
1054  }
1055 
1056  if (interactive && kmkernel) {
1057  KMessageBox::error(kmkernel->mainWin(), TDEIO::buildErrorString(error, errorMsg));
1058  }
1059 
1060 
1061  stage = Quit;
1062  if (error == TDEIO::ERR_COULD_NOT_LOGIN && !mStorePasswd)
1063  mAskAgain = true;
1064  /* We need a timer, otherwise slotSlaveError of the next account is also
1065  executed, if it reuses the slave, because the slave member variable
1066  is changed too early */
1067  TQTimer::singleShot(0, this, TQ_SLOT(slotCancel()));
1068 }
1069 
1070 //-----------------------------------------------------------------------------
1071 void PopAccount::slotGetNextHdr(){
1072  kdDebug(5006) << "slotGetNextHeader" << endl;
1073 
1074  curMsgData.resize(0);
1075  delete curMsgStrm;
1076  curMsgStrm = 0;
1077 
1078  curMsgStrm = new TQDataStream( curMsgData, IO_WriteOnly );
1079 }
1080 
1081 void PopAccount::killAllJobs( bool ) {
1082  // must reimpl., but we don't use it yet
1083 }
1084 
1085 } // namespace KMail
1086 #include "popaccount.moc"
This is a Mime Message.
Definition: kmmessage.h:68
void setComplete(bool v)
Set if the message is a complete message.
Definition: kmmessage.h:869
The account manager is responsible for creating accounts of various types via the factory method crea...
KMail account for pop mail account.
Definition: popaccount.h:27
bool filterOnServer(void) const
Shall messages be filter on the server (TRUE) or not (FALSE).
Definition: popaccount.h:78
bool usePipelining(void) const
Sending of several commands at once.
Definition: popaccount.h:46
int leaveOnServerDays(void) const
If value is positive, leave mail on the server for so many days.
Definition: popaccount.h:59
int leaveOnServerSize(void) const
If value is positive, leave so many MBs on the server.
Definition: popaccount.h:71
bool leaveOnServer(void) const
Shall messages be left on the server upon retreival (TRUE) or deleted (FALSE).
Definition: popaccount.h:53
int leaveOnServerCount(void) const
If value is positive, leave so many messages on the server.
Definition: popaccount.h:65
unsigned int filterOnServerCheckSize(void) const
Size of messages which should be check on the pop server before download.
Definition: popaccount.h:85
folderdiaquotatab.h
Definition: aboutdata.cpp:40