summaryrefslogtreecommitdiffstats
path: root/kmail/popaccount.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kmail/popaccount.cpp')
-rw-r--r--kmail/popaccount.cpp1086
1 files changed, 1086 insertions, 0 deletions
diff --git a/kmail/popaccount.cpp b/kmail/popaccount.cpp
new file mode 100644
index 000000000..ebe8bf48b
--- /dev/null
+++ b/kmail/popaccount.cpp
@@ -0,0 +1,1086 @@
+/*
+ This file is part of KMail, the KDE mail client.
+ Copyright (c) 2000 Don Sanders <sanders@kde.org>
+
+ Based on popaccount by:
+ Stefan Taferner <taferner@kde.org>
+ Markus Wuebben <markus.wuebben@kde.org>
+
+ KMail is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License, version 2, as
+ published by the Free Software Foundation.
+
+ KMail is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "popaccount.h"
+
+#include "broadcaststatus.h"
+using KPIM::BroadcastStatus;
+#include "progressmanager.h"
+#include "kmfoldermgr.h"
+#include "kmfiltermgr.h"
+#include "kmpopfiltercnfrmdlg.h"
+#include "protocols.h"
+#include "kmglobal.h"
+#include "util.h"
+#include "accountmanager.h"
+
+#include <kdebug.h>
+#include <kstandarddirs.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kmainwindow.h>
+#include <kio/scheduler.h>
+#include <kio/passdlg.h>
+#include <kconfig.h>
+using KIO::MetaData;
+
+#include <qstylesheet.h>
+
+static const unsigned short int pop3DefaultPort = 110;
+
+namespace KMail {
+//-----------------------------------------------------------------------------
+PopAccount::PopAccount(AccountManager* aOwner, const QString& aAccountName, uint id)
+ : NetworkAccount(aOwner, aAccountName, id),
+ headerIt(headersOnServer),
+ processMsgsTimer( 0, "processMsgsTimer" )
+{
+ init();
+ job = 0;
+ mSlave = 0;
+ mPort = defaultPort();
+ stage = Idle;
+ indexOfCurrentMsg = -1;
+ curMsgStrm = 0;
+ processingDelay = 2*100;
+ mProcessing = false;
+ dataCounter = 0;
+ mUidsOfSeenMsgsDict.setAutoDelete( false );
+ mUidsOfNextSeenMsgsDict.setAutoDelete( false );
+
+ headersOnServer.setAutoDelete(true);
+ connect(&processMsgsTimer,SIGNAL(timeout()),SLOT(slotProcessPendingMsgs()));
+ KIO::Scheduler::connect(
+ SIGNAL(slaveError(KIO::Slave *, int, const QString &)),
+ this, SLOT(slotSlaveError(KIO::Slave *, int, const QString &)));
+
+ mHeaderDeleteUids.clear();
+ mHeaderDownUids.clear();
+ mHeaderLaterUids.clear();
+}
+
+
+//-----------------------------------------------------------------------------
+PopAccount::~PopAccount()
+{
+ if (job) {
+ job->kill();
+ mMsgsPendingDownload.clear();
+ processRemainingQueuedMessages();
+ saveUidList();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+QString PopAccount::type(void) const
+{
+ return "pop";
+}
+
+QString PopAccount::protocol() const {
+ return useSSL() ? POP_SSL_PROTOCOL : POP_PROTOCOL;
+}
+
+unsigned short int PopAccount::defaultPort() const {
+ return pop3DefaultPort;
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::init(void)
+{
+ NetworkAccount::init();
+
+ mUsePipelining = false;
+ mLeaveOnServer = false;
+ mLeaveOnServerDays = -1;
+ mLeaveOnServerCount = -1;
+ mLeaveOnServerSize = -1;
+ mFilterOnServer = false;
+ //tz todo
+ mFilterOnServerCheckSize = 50000;
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::pseudoAssign( const KMAccount * a ) {
+ slotAbortRequested();
+ NetworkAccount::pseudoAssign( a );
+
+ const PopAccount * p = dynamic_cast<const PopAccount*>( a );
+ if ( !p ) return;
+
+ setUsePipelining( p->usePipelining() );
+ setLeaveOnServer( p->leaveOnServer() );
+ setLeaveOnServerDays( p->leaveOnServerDays() );
+ setLeaveOnServerCount( p->leaveOnServerCount() );
+ setLeaveOnServerSize( p->leaveOnServerSize() );
+ setFilterOnServer( p->filterOnServer() );
+ setFilterOnServerCheckSize( p->filterOnServerCheckSize() );
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::processNewMail(bool _interactive)
+{
+ if (stage == Idle) {
+
+ if ( (mAskAgain || passwd().isEmpty() || mLogin.isEmpty()) &&
+ mAuth != "GSSAPI" ) {
+ QString passwd = NetworkAccount::passwd();
+ bool b = storePasswd();
+ if (KIO::PasswordDialog::getNameAndPassword(mLogin, passwd, &b,
+ i18n("You need to supply a username and a password to access this "
+ "mailbox."), false, QString::null, mName, i18n("Account:"))
+ != QDialog::Accepted)
+ {
+ checkDone( false, CheckAborted );
+ return;
+ } else {
+ setPasswd( passwd, b );
+ if ( b ) {
+ kmkernel->acctMgr()->writeConfig( true );
+ }
+ mAskAgain = false;
+ }
+ }
+
+ QString seenUidList = locateLocal( "data", "kmail/" + mLogin + ":" + "@" +
+ mHost + ":" + QString("%1").arg(mPort) );
+ KConfig config( seenUidList );
+ QStringList uidsOfSeenMsgs = config.readListEntry( "seenUidList" );
+ QValueList<int> timeOfSeenMsgs = config.readIntListEntry( "seenUidTimeList" );
+ mUidsOfSeenMsgsDict.clear();
+ mUidsOfSeenMsgsDict.resize( KMail::nextPrime( ( uidsOfSeenMsgs.count() * 11 ) / 10 ) );
+ int idx = 1;
+ for ( QStringList::ConstIterator it = uidsOfSeenMsgs.begin();
+ it != uidsOfSeenMsgs.end(); ++it, idx++ ) {
+ // we use mUidsOfSeenMsgsDict to just provide fast random access to the
+ // keys, so we can store the index(+1) that corresponds to the index of
+ // mTimeOfSeenMsgsVector for use in PopAccount::slotData()
+ mUidsOfSeenMsgsDict.insert( *it, (const int *)idx );
+ }
+ mTimeOfSeenMsgsVector.clear();
+ mTimeOfSeenMsgsVector.reserve( timeOfSeenMsgs.size() );
+ for ( QValueList<int>::ConstIterator it = timeOfSeenMsgs.begin();
+ it != timeOfSeenMsgs.end(); ++it) {
+ mTimeOfSeenMsgsVector.append( *it );
+ }
+ // If the counts differ then the config file has presumably been tampered
+ // with and so to avoid possible unwanted message deletion we'll treat
+ // them all as newly seen by clearing the seen times vector
+ if ( mTimeOfSeenMsgsVector.count() != mUidsOfSeenMsgsDict.count() )
+ mTimeOfSeenMsgsVector.clear();
+ QStringList downloadLater = config.readListEntry( "downloadLater" );
+ for ( QStringList::Iterator it = downloadLater.begin(); it != downloadLater.end(); ++it ) {
+ mHeaderLaterUids.insert( *it, true );
+ }
+ mUidsOfNextSeenMsgsDict.clear();
+ mTimeOfNextSeenMsgsMap.clear();
+ mSizeOfNextSeenMsgsDict.clear();
+
+ interactive = _interactive;
+ mUidlFinished = false;
+ startJob();
+ }
+ else {
+ checkDone( false, CheckIgnored );
+ return;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::readConfig(KConfig& config)
+{
+ NetworkAccount::readConfig(config);
+
+ mUsePipelining = config.readNumEntry("pipelining", false);
+ mLeaveOnServer = config.readNumEntry("leave-on-server", false);
+ mLeaveOnServerDays = config.readNumEntry("leave-on-server-days", -1);
+ mLeaveOnServerCount = config.readNumEntry("leave-on-server-count", -1);
+ mLeaveOnServerSize = config.readNumEntry("leave-on-server-size", -1);
+ mFilterOnServer = config.readNumEntry("filter-on-server", false);
+ mFilterOnServerCheckSize = config.readUnsignedNumEntry("filter-os-check-size", 50000);
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::writeConfig(KConfig& config)
+{
+ NetworkAccount::writeConfig(config);
+
+ config.writeEntry("pipelining", mUsePipelining);
+ config.writeEntry("leave-on-server", mLeaveOnServer);
+ config.writeEntry("leave-on-server-days", mLeaveOnServerDays);
+ config.writeEntry("leave-on-server-count", mLeaveOnServerCount);
+ config.writeEntry("leave-on-server-size", mLeaveOnServerSize);
+ config.writeEntry("filter-on-server", mFilterOnServer);
+ config.writeEntry("filter-os-check-size", mFilterOnServerCheckSize);
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::setUsePipelining(bool b)
+{
+ mUsePipelining = b;
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::setLeaveOnServer(bool b)
+{
+ mLeaveOnServer = b;
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::setLeaveOnServerDays(int days)
+{
+ mLeaveOnServerDays = days;
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::setLeaveOnServerCount(int count)
+{
+ mLeaveOnServerCount = count;
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::setLeaveOnServerSize(int size)
+{
+ mLeaveOnServerSize = size;
+}
+
+//---------------------------------------------------------------------------
+void PopAccount::setFilterOnServer(bool b)
+{
+ mFilterOnServer = b;
+}
+
+//---------------------------------------------------------------------------
+void PopAccount::setFilterOnServerCheckSize(unsigned int aSize)
+{
+ mFilterOnServerCheckSize = aSize;
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::connectJob() {
+ KIO::Scheduler::assignJobToSlave(mSlave, job);
+ connect(job, SIGNAL( data( KIO::Job*, const QByteArray &)),
+ SLOT( slotData( KIO::Job*, const QByteArray &)));
+ connect(job, SIGNAL( result( KIO::Job * ) ),
+ SLOT( slotResult( KIO::Job * ) ) );
+ connect(job, SIGNAL(infoMessage( KIO::Job*, const QString & )),
+ SLOT( slotMsgRetrieved(KIO::Job*, const QString &)));
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::slotCancel()
+{
+ mMsgsPendingDownload.clear();
+ processRemainingQueuedMessages();
+ saveUidList();
+ slotJobFinished();
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::slotProcessPendingMsgs()
+{
+ if (mProcessing) // not reentrant
+ return;
+ mProcessing = true;
+
+ bool addedOk;
+ QValueList<KMMessage*>::Iterator cur = msgsAwaitingProcessing.begin();
+ QStringList::Iterator curId = msgIdsAwaitingProcessing.begin();
+ QStringList::Iterator curUid = msgUidsAwaitingProcessing.begin();
+
+ while (cur != msgsAwaitingProcessing.end()) {
+ // note we can actually end up processing events in processNewMsg
+ // this happens when send receipts is turned on
+ // hence the check for re-entry at the start of this method.
+ // -sanders Update processNewMsg should no longer process events
+
+ addedOk = processNewMsg(*cur); //added ok? Error displayed if not.
+
+ if (!addedOk) {
+ mMsgsPendingDownload.clear();
+ msgIdsAwaitingProcessing.clear();
+ msgUidsAwaitingProcessing.clear();
+ break;
+ }
+ else {
+ idsOfMsgsToDelete.append( *curId );
+ mUidsOfNextSeenMsgsDict.insert( *curUid, (const int *)1 );
+ mTimeOfNextSeenMsgsMap.insert( *curUid, time(0) );
+ }
+ ++cur;
+ ++curId;
+ ++curUid;
+ }
+
+ msgsAwaitingProcessing.clear();
+ msgIdsAwaitingProcessing.clear();
+ msgUidsAwaitingProcessing.clear();
+ mProcessing = false;
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::slotAbortRequested()
+{
+ if (stage == Idle) return;
+ if ( mMailCheckProgressItem )
+ disconnect( mMailCheckProgressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
+ this, SLOT( slotAbortRequested() ) );
+ stage = Quit;
+ if (job) job->kill();
+ job = 0;
+ mSlave = 0;
+ slotCancel();
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::startJob()
+{
+ // Run the precommand
+ if (!runPrecommand(precommand()))
+ {
+ KMessageBox::sorry(0,
+ i18n("Could not execute precommand: %1").arg(precommand()),
+ i18n("KMail Error Message"));
+ checkDone( false, CheckError );
+ return;
+ }
+ // end precommand code
+
+ KURL url = getUrl();
+
+ if ( !url.isValid() ) {
+ KMessageBox::error(0, i18n("Source URL is malformed"),
+ i18n("Kioslave Error Message") );
+ return;
+ }
+
+ mMsgsPendingDownload.clear();
+ idsOfMsgs.clear();
+ mUidForIdMap.clear();
+ idsOfMsgsToDelete.clear();
+ idsOfForcedDeletes.clear();
+
+ //delete any headers if there are some this have to be done because of check again
+ headersOnServer.clear();
+ headers = false;
+ indexOfCurrentMsg = -1;
+
+ Q_ASSERT( !mMailCheckProgressItem );
+ QString escapedName = QStyleSheet::escape( mName );
+ mMailCheckProgressItem = KPIM::ProgressManager::createProgressItem(
+ "MailCheck" + mName,
+ escapedName,
+ i18n("Preparing transmission from \"%1\"...").arg( escapedName ),
+ true, // can be canceled
+ useSSL() || useTLS() );
+ connect( mMailCheckProgressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
+ this, SLOT( slotAbortRequested() ) );
+
+ numBytes = 0;
+ numBytesRead = 0;
+ stage = List;
+ mSlave = KIO::Scheduler::getConnectedSlave( url, slaveConfig() );
+ if (!mSlave)
+ {
+ slotSlaveError(0, KIO::ERR_CANNOT_LAUNCH_PROCESS, url.protocol());
+ return;
+ }
+ url.setPath(QString("/index"));
+ job = KIO::get( url, false, false );
+ connectJob();
+}
+
+MetaData PopAccount::slaveConfig() const {
+ MetaData m = NetworkAccount::slaveConfig();
+
+ m.insert("progress", "off");
+ m.insert("pipelining", (mUsePipelining) ? "on" : "off");
+ if (mAuth == "PLAIN" || mAuth == "LOGIN" || mAuth == "CRAM-MD5" ||
+ mAuth == "DIGEST-MD5" || mAuth == "NTLM" || mAuth == "GSSAPI") {
+ m.insert("auth", "SASL");
+ m.insert("sasl", mAuth);
+ } else if ( mAuth == "*" )
+ m.insert("auth", "USER");
+ else
+ m.insert("auth", mAuth);
+
+ return m;
+}
+
+//-----------------------------------------------------------------------------
+// one message is finished
+// add data to a KMMessage
+void PopAccount::slotMsgRetrieved(KIO::Job*, const QString & infoMsg)
+{
+ if (infoMsg != "message complete") return;
+ KMMessage *msg = new KMMessage;
+ msg->setComplete(true);
+ // Make sure to use LF as line ending to make the processing easier
+ // when piping through external programs
+ uint newSize = Util::crlf2lf( curMsgData.data(), curMsgData.size() );
+ curMsgData.resize( newSize );
+ msg->fromByteArray( curMsgData , true );
+ if (stage == Head)
+ {
+ int size = mMsgsPendingDownload[ headerIt.current()->id() ];
+ kdDebug(5006) << "Size of Message: " << size << endl;
+ msg->setMsgLength( size );
+ headerIt.current()->setHeader(msg);
+ ++headerIt;
+ slotGetNextHdr();
+ } else {
+ //kdDebug(5006) << kfuncinfo << "stage == Retr" << endl;
+ //kdDebug(5006) << "curMsgData.size() = " << curMsgData.size() << endl;
+ msg->setMsgLength( curMsgData.size() );
+ msgsAwaitingProcessing.append(msg);
+ msgIdsAwaitingProcessing.append(idsOfMsgs[indexOfCurrentMsg]);
+ msgUidsAwaitingProcessing.append( mUidForIdMap[idsOfMsgs[indexOfCurrentMsg]] );
+ slotGetNextMsg();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// finit state machine to cycle trow the stages
+void PopAccount::slotJobFinished() {
+ QStringList emptyList;
+ if (stage == List) {
+ kdDebug(5006) << k_funcinfo << "stage == List" << endl;
+ // set the initial size of mUidsOfNextSeenMsgsDict to the number of
+ // messages on the server + 10%
+ mUidsOfNextSeenMsgsDict.resize( KMail::nextPrime( ( idsOfMsgs.count() * 11 ) / 10 ) );
+ KURL url = getUrl();
+ url.setPath(QString("/uidl"));
+ job = KIO::get( url, false, false );
+ connectJob();
+ stage = Uidl;
+ }
+ else if (stage == Uidl) {
+ kdDebug(5006) << k_funcinfo << "stage == Uidl" << endl;
+ mUidlFinished = true;
+
+ if ( mLeaveOnServer && mUidForIdMap.isEmpty() &&
+ mUidsOfNextSeenMsgsDict.isEmpty() && !idsOfMsgs.isEmpty() ) {
+ KMessageBox::sorry(0, i18n("Your POP3 server (Account: %1) does not support "
+ "the UIDL command: this command is required to determine, in a reliable way, "
+ "which of the mails on the server KMail has already seen before;\n"
+ "the feature to leave the mails on the server will therefore not "
+ "work properly.").arg(NetworkAccount::name()) );
+ // An attempt to work around buggy pop servers, these seem to be popular.
+ mUidsOfNextSeenMsgsDict = mUidsOfSeenMsgsDict;
+ }
+
+ //check if filter on server
+ if (mFilterOnServer == true) {
+ QMap<QString, int>::Iterator hids;
+ for ( hids = mMsgsPendingDownload.begin();
+ hids != mMsgsPendingDownload.end(); hids++ ) {
+ kdDebug(5006) << "Length: " << hids.data() << endl;
+ //check for mails bigger mFilterOnServerCheckSize
+ if ( (unsigned int)hids.data() >= mFilterOnServerCheckSize ) {
+ kdDebug(5006) << "bigger than " << mFilterOnServerCheckSize << endl;
+ headersOnServer.append(new KMPopHeaders( hids.key(),
+ mUidForIdMap[hids.key()],
+ Later));//TODO
+ //set Action if already known
+ if( mHeaderDeleteUids.contains( headersOnServer.current()->uid() ) ) {
+ headersOnServer.current()->setAction(Delete);
+ }
+ else if( mHeaderDownUids.contains( headersOnServer.current()->uid() ) ) {
+ headersOnServer.current()->setAction(Down);
+ }
+ else if( mHeaderLaterUids.contains( headersOnServer.current()->uid() ) ) {
+ headersOnServer.current()->setAction(Later);
+ }
+ }
+ }
+ // delete the uids so that you don't get them twice in the list
+ mHeaderDeleteUids.clear();
+ mHeaderDownUids.clear();
+ mHeaderLaterUids.clear();
+ }
+ // kdDebug(5006) << "Num of Msgs to Filter: " << headersOnServer.count() << endl;
+ // if there are mails which should be checkedc download the headers
+ if ((headersOnServer.count() > 0) && (mFilterOnServer == true)) {
+ headerIt.toFirst();
+ KURL url = getUrl();
+ QString headerIds;
+ while (headerIt.current())
+ {
+ headerIds += headerIt.current()->id();
+ if (!headerIt.atLast()) headerIds += ",";
+ ++headerIt;
+ }
+ headerIt.toFirst();
+ url.setPath(QString("/headers/") + headerIds);
+ job = KIO::get( url, false, false );
+ connectJob();
+ slotGetNextHdr();
+ stage = Head;
+ }
+ else {
+ stage = Retr;
+ numMsgs = mMsgsPendingDownload.count();
+ numBytesToRead = 0;
+ QMap<QString, int>::Iterator len;
+ for ( len = mMsgsPendingDownload.begin();
+ len != mMsgsPendingDownload.end(); len++ )
+ numBytesToRead += len.data();
+ idsOfMsgs = QStringList( mMsgsPendingDownload.keys() );
+ KURL url = getUrl();
+ url.setPath( "/download/" + idsOfMsgs.join(",") );
+ job = KIO::get( url, false, false );
+ connectJob();
+ slotGetNextMsg();
+ processMsgsTimer.start(processingDelay);
+ }
+ }
+ else if (stage == Head) {
+ kdDebug(5006) << k_funcinfo << "stage == Head" << endl;
+
+ // All headers have been downloaded, check which mail you want to get
+ // data is in list headersOnServer
+
+ // check if headers apply to a filter
+ // if set the action of the filter
+ KMPopFilterAction action;
+ bool dlgPopup = false;
+ for (headersOnServer.first(); headersOnServer.current(); headersOnServer.next()) {
+ action = (KMPopFilterAction)kmkernel->popFilterMgr()->process(headersOnServer.current()->header());
+ //debug todo
+ switch ( action ) {
+ case NoAction:
+ kdDebug(5006) << "PopFilterAction = NoAction" << endl;
+ break;
+ case Later:
+ kdDebug(5006) << "PopFilterAction = Later" << endl;
+ break;
+ case Delete:
+ kdDebug(5006) << "PopFilterAction = Delete" << endl;
+ break;
+ case Down:
+ kdDebug(5006) << "PopFilterAction = Down" << endl;
+ break;
+ default:
+ kdDebug(5006) << "PopFilterAction = default oops!" << endl;
+ break;
+ }
+ switch ( action ) {
+ case NoAction:
+ //kdDebug(5006) << "PopFilterAction = NoAction" << endl;
+ dlgPopup = true;
+ break;
+ case Later:
+ if (kmkernel->popFilterMgr()->showLaterMsgs())
+ dlgPopup = true;
+ // fall through
+ default:
+ headersOnServer.current()->setAction(action);
+ headersOnServer.current()->setRuleMatched(true);
+ break;
+ }
+ }
+
+ // if there are some messages which are not coverd by a filter
+ // show the dialog
+ headers = true;
+ if (dlgPopup) {
+ KMPopFilterCnfrmDlg dlg(&headersOnServer, this->name(), kmkernel->popFilterMgr()->showLaterMsgs());
+ dlg.exec();
+ }
+
+ for (headersOnServer.first(); headersOnServer.current(); headersOnServer.next()) {
+ if (headersOnServer.current()->action() == Delete ||
+ headersOnServer.current()->action() == Later) {
+ //remove entries from the lists when the mails should not be downloaded
+ //(deleted or downloaded later)
+ if ( mMsgsPendingDownload.contains( headersOnServer.current()->id() ) ) {
+ mMsgsPendingDownload.remove( headersOnServer.current()->id() );
+ }
+ if (headersOnServer.current()->action() == Delete) {
+ mHeaderDeleteUids.insert(headersOnServer.current()->uid(), true);
+ mUidsOfNextSeenMsgsDict.insert( headersOnServer.current()->uid(),
+ (const int *)1 );
+ idsOfMsgsToDelete.append(headersOnServer.current()->id());
+ mTimeOfNextSeenMsgsMap.insert( headersOnServer.current()->uid(),
+ time(0) );
+ }
+ else {
+ mHeaderLaterUids.insert(headersOnServer.current()->uid(), true);
+ }
+ }
+ else if (headersOnServer.current()->action() == Down) {
+ mHeaderDownUids.insert(headersOnServer.current()->uid(), true);
+ }
+ }
+
+ headersOnServer.clear();
+ stage = Retr;
+ numMsgs = mMsgsPendingDownload.count();
+ numBytesToRead = 0;
+ QMap<QString, int>::Iterator len;
+ for (len = mMsgsPendingDownload.begin();
+ len != mMsgsPendingDownload.end(); len++)
+ numBytesToRead += len.data();
+ idsOfMsgs = QStringList( mMsgsPendingDownload.keys() );
+ KURL url = getUrl();
+ url.setPath( "/download/" + idsOfMsgs.join(",") );
+ job = KIO::get( url, false, false );
+ connectJob();
+ slotGetNextMsg();
+ processMsgsTimer.start(processingDelay);
+ }
+ else if (stage == Retr) {
+ if ( mMailCheckProgressItem )
+ mMailCheckProgressItem->setProgress( 100 );
+ processRemainingQueuedMessages();
+
+ mHeaderDeleteUids.clear();
+ mHeaderDownUids.clear();
+ mHeaderLaterUids.clear();
+
+ kmkernel->folderMgr()->syncAllFolders();
+
+ KURL url = getUrl();
+ QMap< QPair<time_t, QString>, int > idsToSave;
+ idsToSave.clear();
+ // Check if we want to keep any messages
+ if ( mLeaveOnServer && !idsOfMsgsToDelete.isEmpty() ) {
+ // Keep all messages on server
+ if ( mLeaveOnServerDays == -1 && mLeaveOnServerCount <= 0 &&
+ mLeaveOnServerSize <= 0)
+ idsOfMsgsToDelete.clear();
+ // Delete old messages
+ else if ( mLeaveOnServerDays > 0 && !mTimeOfNextSeenMsgsMap.isEmpty() ) {
+ time_t timeLimit = time(0) - (86400 * mLeaveOnServerDays);
+ kdDebug() << "timeLimit is " << timeLimit << endl;
+ QStringList::Iterator cur = idsOfMsgsToDelete.begin();
+ for ( ; cur != idsOfMsgsToDelete.end(); ++cur) {
+ time_t msgTime = mTimeOfNextSeenMsgsMap[mUidForIdMap[*cur]];
+ kdDebug() << "id: " << *cur << " msgTime: " << msgTime << endl;
+ if (msgTime >= timeLimit ||
+ !mTimeOfNextSeenMsgsMap[mUidForIdMap[*cur]]) {
+ kdDebug() << "Saving msg id " << *cur << endl;
+ QPair<time_t, QString> msg(msgTime, *cur);
+ idsToSave.insert( msg, 1 );
+ }
+ }
+ }
+ // Delete more old messages if there are more than mLeaveOnServerCount
+ if ( mLeaveOnServerCount > 0 ) {
+ int numToDelete = idsToSave.count() - mLeaveOnServerCount;
+ kdDebug() << "numToDelete is " << numToDelete << endl;
+ if ( numToDelete > 0 && (unsigned)numToDelete < idsToSave.count() ) {
+ QMap< QPair<time_t, QString>, int >::Iterator cur = idsToSave.begin();
+ for ( int deleted = 0; deleted < numToDelete && cur != idsToSave.end()
+ ; deleted++, cur++ ) {
+ kdDebug() << "deleting msg id " << cur.key().second << endl;
+ idsToSave.remove( cur );
+ }
+ }
+ else if ( numToDelete > 0 && (unsigned)numToDelete >= idsToSave.count() )
+ idsToSave.clear();
+ }
+ // Delete more old messages until we're under mLeaveOnServerSize MBs
+ if ( mLeaveOnServerSize > 0 ) {
+ double sizeOnServer = 0;
+ QMap< QPair<time_t, QString>, int >::Iterator cur = idsToSave.begin();
+ for ( ; cur != idsToSave.end(); cur++ ) {
+ sizeOnServer +=
+ *mSizeOfNextSeenMsgsDict[ mUidForIdMap[ cur.key().second ] ];
+ }
+ kdDebug() << "sizeOnServer is " << sizeOnServer/(1024*1024) << "MB" << endl;
+ long limitInBytes = mLeaveOnServerSize * ( 1024 * 1024 );
+ for ( cur = idsToSave.begin(); cur != idsToSave.end()
+ && sizeOnServer > limitInBytes; cur++ ) {
+ sizeOnServer -=
+ *mSizeOfNextSeenMsgsDict[ mUidForIdMap[ cur.key().second ] ];
+ idsToSave.remove( cur );
+ }
+ }
+ // Save msgs from deletion
+ QMap< QPair<time_t, QString>, int >::Iterator it = idsToSave.begin();
+ kdDebug() << "Going to save " << idsToSave.count() << endl;
+ for ( ; it != idsToSave.end(); ++it ) {
+ kdDebug() << "saving msg id " << it.key().second << endl;
+ idsOfMsgsToDelete.remove( it.key().second );
+ }
+ }
+
+ if ( !idsOfForcedDeletes.isEmpty() ) {
+ idsOfMsgsToDelete += idsOfForcedDeletes;
+ idsOfForcedDeletes.clear();
+ }
+
+ // If there are messages to delete then delete them
+ if ( !idsOfMsgsToDelete.isEmpty() ) {
+ stage = Dele;
+ if ( mMailCheckProgressItem )
+ mMailCheckProgressItem->setStatus(
+ i18n( "Fetched 1 message from %1. Deleting messages from server...",
+ "Fetched %n messages from %1. Deleting messages from server...",
+ numMsgs )
+ .arg( mHost ) );
+ url.setPath("/remove/" + idsOfMsgsToDelete.join(","));
+ kdDebug(5006) << "url: " << url.prettyURL() << endl;
+ } else {
+ stage = Quit;
+ if ( mMailCheckProgressItem )
+ mMailCheckProgressItem->setStatus(
+ i18n( "Fetched 1 message from %1. Terminating transmission...",
+ "Fetched %n messages from %1. Terminating transmission...",
+ numMsgs )
+ .arg( mHost ) );
+ url.setPath(QString("/commit"));
+ kdDebug(5006) << "url: " << url.prettyURL() << endl;
+ }
+ job = KIO::get( url, false, false );
+ connectJob();
+ }
+ else if (stage == Dele) {
+ kdDebug(5006) << k_funcinfo << "stage == Dele" << endl;
+ // remove the uids of all messages which have been deleted
+ for ( QStringList::ConstIterator it = idsOfMsgsToDelete.begin();
+ it != idsOfMsgsToDelete.end(); ++it ) {
+ mUidsOfNextSeenMsgsDict.remove( mUidForIdMap[*it] );
+ }
+ idsOfMsgsToDelete.clear();
+ if ( mMailCheckProgressItem )
+ mMailCheckProgressItem->setStatus(
+ i18n( "Fetched 1 message from %1. Terminating transmission...",
+ "Fetched %n messages from %1. Terminating transmission...",
+ numMsgs )
+ .arg( mHost ) );
+ KURL url = getUrl();
+ url.setPath(QString("/commit"));
+ job = KIO::get( url, false, false );
+ stage = Quit;
+ connectJob();
+ }
+ else if (stage == Quit) {
+ kdDebug(5006) << k_funcinfo << "stage == Quit" << endl;
+ saveUidList();
+ job = 0;
+ if (mSlave) KIO::Scheduler::disconnectSlave(mSlave);
+ mSlave = 0;
+ stage = Idle;
+ if( mMailCheckProgressItem ) { // do this only once...
+ bool canceled = !kmkernel || kmkernel->mailCheckAborted() || mMailCheckProgressItem->canceled();
+ int numMessages = canceled ? indexOfCurrentMsg : idsOfMsgs.count();
+ BroadcastStatus::instance()->setStatusMsgTransmissionCompleted(
+ this->name(), numMessages, numBytes, numBytesRead, numBytesToRead, mLeaveOnServer, mMailCheckProgressItem );
+ // set mMailCheckProgressItem = 0 before calling setComplete() to prevent
+ // a race condition
+ ProgressItem *savedMailCheckProgressItem = mMailCheckProgressItem;
+ mMailCheckProgressItem = 0;
+ savedMailCheckProgressItem->setComplete(); // that will delete it
+ checkDone( ( numMessages > 0 ), canceled ? CheckAborted : CheckOK );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::processRemainingQueuedMessages()
+{
+ kdDebug(5006) << k_funcinfo << endl;
+ slotProcessPendingMsgs(); // Force processing of any messages still in the queue
+ processMsgsTimer.stop();
+
+ stage = Quit;
+ if ( kmkernel && kmkernel->folderMgr() ) {
+ kmkernel->folderMgr()->syncAllFolders();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::saveUidList()
+{
+ kdDebug(5006) << k_funcinfo << endl;
+ // Don't update the seen uid list unless we successfully got
+ // a new list from the server
+ if (!mUidlFinished) return;
+
+ QStringList uidsOfNextSeenMsgs;
+ QValueList<int> seenUidTimeList;
+ QDictIterator<int> it( mUidsOfNextSeenMsgsDict );
+ for( ; it.current(); ++it ) {
+ uidsOfNextSeenMsgs.append( it.currentKey() );
+ seenUidTimeList.append( mTimeOfNextSeenMsgsMap[it.currentKey()] );
+ }
+ QString seenUidList = locateLocal( "data", "kmail/" + mLogin + ":" + "@" +
+ mHost + ":" + QString("%1").arg(mPort) );
+ KConfig config( seenUidList );
+ config.writeEntry( "seenUidList", uidsOfNextSeenMsgs );
+ config.writeEntry( "seenUidTimeList", seenUidTimeList );
+ config.writeEntry( "downloadLater", QStringList( mHeaderLaterUids.keys() ) );
+ config.sync();
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::slotGetNextMsg()
+{
+ QMap<QString, int>::Iterator next = mMsgsPendingDownload.begin();
+
+ curMsgData.resize(0);
+ numMsgBytesRead = 0;
+ curMsgLen = 0;
+ delete curMsgStrm;
+ curMsgStrm = 0;
+
+ if ( next != mMsgsPendingDownload.end() ) {
+ // get the next message
+ int nextLen = next.data();
+ curMsgStrm = new QDataStream( curMsgData, IO_WriteOnly );
+ curMsgLen = nextLen;
+ ++indexOfCurrentMsg;
+ kdDebug(5006) << QString("Length of message about to get %1").arg( nextLen ) << endl;
+ mMsgsPendingDownload.remove( next.key() );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::slotData( KIO::Job* job, const QByteArray &data)
+{
+ if (data.size() == 0) {
+ kdDebug(5006) << "Data: <End>" << endl;
+ if ((stage == Retr) && (numMsgBytesRead < curMsgLen))
+ numBytesRead += curMsgLen - numMsgBytesRead;
+ else if (stage == Head){
+ kdDebug(5006) << "Head: <End>" << endl;
+ }
+ return;
+ }
+
+ int oldNumMsgBytesRead = numMsgBytesRead;
+ if (stage == Retr) {
+ headers = false;
+ curMsgStrm->writeRawBytes( data.data(), data.size() );
+ numMsgBytesRead += data.size();
+ if (numMsgBytesRead > curMsgLen)
+ numMsgBytesRead = curMsgLen;
+ numBytesRead += numMsgBytesRead - oldNumMsgBytesRead;
+ dataCounter++;
+ if ( mMailCheckProgressItem &&
+ ( dataCounter % 5 == 0 ||
+ ( indexOfCurrentMsg + 1 == numMsgs && numMsgBytesRead == curMsgLen ) ) )
+ {
+ QString msg;
+ if (numBytes != numBytesToRead && mLeaveOnServer)
+ {
+ msg = i18n("Fetching message %1 of %2 (%3 of %4 KB) for %5@%6 "
+ "(%7 KB remain on the server).")
+ .arg(indexOfCurrentMsg+1).arg(numMsgs).arg(numBytesRead/1024)
+ .arg(numBytesToRead/1024).arg(mLogin).arg(mHost).arg(numBytes/1024);
+ }
+ else
+ {
+ msg = i18n("Fetching message %1 of %2 (%3 of %4 KB) for %5@%6.")
+ .arg(indexOfCurrentMsg+1).arg(numMsgs).arg(numBytesRead/1024)
+ .arg(numBytesToRead/1024).arg(mLogin).arg(mHost);
+ }
+ mMailCheckProgressItem->setStatus( msg );
+ mMailCheckProgressItem->setProgress(
+ (numBytesToRead <= 100) ? 50 // We never know what the server tells us
+ // This way of dividing is required for > 21MB of mail
+ : (numBytesRead / (numBytesToRead / 100)) );
+ }
+ return;
+ }
+
+ if (stage == Head) {
+ curMsgStrm->writeRawBytes( data.data(), data.size() );
+ return;
+ }
+
+ // otherwise stage is List Or Uidl
+ QString qdata = data;
+ qdata = qdata.simplifyWhiteSpace(); // Workaround for Maillennium POP3/UNIBOX
+ int spc = qdata.find( ' ' );
+ if ( stage == List ) {
+ if ( spc > 0 ) {
+ QString length = qdata.mid(spc+1);
+ if (length.find(' ') != -1) length.truncate(length.find(' '));
+ int len = length.toInt();
+ numBytes += len;
+ QString id = qdata.left(spc);
+ idsOfMsgs.append( id );
+ mMsgsPendingDownload.insert( id, len );
+ }
+ else {
+ stage = Idle;
+ if ( job ) job->kill();
+ job = 0;
+ mSlave = 0;
+ KMessageBox::error( 0, i18n( "Unable to complete LIST operation." ),
+ i18n( "Invalid Response From Server") );
+ return;
+ }
+ }
+ else { // stage == Uidl
+ Q_ASSERT ( stage == Uidl);
+
+ QString id;
+ QString uid;
+
+ if ( spc <= 0 ) {
+ // an invalid uidl line. we might just need to skip it, but
+ // some servers generate invalid uids with valid ids. in that
+ // case we will just make up a uid - which will cause us to
+ // not cache the document, but we will be able to interoperate
+
+ int testid = atoi ( qdata.ascii() );
+ if ( testid < 1 ) {
+ // we'll just have to skip this
+ kdDebug(5006) << "PopAccount::slotData skipping UIDL entry due to parse error "
+ << endl << qdata.ascii() << endl;
+ return;
+ }
+ id.setNum (testid, 10);
+
+ QString datestring, serialstring;
+
+ serialstring.setNum ( ++dataCounter, 10 );
+ datestring.setNum ( time(NULL),10 );
+ uid = QString( "uidlgen" ) + datestring + QString( "." ) + serialstring;
+ kdDebug(5006) << "PopAccount::slotData message " << id.ascii()
+ << "%d has bad UIDL, cannot keep a copy on server" << endl;
+ idsOfForcedDeletes.append( id );
+ }
+ else {
+ id = qdata.left( spc );
+ uid = qdata.mid( spc + 1 );
+ }
+
+ int *size = new int; //malloc(size_of(int));
+ *size = mMsgsPendingDownload[id];
+ mSizeOfNextSeenMsgsDict.insert( uid, size );
+ if ( mUidsOfSeenMsgsDict.find( uid ) != 0 ) {
+ if ( mMsgsPendingDownload.contains( id ) ) {
+ mMsgsPendingDownload.remove( id );
+ }
+ else
+ kdDebug(5006) << "PopAccount::slotData synchronization failure." << endl;
+ idsOfMsgsToDelete.append( id );
+ mUidsOfNextSeenMsgsDict.insert( uid, (const int *)1 );
+ if ( mTimeOfSeenMsgsVector.empty() ) {
+ mTimeOfNextSeenMsgsMap.insert( uid, time(0) );
+ }
+ else {
+ // cast the int* with a long to can convert it to a int, BTW
+ // works with g++-4.0 and amd64
+ mTimeOfNextSeenMsgsMap.insert( uid, mTimeOfSeenMsgsVector[(int)( long )
+ mUidsOfSeenMsgsDict[uid] - 1] );
+ }
+ }
+ mUidForIdMap.insert( id, uid );
+ }
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::slotResult( KIO::Job* )
+{
+ if (!job) return;
+ if ( job->error() )
+ {
+ if (interactive) {
+ if (headers) { // nothing to be done for headers
+ idsOfMsgs.clear();
+ }
+ if (stage == Head && job->error() == KIO::ERR_COULD_NOT_READ)
+ {
+ KMessageBox::error(0, i18n("Your server does not support the "
+ "TOP command. Therefore it is not possible to fetch the headers "
+ "of large emails first, before downloading them."));
+ slotCancel();
+ return;
+ }
+ // force the dialog to be shown next time the account is checked
+ if (!mStorePasswd) mPasswd = "";
+ job->showErrorDialog();
+ }
+ slotCancel();
+ }
+ else
+ slotJobFinished();
+}
+
+
+//-----------------------------------------------------------------------------
+void PopAccount::slotSlaveError(KIO::Slave *aSlave, int error,
+ const QString &errorMsg)
+{
+ if (aSlave != mSlave) return;
+ if (error == KIO::ERR_SLAVE_DIED) mSlave = 0;
+
+ // explicitely disconnect the slave if the connection went down
+ if ( error == KIO::ERR_CONNECTION_BROKEN && mSlave ) {
+ KIO::Scheduler::disconnectSlave( mSlave );
+ mSlave = 0;
+ }
+
+ if (interactive && kmkernel) {
+ KMessageBox::error(kmkernel->mainWin(), KIO::buildErrorString(error, errorMsg));
+ }
+
+
+ stage = Quit;
+ if (error == KIO::ERR_COULD_NOT_LOGIN && !mStorePasswd)
+ mAskAgain = true;
+ /* We need a timer, otherwise slotSlaveError of the next account is also
+ executed, if it reuses the slave, because the slave member variable
+ is changed too early */
+ QTimer::singleShot(0, this, SLOT(slotCancel()));
+}
+
+//-----------------------------------------------------------------------------
+void PopAccount::slotGetNextHdr(){
+ kdDebug(5006) << "slotGetNextHeader" << endl;
+
+ curMsgData.resize(0);
+ delete curMsgStrm;
+ curMsgStrm = 0;
+
+ curMsgStrm = new QDataStream( curMsgData, IO_WriteOnly );
+}
+
+void PopAccount::killAllJobs( bool ) {
+ // must reimpl., but we don't use it yet
+}
+
+} // namespace KMail
+#include "popaccount.moc"