// -*- mode: C++; c-file-style: "gnu" -*-
// kmcommands
// (c) 2002 Don Sanders <sanders@kde.org>
// License: GPL
//
// This file implements various "command" classes. These command classes
// are based on the command design pattern.
//
// Historically various operations were implemented as slots of KMMainWin.
// This proved inadequate as KMail has multiple top level windows
// (KMMainWin, KMReaderMainWin, KMFldSearch, KMComposeWin) that may
// benefit from using these operations. It is desirable that these
// classes can operate without depending on or altering the state of
// a KMMainWin, in fact it is possible no KMMainWin object even exists.
//
// Now these operations have been rewritten as KMCommand based classes,
// making them independent of KMMainWin.
//
// The base command class KMCommand is async, which is a difference
// from the conventional command pattern. As normal derived classes implement
// the execute method, but client classes call start() instead of
// calling execute() directly. start() initiates async operations,
// and on completion of these operations calls execute() and then deletes
// the command. (So the client must not construct commands on the stack).
//
// The type of async operation supported by KMCommand is retrieval
// of messages from an IMAP server.

#include "kmcommands.h"

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <errno.h>
#include <mimelib/enum.h>
#include <mimelib/field.h>
#include <mimelib/mimepp.h>
#include <mimelib/string.h>
#include <kapplication.h>
#include <dcopclient.h>

#include <qtextcodec.h>

#include <libemailfunctions/email.h>
#include <kdebug.h>
#include <kfiledialog.h>
#include <kio/netaccess.h>
#include <kabc/stdaddressbook.h>
#include <kabc/addresseelist.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kparts/browserextension.h>
#include <kprogress.h>
#include <krun.h>
#include <kbookmarkmanager.h>
#include <kstandarddirs.h>
#include <ktempfile.h>
#include <kimproxy.h>
#include <kuserprofile.h>
#include "actionscheduler.h"
using KMail::ActionScheduler;
#include "mailinglist-magic.h"
#include "kmaddrbook.h"
#include <kaddrbook.h>
#include "kmcomposewin.h"
#include "kmfiltermgr.h"
#include "kmfoldermbox.h"
#include "kmfolderimap.h"
#include "kmfoldermgr.h"
#include "kmheaders.h"
#include "headeritem.h"
#include "kmmainwidget.h"
#include "kmmsgdict.h"
#include "kmsender.h"
#include "kmmsgpartdlg.h"
#include "undostack.h"
#include "kcursorsaver.h"
#include "partNode.h"
#include "objecttreeparser.h"
using KMail::ObjectTreeParser;
using KMail::FolderJob;
#include "mailsourceviewer.h"
using KMail::MailSourceViewer;
#include "kmreadermainwin.h"
#include "secondarywindow.h"
using KMail::SecondaryWindow;
#include <redirectdialog.h>
using KMail::RedirectDialog;
#include <libkdepim/kfileio.h>

#include "progressmanager.h"
using KPIM::ProgressManager;
using KPIM::ProgressItem;
#include <kmime_mdn.h>
using namespace KMime;

#include "broadcaststatus.h"

#include "kmcommands.moc"

KMCommand::KMCommand( QWidget *parent )
  : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
    mEmitsCompletedItself( false ), mParent( parent )
{
}

KMCommand::KMCommand( QWidget *parent, const QPtrList<KMMsgBase> &msgList )
  : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
    mEmitsCompletedItself( false ), mParent( parent ), mMsgList( msgList )
{
}

KMCommand::KMCommand( QWidget *parent, KMMsgBase *msgBase )
  : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
    mEmitsCompletedItself( false ), mParent( parent )
{
  mMsgList.append( msgBase );
}

KMCommand::KMCommand( QWidget *parent, KMMessage *msg )
  : mProgressDialog( 0 ), mResult( Undefined ), mDeletesItself( false ),
    mEmitsCompletedItself( false ), mParent( parent )
{
  mMsgList.append( &msg->toMsgBase() );
}

KMCommand::~KMCommand()
{
  QValueListIterator<QGuardedPtr<KMFolder> > fit;
  for ( fit = mFolders.begin(); fit != mFolders.end(); ++fit ) {
    if (!(*fit))
      continue;
    (*fit)->close();
  }
}

KMCommand::Result KMCommand::result()
{
  if ( mResult == Undefined )
    kdDebug(5006) << k_funcinfo << "mResult is Undefined" << endl;
  return mResult;
}

void KMCommand::start()
{
  QTimer::singleShot( 0, this, SLOT( slotStart() ) );
}


const QPtrList<KMMessage> KMCommand::retrievedMsgs() const
{
  return mRetrievedMsgs;
}

KMMessage *KMCommand::retrievedMessage() const
{
  return mRetrievedMsgs.getFirst();
}

QWidget *KMCommand::parentWidget() const
{
  return mParent;
}

int KMCommand::mCountJobs = 0;

void KMCommand::slotStart()
{
  connect( this, SIGNAL( messagesTransfered( KMCommand::Result ) ),
           this, SLOT( slotPostTransfer( KMCommand::Result ) ) );
  kmkernel->filterMgr()->ref();

  if (mMsgList.find(0) != -1) {
      emit messagesTransfered( Failed );
      return;
  }

  if ((mMsgList.count() == 1) &&
      (mMsgList.getFirst()->isMessage()) &&
      (mMsgList.getFirst()->parent() == 0))
  {
    // Special case of operating on message that isn't in a folder
    mRetrievedMsgs.append((KMMessage*)mMsgList.getFirst());
    emit messagesTransfered( OK );
    return;
  }

  for (KMMsgBase *mb = mMsgList.first(); mb; mb = mMsgList.next())
    if (!mb->parent()) {
      emit messagesTransfered( Failed );
      return;
    } else {
      mFolders.append( mb->parent() );
      mb->parent()->open();
    }

  // transfer the selected messages first
  transferSelectedMsgs();
}

void KMCommand::slotPostTransfer( KMCommand::Result result )
{
  disconnect( this, SIGNAL( messagesTransfered( KMCommand::Result ) ),
              this, SLOT( slotPostTransfer( KMCommand::Result ) ) );
  if ( result == OK )
    result = execute();
  mResult = result;
  QPtrListIterator<KMMessage> it( mRetrievedMsgs );
  KMMessage* msg;
  while ( (msg = it.current()) != 0 )
  {
    ++it;
    if (msg->parent())
      msg->setTransferInProgress(false);
  }
  kmkernel->filterMgr()->deref();
  if ( !emitsCompletedItself() )
    emit completed( this );
  if ( !deletesItself() )
    delete this;
}

void KMCommand::transferSelectedMsgs()
{
  // make sure no other transfer is active
  if (KMCommand::mCountJobs > 0) {
    emit messagesTransfered( Failed );
    return;
  }

  bool complete = true;
  KMCommand::mCountJobs = 0;
  mCountMsgs = 0;
  mRetrievedMsgs.clear();
  mCountMsgs = mMsgList.count();
  uint totalSize = 0;
  // the KProgressDialog for the user-feedback. Only enable it if it's needed.
  // For some commands like KMSetStatusCommand it's not needed. Note, that
  // for some reason the KProgressDialog eats the MouseReleaseEvent (if a
  // command is executed after the MousePressEvent), cf. bug #71761.
  if ( mCountMsgs > 0 ) {
    mProgressDialog = new KProgressDialog(mParent, "transferProgress",
      i18n("Please wait"),
      i18n("Please wait while the message is transferred",
        "Please wait while the %n messages are transferred", mMsgList.count()),
      true);
    mProgressDialog->setMinimumDuration(1000);
  }
  for (KMMsgBase *mb = mMsgList.first(); mb; mb = mMsgList.next())
  {
    // check if all messages are complete
    KMMessage *thisMsg = 0;
    if ( mb->isMessage() )
      thisMsg = static_cast<KMMessage*>(mb);
    else
    {
      KMFolder *folder = mb->parent();
      int idx = folder->find(mb);
      if (idx < 0) continue;
      thisMsg = folder->getMsg(idx);
    }
    if (!thisMsg) continue;
    if ( thisMsg->transferInProgress() &&
         thisMsg->parent()->folderType() == KMFolderTypeImap )
    {
      thisMsg->setTransferInProgress( false, true );
      thisMsg->parent()->ignoreJobsForMessage( thisMsg );
    }

    if ( thisMsg->parent() && !thisMsg->isComplete() &&
         ( !mProgressDialog || !mProgressDialog->wasCancelled() ) )
    {
      kdDebug(5006)<<"### INCOMPLETE\n";
      // the message needs to be transferred first
      complete = false;
      KMCommand::mCountJobs++;
      FolderJob *job = thisMsg->parent()->createJob(thisMsg);
      job->setCancellable( false );
      totalSize += thisMsg->msgSizeServer();
      // emitted when the message was transferred successfully
      connect(job, SIGNAL(messageRetrieved(KMMessage*)),
              this, SLOT(slotMsgTransfered(KMMessage*)));
      // emitted when the job is destroyed
      connect(job, SIGNAL(finished()),
              this, SLOT(slotJobFinished()));
      connect(job, SIGNAL(progress(unsigned long, unsigned long)),
              this, SLOT(slotProgress(unsigned long, unsigned long)));
      // msg musn't be deleted
      thisMsg->setTransferInProgress(true);
      job->start();
    } else {
      thisMsg->setTransferInProgress(true);
      mRetrievedMsgs.append(thisMsg);
    }
  }

  if (complete)
  {
    delete mProgressDialog;
    emit messagesTransfered( OK );
  } else {
    // wait for the transfer and tell the progressBar the necessary steps
    if ( mProgressDialog ) {
      connect(mProgressDialog, SIGNAL(cancelClicked()),
              this, SLOT(slotTransferCancelled()));
      mProgressDialog->progressBar()->setTotalSteps(totalSize);
    }
  }
}

void KMCommand::slotMsgTransfered(KMMessage* msg)
{
  if ( mProgressDialog && mProgressDialog->wasCancelled() ) {
    emit messagesTransfered( Canceled );
    return;
  }

  // save the complete messages
  mRetrievedMsgs.append(msg);
}

void KMCommand::slotProgress( unsigned long done, unsigned long /*total*/ )
{
  mProgressDialog->progressBar()->setProgress( done );
}

void KMCommand::slotJobFinished()
{
  // the job is finished (with / without error)
  KMCommand::mCountJobs--;

  if ( mProgressDialog && mProgressDialog->wasCancelled() ) return;

  if ( (mCountMsgs - static_cast<int>(mRetrievedMsgs.count())) > KMCommand::mCountJobs )
  {
    // the message wasn't retrieved before => error
    if ( mProgressDialog )
      mProgressDialog->hide();
    slotTransferCancelled();
    return;
  }
  // update the progressbar
  if ( mProgressDialog ) {
    mProgressDialog->setLabel(i18n("Please wait while the message is transferred",
          "Please wait while the %n messages are transferred", KMCommand::mCountJobs));
  }
  if (KMCommand::mCountJobs == 0)
  {
    // all done
    delete mProgressDialog;
    emit messagesTransfered( OK );
  }
}

void KMCommand::slotTransferCancelled()
{
  // kill the pending jobs
  QValueListIterator<QGuardedPtr<KMFolder> > fit;
  for ( fit = mFolders.begin(); fit != mFolders.end(); ++fit ) {
    if (!(*fit))
      continue;
    KMFolder *folder = *fit;
    KMFolderImap *imapFolder = dynamic_cast<KMFolderImap*>(folder);
    if (imapFolder && imapFolder->account()) {
      imapFolder->account()->killAllJobs();
    }
  }

  KMCommand::mCountJobs = 0;
  mCountMsgs = 0;
  // unget the transfered messages
  QPtrListIterator<KMMessage> it( mRetrievedMsgs );
  KMMessage* msg;
  while ( (msg = it.current()) != 0 )
  {
    KMFolder *folder = msg->parent();
    ++it;
    if (!folder)
      continue;
    msg->setTransferInProgress(false);
    int idx = folder->find(msg);
    if (idx > 0) folder->unGetMsg(idx);
  }
  mRetrievedMsgs.clear();
  emit messagesTransfered( Canceled );
}

