kmail

popaccount.cpp

Go to the documentation of this file.
00001 /*
00002     This file is part of KMail, the KDE mail client.
00003     Copyright (c) 2000 Don Sanders <sanders@kde.org>
00004 
00005     Based on popaccount by:
00006       Stefan Taferner <taferner@kde.org>
00007       Markus Wuebben <markus.wuebben@kde.org>
00008 
00009     KMail is free software; you can redistribute it and/or modify it
00010     under the terms of the GNU General Public License, version 2, as
00011     published by the Free Software Foundation.
00012 
00013     KMail is distributed in the hope that it will be useful, but
00014     WITHOUT ANY WARRANTY; without even the implied warranty of
00015     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016     General Public License for more details.
00017 
00018     You should have received a copy of the GNU General Public License
00019     along with this program; if not, write to the Free Software
00020     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
00021 */
00022 
00023 #ifdef HAVE_CONFIG_H
00024 #include <config.h>
00025 #endif
00026 
00027 #include "popaccount.h"
00028 
00029 #include "broadcaststatus.h"
00030 using KPIM::BroadcastStatus;
00031 #include "progressmanager.h"
00032 #include "kmfoldermgr.h"
00033 #include "kmfiltermgr.h"
00034 #include "kmpopfiltercnfrmdlg.h"
00035 #include "protocols.h"
00036 #include "kmglobal.h"
00037 #include "util.h"
00038 #include "accountmanager.h"
00039 
00040 #include <kdebug.h>
00041 #include <kstandarddirs.h>
00042 #include <klocale.h>
00043 #include <kmessagebox.h>
00044 #include <kmainwindow.h>
00045 #include <kio/scheduler.h>
00046 #include <kio/passdlg.h>
00047 #include <kconfig.h>
00048 using KIO::MetaData;
00049 
00050 #include <qstylesheet.h>
00051 
00052 static const unsigned short int pop3DefaultPort = 110;
00053 
00054 namespace KMail {
00055 //-----------------------------------------------------------------------------
00056 PopAccount::PopAccount(AccountManager* aOwner, const QString& aAccountName, uint id)
00057   : NetworkAccount(aOwner, aAccountName, id),
00058     headerIt(headersOnServer),
00059     processMsgsTimer( 0, "processMsgsTimer" )
00060 {
00061   init();
00062   job = 0;
00063   mSlave = 0;
00064   mPort = defaultPort();
00065   stage = Idle;
00066   indexOfCurrentMsg = -1;
00067   curMsgStrm = 0;
00068   processingDelay = 2*100;
00069   mProcessing = false;
00070   dataCounter = 0;
00071   mUidsOfSeenMsgsDict.setAutoDelete( false );
00072   mUidsOfNextSeenMsgsDict.setAutoDelete( false );
00073 
00074   headersOnServer.setAutoDelete(true);
00075   connect(&processMsgsTimer,SIGNAL(timeout()),SLOT(slotProcessPendingMsgs()));
00076   KIO::Scheduler::connect(
00077     SIGNAL(slaveError(KIO::Slave *, int, const QString &)),
00078     this, SLOT(slotSlaveError(KIO::Slave *, int, const QString &)));
00079 
00080   mHeaderDeleteUids.clear();
00081   mHeaderDownUids.clear();
00082   mHeaderLaterUids.clear();
00083 }
00084 
00085 
00086 //-----------------------------------------------------------------------------
00087 PopAccount::~PopAccount()
00088 {
00089   if (job) {
00090     job->kill();
00091     mMsgsPendingDownload.clear();
00092     processRemainingQueuedMessages();
00093     saveUidList();
00094   }
00095 }
00096 
00097 
00098 //-----------------------------------------------------------------------------
00099 QString PopAccount::type(void) const
00100 {
00101   return "pop";
00102 }
00103 
00104 QString PopAccount::protocol() const {
00105   return useSSL() ? POP_SSL_PROTOCOL : POP_PROTOCOL;
00106 }
00107 
00108 unsigned short int PopAccount::defaultPort() const {
00109   return pop3DefaultPort;
00110 }
00111 
00112 //-----------------------------------------------------------------------------
00113 void PopAccount::init(void)
00114 {
00115   NetworkAccount::init();
00116 
00117   mUsePipelining = false;
00118   mLeaveOnServer = false;
00119   mLeaveOnServerDays = -1;
00120   mLeaveOnServerCount = -1;
00121   mLeaveOnServerSize = -1;
00122   mFilterOnServer = false;
00123   //tz todo
00124   mFilterOnServerCheckSize = 50000;
00125 }
00126 
00127 //-----------------------------------------------------------------------------
00128 void PopAccount::pseudoAssign( const KMAccount * a ) {
00129   slotAbortRequested();
00130   NetworkAccount::pseudoAssign( a );
00131 
00132   const PopAccount * p = dynamic_cast<const PopAccount*>( a );
00133   if ( !p ) return;
00134 
00135   setUsePipelining( p->usePipelining() );
00136   setLeaveOnServer( p->leaveOnServer() );
00137   setLeaveOnServerDays( p->leaveOnServerDays() );
00138   setLeaveOnServerCount( p->leaveOnServerCount() );
00139   setLeaveOnServerSize( p->leaveOnServerSize() );
00140   setFilterOnServer( p->filterOnServer() );
00141   setFilterOnServerCheckSize( p->filterOnServerCheckSize() );
00142 }
00143 
00144 //-----------------------------------------------------------------------------
00145 void PopAccount::processNewMail(bool _interactive)
00146 {
00147   if (stage == Idle) {
00148 
00149     if ( (mAskAgain || passwd().isEmpty() || mLogin.isEmpty()) &&
00150       mAuth != "GSSAPI" ) {
00151       QString passwd = NetworkAccount::passwd();
00152       bool b = storePasswd();
00153       if (KIO::PasswordDialog::getNameAndPassword(mLogin, passwd, &b,
00154         i18n("You need to supply a username and a password to access this "
00155         "mailbox."), false, QString::null, mName, i18n("Account:"))
00156         != QDialog::Accepted)
00157       {
00158         checkDone( false, CheckAborted );
00159         return;
00160       } else {
00161         setPasswd( passwd, b );
00162         if ( b ) {
00163           kmkernel->acctMgr()->writeConfig( true );
00164         }
00165         mAskAgain = false;
00166       }
00167     }
00168 
00169     QString seenUidList = locateLocal( "data", "kmail/" + mLogin + ":" + "@" +
00170                                        mHost + ":" + QString("%1").arg(mPort) );
00171     KConfig config( seenUidList );
00172     QStringList uidsOfSeenMsgs = config.readListEntry( "seenUidList" );
00173     QValueList<int> timeOfSeenMsgs = config.readIntListEntry( "seenUidTimeList" );
00174     mUidsOfSeenMsgsDict.clear();
00175     mUidsOfSeenMsgsDict.resize( KMail::nextPrime( ( uidsOfSeenMsgs.count() * 11 ) / 10 ) );
00176     int idx = 1;
00177     for ( QStringList::ConstIterator it = uidsOfSeenMsgs.begin();
00178           it != uidsOfSeenMsgs.end(); ++it, idx++ ) {
00179       // we use mUidsOfSeenMsgsDict to just provide fast random access to the
00180       // keys, so we can store the index(+1) that corresponds to the index of
00181       // mTimeOfSeenMsgsVector for use in PopAccount::slotData()
00182       mUidsOfSeenMsgsDict.insert( *it, (const int *)idx );
00183     }
00184     mTimeOfSeenMsgsVector.clear();
00185     mTimeOfSeenMsgsVector.reserve( timeOfSeenMsgs.size() );
00186     for ( QValueList<int>::ConstIterator it = timeOfSeenMsgs.begin();
00187           it != timeOfSeenMsgs.end(); ++it) {
00188       mTimeOfSeenMsgsVector.append( *it );
00189     }
00190     // If the counts differ then the config file has presumably been tampered
00191     // with and so to avoid possible unwanted message deletion we'll treat
00192     // them all as newly seen by clearing the seen times vector
00193     if ( mTimeOfSeenMsgsVector.count() != mUidsOfSeenMsgsDict.count() )
00194       mTimeOfSeenMsgsVector.clear();
00195     QStringList downloadLater = config.readListEntry( "downloadLater" );
00196     for ( QStringList::Iterator it = downloadLater.begin(); it != downloadLater.end(); ++it ) {
00197         mHeaderLaterUids.insert( *it, true );
00198     }
00199     mUidsOfNextSeenMsgsDict.clear();
00200     mTimeOfNextSeenMsgsMap.clear();
00201     mSizeOfNextSeenMsgsDict.clear();
00202 
00203     interactive = _interactive;
00204     mUidlFinished = false;
00205     startJob();
00206   }
00207   else {
00208     checkDone( false, CheckIgnored );
00209     return;
00210   }
00211 }
00212 
00213 
00214 //-----------------------------------------------------------------------------
00215 void PopAccount::readConfig(KConfig& config)
00216 {
00217   NetworkAccount::readConfig(config);
00218 
00219   mUsePipelining = config.readNumEntry("pipelining", false);
00220   mLeaveOnServer = config.readNumEntry("leave-on-server", false);
00221   mLeaveOnServerDays = config.readNumEntry("leave-on-server-days", -1);
00222   mLeaveOnServerCount = config.readNumEntry("leave-on-server-count", -1);
00223   mLeaveOnServerSize = config.readNumEntry("leave-on-server-size", -1);
00224   mFilterOnServer = config.readNumEntry("filter-on-server", false);
00225   mFilterOnServerCheckSize = config.readUnsignedNumEntry("filter-os-check-size", 50000);
00226 }
00227 
00228 
00229 //-----------------------------------------------------------------------------
00230 void PopAccount::writeConfig(KConfig& config)
00231 {
00232   NetworkAccount::writeConfig(config);
00233 
00234   config.writeEntry("pipelining", mUsePipelining);
00235   config.writeEntry("leave-on-server", mLeaveOnServer);
00236   config.writeEntry("leave-on-server-days", mLeaveOnServerDays);
00237   config.writeEntry("leave-on-server-count", mLeaveOnServerCount);
00238   config.writeEntry("leave-on-server-size", mLeaveOnServerSize);
00239   config.writeEntry("filter-on-server", mFilterOnServer);
00240   config.writeEntry("filter-os-check-size", mFilterOnServerCheckSize);
00241 }
00242 
00243 
00244 //-----------------------------------------------------------------------------
00245 void PopAccount::setUsePipelining(bool b)
00246 {
00247   mUsePipelining = b;
00248 }
00249 
00250 //-----------------------------------------------------------------------------
00251 void PopAccount::setLeaveOnServer(bool b)
00252 {
00253   mLeaveOnServer = b;
00254 }
00255 
00256 //-----------------------------------------------------------------------------
00257 void PopAccount::setLeaveOnServerDays(int days)
00258 {
00259   mLeaveOnServerDays = days;
00260 }
00261 
00262 //-----------------------------------------------------------------------------
00263 void PopAccount::setLeaveOnServerCount(int count)
00264 {
00265   mLeaveOnServerCount = count;
00266 }
00267 
00268 //-----------------------------------------------------------------------------
00269 void PopAccount::setLeaveOnServerSize(int size)
00270 {
00271   mLeaveOnServerSize = size;
00272 }
00273 
00274 //---------------------------------------------------------------------------
00275 void PopAccount::setFilterOnServer(bool b)
00276 {
00277   mFilterOnServer = b;
00278 }
00279 
00280 //---------------------------------------------------------------------------
00281 void PopAccount::setFilterOnServerCheckSize(unsigned int aSize)
00282 {
00283   mFilterOnServerCheckSize = aSize;
00284 }
00285 
00286 //-----------------------------------------------------------------------------
00287 void PopAccount::connectJob() {
00288   KIO::Scheduler::assignJobToSlave(mSlave, job);
00289   connect(job, SIGNAL( data( KIO::Job*, const QByteArray &)),
00290          SLOT( slotData( KIO::Job*, const QByteArray &)));
00291   connect(job, SIGNAL( result( KIO::Job * ) ),
00292          SLOT( slotResult( KIO::Job * ) ) );
00293   connect(job, SIGNAL(infoMessage( KIO::Job*, const QString & )),
00294          SLOT( slotMsgRetrieved(KIO::Job*, const QString &)));
00295 }
00296 
00297 
00298 //-----------------------------------------------------------------------------
00299 void PopAccount::slotCancel()
00300 {
00301   mMsgsPendingDownload.clear();
00302   processRemainingQueuedMessages();
00303   saveUidList();
00304   slotJobFinished();
00305 }
00306 
00307 
00308 //-----------------------------------------------------------------------------
00309 void PopAccount::slotProcessPendingMsgs()
00310 {
00311   if (mProcessing) // not reentrant
00312     return;
00313   mProcessing = true;
00314 
00315   bool addedOk;
00316   QValueList<KMMessage*>::Iterator cur = msgsAwaitingProcessing.begin();
00317   QStringList::Iterator curId = msgIdsAwaitingProcessing.begin();
00318   QStringList::Iterator curUid = msgUidsAwaitingProcessing.begin();
00319 
00320   while (cur != msgsAwaitingProcessing.end()) {
00321     // note we can actually end up processing events in processNewMsg
00322     // this happens when send receipts is turned on
00323     // hence the check for re-entry at the start of this method.
00324     // -sanders Update processNewMsg should no longer process events
00325 
00326     addedOk = processNewMsg(*cur); //added ok? Error displayed if not.
00327 
00328     if (!addedOk) {
00329       mMsgsPendingDownload.clear();
00330       msgIdsAwaitingProcessing.clear();
00331       msgUidsAwaitingProcessing.clear();
00332       break;
00333     }
00334     else {
00335       idsOfMsgsToDelete.append( *curId );
00336       mUidsOfNextSeenMsgsDict.insert( *curUid, (const int *)1 );
00337       mTimeOfNextSeenMsgsMap.insert( *curUid, time(0) );
00338     }
00339     ++cur;
00340     ++curId;
00341     ++curUid;
00342   }
00343 
00344   msgsAwaitingProcessing.clear();
00345   msgIdsAwaitingProcessing.clear();
00346   msgUidsAwaitingProcessing.clear();
00347   mProcessing = false;
00348 }
00349 
00350 
00351 //-----------------------------------------------------------------------------
00352 void PopAccount::slotAbortRequested()
00353 {
00354   if (stage == Idle) return;
00355   if ( mMailCheckProgressItem )
00356     disconnect( mMailCheckProgressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
00357                 this, SLOT( slotAbortRequested() ) );
00358   stage = Quit;
00359   if (job) job->kill();
00360   job = 0;
00361   mSlave = 0;
00362   slotCancel();
00363 }
00364 
00365 
00366 //-----------------------------------------------------------------------------
00367 void PopAccount::startJob()
00368 {
00369   // Run the precommand
00370   if (!runPrecommand(precommand()))
00371     {
00372       KMessageBox::sorry(0,
00373                          i18n("Could not execute precommand: %1").arg(precommand()),
00374                          i18n("KMail Error Message"));
00375       checkDone( false, CheckError );
00376       return;
00377     }
00378   // end precommand code
00379 
00380   KURL url = getUrl();
00381 
00382   if ( !url.isValid() ) {
00383     KMessageBox::error(0, i18n("Source URL is malformed"),
00384                           i18n("Kioslave Error Message") );
00385     return;
00386   }
00387 
00388   mMsgsPendingDownload.clear();
00389   idsOfMsgs.clear();
00390   mUidForIdMap.clear();
00391   idsOfMsgsToDelete.clear();
00392   idsOfForcedDeletes.clear();
00393 
00394   //delete any headers if there are some this have to be done because of check again
00395   headersOnServer.clear();
00396   headers = false;
00397   indexOfCurrentMsg = -1;
00398 
00399   Q_ASSERT( !mMailCheckProgressItem );
00400   QString escapedName = QStyleSheet::escape( mName );
00401   mMailCheckProgressItem = KPIM::ProgressManager::createProgressItem(
00402     "MailCheck" + mName,
00403     escapedName,
00404     i18n("Preparing transmission from \"%1\"...").arg( escapedName ),
00405     true, // can be canceled
00406     useSSL() || useTLS() );
00407   connect( mMailCheckProgressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
00408            this, SLOT( slotAbortRequested() ) );
00409 
00410   numBytes = 0;
00411   numBytesRead = 0;
00412   stage = List;
00413   mSlave = KIO::Scheduler::getConnectedSlave( url, slaveConfig() );
00414   if (!mSlave)
00415   {
00416     slotSlaveError(0, KIO::ERR_CANNOT_LAUNCH_PROCESS, url.protocol());
00417     return;
00418   }
00419   url.setPath(QString("/index"));
00420   job = KIO::get( url, false, false );
00421   connectJob();
00422 }
00423 
00424 MetaData PopAccount::slaveConfig() const {
00425   MetaData m = NetworkAccount::slaveConfig();
00426 
00427   m.insert("progress", "off");
00428   m.insert("pipelining", (mUsePipelining) ? "on" : "off");
00429   if (mAuth == "PLAIN" || mAuth == "LOGIN" || mAuth == "CRAM-MD5" ||
00430       mAuth == "DIGEST-MD5" || mAuth == "NTLM" || mAuth == "GSSAPI") {
00431     m.insert("auth", "SASL");
00432     m.insert("sasl", mAuth);
00433   } else if ( mAuth == "*" )
00434     m.insert("auth", "USER");
00435   else
00436     m.insert("auth", mAuth);
00437 
00438   return m;
00439 }
00440 
00441 //-----------------------------------------------------------------------------
00442 // one message is finished
00443 // add data to a KMMessage
00444 void PopAccount::slotMsgRetrieved(KIO::Job*, const QString & infoMsg)
00445 {
00446   if (infoMsg != "message complete") return;
00447   KMMessage *msg = new KMMessage;
00448   msg->setComplete(true);
00449   // Make sure to use LF as line ending to make the processing easier
00450   // when piping through external programs
00451   uint newSize = Util::crlf2lf( curMsgData.data(), curMsgData.size() );
00452   curMsgData.resize( newSize );
00453   msg->fromByteArray( curMsgData , true );
00454   if (stage == Head)
00455   {
00456     int size = mMsgsPendingDownload[ headerIt.current()->id() ];
00457     kdDebug(5006) << "Size of Message: " << size << endl;
00458     msg->setMsgLength( size );
00459     headerIt.current()->setHeader(msg);
00460     ++headerIt;
00461     slotGetNextHdr();
00462   } else {
00463     //kdDebug(5006) << kfuncinfo << "stage == Retr" << endl;
00464     //kdDebug(5006) << "curMsgData.size() = " << curMsgData.size() << endl;
00465     msg->setMsgLength( curMsgData.size() );
00466     msgsAwaitingProcessing.append(msg);
00467     msgIdsAwaitingProcessing.append(idsOfMsgs[indexOfCurrentMsg]);
00468     msgUidsAwaitingProcessing.append( mUidForIdMap[idsOfMsgs[indexOfCurrentMsg]] );
00469     slotGetNextMsg();
00470   }
00471 }
00472 
00473 
00474 //-----------------------------------------------------------------------------
00475 // finit state machine to cycle trow the stages
00476 void PopAccount::slotJobFinished() {
00477   QStringList emptyList;
00478   if (stage == List) {
00479     kdDebug(5006) << k_funcinfo << "stage == List" << endl;
00480     // set the initial size of mUidsOfNextSeenMsgsDict to the number of
00481     // messages on the server + 10%
00482     mUidsOfNextSeenMsgsDict.resize( KMail::nextPrime( ( idsOfMsgs.count() * 11 ) / 10 ) );
00483     KURL url = getUrl();
00484     url.setPath(QString("/uidl"));
00485     job = KIO::get( url, false, false );
00486     connectJob();
00487     stage = Uidl;
00488   }
00489   else if (stage == Uidl) {
00490     kdDebug(5006) << k_funcinfo << "stage == Uidl" << endl;
00491     mUidlFinished = true;
00492 
00493     if ( mLeaveOnServer && mUidForIdMap.isEmpty() &&
00494         mUidsOfNextSeenMsgsDict.isEmpty() && !idsOfMsgs.isEmpty() ) {
00495       KMessageBox::sorry(0, i18n("Your POP3 server (Account: %1) does not support "
00496       "the UIDL command: this command is required to determine, in a reliable way, "
00497       "which of the mails on the server KMail has already seen before;\n"
00498       "the feature to leave the mails on the server will therefore not "
00499       "work properly.").arg(NetworkAccount::name()) );
00500       // An attempt to work around buggy pop servers, these seem to be popular.
00501       mUidsOfNextSeenMsgsDict = mUidsOfSeenMsgsDict;
00502     }
00503 
00504     //check if filter on server
00505     if (mFilterOnServer == true) {
00506       QMap<QString, int>::Iterator hids;
00507       for ( hids = mMsgsPendingDownload.begin();
00508             hids != mMsgsPendingDownload.end(); hids++ ) {
00509           kdDebug(5006) << "Length: " << hids.data() << endl;
00510           //check for mails bigger mFilterOnServerCheckSize
00511           if ( (unsigned int)hids.data() >= mFilterOnServerCheckSize ) {
00512             kdDebug(5006) << "bigger than " << mFilterOnServerCheckSize << endl;
00513             headersOnServer.append(new KMPopHeaders( hids.key(),
00514                                                      mUidForIdMap[hids.key()],
00515                                                      Later));//TODO
00516             //set Action if already known
00517             if( mHeaderDeleteUids.contains( headersOnServer.current()->uid() ) ) {
00518               headersOnServer.current()->setAction(Delete);
00519             }
00520             else if( mHeaderDownUids.contains( headersOnServer.current()->uid() ) ) {
00521               headersOnServer.current()->setAction(Down);
00522             }
00523             else if( mHeaderLaterUids.contains( headersOnServer.current()->uid() ) ) {
00524               headersOnServer.current()->setAction(Later);
00525             }
00526           }
00527       }
00528       // delete the uids so that you don't get them twice in the list
00529       mHeaderDeleteUids.clear();
00530       mHeaderDownUids.clear();
00531       mHeaderLaterUids.clear();
00532     }
00533     // kdDebug(5006) << "Num of Msgs to Filter: " << headersOnServer.count() << endl;
00534     // if there are mails which should be checkedc download the headers
00535     if ((headersOnServer.count() > 0) && (mFilterOnServer == true)) {
00536       headerIt.toFirst();
00537       KURL url = getUrl();
00538       QString headerIds;
00539       while (headerIt.current())
00540       {
00541         headerIds += headerIt.current()->id();
00542         if (!headerIt.atLast()) headerIds += ",";
00543         ++headerIt;
00544       }
00545       headerIt.toFirst();
00546       url.setPath(QString("/headers/") + headerIds);
00547       job = KIO::get( url, false, false );
00548       connectJob();
00549       slotGetNextHdr();
00550       stage = Head;
00551     }
00552     else {
00553       stage = Retr;
00554       numMsgs = mMsgsPendingDownload.count();
00555       numBytesToRead = 0;
00556       QMap<QString, int>::Iterator len;
00557       for ( len  = mMsgsPendingDownload.begin();
00558             len != mMsgsPendingDownload.end(); len++ )
00559         numBytesToRead += len.data();
00560       idsOfMsgs = QStringList( mMsgsPendingDownload.keys() );
00561       KURL url = getUrl();
00562       url.setPath( "/download/" + idsOfMsgs.join(",") );
00563       job = KIO::get( url, false, false );
00564       connectJob();
00565       slotGetNextMsg();
00566       processMsgsTimer.start(processingDelay);
00567     }
00568   }
00569   else if (stage == Head) {
00570     kdDebug(5006) << k_funcinfo << "stage == Head" << endl;
00571 
00572     // All headers have been downloaded, check which mail you want to get
00573     // data is in list headersOnServer
00574 
00575     // check if headers apply to a filter
00576     // if set the action of the filter
00577     KMPopFilterAction action;
00578     bool dlgPopup = false;
00579     for (headersOnServer.first(); headersOnServer.current(); headersOnServer.next()) {
00580       action = (KMPopFilterAction)kmkernel->popFilterMgr()->process(headersOnServer.current()->header());
00581       //debug todo
00582       switch ( action ) {
00583         case NoAction:
00584           kdDebug(5006) << "PopFilterAction = NoAction" << endl;
00585           break;
00586         case Later:
00587           kdDebug(5006) << "PopFilterAction = Later" << endl;
00588           break;
00589         case Delete:
00590           kdDebug(5006) << "PopFilterAction = Delete" << endl;
00591           break;
00592         case Down:
00593           kdDebug(5006) << "PopFilterAction = Down" << endl;
00594           break;
00595         default:
00596           kdDebug(5006) << "PopFilterAction = default oops!" << endl;
00597           break;
00598       }
00599       switch ( action ) {
00600         case NoAction:
00601           //kdDebug(5006) << "PopFilterAction = NoAction" << endl;
00602           dlgPopup = true;
00603           break;
00604         case Later:
00605           if (kmkernel->popFilterMgr()->showLaterMsgs())
00606             dlgPopup = true;
00607           // fall through
00608         default:
00609           headersOnServer.current()->setAction(action);
00610           headersOnServer.current()->setRuleMatched(true);
00611           break;
00612       }
00613     }
00614 
00615     // if there are some messages which are not coverd by a filter
00616     // show the dialog
00617     headers = true;
00618     if (dlgPopup) {
00619       KMPopFilterCnfrmDlg dlg(&headersOnServer, this->name(), kmkernel->popFilterMgr()->showLaterMsgs());
00620       dlg.exec();
00621     }
00622 
00623     for (headersOnServer.first(); headersOnServer.current(); headersOnServer.next()) {
00624       if (headersOnServer.current()->action() == Delete ||
00625           headersOnServer.current()->action() == Later) {
00626         //remove entries from the lists when the mails should not be downloaded
00627         //(deleted or downloaded later)
00628         if ( mMsgsPendingDownload.contains( headersOnServer.current()->id() ) ) {
00629           mMsgsPendingDownload.remove( headersOnServer.current()->id() );
00630         }
00631         if (headersOnServer.current()->action() == Delete) {
00632           mHeaderDeleteUids.insert(headersOnServer.current()->uid(), true);
00633           mUidsOfNextSeenMsgsDict.insert( headersOnServer.current()->uid(),
00634                                           (const int *)1 );
00635           idsOfMsgsToDelete.append(headersOnServer.current()->id());
00636           mTimeOfNextSeenMsgsMap.insert( headersOnServer.current()->uid(),
00637                                           time(0) );
00638         }
00639         else {
00640           mHeaderLaterUids.insert(headersOnServer.current()->uid(), true);
00641         }
00642       }
00643       else if (headersOnServer.current()->action() == Down) {
00644         mHeaderDownUids.insert(headersOnServer.current()->uid(), true);
00645       }
00646     }
00647 
00648     headersOnServer.clear();
00649     stage = Retr;
00650     numMsgs = mMsgsPendingDownload.count();
00651     numBytesToRead = 0;
00652     QMap<QString, int>::Iterator len;
00653     for (len = mMsgsPendingDownload.begin();
00654          len != mMsgsPendingDownload.end(); len++)
00655       numBytesToRead += len.data();
00656     idsOfMsgs = QStringList( mMsgsPendingDownload.keys() );
00657     KURL url = getUrl();
00658     url.setPath( "/download/" + idsOfMsgs.join(",") );
00659     job = KIO::get( url, false, false );
00660     connectJob();
00661     slotGetNextMsg();
00662     processMsgsTimer.start(processingDelay);
00663   }
00664   else if (stage == Retr) {
00665     if ( mMailCheckProgressItem )
00666       mMailCheckProgressItem->setProgress( 100 );
00667     processRemainingQueuedMessages();
00668 
00669     mHeaderDeleteUids.clear();
00670     mHeaderDownUids.clear();
00671     mHeaderLaterUids.clear();
00672 
00673     kmkernel->folderMgr()->syncAllFolders();
00674 
00675     KURL url = getUrl();
00676     QMap< QPair<time_t, QString>, int > idsToSave;
00677     idsToSave.clear();
00678     // Check if we want to keep any messages
00679     if ( mLeaveOnServer && !idsOfMsgsToDelete.isEmpty() ) {
00680       // Keep all messages on server
00681       if ( mLeaveOnServerDays == -1 && mLeaveOnServerCount <= 0 &&
00682            mLeaveOnServerSize <= 0)
00683         idsOfMsgsToDelete.clear();
00684       // Delete old messages
00685       else if ( mLeaveOnServerDays > 0 && !mTimeOfNextSeenMsgsMap.isEmpty() ) {
00686         time_t timeLimit = time(0) - (86400 * mLeaveOnServerDays);
00687         kdDebug() << "timeLimit is " << timeLimit << endl;
00688         QStringList::Iterator cur = idsOfMsgsToDelete.begin();
00689         for ( ; cur != idsOfMsgsToDelete.end(); ++cur) {
00690           time_t msgTime = mTimeOfNextSeenMsgsMap[mUidForIdMap[*cur]];
00691           kdDebug() << "id: " << *cur << " msgTime: " << msgTime << endl;
00692           if (msgTime >= timeLimit ||
00693                 !mTimeOfNextSeenMsgsMap[mUidForIdMap[*cur]]) {
00694             kdDebug() << "Saving msg id " << *cur << endl;
00695             QPair<time_t, QString> msg(msgTime, *cur);
00696             idsToSave.insert( msg, 1 );
00697           }
00698         }
00699       }
00700       // Delete more old messages if there are more than mLeaveOnServerCount
00701       if ( mLeaveOnServerCount > 0 ) {
00702         int numToDelete = idsToSave.count() - mLeaveOnServerCount;
00703         kdDebug() << "numToDelete is " << numToDelete << endl;
00704         if ( numToDelete > 0 && (unsigned)numToDelete < idsToSave.count() ) {
00705           QMap< QPair<time_t, QString>, int >::Iterator cur = idsToSave.begin();
00706           for ( int deleted = 0; deleted < numToDelete && cur != idsToSave.end()
00707                 ; deleted++, cur++ ) {
00708             kdDebug() << "deleting msg id " << cur.key().second << endl;
00709             idsToSave.remove( cur );
00710           }
00711         }
00712         else if ( numToDelete > 0 && (unsigned)numToDelete >= idsToSave.count() )
00713           idsToSave.clear();
00714       }
00715       // Delete more old messages until we're under mLeaveOnServerSize MBs
00716       if ( mLeaveOnServerSize > 0 ) {
00717         double sizeOnServer = 0;
00718         QMap< QPair<time_t, QString>, int >::Iterator cur = idsToSave.begin();
00719         for ( ; cur != idsToSave.end(); cur++ ) {
00720           sizeOnServer +=
00721             *mSizeOfNextSeenMsgsDict[ mUidForIdMap[ cur.key().second ] ];
00722         }
00723         kdDebug() << "sizeOnServer is " << sizeOnServer/(1024*1024) << "MB" << endl;
00724         long limitInBytes = mLeaveOnServerSize * ( 1024 * 1024 );
00725         for ( cur = idsToSave.begin(); cur != idsToSave.end()
00726                 && sizeOnServer > limitInBytes; cur++ ) {
00727           sizeOnServer -=
00728             *mSizeOfNextSeenMsgsDict[ mUidForIdMap[ cur.key().second ] ];
00729           idsToSave.remove( cur );
00730         }
00731       }
00732       // Save msgs from deletion
00733       QMap< QPair<time_t, QString>, int >::Iterator it = idsToSave.begin();
00734       kdDebug() << "Going to save " << idsToSave.count() << endl;
00735       for ( ; it != idsToSave.end(); ++it ) {
00736         kdDebug() << "saving msg id " << it.key().second << endl;
00737         idsOfMsgsToDelete.remove( it.key().second );
00738       }
00739     }
00740 
00741     if ( !idsOfForcedDeletes.isEmpty() ) {
00742       idsOfMsgsToDelete += idsOfForcedDeletes;
00743       idsOfForcedDeletes.clear();
00744     }
00745 
00746     // If there are messages to delete then delete them
00747     if ( !idsOfMsgsToDelete.isEmpty() ) {
00748       stage = Dele;
00749       if ( mMailCheckProgressItem )
00750         mMailCheckProgressItem->setStatus(
00751           i18n( "Fetched 1 message from %1. Deleting messages from server...",
00752                 "Fetched %n messages from %1. Deleting messages from server...",
00753                 numMsgs )
00754           .arg( mHost ) );
00755       url.setPath("/remove/" + idsOfMsgsToDelete.join(","));
00756       kdDebug(5006) << "url: " << url.prettyURL() << endl;
00757     } else {
00758       stage = Quit;
00759       if ( mMailCheckProgressItem )
00760         mMailCheckProgressItem->setStatus(
00761           i18n( "Fetched 1 message from %1. Terminating transmission...",
00762                 "Fetched %n messages from %1. Terminating transmission...",
00763                 numMsgs )
00764           .arg( mHost ) );
00765       url.setPath(QString("/commit"));
00766       kdDebug(5006) << "url: " << url.prettyURL() << endl;
00767     }
00768     job = KIO::get( url, false, false );
00769     connectJob();
00770   }
00771   else if (stage == Dele) {
00772     kdDebug(5006) << k_funcinfo << "stage == Dele" << endl;
00773     // remove the uids of all messages which have been deleted
00774     for ( QStringList::ConstIterator it = idsOfMsgsToDelete.begin();
00775           it != idsOfMsgsToDelete.end(); ++it ) {
00776       mUidsOfNextSeenMsgsDict.remove( mUidForIdMap[*it] );
00777     }
00778     idsOfMsgsToDelete.clear();
00779     if ( mMailCheckProgressItem )
00780       mMailCheckProgressItem->setStatus(
00781         i18n( "Fetched 1 message from %1. Terminating transmission...",
00782               "Fetched %n messages from %1. Terminating transmission...",
00783               numMsgs )
00784         .arg( mHost ) );
00785     KURL url = getUrl();
00786     url.setPath(QString("/commit"));
00787     job = KIO::get( url, false, false );
00788     stage = Quit;
00789     connectJob();
00790   }
00791   else if (stage == Quit) {
00792     kdDebug(5006) << k_funcinfo << "stage == Quit" << endl;
00793     saveUidList();
00794     job = 0;
00795     if (mSlave) KIO::Scheduler::disconnectSlave(mSlave);
00796     mSlave = 0;
00797     stage = Idle;
00798     if( mMailCheckProgressItem ) { // do this only once...
00799       bool canceled = !kmkernel || kmkernel->mailCheckAborted() || mMailCheckProgressItem->canceled();
00800       int numMessages = canceled ? indexOfCurrentMsg : idsOfMsgs.count();
00801       BroadcastStatus::instance()->setStatusMsgTransmissionCompleted(
00802         this->name(), numMessages, numBytes, numBytesRead, numBytesToRead, mLeaveOnServer, mMailCheckProgressItem );
00803       // set mMailCheckProgressItem = 0 before calling setComplete() to prevent
00804       // a race condition
00805       ProgressItem *savedMailCheckProgressItem = mMailCheckProgressItem;
00806       mMailCheckProgressItem = 0;
00807       savedMailCheckProgressItem->setComplete(); // that will delete it
00808       checkDone( ( numMessages > 0 ), canceled ? CheckAborted : CheckOK );
00809     }
00810   }
00811 }
00812 
00813 
00814 //-----------------------------------------------------------------------------
00815 void PopAccount::processRemainingQueuedMessages()
00816 {
00817   kdDebug(5006) << k_funcinfo << endl;
00818   slotProcessPendingMsgs(); // Force processing of any messages still in the queue
00819   processMsgsTimer.stop();
00820 
00821   stage = Quit;
00822   if ( kmkernel && kmkernel->folderMgr() ) {
00823     kmkernel->folderMgr()->syncAllFolders();
00824   }
00825 }
00826 
00827 
00828 //-----------------------------------------------------------------------------
00829 void PopAccount::saveUidList()
00830 {
00831   kdDebug(5006) << k_funcinfo << endl;
00832   // Don't update the seen uid list unless we successfully got
00833   // a new list from the server
00834   if (!mUidlFinished) return;
00835 
00836   QStringList uidsOfNextSeenMsgs;
00837   QValueList<int> seenUidTimeList;
00838   QDictIterator<int> it( mUidsOfNextSeenMsgsDict );
00839   for( ; it.current(); ++it ) {
00840     uidsOfNextSeenMsgs.append( it.currentKey() );
00841     seenUidTimeList.append( mTimeOfNextSeenMsgsMap[it.currentKey()] );
00842   }
00843   QString seenUidList = locateLocal( "data", "kmail/" + mLogin + ":" + "@" +
00844                                       mHost + ":" + QString("%1").arg(mPort) );
00845   KConfig config( seenUidList );
00846   config.writeEntry( "seenUidList", uidsOfNextSeenMsgs );
00847   config.writeEntry( "seenUidTimeList", seenUidTimeList );
00848   config.writeEntry( "downloadLater", QStringList( mHeaderLaterUids.keys() ) );
00849   config.sync();
00850 }
00851 
00852 
00853 //-----------------------------------------------------------------------------
00854 void PopAccount::slotGetNextMsg()
00855 {
00856   QMap<QString, int>::Iterator next = mMsgsPendingDownload.begin();
00857 
00858   curMsgData.resize(0);
00859   numMsgBytesRead = 0;
00860   curMsgLen = 0;
00861   delete curMsgStrm;
00862   curMsgStrm = 0;
00863 
00864   if ( next != mMsgsPendingDownload.end() ) {
00865     // get the next message
00866     int nextLen = next.data();
00867     curMsgStrm = new QDataStream( curMsgData, IO_WriteOnly );
00868     curMsgLen = nextLen;
00869     ++indexOfCurrentMsg;
00870     kdDebug(5006) << QString("Length of message about to get %1").arg( nextLen ) << endl;
00871     mMsgsPendingDownload.remove( next.key() );
00872   }
00873 }
00874 
00875 
00876 //-----------------------------------------------------------------------------
00877 void PopAccount::slotData( KIO::Job* job, const QByteArray &data)
00878 {
00879   if (data.size() == 0) {
00880     kdDebug(5006) << "Data: <End>" << endl;
00881     if ((stage == Retr) && (numMsgBytesRead < curMsgLen))
00882       numBytesRead += curMsgLen - numMsgBytesRead;
00883     else if (stage == Head){
00884       kdDebug(5006) << "Head: <End>" << endl;
00885     }
00886     return;
00887   }
00888 
00889   int oldNumMsgBytesRead = numMsgBytesRead;
00890   if (stage == Retr) {
00891     headers = false;
00892     curMsgStrm->writeRawBytes( data.data(), data.size() );
00893     numMsgBytesRead += data.size();
00894     if (numMsgBytesRead > curMsgLen)
00895       numMsgBytesRead = curMsgLen;
00896     numBytesRead += numMsgBytesRead - oldNumMsgBytesRead;
00897     dataCounter++;
00898     if ( mMailCheckProgressItem &&
00899          ( dataCounter % 5 == 0 ||
00900            ( indexOfCurrentMsg + 1 == numMsgs && numMsgBytesRead == curMsgLen ) ) )
00901     {
00902       QString msg;
00903       if (numBytes != numBytesToRead && mLeaveOnServer)
00904       {
00905         msg = i18n("Fetching message %1 of %2 (%3 of %4 KB) for %5@%6 "
00906                    "(%7 KB remain on the server).")
00907           .arg(indexOfCurrentMsg+1).arg(numMsgs).arg(numBytesRead/1024)
00908           .arg(numBytesToRead/1024).arg(mLogin).arg(mHost).arg(numBytes/1024);
00909       }
00910       else
00911       {
00912         msg = i18n("Fetching message %1 of %2 (%3 of %4 KB) for %5@%6.")
00913           .arg(indexOfCurrentMsg+1).arg(numMsgs).arg(numBytesRead/1024)
00914           .arg(numBytesToRead/1024).arg(mLogin).arg(mHost);
00915       }
00916       mMailCheckProgressItem->setStatus( msg );
00917       mMailCheckProgressItem->setProgress(
00918         (numBytesToRead <= 100) ? 50  // We never know what the server tells us
00919         // This way of dividing is required for > 21MB of mail
00920         : (numBytesRead / (numBytesToRead / 100)) );
00921     }
00922     return;
00923   }
00924 
00925   if (stage == Head) {
00926     curMsgStrm->writeRawBytes( data.data(), data.size() );
00927     return;
00928   }
00929 
00930   // otherwise stage is List Or Uidl
00931   QString qdata = data;
00932   qdata = qdata.simplifyWhiteSpace(); // Workaround for Maillennium POP3/UNIBOX
00933   int spc = qdata.find( ' ' );
00934   if ( stage == List ) {
00935     if ( spc > 0 ) {
00936       QString length = qdata.mid(spc+1);
00937       if (length.find(' ') != -1) length.truncate(length.find(' '));
00938       int len = length.toInt();
00939       numBytes += len;
00940       QString id = qdata.left(spc);
00941       idsOfMsgs.append( id );
00942       mMsgsPendingDownload.insert( id, len );
00943     }
00944     else {
00945       stage = Idle;
00946       if ( job ) job->kill();
00947       job = 0;
00948       mSlave = 0;
00949       KMessageBox::error( 0, i18n( "Unable to complete LIST operation." ),
00950                              i18n( "Invalid Response From Server") );
00951       return;
00952     }
00953   }
00954   else { // stage == Uidl
00955     Q_ASSERT ( stage == Uidl);
00956 
00957     QString id;
00958     QString uid;
00959 
00960     if ( spc <= 0 ) {
00961       // an invalid uidl line. we might just need to skip it, but
00962       // some servers generate invalid uids with valid ids. in that
00963       // case we will just make up a uid - which will cause us to
00964       // not cache the document, but we will be able to interoperate
00965 
00966       int testid = atoi ( qdata.ascii() );
00967       if ( testid < 1 ) {
00968         // we'll just have to skip this
00969         kdDebug(5006) << "PopAccount::slotData skipping UIDL entry due to parse error "
00970                       << endl << qdata.ascii() << endl;
00971         return;
00972       }
00973       id.setNum (testid, 10);
00974 
00975       QString datestring, serialstring;
00976 
00977       serialstring.setNum ( ++dataCounter, 10 );
00978       datestring.setNum ( time(NULL),10 );
00979       uid = QString( "uidlgen" ) + datestring + QString( "." ) + serialstring;
00980       kdDebug(5006) << "PopAccount::slotData message " << id.ascii()
00981                     <<  "%d has bad UIDL, cannot keep a copy on server" << endl;
00982       idsOfForcedDeletes.append( id );
00983     }
00984     else {
00985       id = qdata.left( spc );
00986       uid = qdata.mid( spc + 1 );
00987     }
00988 
00989     int *size = new int; //malloc(size_of(int));
00990     *size = mMsgsPendingDownload[id];
00991     mSizeOfNextSeenMsgsDict.insert( uid, size );
00992     if ( mUidsOfSeenMsgsDict.find( uid ) != 0 ) {
00993       if ( mMsgsPendingDownload.contains( id ) ) {
00994         mMsgsPendingDownload.remove( id );
00995       }
00996       else
00997         kdDebug(5006) << "PopAccount::slotData synchronization failure." << endl;
00998       idsOfMsgsToDelete.append( id );
00999       mUidsOfNextSeenMsgsDict.insert( uid, (const int *)1 );
01000       if ( mTimeOfSeenMsgsVector.empty() ) {
01001         mTimeOfNextSeenMsgsMap.insert( uid, time(0) );
01002       }
01003       else {
01004         // cast the int* with a long to can convert it to a int, BTW
01005         // works with g++-4.0 and amd64
01006         mTimeOfNextSeenMsgsMap.insert( uid, mTimeOfSeenMsgsVector[(int)( long )
01007                                                  mUidsOfSeenMsgsDict[uid] - 1] );
01008       }
01009     }
01010     mUidForIdMap.insert( id, uid );
01011   }
01012 }
01013 
01014 //-----------------------------------------------------------------------------
01015 void PopAccount::slotResult( KIO::Job* )
01016 {
01017   if (!job) return;
01018   if ( job->error() )
01019   {
01020     if (interactive) {
01021       if (headers) { // nothing to be done for headers
01022         idsOfMsgs.clear();
01023       }
01024       if (stage == Head && job->error() == KIO::ERR_COULD_NOT_READ)
01025       {
01026         KMessageBox::error(0, i18n("Your server does not support the "
01027           "TOP command. Therefore it is not possible to fetch the headers "
01028           "of large emails first, before downloading them."));
01029         slotCancel();
01030         return;
01031       }
01032       // force the dialog to be shown next time the account is checked
01033       if (!mStorePasswd) mPasswd = "";
01034       job->showErrorDialog();
01035     }
01036     slotCancel();
01037   }
01038   else
01039     slotJobFinished();
01040 }
01041 
01042 
01043 //-----------------------------------------------------------------------------
01044 void PopAccount::slotSlaveError(KIO::Slave *aSlave, int error,
01045   const QString &errorMsg)
01046 {
01047   if (aSlave != mSlave) return;
01048   if (error == KIO::ERR_SLAVE_DIED) mSlave = 0;
01049 
01050   // explicitely disconnect the slave if the connection went down
01051   if ( error == KIO::ERR_CONNECTION_BROKEN && mSlave ) {
01052     KIO::Scheduler::disconnectSlave( mSlave );
01053     mSlave = 0;
01054   }
01055 
01056   if (interactive && kmkernel) {
01057     KMessageBox::error(kmkernel->mainWin(), KIO::buildErrorString(error, errorMsg));
01058   }
01059 
01060 
01061   stage = Quit;
01062   if (error == KIO::ERR_COULD_NOT_LOGIN && !mStorePasswd)
01063     mAskAgain = true;
01064   /* We need a timer, otherwise slotSlaveError of the next account is also
01065      executed, if it reuses the slave, because the slave member variable
01066      is changed too early */
01067   QTimer::singleShot(0, this, SLOT(slotCancel()));
01068 }
01069 
01070 //-----------------------------------------------------------------------------
01071 void PopAccount::slotGetNextHdr(){
01072   kdDebug(5006) << "slotGetNextHeader" << endl;
01073 
01074   curMsgData.resize(0);
01075   delete curMsgStrm;
01076   curMsgStrm = 0;
01077 
01078   curMsgStrm = new QDataStream( curMsgData, IO_WriteOnly );
01079 }
01080 
01081 void PopAccount::killAllJobs( bool ) {
01082   // must reimpl., but we don't use it yet
01083 }
01084 
01085 } // namespace KMail
01086 #include "popaccount.moc"