Make it possible to use a distinct selection model in the foldertreewidget.
[kdepim.git] / messagecomposer / composer.cpp
blob8cb1e730c516e29514a9e407cbbd1557c5b85877
1 /*
2 Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com>
3 Copyright (C) 2009 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
4 Copyright (c) 2009 Leo Franchi <lfranchi@kde.org>
6 This library is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Library General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or (at your
9 option) any later version.
11 This library is distributed in the hope that it will be useful, but WITHOUT
12 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
14 License for more details.
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301, USA.
22 #include "composer.h"
24 #include "attachmentjob.h"
25 #include "globalpart.h"
26 #include "infopart.h"
27 #include "jobbase_p.h"
28 #include "textpart.h"
29 #include "maintextjob.h"
30 #include "multipartjob.h"
31 #include "signjob.h"
32 #include "encryptjob.h"
33 #include "signencryptjob.h"
34 #include "skeletonmessagejob.h"
35 #include "transparentjob.h"
37 #include <QTimer>
39 #include <KDebug>
40 #include <klocalizedstring.h>
42 using namespace Message;
43 using KPIM::AttachmentPart;
45 class Message::ComposerPrivate : public JobBasePrivate
47 public:
48 ComposerPrivate( Composer *qq )
49 : JobBasePrivate( qq )
50 , started( false )
51 , finished( false )
52 , sign( false )
53 , encrypt( false )
54 , noCrypto( false )
55 , globalPart( 0 )
56 , infoPart( 0 )
57 , textPart( 0 )
58 , skeletonMessage( 0 )
59 , resultContent( 0 )
63 void init();
64 void doStart(); // slot
65 void composeStep1();
66 void skeletonJobFinished( KJob *job ); // slot
67 void composeStep2();
68 void contentJobFinished( KJob *job ); // slot
69 void contentJobPreCryptFinished( KJob *job ); // slot
70 void contentJobPreInlineFinished( KJob *job ); // slot
71 void signBeforeEncryptJobFinished( KJob *job ); // slot
72 void startEncryptJobs( KMime::Content* content );
73 void composeWithLateAttachments( KMime::Message* headers, KMime::Content* content, AttachmentPart::List parts, std::vector<GpgME::Key> keys, QStringList recipients );
74 void attachmentsFinished( KJob* job ); // slot
76 void composeFinalStep( KMime::Content* headers, KMime::Content* content );
77 bool started;
78 bool finished;
79 bool sign;
80 bool encrypt;
81 bool noCrypto;
83 Kleo::CryptoMessageFormat format;
84 std::vector<GpgME::Key> signers;
85 QList<QPair<QStringList, std::vector<GpgME::Key> > > encData;
87 QList<KMime::Message::Ptr> resultMessages;
89 // Stuff that the application plays with.
90 GlobalPart *globalPart;
91 InfoPart *infoPart;
92 TextPart *textPart;
93 AttachmentPart::List attachmentParts;
94 // attachments with different sign/encrypt settings from
95 // main message body. added at the end of the process
96 AttachmentPart::List lateAttachmentParts;
99 // Stuff that we play with.
100 KMime::Message *skeletonMessage;
101 KMime::Content *resultContent;
103 Q_DECLARE_PUBLIC( Composer )
106 void ComposerPrivate::init()
108 Q_Q( Composer );
110 // We cannot create these in ComposerPrivate's constructor, because
111 // their parent q is not fully constructed at that time.
112 globalPart = new GlobalPart( q );
113 infoPart = new InfoPart( q );
114 textPart = new TextPart( q );
117 void ComposerPrivate::doStart()
119 Q_ASSERT( !started );
120 started = true;
121 composeStep1();
124 void ComposerPrivate::composeStep1()
126 Q_Q( Composer );
128 // Create skeleton message (containing headers only; no content).
129 SkeletonMessageJob *skeletonJob = new SkeletonMessageJob( infoPart, globalPart, q );
130 QObject::connect( skeletonJob, SIGNAL(finished(KJob*)), q, SLOT(skeletonJobFinished(KJob*)) );
131 q->addSubjob( skeletonJob );
132 skeletonJob->start();
135 void ComposerPrivate::skeletonJobFinished( KJob *job )
137 if( job->error() ) {
138 return; // KCompositeJob takes care of the error.
141 Q_ASSERT( dynamic_cast<SkeletonMessageJob*>( job ) );
142 SkeletonMessageJob *sjob = static_cast<SkeletonMessageJob*>( job );
143 // SkeletonMessageJob is a special job creating a Message instead of a Content.
144 Q_ASSERT( skeletonMessage == 0 );
145 skeletonMessage = sjob->message();
146 Q_ASSERT( skeletonMessage );
147 skeletonMessage->assemble();
149 composeStep2();
152 void ComposerPrivate::composeStep2()
154 Q_Q( Composer );
156 ContentJobBase *mainJob = 0;
157 MainTextJob *mainTextJob = new MainTextJob( textPart, q );
158 if( attachmentParts.isEmpty() ) {
159 // We have no attachments. Use the content given by the MainTextJob.
160 mainJob = mainTextJob;
161 } else {
162 // We have attachments. Create a multipart/mixed content.
163 QMutableListIterator<AttachmentPart::Ptr> iter( attachmentParts );
164 while( iter.hasNext() ) {
165 AttachmentPart::Ptr part = iter.next();
166 kDebug() << "Checking attachment crypto policy..." << part->isSigned() << part->isEncrypted();
167 if( !noCrypto && ( sign != part->isSigned() || encrypt != part->isEncrypted() ) ) { // different policy
168 kDebug() << "got attachment with different crypto policy!";
169 lateAttachmentParts.append( part );
170 iter.remove();
173 MultipartJob *multipartJob = new MultipartJob( q );
174 multipartJob->setMultipartSubtype( "mixed" );
175 multipartJob->appendSubjob( mainTextJob );
176 foreach( AttachmentPart::Ptr part, attachmentParts ) {
177 multipartJob->appendSubjob( new AttachmentJob( part ) );
179 mainJob = multipartJob;
181 if( sign && encrypt && format & Kleo::InlineOpenPGPFormat ) { // needs custom handling--- one SignEncryptJob by itself
182 kDebug() << "sending to sign/enc inline job!";
183 QObject::connect( mainJob, SIGNAL(finished(KJob*)), q, SLOT(contentJobPreInlineFinished(KJob*)) );
184 } else if( sign || encrypt ) {
185 QObject::connect( mainJob, SIGNAL(finished(KJob*)), q, SLOT(contentJobPreCryptFinished(KJob*)) );
186 } else {
187 QObject::connect( mainJob, SIGNAL(finished(KJob*)), q, SLOT(contentJobFinished(KJob*)) );
189 q->addSubjob( mainJob );
190 mainJob->start();
194 void ComposerPrivate::contentJobPreInlineFinished( KJob *job )
196 Q_Q( Composer );
198 Q_ASSERT( format & Kleo::InlineOpenPGPFormat );
199 Q_ASSERT( sign && encrypt ); // for safety... we shouldn't be here otherwise
200 Q_ASSERT( dynamic_cast<ContentJobBase*>( job ) );
201 ContentJobBase *cjob = static_cast<ContentJobBase*>( job );
203 kDebug() << "creaeting inline signandenc job";
204 if( encData.size() == 0 ) { // no key data! bail!
205 q->setErrorText( i18n( "No key data for recipients found." ) );
206 q->setError( Composer::IncompleteError );
207 q->emitResult();
208 return;
212 for( int i = 0; i < encData.size(); ++i ) {
213 QPair<QStringList, std::vector<GpgME::Key> > recipients = encData[ i ];
214 kDebug() << "got first list of recipients:" << recipients.first;
215 SignEncryptJob* seJob = new SignEncryptJob( q );
216 seJob->setContent( cjob->content() );
217 seJob->setCryptoMessageFormat( format );
218 seJob->setEncryptionKeys( recipients.second );
219 seJob->setSigningKeys( signers );
220 seJob->setRecipients( recipients.first );
222 QObject::connect( seJob, SIGNAL( finished( KJob* ) ), q, SLOT( contentJobFinished( KJob* ) ) );
224 q->addSubjob( seJob );
225 seJob->start();
230 void ComposerPrivate::contentJobPreCryptFinished( KJob *job )
232 Q_Q( Composer );
234 // we're signing or encrypting or both, so add an additional job to the process
235 Q_ASSERT( dynamic_cast<ContentJobBase*>( job ) );
236 ContentJobBase *cjob = static_cast<ContentJobBase*>( job );
239 if( sign ) {
240 SignJob* sJob = new SignJob( q );
241 sJob->setContent( cjob->content() );
242 sJob->setCryptoMessageFormat( format );
243 sJob->setSigningKeys( signers );
245 if( encrypt ) {
246 QObject::connect( sJob, SIGNAL( finished( KJob* ) ), q, SLOT( signBeforeEncryptJobFinished( KJob* ) ) );
247 } else {
248 QObject::connect( sJob, SIGNAL( finished( KJob* ) ), q, SLOT( contentJobFinished( KJob* ) ) );
250 q->addSubjob( sJob );
251 sJob->start();
253 } else if( encrypt ) {
254 // just encrypting, so setup the jobs directly
255 startEncryptJobs( cjob->content() );
260 void ComposerPrivate::signBeforeEncryptJobFinished( KJob* job )
263 if( job->error() ) {
264 return; // KCompositeJob takes care of the error.
267 Q_ASSERT( dynamic_cast<ContentJobBase*>( job ) );
268 ContentJobBase *cjob = static_cast<ContentJobBase*>( job );
270 // cjob holds the signed content, now we encrypt per recipient
271 startEncryptJobs( cjob->content() );
275 void ComposerPrivate::startEncryptJobs( KMime::Content* content ) {
276 Q_Q( Composer );
278 // each SplitInfo holds a list of recipients/keys, if there is more than
279 // one item in it then it means there are secondary recipients that need
280 // different messages w/ clean headers
281 kDebug() << "starting enc jobs";
282 kDebug() << "format:" << format;
283 kDebug() << "enc data:" << encData.size();
285 if( encData.size() == 0 ) { // no key data! bail!
286 q->setErrorText( i18n( "No key data for recipients found." ) );
287 q->setError( Composer::IncompleteError );
288 q->emitResult();
289 return;
292 for( int i = 0; i < encData.size(); ++i ) {
293 QPair<QStringList, std::vector<GpgME::Key> > recipients = encData[ i ];
294 kDebug() << "got first list of recipients:" << recipients.first;
295 EncryptJob* eJob = new EncryptJob( q );
296 eJob->setContent( content );
297 eJob->setCryptoMessageFormat( format );
298 eJob->setEncryptionKeys( recipients.second );
299 eJob->setRecipients( recipients.first );
301 QObject::connect( eJob, SIGNAL( finished( KJob* ) ), q, SLOT( contentJobFinished( KJob* ) ) );
303 q->addSubjob( eJob );
304 eJob->start();
309 void ComposerPrivate::contentJobFinished( KJob *job )
311 Q_Q( Composer );
313 if( job->error() ) {
314 return; // KCompositeJob takes care of the error.
316 kDebug() << "composing final message";
318 KMime::Message* headers;
319 KMime::Content* resultContent;
320 std::vector<GpgME::Key> keys;
321 QStringList recipients;
323 Q_ASSERT( dynamic_cast<ContentJobBase*>( job ) == static_cast<ContentJobBase*>( job ) );
324 ContentJobBase* contentJob = static_cast<ContentJobBase*>( job );
326 // create the final headers and body,
327 // taking into account secondary recipients for encryption
328 if( encData.size() > 1 ) { // crypto job with secondary recipients..
329 Q_ASSERT( dynamic_cast<AbstractEncryptJob*>( job ) ); // we need to get the recipients for this job
330 AbstractEncryptJob* eJob = dynamic_cast<AbstractEncryptJob*>( job );
332 keys = eJob->encryptionKeys();
333 recipients = eJob->recipients();
335 resultContent = contentJob->content(); // content() comes from superclass
336 headers = new KMime::Message;
337 headers->setHeader( skeletonMessage->from() );
338 headers->setHeader( skeletonMessage->to() );
339 headers->setHeader( skeletonMessage->subject() );
341 KMime::Headers::Bcc *bcc = new KMime::Headers::Bcc( headers );
342 foreach( const QString &a, eJob->recipients() ) {
343 KMime::Types::Mailbox address;
344 address.fromUnicodeString( a );
345 bcc->addAddress( address );
348 kDebug() << "got one of multiple messages sending to:" << bcc->asUnicodeString();
349 kDebug() << "sending to recipients:" << recipients;
350 headers->setHeader( bcc );
351 headers->assemble();
352 } else { // just use the saved headers from before
353 if( encData.size() > 0 ) {
354 kDebug() << "setting enc data:" << encData[ 0 ].first << "with num keys:" << encData[ 0 ].second.size();
355 keys = encData[ 0 ].second;
356 recipients = encData[ 0 ].first;
359 headers = skeletonMessage;
360 resultContent = contentJob->content();
362 // manually remove the subjob so we can check if we have any left later
363 q->removeSubjob( job );
365 if( lateAttachmentParts.isEmpty() ) {
366 composeFinalStep( headers, resultContent );
367 } else {
368 composeWithLateAttachments( headers, resultContent, lateAttachmentParts, keys, recipients );
373 void ComposerPrivate::composeWithLateAttachments( KMime::Message* headers, KMime::Content* content, AttachmentPart::List parts, std::vector<GpgME::Key> keys, QStringList recipients )
375 Q_Q( Composer );
377 MultipartJob* multiJob = new MultipartJob( q );
378 multiJob->setMultipartSubtype( "mixed" );
380 // wrap the content into a job for the multijob to handle it
381 TransparentJob* tJob = new TransparentJob( q );
382 tJob->setContent( content );
383 multiJob->appendSubjob( tJob );
384 multiJob->setExtraContent( headers );
386 kDebug() << "attachment encr key size:" << keys.size() << recipients;
388 // operate correctly on each attachment that has a different crypto policy than body.
389 foreach( AttachmentPart::Ptr attachment, parts ) {
390 AttachmentJob* attachJob = new AttachmentJob( attachment, q );
392 kDebug() << "got a late attachment";
393 if( attachment->isSigned() ) {
394 kDebug() << "adding signjob for late attachment";
395 SignJob* sJob = new SignJob( q );
396 sJob->setContent( 0 );
397 sJob->setCryptoMessageFormat( format );
398 sJob->setSigningKeys( signers );
400 sJob->appendSubjob( attachJob );
401 if( attachment->isEncrypted() ) {
402 kDebug() << "adding sign + encrypt job for late attachment";
403 EncryptJob* eJob = new EncryptJob( q );
404 eJob->setCryptoMessageFormat( format );
405 eJob->setEncryptionKeys( keys );
406 eJob->setRecipients( recipients );
408 eJob->appendSubjob( sJob );
410 multiJob->appendSubjob( eJob );
411 } else {
412 kDebug() << "Just signing late attachment";
413 multiJob->appendSubjob( sJob );
415 } else if( attachment->isEncrypted() ) { // only encryption
416 kDebug() << "just encrypting late attachment";
417 EncryptJob* eJob = new EncryptJob( q );
418 eJob->setCryptoMessageFormat( format );
419 eJob->setEncryptionKeys( keys );
420 eJob->setRecipients( recipients );
422 eJob->appendSubjob( attachJob );
423 multiJob->appendSubjob( eJob );
424 } else {
425 kDebug() << "attaching plain non-crypto attachment";
426 AttachmentJob* attachJob = new AttachmentJob( attachment, q );
427 multiJob->appendSubjob( attachJob );
431 QObject::connect( multiJob, SIGNAL( finished( KJob* ) ), q, SLOT( attachmentsFinished( KJob* ) ) );
433 q->addSubjob( multiJob );
434 multiJob->start();
437 void ComposerPrivate::attachmentsFinished( KJob* job ) {
438 Q_Q( Composer );
440 if( job->error() ) {
441 return; // KCompositeJob takes care of the error.
443 kDebug() << "composing final message with late attachments";
445 Q_ASSERT( dynamic_cast<ContentJobBase*>( job ) );
446 ContentJobBase* contentJob = static_cast<ContentJobBase*>( job );
448 KMime::Content* content = contentJob->content();
449 KMime::Content* headers = contentJob->extraContent();
451 q->removeSubjob( job );
452 composeFinalStep( headers, content );
456 void ComposerPrivate::composeFinalStep( KMime::Content* headers, KMime::Content* content )
458 Q_Q( Composer );
460 content->assemble();
462 QByteArray allData = headers->head() + content->encodedContent();
463 KMime::Message::Ptr resultMessage( new KMime::Message );
464 resultMessage->setContent( allData );
465 resultMessage->parse(); // Not strictly necessary.
466 resultMessages.append( resultMessage );
468 kDebug() << "still have subjobs:" << q->hasSubjobs() << "num:" << q->subjobs().size();
469 if( !q->hasSubjobs() ) {
470 finished = true;
471 q->emitResult();
475 Composer::Composer( QObject *parent )
476 : JobBase( *new ComposerPrivate( this ), parent )
478 Q_D( Composer );
479 d->init();
482 Composer::~Composer()
486 QList<KMime::Message::Ptr> Composer::resultMessages() const
488 Q_D( const Composer );
489 Q_ASSERT( d->finished );
490 Q_ASSERT( !error() );
491 QList<KMime::Message::Ptr> results = d->resultMessages;
492 return results;
495 GlobalPart *Composer::globalPart()
497 Q_D( Composer );
498 return d->globalPart;
501 InfoPart* Composer::infoPart()
503 Q_D( Composer );
504 return d->infoPart;
507 TextPart *Composer::textPart()
509 Q_D( Composer );
510 return d->textPart;
513 AttachmentPart::List Composer::attachmentParts()
515 Q_D( Composer );
516 return d->attachmentParts;
519 void Composer::addAttachmentPart( AttachmentPart::Ptr part )
521 Q_D( Composer );
522 Q_ASSERT( !d->started );
523 Q_ASSERT( !d->attachmentParts.contains( part ) );
524 d->attachmentParts.append( part );
527 void Composer::addAttachmentParts( const AttachmentPart::List &parts )
529 foreach( AttachmentPart::Ptr part, parts ) {
530 addAttachmentPart( part );
534 void Composer::removeAttachmentPart( AttachmentPart::Ptr part )
536 Q_D( Composer );
537 Q_ASSERT( !d->started );
538 if( d->attachmentParts.contains( part ) ) {
539 d->attachmentParts.removeAll( part );
540 } else {
541 kError() << "Unknown attachment part" << part;
542 Q_ASSERT( false );
543 return;
547 void Composer::setSignAndEncrypt( const bool doSign, const bool doEncrypt )
549 Q_D( Composer );
550 d->sign = doSign;
551 d->encrypt = doEncrypt;
555 void Composer::setMessageCryptoFormat( Kleo::CryptoMessageFormat format )
557 Q_D( Composer );
559 d->format = format;
562 void Composer::setSigningKeys( std::vector<GpgME::Key>& signers )
564 Q_D( Composer );
566 d->signers = signers;
569 void Composer::setEncryptionKeys( QList<QPair<QStringList, std::vector<GpgME::Key> > > encData )
571 Q_D( Composer );
573 d->encData = encData;
576 void Composer::setNoCrypto(bool noCrypto)
578 Q_D( Composer );
580 d->noCrypto = noCrypto;
584 void Composer::start()
586 QTimer::singleShot( 0, this, SLOT(doStart()) );
589 #include "composer.moc"