diff options
Diffstat (limited to 'kmail/kmcommands.cpp')
-rw-r--r-- | kmail/kmcommands.cpp | 3559 |
1 files changed, 3559 insertions, 0 deletions
diff --git a/kmail/kmcommands.cpp b/kmail/kmcommands.cpp new file mode 100644 index 000000000..3119bb4ba --- /dev/null +++ b/kmail/kmcommands.cpp @@ -0,0 +1,3559 @@ +/* -*- mode: C++; c-file-style: "gnu" -*- + This file is part of KMail, the KDE mail client. + Copyright (c) 2002 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +// +// This file implements various "command" classes. These command classes +// are based on the command design pattern. +// +// Historically various operations were implemented as slots of KMMainWin. +// This proved inadequate as KMail has multiple top level windows +// (KMMainWin, KMReaderMainWin, SearchWindow, KMComposeWin) that may +// benefit from using these operations. It is desirable that these +// classes can operate without depending on or altering the state of +// a KMMainWin, in fact it is possible no KMMainWin object even exists. +// +// Now these operations have been rewritten as KMCommand based classes, +// making them independent of KMMainWin. +// +// The base command class KMCommand is async, which is a difference +// from the conventional command pattern. As normal derived classes implement +// the execute method, but client classes call start() instead of +// calling execute() directly. start() initiates async operations, +// and on completion of these operations calls execute() and then deletes +// the command. (So the client must not construct commands on the stack). +// +// The type of async operation supported by KMCommand is retrieval +// of messages from an IMAP server. + +#include "kmcommands.h" + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> +#include <mimelib/enum.h> +#include <mimelib/field.h> +#include <mimelib/mimepp.h> +#include <mimelib/string.h> +#include <kapplication.h> +#include <dcopclient.h> + +#include <qtextcodec.h> +#include <qpopupmenu.h> +#include <qeventloop.h> + +#include <libemailfunctions/email.h> +#include <kdcopservicestarter.h> +#include <kdebug.h> +#include <kfiledialog.h> +#include <kabc/stdaddressbook.h> +#include <kabc/addresseelist.h> +#include <kdirselectdialog.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kparts/browserextension.h> +#include <kprogress.h> +#include <krun.h> +#include <kbookmarkmanager.h> +#include <kstandarddirs.h> +#include <ktempfile.h> +#include <kimproxy.h> +#include <kuserprofile.h> +// KIO headers +#include <kio/job.h> +#include <kio/netaccess.h> + +#include <libkpimidentities/identitymanager.h> + +#include "actionscheduler.h" +using KMail::ActionScheduler; +#include "mailinglist-magic.h" +#include "kmaddrbook.h" +#include <kaddrbook.h> +#include "composer.h" +#include "kmfiltermgr.h" +#include "kmfoldermbox.h" +#include "kmfolderimap.h" +#include "kmfoldermgr.h" +#include "kmheaders.h" +#include "headeritem.h" +#include "kmmainwidget.h" +#include "kmmsgdict.h" +#include "messagesender.h" +#include "kmmsgpartdlg.h" +#include "undostack.h" +#include "kcursorsaver.h" +#include "partNode.h" +#include "objecttreeparser.h" +using KMail::ObjectTreeParser; +using KMail::FolderJob; +#include "chiasmuskeyselector.h" +#include "mailsourceviewer.h" +using KMail::MailSourceViewer; +#include "kmreadermainwin.h" +#include "secondarywindow.h" +using KMail::SecondaryWindow; +#include "redirectdialog.h" +using KMail::RedirectDialog; +#include "util.h" +#include "templateparser.h" +#include "editorwatcher.h" +#include "korghelper.h" + +#include "broadcaststatus.h" +#include "globalsettings.h" + +#include <libkdepim/kfileio.h> +#include "kcalendariface_stub.h" + +#include "progressmanager.h" +using KPIM::ProgressManager; +using KPIM::ProgressItem; +#include <kmime_mdn.h> +using namespace KMime; + +#include <kleo/specialjob.h> +#include <kleo/cryptobackend.h> +#include <kleo/cryptobackendfactory.h> + +#include <qclipboard.h> + +#include <memory> + +class LaterDeleterWithCommandCompletion : public KMail::Util::LaterDeleter +{ +public: + LaterDeleterWithCommandCompletion( KMCommand* command ) + :LaterDeleter( command ), m_result( KMCommand::Failed ) + { + } + ~LaterDeleterWithCommandCompletion() + { + setResult( m_result ); + KMCommand *command = static_cast<KMCommand*>( m_object ); + emit command->completed( command ); + } + void setResult( KMCommand::Result v ) { m_result = v; } +private: + KMCommand::Result m_result; +}; + + +KMCommand::KMCommand( QWidget *parent ) + : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ), + mEmitsCompletedItself( false ), mParent( parent ) +{ +} + +KMCommand::KMCommand( QWidget *parent, const QPtrList<KMMsgBase> &msgList ) + : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ), + mEmitsCompletedItself( false ), mParent( parent ), mMsgList( msgList ) +{ +} + +KMCommand::KMCommand( QWidget *parent, KMMsgBase *msgBase ) + : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ), + mEmitsCompletedItself( false ), mParent( parent ) +{ + mMsgList.append( msgBase ); +} + +KMCommand::KMCommand( QWidget *parent, KMMessage *msg ) + : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ), + mEmitsCompletedItself( false ), mParent( parent ) +{ + if (msg) + mMsgList.append( &msg->toMsgBase() ); +} + +KMCommand::~KMCommand() +{ + QValueListIterator<QGuardedPtr<KMFolder> > fit; + for ( fit = mFolders.begin(); fit != mFolders.end(); ++fit ) { + if (!(*fit)) + continue; + (*fit)->close("kmcommand"); + } +} + +KMCommand::Result KMCommand::result() +{ + if ( mResult == Undefined ) + kdDebug(5006) << k_funcinfo << "mResult is Undefined" << endl; + return mResult; +} + +void KMCommand::start() +{ + QTimer::singleShot( 0, this, SLOT( slotStart() ) ); +} + + +const QPtrList<KMMessage> KMCommand::retrievedMsgs() const +{ + return mRetrievedMsgs; +} + +KMMessage *KMCommand::retrievedMessage() const +{ + return mRetrievedMsgs.getFirst(); +} + +QWidget *KMCommand::parentWidget() const +{ + return mParent; +} + +int KMCommand::mCountJobs = 0; + +void KMCommand::slotStart() +{ + connect( this, SIGNAL( messagesTransfered( KMCommand::Result ) ), + this, SLOT( slotPostTransfer( KMCommand::Result ) ) ); + kmkernel->filterMgr()->ref(); + + if (mMsgList.find(0) != -1) { + emit messagesTransfered( Failed ); + return; + } + + if ((mMsgList.count() == 1) && + (mMsgList.getFirst()->isMessage()) && + (mMsgList.getFirst()->parent() == 0)) + { + // Special case of operating on message that isn't in a folder + mRetrievedMsgs.append((KMMessage*)mMsgList.getFirst()); + emit messagesTransfered( OK ); + return; + } + + for (KMMsgBase *mb = mMsgList.first(); mb; mb = mMsgList.next()) + if (!mb->parent()) { + emit messagesTransfered( Failed ); + return; + } else { + keepFolderOpen( mb->parent() ); + } + + // transfer the selected messages first + transferSelectedMsgs(); +} + +void KMCommand::slotPostTransfer( KMCommand::Result result ) +{ + disconnect( this, SIGNAL( messagesTransfered( KMCommand::Result ) ), + this, SLOT( slotPostTransfer( KMCommand::Result ) ) ); + if ( result == OK ) + result = execute(); + mResult = result; + QPtrListIterator<KMMessage> it( mRetrievedMsgs ); + KMMessage* msg; + while ( (msg = it.current()) != 0 ) + { + ++it; + if (msg->parent()) + msg->setTransferInProgress(false); + } + kmkernel->filterMgr()->deref(); + if ( !emitsCompletedItself() ) + emit completed( this ); + if ( !deletesItself() ) + deleteLater(); +} + +void KMCommand::transferSelectedMsgs() +{ + // make sure no other transfer is active + if (KMCommand::mCountJobs > 0) { + emit messagesTransfered( Failed ); + return; + } + + bool complete = true; + KMCommand::mCountJobs = 0; + mCountMsgs = 0; + mRetrievedMsgs.clear(); + mCountMsgs = mMsgList.count(); + uint totalSize = 0; + // the KProgressDialog for the user-feedback. Only enable it if it's needed. + // For some commands like KMSetStatusCommand it's not needed. Note, that + // for some reason the KProgressDialog eats the MouseReleaseEvent (if a + // command is executed after the MousePressEvent), cf. bug #71761. + if ( mCountMsgs > 0 ) { + mProgressDialog = new KProgressDialog(mParent, "transferProgress", + i18n("Please wait"), + i18n("Please wait while the message is transferred", + "Please wait while the %n messages are transferred", mMsgList.count()), + true); + mProgressDialog->setMinimumDuration(1000); + } + for (KMMsgBase *mb = mMsgList.first(); mb; mb = mMsgList.next()) + { + // check if all messages are complete + KMMessage *thisMsg = 0; + if ( mb->isMessage() ) + thisMsg = static_cast<KMMessage*>(mb); + else + { + KMFolder *folder = mb->parent(); + int idx = folder->find(mb); + if (idx < 0) continue; + thisMsg = folder->getMsg(idx); + } + if (!thisMsg) continue; + if ( thisMsg->transferInProgress() && + thisMsg->parent()->folderType() == KMFolderTypeImap ) + { + thisMsg->setTransferInProgress( false, true ); + thisMsg->parent()->ignoreJobsForMessage( thisMsg ); + } + + if ( thisMsg->parent() && !thisMsg->isComplete() && + ( !mProgressDialog || !mProgressDialog->wasCancelled() ) ) + { + kdDebug(5006)<<"### INCOMPLETE\n"; + // the message needs to be transferred first + complete = false; + KMCommand::mCountJobs++; + FolderJob *job = thisMsg->parent()->createJob(thisMsg); + job->setCancellable( false ); + totalSize += thisMsg->msgSizeServer(); + // emitted when the message was transferred successfully + connect(job, SIGNAL(messageRetrieved(KMMessage*)), + this, SLOT(slotMsgTransfered(KMMessage*))); + // emitted when the job is destroyed + connect(job, SIGNAL(finished()), + this, SLOT(slotJobFinished())); + connect(job, SIGNAL(progress(unsigned long, unsigned long)), + this, SLOT(slotProgress(unsigned long, unsigned long))); + // msg musn't be deleted + thisMsg->setTransferInProgress(true); + job->start(); + } else { + thisMsg->setTransferInProgress(true); + mRetrievedMsgs.append(thisMsg); + } + } + + if (complete) + { + delete mProgressDialog; + mProgressDialog = 0; + emit messagesTransfered( OK ); + } else { + // wait for the transfer and tell the progressBar the necessary steps + if ( mProgressDialog ) { + connect(mProgressDialog, SIGNAL(cancelClicked()), + this, SLOT(slotTransferCancelled())); + mProgressDialog->progressBar()->setTotalSteps(totalSize); + } + } +} + +void KMCommand::slotMsgTransfered(KMMessage* msg) +{ + if ( mProgressDialog && mProgressDialog->wasCancelled() ) { + emit messagesTransfered( Canceled ); + return; + } + + // save the complete messages + mRetrievedMsgs.append(msg); +} + +void KMCommand::slotProgress( unsigned long done, unsigned long /*total*/ ) +{ + mProgressDialog->progressBar()->setProgress( done ); +} + +void KMCommand::slotJobFinished() +{ + // the job is finished (with / without error) + KMCommand::mCountJobs--; + + if ( mProgressDialog && mProgressDialog->wasCancelled() ) return; + + if ( (mCountMsgs - static_cast<int>(mRetrievedMsgs.count())) > KMCommand::mCountJobs ) + { + // the message wasn't retrieved before => error + if ( mProgressDialog ) + mProgressDialog->hide(); + slotTransferCancelled(); + return; + } + // update the progressbar + if ( mProgressDialog ) { + mProgressDialog->setLabel(i18n("Please wait while the message is transferred", + "Please wait while the %n messages are transferred", KMCommand::mCountJobs)); + } + if (KMCommand::mCountJobs == 0) + { + // all done + delete mProgressDialog; + mProgressDialog = 0; + emit messagesTransfered( OK ); + } +} + +void KMCommand::slotTransferCancelled() +{ + // kill the pending jobs + QValueListIterator<QGuardedPtr<KMFolder> > fit; + for ( fit = mFolders.begin(); fit != mFolders.end(); ++fit ) { + if (!(*fit)) + continue; + KMFolder *folder = *fit; + KMFolderImap *imapFolder = dynamic_cast<KMFolderImap*>(folder); + if (imapFolder && imapFolder->account()) { + imapFolder->account()->killAllJobs(); + } + } + + KMCommand::mCountJobs = 0; + mCountMsgs = 0; + // unget the transfered messages + QPtrListIterator<KMMessage> it( mRetrievedMsgs ); + KMMessage* msg; + while ( (msg = it.current()) != 0 ) + { + KMFolder *folder = msg->parent(); + ++it; + if (!folder) + continue; + msg->setTransferInProgress(false); + int idx = folder->find(msg); + if (idx > 0) folder->unGetMsg(idx); + } + mRetrievedMsgs.clear(); + emit messagesTransfered( Canceled ); +} + +void KMCommand::keepFolderOpen( KMFolder *folder ) +{ + folder->open("kmcommand"); + mFolders.append( folder ); +} + +KMMailtoComposeCommand::KMMailtoComposeCommand( const KURL &url, + KMMessage *msg ) + :mUrl( url ), mMessage( msg ) +{ +} + +KMCommand::Result KMMailtoComposeCommand::execute() +{ + KMMessage *msg = new KMMessage; + uint id = 0; + + if ( mMessage && mMessage->parent() ) + id = mMessage->parent()->identity(); + + msg->initHeader(id); + msg->setCharset("utf-8"); + msg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) ); + + KMail::Composer * win = KMail::makeComposer( msg, id ); + win->setCharset("", true); + win->setFocusToSubject(); + win->show(); + + return OK; +} + + +KMMailtoReplyCommand::KMMailtoReplyCommand( QWidget *parent, + const KURL &url, KMMessage *msg, const QString &selection ) + :KMCommand( parent, msg ), mUrl( url ), mSelection( selection ) +{ +} + +KMCommand::Result KMMailtoReplyCommand::execute() +{ + //TODO : consider factoring createReply into this method. + KMMessage *msg = retrievedMessage(); + if ( !msg || !msg->codec() ) { + return Failed; + } + KMMessage *rmsg = msg->createReply( KMail::ReplyNone, mSelection ); + rmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) ); + + KMail::Composer * win = KMail::makeComposer( rmsg, 0 ); + win->setCharset(msg->codec()->mimeName(), true); + win->setReplyFocus(); + win->show(); + + return OK; +} + + +KMMailtoForwardCommand::KMMailtoForwardCommand( QWidget *parent, + const KURL &url, KMMessage *msg ) + :KMCommand( parent, msg ), mUrl( url ) +{ +} + +KMCommand::Result KMMailtoForwardCommand::execute() +{ + //TODO : consider factoring createForward into this method. + KMMessage *msg = retrievedMessage(); + if ( !msg || !msg->codec() ) { + return Failed; + } + KMMessage *fmsg = msg->createForward(); + fmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) ); + + KMail::Composer * win = KMail::makeComposer( fmsg ); + win->setCharset(msg->codec()->mimeName(), true); + win->show(); + + return OK; +} + + +KMAddBookmarksCommand::KMAddBookmarksCommand( const KURL &url, QWidget *parent ) + : KMCommand( parent ), mUrl( url ) +{ +} + +KMCommand::Result KMAddBookmarksCommand::execute() +{ + QString filename = locateLocal( "data", QString::fromLatin1("konqueror/bookmarks.xml") ); + KBookmarkManager *bookManager = KBookmarkManager::managerForFile( filename, + false ); + KBookmarkGroup group = bookManager->root(); + group.addBookmark( bookManager, mUrl.path(), KURL( mUrl ) ); + if( bookManager->save() ) { + bookManager->emitChanged( group ); + } + + return OK; +} + +KMMailtoAddAddrBookCommand::KMMailtoAddAddrBookCommand( const KURL &url, + QWidget *parent ) + : KMCommand( parent ), mUrl( url ) +{ +} + +KMCommand::Result KMMailtoAddAddrBookCommand::execute() +{ + KAddrBookExternal::addEmail( KMMessage::decodeMailtoUrl( mUrl.path() ), + parentWidget() ); + + return OK; +} + + +KMMailtoOpenAddrBookCommand::KMMailtoOpenAddrBookCommand( const KURL &url, + QWidget *parent ) + : KMCommand( parent ), mUrl( url ) +{ +} + +KMCommand::Result KMMailtoOpenAddrBookCommand::execute() +{ + KAddrBookExternal::openEmail( KMMessage::decodeMailtoUrl( mUrl.path() ), + parentWidget() ); + + return OK; +} + + +KMUrlCopyCommand::KMUrlCopyCommand( const KURL &url, KMMainWidget *mainWidget ) + :mUrl( url ), mMainWidget( mainWidget ) +{ +} + +KMCommand::Result KMUrlCopyCommand::execute() +{ + QClipboard* clip = QApplication::clipboard(); + + if (mUrl.protocol() == "mailto") { + // put the url into the mouse selection and the clipboard + QString address = KMMessage::decodeMailtoUrl( mUrl.path() ); + clip->setSelectionMode( true ); + clip->setText( address ); + clip->setSelectionMode( false ); + clip->setText( address ); + KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "Address copied to clipboard." )); + } else { + // put the url into the mouse selection and the clipboard + clip->setSelectionMode( true ); + clip->setText( mUrl.url() ); + clip->setSelectionMode( false ); + clip->setText( mUrl.url() ); + KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "URL copied to clipboard." )); + } + + return OK; +} + + +KMUrlOpenCommand::KMUrlOpenCommand( const KURL &url, KMReaderWin *readerWin ) + :mUrl( url ), mReaderWin( readerWin ) +{ +} + +KMCommand::Result KMUrlOpenCommand::execute() +{ + if ( !mUrl.isEmpty() ) + mReaderWin->slotUrlOpen( mUrl, KParts::URLArgs() ); + + return OK; +} + + +KMUrlSaveCommand::KMUrlSaveCommand( const KURL &url, QWidget *parent ) + : KMCommand( parent ), mUrl( url ) +{ +} + +KMCommand::Result KMUrlSaveCommand::execute() +{ + if ( mUrl.isEmpty() ) + return OK; + KURL saveUrl = KFileDialog::getSaveURL(mUrl.fileName(), QString::null, + parentWidget() ); + if ( saveUrl.isEmpty() ) + return Canceled; + if ( KIO::NetAccess::exists( saveUrl, false, parentWidget() ) ) + { + if (KMessageBox::warningContinueCancel(0, + i18n("<qt>File <b>%1</b> exists.<br>Do you want to replace it?</qt>") + .arg(saveUrl.prettyURL()), i18n("Save to File"), i18n("&Replace")) + != KMessageBox::Continue) + return Canceled; + } + KIO::Job *job = KIO::file_copy(mUrl, saveUrl, -1, true); + connect(job, SIGNAL(result(KIO::Job*)), SLOT(slotUrlSaveResult(KIO::Job*))); + setEmitsCompletedItself( true ); + return OK; +} + +void KMUrlSaveCommand::slotUrlSaveResult( KIO::Job *job ) +{ + if ( job->error() ) { + job->showErrorDialog(); + setResult( Failed ); + emit completed( this ); + } + else { + setResult( OK ); + emit completed( this ); + } +} + + +KMEditMsgCommand::KMEditMsgCommand( QWidget *parent, KMMessage *msg ) + :KMCommand( parent, msg ) +{ +} + +KMCommand::Result KMEditMsgCommand::execute() +{ + KMMessage *msg = retrievedMessage(); + if ( !msg || !msg->parent() || + ( !kmkernel->folderIsDraftOrOutbox( msg->parent() ) && + !kmkernel->folderIsTemplates( msg->parent() ) ) ) + return Failed; + + // Remember the old parent, we need it a bit further down to be able + // to put the unchanged messsage back in the original folder if the nth + // edit is discarded, for n > 1. + KMFolder *parent = msg->parent(); + if ( parent ) + parent->take( parent->find( msg ) ); + + KMail::Composer * win = KMail::makeComposer(); + msg->setTransferInProgress(false); // From here on on, the composer owns the message. + win->setMsg(msg, false, true); + win->setFolder( parent ); + win->show(); + + return OK; +} + +KMUseTemplateCommand::KMUseTemplateCommand( QWidget *parent, KMMessage *msg ) + :KMCommand( parent, msg ) +{ +} + +KMCommand::Result KMUseTemplateCommand::execute() +{ + KMMessage *msg = retrievedMessage(); + if ( !msg || !msg->parent() || + !kmkernel->folderIsTemplates( msg->parent() ) ) + return Failed; + + // Take a copy of the original message, which remains unchanged. + KMMessage *newMsg = new KMMessage( new DwMessage( *msg->asDwMessage() ) ); + newMsg->setComplete( msg->isComplete() ); + + // these fields need to be regenerated for the new message + newMsg->removeHeaderField("Date"); + newMsg->removeHeaderField("Message-ID"); + + KMail::Composer *win = KMail::makeComposer(); + newMsg->setTransferInProgress( false ); // From here on on, the composer owns the message. + win->setMsg( newMsg, false, true ); + win->show(); + + return OK; +} + +KMShowMsgSrcCommand::KMShowMsgSrcCommand( QWidget *parent, + KMMessage *msg, bool fixedFont ) + :KMCommand( parent, msg ), mFixedFont( fixedFont ) +{ + // remember complete state + mMsgWasComplete = msg->isComplete(); +} + +KMCommand::Result KMShowMsgSrcCommand::execute() +{ + KMMessage *msg = retrievedMessage(); + if ( !msg || !msg->codec() ) { + return Failed; + } + if ( msg->isComplete() && !mMsgWasComplete ) + msg->notify(); // notify observers as msg was transfered + QString str = msg->codec()->toUnicode( msg->asString() ); + + MailSourceViewer *viewer = new MailSourceViewer(); // deletes itself upon close + viewer->setCaption( i18n("Message as Plain Text") ); + viewer->setText(str); + if( mFixedFont ) + viewer->setFont(KGlobalSettings::fixedFont()); + + // Well, there is no widget to be seen here, so we have to use QCursor::pos() + // Update: (GS) I'm not going to make this code behave according to Xinerama + // configuration because this is quite the hack. + if (QApplication::desktop()->isVirtualDesktop()) { + int scnum = QApplication::desktop()->screenNumber(QCursor::pos()); + viewer->resize(QApplication::desktop()->screenGeometry(scnum).width()/2, + 2*QApplication::desktop()->screenGeometry(scnum).height()/3); + } else { + viewer->resize(QApplication::desktop()->geometry().width()/2, + 2*QApplication::desktop()->geometry().height()/3); + } + viewer->show(); + + return OK; +} + +static KURL subjectToUrl( const QString & subject ) { + return KFileDialog::getSaveURL( subject.stripWhiteSpace() + .replace( QDir::separator(), '_' ), + "*.mbox" ); +} + +KMSaveMsgCommand::KMSaveMsgCommand( QWidget *parent, KMMessage * msg ) + : KMCommand( parent ), + mMsgListIndex( 0 ), + mStandAloneMessage( 0 ), + mOffset( 0 ), + mTotalSize( msg ? msg->msgSize() : 0 ) +{ + if ( !msg ) return; + setDeletesItself( true ); + // If the mail has a serial number, operate on sernums, if it does not + // we need to work with the pointer, but can be reasonably sure it won't + // go away, since it'll be an encapsulated message or one that was opened + // from an .eml file. + if ( msg->getMsgSerNum() != 0 ) { + mMsgList.append( msg->getMsgSerNum() ); + if ( msg->parent() ) { + msg->parent()->open( "kmsavemsgcommand" ); + } + } else { + mStandAloneMessage = msg; + } + mUrl = subjectToUrl( msg->cleanSubject() ); +} + +KMSaveMsgCommand::KMSaveMsgCommand( QWidget *parent, + const QPtrList<KMMsgBase> &msgList ) + : KMCommand( parent ), + mMsgListIndex( 0 ), + mStandAloneMessage( 0 ), + mOffset( 0 ), + mTotalSize( 0 ) +{ + if (!msgList.getFirst()) + return; + setDeletesItself( true ); + KMMsgBase *msgBase = msgList.getFirst(); + + // We operate on serNums and not the KMMsgBase pointers, as those can + // change, or become invalid when changing the current message, switching + // folders, etc. + QPtrListIterator<KMMsgBase> it(msgList); + while ( it.current() ) { + mMsgList.append( (*it)->getMsgSerNum() ); + mTotalSize += (*it)->msgSize(); + if ((*it)->parent() != 0) + (*it)->parent()->open("kmcommand"); + ++it; + } + mMsgListIndex = 0; + mUrl = subjectToUrl( msgBase->cleanSubject() ); +} + +KURL KMSaveMsgCommand::url() +{ + return mUrl; +} + +KMCommand::Result KMSaveMsgCommand::execute() +{ + mJob = KIO::put( mUrl, S_IRUSR|S_IWUSR, false, false ); + mJob->slotTotalSize( mTotalSize ); + mJob->setAsyncDataEnabled( true ); + mJob->setReportDataSent( true ); + connect(mJob, SIGNAL(dataReq(KIO::Job*, QByteArray &)), + SLOT(slotSaveDataReq())); + connect(mJob, SIGNAL(result(KIO::Job*)), + SLOT(slotSaveResult(KIO::Job*))); + setEmitsCompletedItself( true ); + return OK; +} + +void KMSaveMsgCommand::slotSaveDataReq() +{ + int remainingBytes = mData.size() - mOffset; + if ( remainingBytes > 0 ) { + // eat leftovers first + if ( remainingBytes > MAX_CHUNK_SIZE ) + remainingBytes = MAX_CHUNK_SIZE; + + QByteArray data; + data.duplicate( mData.data() + mOffset, remainingBytes ); + mJob->sendAsyncData( data ); + mOffset += remainingBytes; + return; + } + // No leftovers, process next message. + if ( mMsgListIndex < mMsgList.size() ) { + KMMessage *msg = 0; + int idx = -1; + KMFolder * p = 0; + KMMsgDict::instance()->getLocation( mMsgList[mMsgListIndex], &p, &idx ); + assert( p ); + assert( idx >= 0 ); + //kdDebug() << "SERNUM: " << mMsgList[mMsgListIndex] << " idx: " << idx << " folder: " << p->prettyURL() << endl; + msg = p->getMsg(idx); + + if ( msg ) { + if ( msg->transferInProgress() ) { + QByteArray data = QByteArray(); + mJob->sendAsyncData( data ); + } + msg->setTransferInProgress( true ); + if (msg->isComplete() ) { + slotMessageRetrievedForSaving( msg ); + } else { + // retrieve Message first + if ( msg->parent() && !msg->isComplete() ) { + FolderJob *job = msg->parent()->createJob( msg ); + job->setCancellable( false ); + connect(job, SIGNAL( messageRetrieved( KMMessage* ) ), + this, SLOT( slotMessageRetrievedForSaving( KMMessage* ) ) ); + job->start(); + } + } + } else { + mJob->slotError( KIO::ERR_ABORTED, + i18n("The message was removed while saving it. " + "It has not been saved.") ); + } + } else { + if ( mStandAloneMessage ) { + // do the special case of a standalone message + slotMessageRetrievedForSaving( mStandAloneMessage ); + mStandAloneMessage = 0; + } else { + // No more messages. Tell the putjob we are done. + QByteArray data = QByteArray(); + mJob->sendAsyncData( data ); + } + } +} + +void KMSaveMsgCommand::slotMessageRetrievedForSaving(KMMessage *msg) +{ + if ( msg ) { + mData = KMFolderMbox::escapeFrom( msg->asDwString() ); + KMail::Util::insert( mData, 0, msg->mboxMessageSeparator() ); + KMail::Util::append( mData, "\n" ); + msg->setTransferInProgress(false); + + mOffset = 0; + QByteArray data; + int size; + // Unless it is great than 64 k send the whole message. kio buffers for us. + if( mData.size() > (unsigned int) MAX_CHUNK_SIZE ) + size = MAX_CHUNK_SIZE; + else + size = mData.size(); + + data.duplicate( mData, size ); + mJob->sendAsyncData( data ); + mOffset += size; + } + ++mMsgListIndex; + // Get rid of the message. + if ( msg && msg->parent() && msg->getMsgSerNum() ) { + int idx = -1; + KMFolder * p = 0; + KMMsgDict::instance()->getLocation( msg, &p, &idx ); + assert( p == msg->parent() ); assert( idx >= 0 ); + p->unGetMsg( idx ); + p->close("kmcommand"); + } +} + +void KMSaveMsgCommand::slotSaveResult(KIO::Job *job) +{ + if (job->error()) + { + if (job->error() == KIO::ERR_FILE_ALREADY_EXIST) + { + if (KMessageBox::warningContinueCancel(0, + i18n("File %1 exists.\nDo you want to replace it?") + .arg(mUrl.prettyURL()), i18n("Save to File"), i18n("&Replace")) + == KMessageBox::Continue) { + mOffset = 0; + + mJob = KIO::put( mUrl, S_IRUSR|S_IWUSR, true, false ); + mJob->slotTotalSize( mTotalSize ); + mJob->setAsyncDataEnabled( true ); + mJob->setReportDataSent( true ); + connect(mJob, SIGNAL(dataReq(KIO::Job*, QByteArray &)), + SLOT(slotSaveDataReq())); + connect(mJob, SIGNAL(result(KIO::Job*)), + SLOT(slotSaveResult(KIO::Job*))); + } + } + else + { + job->showErrorDialog(); + setResult( Failed ); + emit completed( this ); + deleteLater(); + } + } else { + setResult( OK ); + emit completed( this ); + deleteLater(); + } +} + +//----------------------------------------------------------------------------- + +KMOpenMsgCommand::KMOpenMsgCommand( QWidget *parent, const KURL & url, + const QString & encoding ) + : KMCommand( parent ), + mUrl( url ), + mEncoding( encoding ) +{ + setDeletesItself( true ); +} + +KMCommand::Result KMOpenMsgCommand::execute() +{ + if ( mUrl.isEmpty() ) { + mUrl = KFileDialog::getOpenURL( ":OpenMessage", "message/rfc822 application/mbox", + parentWidget(), i18n("Open Message") ); + } + if ( mUrl.isEmpty() ) { + setDeletesItself( false ); + return Canceled; + } + mJob = KIO::get( mUrl, false, false ); + mJob->setReportDataSent( true ); + connect( mJob, SIGNAL( data( KIO::Job *, const QByteArray & ) ), + this, SLOT( slotDataArrived( KIO::Job*, const QByteArray & ) ) ); + connect( mJob, SIGNAL( result( KIO::Job * ) ), + SLOT( slotResult( KIO::Job * ) ) ); + setEmitsCompletedItself( true ); + return OK; +} + +void KMOpenMsgCommand::slotDataArrived( KIO::Job *, const QByteArray & data ) +{ + if ( data.isEmpty() ) + return; + + mMsgString.append( data.data(), data.size() ); +} + +void KMOpenMsgCommand::slotResult( KIO::Job *job ) +{ + if ( job->error() ) { + // handle errors + job->showErrorDialog(); + setResult( Failed ); + emit completed( this ); + } + else { + int startOfMessage = 0; + if ( mMsgString.compare( 0, 5, "From ", 5 ) == 0 ) { + startOfMessage = mMsgString.find( '\n' ); + if ( startOfMessage == -1 ) { + KMessageBox::sorry( parentWidget(), + i18n( "The file does not contain a message." ) ); + setResult( Failed ); + emit completed( this ); + // Emulate closing of a secondary window so that KMail exits in case it + // was started with the --view command line option. Otherwise an + // invisible KMail would keep running. + SecondaryWindow *win = new SecondaryWindow(); + win->close(); + win->deleteLater(); + deleteLater(); + return; + } + startOfMessage += 1; // the message starts after the '\n' + } + // check for multiple messages in the file + bool multipleMessages = true; + int endOfMessage = mMsgString.find( "\nFrom " ); + if ( endOfMessage == -1 ) { + endOfMessage = mMsgString.length(); + multipleMessages = false; + } + DwMessage *dwMsg = new DwMessage; + dwMsg->FromString( mMsgString.substr( startOfMessage, + endOfMessage - startOfMessage ) ); + dwMsg->Parse(); + // check whether we have a message ( no headers => this isn't a message ) + if ( dwMsg->Headers().NumFields() == 0 ) { + KMessageBox::sorry( parentWidget(), + i18n( "The file does not contain a message." ) ); + delete dwMsg; dwMsg = 0; + setResult( Failed ); + emit completed( this ); + // Emulate closing of a secondary window (see above). + SecondaryWindow *win = new SecondaryWindow(); + win->close(); + win->deleteLater(); + deleteLater(); + return; + } + KMMessage *msg = new KMMessage( dwMsg ); + msg->setReadyToShow( true ); + KMReaderMainWin *win = new KMReaderMainWin(); + win->showMsg( mEncoding, msg ); + win->show(); + if ( multipleMessages ) + KMessageBox::information( win, + i18n( "The file contains multiple messages. " + "Only the first message is shown." ) ); + setResult( OK ); + emit completed( this ); + } + deleteLater(); +} + +//----------------------------------------------------------------------------- + +//TODO: ReplyTo, NoQuoteReplyTo, ReplyList, ReplyToAll, ReplyAuthor +// are all similar and should be factored +KMReplyToCommand::KMReplyToCommand( QWidget *parent, KMMessage *msg, + const QString &selection ) + : KMCommand( parent, msg ), mSelection( selection ) +{ +} + +KMCommand::Result KMReplyToCommand::execute() +{ + KCursorSaver busy(KBusyPtr::busy()); + KMMessage *msg = retrievedMessage(); + if ( !msg || !msg->codec() ) { + return Failed; + } + KMMessage *reply = msg->createReply( KMail::ReplySmart, mSelection ); + KMail::Composer * win = KMail::makeComposer( reply ); + win->setCharset( msg->codec()->mimeName(), true ); + win->setReplyFocus(); + win->show(); + + return OK; +} + + +KMNoQuoteReplyToCommand::KMNoQuoteReplyToCommand( QWidget *parent, + KMMessage *msg ) + : KMCommand( parent, msg ) +{ +} + +KMCommand::Result KMNoQuoteReplyToCommand::execute() +{ + KCursorSaver busy(KBusyPtr::busy()); + KMMessage *msg = retrievedMessage(); + if ( !msg || !msg->codec() ) { + return Failed; + } + KMMessage *reply = msg->createReply( KMail::ReplySmart, "", true); + KMail::Composer * win = KMail::makeComposer( reply ); + win->setCharset(msg->codec()->mimeName(), true); + win->setReplyFocus(false); + win->show(); + + return OK; +} + + +KMReplyListCommand::KMReplyListCommand( QWidget *parent, + KMMessage *msg, const QString &selection ) + : KMCommand( parent, msg ), mSelection( selection ) +{ +} + +KMCommand::Result KMReplyListCommand::execute() +{ + KCursorSaver busy(KBusyPtr::busy()); + KMMessage *msg = retrievedMessage(); + if ( !msg || !msg->codec() ) { + return Failed; + } + KMMessage *reply = msg->createReply( KMail::ReplyList, mSelection); + KMail::Composer * win = KMail::makeComposer( reply ); + win->setCharset(msg->codec()->mimeName(), true); + win->setReplyFocus(false); + win->show(); + + return OK; +} + + +KMReplyToAllCommand::KMReplyToAllCommand( QWidget *parent, + KMMessage *msg, const QString &selection ) + :KMCommand( parent, msg ), mSelection( selection ) +{ +} + +KMCommand::Result KMReplyToAllCommand::execute() +{ + KCursorSaver busy(KBusyPtr::busy()); + KMMessage *msg = retrievedMessage(); + if ( !msg || !msg->codec() ) { + return Failed; + } + KMMessage *reply = msg->createReply( KMail::ReplyAll, mSelection ); + KMail::Composer * win = KMail::makeComposer( reply ); + win->setCharset( msg->codec()->mimeName(), true ); + win->setReplyFocus(); + win->show(); + + return OK; +} + + +KMReplyAuthorCommand::KMReplyAuthorCommand( QWidget *parent, KMMessage *msg, + const QString &selection ) + : KMCommand( parent, msg ), mSelection( selection ) +{ +} + +KMCommand::Result KMReplyAuthorCommand::execute() +{ + KCursorSaver busy(KBusyPtr::busy()); + KMMessage *msg = retrievedMessage(); + if ( !msg || !msg->codec() ) { + return Failed; + } + KMMessage *reply = msg->createReply( KMail::ReplyAuthor, mSelection ); + KMail::Composer * win = KMail::makeComposer( reply ); + win->setCharset( msg->codec()->mimeName(), true ); + win->setReplyFocus(); + win->show(); + + return OK; +} + + +KMForwardInlineCommand::KMForwardInlineCommand( QWidget *parent, + const QPtrList<KMMsgBase> &msgList, uint identity ) + : KMCommand( parent, msgList ), + mIdentity( identity ) +{ +} + +KMForwardInlineCommand::KMForwardInlineCommand( QWidget *parent, + KMMessage *msg, uint identity ) + : KMCommand( parent, msg ), + mIdentity( identity ) +{ +} + +KMCommand::Result KMForwardInlineCommand::execute() +{ + QPtrList<KMMessage> msgList = retrievedMsgs(); + + if (msgList.count() >= 2) { // Multiple forward + + uint id = 0; + QPtrList<KMMessage> linklist; + for ( KMMessage *msg = msgList.first(); msg; msg = msgList.next() ) { + // set the identity + if (id == 0) + id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt(); + + // msgText += msg->createForwardBody(); + linklist.append( msg ); + } + if ( id == 0 ) + id = mIdentity; // use folder identity if no message had an id set + KMMessage *fwdMsg = new KMMessage; + fwdMsg->initHeader( id ); + fwdMsg->setAutomaticFields( true ); + fwdMsg->setCharset( "utf-8" ); + // fwdMsg->setBody( msgText ); + + for ( KMMessage *msg = linklist.first(); msg; msg = linklist.next() ) { + TemplateParser parser( fwdMsg, TemplateParser::Forward, + msg->body(), false, false, false, false); + parser.process( msg, 0, true ); + + fwdMsg->link( msg, KMMsgStatusForwarded ); + } + + KCursorSaver busy( KBusyPtr::busy() ); + KMail::Composer * win = KMail::makeComposer( fwdMsg, id ); + win->setCharset(""); + win->show(); + + } else { // forward a single message at most + + KMMessage *msg = msgList.getFirst(); + if ( !msg || !msg->codec() ) + return Failed; + + KCursorSaver busy( KBusyPtr::busy() ); + KMMessage *fwdMsg = msg->createForward(); + + uint id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt(); + if ( id == 0 ) + id = mIdentity; + { + KMail::Composer * win = KMail::makeComposer( fwdMsg, id ); + win->setCharset( fwdMsg->codec()->mimeName(), true ); + win->setBody( fwdMsg->bodyToUnicode() ); + win->show(); + } + } + return OK; +} + + +KMForwardAttachedCommand::KMForwardAttachedCommand( QWidget *parent, + const QPtrList<KMMsgBase> &msgList, uint identity, KMail::Composer *win ) + : KMCommand( parent, msgList ), mIdentity( identity ), + mWin( QGuardedPtr<KMail::Composer>( win )) +{ +} + +KMForwardAttachedCommand::KMForwardAttachedCommand( QWidget *parent, + KMMessage * msg, uint identity, KMail::Composer *win ) + : KMCommand( parent, msg ), mIdentity( identity ), + mWin( QGuardedPtr< KMail::Composer >( win )) +{ +} + +KMCommand::Result KMForwardAttachedCommand::execute() +{ + QPtrList<KMMessage> msgList = retrievedMsgs(); + KMMessage *fwdMsg = new KMMessage; + + if (msgList.count() >= 2) { + // don't respect X-KMail-Identity headers because they might differ for + // the selected mails + fwdMsg->initHeader(mIdentity); + } + else if (msgList.count() == 1) { + KMMessage *msg = msgList.getFirst(); + fwdMsg->initFromMessage(msg); + fwdMsg->setSubject( msg->forwardSubject() ); + } + + fwdMsg->setAutomaticFields(true); + + KCursorSaver busy(KBusyPtr::busy()); + if (!mWin) + mWin = KMail::makeComposer(fwdMsg, mIdentity); + + // iterate through all the messages to be forwarded + for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) { + // remove headers that shouldn't be forwarded + msg->removePrivateHeaderFields(); + msg->removeHeaderField("BCC"); + // set the part + KMMessagePart *msgPart = new KMMessagePart; + msgPart->setTypeStr("message"); + msgPart->setSubtypeStr("rfc822"); + msgPart->setCharset(msg->charset()); + msgPart->setName("forwarded message"); + msgPart->setContentDescription(msg->from()+": "+msg->subject()); + msgPart->setContentDisposition( "inline" ); + // THIS HAS TO BE AFTER setCte()!!!! + msgPart->setMessageBody( KMail::Util::ByteArray( msg->asDwString() ) ); + msgPart->setCharset(""); + + fwdMsg->link(msg, KMMsgStatusForwarded); + mWin->addAttach(msgPart); + } + + mWin->show(); + + return OK; +} + + +KMForwardDigestCommand::KMForwardDigestCommand( QWidget *parent, + const QPtrList<KMMsgBase> &msgList, uint identity, KMail::Composer *win ) + : KMCommand( parent, msgList ), mIdentity( identity ), + mWin( QGuardedPtr<KMail::Composer>( win )) +{ +} + +KMForwardDigestCommand::KMForwardDigestCommand( QWidget *parent, + KMMessage * msg, uint identity, KMail::Composer *win ) + : KMCommand( parent, msg ), mIdentity( identity ), + mWin( QGuardedPtr< KMail::Composer >( win )) +{ +} + +KMCommand::Result KMForwardDigestCommand::execute() +{ + QPtrList<KMMessage> msgList = retrievedMsgs(); + + if ( msgList.count() < 2 ) + return Undefined; // must have more than 1 for a digest + + uint id = 0; + KMMessage *fwdMsg = new KMMessage; + KMMessagePart *msgPart = new KMMessagePart; + QString msgPartText; + int msgCnt = 0; // incase there are some we can't forward for some reason + + // dummy header initialization; initialization with the correct identity + // is done below + fwdMsg->initHeader( id ); + fwdMsg->setAutomaticFields( true ); + fwdMsg->mMsg->Headers().ContentType().CreateBoundary( 1 ); + QCString boundary( fwdMsg->mMsg->Headers().ContentType().Boundary().c_str() ); + msgPartText = i18n("\nThis is a MIME digest forward. The content of the" + " message is contained in the attachment(s).\n\n\n"); + // iterate through all the messages to be forwarded + for ( KMMessage *msg = msgList.first(); msg; msg = msgList.next() ) { + // set the identity + if ( id == 0 ) + id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt(); + // set the part header + msgPartText += "--"; + msgPartText += QString::fromLatin1( boundary ); + msgPartText += "\nContent-Type: MESSAGE/RFC822"; + msgPartText += QString( "; CHARSET=%1" ).arg( msg->charset() ); + msgPartText += '\n'; + DwHeaders dwh; + dwh.MessageId().CreateDefault(); + msgPartText += QString( "Content-ID: %1\n" ).arg( dwh.MessageId().AsString().c_str() ); + msgPartText += QString( "Content-Description: %1" ).arg( msg->subject() ); + if ( !msg->subject().contains( "(fwd)" ) ) + msgPartText += " (fwd)"; + msgPartText += "\n\n"; + // remove headers that shouldn't be forwarded + msg->removePrivateHeaderFields(); + msg->removeHeaderField( "BCC" ); + // set the part + msgPartText += msg->headerAsString(); + msgPartText += '\n'; + msgPartText += msg->body(); + msgPartText += '\n'; // eot + msgCnt++; + fwdMsg->link( msg, KMMsgStatusForwarded ); + } + + if ( id == 0 ) + id = mIdentity; // use folder identity if no message had an id set + fwdMsg->initHeader( id ); + msgPartText += "--"; + msgPartText += QString::fromLatin1( boundary ); + msgPartText += "--\n"; + QCString tmp; + msgPart->setTypeStr( "MULTIPART" ); + tmp.sprintf( "Digest; boundary=\"%s\"", boundary.data() ); + msgPart->setSubtypeStr( tmp ); + msgPart->setName( "unnamed" ); + msgPart->setCte( DwMime::kCte7bit ); // does it have to be 7bit? + msgPart->setContentDescription( QString( "Digest of %1 messages." ).arg( msgCnt ) ); + // THIS HAS TO BE AFTER setCte()!!!! + msgPart->setBodyEncoded( QCString( msgPartText.ascii() ) ); + KCursorSaver busy( KBusyPtr::busy() ); + KMail::Composer * win = KMail::makeComposer( fwdMsg, id ); + win->addAttach( msgPart ); + win->show(); + return OK; +} + +KMRedirectCommand::KMRedirectCommand( QWidget *parent, + KMMessage *msg ) + : KMCommand( parent, msg ) +{ +} + +KMCommand::Result KMRedirectCommand::execute() +{ + KMMessage *msg = retrievedMessage(); + if ( !msg || !msg->codec() ) + return Failed; + + RedirectDialog dlg( parentWidget(), "redirect", true, + kmkernel->msgSender()->sendImmediate() ); + if (dlg.exec()==QDialog::Rejected) return Failed; + + KMMessage *newMsg = msg->createRedirect( dlg.to() ); + KMFilterAction::sendMDN( msg, KMime::MDN::Dispatched ); + + const KMail::MessageSender::SendMethod method = dlg.sendImmediate() + ? KMail::MessageSender::SendImmediate + : KMail::MessageSender::SendLater; + if ( !kmkernel->msgSender()->send( newMsg, method ) ) { + kdDebug(5006) << "KMRedirectCommand: could not redirect message (sending failed)" << endl; + return Failed; // error: couldn't send + } + return OK; +} + + +KMCustomReplyToCommand::KMCustomReplyToCommand( QWidget *parent, KMMessage *msg, + const QString &selection, + const QString &tmpl ) + : KMCommand( parent, msg ), mSelection( selection ), mTemplate( tmpl ) +{ +} + +KMCommand::Result KMCustomReplyToCommand::execute() +{ + KCursorSaver busy(KBusyPtr::busy()); + KMMessage *msg = retrievedMessage(); + if ( !msg || !msg->codec() ) { + return Failed; + } + KMMessage *reply = msg->createReply( KMail::ReplySmart, mSelection, + false, true, false, mTemplate ); + KMail::Composer * win = KMail::makeComposer( reply ); + win->setCharset( msg->codec()->mimeName(), true ); + win->setReplyFocus(); + win->show(); + + return OK; +} + + +KMCustomReplyAllToCommand::KMCustomReplyAllToCommand( QWidget *parent, KMMessage *msg, + const QString &selection, + const QString &tmpl ) + : KMCommand( parent, msg ), mSelection( selection ), mTemplate( tmpl ) +{ +} + +KMCommand::Result KMCustomReplyAllToCommand::execute() +{ + KCursorSaver busy(KBusyPtr::busy()); + KMMessage *msg = retrievedMessage(); + if ( !msg || !msg->codec() ) { + return Failed; + } + KMMessage *reply = msg->createReply( KMail::ReplyAll, mSelection, + false, true, false, mTemplate ); + KMail::Composer * win = KMail::makeComposer( reply ); + win->setCharset( msg->codec()->mimeName(), true ); + win->setReplyFocus(); + win->show(); + + return OK; +} + + +KMCustomForwardCommand::KMCustomForwardCommand( QWidget *parent, + const QPtrList<KMMsgBase> &msgList, uint identity, const QString &tmpl ) + : KMCommand( parent, msgList ), + mIdentity( identity ), mTemplate( tmpl ) +{ +} + +KMCustomForwardCommand::KMCustomForwardCommand( QWidget *parent, + KMMessage *msg, uint identity, const QString &tmpl ) + : KMCommand( parent, msg ), + mIdentity( identity ), mTemplate( tmpl ) +{ +} + +KMCommand::Result KMCustomForwardCommand::execute() +{ + QPtrList<KMMessage> msgList = retrievedMsgs(); + + if (msgList.count() >= 2) { // Multiple forward + + uint id = 0; + QPtrList<KMMessage> linklist; + for ( KMMessage *msg = msgList.first(); msg; msg = msgList.next() ) { + // set the identity + if (id == 0) + id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt(); + + // msgText += msg->createForwardBody(); + linklist.append( msg ); + } + if ( id == 0 ) + id = mIdentity; // use folder identity if no message had an id set + KMMessage *fwdMsg = new KMMessage; + fwdMsg->initHeader( id ); + fwdMsg->setAutomaticFields( true ); + fwdMsg->setCharset( "utf-8" ); + // fwdMsg->setBody( msgText ); + + for ( KMMessage *msg = linklist.first(); msg; msg = linklist.next() ) { + TemplateParser parser( fwdMsg, TemplateParser::Forward, + msg->body(), false, false, false, false); + parser.process( msg, 0, true ); + + fwdMsg->link( msg, KMMsgStatusForwarded ); + } + + KCursorSaver busy( KBusyPtr::busy() ); + KMail::Composer * win = KMail::makeComposer( fwdMsg, id ); + win->setCharset(""); + win->show(); + + } else { // forward a single message at most + + KMMessage *msg = msgList.getFirst(); + if ( !msg || !msg->codec() ) + return Failed; + + KCursorSaver busy( KBusyPtr::busy() ); + KMMessage *fwdMsg = msg->createForward( mTemplate ); + + uint id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt(); + if ( id == 0 ) + id = mIdentity; + { + KMail::Composer * win = KMail::makeComposer( fwdMsg, id ); + win->setCharset( fwdMsg->codec()->mimeName(), true ); + win->show(); + } + } + return OK; +} + + +KMPrintCommand::KMPrintCommand( QWidget *parent, + KMMessage *msg, bool htmlOverride, bool htmlLoadExtOverride, + bool useFixedFont, const QString & encoding ) + : KMCommand( parent, msg ), mHtmlOverride( htmlOverride ), + mHtmlLoadExtOverride( htmlLoadExtOverride ), + mUseFixedFont( useFixedFont ), mEncoding( encoding ) +{ + mOverrideFont = KGlobalSettings::generalFont(); +} + + +void KMPrintCommand::setOverrideFont( const QFont& font ) +{ + mOverrideFont = font; +} + +KMCommand::Result KMPrintCommand::execute() +{ + KMReaderWin printWin( 0, 0, 0 ); + printWin.setPrinting( true ); + printWin.readConfig(); + printWin.setHtmlOverride( mHtmlOverride ); + printWin.setHtmlLoadExtOverride( mHtmlLoadExtOverride ); + printWin.setUseFixedFont( mUseFixedFont ); + printWin.setOverrideEncoding( mEncoding ); + printWin.setPrintFont( mOverrideFont ); + printWin.setDecryptMessageOverwrite( true ); + printWin.setMsg( retrievedMessage(), true ); + printWin.printMsg(); + + return OK; +} + + +KMSetStatusCommand::KMSetStatusCommand( KMMsgStatus status, + const QValueList<Q_UINT32> &serNums, bool toggle ) + : mStatus( status ), mSerNums( serNums ), mToggle( toggle ) +{ +} + +KMCommand::Result KMSetStatusCommand::execute() +{ + QValueListIterator<Q_UINT32> it; + int idx = -1; + KMFolder *folder = 0; + bool parentStatus = false; + + // Toggle actions on threads toggle the whole thread + // depending on the state of the parent. + if (mToggle) { + KMMsgBase *msg; + KMMsgDict::instance()->getLocation( *mSerNums.begin(), &folder, &idx ); + if (folder) { + msg = folder->getMsgBase(idx); + if (msg && (msg->status()&mStatus)) + parentStatus = true; + else + parentStatus = false; + } + } + QMap< KMFolder*, QValueList<int> > folderMap; + for ( it = mSerNums.begin(); it != mSerNums.end(); ++it ) { + KMMsgDict::instance()->getLocation( *it, &folder, &idx ); + if (folder) { + if (mToggle) { + KMMsgBase *msg = folder->getMsgBase(idx); + // check if we are already at the target toggle state + if (msg) { + bool myStatus; + if (msg->status()&mStatus) + myStatus = true; + else + myStatus = false; + if (myStatus != parentStatus) + continue; + } + } + /* Collect the ids for each folder in a separate list and + send them off in one go at the end. */ + folderMap[folder].append(idx); + } + } + QMapIterator< KMFolder*, QValueList<int> > it2 = folderMap.begin(); + while ( it2 != folderMap.end() ) { + KMFolder *f = it2.key(); + f->setStatus( (*it2), mStatus, mToggle ); + ++it2; + } + //kapp->dcopClient()->emitDCOPSignal( "unreadCountChanged()", QByteArray() ); + + return OK; +} + + +KMFilterCommand::KMFilterCommand( const QCString &field, const QString &value ) + : mField( field ), mValue( value ) +{ +} + +KMCommand::Result KMFilterCommand::execute() +{ + kmkernel->filterMgr()->createFilter( mField, mValue ); + + return OK; +} + + +KMFilterActionCommand::KMFilterActionCommand( QWidget *parent, + const QPtrList<KMMsgBase> &msgList, + KMFilter *filter ) + : KMCommand( parent, msgList ), mFilter( filter ) +{ + QPtrListIterator<KMMsgBase> it(msgList); + while ( it.current() ) { + serNumList.append( (*it)->getMsgSerNum() ); + ++it; + } +} + +KMCommand::Result KMFilterActionCommand::execute() +{ + KCursorSaver busy( KBusyPtr::busy() ); + + int msgCount = 0; + int msgCountToFilter = serNumList.count(); + ProgressItem* progressItem = + ProgressManager::createProgressItem ( "filter"+ProgressManager::getUniqueID(), + i18n( "Filtering messages" ) ); + progressItem->setTotalItems( msgCountToFilter ); + QValueList<Q_UINT32>::const_iterator it; + for ( it = serNumList.begin(); it != serNumList.end(); it++ ) { + Q_UINT32 serNum = *it; + int diff = msgCountToFilter - ++msgCount; + if ( diff < 10 || !( msgCount % 20 ) || msgCount <= 10 ) { + progressItem->updateProgress(); + QString statusMsg = i18n("Filtering message %1 of %2"); + statusMsg = statusMsg.arg( msgCount ).arg( msgCountToFilter ); + KPIM::BroadcastStatus::instance()->setStatusMsg( statusMsg ); + KApplication::kApplication()->eventLoop()->processEvents( QEventLoop::ExcludeUserInput, 50 ); + } + + int filterResult = kmkernel->filterMgr()->process( serNum, mFilter ); + if (filterResult == 2) { + // something went horribly wrong (out of space?) + perror("Critical error"); + kmkernel->emergencyExit( i18n("Not enough free disk space?" )); + } + progressItem->incCompletedItems(); + } + + progressItem->setComplete(); + progressItem = 0; + return OK; +} + + +KMMetaFilterActionCommand::KMMetaFilterActionCommand( KMFilter *filter, + KMHeaders *headers, + KMMainWidget *main ) + : QObject( main ), + mFilter( filter ), mHeaders( headers ), mMainWidget( main ) +{ +} + +void KMMetaFilterActionCommand::start() +{ + if (ActionScheduler::isEnabled() ) { + // use action scheduler + KMFilterMgr::FilterSet set = KMFilterMgr::All; + QValueList<KMFilter*> filters; + filters.append( mFilter ); + ActionScheduler *scheduler = new ActionScheduler( set, filters, mHeaders ); + scheduler->setAlwaysMatch( true ); + scheduler->setAutoDestruct( true ); + + int contentX, contentY; + HeaderItem *nextItem = mHeaders->prepareMove( &contentX, &contentY ); + QPtrList<KMMsgBase> msgList = *mHeaders->selectedMsgs(true); + mHeaders->finalizeMove( nextItem, contentX, contentY ); + + for (KMMsgBase *msg = msgList.first(); msg; msg = msgList.next()) + scheduler->execFilters( msg ); + } else { + KMCommand *filterCommand = + new KMFilterActionCommand( mMainWidget, + *mHeaders->selectedMsgs(), mFilter ); + filterCommand->start(); + int contentX, contentY; + HeaderItem *item = mHeaders->prepareMove( &contentX, &contentY ); + mHeaders->finalizeMove( item, contentX, contentY ); + } +} + +FolderShortcutCommand::FolderShortcutCommand( KMMainWidget *mainwidget, + KMFolder *folder ) + : mMainWidget( mainwidget ), mFolder( folder ), mAction( 0 ) +{ +} + + +FolderShortcutCommand::~FolderShortcutCommand() +{ + if ( mAction ) mAction->unplugAll(); + delete mAction; +} + +void FolderShortcutCommand::start() +{ + mMainWidget->slotSelectFolder( mFolder ); +} + +void FolderShortcutCommand::setAction( KAction* action ) +{ + mAction = action; +} + +KMMailingListFilterCommand::KMMailingListFilterCommand( QWidget *parent, + KMMessage *msg ) + : KMCommand( parent, msg ) +{ +} + +KMCommand::Result KMMailingListFilterCommand::execute() +{ + QCString name; + QString value; + KMMessage *msg = retrievedMessage(); + if (!msg) + return Failed; + + if ( !MailingList::name( msg, name, value ).isEmpty() ) { + kmkernel->filterMgr()->createFilter( name, value ); + return OK; + } + else + return Failed; +} + + +void KMMenuCommand::folderToPopupMenu(bool move, + QObject *receiver, KMMenuToFolder *aMenuToFolder, QPopupMenu *menu ) +{ + while ( menu->count() ) + { + QPopupMenu *popup = menu->findItem( menu->idAt( 0 ) )->popup(); + if (popup) + delete popup; + else + menu->removeItemAt( 0 ); + } + + if (!kmkernel->imapFolderMgr()->dir().first() && + !kmkernel->dimapFolderMgr()->dir().first()) + { // only local folders + makeFolderMenu( &kmkernel->folderMgr()->dir(), move, + receiver, aMenuToFolder, menu ); + } else { + // operate on top-level items + QPopupMenu* subMenu = new QPopupMenu(menu); + makeFolderMenu( &kmkernel->folderMgr()->dir(), + move, receiver, aMenuToFolder, subMenu ); + menu->insertItem( i18n( "Local Folders" ), subMenu ); + KMFolderDir* fdir = &kmkernel->imapFolderMgr()->dir(); + for (KMFolderNode *node = fdir->first(); node; node = fdir->next()) { + if (node->isDir()) + continue; + subMenu = new QPopupMenu(menu); + makeFolderMenu( node, move, receiver, aMenuToFolder, subMenu ); + menu->insertItem( node->label(), subMenu ); + } + fdir = &kmkernel->dimapFolderMgr()->dir(); + for (KMFolderNode *node = fdir->first(); node; node = fdir->next()) { + if (node->isDir()) + continue; + subMenu = new QPopupMenu(menu); + makeFolderMenu( node, move, receiver, aMenuToFolder, subMenu ); + menu->insertItem( node->label(), subMenu ); + } + } +} + +void KMMenuCommand::makeFolderMenu(KMFolderNode* node, bool move, + QObject *receiver, KMMenuToFolder *aMenuToFolder, QPopupMenu *menu ) +{ + // connect the signals + if (move) + { + disconnect(menu, SIGNAL(activated(int)), receiver, + SLOT(moveSelectedToFolder(int))); + connect(menu, SIGNAL(activated(int)), receiver, + SLOT(moveSelectedToFolder(int))); + } else { + disconnect(menu, SIGNAL(activated(int)), receiver, + SLOT(copySelectedToFolder(int))); + connect(menu, SIGNAL(activated(int)), receiver, + SLOT(copySelectedToFolder(int))); + } + + KMFolder *folder = 0; + KMFolderDir *folderDir = 0; + if (node->isDir()) { + folderDir = static_cast<KMFolderDir*>(node); + } else { + folder = static_cast<KMFolder*>(node); + folderDir = folder->child(); + } + + if (folder && !folder->noContent()) + { + int menuId; + if (move) + menuId = menu->insertItem(i18n("Move to This Folder")); + else + menuId = menu->insertItem(i18n("Copy to This Folder")); + aMenuToFolder->insert( menuId, folder ); + menu->setItemEnabled( menuId, !folder->isReadOnly() ); + menu->insertSeparator(); + } + + if (!folderDir) + return; + + for (KMFolderNode *it = folderDir->first(); it; it = folderDir->next() ) { + if (it->isDir()) + continue; + KMFolder *child = static_cast<KMFolder*>(it); + QString label = child->label(); + label.replace("&","&&"); + if (child->child() && child->child()->first()) { + // descend + QPopupMenu *subMenu = new QPopupMenu(menu, "subMenu"); + makeFolderMenu( child, move, receiver, + aMenuToFolder, subMenu ); + menu->insertItem( label, subMenu ); + } else { + // insert an item + int menuId = menu->insertItem( label ); + aMenuToFolder->insert( menuId, child ); + menu->setItemEnabled( menuId, !child->isReadOnly() ); + } + } + return; +} + + +KMCopyCommand::KMCopyCommand( KMFolder* destFolder, + const QPtrList<KMMsgBase> &msgList ) +:mDestFolder( destFolder ), mMsgList( msgList ) +{ + setDeletesItself( true ); +} + +KMCopyCommand::KMCopyCommand( KMFolder* destFolder, KMMessage * msg ) + :mDestFolder( destFolder ) +{ + setDeletesItself( true ); + mMsgList.append( &msg->toMsgBase() ); +} + +KMCommand::Result KMCopyCommand::execute() +{ + KMMsgBase *msgBase; + KMMessage *msg, *newMsg; + int idx = -1; + bool isMessage; + QPtrList<KMMessage> list; + QPtrList<KMMessage> localList; + + if (mDestFolder && mDestFolder->open("kmcommand") != 0) + { + deleteLater(); + return Failed; + } + + setEmitsCompletedItself( true ); + KCursorSaver busy(KBusyPtr::busy()); + + for (msgBase = mMsgList.first(); msgBase; msgBase = mMsgList.next() ) + { + KMFolder *srcFolder = msgBase->parent(); + if (( isMessage = msgBase->isMessage() )) + { + msg = static_cast<KMMessage*>(msgBase); + } else { + idx = srcFolder->find(msgBase); + assert(idx != -1); + msg = srcFolder->getMsg(idx); + // corrupt IMAP cache, see FolderStorage::getMsg() + if ( msg == 0 ) { + KMessageBox::error( parentWidget(), i18n("Corrupt IMAP cache detected in folder %1. " + "Copying of messages aborted.").arg( srcFolder->prettyURL() ) ); + deleteLater(); + return Failed; + } + } + + if (srcFolder && mDestFolder && + (srcFolder->folderType()== KMFolderTypeImap) && + (mDestFolder->folderType() == KMFolderTypeImap) && + (static_cast<KMFolderImap*>(srcFolder->storage())->account() == + static_cast<KMFolderImap*>(mDestFolder->storage())->account())) + { + // imap => imap with same account + list.append(msg); + } else { + newMsg = new KMMessage( new DwMessage( *msg->asDwMessage() ) ); + newMsg->setComplete(msg->isComplete()); + // make sure the attachment state is only calculated when it's complete + if (!newMsg->isComplete()) + newMsg->setReadyToShow(false); + newMsg->setStatus(msg->status()); + + if (srcFolder && !newMsg->isComplete()) + { + // imap => others + newMsg->setParent(msg->parent()); + FolderJob *job = srcFolder->createJob(newMsg); + job->setCancellable( false ); + mPendingJobs << job; + connect(job, SIGNAL(messageRetrieved(KMMessage*)), + mDestFolder, SLOT(reallyAddCopyOfMsg(KMMessage*))); + connect( job, SIGNAL(result(KMail::FolderJob*)), + this, SLOT(slotJobFinished(KMail::FolderJob*)) ); + job->start(); + } else { + // local => others + localList.append(newMsg); + } + } + + if (srcFolder && !isMessage && list.isEmpty()) + { + assert(idx != -1); + srcFolder->unGetMsg( idx ); + } + + } // end for + + bool deleteNow = false; + if (!localList.isEmpty()) + { + QValueList<int> index; + mDestFolder->addMsg( localList, index ); + for ( QValueListIterator<int> it = index.begin(); it != index.end(); ++it ) { + mDestFolder->unGetMsg( *it ); + } + if ( mDestFolder->folderType() == KMFolderTypeImap ) { + if ( mPendingJobs.isEmpty() ) { + // wait for the end of the copy before closing the folder + KMFolderImap *imapDestFolder = static_cast<KMFolderImap*>(mDestFolder->storage()); + connect( imapDestFolder, SIGNAL( folderComplete( KMFolderImap*, bool ) ), + this, SLOT( slotFolderComplete( KMFolderImap*, bool ) ) ); + } + } else { + deleteNow = list.isEmpty() && mPendingJobs.isEmpty(); // we're done if there are no other mails we need to fetch + } + } + +//TODO: Get rid of the other cases just use this one for all types of folder +//TODO: requires adding copyMsg and getFolder methods to KMFolder.h + if (!list.isEmpty()) + { + // copy the message(s); note: the list is empty afterwards! + KMFolderImap *imapDestFolder = static_cast<KMFolderImap*>(mDestFolder->storage()); + connect( imapDestFolder, SIGNAL( folderComplete( KMFolderImap*, bool ) ), + this, SLOT( slotFolderComplete( KMFolderImap*, bool ) ) ); + imapDestFolder->copyMsg(list); + imapDestFolder->getFolder(); + } + + // only close the folder and delete the job if we're done + // otherwise this is done in slotMsgAdded or slotFolderComplete + if ( deleteNow ) + { + mDestFolder->close("kmcommand"); + setResult( OK ); + emit completed( this ); + deleteLater(); + } + + return OK; +} + +void KMCopyCommand::slotJobFinished(KMail::FolderJob * job) +{ + mPendingJobs.remove( job ); + if ( job->error() ) { + kdDebug(5006) << k_funcinfo << "folder job failed: " << job->error() << endl; + // kill all pending jobs + for ( QValueList<KMail::FolderJob*>::Iterator it = mPendingJobs.begin(); it != mPendingJobs.end(); ++it ) { + disconnect( (*it), SIGNAL(result(KMail::FolderJob*)), + this, SLOT(slotJobFinished(KMail::FolderJob*)) ); + (*it)->kill(); + } + mPendingJobs.clear(); + setResult( Failed ); + } + + if ( mPendingJobs.isEmpty() ) + { + mDestFolder->close("kmcommand"); + emit completed( this ); + deleteLater(); + } +} + +void KMCopyCommand::slotFolderComplete( KMFolderImap*, bool success ) +{ + kdDebug(5006) << k_funcinfo << success << endl; + if ( !success ) + setResult( Failed ); + mDestFolder->close( "kmcommand" ); + emit completed( this ); + deleteLater(); +} + + +KMMoveCommand::KMMoveCommand( KMFolder* destFolder, + const QPtrList<KMMsgBase> &msgList) + : mDestFolder( destFolder ), mProgressItem( 0 ) +{ + QPtrList<KMMsgBase> tmp = msgList; + for ( KMMsgBase *msgBase = tmp.first(); msgBase; msgBase = tmp.next() ) + mSerNumList.append( msgBase->getMsgSerNum() ); +} + +KMMoveCommand::KMMoveCommand( KMFolder* destFolder, + KMMessage *msg ) + : mDestFolder( destFolder ), mProgressItem( 0 ) +{ + mSerNumList.append( msg->getMsgSerNum() ); +} + +KMMoveCommand::KMMoveCommand( KMFolder* destFolder, + KMMsgBase *msgBase ) + : mDestFolder( destFolder ), mProgressItem( 0 ) +{ + mSerNumList.append( msgBase->getMsgSerNum() ); +} + +KMMoveCommand::KMMoveCommand( Q_UINT32 ) + : mProgressItem( 0 ) +{ +} + +KMCommand::Result KMMoveCommand::execute() +{ + setEmitsCompletedItself( true ); + setDeletesItself( true ); + typedef QMap< KMFolder*, QPtrList<KMMessage>* > FolderToMessageListMap; + FolderToMessageListMap folderDeleteList; + + if (mDestFolder && mDestFolder->open("kmcommand") != 0) { + completeMove( Failed ); + return Failed; + } + KCursorSaver busy(KBusyPtr::busy()); + + // TODO set SSL state according to source and destfolder connection? + Q_ASSERT( !mProgressItem ); + mProgressItem = + ProgressManager::createProgressItem ( + "move"+ProgressManager::getUniqueID(), + mDestFolder ? i18n( "Moving messages" ) : i18n( "Deleting messages" ) ); + connect( mProgressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ), + this, SLOT( slotMoveCanceled() ) ); + + KMMessage *msg; + int rc = 0; + int index; + QPtrList<KMMessage> list; + int undoId = -1; + mCompleteWithAddedMsg = false; + + if (mDestFolder) { + connect (mDestFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)), + this, SLOT(slotMsgAddedToDestFolder(KMFolder*, Q_UINT32))); + mLostBoys = mSerNumList; + } + mProgressItem->setTotalItems( mSerNumList.count() ); + + for ( QValueList<Q_UINT32>::ConstIterator it = mSerNumList.constBegin(); it != mSerNumList.constEnd(); ++it ) { + KMFolder *srcFolder; + int idx = -1; + KMMsgDict::instance()->getLocation( *it, &srcFolder, &idx ); + if (srcFolder == mDestFolder) + continue; + assert(idx != -1); + srcFolder->open( "kmmovecommand" ); + mOpenedFolders.append( srcFolder ); + msg = srcFolder->getMsg(idx); + if ( !msg ) { + kdDebug(5006) << k_funcinfo << "No message found for serial number " << *it << endl; + continue; + } + bool undo = msg->enableUndo(); + + if ( msg && msg->transferInProgress() && + srcFolder->folderType() == KMFolderTypeImap ) + { + // cancel the download + msg->setTransferInProgress( false, true ); + static_cast<KMFolderImap*>(srcFolder->storage())->ignoreJobsForMessage( msg ); + } + + if (mDestFolder) { + if (mDestFolder->folderType() == KMFolderTypeImap) { + /* If we are moving to an imap folder, connect to it's completed + * signal so we notice when all the mails should have showed up in it + * but haven't for some reason. */ + KMFolderImap *imapFolder = static_cast<KMFolderImap*> ( mDestFolder->storage() ); + disconnect (imapFolder, SIGNAL(folderComplete( KMFolderImap*, bool )), + this, SLOT(slotImapFolderCompleted( KMFolderImap*, bool ))); + + connect (imapFolder, SIGNAL(folderComplete( KMFolderImap*, bool )), + this, SLOT(slotImapFolderCompleted( KMFolderImap*, bool ))); + list.append(msg); + } else { + // We are moving to a local folder. + if ( srcFolder->folderType() == KMFolderTypeImap ) + { + // do not complete here but wait until all messages are transferred + mCompleteWithAddedMsg = true; + } + rc = mDestFolder->moveMsg(msg, &index); + if (rc == 0 && index != -1) { + KMMsgBase *mb = mDestFolder->unGetMsg( mDestFolder->count() - 1 ); + if (undo && mb) + { + if ( undoId == -1 ) + undoId = kmkernel->undoStack()->newUndoAction( srcFolder, mDestFolder ); + kmkernel->undoStack()->addMsgToAction( undoId, mb->getMsgSerNum() ); + } + } else if (rc != 0) { + // Something went wrong. Stop processing here, it is likely that the + // other moves would fail as well. + completeMove( Failed ); + return Failed; + } + } + } else { + // really delete messages that are already in the trash folder or if + // we are really, really deleting, not just moving to trash + if (srcFolder->folderType() == KMFolderTypeImap) { + if (!folderDeleteList[srcFolder]) + folderDeleteList[srcFolder] = new QPtrList<KMMessage>; + folderDeleteList[srcFolder]->append( msg ); + } else { + srcFolder->removeMsg(idx); + delete msg; + } + } + } + if (!list.isEmpty() && mDestFolder) { + // will be completed with folderComplete signal + mDestFolder->moveMsg(list, &index); + } else { + FolderToMessageListMap::Iterator it; + for ( it = folderDeleteList.begin(); it != folderDeleteList.end(); ++it ) { + it.key()->removeMsg(*it.data()); + delete it.data(); + } + if ( !mCompleteWithAddedMsg ) { + // imap folders will be completed in slotMsgAddedToDestFolder + completeMove( OK ); + } + } + + return OK; +} + +void KMMoveCommand::slotImapFolderCompleted(KMFolderImap* imapFolder, bool success) +{ + disconnect (imapFolder, SIGNAL(folderComplete( KMFolderImap*, bool )), + this, SLOT(slotImapFolderCompleted( KMFolderImap*, bool ))); + if ( success ) { + // the folder was checked successfully but we were still called, so check + // if we are still waiting for messages to show up. If so, uidValidity + // changed, or something else went wrong. Clean up. + + /* Unfortunately older UW imap servers change uid validity for each put job. + * Yes, it is really that broken. *sigh* So we cannot report error here, I guess. */ + if ( !mLostBoys.isEmpty() ) { + kdDebug(5006) << "### Not all moved messages reported back that they were " << endl + << "### added to the target folder. Did uidValidity change? " << endl; + } + completeMove( OK ); + } else { + // Should we inform the user here or leave that to the caller? + completeMove( Failed ); + } +} + +void KMMoveCommand::slotMsgAddedToDestFolder(KMFolder *folder, Q_UINT32 serNum) +{ + if ( folder != mDestFolder || mLostBoys.find( serNum ) == mLostBoys.end() ) { + //kdDebug(5006) << "KMMoveCommand::msgAddedToDestFolder different " + // "folder or invalid serial number." << endl; + return; + } + mLostBoys.remove(serNum); + if ( mLostBoys.isEmpty() ) { + // we are done. All messages transferred to the host succesfully + disconnect (mDestFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)), + this, SLOT(slotMsgAddedToDestFolder(KMFolder*, Q_UINT32))); + if (mDestFolder && mDestFolder->folderType() != KMFolderTypeImap) { + mDestFolder->sync(); + } + if ( mCompleteWithAddedMsg ) { + completeMove( OK ); + } + } else { + if ( mProgressItem ) { + mProgressItem->incCompletedItems(); + mProgressItem->updateProgress(); + } + } +} + +void KMMoveCommand::completeMove( Result result ) +{ + if ( mDestFolder ) + mDestFolder->close("kmcommand"); + while ( !mOpenedFolders.empty() ) { + KMFolder *folder = mOpenedFolders.back(); + mOpenedFolders.pop_back(); + folder->close("kmcommand"); + } + if ( mProgressItem ) { + mProgressItem->setComplete(); + mProgressItem = 0; + } + setResult( result ); + emit completed( this ); + deleteLater(); +} + +void KMMoveCommand::slotMoveCanceled() +{ + completeMove( Canceled ); +} + +// srcFolder doesn't make much sense for searchFolders +KMDeleteMsgCommand::KMDeleteMsgCommand( KMFolder* srcFolder, + const QPtrList<KMMsgBase> &msgList ) +:KMMoveCommand( findTrashFolder( srcFolder ), msgList) +{ + srcFolder->open("kmcommand"); + mOpenedFolders.push_back( srcFolder ); +} + +KMDeleteMsgCommand::KMDeleteMsgCommand( KMFolder* srcFolder, KMMessage * msg ) +:KMMoveCommand( findTrashFolder( srcFolder ), msg) +{ + srcFolder->open("kmcommand"); + mOpenedFolders.push_back( srcFolder ); +} + +KMDeleteMsgCommand::KMDeleteMsgCommand( Q_UINT32 sernum ) +:KMMoveCommand( sernum ) +{ + KMFolder *srcFolder = 0; + int idx; + KMMsgDict::instance()->getLocation( sernum, &srcFolder, &idx ); + if ( srcFolder ) { + KMMsgBase *msg = srcFolder->getMsgBase( idx ); + srcFolder->open("kmcommand"); + mOpenedFolders.push_back( srcFolder ); + addMsg( msg ); + } + setDestFolder( findTrashFolder( srcFolder ) ); +} + +KMFolder * KMDeleteMsgCommand::findTrashFolder( KMFolder * folder ) +{ + KMFolder* trash = folder->trashFolder(); + if( !trash ) + trash = kmkernel->trashFolder(); + if( trash != folder ) + return trash; + return 0; +} + + +KMUrlClickedCommand::KMUrlClickedCommand( const KURL &url, uint identity, + KMReaderWin *readerWin, bool htmlPref, KMMainWidget *mainWidget ) + :mUrl( url ), mIdentity( identity ), mReaderWin( readerWin ), + mHtmlPref( htmlPref ), mMainWidget( mainWidget ) +{ +} + +KMCommand::Result KMUrlClickedCommand::execute() +{ + KMMessage* msg; + + if (mUrl.protocol() == "mailto") + { + msg = new KMMessage; + msg->initHeader(mIdentity); + msg->setCharset("utf-8"); + msg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) ); + QString query=mUrl.query(); + while (!query.isEmpty()) { + QString queryPart; + int secondQuery = query.find('?',1); + if (secondQuery != -1) + queryPart = query.left(secondQuery); + else + queryPart = query; + query = query.mid(queryPart.length()); + + if (queryPart.left(9) == "?subject=") + msg->setSubject( KURL::decode_string(queryPart.mid(9)) ); + else if (queryPart.left(6) == "?body=") + // It is correct to convert to latin1() as URL should not contain + // anything except ascii. + msg->setBody( KURL::decode_string(queryPart.mid(6)).latin1() ); + else if (queryPart.left(4) == "?cc=") + msg->setCc( KURL::decode_string(queryPart.mid(4)) ); + } + + KMail::Composer * win = KMail::makeComposer( msg, mIdentity ); + win->setCharset("", true); + win->show(); + } + else if ( mUrl.protocol() == "im" ) + { + kmkernel->imProxy()->chatWithContact( mUrl.path() ); + } + else if ((mUrl.protocol() == "http") || (mUrl.protocol() == "https") || + (mUrl.protocol() == "ftp") || (mUrl.protocol() == "file") || + (mUrl.protocol() == "ftps") || (mUrl.protocol() == "sftp" ) || + (mUrl.protocol() == "help") || (mUrl.protocol() == "vnc") || + (mUrl.protocol() == "smb") || (mUrl.protocol() == "fish") || + (mUrl.protocol() == "news")) + { + KPIM::BroadcastStatus::instance()->setStatusMsg( i18n("Opening URL...")); + KMimeType::Ptr mime = KMimeType::findByURL( mUrl ); + if (mime->name() == "application/x-desktop" || + mime->name() == "application/x-executable" || + mime->name() == "application/x-msdos-program" || + mime->name() == "application/x-shellscript" ) + { + if (KMessageBox::warningYesNo( 0, i18n( "<qt>Do you really want to execute <b>%1</b>?</qt>" ) + .arg( mUrl.prettyURL() ), QString::null, i18n("Execute"), KStdGuiItem::cancel() ) != KMessageBox::Yes) + return Canceled; + } + KRun * runner = new KRun( mUrl ); + runner->setRunExecutables( false ); + } + else + return Failed; + + return OK; +} + +KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, KMMessage *msg ) + : KMCommand( parent, msg ), mImplicitAttachments( true ), mEncoded( false ) +{ +} + +KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, const QPtrList<KMMsgBase>& msgs ) + : KMCommand( parent, msgs ), mImplicitAttachments( true ), mEncoded( false ) +{ +} + +KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, QPtrList<partNode>& attachments, + KMMessage *msg, bool encoded ) + : KMCommand( parent ), mImplicitAttachments( false ), mEncoded( encoded ) +{ + for ( QPtrListIterator<partNode> it( attachments ); it.current(); ++it ) { + mAttachmentMap.insert( it.current(), msg ); + } +} + +KMCommand::Result KMSaveAttachmentsCommand::execute() +{ + setEmitsCompletedItself( true ); + if ( mImplicitAttachments ) { + QPtrList<KMMessage> msgList = retrievedMsgs(); + KMMessage *msg; + for ( QPtrListIterator<KMMessage> itr( msgList ); + ( msg = itr.current() ); + ++itr ) { + partNode *rootNode = partNode::fromMessage( msg ); + for ( partNode *child = rootNode; child; + child = child->firstChild() ) { + for ( partNode *node = child; node; node = node->nextSibling() ) { + if ( node->type() != DwMime::kTypeMultipart ) + mAttachmentMap.insert( node, msg ); + } + } + } + } + setDeletesItself( true ); + // load all parts + KMLoadPartsCommand *command = new KMLoadPartsCommand( mAttachmentMap ); + connect( command, SIGNAL( partsRetrieved() ), + this, SLOT( slotSaveAll() ) ); + command->start(); + + return OK; +} + +void KMSaveAttachmentsCommand::slotSaveAll() +{ + // now that all message parts have been retrieved, remove all parts which + // don't represent an attachment if they were not explicitely passed in the + // c'tor + if ( mImplicitAttachments ) { + for ( PartNodeMessageMap::iterator it = mAttachmentMap.begin(); + it != mAttachmentMap.end(); ) { + // only body parts which have a filename or a name parameter (except for + // the root node for which name is set to the message's subject) are + // considered attachments + if ( it.key()->msgPart().fileName().stripWhiteSpace().isEmpty() && + ( it.key()->msgPart().name().stripWhiteSpace().isEmpty() || + !it.key()->parentNode() ) ) { + PartNodeMessageMap::iterator delIt = it; + ++it; + mAttachmentMap.remove( delIt ); + } + else + ++it; + } + if ( mAttachmentMap.isEmpty() ) { + KMessageBox::information( 0, i18n("Found no attachments to save.") ); + setResult( OK ); // The user has already been informed. + emit completed( this ); + deleteLater(); + return; + } + } + + KURL url, dirUrl; + if ( mAttachmentMap.count() > 1 ) { + // get the dir + dirUrl = KDirSelectDialog::selectDirectory( QString::null, false, + parentWidget(), + i18n("Save Attachments To") ); + if ( !dirUrl.isValid() ) { + setResult( Canceled ); + emit completed( this ); + deleteLater(); + return; + } + + // we may not get a slash-terminated url out of KDirSelectDialog + dirUrl.adjustPath( 1 ); + } + else { + // only one item, get the desired filename + partNode *node = mAttachmentMap.begin().key(); + // replace all ':' with '_' because ':' isn't allowed on FAT volumes + QString s = + node->msgPart().fileName().stripWhiteSpace().replace( ':', '_' ); + if ( s.isEmpty() ) + s = node->msgPart().name().stripWhiteSpace().replace( ':', '_' ); + if ( s.isEmpty() ) + s = i18n("filename for an unnamed attachment", "attachment.1"); + url = KFileDialog::getSaveURL( s, QString::null, parentWidget(), + QString::null ); + if ( url.isEmpty() ) { + setResult( Canceled ); + emit completed( this ); + deleteLater(); + return; + } + } + + QMap< QString, int > renameNumbering; + + Result globalResult = OK; + int unnamedAtmCount = 0; + for ( PartNodeMessageMap::const_iterator it = mAttachmentMap.begin(); + it != mAttachmentMap.end(); + ++it ) { + KURL curUrl; + if ( !dirUrl.isEmpty() ) { + curUrl = dirUrl; + QString s = + it.key()->msgPart().fileName().stripWhiteSpace().replace( ':', '_' ); + if ( s.isEmpty() ) + s = it.key()->msgPart().name().stripWhiteSpace().replace( ':', '_' ); + if ( s.isEmpty() ) { + ++unnamedAtmCount; + s = i18n("filename for the %1-th unnamed attachment", + "attachment.%1") + .arg( unnamedAtmCount ); + } + curUrl.setFileName( s ); + } else { + curUrl = url; + } + + if ( !curUrl.isEmpty() ) { + + // Rename the file if we have already saved one with the same name: + // try appending a number before extension (e.g. "pic.jpg" => "pic_2.jpg") + QString origFile = curUrl.fileName(); + QString file = origFile; + + while ( renameNumbering.contains(file) ) { + file = origFile; + int num = renameNumbering[file] + 1; + int dotIdx = file.findRev('.'); + file = file.insert( (dotIdx>=0) ? dotIdx : file.length(), QString("_") + QString::number(num) ); + } + curUrl.setFileName(file); + + // Increment the counter for both the old and the new filename + if ( !renameNumbering.contains(origFile)) + renameNumbering[origFile] = 1; + else + renameNumbering[origFile]++; + + if ( file != origFile ) { + if ( !renameNumbering.contains(file)) + renameNumbering[file] = 1; + else + renameNumbering[file]++; + } + + + if ( KIO::NetAccess::exists( curUrl, false, parentWidget() ) ) { + if ( KMessageBox::warningContinueCancel( parentWidget(), + i18n( "A file named %1 already exists. Do you want to overwrite it?" ) + .arg( curUrl.fileName() ), + i18n( "File Already Exists" ), i18n("&Overwrite") ) == KMessageBox::Cancel) { + continue; + } + } + // save + const Result result = saveItem( it.key(), curUrl ); + if ( result != OK ) + globalResult = result; + } + } + setResult( globalResult ); + emit completed( this ); + deleteLater(); +} + +KMCommand::Result KMSaveAttachmentsCommand::saveItem( partNode *node, + const KURL& url ) +{ + bool bSaveEncrypted = false; + bool bEncryptedParts = node->encryptionState() != KMMsgNotEncrypted; + if( bEncryptedParts ) + if( KMessageBox::questionYesNo( parentWidget(), + i18n( "The part %1 of the message is encrypted. Do you want to keep the encryption when saving?" ). + arg( url.fileName() ), + i18n( "KMail Question" ), i18n("Keep Encryption"), i18n("Do Not Keep") ) == + KMessageBox::Yes ) + bSaveEncrypted = true; + + bool bSaveWithSig = true; + if( node->signatureState() != KMMsgNotSigned ) + if( KMessageBox::questionYesNo( parentWidget(), + i18n( "The part %1 of the message is signed. Do you want to keep the signature when saving?" ). + arg( url.fileName() ), + i18n( "KMail Question" ), i18n("Keep Signature"), i18n("Do Not Keep") ) != + KMessageBox::Yes ) + bSaveWithSig = false; + + QByteArray data; + if ( mEncoded ) + { + // This does not decode the Message Content-Transfer-Encoding + // but saves the _original_ content of the message part + data = KMail::Util::ByteArray( node->msgPart().dwBody() ); + } + else + { + if( bSaveEncrypted || !bEncryptedParts) { + partNode *dataNode = node; + QCString rawReplyString; + bool gotRawReplyString = false; + if( !bSaveWithSig ) { + if( DwMime::kTypeMultipart == node->type() && + DwMime::kSubtypeSigned == node->subType() ){ + // carefully look for the part that is *not* the signature part: + if( node->findType( DwMime::kTypeApplication, + DwMime::kSubtypePgpSignature, + true, false ) ){ + dataNode = node->findTypeNot( DwMime::kTypeApplication, + DwMime::kSubtypePgpSignature, + true, false ); + }else if( node->findType( DwMime::kTypeApplication, + DwMime::kSubtypePkcs7Mime, + true, false ) ){ + dataNode = node->findTypeNot( DwMime::kTypeApplication, + DwMime::kSubtypePkcs7Mime, + true, false ); + }else{ + dataNode = node->findTypeNot( DwMime::kTypeMultipart, + DwMime::kSubtypeUnknown, + true, false ); + } + }else{ + ObjectTreeParser otp( 0, 0, false, false, false ); + + // process this node and all it's siblings and descendants + dataNode->setProcessed( false, true ); + otp.parseObjectTree( dataNode ); + + rawReplyString = otp.rawReplyString(); + gotRawReplyString = true; + } + } + QByteArray cstr = gotRawReplyString + ? rawReplyString + : dataNode->msgPart().bodyDecodedBinary(); + data = cstr; + size_t size = cstr.size(); + if ( dataNode->msgPart().type() == DwMime::kTypeText ) { + // convert CRLF to LF before writing text attachments to disk + size = KMail::Util::crlf2lf( cstr.data(), size ); + } + data.resize( size ); + } + } + QDataStream ds; + QFile file; + KTempFile tf; + tf.setAutoDelete( true ); + if ( url.isLocalFile() ) + { + // save directly + file.setName( url.path() ); + if ( !file.open( IO_WriteOnly ) ) + { + KMessageBox::error( parentWidget(), + i18n( "%2 is detailed error description", + "Could not write the file %1:\n%2" ) + .arg( file.name() ) + .arg( QString::fromLocal8Bit( strerror( errno ) ) ), + i18n( "KMail Error" ) ); + return Failed; + } + + // #79685 by default use the umask the user defined, but let it be configurable + if ( GlobalSettings::self()->disregardUmask() ) + fchmod( file.handle(), S_IRUSR | S_IWUSR ); + + ds.setDevice( &file ); + } else + { + // tmp file for upload + ds.setDevice( tf.file() ); + } + + ds.writeRawBytes( data.data(), data.size() ); + if ( !url.isLocalFile() ) + { + tf.close(); + if ( !KIO::NetAccess::upload( tf.name(), url, parentWidget() ) ) + { + KMessageBox::error( parentWidget(), + i18n( "Could not write the file %1." ) + .arg( url.path() ), + i18n( "KMail Error" ) ); + return Failed; + } + } else + file.close(); + return OK; +} + +KMLoadPartsCommand::KMLoadPartsCommand( QPtrList<partNode>& parts, KMMessage *msg ) + : mNeedsRetrieval( 0 ) +{ + for ( QPtrListIterator<partNode> it( parts ); it.current(); ++it ) { + mPartMap.insert( it.current(), msg ); + } +} + +KMLoadPartsCommand::KMLoadPartsCommand( partNode *node, KMMessage *msg ) + : mNeedsRetrieval( 0 ) +{ + mPartMap.insert( node, msg ); +} + +KMLoadPartsCommand::KMLoadPartsCommand( PartNodeMessageMap& partMap ) + : mNeedsRetrieval( 0 ), mPartMap( partMap ) +{ +} + +void KMLoadPartsCommand::slotStart() +{ + for ( PartNodeMessageMap::const_iterator it = mPartMap.begin(); + it != mPartMap.end(); + ++it ) { + if ( !it.key()->msgPart().isComplete() && + !it.key()->msgPart().partSpecifier().isEmpty() ) { + // incomplete part, so retrieve it first + ++mNeedsRetrieval; + KMFolder* curFolder = it.data()->parent(); + if ( curFolder ) { + FolderJob *job = + curFolder->createJob( it.data(), FolderJob::tGetMessage, + 0, it.key()->msgPart().partSpecifier() ); + job->setCancellable( false ); + connect( job, SIGNAL(messageUpdated(KMMessage*, QString)), + this, SLOT(slotPartRetrieved(KMMessage*, QString)) ); + job->start(); + } else + kdWarning(5006) << "KMLoadPartsCommand - msg has no parent" << endl; + } + } + if ( mNeedsRetrieval == 0 ) + execute(); +} + +void KMLoadPartsCommand::slotPartRetrieved( KMMessage *msg, + QString partSpecifier ) +{ + DwBodyPart *part = + msg->findDwBodyPart( msg->getFirstDwBodyPart(), partSpecifier ); + if ( part ) { + // update the DwBodyPart in the partNode + for ( PartNodeMessageMap::const_iterator it = mPartMap.begin(); + it != mPartMap.end(); + ++it ) { + if ( it.key()->dwPart()->partId() == part->partId() ) + it.key()->setDwPart( part ); + } + } else + kdWarning(5006) << "KMLoadPartsCommand::slotPartRetrieved - could not find bodypart!" << endl; + --mNeedsRetrieval; + if ( mNeedsRetrieval == 0 ) + execute(); +} + +KMCommand::Result KMLoadPartsCommand::execute() +{ + emit partsRetrieved(); + setResult( OK ); + emit completed( this ); + deleteLater(); + return OK; +} + +KMResendMessageCommand::KMResendMessageCommand( QWidget *parent, + KMMessage *msg ) + :KMCommand( parent, msg ) +{ +} + +KMCommand::Result KMResendMessageCommand::execute() +{ + KMMessage *msg = retrievedMessage(); + if ( !msg || !msg->codec() ) { + return Failed; + } + KMMessage *newMsg = new KMMessage(*msg); + + QStringList whiteList; + whiteList << "To" << "Cc" << "Bcc" << "Subject"; + newMsg->sanitizeHeaders( whiteList ); + + newMsg->setCharset(msg->codec()->mimeName()); + newMsg->setParent( 0 ); + + // make sure we have an identity set, default, if necessary + newMsg->setHeaderField("X-KMail-Identity", QString::number( newMsg->identityUoid() )); + newMsg->applyIdentity( newMsg->identityUoid() ); + + KMail::Composer * win = KMail::makeComposer(); + win->setMsg(newMsg, false, true); + win->show(); + + return OK; +} + +KMMailingListCommand::KMMailingListCommand( QWidget *parent, KMFolder *folder ) + : KMCommand( parent ), mFolder( folder ) +{ +} + +KMCommand::Result KMMailingListCommand::execute() +{ + KURL::List lst = urls(); + QString handler = ( mFolder->mailingList().handler() == MailingList::KMail ) + ? "mailto" : "https"; + + KMCommand *command = 0; + for ( KURL::List::Iterator itr = lst.begin(); itr != lst.end(); ++itr ) { + if ( handler == (*itr).protocol() ) { + command = new KMUrlClickedCommand( *itr, mFolder->identity(), 0, false ); + } + } + if ( !command && !lst.empty() ) { + command = + new KMUrlClickedCommand( lst.first(), mFolder->identity(), 0, false ); + } + if ( command ) { + connect( command, SIGNAL( completed( KMCommand * ) ), + this, SLOT( commandCompleted( KMCommand * ) ) ); + setDeletesItself( true ); + setEmitsCompletedItself( true ); + command->start(); + return OK; + } + return Failed; +} + +void KMMailingListCommand::commandCompleted( KMCommand *command ) +{ + setResult( command->result() ); + emit completed( this ); + deleteLater(); +} + +KMMailingListPostCommand::KMMailingListPostCommand( QWidget *parent, KMFolder *folder ) + : KMMailingListCommand( parent, folder ) +{ +} +KURL::List KMMailingListPostCommand::urls() const +{ + return mFolder->mailingList().postURLS(); +} + +KMMailingListSubscribeCommand::KMMailingListSubscribeCommand( QWidget *parent, KMFolder *folder ) + : KMMailingListCommand( parent, folder ) +{ +} +KURL::List KMMailingListSubscribeCommand::urls() const +{ + return mFolder->mailingList().subscribeURLS(); +} + +KMMailingListUnsubscribeCommand::KMMailingListUnsubscribeCommand( QWidget *parent, KMFolder *folder ) + : KMMailingListCommand( parent, folder ) +{ +} +KURL::List KMMailingListUnsubscribeCommand::urls() const +{ + return mFolder->mailingList().unsubscribeURLS(); +} + +KMMailingListArchivesCommand::KMMailingListArchivesCommand( QWidget *parent, KMFolder *folder ) + : KMMailingListCommand( parent, folder ) +{ +} +KURL::List KMMailingListArchivesCommand::urls() const +{ + return mFolder->mailingList().archiveURLS(); +} + +KMMailingListHelpCommand::KMMailingListHelpCommand( QWidget *parent, KMFolder *folder ) + : KMMailingListCommand( parent, folder ) +{ +} +KURL::List KMMailingListHelpCommand::urls() const +{ + return mFolder->mailingList().helpURLS(); +} + +KMIMChatCommand::KMIMChatCommand( const KURL &url, KMMessage *msg ) + :mUrl( url ), mMessage( msg ) +{ +} + +KMCommand::Result KMIMChatCommand::execute() +{ + kdDebug( 5006 ) << k_funcinfo << " URL is: " << mUrl << endl; + QString addr = KMMessage::decodeMailtoUrl( mUrl.path() ); + // find UID for mail address + KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true ); + KABC::AddresseeList addressees = addressBook->findByEmail( KPIM::getEmailAddress( addr ) ) ; + + // start chat + if( addressees.count() == 1 ) { + kmkernel->imProxy()->chatWithContact( addressees[0].uid() ); + return OK; + } + else + { + kdDebug( 5006 ) << "Didn't find exactly one addressee, couldn't tell who to chat to for that email address. Count = " << addressees.count() << endl; + + QString apology; + if ( addressees.isEmpty() ) + apology = i18n( "There is no Address Book entry for this email address. Add them to the Address Book and then add instant messaging addresses using your preferred messaging client." ); + else + { + apology = i18n( "More than one Address Book entry uses this email address:\n %1\n it is not possible to determine who to chat with." ); + QStringList nameList; + KABC::AddresseeList::const_iterator it = addressees.begin(); + KABC::AddresseeList::const_iterator end = addressees.end(); + for ( ; it != end; ++it ) + { + nameList.append( (*it).realName() ); + } + QString names = nameList.join( QString::fromLatin1( ",\n" ) ); + apology = apology.arg( names ); + } + + KMessageBox::sorry( parentWidget(), apology ); + return Failed; + } +} + +KMHandleAttachmentCommand::KMHandleAttachmentCommand( partNode* node, + KMMessage* msg, int atmId, const QString& atmName, + AttachmentAction action, KService::Ptr offer, QWidget* parent ) +: KMCommand( parent ), mNode( node ), mMsg( msg ), mAtmId( atmId ), mAtmName( atmName ), + mAction( action ), mOffer( offer ), mJob( 0 ) +{ +} + +void KMHandleAttachmentCommand::slotStart() +{ + if ( !mNode->msgPart().isComplete() ) + { + // load the part + kdDebug(5006) << "load part" << endl; + KMLoadPartsCommand *command = new KMLoadPartsCommand( mNode, mMsg ); + connect( command, SIGNAL( partsRetrieved() ), + this, SLOT( slotPartComplete() ) ); + command->start(); + } else + { + execute(); + } +} + +void KMHandleAttachmentCommand::slotPartComplete() +{ + execute(); +} + +KMCommand::Result KMHandleAttachmentCommand::execute() +{ + switch( mAction ) + { + case Open: + atmOpen(); + break; + case OpenWith: + atmOpenWith(); + break; + case View: + atmView(); + break; + case Save: + atmSave(); + break; + case Properties: + atmProperties(); + break; + case ChiasmusEncrypt: + atmEncryptWithChiasmus(); + return Undefined; + break; + default: + kdDebug(5006) << "unknown action " << mAction << endl; + break; + } + setResult( OK ); + emit completed( this ); + deleteLater(); + return OK; +} + +QString KMHandleAttachmentCommand::createAtmFileLink() const +{ + QFileInfo atmFileInfo( mAtmName ); + + if ( atmFileInfo.size() == 0 ) + { + kdDebug(5006) << k_funcinfo << "rewriting attachment" << endl; + // there is something wrong so write the file again + QByteArray data = mNode->msgPart().bodyDecodedBinary(); + size_t size = data.size(); + if ( mNode->msgPart().type() == DwMime::kTypeText && size) { + // convert CRLF to LF before writing text attachments to disk + size = KMail::Util::crlf2lf( data.data(), size ); + } + KPIM::kBytesToFile( data.data(), size, mAtmName, false, false, false ); + } + + KTempFile *linkFile = new KTempFile( locateLocal("tmp", atmFileInfo.fileName() +"_["), + "]."+ atmFileInfo.extension() ); + + linkFile->setAutoDelete(true); + QString linkName = linkFile->name(); + delete linkFile; + + if ( ::link(QFile::encodeName( mAtmName ), QFile::encodeName( linkName )) == 0 ) { + return linkName; // success + } + return QString::null; +} + +KService::Ptr KMHandleAttachmentCommand::getServiceOffer() +{ + KMMessagePart& msgPart = mNode->msgPart(); + const QString contentTypeStr = + ( msgPart.typeStr() + '/' + msgPart.subtypeStr() ).lower(); + + if ( contentTypeStr == "text/x-vcard" ) { + atmView(); + return 0; + } + // determine the MIME type of the attachment + KMimeType::Ptr mimetype; + // prefer the value of the Content-Type header + mimetype = KMimeType::mimeType( contentTypeStr ); + if ( mimetype->name() == "application/octet-stream" ) { + // consider the filename if Content-Type is application/octet-stream + mimetype = KMimeType::findByPath( mAtmName, 0, true /* no disk access */ ); + } + if ( ( mimetype->name() == "application/octet-stream" ) + && msgPart.isComplete() ) { + // consider the attachment's contents if neither the Content-Type header + // nor the filename give us a clue + mimetype = KMimeType::findByFileContent( mAtmName ); + } + return KServiceTypeProfile::preferredService( mimetype->name(), "Application" ); +} + +void KMHandleAttachmentCommand::atmOpen() +{ + if ( !mOffer ) + mOffer = getServiceOffer(); + if ( !mOffer ) { + kdDebug(5006) << k_funcinfo << "got no offer" << endl; + return; + } + + KURL::List lst; + KURL url; + bool autoDelete = true; + QString fname = createAtmFileLink(); + + if ( fname.isNull() ) { + autoDelete = false; + fname = mAtmName; + } + + url.setPath( fname ); + lst.append( url ); + if ( (KRun::run( *mOffer, lst, autoDelete ) <= 0) && autoDelete ) { + QFile::remove(url.path()); + } +} + +void KMHandleAttachmentCommand::atmOpenWith() +{ + KURL::List lst; + KURL url; + bool autoDelete = true; + QString fname = createAtmFileLink(); + + if ( fname.isNull() ) { + autoDelete = false; + fname = mAtmName; + } + + url.setPath( fname ); + lst.append( url ); + if ( (! KRun::displayOpenWithDialog(lst, autoDelete)) && autoDelete ) { + QFile::remove( url.path() ); + } +} + +void KMHandleAttachmentCommand::atmView() +{ + // we do not handle this ourself + emit showAttachment( mAtmId, mAtmName ); +} + +void KMHandleAttachmentCommand::atmSave() +{ + QPtrList<partNode> parts; + parts.append( mNode ); + // save, do not leave encoded + KMSaveAttachmentsCommand *command = + new KMSaveAttachmentsCommand( 0, parts, mMsg, false ); + command->start(); +} + +void KMHandleAttachmentCommand::atmProperties() +{ + KMMsgPartDialogCompat dlg( parentWidget() , 0, true ); + KMMessagePart& msgPart = mNode->msgPart(); + dlg.setMsgPart( &msgPart ); + dlg.exec(); +} + +void KMHandleAttachmentCommand::atmEncryptWithChiasmus() +{ + const partNode * node = mNode; + Q_ASSERT( node ); + if ( !node ) + return; + + // FIXME: better detection of mimetype?? + if ( !mAtmName.endsWith( ".xia", false ) ) + return; + + const Kleo::CryptoBackend::Protocol * chiasmus = + Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" ); + Q_ASSERT( chiasmus ); + if ( !chiasmus ) + return; + + const STD_NAMESPACE_PREFIX auto_ptr<Kleo::SpecialJob> listjob( chiasmus->specialJob( "x-obtain-keys", QMap<QString,QVariant>() ) ); + if ( !listjob.get() ) { + const QString msg = i18n( "Chiasmus backend does not offer the " + "\"x-obtain-keys\" function. Please report this bug." ); + KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) ); + return; + } + + if ( listjob->exec() ) { + listjob->showErrorDialog( parentWidget(), i18n( "Chiasmus Backend Error" ) ); + return; + } + + const QVariant result = listjob->property( "result" ); + if ( result.type() != QVariant::StringList ) { + const QString msg = i18n( "Unexpected return value from Chiasmus backend: " + "The \"x-obtain-keys\" function did not return a " + "string list. Please report this bug." ); + KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) ); + return; + } + + const QStringList keys = result.toStringList(); + if ( keys.empty() ) { + const QString msg = i18n( "No keys have been found. Please check that a " + "valid key path has been set in the Chiasmus " + "configuration." ); + KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) ); + return; + } + + ChiasmusKeySelector selectorDlg( parentWidget(), i18n( "Chiasmus Decryption Key Selection" ), + keys, GlobalSettings::chiasmusDecryptionKey(), + GlobalSettings::chiasmusDecryptionOptions() ); + if ( selectorDlg.exec() != QDialog::Accepted ) + return; + + GlobalSettings::setChiasmusDecryptionOptions( selectorDlg.options() ); + GlobalSettings::setChiasmusDecryptionKey( selectorDlg.key() ); + assert( !GlobalSettings::chiasmusDecryptionKey().isEmpty() ); + + Kleo::SpecialJob * job = chiasmus->specialJob( "x-decrypt", QMap<QString,QVariant>() ); + if ( !job ) { + const QString msg = i18n( "Chiasmus backend does not offer the " + "\"x-decrypt\" function. Please report this bug." ); + KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) ); + return; + } + + const QByteArray input = node->msgPart().bodyDecodedBinary(); + + if ( !job->setProperty( "key", GlobalSettings::chiasmusDecryptionKey() ) || + !job->setProperty( "options", GlobalSettings::chiasmusDecryptionOptions() ) || + !job->setProperty( "input", input ) ) { + const QString msg = i18n( "The \"x-decrypt\" function does not accept " + "the expected parameters. Please report this bug." ); + KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) ); + return; + } + + setDeletesItself( true ); // the job below is async, we have to cleanup ourselves + if ( job->start() ) { + job->showErrorDialog( parentWidget(), i18n( "Chiasmus Decryption Error" ) ); + return; + } + + mJob = job; + connect( job, SIGNAL(result(const GpgME::Error&,const QVariant&)), + this, SLOT(slotAtmDecryptWithChiasmusResult(const GpgME::Error&,const QVariant&)) ); +} + +static const QString chomp( const QString & base, const QString & suffix, bool cs ) { + return base.endsWith( suffix, cs ) ? base.left( base.length() - suffix.length() ) : base ; +} + +void KMHandleAttachmentCommand::slotAtmDecryptWithChiasmusResult( const GpgME::Error & err, const QVariant & result ) +{ + LaterDeleterWithCommandCompletion d( this ); + if ( !mJob ) + return; + Q_ASSERT( mJob == sender() ); + if ( mJob != sender() ) + return; + Kleo::Job * job = mJob; + mJob = 0; + if ( err.isCanceled() ) + return; + if ( err ) { + job->showErrorDialog( parentWidget(), i18n( "Chiasmus Decryption Error" ) ); + return; + } + + if ( result.type() != QVariant::ByteArray ) { + const QString msg = i18n( "Unexpected return value from Chiasmus backend: " + "The \"x-decrypt\" function did not return a " + "byte array. Please report this bug." ); + KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) ); + return; + } + + const KURL url = KFileDialog::getSaveURL( chomp( mAtmName, ".xia", false ), QString::null, parentWidget() ); + if ( url.isEmpty() ) + return; + + bool overwrite = KMail::Util::checkOverwrite( url, parentWidget() ); + if ( !overwrite ) + return; + + d.setDisabled( true ); // we got this far, don't delete yet + KIO::Job * uploadJob = KIO::storedPut( result.toByteArray(), url, -1, overwrite, false /*resume*/ ); + uploadJob->setWindow( parentWidget() ); + connect( uploadJob, SIGNAL(result(KIO::Job*)), + this, SLOT(slotAtmDecryptWithChiasmusUploadResult(KIO::Job*)) ); +} + +void KMHandleAttachmentCommand::slotAtmDecryptWithChiasmusUploadResult( KIO::Job * job ) +{ + if ( job->error() ) + job->showErrorDialog(); + LaterDeleterWithCommandCompletion d( this ); + d.setResult( OK ); +} + + +AttachmentModifyCommand::AttachmentModifyCommand(partNode * node, KMMessage * msg, QWidget * parent) : + KMCommand( parent, msg ), + mPartIndex( node->nodeId() ), + mSernum( 0 ) +{ +} + +AttachmentModifyCommand::~ AttachmentModifyCommand() +{ +} + +KMCommand::Result AttachmentModifyCommand::execute() +{ + KMMessage *msg = retrievedMessage(); + if ( !msg ) + return Failed; + mSernum = msg->getMsgSerNum(); + + mFolder = msg->parent(); + if ( !mFolder || !mFolder->storage() ) + return Failed; + + Result res = doAttachmentModify(); + if ( res != OK ) + return res; + + setEmitsCompletedItself( true ); + setDeletesItself( true ); + return OK; +} + +void AttachmentModifyCommand::storeChangedMessage(KMMessage * msg) +{ + if ( !mFolder || !mFolder->storage() ) { + kdWarning(5006) << k_funcinfo << "We lost the folder!" << endl; + setResult( Failed ); + emit completed( this ); + deleteLater(); + } + int res = mFolder->addMsg( msg ) != 0; + if ( mFolder->folderType() == KMFolderTypeImap ) { + KMFolderImap *f = static_cast<KMFolderImap*>( mFolder->storage() ); + connect( f, SIGNAL(folderComplete(KMFolderImap*,bool)), + SLOT(messageStoreResult(KMFolderImap*,bool)) ); + } else { + messageStoreResult( 0, res == 0 ); + } +} + +void AttachmentModifyCommand::messageStoreResult(KMFolderImap* folder, bool success ) +{ + Q_UNUSED( folder ); + if ( success ) { + KMCommand *delCmd = new KMDeleteMsgCommand( mSernum ); + connect( delCmd, SIGNAL(completed(KMCommand*)), SLOT(messageDeleteResult(KMCommand*)) ); + delCmd->start(); + return; + } + kdWarning(5006) << k_funcinfo << "Adding modified message failed." << endl; + setResult( Failed ); + emit completed( this ); + deleteLater(); +} + +void AttachmentModifyCommand::messageDeleteResult(KMCommand * cmd) +{ + setResult( cmd->result() ); + emit completed( this ); + deleteLater(); +} + +DwBodyPart * AttachmentModifyCommand::findPart(KMMessage* msg, int index) +{ + int accu = 0; + return findPartInternal( msg->getTopLevelPart(), index, accu ); +} + +DwBodyPart * AttachmentModifyCommand::findPartInternal(DwEntity * root, int index, int & accu) +{ + accu++; + if ( index < accu ) // should not happen + return 0; + DwBodyPart *current = dynamic_cast<DwBodyPart*>( root ); + if ( index == accu ) + return current; + DwBodyPart *rv = 0; + if ( root->Body().FirstBodyPart() ) + rv = findPartInternal( root->Body().FirstBodyPart(), index, accu ); + if ( !rv && current && current->Next() ) + rv = findPartInternal( current->Next(), index, accu ); + return rv; +} + + +KMDeleteAttachmentCommand::KMDeleteAttachmentCommand(partNode * node, KMMessage * msg, QWidget * parent) : + AttachmentModifyCommand( node, msg, parent ) +{ + kdDebug(5006) << k_funcinfo << endl; +} + +KMDeleteAttachmentCommand::~KMDeleteAttachmentCommand() +{ + kdDebug(5006) << k_funcinfo << endl; +} + +KMCommand::Result KMDeleteAttachmentCommand::doAttachmentModify() +{ + KMMessage *msg = retrievedMessage(); + KMMessagePart part; + DwBodyPart *dwpart = findPart( msg, mPartIndex ); + if ( !dwpart ) + return Failed; + KMMessage::bodyPart( dwpart, &part, true ); + if ( !part.isComplete() ) + return Failed; + + DwBody *parentNode = dynamic_cast<DwBody*>( dwpart->Parent() ); + if ( !parentNode ) + return Failed; + parentNode->RemoveBodyPart( dwpart ); + + // add dummy part to show that a attachment has been deleted + KMMessagePart dummyPart; + dummyPart.duplicate( part ); + QString comment = i18n("This attachment has been deleted."); + if ( !part.fileName().isEmpty() ) + comment = i18n( "The attachment '%1' has been deleted." ).arg( part.fileName() ); + dummyPart.setContentDescription( comment ); + dummyPart.setBodyEncodedBinary( QByteArray() ); + QCString cd = dummyPart.contentDisposition(); + if ( cd.find( "inline", 0, false ) == 0 ) { + cd.replace( 0, 10, "attachment" ); + dummyPart.setContentDisposition( cd ); + } else if ( cd.isEmpty() ) { + dummyPart.setContentDisposition( "attachment" ); + } + DwBodyPart* newDwPart = msg->createDWBodyPart( &dummyPart ); + parentNode->AddBodyPart( newDwPart ); + msg->getTopLevelPart()->Assemble(); + + KMMessage *newMsg = new KMMessage(); + newMsg->fromDwString( msg->asDwString() ); + newMsg->setStatus( msg->status() ); + + storeChangedMessage( newMsg ); + return OK; +} + + +KMEditAttachmentCommand::KMEditAttachmentCommand(partNode * node, KMMessage * msg, QWidget * parent) : + AttachmentModifyCommand( node, msg, parent ) +{ + kdDebug(5006) << k_funcinfo << endl; + mTempFile.setAutoDelete( true ); +} + +KMEditAttachmentCommand::~ KMEditAttachmentCommand() +{ +} + +KMCommand::Result KMEditAttachmentCommand::doAttachmentModify() +{ + KMMessage *msg = retrievedMessage(); + KMMessagePart part; + DwBodyPart *dwpart = findPart( msg, mPartIndex ); + if ( !dwpart ) + return Failed; + KMMessage::bodyPart( dwpart, &part, true ); + if ( !part.isComplete() ) + return Failed; + + if( !dynamic_cast<DwBody*>( dwpart->Parent() ) ) + return Failed; + + mTempFile.file()->writeBlock( part.bodyDecodedBinary() ); + mTempFile.file()->flush(); + + KMail::EditorWatcher *watcher = new KMail::EditorWatcher( KURL(mTempFile.file()->name()), part.typeStr() + "/" + part.subtypeStr(), false, this ); + connect( watcher, SIGNAL(editDone(KMail::EditorWatcher*)), SLOT(editDone(KMail::EditorWatcher*)) ); + if ( !watcher->start() ) + return Failed; + setEmitsCompletedItself( true ); + setDeletesItself( true ); + return OK; +} + +void KMEditAttachmentCommand::editDone(KMail::EditorWatcher * watcher) +{ + kdDebug(5006) << k_funcinfo << endl; + // anything changed? + if ( !watcher->fileChanged() ) { + kdDebug(5006) << k_funcinfo << "File has not been changed" << endl; + setResult( Canceled ); + emit completed( this ); + deleteLater(); + } + + mTempFile.file()->reset(); + QByteArray data = mTempFile.file()->readAll(); + + // build the new message + KMMessage *msg = retrievedMessage(); + KMMessagePart part; + DwBodyPart *dwpart = findPart( msg, mPartIndex ); + KMMessage::bodyPart( dwpart, &part, true ); + + DwBody *parentNode = dynamic_cast<DwBody*>( dwpart->Parent() ); + assert( parentNode ); + parentNode->RemoveBodyPart( dwpart ); + + KMMessagePart att; + att.duplicate( part ); + att.setBodyEncodedBinary( data ); + + DwBodyPart* newDwPart = msg->createDWBodyPart( &att ); + parentNode->AddBodyPart( newDwPart ); + msg->getTopLevelPart()->Assemble(); + + KMMessage *newMsg = new KMMessage(); + newMsg->fromDwString( msg->asDwString() ); + newMsg->setStatus( msg->status() ); + + storeChangedMessage( newMsg ); +} + + +CreateTodoCommand::CreateTodoCommand(QWidget * parent, KMMessage * msg) + : KMCommand( parent, msg ) +{ +} + +KMCommand::Result CreateTodoCommand::execute() +{ + KMMessage *msg = retrievedMessage(); + if ( !msg || !msg->codec() ) { + return Failed; + } + + KMail::KorgHelper::ensureRunning(); + + QString txt = i18n("From: %1\nTo: %2\nSubject: %3").arg( msg->from() ) + .arg( msg->to() ).arg( msg->subject() ); + + KTempFile tf; + tf.setAutoDelete( true ); + QString uri = "kmail:" + QString::number( msg->getMsgSerNum() ) + "/" + msg->msgId(); + tf.file()->writeBlock( msg->asDwString().c_str(), msg->asDwString().length() ); + tf.close(); + + KCalendarIface_stub *iface = new KCalendarIface_stub( kapp->dcopClient(), "korganizer", "CalendarIface" ); + iface->openTodoEditor( i18n("Mail: %1").arg( msg->subject() ), txt, + uri, tf.name(), QStringList(), "message/rfc822" ); + delete iface; + + return OK; +} + +#include "kmcommands.moc" |