From cdb3d99f65e4818bc59fdbf86ac3e779bbeeb0fc Mon Sep 17 00:00:00 2001 From: Till Adam Date: Sun, 25 May 2003 18:23:25 +0000 Subject: [PATCH] Refactor the saving of messages. o Add bool deletesItself to KMCommand to remove the need to keep job data globally in kmkernel. o Don't suck all messages into memory and then dump into KIO, but handle them individually, downloading each if necessary first and then appending it. Improves memory usage brutally. ;) o Make it work safely in the background so changing the current message or folder doesn't crash while a save is going, on by using sernums instead of KMMsgBase pointers. This relies on changes to kio in head committed today, which make async putting of data and reporting of sent data in the progress dialog possible. svn path=/trunk/kdepim/; revision=227839 --- Makefile.am | 4 +- kmcommands.cpp | 146 ++++++++++++++++++++++++++++++++++++++++++++----- kmcommands.h | 20 ++++++- 3 files changed, 154 insertions(+), 16 deletions(-) diff --git a/Makefile.am b/Makefile.am index f4a53cf79..f22910d9e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -74,7 +74,7 @@ libkmailcommon_la_SOURCES = kmmessage.cpp kmmainwin.cpp configuredialog.cpp \ maildirjob.cpp mboxjob.cpp imapjob.cpp \ subscriptiondialog.cpp kmailicalifaceimpl.cpp aboutdata.cpp \ folderIface.cpp folderIface.skel mailserviceimpl.cpp \ - attachmentlistview.cpp + attachmentlistview.cpp kmail_SOURCES = main.cpp @@ -91,7 +91,7 @@ noinst_HEADERS = adddirectoryservicedialogimpl.h kmmimeparttree.h \ signatureconfigurationdialogimpl.h \ encryptionconfigurationdialogimpl.h \ signatureconfigurationdialogimpl.h \ - kmailicalifaceimpl.h mailserviceimpl.h + kmailicalifaceimpl.h mailserviceimpl.h dcoptest_SOURCES = dcoptest.cpp kmailIface.skel mailcomposerIface.skel diff --git a/kmcommands.cpp b/kmcommands.cpp index 7841a057d..494d27f3c 100644 --- a/kmcommands.cpp +++ b/kmcommands.cpp @@ -149,7 +149,8 @@ void KMCommand::slotPostTransfer(bool success) if (msg->parent()) msg->setTransferInProgress(false); } - delete this; + if ( !deletesItself() ) + delete this; } void KMCommand::transferSelectedMsgs() @@ -516,11 +517,24 @@ void KMShowMsgSrcCommand::execute() KMSaveMsgCommand::KMSaveMsgCommand( QWidget *parent, const QPtrList &msgList ) - :KMCommand( parent, msgList ) + :KMCommand( parent ), 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 it(msgList); + while ( it.current() ) { + mMsgList.append( (*it)->getMsgSerNum() ); + mTotalSize += (*it)->msgSize(); + (*it)->parent()->open(); + ++it; + } + mMsgListIndex = 0; QString subject = msgBase->subject(); while (subject.find(':') != -1) subject = subject.mid(subject.find(':') + 1).stripWhiteSpace(); @@ -535,21 +549,127 @@ KURL KMSaveMsgCommand::url() void KMSaveMsgCommand::execute() { -//TODO: Handle messages one by one - QPtrList msgList = retrievedMsgs(); - QCString str; - if (!msgList.getFirst()) + mJob = KIO::put( mUrl, -1, 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*))); +} + +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 + mOffset, remainingBytes ); + mJob->sendAsyncData( mData ); + mOffset += remainingBytes; return; + } + // No leftovers, process next message. + if ( mMsgListIndex < mMsgList.size() ) { + KMMessage *msg = 0; + int idx = -1; + KMFolder * p = 0; + kernel->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); + connect(job, SIGNAL(messageRetrieved(KMMessage*)), + this, SLOT(slotMessageRetrievedForSaving(KMMessage*))); + job->start(); + } + } + } else { + // No more messages. Tell the putjob we are done. + QByteArray data = QByteArray(); + mJob->sendAsyncData( data ); + } +} - for (KMMessage *msg = msgList.first(); msg; msg = msgList.next()) { - str += "From " + msg->fromEmail() + " " + msg->dateShortStr() + "\n"; - str += msg->asString(); - str += "\n"; +void KMSaveMsgCommand::slotMessageRetrievedForSaving(KMMessage *msg) +{ + QCString str; + str += "From " + msg->fromEmail() + " " + msg->dateShortStr() + "\n"; + str += 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->parent()) { + int idx = -1; + KMFolder * p = 0; + kernel->msgDict()->getLocation( msg, &p, &idx ); + assert( p == msg->parent() ); assert( idx >= 0 ); + p->unGetMsg( idx ); + p->close(); } +} - QByteArray ba = str; - ba.resize(ba.size() - 1); - kernel->byteArrayToRemoteFile(ba, mUrl); +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) { + mMsgListIndex = 0; + + mJob = KIO::put( mUrl, -1, 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(); + delete this; + } + } else { + delete this; + } } //TODO: ReplyTo, NoQuoteReplyTo, ReplyList, ReplyToAll are all similar diff --git a/kmcommands.h b/kmcommands.h index d97e09246..01bbbe89c 100644 --- a/kmcommands.h +++ b/kmcommands.h @@ -37,6 +37,11 @@ public: KMCommand( QWidget *parent, KMMsgBase *msgBase ); virtual ~KMCommand(); + bool deletesItself () { return mDeletesItself; } + void setDeletesItself( bool deletesItself ) + { mDeletesItself = deletesItself; } + + public slots: // Retrieve messages then calls execute void start(); @@ -65,7 +70,6 @@ private slots: void slotJobFinished(); /** the transfer was cancelled */ void slotTransferCancelled(); - signals: void messagesTransfered(bool); @@ -75,6 +79,7 @@ private: //Currently only one async command allowed at a time static int mCountJobs; int mCountMsgs; + bool mDeletesItself; QWidget *mParent; QPtrList mRetrievedMsgs; @@ -249,8 +254,21 @@ public: private: virtual void execute(); +private slots: + void slotSaveDataReq(); + void slotSaveResult(KIO::Job *job); + /** the message has been transferred for saving */ + void slotMessageRetrievedForSaving(KMMessage *msg); + private: + static const int MAX_CHUNK_SIZE = 64*1024; KURL mUrl; + QValueList mMsgList; + unsigned int mMsgListIndex; + QByteArray mData; + int mOffset; + size_t mTotalSize; + KIO::TransferJob *mJob; }; class KMReplyToCommand : public KMCommand