diff --git a/akonadi/kmime/CMakeLists.txt b/akonadi/kmime/CMakeLists.txt index db33766bb..db14a987e 100644 --- a/akonadi/kmime/CMakeLists.txt +++ b/akonadi/kmime/CMakeLists.txt @@ -1,41 +1,42 @@ include_directories( ${CMAKE_SOURCE_DIR}/ ${QT_QTDBUS_INCLUDE_DIR} ${Boost_INCLUDE_DIR} ) add_subdirectory( tests ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII ${KDE4_ENABLE_EXCEPTIONS}" ) ########### next target ############### set( kmimeakonadi_LIB_SRC localfolders.cpp + localfoldersbuildjob.cpp messagemodel.cpp messageparts.cpp messagethreadingattribute.cpp messagethreaderproxymodel.cpp resourcesynchronizationjob.cpp # copied from playground/pim/akonaditest/resourcetester ) kde4_add_kcfg_files( kmimeakonadi_LIB_SRC localfolderssettings.kcfgc ) install( FILES localfolders.kcfg DESTINATION ${KCFG_INSTALL_DIR} ) kde4_add_library( akonadi-kmime SHARED ${kmimeakonadi_LIB_SRC} ) target_link_libraries( akonadi-kmime akonadi-kde kmime ${QT_QTGUI_LIBRARY} ${KDE4_KDECORE_LIBS} ${KDE4_KIO_LIBS} ) set_target_properties( akonadi-kmime PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) install(TARGETS akonadi-kmime EXPORT kdepimlibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install( FILES localfolders.h akonadi-kmime_export.h messagemodel.h messageparts.h messagethreadingattribute.h messagethreaderproxymodel.h DESTINATION ${INCLUDE_INSTALL_DIR}/akonadi/kmime COMPONENT Devel ) diff --git a/akonadi/kmime/localfolders.cpp b/akonadi/kmime/localfolders.cpp index 7b934c2b7..68fcc85ba 100644 --- a/akonadi/kmime/localfolders.cpp +++ b/akonadi/kmime/localfolders.cpp @@ -1,573 +1,279 @@ /* 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 "localfoldersbuildjob_p.h" #include "localfolderssettings.h" -#include #include -#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 - */ + @internal +*/ class Akonadi::LocalFoldersPrivate { public: LocalFoldersPrivate(); ~LocalFoldersPrivate(); LocalFolders *instance; bool ready; bool preparing; bool scheduled; bool isMainInstance; Collection rootMaildir; - QMultiHash folders; + Collection::List defaultFolders; 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. + If this is the main instance, attempts to build and fetch the local + folder structure. Otherwise, it waits for the structure to be created + by the main instance, and then just fetches it. - Will emit foldersReady() when done + Will emit foldersReady() when the folders are ready. */ - void prepare(); + void prepare(); // slot + + // slots: + void buildResult( KJob *job ); + void collectionRemoved( const Collection &col ); /** 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 - - /** - Emits foldersReady(). - */ - void emitReady(); + void schedulePrepare(); - /** - 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 ); - void collectionModifyResult( KJob *job ); - - /** - Returns the English name for a default (i.e. non-custom) folder type. - */ - static QString nameForType( int type ); - - /** - Returns the standard icon name for a default (non-custom) folder type. - */ - static QString iconNameForType( 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::root() const +{ + return folder( Root ); +} + Collection LocalFolders::inbox() const { return folder( Inbox ); } Collection LocalFolders::outbox() const { 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 ); - Q_FOREACH( const Collection &col, d->folders.values() ) { - if( col.name() == name ) { - return col; - } - } -} -#endif - -Collection LocalFolders::folder( Type type ) const +Collection LocalFolders::folder( int 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(); - } + Q_ASSERT( type >= 0 && type < LastDefaultType ); + return d->defaultFolders[ type ]; } LocalFoldersPrivate::LocalFoldersPrivate() : instance( new LocalFolders(this) ) { - isMainInstance = QDBusConnection::sessionBus().registerService( DBUS_SERVICE_NAME ); - + isMainInstance = false; ready = false; - // prepare() expects these preparing = false; monitor = 0; prepare(); } LocalFoldersPrivate::~LocalFoldersPrivate() { delete instance; } void LocalFoldersPrivate::prepare() { if( ready ) { - //kDebug() << "Already ready. Emitting foldersReady()."; + kDebug() << "Already ready. Emitting foldersReady()."; emit instance->foldersReady(); return; } if( preparing ) { kDebug() << "Already preparing."; return; } - kDebug() << "Preparing. isMainInstance" << isMainInstance; - preparing = true; - scheduled = false; - rootMaildir = Collection( -1 ); - createResourceIfNeeded(); -} - -void LocalFoldersPrivate::schedulePrepare() -{ - if( scheduled ) { - kDebug() << "Prepare already scheduled."; - return; + // Try to grab main instance status (if previous main instance quit). + if( !isMainInstance ) { + isMainInstance = QDBusConnection::sessionBus().registerService( DBUS_SERVICE_NAME ); } - kDebug() << "Scheduling prepare."; - - if( monitor ) { - monitor->disconnect( instance ); - monitor->deleteLater(); - monitor = 0; + // Fetch / build the folder structure. + { + kDebug() << "Preparing. isMainInstance" << isMainInstance; + preparing = true; + scheduled = false; + LocalFoldersBuildJob *bjob = new LocalFoldersBuildJob( isMainInstance, instance ); + QObject::connect( bjob, SIGNAL(result(KJob*)), instance, SLOT(buildResult(KJob*)) ); + // auto-starts } - - ready = false; - preparing = false; - scheduled = true; - QTimer::singleShot( 1000, instance, SLOT( prepare() ) ); } -void LocalFoldersPrivate::emitReady() +void LocalFoldersPrivate::buildResult( KJob *job ) { - 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(); + if( job->error() ) { + kDebug() << "BuildJob failed with error" << job->errorString(); + schedulePrepare(); + return; } - Q_ASSERT( !ready ); - ready = true; - preparing = false; - Settings::self()->writeConfig(); - emit instance->foldersReady(); -} - -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(); + // Get the folders from the job. + { + Q_ASSERT( dynamic_cast( job ) ); + LocalFoldersBuildJob *bjob = static_cast( job ); + Q_ASSERT( defaultFolders.isEmpty() ); + defaultFolders = bjob->defaultFolders(); } -} -void LocalFoldersPrivate::createCollectionsIfNeeded() -{ - Q_ASSERT( preparing ); // but I may not be the main instance - Q_ASSERT( rootMaildir.isValid() ); - - 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" ) ) ); - EntityDisplayAttribute *attr = new EntityDisplayAttribute; - attr->setIconName( iconNameForType( type ) ); - attr->setDisplayName( i18nc( "local mail folder", nameForType( type ).toLatin1() ) ); - col.addAttribute( attr ); - CollectionCreateJob *cjob = new CollectionCreateJob( col ); - QObject::connect( cjob, SIGNAL(result(KJob*)), - instance, SLOT(collectionCreateResult(KJob*)) ); - pendingJobs.insert( cjob ); + // Verify everything. + { + Q_ASSERT( defaultFolders.count() == LocalFolders::LastDefaultType ); + Collection::Id rootId = defaultFolders[ LocalFolders::Root ].id(); + Q_ASSERT( rootId >= 0 ); + for( int type = 1; type < LocalFolders::LastDefaultType; type++ ) { + Q_ASSERT( defaultFolders[ type ].isValid() ); + Q_ASSERT( defaultFolders[ type ].parent() == rootId ); } } - if( pendingJobs.isEmpty() ) { - // Everything is ready (created and fetched). - emitReady(); - } -} - -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."; + // Connect monitor. + { + Q_ASSERT( monitor == 0 ); + monitor = new Monitor( instance ); + monitor->setResourceMonitored( Settings::resourceId().toAscii() ); + QObject::connect( monitor, SIGNAL(collectionRemoved(Akonadi::Collection)), + instance, SLOT(collectionRemoved(Akonadi::Collection)) ); } - 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."; + // Emit ready. + { + kDebug() << "Local folders ready."; + Q_ASSERT( !ready ); + ready = true; + preparing = false; + emit instance->foldersReady(); } - 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 ) +void LocalFoldersPrivate::collectionRemoved( const Akonadi::Collection &col ) { - Q_ASSERT( isMainInstance ); - Q_ASSERT( preparing ); - if( job->error() ) { - kFatal() << "ResourceSynchronizationJob failed."; + kDebug() << "id" << col.id(); + if( defaultFolders.contains( col ) ) { + // These are undeletable folders. If one of them got removed, it means + // the entire resource has been removed. + schedulePrepare(); } - - connectMonitor(); } -void LocalFoldersPrivate::collectionCreateResult( KJob *job ) +void LocalFoldersPrivate::schedulePrepare() { - Q_ASSERT( isMainInstance ); - if( job->error() ) { - kFatal() << "CollectionCreateJob failed to make a collection for us."; - } - - Q_ASSERT( pendingJobs.contains( job ) ); - pendingJobs.remove( job ); - if( pendingJobs.isEmpty() ) { - // Done creating. Refetch everything. - fetchCollections(); + if( scheduled ) { + kDebug() << "Prepare already scheduled."; + return; } -} -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."; - - folders.clear(); - Q_FOREACH( Collection col, cols ) { // TODO krazy: we want a copy here - if( col.parent() == Collection::root().id() ) { - rootMaildir = col; - kDebug() << "Fetched root maildir collection."; - } else { - // 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; - } - - // Create EntityDisplayAttribute if needed and not present. - if( type != LocalFolders::Custom && !col.hasAttribute() ) { - if( !isMainInstance ) { - kWarning() << "Main instance forgot to set EntityDisplayAttribute."; - } else { - kDebug() << "Does not have EntityDisplayAttribute; creating."; - EntityDisplayAttribute *attr = new EntityDisplayAttribute; - attr->setIconName( iconNameForType( type ) ); - attr->setDisplayName( i18nc( "local mail folder", nameForType( type ).toLatin1() ) ); - col.addAttribute( attr ); - CollectionModifyJob *mjob = new CollectionModifyJob( col ); - QObject::connect( mjob, SIGNAL(result(KJob*)), - instance, SLOT(collectionModifyResult(KJob*)) ); - pendingJobs << mjob; - } - } - - folders.insert( type, col ); + // Clean up. + { + if( monitor ) { + monitor->disconnect( instance ); + monitor->deleteLater(); + monitor = 0; } + defaultFolders.clear(); } - if( !rootMaildir.isValid() ) { - kFatal() << "Failed to fetch root maildir collection."; - } - - createCollectionsIfNeeded(); -} - -void LocalFoldersPrivate::collectionModifyResult( KJob *job ) -{ - kDebug() << "CollectionModifyJob to set EntityDisplayAttribute done."; - - Q_ASSERT( isMainInstance ); - if( job->error() ) { - kWarning() << "CollectionModifyJob failed to set an EntityDisplayAttribute."; - } - - Q_ASSERT( pendingJobs.contains( job ) ); - pendingJobs.remove( job ); - if( pendingJobs.isEmpty() ) { - // Done creating / modifying. - emitReady(); + // Schedule prepare in 1s. + { + kDebug() << "Scheduling prepare."; + ready = false; + preparing = false; + scheduled = true; + QTimer::singleShot( 1000, instance, SLOT( prepare() ) ); } } - -// 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 -QString LocalFoldersPrivate::iconNameForType( int type ) -{ - // Icons imitating KMail. - Q_ASSERT( type >= 0 && type < LocalFolders::LastDefaultType ); - switch( type ) { - case LocalFolders::Inbox: return QLatin1String( "mail-folder-inbox" ); - case LocalFolders::Outbox: return QLatin1String( "mail-folder-outbox" ); - case LocalFolders::SentMail: return QLatin1String( "mail-folder-sent" ); - case LocalFolders::Trash: return QLatin1String( "user-trash" ); - case LocalFolders::Drafts: return QLatin1String( "document-properties" ); - case LocalFolders::Templates: return QLatin1String( "document-new" ); - 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 8f55da804..929512b1a 100644 --- a/akonadi/kmime/localfolders.h +++ b/akonadi/kmime/localfolders.h @@ -1,181 +1,184 @@ /* 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 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. - + @short Interface to local folders such as inbox, outbox etc. + + This class provides access to the following default (and undeletable) local + folders: inbox, outbox, sent-mail, trash, drafts, and templates; as well as + the root local folder containing them. The user may create custom + subfolders in the root local folder, or in any of the default local folders. + + By default, these folders are stored in a maildir in + $HOME/.local/share/mail. + + This class also monitors the local folders and the maildir resource hosting + them, and re-creates them when necessary (for example, if the user + accidentally removed the maildir resource). For this reason, you must make + sure the local folders are ready before calling any of the accessor + functions. It is also recommended that you always use this class to access + the local folders, instead of, for example, storing the result of + LocalFolders::self()->outbox() locally. + + @code + connect( LocalFolders::self(), SIGNAL(foldersReady()), + this, SLOT(riseAndShine()) ); + LocalFolders::self()->fetch(); + @endcode + + @author Constantin Berzan + @since 4.4 */ class AKONADI_KMIME_EXPORT LocalFolders : public QObject { Q_OBJECT public: /** - Each folder has one of the types below. There is only one folder of - each type, except for Custom, which multiple folders can use. + Each local folder has one of the types below. There cannot be more + than one folder of each type, except for Custom. */ 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 + Root, ///< the root folder containing the local folders + Inbox, ///< inbox + Outbox, ///< outbox + SentMail, ///< sent-mail + Trash, ///< trash + Drafts, ///< drafts + Templates, ///< templates + LastDefaultType, ///< @internal marker + Custom = 15 ///< for custom folders created by the user }; /** Returns the LocalFolders instance. Does a fetch() when first called. + + @see 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(). + Returns whether the local folder collections have been fetched and are + ready to be accessed. + + @see fetch + @see foldersReady */ bool isReady() const; + /** + Returns the root collection containing all local folders. + */ + Akonadi::Collection root() const; + /** 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; + This function only works for default (non-Custom) folder types. - /** - 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. + @see Type */ - Akonadi::Collection::List folders( Type type ) const; + Akonadi::Collection folder( int type ) const; public Q_SLOTS: /** - Begins creating / fetching the resource and collections. - Emits foldersReady() when done. + Begins fetching the resource and collections, or creating them if + necessary. Emits foldersReady() when done. + + @code + connect( LocalFolders::self(), SIGNAL(foldersReady()), + this, SLOT(riseAndShine()) ); + LocalFolders::self()->fetch(); + @endcode */ 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(). + Emitted when the local folder collections have been fetched and + are ready to be accessed. + + @see isReady */ 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 * ) ) - Q_PRIVATE_SLOT( d, void collectionModifyResult( KJob * ) ) + Q_PRIVATE_SLOT( d, void buildResult( KJob* ) ) + Q_PRIVATE_SLOT( d, void collectionRemoved( Akonadi::Collection ) ) }; +} // namespace Akonadi -} - - -#endif +#endif // AKONADI_LOCALFOLDERS_H diff --git a/akonadi/kmime/localfolders.kcfg b/akonadi/kmime/localfolders.kcfg index 362ec4a95..39ca5b4d3 100644 --- a/akonadi/kmime/localfolders.kcfg +++ b/akonadi/kmime/localfolders.kcfg @@ -1,14 +1,14 @@ Id of the maildir resource containing the local folder collections. - "" + diff --git a/akonadi/kmime/localfoldersbuildjob.cpp b/akonadi/kmime/localfoldersbuildjob.cpp new file mode 100644 index 000000000..78017cec6 --- /dev/null +++ b/akonadi/kmime/localfoldersbuildjob.cpp @@ -0,0 +1,405 @@ +/* + 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 "localfoldersbuildjob_p.h" + +#include "localfolders.h" +#include "localfolderssettings.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include // copied from playground/pim/akonaditest + +using namespace Akonadi; + +typedef LocalFoldersSettings Settings; + +/** + @internal +*/ +class Akonadi::LocalFoldersBuildJob::Private +{ + public: + Private( LocalFoldersBuildJob *qq ) + : q( qq ) + { + for( int type = 0; type < LocalFolders::LastDefaultType; type++ ) { + defaultFolders.append( Collection() ); + } + } + + void resourceCreateResult( KJob *job ); // slot + void resourceSyncResult( KJob *job ); // slot + void fetchCollections(); + void collectionFetchResult( KJob *job ); // slot + void createAndConfigureCollections(); + void collectionCreateResult( KJob *job ); // slot + void collectionModifyResult( KJob *job ); // slot + + // May be called for any default type, including Root. + static QString nameForType( int type ); + + // May be called for any default type, including Root. + static QString iconNameForType( int type ); + + // May be called for any default or custom type, other than Root. + static LocalFolders::Type typeForName( const QString &name ); + + LocalFoldersBuildJob *q; + bool canBuild; + Collection::List defaultFolders; + //QSet customFolders; + QSet pendingJobs; + +}; + + + +LocalFoldersBuildJob::LocalFoldersBuildJob( bool canBuild, QObject *parent ) + : TransactionSequence( parent ) + , d( new Private( this ) ) +{ + d->canBuild = canBuild; +} + +LocalFoldersBuildJob::~LocalFoldersBuildJob() +{ + delete d; +} + +const Collection::List &LocalFoldersBuildJob::defaultFolders() const +{ + return d->defaultFolders; +} + +#if 0 +const QSet &LocalFoldersBuildJob::customFolders() const +{ + return d->customFolders; +} +#endif + +void LocalFoldersBuildJob::doStart() +{ + // Update config and check if resource exists. + Settings::self()->readConfig(); + kDebug() << "resourceId from settings" << Settings::resourceId(); + AgentInstance resource = AgentManager::self()->instance( Settings::resourceId() ); + if( resource.isValid() ) { + // Good, the resource exists. + d->fetchCollections(); + } else { + // Create resource if allowed. + if( !d->canBuild ) { + setError( UserDefinedError ); + setErrorText( QLatin1String( "Resource not found, and creating not allowed." ) ); + emitResult(); + } else { + kDebug() << "Creating maildir resource."; + AgentType type = AgentManager::self()->type( QString::fromLatin1( "akonadi_maildir_resource" ) ); + AgentInstanceCreateJob *job = new AgentInstanceCreateJob( type, this ); + connect( job, SIGNAL(result(KJob*)), this, SLOT(resourceCreateResult(KJob*)) ); + job->start(); // non-Akonadi::Job + } + } +} + + + +void LocalFoldersBuildJob::Private::resourceCreateResult( KJob *job ) +{ + Q_ASSERT( canBuild ); + if( job->error() ) { + q->setError( UserDefinedError ); + q->setErrorText( QLatin1String( "Resource creation failed." ) ); + q->emitResult(); + } else { + AgentInstance agent; + + // Get the resource instance, and save its ID to config. + { + Q_ASSERT( dynamic_cast( job ) ); + AgentInstanceCreateJob *cjob = static_cast( job ); + agent = cjob->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() ) { + q->setError( UserDefinedError ); + q->setErrorText( QLatin1String( "Failed to set the root maildir via D-Bus." ) ); + q->emitResult(); + } else { + agent.reconfigure(); + } + } + + // Sync the resource. + if( !q->error() ) + { + ResourceSynchronizationJob *sjob = new ResourceSynchronizationJob( agent, q ); + QObject::connect( sjob, SIGNAL(result(KJob*)), q, SLOT(resourceSyncResult(KJob*)) ); + sjob->start(); // non-Akonadi + } + } +} + +void LocalFoldersBuildJob::Private::resourceSyncResult( KJob *job ) +{ + Q_ASSERT( canBuild ); + if( job->error() ) { + q->setError( UserDefinedError ); + q->setErrorText( QLatin1String( "ResourceSynchronizationJob failed." ) ); + q->emitResult(); + } else { + fetchCollections(); + } +} + +void LocalFoldersBuildJob::Private::fetchCollections() +{ + kDebug() << "Fetching collections in the maildir resource."; + + CollectionFetchJob *job = new CollectionFetchJob( Collection::root(), CollectionFetchJob::Recursive, q ); + job->setResource( Settings::resourceId() ); // limit search + QObject::connect( job, SIGNAL(result(KJob*)), q, SLOT(collectionFetchResult(KJob*)) ); +} + +void LocalFoldersBuildJob::Private::collectionFetchResult( KJob *job ) +{ + QSet cols; + + // Get the root maildir collection, and put all the other ones in cols. + { + Q_ASSERT( dynamic_cast( job ) ); + CollectionFetchJob *fjob = static_cast( job ); + Q_FOREACH( const Collection &col, fjob->collections() ) { + if( col.parent() == Collection::root().id() ) { + // This is the root maildir collection. + Q_ASSERT( !defaultFolders[ LocalFolders::Root ].isValid() ); + defaultFolders[ LocalFolders::Root ] = col; + } else { + // This is some local folder. + cols.insert( col ); + } + } + } + + // Go no further if the root maildir was not found. + if( !defaultFolders[ LocalFolders::Root ].isValid() ) { + q->setError( UserDefinedError ); + q->setErrorText( QLatin1String( "Failed to fetch root maildir collection." ) ); + q->emitResult(); + return; + } + + // Get the default folders. They must be direct children of the root maildir, + // and have appropriate names. Leave all the other folders in cols. + Q_FOREACH( const Collection &col, cols ) { + if( col.parent() == defaultFolders[ LocalFolders::Root ].id() ) { + // This is a direct child; guess its type. + LocalFolders::Type type = typeForName( col.name() ); + if( type != LocalFolders::Custom ) { + // This is one of the default folders. + Q_ASSERT( !defaultFolders[ type ].isValid() ); + defaultFolders[ type ] = col; + cols.remove( col ); + } + } + } + + // Everything left in cols is custom folders. + //customFolders = cols; + + // Some collections may still need to be created (if the evil user deleted + // them from disk, for example), and some may need to be configured (rights, + // icon, i18n'ed name). + if( canBuild ) { + createAndConfigureCollections(); + } else { + for( int type = 0; type < LocalFolders::LastDefaultType; type++ ) { + if( !defaultFolders[ type ].isValid() ) { + q->setError( UserDefinedError ); + q->setErrorText( QString::fromLatin1( "Failed to fetch %1 collection." ).arg( nameForType( type ) ) ); + q->emitResult(); + break; + } + } + + // If all collections are valid, we are done. + if( !q->error() ) { + q->commit(); + } + } +} + +void LocalFoldersBuildJob::Private::createAndConfigureCollections() +{ + Q_ASSERT( canBuild ); + + // We need the root maildir at least. + Q_ASSERT( defaultFolders[ LocalFolders::Root ].isValid() ); + + for( int type = 0; type < LocalFolders::LastDefaultType; type++ ) { + if( !defaultFolders[ type ].isValid() ) { + // This default folder needs to be created. + kDebug() << "Creating default folder" << nameForType( type ); + Q_ASSERT( type != LocalFolders::Root ); + Collection col; + col.setParent( defaultFolders[ LocalFolders::Root ].id() ); + col.setName( nameForType( type ) ); + EntityDisplayAttribute *attr = new EntityDisplayAttribute; + attr->setIconName( iconNameForType( type ) ); + attr->setDisplayName( i18nc( "local mail folder", nameForType( type ).toLatin1() ) ); + col.addAttribute( attr ); + col.setRights( Collection::Rights( Collection::AllRights ^ Collection::CanDeleteCollection ) ); + CollectionCreateJob *cjob = new CollectionCreateJob( col, q ); + QObject::connect( cjob, SIGNAL(result(KJob*)), q, SLOT(collectionCreateResult(KJob*)) ); + } else if( defaultFolders[ type ].rights() & Collection::CanDeleteCollection ) { + // This default folder needs to be modified. + kDebug() << "Modifying default folder" << nameForType( type ); + Collection &col = defaultFolders[ type ]; + EntityDisplayAttribute *attr = new EntityDisplayAttribute; + attr->setIconName( iconNameForType( type ) ); + attr->setDisplayName( i18nc( "local mail folder", nameForType( type ).toLatin1() ) ); + col.addAttribute( attr ); + col.setRights( Collection::Rights( Collection::AllRights ^ Collection::CanDeleteCollection ) ); + CollectionModifyJob *mjob = new CollectionModifyJob( col, q ); + QObject::connect( mjob, SIGNAL(result(KJob*)), q, SLOT(collectionModifyResult(KJob*)) ); + } + + // NOTE: We do not set the mime type here because the maildir resource does that. + // It sets inode/directory (for child collections) and message/rfc822 (for messages), + // which is exactly what we want. + } + + // When those subjobs are finished, we are done. + Settings::self()->writeConfig(); + q->commit(); +} + +void LocalFoldersBuildJob::Private::collectionCreateResult( KJob *job ) +{ + if( job->error() ) { + kDebug() << "CollectionCreateJob failed."; + // TransactionSequence will take care of the error. + return; + } + + Q_ASSERT( dynamic_cast( job ) ); + CollectionCreateJob *cjob = static_cast( job ); + const Collection col = cjob->collection(); + int type = typeForName( col.name() ); + Q_ASSERT( type < LocalFolders::LastDefaultType ); + Q_ASSERT( !defaultFolders[ type ].isValid() ); + defaultFolders[ type ] = col; +} + +void LocalFoldersBuildJob::Private::collectionModifyResult( KJob *job ) +{ + if( job->error() ) { + kDebug() << "CollectionModifyJob failed."; + // TransactionSequence will take care of the error. + return; + } + + Q_ASSERT( dynamic_cast( job ) ); + // Our collection in defaultFolders[...] is already modified. + // (And CollectionModifyJob does not provide collection().) +} + + +// static +QString LocalFoldersBuildJob::Private::nameForType( int type ) +{ + switch( type ) { + case LocalFolders::Root: return QLatin1String( "Local Folders" ); + 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 +QString LocalFoldersBuildJob::Private::iconNameForType( int type ) +{ + // Icons imitating KMail. + switch( type ) { + case LocalFolders::Root: return QString(); + case LocalFolders::Inbox: return QLatin1String( "mail-folder-inbox" ); + case LocalFolders::Outbox: return QLatin1String( "mail-folder-outbox" ); + case LocalFolders::SentMail: return QLatin1String( "mail-folder-sent" ); + case LocalFolders::Trash: return QLatin1String( "user-trash" ); + case LocalFolders::Drafts: return QLatin1String( "document-properties" ); + case LocalFolders::Templates: return QLatin1String( "document-new" ); + default: Q_ASSERT( false ); return QString(); + } +} + +//static +LocalFolders::Type LocalFoldersBuildJob::Private::typeForName( const QString &name ) +{ + if( name == QLatin1String( "root" ) ) { + Q_ASSERT( false ); + return LocalFolders::Root; + } else 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 "localfoldersbuildjob_p.moc" diff --git a/akonadi/kmime/localfoldersbuildjob_p.h b/akonadi/kmime/localfoldersbuildjob_p.h new file mode 100644 index 000000000..05fb9bc25 --- /dev/null +++ b/akonadi/kmime/localfoldersbuildjob_p.h @@ -0,0 +1,83 @@ +/* + 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_LOCALFOLDERSBUILDJOB_P_H +#define AKONADI_LOCALFOLDERSBUILDJOB_P_H + +#include +#include + +namespace Akonadi { + +/** + A job building and fetching the local folder structure for LocalFolders. + + Whether this job is allowed to create stuff or not is determined by whether + the LocalFolders who created it is the main instance of LocalFolders or not. + (This is done to avoid race conditions.) + + Logic if main instance: + 1) if loading the resource from config fails, create the resource. + 2) if after fetching, some collections need to be created / modified, do that. + 3) if creating / modification succeeded -> success. + else -> failure. + + Logic if not main instance: + 1) if loading the resource from config fails -> failure. + 2) if after fetching, some collections need to be created / modified -> failure. + 2) if all collections were fetched ok -> success. + + @author Constantin Berzan + @since 4.4 +*/ +class LocalFoldersBuildJob : public TransactionSequence +{ + Q_OBJECT + + public: + /** + @param canBuild Whether this job is allowed to build the folder + structure (as opposed to only fetching it). + */ + LocalFoldersBuildJob( bool canBuild, QObject *parent = 0 ); + ~LocalFoldersBuildJob(); + + const Collection::List &defaultFolders() const; + //const QSet &customFolders() const; + + protected: + /* reimpl */ + virtual void doStart(); + + private: + class Private; + friend class Private; + Private *const d; + + Q_PRIVATE_SLOT( d, void resourceCreateResult( KJob* ) ) + Q_PRIVATE_SLOT( d, void resourceSyncResult( KJob* ) ) + Q_PRIVATE_SLOT( d, void collectionFetchResult( KJob* ) ) + Q_PRIVATE_SLOT( d, void collectionCreateResult( KJob* ) ) + Q_PRIVATE_SLOT( d, void collectionModifyResult( KJob* ) ) + +}; + +} // namespace Akonadi + +#endif // AKONADI_LOCALFOLDERSBUILDJOB_P_H diff --git a/akonadi/kmime/tests/CMakeLists.txt b/akonadi/kmime/tests/CMakeLists.txt index 4bba839bf..06c85a1a2 100644 --- a/akonadi/kmime/tests/CMakeLists.txt +++ b/akonadi/kmime/tests/CMakeLists.txt @@ -1,54 +1,55 @@ set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) # TODO apparently QTEST_AKONADIMAIN doesn't like NO_CASTs... remove_definitions( -DQT_NO_CAST_FROM_ASCII ) remove_definitions( -DQT_NO_CAST_TO_ASCII ) set( requester_srcs foldersrequester.cpp ) kde4_add_executable( requester TEST ${requester_srcs} ) target_link_libraries( requester akonadi-kmime ) # for racetest set( requester_exe "QLatin1String( \"${CMAKE_CURRENT_BINARY_DIR}/requester\" )" ) add_definitions( -DREQUESTER_EXE='${requester_exe}' ) macro(add_akonadi_isolated_test _source) get_filename_component(_targetName ${_source} NAME_WE) set(_srcList ${_source} ) kde4_add_executable(${_targetName} TEST ${_srcList}) target_link_libraries(${_targetName} ${QT_QTTEST_LIBRARY} ${QT_QTGUI_LIBRARY} ${KDE4_AKONADI_LIBS} ${KDE4_KDECORE_LIBS} ${KDE4_MAILTRANSPORT_LIBS} ${KDE4_KMIME_LIBS} ${QT_QTCORE_LIBRARY} ${QT_QTDBUS_LIBRARY} akonadi-kmime ) # based on kde4_add_unit_test if (WIN32) get_target_property( _loc ${_targetName} LOCATION ) set(_executable ${_loc}.bat) else (WIN32) set(_executable ${EXECUTABLE_OUTPUT_PATH}/${_targetName}) endif (WIN32) if (UNIX) set(_executable ${_executable}.shell) endif (UNIX) find_program(_testrunner akonaditest) add_test( akonadikmime-${_targetName} ${_testrunner} -c ${CMAKE_CURRENT_SOURCE_DIR}/unittestenv/config.xml ${_executable} ) endmacro(add_akonadi_isolated_test) +add_akonadi_isolated_test( customfoldertest.cpp ) add_akonadi_isolated_test( racetest.cpp ) diff --git a/akonadi/kmime/tests/TODO b/akonadi/kmime/tests/TODO index a571f6720..ff21324a2 100644 --- a/akonadi/kmime/tests/TODO +++ b/akonadi/kmime/tests/TODO @@ -1,6 +1,4 @@ LocalFolders: -* LocalFolders should be able to recover from any of the following situations: -- maildir resource and collection were removed -- maildir resource was removed, but collections are still there -- one or both collections have been removed +* test for deletion of actual dir from local file system +* test for user creating a custom folder with a default folder name (such as 'inbox') diff --git a/akonadi/kmime/tests/customfoldertest.cpp b/akonadi/kmime/tests/customfoldertest.cpp new file mode 100644 index 000000000..2debb5802 --- /dev/null +++ b/akonadi/kmime/tests/customfoldertest.cpp @@ -0,0 +1,246 @@ +/* + Copyright 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 "customfoldertest.h" + +#include +#include + +#include +#include +#include +#include +#include + +using namespace Akonadi; + + +void CustomFolderTest::initTestCase() +{ + QVERIFY( Control::start() ); + QTest::qWait( 1000 ); +} + +void CustomFolderTest::testDefaultFolders() +{ + LocalFolders::self()->fetch(); + QTest::kWaitForSignal( LocalFolders::self(), SIGNAL(foldersReady()) ); + for( int type = LocalFolders::Root; type < LocalFolders::LastDefaultType; type++ ) { + QVERIFY( LocalFolders::self()->folder( type ).isValid() ); + + // TODO verify icon, name, rights. + // TODO verify functions like outbox(). + } +} + +void CustomFolderTest::testCustomFoldersDontRebuild() +{ + QVERIFY( LocalFolders::self()->isReady() ); + Collection a, b; + + // Create a custom folder in root. + { + const Collection root = LocalFolders::self()->root(); + QVERIFY( root.isValid() ); + + a.setParent( root.id() ); + a.setName( "a" ); + CollectionCreateJob *cjob = new CollectionCreateJob( a ); + AKVERIFYEXEC( cjob ); + a = cjob->collection(); + QVERIFY( LocalFolders::self()->isReady() ); + } + + // Create a custom folder in a default folder (inbox). + { + const Collection inbox = LocalFolders::self()->inbox(); + QVERIFY( inbox.isValid() ); + + b.setParent( inbox.id() ); + b.setName( "b" ); + CollectionCreateJob *cjob = new CollectionCreateJob( b ); + AKVERIFYEXEC( cjob ); + b = cjob->collection(); + QVERIFY( LocalFolders::self()->isReady() ); + } + + QTest::qWait( 500 ); + + // Delete those, and make sure LocalFolders does not rebuild. + { + QVERIFY( LocalFolders::self()->isReady() ); + CollectionDeleteJob *djob = new CollectionDeleteJob( a ); + AKVERIFYEXEC( djob ); + QVERIFY( LocalFolders::self()->isReady() ); + djob = new CollectionDeleteJob( b ); + AKVERIFYEXEC( djob ); + QVERIFY( LocalFolders::self()->isReady() ); + + for( int i = 0; i < 200; i++ ) { + QVERIFY( LocalFolders::self()->isReady() ); + QTest::qWait( 10 ); + } + } + + // Check that a fetch() does not trigger a rebuild. + { + LocalFolders::self()->fetch(); + QVERIFY( LocalFolders::self()->isReady() ); + } +} + +// NOTE: This was written when I added LocalFolders::customFolders(). +// I removed that later because I'm not sure how it could be useful. +#if 0 +void CustomFolderTest::testCustomFoldersInRoot() +{ + QSet mine; + QVERIFY( LocalFolders::self()->isReady() ); + const Collection root = LocalFolders::self()->root(); + QVERIFY( root.isValid() ); + + // No custom folders yet. + QVERIFY( LocalFolders::self()->customFolders().isEmpty() ); + + // Create hierarchy of custom folders under root: + // (a/(a1,a2/a2i), b/b1) + { + Collection a; + a.setParent( root.id() ); + a.setName( "a" ); + CollectionCreateJob *cjob = new CollectionCreateJob( a ); + AKVERIFYEXEC( cjob ); + a = cjob->collection(); + mine.insert( a ); + + Collection a1; + a1.setParent( a.id() ); + a1.setName( "a1" ); + cjob = new CollectionCreateJob( a1 ); + AKVERIFYEXEC( cjob ); + a1 = cjob->collection(); + mine.insert( a1 ); + + Collection a2; + a2.setParent( a.id() ); + a2.setName( "a2" ); + cjob = new CollectionCreateJob( a2 ); + AKVERIFYEXEC( cjob ); + a2 = cjob->collection(); + mine.insert( a2 ); + + Collection a2i; + a2i.setParent( a2.id() ); + a2i.setName( "a2i" ); + cjob = new CollectionCreateJob( a2i ); + AKVERIFYEXEC( cjob ); + a2i = cjob->collection(); + mine.insert( a2i ); + + Collection b; + b.setParent( root.id() ); + b.setName( "b" ); + cjob = new CollectionCreateJob( b ); + AKVERIFYEXEC( cjob ); + b = cjob->collection(); + mine.insert( b ); + + Collection b1; + b1.setParent( root.id() ); + b1.setName( "b1" ); + cjob = new CollectionCreateJob( b1 ); + AKVERIFYEXEC( cjob ); + b1 = cjob->collection(); + mine.insert( b1 ); + } + + // Check that LocalFolders is aware of everything. + QTest::qWait( 500 ); + QSet theirs = LocalFolders::self()->customFolders(); + QCOMPARE( mine, theirs ); + + // Remove the custom folders and make sure LocalFolders does not rebuild. + foreach( const Collection &col, mine ) { + QVERIFY( LocalFolders::self()->isReady() ); + CollectionDeleteJob *djob = new CollectionDeleteJob( col ); + AKVERIFYEXEC( djob ); + + for( int i = 0; i < 50; i++ ) { + QVERIFY( LocalFolders::self()->isReady() ); + QTest::qWait( 10 ); + } + + mine.remove( col ); + theirs = LocalFolders::self()->customFolders(); + QCOMPARE( mine, theirs ); + } + + // Check that a fetch() does not trigger a rebuild. + LocalFolders::self()->fetch(); + QVERIFY( LocalFolders::self()->isReady() ); + + // No custom folders after deletion. + QVERIFY( LocalFolders::self()->customFolders().isEmpty() ); +} + +void CustomFolderTest::testCustomFoldersInDefaultFolders() +{ + QVERIFY( LocalFolders::self()->isReady() ); + const Collection inbox = LocalFolders::self()->inbox(); + QVERIFY( inbox.isValid() ); + + // No custom folders yet. + QVERIFY( LocalFolders::self()->customFolders().isEmpty() ); + + // Create a custom folder under inbox. + Collection col; + col.setParent( inbox.id() ); + col.setName( "col" ); + CollectionCreateJob *cjob = new CollectionCreateJob( col ); + AKVERIFYEXEC( cjob ); + col = cjob->collection(); + + // Verify that LocalFolders is aware of it. + QTest::qWait( 500 ); + QSet theirs = LocalFolders::self()->customFolders(); + QCOMPARE( theirs.count(), 1 ); + QVERIFY( theirs.contains( col ) ); + + // Delete it and make sure LocalFolders does not rebuild. + QVERIFY( LocalFolders::self()->isReady() ); + CollectionDeleteJob *djob = new CollectionDeleteJob( col ); + AKVERIFYEXEC( djob ); + for( int i = 0; i < 50; i++ ) { + QVERIFY( LocalFolders::self()->isReady() ); + QTest::qWait( 10 ); + } + + // Check that a fetch() does not trigger a rebuild. + LocalFolders::self()->fetch(); + QVERIFY( LocalFolders::self()->isReady() ); + + // No custom folders after deletion. + QVERIFY( LocalFolders::self()->customFolders().isEmpty() ); +} +#endif + + +QTEST_AKONADIMAIN( CustomFolderTest, NoGUI ) + +#include "customfoldertest.moc" diff --git a/akonadi/kmime/tests/customfoldertest.h b/akonadi/kmime/tests/customfoldertest.h new file mode 100644 index 000000000..684fdeb14 --- /dev/null +++ b/akonadi/kmime/tests/customfoldertest.h @@ -0,0 +1,46 @@ +/* + Copyright 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 CUSTOMFOLDERTEST_H +#define CUSTOMFOLDERTEST_H + +#include + +#include + +/** + Tests the correct handling of custom (i.e. user-created, non-default) folders + in LocalFolders. + */ +class CustomFolderTest : public QObject +{ + Q_OBJECT + + private Q_SLOTS: + void initTestCase(); + void testDefaultFolders(); + void testCustomFoldersDontRebuild(); +#if 0 + void testCustomFoldersInRoot(); + void testCustomFoldersInDefaultFolders(); +#endif + +}; + +#endif