kmail Library API Documentation

actionscheduler.cpp

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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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 
00031 #ifdef HAVE_CONFIG_H
00032 #include <config.h>
00033 #endif
00034 
00035 #include "actionscheduler.h"
00036 
00037 #include "messageproperty.h"
00038 #include "kmfilter.h"
00039 #include "kmfolderindex.h"
00040 #include "kmfoldermgr.h"
00041 #include "kmmsgdict.h"
00042 #include "kmcommands.h"
00043 #include "kmheaders.h"
00044 
00045 #include <qtimer.h>
00046 #include <kconfig.h>
00047 #include <kstandarddirs.h>
00048 
00049 using namespace KMail;
00050 typedef QPtrList<KMMsgBase> KMMessageList;
00051 
00052 KMFolderMgr* ActionScheduler::tempFolderMgr = 0;
00053 int ActionScheduler::refCount = 0;
00054 int ActionScheduler::count = 0;
00055 
00056 ActionScheduler::ActionScheduler(KMFilterMgr::FilterSet set,
00057                  QPtrList<KMFilter> filters,
00058                  KMHeaders *headers,
00059                  KMFolder *srcFolder)
00060              :mSet( set ), mHeaders( headers )
00061 {
00062   ++count;
00063   ++refCount;
00064   mExecuting = false;
00065   mExecutingLock = false;
00066   mFetchExecuting = false;
00067   mFiltersAreQueued = false;
00068   mResult = ResultOk;
00069   mIgnore = false;
00070   mAutoDestruct = false;
00071   mAlwaysMatch = false;
00072   KMFilter *filter;
00073   finishTimer = new QTimer( this );
00074   connect( finishTimer, SIGNAL(timeout()), this, SLOT(finish()));
00075   fetchMessageTimer = new QTimer( this );
00076   connect( fetchMessageTimer, SIGNAL(timeout()), this, SLOT(fetchMessage()));
00077   tempCloseFoldersTimer = new QTimer( this );
00078   connect( tempCloseFoldersTimer, SIGNAL(timeout()), this, SLOT(tempCloseFolders()));
00079   processMessageTimer = new QTimer( this );
00080   connect( processMessageTimer, SIGNAL(timeout()), this, SLOT(processMessage()));
00081   filterMessageTimer = new QTimer( this );
00082   connect( filterMessageTimer, SIGNAL(timeout()), this, SLOT(filterMessage()));
00083 
00084   for (filter = filters.first(); filter; filter = filters.next())
00085     mFilters.append( *filter );
00086   mDestFolder = 0;
00087   if (srcFolder) {
00088     mDeleteSrcFolder = false;
00089     setSourceFolder( srcFolder );
00090   } else {
00091     QString tmpName;
00092     tmpName.setNum( count );
00093     if (!tempFolderMgr)
00094       tempFolderMgr = new KMFolderMgr(locateLocal("data","kmail/filter"));
00095     KMFolder *tempFolder = tempFolderMgr->findOrCreate( tmpName );
00096     tempFolder->expunge();
00097     mDeleteSrcFolder = true;
00098     setSourceFolder( tempFolder );
00099   }
00100 }
00101 
00102 ActionScheduler::~ActionScheduler()
00103 {
00104   tempCloseFolders();
00105   mSrcFolder->close();
00106 
00107   if (mDeleteSrcFolder)
00108     tempFolderMgr->remove(mSrcFolder);
00109 
00110   --refCount;
00111   if (refCount == 0) {
00112     delete tempFolderMgr;
00113     tempFolderMgr = 0;
00114   }
00115 }
00116 
00117 void ActionScheduler::setAutoDestruct( bool autoDestruct )
00118 {
00119   mAutoDestruct = autoDestruct;
00120 }
00121 
00122 void ActionScheduler::setAlwaysMatch( bool alwaysMatch )
00123 {
00124   mAlwaysMatch = alwaysMatch;
00125 }
00126 
00127 void ActionScheduler::setDefaultDestinationFolder( KMFolder *destFolder )
00128 {
00129   mDestFolder = destFolder;
00130 }
00131 
00132 void ActionScheduler::setSourceFolder( KMFolder *srcFolder )
00133 {
00134   srcFolder->open();
00135   if (mSrcFolder) {
00136     disconnect( mSrcFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
00137         this, SLOT(msgAdded(KMFolder*, Q_UINT32)) );
00138     mSrcFolder->close();
00139   }
00140   mSrcFolder = srcFolder;
00141   int i = 0;
00142   for (i = 0; i < mSrcFolder->count(); ++i)
00143     enqueue( mSrcFolder->getMsgBase( i )->getMsgSerNum() );
00144   if (mSrcFolder)
00145     connect( mSrcFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
00146          this, SLOT(msgAdded(KMFolder*, Q_UINT32)) );
00147 }
00148 
00149 void ActionScheduler::setFilterList( QPtrList<KMFilter> filters )
00150 {
00151   mFiltersAreQueued = true;
00152   mQueuedFilters.clear();
00153   KMFilter *filter;
00154   for (filter = filters.first(); filter; filter = filters.next())
00155     mQueuedFilters.append( *filter );
00156 }
00157 
00158 int ActionScheduler::tempOpenFolder( KMFolder* aFolder )
00159 {
00160   assert( aFolder );
00161   tempCloseFoldersTimer->stop();
00162   if ( aFolder == mSrcFolder.operator->() )
00163     return 0;
00164 
00165   int rc = aFolder->open();
00166   if (rc)
00167     return rc;
00168 
00169   mOpenFolders.append( aFolder );
00170   return 0;
00171 }
00172 
00173 void ActionScheduler::tempCloseFolders()
00174 {
00175   // close temp opened folders
00176   QValueListConstIterator<QGuardedPtr<KMFolder> > it;
00177   for (it = mOpenFolders.begin(); it != mOpenFolders.end(); ++it) {
00178     KMFolder *folder = *it;
00179     if (folder)
00180       folder->close();
00181   }
00182   mOpenFolders.clear();
00183 }
00184 
00185 void ActionScheduler::execFilters(const QValueList<Q_UINT32> serNums)
00186 {
00187   QValueListConstIterator<Q_UINT32> it;
00188   for (it = serNums.begin(); it != serNums.end(); ++it)
00189     execFilters( *it );
00190 }
00191 
00192 void ActionScheduler::execFilters(const QPtrList<KMMsgBase> msgList)
00193 {
00194   KMMsgBase *msgBase;
00195   QPtrList<KMMsgBase> list = msgList;
00196   for (msgBase = list.first(); msgBase; msgBase = list.next())
00197     execFilters( msgBase->getMsgSerNum() );
00198 }
00199 
00200 void ActionScheduler::execFilters(KMMsgBase* msgBase)
00201 {
00202   execFilters( msgBase->getMsgSerNum() );
00203 }
00204 
00205 void ActionScheduler::execFilters(Q_UINT32 serNum)
00206 {
00207   if (mResult != ResultOk)
00208     return; // An error has already occurred don't even try to process this msg
00209 
00210   if (MessageProperty::filtering( serNum )) {
00211     // Not good someone else is already filtering this msg
00212     mResult = ResultError;
00213     if (!mExecuting)
00214       finishTimer->start( 0, true );
00215   } else {
00216     // Everything is ok async fetch this message
00217     mFetchSerNums.append( serNum );
00218     if (!mFetchExecuting) {
00219       //Need to (re)start incomplete msg fetching chain
00220       mFetchExecuting = true;
00221       fetchMessageTimer->start( 0, true );
00222     }
00223   }
00224 }
00225 
00226 KMMsgBase *ActionScheduler::messageBase(Q_UINT32 serNum)
00227 {
00228   int idx = -1;
00229   KMFolder *folder = 0;
00230   KMMsgBase *msg = 0;
00231   kmkernel->msgDict()->getLocation( serNum, &folder, &idx );
00232   // It's possible that the message has been deleted or moved into a
00233   // different folder
00234   if (folder && (idx != -1)) {
00235     // everything is ok
00236     msg = folder->getMsgBase( idx );
00237     tempOpenFolder( folder ); // just in case msg has moved
00238   } else {
00239     // the message is gone!
00240     mResult = ResultError;
00241     finishTimer->start( 0, true );
00242   }
00243   return msg;
00244 }
00245 
00246 KMMessage *ActionScheduler::message(Q_UINT32 serNum)
00247 {
00248   int idx = -1;
00249   KMFolder *folder = 0;
00250   KMMessage *msg = 0;
00251   kmkernel->msgDict()->getLocation( serNum, &folder, &idx );
00252   // It's possible that the message has been deleted or moved into a
00253   // different folder
00254   if (folder && (idx != -1)) {
00255     // everything is ok
00256     msg = folder->getMsg( idx );
00257     tempOpenFolder( folder ); // just in case msg has moved
00258   } else {
00259     // the message is gone!
00260     mResult = ResultError;
00261     finishTimer->start( 0, true );
00262   }
00263   return msg;
00264 }
00265 
00266 void ActionScheduler::finish()
00267 {
00268   if (mResult == ResultCriticalError) {
00269     // Must handle critical errors immediately
00270     emit result( mResult );
00271     return;
00272   }
00273 
00274   if (!mFetchExecuting && !mExecuting) {
00275     // If an error has occurred and a permanent source folder has
00276     // been set then move all the messages left in the source folder
00277     // to the inbox. If no permanent source folder has been set
00278     // then abandon filtering of queued messages.
00279     if (!mDeleteSrcFolder && !mDestFolder.isNull() ) {
00280       while ( mSrcFolder->count() > 0 ) {
00281     KMMessage *msg = mSrcFolder->getMsg( 0 );
00282     mDestFolder->moveMsg( msg );
00283       }
00284 
00285       // Wait a little while before closing temp folders, just in case
00286       // new messages arrive for filtering.
00287       tempCloseFoldersTimer->start( 60*1000, true );
00288     }
00289     mSerNums.clear(); //abandon
00290     mFetchSerNums.clear(); //abandon
00291 
00292     if (mFiltersAreQueued)
00293       mFilters = mQueuedFilters;
00294     mQueuedFilters.clear();
00295     mFiltersAreQueued = false;
00296     ReturnCode aResult = mResult;
00297     mResult = ResultOk;
00298     mExecutingLock = false;
00299     emit result( aResult );
00300     if (mAutoDestruct)
00301       delete this;
00302   }
00303   // else a message may be in the process of being fetched or filtered
00304   // wait until both of these commitments are finished  then this
00305   // method should be called again.
00306 }
00307 
00308 void ActionScheduler::fetchMessage()
00309 {
00310   QValueListIterator<Q_UINT32> mFetchMessageIt = mFetchSerNums.begin();
00311   while (mFetchMessageIt != mFetchSerNums.end()) {
00312     if (!MessageProperty::transferInProgress(*mFetchMessageIt))
00313       break;
00314     ++mFetchMessageIt;
00315   }
00316   if (mFetchMessageIt == mFetchSerNums.end() && !mFetchSerNums.isEmpty())
00317     mResult = ResultError;
00318   if ((mFetchMessageIt == mFetchSerNums.end()) || (mResult != ResultOk)) {
00319     mFetchExecuting = false;
00320     if (!mSrcFolder->count())
00321       mSrcFolder->expunge();
00322     finishTimer->start( 0, true );
00323     return;
00324   }
00325 
00326   //If we got this far then there's a valid message to work with
00327   KMMsgBase *msgBase = messageBase( *mFetchMessageIt );
00328   if (mResult != ResultOk) {
00329     mFetchExecuting = false;
00330     return;
00331   }
00332   mFetchUnget = msgBase->isMessage();
00333   KMMessage *msg = message( *mFetchMessageIt );
00334   if (mResult != ResultOk) {
00335     mFetchExecuting = false;
00336     return;
00337   }
00338 
00339   if (msg && msg->isComplete()) {
00340     messageFetched( msg );
00341   } else if (msg) {
00342     FolderJob *job = msg->parent()->createJob( msg );
00343     connect( job, SIGNAL(messageRetrieved( KMMessage* )),
00344          SLOT(messageFetched( KMMessage* )) );
00345     job->start();
00346   } else {
00347     mFetchExecuting = false;
00348     mResult = ResultError;
00349     finishTimer->start( 0, true );
00350     return;
00351   }
00352 }
00353 
00354 void ActionScheduler::messageFetched( KMMessage *msg )
00355 {
00356   mFetchSerNums.remove( mFetchSerNums.begin() );
00357 
00358   if ((mSet & KMFilterMgr::Explicit) ||
00359       (msg->headerField( "X-KMail-Filtered" ).isEmpty())) {
00360     QString serNumS;
00361     serNumS.setNum( msg->getMsgSerNum() );
00362     KMMessage *newMsg = new KMMessage;
00363     newMsg->fromString(msg->asString());
00364     newMsg->setStatus(msg->status());
00365     newMsg->setComplete(msg->isComplete());
00366     newMsg->setHeaderField( "X-KMail-Filtered", serNumS );
00367     mSrcFolder->addMsg( newMsg );
00368   }
00369   if (mFetchUnget && msg->parent())
00370     msg->parent()->unGetMsg( msg->parent()->find( msg ));
00371   fetchMessageTimer->start( 0, true );
00372   return;
00373 }
00374 
00375 void ActionScheduler::msgAdded( KMFolder*, Q_UINT32 serNum )
00376 {
00377   if (!mIgnore)
00378     enqueue( serNum );
00379 }
00380 
00381 void ActionScheduler::enqueue(Q_UINT32 serNum)
00382 {
00383   if (mResult != ResultOk)
00384     return; // An error has already occurred don't even try to process this msg
00385 
00386   if (MessageProperty::filtering( serNum )) {
00387     // Not good someone else is already filtering this msg
00388     mResult = ResultError;
00389     if (!mExecuting)
00390       finishTimer->start( 0, true );
00391   } else {
00392     // Everything is ok async filter this message
00393     mSerNums.append( serNum );
00394 
00395     if (!mExecuting) {
00396       //Need to (re)start incomplete msg filtering chain
00397       mExecuting = true;
00398       mMessageIt = mSerNums.begin();
00399       processMessageTimer->start( 0, true );
00400     }
00401   }
00402 }
00403 
00404 void ActionScheduler::processMessage()
00405 {
00406   if (mExecutingLock)
00407     return;
00408   mExecutingLock = true;
00409   mMessageIt = mSerNums.begin();
00410   while (mMessageIt != mSerNums.end()) {
00411     if (!MessageProperty::transferInProgress(*mMessageIt))
00412       break;
00413     ++mMessageIt;
00414   }
00415   if (mMessageIt == mSerNums.end() && !mSerNums.isEmpty())
00416     mResult = ResultError;
00417   if ((mMessageIt == mSerNums.end()) || (mResult != ResultOk)) {
00418     mExecutingLock = false;
00419     mExecuting = false;
00420     finishTimer->start( 0, true );
00421     return;
00422   }
00423 
00424   //If we got this far then there's a valid message to work with
00425   KMMsgBase *msgBase = messageBase( *mMessageIt );
00426   if (mResult != ResultOk) {
00427     mExecuting = false;
00428     return;
00429   }
00430 
00431   MessageProperty::setFiltering( *mMessageIt, true );
00432   MessageProperty::setFilterHandler( *mMessageIt, this );
00433   MessageProperty::setFilterFolder( *mMessageIt, mDestFolder );
00434   mFilterIt = mFilters.begin();
00435 
00436   mUnget = msgBase->isMessage();
00437   KMMessage *msg = message( *mMessageIt );
00438   if (mResult != ResultOk) {
00439     mExecuting = false;
00440     return;
00441   }
00442 
00443   bool mdnEnabled = true;
00444   {
00445     KConfigGroup mdnConfig( kmkernel->config(), "MDN" );
00446     int mode = mdnConfig.readNumEntry( "default-policy", 0 );
00447     if (!mode || mode < 0 || mode > 3)
00448       mdnEnabled = false;
00449   }
00450   mdnEnabled = true; // For 3.2 force all mails to be complete
00451 
00452   if ((msg && msg->isComplete()) ||
00453       (msg && !(*mFilterIt).requiresBody(msg) && !mdnEnabled))
00454   {
00455     // We have a complete message or
00456     // we can work with an incomplete message
00457     // Get a write lock on the message while it's being filtered
00458     msg->setTransferInProgress( true );
00459     filterMessageTimer->start( 0, true );
00460     return;
00461   }
00462   if (msg) {
00463     FolderJob *job = msg->parent()->createJob( msg );
00464     connect( job, SIGNAL(messageRetrieved( KMMessage* )),
00465          SLOT(messageRetrieved( KMMessage* )) );
00466     job->start();
00467   } else {
00468     mExecuting = false;
00469     mResult = ResultError;
00470     finishTimer->start( 0, true );
00471     return;
00472   }
00473 }
00474 
00475 void ActionScheduler::messageRetrieved(KMMessage* msg)
00476 {
00477   // Get a write lock on the message while it's being filtered
00478   msg->setTransferInProgress( true );
00479   filterMessageTimer->start( 0, true );
00480 }
00481 
00482 void ActionScheduler::filterMessage()
00483 {
00484   if (mFilterIt == mFilters.end()) {
00485     moveMessage();
00486     return;
00487   }
00488   if (((mSet & KMFilterMgr::Outbound) && (*mFilterIt).applyOnOutbound()) ||
00489       ((mSet & KMFilterMgr::Inbound) && (*mFilterIt).applyOnInbound()) ||
00490       ((mSet & KMFilterMgr::Explicit) && (*mFilterIt).applyOnExplicit())) {
00491       // filter is applicable
00492     if (mAlwaysMatch ||
00493     (*mFilterIt).pattern()->matches( *mMessageIt )) {
00494       mFilterAction = (*mFilterIt).actions()->first();
00495       actionMessage();
00496       return;
00497     }
00498   }
00499   ++mFilterIt;
00500   filterMessageTimer->start( 0, true );
00501 }
00502 
00503 void ActionScheduler::actionMessage(KMFilterAction::ReturnCode res)
00504 {
00505   if (res == KMFilterAction::CriticalError) {
00506     mResult = ResultCriticalError;
00507     finish(); //must handle critical errors immediately
00508   }
00509   if (mFilterAction) {
00510     KMMessage *msg = message( *mMessageIt );
00511     if (msg) {
00512       KMFilterAction *action = mFilterAction;
00513       mFilterAction = (*mFilterIt).actions()->next();
00514       action->processAsync( msg );
00515     }
00516   } else {
00517     // there are no more actions
00518     if ((*mFilterIt).stopProcessingHere())
00519       mFilterIt = mFilters.end();
00520     else
00521       ++mFilterIt;
00522     filterMessageTimer->start( 0, true );
00523   }
00524 }
00525 
00526 void ActionScheduler::moveMessage()
00527 {
00528   KMMsgBase *msgBase = messageBase( *mMessageIt );
00529   if (!msgBase)
00530     return;
00531 
00532   MessageProperty::setTransferInProgress( *mMessageIt, false, true );
00533   KMMessage *msg = message( *mMessageIt );
00534   KMFolder *folder = MessageProperty::filterFolder( *mMessageIt );
00535   QString serNumS = msg->headerField( "X-KMail-Filtered" );
00536   if (!serNumS.isEmpty())
00537     mOriginalSerNum = serNumS.toUInt();
00538   else
00539     mOriginalSerNum = 0;
00540   MessageProperty::setFilterHandler( *mMessageIt, 0 );
00541   MessageProperty::setFiltering( *mMessageIt, false );
00542   mSerNums.remove( *mMessageIt );
00543 
00544   KMMessage *orgMsg = 0;
00545   ReturnCode mOldReturnCode = mResult;
00546   if (mOriginalSerNum)
00547     orgMsg = message( mOriginalSerNum );
00548   mResult = mOldReturnCode; // ignore errors in deleting original message
00549   if (!orgMsg || !orgMsg->parent()) {
00550     // Original message is gone, no point filtering it anymore
00551     mSrcFolder->removeMsg( mSrcFolder->find( msg ) );
00552     mExecutingLock = false;
00553     processMessageTimer->start( 0, true );
00554   } else {
00555     if (!folder) // no filter folder specified leave in current place
00556       folder = orgMsg->parent();
00557   }
00558 
00559   mIgnore = true;
00560   assert( msg->parent() == mSrcFolder.operator->() );
00561   mSrcFolder->take( mSrcFolder->find( msg ) );
00562   mSrcFolder->addMsg( msg );
00563   mIgnore = false;
00564 
00565   if (msg && kmkernel->folderIsTrash( folder ))
00566     KMFilterAction::sendMDN( msg, KMime::MDN::Deleted );
00567 
00568   KMCommand *cmd = new KMMoveCommand( folder, msg );
00569   connect ( cmd, SIGNAL( completed(bool) ),
00570         this, SLOT( moveMessageFinished(bool) ) );
00571   cmd->start();
00572 }
00573 
00574 void ActionScheduler::moveMessageFinished(bool success)
00575 {
00576   if ( !success )
00577     mResult = ResultError;
00578 
00579   if (!mSrcFolder->count())
00580     mSrcFolder->expunge();
00581 
00582   // in case the message stayed in the current folder TODO optimize
00583   if ( mHeaders )
00584     mHeaders->clearSelectableAndAboutToBeDeleted( mOriginalSerNum );
00585   KMMessage *msg = 0;
00586   ReturnCode mOldReturnCode = mResult;
00587   if (mOriginalSerNum)
00588     msg = message( mOriginalSerNum );
00589   mResult = mOldReturnCode; // ignore errors in deleting original message
00590   if (msg && msg->parent()) {
00591     KMCommand *cmd = new KMMoveCommand( 0, msg );
00592     cmd->start();
00593   }
00594 
00595   if (mResult == ResultOk) {
00596     mExecutingLock = false;
00597     processMessageTimer->start( 0, true );
00598   } else {
00599     finishTimer->start( 0, true );
00600   }
00601   // else moveMessageFinished should call finish
00602 }
00603 
00604 #include "actionscheduler.moc"
KDE Logo
This file is part of the documentation for kmail Library Version 3.2.2.
Documentation copyright © 1996-2004 the KDE developers.
Generated on Sat May 1 11:37:13 2004 by doxygen 1.2.15 written by Dimitri van Heesch, © 1997-2003