krop's commit fixes my problem in a better way, reverting
[kdepim.git] / kmail / popaccount.cpp
blob191b39db9000c315bee5911547414185e3a3f78b
1 /*
2 This file is part of KMail, the KDE mail client.
3 Copyright (c) 2000 Don Sanders <sanders@kde.org>
5 Based on popaccount by:
6 Stefan Taferner <taferner@kde.org>
7 Markus Wuebben <markus.wuebben@kde.org>
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.
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.
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
24 #include "popaccount.h"
26 #include "broadcaststatus.h"
27 using KPIM::BroadcastStatus;
28 #include "progressmanager.h"
29 #include "kmfoldermgr.h"
30 #include "kmfiltermgr.h"
31 #include "kmpopfiltercnfrmdlg.h"
32 #include "protocols.h"
33 #include "kmglobal.h"
34 #include "util.h"
35 #include "accountmanager.h"
37 #include <kdebug.h>
38 #include <kstandarddirs.h>
39 #include <klocale.h>
40 #include <kmessagebox.h>
41 #include <kmainwindow.h>
42 #include <kio/scheduler.h>
43 #include <kio/passworddialog.h>
44 #include <kconfig.h>
45 #include <kconfiggroup.h>
46 #include <kio/jobuidelegate.h>
47 #include <knotification.h>
49 using KIO::MetaData;
51 static const unsigned short int pop3DefaultPort = 110;
53 namespace KMail {
54 //-----------------------------------------------------------------------------
55 PopAccount::PopAccount(AccountManager* aOwner, const QString& aAccountName, uint id)
56 : NetworkAccount(aOwner, aAccountName, id),
57 mPopFilterConfirmationDialog( 0 ),
58 mHeaderIndex( 0 )
60 init();
61 job = 0;
62 mSlave = 0;
63 mPort = defaultPort();
64 stage = Idle;
65 indexOfCurrentMsg = -1;
66 curMsgStrm = 0;
67 processingDelay = 2*100;
68 mProcessing = false;
69 dataCounter = 0;
71 connect(&processMsgsTimer,SIGNAL(timeout()),SLOT(slotProcessPendingMsgs()));
72 KIO::Scheduler::connect(
73 SIGNAL(slaveError(KIO::Slave *, int, const QString &)),
74 this, SLOT(slotSlaveError(KIO::Slave *, int, const QString &)));
76 mHeaderDeleteUids.clear();
77 mHeaderDownUids.clear();
78 mHeaderLaterUids.clear();
82 //-----------------------------------------------------------------------------
83 PopAccount::~PopAccount()
85 if (job) {
86 job->kill();
87 mMsgsPendingDownload.clear();
88 processRemainingQueuedMessages();
89 saveUidList();
93 //-----------------------------------------------------------------------------
94 QString PopAccount::protocol() const {
95 return useSSL() ? POP_SSL_PROTOCOL : POP_PROTOCOL;
98 unsigned short int PopAccount::defaultPort() const {
99 return pop3DefaultPort;
102 //-----------------------------------------------------------------------------
103 void PopAccount::init(void)
105 NetworkAccount::init();
107 mUsePipelining = false;
108 mLeaveOnServer = false;
109 mLeaveOnServerDays = -1;
110 mLeaveOnServerCount = -1;
111 mLeaveOnServerSize = -1;
112 mFilterOnServer = false;
113 //tz todo
114 mFilterOnServerCheckSize = 50000;
117 //-----------------------------------------------------------------------------
118 void PopAccount::cancelMailCheck()
120 slotAbortRequested();
123 //-----------------------------------------------------------------------------
124 void PopAccount::pseudoAssign( const KMAccount * a ) {
125 slotAbortRequested();
126 NetworkAccount::pseudoAssign( a );
128 const PopAccount * p = dynamic_cast<const PopAccount*>( a );
129 if ( !p ) return;
131 setUsePipelining( p->usePipelining() );
132 setLeaveOnServer( p->leaveOnServer() );
133 setLeaveOnServerDays( p->leaveOnServerDays() );
134 setLeaveOnServerCount( p->leaveOnServerCount() );
135 setLeaveOnServerSize( p->leaveOnServerSize() );
136 setFilterOnServer( p->filterOnServer() );
137 setFilterOnServerCheckSize( p->filterOnServerCheckSize() );
140 //-----------------------------------------------------------------------------
141 void PopAccount::processNewMail(bool _interactive)
143 if (stage == Idle) {
145 if ( (mAskAgain || passwd().isEmpty() || mLogin.isEmpty()) &&
146 mAuth != "GSSAPI" ) {
147 QString passwd = NetworkAccount::passwd();
148 bool b = storePasswd();
149 if (KIO::PasswordDialog::getNameAndPassword(mLogin, passwd, &b,
150 i18n("You need to supply a username and a password to access this "
151 "mailbox."), false, QString(), mName, i18n("Account:"))
152 != KDialog::Accepted)
154 checkDone( false, CheckAborted );
155 return;
156 } else {
157 setPasswd( passwd, b );
158 if ( b ) {
159 kmkernel->acctMgr()->writeConfig( true );
161 mAskAgain = false;
165 QString seenUidList = KStandardDirs::locateLocal( "data", "kmail/" + mLogin + ':' + '@' +
166 mHost + ':' + QString("%1").arg(mPort) );
167 KConfig config( seenUidList );
168 KConfigGroup group( &config, "<default>" );
169 QStringList uidsOfSeenMsgs = group.readEntry( "seenUidList", QStringList() );
170 mUidsOfSeenMsgsDict.clear();
171 mUidsOfSeenMsgsDict.reserve( KMail::nextPrime( ( uidsOfSeenMsgs.count() * 11 ) / 10 ) );
172 for ( int i = 0; i < uidsOfSeenMsgs.count(); ++i ) {
173 // we use mUidsOfSeenMsgsDict to just provide fast random access to the
174 // keys, so we can store the index that corresponds to the index of
175 // mTimeOfSeenMsgsVector for use in PopAccount::slotData()
176 mUidsOfSeenMsgsDict.insert( uidsOfSeenMsgs[i].toLatin1(), i );
178 QList<int> timeOfSeenMsgs = group.readEntry( "seenUidTimeList",QList<int>() );
179 // If the counts differ then the config file has presumably been tampered
180 // with and so to avoid possible unwanted message deletion we'll treat
181 // them all as newly seen by clearing the seen times vector
182 if ( timeOfSeenMsgs.count() == mUidsOfSeenMsgsDict.count() )
183 mTimeOfSeenMsgsVector = timeOfSeenMsgs.toVector();
184 else
185 mTimeOfSeenMsgsVector.clear();
186 QStringList downloadLater = group.readEntry( "downloadLater", QStringList() );
187 for ( int i = 0; i < downloadLater.count(); ++i ) {
188 mHeaderLaterUids.insert( downloadLater[i].toLatin1() );
190 mUidsOfNextSeenMsgsDict.clear();
191 mTimeOfNextSeenMsgsMap.clear();
192 mSizeOfNextSeenMsgsDict.clear();
194 interactive = _interactive;
195 mUidlFinished = false;
196 startJob();
198 else {
199 checkDone( false, CheckIgnored );
200 return;
205 //-----------------------------------------------------------------------------
206 void PopAccount::readConfig(KConfigGroup& config)
208 NetworkAccount::readConfig(config);
210 mUsePipelining = config.readEntry("pipelining", false );
211 mLeaveOnServer = config.readEntry("leave-on-server", false );
212 mLeaveOnServerDays = config.readEntry("leave-on-server-days", -1 );
213 mLeaveOnServerCount = config.readEntry("leave-on-server-count", -1 );
214 mLeaveOnServerSize = config.readEntry("leave-on-server-size", -1 );
215 mFilterOnServer = config.readEntry("filter-on-server", false );
216 mFilterOnServerCheckSize = config.readEntry("filter-os-check-size", (uint) 50000 );
220 //-----------------------------------------------------------------------------
221 void PopAccount::writeConfig(KConfigGroup& config)
223 NetworkAccount::writeConfig(config);
225 config.writeEntry("pipelining", mUsePipelining);
226 config.writeEntry("leave-on-server", mLeaveOnServer);
227 config.writeEntry("leave-on-server-days", mLeaveOnServerDays);
228 config.writeEntry("leave-on-server-count", mLeaveOnServerCount);
229 config.writeEntry("leave-on-server-size", mLeaveOnServerSize);
230 config.writeEntry("filter-on-server", mFilterOnServer);
231 config.writeEntry("filter-os-check-size", mFilterOnServerCheckSize);
235 //-----------------------------------------------------------------------------
236 void PopAccount::setUsePipelining(bool b)
238 mUsePipelining = b;
241 //-----------------------------------------------------------------------------
242 void PopAccount::setLeaveOnServer(bool b)
244 mLeaveOnServer = b;
247 //-----------------------------------------------------------------------------
248 void PopAccount::setLeaveOnServerDays(int days)
250 mLeaveOnServerDays = days;
253 //-----------------------------------------------------------------------------
254 void PopAccount::setLeaveOnServerCount(int count)
256 mLeaveOnServerCount = count;
259 //-----------------------------------------------------------------------------
260 void PopAccount::setLeaveOnServerSize(int size)
262 mLeaveOnServerSize = size;
265 //---------------------------------------------------------------------------
266 void PopAccount::setFilterOnServer(bool b)
268 mFilterOnServer = b;
271 //---------------------------------------------------------------------------
272 void PopAccount::setFilterOnServerCheckSize(unsigned int aSize)
274 mFilterOnServerCheckSize = aSize;
277 //-----------------------------------------------------------------------------
278 void PopAccount::connectJob() {
279 KIO::Scheduler::assignJobToSlave(mSlave, job);
280 connect(job, SIGNAL( data( KIO::Job*, const QByteArray &)),
281 SLOT( slotData( KIO::Job*, const QByteArray &)));
282 connect(job, SIGNAL( result( KJob * ) ),
283 SLOT( slotResult( KJob * ) ) );
284 connect(job, SIGNAL(infoMessage( KJob*, const QString &, const QString & )),
285 SLOT( slotMsgRetrieved(KJob*, const QString &, const QString &)));
289 //-----------------------------------------------------------------------------
290 void PopAccount::slotCancel()
292 mMsgsPendingDownload.clear();
293 processRemainingQueuedMessages();
294 saveUidList();
295 slotJobFinished();
297 // Close the pop filter confirmation dialog. Otherwise, KMail crashes because
298 // slotJobFinished(), which creates that dialog, will try to continue downloading
299 // when the user closes the dialog.
300 if ( mPopFilterConfirmationDialog ) {
302 // Disconnect the signal, as we are already called from slotAbortRequested()
303 disconnect( mPopFilterConfirmationDialog, SIGNAL( rejected() ),
304 this, SLOT( slotAbortRequested() ) );
305 mPopFilterConfirmationDialog->reject();
310 //-----------------------------------------------------------------------------
311 void PopAccount::slotProcessPendingMsgs()
313 if (mProcessing) // not reentrant
314 return;
315 mProcessing = true;
317 while ( !msgsAwaitingProcessing.isEmpty() ) {
318 // note we can actually end up processing events in processNewMsg
319 // this happens when send receipts is turned on
320 // hence the check for re-entry at the start of this method.
321 // -sanders Update processNewMsg should no longer process events
323 KMMessage *msg = msgsAwaitingProcessing.dequeue();
324 const bool addedOk = processNewMsg( msg ); //added ok? Error displayed if not.
326 if ( !addedOk ) {
327 kWarning() << "Error while processing new mail, aborting mail check.";
328 mMsgsPendingDownload.clear();
329 slotAbortRequested();
330 break;
333 const QByteArray curId = msgIdsAwaitingProcessing.dequeue();
334 const QByteArray curUid = msgUidsAwaitingProcessing.dequeue();
335 idsOfMsgsToDelete.insert( curId );
336 mUidsOfNextSeenMsgsDict.insert( curUid, 1 );
337 mTimeOfNextSeenMsgsMap.insert( curUid, time(0) );
340 msgsAwaitingProcessing.clear();
341 msgIdsAwaitingProcessing.clear();
342 msgUidsAwaitingProcessing.clear();
343 mProcessing = false;
347 //-----------------------------------------------------------------------------
348 void PopAccount::slotAbortRequested()
350 kDebug();
351 if (stage == Idle)
352 return;
353 disconnect( mMailCheckProgressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
354 this, SLOT( slotAbortRequested() ) );
355 stage = Quit;
356 if (job)
357 job->kill();
358 job = 0;
359 mSlave = 0;
360 slotCancel();
364 //-----------------------------------------------------------------------------
365 void PopAccount::startJob()
367 // Run the precommand
368 if ( !runPrecommand(precommand() ) ) {
369 checkDone( false, CheckError );
370 return;
372 // end precommand code
374 KUrl url = getUrl();
376 if ( !url.isValid() ) {
377 KMessageBox::error(0, i18n("Source URL is malformed"),
378 i18n("Kioslave Error Message") );
379 return;
382 mMsgsPendingDownload.clear();
383 idsOfMsgs.clear();
384 mUidForIdMap.clear();
385 idsOfMsgsToDelete.clear();
386 idsOfForcedDeletes.clear();
387 //delete any headers if there are some this have to be done because of check again
388 qDeleteAll( mHeadersOnServer );
389 mHeadersOnServer.clear();
390 headers = false;
391 indexOfCurrentMsg = -1;
393 Q_ASSERT( !mMailCheckProgressItem );
394 mMailCheckProgressItem = KPIM::ProgressManager::createProgressItem(
395 "MailCheck" + mName,
396 mName,
397 i18n("Preparing transmission from \"%1\"...", mName),
398 true, // can be canceled
399 useSSL() || useTLS() );
400 connect( mMailCheckProgressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
401 this, SLOT( slotAbortRequested() ) );
403 numBytes = 0;
404 numBytesRead = 0;
405 stage = List;
406 mSlave = KIO::Scheduler::getConnectedSlave( url, slaveConfig() );
407 if (!mSlave)
409 slotSlaveError(0, KIO::ERR_CANNOT_LAUNCH_PROCESS, url.protocol());
410 return;
412 url.setPath( "/index" );
413 job = KIO::get( url, KIO::NoReload, KIO::HideProgressInfo );
414 connectJob();
417 MetaData PopAccount::slaveConfig() const {
418 MetaData m = NetworkAccount::slaveConfig();
420 m.insert("progress", "off");
421 m.insert("pipelining", (mUsePipelining) ? "on" : "off");
422 if (mAuth == "PLAIN" || mAuth == "LOGIN" || mAuth == "CRAM-MD5" ||
423 mAuth == "DIGEST-MD5" || mAuth == "NTLM" || mAuth == "GSSAPI") {
424 m.insert("auth", "SASL");
425 m.insert("sasl", mAuth);
426 } else if ( mAuth == "*" )
427 m.insert("auth", "USER");
428 else
429 m.insert("auth", mAuth);
431 return m;
434 //-----------------------------------------------------------------------------
435 // one message is finished
436 // add data to a KMMessage
437 void PopAccount::slotMsgRetrieved(KJob*, const QString & infoMsg, const QString &)
439 if (infoMsg != "message complete") return;
440 KMMessage *msg = new KMMessage;
441 msg->setComplete(true);
442 // Make sure to use LF as line ending to make the processing easier
443 // when piping through external programs
444 uint newSize = Util::crlf2lf( curMsgData.data(), curMsgData.size() );
445 curMsgData.resize( newSize );
446 msg->fromString( curMsgData, true );
447 if ( stage == Head ) {
448 KMPopHeaders *header = mHeadersOnServer[ mHeaderIndex ];
449 int size = mMsgsPendingDownload[ header->id() ];
450 kDebug() << "Size of Message:" << size;
451 msg->setMsgLength( size );
452 header->setHeader( msg );
453 ++mHeaderIndex;
454 slotGetNextHdr();
455 } else {
456 //kDebug() << kfuncinfo <<"stage == Retr";
457 //kDebug() << "curMsgData.size() =" << curMsgData.size();
458 msg->setMsgLength( curMsgData.size() );
459 msgsAwaitingProcessing.enqueue( msg );
460 msgIdsAwaitingProcessing.enqueue( idsOfMsgs[indexOfCurrentMsg] );
461 msgUidsAwaitingProcessing.enqueue( mUidForIdMap[ idsOfMsgs[indexOfCurrentMsg] ] );
462 slotGetNextMsg();
467 //-----------------------------------------------------------------------------
468 // finit state machine to cycle trow the stages
469 void PopAccount::slotJobFinished() {
470 QStringList emptyList;
471 if (stage == List) {
472 kDebug() << "stage == List";
473 // set the initial size of mUidsOfNextSeenMsgsDict to the number of
474 // messages on the server + 10%
475 mUidsOfNextSeenMsgsDict.reserve( KMail::nextPrime( ( idsOfMsgs.count() * 11 ) / 10 ) );
476 KUrl url = getUrl();
477 url.setPath( "/uidl" );
478 job = KIO::get( url, KIO::NoReload, KIO::HideProgressInfo );
479 connectJob();
480 stage = Uidl;
482 else if (stage == Uidl) {
483 kDebug() << "stage == Uidl";
484 mUidlFinished = true;
486 if ( mLeaveOnServer && mUidForIdMap.isEmpty() &&
487 mUidsOfNextSeenMsgsDict.isEmpty() && !idsOfMsgs.isEmpty() ) {
488 KMessageBox::sorry(0, i18n("Your POP3 server (Account: %1) does not support "
489 "the UIDL command: this command is required to determine, in a reliable way, "
490 "which of the mails on the server KMail has already seen before;\n"
491 "the feature to leave the mails on the server will therefore not "
492 "work properly.", NetworkAccount::name()) );
493 // An attempt to work around buggy pop servers, these seem to be popular.
494 mUidsOfNextSeenMsgsDict = mUidsOfSeenMsgsDict;
497 //check if filter on server
498 if (mFilterOnServer == true) {
499 for ( QMap<QByteArray, int>::const_iterator hids = mMsgsPendingDownload.constBegin();
500 hids != mMsgsPendingDownload.constEnd(); ++hids ) {
501 kDebug() << "Length:" << hids.value();
502 //check for mails bigger mFilterOnServerCheckSize
503 if ( (unsigned int)hids.value() >= mFilterOnServerCheckSize ) {
504 kDebug() << "bigger than" << mFilterOnServerCheckSize;
505 const QByteArray uid = mUidForIdMap[ hids.key() ];
506 KMPopHeaders *header = new KMPopHeaders( hids.key(), uid, Later );
507 //set Action if already known
508 if ( mHeaderDeleteUids.contains( uid ) ) {
509 header->setAction(Delete);
511 else if ( mHeaderDownUids.contains( uid ) ) {
512 header->setAction(Down);
514 else if ( mHeaderLaterUids.contains( uid ) ) {
515 header->setAction(Later);
517 mHeadersOnServer.append( header );
520 // delete the uids so that you don't get them twice in the list
521 mHeaderDeleteUids.clear();
522 mHeaderDownUids.clear();
523 mHeaderLaterUids.clear();
525 // kDebug() << "Num of Msgs to Filter:" << mHeadersOnServer.count();
526 // if there are mails which should be checkedc download the headers
527 if ( ( mHeadersOnServer.count() > 0 ) && ( mFilterOnServer == true ) ) {
528 KUrl url = getUrl();
529 QByteArray headerIds = mHeadersOnServer[0]->id();
530 for ( int i = 1; i < mHeadersOnServer.count(); ++i ) {
531 headerIds += ',';
532 headerIds += mHeadersOnServer[i]->id();
534 mHeaderIndex = 0;
535 url.setPath( "/headers/" + headerIds );
536 job = KIO::get( url, KIO::NoReload, KIO::HideProgressInfo );
537 connectJob();
538 slotGetNextHdr();
539 stage = Head;
541 else {
542 stage = Retr;
543 numMsgs = mMsgsPendingDownload.count();
544 numBytesToRead = 0;
545 idsOfMsgs.clear();
546 QByteArray ids;
547 if ( numMsgs > 0 ) {
548 for ( QMap<QByteArray, int>::const_iterator it = mMsgsPendingDownload.constBegin();
549 it != mMsgsPendingDownload.constEnd(); ++it ) {
550 numBytesToRead += it.value();
551 ids += it.key() + ',';
552 idsOfMsgs.append( it.key() );
554 ids.chop( 1 ); // cut off the trailing ','
556 KUrl url = getUrl();
557 url.setPath( "/download/" + ids );
558 job = KIO::get( url, KIO::NoReload, KIO::HideProgressInfo );
559 connectJob();
560 slotGetNextMsg();
561 processMsgsTimer.start(processingDelay);
564 else if (stage == Head) {
565 kDebug() << "stage == Head";
567 // All headers have been downloaded, check which mail you want to get
568 // data is in list mHeadersOnServer
570 // check if headers apply to a filter
571 // if set the action of the filter
572 KMPopFilterAction action;
573 bool dlgPopup = false;
574 for ( int i = 0; i < mHeadersOnServer.count(); ++i ) {
575 KMPopHeaders *header = mHeadersOnServer[i];
576 action = (KMPopFilterAction)kmkernel->popFilterMgr()->process( header->header() );
577 //debug todo
578 switch ( action ) {
579 case NoAction:
580 kDebug() << "PopFilterAction = NoAction";
581 break;
582 case Later:
583 kDebug() << "PopFilterAction = Later";
584 break;
585 case Delete:
586 kDebug() << "PopFilterAction = Delete";
587 break;
588 case Down:
589 kDebug() << "PopFilterAction = Down";
590 break;
591 default:
592 kDebug() << "PopFilterAction = default oops!";
593 break;
595 switch ( action ) {
596 case NoAction:
597 //kDebug() << "PopFilterAction = NoAction";
598 dlgPopup = true;
599 break;
600 case Later:
601 if (kmkernel->popFilterMgr()->showLaterMsgs())
602 dlgPopup = true;
603 // fall through
604 default:
605 header->setAction( action );
606 header->setRuleMatched( true );
607 break;
611 // if there are some messages which are not coverd by a filter
612 // show the dialog
613 headers = true;
614 if ( dlgPopup ) {
615 mPopFilterConfirmationDialog =
616 new KMPopFilterCnfrmDlg( mHeadersOnServer, this->name(),
617 kmkernel->popFilterMgr()->showLaterMsgs() );
618 connect( mPopFilterConfirmationDialog, SIGNAL( rejected() ),
619 this, SLOT( slotAbortRequested() ) );
620 mPopFilterConfirmationDialog->exec();
623 // If the dialog was accepted or never shown (because all pop filters already
624 // set the actions), mark the messages for deletion, download or for keeping
625 // them. Then advance to the next stage, the download stage.
626 if ( !dlgPopup ||
627 mPopFilterConfirmationDialog->result() == KDialog::Accepted ) {
629 for ( int i = 0; i < mHeadersOnServer.count(); ++i ) {
630 const KMPopHeaders *header = mHeadersOnServer[i];
631 if ( header->action() == Delete || header->action() == Later) {
632 //remove entries from the lists when the mails should not be downloaded
633 //(deleted or downloaded later)
634 mMsgsPendingDownload.remove( header->id() );
635 if ( header->action() == Delete ) {
636 mHeaderDeleteUids.insert( header->uid() );
637 mUidsOfNextSeenMsgsDict.insert( header->uid(), 1 );
638 idsOfMsgsToDelete.insert( header->id() );
639 mTimeOfNextSeenMsgsMap.insert( header->uid(), time(0) );
641 else {
642 mHeaderLaterUids.insert( header->uid() );
645 else if ( header->action() == Down ) {
646 mHeaderDownUids.insert( header->uid() );
650 qDeleteAll( mHeadersOnServer );
651 mHeadersOnServer.clear();
652 stage = Retr;
653 numMsgs = mMsgsPendingDownload.count();
654 numBytesToRead = 0;
655 idsOfMsgs.clear();
656 QByteArray ids;
657 if ( numMsgs > 0 ) {
658 for ( QMap<QByteArray, int>::const_iterator it = mMsgsPendingDownload.constBegin();
659 it != mMsgsPendingDownload.constEnd(); ++it ) {
660 numBytesToRead += it.value();
661 ids += it.key() + ',';
662 idsOfMsgs.append( it.key() );
664 ids.chop( 1 ); // cut off the trailing ','
666 KUrl url = getUrl();
667 url.setPath( "/download/" + ids );
668 job = KIO::get( url, KIO::NoReload, KIO::HideProgressInfo );
669 connectJob();
670 slotGetNextMsg();
671 processMsgsTimer.start(processingDelay);
673 delete mPopFilterConfirmationDialog;
674 mPopFilterConfirmationDialog = 0;
676 else if (stage == Retr) {
677 mMailCheckProgressItem->setProgress( 100 );
678 processRemainingQueuedMessages();
680 mHeaderDeleteUids.clear();
681 mHeaderDownUids.clear();
682 mHeaderLaterUids.clear();
684 kmkernel->folderMgr()->syncAllFolders();
686 KUrl url = getUrl();
688 // Check if we want to keep any messages.
690 // The default is to delete all messages which have been successfully downloaded
691 // or which we have seen before (which are remembered in the config file).
692 // This excludes only messages which we have not seen before and at
693 // the same time failed to download correctly, or messages which the pop
694 // filter manager decided to leave on the server (the "download later" option)
695 // The messages which we want to delete are contained in idsOfMsgsToDelete.
697 // In the code below, we check if any "leave on server" rules apply and remove
698 // the messages which should be left on the server from idsOfMsgsToDelete.
699 // This is done by storing the messages to leave on the server in idsToSave,
700 // which is later subtracted from idsOfMsgsToDelete.
702 // Start with an empty list of messages to keep
703 QList< QPair<time_t, QByteArray> > idsToSave;
705 if ( mLeaveOnServer && !idsOfMsgsToDelete.isEmpty() ) {
707 // If the time-limited leave rule is checked, add the newer messages to
708 // the list of messages to keep
709 if ( mLeaveOnServerDays > 0 && !mTimeOfNextSeenMsgsMap.isEmpty() ) {
710 time_t timeLimit = time(0) - (86400 * mLeaveOnServerDays);
711 for ( QSet<QByteArray>::const_iterator it = idsOfMsgsToDelete.constBegin();
712 it != idsOfMsgsToDelete.constEnd(); ++it ) {
713 time_t msgTime = mTimeOfNextSeenMsgsMap[ mUidForIdMap[*it] ];
714 if ( msgTime >= timeLimit || msgTime == 0 ) {
715 QPair<time_t, QByteArray> pair( msgTime, *it );
716 idsToSave.append( pair );
721 // Otherwise, add all messages to the list of messages to keep - this may
722 // be reduced in the following number-limited leave rule and size-limited
723 // leave rule checks
724 else {
725 foreach ( const QByteArray& id, idsOfMsgsToDelete ) {
726 time_t msgTime = mTimeOfNextSeenMsgsMap[ mUidForIdMap[id] ];
727 QPair<time_t, QByteArray> pair( msgTime, id );
728 idsToSave.append( pair );
732 // sort the idsToSave list so that in the following we remove the oldest messages
733 qSort( idsToSave );
735 // Delete more old messages if there are more than mLeaveOnServerCount
736 if ( mLeaveOnServerCount > 0 ) {
737 int numToDelete = idsToSave.count() - mLeaveOnServerCount;
738 if ( numToDelete > 0 && numToDelete < idsToSave.count() ) {
739 // get rid of the first numToDelete messages
740 #ifdef DEBUG
741 for ( int i = 0; i < numToDelete; ++i )
742 kDebug() << "deleting msg id" << idsToSave[i].second;
743 #endif
744 idsToSave = idsToSave.mid( numToDelete );
746 else if ( numToDelete >= idsToSave.count() )
747 idsToSave.clear();
750 // Delete more old messages until we're under mLeaveOnServerSize MBs
751 if ( mLeaveOnServerSize > 0 ) {
752 const long limitInBytes = mLeaveOnServerSize * ( 1024 * 1024 );
753 long sizeOnServer = 0;
754 int firstMsgToKeep = idsToSave.count() - 1;
755 for ( ; firstMsgToKeep >= 0 && sizeOnServer <= limitInBytes; --firstMsgToKeep ) {
756 sizeOnServer +=
757 mSizeOfNextSeenMsgsDict[ mUidForIdMap[ idsToSave[firstMsgToKeep].second ] ];
759 if ( sizeOnServer > limitInBytes )
760 firstMsgToKeep++;
761 #ifdef DEBUG
762 for ( int i = 0; i < firstMsgToKeep; ++i )
763 kDebug() << "deleting msg id" << idsToSave[i].second;
764 #endif
765 if ( firstMsgToKeep > 0 )
766 idsToSave = idsToSave.mid( firstMsgToKeep );
768 // Save msgs from deletion
769 kDebug() << "Going to save" << idsToSave.count();
770 for ( int i = 0; i < idsToSave.count(); ++i ) {
771 kDebug() << "keeping msg id" << idsToSave[i].second;
772 idsOfMsgsToDelete.remove( idsToSave[i].second );
776 if ( !idsOfForcedDeletes.isEmpty() ) {
777 idsOfMsgsToDelete += idsOfForcedDeletes;
778 idsOfForcedDeletes.clear();
781 // If there are messages to delete then delete them
782 if ( !idsOfMsgsToDelete.isEmpty() ) {
783 stage = Dele;
784 mMailCheckProgressItem->setStatus(
785 i18np( "Fetched 1 message from %2. Deleting messages from server...",
786 "Fetched %1 messages from %2. Deleting messages from server...",
787 numMsgs,
788 mHost ) );
789 QSet<QByteArray>::const_iterator it = idsOfMsgsToDelete.constBegin();
790 QByteArray ids = *it;
791 ++it;
792 for ( ; it != idsOfMsgsToDelete.constEnd(); ++it ) {
793 ids += ',';
794 ids += *it;
796 url.setPath( "/remove/" + ids );
797 } else {
798 stage = Quit;
799 mMailCheckProgressItem->setStatus(
800 i18np( "Fetched 1 message from %2. Terminating transmission...",
801 "Fetched %1 messages from %2. Terminating transmission...",
802 numMsgs,
803 mHost ) );
804 url.setPath( "/commit" );
806 job = KIO::get( url, KIO::NoReload, KIO::HideProgressInfo );
807 connectJob();
809 else if (stage == Dele) {
810 kDebug() << "stage == Dele";
811 // remove the uids of all messages which have been deleted
812 for ( QSet<QByteArray>::const_iterator it = idsOfMsgsToDelete.constBegin();
813 it != idsOfMsgsToDelete.constEnd(); ++it ) {
814 mUidsOfNextSeenMsgsDict.remove( mUidForIdMap[*it] );
816 idsOfMsgsToDelete.clear();
817 mMailCheckProgressItem->setStatus(
818 i18np( "Fetched 1 message from %2. Terminating transmission...",
819 "Fetched %1 messages from %2. Terminating transmission...",
820 numMsgs,
821 mHost ) );
822 KUrl url = getUrl();
823 url.setPath( "/commit" );
824 job = KIO::get( url, KIO::NoReload, KIO::HideProgressInfo );
825 stage = Quit;
826 connectJob();
828 else if (stage == Quit) {
829 kDebug() << "stage == Quit";
830 saveUidList();
831 job = 0;
832 if ( mSlave )
833 KIO::Scheduler::disconnectSlave(mSlave);
834 mSlave = 0;
835 stage = Idle;
836 if ( mMailCheckProgressItem ) { // do this only once...
837 bool canceled = !kmkernel || kmkernel->mailCheckAborted() ||
838 mMailCheckProgressItem->canceled();
839 int numMessages = canceled ? indexOfCurrentMsg : idsOfMsgs.count();
840 BroadcastStatus::instance()->setStatusMsgTransmissionCompleted(
841 this->name(), numMessages, numBytes, numBytesRead,
842 numBytesToRead, mLeaveOnServer, mMailCheckProgressItem );
843 mMailCheckProgressItem->setComplete();
844 mMailCheckProgressItem = 0;
845 checkDone( ( numMessages > 0 ), canceled ? CheckAborted : CheckOK );
851 //-----------------------------------------------------------------------------
852 void PopAccount::processRemainingQueuedMessages()
854 kDebug() ;
855 slotProcessPendingMsgs(); // Force processing of any messages still in the queue
856 processMsgsTimer.stop();
858 stage = Quit;
859 if ( kmkernel && kmkernel->folderMgr() ) {
860 kmkernel->folderMgr()->syncAllFolders();
865 //-----------------------------------------------------------------------------
866 void PopAccount::saveUidList()
868 kDebug() ;
869 // Don't update the seen uid list unless we successfully got
870 // a new list from the server
871 if (!mUidlFinished) return;
873 QStringList uidsOfNextSeenMsgs;
874 QList<int> seenUidTimeList;
875 for ( QHash<QByteArray,int>::const_iterator it = mUidsOfNextSeenMsgsDict.constBegin();
876 it != mUidsOfNextSeenMsgsDict.constEnd(); ++it ) {
877 uidsOfNextSeenMsgs.append( it.key() );
878 seenUidTimeList.append( mTimeOfNextSeenMsgsMap[ it.key() ] );
880 QString seenUidList = KStandardDirs::locateLocal( "data", "kmail/" + mLogin + ':' + '@' +
881 mHost + ':' + QString::number( mPort ) );
882 KConfig config( seenUidList );
883 KConfigGroup group( &config, "<default>" );
884 group.writeEntry( "seenUidList", uidsOfNextSeenMsgs );
885 group.writeEntry( "seenUidTimeList", seenUidTimeList );
886 QByteArray laterList;
887 laterList.reserve( mHeaderLaterUids.count() * 5 ); // what's the average size of a uid?
888 foreach( const QByteArray& uid, mHeaderLaterUids ) {
889 if ( !laterList.isEmpty() )
890 laterList += ',';
891 laterList.append( uid );
893 group.writeEntry( "downloadLater", laterList.constData() );
894 config.sync();
898 //-----------------------------------------------------------------------------
899 void PopAccount::slotGetNextMsg()
901 curMsgData.resize(0);
902 numMsgBytesRead = 0;
903 curMsgLen = 0;
904 delete curMsgStrm;
905 curMsgStrm = 0;
907 if ( !mMsgsPendingDownload.isEmpty() ) {
908 // get the next message
909 QMap<QByteArray, int>::iterator next = mMsgsPendingDownload.begin();
910 curMsgStrm = new QDataStream( &curMsgData, QIODevice::WriteOnly );
911 curMsgLen = next.value();
912 ++indexOfCurrentMsg;
913 kDebug() << QString("Length of message about to get %1").arg( curMsgLen );
914 mMsgsPendingDownload.erase( next );
919 //-----------------------------------------------------------------------------
920 void PopAccount::slotData( KIO::Job* job, const QByteArray &data)
922 Q_UNUSED( job );
924 if (data.size() == 0) {
925 kDebug() << "Data: <End>";
926 if ((stage == Retr) && (numMsgBytesRead < curMsgLen))
927 numBytesRead += curMsgLen - numMsgBytesRead;
928 else if (stage == Head){
929 kDebug() << "Head: <End>";
931 return;
934 int oldNumMsgBytesRead = numMsgBytesRead;
935 if (stage == Retr) {
936 headers = false;
937 curMsgStrm->writeRawData( data.data(), data.size() );
938 numMsgBytesRead += data.size();
939 if (numMsgBytesRead > curMsgLen)
940 numMsgBytesRead = curMsgLen;
941 numBytesRead += numMsgBytesRead - oldNumMsgBytesRead;
942 dataCounter++;
943 if (dataCounter % 5 == 0)
945 QString msg;
946 if (numBytes != numBytesToRead && mLeaveOnServer)
948 msg = ki18n("Fetching message %1 of %2 (%3 of %4 KB) for %5@%6 "
949 "(%7 KB remain on the server).")
950 .subs( indexOfCurrentMsg+1 ).subs( numMsgs )
951 .subs( numBytesRead/1024 ).subs( numBytesToRead/1024 )
952 .subs( mLogin ).subs( mHost ).subs( numBytes/1024 )
953 .toString();
955 else
957 msg = ki18n("Fetching message %1 of %2 (%3 of %4 KB) for %5@%6.")
958 .subs( indexOfCurrentMsg+1 ).subs( numMsgs ).subs( numBytesRead/1024 )
959 .subs( numBytesToRead/1024 ).subs( mLogin ).subs( mHost )
960 .toString();
962 mMailCheckProgressItem->setStatus( msg );
963 mMailCheckProgressItem->setProgress(
964 (numBytesToRead <= 100) ? 50 // We never know what the server tells us
965 // This way of dividing is required for > 21MB of mail
966 : (numBytesRead / (numBytesToRead / 100)) );
968 return;
971 if (stage == Head) {
972 curMsgStrm->writeRawData( data.data(), data.size() );
973 return;
976 // otherwise stage is List Or Uidl
977 QByteArray qdata = data.simplified(); // Workaround for Maillennium POP3/UNIBOX
978 if ( qdata.size() == 0 ) // data contained only whitespace
979 return;
980 const int spc = qdata.indexOf( ' ' );
982 //Get rid of the null-terminating character if that exists.
983 //Because mUidsOfSeenMsgsDict doesn't have those either, comparing the
984 //values would otherwise fail.
985 if ( qdata.at( qdata.size() - 1 ) == 0 )
986 qdata.chop( 1 );
988 if ( stage == List ) {
989 if ( spc > 0 ) {
990 QByteArray length = qdata.mid(spc+1);
991 const int spaceInLengthPos = length.indexOf(' ');
992 if ( spaceInLengthPos != -1 )
993 length.truncate( spaceInLengthPos );
994 const int len = length.toInt();
995 numBytes += len;
996 QByteArray id = qdata.left(spc);
997 idsOfMsgs.append( id );
998 mMsgsPendingDownload.insert( id, len );
1000 else {
1001 slotAbortRequested();
1002 if (interactive && kmkernel) {
1003 KNotification::event( "mail-check-error",
1004 i18n( "Error while checking account %1 for new mail:\n%2",
1005 name(), i18n( "Unable to complete LIST operation." ) ),
1006 QPixmap(),
1007 kmkernel->mainWin(),
1008 KNotification::CloseOnTimeout );
1010 return;
1013 else { // stage == Uidl
1015 Q_ASSERT ( stage == Uidl);
1016 QByteArray id;
1017 QByteArray uid;
1019 // If there is no space in the UIDL entry, the response is invalid.
1020 // However, some servers seem to do this, see bug 127696. Try to work
1021 // around this problem by downloading and deleting the message if we
1022 // can still parse the ID part of the entry.
1023 // Otherwise, just skip that message.
1024 if ( spc <=0 ) {
1026 // Try to convert the entire UIDL entry to an ID
1027 QByteArray testID = qdata;
1028 bool idIsNumber;
1029 testID.toInt( &idIsNumber );
1030 if ( !idIsNumber ) {
1031 // we'll just have to skip this
1032 kWarning() << "Skipping UIDL entry due to parse error:" << qdata;
1033 return;
1036 id = testID;
1037 // Generate a fake UID, so we don't get problems because all the code
1038 // requires a UID. This is a rather bad hack, but it works, since the
1039 // message will be deleted from the server.
1040 uid = QString( QString("uidlgen") + time(0) + QString(".") +
1041 (++dataCounter) ).toAscii();
1042 kWarning() << "Message" << id << "has bad UIDL, cannot keep a copy on server!";
1043 idsOfForcedDeletes.insert( id );
1045 else {
1046 id = qdata.left( spc );
1047 uid = qdata.mid( spc + 1 );
1050 mSizeOfNextSeenMsgsDict.insert( uid, mMsgsPendingDownload[id] );
1051 if ( mUidsOfSeenMsgsDict.contains( uid ) ) {
1052 if ( mMsgsPendingDownload.contains( id ) ) {
1053 mMsgsPendingDownload.remove( id );
1055 else
1056 kDebug() << "Synchronization failure.";
1057 idsOfMsgsToDelete.insert( id );
1058 mUidsOfNextSeenMsgsDict.insert( uid, 1 );
1059 if ( mTimeOfSeenMsgsVector.empty() ) {
1060 mTimeOfNextSeenMsgsMap.insert( uid, time(0) );
1062 else {
1063 mTimeOfNextSeenMsgsMap.insert( uid,
1064 mTimeOfSeenMsgsVector[ mUidsOfSeenMsgsDict[uid] ] );
1067 mUidForIdMap.insert( id, uid );
1072 //-----------------------------------------------------------------------------
1073 void PopAccount::slotResult( KJob* )
1075 if (!job) return;
1076 if ( job->error() )
1078 if (interactive) {
1079 if (headers) { // nothing to be done for headers
1080 idsOfMsgs.clear();
1082 if (stage == Head && job->error() == KIO::ERR_COULD_NOT_READ)
1084 KMessageBox::error( 0, i18n( "Your POP3 server (Account: %1) does not support the "
1085 "TOP command. Therefore it is not possible to fetch the headers "
1086 "of large emails first, before downloading them.", NetworkAccount::name() ) );
1087 slotCancel();
1088 return;
1090 // force the dialog to be shown next time the account is checked
1091 if (!mStorePasswd) mPasswd = "";
1092 if ( job->error() == KIO::ERR_COULD_NOT_CONNECT ) {
1093 KNotification::event( "mail-check-error",
1094 i18n( "Error while checking account %1 for new mail:\n%2",
1095 name(), job->errorString() ) );
1097 else {
1098 job->ui()->setWindow( 0 );
1099 job->ui()->showErrorMessage();
1102 slotCancel();
1104 else
1105 slotJobFinished();
1109 //-----------------------------------------------------------------------------
1110 void PopAccount::slotSlaveError(KIO::Slave *aSlave, int error,
1111 const QString &errorMsg)
1113 if (aSlave != mSlave) return;
1114 if (error == KIO::ERR_SLAVE_DIED) mSlave = 0;
1116 // explicitly disconnect the slave if the connection went down
1117 if ( error == KIO::ERR_CONNECTION_BROKEN && mSlave ) {
1118 KIO::Scheduler::disconnectSlave( mSlave );
1119 mSlave = 0;
1122 if (interactive && kmkernel) {
1123 KNotification::event( "mail-check-error",
1124 i18n( "Error while checking account %1 for new mail:\n%2",
1125 name(), KIO::buildErrorString( error, errorMsg ) ),
1126 QPixmap(),
1127 kmkernel->mainWin(),
1128 KNotification::CloseOnTimeout );
1132 stage = Quit;
1133 if (error == KIO::ERR_COULD_NOT_LOGIN && !mStorePasswd)
1134 mAskAgain = true;
1135 /* We need a timer, otherwise slotSlaveError of the next account is also
1136 executed, if it reuses the slave, because the slave member variable
1137 is changed too early */
1138 QTimer::singleShot(0, this, SLOT(slotCancel()));
1141 //-----------------------------------------------------------------------------
1142 void PopAccount::slotGetNextHdr(){
1143 kDebug() << "slotGetNextHeader";
1145 curMsgData.resize(0);
1146 delete curMsgStrm;
1147 curMsgStrm = 0;
1149 curMsgStrm = new QDataStream( &curMsgData, QIODevice::WriteOnly );
1152 void PopAccount::killAllJobs( bool ) {
1153 // must reimpl., but we don't use it yet
1156 } // namespace KMail
1157 #include "popaccount.moc"