kmail

kmcommands.cpp

00001 /* -*- mode: C++; c-file-style: "gnu" -*-
00002     This file is part of KMail, the KDE mail client.
00003     Copyright (c) 2002 Don Sanders <sanders@kde.org>
00004 
00005     KMail is free software; you can redistribute it and/or modify it
00006     under the terms of the GNU General Public License, version 2, as
00007     published by the Free Software Foundation.
00008 
00009     KMail is distributed in the hope that it will be useful, but
00010     WITHOUT ANY WARRANTY; without even the implied warranty of
00011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012     General Public License for more details.
00013 
00014     You should have received a copy of the GNU General Public License
00015     along with this program; if not, write to the Free Software
00016     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
00017 */
00018 
00019 //
00020 // This file implements various "command" classes. These command classes
00021 // are based on the command design pattern.
00022 //
00023 // Historically various operations were implemented as slots of KMMainWin.
00024 // This proved inadequate as KMail has multiple top level windows
00025 // (KMMainWin, KMReaderMainWin, SearchWindow, KMComposeWin) that may
00026 // benefit from using these operations. It is desirable that these
00027 // classes can operate without depending on or altering the state of
00028 // a KMMainWin, in fact it is possible no KMMainWin object even exists.
00029 //
00030 // Now these operations have been rewritten as KMCommand based classes,
00031 // making them independent of KMMainWin.
00032 //
00033 // The base command class KMCommand is async, which is a difference
00034 // from the conventional command pattern. As normal derived classes implement
00035 // the execute method, but client classes call start() instead of
00036 // calling execute() directly. start() initiates async operations,
00037 // and on completion of these operations calls execute() and then deletes
00038 // the command. (So the client must not construct commands on the stack).
00039 //
00040 // The type of async operation supported by KMCommand is retrieval
00041 // of messages from an IMAP server.
00042 
00043 #include "kmcommands.h"
00044 
00045 #ifdef HAVE_CONFIG_H
00046 #include <config.h>
00047 #endif
00048 
00049 #include <errno.h>
00050 #include <mimelib/enum.h>
00051 #include <mimelib/field.h>
00052 #include <mimelib/mimepp.h>
00053 #include <mimelib/string.h>
00054 #include <kapplication.h>
00055 #include <dcopclient.h>
00056 
00057 #include <qtextcodec.h>
00058 #include <qpopupmenu.h>
00059 #include <qeventloop.h>
00060 
00061 #include <libemailfunctions/email.h>
00062 #include <kdebug.h>
00063 #include <kfiledialog.h>
00064 #include <kabc/stdaddressbook.h>
00065 #include <kabc/addresseelist.h>
00066 #include <kdirselectdialog.h>
00067 #include <klocale.h>
00068 #include <kmessagebox.h>
00069 #include <kparts/browserextension.h>
00070 #include <kprogress.h>
00071 #include <krun.h>
00072 #include <kbookmarkmanager.h>
00073 #include <kstandarddirs.h>
00074 #include <ktempfile.h>
00075 #include <kimproxy.h>
00076 #include <kuserprofile.h>
00077 // KIO headers
00078 #include <kio/job.h>
00079 #include <kio/netaccess.h>
00080 
00081 #include "actionscheduler.h"
00082 using KMail::ActionScheduler;
00083 #include "mailinglist-magic.h"
00084 #include "kmaddrbook.h"
00085 #include <kaddrbook.h>
00086 #include "composer.h"
00087 #include "kmfiltermgr.h"
00088 #include "kmfoldermbox.h"
00089 #include "kmfolderimap.h"
00090 #include "kmfoldermgr.h"
00091 #include "kmheaders.h"
00092 #include "headeritem.h"
00093 #include "kmmainwidget.h"
00094 #include "kmmsgdict.h"
00095 #include "messagesender.h"
00096 #include "kmmsgpartdlg.h"
00097 #include "undostack.h"
00098 #include "kcursorsaver.h"
00099 #include "partNode.h"
00100 #include "objecttreeparser.h"
00101 using KMail::ObjectTreeParser;
00102 using KMail::FolderJob;
00103 #include "chiasmuskeyselector.h"
00104 #include "mailsourceviewer.h"
00105 using KMail::MailSourceViewer;
00106 #include "kmreadermainwin.h"
00107 #include "secondarywindow.h"
00108 using KMail::SecondaryWindow;
00109 #include "redirectdialog.h"
00110 using KMail::RedirectDialog;
00111 #include "util.h"
00112 
00113 #include "broadcaststatus.h"
00114 #include "globalsettings.h"
00115 
00116 #include <libkdepim/kfileio.h>
00117 
00118 #include "progressmanager.h"
00119 using KPIM::ProgressManager;
00120 using KPIM::ProgressItem;
00121 #include <kmime_mdn.h>
00122 using namespace KMime;
00123 
00124 #include <kleo/specialjob.h>
00125 #include <kleo/cryptobackend.h>
00126 #include <kleo/cryptobackendfactory.h>
00127 
00128 #include <qclipboard.h>
00129 
00130 #include <memory>
00131 
00132 class LaterDeleterWithCommandCompletion : public KMail::Util::LaterDeleter
00133 {
00134 public:
00135   LaterDeleterWithCommandCompletion( KMCommand* command )
00136     :LaterDeleter( command ), m_result( KMCommand::Failed )
00137   {
00138   }
00139   ~LaterDeleterWithCommandCompletion()
00140   {
00141     setResult( m_result );
00142     KMCommand *command = static_cast<KMCommand*>( m_object );
00143     emit command->completed( command );
00144   }
00145   void setResult( KMCommand::Result v ) { m_result = v; }
00146 private:
00147   KMCommand::Result m_result;
00148 };
00149 
00150 
00151 KMCommand::KMCommand( QWidget *parent )
00152   : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
00153     mEmitsCompletedItself( false ), mParent( parent )
00154 {
00155 }
00156 
00157 KMCommand::KMCommand( QWidget *parent, const QPtrList<KMMsgBase> &msgList )
00158   : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
00159     mEmitsCompletedItself( false ), mParent( parent ), mMsgList( msgList )
00160 {
00161 }
00162 
00163 KMCommand::KMCommand( QWidget *parent, KMMsgBase *msgBase )
00164   : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
00165     mEmitsCompletedItself( false ), mParent( parent )
00166 {
00167   mMsgList.append( msgBase );
00168 }
00169 
00170 KMCommand::KMCommand( QWidget *parent, KMMessage *msg )
00171   : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
00172     mEmitsCompletedItself( false ), mParent( parent )
00173 {
00174   if (msg)
00175     mMsgList.append( &msg->toMsgBase() );
00176 }
00177 
00178 KMCommand::~KMCommand()
00179 {
00180   QValueListIterator<QGuardedPtr<KMFolder> > fit;
00181   for ( fit = mFolders.begin(); fit != mFolders.end(); ++fit ) {
00182     if (!(*fit))
00183       continue;
00184     (*fit)->close();
00185   }
00186 }
00187 
00188 KMCommand::Result KMCommand::result()
00189 {
00190   if ( mResult == Undefined )
00191     kdDebug(5006) << k_funcinfo << "mResult is Undefined" << endl;
00192   return mResult;
00193 }
00194 
00195 void KMCommand::start()
00196 {
00197   QTimer::singleShot( 0, this, SLOT( slotStart() ) );
00198 }
00199 
00200 
00201 const QPtrList<KMMessage> KMCommand::retrievedMsgs() const
00202 {
00203   return mRetrievedMsgs;
00204 }
00205 
00206 KMMessage *KMCommand::retrievedMessage() const
00207 {
00208   return mRetrievedMsgs.getFirst();
00209 }
00210 
00211 QWidget *KMCommand::parentWidget() const
00212 {
00213   return mParent;
00214 }
00215 
00216 int KMCommand::mCountJobs = 0;
00217 
00218 void KMCommand::slotStart()
00219 {
00220   connect( this, SIGNAL( messagesTransfered( KMCommand::Result ) ),
00221            this, SLOT( slotPostTransfer( KMCommand::Result ) ) );
00222   kmkernel->filterMgr()->ref();
00223 
00224   if (mMsgList.find(0) != -1) {
00225       emit messagesTransfered( Failed );
00226       return;
00227   }
00228 
00229   if ((mMsgList.count() == 1) &&
00230       (mMsgList.getFirst()->isMessage()) &&
00231       (mMsgList.getFirst()->parent() == 0))
00232   {
00233     // Special case of operating on message that isn't in a folder
00234     mRetrievedMsgs.append((KMMessage*)mMsgList.getFirst());
00235     emit messagesTransfered( OK );
00236     return;
00237   }
00238 
00239   for (KMMsgBase *mb = mMsgList.first(); mb; mb = mMsgList.next())
00240     if (!mb->parent()) {
00241       emit messagesTransfered( Failed );
00242       return;
00243     } else {
00244       keepFolderOpen( mb->parent() );
00245     }
00246 
00247   // transfer the selected messages first
00248   transferSelectedMsgs();
00249 }
00250 
00251 void KMCommand::slotPostTransfer( KMCommand::Result result )
00252 {
00253   disconnect( this, SIGNAL( messagesTransfered( KMCommand::Result ) ),
00254               this, SLOT( slotPostTransfer( KMCommand::Result ) ) );
00255   if ( result == OK )
00256     result = execute();
00257   mResult = result;
00258   QPtrListIterator<KMMessage> it( mRetrievedMsgs );
00259   KMMessage* msg;
00260   while ( (msg = it.current()) != 0 )
00261   {
00262     ++it;
00263     if (msg->parent())
00264       msg->setTransferInProgress(false);
00265   }
00266   kmkernel->filterMgr()->deref();
00267   if ( !emitsCompletedItself() )
00268     emit completed( this );
00269   if ( !deletesItself() )
00270     deleteLater();
00271 }
00272 
00273 void KMCommand::transferSelectedMsgs()
00274 {
00275   // make sure no other transfer is active
00276   if (KMCommand::mCountJobs > 0) {
00277     emit messagesTransfered( Failed );
00278     return;
00279   }
00280 
00281   bool complete = true;
00282   KMCommand::mCountJobs = 0;
00283   mCountMsgs = 0;
00284   mRetrievedMsgs.clear();
00285   mCountMsgs = mMsgList.count();
00286   uint totalSize = 0;
00287   // the KProgressDialog for the user-feedback. Only enable it if it's needed.
00288   // For some commands like KMSetStatusCommand it's not needed. Note, that
00289   // for some reason the KProgressDialog eats the MouseReleaseEvent (if a
00290   // command is executed after the MousePressEvent), cf. bug #71761.
00291   if ( mCountMsgs > 0 ) {
00292     mProgressDialog = new KProgressDialog(mParent, "transferProgress",
00293       i18n("Please wait"),
00294       i18n("Please wait while the message is transferred",
00295         "Please wait while the %n messages are transferred", mMsgList.count()),
00296       true);
00297     mProgressDialog->setMinimumDuration(1000);
00298   }
00299   for (KMMsgBase *mb = mMsgList.first(); mb; mb = mMsgList.next())
00300   {
00301     // check if all messages are complete
00302     KMMessage *thisMsg = 0;
00303     if ( mb->isMessage() )
00304       thisMsg = static_cast<KMMessage*>(mb);
00305     else
00306     {
00307       KMFolder *folder = mb->parent();
00308       int idx = folder->find(mb);
00309       if (idx < 0) continue;
00310       thisMsg = folder->getMsg(idx);
00311     }
00312     if (!thisMsg) continue;
00313     if ( thisMsg->transferInProgress() &&
00314          thisMsg->parent()->folderType() == KMFolderTypeImap )
00315     {
00316       thisMsg->setTransferInProgress( false, true );
00317       thisMsg->parent()->ignoreJobsForMessage( thisMsg );
00318     }
00319 
00320     if ( thisMsg->parent() && !thisMsg->isComplete() &&
00321          ( !mProgressDialog || !mProgressDialog->wasCancelled() ) )
00322     {
00323       kdDebug(5006)<<"### INCOMPLETE\n";
00324       // the message needs to be transferred first
00325       complete = false;
00326       KMCommand::mCountJobs++;
00327       FolderJob *job = thisMsg->parent()->createJob(thisMsg);
00328       job->setCancellable( false );
00329       totalSize += thisMsg->msgSizeServer();
00330       // emitted when the message was transferred successfully
00331       connect(job, SIGNAL(messageRetrieved(KMMessage*)),
00332               this, SLOT(slotMsgTransfered(KMMessage*)));
00333       // emitted when the job is destroyed
00334       connect(job, SIGNAL(finished()),
00335               this, SLOT(slotJobFinished()));
00336       connect(job, SIGNAL(progress(unsigned long, unsigned long)),
00337               this, SLOT(slotProgress(unsigned long, unsigned long)));
00338       // msg musn't be deleted
00339       thisMsg->setTransferInProgress(true);
00340       job->start();
00341     } else {
00342       thisMsg->setTransferInProgress(true);
00343       mRetrievedMsgs.append(thisMsg);
00344     }
00345   }
00346 
00347   if (complete)
00348   {
00349     delete mProgressDialog;
00350     mProgressDialog = 0;
00351     emit messagesTransfered( OK );
00352   } else {
00353     // wait for the transfer and tell the progressBar the necessary steps
00354     if ( mProgressDialog ) {
00355       connect(mProgressDialog, SIGNAL(cancelClicked()),
00356               this, SLOT(slotTransferCancelled()));
00357       mProgressDialog->progressBar()->setTotalSteps(totalSize);
00358     }
00359   }
00360 }
00361 
00362 void KMCommand::slotMsgTransfered(KMMessage* msg)
00363 {
00364   if ( mProgressDialog && mProgressDialog->wasCancelled() ) {
00365     emit messagesTransfered( Canceled );
00366     return;
00367   }
00368 
00369   // save the complete messages
00370   mRetrievedMsgs.append(msg);
00371 }
00372 
00373 void KMCommand::slotProgress( unsigned long done, unsigned long /*total*/ )
00374 {
00375   mProgressDialog->progressBar()->setProgress( done );
00376 }
00377 
00378 void KMCommand::slotJobFinished()
00379 {
00380   // the job is finished (with / without error)
00381   KMCommand::mCountJobs--;
00382 
00383   if ( mProgressDialog && mProgressDialog->wasCancelled() ) return;
00384 
00385   if ( (mCountMsgs - static_cast<int>(mRetrievedMsgs.count())) > KMCommand::mCountJobs )
00386   {
00387     // the message wasn't retrieved before => error
00388     if ( mProgressDialog )
00389       mProgressDialog->hide();
00390     slotTransferCancelled();
00391     return;
00392   }
00393   // update the progressbar
00394   if ( mProgressDialog ) {
00395     mProgressDialog->setLabel(i18n("Please wait while the message is transferred",
00396           "Please wait while the %n messages are transferred", KMCommand::mCountJobs));
00397   }
00398   if (KMCommand::mCountJobs == 0)
00399   {
00400     // all done
00401     delete mProgressDialog;
00402     mProgressDialog = 0;
00403     emit messagesTransfered( OK );
00404   }
00405 }
00406 
00407 void KMCommand::slotTransferCancelled()
00408 {
00409   // kill the pending jobs
00410   QValueListIterator<QGuardedPtr<KMFolder> > fit;
00411   for ( fit = mFolders.begin(); fit != mFolders.end(); ++fit ) {
00412     if (!(*fit))
00413       continue;
00414     KMFolder *folder = *fit;
00415     KMFolderImap *imapFolder = dynamic_cast<KMFolderImap*>(folder);
00416     if (imapFolder && imapFolder->account()) {
00417       imapFolder->account()->killAllJobs();
00418     }
00419   }
00420 
00421   KMCommand::mCountJobs = 0;
00422   mCountMsgs = 0;
00423   // unget the transfered messages
00424   QPtrListIterator<KMMessage> it( mRetrievedMsgs );
00425   KMMessage* msg;
00426   while ( (msg = it.current()) != 0 )
00427   {
00428     KMFolder *folder = msg->parent();
00429     ++it;
00430     if (!folder)
00431       continue;
00432     msg->setTransferInProgress(false);
00433     int idx = folder->find(msg);
00434     if (idx > 0) folder->unGetMsg(idx);
00435   }
00436   mRetrievedMsgs.clear();
00437   emit messagesTransfered( Canceled );
00438 }
00439 
00440 void KMCommand::keepFolderOpen( KMFolder *folder )
00441 {
00442   folder->open();
00443   mFolders.append( folder );
00444 }
00445 
00446 KMMailtoComposeCommand::KMMailtoComposeCommand( const KURL &url,
00447                                                 KMMessage *msg )
00448   :mUrl( url ), mMessage( msg )
00449 {
00450 }
00451 
00452 KMCommand::Result KMMailtoComposeCommand::execute()
00453 {
00454   KMMessage *msg = new KMMessage;
00455   uint id = 0;
00456 
00457   if ( mMessage && mMessage->parent() )
00458     id = mMessage->parent()->identity();
00459 
00460   msg->initHeader(id);
00461   msg->setCharset("utf-8");
00462   msg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
00463 
00464   KMail::Composer * win = KMail::makeComposer( msg, id );
00465   win->setCharset("", TRUE);
00466   win->setFocusToSubject();
00467   win->show();
00468 
00469   return OK;
00470 }
00471 
00472 
00473 KMMailtoReplyCommand::KMMailtoReplyCommand( QWidget *parent,
00474    const KURL &url, KMMessage *msg, const QString &selection )
00475   :KMCommand( parent, msg ), mUrl( url ), mSelection( selection  )
00476 {
00477 }
00478 
00479 KMCommand::Result KMMailtoReplyCommand::execute()
00480 {
00481   //TODO : consider factoring createReply into this method.
00482   KMMessage *msg = retrievedMessage();
00483   KMMessage *rmsg = msg->createReply( KMail::ReplyNone, mSelection );
00484   rmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
00485 
00486   KMail::Composer * win = KMail::makeComposer( rmsg, 0 );
00487   win->setCharset(msg->codec()->mimeName(), TRUE);
00488   win->setReplyFocus();
00489   win->show();
00490 
00491   return OK;
00492 }
00493 
00494 
00495 KMMailtoForwardCommand::KMMailtoForwardCommand( QWidget *parent,
00496    const KURL &url, KMMessage *msg )
00497   :KMCommand( parent, msg ), mUrl( url )
00498 {
00499 }
00500 
00501 KMCommand::Result KMMailtoForwardCommand::execute()
00502 {
00503   //TODO : consider factoring createForward into this method.
00504   KMMessage *msg = retrievedMessage();
00505   KMMessage *fmsg = msg->createForward();
00506   fmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
00507 
00508   KMail::Composer * win = KMail::makeComposer( fmsg );
00509   win->setCharset(msg->codec()->mimeName(), TRUE);
00510   win->show();
00511 
00512   return OK;
00513 }
00514 
00515 
00516 KMAddBookmarksCommand::KMAddBookmarksCommand( const KURL &url, QWidget *parent )
00517   : KMCommand( parent ), mUrl( url )
00518 {
00519 }
00520 
00521 KMCommand::Result KMAddBookmarksCommand::execute()
00522 {
00523   QString filename = locateLocal( "data", QString::fromLatin1("konqueror/bookmarks.xml") );
00524   KBookmarkManager *bookManager = KBookmarkManager::managerForFile( filename,
00525                                                                     false );
00526   KBookmarkGroup group = bookManager->root();
00527   group.addBookmark( bookManager, mUrl.path(), KURL( mUrl ) );
00528   if( bookManager->save() ) {
00529     bookManager->emitChanged( group );
00530   }
00531 
00532   return OK;
00533 }
00534 
00535 KMMailtoAddAddrBookCommand::KMMailtoAddAddrBookCommand( const KURL &url,
00536    QWidget *parent )
00537   : KMCommand( parent ), mUrl( url )
00538 {
00539 }
00540 
00541 KMCommand::Result KMMailtoAddAddrBookCommand::execute()
00542 {
00543   KAddrBookExternal::addEmail( KMMessage::decodeMailtoUrl( mUrl.path() ),
00544                                parentWidget() );
00545 
00546   return OK;
00547 }
00548 
00549 
00550 KMMailtoOpenAddrBookCommand::KMMailtoOpenAddrBookCommand( const KURL &url,
00551    QWidget *parent )
00552   : KMCommand( parent ), mUrl( url )
00553 {
00554 }
00555 
00556 KMCommand::Result KMMailtoOpenAddrBookCommand::execute()
00557 {
00558   KAddrBookExternal::openEmail( KMMessage::decodeMailtoUrl( mUrl.path() ),
00559                                 parentWidget() );
00560 
00561   return OK;
00562 }
00563 
00564 
00565 KMUrlCopyCommand::KMUrlCopyCommand( const KURL &url, KMMainWidget *mainWidget )
00566   :mUrl( url ), mMainWidget( mainWidget )
00567 {
00568 }
00569 
00570 KMCommand::Result KMUrlCopyCommand::execute()
00571 {
00572   QClipboard* clip = QApplication::clipboard();
00573 
00574   if (mUrl.protocol() == "mailto") {
00575     // put the url into the mouse selection and the clipboard
00576     QString address = KMMessage::decodeMailtoUrl( mUrl.path() );
00577     clip->setSelectionMode( true );
00578     clip->setText( address );
00579     clip->setSelectionMode( false );
00580     clip->setText( address );
00581     KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "Address copied to clipboard." ));
00582   } else {
00583     // put the url into the mouse selection and the clipboard
00584     clip->setSelectionMode( true );
00585     clip->setText( mUrl.url() );
00586     clip->setSelectionMode( false );
00587     clip->setText( mUrl.url() );
00588     KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "URL copied to clipboard." ));
00589   }
00590 
00591   return OK;
00592 }
00593 
00594 
00595 KMUrlOpenCommand::KMUrlOpenCommand( const KURL &url, KMReaderWin *readerWin )
00596   :mUrl( url ), mReaderWin( readerWin )
00597 {
00598 }
00599 
00600 KMCommand::Result KMUrlOpenCommand::execute()
00601 {
00602   if ( !mUrl.isEmpty() )
00603     mReaderWin->slotUrlOpen( mUrl, KParts::URLArgs() );
00604 
00605   return OK;
00606 }
00607 
00608 
00609 KMUrlSaveCommand::KMUrlSaveCommand( const KURL &url, QWidget *parent )
00610   : KMCommand( parent ), mUrl( url )
00611 {
00612 }
00613 
00614 KMCommand::Result KMUrlSaveCommand::execute()
00615 {
00616   if ( mUrl.isEmpty() )
00617     return OK;
00618   KURL saveUrl = KFileDialog::getSaveURL(mUrl.fileName(), QString::null,
00619                                          parentWidget() );
00620   if ( saveUrl.isEmpty() )
00621     return Canceled;
00622   if ( KIO::NetAccess::exists( saveUrl, false, parentWidget() ) )
00623   {
00624     if (KMessageBox::warningContinueCancel(0,
00625         i18n("<qt>File <b>%1</b> exists.<br>Do you want to replace it?</qt>")
00626         .arg(saveUrl.prettyURL()), i18n("Save to File"), i18n("&Replace"))
00627         != KMessageBox::Continue)
00628       return Canceled;
00629   }
00630   KIO::Job *job = KIO::file_copy(mUrl, saveUrl, -1, true);
00631   connect(job, SIGNAL(result(KIO::Job*)), SLOT(slotUrlSaveResult(KIO::Job*)));
00632   setEmitsCompletedItself( true );
00633   return OK;
00634 }
00635 
00636 void KMUrlSaveCommand::slotUrlSaveResult( KIO::Job *job )
00637 {
00638   if ( job->error() ) {
00639     job->showErrorDialog();
00640     setResult( Failed );
00641     emit completed( this );
00642   }
00643   else {
00644     setResult( OK );
00645     emit completed( this );
00646   }
00647 }
00648 
00649 
00650 KMEditMsgCommand::KMEditMsgCommand( QWidget *parent, KMMessage *msg )
00651   :KMCommand( parent, msg )
00652 {
00653 }
00654 
00655 KMCommand::Result KMEditMsgCommand::execute()
00656 {
00657   KMMessage *msg = retrievedMessage();
00658   if (!msg || !msg->parent() ||
00659       !kmkernel->folderIsDraftOrOutbox( msg->parent() ))
00660     return Failed;
00661 
00662   // Remember the old parent, we need it a bit further down to be able
00663   // to put the unchanged messsage back in the drafts folder if the nth
00664   // edit is discarded, for n > 1.
00665   KMFolder *parent = msg->parent();
00666   if ( parent )
00667     parent->take( parent->find( msg ) );
00668 
00669   KMail::Composer * win = KMail::makeComposer();
00670   msg->setTransferInProgress(false); // From here on on, the composer owns the message.
00671   win->setMsg(msg, FALSE, TRUE);
00672   win->setFolder( parent );
00673   win->show();
00674 
00675   return OK;
00676 }
00677 
00678 
00679 KMShowMsgSrcCommand::KMShowMsgSrcCommand( QWidget *parent,
00680   KMMessage *msg, bool fixedFont )
00681   :KMCommand( parent, msg ), mFixedFont( fixedFont )
00682 {
00683   // remember complete state
00684   mMsgWasComplete = msg->isComplete();
00685 }
00686 
00687 KMCommand::Result KMShowMsgSrcCommand::execute()
00688 {
00689   KMMessage *msg = retrievedMessage();
00690   if ( msg->isComplete() && !mMsgWasComplete )
00691     msg->notify(); // notify observers as msg was transfered
00692   QString str = msg->codec()->toUnicode( msg->asString() );
00693 
00694   MailSourceViewer *viewer = new MailSourceViewer(); // deletes itself upon close
00695   viewer->setCaption( i18n("Message as Plain Text") );
00696   viewer->setText(str);
00697   if( mFixedFont )
00698     viewer->setFont(KGlobalSettings::fixedFont());
00699 
00700   // Well, there is no widget to be seen here, so we have to use QCursor::pos()
00701   // Update: (GS) I'm not going to make this code behave according to Xinerama
00702   //         configuration because this is quite the hack.
00703   if (QApplication::desktop()->isVirtualDesktop()) {
00704     int scnum = QApplication::desktop()->screenNumber(QCursor::pos());
00705     viewer->resize(QApplication::desktop()->screenGeometry(scnum).width()/2,
00706                   2*QApplication::desktop()->screenGeometry(scnum).height()/3);
00707   } else {
00708     viewer->resize(QApplication::desktop()->geometry().width()/2,
00709                   2*QApplication::desktop()->geometry().height()/3);
00710   }
00711   viewer->show();
00712 
00713   return OK;
00714 }
00715 
00716 static KURL subjectToUrl( const QString & subject ) {
00717     return KFileDialog::getSaveURL( subject.stripWhiteSpace()
00718                                            .replace( QDir::separator(), '_' ),
00719                                     QString::null );
00720 }
00721 
00722 KMSaveMsgCommand::KMSaveMsgCommand( QWidget *parent, KMMessage * msg )
00723   : KMCommand( parent ),
00724     mMsgListIndex( 0 ),
00725     mStandAloneMessage( 0 ),
00726     mOffset( 0 ),
00727     mTotalSize( msg ? msg->msgSize() : 0 )
00728 {
00729   if ( !msg ) return;
00730   setDeletesItself( true );
00731   // If the mail has a serial number, operate on sernums, if it does not
00732   // we need to work with the pointer, but can be reasonably sure it won't
00733   // go away, since it'll be an encapsulated message or one that was opened
00734   // from an .eml file.
00735   if ( msg->getMsgSerNum() != 0 ) {
00736     mMsgList.append( msg->getMsgSerNum() );
00737   } else {
00738     mStandAloneMessage = msg;
00739   }
00740   mUrl = subjectToUrl( msg->cleanSubject() );
00741 }
00742 
00743 KMSaveMsgCommand::KMSaveMsgCommand( QWidget *parent,
00744                                     const QPtrList<KMMsgBase> &msgList )
00745   : KMCommand( parent ),
00746     mMsgListIndex( 0 ),
00747     mStandAloneMessage( 0 ),
00748     mOffset( 0 ),
00749     mTotalSize( 0 )
00750 {
00751   if (!msgList.getFirst())
00752     return;
00753   setDeletesItself( true );
00754   KMMsgBase *msgBase = msgList.getFirst();
00755 
00756   // We operate on serNums and not the KMMsgBase pointers, as those can
00757   // change, or become invalid when changing the current message, switching
00758   // folders, etc.
00759   QPtrListIterator<KMMsgBase> it(msgList);
00760   while ( it.current() ) {
00761     mMsgList.append( (*it)->getMsgSerNum() );
00762     mTotalSize += (*it)->msgSize();
00763     if ((*it)->parent() != 0)
00764       (*it)->parent()->open();
00765     ++it;
00766   }
00767   mMsgListIndex = 0;
00768   mUrl = subjectToUrl( msgBase->cleanSubject() );
00769 }
00770 
00771 KURL KMSaveMsgCommand::url()
00772 {
00773   return mUrl;
00774 }
00775 
00776 KMCommand::Result KMSaveMsgCommand::execute()
00777 {
00778   mJob = KIO::put( mUrl, S_IRUSR|S_IWUSR, false, false );
00779   mJob->slotTotalSize( mTotalSize );
00780   mJob->setAsyncDataEnabled( true );
00781   mJob->setReportDataSent( true );
00782   connect(mJob, SIGNAL(dataReq(KIO::Job*, QByteArray &)),
00783     SLOT(slotSaveDataReq()));
00784   connect(mJob, SIGNAL(result(KIO::Job*)),
00785     SLOT(slotSaveResult(KIO::Job*)));
00786   setEmitsCompletedItself( true );
00787   return OK;
00788 }
00789 
00790 void KMSaveMsgCommand::slotSaveDataReq()
00791 {
00792   int remainingBytes = mData.size() - mOffset;
00793   if ( remainingBytes > 0 ) {
00794     // eat leftovers first
00795     if ( remainingBytes > MAX_CHUNK_SIZE )
00796       remainingBytes = MAX_CHUNK_SIZE;
00797 
00798     QByteArray data;
00799     data.duplicate( mData.data() + mOffset, remainingBytes );
00800     mJob->sendAsyncData( data );
00801     mOffset += remainingBytes;
00802     return;
00803   }
00804   // No leftovers, process next message.
00805   if ( mMsgListIndex < mMsgList.size() ) {
00806     KMMessage *msg = 0;
00807     int idx = -1;
00808     KMFolder * p = 0;
00809     KMMsgDict::instance()->getLocation( mMsgList[mMsgListIndex], &p, &idx );
00810     assert( p );
00811     assert( idx >= 0 );
00812     msg = p->getMsg(idx);
00813 
00814     if (msg->transferInProgress()) {
00815       QByteArray data = QByteArray();
00816       mJob->sendAsyncData( data );
00817     }
00818     msg->setTransferInProgress( true );
00819     if (msg->isComplete() ) {
00820       slotMessageRetrievedForSaving(msg);
00821     } else {
00822       // retrieve Message first
00823       if (msg->parent()  && !msg->isComplete() ) {
00824         FolderJob *job = msg->parent()->createJob(msg);
00825         job->setCancellable( false );
00826         connect(job, SIGNAL(messageRetrieved(KMMessage*)),
00827             this, SLOT(slotMessageRetrievedForSaving(KMMessage*)));
00828         job->start();
00829       }
00830     }
00831   } else {
00832     if ( mStandAloneMessage ) {
00833       // do the special case of a standalone message
00834       slotMessageRetrievedForSaving( mStandAloneMessage );
00835       mStandAloneMessage = 0;
00836     } else {
00837       // No more messages. Tell the putjob we are done.
00838       QByteArray data = QByteArray();
00839       mJob->sendAsyncData( data );
00840     }
00841   }
00842 }
00843 
00844 void KMSaveMsgCommand::slotMessageRetrievedForSaving(KMMessage *msg)
00845 {
00846   if ( msg ) {
00847     QCString str( msg->mboxMessageSeparator() );
00848     str += KMFolderMbox::escapeFrom( msg->asString() );
00849     str += '\n';
00850     msg->setTransferInProgress(false);
00851 
00852     mData = str;
00853     mData.resize(mData.size() - 1);
00854     mOffset = 0;
00855     QByteArray data;
00856     int size;
00857     // Unless it is great than 64 k send the whole message. kio buffers for us.
00858     if( mData.size() > (unsigned int) MAX_CHUNK_SIZE )
00859       size = MAX_CHUNK_SIZE;
00860     else
00861       size = mData.size();
00862 
00863     data.duplicate( mData, size );
00864     mJob->sendAsyncData( data );
00865     mOffset += size;
00866   }
00867   ++mMsgListIndex;
00868   // Get rid of the message.
00869   if ( msg && msg->parent() && msg->getMsgSerNum() ) {
00870     int idx = -1;
00871     KMFolder * p = 0;
00872     KMMsgDict::instance()->getLocation( msg, &p, &idx );
00873     assert( p == msg->parent() ); assert( idx >= 0 );
00874     p->unGetMsg( idx );
00875     p->close();
00876   }
00877 }
00878 
00879 void KMSaveMsgCommand::slotSaveResult(KIO::Job *job)
00880 {
00881   if (job->error())
00882   {
00883     if (job->error() == KIO::ERR_FILE_ALREADY_EXIST)
00884     {
00885       if (KMessageBox::warningContinueCancel(0,
00886         i18n("File %1 exists.\nDo you want to replace it?")
00887         .arg(mUrl.prettyURL()), i18n("Save to File"), i18n("&Replace"))
00888         == KMessageBox::Continue) {
00889         mOffset = 0;
00890 
00891         mJob = KIO::put( mUrl, S_IRUSR|S_IWUSR, true, false );
00892         mJob->slotTotalSize( mTotalSize );
00893         mJob->setAsyncDataEnabled( true );
00894         mJob->setReportDataSent( true );
00895         connect(mJob, SIGNAL(dataReq(KIO::Job*, QByteArray &)),
00896             SLOT(slotSaveDataReq()));
00897         connect(mJob, SIGNAL(result(KIO::Job*)),
00898             SLOT(slotSaveResult(KIO::Job*)));
00899       }
00900     }
00901     else
00902     {
00903       job->showErrorDialog();
00904       setResult( Failed );
00905       emit completed( this );
00906       deleteLater();
00907     }
00908   } else {
00909     setResult( OK );
00910     emit completed( this );
00911     deleteLater();
00912   }
00913 }
00914 
00915 //-----------------------------------------------------------------------------
00916 
00917 KMOpenMsgCommand::KMOpenMsgCommand( QWidget *parent, const KURL & url,
00918                                     const QString & encoding )
00919   : KMCommand( parent ),
00920     mUrl( url ),
00921     mEncoding( encoding )
00922 {
00923   setDeletesItself( true );
00924 }
00925 
00926 KMCommand::Result KMOpenMsgCommand::execute()
00927 {
00928   if ( mUrl.isEmpty() ) {
00929     mUrl = KFileDialog::getOpenURL( ":OpenMessage", "message/rfc822",
00930                                     parentWidget(), i18n("Open Message") );
00931   }
00932   if ( mUrl.isEmpty() ) {
00933     setDeletesItself( false );
00934     return Canceled;
00935   }
00936   mJob = KIO::get( mUrl, false, false );
00937   mJob->setReportDataSent( true );
00938   connect( mJob, SIGNAL( data( KIO::Job *, const QByteArray & ) ),
00939            this, SLOT( slotDataArrived( KIO::Job*, const QByteArray & ) ) );
00940   connect( mJob, SIGNAL( result( KIO::Job * ) ),
00941            SLOT( slotResult( KIO::Job * ) ) );
00942   setEmitsCompletedItself( true );
00943   return OK;
00944 }
00945 
00946 void KMOpenMsgCommand::slotDataArrived( KIO::Job *, const QByteArray & data )
00947 {
00948   if ( data.isEmpty() )
00949     return;
00950 
00951   mMsgString.append( data.data(), data.size() );
00952 }
00953 
00954 void KMOpenMsgCommand::slotResult( KIO::Job *job )
00955 {
00956   if ( job->error() ) {
00957     // handle errors
00958     job->showErrorDialog();
00959     setResult( Failed );
00960     emit completed( this );
00961   }
00962   else {
00963     int startOfMessage = 0;
00964     if ( mMsgString.compare( 0, 5, "From ", 5 ) == 0 ) {
00965       startOfMessage = mMsgString.find( '\n' );
00966       if ( startOfMessage == -1 ) {
00967         KMessageBox::sorry( parentWidget(),
00968                             i18n( "The file does not contain a message." ) );
00969         setResult( Failed );
00970         emit completed( this );
00971         // Emulate closing of a secondary window so that KMail exits in case it
00972         // was started with the --view command line option. Otherwise an
00973         // invisible KMail would keep running.
00974         SecondaryWindow *win = new SecondaryWindow();
00975         win->close();
00976         win->deleteLater();
00977         deleteLater();
00978         return;
00979       }
00980       startOfMessage += 1; // the message starts after the '\n'
00981     }
00982     // check for multiple messages in the file
00983     bool multipleMessages = true;
00984     int endOfMessage = mMsgString.find( "\nFrom " );
00985     if ( endOfMessage == -1 ) {
00986       endOfMessage = mMsgString.length();
00987       multipleMessages = false;
00988     }
00989     DwMessage *dwMsg = new DwMessage;
00990     dwMsg->FromString( mMsgString.substr( startOfMessage,
00991                                           endOfMessage - startOfMessage ) );
00992     dwMsg->Parse();
00993     // check whether we have a message ( no headers => this isn't a message )
00994     if ( dwMsg->Headers().NumFields() == 0 ) {
00995       KMessageBox::sorry( parentWidget(),
00996                           i18n( "The file does not contain a message." ) );
00997       delete dwMsg; dwMsg = 0;
00998       setResult( Failed );
00999       emit completed( this );
01000       // Emulate closing of a secondary window (see above).
01001       SecondaryWindow *win = new SecondaryWindow();
01002       win->close();
01003       win->deleteLater();
01004       deleteLater();
01005       return;
01006     }
01007     KMMessage *msg = new KMMessage( dwMsg );
01008     msg->setReadyToShow( true );
01009     KMReaderMainWin *win = new KMReaderMainWin();
01010     win->showMsg( mEncoding, msg );
01011     win->show();
01012     if ( multipleMessages )
01013       KMessageBox::information( win,
01014                                 i18n( "The file contains multiple messages. "
01015                                       "Only the first message is shown." ) );
01016     setResult( OK );
01017     emit completed( this );
01018   }
01019   deleteLater();
01020 }
01021 
01022 //-----------------------------------------------------------------------------
01023 
01024 //TODO: ReplyTo, NoQuoteReplyTo, ReplyList, ReplyToAll, ReplyAuthor
01025 //      are all similar and should be factored
01026 KMReplyToCommand::KMReplyToCommand( QWidget *parent, KMMessage *msg,
01027                                     const QString &selection )
01028   : KMCommand( parent, msg ), mSelection( selection )
01029 {
01030 }
01031 
01032 KMCommand::Result KMReplyToCommand::execute()
01033 {
01034   KCursorSaver busy(KBusyPtr::busy());
01035   KMMessage *msg = retrievedMessage();
01036   KMMessage *reply = msg->createReply( KMail::ReplySmart, mSelection );
01037   KMail::Composer * win = KMail::makeComposer( reply );
01038   win->setCharset( msg->codec()->mimeName(), TRUE );
01039   win->setReplyFocus();
01040   win->show();
01041 
01042   return OK;
01043 }
01044 
01045 
01046 KMNoQuoteReplyToCommand::KMNoQuoteReplyToCommand( QWidget *parent,
01047                                                   KMMessage *msg )
01048   : KMCommand( parent, msg )
01049 {
01050 }
01051 
01052 KMCommand::Result KMNoQuoteReplyToCommand::execute()
01053 {
01054   KCursorSaver busy(KBusyPtr::busy());
01055   KMMessage *msg = retrievedMessage();
01056   KMMessage *reply = msg->createReply( KMail::ReplySmart, "", TRUE);
01057   KMail::Composer * win = KMail::makeComposer( reply );
01058   win->setCharset(msg->codec()->mimeName(), TRUE);
01059   win->setReplyFocus(false);
01060   win->show();
01061 
01062   return OK;
01063 }
01064 
01065 
01066 KMReplyListCommand::KMReplyListCommand( QWidget *parent,
01067   KMMessage *msg, const QString &selection )
01068  : KMCommand( parent, msg ), mSelection( selection )
01069 {
01070 }
01071 
01072 KMCommand::Result KMReplyListCommand::execute()
01073 {
01074   KCursorSaver busy(KBusyPtr::busy());
01075   KMMessage *msg = retrievedMessage();
01076   KMMessage *reply = msg->createReply( KMail::ReplyList, mSelection);
01077   KMail::Composer * win = KMail::makeComposer( reply );
01078   win->setCharset(msg->codec()->mimeName(), TRUE);
01079   win->setReplyFocus(false);
01080   win->show();
01081 
01082   return OK;
01083 }
01084 
01085 
01086 KMReplyToAllCommand::KMReplyToAllCommand( QWidget *parent,
01087   KMMessage *msg, const QString &selection )
01088   :KMCommand( parent, msg ), mSelection( selection )
01089 {
01090 }
01091 
01092 KMCommand::Result KMReplyToAllCommand::execute()
01093 {
01094   KCursorSaver busy(KBusyPtr::busy());
01095   KMMessage *msg = retrievedMessage();
01096   KMMessage *reply = msg->createReply( KMail::ReplyAll, mSelection );
01097   KMail::Composer * win = KMail::makeComposer( reply );
01098   win->setCharset( msg->codec()->mimeName(), TRUE );
01099   win->setReplyFocus();
01100   win->show();
01101 
01102   return OK;
01103 }
01104 
01105 
01106 KMReplyAuthorCommand::KMReplyAuthorCommand( QWidget *parent, KMMessage *msg,
01107                                             const QString &selection )
01108   : KMCommand( parent, msg ), mSelection( selection )
01109 {
01110 }
01111 
01112 KMCommand::Result KMReplyAuthorCommand::execute()
01113 {
01114   KCursorSaver busy(KBusyPtr::busy());
01115   KMMessage *msg = retrievedMessage();
01116   KMMessage *reply = msg->createReply( KMail::ReplyAuthor, mSelection );
01117   KMail::Composer * win = KMail::makeComposer( reply );
01118   win->setCharset( msg->codec()->mimeName(), TRUE );
01119   win->setReplyFocus();
01120   win->show();
01121 
01122   return OK;
01123 }
01124 
01125 
01126 KMForwardInlineCommand::KMForwardInlineCommand( QWidget *parent,
01127   const QPtrList<KMMsgBase> &msgList, uint identity )
01128   : KMCommand( parent, msgList ),
01129     mIdentity( identity )
01130 {
01131 }
01132 
01133 KMForwardInlineCommand::KMForwardInlineCommand( QWidget *parent,
01134   KMMessage *msg, uint identity )
01135   : KMCommand( parent, msg ),
01136     mIdentity( identity )
01137 {
01138 }
01139 
01140 KMCommand::Result KMForwardInlineCommand::execute()
01141 {
01142   QPtrList<KMMessage> msgList = retrievedMsgs();
01143 
01144   if (msgList.count() >= 2) { // Multiple forward
01145 
01146     uint id = 0;
01147     QCString msgText = "";
01148     QPtrList<KMMessage> linklist;
01149     for ( KMMessage *msg = msgList.first(); msg; msg = msgList.next() ) {
01150       // set the identity
01151       if (id == 0)
01152         id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt();
01153 
01154       msgText += msg->createForwardBody();
01155       linklist.append( msg );
01156     }
01157     if ( id == 0 )
01158       id = mIdentity; // use folder identity if no message had an id set
01159     KMMessage *fwdMsg = new KMMessage;
01160     fwdMsg->initHeader( id );
01161     fwdMsg->setAutomaticFields( true );
01162     fwdMsg->setCharset( "utf-8" );
01163     fwdMsg->setBody( msgText );
01164 
01165     for ( KMMessage *msg = linklist.first(); msg; msg = linklist.next() )
01166       fwdMsg->link( msg, KMMsgStatusForwarded );
01167 
01168     KCursorSaver busy( KBusyPtr::busy() );
01169     KMail::Composer * win = KMail::makeComposer( fwdMsg, id );
01170     win->setCharset("");
01171     win->show();
01172 
01173   } else { // forward a single message at most
01174 
01175     KMMessage *msg = msgList.getFirst();
01176     if ( !msg || !msg->codec() )
01177       return Failed;
01178 
01179     KCursorSaver busy( KBusyPtr::busy() );
01180     KMMessage *fwdMsg = msg->createForward();
01181 
01182     uint id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt();
01183     if ( id == 0 )
01184       id = mIdentity;
01185     {
01186       KMail::Composer * win = KMail::makeComposer( fwdMsg, id );
01187       win->setCharset( fwdMsg->codec()->mimeName(), true );
01188       win->setBody( QString::fromUtf8( msg->createForwardBody() ) );
01189       win->show();
01190     }
01191   }
01192   return OK;
01193 }
01194 
01195 
01196 KMForwardAttachedCommand::KMForwardAttachedCommand( QWidget *parent,
01197            const QPtrList<KMMsgBase> &msgList, uint identity, KMail::Composer *win )
01198   : KMCommand( parent, msgList ), mIdentity( identity ),
01199     mWin( QGuardedPtr<KMail::Composer>( win ))
01200 {
01201 }
01202 
01203 KMForwardAttachedCommand::KMForwardAttachedCommand( QWidget *parent,
01204            KMMessage * msg, uint identity, KMail::Composer *win )
01205   : KMCommand( parent, msg ), mIdentity( identity ),
01206     mWin( QGuardedPtr< KMail::Composer >( win ))
01207 {
01208 }
01209 
01210 KMCommand::Result KMForwardAttachedCommand::execute()
01211 {
01212   QPtrList<KMMessage> msgList = retrievedMsgs();
01213   KMMessage *fwdMsg = new KMMessage;
01214 
01215   if (msgList.count() >= 2) {
01216     // don't respect X-KMail-Identity headers because they might differ for
01217     // the selected mails
01218     fwdMsg->initHeader(mIdentity);
01219   }
01220   else if (msgList.count() == 1) {
01221     KMMessage *msg = msgList.getFirst();
01222     fwdMsg->initFromMessage(msg);
01223     fwdMsg->setSubject( msg->forwardSubject() );
01224   }
01225 
01226   fwdMsg->setAutomaticFields(true);
01227 
01228   KCursorSaver busy(KBusyPtr::busy());
01229   if (!mWin)
01230     mWin = KMail::makeComposer(fwdMsg, mIdentity);
01231 
01232   // iterate through all the messages to be forwarded
01233   for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
01234     // remove headers that shouldn't be forwarded
01235     msg->removePrivateHeaderFields();
01236     msg->removeHeaderField("BCC");
01237     // set the part
01238     KMMessagePart *msgPart = new KMMessagePart;
01239     msgPart->setTypeStr("message");
01240     msgPart->setSubtypeStr("rfc822");
01241     msgPart->setCharset(msg->charset());
01242     msgPart->setName("forwarded message");
01243     msgPart->setContentDescription(msg->from()+": "+msg->subject());
01244     msgPart->setContentDisposition( "inline" );
01245     // THIS HAS TO BE AFTER setCte()!!!!
01246     QValueList<int> dummy;
01247     msgPart->setBodyAndGuessCte(msg->asString(), dummy, true);
01248     msgPart->setCharset("");
01249 
01250     fwdMsg->link(msg, KMMsgStatusForwarded);
01251     mWin->addAttach(msgPart);
01252   }
01253 
01254   mWin->show();
01255 
01256   return OK;
01257 }
01258 
01259 
01260 KMForwardDigestCommand::KMForwardDigestCommand( QWidget *parent,
01261            const QPtrList<KMMsgBase> &msgList, uint identity, KMail::Composer *win )
01262   : KMCommand( parent, msgList ), mIdentity( identity ),
01263     mWin( QGuardedPtr<KMail::Composer>( win ))
01264 {
01265 }
01266 
01267 KMForwardDigestCommand::KMForwardDigestCommand( QWidget *parent,
01268            KMMessage * msg, uint identity, KMail::Composer *win )
01269   : KMCommand( parent, msg ), mIdentity( identity ),
01270     mWin( QGuardedPtr< KMail::Composer >( win ))
01271 {
01272 }
01273 
01274 KMCommand::Result KMForwardDigestCommand::execute()
01275 {
01276   QPtrList<KMMessage> msgList = retrievedMsgs();
01277 
01278   if ( msgList.count() < 2 )
01279     return Undefined; // must have more than 1 for a digest
01280 
01281   uint id = 0;
01282   KMMessage *fwdMsg = new KMMessage;
01283   KMMessagePart *msgPart = new KMMessagePart;
01284   QString msgPartText;
01285   int msgCnt = 0; // incase there are some we can't forward for some reason
01286 
01287   // dummy header initialization; initialization with the correct identity
01288   // is done below
01289   fwdMsg->initHeader( id );
01290   fwdMsg->setAutomaticFields( true );
01291   fwdMsg->mMsg->Headers().ContentType().CreateBoundary( 1 );
01292   QCString boundary( fwdMsg->mMsg->Headers().ContentType().Boundary().c_str() );
01293   msgPartText = i18n("\nThis is a MIME digest forward. The content of the"
01294                      " message is contained in the attachment(s).\n\n\n");
01295   // iterate through all the messages to be forwarded
01296   for ( KMMessage *msg = msgList.first(); msg; msg = msgList.next() ) {
01297     // set the identity
01298     if ( id == 0 )
01299       id = msg->headerField( "X-KMail-Identity" ).stripWhiteSpace().toUInt();
01300     // set the part header
01301     msgPartText += "--";
01302     msgPartText += QString::fromLatin1( boundary );
01303     msgPartText += "\nContent-Type: MESSAGE/RFC822";
01304     msgPartText += QString( "; CHARSET=%1" ).arg( msg->charset() );
01305     msgPartText += '\n';
01306     DwHeaders dwh;
01307     dwh.MessageId().CreateDefault();
01308     msgPartText += QString( "Content-ID: %1\n" ).arg( dwh.MessageId().AsString().c_str() );
01309     msgPartText += QString( "Content-Description: %1" ).arg( msg->subject() );
01310     if ( !msg->subject().contains( "(fwd)" ) )
01311       msgPartText += " (fwd)";
01312     msgPartText += "\n\n";
01313     // remove headers that shouldn't be forwarded
01314     msg->removePrivateHeaderFields();
01315     msg->removeHeaderField( "BCC" );
01316     // set the part
01317     msgPartText += msg->headerAsString();
01318     msgPartText += '\n';
01319     msgPartText += msg->body();
01320     msgPartText += '\n';     // eot
01321     msgCnt++;
01322     fwdMsg->link( msg, KMMsgStatusForwarded );
01323   }
01324 
01325   if ( id == 0 )
01326     id = mIdentity; // use folder identity if no message had an id set
01327   fwdMsg->initHeader( id );
01328   msgPartText += "--";
01329   msgPartText += QString::fromLatin1( boundary );
01330   msgPartText += "--\n";
01331   QCString tmp;
01332   msgPart->setTypeStr( "MULTIPART" );
01333   tmp.sprintf( "Digest; boundary=\"%s\"", boundary.data() );
01334   msgPart->setSubtypeStr( tmp );
01335   msgPart->setName( "unnamed" );
01336   msgPart->setCte( DwMime::kCte7bit );   // does it have to be 7bit?
01337   msgPart->setContentDescription( QString( "Digest of %1 messages." ).arg( msgCnt ) );
01338   // THIS HAS TO BE AFTER setCte()!!!!
01339   msgPart->setBodyEncoded( QCString( msgPartText.ascii() ) );
01340   KCursorSaver busy( KBusyPtr::busy() );
01341   KMail::Composer * win = KMail::makeComposer( fwdMsg, id );
01342   win->addAttach( msgPart );
01343   win->show();
01344   return OK;
01345 }
01346 
01347 KMRedirectCommand::KMRedirectCommand( QWidget *parent,
01348                                       KMMessage *msg )
01349   : KMCommand( parent, msg )
01350 {
01351 }
01352 
01353 KMCommand::Result KMRedirectCommand::execute()
01354 {
01355   KMMessage *msg = retrievedMessage();
01356   if ( !msg || !msg->codec() )
01357     return Failed;
01358 
01359   RedirectDialog dlg( parentWidget(), "redirect", true,
01360                       kmkernel->msgSender()->sendImmediate() );
01361   if (dlg.exec()==QDialog::Rejected) return Failed;
01362 
01363   KMMessage *newMsg = msg->createRedirect( dlg.to() );
01364   KMFilterAction::sendMDN( msg, KMime::MDN::Dispatched );
01365 
01366   const KMail::MessageSender::SendMethod method = dlg.sendImmediate()
01367     ? KMail::MessageSender::SendImmediate
01368     : KMail::MessageSender::SendLater;
01369   if ( !kmkernel->msgSender()->send( newMsg, method ) ) {
01370     kdDebug(5006) << "KMRedirectCommand: could not redirect message (sending failed)" << endl;
01371     return Failed; // error: couldn't send
01372   }
01373   return OK;
01374 }
01375 
01376 
01377 KMPrintCommand::KMPrintCommand( QWidget *parent,
01378   KMMessage *msg, bool htmlOverride, bool htmlLoadExtOverride,
01379   bool useFixedFont, const QString & encoding )
01380   : KMCommand( parent, msg ), mHtmlOverride( htmlOverride ),
01381     mHtmlLoadExtOverride( htmlLoadExtOverride ),
01382     mUseFixedFont( useFixedFont ), mEncoding( encoding )
01383 {
01384 }
01385 
01386 KMCommand::Result KMPrintCommand::execute()
01387 {
01388   KMReaderWin printWin( 0, 0, 0 );
01389   printWin.setPrinting( true );
01390   printWin.readConfig();
01391   printWin.setHtmlOverride( mHtmlOverride );
01392   printWin.setHtmlLoadExtOverride( mHtmlLoadExtOverride );
01393   printWin.setUseFixedFont( mUseFixedFont );
01394   printWin.setOverrideEncoding( mEncoding );
01395   printWin.setMsg( retrievedMessage(), true );
01396   printWin.printMsg();
01397 
01398   return OK;
01399 }
01400 
01401 
01402 KMSetStatusCommand::KMSetStatusCommand( KMMsgStatus status,
01403   const QValueList<Q_UINT32> &serNums, bool toggle )
01404   : mStatus( status ), mSerNums( serNums ), mToggle( toggle )
01405 {
01406 }
01407 
01408 KMCommand::Result KMSetStatusCommand::execute()
01409 {
01410   QValueListIterator<Q_UINT32> it;
01411   int idx = -1;
01412   KMFolder *folder = 0;
01413   bool parentStatus = false;
01414 
01415   // Toggle actions on threads toggle the whole thread
01416   // depending on the state of the parent.
01417   if (mToggle) {
01418     KMMsgBase *msg;
01419     KMMsgDict::instance()->getLocation( *mSerNums.begin(), &folder, &idx );
01420     if (folder) {
01421       msg = folder->getMsgBase(idx);
01422       if (msg && (msg->status()&mStatus))
01423         parentStatus = true;
01424       else
01425         parentStatus = false;
01426     }
01427   }
01428   QMap< KMFolder*, QValueList<int> > folderMap;
01429   for ( it = mSerNums.begin(); it != mSerNums.end(); ++it ) {
01430     KMMsgDict::instance()->getLocation( *it, &folder, &idx );
01431     if (folder) {
01432       if (mToggle) {
01433         KMMsgBase *msg = folder->getMsgBase(idx);
01434         // check if we are already at the target toggle state
01435         if (msg) {
01436           bool myStatus;
01437           if (msg->status()&mStatus)
01438             myStatus = true;
01439           else
01440             myStatus = false;
01441           if (myStatus != parentStatus)
01442             continue;
01443         }
01444       }
01445       /* Collect the ids for each folder in a separate list and
01446          send them off in one go at the end. */
01447       folderMap[folder].append(idx);
01448     }
01449   }
01450   QMapIterator< KMFolder*, QValueList<int> > it2 = folderMap.begin();
01451   while ( it2 != folderMap.end() ) {
01452      KMFolder *f = it2.key();
01453      f->setStatus( (*it2), mStatus, mToggle );
01454      ++it2;
01455   }
01456   //kapp->dcopClient()->emitDCOPSignal( "unreadCountChanged()", QByteArray() );
01457 
01458   return OK;
01459 }
01460 
01461 
01462 KMFilterCommand::KMFilterCommand( const QCString &field, const QString &value )
01463   : mField( field ), mValue( value )
01464 {
01465 }
01466 
01467 KMCommand::Result KMFilterCommand::execute()
01468 {
01469   kmkernel->filterMgr()->createFilter( mField, mValue );
01470 
01471   return OK;
01472 }
01473 
01474 
01475 KMFilterActionCommand::KMFilterActionCommand( QWidget *parent,
01476                                               const QPtrList<KMMsgBase> &msgList,
01477                                               KMFilter *filter )
01478   : KMCommand( parent, msgList ), mFilter( filter  )
01479 {
01480 }
01481 
01482 KMCommand::Result KMFilterActionCommand::execute()
01483 {
01484   KCursorSaver busy( KBusyPtr::busy() );
01485   QPtrList<KMMessage> msgList = retrievedMsgs();
01486 
01487   for (KMMessage *msg = msgList.first(); msg; msg = msgList.next())
01488     if( msg->parent() )
01489       kmkernel->filterMgr()->tempOpenFolder(msg->parent());
01490 
01491   int msgCount = 0;
01492   int msgCountToFilter = msgList.count();
01493   for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
01494     int diff = msgCountToFilter - ++msgCount;
01495     if ( diff < 10 || !( msgCount % 20 ) || msgCount <= 10 ) {
01496       QString statusMsg = i18n("Filtering message %1 of %2");
01497       statusMsg = statusMsg.arg( msgCount ).arg( msgCountToFilter );
01498       KPIM::BroadcastStatus::instance()->setStatusMsg( statusMsg );
01499       KApplication::kApplication()->eventLoop()->processEvents( QEventLoop::ExcludeUserInput, 50 );
01500     }
01501     msg->setTransferInProgress(false);
01502     int filterResult = kmkernel->filterMgr()->process(msg, mFilter);
01503     if (filterResult == 2) {
01504       // something went horribly wrong (out of space?)
01505       perror("Critical error");
01506       kmkernel->emergencyExit( i18n("Not enough free disk space?" ));
01507     }
01508     msg->setTransferInProgress(true);
01509   }
01510 
01511   return OK;
01512 }
01513 
01514 
01515 KMMetaFilterActionCommand::KMMetaFilterActionCommand( KMFilter *filter,
01516                                                       KMHeaders *headers,
01517                                                       KMMainWidget *main )
01518     : QObject( main ),
01519       mFilter( filter ), mHeaders( headers ), mMainWidget( main )
01520 {
01521 }
01522 
01523 void KMMetaFilterActionCommand::start()
01524 {
01525   if (ActionScheduler::isEnabled() ) {
01526     // use action scheduler
01527     KMFilterMgr::FilterSet set = KMFilterMgr::All;
01528     QValueList<KMFilter*> filters;
01529     filters.append( mFilter );
01530     ActionScheduler *scheduler = new ActionScheduler( set, filters, mHeaders );
01531     scheduler->setAlwaysMatch( true );
01532     scheduler->setAutoDestruct( true );
01533 
01534     int contentX, contentY;
01535     HeaderItem *nextItem = mHeaders->prepareMove( &contentX, &contentY );
01536     QPtrList<KMMsgBase> msgList = *mHeaders->selectedMsgs(true);
01537     mHeaders->finalizeMove( nextItem, contentX, contentY );
01538 
01539     for (KMMsgBase *msg = msgList.first(); msg; msg = msgList.next())
01540       scheduler->execFilters( msg );
01541   } else {
01542     KMCommand *filterCommand = new KMFilterActionCommand( mMainWidget,
01543     *mHeaders->selectedMsgs(), mFilter);
01544     filterCommand->start();
01545     int contentX, contentY;
01546     HeaderItem *item = mHeaders->prepareMove( &contentX, &contentY );
01547     mHeaders->finalizeMove( item, contentX, contentY );
01548   }
01549 }
01550 
01551 FolderShortcutCommand::FolderShortcutCommand( KMMainWidget *mainwidget,
01552                                               KMFolder *folder )
01553     : mMainWidget( mainwidget ), mFolder( folder ), mAction( 0 )
01554 {
01555 }
01556 
01557 
01558 FolderShortcutCommand::~FolderShortcutCommand()
01559 {
01560   if ( mAction ) mAction->unplugAll();
01561   delete mAction;
01562 }
01563 
01564 void FolderShortcutCommand::start()
01565 {
01566   mMainWidget->slotSelectFolder( mFolder );
01567 }
01568 
01569 void FolderShortcutCommand::setAction( KAction* action )
01570 {
01571   mAction = action;
01572 }
01573 
01574 KMMailingListFilterCommand::KMMailingListFilterCommand( QWidget *parent,
01575                                                         KMMessage *msg )
01576   : KMCommand( parent, msg )
01577 {
01578 }
01579 
01580 KMCommand::Result KMMailingListFilterCommand::execute()
01581 {
01582   QCString name;
01583   QString value;
01584   KMMessage *msg = retrievedMessage();
01585   if (!msg)
01586     return Failed;
01587 
01588   if ( !MailingList::name( msg, name, value ).isEmpty() ) {
01589     kmkernel->filterMgr()->createFilter( name, value );
01590     return OK;
01591   }
01592   else
01593     return Failed;
01594 }
01595 
01596 
01597 void KMMenuCommand::folderToPopupMenu(bool move,
01598   QObject *receiver, KMMenuToFolder *aMenuToFolder, QPopupMenu *menu )
01599 {
01600   while ( menu->count() )
01601   {
01602     QPopupMenu *popup = menu->findItem( menu->idAt( 0 ) )->popup();
01603     if (popup)
01604       delete popup;
01605     else
01606       menu->removeItemAt( 0 );
01607   }
01608 
01609   if (!kmkernel->imapFolderMgr()->dir().first() &&
01610       !kmkernel->dimapFolderMgr()->dir().first())
01611   { // only local folders
01612     makeFolderMenu( &kmkernel->folderMgr()->dir(), move,
01613                     receiver, aMenuToFolder, menu );
01614   } else {
01615     // operate on top-level items
01616     QPopupMenu* subMenu = new QPopupMenu(menu);
01617     makeFolderMenu( &kmkernel->folderMgr()->dir(),
01618                     move, receiver, aMenuToFolder, subMenu );
01619     menu->insertItem( i18n( "Local Folders" ), subMenu );
01620     KMFolderDir* fdir = &kmkernel->imapFolderMgr()->dir();
01621     for (KMFolderNode *node = fdir->first(); node; node = fdir->next()) {
01622       if (node->isDir())
01623         continue;
01624       subMenu = new QPopupMenu(menu);
01625       makeFolderMenu( node, move, receiver, aMenuToFolder, subMenu );
01626       menu->insertItem( node->label(), subMenu );
01627     }
01628     fdir = &kmkernel->dimapFolderMgr()->dir();
01629     for (KMFolderNode *node = fdir->first(); node; node = fdir->next()) {
01630       if (node->isDir())
01631         continue;
01632       subMenu = new QPopupMenu(menu);
01633       makeFolderMenu( node, move, receiver, aMenuToFolder, subMenu );
01634       menu->insertItem( node->label(), subMenu );
01635     }
01636   }
01637 }
01638 
01639 void KMMenuCommand::makeFolderMenu(KMFolderNode* node, bool move,
01640   QObject *receiver, KMMenuToFolder *aMenuToFolder, QPopupMenu *menu )
01641 {
01642   // connect the signals
01643   if (move)
01644   {
01645     disconnect(menu, SIGNAL(activated(int)), receiver,
01646            SLOT(moveSelectedToFolder(int)));
01647     connect(menu, SIGNAL(activated(int)), receiver,
01648              SLOT(moveSelectedToFolder(int)));
01649   } else {
01650     disconnect(menu, SIGNAL(activated(int)), receiver,
01651            SLOT(copySelectedToFolder(int)));
01652     connect(menu, SIGNAL(activated(int)), receiver,
01653              SLOT(copySelectedToFolder(int)));
01654   }
01655 
01656   KMFolder *folder = 0;
01657   KMFolderDir *folderDir = 0;
01658   if (node->isDir()) {
01659     folderDir = static_cast<KMFolderDir*>(node);
01660   } else {
01661     folder = static_cast<KMFolder*>(node);
01662     folderDir = folder->child();
01663   }
01664 
01665   if (folder && !folder->noContent())
01666   {
01667     int menuId;
01668     if (move)
01669       menuId = menu->insertItem(i18n("Move to This Folder"));
01670     else
01671       menuId = menu->insertItem(i18n("Copy to This Folder"));
01672     aMenuToFolder->insert( menuId, folder );
01673     menu->setItemEnabled( menuId, !folder->isReadOnly() );
01674     menu->insertSeparator();
01675   }
01676 
01677   if (!folderDir)
01678     return;
01679 
01680   for (KMFolderNode *it = folderDir->first(); it; it = folderDir->next() ) {
01681     if (it->isDir())
01682       continue;
01683     KMFolder *child = static_cast<KMFolder*>(it);
01684     QString label = child->label();
01685     label.replace("&","&&");
01686     if (child->child() && child->child()->first()) {
01687       // descend
01688       QPopupMenu *subMenu = new QPopupMenu(menu, "subMenu");
01689       makeFolderMenu( child, move, receiver,
01690                       aMenuToFolder, subMenu );
01691       menu->insertItem( label, subMenu );
01692     } else {
01693       // insert an item
01694       int menuId = menu->insertItem( label );
01695       aMenuToFolder->insert( menuId, child );
01696       menu->setItemEnabled( menuId, !child->isReadOnly() );
01697     }
01698   }
01699   return;
01700 }
01701 
01702 
01703 KMCopyCommand::KMCopyCommand( KMFolder* destFolder,
01704                               const QPtrList<KMMsgBase> &msgList )
01705 :mDestFolder( destFolder ), mMsgList( msgList )
01706 {
01707   setDeletesItself( true );
01708 }
01709 
01710 KMCopyCommand::KMCopyCommand( KMFolder* destFolder, KMMessage * msg )
01711   :mDestFolder( destFolder )
01712 {
01713   setDeletesItself( true );
01714   mMsgList.append( &msg->toMsgBase() );
01715 }
01716 
01717 KMCommand::Result KMCopyCommand::execute()
01718 {
01719   KMMsgBase *msgBase;
01720   KMMessage *msg, *newMsg;
01721   int idx = -1;
01722   bool isMessage;
01723   QPtrList<KMMessage> list;
01724   QPtrList<KMMessage> localList;
01725 
01726   if (mDestFolder && mDestFolder->open() != 0)
01727   {
01728     deleteLater();
01729     return Failed;
01730   }
01731 
01732   KCursorSaver busy(KBusyPtr::busy());
01733 
01734   mWaitingForMsgs.clear();
01735   for (msgBase = mMsgList.first(); msgBase; msgBase = mMsgList.next() )
01736   {
01737     KMFolder *srcFolder = msgBase->parent();
01738     if (isMessage = msgBase->isMessage())
01739     {
01740       msg = static_cast<KMMessage*>(msgBase);
01741     } else {
01742       idx = srcFolder->find(msgBase);
01743       assert(idx != -1);
01744       msg = srcFolder->getMsg(idx);
01745     }
01746 
01747     if (srcFolder && mDestFolder &&
01748         (srcFolder->folderType()== KMFolderTypeImap) &&
01749         (mDestFolder->folderType() == KMFolderTypeImap) &&
01750         (static_cast<KMFolderImap*>(srcFolder->storage())->account() ==
01751          static_cast<KMFolderImap*>(mDestFolder->storage())->account()))
01752     {
01753       // imap => imap with same account
01754       list.append(msg);
01755     } else {
01756       newMsg = new KMMessage;
01757       newMsg->setComplete(msg->isComplete());
01758       // make sure the attachment state is only calculated when it's complete
01759       if (!newMsg->isComplete())
01760         newMsg->setReadyToShow(false);
01761       newMsg->fromString(msg->asString());
01762       newMsg->setStatus(msg->status());
01763 
01764       if (srcFolder && !newMsg->isComplete())
01765       {
01766         // imap => others
01767         mWaitingForMsgs.append( msg->getMsgSerNum() );
01768         disconnect(mDestFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
01769             this, SLOT(slotMsgAdded(KMFolder*, Q_UINT32)));
01770         connect(mDestFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
01771             this, SLOT(slotMsgAdded(KMFolder*, Q_UINT32)));
01772         newMsg->setParent(msg->parent());
01773         FolderJob *job = srcFolder->createJob(newMsg);
01774         job->setCancellable( false );
01775         connect(job, SIGNAL(messageRetrieved(KMMessage*)),
01776                 mDestFolder, SLOT(reallyAddCopyOfMsg(KMMessage*)));
01777         job->start();
01778       } else {
01779         // local => others
01780         localList.append(newMsg);
01781       }
01782     }
01783 
01784     if (srcFolder && !isMessage && list.isEmpty())
01785     {
01786       assert(idx != -1);
01787       srcFolder->unGetMsg( idx );
01788     }
01789 
01790   } // end for
01791 
01792   bool deleteNow = false;
01793   if (!localList.isEmpty())
01794   {
01795     QValueList<int> index;
01796     mDestFolder->addMsg( localList, index );
01797     for ( QValueListIterator<int> it = index.begin(); it != index.end(); ++it ) {
01798       mDestFolder->unGetMsg( *it );
01799     }
01800     if ( mDestFolder->folderType() == KMFolderTypeImap ) {
01801       if ( mWaitingForMsgs.isEmpty() ) {
01802         // wait for the end of the copy before closing the folder
01803         KMFolderImap *imapDestFolder = static_cast<KMFolderImap*>(mDestFolder->storage());
01804         connect( imapDestFolder, SIGNAL( folderComplete( KMFolderImap*, bool ) ),
01805             this, SLOT( slotFolderComplete() ) );
01806       }
01807     } else {
01808       deleteNow = true; // we're done
01809     }
01810   }
01811 
01812 //TODO: Get rid of the other cases just use this one for all types of folder
01813 //TODO: requires adding copyMsg and getFolder methods to KMFolder.h
01814   if (!list.isEmpty())
01815   {
01816     // copy the message(s); note: the list is empty afterwards!
01817     KMFolderImap *imapDestFolder = static_cast<KMFolderImap*>(mDestFolder->storage());
01818     connect( imapDestFolder, SIGNAL( folderComplete( KMFolderImap*, bool ) ),
01819         this, SLOT( slotFolderComplete() ) );
01820     imapDestFolder->copyMsg(list);
01821     imapDestFolder->getFolder();
01822   }
01823 
01824   // only close the folder and delete the job if we're done
01825   // otherwise this is done in slotMsgAdded or slotFolderComplete
01826   if ( deleteNow )
01827   {
01828     mDestFolder->close();
01829     deleteLater();
01830   }
01831 
01832   return OK;
01833 }
01834 
01835 void KMCopyCommand::slotMsgAdded( KMFolder*, Q_UINT32 serNum )
01836 {
01837   mWaitingForMsgs.remove( serNum );
01838   if ( mWaitingForMsgs.isEmpty() )
01839   {
01840     mDestFolder->close();
01841     deleteLater();
01842   }
01843 }
01844 
01845 void KMCopyCommand::slotFolderComplete()
01846 {
01847   mDestFolder->close();
01848   deleteLater();
01849 }
01850 
01851 
01852 KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
01853                               const QPtrList<KMMsgBase> &msgList)
01854   : mDestFolder( destFolder ), mMsgList( msgList ), mProgressItem( 0 )
01855 {
01856 }
01857 
01858 KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
01859                               KMMessage *msg )
01860   : mDestFolder( destFolder ), mProgressItem( 0 )
01861 {
01862   mMsgList.append( &msg->toMsgBase() );
01863 }
01864 
01865 KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
01866                               KMMsgBase *msgBase )
01867   : mDestFolder( destFolder ), mProgressItem( 0 )
01868 {
01869   mMsgList.append( msgBase );
01870 }
01871 
01872 KMMoveCommand::KMMoveCommand( Q_UINT32 )
01873   : mProgressItem( 0 )
01874 {
01875 }
01876 
01877 KMCommand::Result KMMoveCommand::execute()
01878 {
01879   setEmitsCompletedItself( true );
01880   setDeletesItself( true );
01881   typedef QMap< KMFolder*, QPtrList<KMMessage>* > FolderToMessageListMap;
01882   FolderToMessageListMap folderDeleteList;
01883 
01884   if (mDestFolder && mDestFolder->open() != 0) {
01885     completeMove( Failed );
01886     return Failed;
01887   }
01888   KCursorSaver busy(KBusyPtr::busy());
01889 
01890   // TODO set SSL state according to source and destfolder connection?
01891   Q_ASSERT( !mProgressItem );
01892   mProgressItem =
01893      ProgressManager::createProgressItem (
01894          "move"+ProgressManager::getUniqueID(),
01895          mDestFolder ? i18n( "Moving messages" ) : i18n( "Deleting messages" ) );
01896   connect( mProgressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
01897            this, SLOT( slotMoveCanceled() ) );
01898 
01899   KMMessage *msg;
01900   KMMsgBase *msgBase;
01901   int rc = 0;
01902   int index;
01903   QPtrList<KMMessage> list;
01904   int undoId = -1;
01905   mCompleteWithAddedMsg = false;
01906 
01907   if (mDestFolder) {
01908     connect (mDestFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
01909              this, SLOT(slotMsgAddedToDestFolder(KMFolder*, Q_UINT32)));
01910     for ( msgBase=mMsgList.first(); msgBase; msgBase=mMsgList.next() ) {
01911       mLostBoys.append( msgBase->getMsgSerNum() );
01912     }
01913   }
01914   mProgressItem->setTotalItems( mMsgList.count() );
01915 
01916   for (msgBase=mMsgList.first(); msgBase && !rc; msgBase=mMsgList.next()) {
01917     KMFolder *srcFolder = msgBase->parent();
01918     if (srcFolder == mDestFolder)
01919       continue;
01920     bool undo = msgBase->enableUndo();
01921     int idx = srcFolder->find(msgBase);
01922     assert(idx != -1);
01923     if ( msgBase->isMessage() ) {
01924       msg = static_cast<KMMessage*>(msgBase);
01925     } else {
01926       msg = srcFolder->getMsg(idx);
01927     }
01928 
01929     if ( msg && msg->transferInProgress() &&
01930          srcFolder->folderType() == KMFolderTypeImap )
01931     {
01932       // cancel the download
01933       msg->setTransferInProgress( false, true );
01934       static_cast<KMFolderImap*>(srcFolder->storage())->ignoreJobsForMessage( msg );
01935     }
01936 
01937     if (mDestFolder) {
01938       if (mDestFolder->folderType() == KMFolderTypeImap) {
01939         /* If we are moving to an imap folder, connect to it's completed
01940          * signal so we notice when all the mails should have showed up in it
01941          * but haven't for some reason. */
01942         KMFolderImap *imapFolder = static_cast<KMFolderImap*> ( mDestFolder->storage() );
01943         disconnect (imapFolder, SIGNAL(folderComplete( KMFolderImap*, bool )),
01944                  this, SLOT(slotImapFolderCompleted( KMFolderImap*, bool )));
01945 
01946         connect (imapFolder, SIGNAL(folderComplete( KMFolderImap*, bool )),
01947                  this, SLOT(slotImapFolderCompleted( KMFolderImap*, bool )));
01948         list.append(msg);
01949       } else {
01950         // We are moving to a local folder.
01951         if ( srcFolder->folderType() == KMFolderTypeImap )
01952         {
01953           // do not complete here but wait until all messages are transferred
01954           mCompleteWithAddedMsg = true;
01955         }
01956         rc = mDestFolder->moveMsg(msg, &index);
01957         if (rc == 0 && index != -1) {
01958           KMMsgBase *mb = mDestFolder->unGetMsg( mDestFolder->count() - 1 );
01959           if (undo && mb)
01960           {
01961             if ( undoId == -1 )
01962               undoId = kmkernel->undoStack()->newUndoAction( srcFolder, mDestFolder );
01963             kmkernel->undoStack()->addMsgToAction( undoId, mb->getMsgSerNum() );
01964           }
01965         } else if (rc != 0) {
01966           // Something  went wrong. Stop processing here, it is likely that the
01967           // other moves would fail as well.
01968           completeMove( Failed );
01969           return Failed;
01970         }
01971       }
01972     } else {
01973       // really delete messages that are already in the trash folder or if
01974       // we are really, really deleting, not just moving to trash
01975       if (srcFolder->folderType() == KMFolderTypeImap) {
01976         if (!folderDeleteList[srcFolder])
01977           folderDeleteList[srcFolder] = new QPtrList<KMMessage>;
01978         folderDeleteList[srcFolder]->append( msg );
01979       } else {
01980         srcFolder->removeMsg(idx);
01981         delete msg;
01982       }
01983     }
01984   }
01985   if (!list.isEmpty() && mDestFolder) {
01986     // will be completed with folderComplete signal
01987     mDestFolder->moveMsg(list, &index);
01988   } else {
01989     FolderToMessageListMap::Iterator it;
01990     for ( it = folderDeleteList.begin(); it != folderDeleteList.end(); ++it ) {
01991       it.key()->removeMsg(*it.data());
01992       delete it.data();
01993     }
01994     if ( !mCompleteWithAddedMsg ) {
01995       // imap folders will be completed in slotMsgAddedToDestFolder
01996       completeMove( OK );
01997     }
01998   }
01999 
02000   return OK;
02001 }
02002 
02003 void KMMoveCommand::slotImapFolderCompleted(KMFolderImap* imapFolder, bool success)
02004 {
02005   disconnect (imapFolder, SIGNAL(folderComplete( KMFolderImap*, bool )),
02006       this, SLOT(slotImapFolderCompleted( KMFolderImap*, bool )));
02007   if ( success ) {
02008     // the folder was checked successfully but we were still called, so check
02009     // if we are still waiting for messages to show up. If so, uidValidity
02010     // changed, or something else went wrong. Clean up.
02011 
02012     /* Unfortunately older UW imap servers change uid validity for each put job.
02013      * Yes, it is really that broken. *sigh* So we cannot report error here, I guess. */
02014     if ( !mLostBoys.isEmpty() ) {
02015       kdDebug(5006) <<  "### Not all moved messages reported back that they were " << endl
02016                     <<  "### added to the target folder. Did uidValidity change? " << endl;
02017     }
02018     completeMove( OK );
02019   } else {
02020     // Should we inform the user here or leave that to the caller?
02021     completeMove( Failed );
02022   }
02023 }
02024 
02025 void KMMoveCommand::slotMsgAddedToDestFolder(KMFolder *folder, Q_UINT32 serNum)
02026 {
02027   if ( folder != mDestFolder || mLostBoys.find( serNum ) == mLostBoys.end() ) {
02028     //kdDebug(5006) << "KMMoveCommand::msgAddedToDestFolder different "
02029     //                 "folder or invalid serial number." << endl;
02030     return;
02031   }
02032   mLostBoys.remove(serNum);
02033   if ( mLostBoys.isEmpty() ) {
02034     // we are done. All messages transferred to the host succesfully
02035     disconnect (mDestFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
02036              this, SLOT(slotMsgAddedToDestFolder(KMFolder*, Q_UINT32)));
02037     if (mDestFolder && mDestFolder->folderType() != KMFolderTypeImap) {
02038       mDestFolder->sync();
02039     }
02040     if ( mCompleteWithAddedMsg ) {
02041       completeMove( OK );
02042     }
02043   } else {
02044     if ( mProgressItem ) {
02045       mProgressItem->incCompletedItems();
02046       mProgressItem->updateProgress();
02047     }
02048   }
02049 }
02050 
02051 void KMMoveCommand::completeMove( Result result )
02052 {
02053   if ( mDestFolder )
02054     mDestFolder->close();
02055   while ( !mOpenedFolders.empty() ) {
02056     KMFolder *folder = mOpenedFolders.back();
02057     mOpenedFolders.pop_back();
02058     folder->close();
02059   }
02060   if ( mProgressItem ) {
02061     mProgressItem->setComplete();
02062     mProgressItem = 0;
02063   }
02064   setResult( result );
02065   emit completed( this );
02066   deleteLater();
02067 }
02068 
02069 void KMMoveCommand::slotMoveCanceled()
02070 {
02071   completeMove( Canceled );
02072 }
02073 
02074 // srcFolder doesn't make much sense for searchFolders
02075 KMDeleteMsgCommand::KMDeleteMsgCommand( KMFolder* srcFolder,
02076   const QPtrList<KMMsgBase> &msgList )
02077 :KMMoveCommand( findTrashFolder( srcFolder ), msgList)
02078 {
02079   srcFolder->open();
02080   mOpenedFolders.push_back( srcFolder );
02081 }
02082 
02083 KMDeleteMsgCommand::KMDeleteMsgCommand( KMFolder* srcFolder, KMMessage * msg )
02084 :KMMoveCommand( findTrashFolder( srcFolder ), msg)
02085 {
02086   srcFolder->open();
02087   mOpenedFolders.push_back( srcFolder );
02088 }
02089 
02090 KMDeleteMsgCommand::KMDeleteMsgCommand( Q_UINT32 sernum )
02091 :KMMoveCommand( sernum )
02092 {
02093   KMFolder *srcFolder = 0;
02094   int idx;
02095   KMMsgDict::instance()->getLocation( sernum, &srcFolder, &idx );
02096   if ( srcFolder ) {
02097     KMMsgBase *msg = srcFolder->getMsgBase( idx );
02098     srcFolder->open();
02099     mOpenedFolders.push_back( srcFolder );
02100     addMsg( msg );
02101   }
02102   setDestFolder( findTrashFolder( srcFolder ) );
02103 }
02104 
02105 KMFolder * KMDeleteMsgCommand::findTrashFolder( KMFolder * folder )
02106 {
02107   KMFolder* trash = folder->trashFolder();
02108   if( !trash )
02109     trash = kmkernel->trashFolder();
02110   if( trash != folder )
02111     return trash;
02112   return 0;
02113 }
02114 
02115 
02116 KMUrlClickedCommand::KMUrlClickedCommand( const KURL &url, uint identity,
02117   KMReaderWin *readerWin, bool htmlPref, KMMainWidget *mainWidget )
02118   :mUrl( url ), mIdentity( identity ), mReaderWin( readerWin ),
02119    mHtmlPref( htmlPref ), mMainWidget( mainWidget )
02120 {
02121 }
02122 
02123 KMCommand::Result KMUrlClickedCommand::execute()
02124 {
02125   KMMessage* msg;
02126 
02127   if (mUrl.protocol() == "mailto")
02128   {
02129     msg = new KMMessage;
02130     msg->initHeader(mIdentity);
02131     msg->setCharset("utf-8");
02132     msg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
02133     QString query=mUrl.query();
02134     while (!query.isEmpty()) {
02135       QString queryPart;
02136       int secondQuery = query.find('?',1);
02137       if (secondQuery != -1)
02138         queryPart = query.left(secondQuery);
02139       else
02140         queryPart = query;
02141       query = query.mid(queryPart.length());
02142 
02143       if (queryPart.left(9) == "?subject=")
02144         msg->setSubject( KURL::decode_string(queryPart.mid(9)) );
02145       else if (queryPart.left(6) == "?body=")
02146         // It is correct to convert to latin1() as URL should not contain
02147         // anything except ascii.
02148         msg->setBody( KURL::decode_string(queryPart.mid(6)).latin1() );
02149       else if (queryPart.left(4) == "?cc=")
02150         msg->setCc( KURL::decode_string(queryPart.mid(4)) );
02151     }
02152 
02153     KMail::Composer * win = KMail::makeComposer( msg, mIdentity );
02154     win->setCharset("", TRUE);
02155     win->show();
02156   }
02157   else if ( mUrl.protocol() == "im" )
02158   {
02159     kmkernel->imProxy()->chatWithContact( mUrl.path() );
02160   }
02161   else if ((mUrl.protocol() == "http") || (mUrl.protocol() == "https") ||
02162            (mUrl.protocol() == "ftp")  || (mUrl.protocol() == "file")  ||
02163            (mUrl.protocol() == "ftps") || (mUrl.protocol() == "sftp" ) ||
02164            (mUrl.protocol() == "help") || (mUrl.protocol() == "vnc")   ||
02165            (mUrl.protocol() == "smb")  || (mUrl.protocol() == "fish")  ||
02166            (mUrl.protocol() == "news"))
02167   {
02168     KPIM::BroadcastStatus::instance()->setStatusMsg( i18n("Opening URL..."));
02169     KMimeType::Ptr mime = KMimeType::findByURL( mUrl );
02170     if (mime->name() == "application/x-desktop" ||
02171         mime->name() == "application/x-executable" ||
02172         mime->name() == "application/x-msdos-program" ||
02173         mime->name() == "application/x-shellscript" )
02174     {
02175       if (KMessageBox::warningYesNo( 0, i18n( "<qt>Do you really want to execute <b>%1</b>?</qt>" )
02176         .arg( mUrl.prettyURL() ), QString::null, i18n("Execute"), KStdGuiItem::cancel() ) != KMessageBox::Yes)
02177         return Canceled;
02178     }
02179     (void) new KRun( mUrl );
02180   }
02181   else
02182     return Failed;
02183 
02184   return OK;
02185 }
02186 
02187 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, KMMessage *msg )
02188   : KMCommand( parent, msg ), mImplicitAttachments( true ), mEncoded( false )
02189 {
02190 }
02191 
02192 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, const QPtrList<KMMsgBase>& msgs )
02193   : KMCommand( parent, msgs ), mImplicitAttachments( true ), mEncoded( false )
02194 {
02195 }
02196 
02197 KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, QPtrList<partNode>& attachments,
02198                                                     KMMessage *msg, bool encoded )
02199   : KMCommand( parent ), mImplicitAttachments( false ), mEncoded( encoded )
02200 {
02201   for ( QPtrListIterator<partNode> it( attachments ); it.current(); ++it ) {
02202     mAttachmentMap.insert( it.current(), msg );
02203   }
02204 }
02205 
02206 KMCommand::Result KMSaveAttachmentsCommand::execute()
02207 {
02208   setEmitsCompletedItself( true );
02209   if ( mImplicitAttachments ) {
02210     QPtrList<KMMessage> msgList = retrievedMsgs();
02211     KMMessage *msg;
02212     for ( QPtrListIterator<KMMessage> itr( msgList );
02213           ( msg = itr.current() );
02214           ++itr ) {
02215       partNode *rootNode = partNode::fromMessage( msg );
02216       for ( partNode *child = rootNode; child;
02217             child = child->firstChild() ) {
02218         for ( partNode *node = child; node; node = node->nextSibling() ) {
02219           if ( node->type() != DwMime::kTypeMultipart )
02220             mAttachmentMap.insert( node, msg );
02221         }
02222       }
02223     }
02224   }
02225   setDeletesItself( true );
02226   // load all parts
02227   KMLoadPartsCommand *command = new KMLoadPartsCommand( mAttachmentMap );
02228   connect( command, SIGNAL( partsRetrieved() ),
02229            this, SLOT( slotSaveAll() ) );
02230   command->start();
02231 
02232   return OK;
02233 }
02234 
02235 void KMSaveAttachmentsCommand::slotSaveAll()
02236 {
02237   // now that all message parts have been retrieved, remove all parts which
02238   // don't represent an attachment if they were not explicitely passed in the
02239   // c'tor
02240   if ( mImplicitAttachments ) {
02241     for ( PartNodeMessageMap::iterator it = mAttachmentMap.begin();
02242           it != mAttachmentMap.end(); ) {
02243       // only body parts which have a filename or a name parameter (except for
02244       // the root node for which name is set to the message's subject) are
02245       // considered attachments
02246       if ( it.key()->msgPart().fileName().stripWhiteSpace().isEmpty() &&
02247            ( it.key()->msgPart().name().stripWhiteSpace().isEmpty() ||
02248              !it.key()->parentNode() ) ) {
02249         PartNodeMessageMap::iterator delIt = it;
02250         ++it;
02251         mAttachmentMap.remove( delIt );
02252       }
02253       else
02254         ++it;
02255     }
02256     if ( mAttachmentMap.isEmpty() ) {
02257       KMessageBox::information( 0, i18n("Found no attachments to save.") );
02258       setResult( OK ); // The user has already been informed.
02259       emit completed( this );
02260       deleteLater();
02261       return;
02262     }
02263   }
02264 
02265   KURL url, dirUrl;
02266   if ( mAttachmentMap.count() > 1 ) {
02267     // get the dir
02268     dirUrl = KDirSelectDialog::selectDirectory( QString::null, false,
02269                                                 parentWidget(),
02270                                                 i18n("Save Attachments To") );
02271     if ( !dirUrl.isValid() ) {
02272       setResult( Canceled );
02273       emit completed( this );
02274       deleteLater();
02275       return;
02276     }
02277 
02278     // we may not get a slash-terminated url out of KDirSelectDialog
02279     dirUrl.adjustPath( 1 );
02280   }
02281   else {
02282     // only one item, get the desired filename
02283     partNode *node = mAttachmentMap.begin().key();
02284     // replace all ':' with '_' because ':' isn't allowed on FAT volumes
02285     QString s =
02286       node->msgPart().fileName().stripWhiteSpace().replace( ':', '_' );
02287     if ( s.isEmpty() )
02288       s = node->msgPart().name().stripWhiteSpace().replace( ':', '_' );
02289     if ( s.isEmpty() )
02290       s = i18n("filename for an unnamed attachment", "attachment.1");
02291     url = KFileDialog::getSaveURL( s, QString::null, parentWidget(),
02292                                    QString::null );
02293     if ( url.isEmpty() ) {
02294       setResult( Canceled );
02295       emit completed( this );
02296       deleteLater();
02297       return;
02298     }
02299   }
02300 
02301   QMap< QString, int > renameNumbering;
02302 
02303   Result globalResult = OK;
02304   int unnamedAtmCount = 0;
02305   for ( PartNodeMessageMap::const_iterator it = mAttachmentMap.begin();
02306         it != mAttachmentMap.end();
02307         ++it ) {
02308     KURL curUrl;
02309     if ( !dirUrl.isEmpty() ) {
02310       curUrl = dirUrl;
02311       QString s =
02312         it.key()->msgPart().fileName().stripWhiteSpace().replace( ':', '_' );
02313       if ( s.isEmpty() )
02314         s = it.key()->msgPart().name().stripWhiteSpace().replace( ':', '_' );
02315       if ( s.isEmpty() ) {
02316         ++unnamedAtmCount;
02317         s = i18n("filename for the %1-th unnamed attachment",
02318                  "attachment.%1")
02319             .arg( unnamedAtmCount );
02320       }
02321       curUrl.setFileName( s );
02322     } else {
02323       curUrl = url;
02324     }
02325 
02326     if ( !curUrl.isEmpty() ) {
02327 
02328      // Rename the file if we have already saved one with the same name:
02329      // try appending a number before extension (e.g. "pic.jpg" => "pic_2.jpg")
02330      QString origFile = curUrl.fileName();
02331      QString file = origFile;
02332 
02333      while ( renameNumbering.contains(file) ) {
02334        file = origFile;
02335        int num = renameNumbering[file] + 1;
02336        int dotIdx = file.findRev('.');
02337        file = file.insert( (dotIdx>=0) ? dotIdx : file.length(), QString("_") + QString::number(num) );
02338      }
02339      curUrl.setFileName(file);
02340 
02341      // Increment the counter for both the old and the new filename
02342      if ( !renameNumbering.contains(origFile))
02343          renameNumbering[origFile] = 1;
02344      else
02345          renameNumbering[origFile]++;
02346 
02347      if ( file != origFile ) {
02348         if ( !renameNumbering.contains(file))
02349             renameNumbering[file] = 1;
02350         else
02351             renameNumbering[file]++;
02352      }
02353 
02354 
02355       if ( KIO::NetAccess::exists( curUrl, false, parentWidget() ) ) {
02356         if ( KMessageBox::warningContinueCancel( parentWidget(),
02357               i18n( "A file named %1 already exists. Do you want to overwrite it?" )
02358               .arg( curUrl.fileName() ),
02359               i18n( "File Already Exists" ), i18n("&Overwrite") ) == KMessageBox::Cancel) {
02360           continue;
02361         }
02362       }
02363       // save
02364       const Result result = saveItem( it.key(), curUrl );
02365       if ( result != OK )
02366         globalResult = result;
02367     }
02368   }
02369   setResult( globalResult );
02370   emit completed( this );
02371   deleteLater();
02372 }
02373 
02374 KMCommand::Result KMSaveAttachmentsCommand::saveItem( partNode *node,
02375                                                       const KURL& url )
02376 {
02377   bool bSaveEncrypted = false;
02378   bool bEncryptedParts = node->encryptionState() != KMMsgNotEncrypted;
02379   if( bEncryptedParts )
02380     if( KMessageBox::questionYesNo( parentWidget(),
02381           i18n( "The part %1 of the message is encrypted. Do you want to keep the encryption when saving?" ).
02382           arg( url.fileName() ),
02383           i18n( "KMail Question" ), i18n("Keep Encryption"), i18n("Do Not Keep") ) ==
02384         KMessageBox::Yes )
02385       bSaveEncrypted = true;
02386 
02387   bool bSaveWithSig = true;
02388   if( node->signatureState() != KMMsgNotSigned )
02389     if( KMessageBox::questionYesNo( parentWidget(),
02390           i18n( "The part %1 of the message is signed. Do you want to keep the signature when saving?" ).
02391           arg( url.fileName() ),
02392           i18n( "KMail Question" ), i18n("Keep Signature"), i18n("Do Not Keep") ) !=
02393         KMessageBox::Yes )
02394       bSaveWithSig = false;
02395 
02396   QByteArray data;
02397   if ( mEncoded )
02398   {
02399     // This does not decode the Message Content-Transfer-Encoding
02400     // but saves the _original_ content of the message part
02401     QCString cstr( node->msgPart().body() );
02402     data = cstr;
02403     data.resize(data.size() - 1);
02404   }
02405   else
02406   {
02407     if( bSaveEncrypted || !bEncryptedParts) {
02408       partNode *dataNode = node;
02409       QCString rawReplyString;
02410       bool gotRawReplyString = false;
02411       if( !bSaveWithSig ) {
02412         if( DwMime::kTypeMultipart == node->type() &&
02413             DwMime::kSubtypeSigned == node->subType() ){
02414           // carefully look for the part that is *not* the signature part:
02415           if( node->findType( DwMime::kTypeApplication,
02416                 DwMime::kSubtypePgpSignature,
02417                 TRUE, false ) ){
02418             dataNode = node->findTypeNot( DwMime::kTypeApplication,
02419                 DwMime::kSubtypePgpSignature,
02420                 TRUE, false );
02421           }else if( node->findType( DwMime::kTypeApplication,
02422                 DwMime::kSubtypePkcs7Mime,
02423                 TRUE, false ) ){
02424             dataNode = node->findTypeNot( DwMime::kTypeApplication,
02425                 DwMime::kSubtypePkcs7Mime,
02426                 TRUE, false );
02427           }else{
02428             dataNode = node->findTypeNot( DwMime::kTypeMultipart,
02429                 DwMime::kSubtypeUnknown,
02430                 TRUE, false );
02431           }
02432     }else{
02433       ObjectTreeParser otp( 0, 0, false, false, false );
02434 
02435       // process this node and all it's siblings and descendants
02436       dataNode->setProcessed( false, true );
02437       otp.parseObjectTree( dataNode );
02438 
02439       rawReplyString = otp.rawReplyString();
02440       gotRawReplyString = true;
02441         }
02442       }
02443       QByteArray cstr = gotRawReplyString
02444                          ? rawReplyString
02445                          : dataNode->msgPart().bodyDecodedBinary();
02446       data = cstr;
02447       size_t size = cstr.size();
02448       if ( dataNode->msgPart().type() == DwMime::kTypeText ) {
02449         // convert CRLF to LF before writing text attachments to disk
02450         size = KMail::Util::crlf2lf( cstr.data(), size );
02451       }
02452       data.resize( size );
02453     }
02454   }
02455   QDataStream ds;
02456   QFile file;
02457   KTempFile tf;
02458   tf.setAutoDelete( true );
02459   if ( url.isLocalFile() )
02460   {
02461     // save directly
02462     file.setName( url.path() );
02463     if ( !file.open( IO_WriteOnly ) )
02464     {
02465       KMessageBox::error( parentWidget(),
02466           i18n( "%2 is detailed error description",
02467             "Could not write the file %1:\n%2" )
02468           .arg( file.name() )
02469           .arg( QString::fromLocal8Bit( strerror( errno ) ) ),
02470           i18n( "KMail Error" ) );
02471       return Failed;
02472     }
02473     fchmod( file.handle(), S_IRUSR | S_IWUSR );
02474     ds.setDevice( &file );
02475   } else
02476   {
02477     // tmp file for upload
02478     ds.setDevice( tf.file() );
02479   }
02480 
02481   ds.writeRawBytes( data.data(), data.size() );
02482   if ( !url.isLocalFile() )
02483   {
02484     tf.close();
02485     if ( !KIO::NetAccess::upload( tf.name(), url, parentWidget() ) )
02486     {
02487       KMessageBox::error( parentWidget(),
02488           i18n( "Could not write the file %1." )
02489           .arg( url.path() ),
02490           i18n( "KMail Error" ) );
02491       return Failed;
02492     }
02493   } else
02494     file.close();
02495   return OK;
02496 }
02497 
02498 KMLoadPartsCommand::KMLoadPartsCommand( QPtrList<partNode>& parts, KMMessage *msg )
02499   : mNeedsRetrieval( 0 )
02500 {
02501   for ( QPtrListIterator<partNode> it( parts ); it.current(); ++it ) {
02502     mPartMap.insert( it.current(), msg );
02503   }
02504 }
02505 
02506 KMLoadPartsCommand::KMLoadPartsCommand( partNode *node, KMMessage *msg )
02507   : mNeedsRetrieval( 0 )
02508 {
02509   mPartMap.insert( node, msg );
02510 }
02511 
02512 KMLoadPartsCommand::KMLoadPartsCommand( PartNodeMessageMap& partMap )
02513   : mNeedsRetrieval( 0 ), mPartMap( partMap )
02514 {
02515 }
02516 
02517 void KMLoadPartsCommand::slotStart()
02518 {
02519   for ( PartNodeMessageMap::const_iterator it = mPartMap.begin();
02520         it != mPartMap.end();
02521         ++it ) {
02522     if ( !it.key()->msgPart().isComplete() &&
02523          !it.key()->msgPart().partSpecifier().isEmpty() ) {
02524       // incomplete part, so retrieve it first
02525       ++mNeedsRetrieval;
02526       KMFolder* curFolder = it.data()->parent();
02527       if ( curFolder ) {
02528         FolderJob *job =
02529           curFolder->createJob( it.data(), FolderJob::tGetMessage,
02530                                 0, it.key()->msgPart().partSpecifier() );
02531         job->setCancellable( false );
02532         connect( job, SIGNAL(messageUpdated(KMMessage*, QString)),
02533                  this, SLOT(slotPartRetrieved(KMMessage*, QString)) );
02534         job->start();
02535       } else
02536         kdWarning(5006) << "KMLoadPartsCommand - msg has no parent" << endl;
02537     }
02538   }
02539   if ( mNeedsRetrieval == 0 )
02540     execute();
02541 }
02542 
02543 void KMLoadPartsCommand::slotPartRetrieved( KMMessage *msg,
02544                                             QString partSpecifier )
02545 {
02546   DwBodyPart *part =
02547     msg->findDwBodyPart( msg->getFirstDwBodyPart(), partSpecifier );
02548   if ( part ) {
02549     // update the DwBodyPart in the partNode
02550     for ( PartNodeMessageMap::const_iterator it = mPartMap.begin();
02551           it != mPartMap.end();
02552           ++it ) {
02553       if ( it.key()->dwPart()->partId() == part->partId() )
02554         it.key()->setDwPart( part );
02555     }
02556   } else
02557     kdWarning(5006) << "KMLoadPartsCommand::slotPartRetrieved - could not find bodypart!" << endl;
02558   --mNeedsRetrieval;
02559   if ( mNeedsRetrieval == 0 )
02560     execute();
02561 }
02562 
02563 KMCommand::Result KMLoadPartsCommand::execute()
02564 {
02565   emit partsRetrieved();
02566   setResult( OK );
02567   emit completed( this );
02568   deleteLater();
02569   return OK;
02570 }
02571 
02572 KMResendMessageCommand::KMResendMessageCommand( QWidget *parent,
02573    KMMessage *msg )
02574   :KMCommand( parent, msg )
02575 {
02576 }
02577 
02578 KMCommand::Result KMResendMessageCommand::execute()
02579 {
02580   KMMessage *msg = retrievedMessage();
02581 
02582   KMMessage *newMsg = new KMMessage(*msg);
02583   newMsg->setCharset(msg->codec()->mimeName());
02584   // the message needs a new Message-Id
02585   newMsg->removeHeaderField( "Message-Id" );
02586   newMsg->setParent( 0 );
02587 
02588   // adds the new date to the message
02589   newMsg->removeHeaderField( "Date" );
02590 
02591   KMail::Composer * win = KMail::makeComposer();
02592   win->setMsg(newMsg, false, true);
02593   win->show();
02594 
02595   return OK;
02596 }
02597 
02598 KMMailingListCommand::KMMailingListCommand( QWidget *parent, KMFolder *folder )
02599   : KMCommand( parent ), mFolder( folder )
02600 {
02601 }
02602 
02603 KMCommand::Result KMMailingListCommand::execute()
02604 {
02605   KURL::List lst = urls();
02606   QString handler = ( mFolder->mailingList().handler() == MailingList::KMail )
02607     ? "mailto" : "https";
02608 
02609   KMCommand *command = 0;
02610   for ( KURL::List::Iterator itr = lst.begin(); itr != lst.end(); ++itr ) {
02611     if ( handler == (*itr).protocol() ) {
02612       command = new KMUrlClickedCommand( *itr, mFolder->identity(), 0, false );
02613     }
02614   }
02615   if ( !command && !lst.empty() ) {
02616     command =
02617       new KMUrlClickedCommand( lst.first(), mFolder->identity(), 0, false );
02618   }
02619   if ( command ) {
02620     connect( command, SIGNAL( completed( KMCommand * ) ),
02621              this, SLOT( commandCompleted( KMCommand * ) ) );
02622     setDeletesItself( true );
02623     setEmitsCompletedItself( true );
02624     command->start();
02625     return OK;
02626   }
02627   return Failed;
02628 }
02629 
02630 void KMMailingListCommand::commandCompleted( KMCommand *command )
02631 {
02632   setResult( command->result() );
02633   emit completed( this );
02634   deleteLater();
02635 }
02636 
02637 KMMailingListPostCommand::KMMailingListPostCommand( QWidget *parent, KMFolder *folder )
02638   : KMMailingListCommand( parent, folder )
02639 {
02640 }
02641 KURL::List KMMailingListPostCommand::urls() const
02642 {
02643   return mFolder->mailingList().postURLS();
02644 }
02645 
02646 KMMailingListSubscribeCommand::KMMailingListSubscribeCommand( QWidget *parent, KMFolder *folder )
02647   : KMMailingListCommand( parent, folder )
02648 {
02649 }
02650 KURL::List KMMailingListSubscribeCommand::urls() const
02651 {
02652   return mFolder->mailingList().subscribeURLS();
02653 }
02654 
02655 KMMailingListUnsubscribeCommand::KMMailingListUnsubscribeCommand( QWidget *parent, KMFolder *folder )
02656   : KMMailingListCommand( parent, folder )
02657 {
02658 }
02659 KURL::List KMMailingListUnsubscribeCommand::urls() const
02660 {
02661   return mFolder->mailingList().unsubscribeURLS();
02662 }
02663 
02664 KMMailingListArchivesCommand::KMMailingListArchivesCommand( QWidget *parent, KMFolder *folder )
02665   : KMMailingListCommand( parent, folder )
02666 {
02667 }
02668 KURL::List KMMailingListArchivesCommand::urls() const
02669 {
02670   return mFolder->mailingList().archiveURLS();
02671 }
02672 
02673 KMMailingListHelpCommand::KMMailingListHelpCommand( QWidget *parent, KMFolder *folder )
02674   : KMMailingListCommand( parent, folder )
02675 {
02676 }
02677 KURL::List KMMailingListHelpCommand::urls() const
02678 {
02679   return mFolder->mailingList().helpURLS();
02680 }
02681 
02682 KMIMChatCommand::KMIMChatCommand( const KURL &url, KMMessage *msg )
02683   :mUrl( url ), mMessage( msg )
02684 {
02685 }
02686 
02687 KMCommand::Result KMIMChatCommand::execute()
02688 {
02689   kdDebug( 5006 ) << k_funcinfo << " URL is: " << mUrl << endl;
02690   QString addr = KMMessage::decodeMailtoUrl( mUrl.path() );
02691   // find UID for mail address
02692   KABC::AddressBook *addressBook = KABC::StdAddressBook::self( true );
02693   KABC::AddresseeList addressees = addressBook->findByEmail( KPIM::getEmailAddress( addr ) ) ;
02694 
02695   // start chat
02696   if( addressees.count() == 1 ) {
02697     kmkernel->imProxy()->chatWithContact( addressees[0].uid() );
02698     return OK;
02699   }
02700   else
02701   {
02702     kdDebug( 5006 ) << "Didn't find exactly one addressee, couldn't tell who to chat to for that email address.  Count = " << addressees.count() << endl;
02703 
02704     QString apology;
02705     if ( addressees.isEmpty() )
02706       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." );
02707     else
02708     {
02709       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." );
02710       QStringList nameList;
02711       KABC::AddresseeList::const_iterator it = addressees.begin();
02712       KABC::AddresseeList::const_iterator end = addressees.end();
02713       for ( ; it != end; ++it )
02714       {
02715           nameList.append( (*it).realName() );
02716       }
02717       QString names = nameList.join( QString::fromLatin1( ",\n" ) );
02718       apology = apology.arg( names );
02719     }
02720 
02721     KMessageBox::sorry( parentWidget(), apology );
02722     return Failed;
02723   }
02724 }
02725 
02726 KMHandleAttachmentCommand::KMHandleAttachmentCommand( partNode* node,
02727      KMMessage* msg, int atmId, const QString& atmName,
02728      AttachmentAction action, KService::Ptr offer, QWidget* parent )
02729 : KMCommand( parent ), mNode( node ), mMsg( msg ), mAtmId( atmId ), mAtmName( atmName ),
02730   mAction( action ), mOffer( offer ), mJob( 0 )
02731 {
02732 }
02733 
02734 void KMHandleAttachmentCommand::slotStart()
02735 {
02736   if ( !mNode->msgPart().isComplete() )
02737   {
02738     // load the part
02739     kdDebug(5006) << "load part" << endl;
02740     KMLoadPartsCommand *command = new KMLoadPartsCommand( mNode, mMsg );
02741     connect( command, SIGNAL( partsRetrieved() ),
02742         this, SLOT( slotPartComplete() ) );
02743     command->start();
02744   } else
02745   {
02746     execute();
02747   }
02748 }
02749 
02750 void KMHandleAttachmentCommand::slotPartComplete()
02751 {
02752   execute();
02753 }
02754 
02755 KMCommand::Result KMHandleAttachmentCommand::execute()
02756 {
02757   switch( mAction )
02758   {
02759     case Open:
02760       atmOpen();
02761       break;
02762     case OpenWith:
02763       atmOpenWith();
02764       break;
02765     case View:
02766       atmView();
02767       break;
02768     case Save:
02769       atmSave();
02770       break;
02771     case Properties:
02772       atmProperties();
02773       break;
02774     case ChiasmusEncrypt:
02775       atmEncryptWithChiasmus();
02776       return Undefined;
02777       break;
02778     default:
02779       kdDebug(5006) << "unknown action " << mAction << endl;
02780       break;
02781   }
02782   setResult( OK );
02783   emit completed( this );
02784   deleteLater();
02785   return OK;
02786 }
02787 
02788 QString KMHandleAttachmentCommand::createAtmFileLink() const
02789 {
02790   QFileInfo atmFileInfo( mAtmName );
02791 
02792   if ( atmFileInfo.size() == 0 )
02793   {
02794     kdDebug(5006) << k_funcinfo << "rewriting attachment" << endl;
02795     // there is something wrong so write the file again
02796     QByteArray data = mNode->msgPart().bodyDecodedBinary();
02797     size_t size = data.size();
02798     if ( mNode->msgPart().type() == DwMime::kTypeText && size) {
02799       // convert CRLF to LF before writing text attachments to disk
02800       size = KMail::Util::crlf2lf( data.data(), size );
02801     }
02802     KPIM::kBytesToFile( data.data(), size, mAtmName, false, false, false );
02803   }
02804 
02805   KTempFile *linkFile = new KTempFile( locateLocal("tmp", atmFileInfo.fileName() +"_["),
02806                           "]."+ atmFileInfo.extension() );
02807 
02808   linkFile->setAutoDelete(true);
02809   QString linkName = linkFile->name();
02810   delete linkFile;
02811 
02812   if ( ::link(QFile::encodeName( mAtmName ), QFile::encodeName( linkName )) == 0 ) {
02813     return linkName; // success
02814   }
02815   return QString::null;
02816 }
02817 
02818 KService::Ptr KMHandleAttachmentCommand::getServiceOffer()
02819 {
02820   KMMessagePart& msgPart = mNode->msgPart();
02821   const QString contentTypeStr =
02822     ( msgPart.typeStr() + '/' + msgPart.subtypeStr() ).lower();
02823 
02824   if ( contentTypeStr == "text/x-vcard" ) {
02825     atmView();
02826     return 0;
02827   }
02828   // determine the MIME type of the attachment
02829   KMimeType::Ptr mimetype;
02830   // prefer the value of the Content-Type header
02831   mimetype = KMimeType::mimeType( contentTypeStr );
02832   if ( mimetype->name() == "application/octet-stream" ) {
02833     // consider the filename if Content-Type is application/octet-stream
02834     mimetype = KMimeType::findByPath( mAtmName, 0, true /* no disk access */ );
02835   }
02836   if ( ( mimetype->name() == "application/octet-stream" )
02837        && msgPart.isComplete() ) {
02838     // consider the attachment's contents if neither the Content-Type header
02839     // nor the filename give us a clue
02840     mimetype = KMimeType::findByFileContent( mAtmName );
02841   }
02842   return KServiceTypeProfile::preferredService( mimetype->name(), "Application" );
02843 }
02844 
02845 void KMHandleAttachmentCommand::atmOpen()
02846 {
02847   if ( !mOffer )
02848     mOffer = getServiceOffer();
02849   if ( !mOffer ) {
02850     kdDebug(5006) << k_funcinfo << "got no offer" << endl;
02851     return;
02852   }
02853 
02854   KURL::List lst;
02855   KURL url;
02856   bool autoDelete = true;
02857   QString fname = createAtmFileLink();
02858 
02859   if ( fname.isNull() ) {
02860     autoDelete = false;
02861     fname = mAtmName;
02862   }
02863 
02864   url.setPath( fname );
02865   lst.append( url );
02866   if ( (KRun::run( *mOffer, lst, autoDelete ) <= 0) && autoDelete ) {
02867       QFile::remove(url.path());
02868   }
02869 }
02870 
02871 void KMHandleAttachmentCommand::atmOpenWith()
02872 {
02873   KURL::List lst;
02874   KURL url;
02875   bool autoDelete = true;
02876   QString fname = createAtmFileLink();
02877 
02878   if ( fname.isNull() ) {
02879     autoDelete = false;
02880     fname = mAtmName;
02881   }
02882 
02883   url.setPath( fname );
02884   lst.append( url );
02885   if ( (! KRun::displayOpenWithDialog(lst, autoDelete)) && autoDelete ) {
02886     QFile::remove( url.path() );
02887   }
02888 }
02889 
02890 void KMHandleAttachmentCommand::atmView()
02891 {
02892   // we do not handle this ourself
02893   emit showAttachment( mAtmId, mAtmName );
02894 }
02895 
02896 void KMHandleAttachmentCommand::atmSave()
02897 {
02898   QPtrList<partNode> parts;
02899   parts.append( mNode );
02900   // save, do not leave encoded
02901   KMSaveAttachmentsCommand *command =
02902     new KMSaveAttachmentsCommand( 0, parts, mMsg, false );
02903   command->start();
02904 }
02905 
02906 void KMHandleAttachmentCommand::atmProperties()
02907 {
02908   KMMsgPartDialogCompat dlg( parentWidget() , 0, true );
02909   KMMessagePart& msgPart = mNode->msgPart();
02910   dlg.setMsgPart( &msgPart );
02911   dlg.exec();
02912 }
02913 
02914 void KMHandleAttachmentCommand::atmEncryptWithChiasmus()
02915 {
02916   const partNode * node = mNode;
02917   Q_ASSERT( node );
02918   if ( !node )
02919     return;
02920 
02921   // FIXME: better detection of mimetype??
02922   if ( !mAtmName.endsWith( ".xia", false ) )
02923     return;
02924 
02925   const Kleo::CryptoBackend::Protocol * chiasmus =
02926     Kleo::CryptoBackendFactory::instance()->protocol( "Chiasmus" );
02927   Q_ASSERT( chiasmus );
02928   if ( !chiasmus )
02929     return;
02930 
02931   const STD_NAMESPACE_PREFIX auto_ptr<Kleo::SpecialJob> listjob( chiasmus->specialJob( "x-obtain-keys", QMap<QString,QVariant>() ) );
02932   if ( !listjob.get() ) {
02933     const QString msg = i18n( "Chiasmus backend does not offer the "
02934                               "\"x-obtain-keys\" function. Please report this bug." );
02935     KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
02936     return;
02937   }
02938 
02939   if ( listjob->exec() ) {
02940     listjob->showErrorDialog( parentWidget(), i18n( "Chiasmus Backend Error" ) );
02941     return;
02942   }
02943 
02944   const QVariant result = listjob->property( "result" );
02945   if ( result.type() != QVariant::StringList ) {
02946     const QString msg = i18n( "Unexpected return value from Chiasmus backend: "
02947                               "The \"x-obtain-keys\" function did not return a "
02948                               "string list. Please report this bug." );
02949     KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
02950     return;
02951   }
02952 
02953   const QStringList keys = result.toStringList();
02954   if ( keys.empty() ) {
02955     const QString msg = i18n( "No keys have been found. Please check that a "
02956                               "valid key path has been set in the Chiasmus "
02957                               "configuration." );
02958     KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
02959     return;
02960   }
02961 
02962   ChiasmusKeySelector selectorDlg( parentWidget(), i18n( "Chiasmus Decryption Key Selection" ),
02963                                    keys, GlobalSettings::chiasmusDecryptionKey(),
02964                                    GlobalSettings::chiasmusDecryptionOptions() );
02965   if ( selectorDlg.exec() != QDialog::Accepted )
02966     return;
02967 
02968   GlobalSettings::setChiasmusDecryptionOptions( selectorDlg.options() );
02969   GlobalSettings::setChiasmusDecryptionKey( selectorDlg.key() );
02970   assert( !GlobalSettings::chiasmusDecryptionKey().isEmpty() );
02971 
02972   Kleo::SpecialJob * job = chiasmus->specialJob( "x-decrypt", QMap<QString,QVariant>() );
02973   if ( !job ) {
02974     const QString msg = i18n( "Chiasmus backend does not offer the "
02975                               "\"x-decrypt\" function. Please report this bug." );
02976     KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
02977     return;
02978   }
02979 
02980   const QByteArray input = node->msgPart().bodyDecodedBinary();
02981 
02982   if ( !job->setProperty( "key", GlobalSettings::chiasmusDecryptionKey() ) ||
02983        !job->setProperty( "options", GlobalSettings::chiasmusDecryptionOptions() ) ||
02984        !job->setProperty( "input", input ) ) {
02985     const QString msg = i18n( "The \"x-decrypt\" function does not accept "
02986                               "the expected parameters. Please report this bug." );
02987     KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
02988     return;
02989   }
02990 
02991   setDeletesItself( true ); // the job below is async, we have to cleanup ourselves
02992   if ( job->start() ) {
02993     job->showErrorDialog( parentWidget(), i18n( "Chiasmus Decryption Error" ) );
02994     return;
02995   }
02996 
02997   mJob = job;
02998   connect( job, SIGNAL(result(const GpgME::Error&,const QVariant&)),
02999            this, SLOT(slotAtmDecryptWithChiasmusResult(const GpgME::Error&,const QVariant&)) );
03000 }
03001 
03002 // return true if we should proceed, false if we should abort
03003 static bool checkOverwrite( const KURL& url, bool& overwrite, QWidget* w )
03004 {
03005   if ( KIO::NetAccess::exists( url, false /*dest*/, w ) ) {
03006     if ( KMessageBox::Cancel ==
03007          KMessageBox::warningContinueCancel(
03008                                             w,
03009                                             i18n( "A file named \"%1\" already exists. "
03010                                                   "Are you sure you want to overwrite it?" ).arg( url.prettyURL() ),
03011                                             i18n( "Overwrite File?" ),
03012                                             i18n( "&Overwrite" ) ) )
03013       return false;
03014     overwrite = true;
03015   }
03016   return true;
03017 }
03018 
03019 static const QString chomp( const QString & base, const QString & suffix, bool cs ) {
03020   return base.endsWith( suffix, cs ) ? base.left( base.length() - suffix.length() ) : base ;
03021 }
03022 
03023 void KMHandleAttachmentCommand::slotAtmDecryptWithChiasmusResult( const GpgME::Error & err, const QVariant & result )
03024 {
03025   LaterDeleterWithCommandCompletion d( this );
03026   if ( !mJob )
03027     return;
03028   Q_ASSERT( mJob == sender() );
03029   if ( mJob != sender() )
03030     return;
03031   Kleo::Job * job = mJob;
03032   mJob = 0;
03033   if ( err.isCanceled() )
03034     return;
03035   if ( err ) {
03036     job->showErrorDialog( parentWidget(), i18n( "Chiasmus Decryption Error" ) );
03037     return;
03038   }
03039 
03040   if ( result.type() != QVariant::ByteArray ) {
03041     const QString msg = i18n( "Unexpected return value from Chiasmus backend: "
03042                               "The \"x-decrypt\" function did not return a "
03043                               "byte array. Please report this bug." );
03044     KMessageBox::error( parentWidget(), msg, i18n( "Chiasmus Backend Error" ) );
03045     return;
03046   }
03047 
03048   const KURL url = KFileDialog::getSaveURL( chomp( mAtmName, ".xia", false ), QString::null, parentWidget() );
03049   if ( url.isEmpty() )
03050     return;
03051 
03052   bool overwrite = false;
03053   if ( !checkOverwrite( url, overwrite, parentWidget() ) )
03054     return;
03055 
03056   d.setDisabled( true ); // we got this far, don't delete yet
03057   KIO::Job * uploadJob = KIO::storedPut( result.toByteArray(), url, -1, overwrite, false /*resume*/ );
03058   uploadJob->setWindow( parentWidget() );
03059   connect( uploadJob, SIGNAL(result(KIO::Job*)),
03060            this, SLOT(slotAtmDecryptWithChiasmusUploadResult(KIO::Job*)) );
03061 }
03062 
03063 void KMHandleAttachmentCommand::slotAtmDecryptWithChiasmusUploadResult( KIO::Job * job )
03064 {
03065   if ( job->error() )
03066     job->showErrorDialog();
03067   LaterDeleterWithCommandCompletion d( this );
03068   d.setResult( OK );
03069 }
03070 
03071 #include "kmcommands.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys