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"
35 #include "accountmanager.h"
38 #include <kstandarddirs.h>
40 #include <kmessagebox.h>
41 #include <kmainwindow.h>
42 #include <kio/scheduler.h>
43 #include <kio/passworddialog.h>
45 #include <kconfiggroup.h>
46 #include <kio/jobuidelegate.h>
47 #include <knotification.h>
51 static const unsigned short int pop3DefaultPort
= 110;
54 //-----------------------------------------------------------------------------
55 PopAccount::PopAccount(AccountManager
* aOwner
, const QString
& aAccountName
, uint id
)
56 : NetworkAccount(aOwner
, aAccountName
, id
),
57 mPopFilterConfirmationDialog( 0 ),
63 mPort
= defaultPort();
65 indexOfCurrentMsg
= -1;
67 processingDelay
= 2*100;
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()
87 mMsgsPendingDownload
.clear();
88 processRemainingQueuedMessages();
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;
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
);
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
)
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
);
157 setPasswd( passwd
, b
);
159 kmkernel
->acctMgr()->writeConfig( true );
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();
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;
199 checkDone( false, CheckIgnored
);
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
)
241 //-----------------------------------------------------------------------------
242 void PopAccount::setLeaveOnServer(bool 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
)
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();
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
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.
327 kWarning() << "Error while processing new mail, aborting mail check.";
328 mMsgsPendingDownload
.clear();
329 slotAbortRequested();
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();
347 //-----------------------------------------------------------------------------
348 void PopAccount::slotAbortRequested()
353 disconnect( mMailCheckProgressItem
, SIGNAL( progressItemCanceled( KPIM::ProgressItem
* ) ),
354 this, SLOT( slotAbortRequested() ) );
364 //-----------------------------------------------------------------------------
365 void PopAccount::startJob()
367 // Run the precommand
368 if ( !runPrecommand(precommand() ) ) {
369 checkDone( false, CheckError
);
372 // end precommand code
376 if ( !url
.isValid() ) {
377 KMessageBox::error(0, i18n("Source URL is malformed"),
378 i18n("Kioslave Error Message") );
382 mMsgsPendingDownload
.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();
391 indexOfCurrentMsg
= -1;
393 Q_ASSERT( !mMailCheckProgressItem
);
394 mMailCheckProgressItem
= KPIM::ProgressManager::createProgressItem(
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() ) );
406 mSlave
= KIO::Scheduler::getConnectedSlave( url
, slaveConfig() );
409 slotSlaveError(0, KIO::ERR_CANNOT_LAUNCH_PROCESS
, url
.protocol());
412 url
.setPath( "/index" );
413 job
= KIO::get( url
, KIO::NoReload
, KIO::HideProgressInfo
);
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");
429 m
.insert("auth", mAuth
);
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
);
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
] ] );
467 //-----------------------------------------------------------------------------
468 // finit state machine to cycle trow the stages
469 void PopAccount::slotJobFinished() {
470 QStringList emptyList
;
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 ) );
477 url
.setPath( "/uidl" );
478 job
= KIO::get( url
, KIO::NoReload
, KIO::HideProgressInfo
);
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 ) ) {
529 QByteArray headerIds
= mHeadersOnServer
[0]->id();
530 for ( int i
= 1; i
< mHeadersOnServer
.count(); ++i
) {
532 headerIds
+= mHeadersOnServer
[i
]->id();
535 url
.setPath( "/headers/" + headerIds
);
536 job
= KIO::get( url
, KIO::NoReload
, KIO::HideProgressInfo
);
543 numMsgs
= mMsgsPendingDownload
.count();
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 ','
557 url
.setPath( "/download/" + ids
);
558 job
= KIO::get( url
, KIO::NoReload
, KIO::HideProgressInfo
);
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() );
580 kDebug() << "PopFilterAction = NoAction";
583 kDebug() << "PopFilterAction = Later";
586 kDebug() << "PopFilterAction = Delete";
589 kDebug() << "PopFilterAction = Down";
592 kDebug() << "PopFilterAction = default oops!";
597 //kDebug() << "PopFilterAction = NoAction";
601 if (kmkernel
->popFilterMgr()->showLaterMsgs())
605 header
->setAction( action
);
606 header
->setRuleMatched( true );
611 // if there are some messages which are not coverd by a filter
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.
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) );
642 mHeaderLaterUids
.insert( header
->uid() );
645 else if ( header
->action() == Down
) {
646 mHeaderDownUids
.insert( header
->uid() );
650 qDeleteAll( mHeadersOnServer
);
651 mHeadersOnServer
.clear();
653 numMsgs
= mMsgsPendingDownload
.count();
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 ','
667 url
.setPath( "/download/" + ids
);
668 job
= KIO::get( url
, KIO::NoReload
, KIO::HideProgressInfo
);
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();
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
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
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
741 for ( int i
= 0; i
< numToDelete
; ++i
)
742 kDebug() << "deleting msg id" << idsToSave
[i
].second
;
744 idsToSave
= idsToSave
.mid( numToDelete
);
746 else if ( numToDelete
>= idsToSave
.count() )
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
) {
757 mSizeOfNextSeenMsgsDict
[ mUidForIdMap
[ idsToSave
[firstMsgToKeep
].second
] ];
759 if ( sizeOnServer
> limitInBytes
)
762 for ( int i
= 0; i
< firstMsgToKeep
; ++i
)
763 kDebug() << "deleting msg id" << idsToSave
[i
].second
;
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() ) {
784 mMailCheckProgressItem
->setStatus(
785 i18np( "Fetched 1 message from %2. Deleting messages from server...",
786 "Fetched %1 messages from %2. Deleting messages from server...",
789 QSet
<QByteArray
>::const_iterator it
= idsOfMsgsToDelete
.constBegin();
790 QByteArray ids
= *it
;
792 for ( ; it
!= idsOfMsgsToDelete
.constEnd(); ++it
) {
796 url
.setPath( "/remove/" + ids
);
799 mMailCheckProgressItem
->setStatus(
800 i18np( "Fetched 1 message from %2. Terminating transmission...",
801 "Fetched %1 messages from %2. Terminating transmission...",
804 url
.setPath( "/commit" );
806 job
= KIO::get( url
, KIO::NoReload
, KIO::HideProgressInfo
);
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...",
823 url
.setPath( "/commit" );
824 job
= KIO::get( url
, KIO::NoReload
, KIO::HideProgressInfo
);
828 else if (stage
== Quit
) {
829 kDebug() << "stage == Quit";
833 KIO::Scheduler::disconnectSlave(mSlave
);
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()
855 slotProcessPendingMsgs(); // Force processing of any messages still in the queue
856 processMsgsTimer
.stop();
859 if ( kmkernel
&& kmkernel
->folderMgr() ) {
860 kmkernel
->folderMgr()->syncAllFolders();
865 //-----------------------------------------------------------------------------
866 void PopAccount::saveUidList()
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() )
891 laterList
.append( uid
);
893 group
.writeEntry( "downloadLater", laterList
.constData() );
898 //-----------------------------------------------------------------------------
899 void PopAccount::slotGetNextMsg()
901 curMsgData
.resize(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();
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
)
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>";
934 int oldNumMsgBytesRead
= numMsgBytesRead
;
937 curMsgStrm
->writeRawData( data
.data(), data
.size() );
938 numMsgBytesRead
+= data
.size();
939 if (numMsgBytesRead
> curMsgLen
)
940 numMsgBytesRead
= curMsgLen
;
941 numBytesRead
+= numMsgBytesRead
- oldNumMsgBytesRead
;
943 if (dataCounter
% 5 == 0)
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 )
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
)
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)) );
972 curMsgStrm
->writeRawData( data
.data(), data
.size() );
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
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 )
988 if ( stage
== List
) {
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();
996 QByteArray id
= qdata
.left(spc
);
997 idsOfMsgs
.append( id
);
998 mMsgsPendingDownload
.insert( id
, len
);
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." ) ),
1007 kmkernel
->mainWin(),
1008 KNotification::CloseOnTimeout
);
1013 else { // stage == Uidl
1015 Q_ASSERT ( stage
== Uidl
);
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.
1026 // Try to convert the entire UIDL entry to an ID
1027 QByteArray testID
= qdata
;
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
;
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
);
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
);
1056 kDebug() << "Synchronization failure.";
1057 idsOfMsgsToDelete
.insert( id
);
1058 mUidsOfNextSeenMsgsDict
.insert( uid
, 1 );
1059 if ( mTimeOfSeenMsgsVector
.empty() ) {
1060 mTimeOfNextSeenMsgsMap
.insert( uid
, time(0) );
1063 mTimeOfNextSeenMsgsMap
.insert( uid
,
1064 mTimeOfSeenMsgsVector
[ mUidsOfSeenMsgsDict
[uid
] ] );
1067 mUidForIdMap
.insert( id
, uid
);
1072 //-----------------------------------------------------------------------------
1073 void PopAccount::slotResult( KJob
* )
1079 if (headers
) { // nothing to be done for headers
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() ) );
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() ) );
1098 job
->ui()->setWindow( 0 );
1099 job
->ui()->showErrorMessage();
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
);
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
) ),
1127 kmkernel
->mainWin(),
1128 KNotification::CloseOnTimeout
);
1133 if (error
== KIO::ERR_COULD_NOT_LOGIN
&& !mStorePasswd
)
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);
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"