You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
604 lines
17 KiB
604 lines
17 KiB
/* Action Scheduler |
|
|
|
This file is part of KMail, the KDE mail client. |
|
Copyright (c) Don Sanders <sanders@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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
|
|
In addition, as a special exception, the copyright holders give |
|
permission to link the code of this program with any edition of |
|
the Qt library by Trolltech AS, Norway (or with modified versions |
|
of Qt that use the same license as Qt), and distribute linked |
|
combinations including the two. You must obey the GNU General |
|
Public License in all respects for all of the code used other than |
|
Qt. If you modify this file, you may extend this exception to |
|
your version of the file, but you are not obligated to do so. If |
|
you do not wish to do so, delete this exception statement from |
|
your version. |
|
*/ |
|
|
|
#ifdef HAVE_CONFIG_H |
|
#include <config.h> |
|
#endif |
|
|
|
#include "actionscheduler.h" |
|
|
|
#include "messageproperty.h" |
|
#include "kmfilter.h" |
|
#include "kmfolderindex.h" |
|
#include "kmfoldermgr.h" |
|
#include "kmmsgdict.h" |
|
#include "kmcommands.h" |
|
#include "kmheaders.h" |
|
|
|
#include <qtimer.h> |
|
#include <kconfig.h> |
|
#include <kstandarddirs.h> |
|
|
|
using namespace KMail; |
|
typedef QPtrList<KMMsgBase> KMMessageList; |
|
|
|
KMFolderMgr* ActionScheduler::tempFolderMgr = 0; |
|
int ActionScheduler::refCount = 0; |
|
int ActionScheduler::count = 0; |
|
|
|
ActionScheduler::ActionScheduler(KMFilterMgr::FilterSet set, |
|
QPtrList<KMFilter> filters, |
|
KMHeaders *headers, |
|
KMFolder *srcFolder) |
|
:mSet( set ), mHeaders( headers ) |
|
{ |
|
++count; |
|
++refCount; |
|
mExecuting = false; |
|
mExecutingLock = false; |
|
mFetchExecuting = false; |
|
mFiltersAreQueued = false; |
|
mResult = ResultOk; |
|
mIgnore = false; |
|
mAutoDestruct = false; |
|
mAlwaysMatch = false; |
|
KMFilter *filter; |
|
finishTimer = new QTimer( this ); |
|
connect( finishTimer, SIGNAL(timeout()), this, SLOT(finish())); |
|
fetchMessageTimer = new QTimer( this ); |
|
connect( fetchMessageTimer, SIGNAL(timeout()), this, SLOT(fetchMessage())); |
|
tempCloseFoldersTimer = new QTimer( this ); |
|
connect( tempCloseFoldersTimer, SIGNAL(timeout()), this, SLOT(tempCloseFolders())); |
|
processMessageTimer = new QTimer( this ); |
|
connect( processMessageTimer, SIGNAL(timeout()), this, SLOT(processMessage())); |
|
filterMessageTimer = new QTimer( this ); |
|
connect( filterMessageTimer, SIGNAL(timeout()), this, SLOT(filterMessage())); |
|
|
|
for (filter = filters.first(); filter; filter = filters.next()) |
|
mFilters.append( *filter ); |
|
mDestFolder = 0; |
|
if (srcFolder) { |
|
mDeleteSrcFolder = false; |
|
setSourceFolder( srcFolder ); |
|
} else { |
|
QString tmpName; |
|
tmpName.setNum( count ); |
|
if (!tempFolderMgr) |
|
tempFolderMgr = new KMFolderMgr(locateLocal("data","kmail/filter")); |
|
KMFolder *tempFolder = tempFolderMgr->findOrCreate( tmpName ); |
|
tempFolder->expunge(); |
|
mDeleteSrcFolder = true; |
|
setSourceFolder( tempFolder ); |
|
} |
|
} |
|
|
|
ActionScheduler::~ActionScheduler() |
|
{ |
|
tempCloseFolders(); |
|
mSrcFolder->close(); |
|
|
|
if (mDeleteSrcFolder) |
|
tempFolderMgr->remove(mSrcFolder); |
|
|
|
--refCount; |
|
if (refCount == 0) { |
|
delete tempFolderMgr; |
|
tempFolderMgr = 0; |
|
} |
|
} |
|
|
|
void ActionScheduler::setAutoDestruct( bool autoDestruct ) |
|
{ |
|
mAutoDestruct = autoDestruct; |
|
} |
|
|
|
void ActionScheduler::setAlwaysMatch( bool alwaysMatch ) |
|
{ |
|
mAlwaysMatch = alwaysMatch; |
|
} |
|
|
|
void ActionScheduler::setDefaultDestinationFolder( KMFolder *destFolder ) |
|
{ |
|
mDestFolder = destFolder; |
|
} |
|
|
|
void ActionScheduler::setSourceFolder( KMFolder *srcFolder ) |
|
{ |
|
srcFolder->open(); |
|
if (mSrcFolder) { |
|
disconnect( mSrcFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)), |
|
this, SLOT(msgAdded(KMFolder*, Q_UINT32)) ); |
|
mSrcFolder->close(); |
|
} |
|
mSrcFolder = srcFolder; |
|
int i = 0; |
|
for (i = 0; i < mSrcFolder->count(); ++i) |
|
enqueue( mSrcFolder->getMsgBase( i )->getMsgSerNum() ); |
|
if (mSrcFolder) |
|
connect( mSrcFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)), |
|
this, SLOT(msgAdded(KMFolder*, Q_UINT32)) ); |
|
} |
|
|
|
void ActionScheduler::setFilterList( QPtrList<KMFilter> filters ) |
|
{ |
|
mFiltersAreQueued = true; |
|
mQueuedFilters.clear(); |
|
KMFilter *filter; |
|
for (filter = filters.first(); filter; filter = filters.next()) |
|
mQueuedFilters.append( *filter ); |
|
} |
|
|
|
int ActionScheduler::tempOpenFolder( KMFolder* aFolder ) |
|
{ |
|
assert( aFolder ); |
|
tempCloseFoldersTimer->stop(); |
|
if ( aFolder == mSrcFolder.operator->() ) |
|
return 0; |
|
|
|
int rc = aFolder->open(); |
|
if (rc) |
|
return rc; |
|
|
|
mOpenFolders.append( aFolder ); |
|
return 0; |
|
} |
|
|
|
void ActionScheduler::tempCloseFolders() |
|
{ |
|
// close temp opened folders |
|
QValueListConstIterator<QGuardedPtr<KMFolder> > it; |
|
for (it = mOpenFolders.begin(); it != mOpenFolders.end(); ++it) { |
|
KMFolder *folder = *it; |
|
if (folder) |
|
folder->close(); |
|
} |
|
mOpenFolders.clear(); |
|
} |
|
|
|
void ActionScheduler::execFilters(const QValueList<Q_UINT32> serNums) |
|
{ |
|
QValueListConstIterator<Q_UINT32> it; |
|
for (it = serNums.begin(); it != serNums.end(); ++it) |
|
execFilters( *it ); |
|
} |
|
|
|
void ActionScheduler::execFilters(const QPtrList<KMMsgBase> msgList) |
|
{ |
|
KMMsgBase *msgBase; |
|
QPtrList<KMMsgBase> list = msgList; |
|
for (msgBase = list.first(); msgBase; msgBase = list.next()) |
|
execFilters( msgBase->getMsgSerNum() ); |
|
} |
|
|
|
void ActionScheduler::execFilters(KMMsgBase* msgBase) |
|
{ |
|
execFilters( msgBase->getMsgSerNum() ); |
|
} |
|
|
|
void ActionScheduler::execFilters(Q_UINT32 serNum) |
|
{ |
|
if (mResult != ResultOk) |
|
return; // An error has already occurred don't even try to process this msg |
|
|
|
if (MessageProperty::filtering( serNum )) { |
|
// Not good someone else is already filtering this msg |
|
mResult = ResultError; |
|
if (!mExecuting) |
|
finishTimer->start( 0, true ); |
|
} else { |
|
// Everything is ok async fetch this message |
|
mFetchSerNums.append( serNum ); |
|
if (!mFetchExecuting) { |
|
//Need to (re)start incomplete msg fetching chain |
|
mFetchExecuting = true; |
|
fetchMessageTimer->start( 0, true ); |
|
} |
|
} |
|
} |
|
|
|
KMMsgBase *ActionScheduler::messageBase(Q_UINT32 serNum) |
|
{ |
|
int idx = -1; |
|
KMFolder *folder = 0; |
|
KMMsgBase *msg = 0; |
|
kmkernel->msgDict()->getLocation( serNum, &folder, &idx ); |
|
// It's possible that the message has been deleted or moved into a |
|
// different folder |
|
if (folder && (idx != -1)) { |
|
// everything is ok |
|
msg = folder->getMsgBase( idx ); |
|
tempOpenFolder( folder ); // just in case msg has moved |
|
} else { |
|
// the message is gone! |
|
mResult = ResultError; |
|
finishTimer->start( 0, true ); |
|
} |
|
return msg; |
|
} |
|
|
|
KMMessage *ActionScheduler::message(Q_UINT32 serNum) |
|
{ |
|
int idx = -1; |
|
KMFolder *folder = 0; |
|
KMMessage *msg = 0; |
|
kmkernel->msgDict()->getLocation( serNum, &folder, &idx ); |
|
// It's possible that the message has been deleted or moved into a |
|
// different folder |
|
if (folder && (idx != -1)) { |
|
// everything is ok |
|
msg = folder->getMsg( idx ); |
|
tempOpenFolder( folder ); // just in case msg has moved |
|
} else { |
|
// the message is gone! |
|
mResult = ResultError; |
|
finishTimer->start( 0, true ); |
|
} |
|
return msg; |
|
} |
|
|
|
void ActionScheduler::finish() |
|
{ |
|
if (mResult == ResultCriticalError) { |
|
// Must handle critical errors immediately |
|
emit result( mResult ); |
|
return; |
|
} |
|
|
|
if (!mFetchExecuting && !mExecuting) { |
|
// If an error has occurred and a permanent source folder has |
|
// been set then move all the messages left in the source folder |
|
// to the inbox. If no permanent source folder has been set |
|
// then abandon filtering of queued messages. |
|
if (!mDeleteSrcFolder && !mDestFolder.isNull() ) { |
|
while ( mSrcFolder->count() > 0 ) { |
|
KMMessage *msg = mSrcFolder->getMsg( 0 ); |
|
mDestFolder->moveMsg( msg ); |
|
} |
|
|
|
// Wait a little while before closing temp folders, just in case |
|
// new messages arrive for filtering. |
|
tempCloseFoldersTimer->start( 60*1000, true ); |
|
} |
|
mSerNums.clear(); //abandon |
|
mFetchSerNums.clear(); //abandon |
|
|
|
if (mFiltersAreQueued) |
|
mFilters = mQueuedFilters; |
|
mQueuedFilters.clear(); |
|
mFiltersAreQueued = false; |
|
ReturnCode aResult = mResult; |
|
mResult = ResultOk; |
|
mExecutingLock = false; |
|
emit result( aResult ); |
|
if (mAutoDestruct) |
|
delete this; |
|
} |
|
// else a message may be in the process of being fetched or filtered |
|
// wait until both of these commitments are finished then this |
|
// method should be called again. |
|
} |
|
|
|
void ActionScheduler::fetchMessage() |
|
{ |
|
QValueListIterator<Q_UINT32> mFetchMessageIt = mFetchSerNums.begin(); |
|
while (mFetchMessageIt != mFetchSerNums.end()) { |
|
if (!MessageProperty::transferInProgress(*mFetchMessageIt)) |
|
break; |
|
++mFetchMessageIt; |
|
} |
|
if (mFetchMessageIt == mFetchSerNums.end() && !mFetchSerNums.isEmpty()) |
|
mResult = ResultError; |
|
if ((mFetchMessageIt == mFetchSerNums.end()) || (mResult != ResultOk)) { |
|
mFetchExecuting = false; |
|
if (!mSrcFolder->count()) |
|
mSrcFolder->expunge(); |
|
finishTimer->start( 0, true ); |
|
return; |
|
} |
|
|
|
//If we got this far then there's a valid message to work with |
|
KMMsgBase *msgBase = messageBase( *mFetchMessageIt ); |
|
if (mResult != ResultOk) { |
|
mFetchExecuting = false; |
|
return; |
|
} |
|
mFetchUnget = msgBase->isMessage(); |
|
KMMessage *msg = message( *mFetchMessageIt ); |
|
if (mResult != ResultOk) { |
|
mFetchExecuting = false; |
|
return; |
|
} |
|
|
|
if (msg && msg->isComplete()) { |
|
messageFetched( msg ); |
|
} else if (msg) { |
|
FolderJob *job = msg->parent()->createJob( msg ); |
|
connect( job, SIGNAL(messageRetrieved( KMMessage* )), |
|
SLOT(messageFetched( KMMessage* )) ); |
|
job->start(); |
|
} else { |
|
mFetchExecuting = false; |
|
mResult = ResultError; |
|
finishTimer->start( 0, true ); |
|
return; |
|
} |
|
} |
|
|
|
void ActionScheduler::messageFetched( KMMessage *msg ) |
|
{ |
|
mFetchSerNums.remove( mFetchSerNums.begin() ); |
|
|
|
if ((mSet & KMFilterMgr::Explicit) || |
|
(msg->headerField( "X-KMail-Filtered" ).isEmpty())) { |
|
QString serNumS; |
|
serNumS.setNum( msg->getMsgSerNum() ); |
|
KMMessage *newMsg = new KMMessage; |
|
newMsg->fromString(msg->asString()); |
|
newMsg->setStatus(msg->status()); |
|
newMsg->setComplete(msg->isComplete()); |
|
newMsg->setHeaderField( "X-KMail-Filtered", serNumS ); |
|
mSrcFolder->addMsg( newMsg ); |
|
} |
|
if (mFetchUnget && msg->parent()) |
|
msg->parent()->unGetMsg( msg->parent()->find( msg )); |
|
fetchMessageTimer->start( 0, true ); |
|
return; |
|
} |
|
|
|
void ActionScheduler::msgAdded( KMFolder*, Q_UINT32 serNum ) |
|
{ |
|
if (!mIgnore) |
|
enqueue( serNum ); |
|
} |
|
|
|
void ActionScheduler::enqueue(Q_UINT32 serNum) |
|
{ |
|
if (mResult != ResultOk) |
|
return; // An error has already occurred don't even try to process this msg |
|
|
|
if (MessageProperty::filtering( serNum )) { |
|
// Not good someone else is already filtering this msg |
|
mResult = ResultError; |
|
if (!mExecuting) |
|
finishTimer->start( 0, true ); |
|
} else { |
|
// Everything is ok async filter this message |
|
mSerNums.append( serNum ); |
|
|
|
if (!mExecuting) { |
|
//Need to (re)start incomplete msg filtering chain |
|
mExecuting = true; |
|
mMessageIt = mSerNums.begin(); |
|
processMessageTimer->start( 0, true ); |
|
} |
|
} |
|
} |
|
|
|
void ActionScheduler::processMessage() |
|
{ |
|
if (mExecutingLock) |
|
return; |
|
mExecutingLock = true; |
|
mMessageIt = mSerNums.begin(); |
|
while (mMessageIt != mSerNums.end()) { |
|
if (!MessageProperty::transferInProgress(*mMessageIt)) |
|
break; |
|
++mMessageIt; |
|
} |
|
if (mMessageIt == mSerNums.end() && !mSerNums.isEmpty()) |
|
mResult = ResultError; |
|
if ((mMessageIt == mSerNums.end()) || (mResult != ResultOk)) { |
|
mExecutingLock = false; |
|
mExecuting = false; |
|
finishTimer->start( 0, true ); |
|
return; |
|
} |
|
|
|
//If we got this far then there's a valid message to work with |
|
KMMsgBase *msgBase = messageBase( *mMessageIt ); |
|
if (mResult != ResultOk) { |
|
mExecuting = false; |
|
return; |
|
} |
|
|
|
MessageProperty::setFiltering( *mMessageIt, true ); |
|
MessageProperty::setFilterHandler( *mMessageIt, this ); |
|
MessageProperty::setFilterFolder( *mMessageIt, mDestFolder ); |
|
mFilterIt = mFilters.begin(); |
|
|
|
mUnget = msgBase->isMessage(); |
|
KMMessage *msg = message( *mMessageIt ); |
|
if (mResult != ResultOk) { |
|
mExecuting = false; |
|
return; |
|
} |
|
|
|
bool mdnEnabled = true; |
|
{ |
|
KConfigGroup mdnConfig( kmkernel->config(), "MDN" ); |
|
int mode = mdnConfig.readNumEntry( "default-policy", 0 ); |
|
if (!mode || mode < 0 || mode > 3) |
|
mdnEnabled = false; |
|
} |
|
mdnEnabled = true; // For 3.2 force all mails to be complete |
|
|
|
if ((msg && msg->isComplete()) || |
|
(msg && !(*mFilterIt).requiresBody(msg) && !mdnEnabled)) |
|
{ |
|
// We have a complete message or |
|
// we can work with an incomplete message |
|
// Get a write lock on the message while it's being filtered |
|
msg->setTransferInProgress( true ); |
|
filterMessageTimer->start( 0, true ); |
|
return; |
|
} |
|
if (msg) { |
|
FolderJob *job = msg->parent()->createJob( msg ); |
|
connect( job, SIGNAL(messageRetrieved( KMMessage* )), |
|
SLOT(messageRetrieved( KMMessage* )) ); |
|
job->start(); |
|
} else { |
|
mExecuting = false; |
|
mResult = ResultError; |
|
finishTimer->start( 0, true ); |
|
return; |
|
} |
|
} |
|
|
|
void ActionScheduler::messageRetrieved(KMMessage* msg) |
|
{ |
|
// Get a write lock on the message while it's being filtered |
|
msg->setTransferInProgress( true ); |
|
filterMessageTimer->start( 0, true ); |
|
} |
|
|
|
void ActionScheduler::filterMessage() |
|
{ |
|
if (mFilterIt == mFilters.end()) { |
|
moveMessage(); |
|
return; |
|
} |
|
if (((mSet & KMFilterMgr::Outbound) && (*mFilterIt).applyOnOutbound()) || |
|
((mSet & KMFilterMgr::Inbound) && (*mFilterIt).applyOnInbound()) || |
|
((mSet & KMFilterMgr::Explicit) && (*mFilterIt).applyOnExplicit())) { |
|
// filter is applicable |
|
if (mAlwaysMatch || |
|
(*mFilterIt).pattern()->matches( *mMessageIt )) { |
|
mFilterAction = (*mFilterIt).actions()->first(); |
|
actionMessage(); |
|
return; |
|
} |
|
} |
|
++mFilterIt; |
|
filterMessageTimer->start( 0, true ); |
|
} |
|
|
|
void ActionScheduler::actionMessage(KMFilterAction::ReturnCode res) |
|
{ |
|
if (res == KMFilterAction::CriticalError) { |
|
mResult = ResultCriticalError; |
|
finish(); //must handle critical errors immediately |
|
} |
|
if (mFilterAction) { |
|
KMMessage *msg = message( *mMessageIt ); |
|
if (msg) { |
|
KMFilterAction *action = mFilterAction; |
|
mFilterAction = (*mFilterIt).actions()->next(); |
|
action->processAsync( msg ); |
|
} |
|
} else { |
|
// there are no more actions |
|
if ((*mFilterIt).stopProcessingHere()) |
|
mFilterIt = mFilters.end(); |
|
else |
|
++mFilterIt; |
|
filterMessageTimer->start( 0, true ); |
|
} |
|
} |
|
|
|
void ActionScheduler::moveMessage() |
|
{ |
|
KMMsgBase *msgBase = messageBase( *mMessageIt ); |
|
if (!msgBase) |
|
return; |
|
|
|
MessageProperty::setTransferInProgress( *mMessageIt, false, true ); |
|
KMMessage *msg = message( *mMessageIt ); |
|
KMFolder *folder = MessageProperty::filterFolder( *mMessageIt ); |
|
QString serNumS = msg->headerField( "X-KMail-Filtered" ); |
|
if (!serNumS.isEmpty()) |
|
mOriginalSerNum = serNumS.toUInt(); |
|
else |
|
mOriginalSerNum = 0; |
|
MessageProperty::setFilterHandler( *mMessageIt, 0 ); |
|
MessageProperty::setFiltering( *mMessageIt, false ); |
|
mSerNums.remove( *mMessageIt ); |
|
|
|
KMMessage *orgMsg = 0; |
|
ReturnCode mOldReturnCode = mResult; |
|
if (mOriginalSerNum) |
|
orgMsg = message( mOriginalSerNum ); |
|
mResult = mOldReturnCode; // ignore errors in deleting original message |
|
if (!orgMsg || !orgMsg->parent()) { |
|
// Original message is gone, no point filtering it anymore |
|
mSrcFolder->removeMsg( mSrcFolder->find( msg ) ); |
|
mExecutingLock = false; |
|
processMessageTimer->start( 0, true ); |
|
} else { |
|
if (!folder) // no filter folder specified leave in current place |
|
folder = orgMsg->parent(); |
|
} |
|
|
|
mIgnore = true; |
|
assert( msg->parent() == mSrcFolder.operator->() ); |
|
mSrcFolder->take( mSrcFolder->find( msg ) ); |
|
mSrcFolder->addMsg( msg ); |
|
mIgnore = false; |
|
|
|
if (msg && kmkernel->folderIsTrash( folder )) |
|
KMFilterAction::sendMDN( msg, KMime::MDN::Deleted ); |
|
|
|
KMCommand *cmd = new KMMoveCommand( folder, msg ); |
|
connect( cmd, SIGNAL( completed( KMCommand * ) ), |
|
this, SLOT( moveMessageFinished( KMCommand * ) ) ); |
|
cmd->start(); |
|
} |
|
|
|
void ActionScheduler::moveMessageFinished( KMCommand *command ) |
|
{ |
|
if ( command->result() != KMCommand::OK ) |
|
mResult = ResultError; |
|
|
|
if (!mSrcFolder->count()) |
|
mSrcFolder->expunge(); |
|
|
|
// in case the message stayed in the current folder TODO optimize |
|
if ( mHeaders ) |
|
mHeaders->clearSelectableAndAboutToBeDeleted( mOriginalSerNum ); |
|
KMMessage *msg = 0; |
|
ReturnCode mOldReturnCode = mResult; |
|
if (mOriginalSerNum) |
|
msg = message( mOriginalSerNum ); |
|
mResult = mOldReturnCode; // ignore errors in deleting original message |
|
if (msg && msg->parent()) { |
|
KMCommand *cmd = new KMMoveCommand( 0, msg ); |
|
cmd->start(); |
|
} |
|
|
|
if (mResult == ResultOk) { |
|
mExecutingLock = false; |
|
processMessageTimer->start( 0, true ); |
|
} else { |
|
finishTimer->start( 0, true ); |
|
} |
|
// else moveMessageFinished should call finish |
|
} |
|
|
|
#include "actionscheduler.moc"
|
|
|