55
58
#include <kimap/logoutjob.h>
56
59
#include <kimap/myrightsjob.h>
57
60
#include <kimap/renamejob.h>
61
#include <kimap/rfccodecs.h>
58
62
#include <kimap/selectjob.h>
63
#include <kimap/setacljob.h>
64
#include <kimap/setmetadatajob.h>
59
65
#include <kimap/storejob.h>
66
#include <kimap/subscribejob.h>
61
68
#include <kmime/kmime_message.h>
63
typedef boost::shared_ptr<KMime::Message> MessagePtr;
65
70
#include <akonadi/attributefactory.h>
66
71
#include <akonadi/cachepolicy.h>
67
72
#include <akonadi/collectionfetchjob.h>
68
73
#include <akonadi/collectionmodifyjob.h>
74
#include <akonadi/collectionquotaattribute.h>
69
75
#include <akonadi/collectionstatisticsjob.h>
70
76
#include <akonadi/collectionstatistics.h>
71
77
#include <akonadi/monitor.h>
72
78
#include <akonadi/changerecorder.h>
73
79
#include <akonadi/collectiondeletejob.h>
80
#include <akonadi/entitydisplayattribute.h>
74
81
#include <akonadi/itemdeletejob.h>
75
82
#include <akonadi/itemfetchjob.h>
76
83
#include <akonadi/itemfetchscope.h>
77
84
#include <akonadi/session.h>
78
85
#include <akonadi/transactionsequence.h>
86
#include <akonadi/collectionfetchscope.h>
88
#include <akonadi/kmime/messageparts.h>
90
#include "collectionannotationsattribute.h"
80
91
#include "collectionflagsattribute.h"
81
#include "collectionannotationsattribute.h"
82
93
#include "imapaclattribute.h"
83
94
#include "imapquotaattribute.h"
85
96
#include "imapaccount.h"
97
#include "imapidlemanager.h"
99
#include "resourceadaptor.h"
87
101
using namespace Akonadi;
103
static const char AKONADI_COLLECTION[] = "akonadiCollection";
104
static const char AKONADI_ITEM[] = "akonadiItem";
105
static const char AKONADI_PARTS[] = "akonadiParts";
106
static const char REPORTED_COLLECTIONS[] = "reportedCollections";
107
static const char PREVIOUS_REMOTEID[] = "previousRemoteId";
108
static const char SOURCE_COLLECTION[] = "sourceCollection";
109
static const char DESTINATION_COLLECTION[] = "destinationCollection";
89
111
ImapResource::ImapResource( const QString &id )
90
:ResourceBase( id ), m_account( 0 )
112
:ResourceBase( id ), m_account( 0 ), m_idle( 0 )
92
114
Akonadi::AttributeFactory::registerAttribute<UidValidityAttribute>();
93
115
Akonadi::AttributeFactory::registerAttribute<UidNextAttribute>();
94
116
Akonadi::AttributeFactory::registerAttribute<NoSelectAttribute>();
118
Akonadi::AttributeFactory::registerAttribute<CollectionAnnotationsAttribute>();
95
119
Akonadi::AttributeFactory::registerAttribute<CollectionFlagsAttribute>();
96
Akonadi::AttributeFactory::registerAttribute<CollectionAnnotationsAttribute>();
97
121
Akonadi::AttributeFactory::registerAttribute<ImapAclAttribute>();
98
122
Akonadi::AttributeFactory::registerAttribute<ImapQuotaAttribute>();
100
124
changeRecorder()->fetchCollection( true );
125
changeRecorder()->collectionFetchScope().setAncestorRetrieval( CollectionFetchScope::All );
101
126
changeRecorder()->itemFetchScope().fetchFullPayload( true );
103
connect( this, SIGNAL(reloadConfiguration()), SLOT(startConnect()) );
127
changeRecorder()->itemFetchScope().setAncestorRetrieval( ItemFetchScope::All );
129
setHierarchicalRemoteIdentifiersEnabled( true );
131
connect( this, SIGNAL(reloadConfiguration()), SLOT(reconnect()) );
133
new ResourceAdaptor( this );
107
136
ImapResource::~ImapResource()
188
225
setName( KGlobal::mainComponent().aboutData()->appName() );
228
if ( dlg.result() == QDialog::Accepted ) {
229
Settings::self()->writeConfig();
232
emit configurationDialogAccepted();
234
emit configurationDialogRejected();
194
238
void ImapResource::startConnect( bool forceManualAuth )
196
240
if ( Settings::self()->imapServer().isEmpty() ) {
200
QString password = Settings::self()->password();
202
if ( password.isEmpty() || forceManualAuth ) {
203
if ( !manualAuth( Settings::self()->userName(), password ) ) {
241
emit status( Broken, i18n( "No server configured yet." ) );
245
connect( Settings::self(), SIGNAL(passwordRequestCompleted(QString, bool)),
246
this, SLOT(onPasswordRequestCompleted(QString, bool)) );
247
if ( forceManualAuth ) {
248
Settings::self()->requestManualAuth();
250
Settings::self()->requestPassword();
254
void ImapResource::onPasswordRequestCompleted( const QString &password, bool userRejected )
256
disconnect( Settings::self(), SIGNAL(passwordRequestCompleted(QString, bool)),
257
this, SLOT(onPasswordRequestCompleted(QString, bool)) );
259
if ( userRejected ) {
260
emit status( Broken, i18n( "Could not read the password: user rejected wallet access." ) );
262
} else if ( password.isEmpty() ) {
263
emit status( Broken, i18n( "Authentication failed." ) );
266
Settings::self()->setPassword( password );
269
if ( m_account!=0 ) {
270
m_account->deleteLater();
271
disconnect( m_account, 0, this, 0 );
209
274
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& ) ) );
276
connect( m_account, SIGNAL( success( KIMAP::Session* ) ),
277
this, SLOT( onConnectSuccess( KIMAP::Session* ) ) );
278
connect( m_account, SIGNAL( error( KIMAP::Session*, int, const QString& ) ),
279
this, SLOT( onConnectError( KIMAP::Session*, int, const QString& ) ) );
216
281
m_account->connect( password );
219
284
void ImapResource::itemAdded( const Item &item, const Collection &collection )
221
const QString mailBox = mailBoxForRemoteId( collection.remoteId() );
286
if ( !isSessionAvailable() ) {
287
kDebug() << "Defering this request. Probably there is no connection.";
292
if ( !item.hasPayload<KMime::Message::Ptr>() ) {
297
const QString mailBox = mailBoxForCollection( collection );
299
kDebug(5327) << "Got notification about item added for local id " << item.id() << " and remote id " << item.remoteId();
223
301
// save message to the server.
224
MessagePtr msg = item.payload<MessagePtr>();
302
KMime::Message::Ptr msg = item.payload<KMime::Message::Ptr>();
226
KIMAP::AppendJob *job = new KIMAP::AppendJob( m_account->session() );
227
job->setProperty( "akonadiCollection", QVariant::fromValue( collection ) );
304
KIMAP::AppendJob *job = new KIMAP::AppendJob( m_account->mainSession() );
305
job->setProperty( AKONADI_COLLECTION, QVariant::fromValue( collection ) );
228
306
job->setProperty( "akonadiItem", QVariant::fromValue( item ) );
229
307
job->setMailBox( mailBox );
230
308
job->setContent( msg->encodedContent( true ) );
470
void ImapResource::itemMoved( const Akonadi::Item &item, const Akonadi::Collection &source,
471
const Akonadi::Collection &destination )
473
if ( !isSessionAvailable() ) {
474
kDebug() << "Defering this request. Probably there is no connection.";
479
if ( item.remoteId().isEmpty() ) {
480
emit error( i18n( "Cannot move message, it does not exist on the server." ) );
485
if ( source.remoteId().isEmpty() ) {
486
emit error( i18n( "Cannot move message out of '%1', '%1' does not exist on the server.",
492
if ( destination.remoteId().isEmpty() ) {
493
emit error( i18n( "Cannot move message to '%1', '%1' does not exist on the server.",
499
const QString oldMailBox = mailBoxForCollection( source );
500
const QString newMailBox = mailBoxForCollection( destination );
502
if ( oldMailBox != newMailBox ) {
503
KIMAP::SelectJob *select = new KIMAP::SelectJob( m_account->mainSession() );
504
select->setMailBox( oldMailBox );
505
select->setProperty( AKONADI_ITEM, QVariant::fromValue( item ) );
506
select->setProperty( SOURCE_COLLECTION, QVariant::fromValue( source ) );
507
select->setProperty( DESTINATION_COLLECTION, QVariant::fromValue( destination ) );
508
connect( select, SIGNAL( result( KJob* ) ), SLOT( onPreItemMoveSelectDone( KJob* ) ) );
515
void ImapResource::onPreItemMoveSelectDone( KJob *job )
517
if ( !job->error() ) {
518
Item item = job->property( AKONADI_ITEM ).value<Item>();
519
const qint64 uid = item.remoteId().toLongLong();
521
Collection destination = job->property( DESTINATION_COLLECTION ).value<Collection>();
522
const QString newMailBox = mailBoxForCollection( destination );
524
KIMAP::CopyJob *copy = new KIMAP::CopyJob( m_account->mainSession() );
525
copy->setProperty( AKONADI_ITEM, job->property( AKONADI_ITEM ) );
526
copy->setProperty( SOURCE_COLLECTION, job->property( SOURCE_COLLECTION ) );
527
copy->setProperty( DESTINATION_COLLECTION, job->property( DESTINATION_COLLECTION ) );
528
copy->setUidBased( true );
529
copy->setSequenceSet( KIMAP::ImapSet( uid ) );
530
copy->setMailBox( newMailBox );
531
connect( copy, SIGNAL( result( KJob* ) ), SLOT( onCopyMessageDone( KJob* ) ) );
535
const Collection source = job->property( SOURCE_COLLECTION ).value<Collection>();
536
Q_ASSERT( source.isValid() );
537
emit error( i18n( "Failed to move message out of '%1' on the IMAP server. Could not select '%1'.",
543
void ImapResource::onCopyMessageDone( KJob *job )
545
if ( !job->error() ) {
546
Item item = job->property( AKONADI_ITEM ).value<Item>();
547
Collection destination = job->property( DESTINATION_COLLECTION ).value<Collection>();
548
const qint64 oldUid = item.remoteId().toLongLong();
550
// Go ahead, UIDPLUS is supposed to be supported and we copied a single message
551
KIMAP::CopyJob *copy = static_cast<KIMAP::CopyJob*>( job );
552
const qint64 newUid = copy->resultingUids().intervals().first().begin();
554
// Update the item content with the new UID from the copy
555
item.setRemoteId( QString::number( newUid ) );
556
item.setParentCollection( destination );
558
// Mark the old one ready for deletion
559
KIMAP::StoreJob *store = new KIMAP::StoreJob( m_account->mainSession() );
560
store->setProperty( AKONADI_ITEM, QVariant::fromValue( item ) );
561
store->setProperty( SOURCE_COLLECTION, job->property( SOURCE_COLLECTION ) );
562
store->setProperty( DESTINATION_COLLECTION, job->property( DESTINATION_COLLECTION ) );
563
store->setUidBased( true );
564
store->setSequenceSet( KIMAP::ImapSet( oldUid ) );
565
store->setFlags( QList<QByteArray>() << "\\Deleted" );
566
store->setMode( KIMAP::StoreJob::AppendFlags );
567
connect( store, SIGNAL( result( KJob* ) ), SLOT( onPostItemMoveStoreFlagsDone( KJob* ) ) );
571
const Collection destination = job->property( DESTINATION_COLLECTION ).value<Collection>();
572
Q_ASSERT( destination.isValid() );
573
emit error( i18n( "Failed to move message to '%1' on the IMAP server. Could not copy into '%1'.",
574
destination.name() ) );
579
void ImapResource::onPostItemMoveStoreFlagsDone( KJob *job )
581
Item item = job->property( AKONADI_ITEM ).value<Item>();
583
if ( job->error() ) {
584
const Collection source = job->property( SOURCE_COLLECTION ).value<Collection>();
585
Q_ASSERT( source.isValid() );
586
emit warning( i18n( "Failed to mark the message from '%1' for deletion on the IMAP server. "
587
"It will reappear on next sync.",
591
changeCommitted( item );
594
typedef QHash<QString, Collection> StringCollectionMap;
595
Q_DECLARE_METATYPE( StringCollectionMap )
380
597
void ImapResource::retrieveCollections()
382
if ( !m_account || !m_account->session() ) {
383
kDebug() << "Ignoring this request. Probably there is no connection.";
599
if ( !isSessionAvailable() ) {
600
kDebug(5327) << "Ignoring this request. Probably there is no connection.";
601
cancelTask( i18n( "There is currently no connection to the IMAP server." ) );
418
662
for ( int i=0; i<descriptors.size(); ++i ) {
419
663
KIMAP::MailBoxDescriptor descriptor = descriptors[i];
421
QStringList pathParts = descriptor.name.split(descriptor.separator);
422
QString separator = descriptor.separator;
665
const QStringList pathParts = descriptor.name.split(descriptor.separator);
666
const QString separator = descriptor.separator;
667
Q_ASSERT( separator.size() == 1 ); // that's what the spec says
424
669
QString parentPath;
425
670
QString currentPath;
427
foreach ( const QString &pathPart, pathParts ) {
428
currentPath+='/'+pathPart;
429
if ( currentPath.startsWith( '/' ) ) {
430
currentPath.remove( 0, 1 );
672
for ( int j = 0; j < pathParts.size(); ++j ) {
673
const bool isDummy = j != pathParts.size() - 1;
674
const QString pathPart = pathParts.at( j );
675
currentPath += separator + pathPart;
433
if ( reportedPaths.contains( currentPath ) ) {
677
if ( reportedCollections.contains( currentPath ) ) {
679
kWarning() << "Something is wrong here, we already have created a collection for" << currentPath;
434
680
parentPath = currentPath;
437
reportedPaths << currentPath;
684
const QList<QByteArray> currentFlags = isDummy ? (QList<QByteArray>() << "\\NoSelect") : flags[i];
441
687
c.setName( pathPart );
442
c.setRemoteId( remoteIdForMailBox( currentPath ) );
443
c.setParentRemoteId( remoteIdForMailBox( parentPath ) );
444
c.setRights( Collection::AllRights );
688
c.setRemoteId( separator + pathPart );
689
const Collection parentCollection = reportedCollections.value( parentPath );
690
c.setParentCollection( parentCollection );
445
691
c.setContentMimeTypes( contentTypes );
447
CachePolicy cachePolicy;
448
cachePolicy.setInheritFromParent( false );
449
cachePolicy.setIntervalCheckTime( -1 );
450
cachePolicy.setSyncOnDemand( true );
452
693
// 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" );
694
if ( currentPath.compare( separator + QLatin1String("INBOX") , Qt::CaseInsensitive ) == 0 ) {
695
EntityDisplayAttribute *attr = c.attribute<EntityDisplayAttribute>( Collection::AddIfMissing );
696
attr->setDisplayName( i18n( "Inbox" ) );
697
attr->setIconName( "mail-folder-inbox" );
699
Collection *curCol = &c;
700
while ( (*curCol) != Collection::root() && !curCol->remoteId().isEmpty() ) {
701
ridPath.append( curCol->remoteId() );
702
curCol = &curCol->parentCollection();
704
Settings::self()->setIdleRidPath( ridPath );
705
Settings::self()->writeConfig();
710
// If the folder is the user top-level folder, mark it as well, even although it is not officially noted in the RFC
711
if ( currentPath == (separator + QLatin1String( "user" )) && currentFlags.contains( "\\NoSelect" ) ) {
712
EntityDisplayAttribute *attr = c.attribute<EntityDisplayAttribute>( Collection::AddIfMissing );
713
attr->setDisplayName( i18n( "Shared Folders" ) );
714
attr->setIconName( "x-mail-distribution-list" );
458
717
// If this folder is a noselect folder, make some special settings.
459
if ( flags[i].contains( "\\NoSelect" ) ) {
460
cachePolicy.setSyncOnDemand( false );
718
if ( currentFlags.contains( "\\NoSelect" ) ) {
461
719
c.addAttribute( new NoSelectAttribute( true ) );
720
c.setContentMimeTypes( QStringList() << Collection::mimeType() );
721
c.setRights( Collection::ReadOnly );
464
c.setCachePolicy( cachePolicy );
466
724
collections << c;
726
reportedCollections.insert( currentPath, c );
467
727
parentPath = currentPath;
471
sender()->setProperty("reportedPaths", reportedPaths);
472
collectionsRetrievedIncremental( collections, Collection::List() );
731
sender()->setProperty( REPORTED_COLLECTIONS, QVariant::fromValue<StringCollectionMap>( reportedCollections ) );
732
collectionsRetrieved( collections );
734
if ( Settings::self()->retrieveMetadataOnFolderListing() ) {
735
foreach ( const Collection &c, collections ) {
736
triggerCollectionExtraInfoJobs( c );
475
741
void ImapResource::onMailBoxesReceiveDone(KJob* job)
477
743
// TODO error handling
478
745
collectionsRetrievalDone();
481
748
// ----------------------------------------------------------------------------------
483
void ImapResource::retrieveItems( const Collection &col )
750
void ImapResource::triggerCollectionExtraInfoJobs( const Collection &collection )
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() );
752
const QString mailBox = mailBoxForCollection( collection );
498
753
const QStringList capabilities = m_account->capabilities();
500
755
// First get the annotations from the mailbox if it's supported
501
756
if ( capabilities.contains( "METADATA" ) || capabilities.contains( "ANNOTATEMORE" ) ) {
502
KIMAP::GetMetaDataJob *meta = new KIMAP::GetMetaDataJob( m_account->session() );
503
meta->setProperty( "akonadiCollection", QVariant::fromValue( col ) );
757
KIMAP::GetMetaDataJob *meta = new KIMAP::GetMetaDataJob( m_account->mainSession() );
758
meta->setProperty( AKONADI_COLLECTION, QVariant::fromValue( collection ) );
504
759
meta->setMailBox( mailBox );
505
760
if ( capabilities.contains( "METADATA" ) ) {
506
761
meta->setServerCapability( KIMAP::MetaDataJobBase::Metadata );
531
786
// Get the QUOTA info from the mailbox if it's supported
532
787
if ( capabilities.contains( "QUOTA" ) ) {
533
KIMAP::GetQuotaRootJob *quota = new KIMAP::GetQuotaRootJob( m_account->session() );
534
quota->setProperty( "akonadiCollection", QVariant::fromValue( col ) );
788
KIMAP::GetQuotaRootJob *quota = new KIMAP::GetQuotaRootJob( m_account->mainSession() );
789
quota->setProperty( AKONADI_COLLECTION, QVariant::fromValue( collection ) );
535
790
quota->setMailBox( mailBox );
536
791
connect( quota, SIGNAL( result( KJob* ) ), SLOT( onQuotasReceived( KJob* ) ) );
796
void ImapResource::retrieveItems( const Collection &col )
798
if ( !isSessionAvailable() ) {
799
kDebug() << "Ignoring this request. Probably there is no connection.";
800
cancelTask( i18n( "There is currently no connection to the IMAP server." ) );
804
kDebug(5327) << col.remoteId();
806
// Prevent fetching items from noselect folders.
807
if ( col.hasAttribute( "noselect" ) ) {
808
NoSelectAttribute* noselect = static_cast<NoSelectAttribute*>( col.attribute( "noselect" ) );
809
if ( noselect->noSelect() ) {
810
kDebug(5327) << "No Select folder";
811
itemsRetrievalDone();
816
triggerCollectionExtraInfoJobs( col );
818
const QString mailBox = mailBoxForCollection( col );
540
820
// 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() );
821
if ( Settings::self()->automaticExpungeEnabled() ) {
822
triggerExpunge( mailBox );
547
825
// Issue another select to get the updated info from the mailbox
548
select = new KIMAP::SelectJob( m_account->session() );
826
KIMAP::SelectJob *select = new KIMAP::SelectJob( m_account->mainSession() );
827
select->setProperty( AKONADI_COLLECTION, QVariant::fromValue( col ) );
549
828
select->setMailBox( mailBox );
550
829
connect( select, SIGNAL( result( KJob* ) ),
551
830
this, SLOT( onSelectDone( KJob* ) ) );
834
void ImapResource::triggerExpunge( const QString &mailBox )
836
kDebug(5327) << mailBox;
838
KIMAP::SelectJob *select = new KIMAP::SelectJob( m_account->mainSession() );
839
select->setMailBox( mailBox );
842
KIMAP::ExpungeJob *expunge = new KIMAP::ExpungeJob( m_account->mainSession() );
555
846
void ImapResource::onHeadersReceived( const QString &mailBox, const QMap<qint64, qint64> &uids,
556
847
const QMap<qint64, qint64> &sizes,
557
848
const QMap<qint64, KIMAP::MessageFlags> &flags,
558
849
const QMap<qint64, KIMAP::MessagePtr> &messages )
560
853
Item::List addedItems;
562
855
foreach ( qint64 number, uids.keys() ) {
564
i.setRemoteId( remoteIdForMailBox( mailBox ) + "-+-" + QString::number( uids[number] ) );
857
i.setRemoteId( QString::number( uids[number] ) );
565
858
i.setMimeType( "message/rfc822" );
566
i.setPayload( MessagePtr( messages[number] ) );
859
i.setPayload( KMime::Message::Ptr( messages[number] ) );
567
860
i.setSize( sizes[number] );
569
862
foreach( const QByteArray &flag, flags[number] ) {
570
863
i.setFlag( flag );
572
kDebug() << "Flags: " << i.flags();
865
kDebug(5327) << "Flags: " << i.flags();
576
itemsRetrievedIncremental( addedItems, Item::List() );
579
void ImapResource::onHeadersFetchDone( KJob */*job*/ )
869
if ( sender()->property( "nonIncremental" ).toBool() ) {
870
itemsRetrieved( addedItems );
872
itemsRetrievedIncremental( addedItems, Item::List() );
876
void ImapResource::onHeadersFetchDone( KJob *job )
878
if ( job->property( "nonIncremental" ).toBool() ) {
879
itemsRetrievalDone();
882
KIMAP::FetchJob *fetch = static_cast<KIMAP::FetchJob*>( job );
883
KIMAP::ImapSet alreadyFetched = fetch->sequenceSet();
885
KIMAP::FetchJob::FetchScope scope;
887
scope.mode = KIMAP::FetchJob::FetchScope::Flags;
889
fetch = new KIMAP::FetchJob( m_account->mainSession() );
890
fetch->setSequenceSet( KIMAP::ImapSet( 1, alreadyFetched.intervals().first().begin()-1 ) );
891
fetch->setScope( scope );
892
connect( fetch, SIGNAL( headersReceived( QString, QMap<qint64, qint64>, QMap<qint64, qint64>,
893
QMap<qint64, KIMAP::MessageFlags>, QMap<qint64, KIMAP::MessagePtr> ) ),
894
this, SLOT( onFlagsReceived( QString, QMap<qint64, qint64>, QMap<qint64, qint64>,
895
QMap<qint64, KIMAP::MessageFlags>, QMap<qint64, KIMAP::MessagePtr> ) ) );
896
connect( fetch, SIGNAL( result( KJob* ) ),
897
this, SLOT( onFlagsFetchDone( KJob* ) ) );
902
void ImapResource::onFlagsReceived( const QString &mailBox, const QMap<qint64, qint64> &uids,
903
const QMap<qint64, qint64> &sizes,
904
const QMap<qint64, KIMAP::MessageFlags> &flags,
905
const QMap<qint64, KIMAP::MessagePtr> &messages )
909
Item::List changedItems;
911
foreach ( qint64 number, uids.keys() ) {
913
i.setRemoteId( QString::number( uids[number] ) );
914
i.setMimeType( "message/rfc822" );
915
i.setFlags( Akonadi::Item::Flags::fromList( flags[number] ) );
917
kDebug(5327) << "Flags: " << i.flags();
921
itemsRetrievedIncremental( changedItems, Item::List() );
924
void ImapResource::onFlagsFetchDone( KJob * /*job*/ )
581
926
itemsRetrievalDone();
585
929
// ----------------------------------------------------------------------------------
587
931
void ImapResource::collectionAdded( const Collection & collection, const Collection &parent )
589
const QString remoteName = parent.remoteId() + '/' + collection.name();
591
kDebug( ) << "New folder: " << remoteName;
933
if ( !isSessionAvailable() ) {
934
kDebug() << "Defering this request. Probably there is no connection.";
939
if ( parent.remoteId().isEmpty() ) {
940
emit error( i18n("Cannot add IMAP folder '%1' for a non-existing parent folder '%2'.", collection.name(), parent.name() ) );
945
QString newMailBox = mailBoxForCollection( parent );
946
if ( !newMailBox.isEmpty() )
947
newMailBox += parent.remoteId().at( 0 ); // separator for non-toplevel mailboxes
948
newMailBox += collection.name();
950
kDebug(5327) << "New folder: " << newMailBox;
593
952
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 );
953
c.setRemoteId( parent.remoteId().at( 0 ) + collection.name() );
955
KIMAP::CreateJob *job = new KIMAP::CreateJob( m_account->mainSession() );
956
job->setProperty( AKONADI_COLLECTION, QVariant::fromValue( c ) );
957
job->setMailBox( newMailBox );
601
958
connect( job, SIGNAL( result( KJob* ) ), SLOT( onCreateMailBoxDone( KJob* ) ) );
605
962
void ImapResource::onCreateMailBoxDone( KJob *job )
607
Collection collection = job->property( "akonadiCollection" ).value<Collection>();
964
const Collection collection = job->property( AKONADI_COLLECTION ).value<Collection>();
966
// Automatically subscribe to newly created mailbox
967
KIMAP::CreateJob *create = static_cast<KIMAP::CreateJob*>( job );
968
KIMAP::SubscribeJob *subscribe = new KIMAP::SubscribeJob( m_account->mainSession() );
969
subscribe->setMailBox( create->mailBox() );
609
972
if ( !job->error() ) {
610
973
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* ) ) );
975
emit error( i18n( "Failed to create folder '%1' on the IMAP server.", collection.name() ) );
980
void ImapResource::collectionChanged( const Collection &collection, const QSet<QByteArray> &parts )
982
if ( !isSessionAvailable() ) {
983
kDebug() << "Defering this request. Probably there is no connection.";
988
if ( collection.remoteId().isEmpty() ) {
989
emit error( i18n("Cannot modify IMAP folder '%1', it does not exist on the server.", collection.name() ) );
994
QStringList encodedParts;
995
foreach ( const QByteArray &part, parts ) {
996
encodedParts << QString::fromUtf8( part );
999
kDebug(5327) << "parts:" << encodedParts;
1001
triggerNextCollectionChangeJob( collection, encodedParts );
1004
void ImapResource::triggerNextCollectionChangeJob( const Akonadi::Collection &collection,
1005
const QStringList &remainingParts )
1007
if ( remainingParts.isEmpty() ) { // We processed all parts, we're done here
1008
changeCommitted( collection );
1012
QStringList parts = remainingParts;
1013
QString currentPart = parts.takeFirst();
1015
if ( currentPart == "NAME" ) {
1016
Collection c = collection;
1017
c.setRemoteId( collection.remoteId().at( 0 ) + collection.name() );
1019
const QString oldMailBox = mailBoxForCollection( collection );
1020
const QString newMailBox = mailBoxForCollection( c );
1022
if ( oldMailBox != newMailBox ) {
1023
KIMAP::RenameJob *job = new KIMAP::RenameJob( m_account->mainSession() );
1024
job->setProperty( AKONADI_COLLECTION, QVariant::fromValue( c ) );
1025
job->setProperty( AKONADI_PARTS, parts );
1026
job->setProperty( PREVIOUS_REMOTEID, collection.remoteId() );
1027
job->setSourceMailBox( oldMailBox );
1028
job->setDestinationMailBox( newMailBox );
1029
connect( job, SIGNAL( result( KJob* ) ), SLOT( onRenameMailBoxDone( KJob* ) ) );
1032
triggerNextCollectionChangeJob( collection, parts );
1035
} else if ( currentPart == "AccessRights" ) {
1036
ImapAclAttribute *aclAttribute =
1037
collection.attribute<ImapAclAttribute>();
1039
if ( aclAttribute==0 ) {
1040
emit error( i18n( "ACLs for '%1' need to be retrieved from the IMAP server first. Skipping ACL change",
1041
collection.name() ) );
1042
triggerNextCollectionChangeJob( collection, parts );
1046
KIMAP::Acl::Rights imapRights = aclAttribute->rights()[m_account->userName().toUtf8()];
1047
Collection::Rights newRights = collection.rights();
1049
if ( newRights & Collection::CanChangeItem ) {
1050
imapRights|= KIMAP::Acl::Write;
1052
imapRights&= ~KIMAP::Acl::Write;
1055
if ( newRights & Collection::CanCreateItem ) {
1056
imapRights|= KIMAP::Acl::Insert;
1058
imapRights&= ~KIMAP::Acl::Insert;
1061
if ( newRights & Collection::CanDeleteItem ) {
1062
imapRights|= KIMAP::Acl::DeleteMessage;
1064
imapRights&= ~KIMAP::Acl::DeleteMessage;
1067
if ( newRights & ( Collection::CanChangeCollection | Collection::CanCreateCollection ) ) {
1068
imapRights|= KIMAP::Acl::CreateMailbox;
1069
imapRights|= KIMAP::Acl::Create;
1071
imapRights&= ~KIMAP::Acl::CreateMailbox;
1072
imapRights&= ~KIMAP::Acl::Create;
1075
if ( newRights & Collection::CanDeleteCollection ) {
1076
imapRights|= KIMAP::Acl::DeleteMailbox;
1078
imapRights&= ~KIMAP::Acl::DeleteMailbox;
1081
if ( ( newRights & Collection::CanDeleteItem )
1082
&& ( newRights & Collection::CanDeleteCollection ) ) {
1083
imapRights|= KIMAP::Acl::Delete;
1085
imapRights&= ~KIMAP::Acl::Delete;
1088
kDebug(5327) << "imapRights:" << imapRights
1089
<< "newRights:" << newRights;
1091
KIMAP::SetAclJob *job = new KIMAP::SetAclJob( m_account->mainSession() );
1092
job->setProperty( AKONADI_COLLECTION, QVariant::fromValue( collection ) );
1093
job->setProperty( AKONADI_PARTS, parts );
1094
job->setMailBox( mailBoxForCollection( collection ) );
1095
job->setRights( KIMAP::SetAclJob::Change, imapRights );
1096
job->setIdentifier( m_account->userName().toUtf8() );
1097
connect( job, SIGNAL( result( KJob* ) ), SLOT( onSetAclDone( KJob* ) ) );
1100
} else if ( currentPart == "collectionannotations" ) {
1101
CollectionAnnotationsAttribute *annotationsAttribute =
1102
collection.attribute<CollectionAnnotationsAttribute>();
1104
if ( annotationsAttribute==0 ) { // No annotations it seems... server is lieing to us?
1105
triggerNextCollectionChangeJob( collection, parts );
1108
KIMAP::SetMetaDataJob *job = 0;
1110
QMap<QByteArray, QByteArray> annotations = annotationsAttribute->annotations();
1111
kDebug(5327) << "All annotations: " << annotations;
1112
foreach ( const QByteArray &entry, annotations.keys() ) {
1113
job = new KIMAP::SetMetaDataJob( m_account->mainSession() );
1114
if ( m_account->capabilities().contains( "METADATA" ) ) {
1115
job->setServerCapability( KIMAP::MetaDataJobBase::Metadata );
1117
job->setServerCapability( KIMAP::MetaDataJobBase::Annotatemore );
1120
QByteArray attribute = entry;
1121
if ( job->serverCapability()==KIMAP::MetaDataJobBase::Annotatemore ) {
1122
attribute = "value.shared";
1125
job->setMailBox( mailBoxForCollection( collection ) );
1126
job->setEntry( entry );
1127
job->addMetaData( attribute, annotations[entry] );
1128
kDebug(5327) << "Job got entry:" << entry << " attribute:" << attribute << "value:" << annotations[entry];
1133
// We'll get info out of the last job only to trigger the next phase
1134
// of the collection change. The other ones we fire and forget.
1135
// Obviously we assume here that they will all succeed or all fail.
1136
job->setProperty( AKONADI_COLLECTION, QVariant::fromValue( collection ) );
1137
job->setProperty( AKONADI_PARTS, parts );
1138
connect( job, SIGNAL( result( KJob* ) ), SLOT( onSetMetaDataDone( KJob* ) ) );
1140
} else if ( currentPart == "imapacl" ) {
1141
ImapAclAttribute *aclAttribute = collection.attribute<ImapAclAttribute>();
1142
const QMap<QByteArray, KIMAP::Acl::Rights> rights = aclAttribute->rights();
1143
const QList<QByteArray> ids = rights.keys();
1145
for ( int i = 0; i<ids.size(); i++ ) {
1146
const QByteArray id = ids[i];
1148
KIMAP::SetAclJob *job = new KIMAP::SetAclJob( m_account->mainSession() );
1149
job->setMailBox( mailBoxForCollection( collection ) );
1150
job->setIdentifier( id );
1151
job->setRights( KIMAP::SetAclJob::Change, rights[id] );
1153
if ( i < ids.size()-1 ) {
1154
// Only the last set acl job will trigger the next collection change job
1155
job->setProperty( "dontTriggerNextJob", true );
1158
connect( job, SIGNAL( result( KJob* ) ), SLOT( onSetAclDone( KJob* ) ) );
1164
triggerNextCollectionChangeJob( collection, parts );
640
1169
void ImapResource::onRenameMailBoxDone( KJob *job )
642
Collection collection = job->property( "akonadiCollection" ).value<Collection>();
1171
Collection collection = job->property( AKONADI_COLLECTION ).value<Collection>();
1172
QStringList parts = job->property( AKONADI_PARTS ).toStringList();
644
1174
if ( !job->error() ) {
645
changeCommitted( collection );
1175
triggerNextCollectionChangeJob( collection, parts );
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() ) );
1177
kDebug(5327) << "Failed to rename the folder, resetting it in akonadi again";
1178
const QString prevRid = job->property( PREVIOUS_REMOTEID ).toString();
1179
Q_ASSERT( !prevRid.isEmpty() );
1180
collection.setName( prevRid.mid( 1 ) );
1181
collection.setRemoteId( prevRid );
653
1182
emit warning( i18n( "Failed to rename the folder, restoring folder list." ) );
654
1183
changeCommitted( collection );
1187
void ImapResource::onSetAclDone( KJob *job )
1189
Collection collection = job->property( AKONADI_COLLECTION ).value<Collection>();
1190
QStringList parts = job->property( AKONADI_PARTS ).toStringList();
1192
if ( job->error() ) {
1193
emit error( i18n( "Failed to write the new ACLs for '%1' on the IMAP server. %2",
1194
collection.name(), job->errorText() ) );
1197
if ( !job->property( "dontTriggerNextJob" ).toBool() ) {
1198
triggerNextCollectionChangeJob( collection, parts );
1202
void ImapResource::onSetMetaDataDone( KJob *job )
1204
Collection collection = job->property( AKONADI_COLLECTION ).value<Collection>();
1205
QStringList parts = job->property( AKONADI_PARTS ).toStringList();
1207
if ( job->error() ) {
1208
emit error( i18n( "Failed to write the new annotations for '%1' on the IMAP server. %2",
1209
collection.name(), job->errorText() ) );
1212
triggerNextCollectionChangeJob( collection, parts );
658
1215
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 ) );
1217
if ( !isSessionAvailable() ) {
1218
kDebug() << "Defering this request. Probably there is no connection.";
1223
const QString mailBox = mailBoxForCollection( collection );
1225
KIMAP::DeleteJob *job = new KIMAP::DeleteJob( m_account->mainSession() );
1226
job->setProperty( AKONADI_COLLECTION, QVariant::fromValue( collection ) );
664
1227
job->setMailBox( mailBox );
665
1228
connect( job, SIGNAL( result( KJob* ) ), SLOT( onDeleteMailBoxDone( KJob* ) ) );
1021
1694
QMap<qint64, KIMAP::MessageFlags>, QMap<qint64, KIMAP::MessagePtr> ) ) );
1022
1695
connect( fetch, SIGNAL( result( KJob* ) ),
1023
1696
this, SLOT( onHeadersFetchDone( KJob* ) ) );
1697
fetch->setProperty( "nonIncremental", true );
1024
1698
fetch->start();
1028
kDebug() << "All fine, nothing to do";
1029
itemsRetrievalDone();
1702
kDebug(5327) << "All fine, asking for all message flags looking for changes";
1704
setItemStreamingEnabled( true );
1706
scope.parts.clear();
1707
scope.mode = KIMAP::FetchJob::FetchScope::Flags;
1709
KIMAP::FetchJob *fetch = new KIMAP::FetchJob( m_account->mainSession() );
1710
fetch->setSequenceSet( KIMAP::ImapSet( 1, messageCount ) );
1711
fetch->setScope( scope );
1712
connect( fetch, SIGNAL( headersReceived( QString, QMap<qint64, qint64>, QMap<qint64, qint64>,
1713
QMap<qint64, KIMAP::MessageFlags>, QMap<qint64, KIMAP::MessagePtr> ) ),
1714
this, SLOT( onFlagsReceived( QString, QMap<qint64, qint64>, QMap<qint64, qint64>,
1715
QMap<qint64, KIMAP::MessageFlags>, QMap<qint64, KIMAP::MessagePtr> ) ) );
1716
connect( fetch, SIGNAL( result( KJob* ) ),
1717
this, SLOT( onFlagsFetchDone( KJob* ) ) );
1033
1722
/******************* 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
1724
QString ImapResource::rootRemoteId() const
1050
1726
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 ) {
1729
QString ImapResource::mailBoxForCollection( const Collection& col ) const
1731
if ( col.remoteId().isEmpty() ) {
1732
kWarning() << "Got incomplete ancestor chain:" << col;
1736
if ( col.parentCollection() == Collection::root() ) {
1737
kWarning( col.remoteId() != rootRemoteId() ) << "RID mismatch, is " << col.remoteId() << " expected " << rootRemoteId();
1738
return QString( "" );
1740
const QString parentMailbox = mailBoxForCollection( col.parentCollection() );
1741
if ( parentMailbox.isNull() ) // invalid, != isEmpty() here!
1744
const QString mailbox = parentMailbox + col.remoteId();
1745
if ( parentMailbox.isEmpty() )
1746
return mailbox.mid( 1 ); // strip of the separator on top-level mailboxes
1096
1750
void ImapResource::itemsClear( const Collection &collection )
1108
1762
transaction->exec();
1765
void ImapResource::doSetOnline(bool online)
1767
if ( !online && isSessionAvailable() ) {
1768
m_account->disconnect();
1769
} else if ( online ) {
1772
ResourceBase::doSetOnline( online );
1775
bool ImapResource::needsNetwork() const
1777
const QString hostName = Settings::self()->imapServer().section( ':', 0, 0 );
1778
// ### is there a better way to do this?
1779
if ( hostName == QLatin1String( "127.0.0.1" ) ||
1780
hostName == QLatin1String( "localhost" ) ||
1781
hostName == QHostInfo::localHostName() ) {
1787
bool ImapResource::isSessionAvailable() const
1789
return m_account && m_account->mainSession()
1790
&& m_account->mainSession()->state() != KIMAP::Session::Disconnected;
1793
void ImapResource::reconnect()
1795
setNeedsNetwork( needsNetwork() );
1796
setOnline( false ); // we are not connected initially
1797
setOnline( !needsNetwork() ||
1798
Solid::Networking::status() == Solid::Networking::Unknown ||
1799
Solid::Networking::status() == Solid::Networking::Connected );
1802
void ImapResource::startIdle()
1807
if ( !m_account || !m_account->capabilities().contains( "IDLE" ) )
1810
const QStringList ridPath = Settings::self()->idleRidPath();
1811
if ( ridPath.size() < 2 )
1815
p.setParentCollection( Collection::root() );
1816
for ( int i = ridPath.size() - 1; i > 0; --i ) {
1817
p.setRemoteId( ridPath.at( i ) );
1818
c.setParentCollection( p );
1821
c.setRemoteId( ridPath.first() );
1823
Akonadi::CollectionFetchScope scope;
1824
scope.setResource( identifier() );
1826
Akonadi::CollectionFetchJob *fetch
1827
= new Akonadi::CollectionFetchJob( c, Akonadi::CollectionFetchJob::Base, this );
1828
fetch->setFetchScope( scope );
1829
fetch->setProperty( "mailBox", mailBoxForCollection( c ) );
1831
connect( fetch, SIGNAL(result(KJob*)),
1832
this, SLOT(onIdleCollectionFetchDone(KJob*)) );
1835
void ImapResource::onIdleCollectionFetchDone( KJob *job )
1837
const QString mailBox = job->property( "mailBox" ).toString();
1839
if ( job->error() == 0 ) {
1840
Akonadi::CollectionFetchJob *fetch = static_cast<Akonadi::CollectionFetchJob*>( job );
1841
Akonadi::Collection c = fetch->collections().first();
1843
const QString password = Settings::self()->password();
1844
if ( password.isEmpty() )
1847
m_idle = new ImapIdleManager( c, mailBox,
1848
m_account->extraSession( "idle", password ),
1852
kWarning() << "CollectionFetch for mail box "
1853
<< mailBox << "failed. error="
1854
<< job->error() << ", errorString=" << job->errorString();
1858
void ImapResource::requestManualExpunge( qint64 collectionId )
1860
if ( !Settings::self()->automaticExpungeEnabled() ) {
1861
scheduleCustomTask( this, "expungeRequested",
1862
QVariant::fromValue( Collection( collectionId ) ) );
1866
void ImapResource::expungeRequested( const QVariant &collectionArgument )
1868
const Collection collection = collectionArgument.value<Collection>();
1870
if ( collection.isValid() ) {
1871
Akonadi::CollectionFetchScope scope;
1872
scope.setResource( identifier() );
1873
scope.setAncestorRetrieval( Akonadi::CollectionFetchScope::All );
1875
Akonadi::CollectionFetchJob *fetch
1876
= new Akonadi::CollectionFetchJob( collection,
1877
Akonadi::CollectionFetchJob::Base,
1879
fetch->setFetchScope( scope );
1880
fetch->setProperty( AKONADI_COLLECTION, collection.id() );
1882
connect( fetch, SIGNAL(result(KJob*)),
1883
this, SLOT(onExpungeCollectionFetchDone(KJob*)) );
1889
void ImapResource::onExpungeCollectionFetchDone( KJob *job )
1891
const Collection::Id collectionId = job->property( AKONADI_COLLECTION ).toLongLong();
1893
if ( job->error() == 0 ) {
1894
Akonadi::CollectionFetchJob *fetch = static_cast<Akonadi::CollectionFetchJob*>( job );
1896
foreach ( const Akonadi::Collection &c, fetch->collections() ) {
1897
if ( c.id() == collectionId ) {
1898
const QString mailBox = mailBoxForCollection( c );
1900
if ( !mailBox.isEmpty() ) {
1901
triggerExpunge( mailBox );
1907
kWarning() << "CollectionFetch for collection "
1908
<< collectionId << "failed. error="
1909
<< job->error() << ", errorString=" << job->errorString();
1111
1915
AKONADI_RESOURCE_MAIN( ImapResource )
1113
1917
#include "imapresource.moc"