kmail

actionscheduler.cpp

Go to the documentation of this file.
00001 /*  Action Scheduler
00002 
00003     This file is part of KMail, the KDE mail client.
00004     Copyright (c) Don Sanders <sanders@kde.org>
00005 
00006     KMail is free software; you can redistribute it and/or modify it
00007     under the terms of the GNU General Public License, version 2, as
00008     published by the Free Software Foundation.
00009 
00010     KMail is distributed in the hope that it will be useful, but
00011     WITHOUT ANY WARRANTY; without even the implied warranty of
00012     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013     General Public License for more details.
00014 
00015     You should have received a copy of the GNU General Public License
00016     along with this program; if not, write to the Free Software
00017     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
00018 
00019     In addition, as a special exception, the copyright holders give
00020     permission to link the code of this program with any edition of
00021     the Qt library by Trolltech AS, Norway (or with modified versions
00022     of Qt that use the same license as Qt), and distribute linked
00023     combinations including the two.  You must obey the GNU General
00024     Public License in all respects for all of the code used other than
00025     Qt.  If you modify this file, you may extend this exception to
00026     your version of the file, but you are not obligated to do so.  If
00027     you do not wish to do so, delete this exception statement from
00028     your version.
00029 */
00030 #include <kdebug.h> // FIXME
00031 
00032 #ifdef HAVE_CONFIG_H
00033 #include <config.h>
00034 #endif
00035 
00036 #include "actionscheduler.h"
00037 
00038 #include "filterlog.h"
00039 #include "messageproperty.h"
00040 #include "kmfilter.h"
00041 #include "kmfolderindex.h"
00042 #include "kmfoldermgr.h"
00043 #include "kmmsgdict.h"
00044 #include "kmcommands.h"
00045 #include "kmheaders.h"
00046 #include "accountmanager.h"
00047 using KMail::AccountManager;
00048 
00049 #include <qtimer.h>
00050 #include <kconfig.h>
00051 #include <kstandarddirs.h>
00052 
00053 using namespace KMail;
00054 typedef QPtrList<KMMsgBase> KMMessageList;
00055 
00056 
00057 KMFolderMgr* ActionScheduler::tempFolderMgr = 0;
00058 int ActionScheduler::refCount = 0;
00059 int ActionScheduler::count = 0;
00060 QValueList<ActionScheduler*> *ActionScheduler::schedulerList = 0;
00061 bool ActionScheduler::sEnabled = false;
00062 bool ActionScheduler::sEnabledChecked = false;
00063 
00064 ActionScheduler::ActionScheduler(KMFilterMgr::FilterSet set,
00065                  QValueList<KMFilter*> filters,
00066                  KMHeaders *headers,
00067                  KMFolder *srcFolder)
00068              :mSet( set ), mHeaders( headers )
00069 {
00070   ++count;
00071   ++refCount;
00072   mExecuting = false;
00073   mExecutingLock = false;
00074   mFetchExecuting = false;
00075   mFiltersAreQueued = false;
00076   mResult = ResultOk;
00077   mIgnore = false;
00078   mAutoDestruct = false;
00079   mAlwaysMatch = false;
00080   mAccountId = 0;
00081   mAccount = false;
00082   lastCommand = 0;
00083   lastJob = 0;
00084   finishTimer = new QTimer( this, "finishTimer" );
00085   connect( finishTimer, SIGNAL(timeout()), this, SLOT(finish()));
00086   fetchMessageTimer = new QTimer( this, "fetchMessageTimer" );
00087   connect( fetchMessageTimer, SIGNAL(timeout()), this, SLOT(fetchMessage()));
00088   tempCloseFoldersTimer = new QTimer( this, "tempCloseFoldersTimer" );
00089   connect( tempCloseFoldersTimer, SIGNAL(timeout()), this, SLOT(tempCloseFolders()));
00090   processMessageTimer = new QTimer( this, "processMessageTimer" );
00091   connect( processMessageTimer, SIGNAL(timeout()), this, SLOT(processMessage()));
00092   filterMessageTimer = new QTimer( this, "filterMessageTimer" );
00093   connect( filterMessageTimer, SIGNAL(timeout()), this, SLOT(filterMessage()));
00094   timeOutTimer = new QTimer( this, "timeOutTimer" );
00095   connect( timeOutTimer, SIGNAL(timeout()), this, SLOT(timeOut()));
00096   fetchTimeOutTimer = new QTimer( this, "fetchTimeOutTimer" );
00097   connect( fetchTimeOutTimer, SIGNAL(timeout()), this, SLOT(fetchTimeOut()));
00098 
00099   QValueList<KMFilter*>::Iterator it = filters.begin();
00100   for (; it != filters.end(); ++it)
00101     mFilters.append( **it );
00102   mDestFolder = 0;
00103   if (srcFolder) {
00104     mDeleteSrcFolder = false;
00105     setSourceFolder( srcFolder );
00106   } else {
00107     QString tmpName;
00108     tmpName.setNum( count );
00109     if (!tempFolderMgr)
00110       tempFolderMgr = new KMFolderMgr(locateLocal("data","kmail/filter"));
00111     KMFolder *tempFolder = tempFolderMgr->findOrCreate( tmpName );
00112     tempFolder->expunge();
00113     mDeleteSrcFolder = true;
00114     setSourceFolder( tempFolder );
00115   }
00116   if (!schedulerList)
00117       schedulerList = new QValueList<ActionScheduler*>;
00118   schedulerList->append( this );
00119 }
00120 
00121 ActionScheduler::~ActionScheduler()
00122 {
00123   schedulerList->remove( this );
00124   tempCloseFolders();
00125   disconnect( mSrcFolder, SIGNAL(closed()),
00126               this, SLOT(folderClosedOrExpunged()) );
00127   disconnect( mSrcFolder, SIGNAL(expunged(KMFolder*)),
00128               this, SLOT(folderClosedOrExpunged()) );
00129   mSrcFolder->close("actionschedsrc");
00130 
00131   if (mDeleteSrcFolder)
00132     tempFolderMgr->remove(mSrcFolder);
00133 
00134   --refCount;
00135   if (refCount == 0) {
00136     delete tempFolderMgr;
00137     tempFolderMgr = 0;
00138   }
00139 }
00140 
00141 void ActionScheduler::setAutoDestruct( bool autoDestruct )
00142 {
00143   mAutoDestruct = autoDestruct;
00144 }
00145 
00146 void ActionScheduler::setAlwaysMatch( bool alwaysMatch )
00147 {
00148   mAlwaysMatch = alwaysMatch;
00149 }
00150 
00151 void ActionScheduler::setDefaultDestinationFolder( KMFolder *destFolder )
00152 {
00153   mDestFolder = destFolder;
00154 }
00155 
00156 void ActionScheduler::setSourceFolder( KMFolder *srcFolder )
00157 {
00158   srcFolder->open("actionschedsrc");
00159   if (mSrcFolder) {
00160     disconnect( mSrcFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
00161         this, SLOT(msgAdded(KMFolder*, Q_UINT32)) );
00162     disconnect( mSrcFolder, SIGNAL(closed()),
00163                 this, SLOT(folderClosedOrExpunged()) );
00164     disconnect( mSrcFolder, SIGNAL(expunged(KMFolder*)),
00165                 this, SLOT(folderClosedOrExpunged()) );
00166     mSrcFolder->close("actionschedsrc");
00167   }
00168   mSrcFolder = srcFolder;
00169   int i = 0;
00170   for (i = 0; i < mSrcFolder->count(); ++i)
00171     enqueue( mSrcFolder->getMsgBase( i )->getMsgSerNum() );
00172   if (mSrcFolder) {
00173     connect( mSrcFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
00174          this, SLOT(msgAdded(KMFolder*, Q_UINT32)) );
00175     connect( mSrcFolder, SIGNAL(closed()),
00176              this, SLOT(folderClosedOrExpunged()) );
00177     connect( mSrcFolder, SIGNAL(expunged(KMFolder*)),
00178              this, SLOT(folderClosedOrExpunged()) );
00179   }
00180 }
00181 
00182 void ActionScheduler::setFilterList( QValueList<KMFilter*> filters )
00183 {
00184   mFiltersAreQueued = true;
00185   mQueuedFilters.clear();
00186 
00187   QValueList<KMFilter*>::Iterator it = filters.begin();
00188   for (; it != filters.end(); ++it)
00189     mQueuedFilters.append( **it );
00190   if (!mExecuting) {
00191       mFilters = mQueuedFilters;
00192       mFiltersAreQueued = false;
00193       mQueuedFilters.clear();
00194   }
00195 }
00196 
00197 void ActionScheduler::folderClosedOrExpunged()
00198 {
00199   // mSrcFolder has been closed. reopen it.
00200   if ( mSrcFolder )
00201   {
00202     mSrcFolder->open( "actionsched" );
00203   }
00204 }
00205 
00206 int ActionScheduler::tempOpenFolder( KMFolder* aFolder )
00207 {
00208   assert( aFolder );
00209   tempCloseFoldersTimer->stop();
00210   if ( aFolder == mSrcFolder.operator->() )
00211     return 0;
00212 
00213   int rc = aFolder->open("actionsched");
00214   if (rc)
00215     return rc;
00216 
00217   mOpenFolders.append( aFolder );
00218   return 0;
00219 }
00220 
00221 void ActionScheduler::tempCloseFolders()
00222 {
00223   // close temp opened folders
00224   QValueListConstIterator<QGuardedPtr<KMFolder> > it;
00225   for (it = mOpenFolders.begin(); it != mOpenFolders.end(); ++it) {
00226     KMFolder *folder = *it;
00227     if (folder)
00228       folder->close("actionsched");
00229   }
00230   mOpenFolders.clear();
00231 }
00232 
00233 void ActionScheduler::execFilters(const QValueList<Q_UINT32> serNums)
00234 {
00235   QValueListConstIterator<Q_UINT32> it;
00236   for (it = serNums.begin(); it != serNums.end(); ++it)
00237     execFilters( *it );
00238 }
00239 
00240 void ActionScheduler::execFilters(const QPtrList<KMMsgBase> msgList)
00241 {
00242   KMMsgBase *msgBase;
00243   QPtrList<KMMsgBase> list = msgList;
00244   for (msgBase = list.first(); msgBase; msgBase = list.next())
00245     execFilters( msgBase->getMsgSerNum() );
00246 }
00247 
00248 void ActionScheduler::execFilters(KMMsgBase* msgBase)
00249 {
00250   execFilters( msgBase->getMsgSerNum() );
00251 }
00252 
00253 void ActionScheduler::execFilters(Q_UINT32 serNum)
00254 {
00255   if (mResult != ResultOk) {
00256       if ((mResult != ResultCriticalError) &&
00257       !mExecuting && !mExecutingLock && !mFetchExecuting) {
00258       mResult = ResultOk; // Recoverable error
00259       if (!mFetchSerNums.isEmpty()) {
00260           mFetchSerNums.push_back( mFetchSerNums.first() );
00261           mFetchSerNums.pop_front();
00262       }
00263       } else
00264       return; // An error has already occurred don't even try to process this msg
00265   }
00266   if (MessageProperty::filtering( serNum )) {
00267     // Not good someone else is already filtering this msg
00268     mResult = ResultError;
00269     if (!mExecuting && !mFetchExecuting)
00270       finishTimer->start( 0, true );
00271   } else {
00272     // Everything is ok async fetch this message
00273     mFetchSerNums.append( serNum );
00274     if (!mFetchExecuting) {
00275       //Need to (re)start incomplete msg fetching chain
00276       mFetchExecuting = true;
00277       fetchMessageTimer->start( 0, true );
00278     }
00279   }
00280 }
00281 
00282 KMMsgBase *ActionScheduler::messageBase(Q_UINT32 serNum)
00283 {
00284   int idx = -1;
00285   KMFolder *folder = 0;
00286   KMMsgBase *msg = 0;
00287   KMMsgDict::instance()->getLocation( serNum, &folder, &idx );
00288   // It's possible that the message has been deleted or moved into a
00289   // different folder
00290   if (folder && (idx != -1)) {
00291     // everything is ok
00292     tempOpenFolder( folder ); // just in case msg has moved
00293     msg = folder->getMsgBase( idx );
00294   } else {
00295     // the message is gone!
00296     mResult = ResultError;
00297     finishTimer->start( 0, true );
00298   }
00299   return msg;
00300 }
00301 
00302 KMMessage *ActionScheduler::message(Q_UINT32 serNum)
00303 {
00304   int idx = -1;
00305   KMFolder *folder = 0;
00306   KMMessage *msg = 0;
00307   KMMsgDict::instance()->getLocation( serNum, &folder, &idx );
00308   // It's possible that the message has been deleted or moved into a
00309   // different folder
00310   if (folder && (idx != -1)) {
00311     // everything is ok
00312     msg = folder->getMsg( idx );
00313     tempOpenFolder( folder ); // just in case msg has moved
00314   } else {
00315     // the message is gone!
00316     mResult = ResultError;
00317     finishTimer->start( 0, true );
00318   }
00319   return msg;
00320 }
00321 
00322 void ActionScheduler::finish()
00323 {
00324   if (mResult != ResultOk) {
00325     // Must handle errors immediately
00326     emit result( mResult );
00327     return;
00328   }
00329 
00330   if (!mExecuting) {
00331 
00332   if (!mFetchSerNums.isEmpty()) {
00333     // Possibly if (mResult == ResultOk) should cancel job and start again.
00334     // Believe smarter logic to bail out if an error has occurred is required.
00335     // Perhaps should be testing for mFetchExecuting or at least set it to true
00336     fetchMessageTimer->start( 0, true ); // give it a bit of time at a test
00337     return;
00338   } else {
00339     mFetchExecuting = false;
00340   }
00341 
00342   if (mSerNums.begin() != mSerNums.end()) {
00343     mExecuting = true;
00344     processMessageTimer->start( 0, true );
00345     return;
00346   }
00347 
00348     // If an error has occurred and a permanent source folder has
00349     // been set then move all the messages left in the source folder
00350     // to the inbox. If no permanent source folder has been set
00351     // then abandon filtering of queued messages.
00352     if (!mDeleteSrcFolder && !mDestFolder.isNull() ) {
00353       while ( mSrcFolder->count() > 0 ) {
00354     KMMessage *msg = mSrcFolder->getMsg( 0 );
00355     mDestFolder->moveMsg( msg );
00356       }
00357 
00358       // Wait a little while before closing temp folders, just in case
00359       // new messages arrive for filtering.
00360       tempCloseFoldersTimer->start( 60*1000, true );
00361     }
00362     mSerNums.clear(); //abandon
00363     mFetchSerNums.clear(); //abandon
00364 
00365     if (mFiltersAreQueued)
00366       mFilters = mQueuedFilters;
00367     mQueuedFilters.clear();
00368     mFiltersAreQueued = false;
00369     ReturnCode aResult = mResult;
00370     mResult = ResultOk;
00371     mExecutingLock = false;
00372     emit result( aResult );
00373     if (mAutoDestruct)
00374       delete this;
00375   }
00376   // else a message may be in the process of being fetched or filtered
00377   // wait until both of these commitments are finished  then this
00378   // method should be called again.
00379 }
00380 
00381 void ActionScheduler::fetchMessage()
00382 {
00383   QValueListIterator<Q_UINT32> mFetchMessageIt = mFetchSerNums.begin();
00384   while (mFetchMessageIt != mFetchSerNums.end()) {
00385     if (!MessageProperty::transferInProgress(*mFetchMessageIt))
00386       break;
00387     ++mFetchMessageIt;
00388   }
00389 
00390   //  Note: Perhaps this could be improved. We shouldn't give up straight away
00391   //        if !mFetchSerNums.isEmpty (becausing transferInProgress is true
00392   //        for some messages). Instead we should delay for a minute or so and
00393   //        again.
00394   if (mFetchMessageIt == mFetchSerNums.end() && !mFetchSerNums.isEmpty()) {
00395     mResult = ResultError;
00396   }
00397   if ((mFetchMessageIt == mFetchSerNums.end()) || (mResult != ResultOk)) {
00398     mFetchExecuting = false;
00399     if (!mSrcFolder->count())
00400       mSrcFolder->expunge();
00401     finishTimer->start( 0, true );
00402     return;
00403   }
00404 
00405   //If we got this far then there's a valid message to work with
00406   KMMsgBase *msgBase = messageBase( *mFetchMessageIt );
00407 
00408   if ((mResult != ResultOk) || (!msgBase)) {
00409     mFetchExecuting = false;
00410     return;
00411   }
00412   mFetchUnget = msgBase->isMessage();
00413   KMMessage *msg = message( *mFetchMessageIt );
00414   if (mResult != ResultOk) {
00415     mFetchExecuting = false;
00416     return;
00417   }
00418 
00419   if (msg && msg->isComplete()) {
00420     messageFetched( msg );
00421   } else if (msg) {
00422     fetchTimeOutTime = QTime::currentTime();
00423     fetchTimeOutTimer->start( 60 * 1000, true );
00424     FolderJob *job = msg->parent()->createJob( msg );
00425     connect( job, SIGNAL(messageRetrieved( KMMessage* )),
00426          SLOT(messageFetched( KMMessage* )) );
00427     lastJob = job;
00428     job->start();
00429   } else {
00430     mFetchExecuting = false;
00431     mResult = ResultError;
00432     finishTimer->start( 0, true );
00433     return;
00434   }
00435 }
00436 
00437 void ActionScheduler::messageFetched( KMMessage *msg )
00438 {
00439   fetchTimeOutTimer->stop();
00440   if (!msg) {
00441       // Should never happen, but sometimes does;
00442       fetchMessageTimer->start( 0, true );
00443       return;
00444   }
00445 
00446   mFetchSerNums.remove( msg->getMsgSerNum() );
00447 
00448   // Note: This may not be necessary. What about when it's time to
00449   //       delete the original message?
00450   //       Is the new serial number being set correctly then?
00451   if ((mSet & KMFilterMgr::Explicit) ||
00452       (msg->headerField( "X-KMail-Filtered" ).isEmpty())) {
00453     QString serNumS;
00454     serNumS.setNum( msg->getMsgSerNum() );
00455     KMMessage *newMsg = new KMMessage;
00456     newMsg->fromString(msg->asString());
00457     newMsg->setStatus(msg->status());
00458     newMsg->setComplete(msg->isComplete());
00459     newMsg->setHeaderField( "X-KMail-Filtered", serNumS );
00460     mSrcFolder->addMsg( newMsg );
00461   } else {
00462     fetchMessageTimer->start( 0, true );
00463   }
00464   if (mFetchUnget && msg->parent())
00465     msg->parent()->unGetMsg( msg->parent()->find( msg ));
00466   return;
00467 }
00468 
00469 void ActionScheduler::msgAdded( KMFolder*, Q_UINT32 serNum )
00470 {
00471   if (!mIgnore)
00472     enqueue( serNum );
00473 }
00474 
00475 void ActionScheduler::enqueue(Q_UINT32 serNum)
00476 {
00477   if (mResult != ResultOk)
00478     return; // An error has already occurred don't even try to process this msg
00479 
00480   if (MessageProperty::filtering( serNum )) {
00481     // Not good someone else is already filtering this msg
00482     mResult = ResultError;
00483     if (!mExecuting && !mFetchExecuting)
00484       finishTimer->start( 0, true );
00485   } else {
00486     // Everything is ok async filter this message
00487     mSerNums.append( serNum );
00488 
00489     if (!mExecuting) {
00490       // Note: Need to (re)start incomplete msg filtering chain
00491       //       The state of mFetchExecuting is of some concern.
00492       mExecuting = true;
00493       mMessageIt = mSerNums.begin();
00494       processMessageTimer->start( 0, true );
00495     }
00496   }
00497 }
00498 
00499 void ActionScheduler::processMessage()
00500 {
00501   if (mExecutingLock)
00502     return;
00503   mExecutingLock = true;
00504   mMessageIt = mSerNums.begin();
00505   while (mMessageIt != mSerNums.end()) {
00506     if (!MessageProperty::transferInProgress(*mMessageIt))
00507       break;
00508     ++mMessageIt;
00509   }
00510 
00511   if (mMessageIt == mSerNums.end() && !mSerNums.isEmpty()) {
00512     mExecuting = false;
00513     processMessageTimer->start( 600, true );
00514   }
00515 
00516   if ((mMessageIt == mSerNums.end()) || (mResult != ResultOk)) {
00517     mExecutingLock = false;
00518     mExecuting = false;
00519     finishTimer->start( 0, true );
00520     return;
00521   }
00522 
00523   //If we got this far then there's a valid message to work with
00524   KMMsgBase *msgBase = messageBase( *mMessageIt );
00525   if (!msgBase || mResult != ResultOk) {
00526     mExecuting = false;
00527     return;
00528   }
00529 
00530   MessageProperty::setFiltering( *mMessageIt, true );
00531   MessageProperty::setFilterHandler( *mMessageIt, this );
00532   MessageProperty::setFilterFolder( *mMessageIt, mDestFolder );
00533   if ( FilterLog::instance()->isLogging() ) {
00534     FilterLog::instance()->addSeparator();
00535   }
00536   mFilterIt = mFilters.begin();
00537 
00538   mUnget = msgBase->isMessage();
00539   KMMessage *msg = message( *mMessageIt );
00540   if (mResult != ResultOk) {
00541     mExecuting = false;
00542     return;
00543   }
00544 
00545   bool mdnEnabled = true;
00546   {
00547     KConfigGroup mdnConfig( kmkernel->config(), "MDN" );
00548     int mode = mdnConfig.readNumEntry( "default-policy", 0 );
00549     if (!mode || mode < 0 || mode > 3)
00550       mdnEnabled = false;
00551   }
00552   mdnEnabled = true; // For 3.2 force all mails to be complete
00553 
00554   if ((msg && msg->isComplete()) ||
00555       (msg && !(*mFilterIt).requiresBody(msg) && !mdnEnabled))
00556   {
00557     // We have a complete message or
00558     // we can work with an incomplete message
00559     // Get a write lock on the message while it's being filtered
00560     msg->setTransferInProgress( true );
00561     filterMessageTimer->start( 0, true );
00562     return;
00563   }
00564   if (msg) {
00565     FolderJob *job = msg->parent()->createJob( msg );
00566     connect( job, SIGNAL(messageRetrieved( KMMessage* )),
00567          SLOT(messageRetrieved( KMMessage* )) );
00568     job->start();
00569   } else {
00570     mExecuting = false;
00571     mResult = ResultError;
00572     finishTimer->start( 0, true );
00573     return;
00574   }
00575 }
00576 
00577 void ActionScheduler::messageRetrieved(KMMessage* msg)
00578 {
00579   // Get a write lock on the message while it's being filtered
00580   msg->setTransferInProgress( true );
00581   filterMessageTimer->start( 0, true );
00582 }
00583 
00584 void ActionScheduler::filterMessage()
00585 {
00586   if (mFilterIt == mFilters.end()) {
00587     moveMessage();
00588     return;
00589   }
00590   if (((mSet & KMFilterMgr::Outbound) && (*mFilterIt).applyOnOutbound()) ||
00591       ((mSet & KMFilterMgr::Inbound) && (*mFilterIt).applyOnInbound() &&
00592        (!mAccount ||
00593     (mAccount && (*mFilterIt).applyOnAccount(mAccountId)))) ||
00594       ((mSet & KMFilterMgr::Explicit) && (*mFilterIt).applyOnExplicit())) {
00595 
00596       // filter is applicable
00597     if ( FilterLog::instance()->isLogging() ) {
00598       QString logText( i18n( "<b>Evaluating filter rules:</b> " ) );
00599       logText.append( (*mFilterIt).pattern()->asString() );
00600       FilterLog::instance()->add( logText, FilterLog::patternDesc );
00601     }
00602     if (mAlwaysMatch ||
00603     (*mFilterIt).pattern()->matches( *mMessageIt )) {
00604       if ( FilterLog::instance()->isLogging() ) {
00605         FilterLog::instance()->add( i18n( "<b>Filter rules have matched.</b>" ),
00606                                     FilterLog::patternResult );
00607       }
00608       mFilterAction = (*mFilterIt).actions()->first();
00609       actionMessage();
00610       return;
00611     }
00612   }
00613   ++mFilterIt;
00614   filterMessageTimer->start( 0, true );
00615 }
00616 
00617 void ActionScheduler::actionMessage(KMFilterAction::ReturnCode res)
00618 {
00619   if (res == KMFilterAction::CriticalError) {
00620     mResult = ResultCriticalError;
00621     finish(); //must handle critical errors immediately
00622   }
00623   if (mFilterAction) {
00624     KMMessage *msg = message( *mMessageIt );
00625     if (msg) {
00626       if ( FilterLog::instance()->isLogging() ) {
00627         QString logText( i18n( "<b>Applying filter action:</b> %1" )
00628                         .arg( mFilterAction->displayString() ) );
00629         FilterLog::instance()->add( logText, FilterLog::appliedAction );
00630       }
00631       KMFilterAction *action = mFilterAction;
00632       mFilterAction = (*mFilterIt).actions()->next();
00633       action->processAsync( msg );
00634     }
00635   } else {
00636     // there are no more actions
00637     if ((*mFilterIt).stopProcessingHere())
00638       mFilterIt = mFilters.end();
00639     else
00640       ++mFilterIt;
00641     filterMessageTimer->start( 0, true );
00642   }
00643 }
00644 
00645 void ActionScheduler::moveMessage()
00646 {
00647   KMMsgBase *msgBase = messageBase( *mMessageIt );
00648   if (!msgBase)
00649     return;
00650 
00651   MessageProperty::setTransferInProgress( *mMessageIt, false, true );
00652   KMMessage *msg = message( *mMessageIt );
00653   KMFolder *folder = MessageProperty::filterFolder( *mMessageIt );
00654   QString serNumS = msg->headerField( "X-KMail-Filtered" );
00655   if (!serNumS.isEmpty())
00656     mOriginalSerNum = serNumS.toUInt();
00657   else
00658     mOriginalSerNum = 0;
00659   MessageProperty::setFilterHandler( *mMessageIt, 0 );
00660   MessageProperty::setFiltering( *mMessageIt, false );
00661   mSerNums.remove( *mMessageIt );
00662 
00663   KMMessage *orgMsg = 0;
00664   ReturnCode mOldReturnCode = mResult;
00665   if (mOriginalSerNum)
00666     orgMsg = message( mOriginalSerNum );
00667   mResult = mOldReturnCode; // ignore errors in deleting original message
00668   if (!orgMsg || !orgMsg->parent()) {
00669     // Original message is gone, no point filtering it anymore
00670     mSrcFolder->removeMsg( mSrcFolder->find( msg ) );
00671     kdDebug(5006) << "The original serial number is missing. "
00672                   << "Cannot complete the filtering." << endl;
00673     mExecutingLock = false;
00674     processMessageTimer->start( 0, true );
00675     return;
00676   } else {
00677     if (!folder) // no filter folder specified leave in current place
00678       folder = orgMsg->parent();
00679   }
00680 
00681   mIgnore = true;
00682   assert( msg->parent() == mSrcFolder.operator->() );
00683   mSrcFolder->take( mSrcFolder->find( msg ) );
00684   mSrcFolder->addMsg( msg );
00685   mIgnore = false;
00686 
00687   if (msg && folder && kmkernel->folderIsTrash( folder ))
00688     KMFilterAction::sendMDN( msg, KMime::MDN::Deleted );
00689 
00690   timeOutTime = QTime::currentTime();
00691   KMCommand *cmd = new KMMoveCommand( folder, msg );
00692   connect( cmd, SIGNAL( completed( KMCommand * ) ),
00693        this, SLOT( moveMessageFinished( KMCommand * ) ) );
00694   cmd->start();
00695   // sometimes the move command doesn't complete so time out after a minute
00696   // and move onto the next message
00697   lastCommand = cmd;
00698   timeOutTimer->start( 60 * 1000, true );
00699 }
00700 
00701 void ActionScheduler::moveMessageFinished( KMCommand *command )
00702 {
00703   timeOutTimer->stop();
00704   if ( command->result() != KMCommand::OK )
00705     mResult = ResultError;
00706 
00707   if (!mSrcFolder->count())
00708     mSrcFolder->expunge();
00709 
00710   // in case the message stayed in the current folder TODO optimize
00711   if ( mHeaders )
00712     mHeaders->clearSelectableAndAboutToBeDeleted( mOriginalSerNum );
00713   KMMessage *msg = 0;
00714   ReturnCode mOldReturnCode = mResult;
00715   if (mOriginalSerNum) {
00716     msg = message( mOriginalSerNum );
00717     emit filtered( mOriginalSerNum );
00718   }
00719 
00720   mResult = mOldReturnCode; // ignore errors in deleting original message
00721   KMCommand *cmd = 0;
00722   if (msg && msg->parent()) {
00723     cmd = new KMMoveCommand( 0, msg );
00724 //    cmd->start(); // Note: sensitive logic here.
00725   }
00726 
00727   if (mResult == ResultOk) {
00728     mExecutingLock = false;
00729     if (cmd)
00730     connect( cmd, SIGNAL( completed( KMCommand * ) ),
00731          this, SLOT( processMessage() ) );
00732     else
00733     processMessageTimer->start( 0, true );
00734   } else {
00735     // Note: An alternative to consider is just calling
00736     //       finishTimer->start and returning
00737     if (cmd)
00738     connect( cmd, SIGNAL( completed( KMCommand * ) ),
00739          this, SLOT( finish() ) );
00740     else
00741     finishTimer->start( 0, true );
00742   }
00743   if (cmd)
00744     cmd->start();
00745   // else moveMessageFinished should call finish
00746 }
00747 
00748 void ActionScheduler::copyMessageFinished( KMCommand *command )
00749 {
00750   if ( command->result() != KMCommand::OK )
00751     actionMessage( KMFilterAction::ErrorButGoOn );
00752   else
00753     actionMessage();
00754 }
00755 
00756 void ActionScheduler::timeOut()
00757 {
00758   // Note: This is a good place for a debug statement
00759   assert( lastCommand );
00760   // sometimes imap jobs seem to just stall so give up and move on
00761   disconnect( lastCommand, SIGNAL( completed( KMCommand * ) ),
00762           this, SLOT( moveMessageFinished( KMCommand * ) ) );
00763   lastCommand = 0;
00764   mExecutingLock = false;
00765   mExecuting = false;
00766   finishTimer->start( 0, true );
00767   if (mOriginalSerNum) // Try again
00768       execFilters( mOriginalSerNum );
00769 }
00770 
00771 void ActionScheduler::fetchTimeOut()
00772 {
00773   // Note: This is a good place for a debug statement
00774   assert( lastJob );
00775   // sometimes imap jobs seem to just stall so give up and move on
00776   disconnect( lastJob, SIGNAL(messageRetrieved( KMMessage* )),
00777           this, SLOT(messageFetched( KMMessage* )) );
00778   lastJob->kill();
00779   lastJob = 0;
00780   fetchMessageTimer->start( 0, true );
00781 }
00782 
00783 QString ActionScheduler::debug()
00784 {
00785     QString res;
00786     QValueList<ActionScheduler*>::iterator it;
00787     int i = 1;
00788     for ( it = schedulerList->begin(); it != schedulerList->end(); ++it ) {
00789     res.append( QString( "ActionScheduler #%1.\n" ).arg( i ) );
00790     if ((*it)->mAccount && kmkernel->find( (*it)->mAccountId )) {
00791         res.append( QString( "Account %1, Name %2.\n" )
00792             .arg( (*it)->mAccountId )
00793             .arg( kmkernel->acctMgr()->find( (*it)->mAccountId )->name() ) );
00794     }
00795     res.append( QString( "mExecuting %1, " ).arg( (*it)->mExecuting ? "true" : "false" ) );
00796     res.append( QString( "mExecutingLock %1, " ).arg( (*it)->mExecutingLock ? "true" : "false" ) );
00797     res.append( QString( "mFetchExecuting %1.\n" ).arg( (*it)->mFetchExecuting ? "true" : "false" ) );
00798     res.append( QString( "mOriginalSerNum %1.\n" ).arg( (*it)->mOriginalSerNum ) );
00799     res.append( QString( "mMessageIt %1.\n" ).arg( ((*it)->mMessageIt != 0) ? *(*it)->mMessageIt : 0 ) );
00800     res.append( QString( "mSerNums count %1, " ).arg( (*it)->mSerNums.count() ) );
00801     res.append( QString( "mFetchSerNums count %1.\n" ).arg( (*it)->mFetchSerNums.count() ) );
00802     res.append( QString( "mResult " ) );
00803     if ((*it)->mResult == ResultOk)
00804         res.append( QString( "ResultOk.\n" ) );
00805     else if ((*it)->mResult == ResultError)
00806         res.append( QString( "ResultError.\n" ) );
00807     else if ((*it)->mResult == ResultCriticalError)
00808         res.append( QString( "ResultCriticalError.\n" ) );
00809     else
00810         res.append( QString( "Unknown.\n" ) );
00811 
00812     ++i;
00813     }
00814     return res;
00815 }
00816 
00817 bool ActionScheduler::isEnabled()
00818 {
00819     if (sEnabledChecked)
00820     return sEnabled;
00821 
00822     sEnabledChecked = true;
00823     KConfig* config = KMKernel::config();
00824     KConfigGroupSaver saver(config, "General");
00825     sEnabled = config->readBoolEntry("action-scheduler", false);
00826     return sEnabled;
00827 }
00828 
00829 bool ActionScheduler::ignoreChanges( bool ignore )
00830 {
00831   bool oldValue = mIgnore;
00832   mIgnore = ignore;
00833   return oldValue;
00834 }
00835 
00836 #include "actionscheduler.moc"