diff --git a/akonadi/kmime/localfolders.cpp b/akonadi/kmime/localfolders.cpp index d1c376b4b..75029319d 100644 --- a/akonadi/kmime/localfolders.cpp +++ b/akonadi/kmime/localfolders.cpp @@ -1,436 +1,500 @@ /* Copyright (c) 2009 Constantin Berzan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "localfolders.h" #include "localfolderssettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include // copied from playground/pim/akonaditest #define DBUS_SERVICE_NAME QLatin1String( "org.kde.pim.LocalFolders" ) using namespace Akonadi; typedef LocalFoldersSettings Settings; /** * Private class that helps to provide binary compatibility between releases. * @internal */ class Akonadi::LocalFoldersPrivate { public: LocalFoldersPrivate(); ~LocalFoldersPrivate(); LocalFolders *instance; bool ready; bool preparing; bool scheduled; bool isMainInstance; - Collection outbox; - Collection sentMail; Collection rootMaildir; - KJob *outboxJob; - KJob *sentMailJob; + QMultiHash folders; + QSet pendingJobs; Monitor *monitor; /** If this is the main instance, attempts to create the resource and collections if necessary, then fetches them. If this is not the main instance, waits for them to be created by the main instance, and then fetches them. Will emit foldersReady() when done */ void prepare(); /** Schedules a prepare() in 1 second. Called when this is not the main instance and we need to wait, or when something disappeared and needs to be recreated. */ void schedulePrepare(); // slot /** Creates the maildir resource, if it is not found. */ void createResourceIfNeeded(); /** Creates the outbox and sent-mail collections, if they are not present. */ void createCollectionsIfNeeded(); /** Creates a Monitor to watch the resource and connects to its signals. This is used to watch for evil users deleting the resource / outbox / etc. */ void connectMonitor(); /** Fetches the collections of the maildir resource. There is one root collection, which contains the outbox and sent-mail collections. */ void fetchCollections(); void resourceCreateResult( KJob *job ); void resourceSyncResult( KJob *job ); void collectionCreateResult( KJob *job ); void collectionFetchResult( KJob *job ); + /** + Returns the English name for a default (i.e. non-custom) folder type. + */ + static QString nameForType( int type ); + + /** + Returns the Type for an English name for a folder type. + */ + static LocalFolders::Type typeForName( const QString &name ); }; K_GLOBAL_STATIC( LocalFoldersPrivate, sInstance ) LocalFolders::LocalFolders( LocalFoldersPrivate *dd ) : QObject() , d( dd ) { } LocalFolders *LocalFolders::self() { return sInstance->instance; } void LocalFolders::fetch() { d->prepare(); } bool LocalFolders::isReady() const { return d->ready; } +Collection LocalFolders::inbox() const +{ + return folder( Inbox ); +} + Collection LocalFolders::outbox() const { - Q_ASSERT( d->ready ); - return d->outbox; + return folder( Outbox ); } Collection LocalFolders::sentMail() const +{ + return folder( SentMail ); +} + +Collection LocalFolders::trash() const +{ + return folder( Trash ); +} + +Collection LocalFolders::drafts() const +{ + return folder( Drafts ); +} + +Collection LocalFolders::templates() const +{ + return folder( Templates ); +} + +#if 0 +Collection LocalFolders::folder( const QString &name ) const { Q_ASSERT( d->ready ); - return d->sentMail; + Q_FOREACH( const Collection &col, d->folders.values() ) { + if( col.name() == name ) { + return col; + } + } +} +#endif + +Collection LocalFolders::folder( Type type ) const +{ + Q_ASSERT( type < Custom ); + Q_ASSERT( d->ready ); + if( d->folders.contains( type ) ) { + return d->folders.value( type ); + } else { + kWarning() << "Non-existent folder of type" << type; + return Collection(); + } +} + +Collection::List LocalFolders::folders( Type type ) const +{ + Q_ASSERT( d->ready ); + if( d->folders.contains( type ) ) { + return d->folders.values( type ); + } else { + kWarning() << "No folders of type" << type; + return Collection::List(); + } } LocalFoldersPrivate::LocalFoldersPrivate() : instance( new LocalFolders(this) ) { isMainInstance = QDBusConnection::sessionBus().registerService( DBUS_SERVICE_NAME ); ready = false; // prepare() expects these preparing = false; - outboxJob = 0; - sentMailJob = 0; monitor = 0; prepare(); } LocalFoldersPrivate::~LocalFoldersPrivate() { delete instance; } void LocalFoldersPrivate::prepare() { if( ready ) { //kDebug() << "Already ready. Emitting foldersReady()."; emit instance->foldersReady(); return; } if( preparing ) { kDebug() << "Already preparing."; return; } kDebug() << "Preparing. isMainInstance" << isMainInstance; preparing = true; scheduled = false; - Q_ASSERT( outboxJob == 0 ); - Q_ASSERT( sentMailJob == 0 ); - Q_ASSERT( monitor == 0); - rootMaildir = Collection( -1 ); - outbox = Collection( -1 ); - sentMail = Collection( -1 ); - createResourceIfNeeded(); } void LocalFoldersPrivate::schedulePrepare() { if( scheduled ) { kDebug() << "Prepare already scheduled."; return; } kDebug() << "Scheduling prepare."; if( monitor ) { monitor->disconnect( instance ); monitor->deleteLater(); monitor = 0; } ready = false; preparing = false; scheduled = true; QTimer::singleShot( 1000, instance, SLOT( prepare() ) ); } void LocalFoldersPrivate::createResourceIfNeeded() { Q_ASSERT( preparing ); // Another instance might have created the resource and updated the config. Settings::self()->readConfig(); kDebug() << "Resource from config:" << Settings::resourceId(); // check that the maildir resource exists AgentInstance resource = AgentManager::self()->instance( Settings::resourceId() ); if( !resource.isValid() ) { // Try to grab main instance status (if previous main instance quit). if( !isMainInstance ) { isMainInstance = QDBusConnection::sessionBus().registerService( DBUS_SERVICE_NAME ); if( isMainInstance ) { kDebug() << "I have become the main instance."; } } // Create resource if main instance. if( !isMainInstance ) { kDebug() << "Waiting for the main instance to create the resource."; schedulePrepare(); } else { kDebug() << "Creating maildir resource."; AgentType type = AgentManager::self()->type( QString::fromLatin1( "akonadi_maildir_resource" ) ); AgentInstanceCreateJob *job = new AgentInstanceCreateJob( type ); QObject::connect( job, SIGNAL( result( KJob * ) ), instance, SLOT( resourceCreateResult( KJob * ) ) ); // this is not an Akonadi::Job, so we must start it ourselves job->start(); } } else { connectMonitor(); } } void LocalFoldersPrivate::createCollectionsIfNeeded() { Q_ASSERT( preparing ); // but I may not be the main instance Q_ASSERT( rootMaildir.isValid() ); - if( !outbox.isValid() ) { - kDebug() << "Creating outbox collection."; - Collection col; - col.setParent( rootMaildir ); - col.setName( QLatin1String( "outbox" ) ); - col.setContentMimeTypes( QStringList( QLatin1String( "message/rfc822" ) ) ); - Q_ASSERT( outboxJob == 0 ); - outboxJob = new CollectionCreateJob( col ); - QObject::connect( outboxJob, SIGNAL( result( KJob * ) ), - instance, SLOT( collectionCreateResult( KJob * ) ) ); - } - - if( !sentMail.isValid() ) { - kDebug() << "Creating sent-mail collection."; - Collection col; - col.setParent( rootMaildir ); - col.setName( QLatin1String( "sent-mail" ) ); - col.setContentMimeTypes( QStringList( QLatin1String( "message/rfc822" ) ) ); - Q_ASSERT( sentMailJob == 0 ); - sentMailJob = new CollectionCreateJob( col ); - QObject::connect( sentMailJob, SIGNAL( result( KJob * ) ), - instance, SLOT( collectionCreateResult( KJob * ) ) ); + for( int type = 0; type < LocalFolders::LastDefaultType; type++ ) { + if( !folders.contains( type ) ) { + kDebug() << "Creating" << nameForType( type ) << "collection."; + Collection col; + col.setParent( rootMaildir ); + col.setName( nameForType( type ) ); + col.setContentMimeTypes( QStringList( QLatin1String( "message/rfc822" ) ) ); + CollectionCreateJob *cjob = new CollectionCreateJob( col ); + QObject::connect( cjob, SIGNAL(result(KJob*)), + instance, SLOT(collectionCreateResult(KJob*)) ); + pendingJobs.insert( cjob ); + } } - if( outboxJob == 0 && sentMailJob == 0 ) { + if( pendingJobs.isEmpty() ) { // Everything is ready (created and fetched). - kDebug() << "Local folders ready. resourceId" << Settings::resourceId() - << "outbox id" << outbox.id() << "sentMail id" << sentMail.id(); + kDebug() << "Local folders ready. resourceId" << Settings::resourceId(); + for( int type = 0; type < LocalFolders::LastDefaultType; type++ ) { + kDebug() << nameForType( type ) << "collection has id" << folders.value(type).id(); + } Q_ASSERT( !ready ); ready = true; preparing = false; Settings::self()->writeConfig(); emit instance->foldersReady(); } } void LocalFoldersPrivate::connectMonitor() { Q_ASSERT( preparing ); // but I may not be the main instance Q_ASSERT( monitor == 0 ); monitor = new Monitor( instance ); monitor->setResourceMonitored( Settings::resourceId().toAscii() ); QObject::connect( monitor, SIGNAL( collectionRemoved( Akonadi::Collection ) ), instance, SLOT( schedulePrepare() ) ); kDebug() << "Connected monitor."; fetchCollections(); } void LocalFoldersPrivate::fetchCollections() { Q_ASSERT( preparing ); // but I may not be the main instance kDebug() << "Fetching collections in maildir resource."; CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive ); job->setResource( Settings::resourceId() ); // limit search QObject::connect( job, SIGNAL( result( KJob * ) ), instance, SLOT( collectionFetchResult( KJob * ) ) ); } void LocalFoldersPrivate::resourceCreateResult( KJob *job ) { Q_ASSERT( isMainInstance ); Q_ASSERT( preparing ); if( job->error() ) { kFatal() << "AgentInstanceCreateJob failed to make a maildir resource for us."; } AgentInstanceCreateJob *createJob = static_cast( job ); AgentInstance agent = createJob->instance(); Settings::setResourceId( agent.identifier() ); kDebug() << "Created maildir resource with id" << Settings::resourceId(); // configure the resource agent.setName( i18n( "Local Mail Folders" ) ); QDBusInterface conf( QString::fromLatin1( "org.freedesktop.Akonadi.Resource." ) + Settings::resourceId(), QString::fromLatin1( "/Settings" ), QString::fromLatin1( "org.kde.Akonadi.Maildir.Settings" ) ); QDBusReply reply = conf.call( QString::fromLatin1( "setPath" ), KGlobal::dirs()->localxdgdatadir() + QString::fromLatin1( "mail" ) ); if( !reply.isValid() ) { kFatal() << "Failed to set the root maildir."; } agent.reconfigure(); // sync the resource ResourceSynchronizationJob *sjob = new ResourceSynchronizationJob( agent ); QObject::connect( sjob, SIGNAL( result( KJob* ) ), instance, SLOT( resourceSyncResult( KJob* ) ) ); sjob->start(); // non-Akonadi } void LocalFoldersPrivate::resourceSyncResult( KJob *job ) { Q_ASSERT( isMainInstance ); Q_ASSERT( preparing ); if( job->error() ) { kFatal() << "ResourceSynchronizationJob failed."; } connectMonitor(); } void LocalFoldersPrivate::collectionCreateResult( KJob *job ) { Q_ASSERT( isMainInstance ); if( job->error() ) { kFatal() << "CollectionCreateJob failed to make a collection for us."; } - CollectionCreateJob *createJob = static_cast( job ); - if( job == outboxJob ) { - outboxJob = 0; - outbox = createJob->collection(); - kDebug() << "Created outbox collection with id" << outbox.id(); - } else if( job == sentMailJob ) { - sentMailJob = 0; - sentMail = createJob->collection(); - kDebug() << "Created sent-mail collection with id" << sentMail.id(); - } else { - kFatal() << "Got a result for a job I don't know about."; - } - - if( outboxJob == 0 && sentMailJob == 0 ) { + Q_ASSERT( pendingJobs.contains( job ) ); + pendingJobs.remove( job ); + if( pendingJobs.isEmpty() ) { // Done creating. Refetch everything. fetchCollections(); } } void LocalFoldersPrivate::collectionFetchResult( KJob *job ) { Q_ASSERT( preparing ); // but I may not be the main instance CollectionFetchJob *fetchJob = static_cast( job ); Collection::List cols = fetchJob->collections(); kDebug() << "CollectionFetchJob fetched" << cols.count() << "collections."; - outbox = Collection( -1 ); - sentMail = Collection( -1 ); + folders.clear(); Q_FOREACH( const Collection &col, cols ) { if( col.parent() == Collection::root().id() ) { rootMaildir = col; kDebug() << "Fetched root maildir collection."; - } else if( col.name() == QLatin1String( "outbox" ) ) { - Q_ASSERT( outbox.id() == -1 ); - outbox = col; - kDebug() << "Fetched outbox collection."; - } else if( col.name() == QLatin1String( "sent-mail" ) ) { - Q_ASSERT( sentMail.id() == -1 ); - sentMail = col; - kDebug() << "Fetched sent-mail collection."; } else { - kWarning() << "Extraneous collection" << col.name() << "with id" - << col.id() << "found."; + // Try to guess folder type. + LocalFolders::Type type = typeForName( col.name() ); + kDebug() << "Fetched" << nameForType( type ) << "collection."; + if( type != LocalFolders::Custom && folders.contains( type ) ) { + kDebug() << "But I have this type already, so making it Custom."; + type = LocalFolders::Custom; + } + folders.insert( type, col ); } } if( !rootMaildir.isValid() ) { kFatal() << "Failed to fetch root maildir collection."; } createCollectionsIfNeeded(); } +// static +QString LocalFoldersPrivate::nameForType( int type ) +{ + Q_ASSERT( type >= 0 && type < LocalFolders::LastDefaultType ); + switch( type ) { + case LocalFolders::Inbox: return QLatin1String( "inbox" ); + case LocalFolders::Outbox: return QLatin1String( "outbox" ); + case LocalFolders::SentMail: return QLatin1String( "sent-mail" ); + case LocalFolders::Trash: return QLatin1String( "trash" ); + case LocalFolders::Drafts: return QLatin1String( "drafts" ); + case LocalFolders::Templates: return QLatin1String( "templates" ); + default: Q_ASSERT( false ); return QString(); + } +} + +//static +LocalFolders::Type LocalFoldersPrivate::typeForName( const QString &name ) +{ + if( name == QLatin1String( "inbox" ) ) { + return LocalFolders::Inbox; + } else if( name == QLatin1String( "outbox" ) ) { + return LocalFolders::Outbox; + } else if( name == QLatin1String( "sent-mail" ) ) { + return LocalFolders::SentMail; + } else if( name == QLatin1String( "trash" ) ) { + return LocalFolders::Trash; + } else if( name == QLatin1String( "drafts" ) ) { + return LocalFolders::Drafts; + } else if( name == QLatin1String( "templates" ) ) { + return LocalFolders::Templates; + } else { + return LocalFolders::Custom; + } +} + + #include "localfolders.moc" diff --git a/akonadi/kmime/localfolders.h b/akonadi/kmime/localfolders.h index cad2f0923..bb2c333cf 100644 --- a/akonadi/kmime/localfolders.h +++ b/akonadi/kmime/localfolders.h @@ -1,118 +1,180 @@ /* Copyright (c) 2009 Constantin Berzan This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef AKONADI_LOCALFOLDERS_H #define AKONADI_LOCALFOLDERS_H +#include "akonadi-kmime_export.h" + #include +#include -#include "akonadi-kmime_export.h" +#include class KJob; namespace Akonadi { class Collection; } namespace Akonadi { class LocalFoldersPrivate; /** Creates and monitors the Outbox and Sent-Mail collections for the mail dispatcher agent. The first time it is used, or when you call fetch(), this class checks for the following: * a maildir resource with name 'Local Mail Folders' * 'outbox' and 'sent-mail' collections under that resource If they do not exist, this class creates them, and then emits foldersReady(). Do not store the collections returned by outbox() and sentMail(). They can become invalid if e.g. the user deletes them and LocalFolders creates them again, so you should always use LocalFolders::outbox() instead of storing its return value. */ class AKONADI_KMIME_EXPORT LocalFolders : public QObject { Q_OBJECT public: /** - Returns the LocalFolders instance. - Does a fetch() when first called. + Each folder has one of the types below. There is only one folder of + each type, except for Custom, which multiple folders can use. */ - static LocalFolders *self(); + enum Type + { + Inbox, + Outbox, + SentMail, + Trash, + Drafts, + Templates, + LastDefaultType, //< internal + Custom = 31 //< for custom folders created by the user + //,User = 32 //< for user-defined types + }; /** - Begins creating / fetching the resource and collections. - Emits foldersReady() when done. + Returns the LocalFolders instance. + Does a fetch() when first called. */ - void fetch(); + static LocalFolders *self(); /** Returns whether the outbox and sent-mail collections have been fetched and are ready to be used via outbox() and sentMail(). */ bool isReady() const; - public Q_SLOTS: + /** + Returns the inbox collection. + */ + Akonadi::Collection inbox() const; + /** Returns the outbox collection. */ Akonadi::Collection outbox() const; /** Returns the sent-mail collection. */ Akonadi::Collection sentMail() const; + /** + Returns the trash collection. + */ + Akonadi::Collection trash() const; + + /** + Returns the drafts collection. + */ + Akonadi::Collection drafts() const; + + /** + Returns the templates collection. + */ + Akonadi::Collection templates() const; + +#if 0 + /** + Get a folder by its name. + Returns an invalid collection if no such folder exists. + */ + Akonadi::Collection folder( const QString &name ) const; +#endif + + /** + Get a folder by its type. + Returns an invalid collection if no such folder exists. + For Custom folders, call folders() instead. + */ + Akonadi::Collection folder( Type type ) const; + + /** + Returns all folders of type @p type. + If this is a default type (such as inbox, outbox etc.), then there is + at most one folder of that type, so you may call folder() instead. + */ + Akonadi::Collection::List folders( Type type ) const; + + public Q_SLOTS: + /** + Begins creating / fetching the resource and collections. + Emits foldersReady() when done. + */ + void fetch(); + Q_SIGNALS: /** Emitted when the outbox and sent-mail collections have been fetched and are ready to be used via outbox() and sentMail(). */ void foldersReady(); private: friend class LocalFoldersPrivate; // singleton class; the only instance resides in sInstance->instance LocalFolders( LocalFoldersPrivate *dd ); LocalFoldersPrivate *const d; Q_PRIVATE_SLOT( d, void prepare() ) Q_PRIVATE_SLOT( d, void schedulePrepare() ) Q_PRIVATE_SLOT( d, void resourceCreateResult( KJob * ) ) Q_PRIVATE_SLOT( d, void resourceSyncResult( KJob * ) ) Q_PRIVATE_SLOT( d, void collectionCreateResult( KJob * ) ) Q_PRIVATE_SLOT( d, void collectionFetchResult( KJob * ) ) }; } #endif diff --git a/akonadi/kmime/localfolders.kcfg b/akonadi/kmime/localfolders.kcfg index 3b6ecc510..362ec4a95 100644 --- a/akonadi/kmime/localfolders.kcfg +++ b/akonadi/kmime/localfolders.kcfg @@ -1,22 +1,14 @@ - Id of the maildir resource containing the outbox and sent-mail collections. + Id of the maildir resource containing the local folder collections. "" - - - -1 - - - - -1 -