void KMCommand::keepFolderOpen( KMFolder *folder )
{
  folder->open();
  mFolders.append( folder );
}

KMMailtoComposeCommand::KMMailtoComposeCommand( const KURL &url,
                                                KMMessage *msg )
  :mUrl( url ), mMessage( msg )
{
}

KMCommand::Result KMMailtoComposeCommand::execute()
{
  KMComposeWin *win;
  KMMessage *msg = new KMMessage;
  uint id = 0;

  if ( mMessage && mMessage->parent() )
    id = mMessage->parent()->identity();

  msg->initHeader(id);
  msg->setCharset("utf-8");
  msg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );

  win = new KMComposeWin(msg, id);
  win->setCharset("", TRUE);
  win->setFocusToSubject();
  win->show();

  return OK;
}


KMMailtoReplyCommand::KMMailtoReplyCommand( QWidget *parent,
   const KURL &url, KMMessage *msg, const QString &selection )
  :KMCommand( parent, msg ), mUrl( url ), mSelection( selection  )
{
}

KMCommand::Result KMMailtoReplyCommand::execute()
{
  //TODO : consider factoring createReply into this method.
  KMMessage *msg = retrievedMessage();
  KMComposeWin *win;
  KMMessage *rmsg = msg->createReply( KMail::ReplyNone, mSelection );
  rmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );

  win = new KMComposeWin(rmsg, 0);
  win->setCharset(msg->codec()->mimeName(), TRUE);
  win->setReplyFocus();
  win->show();

  return OK;
}


KMMailtoForwardCommand::KMMailtoForwardCommand( QWidget *parent,
   const KURL &url, KMMessage *msg )
  :KMCommand( parent, msg ), mUrl( url )
{
}

KMCommand::Result KMMailtoForwardCommand::execute()
{
  //TODO : consider factoring createForward into this method.
  KMMessage *msg = retrievedMessage();
  KMComposeWin *win;
  KMMessage *fmsg = msg->createForward();
  fmsg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );

  win = new KMComposeWin(fmsg);
  win->setCharset(msg->codec()->mimeName(), TRUE);
  win->show();

  return OK;
}


KMAddBookmarksCommand::KMAddBookmarksCommand( const KURL &url, QWidget *parent )
  : KMCommand( parent ), mUrl( url )
{
}

KMCommand::Result KMAddBookmarksCommand::execute()
{
  QString filename = locateLocal( "data", QString::fromLatin1("konqueror/bookmarks.xml") );
  KBookmarkManager *bookManager = KBookmarkManager::managerForFile( filename,
                                                                    false );
  KBookmarkGroup group = bookManager->root();
  group.addBookmark( bookManager, mUrl.path(), KURL( mUrl ) );
  if( bookManager->save() ) {
    bookManager->emitChanged( group );
  }

  return OK;
}

KMMailtoAddAddrBookCommand::KMMailtoAddAddrBookCommand( const KURL &url,
   QWidget *parent )
  : KMCommand( parent ), mUrl( url )
{
}

KMCommand::Result KMMailtoAddAddrBookCommand::execute()
{
  KAddrBookExternal::addEmail( KMMessage::decodeMailtoUrl( mUrl.path() ),
                               parentWidget() );

  return OK;
}


KMMailtoOpenAddrBookCommand::KMMailtoOpenAddrBookCommand( const KURL &url,
   QWidget *parent )
  : KMCommand( parent ), mUrl( url )
{
}

KMCommand::Result KMMailtoOpenAddrBookCommand::execute()
{
  QString addr = KMMessage::decodeMailtoUrl( mUrl.path() );
  KAddrBookExternal::openEmail( KPIM::getEmailAddress(addr), addr ,
                                parentWidget() );

  return OK;
}


KMUrlCopyCommand::KMUrlCopyCommand( const KURL &url, KMMainWidget *mainWidget )
  :mUrl( url ), mMainWidget( mainWidget )
{
}

KMCommand::Result KMUrlCopyCommand::execute()
{
  QClipboard* clip = QApplication::clipboard();

  if (mUrl.protocol() == "mailto") {
    // put the url into the mouse selection and the clipboard
    QString address = KMMessage::decodeMailtoUrl( mUrl.path() );
    clip->setSelectionMode( true );
    clip->setText( address );
    clip->setSelectionMode( false );
    clip->setText( address );
    KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "Address copied to clipboard." ));
  } else {
    // put the url into the mouse selection and the clipboard
    clip->setSelectionMode( true );
    clip->setText( mUrl.url() );
    clip->setSelectionMode( false );
    clip->setText( mUrl.url() );
    KPIM::BroadcastStatus::instance()->setStatusMsg( i18n( "URL copied to clipboard." ));
  }

  return OK;
}


KMUrlOpenCommand::KMUrlOpenCommand( const KURL &url, KMReaderWin *readerWin )
  :mUrl( url ), mReaderWin( readerWin )
{
}

KMCommand::Result KMUrlOpenCommand::execute()
{
  if ( !mUrl.isEmpty() )
    mReaderWin->slotUrlOpen( mUrl, KParts::URLArgs() );

  return OK;
}


KMUrlSaveCommand::KMUrlSaveCommand( const KURL &url, QWidget *parent )
  : KMCommand( parent ), mUrl( url )
{
}

KMCommand::Result KMUrlSaveCommand::execute()
{
  if ( mUrl.isEmpty() )
    return OK;
  KURL saveUrl = KFileDialog::getSaveURL(mUrl.fileName(), QString::null,
                                         parentWidget() );
  if ( saveUrl.isEmpty() )
    return Canceled;
  if ( KIO::NetAccess::exists( saveUrl, false, parentWidget() ) )
  {
    if (KMessageBox::warningContinueCancel(0,
        i18n("<qt>File <b>%1</b> exists.<br>Do you want to replace it?</qt>")
        .arg(saveUrl.prettyURL()), i18n("Save to File"), i18n("&Replace"))
        != KMessageBox::Continue)
      return Canceled;
  }
  KIO::Job *job = KIO::file_copy(mUrl, saveUrl, -1, true);
  connect(job, SIGNAL(result(KIO::Job*)), SLOT(slotUrlSaveResult(KIO::Job*)));
  setEmitsCompletedItself( true );
  return OK;
}

void KMUrlSaveCommand::slotUrlSaveResult( KIO::Job *job )
{
  if ( job->error() ) {
    job->showErrorDialog();
    setResult( Failed );
    emit completed( this );
  }
  else {
    setResult( OK );
    emit completed( this );
  }
}


KMEditMsgCommand::KMEditMsgCommand( QWidget *parent, KMMessage *msg )
  :KMCommand( parent, msg )
{
}

KMCommand::Result KMEditMsgCommand::execute()
{
  KMMessage *msg = retrievedMessage();
  if (!msg || !msg->parent() ||
      !kmkernel->folderIsDraftOrOutbox( msg->parent() ))
    return Failed;

  // Remember the old parent, we need it a bit further down to be able
  // to put the unchanged messsage back in the drafts folder if the nth
  // edit is discarded, for n > 1.
  KMFolder *parent = msg->parent();
  if ( parent )
    parent->take( parent->find( msg ) );

  KMComposeWin *win = new KMComposeWin();
  msg->setTransferInProgress(false); // From here on on, the composer owns the message.
  win->setMsg(msg, FALSE, TRUE);
  win->setFolder( parent );
  win->show();

  return OK;
}


KMShowMsgSrcCommand::KMShowMsgSrcCommand( QWidget *parent,
  KMMessage *msg, bool fixedFont )
  :KMCommand( parent, msg ), mFixedFont( fixedFont )
{
  // remember complete state
  mMsgWasComplete = msg->isComplete();
}

KMCommand::Result KMShowMsgSrcCommand::execute()
{
  KMMessage *msg = retrievedMessage();
  if ( msg->isComplete() && !mMsgWasComplete )
    msg->notify(); // notify observers as msg was transfered
  QString str = msg->codec()->toUnicode( msg->asString() );

  MailSourceViewer *viewer = new MailSourceViewer(); // deletes itself upon close
  viewer->setCaption( i18n("Message as Plain Text") );
  viewer->setText(str);
  if( mFixedFont )
    viewer->setFont(KGlobalSettings::fixedFont());

  // Well, there is no widget to be seen here, so we have to use QCursor::pos()
  // Update: (GS) I'm not going to make this code behave according to Xinerama
  //         configuration because this is quite the hack.
  if (QApplication::desktop()->isVirtualDesktop()) {
    int scnum = QApplication::desktop()->screenNumber(QCursor::pos());
    viewer->resize(QApplication::desktop()->screenGeometry(scnum).width()/2,
                  2*QApplication::desktop()->screenGeometry(scnum).height()/3);
  } else {
    viewer->resize(QApplication::desktop()->geometry().width()/2,
                  2*QApplication::desktop()->geometry().height()/3);
  }
  viewer->show();

  return OK;
}

static KURL subjectToUrl( const QString & subject ) {
    return KFileDialog::getSaveURL( subject.stripWhiteSpace()
                                           .replace( QDir::separator(), '_' ),
                                    QString::null );
}

KMSaveMsgCommand::KMSaveMsgCommand( QWidget *parent, KMMessage * msg )
  : KMCommand( parent ),
    mMsgListIndex( 0 ),
    mStandAloneMessage( 0 ),
    mOffset( 0 ),
    mTotalSize( msg ? msg->msgSize() : 0 )
{
  if ( !msg ) return;
  setDeletesItself( true );
  // If the mail has a serial number, operate on sernums, if it does not
  // we need to work with the pointer, but can be reasonably sure it won't
  // go away, since it'll be an encapsulated message or one that was opened
  // from an .eml file.
  if ( msg->getMsgSerNum() != 0 ) {
    mMsgList.append( msg->getMsgSerNum() );
  } else {
    mStandAloneMessage = msg;
  }
  mUrl = subjectToUrl( msg->cleanSubject() );
}

KMSaveMsgCommand::KMSaveMsgCommand( QWidget *parent,
                                    const QPtrList<KMMsgBase> &msgList )
  : KMCommand( parent ),
    mMsgListIndex( 0 ),
    mStandAloneMessage( 0 ),
    mOffset( 0 ),
    mTotalSize( 0 )
{
  if (!msgList.getFirst())
    return;
  setDeletesItself( true );
  KMMsgBase *msgBase = msgList.getFirst();

  // We operate on serNums and not the KMMsgBase pointers, as those can
  // change, or become invalid when changing the current message, switching
  // folders, etc.
  QPtrListIterator<KMMsgBase> it(msgList);
  while ( it.current() ) {
    mMsgList.append( (*it)->getMsgSerNum() );
    mTotalSize += (*it)->msgSize();
    if ((*it)->parent() != 0)
      (*it)->parent()->open();
    ++it;
  }
  mMsgListIndex = 0;
  mUrl = subjectToUrl( msgBase->cleanSubject() );
}

KURL KMSaveMsgCommand::url()
{
  return mUrl;
}

KMCommand::Result KMSaveMsgCommand::execute()
{
  mJob = KIO::put( mUrl, S_IRUSR|S_IWUSR, false, false );
  mJob->slotTotalSize( mTotalSize );
  mJob->setAsyncDataEnabled( true );
  mJob->setReportDataSent( true );
  connect(mJob, SIGNAL(dataReq(KIO::Job*, QByteArray &)),
    SLOT(slotSaveDataReq()));
  connect(mJob, SIGNAL(result(KIO::Job*)),
    SLOT(slotSaveResult(KIO::Job*)));
  setEmitsCompletedItself( true );
  return OK;
}

void KMSaveMsgCommand::slotSaveDataReq()
{
  int remainingBytes = mData.size() - mOffset;
  if ( remainingBytes > 0 ) {
    // eat leftovers first
    if ( remainingBytes > MAX_CHUNK_SIZE )
      remainingBytes = MAX_CHUNK_SIZE;

    QByteArray data;
    data.duplicate( mData.data() + mOffset, remainingBytes );
    mJob->sendAsyncData( data );
    mOffset += remainingBytes;
    return;
  }
  // No leftovers, process next message.
  if ( mMsgListIndex < mMsgList.size() ) {
    KMMessage *msg = 0;
    int idx = -1;
    KMFolder * p = 0;
    kmkernel->msgDict()->getLocation( mMsgList[mMsgListIndex], &p, &idx );
    assert( p );
    assert( idx >= 0 );
    msg = p->getMsg(idx);

    if (msg->transferInProgress()) {
      QByteArray data = QByteArray();
      mJob->sendAsyncData( data );
    }
    msg->setTransferInProgress( true );
    if (msg->isComplete() ) {
      slotMessageRetrievedForSaving(msg);
    } else {
      // retrieve Message first
      if (msg->parent()  && !msg->isComplete() ) {
        FolderJob *job = msg->parent()->createJob(msg);
        job->setCancellable( false );
        connect(job, SIGNAL(messageRetrieved(KMMessage*)),
            this, SLOT(slotMessageRetrievedForSaving(KMMessage*)));
        job->start();
      }
    }
  } else {
    if ( mStandAloneMessage ) {
      // do the special case of a standalone message
      slotMessageRetrievedForSaving( mStandAloneMessage );
      mStandAloneMessage = 0;
    } else {
      // No more messages. Tell the putjob we are done.
      QByteArray data = QByteArray();
      mJob->sendAsyncData( data );
    }
  }
}

void KMSaveMsgCommand::slotMessageRetrievedForSaving(KMMessage *msg)
{
  if ( msg ) {
    QCString str( msg->mboxMessageSeparator() );
    str += KMFolderMbox::escapeFrom( msg->asString() );
    str += "\n";
    msg->setTransferInProgress(false);

    mData = str;
    mData.resize(mData.size() - 1);
    mOffset = 0;
    QByteArray data;
    int size;
    // Unless it is great than 64 k send the whole message. kio buffers for us.
    if( mData.size() > (unsigned int) MAX_CHUNK_SIZE )
      size = MAX_CHUNK_SIZE;
    else
      size = mData.size();

    data.duplicate( mData, size );
    mJob->sendAsyncData( data );
    mOffset += size;
  }
  ++mMsgListIndex;
  // Get rid of the message.
  if ( msg && msg->parent() && msg->getMsgSerNum() ) {
    int idx = -1;
    KMFolder * p = 0;
    kmkernel->msgDict()->getLocation( msg, &p, &idx );
    assert( p == msg->parent() ); assert( idx >= 0 );
    p->unGetMsg( idx );
    p->close();
  }
}

void KMSaveMsgCommand::slotSaveResult(KIO::Job *job)
{
  if (job->error())
  {
    if (job->error() == KIO::ERR_FILE_ALREADY_EXIST)
    {
      if (KMessageBox::warningContinueCancel(0,
        i18n("File %1 exists.\nDo you want to replace it?")
        .arg(mUrl.prettyURL()), i18n("Save to File"), i18n("&Replace"))
        == KMessageBox::Continue) {
        mOffset = 0;

        mJob = KIO::put( mUrl, S_IRUSR|S_IWUSR, true, false );
        mJob->slotTotalSize( mTotalSize );
        mJob->setAsyncDataEnabled( true );
        mJob->setReportDataSent( true );
        connect(mJob, SIGNAL(dataReq(KIO::Job*, QByteArray &)),
            SLOT(slotSaveDataReq()));
        connect(mJob, SIGNAL(result(KIO::Job*)),
            SLOT(slotSaveResult(KIO::Job*)));
      }
    }
    else
    {
      job->showErrorDialog();
      setResult( Failed );
      emit completed( this );
      delete this;
    }
  } else {
    setResult( OK );
    emit completed( this );
    delete this;
  }
}

//-----------------------------------------------------------------------------

KMOpenMsgCommand::KMOpenMsgCommand( QWidget *parent, const KURL & url,
                                    const QTextCodec *codec )
  : KMCommand( parent ),
    mUrl( url ),
    mCodec( codec )
{
  setDeletesItself( true );
}

KMCommand::Result KMOpenMsgCommand::execute()
{
  if ( mUrl.isEmpty() ) {
    mUrl = KFileDialog::getOpenURL( ":OpenMessage", "message/rfc822",
                                    parentWidget(), i18n("Open Message") );
  }
  if ( mUrl.isEmpty() ) {
    setDeletesItself( false );
    return Canceled;
  }
  mJob = KIO::get( mUrl, false, false );
  mJob->setReportDataSent( true );
  connect( mJob, SIGNAL( data( KIO::Job *, const QByteArray & ) ),
           this, SLOT( slotDataArrived( KIO::Job*, const QByteArray & ) ) );
  connect( mJob, SIGNAL( result( KIO::Job * ) ),
           SLOT( slotResult( KIO::Job * ) ) );
  setEmitsCompletedItself( true );
  return OK;
}

void KMOpenMsgCommand::slotDataArrived( KIO::Job *, const QByteArray & data )
{
  if ( data.isEmpty() )
    return;

  mMsgString.append( data.data(), data.size() );
}

void KMOpenMsgCommand::slotResult( KIO::Job *job )
{
  if ( job->error() ) {
    // handle errors
    job->showErrorDialog();
    setResult( Failed );
    emit completed( this );
  }
  else {
    int startOfMessage = 0;
    if ( mMsgString.compare( 0, 5, "From ", 5 ) == 0 ) {
      startOfMessage = mMsgString.find( '\n' );
      if ( startOfMessage == -1 ) {
        KMessageBox::sorry( parentWidget(),
                            i18n( "The file does not contain a message." ) );
        setResult( Failed );
        emit completed( this );
        // Emulate closing of a secondary window so that KMail exits in case it
        // was started with the --view command line option. Otherwise an
        // invisible KMail would keep running.
        SecondaryWindow *win = new SecondaryWindow();
        win->close();
        win->deleteLater();
        deleteLater();
        return;
      }
      startOfMessage += 1; // the message starts after the '\n'
    }
    // check for multiple messages in the file
    bool multipleMessages = true;
    int endOfMessage = mMsgString.find( "\nFrom " );
    if ( endOfMessage == -1 ) {
      endOfMessage = mMsgString.length();
      multipleMessages = false;
    }
    DwMessage *dwMsg = new DwMessage;
    dwMsg->FromString( mMsgString.substr( startOfMessage,
                                          endOfMessage - startOfMessage ) );
    dwMsg->Parse();
    // check whether we have a message ( no headers => this isn't a message )
    if ( dwMsg->Headers().NumFields() == 0 ) {
      KMessageBox::sorry( parentWidget(),
                          i18n( "The file does not contain a message." ) );
      delete dwMsg; dwMsg = 0;
      setResult( Failed );
      emit completed( this );
      // Emulate closing of a secondary window (see above).
      SecondaryWindow *win = new SecondaryWindow();
      win->close();
      win->deleteLater();
      deleteLater();
      return;
    }
    KMMessage *msg = new KMMessage( dwMsg );
    msg->setReadyToShow( true );
    KMReaderMainWin *win = new KMReaderMainWin();
    win->showMsg( mCodec, msg );
    win->show();
    if ( multipleMessages )
      KMessageBox::information( win,
                                i18n( "The file contains multiple messages. "
                                      "Only the first message is shown." ) );
    setResult( OK );
    emit completed( this );
  }
  delete this;
}

//-----------------------------------------------------------------------------

//TODO: ReplyTo, NoQuoteReplyTo, ReplyList, ReplyToAll, ReplyAuthor
//      are all similar and should be factored
KMReplyToCommand::KMReplyToCommand( QWidget *parent, KMMessage *msg,
                                    const QString &selection )
  : KMCommand( parent, msg ), mSelection( selection )
{
}

KMCommand::Result KMReplyToCommand::execute()
{
  KCursorSaver busy(KBusyPtr::busy());
  KMMessage *msg = retrievedMessage();
  KMMessage *reply = msg->createReply( KMail::ReplySmart, mSelection );
  KMComposeWin *win = new KMComposeWin( reply );
  win->setCharset( msg->codec()->mimeName(), TRUE );
  win->setReplyFocus();
  win->show();

  return OK;
}


KMNoQuoteReplyToCommand::KMNoQuoteReplyToCommand( QWidget *parent,
                                                  KMMessage *msg )
  : KMCommand( parent, msg )
{
}

KMCommand::Result KMNoQuoteReplyToCommand::execute()
{
  KCursorSaver busy(KBusyPtr::busy());
  KMMessage *msg = retrievedMessage();
  KMMessage *reply = msg->createReply( KMail::ReplySmart, "", TRUE);
  KMComposeWin *win = new KMComposeWin( reply );
  win->setCharset(msg->codec()->mimeName(), TRUE);
  win->setReplyFocus(false);
  win->show();

  return OK;
}


KMReplyListCommand::KMReplyListCommand( QWidget *parent,
  KMMessage *msg, const QString &selection )
 : KMCommand( parent, msg ), mSelection( selection )
{
}

KMCommand::Result KMReplyListCommand::execute()
{
  KCursorSaver busy(KBusyPtr::busy());
  KMMessage *msg = retrievedMessage();
  KMMessage *reply = msg->createReply( KMail::ReplyList, mSelection);
  KMComposeWin *win = new KMComposeWin( reply );
  win->setCharset(msg->codec()->mimeName(), TRUE);
  win->setReplyFocus(false);
  win->show();

  return OK;
}


KMReplyToAllCommand::KMReplyToAllCommand( QWidget *parent,
  KMMessage *msg, const QString &selection )
  :KMCommand( parent, msg ), mSelection( selection )
{
}

KMCommand::Result KMReplyToAllCommand::execute()
{
  KCursorSaver busy(KBusyPtr::busy());
  KMMessage *msg = retrievedMessage();
  KMMessage *reply = msg->createReply( KMail::ReplyAll, mSelection );
  KMComposeWin *win = new KMComposeWin( reply );
  win->setCharset( msg->codec()->mimeName(), TRUE );
  win->setReplyFocus();
  win->show();

  return OK;
}


KMReplyAuthorCommand::KMReplyAuthorCommand( QWidget *parent, KMMessage *msg,
                                            const QString &selection )
  : KMCommand( parent, msg ), mSelection( selection )
{
}

KMCommand::Result KMReplyAuthorCommand::execute()
{
  KCursorSaver busy(KBusyPtr::busy());
  KMMessage *msg = retrievedMessage();
  KMMessage *reply = msg->createReply( KMail::ReplyAuthor, mSelection );
  KMComposeWin *win = new KMComposeWin( reply );
  win->setCharset( msg->codec()->mimeName(), TRUE );
  win->setReplyFocus();
  win->show();

  return OK;
}


KMForwardCommand::KMForwardCommand( QWidget *parent,
  const QPtrList<KMMsgBase> &msgList, uint identity )
  : KMCommand( parent, msgList ),
    mIdentity( identity )
{
}

KMForwardCommand::KMForwardCommand( QWidget *parent, KMMessage *msg,
                                    uint identity )
  : KMCommand( parent, msg ),
    mIdentity( identity )
{
}

KMCommand::Result KMForwardCommand::execute()
{
  KMComposeWin *win;
  QPtrList<KMMessage> msgList = retrievedMsgs();

  if (msgList.count() >= 2) {
    // ask if they want a mime digest forward

    if (KMessageBox::questionYesNo( parentWidget(),
                                    i18n("Forward selected messages as "
                                         "a MIME digest?") )
        == KMessageBox::Yes) {
      uint id = 0;
      KMMessage *fwdMsg = new KMMessage;
      KMMessagePart *msgPart = new KMMessagePart;
      QString msgPartText;
      int msgCnt = 0; // incase there are some we can't forward for some reason

      // dummy header initialization; initialization with the correct identity
      // is done below
      fwdMsg->initHeader(id);
      fwdMsg->setAutomaticFields(true);
      fwdMsg->mMsg->Headers().ContentType().CreateBoundary(1);
      QCString boundary( fwdMsg->mMsg->Headers().ContentType().Boundary().c_str() );
      msgPartText = i18n("\nThis is a MIME digest forward. The content of the"
                         " message is contained in the attachment(s).\n\n\n");
      // iterate through all the messages to be forwarded
      for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
        // set the identity
        if (id == 0)
          id = msg->headerField("X-KMail-Identity").stripWhiteSpace().toUInt();
        // set the part header
        msgPartText += "--";
        msgPartText += QString::fromLatin1( boundary );
        msgPartText += "\nContent-Type: MESSAGE/RFC822";
        msgPartText += QString("; CHARSET=%1").arg(msg->charset());
        msgPartText += "\n";
        DwHeaders dwh;
        dwh.MessageId().CreateDefault();
        msgPartText += QString("Content-ID: %1\n").arg(dwh.MessageId().AsString().c_str());
        msgPartText += QString("Content-Description: %1").arg(msg->subject());
        if (!msg->subject().contains("(fwd)"))
          msgPartText += " (fwd)";
        msgPartText += "\n\n";
        // remove headers that shouldn't be forwarded
        msg->removePrivateHeaderFields();
        msg->removeHeaderField("BCC");
        // set the part
        msgPartText += msg->headerAsString();
        msgPartText += "\n";
        msgPartText += msg->body();
        msgPartText += "\n";     // eot
        msgCnt++;
        fwdMsg->link(msg, KMMsgStatusForwarded);
      }
      if ( id == 0 )
        id = mIdentity; // use folder identity if no message had an id set
      fwdMsg->initHeader(id);
      msgPartText += "--";
      msgPartText += QString::fromLatin1( boundary );
      msgPartText += "--\n";
      QCString tmp;
      msgPart->setTypeStr("MULTIPART");
      tmp.sprintf( "Digest; boundary=\"%s\"", boundary.data() );
      msgPart->setSubtypeStr( tmp );
      msgPart->setName("unnamed");
      msgPart->setCte(DwMime::kCte7bit);   // does it have to be 7bit?
      msgPart->setContentDescription(QString("Digest of %1 messages.").arg(msgCnt));
      // THIS HAS TO BE AFTER setCte()!!!!
      msgPart->setBodyEncoded(QCString(msgPartText.ascii()));
      KCursorSaver busy(KBusyPtr::busy());
      win = new KMComposeWin(fwdMsg, id);
      win->addAttach(msgPart);
      win->show();
      return OK;
    } else {            // NO MIME DIGEST, Multiple forward
      uint id = 0;
      QCString msgText = "";
      QPtrList<KMMessage> linklist;
      for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
        // set the identity
        if (id == 0)
          id = msg->headerField("X-KMail-Identity").stripWhiteSpace().toUInt();

        msgText += msg->createForwardBody();
        linklist.append(msg);
      }
      if ( id == 0 )
        id = mIdentity; // use folder identity if no message had an id set
      KMMessage *fwdMsg = new KMMessage;
      fwdMsg->initHeader(id);
      fwdMsg->setAutomaticFields(true);
      fwdMsg->setCharset("utf-8");
      fwdMsg->setBody(msgText);

      for (KMMessage *msg = linklist.first(); msg; msg = linklist.next())
        fwdMsg->link(msg, KMMsgStatusForwarded);

      KCursorSaver busy(KBusyPtr::busy());
      win = new KMComposeWin(fwdMsg, id);
      win->setCharset("");
      win->show();
      return OK;
    }
  }

  // forward a single message at most.
  KMMessage *msg = msgList.getFirst();
  if ( !msg || !msg->codec() )
    return Failed;

  KCursorSaver busy(KBusyPtr::busy());
  KMMessage *fwdMsg = msg->createForward();


  win = new KMComposeWin( fwdMsg );
  win->setCharset( fwdMsg->codec()->mimeName(), true );
  // Only replace the body if this is a multipart mail or if the main part is only
  // the text part. This avoids scrambling top level text/calendar mails, for example.
  if ( fwdMsg->typeStr().lower() == "multipart" ||
     ( fwdMsg->typeStr().lower() == "text" && fwdMsg->subtypeStr().lower() == "plain" ) ) {
    win->setBody( QString::fromUtf8( msg->createForwardBody() ) );
  }
  win->show();

  return OK;
}


KMForwardAttachedCommand::KMForwardAttachedCommand( QWidget *parent,
  const QPtrList<KMMsgBase> &msgList, uint identity, KMComposeWin *win )
  : KMCommand( parent, msgList ), mIdentity( identity ),
    mWin( QGuardedPtr< KMComposeWin >( win ))
{
}

KMForwardAttachedCommand::KMForwardAttachedCommand( QWidget *parent,
  KMMessage * msg, uint identity, KMComposeWin *win )
  : KMCommand( parent, msg ), mIdentity( identity ),
    mWin( QGuardedPtr< KMComposeWin >( win ))
{
}

KMCommand::Result KMForwardAttachedCommand::execute()
{
  QPtrList<KMMessage> msgList = retrievedMsgs();
  KMMessage *fwdMsg = new KMMessage;

  if (msgList.count() >= 2) {
    // don't respect X-KMail-Identity headers because they might differ for
    // the selected mails
    fwdMsg->initHeader(mIdentity);
  }
  else if (msgList.count() == 1) {
    KMMessage *msg = msgList.getFirst();
    fwdMsg->initFromMessage(msg);
    fwdMsg->setSubject( msg->forwardSubject() );
  }

  fwdMsg->setAutomaticFields(true);

  KCursorSaver busy(KBusyPtr::busy());
  if (!mWin)
    mWin = new KMComposeWin(fwdMsg, mIdentity);

  // iterate through all the messages to be forwarded
  for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
    // remove headers that shouldn't be forwarded
    msg->removePrivateHeaderFields();
    msg->removeHeaderField("BCC");
    // set the part
    KMMessagePart *msgPart = new KMMessagePart;
    msgPart->setTypeStr("message");
    msgPart->setSubtypeStr("rfc822");
    msgPart->setCharset(msg->charset());
    msgPart->setName("forwarded message");
    msgPart->setContentDescription(msg->from()+": "+msg->subject());
    msgPart->setContentDisposition( "inline" );
    // THIS HAS TO BE AFTER setCte()!!!!
    QValueList<int> dummy;
    msgPart->setBodyAndGuessCte(msg->asString(), dummy, true);
    msgPart->setCharset("");

    fwdMsg->link(msg, KMMsgStatusForwarded);
    mWin->addAttach(msgPart);
  }

  mWin->show();

  return OK;
}


KMRedirectCommand::KMRedirectCommand( QWidget *parent,
  KMMessage *msg )
  : KMCommand( parent, msg )
{
}

KMCommand::Result KMRedirectCommand::execute()
{
  KMMessage *msg = retrievedMessage();
  if ( !msg || !msg->codec() )
    return Failed;

  RedirectDialog dlg( parentWidget(), "redirect", true,
                      kmkernel->msgSender()->sendImmediate() );
  if (dlg.exec()==QDialog::Rejected) return Failed;

  KMMessage *newMsg = msg->createRedirect( dlg.to() );
  KMFilterAction::sendMDN( msg, KMime::MDN::Dispatched );

  if ( !kmkernel->msgSender()->send( newMsg, dlg.sendImmediate() ) ) {
    kdDebug(5006) << "KMRedirectCommand: could not redirect message (sending failed)" << endl;
    return Failed; // error: couldn't send
  }
  return OK;
}


KMPrintCommand::KMPrintCommand( QWidget *parent,
  KMMessage *msg, bool htmlOverride, bool htmlLoadExtOverride,
  bool useFixedFont, const QTextCodec *codec )
  : KMCommand( parent, msg ), mHtmlOverride( htmlOverride ),
    mHtmlLoadExtOverride( htmlLoadExtOverride ),
    mUseFixedFont( useFixedFont ), mCodec( codec )
{
}

KMCommand::Result KMPrintCommand::execute()
{
  KMReaderWin printWin( 0, 0, 0 );
  printWin.setPrinting( true );
  printWin.readConfig();
  printWin.setHtmlOverride( mHtmlOverride );
  printWin.setHtmlLoadExtOverride( mHtmlLoadExtOverride );
  printWin.setUseFixedFont( mUseFixedFont );
  printWin.setOverrideCodec( mCodec );
  printWin.setMsg( retrievedMessage(), true );
  printWin.printMsg();

  return OK;
}


KMSetStatusCommand::KMSetStatusCommand( KMMsgStatus status,
  const QValueList<Q_UINT32> &serNums, bool toggle )
  : mStatus( status ), mSerNums( serNums ), mToggle( toggle )
{
}

KMCommand::Result KMSetStatusCommand::execute()
{
  QValueListIterator<Q_UINT32> it;
  int idx = -1;
  KMFolder *folder = 0;
  bool parentStatus = false;

  // Toggle actions on threads toggle the whole thread
  // depending on the state of the parent.
  if (mToggle) {
    KMMsgBase *msg;
    kmkernel->msgDict()->getLocation( *mSerNums.begin(), &folder, &idx );
    if (folder) {
      msg = folder->getMsgBase(idx);
      if (msg && (msg->status()&mStatus))
        parentStatus = true;
      else
        parentStatus = false;
    }
  }
  QMap< KMFolder*, QValueList<int> > folderMap;
  for ( it = mSerNums.begin(); it != mSerNums.end(); ++it ) {
    kmkernel->msgDict()->getLocation( *it, &folder, &idx );
    if (folder) {
      if (mToggle) {
        KMMsgBase *msg = folder->getMsgBase(idx);
        // check if we are already at the target toggle state
        if (msg) {
          bool myStatus;
          if (msg->status()&mStatus)
            myStatus = true;
          else
            myStatus = false;
          if (myStatus != parentStatus)
            continue;
        }
      }
      /* Collect the ids for each folder in a separate list and
         send them off in one go at the end. */
      folderMap[folder].append(idx);
    }
  }
  QMapIterator< KMFolder*, QValueList<int> > it2 = folderMap.begin();
  while ( it2 != folderMap.end() ) {
     KMFolder *f = it2.key();
     f->setStatus( (*it2), mStatus, mToggle );
     ++it2;
  }
  //kapp->dcopClient()->emitDCOPSignal( "unreadCountChanged()", QByteArray() );

  return OK;
}


KMFilterCommand::KMFilterCommand( const QCString &field, const QString &value )
  : mField( field ), mValue( value )
{
}

KMCommand::Result KMFilterCommand::execute()
{
  kmkernel->filterMgr()->createFilter( mField, mValue );

  return OK;
}


KMFilterActionCommand::KMFilterActionCommand( QWidget *parent,
                                              const QPtrList<KMMsgBase> &msgList,
                                              KMFilter *filter )
  : KMCommand( parent, msgList ), mFilter( filter  )
{
}

KMCommand::Result KMFilterActionCommand::execute()
{
  QPtrList<KMMessage> msgList = retrievedMsgs();

  for (KMMessage *msg = msgList.first(); msg; msg = msgList.next())
    if( msg->parent() )
      kmkernel->filterMgr()->tempOpenFolder(msg->parent());

  for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) {
    msg->setTransferInProgress(false);

    int filterResult = kmkernel->filterMgr()->process(msg, mFilter);
    if (filterResult == 2) {
      // something went horribly wrong (out of space?)
      perror("Critical error");
      kmkernel->emergencyExit( i18n("Not enough free disk space?" ));
    }
    msg->setTransferInProgress(true);
  }

  return OK;
}


KMMetaFilterActionCommand::KMMetaFilterActionCommand( KMFilter *filter,
                                                      KMHeaders *headers,
                                                      KMMainWidget *main )
    : QObject( main ),
      mFilter( filter ), mHeaders( headers ), mMainWidget( main )
{
}

void KMMetaFilterActionCommand::start()
{
#if 0 // use action scheduler
  KMFilterMgr::FilterSet set = KMFilterMgr::All;
  QPtrList<KMFilter> filters;
  filters.append( mFilter );
  ActionScheduler *scheduler = new ActionScheduler( set, filters, mHeaders );
  scheduler->setAlwaysMatch( true );
  scheduler->setAutoDestruct( true );

  int contentX, contentY;
  HeaderItem *nextItem = mHeaders->prepareMove( &contentX, &contentY );
  QPtrList<KMMsgBase> msgList = *mHeaders->selectedMsgs(true);
  mHeaders->finalizeMove( nextItem, contentX, contentY );


  for (KMMsgBase *msg = msgList.first(); msg; msg = msgList.next())
    scheduler->execFilters( msg );
#else
  KMCommand *filterCommand = new KMFilterActionCommand( mMainWidget,
  *mHeaders->selectedMsgs(), mFilter);
  filterCommand->start();
  int contentX, contentY;
  HeaderItem *item = mHeaders->prepareMove( &contentX, &contentY );
  mHeaders->finalizeMove( item, contentX, contentY );
#endif
}

FolderShortcutCommand::FolderShortcutCommand( KMMainWidget *mainwidget,
                                              KMFolder *folder )
    : mMainWidget( mainwidget ), mFolder( folder ), mAction( 0 )
{
}


FolderShortcutCommand::~FolderShortcutCommand()
{
  if ( mAction ) mAction->unplugAll();
  delete mAction;
}

void FolderShortcutCommand::start()
{
  mMainWidget->slotSelectFolder( mFolder );
}

void FolderShortcutCommand::setAction( KAction* action )
{
  mAction = action;
}

KMMailingListFilterCommand::KMMailingListFilterCommand( QWidget *parent,
                                                        KMMessage *msg )
  : KMCommand( parent, msg )
{
}

KMCommand::Result KMMailingListFilterCommand::execute()
{
  QCString name;
  QString value;
  KMMessage *msg = retrievedMessage();
  if (!msg)
    return Failed;

  if ( !MailingList::name( msg, name, value ).isEmpty() ) {
    kmkernel->filterMgr()->createFilter( name, value );
    return OK;
  }
  else
    return Failed;
}


void KMMenuCommand::folderToPopupMenu(bool move,
  QObject *receiver, KMMenuToFolder *aMenuToFolder, QPopupMenu *menu )
{
  while ( menu->count() )
  {
    QPopupMenu *popup = menu->findItem( menu->idAt( 0 ) )->popup();
    if (popup)
      delete popup;
    else
      menu->removeItemAt( 0 );
  }

  if (!kmkernel->imapFolderMgr()->dir().first() &&
      !kmkernel->dimapFolderMgr()->dir().first())
  { // only local folders
    makeFolderMenu( &kmkernel->folderMgr()->dir(), move,
                    receiver, aMenuToFolder, menu );
  } else {
    // operate on top-level items
    QPopupMenu* subMenu = new QPopupMenu(menu);
    makeFolderMenu( &kmkernel->folderMgr()->dir(),
                    move, receiver, aMenuToFolder, subMenu );
    menu->insertItem( i18n( "Local Folders" ), subMenu );
    KMFolderDir* fdir = &kmkernel->imapFolderMgr()->dir();
    for (KMFolderNode *node = fdir->first(); node; node = fdir->next()) {
      if (node->isDir())
        continue;
      subMenu = new QPopupMenu(menu);
      makeFolderMenu( node, move, receiver, aMenuToFolder, subMenu );
      menu->insertItem( node->label(), subMenu );
    }
    fdir = &kmkernel->dimapFolderMgr()->dir();
    for (KMFolderNode *node = fdir->first(); node; node = fdir->next()) {
      if (node->isDir())
        continue;
      subMenu = new QPopupMenu(menu);
      makeFolderMenu( node, move, receiver, aMenuToFolder, subMenu );
      menu->insertItem( node->label(), subMenu );
    }
  }
}

void KMMenuCommand::makeFolderMenu(KMFolderNode* node, bool move,
  QObject *receiver, KMMenuToFolder *aMenuToFolder, QPopupMenu *menu )
{
  // connect the signals
  if (move)
  {
    disconnect(menu, SIGNAL(activated(int)), receiver,
           SLOT(moveSelectedToFolder(int)));
    connect(menu, SIGNAL(activated(int)), receiver,
             SLOT(moveSelectedToFolder(int)));
  } else {
    disconnect(menu, SIGNAL(activated(int)), receiver,
           SLOT(copySelectedToFolder(int)));
    connect(menu, SIGNAL(activated(int)), receiver,
             SLOT(copySelectedToFolder(int)));
  }

  KMFolder *folder = 0;
  KMFolderDir *folderDir = 0;
  if (node->isDir()) {
    folderDir = static_cast<KMFolderDir*>(node);
  } else {
    folder = static_cast<KMFolder*>(node);
    folderDir = folder->child();
  }

  if (folder && !folder->noContent())
  {
    int menuId;
    if (move)
      menuId = menu->insertItem(i18n("Move to This Folder"));
    else
      menuId = menu->insertItem(i18n("Copy to This Folder"));
    aMenuToFolder->insert( menuId, folder );
    menu->setItemEnabled( menuId, !folder->isReadOnly() );
    menu->insertSeparator();
  }

  if (!folderDir)
    return;

  for (KMFolderNode *it = folderDir->first(); it; it = folderDir->next() ) {
    if (it->isDir())
      continue;
    KMFolder *child = static_cast<KMFolder*>(it);
    QString label = child->label();
    label.replace("&","&&");
    if (child->child() && child->child()->first()) {
      // descend
      QPopupMenu *subMenu = new QPopupMenu(menu, "subMenu");
      makeFolderMenu( child, move, receiver,
                      aMenuToFolder, subMenu );
      menu->insertItem( label, subMenu );
    } else {
      // insert an item
      int menuId = menu->insertItem( label );
      aMenuToFolder->insert( menuId, child );
      menu->setItemEnabled( menuId, !child->isReadOnly() );
    }
  }
  return;
}


KMCopyCommand::KMCopyCommand( KMFolder* destFolder,
                              const QPtrList<KMMsgBase> &msgList )
:mDestFolder( destFolder ), mMsgList( msgList )
{
  setDeletesItself( true );
}

KMCopyCommand::KMCopyCommand( KMFolder* destFolder, KMMessage * msg )
  :mDestFolder( destFolder )
{
  setDeletesItself( true );
  mMsgList.append( &msg->toMsgBase() );
}

KMCommand::Result KMCopyCommand::execute()
{
  KMMsgBase *msgBase;
  KMMessage *msg, *newMsg;
  int idx = -1;
  bool isMessage;
  QPtrList<KMMessage> list;
  QPtrList<KMMessage> localList;

  if (mDestFolder && mDestFolder->open() != 0)
  {
    deleteLater();
    return Failed;
  }

  KCursorSaver busy(KBusyPtr::busy());

  mWaitingForMsgs.clear();
  for (msgBase = mMsgList.first(); msgBase; msgBase = mMsgList.next() )
  {
    KMFolder *srcFolder = msgBase->parent();
    if (isMessage = msgBase->isMessage())
    {
      msg = static_cast<KMMessage*>(msgBase);
    } else {
      idx = srcFolder->find(msgBase);
      assert(idx != -1);
      msg = srcFolder->getMsg(idx);
    }

    if (srcFolder &&
        (srcFolder->folderType()== KMFolderTypeImap) &&
        (mDestFolder->folderType() == KMFolderTypeImap) &&
        (static_cast<KMFolderImap*>(srcFolder->storage())->account() ==
         static_cast<KMFolderImap*>(mDestFolder->storage())->account()))
    {
      // imap => imap with same account
      list.append(msg);
    } else {
      newMsg = new KMMessage;
      newMsg->setComplete(msg->isComplete());
      // make sure the attachment state is only calculated when it's complete
      if (!newMsg->isComplete())
        newMsg->setReadyToShow(false);
      newMsg->fromString(msg->asString());
      newMsg->setStatus(msg->status());

      if (srcFolder && !newMsg->isComplete())
      {
        // imap => others
        mWaitingForMsgs.append( msg->getMsgSerNum() );
        disconnect(mDestFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
            this, SLOT(slotMsgAdded(KMFolder*, Q_UINT32)));
        connect(mDestFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
            this, SLOT(slotMsgAdded(KMFolder*, Q_UINT32)));
        newMsg->setParent(msg->parent());
        FolderJob *job = srcFolder->createJob(newMsg);
        job->setCancellable( false );
        connect(job, SIGNAL(messageRetrieved(KMMessage*)),
                mDestFolder, SLOT(reallyAddCopyOfMsg(KMMessage*)));
        job->start();
      } else {
        // local => others
        localList.append(newMsg);
      }
    }

    if (!isMessage && list.isEmpty())
    {
      assert(idx != -1);
      srcFolder->unGetMsg( idx );
    }

  } // end for

  if (!localList.isEmpty())
  {
    QValueList<int> index;
    mDestFolder->addMsg( localList, index );
    for ( QValueListIterator<int> it = index.begin(); it != index.end(); ++it ) {
      mDestFolder->unGetMsg( *it );
    }
  }

  // only close the folder if we're done
  if ( mWaitingForMsgs.isEmpty() )
    mDestFolder->close();

//TODO: Get rid of the other cases just use this one for all types of folder
//TODO: requires adding copyMsg and getFolder methods to KMFolder.h

  if (!list.isEmpty())
  {
    // copy the message(s); note: the list is empty afterwards!
    KMFolderImap *imapDestFolder = static_cast<KMFolderImap*>(mDestFolder->storage());
    imapDestFolder->copyMsg(list);
    imapDestFolder->getFolder();
  }

  if ( mWaitingForMsgs.isEmpty() )
    deleteLater();
  return OK;
}

void KMCopyCommand::slotMsgAdded( KMFolder*, Q_UINT32 serNum )
{
  mWaitingForMsgs.remove( serNum );
  if ( mWaitingForMsgs.isEmpty() )
  {
    mDestFolder->close();
    deleteLater();
  }
}


KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
                              const QPtrList<KMMsgBase> &msgList)
  :mDestFolder( destFolder ), mMsgList( msgList ), mProgressItem( 0 )
{
}

KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
                              KMMessage *msg )
  :mDestFolder( destFolder ), mProgressItem( 0 )
{
  mMsgList.append( &msg->toMsgBase() );
}

KMMoveCommand::KMMoveCommand( KMFolder* destFolder,
                              KMMsgBase *msgBase )
  :mDestFolder( destFolder ), mProgressItem( 0 )
{
  mMsgList.append( msgBase );
}

KMMoveCommand::KMMoveCommand( Q_UINT32 )
:mProgressItem( 0 )
{
}

KMCommand::Result KMMoveCommand::execute()
{
  setDeletesItself( true );
  setEmitsCompletedItself( true );
  typedef QMap< KMFolder*, QPtrList<KMMessage>* > FolderToMessageListMap;
  FolderToMessageListMap folderDeleteList;

  if (mDestFolder && mDestFolder->open() != 0) {
    completeMove( Failed );
    return Failed;
  }
  KCursorSaver busy(KBusyPtr::busy());

  // TODO set SSL state according to source and destfolder connection?
  Q_ASSERT( !mProgressItem );
  mProgressItem =
     ProgressManager::createProgressItem (
         "move"+ProgressManager::getUniqueID(),
         mDestFolder ? i18n( "Moving messages" ) : i18n( "Deleting messages" ) );
  connect( mProgressItem, SIGNAL( progressItemCanceled( KPIM::ProgressItem* ) ),
           this, SLOT( slotMoveCanceled() ) );

  KMMessage *msg;
  KMMsgBase *msgBase;
  int rc = 0;
  int index;
  QPtrList<KMMessage> list;
  int undoId = -1;

  if (mDestFolder) {
    connect (mDestFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
             this, SLOT(slotMsgAddedToDestFolder(KMFolder*, Q_UINT32)));

  }
  for ( msgBase=mMsgList.first(); msgBase; msgBase=mMsgList.next() ) {
    mLostBoys.append( msgBase->getMsgSerNum() );
  }
  mProgressItem->setTotalItems( mMsgList.count() );

  for (msgBase=mMsgList.first(); msgBase && !rc; msgBase=mMsgList.next()) {
    KMFolder *srcFolder = msgBase->parent();
    if (srcFolder == mDestFolder)
      continue;
    bool undo = msgBase->enableUndo();
    int idx = srcFolder->find(msgBase);
    assert(idx != -1);
    if ( msgBase->isMessage() )
      msg = static_cast<KMMessage*>(msgBase);
    else
      msg = srcFolder->getMsg(idx);

    if ( msg->transferInProgress() &&
         srcFolder->folderType() == KMFolderTypeImap )
    {
      // cancel the download
      msg->setTransferInProgress( false, true );
      static_cast<KMFolderImap*>(srcFolder->storage())->ignoreJobsForMessage( msg );
    }

    if (mDestFolder) {
      if (mDestFolder->folderType() == KMFolderTypeImap) {
        /* If we are moving to an imap folder, connect to it's completed
         * signal so we notice when all the mails should have showed up in it
         * but haven't for some reason. */
        KMFolderImap *imapFolder = static_cast<KMFolderImap*> ( mDestFolder->storage() );
        disconnect (imapFolder, SIGNAL(folderComplete( KMFolderImap*, bool )),
                 this, SLOT(slotImapFolderCompleted( KMFolderImap*, bool )));

        connect (imapFolder, SIGNAL(folderComplete( KMFolderImap*, bool )),
                 this, SLOT(slotImapFolderCompleted( KMFolderImap*, bool )));
        list.append(msg);
      } else {
        // We are moving to a local folder.
        rc = mDestFolder->moveMsg(msg, &index);
        if (rc == 0 && index != -1) {
          KMMsgBase *mb = mDestFolder->unGetMsg( mDestFolder->count() - 1 );
          if (undo && mb)
          {
            if ( undoId == -1 )
              undoId = kmkernel->undoStack()->newUndoAction( srcFolder, mDestFolder );
            kmkernel->undoStack()->addMsgToAction( undoId, mb->getMsgSerNum() );
          }
        } else if (rc != 0) {
          // Something  went wrong. Stop processing here, it is likely that the
          // other moves would fail as well.
          completeMove( Failed );
          return Failed;
        }
      }
    } else {
      // really delete messages that are already in the trash folder or if
      // we are really, really deleting, not just moving to trash
      if (srcFolder->folderType() == KMFolderTypeImap) {
        if (!folderDeleteList[srcFolder])
          folderDeleteList[srcFolder] = new QPtrList<KMMessage>;
        folderDeleteList[srcFolder]->append( msg );
      } else {
        srcFolder->removeMsg(idx);
        delete msg;
      }
    }
  }
  if (!list.isEmpty() && mDestFolder) {
    // will be completed with folderComplete signal
    mDestFolder->moveMsg(list, &index);
  } else {
    FolderToMessageListMap::Iterator it;
    for ( it = folderDeleteList.begin(); it != folderDeleteList.end(); ++it ) {
      it.key()->removeMsg(*it.data());
      delete it.data();
    }
    Result result = ( mLostBoys.isEmpty() ? OK : Failed );
    completeMove( result );
  }

  return OK;
}

void KMMoveCommand::slotImapFolderCompleted(KMFolderImap* imapFolder, bool success)
{
  disconnect (imapFolder, SIGNAL(folderComplete( KMFolderImap*, bool )),
      this, SLOT(slotImapFolderCompleted( KMFolderImap*, bool )));
  if ( success ) {
    // the folder was checked successfully but we were still called, so check
    // if we are still waiting for messages to show up. If so, uidValidity
    // changed, or something else went wrong. Clean up.

    /* Unfortunately older UW imap servers change uid validity for each put job.
     * Yes, it is really that broken. *sigh* So we cannot report error here, I guess. */
    if ( !mLostBoys.isEmpty() ) {
      kdDebug(5006) <<  "### Not all moved messages reported back that they were " << endl
                    <<  "### added to the target folder. Did uidValidity change? " << endl;
    }
    completeMove( OK );
  } else {
    // Should we inform the user here or leave that to the caller?
    completeMove( Failed );
  }
}

void KMMoveCommand::slotMsgAddedToDestFolder(KMFolder *folder, Q_UINT32 serNum)
{
  if (folder != mDestFolder || mLostBoys.find( serNum ) == mLostBoys.end() ) {
    //kdDebug(5006) << "KMMoveCommand::msgAddedToDestFolder different "
    //                 "folder or invalid serial number." << endl;
    return;
  }
  mLostBoys.remove(serNum);
  if ( mLostBoys.isEmpty() ) {
    // we are done. All messages transferred to the host succesfully
    disconnect (mDestFolder, SIGNAL(msgAdded(KMFolder*, Q_UINT32)),
        this, SLOT(slotMsgAddedToDestFolder(KMFolder*, Q_UINT32)));
    if (mDestFolder && mDestFolder->folderType() != KMFolderTypeImap) {
      mDestFolder->sync();
    }
  } else {
    mProgressItem->incCompletedItems();
    mProgressItem->updateProgress();
  }
}

void KMMoveCommand::completeMove( Result result )
{
  if ( mDestFolder )
    mDestFolder->close();
  while ( !mOpenedFolders.empty() ) {
    KMFolder *folder = mOpenedFolders.back();
    mOpenedFolders.pop_back();
    folder->close();
  }
  if ( mProgressItem )
  {
    mProgressItem->setComplete();
    mProgressItem = 0;
  }
  setResult( result );
  emit completed( this );
  deleteLater();
}

void KMMoveCommand::slotMoveCanceled()
{
  completeMove( Canceled );
}

// srcFolder doesn't make much sense for searchFolders
KMDeleteMsgCommand::KMDeleteMsgCommand( KMFolder* srcFolder,
  const QPtrList<KMMsgBase> &msgList )
:KMMoveCommand( findTrashFolder( srcFolder ), msgList)
{
  srcFolder->open();
  mOpenedFolders.push_back( srcFolder );
}

KMDeleteMsgCommand::KMDeleteMsgCommand( KMFolder* srcFolder, KMMessage * msg )
:KMMoveCommand( findTrashFolder( srcFolder ), msg)
{
  srcFolder->open();
  mOpenedFolders.push_back( srcFolder );
}

KMDeleteMsgCommand::KMDeleteMsgCommand( Q_UINT32 sernum )
:KMMoveCommand( sernum )
{
  KMFolder *srcFolder;
  int idx;
  kmkernel->msgDict()->getLocation( sernum, &srcFolder, &idx );
  KMMsgBase *msg = srcFolder->getMsgBase( idx );
  srcFolder->open();
  mOpenedFolders.push_back( srcFolder );
  addMsg( msg );
  setDestFolder( findTrashFolder( srcFolder ) );
}

KMFolder * KMDeleteMsgCommand::findTrashFolder( KMFolder * folder )
{
  KMFolder* trash = folder->trashFolder();
  if( !trash )
    trash = kmkernel->trashFolder();
  if( trash != folder )
    return trash;
  return 0;
}


KMUrlClickedCommand::KMUrlClickedCommand( const KURL &url, uint identity,
  KMReaderWin *readerWin, bool htmlPref, KMMainWidget *mainWidget )
  :mUrl( url ), mIdentity( identity ), mReaderWin( readerWin ),
   mHtmlPref( htmlPref ), mMainWidget( mainWidget )
{
}

KMCommand::Result KMUrlClickedCommand::execute()
{
  KMComposeWin *win;
  KMMessage* msg;

  if (mUrl.protocol() == "mailto")
  {
    msg = new KMMessage;
    msg->initHeader(mIdentity);
    msg->setCharset("utf-8");
    msg->setTo( KMMessage::decodeMailtoUrl( mUrl.path() ) );
    QString query=mUrl.query();
    while (!query.isEmpty()) {
      QString queryPart;
      int secondQuery = query.find('?',1);
      if (secondQuery != -1)
        queryPart = query.left(secondQuery);
      else
        queryPart = query;
      query = query.mid(queryPart.length());

      if (queryPart.left(9) == "?subject=")
        msg->setSubject( KURL::decode_string(queryPart.mid(9)) );
      else if (queryPart.left(6) == "?body=")
        // It is correct to convert to latin1() as URL should not contain
        // anything except ascii.
        msg->setBody( KURL::decode_string(queryPart.mid(6)).latin1() );
      else if (queryPart.left(4) == "?cc=")
        msg->setCc( KURL::decode_string(queryPart.mid(4)) );
    }

    win = new KMComposeWin(msg, mIdentity);
    win->setCharset("", TRUE);
    win->show();
  }
  else if ( mUrl.protocol() == "im" )
  {
    kmkernel->imProxy()->chatWithContact( mUrl.path() );
  }
  else if ((mUrl.protocol() == "http") || (mUrl.protocol() == "https") ||
           (mUrl.protocol() == "ftp") || (mUrl.protocol() == "file") ||
           (mUrl.protocol() == "ftps") || (mUrl.protocol() == "sftp" ) ||
           (mUrl.protocol() == "help") || (mUrl.protocol() == "vnc") ||
           (mUrl.protocol() == "smb"))
  {
    KPIM::BroadcastStatus::instance()->setStatusMsg( i18n("Opening URL..."));
    KMimeType::Ptr mime = KMimeType::findByURL( mUrl );
    if (mime->name() == "application/x-desktop" ||
        mime->name() == "application/x-executable" ||
        mime->name() == "application/x-msdos-program" ||
        mime->name() == "application/x-shellscript" )
    {
      if (KMessageBox::warningYesNo( 0, i18n( "<qt>Do you really want to execute <b>%1</b>?</qt>" )
        .arg( mUrl.prettyURL() ) ) != KMessageBox::Yes)
        return Canceled;
    }
    (void) new KRun( mUrl );
  }
  else
    return Failed;

  return OK;
}

KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, KMMessage *msg )
  : KMCommand( parent, msg ), mImplicitAttachments( true ), mEncoded( false )
{
}

KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, const QPtrList<KMMsgBase>& msgs )
  : KMCommand( parent, msgs ), mImplicitAttachments( true ), mEncoded( false )
{
}

KMSaveAttachmentsCommand::KMSaveAttachmentsCommand( QWidget *parent, QPtrList<partNode>& attachments,
                                                    KMMessage *msg, bool encoded )
  : KMCommand( parent, msg ), mImplicitAttachments( false ),
    mEncoded( encoded )
{
  // do not load the complete message but only parts
  msg->setComplete( true );
  for ( QPtrListIterator<partNode> it( attachments ); it.current(); ++it ) {
    mAttachmentMap.insert( it.current(), msg );
  }
}

KMCommand::Result KMSaveAttachmentsCommand::execute()
{
  setEmitsCompletedItself( true );
  if ( mImplicitAttachments ) {
    QPtrList<KMMessage> msgList = retrievedMsgs();
    KMMessage *msg;
    for ( QPtrListIterator<KMMessage> itr( msgList );
          ( msg = itr.current() );
          ++itr ) {
      partNode *rootNode = partNode::fromMessage( msg );
      for ( partNode *child = rootNode; child;
            child = child->firstChild() ) {
        for ( partNode *node = child; node; node = node->nextSibling() ) {
          if ( node->type() != DwMime::kTypeMultipart )
            mAttachmentMap.insert( node, msg );
        }
      }
    }
  }
  setDeletesItself( true );
  // load all parts
  KMLoadPartsCommand *command = new KMLoadPartsCommand( mAttachmentMap );
  connect( command, SIGNAL( partsRetrieved() ),
           this, SLOT( slotSaveAll() ) );
  command->start();

  return OK;
}

void KMSaveAttachmentsCommand::slotSaveAll()
{
  // now that all message parts have been retrieved, remove all parts which
  // don't represent an attachment if they were not explicitely passed in the
  // c'tor
  if ( mImplicitAttachments ) {
    for ( PartNodeMessageMap::iterator it = mAttachmentMap.begin();
          it != mAttachmentMap.end(); ) {
      // only body parts which have a filename or a name parameter (except for
      // the root node for which name is set to the message's subject) are
      // considered attachments
      if ( it.key()->msgPart().fileName().stripWhiteSpace().isEmpty() &&
           ( it.key()->msgPart().name().stripWhiteSpace().isEmpty() ||
             !it.key()->parentNode() ) ) {
        PartNodeMessageMap::iterator delIt = it;
        ++it;
        mAttachmentMap.remove( delIt );
      }
      else
        ++it;
    }
    if ( mAttachmentMap.isEmpty() ) {
      KMessageBox::information( 0, i18n("Found no attachments to save.") );
      setResult( OK ); // The user has already been informed.
      emit completed( this );
      delete this;
      return;
    }
  }

  KURL url, dirUrl;
  if ( mAttachmentMap.count() > 1 ) {
    // get the dir
    KFileDialog fdlg( ":saveAttachments", QString::null, parentWidget(),
                      "save attachments dialog", true );
    fdlg.setCaption( i18n("Save Attachments To") );
    fdlg.setOperationMode( KFileDialog::Saving );
    fdlg.setMode( (unsigned int) KFile::Directory );
    if ( fdlg.exec() == QDialog::Rejected || !fdlg.selectedURL().isValid() ) {
      setResult( Canceled );
      emit completed( this );
      delete this;
      return;
    }
    dirUrl = fdlg.selectedURL();
  }
  else {
    // only one item, get the desired filename
    partNode *node = mAttachmentMap.begin().key();
    // replace all ':' with '_' because ':' isn't allowed on FAT volumes
    QString s =
      node->msgPart().fileName().stripWhiteSpace().replace( ':', '_' );
    if ( s.isEmpty() )
      s = node->msgPart().name().stripWhiteSpace().replace( ':', '_' );
    if ( s.isEmpty() )
      s = i18n("filename for an unnamed attachment", "attachment.1");
    url = KFileDialog::getSaveURL( s, QString::null, parentWidget(),
                                   QString::null );
    if ( url.isEmpty() ) {
      setResult( Canceled );
      emit completed( this );
      delete this;
      return;
    }
  }

  QMap< QString, int > renameNumbering;

  Result globalResult = OK;
  int unnamedAtmCount = 0;
  for ( PartNodeMessageMap::const_iterator it = mAttachmentMap.begin();
        it != mAttachmentMap.end();
        ++it ) {
    KURL curUrl;
    if ( !dirUrl.isEmpty() ) {
      curUrl = dirUrl;
      QString s =
        it.key()->msgPart().fileName().stripWhiteSpace().replace( ':', '_' );
      if ( s.isEmpty() )
        s = it.key()->msgPart().name().stripWhiteSpace().replace( ':', '_' );
      if ( s.isEmpty() ) {
        ++unnamedAtmCount;
        s = i18n("filename for the %1-th unnamed attachment",
                 "attachment.%1")
            .arg( unnamedAtmCount );
      }
      curUrl.setFileName( s );
    } else {
      curUrl = url;
    }

    if ( !curUrl.isEmpty() ) {

     // Rename the file if we have already saved one with the same name:
     // try appending a number before extension (e.g. "pic.jpg" => "pic_2.jpg")
     QString origFile = curUrl.fileName();
     QString file = origFile;

     while ( renameNumbering.contains(file) ) {
       file = origFile;
       int num = renameNumbering[file] + 1;
       int dotIdx = file.findRev('.');
       file = file.insert( (dotIdx>=0) ? dotIdx : file.length(), QString("_") + QString::number(num) );
     }
     curUrl.setFileName(file);

     // Increment the counter for both the old and the new filename
     if ( !renameNumbering.contains(origFile))
         renameNumbering[origFile] = 1;
     else
         renameNumbering[origFile]++;

     if ( file != origFile ) {
        if ( !renameNumbering.contains(file))
            renameNumbering[file] = 1;
        else
            renameNumbering[file]++;
     }


      if ( KIO::NetAccess::exists( curUrl, false, parentWidget() ) ) {
        if ( KMessageBox::warningContinueCancel( parentWidget(),
              i18n( "A file named %1 already exists. Do you want to overwrite it?" )
              .arg( curUrl.fileName() ),
              i18n( "File Already Exists" ), i18n("&Overwrite") ) == KMessageBox::Cancel) {
          continue;
        }
      }
      // save
      const Result result = saveItem( it.key(), curUrl );
      if ( result != OK )
        globalResult = result;
    }
  }
  setResult( globalResult );
  emit completed( this );
  delete this;
}

KMCommand::Result KMSaveAttachmentsCommand::saveItem( partNode *node,
                                                      const KURL& url )
{
  bool bSaveEncrypted = false;
  bool bEncryptedParts = node->encryptionState() != KMMsgNotEncrypted;
  if( bEncryptedParts )
    if( KMessageBox::questionYesNo( parentWidget(),
          i18n( "The part %1 of the message is encrypted. Do you want to keep the encryption when saving?" ).
          arg( url.fileName() ),
          i18n( "KMail Question" ) ) ==
        KMessageBox::Yes )
      bSaveEncrypted = true;

  bool bSaveWithSig = true;
  if( node->signatureState() != KMMsgNotSigned )
    if( KMessageBox::questionYesNo( parentWidget(),
          i18n( "The part %1 of the message is signed. Do you want to keep the signature when saving?" ).
          arg( url.fileName() ),
          i18n( "KMail Question" ) ) !=
        KMessageBox::Yes )
      bSaveWithSig = false;

  QByteArray data;
  if ( mEncoded )
  {
    // This does not decode the Message Content-Transfer-Encoding
    // but saves the _original_ content of the message part
    QCString cstr( node->msgPart().body() );
    data = cstr;
    data.resize(data.size() - 1);
  }
  else
  {
    if( bSaveEncrypted || !bEncryptedParts) {
      partNode *dataNode = node;
      QCString rawReplyString;
      bool gotRawReplyString = false;
      if( !bSaveWithSig ) {
        if( DwMime::kTypeMultipart == node->type() &&
            DwMime::kSubtypeSigned == node->subType() ){
          // carefully look for the part that is *not* the signature part:
          if( node->findType( DwMime::kTypeApplication,
                DwMime::kSubtypePgpSignature,
                TRUE, false ) ){
            dataNode = node->findTypeNot( DwMime::kTypeApplication,
                DwMime::kSubtypePgpSignature,
                TRUE, false );
          }else if( node->findType( DwMime::kTypeApplication,
                DwMime::kSubtypePkcs7Mime,
                TRUE, false ) ){
            dataNode = node->findTypeNot( DwMime::kTypeApplication,
                DwMime::kSubtypePkcs7Mime,
                TRUE, false );
          }else{
            dataNode = node->findTypeNot( DwMime::kTypeMultipart,
                DwMime::kSubtypeUnknown,
                TRUE, false );
          }
	}else{
	  ObjectTreeParser otp( 0, 0, false, false, false );

	  // process this node and all it's siblings and descendants
	  dataNode->setProcessed( false, true );
	  otp.parseObjectTree( dataNode );

	  rawReplyString = otp.rawReplyString();
	  gotRawReplyString = true;
        }
      }
      QByteArray cstr = gotRawReplyString
                         ? rawReplyString
                         : dataNode->msgPart().bodyDecodedBinary();
      data = cstr;
      size_t size = cstr.size();
      if ( dataNode->msgPart().type() == DwMime::kTypeText ) {
        // convert CRLF to LF before writing text attachments to disk
        size = KMFolder::crlf2lf( cstr.data(), size );
      }
      data.resize( size );
    }
  }
  QDataStream ds;
  QFile file;
  KTempFile tf;
  tf.setAutoDelete( true );
  if ( url.isLocalFile() )
  {
    // save directly
    file.setName( url.path() );
    if ( !file.open( IO_WriteOnly ) )
    {
      KMessageBox::error( parentWidget(),
          i18n( "%2 is detailed error description",
            "Could not write the file %1:\n%2" )
          .arg( file.name() )
          .arg( QString::fromLocal8Bit( strerror( errno ) ) ),
          i18n( "KMail Error" ) );
      return Failed;
    }
    fchmod( file.handle(), S_IRUSR | S_IWUSR );
    ds.setDevice( &file );
  } else
  {
    // tmp file for upload
    ds.setDevice( tf.file() );
  }

  ds.writeRawBytes( data.data(), data.size() );
  if ( !url.isLocalFile() )
  {
    tf.close();
    if ( !KIO::NetAccess::upload( tf.name(), url, parentWidget() ) )
    {
      KMessageBox::error( parentWidget(),
          i18n( "Could not write the file %1." )
          .arg( url.path() ),
          i18n( "KMail Error" ) );
      return Failed;
    }
  } else
    file.close();
  return OK;
}

KMLoadPartsCommand::KMLoadPartsCommand( QPtrList<partNode>& parts, KMMessage *msg )
  : mNeedsRetrieval( 0 )
{
  for ( QPtrListIterator<partNode> it( parts ); it.current(); ++it ) {
    mPartMap.insert( it.current(), msg );
  }
}

KMLoadPartsCommand::KMLoadPartsCommand( partNode *node, KMMessage *msg )
  : mNeedsRetrieval( 0 )
{
  mPartMap.insert( node, msg );
}

KMLoadPartsCommand::KMLoadPartsCommand( PartNodeMessageMap& partMap )
  : mNeedsRetrieval( 0 ), mPartMap( partMap )
{
}

void KMLoadPartsCommand::slotStart()
{
  for ( PartNodeMessageMap::const_iterator it = mPartMap.begin();
        it != mPartMap.end();
        ++it ) {
    if ( !it.key()->msgPart().isComplete() &&
         !it.key()->msgPart().partSpecifier().isEmpty() ) {
      // incomplete part, so retrieve it first
      ++mNeedsRetrieval;
      KMFolder* curFolder = it.data()->parent();
      if ( curFolder ) {
        FolderJob *job =
          curFolder->createJob( it.data(), FolderJob::tGetMessage,
                                0, it.key()->msgPart().partSpecifier() );
        job->setCancellable( false );
        connect( job, SIGNAL(messageUpdated(KMMessage*, QString)),
                 this, SLOT(slotPartRetrieved(KMMessage*, QString)) );
        job->start();
      } else
        kdWarning(5006) << "KMLoadPartsCommand - msg has no parent" << endl;
    }
  }
  if ( mNeedsRetrieval == 0 )
    execute();
}

