2
Copyright (c) 2007 Till Adam <adam@kde.org>
3
Copyright (C) 2008 Omat Holding B.V. <info@omat.nl>
4
Copyright (C) 2009 Kevin Ottens <ervin@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
22
#include "imapresource.h"
24
#include "setupserver.h"
26
#include "uidvalidityattribute.h"
27
#include "uidnextattribute.h"
28
#include "noselectattribute.h"
30
#include <QtCore/QDebug>
31
#include <QtDBus/QDBusConnection>
32
#include <QtNetwork/QSslSocket>
36
#include <kpassworddialog.h>
37
#include <kmessagebox.h>
38
#include <KWindowSystem>
41
#include <kimap/session.h>
42
#include <kimap/sessionuiproxy.h>
44
#include <kimap/appendjob.h>
45
#include <kimap/capabilitiesjob.h>
46
#include <kimap/createjob.h>
47
#include <kimap/deletejob.h>
48
#include <kimap/expungejob.h>
49
#include <kimap/fetchjob.h>
50
#include <kimap/getacljob.h>
51
#include <kimap/getmetadatajob.h>
52
#include <kimap/getquotarootjob.h>
53
#include <kimap/listjob.h>
54
#include <kimap/loginjob.h>
55
#include <kimap/logoutjob.h>
56
#include <kimap/myrightsjob.h>
57
#include <kimap/renamejob.h>
58
#include <kimap/selectjob.h>
59
#include <kimap/storejob.h>
61
#include <kmime/kmime_message.h>
63
typedef boost::shared_ptr<KMime::Message> MessagePtr;
65
#include <akonadi/attributefactory.h>
66
#include <akonadi/cachepolicy.h>
67
#include <akonadi/collectionfetchjob.h>
68
#include <akonadi/collectionmodifyjob.h>
69
#include <akonadi/collectionstatisticsjob.h>
70
#include <akonadi/collectionstatistics.h>
71
#include <akonadi/monitor.h>
72
#include <akonadi/changerecorder.h>
73
#include <akonadi/collectiondeletejob.h>
74
#include <akonadi/itemdeletejob.h>
75
#include <akonadi/itemfetchjob.h>
76
#include <akonadi/itemfetchscope.h>
77
#include <akonadi/session.h>
78
#include <akonadi/transactionsequence.h>
80
#include "collectionflagsattribute.h"
81
#include "collectionannotationsattribute.h"
82
#include "imapaclattribute.h"
83
#include "imapquotaattribute.h"
85
#include "imapaccount.h"
87
using namespace Akonadi;
89
ImapResource::ImapResource( const QString &id )
90
:ResourceBase( id ), m_account( 0 )
92
Akonadi::AttributeFactory::registerAttribute<UidValidityAttribute>();
93
Akonadi::AttributeFactory::registerAttribute<UidNextAttribute>();
94
Akonadi::AttributeFactory::registerAttribute<NoSelectAttribute>();
95
Akonadi::AttributeFactory::registerAttribute<CollectionFlagsAttribute>();
96
Akonadi::AttributeFactory::registerAttribute<CollectionAnnotationsAttribute>();
97
Akonadi::AttributeFactory::registerAttribute<ImapAclAttribute>();
98
Akonadi::AttributeFactory::registerAttribute<ImapQuotaAttribute>();
100
changeRecorder()->fetchCollection( true );
101
changeRecorder()->itemFetchScope().fetchFullPayload( true );
103
connect( this, SIGNAL(reloadConfiguration()), SLOT(startConnect()) );
107
ImapResource::~ImapResource()
111
bool ImapResource::retrieveItem( const Akonadi::Item &item, const QSet<QByteArray> &parts )
113
const QString remoteId = item.remoteId();
114
const QStringList temp = remoteId.split( "-+-" );
115
const QString mailBox = mailBoxForRemoteId( temp[0] );
116
const qint64 uid = temp[1].toLongLong();
118
KIMAP::SelectJob *select = new KIMAP::SelectJob( m_account->session() );
119
select->setMailBox( mailBox );
121
KIMAP::FetchJob *fetch = new KIMAP::FetchJob( m_account->session() );
122
fetch->setProperty( "akonadiItem", QVariant::fromValue( item ) );
123
KIMAP::FetchJob::FetchScope scope;
124
fetch->setUidBased( true );
125
fetch->setSequenceSet( KIMAP::ImapSet( uid ) );
126
scope.parts.clear();// = parts.toList();
127
scope.mode = KIMAP::FetchJob::FetchScope::Content;
128
fetch->setScope( scope );
129
connect( fetch, SIGNAL( messagesReceived( QString, QMap<qint64, qint64>, QMap<qint64, KIMAP::MessagePtr> ) ),
130
this, SLOT( onMessagesReceived( QString, QMap<qint64, qint64>, QMap<qint64, KIMAP::MessagePtr> ) ) );
131
connect( fetch, SIGNAL( partsReceived( QString, QMap<qint64, qint64>, QMap<qint64, KIMAP::MessageParts> ) ),
132
this, SLOT( onPartsReceived( QString, QMap<qint64, qint64>, QMap<qint64, KIMAP::MessageParts> ) ) );
133
connect( fetch, SIGNAL( result( KJob* ) ),
134
this, SLOT( onContentFetchDone( KJob* ) ) );
139
void ImapResource::onMessagesReceived( const QString &mailBox, const QMap<qint64, qint64> &uids,
140
const QMap<qint64, KIMAP::MessagePtr> &messages )
142
KIMAP::FetchJob *fetch = qobject_cast<KIMAP::FetchJob*>( sender() );
143
Q_ASSERT( fetch!=0 );
144
Q_ASSERT( uids.size()==1 );
145
Q_ASSERT( messages.size()==1 );
147
Item i = fetch->property( "akonadiItem" ).value<Item>();
149
kDebug() << "MESSAGE from Imap server" << i.remoteId();
150
Q_ASSERT( i.isValid() );
152
KIMAP::MessagePtr message = messages[messages.keys().first()];
154
i.setMimeType( "message/rfc822" );
155
i.setPayload( MessagePtr( message ) );
157
kDebug() << "Has Payload: " << i.hasPayload();
158
kDebug() << message->head().isEmpty() << message->body().isEmpty() << message->contents().isEmpty() << message->hasContent() << message->hasHeader("Message-ID");
163
void ImapResource::onContentFetchDone( KJob *job )
165
if ( job->error() ) {
166
cancelTask( job->errorString() );
168
KIMAP::FetchJob *fetch = qobject_cast<KIMAP::FetchJob*>( job );
169
if ( fetch->messages().isEmpty() && fetch->parts().isEmpty() ) {
170
cancelTask( i18n("No message retrieved, server reply was empty.") );
175
void ImapResource::configure( WId windowId )
177
SetupServer dlg( windowId );
178
KWindowSystem::setMainWindow( &dlg, windowId );
181
if ( dlg.shouldClearCache() ) {
185
if ( !Settings::self()->imapServer().isEmpty() && !Settings::self()->userName().isEmpty() ) {
186
setName( Settings::self()->imapServer() + '/' + Settings::self()->userName() );
188
setName( KGlobal::mainComponent().aboutData()->appName() );
194
void ImapResource::startConnect( bool forceManualAuth )
196
if ( Settings::self()->imapServer().isEmpty() ) {
200
QString password = Settings::self()->password();
202
if ( password.isEmpty() || forceManualAuth ) {
203
if ( !manualAuth( Settings::self()->userName(), password ) ) {
209
m_account = new ImapAccount( Settings::self(), this );
211
connect( m_account, SIGNAL( success() ),
212
this, SLOT( onConnectSuccess() ) );
213
connect( m_account, SIGNAL( error( int, const QString& ) ),
214
this, SLOT( onConnectError( int, const QString& ) ) );
216
m_account->connect( password );
219
void ImapResource::itemAdded( const Item &item, const Collection &collection )
221
const QString mailBox = mailBoxForRemoteId( collection.remoteId() );
223
// save message to the server.
224
MessagePtr msg = item.payload<MessagePtr>();
226
KIMAP::AppendJob *job = new KIMAP::AppendJob( m_account->session() );
227
job->setProperty( "akonadiCollection", QVariant::fromValue( collection ) );
228
job->setProperty( "akonadiItem", QVariant::fromValue( item ) );
229
job->setMailBox( mailBox );
230
job->setContent( msg->encodedContent( true ) );
231
connect( job, SIGNAL( result( KJob* ) ), SLOT( onAppendMessageDone( KJob* ) ) );
235
void ImapResource::onAppendMessageDone( KJob *job )
237
KIMAP::AppendJob *append = qobject_cast<KIMAP::AppendJob*>( job );
239
const QString collectionRemoteId = remoteIdForMailBox( append->mailBox() );
240
Item item = job->property( "akonadiItem" ).value<Item>();
242
if ( append->error() ) {
246
qint64 uid = append->uid();
249
const QString remoteId = collectionRemoteId + "-+-" + QString::number( uid );
250
kDebug() << "Setting remote ID to " << remoteId;
251
item.setRemoteId( remoteId );
253
changeCommitted( item );
255
// Check if it we got here because an itemChanged() call
256
// (since in IMAP you're forced to append+remove in this case)
257
qint64 oldUid = job->property( "oldUid" ).toLongLong();
259
// OK it's indeed a content change, so we've to mark the old version as deleted
260
KIMAP::StoreJob *store = new KIMAP::StoreJob( m_account->session() );
261
store->setUidBased( true );
262
store->setSequenceSet( KIMAP::ImapSet( oldUid ) );
263
store->setFlags( QList<QByteArray>() << "\\Deleted" );
264
store->setMode( KIMAP::StoreJob::AppendFlags );
268
Collection collection = job->property( "akonadiCollection" ).value<Collection>();
269
if ( !collection.isValid() ) {
270
collection = collectionFromRemoteId( collectionRemoteId );
273
// Get the current uid next value and store it
274
UidNextAttribute *uidAttr = 0;
276
if ( collection.hasAttribute( "uidnext" ) ) {
277
uidAttr = static_cast<UidNextAttribute*>( collection.attribute( "uidnext" ) );
278
oldNextUid = uidAttr->uidNext();
281
// If the uid we just got back is the expected next one of the box
282
// then update the property to the probable next uid to keep the cache in sync.
283
// If not something happened in our back, so we don't update and a refetch will
284
// happen at some point.
285
if ( uid==oldNextUid ) {
287
uidAttr = new UidNextAttribute( uid+1 );
288
collection.addAttribute( uidAttr );
290
uidAttr->setUidNext( uid+1 );
293
CollectionModifyJob *modify = new CollectionModifyJob( collection );
297
void ImapResource::itemChanged( const Item &item, const QSet<QByteArray> &parts )
299
kDebug() << item.remoteId() << parts;
301
const QString remoteId = item.remoteId();
302
const QStringList temp = remoteId.split( "-+-" );
303
const QString mailBox = mailBoxForRemoteId( temp[0] );
304
const qint64 uid = temp[1].toLongLong();
306
if ( parts.contains( "PLD:RFC822" ) ) {
307
// save message to the server.
308
MessagePtr msg = item.payload<MessagePtr>();
310
KIMAP::AppendJob *job = new KIMAP::AppendJob( m_account->session() );
311
job->setProperty( "akonadiItem", QVariant::fromValue( item ) );
312
job->setProperty( "oldUid", uid ); // Will be used in onAppendMessageDone
313
job->setMailBox( mailBox );
314
job->setContent( msg->encodedContent( true ) );
315
job->setFlags( item.flags().toList() );
316
connect( job, SIGNAL( result( KJob* ) ), SLOT( onAppendMessageDone( KJob* ) ) );
319
} else if ( parts.contains( "FLAGS" ) ) {
320
KIMAP::SelectJob *select = new KIMAP::SelectJob( m_account->session() );
321
select->setMailBox( mailBox );
323
KIMAP::StoreJob *store = new KIMAP::StoreJob( m_account->session() );
324
store->setProperty( "akonadiItem", QVariant::fromValue( item ) );
325
store->setProperty( "itemUid", uid );
326
store->setUidBased( true );
327
store->setSequenceSet( KIMAP::ImapSet( uid ) );
328
store->setFlags( item.flags().toList() );
329
store->setMode( KIMAP::StoreJob::SetFlags );
330
connect( store, SIGNAL( result( KJob* ) ), SLOT( onStoreFlagsDone( KJob* ) ) );
335
void ImapResource::onStoreFlagsDone( KJob *job )
337
KIMAP::StoreJob *store = qobject_cast<KIMAP::StoreJob*>( job );
339
if ( store->error() ) {
343
Item item = job->property( "akonadiItem" ).value<Item>();
344
qint64 uid = job->property( "itemUid" ).toLongLong();
345
bool itemRemoval = job->property( "itemRemoval" ).toBool();
347
if ( !itemRemoval ) {
348
item.setFlags( store->resultingFlags()[uid].toSet() );
349
changeCommitted( item );
355
void ImapResource::itemRemoved( const Akonadi::Item &item )
357
// The imap specs do not allow for a single message to be deleted. We can only
358
// set the \Deleted flag. The message will actually be deleted when EXPUNGE will
359
// be issued on the next retrieveItems().
361
const QString remoteId = item.remoteId();
362
const QStringList temp = remoteId.split( "-+-" );
363
const QString mailBox = mailBoxForRemoteId( temp[0] );
364
const qint64 uid = temp[1].toLongLong();
366
KIMAP::SelectJob *select = new KIMAP::SelectJob( m_account->session() );
367
select->setMailBox( mailBox );
369
KIMAP::StoreJob *store = new KIMAP::StoreJob( m_account->session() );
370
store->setProperty( "akonadiItem", QVariant::fromValue( item ) );
371
store->setProperty( "itemRemoval", true );
372
store->setUidBased( true );
373
store->setSequenceSet( KIMAP::ImapSet( uid ) );
374
store->setFlags( QList<QByteArray>() << "\\Deleted" );
375
store->setMode( KIMAP::StoreJob::AppendFlags );
376
connect( store, SIGNAL( result( KJob* ) ), SLOT( onStoreFlagsDone( KJob* ) ) );
380
void ImapResource::retrieveCollections()
382
if ( !m_account || !m_account->session() ) {
383
kDebug() << "Ignoring this request. Probably there is no connection.";
389
root.setName( m_account->server() + '/' + m_account->userName() );
390
root.setRemoteId( rootRemoteId() );
391
root.setContentMimeTypes( QStringList( Collection::mimeType() ) );
394
policy.setInheritFromParent( false );
395
policy.setSyncOnDemand( true );
396
root.setCachePolicy( policy );
398
setCollectionStreamingEnabled( true );
399
collectionsRetrievedIncremental( Collection::List() << root, Collection::List() );
401
KIMAP::ListJob *listJob = new KIMAP::ListJob( m_account->session() );
402
listJob->setIncludeUnsubscribed( !m_account->isSubscriptionEnabled() );
403
connect( listJob, SIGNAL( mailBoxesReceived(QList<KIMAP::MailBoxDescriptor>, QList< QList<QByteArray> >) ),
404
this, SLOT( onMailBoxesReceived(QList<KIMAP::MailBoxDescriptor>, QList< QList<QByteArray> >) ) );
405
connect( listJob, SIGNAL(result(KJob*)), SLOT(onMailBoxesReceiveDone(KJob*)) );
409
void ImapResource::onMailBoxesReceived( const QList< KIMAP::MailBoxDescriptor > &descriptors,
410
const QList< QList<QByteArray> > &flags )
412
QStringList reportedPaths = sender()->property("reportedPaths").toStringList();
414
Collection::List collections;
415
QStringList contentTypes;
416
contentTypes << "message/rfc822" << Collection::mimeType();
418
for ( int i=0; i<descriptors.size(); ++i ) {
419
KIMAP::MailBoxDescriptor descriptor = descriptors[i];
421
QStringList pathParts = descriptor.name.split(descriptor.separator);
422
QString separator = descriptor.separator;
427
foreach ( const QString &pathPart, pathParts ) {
428
currentPath+='/'+pathPart;
429
if ( currentPath.startsWith( '/' ) ) {
430
currentPath.remove( 0, 1 );
433
if ( reportedPaths.contains( currentPath ) ) {
434
parentPath = currentPath;
437
reportedPaths << currentPath;
441
c.setName( pathPart );
442
c.setRemoteId( remoteIdForMailBox( currentPath ) );
443
c.setParentRemoteId( remoteIdForMailBox( parentPath ) );
444
c.setRights( Collection::AllRights );
445
c.setContentMimeTypes( contentTypes );
447
CachePolicy cachePolicy;
448
cachePolicy.setInheritFromParent( false );
449
cachePolicy.setIntervalCheckTime( -1 );
450
cachePolicy.setSyncOnDemand( true );
452
// If the folder is the Inbox, make some special settings.
453
if ( currentPath.compare( QLatin1String("INBOX") , Qt::CaseInsensitive ) == 0 ) {
454
cachePolicy.setIntervalCheckTime( 1 );
455
c.setName( "Inbox" );
458
// If this folder is a noselect folder, make some special settings.
459
if ( flags[i].contains( "\\NoSelect" ) ) {
460
cachePolicy.setSyncOnDemand( false );
461
c.addAttribute( new NoSelectAttribute( true ) );
464
c.setCachePolicy( cachePolicy );
467
parentPath = currentPath;
471
sender()->setProperty("reportedPaths", reportedPaths);
472
collectionsRetrievedIncremental( collections, Collection::List() );
475
void ImapResource::onMailBoxesReceiveDone(KJob* job)
477
// TODO error handling
478
collectionsRetrievalDone();
481
// ----------------------------------------------------------------------------------
483
void ImapResource::retrieveItems( const Collection &col )
485
kDebug( ) << col.remoteId();
487
// Prevent fetching items from noselect folders.
488
if ( col.hasAttribute( "noselect" ) ) {
489
NoSelectAttribute* noselect = static_cast<NoSelectAttribute*>( col.attribute( "noselect" ) );
490
if ( noselect->noSelect() ) {
491
kDebug() << "No Select folder";
492
itemsRetrievalDone();
497
const QString mailBox = mailBoxForRemoteId( col.remoteId() );
498
const QStringList capabilities = m_account->capabilities();
500
// First get the annotations from the mailbox if it's supported
501
if ( capabilities.contains( "METADATA" ) || capabilities.contains( "ANNOTATEMORE" ) ) {
502
KIMAP::GetMetaDataJob *meta = new KIMAP::GetMetaDataJob( m_account->session() );
503
meta->setProperty( "akonadiCollection", QVariant::fromValue( col ) );
504
meta->setMailBox( mailBox );
505
if ( capabilities.contains( "METADATA" ) ) {
506
meta->setServerCapability( KIMAP::MetaDataJobBase::Metadata );
507
meta->addEntry( "*" );
509
meta->setServerCapability( KIMAP::MetaDataJobBase::Annotatemore );
510
meta->addEntry( "*", "value.shared" );
512
connect( meta, SIGNAL( result( KJob* ) ), SLOT( onGetMetaDataDone( KJob* ) ) );
516
// Get the ACLs from the mailbox if it's supported
517
if ( capabilities.contains( "ACL" ) ) {
518
KIMAP::GetAclJob *acl = new KIMAP::GetAclJob( m_account->session() );
519
acl->setProperty( "akonadiCollection", QVariant::fromValue( col ) );
520
acl->setMailBox( mailBox );
521
connect( acl, SIGNAL( result( KJob* ) ), SLOT( onGetAclDone( KJob* ) ) );
524
KIMAP::MyRightsJob *rights = new KIMAP::MyRightsJob( m_account->session() );
525
rights->setProperty( "akonadiCollection", QVariant::fromValue( col ) );
526
rights->setMailBox( mailBox );
527
connect( rights, SIGNAL( result( KJob* ) ), SLOT( onRightsReceived( KJob* ) ) );
531
// Get the QUOTA info from the mailbox if it's supported
532
if ( capabilities.contains( "QUOTA" ) ) {
533
KIMAP::GetQuotaRootJob *quota = new KIMAP::GetQuotaRootJob( m_account->session() );
534
quota->setProperty( "akonadiCollection", QVariant::fromValue( col ) );
535
quota->setMailBox( mailBox );
536
connect( quota, SIGNAL( result( KJob* ) ), SLOT( onQuotasReceived( KJob* ) ) );
540
// Now is the right time to expunge the messages marked \\Deleted from this mailbox.
541
KIMAP::SelectJob *select = new KIMAP::SelectJob( m_account->session() );
542
select->setMailBox( mailBox );
544
KIMAP::ExpungeJob *expunge = new KIMAP::ExpungeJob( m_account->session() );
547
// Issue another select to get the updated info from the mailbox
548
select = new KIMAP::SelectJob( m_account->session() );
549
select->setMailBox( mailBox );
550
connect( select, SIGNAL( result( KJob* ) ),
551
this, SLOT( onSelectDone( KJob* ) ) );
555
void ImapResource::onHeadersReceived( const QString &mailBox, const QMap<qint64, qint64> &uids,
556
const QMap<qint64, qint64> &sizes,
557
const QMap<qint64, KIMAP::MessageFlags> &flags,
558
const QMap<qint64, KIMAP::MessagePtr> &messages )
560
Item::List addedItems;
562
foreach ( qint64 number, uids.keys() ) {
564
i.setRemoteId( remoteIdForMailBox( mailBox ) + "-+-" + QString::number( uids[number] ) );
565
i.setMimeType( "message/rfc822" );
566
i.setPayload( MessagePtr( messages[number] ) );
567
i.setSize( sizes[number] );
569
foreach( const QByteArray &flag, flags[number] ) {
572
kDebug() << "Flags: " << i.flags();
576
itemsRetrievedIncremental( addedItems, Item::List() );
579
void ImapResource::onHeadersFetchDone( KJob */*job*/ )
581
itemsRetrievalDone();
585
// ----------------------------------------------------------------------------------
587
void ImapResource::collectionAdded( const Collection & collection, const Collection &parent )
589
const QString remoteName = parent.remoteId() + '/' + collection.name();
591
kDebug( ) << "New folder: " << remoteName;
593
Collection c = collection;
594
c.setRemoteId( remoteName );
596
const QString mailBox = mailBoxForRemoteId( remoteName );
598
KIMAP::CreateJob *job = new KIMAP::CreateJob( m_account->session() );
599
job->setProperty( "akonadiCollection", QVariant::fromValue( c ) );
600
job->setMailBox( mailBox );
601
connect( job, SIGNAL( result( KJob* ) ), SLOT( onCreateMailBoxDone( KJob* ) ) );
605
void ImapResource::onCreateMailBoxDone( KJob *job )
607
Collection collection = job->property( "akonadiCollection" ).value<Collection>();
609
if ( !job->error() ) {
610
changeCommitted( collection );
612
// remove the collection again.
613
kDebug() << "Failed to create the folder, deleting it in akonadi again";
614
emit warning( i18n( "Failed to create the folder, restoring folder list." ) );
615
new CollectionDeleteJob( collection, this );
619
void ImapResource::collectionChanged( const Collection & collection )
621
QString oldRemoteId = collection.remoteId();
622
QString parentRemoteId = oldRemoteId.mid( 0, oldRemoteId.lastIndexOf('/') );
624
QString newRemoteId = parentRemoteId + '/' + collection.name();
626
Collection c = collection;
627
c.setRemoteId( newRemoteId );
629
const QString oldMailBox = mailBoxForRemoteId( oldRemoteId );
630
const QString newMailBox = mailBoxForRemoteId( newRemoteId );
632
KIMAP::RenameJob *job = new KIMAP::RenameJob( m_account->session() );
633
job->setProperty( "akonadiCollection", QVariant::fromValue( c ) );
634
job->setSourceMailBox( oldMailBox );
635
job->setDestinationMailBox( newMailBox );
636
connect( job, SIGNAL( result( KJob* ) ), SLOT( onRenameMailBoxDone( KJob* ) ) );
640
void ImapResource::onRenameMailBoxDone( KJob *job )
642
Collection collection = job->property( "akonadiCollection" ).value<Collection>();
644
if ( !job->error() ) {
645
changeCommitted( collection );
647
KIMAP::RenameJob *rename = qobject_cast<KIMAP::RenameJob*>( job );
649
// rename the collection again.
650
kDebug() << "Failed to rename the folder, resetting it in akonadi again";
651
collection.setName( rename->sourceMailBox().split('/').last() );
652
collection.setRemoteId( remoteIdForMailBox( rename->sourceMailBox() ) );
653
emit warning( i18n( "Failed to rename the folder, restoring folder list." ) );
654
changeCommitted( collection );
658
void ImapResource::collectionRemoved( const Collection &collection )
660
const QString mailBox = mailBoxForRemoteId( collection.remoteId() );
662
KIMAP::DeleteJob *job = new KIMAP::DeleteJob( m_account->session() );
663
job->setProperty( "akonadiCollection", QVariant::fromValue( collection ) );
664
job->setMailBox( mailBox );
665
connect( job, SIGNAL( result( KJob* ) ), SLOT( onDeleteMailBoxDone( KJob* ) ) );
669
void ImapResource::onDeleteMailBoxDone( KJob *job )
674
if ( !job->error() ) {
675
kDebug() << "Failed to delete the folder, resync the folder tree";
676
emit warning( i18n( "Failed to delete the folder, restoring folder list." ) );
677
synchronizeCollectionTree();
681
/******************* Slots ***********************************************/
683
void ImapResource::onConnectError( int code, const QString &message )
685
if ( code==ImapAccount::LoginFailError ) {
686
// the credentials where not ok....
687
int i = KMessageBox::questionYesNoCancelWId( winIdForDialogs(),
688
i18n( "The server refused the supplied username and password. "
689
"Do you want to go to the settings, have another attempt "
690
"at logging in, or do nothing?" ),
691
i18n( "Could Not Authenticate" ),
692
KGuiItem( i18n( "Settings" ) ),
693
KGuiItem( i18nc( "Input username/password manually and not store them", "Single Input" ) ) );
694
if ( i == KMessageBox::Yes ) {
695
configure( winIdForDialogs() );
697
} else if ( i == KMessageBox::No ) {
698
startConnect( true );
701
KIMAP::LogoutJob *logout = new KIMAP::LogoutJob( m_account->session() );
703
emit warning( i18n( "Could not connect to the IMAP-server %1.", m_account->server() ) );
707
m_account->disconnect();
708
emit error( message );
711
void ImapResource::onConnectSuccess()
713
synchronizeCollectionTree();
716
void ImapResource::onGetAclDone( KJob *job )
718
if ( job->error() ) {
719
return; // Well, no metadata for us then...
722
KIMAP::GetAclJob *acl = qobject_cast<KIMAP::GetAclJob*>( job );
723
Collection collection = job->property( "akonadiCollection" ).value<Collection>();
725
// Store the mailbox ACLs
726
if ( !collection.hasAttribute( "imapacl" ) ) {
727
ImapAclAttribute *aclAttribute = new ImapAclAttribute( acl->allRights() );
728
collection.addAttribute( aclAttribute );
730
ImapAclAttribute *aclAttribute =
731
static_cast<ImapAclAttribute*>( collection.attribute( "imapacl" ) );
732
const QMap<QByteArray, KIMAP::Acl::Rights> oldRights = aclAttribute->rights();
733
if ( oldRights != acl->allRights() ) {
734
aclAttribute->setRights( acl->allRights() );
738
CollectionModifyJob *modify = new CollectionModifyJob( collection );
741
void ImapResource::onRightsReceived( KJob *job )
743
if ( job->error() ) {
744
return; // Well, no metadata for us then...
747
KIMAP::MyRightsJob *rightsJob = qobject_cast<KIMAP::MyRightsJob*>( job );
748
Collection collection = job->property( "akonadiCollection" ).value<Collection>();
750
KIMAP::Acl::Rights imapRights = rightsJob->rights();
751
Collection::Rights newRights = Collection::ReadOnly;
753
if ( imapRights & KIMAP::Acl::Write ) {
754
newRights|= Collection::CanChangeItem;
757
if ( imapRights & KIMAP::Acl::Insert ) {
758
newRights|= Collection::CanCreateItem;
761
if ( imapRights & KIMAP::Acl::DeleteMessage ) {
762
newRights|= Collection::CanDeleteItem;
765
if ( imapRights & KIMAP::Acl::CreateMailbox ) {
766
newRights|= Collection::CanChangeCollection;
767
newRights|= Collection::CanCreateCollection;
770
if ( imapRights & KIMAP::Acl::DeleteMailbox ) {
771
newRights|= Collection::CanDeleteCollection;
774
if ( newRights != collection.rights() ) {
775
collection.setRights( newRights );
777
CollectionModifyJob *modify = new CollectionModifyJob( collection );
781
void ImapResource::onQuotasReceived( KJob *job )
783
if ( job->error() ) {
784
return; // Well, no metadata for us then...
787
KIMAP::GetQuotaRootJob *quotaJob = qobject_cast<KIMAP::GetQuotaRootJob*>( job );
788
Collection collection = job->property( "akonadiCollection" ).value<Collection>();
790
QList<QByteArray> newRoots = quotaJob->roots();
791
QList< QMap<QByteArray, qint64> > newLimits;
792
QList< QMap<QByteArray, qint64> > newUsages;
794
foreach ( const QByteArray &root, newRoots ) {
795
newLimits << quotaJob->allLimits( root );
796
newUsages << quotaJob->allUsages( root );
799
// Store the mailbox Quotas
800
if ( !collection.hasAttribute( "imapquota" ) ) {
801
ImapQuotaAttribute *quotaAttribute = new ImapQuotaAttribute( newRoots, newLimits, newUsages );
802
collection.addAttribute( quotaAttribute );
804
ImapQuotaAttribute *quotaAttribute =
805
static_cast<ImapQuotaAttribute*>( collection.attribute( "imapquota" ) );
806
const QList<QByteArray> oldRoots = quotaAttribute->roots();
807
const QList< QMap<QByteArray, qint64> > oldLimits = quotaAttribute->limits();
808
const QList< QMap<QByteArray, qint64> > oldUsages = quotaAttribute->usages();
810
if ( oldRoots != newRoots
811
|| oldLimits != newLimits
812
|| oldUsages != newUsages ) {
813
quotaAttribute->setQuotas( newRoots, newLimits, newUsages );
817
CollectionModifyJob *modify = new CollectionModifyJob( collection );
820
void ImapResource::onGetMetaDataDone( KJob *job )
822
if ( job->error() ) {
823
return; // Well, no metadata for us then...
826
KIMAP::GetMetaDataJob *meta = qobject_cast<KIMAP::GetMetaDataJob*>( job );
827
QMap<QByteArray, QMap<QByteArray, QByteArray> > rawAnnotations = meta->allMetaData( meta->mailBox() );
829
QMap<QByteArray, QByteArray> annotations;
830
QByteArray attribute = "";
831
if ( meta->serverCapability()==KIMAP::MetaDataJobBase::Annotatemore ) {
832
attribute = "value.shared";
835
foreach ( const QByteArray &entry, rawAnnotations.keys() ) {
836
annotations[entry] = rawAnnotations[entry][attribute];
839
Collection collection = job->property( "akonadiCollection" ).value<Collection>();
841
// Store the mailbox metadata
842
CollectionAnnotationsAttribute *annotationsAttribute =
843
collection.attribute<CollectionAnnotationsAttribute>( Collection::AddIfMissing );
844
const QMap<QByteArray, QByteArray> oldAnnotations = annotationsAttribute->annotations();
845
if ( oldAnnotations != annotations ) {
846
annotationsAttribute->setAnnotations( annotations );
849
CollectionModifyJob *modify = new CollectionModifyJob( collection );
852
void ImapResource::onSelectDone( KJob *job )
854
if ( job->error() ) {
855
itemsRetrievalDone();
859
KIMAP::SelectJob *select = qobject_cast<KIMAP::SelectJob*>( job );
861
const QString mailBox = select->mailBox();
862
const int messageCount = select->messageCount();
863
const qint64 uidValidity = select->uidValidity();
864
const qint64 nextUid = select->nextUid();
865
const QList<QByteArray> flags = select->flags();
867
// uidvalidity can change between sessions, we don't want to refetch
868
// folders in that case. Keep track of what is processed and what not.
869
static QStringList processed;
870
bool firstTime = false;
871
if ( processed.indexOf( mailBox ) == -1 ) {
873
processed.append( mailBox );
876
Collection collection = collectionFromRemoteId( remoteIdForMailBox( mailBox ) );
877
Q_ASSERT( collection.isValid() );
879
// Get the current uid validity value and store it
880
int oldUidValidity = 0;
881
if ( !collection.hasAttribute( "uidvalidity" ) ) {
882
UidValidityAttribute* currentUidValidity = new UidValidityAttribute( uidValidity );
883
collection.addAttribute( currentUidValidity );
885
UidValidityAttribute* currentUidValidity =
886
static_cast<UidValidityAttribute*>( collection.attribute( "uidvalidity" ) );
887
oldUidValidity = currentUidValidity->uidValidity();
888
if ( oldUidValidity != uidValidity ) {
889
currentUidValidity->setUidValidity( uidValidity );
893
// Get the current uid next value and store it
895
if ( !collection.hasAttribute( "uidnext" ) ) {
896
UidNextAttribute* currentNextUid = new UidNextAttribute( nextUid );
897
collection.addAttribute( currentNextUid );
899
UidNextAttribute* currentNextUid =
900
static_cast<UidNextAttribute*>( collection.attribute( "uidnext" ) );
901
oldNextUid = currentNextUid->uidNext();
902
if ( oldNextUid != nextUid ) {
903
currentNextUid->setUidNext( nextUid );
907
// Store the mailbox flags
908
if ( !collection.hasAttribute( "collectionflags" ) ) {
909
CollectionFlagsAttribute *flagsAttribute = new CollectionFlagsAttribute( flags );
910
collection.addAttribute( flagsAttribute );
912
CollectionFlagsAttribute *flagsAttribute =
913
static_cast<CollectionFlagsAttribute*>( collection.attribute( "collectionflags" ) );
914
const QList<QByteArray> oldFlags = flagsAttribute->flags();
915
if ( oldFlags != flags ) {
916
flagsAttribute->setFlags( flags );
920
CollectionModifyJob *modify = new CollectionModifyJob( collection );
922
// First check the uidvalidity, if this has changed, it means the folder
923
// has been deleted and recreated. So we wipe out the messages and
925
if ( oldUidValidity != uidValidity && !firstTime
926
&& oldUidValidity != 0 ) {
927
kDebug() << "UIDVALIDITY check failed (" << oldUidValidity << "|"
928
<< uidValidity <<") refetching "<< mailBox;
930
setItemStreamingEnabled( true );
932
KIMAP::FetchJob *fetch = new KIMAP::FetchJob( m_account->session() );
933
KIMAP::FetchJob::FetchScope scope;
934
fetch->setSequenceSet( KIMAP::ImapSet( 1, messageCount ) );
936
scope.mode = KIMAP::FetchJob::FetchScope::Headers;
937
fetch->setScope( scope );
938
connect( fetch, SIGNAL( headersReceived( QString, QMap<qint64, qint64>, QMap<qint64, qint64>,
939
QMap<qint64, KIMAP::MessageFlags>, QMap<qint64, KIMAP::MessagePtr> ) ),
940
this, SLOT( onHeadersReceived( QString, QMap<qint64, qint64>, QMap<qint64, qint64>,
941
QMap<qint64, KIMAP::MessageFlags>, QMap<qint64, KIMAP::MessagePtr> ) ) );
942
connect( fetch, SIGNAL( result( KJob* ) ),
943
this, SLOT( onHeadersFetchDone( KJob* ) ) );
948
// See how many messages are in the folder currently
949
qint64 realMessageCount = collection.statistics().count();
950
if ( realMessageCount == -1 ) {
951
Akonadi::CollectionStatisticsJob *job = new Akonadi::CollectionStatisticsJob( collection );
953
Akonadi::CollectionStatistics statistics = job->statistics();
954
realMessageCount = statistics.count();
958
kDebug() << "integrity: " << mailBox << " should be: " << messageCount << " current: " << realMessageCount;
960
if ( messageCount > realMessageCount ) {
961
// The amount on the server is bigger than that we have in the cache
962
// that probably means that there is new mail. Fetch missing.
963
kDebug() << "Fetch missing: " << messageCount << " But: " << realMessageCount;
965
setItemStreamingEnabled( true );
967
KIMAP::FetchJob *fetch = new KIMAP::FetchJob( m_account->session() );
968
KIMAP::FetchJob::FetchScope scope;
969
fetch->setSequenceSet( KIMAP::ImapSet( realMessageCount+1, messageCount ) );
971
scope.mode = KIMAP::FetchJob::FetchScope::Headers;
972
fetch->setScope( scope );
973
connect( fetch, SIGNAL( headersReceived( QString, QMap<qint64, qint64>, QMap<qint64, qint64>,
974
QMap<qint64, KIMAP::MessageFlags>, QMap<qint64, KIMAP::MessagePtr> ) ),
975
this, SLOT( onHeadersReceived( QString, QMap<qint64, qint64>, QMap<qint64, qint64>,
976
QMap<qint64, KIMAP::MessageFlags>, QMap<qint64, KIMAP::MessagePtr> ) ) );
977
connect( fetch, SIGNAL( result( KJob* ) ),
978
this, SLOT( onHeadersFetchDone( KJob* ) ) );
981
} else if ( messageCount != realMessageCount ) {
982
// The amount on the server does not match the amount in the cache.
983
// that means we need reget the catch completely.
984
kDebug() << "O OH: " << messageCount << " But: " << realMessageCount;
986
itemsClear( collection );
987
setItemStreamingEnabled( true );
989
KIMAP::FetchJob *fetch = new KIMAP::FetchJob( m_account->session() );
990
KIMAP::FetchJob::FetchScope scope;
991
fetch->setSequenceSet( KIMAP::ImapSet( 1, messageCount ) );
993
scope.mode = KIMAP::FetchJob::FetchScope::Headers;
994
fetch->setScope( scope );
995
connect( fetch, SIGNAL( headersReceived( QString, QMap<qint64, qint64>, QMap<qint64, qint64>,
996
QMap<qint64, KIMAP::MessageFlags>, QMap<qint64, KIMAP::MessagePtr> ) ),
997
this, SLOT( onHeadersReceived( QString, QMap<qint64, qint64>, QMap<qint64, qint64>,
998
QMap<qint64, KIMAP::MessageFlags>, QMap<qint64, KIMAP::MessagePtr> ) ) );
999
connect( fetch, SIGNAL( result( KJob* ) ),
1000
this, SLOT( onHeadersFetchDone( KJob* ) ) );
1003
} else if ( messageCount == realMessageCount && oldNextUid != nextUid
1004
&& oldNextUid != 0 && !firstTime ) {
1005
// amount is right but uidnext is different.... something happened
1006
// behind our back...
1007
kDebug() << "UIDNEXT check failed, refetching mailbox";
1009
itemsClear( collection );
1010
setItemStreamingEnabled( true );
1012
KIMAP::FetchJob *fetch = new KIMAP::FetchJob( m_account->session() );
1013
KIMAP::FetchJob::FetchScope scope;
1014
fetch->setSequenceSet( KIMAP::ImapSet( 1, messageCount ) );
1015
scope.parts.clear();
1016
scope.mode = KIMAP::FetchJob::FetchScope::Headers;
1017
fetch->setScope( scope );
1018
connect( fetch, SIGNAL( headersReceived( QString, QMap<qint64, qint64>, QMap<qint64, qint64>,
1019
QMap<qint64, KIMAP::MessageFlags>, QMap<qint64, KIMAP::MessagePtr> ) ),
1020
this, SLOT( onHeadersReceived( QString, QMap<qint64, qint64>, QMap<qint64, qint64>,
1021
QMap<qint64, KIMAP::MessageFlags>, QMap<qint64, KIMAP::MessagePtr> ) ) );
1022
connect( fetch, SIGNAL( result( KJob* ) ),
1023
this, SLOT( onHeadersFetchDone( KJob* ) ) );
1028
kDebug() << "All fine, nothing to do";
1029
itemsRetrievalDone();
1033
/******************* Private ***********************************************/
1035
bool ImapResource::manualAuth( const QString& username, QString &password )
1037
KPasswordDialog dlg( 0 );
1038
dlg.setPrompt( i18n( "Could not find a valid password, please enter it here." ) );
1039
if ( dlg.exec() == QDialog::Accepted ) {
1040
password = dlg.password();
1043
password = QString();
1048
QString ImapResource::rootRemoteId() const
1050
return "imap://"+m_account->userName()+'@'+m_account->server()+'/';
1053
QString ImapResource::remoteIdForMailBox( const QString &path ) const
1055
return rootRemoteId()+path;
1058
QString ImapResource::mailBoxForRemoteId( const QString &remoteId ) const
1060
QString path = remoteId;
1061
path.replace( rootRemoteId(), "" );
1065
Collection ImapResource::collectionFromRemoteId( const QString &remoteId )
1067
CollectionFetchJob *fetch = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive );
1068
fetch->setResource( identifier() );
1071
Collection::List collections = fetch->collections();
1072
foreach ( const Collection &collection, collections ) {
1073
if ( collection.remoteId()==remoteId ) {
1078
return Collection();
1081
Item ImapResource::itemFromRemoteId( const Akonadi::Collection &collection, const QString &remoteId )
1083
ItemFetchJob *fetch = new ItemFetchJob( collection );
1086
Item::List items = fetch->items();
1087
foreach ( const Item &item, items ) {
1088
if ( item.remoteId()==remoteId ) {
1096
void ImapResource::itemsClear( const Collection &collection )
1098
ItemFetchJob *fetch = new ItemFetchJob( collection );
1101
TransactionSequence *transaction = new TransactionSequence;
1103
Item::List items = fetch->items();
1104
foreach ( const Item &item, items ) {
1105
new ItemDeleteJob( item, transaction );
1108
transaction->exec();
1111
AKONADI_RESOURCE_MAIN( ImapResource )
1113
#include "imapresource.moc"