void KMLoadPartsCommand::slotPartRetrieved( KMMessage *msg,
                                            QString partSpecifier )
{
  DwBodyPart *part =
    msg->findDwBodyPart( msg->getFirstDwBodyPart(), partSpecifier );
  if ( part ) {
    // update the DwBodyPart in the partNode
    for ( PartNodeMessageMap::const_iterator it = mPartMap.begin();
          it != mPartMap.end();
          ++it ) {
      if ( it.key()->dwPart()->partId() == part->partId() )
        it.key()->setDwPart( part );
    }
  } else
    kdWarning(5006) << "KMLoadPartsCommand::slotPartRetrieved - could not find bodypart!" << endl;
  --mNeedsRetrieval;
  if ( mNeedsRetrieval == 0 )
    execute();
}

KMCommand::Result KMLoadPartsCommand::execute()
{
  emit partsRetrieved();
  setResult( OK );
  emit completed( this );
  deleteLater();
  return OK;
}

KMResendMessageCommand::KMResendMessageCommand( QWidget *parent,
   KMMessage *msg )
  :KMCommand( parent, msg )
{
}

KMCommand::Result KMResendMessageCommand::execute()
{
  KMComposeWin *win;
  KMMessage *msg = retrievedMessage();

  KMMessage *newMsg = new KMMessage(*msg);
  newMsg->setCharset(msg->codec()->mimeName());
  // the message needs a new Message-Id
  newMsg->removeHeaderField( "Message-Id" );
  newMsg->setParent( 0 );

  // adds the new date to the message
  newMsg->removeHeaderField( "Date" );

  win = new KMComposeWin();
  win->setMsg(newMsg, false, true);
  win->show();

  return OK;
}

KMMailingListCommand::KMMailingListCommand( QWidget *parent, KMFolder *folder )
  : KMCommand( parent ), mFolder( folder )
{
}

KMCommand::Result KMMailingListCommand::execute()
{
  KURL::List lst = urls();
  QString handler = ( mFolder->mailingList().handler() == MailingList::KMail )
    ? "mailto" : "https";

  KMCommand *command = 0;
  for ( KURL::List::Iterator itr = lst.begin(); itr != lst.end(); ++itr ) {
    if ( handler == (*itr).protocol() ) {
      command = new KMUrlClickedCommand( *itr, mFolder->identity(), 0, false );
    }
  }
  if ( !command && !lst.empty() ) {
    command =
      new KMUrlClickedCommand( lst.first(), mFolder->identity(), 0, false );
  }
  if ( command ) {
    connect( command, SIGNAL( completed( KMCommand * ) ),
             this, SLOT( commandCompleted( KMCommand * ) ) );
    setDeletesItself( true );
    setEmitsCompletedItself( true );
    command->start();
    return OK;
  }
  return Failed;
}

void KMMailingListCommand::commandCompleted( KMCommand *command )
{
  setResult( command->result() );
  emit completed( this );
  delete this;
}

KMMailingListPostCommand::KMMailingListPostCommand( QWidget *parent, KMFolder *folder )
  : KMMailingListCommand( parent, folder )
{
}
KURL::List KMMailingListPostCommand::urls() const
{
  return mFolder->mailingList().postURLS();
}

KMMailingListSubscribeCommand::KMMailingListSubscribeCommand( QWidget *parent, KMFolder *folder )
  : KMMailingListCommand( parent, folder )
{
}
KURL::List KMMailingListSubscribeCommand::urls() const
{
  return mFolder->mailingList().subscribeURLS();
}

KMMailingListUnsubscribeCommand::KMMailingListUnsubscribeCommand( QWidget *parent, KMFolder *folder )
  : KMMailingListCommand( parent, folder )
{
}
KURL::List KMMailingListUnsubscribeCommand::urls() const
{
  return mFolder->mailingList().unsubscribeURLS();
}

KMMailingListArchivesCommand::KMMailingListArchivesCommand( QWidget *parent, KMFolder *folder )
  : KMMailingListCommand( parent, folder )
{
}
KURL::List KMMailingListArchivesCommand::urls() const
{
  return mFolder->mailingList().archiveURLS();
}

KMMailingListHelpCommand::KMMailingListHelpCommand( QWidget *parent, KMFolder *folder )
  : KMMailingListCommand( parent, folder )
{
}
KURL::List KMMailingListHelpCommand::urls() const
{
  return mFolder->mailingList().helpURLS();
}

KMIMChatCommand::KMIMChatCommand( const KURL &url, KMMessage *msg )
  :mUrl( url ), mMessage( msg )
{
}

KMCommand::Result KMIMChatCommand::execute()
{
  kdDebug( 5006 ) << k_funcinfo << " URL is: " << mUrl << endl;
  QString addr = KMMessage::decodeMailtoUrl( mUrl.path() );
  // find UID for mail address
  KABC::AddressBook *addressBook = KABC::StdAddressBook::self();
  KABC::AddresseeList addressees = addressBook->findByEmail( KPIM::getEmailAddress( addr ) ) ;

  // start chat
  if( addressees.count() == 1 ) {
    kmkernel->imProxy()->chatWithContact( addressees[0].uid() );
    return OK;
  }
  else
  {
    kdDebug( 5006 ) << "Didn't find exactly one addressee, couldn't tell who to chat to for that email address.  Count = " << addressees.count() << endl;

    QString apology;
    if ( addressees.isEmpty() )
      apology = i18n( "There is no Address Book entry for this email address. Add them to the Address Book and then add instant messaging addresses using your preferred messaging client." );
    else
    {
      apology = i18n( "More than one Address Book entry uses this email address:\n %1\n it is not possible to determine who to chat with." );
      QStringList nameList;
      KABC::AddresseeList::const_iterator it = addressees.begin();
      KABC::AddresseeList::const_iterator end = addressees.end();
      for ( ; it != end; ++it )
      {
          nameList.append( (*it).realName() );
      }
      QString names = nameList.join( QString::fromLatin1( ",\n" ) );
      apology = apology.arg( names );
    }

    KMessageBox::sorry( parentWidget(), apology );
    return Failed;
  }
}

KMHandleAttachmentCommand::KMHandleAttachmentCommand( partNode* node,
     KMMessage* msg, int atmId, const QString& atmName,
     AttachmentAction action, KService::Ptr offer )
: mNode( node ), mMsg( msg ), mAtmId( atmId ), mAtmName( atmName ),
  mAction( action ), mOffer( offer )
{
}

void KMHandleAttachmentCommand::slotStart()
{
  if ( !mNode->msgPart().isComplete() )
  {
    // load the part
    KMLoadPartsCommand *command = new KMLoadPartsCommand( mNode, mMsg );
    connect( command, SIGNAL( partsRetrieved() ),
        this, SLOT( slotPartComplete() ) );
    command->start();
  } else
  {
    execute();
  }
}

void KMHandleAttachmentCommand::slotPartComplete()
{
  execute();
}

KMCommand::Result KMHandleAttachmentCommand::execute()
{
  switch( mAction )
  {
    case Open:
      atmOpen();
      break;
    case OpenWith:
      atmOpenWith();
      break;
    case View:
      atmView();
      break;
    case Save:
      atmSave();
      break;
    case Properties:
      atmProperties();
      break;
    default:
      kdDebug(5006) << "unknown action " << mAction << endl;
      break;
  }
  setResult( OK );
  emit completed( this );
  deleteLater();
  return OK;
}

QString KMHandleAttachmentCommand::createAtmFileLink() const
{
  QFileInfo atmFileInfo( mAtmName );

  if ( atmFileInfo.size() == 0 )
  {
    kdDebug(5006) << k_funcinfo << "rewriting attachment" << endl;
    // there is something wrong so write the file again
    QByteArray data = mNode->msgPart().bodyDecodedBinary();
    size_t size = data.size();
    if ( mNode->msgPart().type() == DwMime::kTypeText && size) {
      // convert CRLF to LF before writing text attachments to disk
      size = KMFolder::crlf2lf( data.data(), size );
    }
    KPIM::kBytesToFile( data.data(), size, mAtmName, false, false, false );
  }

  KTempFile *linkFile = new KTempFile( locateLocal("tmp", atmFileInfo.fileName() +"_["),
                          "]."+ atmFileInfo.extension() );

  linkFile->setAutoDelete(true);
  QString linkName = linkFile->name();
  delete linkFile;

  if ( ::link(QFile::encodeName( mAtmName ), QFile::encodeName( linkName )) == 0 ) {
    return linkName; // success
  }
  return QString::null;
}

KService::Ptr KMHandleAttachmentCommand::getServiceOffer()
{
  KMMessagePart& msgPart = mNode->msgPart();
  const QString contentTypeStr =
    ( msgPart.typeStr() + '/' + msgPart.subtypeStr() ).lower();

  if ( contentTypeStr == "text/x-vcard" ) {
    atmView();
    return 0;
  }
  // determine the MIME type of the attachment
  KMimeType::Ptr mimetype;
  // prefer the value of the Content-Type header
  mimetype = KMimeType::mimeType( contentTypeStr );
  if ( mimetype->name() == "application/octet-stream" ) {
    // consider the filename if Content-Type is application/octet-stream
    mimetype = KMimeType::findByPath( mAtmName, 0, true /* no disk access */ );
  }
  if ( ( mimetype->name() == "application/octet-stream" )
       && msgPart.isComplete() ) {
    // consider the attachment's contents if neither the Content-Type header
    // nor the filename give us a clue
    mimetype = KMimeType::findByFileContent( mAtmName );
  }
  return KServiceTypeProfile::preferredService( mimetype->name(), "Application" );
}

void KMHandleAttachmentCommand::atmOpen()
{
  if ( !mOffer )
    mOffer = getServiceOffer();
  if ( !mOffer )
    return;

  KURL::List lst;
  KURL url;
  bool autoDelete = true;
  QString fname = createAtmFileLink();

  if ( fname.isNull() ) {
    autoDelete = false;
    fname = mAtmName;
  }

  url.setPath( fname );
  lst.append( url );
  if ( (KRun::run( *mOffer, lst, autoDelete ) <= 0) && autoDelete ) {
      QFile::remove(url.path());
  }
}

void KMHandleAttachmentCommand::atmOpenWith()
{
  KURL::List lst;
  KURL url;
  bool autoDelete = true;
  QString fname = createAtmFileLink();

  if ( fname.isNull() ) {
    autoDelete = false;
    fname = mAtmName;
  }

  url.setPath( fname );
  lst.append( url );
  if ( (! KRun::displayOpenWithDialog(lst, autoDelete)) && autoDelete ) {
    QFile::remove( url.path() );
  }
}

void KMHandleAttachmentCommand::atmView()
{
  // we do not handle this ourself
  emit showAttachment( mAtmId, mAtmName );
}

void KMHandleAttachmentCommand::atmSave()
{
  QPtrList<partNode> parts;
  parts.append( mNode );
  // save, do not leave encoded
  KMSaveAttachmentsCommand *command =
    new KMSaveAttachmentsCommand( 0, parts, mMsg, false );
  command->start();
}

void KMHandleAttachmentCommand::atmProperties()
{
  KMMsgPartDialogCompat dlg( 0, true );
  KMMessagePart& msgPart = mNode->msgPart();
  dlg.setMsgPart( &msgPart );
  dlg.exec();
}

